之前在 javascript this 相关总结一文中说要对call()、apply()、bind() 要再写一篇总结详谈,今天把坑填起来。
先说call()和apply()
call() apply() 可以改变函数执行上下文中 this 的指向。当某个方法并不存在于某个对象, 可以借助call()或apply()在该对象上调用该方法。
一个最简单的例子
//有一个函数定义如下:
function sayHi( species,country) {
console.log(`Hello I am ${species} ${this.name}, live in ${country}`);
}
//有一个对象pandaBob定义如下:
var pandaBob={name:'Bob'}
//对象pandaBob上并不存在sayHi(),但可借助call或apply在/pandaBob对象上调用sayHi()。
sayHi.call(pandaBob, 'panda', 'china');//Hello I am panda Bob, live in china
sayHi.apply(pandaBob, [ 'panda', 'china'])//Hello I am panda Bob, live in china
可以看到:对于 apply()、call() 二者而言,作用基本一样,只是接受参数的方式不太相同。
两者接收的第一个参数的作用都是指定要被调用的函数 运行时的 执行上下文的this。
两者的区别在于:
- 借用call调用某方法时,该方法的参数会通过call()的第一个之后的参数 以若干个参数的列表的形式传入。
- 借用apply调用某方法时,该方法的参数会通过apply()的第二个参数 以一个包含多个参数的数组或类数组对象的形式传入。
注意:通常多是以数组或 arguments这个类数组对象的形式传入。事实上对其他类数组对象的支持是ES5 之后的事情了。也就是说ES5之前只能以数组或arguments的形式传入。
正因这点不同两者分别有各自更适合的应用场景。
call() 的使用场景及例子:
参数数量明确或者不需要参数时用建议用 call ,这样更简高效。
举几个例子:
- 在继承中使用call,调用父函数的构造函数。(参数数量确定)
function Product(name, price) {
this.name = name;
this.price = price;
if (price < 0) {
throw RangeError('Cannot create product ' +this.name + ' with a negative price');
}
}
function Food(caloriesPerGram,name, price) {
Product.call(this, name, price);
}
var cheese = new Food('feta', 5);
其实上例中的子类的构造函数 如果不需要传入更多的的参入(即caloriesPerGram)或者把额外的参数放在 name, price之后还可以这么用:
function Food(name, price,caloriesPerGram) {
this.category = 'food';
this.caloriesPerGram=caloriesPerGram;
Product.apply(this, arguments);
}
这种情况下用call()可以适应更灵活的参数列表。
2.使用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);
}
3.安全的类型检查(不需要参数)
function isArray(value){
return Object.prototype.toString.call(value) == "[object Array]";
}
4.使用call方法调用函数并且指定上下文的'this'(不需要参数)
function greet() {
var reply = [this.person, 'Is An Awesome', this.role].join(' ');
console.log(reply);
}
var i = {
person: 'Douglas Crockford', role: 'Javascript Developer'
};
greet.call(i); // Douglas Crockford Is An Awesome Javascript Developer
这几个例子 归根结底都是为了指定在函数被调用时执行上下文中 this 的指向。
apply() 的使用场景和例子
参数数量不明确时只能用 apply(),然后把参数 push 进数组传递进去。当然 函数内部也可以通过 arguments 这个类数组对象来遍历所有的参数。
1.redux 的 bindActionCreators()中的bindActionCreator()定义如下
//ES6 写法
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
//被bable 转译到ES5后
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(undefined, arguments));
};
}
因为被返回的函数调用时传入的参数个数不确定,所以这里只能用apply().
要注意该例中的arguments 是类数组对象。并不是数组(并不是Array 的实例)
对数组或类数组对象, 用apply()调用参数列表与之相对应的已存在函数。
1.数组之间追加
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] */
2.获取数组中的最大值和最小值
var numbers = [5, 6, 2, 3, 7]
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
/*若不用apply() 就要做复杂的数组遍历*/
max = -Infinity, min = +Infinity;
for (var i = 0; i < numbers.length; i++) {
if (numbers[i] > max)
max = numbers[i];
if (numbers[i] < min)
min = numbers[i];
}
/*ES6中也可以使用数组扩展运算符配合call()代替,但还是略麻烦*/
var max = Math.max.call(null, ...numbers);
再谈bind()
bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
bind 和与其他两者有个重要区别: bind 是在函数定义阶段指定函数执行上下文的this指向,绑定之后不会立即去调用。