这三个方法都是为了改变调用方法的 this
指向的,所以了解他们之前需要先聊一下 JavaScript 中的 this 关键字。
this
JavaScript 中的 this
总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
注:ES6 的箭头函数 this
关键字是在声明时就被绑定了,不存在改变的情况。
this 的指向
除去不常用的 with
和 eval
的情况,this
的指向大致可以分为以下 4 种。
- 作为对象的方法调用
- 作为普通函数调用
- 构造器调用
-
Function.prototype.call
或Function.prototype.apply
调用
1. 对象的方法调用
当函数作为对象的方法调用是,this 指向该对象:
var obj = {
name: 1,
getName: function() {
console.log(this === obj); // true
console.log(this.a); // 1
}
};
obj.getName();
2. 普通函数调用
当作为普通函数调用时,this
总是指向全局对象。在浏览器的 JavaScript 里,这个全局对象是 window
对象。
window.name = 'globalName';
var obj = {
name: 'Zhang San',
getName: function() {
return this.name;
}
};
var getName = obj.getName;
console.log(getName()); // globalName
正是由于 JavaScript 的这个特点使得有的时候我们所使用的方法指向了全局 window
对象。
比如在 div 节点的时间函数内部,有一个局部的 callback 方法, callback 方法被作为普通函数调用时,callback 内部的 this 指向了 window 而我们往往是想让它指向该 div 节点。
<html>
<body>
<div id="div1">我是一个 div</div>
</body>
<script>
window.id = 'window';
document.getElementById('div1').onclick = function() {
alert(this.id); // div1
var callback = function() {
alert(this.id); // window
}
callback();
};
</script>
</html>
此问题有一种简单的解决方案就是在外部把this用变量保存下来:
document.getElementById('div1').onclick = function() {
var _this = this;
var callback = function() {
alert(_this.id); // div1
}
callback();
};
3. 构造器调用
除了宿主提供的一些内置函数,大部分 JavaScript 函数都可以当做构造器使用。构造器的外表跟普通函数一模一样,区别只是被调用的方式。当用 new 运算符创建对象时,该函数总会返回一个对象,通常情况下,构造器中的 this 指向返回的这个对象:
var MyClass = function(name) {
this.name = name;
this.getName = function() {
return this.name;
}
}
var obj = new MyClass('Zhang San')
console.log(obj.getName()); // Zhang San
4. Function.prototype.call
或 Function.prototype.apply
调用
Function.prototype.call
或 Function.prototype.apply
可以动态的改变传输函数的 this
var obj1 = {
name: 'Zhang San',
getName: function() {
return this.name;
}
};
var obj2 = {
name: 'Li Si'
};
console.log(obj1.getName()); // Zhang San
console.log(obj1.getName.call(obj2)); // Li Si
apply,call
call
和 apply
是ECAMScript 3 给 Function 的原型定义的两个方法,在实际开发中,特别是在一些函数式风格的代码编写中,call
和 apply
方法尤为有用。
区别
apply
和 call
这两个方法的作用一模一样, 区别只是传入的参数不同。
apply
接收两个参数,第一个参数指定了函数体内 this
对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply
方法把这个集合中的元素作为参数传递给被调用的函数:
var func = function(a, b, c) {
alert([a, b, c]); // [1, 2, 3]
}
func.apply(null, [1, 2, 3]);
如果传入的第一个参数是 null
,函数体内的 this
会指向默认的宿主对象,在浏览器中则是 window
。
call
可以说是 apply
的一个语法糖,如果我们明确地知道函数接受多少个参数,而且像一目了然的表达形参和实参的对应关系,那么也可以用 call
来传送参数。
bind
bind
是ECAMScript 5 中定义的方法,它只是改变了函数内部 this
的指向,但是并没有调用,我们可以通过简化版的bind的内部实现来理解 bind
。
Function.prototype.bind = function() {
var self = this,
context = [].shift.call(arguments),
args = [].slice.call(arguments);
return function() {
return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
}
};
用途
- 改变
this
指向
比如上述 callback的问题,也可以用 call / apply / bind
的方式解决
document.getElementById('div1').onclick = function() {
var callback = function() {
alert(this.id); // div1
}
callback.call(this);
};
- 借用其他对象的方法
杜鹃既不会筑巢,也不会孵雏,而是把自己的蛋寄托给云雀等其他鸟类,让他们代为孵化和养育。
在 JavaScript 中也存在类似的借用现象。
借用的第一种场景是 “借用构造函数”,通过这种,可以实现一种类似继承的效果:
var A = function(name) {
this.name = name;
}
var B = function() {
A.apply(this, arguments);
}
B.prototype.getName = function() {
return this.name;
}
var b = new B('Zhang San');
console.log(b.getName()); // Zhang San
第二种场景跟我们的关系更加密切,就是可以让类数组对象调用数组对象的方法
// 向 arguments 里面添加一个新的元素
(function() {
Array.prototype.push.call(arguments, 3);
console.log(arguments); // [1, 2, 3]
})(1, 2)
// arguments 转化为真正的数组
(function() {
const args = [].slice.call(arguments);
console.log(args); // [1, 2]
})(1, 2)
网友评论