call、apply和bind

152 阅读3分钟

JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向。

一、call

函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

var obj = {}
var f = function () {
  return this;
};
f() === window // true
f.call(obj) === obj // true

call方法的参数,应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象。

var n = 123;
var obj = { n: 456 };
function a() {
  console.log(this.n);
}
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456

call方法还可以接受多个参数。

function add(a, b) {
  return a + b;
}
add.call(this, 1, 2) // 3

可以通过使用call()方法调用父构造函数来实现继承。

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

banana = {
color: "yellow"
}
apple.say.call(banana); //My color is yellow

二、apply

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。

var func = function(arg1, arg2) {
};

func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])

某个函数的参数是明确知道数量时用 call ; 而不确定的时候用 apply,然后把参数 push进数组传递进去。

var array1 = [12 , "foo" , {name "Joe"} , -2458];
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
/* array1 值为 [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */

利用这一点,可以做一些有趣的应用。

  1. 找出数组最大元素
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15
  1. 数组的空元素变为undefined
Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]

空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。

var a = ['a', , 'b'];

function print(i) {
  console.log(i);
}

a.forEach(print)
// a
// b

Array.apply(null, a).forEach(print)
// a
// undefined
// b
  1. 利用数组对象的slice方法,可以将一个类似数组的对象(比如arguments对象)转为真正的数组。
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

三、bind

bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

在常见的单体模式中,通常我们会使用 _this , that , self 等保存 this ,这样我们可以在改变了上下文之后继续引用到它。 像这样:

var foo = {bar : 1,eventBind: function(){
 var _this = this;
 $('.someClass').on('click',function(event) {
  /* Act on the event */
  console.log(_this.bar); //1
  });
 }
}

使用 bind()实现相同效果:

var foo = {bar : 1,eventBind: function(){
$('.someClass').on('click',function(event) {
   /* Act on the event */
      console.log(this.bar); //1
   }.bind(this));
 }
}

bind()方法有一些使用注意点。

  1. bind()方法每运行一次,就返回一个新函数,这会产生一些问题。比如,监听事件的时候,不能写成下面这样。
element.addEventListener('click', o.m.bind(o));

正确的方法是写成下面这样:

var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);
  1. 在Javascript中,多次 bind() 是无效的。

四、apply、call、bind比较

var obj = {x: 81,
};
var foo = {
    getX: function() {
        return this.x;
    }
}
console.log(foo.getX.bind(obj)()); //81
console.log(foo.getX.call(obj)); //81
console.log(foo.getX.apply(obj)); //81

三个输出的都是81,但是注意看使用 bind() 方法的,他后面多了对括号。

也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。