call apply bind

718 阅读5分钟

call apply bind

  • apply 与 call 是为了改变this指向而出现的,如下:
function fruits () {}
fruits.prototype = {
    color: 'red',
    say: function () {
        console.log('my color is ' + this.color)
    }
}
var apple = new fruits;
apple.say();     // my color is red

var banana = {
    color: 'yellow';
}
apple.say.call(banana);     // my color is yellow
apple.say.apply(banana);    // my color is yellow
  • apply call 区别 作用完全一样,只是接受参数不太一样,call要列举参数,apply要传一个数组,如果参数个数不确定,用apply更好
var func = function (arg1, arg2) {

}
// 调用
func.call(this, arg1, arg2);    // 第一个参数为指向的任何对象
func.apply(this, [arg1, arg2]);  // 其他参数用数组形式传入
  • 实例 数组追加
var arr1 = [12, 'foo', {name: 'mac'}];
var arr2 = ['doe', 34, 23];
Array.prototype.push.apply(arr1, arr2);
// arr1的值将变为[12, 'foo', {name: 'mac'}, 'doe', 34, 23]

获取数组中的最大值最小值

var numbers = [3, 0, 34, -43];
var maxInNumbers = Math.max.apply(Math, numbers),
     maxInNumbers = Math.max.call(Math, 3,0, 34, -43);

验证是否是数组(toString()没有被重写的前提下)

function isArray (obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
}

Javascript中存在一种名为伪数组的对象结构。比较特别的是 arguments 对象,还有像调用 getElementsByTagName , document.childNodes 之类的,它们返回NodeList对象都属于伪数组。不能应用 Array下的 push , pop 等方法。但是我们能通过 Array.prototype.slice.call 转换为真正的数组的带有 length 属性的对象,这样 domNodes 就可以应用 Array 下的所有方法了。

var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

console.log的重写,可传入多个输出对象

function log(){
  console.log.apply(console, arguments);
};
log(1);    //1
log(1,2);    //1 2
// 加入前缀方法
function log(){
  var args = Array.prototype.slice.call(arguments);
  args.unshift('(app)');
 
  console.log.apply(console, args);
};

bind

bind() 与 apply 和 call 很相似,同样可以改变this指向 bind会创建一个新函数(绑定函数),创建时会传入bind方法的第一个参数作为this,第二个及以后的参数作为原函数参数来调用函数。

var docWrite = document.write;
// docWrite("hello");// Uncaught TypeError: Illegal invocation非法调用
docWrite.bind(document)("hello");
docWrite.call(document, "hello");
  • 一个简单的绑定函数,使他无论怎么调用都有相同的this值
this.num = 9; 
var mymodule = {
  num: 81,
  getNum: function() { 
    console.log(this.num);
  }
};

mymodule.getNum(); // 81

var getNum = mymodule.getNum;
getNum(); // 9, 因为在这个例子中,"this"指向全局对象

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 81
  • 用bind() 来替代我们常用_this, self等保存this
var foo = {
    num: 1,
    eventBind: function () {
        var self = this;
        $('.class').on('click', function (event) {
            console.log(self.num);
        })
    }
}

替换

var foo = {
    num: 1,
    eventBind: function () {
        $('.class').on('click', function (event) {
            console.log(this.num);
        }.bind(this) ); // 传入的this就是foo
    }
}
  • 多次bind 在Javascript中,多次 bind() 是无效的。更深层次的原因是, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。
  • bind的实现 上面的几个小节可以看出bind()有很多的使用场景,但是bind()函数是在 ECMA-262 第五版才被加入;它可能无法在所有浏览器上运行。这就需要我们自己实现bind()函数了。首先我们可以通过给目标函数指定作用域来简单实现bind()方法:
Function.prototype.bind = function(context){
  self = this;  //保存this,即调用bind方法的目标函数
  return function(){
      return self.apply(context,arguments);
  };
};

考虑到函数柯里化的情况,我们可以构建一个更加健壮的bind():

Function.prototype.bind = function(context){
  var args = Array.prototype.slice.call(arguments, 1),
  self = this;
  return function(){
      var innerArgs = Array.prototype.slice.call(arguments);
      var finalArgs = args.concat(innerArgs);
      return self.apply(context,finalArgs);
  };
};

这次的bind()方法可以绑定对象,也支持在绑定的时候传参。继续,Javascript的函数还可以作为构造函数,那么绑定后的函数用这种方式调用时,情况就比较微妙了,需要涉及到原型链的传递:

Function.prototype.bind = function(context){
  var args = Array.prototype.slice(arguments, 1),
  F = function(){},
  self = this,
  bound = function(){
      var innerArgs = Array.prototype.slice.call(arguments);
      var finalArgs = args.concat(innerArgs);
      return self.apply((this instanceof F ? this : context), finalArgs);
  };

  F.prototype = self.prototype;
  bound.prototype = new F();
  return bound;
};

这是《JavaScript Web Application》一书中对bind()的实现:通过设置一个中转构造函数F,使绑定后的函数与调用bind()的函数处于同一原型链上,用new操作符调用绑定后的函数,返回的对象也能正常使用instanceof,因此这是最严谨的bind()实现。对于为了在浏览器中能支持bind()函数,只需要对上述函数稍微修改即可:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}

apply、call、bind 三者比较

var obj = {
    x: 22,
};
 
var foo = {
    getX: function() {
        return this.x;
    }
}
 
console.log(foo.getX.bind(obj)());  //如果没有(),将返回一个函数function(){return this.x}
//22console.log(foo.getX.call(obj));    //22console.log(foo.getX.apply(obj));   //22

三个输出的都是22,但是注意看使用 bind() 方法的,他后面多了对括号。也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。再总结一下:apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;apply 、 call 、bind 三者都可以利用后续参数传参;bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

仅作为个人学习、阅读笔记,如有不足之处请指教,如有侵权请联系本人即删除