笔者最近在看 你不晓得的 JavaScript 上卷,外面对于 this
的解说集体感觉十分精彩。JavaScript
中的 this
算是一个外围的概念,有一些同学会对其有点含糊和小恐怖,究其原因,当初对 this
探讨的文章很多,让咱们感觉 this
无规律可寻,就像一个幽灵一样
如果你还没弄懂 this
,或者对它比拟含糊,这篇文章就是专门为你筹备的,如果你绝对比拟相熟了,那你也能够当做温习坚固你的知识点
本篇文章,算是一篇读书笔记,当然也加上了很多我的集体了解,我感觉必定对大家有所帮忙
执行上下文
在了解 this
之前,咱们先来看下什么是执行上下文
简而言之,执行上下文是评估和执行 JavaScript
代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行
JavaScript 中有三种执行上下文类型
- 全局执行上下文 — 这是默认或者说根底的上下文,任何不在函数外部的代码都在全局上下文中。它会执行两件事:创立一个全局的
window
对象(浏览器的状况下),并且设置this
的值等于这个全局对象。一个程序中只会有一个全局执行上下文 - 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创立一个新的上下文。每个函数都有它本人的执行上下文,不过是在函数被调用时创立的。函数上下文能够有任意多个
eval
函数执行上下文 — 执行在eval
函数外部的代码也会有它属于本人的执行上下文,但因为 JavaScript 开发者并不常常应用eval
,所以在这里我不会探讨它
这里咱们先得出一个论断,非严格模式和严格模式中 this 都是指向顶层对象(浏览器中是 window)
console.log(this === window); // true
'use strict'
console.log(this === window); // true
this.name = 'vnues';
console.log(this.name); // vnues
前面咱们的探讨更多的是针对函数执行上下文
this 到底是什么?为什么要用 this
this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件
牢记:this
的绑定和函数申明的地位没有任何关系,只取决于函数的调用形式
当一个函数被调用时,会创立一个流动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用办法、传入的参数等信息。this
就是记录的 其中一个属性,会在函数执行的过程中用到
看个实例,了解为什么要用 this
,有时候,咱们须要实现相似如下的代码:
function identify(context) {return context.name.toUpperCase();
}
function speak(context) {var greeting = "Hello, I'm " + identify(context);
console.log(greeting);
}
var me = {name: "Kyle"};
speak(me); //hello, 我是 KYLE
这段代码的问题,在于须要显示传递上下文对象,如果代码越来越简单,这种形式会让你的代码看起来很凌乱,用 this
则更加的优雅
var me = {name: "Kyle"};
function identify() {return this.name.toUpperCase();
}
function speak() {var greeting = "Hello, I'm " + identify.call(this);
console.log(greeting);
}
speak.call(me); // Hello, 我是 KYLE
this 的四种绑定规定
上面咱们来看在函数上下文中的绑定规定,有以下四种
- 默认绑定
- 隐式绑定
- 显式绑定
new
绑定
默认绑定
最罕用的函数调用类型:独立函数调用,这个也是优先级最低的一个,此事 this
指向全局对象。留神:如果应用严格模式(strict mode
),那么全局对象将无奈应用默认绑定,因而 this
会绑定 到 undefined
,如下所示
var a = 2; // 变量申明到全局对象中
function foo() {console.log(this.a); // 输入 a
}
function bar() {
'use strict';
console.log(this); // undefined
}
foo();
bar();
隐式绑定
还能够咱们结尾说的:this
的绑定和函数申明的地位没有任何关系,只取决于函数的调用形式
先来看一个例子:
function foo() {console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
当调用 obj.foo()
的时候,this
指向 obj 对象。当函数援用有上下文对象时,隐式绑定规定会把函数调用中的 this
绑定到这个上下文对象。因为调 用 foo()
时 this
被绑定到 obj,因而 this.a 和 obj.a 是一样的
记住:对象属性援用链中只有最顶层或者说最初一层会影响调用地位
function foo() {console.log(this.a);
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
间接援用
另一个须要留神的是,你有可能 (无意或者无心地) 创立一个函数的“间接援用”,在这 种状况下,调用这个函数会利用默认绑定规定
function foo() {console.log(this.a);
}
var a = 2;
var o = {a: 3, foo: foo};
var p = {a: 4};
o.foo(); // 3
(p.foo = o.foo)(); // 2
另一个须要留神的是,你有可能 (无意或者无心地) 创立一个函数的“间接援用”,在这 种状况下,调用这个函数会利用默认绑定规定
赋值表达式 p.foo = o.foo
的返回值是指标函数的援用,因而调用地位是 foo()
而不是 p.foo()
或者 o.foo()
。依据咱们之前说过的,这里会利用默认绑定
显示绑定
在剖析隐式绑定时,咱们必须在一个对象外部蕴含一个指向函数的属性,并通过这个属性间接援用函数,从而把 this
间接 (隐式) 绑定到这个对象上。那么如果咱们不想在对象外部蕴含函数援用,而想在某个对象上强制调用函数,该怎么
做呢?
Javascript
中提供了 apply
、call
和 bind
办法能够让咱们实现
不同之处在于,call()
和 apply()
是立刻执行函数,并且承受的参数的模式不同:
call(this, arg1, arg2, ...)
apply(this, [arg1, arg2, ...])
而 bind()
则是创立一个新的包装函数,并且返回,而不是立即执行
bind(this, arg1, arg2, ...)
看如下的例子:
function foo(b) {console.log(this.a + '' + b);
}
var obj = {
a: 2,
foo: foo
};
var a = 1;
foo('Gopal'); // 1Gopal
obj.foo('Gopal'); // 2Gopal
foo.call(obj, 'Gopal'); // 2Gopal
foo.apply(obj, ['Gopal']); // 2Gopal
let bar = foo.bind(obj, 'Gopal');
bar(); // 2Gopal
被疏忽的 this
如果你把 null
或者 undefined
作为 this
的绑定对象传入 call
、apply
或者 bind
,这些值在调用时会被疏忽,理论利用的是默认绑定规定
function foo() {console.log(this.a);
}
var a = 2;
foo.call(null); // 2
利用这个用法应用 apply(..)
来“开展”一个数组,并当作参数传入一个函数。
相似地,bind(..)
能够对参数进行柯里化(事后设置一些参数)
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
new 绑定
当咱们应用构造函数 new
一个实例的时候,这个实例的 this
指向是什么呢?
咱们先来看下应用 new
来调用函数,或者说产生结构函数调用时,会执行什么操作,如下:
- 创立 (或者说结构) 一个全新的对象
- 这个新对象会被执行 [[原型]] 连贯,将对象(实例)的
__proto__
和构造函数的prototype
绑定 - 这个新对象会绑定到函数调用的
this
- 如果函数没有返回其余对象,那么 new 表达式中的函数调用会主动返回这个新对象
原理实现相似如下:
function create (ctr) {
// 创立一个空对象
let obj = new Object()
// 链接到构造函数的原型对象中
let Con = [].shift.call(arguments)
obj.__proto__ = Con.prototype
// 绑定 this
let result = Con.apply(obj, arguments);
// 如果返回是一个对象,则间接返回这个对象,否则返回实例
return typeof result === 'object'? result : obj;
}
留神:let result = Con.apply(obj, arguments);
实际上就是指的是新对象会绑定到函数调用的 this
function Foo(a) {this.a = a;}
var bar = new Foo(2);
console.log(bar.a); // 2
非凡状况——箭头函数
咱们之前介绍的四条规定曾经能够蕴含所有失常的函数。然而 ES6 中介绍了一种无奈应用 这些规定的非凡函数类型:箭头函数
箭头函数不应用 this
的四种规范规定,而是依据定义时候的外层 (函数或者全局) 作用域来决 定 this
。也就是说箭头函数不会创立本人的 this
, 它只会从本人的作用域链的上一层继承 this
function foo() {
// 返回一个箭头函数
// this 继承自 foo()
return (a) => {console.log(this.a);
}
};
var obj1 = {a: 2};
var obj2 = {a: 3};
var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是 3 !
foo()
外部创立的箭头函数会捕捉调用时 foo()
的 this
。因为 foo()
的 this
绑定到 obj1
,bar
(援用箭头函数)的 this
也会绑定到 obj1
,箭头函数的绑定无奈被批改。(new
也不 行!)
总结——this 优先级
判断是否为箭头函数,是则依照箭头函数的规定
否则如果要判断一个运行中函数的 this
绑定,就须要找到这个函数的间接调用地位。找到之后就能够程序利用上面这四条规定来判断 this
的绑定对象
- 由
new
调用? 绑定到新创建的对象 - 由
call
或者apply
(或者bind
)调用? 绑定到指定的对象 - 由上下文对象调用? 绑定到那个上下文对象
- 默认: 在严格模式下绑定到
undefined
,否则绑定到全局对象
如下图所示:
参考
- [[译] 了解 JavaScript 中的执行上下文和执行栈](https://juejin.im/post/684490…)
- 你不晓得的 JavaScript 上卷