共计 2686 个字符,预计需要花费 7 分钟才能阅读完成。
写在前面
上一篇博客我们知道词法作用域是由变量书写的位置决定的,那 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(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)
}
上一篇:《你不知道的 javascript》笔记_作用域与闭包