写在前面
上一篇博客我们知道词法作用域是由变量书写的位置决定的,那this
又是在哪里确定的呢?如何能够精准的判断this
的指向?这篇博客会逐条阐述
书中有这样几句话:
this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。
关于执行上下文,可以参考《javascript高级程序设计》笔记:内存与执行环境
一、 绑定规则
1.1 默认绑定
最常用的函数调用类型——独立函数调用,使用的即为默认绑定规则,在非strict mode
下,this指向全局对象
function foo1() {
console.log(this.a);
}
var a = 10;
foo1(); // 10
// 即使函数嵌套比较深
function foo2() {
foo1();
}
function foo3() {
foo2();
}
foo3();
当然,我们实际使用中,难以判别的并不是直接型的默认绑定模式,而是隐式绑定丢失型的默认绑定(下面会着重说明)
1.2 隐式绑定【重点】
调用的位置是否有上下文对象,或者说被某个对象拥有或包含
// 基本形式
function foo() {
console.log(this.a);
}
var obj = { a: 10, foo };
obj.foo(); // 10
隐式绑定中的几个雷区:
1. 多个对象嵌套引用时,只有最后一层在调用位置中起作用
function foo() {
console.log(this.a);
}
var obj2 = { a: 42, foo };
var obj1 = { a: 10, obj2 };
obj1.obj2.foo(); // 42
2.【隐式丢失】当调用函数被重新赋值为新变量,调用新变量时this
指向会有不同
// 共用部分
function foo(){
console.log(this.a);
}
var obj = { a: 10, foo };
var a = 'opps, global';
// 直接赋值
var bar = obj.foo;
bar(); // 'oops, global'
// 回调间接赋值1
function doFoo(fn) {
fn();
}
doFoo(obj.foo); // 'oops, global' 相当于间接赋值
// 回调间接赋值2
setTimeout(obj.foo, 100); // 'oops, global' 内置的setTimeout也相当于间接赋值
经典综合案例:
var length = 10;
function fn(){
console.log(this.length);
}
var obj = {
length: 5,
method: function (fn) {
fn();
arguments[0]();
}
};
obj.method(fn, 123);
分析:fn()
为函数fn
的引用,默认绑定,指向全局;arguments[0]();
相当于下面的引用,数据隐式绑定,绑定对象为arguments
,其属性length
值为参数数量2
arguments: {
'0': function fn(){
console.log(this.length);
}
}
答案:10 2
1.3 显式绑定
call()
/apply()
/bind()
能够显式修改this
指向
通过上述方法调用的方式为显示绑定,它们第一个参数是一个对象,在调用函数时,绑定在this
中。
关于三者的基本用法和说明在之前博客《javascript高级程序设计》函数调用模式 & this深度理解中已作说明,在此不做唠述
两点注意:
1. 通过显式绑定的不能再修改它的this
指向
function foo() {
console.log(this.a);
}
var obj = { a: 2 };
var bar = function() {
foo.call(obj);
}
bar(); // 2
setTimeout(bar, 200); // 2
bar.call(window); // 2
2. 将null
/undefined
作为第一个参数时,调用会忽略这些值,采用默认绑定规则
function foo() {
console.log(this.a);
}
var a = 2;
foo.call(null); // 2
1.4 new
绑定
使用关键字new
执行函数,当函数无返回值或返回值非对象时,this
指向为实例对象
new
关键字执行函数流程:
- 创建一个全新的对象
- 这个新对象会被执行[[prototype]]连接
- 这个新对象会绑定到函数调用的
this
上 - 如果函数没有返回其他对象,所执行函数会自动返回这个新的对象
须知:构造函数与普通函数无异,作为区分,我们一般讲通过new
调用的函数称为构造函数,并大写第一个单词。所有函数均可由关键字new
调用
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2
二、优先级&判断规则
2.1 优先级
new
绑定 --> 显式绑定 --> 隐式绑定 --> 默认绑定
2.2. 判断规则【重点】
- 【
new
绑定】函数是否在new
中调用?如果是,this
绑定的是新创建的对象 - 【显式绑定】函数是否在
call
/aplly
/bind
中调用?如果是,this
绑定的是指定对象 - 【隐式绑定】函数是否在某个上下文中调用?如果是,
this
绑定到那个上下文对象 - 【默认绑定】如果都不是,
this
绑定严格模式下为undefined
,非严格模式下为全局对象
三、箭头函数中的this
ES6中箭头函数不使用上面this
的四种标准规格,而是根据外层(函数或者全局)作用域来决定this
指向
下面是一个普通函数和箭头函数的对比:
function foo1() {
setTimeout(() => {
console.log(this.a)
}, 100)
}
function foo2() {
setTimeout(function() {
console.log(this.a)
}, 100)
}
var a = 10;
var obj = { a: 2 };
foo1.call(obj); // 2 箭头函数this指向外层(obj)
foo2.call(obj); // 10 隐式丢失,默认绑定
【利用闭包】理解箭头函数中的this
:
// 上例中的箭头函数相当于
function foo1() {
var self = this;
setTimeout(function() {
console.log(self.a)
}, 100)
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。