期末考试以前电话面了一次腾讯的暑期实习生,问题都比较简单,可是稍微一深刻本身的回答就不清楚了,其中有一个问题是ES6的箭头函数中this的相关知识点,想到本身连普通函数的this都没理解好就很丢人,惋惜这么好的机会没有把握住,此次索性从头深刻的学习一下,这篇文章就做为本身的学习笔记。javascript
文章大部份内容是摘抄,根据本身的学习经历和理解过程从基本的this概念入手,逐步涉及this原理和后续的扩展。html
一句话解释,this表示函数执行时所在的运行环境(执行上下文对象),换句话说就是,谁调用的函数,this就表示是谁。若是不理解,看下面这个例子。java
var obj = {
bar: 1,
foo: function() {
console.log(this.bar);
}
};
var bar = 2;
var foo = obj.foo;
obj.foo(); // 1
foo(); // 2
复制代码
对于obj.foo()
来讲,obj.foo()
中foo()
的执行上下文对象是obj
,因此this
表示obj
;而对于foo()
来讲,foo()
的执行上下文对象是全局环境,this
表示全局环境。git
this
的目的就是在函数体内部,指代函数当前的运行环境(context),若是还不理解,就是不明白什么是运行环境,那接着看下面的解释吧。es6
JavaScript 语言之因此有this
的设计,跟内存里面的数据结构有关系。github
var obj = {
foo: 5
};
复制代码
JavaScript引擎会先在内存里面,生成一个对象{foo: 5}
,而后把这个对象的内存地址赋值给obj
。也就是说,变量obj
是一个地址,若是要读取obj.foo
,引擎先从obj
拿到内存地址,而后再从该地址读出原始对象,返回它的foo
属性。数组
原始对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来讲,上面例子的foo
属性,其实是如下面形式保存的。浏览器
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
复制代码
foo
属性的值保存在属性描述对象的value
属性里面。数据结构
这样的结构是清晰的,问题在于属性的值多是一个函数。app
var obj = {
foo: function() {}
};
复制代码
这时,引擎会将函数单独保存到内存中,而后再将函数的地址赋给foo
属性的value
属性。
{
foo: {
[[value]]: 函数的地址
...
}
}
复制代码
因为函数是一个单独的值,因此它能够在不一样的环境(执行上下文)中执行。
上面这句话能够说是this
这个知识点的核心了,以前一直不懂,就是由于这里了解的少,或者理解的很差,下面再详细一点说。
这里的执行上下文在更深层次上有一个执行上下文栈的概念,归纳来讲就是,浏览器永远执行在当前栈中最顶部的那个执行上下文,此时函数就是运行在这个执行上下文中。若是在一个对象或者函数内部又调用一个函数,都会建立一个新的执行上下文,并将这个新的执行上下文压入执行栈中,调用的函数就会在这个新的执行上下文中执行。这一段很差理解,咱们举个例子:
在阅读下面那段话以前,先区分一下这些概念的读法:
例如,在全局中执行foo()
,也就是在全局执行上下文中执行时,执行栈内只有一个全局执行上下文;当执行obj.foo()
时,obj
这个执行上下文将被压入执行栈中,foo()
会在这个新的当前执行上下文中被执行,因此就有了函数能够在不一样的环境(执行上下文)中执行。
明白执行环境(执行上下文)的概念后,this
的做用就容易理解了,this
就是用来指代函数当前运行环境的!
若是函数的当前运行环境内还定义了其它变量(环境变量),咱们就可使用this来调用了。
例如:
var f = function() {
console.log(this.x);
};
var x = 1;
var obj = {
f: f,
x: 2
};
// 全局环境下执行
f(); // 1
// obj 环境下执行
obj.f(); // 2
复制代码
上面代码中,函数f
在全局环境执行,this.x
指向全局环境的x
。
在obj
环境执行,this.x
指向obj.x
。
深刻理解原理后,this
的概念和使用就变得清晰了。
为了加深理解,这里有一个嵌套对象的示例。
var obj = {
bar: 1,
obj1: {
bar: 2,
foo: function() {
console.log(this.bar);
}
}
};
obj.obj1.foo(); // 2
复制代码
上面这段代码中,foo
所在的运行环境(执行上下文对象)为obj1
,此时执行栈中从下到上依次是全局执行上下文、obj
执行上下文和obj1
执行上下文,因此this
表示obj1
。
var obj = {
bar: 1,
foo1: function() {
var bar = 2;
var foo2 = function() {
return this.bar;
}
console.log(foo2());
}
}
var bar = 3;
obj.foo1(); // 3
复制代码
上面这段代码中foo2
函数在被定义后就被调用,比obj.foo1
更早调用,此时执行栈中只有全局执行上下文(window),在严格模式(strict)下,执行上下文则是undefined
,这也是 JavaScript 的一个大坑。必定要注意单独调用函数时,其内部this
的指向!
有时咱们想在函数中使用的环境变量并不必定是函数所在运行环境中的变量,而是某一个特定运行环境中的变量,在这种状况下,咱们就须要将函数中的this
绑定咱们须要的运行环境(上下文对象)上,ECMAScript 规范给全部函数都定义了 call 与 apply 两个方法,能够用来绑定。
call
和apply
用法基本一致,主要区别是传参的形式不一样。
apply
方法传入两个参数:一个是做为函数上下文的对象,另一个是做为函数参数所组成的数组。
var obj = {
bar: 1;
};
function foo(fistParam, secondParm) {
console.log(firstParam + ' ' + this.bar + ' ' + secondParam);
};
foo.apply(obj, ['A', 'B']); // A 1 B
复制代码
能够看到,obj
是做为函数上下文的对象,函数 foo
中 this
指向了 obj
这个对象。参数 A 和 B 是放在数组中传入 foo
函数,分别对应 foo
参数的列表元素。
call
方法第一个参数也是做为函数上下文的对象,可是后面传入的是一个参数列表,而不是单个数组。
var obj = {
bar: 1
}
function foo(fistParam, secondParm) {
console.log(firstParam + ' ' + this.bar + ' ' + secondParam);
};
func.call(obj, 'C', 'D'); // C 1 D
复制代码
对比 apply
咱们能够看到区别,C 和 D 是做为单独的参数传给 foo
函数,而不是放到数组中。
上面两种方法均可以经过第一个参数,把要绑定的上下文对象传递给函数。
在 ECMAScript5 中扩展了叫 bind
的方法,在低版本的 IE 中不兼容。它和 call
很类似,接受的参数有两部分,第一个参数是是做为函数上下文的对象,第二部分参数是个列表,能够接受多个参数。 它们之间的区别有如下两点。
var obj = {
bar: 1
}
function foo() {
console.log(this.bar);
}
var func = foo.bind(obj);
func(); // 1
复制代码
bind
方法不会当即执行,而是返回一个改变了上下文 this
后的函数。而原函数 foo
中的 this
并无被改变,依旧指向全局对象 window
。
function func1(a, b, c) {
console.log(a, b, c);
}
var func2 = func1.bind(null, 1);
func1('A', 'B', 'C'); // A B C
func2('A', 'B', 'C'); // 1 A B
func2('B', 'C'); // 1 B C
func1.call(null, 1); // 1 undefined undefined
复制代码
call
是把第二个及之后的参数做为 func1
方法的实参传进去,而 func2
方法的实参实则是在 bind
中参数的基础上再日后排。
var obj = {
bar: 1,
foo1: function() {
var bar = 2;
var foo2 = function() {
return this.bar;
}
console.log(foo2());
}
}
var bar = 3;
obj.foo1(); // 3
复制代码
仍是看这个例子,当在函数foo1
中单独调用内部的函数foo2
时,foo2
中的this
可能指向window
或者undefined
;除此以外,咱们通常是经过对象来调用,无论如何调用,this
所表明的对象老是视状况而定,这会给咱们带来必定的麻烦,如今,箭头函数帮咱们解决了这个问题。
在《ECMAScript 6 入门》一书中,阮一峰老师这样描述:
箭头函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象。
这句话是存在歧义的,由于在JavaScript中函数和对象之间的界限并不清晰,看下面这个例子,foo2
函数是定义在foo1
中的,那foo2
中的this
是否能够用来表示foo1
呢? 这种思惟得出的结果是2(错误)。
阮老师在 ruanyf/es6tutorial issue中解释到,foo2
位于foo1
内部,只有当foo1
函数运行后,foo2
才会按照定义生成。这种解释对应书中的概念是没有问题的,但用第一种理解方式会给咱们带来必定的困扰。
那该如何理解呢?
在 issue 中有另一个解释:
全部的箭头函数都没有本身的this,都指向外层
或者将书中的描述改成
箭头函数中的this老是指向所在函数运行时的this
这两种描述就容易理解了。
const obj = {
bar: 1,
foo1: function() {
const bar = 2;
const foo2 = () => {
return this.bar;
}
console.log(foo2());
}
}
const bar = 3;
obj.foo1(); // 1
复制代码
由于箭头函数内部的this
没有指向,因此当执行foo2()
时,要去外层foo1
寻找this
,那么foo1
的this
指向哪里呢?
要想拿到foo1
的执行上下文,就需先执行foo1()
,若是是obj.foo()
,this
指向的是obj
,最终的结果就是1(正确)。
文章内部可能在理解上和描述上存在错误,若是大佬们发现了请多多帮忙指正呀!😊