首发地址:https://github.com/jeuino/Blo...
前言
本篇文章将介绍 JavaScript 执行上下文中,最后一个重要的属性 —— this。
this
在《JavaScript 之作用域与作用域链》中,介绍了词法作用域与动态作用域,JavaScript 是基于词法作用域的,而 this 的行为却与动态作用域类似,this 的指向是基于调用栈的。
this 是在运行时进行绑定的,而不是在编写时绑定,它的指向取决于函数是怎么被引用的。
this 绑定的类型可以分为以下几类:
- 隐式绑定
- 显示绑定
- new 绑定
我们直接通过例子来看 this 是如何绑定的。(多例预警,部分示例参考书籍《你不知道的JavaScript》)
隐式绑定
eg1.1:
function foo() {
console.log(this.a);
}
var a = 1;
foo(); // 1
输出:1
原因:
如果你不清楚变量和函数在内存中是如何存储的,请阅读《JavaScript 之内存空间》
通过 foo()
调用函数时:解释器首先会在栈中找到 foo 函数的引用地址,然后根据引用地址到堆中找到函数并执行。这种直接在词法作用域中查找函数引用并调用的方式,this 会指向全局对象 window。
注意:在严格模式下,this 的值为 undefined。此处的严格模式是指,函数体处于严格模式下,this 的值为 undefined。否则 this 会被绑定到 Window 对象。
eg1.2:巩固例,输出结果的原因自行分析呦
function foo2() {
console.log(this.a);
}
function foo() {
var a = 'foo';
foo2();
}
var a = 1;
foo(); // 1
eg2.1:
function foo() {
console.log(this.a);
}
var obj = {
a: 1,
foo: foo
};
var a = 'global'
obj.foo(); // 1
输出:1
原因:
通过 obj.foo()
调用函数时:解释器首先会在栈中找到 obj 对象的引用地址,然后根据引用地址到堆中找到原始对象,然后再根据 obj.foo 属性值(属性值为 foo 函数引用地址)在堆中找到函数并执行。
foo 函数是通过对象 obj
来引用的,也就是说,它的调用位置是 obj 对象,所以 this 指向 obj 对象。
eg2.2:巩固例,输出结果的原因自行分析呦
function foo() {
console.log(this.a);
}
var obj1 = {
foo: foo,
a: 1
};
var obj2 = {
obj1: obj1,
a: 2
};
obj2.obj1.foo(); // 1
eg3.1:
function foo() {
console.log(this.a);
}
var obj = {
foo: foo,
a: 1
};
var a = 'global';
var bar = obj.foo;
bar(); // 'global'
输出:'global'
原因:
我想大家通过上图应该就已经明白为什么输出的是'global'
了。(参考 eg1.1)
eg3.2:巩固例,输出结果的原因自行分析呦
function foo() {
console.log(this.a);
}
function doFoo(fn) {
fn();
}
var obj = {
foo: foo,
a: 1
};
var a = 'global';
doFoo(obj.foo); // 'global'
总结:
隐式绑定有两种形式:
- 直接在词法作用域中引用函数 -> this 指向 window 对象
- 通过对象属性间接引用函数 -> this 指向这个对象
显示绑定
显示绑定 this 的几种方法:
- call(...) 方法
- apply(...) 方法
- bind(...) 方法 (ES5)
call/apply 方法
举例说明:
function foo() {
console.log(this.a);
}
var obj = {
a: 1
};
foo.call(obj); // 1
// or
foo.apply(obj); // 1
通过 call
或 apply
方法,可以在调用 foo 时,强制将它的 this 绑定到 obj 上。
call
和apply
方法的功能是一样的,它们的区别在于传递参数的格式不同。call
逐个传递单个参数(foo.call(obj,1,2,3)
),apply
一次性传递一个参数数组(foo.apply(obj,[1,2,3])
)。如果 call 和 apply 中传入的第一个参数是原始值(eg: 'str', true, 1),那么这个原始值会被转换成它的对象形式(也就是 new String()、new Boolean()、new Number() ..),这个过程通常被称为 “装箱” 操作。
bind 方法
使用 bind(...) 方法,会返回一个指定 this 后的函数,该函数可重复使用。
举例说明:
function foo() {
console.log(this.a);
}
var obj = {
a: 1
};
var bar = foo.bind(obj);
bar(); // 1
特殊情况
如果将 null 或者 undefined 作为 this 的绑定对象传入到 call/apply/bind 中,此时 this 指向 window。
new 绑定
使用 new 操作符来调用函数时,也就是我们经常说的发生构造函数调用时,会进行以下操作:
- 创建一个全新的对象;
- 这个新对象会被执行[[Prototype]]连接;
- 这个新对象会被绑定到调用的函数的 this 上;
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象;
function Foo(a) {
this.a = a;
}
var obj = new Foo(1);
console.log(obj.a); // 1
简单来说,就是使用 new 操作符来调用函数 Foo 时,会创建一个新的对象并把它绑定到 Foo 函数执行上下文中的 this 上。
优先级
以上四条 this 绑定规则的优先级如下:
- new 绑定
- 显示绑定 call/apply/bind
- 隐式绑定:通过对象属性间接调用函数
- 隐式绑定:直接在词法作用域中调用函数,这种绑定方式也被称为默认绑定
我们再来思考一个例子:
function foo() {
console.log(this.a);
}
var a = 1;
var obj = {
a: 'obj',
foo: foo
};
var bar = {
a: 'bar'
};
(bar.foo = obj.foo)(); // 1
赋值表达式 bar.foo = obj.foo
的返回值是 obj.foo
的值,也就是目标函数 foo 的引用。因此,调用位置等价于 foo()
,所以这里应用的是默认绑定,输出结果为 1。
下一篇
在每个执行上下文中,都包括三个重要的属性:
- 变量对象(Variable Object,VO)
- 作用域链(Scope Chain)
- this指向
截止到此篇文章,执行上下文中的三个属性就都介绍完啦。下篇文章将开始介绍执行上下文的生命周期,敬请期待。
参考:
JavaScript 的 this 原理
《你不知道的 JavaScript》上
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。