JavaScript 语法基础—— this
this 的指向
除去不常用的 with 和 eval 的情况,具体到实际应用中,this 的指向大致可以分为以下 4 种。
- 作为对象的方法调用,当函数作为对象的方法被调用时,this 指向该对象。
var module = {
x: 42,
getX: function() {
return this.x;
}
};
console.log(module.getX()); // 42
- 作为普通函数调用,当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的 this 总是指向全局对象。在浏览器的 JavaScript 里,这个全局对象是 window 对象。本质上其实我们也可以理解为此时作为全局对象的方法。
var name = "global";
function getName() {
this.name = "inner";
}
getName();
console.log(name); // inner
- 构造器调用,构造器的外表跟普通函数一模一样,它们的区别在于被调用的方式。当用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的 this 就指向返回的这个对象。
function MyClass() {
this.name = "sven";
}
var obj = new MyClass();
console.log(obj.name); // sven
但用 new 调用构造器时,还要注意一个问题,如果构造器显式地返回了一个 object 类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的 this:
var MyClass = function() {
this.name = "sven";
return {
name: "anne"
};
};
var obj = new MyClass();
console.log(obj.name); // anne
如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成上述问题。
- Function.prototype.call 或 Function.prototype.apply 调用。
跟普通的函数调用相比,用 Function.prototype.call 或 Function.prototype.apply 可以动态地 改变传入函数的 this。
var obj1 = {
name: "sven",
getName: function() {
return this.name;
}
};
var obj2 = {
name: "anne"
};
console.log(obj1.getName()); // sven
console.log(obj1.getName.call(obj2)); // anne
call 和 apply 方法能很好地体现 JavaScript 的函数式语言特性,在 JavaScript 中,几乎每一次编写函数式语言风格的代码,都离不开 call 和 apply。
call 和 apply
apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。
var module = {
x: 42,
getX: function() {
return this.x;
}
};
var unboundGetX = module.getX;
console.log(unboundGetX()); // undefined
var boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // 42
大部分高级浏览器都实现了内置的 Function.prototype.bind,用来指定函数内部的 this 指向,即使没有原生的 Function.prototype.bind 实现,我们来模拟一个也不是难事,代码如下:
Function.prototype.bind = function() {
var self = this,
context = [].shift.call(arguments),
args = [].slice.call(arguments);
return function() {
// 返回一个新的函数,需要绑定的 this 上下文,剩余的参数转成数组
// 执行新的函数的时候,会把之前传入的 context 当作新函数体内的 this
// 并且组合两次分别传入的参数,作为新函数的参数
return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
};
};
[].slice.call(arguments)
即 Array.prototype.slice.call(arguments)
能够将 arguments 转成数组。
测试如下:
var obj = {
name: "sven"
};
var func = function(a, b, c, d) {
console.log([a, b, c, d]); // 输出:[ 1, 2, 3, 4 ]
}.bind(obj, 1, 2);
func(3, 4);
对于 Function.prototype.bind 完整严谨的实现可以参考 MDN 上的 Ployfill。
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() {
return fToBind.apply(
this instanceof fNOP ? this : oThis,
aArgs.concat(Array.prototype.slice.call(arguments))
);
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}