JavaScript深入系列之this
archerU opened this issue · comments
this是一个很特别的关键字,被自动定义在所有函数的作用域中。
this既不指向函数自身也不指向函数的词法作用域。
this是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
this 绑定规则优先级:new > call(..)
或者 apply(..)
> 上下文对象调用 > 默认绑定
调用位置
调用栈:为了到达当前执行位置所调用的所有函数。
调用位置在当前正在执行的函数的前一个调用中。
function baz() {
// 当前调用栈是:baz
// 因此,当前调用位置是全局作用域
console.log("baz");
bar(); // <-- bar的调用位置
}
function bar() {
// 当前调用栈是 baz -> bar
// 因此,当前调用位置在baz中
console.log("bar");
foo(); // <-- foo的调用位置
}
function foo() {
// 当前调用栈是 baz -> bar -> foo
// 因此,当前调用位置在bar中
console.log("foo");
}
baz(); // <-- baz的调用位置
注意我们是如何(从调用栈中)分析出真正的调用位置的,因为它决定了 this 的绑定。
绑定规则
默认绑定
var a = 2;
function foo() {
console.log(this.a);
}
foo(); // 2
独立函数调用,既使用不带任何修饰的函数引用进行调用,无法应用其他规则。非 static mode 下,this 默认绑定全局对象。strict mode 下,this 默认绑定 undefined。
隐式绑定
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
当函数引用有上下文对象时,隐私绑定规则会把函数调用中的 this 绑定到这个上下文对象。
显示绑定
call(..)
apply(..)
bing(..)
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj) // 2
通过 foo.call(..)
,我们可以在调用foo
时强制把它的 this 绑定到 obj 上。
如果传入了一个原始值(字符串类型、布尔类型、数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String(..)、new Boolean(..)、new Number(..) )。
从 this 绑定的角度来说,call(..) 和 apply(..) 是一样的,它们区别体现在其他的参数上。
new绑定
使用 new 来调用函数,会自动执行下面的操作
- 创建(构造)一个全新的对象。
- 这个新对象会被执行[[原型]]连接。
- 这个新对象会绑定到函数调用的this。
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); //2
使用 new 来调用 foo(..)
时,我们会构造一个新对象并把它绑定到foo(..)
调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法。
绑定例外
被忽略的this
如果你把null
或者 undefined
作为 this 的绑定对象传入 call, apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
function foo() {
console.log(this.a);
}
var a = 2;
foo.call( null ); // 2
bing(..) 可以对参数进行柯里化(预先设置一些参数)。
function foo(a,b) {
console.log("a:" + a + ",b:" + b);
}
// 把数组“展开”成参数
foo.apply(null,[2,3]); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind(null, 2);
bar(3); // a:2, b:3
这两种方法都需要传入一个参数当作 this 的绑定对象。如果函数并不关心 this 的话,你仍然需要传入一个占位值,这时 null 可能是一个不错的选择。
总是使用 null 来忽略 this 绑定可能产生一些副作用。如果某个函数确实使用了 this (比如第三方库中的一个函数),那默认绑定规则会把 this 绑定到全局对象(在浏览器中这个对象是window),这将导致不可预计的后果(比如修改全局对象)。
更安全的this
function foo(a,b) {
console.log("a:" + a + ",b:" + b);
}
// 我们的 DMZ 空对象
var o = Object.create( null );
// 把数组展开成参数
foo.apply(o , [2,3]); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind(o, 2);
bar(3); // a:2, b:3
在 JavaScript
中创建一个空对象最简单的方法都是 Object.create(null)
。Object.create(null)
和 {} 很像,但是并不会创建 Object.prototype
这个委托,所以它比 {} "更空"。
箭头函数
箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。
function foo() {
//返回一个箭头函数
return (a) => {
// this 继承自 foo()
console.log(this.a);
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
}
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2,不是 3 !