谈谈javascript的Function中那些隐蔽的属性/要领:caller/callee/apply/call/bind

javascript的Function中有不少不那么经常运用,又或许用了也是知其然而不知其所以然的属性/要领,本文就来谈谈这一系列属性/要领:caller/callee/apply/call/bind

caller属性

直接上DEMO比较好邃晓:

// caller demo {
function callerDemo() {
    if (callerDemo.caller) {
      var a = callerDemo.caller.toString();
      console.log(a);
    } else {
      console.log("this is a top function");
    }
}
function handleCaller() {
    callerDemo();
}

handleCaller();    //"function handleCaller() { callerDemo(); }"
callerDemo();    //"this is a top function"

我们先来根据思绪一步一步来看这段代码:

  1. 起首我们看到定义了俩function:handleCaller和callerDemo,而且还能够看出handleCaller函数里是挪用了callerDemo函数的。
  2. 在callerDemo函数里,我们看到了本文引见的主角之一:caller属性,而且能够看出这caller属性是函数对象自身的一个成员属性。
  3. 在callerDemo函数里,有一段推断caller属性是不是存在的代码,这段代码有什么意义呢?这就要看末了的结果了:直接挪用callerDemo()发明此时callerDemo.caller是为空的,而反观经由过程挪用handleCaller()并在其内部挪用callerDemo()则callerDemo.caller不为空。这说明只要在函数里挪用函数,才会天生caller属性,而直接在全局环境里挪用函数则不会天生。
  4. 继承看var a = callerDemo.caller.toString();console.log(a);,这里打印出来的居然是handleCaller全部函数体,这说明,此时的callerDemo.caller实际上就是关于handleCaller这个函数对象的一个援用。

这么剖析下来,caller属性就很轻易邃晓了:

  • caller属性是协助我们在当前函数里猎取挪用当前函数的某个未知函数,之所以称未知函数,是由于我们在写一个函数时,极可能基础不知道哪一个函数会挪用到我们的这个函数。
  • 在全局环境中挪用函数是不会天生此caller属性,由于不符合此属性的存在意义/代价(见上条)。
  • 只要在当前函数的内部(上下文环境)才挪用当前函数的caller属性,不能从外部挪用。

callee属性

照样先放代码:

function calleeDemo() { 
    console.log(arguments.callee); 
} 

有了上文对caller属性的认知,callee属性就很好邃晓了,它实际上就是对当前函数对象的一个援用。有以下的点须要注重:

  • callee属性隶属于Function的一个隐蔽对象——arguments中,这个arguments对象人人应当不生疏,示意的就是当前函数传入的参数,平常用于函数不限定参数数目标传参。
  • caller属性一样,也是要在当前函数的内部(上下文环境)才有用。
  • 可合营caller属性一同运用:arguments.callee.caller,如许就能够完整疏忽到详细的函数名了。
  • 函数递归时用起来比用函数名挪用函数更带感!

apply/call要领

这俩要领性子一样,只是用法稍有差别,因而放在一同来引见。还记得我上一篇文章《javascript怎样推断变量的数据类型》中引见的应用Object.prototype.toString.call来推断数据类型的要领么:

function type(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1);    //换成用apply要领亦可
}
  • apply/call要领的意义在于借用别的对象的成员要领来对目标对象实行操纵。
  • 借用的过程当中,apply/call要领会转变被借用的成员要领的上下文环境:把this这一与上下文环境高度相干的变量指向目标对象,而非本来的对象。看下面的这段代码:
function Point(x, y){
    this.x = x;
    this.y = y;
}
Point.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
}
var p = new Point(0,0);
var circle = {x:1, y:1, r:1};    //只是一个一般的Object对象
p.move.call(circle, 2, 1);    //借用了Point类对象中的move要领
//p.move.apply(circle, [2, 1]);    //等价于p.move.call(circle, 2, 1);

这里的circle只是一个一般的Object对象,不含任何自定义的成员要领,但经由过程apply/call要领,能够借用Point类对象定义的move要领来协助circle到达目标(本例实际上是圆心在坐标轴上的挪动)。在借用Point类对象的move要领时,move要领中的this就不再指向p,而是指向circle了,到达了上下文环境转变的结果。
别的,从代码里也能够看出,call要领与apply要领的区分仅在于:call要领直接把须要传入的参数列在目标对象厥后,而apply要领则以数组的情势团体传入。

bind要领

bind要领与apply/call要领也异常相似,相当于轻微再封装了一下,仍以上述DEMO作为案例:

function Point(x, y){
    this.x = x;
    this.y = y;
}
Point.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
}
var p = new Point(0,0);
var circle = {x:1, y:1, r:1};
// p.move.call(circle, 2, 1);
// p.move.apply(circle, [2, 1]);
var circleMove = p.move.bind(circle, 2, 1);    //此时并不实行move要领
circleMove();    //此时才实行

从上面这段DEMO能够看出,bind要领实际上是给apply/call要领缓了一下,也能够说是封装了一下轻易后续挪用,其实质上相当于下面的这段代码:

function circleMove() {
    p.move.call(circle, 2, 1);
}
circleMove();

bind要领兼容性顺应

bind要领,即Function.prototype.bind,属于ECMAScript 5,IE从IE 10版本才最先支撑,那怎么做兼容性顺应呢?

if(typeof Function.prototype.bind !== 'function') {
  Function.prototype.bind = function() {
    var thatFunc = this;
    var args = [];
    for(var i = 0; i < arguments.length; i++) {
      args[i] = arguments[i];
    }

    return function() {
      var context = args.shift();
      thatFunc.apply(context, args);
    }
  }
}

其思绪是应用apply要领来封装成bind要领。

    原文作者:array_huang
    原文地址: https://segmentfault.com/a/1190000004265632
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞