简单阐述下 call/apply/bind 的用途

187 阅读3分钟

一、概念

  • call 方法:指定函数内部 this 的指向,然后在指定的作用域中,调用该函数。假设参数为空、null 或者undefined,非严格模式下,this 默认指向全局对象;而严格模式下,this 的值为 undefined;如果第一个参数为原始值,会被包装成对象
  • apply 方法: apply 方法的作用与 call 方法类似,也是改变 this 指向,然后再调用该函数。唯一的区别就是:它接收一个数组作为函数执行时的参数。第一个参数为 null 、undefined 或者原始值时,结果和 call 方法一致。
  • bind 方法: 用于将函数体内的 this 绑定到某个对象,然后返回一个新函数不论怎么调用,这个函数都有同样的 this 值。 第一个参数设为 null 或者 undefined 时,this 指向和 call 方法一致。

二、 call 方法的例子

  • 如果 call 方法的参数为空、null 或者 undefined,this 默认指向全局对象。
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 方法的第一个参数为原始值,会被包装成对象,再传入 call 方法。
var f = function () {
  return this;
};

f.call(5)
// Number {[[PrimitiveValue]]: 5}
  • 使用 call 方法调用对象的原生方法
var obj = {};
obj.hasOwnProperty('toString') // false

// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
  return true;
};
obj.hasOwnProperty('toString') // true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false
  • 使用 call 方法调用匿名函数
var animals = [
  { species: 'Lion', name: 'King' },
  { species: 'Whale', name: 'Fail' }];


for (var i = 0; i < animals.length; i++) {
  (function(i) {
    this.print = function() {
      console.log('#' + i + ' ' + this.species
                  + ': ' + this.name);
    }
    this.print();
  }).call(animals[i], i);}
  • 使用 call 方法调用函数且不指定第一个参数。非严格模式下,this 指向全局对象;严格模式下,this 的值是 undefined。
1)示例一
var sData = 'Wisen';
function display() {
  console.log('sData value is %s ', this.sData);
}
display.call();  // sData value is Wisen2)示例2
'use strict';
var sData = 'Wisen';
function display() {
  console.log('sData value is %s ', this.sData);
}
display.call(); // Cannot read the property of 'sData' of undefined

三、apply 方法的例子

  • 使用 apply 方法找出数组的最大元素
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15
  • 使用 apply 方法将数组的空元素变为 undefined( 遍历时,数组的 forEach 方法会跳过空元素)
var a = ['a', , 'b'];
function print(i) {
  console.log(i);
}

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

Array.apply(null, a).forEach(print)
// a
// undefined
// b
  • 使用 apply 方法转换类似数组的对象(伪数组,必须有 length 属性)
Array.prototype.slice.apply({0: 1, length: 1}) // [1]Array.prototype.slice.apply({0: 1}) // []

Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]

Array.prototype.slice.apply({length: 1}) // [undefined]
  • 使用 apply 方法将数组各项添加到另一个数组
var array = ['a', 'b'];var elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]

四、bind 方法的例子

  • bind 方法每次都会返回一个新函数
// 错误的写法。每次返回一个新的匿名函数,从而导致绑定无法取消。
element.addEventListener('click', o.m.bind(o));

// 正确的写法
var listener = o.m.bind(o);
element.addEventListener('click', listener);
element.removeEventListener('click', listener);
  • bind 方法:使一个函数拥有预设的初始参数
function list() {
  return Array.prototype.slice.call(arguments);}
function addArguments(arg1, arg2) {
    return arg1 + arg2
}
var list1 = list(1, 2, 3); // [1, 2, 3]  // slice 方法的另一个作用:将一个类数组对象/集合转换成一个新数组
var result1 = addArguments(1, 2); // 3
// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);
// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37);
var list2 = leadingThirtysevenList();// [37]
var list3 = leadingThirtysevenList(1, 2, 3);// [37, 1, 2, 3]
var result2 = addThirtySeven(5);// 37 + 5 = 42
var result3 = addThirtySeven(5, 10);// 37 + 5 = 42 ,第二个参数被忽略
  • bind 方法结合 call()、apply() 使用
1)示例一
[1, 2, 3].slice(0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]2)示例二
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]3)示例三
var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);
var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]
pop(a)
a // [1, 2, 3]4)示例四
function f() {
  console.log(this.v);
}
var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 1235)示例五
var slice = Array.prototype.slice;
slice.apply(arguments);
-----------------------------------------------------------------
var slice = Function.prototype.apply.bind(Array.prototype.slice);
slice(arguments);  // 省略了 apply