2019独角兽企业重金招聘Python工程师标准>>>
js 中的 this 指向确实是个坑,网上有人轰轰烈烈地讨论它,讨论 js 闭包,其实并没有那么玄学,让我们一点点剥开它的面纱。
很多知识性内容来自 邱桐城《JavaScript 中的 this》 的启发,基于他的文章,我写下了我的总结。
在全局作用域下
在浏览器环境下:
console.log(this);
// Window { .. }
this === window;
// true
全局作用域下,this 指向 Window 对象,这很好理解,仍然是传统 js 的结果。
在 node 环境下:
console.log(this);
// global
this === global;
// true
全局作用域下,this 指向 global 对象。
严格模式,在 node 环境下:
'use strict';
console.log(this);
// {}
遵循严格模式的规范,this 不再指向全局对象。
函数对象作用域下
function foo() {
console.log(this);
}
foo();
// global / Window
严格模式,在 node 环境下:
'use strict';
function foo() {
console.log(this);
}
foo();
// undefined
经过我的测试,虽然满足了规范要求,但在 node 7.2.0 下仍然出现了如上所示的不一致结果。
对象方法作用域下
let obj = {
foo: function() {
console.log(this);
}
};
obj.foo();
// { foo: [Function] }
// obj 的值实际上是个匿名类的对象,foo 的值实际上是个匿名函数
作为对象方法时,this 指向该对象。
function func() {
console.log(this);
}
let obj = {
foo: func
};
obj.foo();
// { foo: [Function func] }
let foo1 = obj.foo;
foo1();
// global
注意到:在函数体内使用的、在函数体外定义(声明)的变量,是 传引用
的。
你可能对这个例子印象深刻:
var foos = [];
for (var i = 0; i < 3; ++i) {
foos.unshift(function () {
console.log(i);
});
}
console.log(i);
// 3
for (var j in foos) {
++i;
console.log(i);
// 4 5 6
foos[j]();
// 4 5 6
}
i 变量在函数内是外部变量的引用,所以当函数外的 i 值变化了,函数内的 i 值也一同变化。
避免这样的外部变量引用也很简单,使用 const
、let
这样的新关键字是最简单的一种,它们比传统的 var
有着更严谨的定义域,使该变量无法被运行时上下文访问到而保证其值不被替换:
const foos = [];
for (let i = 0; i < 3; ++i) {
foos.unshift(function () {
console.log(i);
});
}
for (let i in foos) {
foos[i]();
}
// 0 1 2
并不是新的关键字解决了该问题,而是新的关键字拥有更严谨的作用域。如果该变量在运行时上下文中仍能访问,那问题依旧:
const foos = [];
let i;
for (i = 0; i < 3; ++i) {
foos.unshift(function () {
console.log(i);
});
}
for (let j in foos) {
foos[j]();
}
// 3 3 3
看过上面的例子,你应该明白,函数内的 this 也是这样一个外部变量的引用。在传统使用 function
关键字的语法中,确实如此;但在 ES6 引入的新式 =>
语法中,事情不一样了,你可以看做在函数内插入了这么一行伪代码:
// 这不是真正的 js 代码
const this = outer.this;
试看一例:
'use strict';
const obj1 = {
foo: function() {
console.log(this);
return () => {
console.log(this);
};
}
};
const foo1 = obj1.foo();
// { foo: [Function: foo] }
foo1();
// { foo: [Function: foo] }
const obj2 = {
foo: function() {
console.log(this);
return function () {
console.log(this);
};
}
};
const foo2 = obj2.foo();
// { foo: [Function: foo] }
foo2();
// undefined
=>
语法定义的函数对象,其 (const) this 指向定义时的上下文的 this,而不像 function
关键字定义的函数对象,其 (var) this 会跟随外部 this 的变化而变化。
在构造函数对象作用域下(使用 new
关键字)
'use strict';
function A() {
console.log(this);
}
var a = new A();
// A {}
console.log(a);
// A {}
var b = A();
// undefined
console.log(b);
// undefined
构造函数中 this 指向其构造出来的对象,但是一定不要忘记使用 new
关键字。
call / apply / bind
js 中的函数对象,其 prototype 中定义了如下三个函数:
func.call(thisArg[, arg1[, arg2[, ...]]]);
执行函数 func,使用第一个参数作为 this,其他参数作为 func 的实参,一一对应。
func.apply(thisArg[, [arg1, arg2, ...]]);
执行函数 func,使用第一个参数作为 this,第二个参数为数组,数组中的每个元素作为 func 的实参,一一对应。
var foo = func.bind(thisArg[, arg1[, arg2[, ...]]]);
绑定 func 的 this 和所有参数,返回一个新的函数,但不执行它。
bind 的 this 对 new
关键字无效,但其他实参有效:
function A(name) {
console.log(this.name);
this.name = name;
console.log(this.name);
}
var obj = {
name: "obj"
};
var B = A.bind(obj, "B");
var b = new B('b');
// undefined B
console.log(obj.name);
// obj
要注意,=>
语法下的 this 不受影响,该语法下 this 视为 const 变量,不接受修改。