任何足够先进的技术都和魔法无异。
前言
this
关键字是JavaScript
中最复杂的机制之一。它是一个很特别的关键字,被自动定义在 所有函数的作用域中。但是即使是非常有经验的JavaScript
开发者也很难说清它到底指向什么。所以很多人觉得this
会魔法
1.1 误解
我们之后会解释 this
到底是如何工作的,但是首先需要消除一些关于 this
的错误认识。
太拘泥于“this
”的字面意思就会产生一些误解。有两种常见的对于 this
的解释,但是它
们都是错误的。
1.1.1 指向自身
人们很容易把 this
理解成指向函数自身,这个推断从英语的语法角度来说是说得通的。
function foo(num) {
console.log("foo:"+num);
// 记录foo被调用的次数
this.count++;
}
foo.count = 0;
var i ;
for(i = 0; i < 10; i++) {
if(i>5) {
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo :9
// foo 被调用了多少次
console.log(foo.count);// 0 --?
console.log
语句产生了 4 条输出,证明 foo(..)
确实被调用了 4 次,但是 foo.count
仍然是 0。显然
从字面意思来理解 this
是错误的。
1.1.2 它的作用域
第二种常见的误解是,this
指向函数的作用域。
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); //TypeError: this.bar is not a function
编写这段代码的开发者还试图使用this
连通 foo()
和 bar()
的词法作用域,从而让bar()
可以访问foo()
作用域里的变量 a
,这是不可能实现的。每当你想要把 this
和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的。
那么
this
到底是什么?this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
2.1 this
的四种绑定规则
(1) 默认绑定 独立函数调用 this
指向全局
可以把这条规则看作是无法应用其他规则时的默认规则。
function foo() {
console.log( this.a ); // this 指向全局
}
var a = 2;
foo(); // 2
如果使用严格模式(strict mode
),则不能将全局对象用于默认绑定,因此 this
会绑定到undefined
function foo() {
"use strict"
console.log( this.a ); // this 指向全局
}
var a = 2;
foo(); // TypeError: Cannot read properties of undefined (reading 'a')
(2) 隐式绑定 对象函数调用 this
指向上下文对象
//隐式绑定
function foo(){
console.log(this.a); // this.a 等同于 obj.a
}
var obj={
a:2,
foo:foo // fool()的声明方式 被当作引用属性添加到对象,严格来说不属于obj对象
};
obj.foo() //2
console.log(obj.a); //2
注意!!!对象属性引用链中只有上一层或者说最后一层在调用位置中起作用
// 隐式绑定
function foo(){
console.log(this.a);
}
var obj3={
a:34,
foo:foo
}
var obj2={
a:42,
obj3:obj3
}
var obj1={
a:2,
obj2:obj2
}
// 对象属性引用链中只有上一层或者说最后一层在调用位置中起作用
obj1.obj2.obj3.foo();//34
var bar=obj3.foo;//函数别名
var a='oops';
bar();// oops 浏览器全局 node环境 undefined
(3) 显式绑定 直接指定 this
的绑定对象 apply
, bind
, call
//显式绑定
var a = 1 ;
function foo(){
console.log(this.a)
}
var obj={
a:2
}
foo.call(obj); // 2
var bar=function(){
foo.call(obj);/// 硬绑定
}
bar();
setTimeout(bar,1000);
bar.call(window);// 硬绑定,不能修改它的this了
(4)
new
绑定 this
指向实例化的对象
使用 new
来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1. 创建一个全新的对象。
2. 这个新对象会被执行 [[Prototype]]
连接。
3. 这个新对象会绑定到函数调用的 this
。
4. 如果函数没有返回其他对象,那么 new
表达式中的函数调用会自动返回这个新对象。
function func(a) {
this.a = a;
}
var bar = new func(2);
console.log( bar.a ); // 2
学习了 this
的四种绑定规则,让我们开做个小测试吧!
// 控制台输出什么?
var obj = {
count : 0,
cool : function coolfn() {
if( this.count < 1 ){
setTimeout(function timer(){
this.count++;
console.log( this.count );
}.bind(this),1000);
}
}
}
console.log( obj.count );
你答对了吗?
var obj={
count:0,
cool:function coolfn(){
if(this.count<1){ // 此时的 this 指向obj , this.count为 0
setTimeout(function timer(){
this.count++ // 1
console.log(this.count,'----'); // 当前作用域count为1
console.log('awesome');
}.bind(this),1000);
}
}
}
var count = 4;// 迷惑你的
obj.cool();// 1
3. 总结
-
函数是否在
new
中调用(new
绑定)?如果是的话this
绑定的是新创建的对象。var bar = new foo()
-
函数是否通过
call
、apply
(显式绑定)或者硬绑定调用?如果是的话,this
绑定的是指定的对象。var bar = foo.call(obj2)
-
函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,
this
绑定的是那个上下文对象。obj.foo()
-
如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到
undefined
,否则绑定到全局对象。var bar =foo()
当然这四种规则并不是所有情况都适用,还有一些例外,这里就不一 一赘述了。 讨论到这里就结束了啊,欢迎大家多多指教啊!
参考文献
- 你不知道的 javascript