面向对象编程语言中 this 是一个很是重要的关键字,其在函数执行中经过 this 来明确操做的对象,在 JS 中,JS 并不是面向对象语言,可是他也有 this 关键字,用来指向函数的调用对象,博主在学习 es5 的时候对 this 理解很是容易,由于之前学过面向对象语言,也曾大量的使用,可是学习到 es6 的箭头函数,它的 this 指向就让我有点困惑,因而花了一些时间从各个技术博客, MDN 文档,还有一些其余的资料进行了研究,总算是对箭头函数的 this 指向有个深入的认识了,下面就会讲述下我本身对箭头函数 this 的理解。前端
this 在编程语言中用的次数很是多,多到你不知不觉就会下意识的写出 this,好比事件绑定,对事件源的操做,好比对一个对象的相关属性操做,继承中的 this 使用等。因此 this 的指向是一个一直须要理解并掌握的一个知识,若是不清楚 this 的指向,那么不少方法就会出现大问题,而且非语法的 bug 维护起来更是使人头大。严格模式中的 this 在全局中指向 undefined,其余地方下并无什么影响,因此一下论点也只讨论在非严格模式下的 this 指向。es6
普通函数中的 this 很好理解,无非如下四点:编程
总结一句话就是谁调用函数,this 就指向谁,普通函数的 this 取决于执行时的函数。app
下面重点介绍下箭头函数中的 this 问题编程语言
箭头函数的语法:函数
// 无参数直接输出一句话
var fun1 = () => console.log('hello');
// 有一个参数并返回 x*x
var fun2 = x => x*x;
// 有一个参数并返回 y*y
var fun3 = (y) => y*y;
// 有两个参数并返回 x+y,也能够简写 (x,y) => x+y;
var fun4 = (x,y) => {
return x+y;
}
复制代码
若是箭头函数中没有用到 this 的话,那么大可放心的直接使用,由于代码写的更少更方便,但若是须要 this,那么必定得清楚箭头函数中 this 指向谁。箭头函数的 this 指向也有不一样的说法,下面列举出不一样说法。学习
第一种说法较为模糊,概念不是那么的清晰,第二种说法经过继承而来有点牵强的感受,并且我我的觉的有点误导的感受,所以我看到第三种说法时,虽然以为有点陌生,说法很是官方的感受,可是却以为自习深刻了解第三种说法,应该能彻底掌握箭头函数的 this,因此就仔细研究了一下,下面将展开对第三种说法的论点,也是本篇的核心(前面一堆废话,凑字数:huaji:)ui
词法做用域简单来讲指的是函数做用域的一种工做模式,因此词法做用域的法则是基于做用域的概念。ES6 以前做用域分为全局做用域、局部做用域,变量遵循词法做用域,ES6 引入了块级做用域,使得JS也能像其余的编程语言有了真正的块级代码。执行上下文其实就是执行环境,也就是当前的 this,这里有点绕了吧,其实不要紧,下面会说明的,只不过此时是把上面的第二种继承方式的原理说明了,箭头函数的 this 就取决于这个执行上下文的 this,所以才说他是经过继承而来。this
有了上面的知识做为根基,那么究竟怎么理解此法做用域,和执行上下文,以及若是肯定箭头函数的 this 指向,接下来继续说明。 先来一段代码:es5
var num = 100;
var obj = {
fun1: function () {
num = 200;
console.log(num);
},
fun2: function () {
var num = 300;
console.log(num)
}
}
obj.fun1(); // No.2 200
obj.fun2(); // No.3 300
console.log(num); // No.1 200
复制代码
从上述代码中,fun1 执行时为 num 赋值,可是能够从全局中寻找到 num,所以对全局的 num 进行操做,fun2 执行时在本身的局部做用域(函数)声明了一个 num,此时的 num 为局部的,与全局的 num 无关,fun2 执行完毕后局部 num 就消失了,因此全局的 num 最终结果为200,这一段代码中的变量使用的法则,遵循的就是此法做用域,说白点就是寻找变量的过程和其生命周期的范围受此法做用域约束,另外一个隐藏的知识点就是 obj.fun1(),obj.fun2() 执行时的执行上下文就是 obj, this 就是obj,执行环境就是 obj。
如今真正进入箭头函数的 this 讨论,若是有点忘了箭头函数 this 指向的第三种说明,如今能够立马向上翻滚看一下 :huaji:
看以下 Demo:
var obj = {};
var fun1 = function () {
console.log(this);
}
var fun2 = () => {
console.log(this);
}
console.log('normal------------');
// 全局环境下直接调用
fun1();
fun2();
console.log('call------------');
// 经过 call 进行执行环境的绑定
fun1.call(obj);
fun2.call(obj);
复制代码
再看下一段代码:
var obj = {
fun1: function () {
console.log(this);
},
fun2: () => {
console.log(this);
}
}
obj.fun1();
obj.fun2();
复制代码
obj.fun1();
obj.fun2();
obj.fun2.call(obj);
复制代码
箭头函数的 this 指向也遵循**词法做用域**,指向当前环境的**执行上下文**
再来一段代码巩固下:
var obj = {
fun1: function () {
setTimeout(function () {
console.log('普通函数', this);
})
},
fun2: function () {
setTimeout(() => {
console.log('箭头函数', this);
})
}
}
obj.fun1();
obj.fun2();
复制代码
再来最后一段代码:
var obj = {
// 普通函数中定义一个当即执行函数输出 this
fun1: function () {
(function () {
console.log(this);
})();
},
// 普通函数中定义一个当即执行箭头函数输出 this
fun2: function () {
(() => {
console.log(this);
})();
},
// 箭头函数中定义一个当即执行的普通函数输出 this
fun3: () => {
(function () {
console.log(this);
})();
},
// 箭头函数中定义一个当即执行的箭头函数输出 this
fun4: () => {
(() => {
console.log(this);
})();
}
}
复制代码
console.log('正常执行------------');
obj.fun1(); // window
obj.fun2(); // obj
obj.fun3(); // window
obj.fun4(); // window
复制代码
// 下面 this 是全局的 window
console.log('使用call执行------------');
obj.fun1.call(this); // window
obj.fun2.call(this); // window
obj.fun3.call(this); // window
obj.fun4.call(this); // window
复制代码
fun2: function () {
var testFun = () => {
console.log(this);
}
testFun();
}
复制代码
再次根据箭头函数 this 绑定的法则来看(箭头函数的 this 遵循词法做用域,指向其所属环境的执行上下文(也能够说是宿主对象)。),每当 fun2 被调用时,会从新定义箭头函数,当前箭头函数的词法做用域是 fun2,其指向 fun2 的执行上下文,正常状况是 obj,但咱们经过 obj.fun2.call(this) 强行改变了 fun2 的执行上下文为 window,因此 fun2 的箭头函数从新定义时则指向了 fun2 的执行上下文 window,也就是经过 call 的结果,因此这并不矛盾。 验证代码以下:
var obj = {
// 普通函数中定义一个当即执行箭头函数输出 this
fun2: function () {
(() => {
console.log(this); // 正常调用 fun2 时,this 已经给被绑定为 obj 了
}).call(window); // 没法经过 call 强行绑定 window
},
}
obj.fun2();
复制代码
若是读者看到哪里有误或哪里说的模糊还请说明,我会及时学习更正的,我也只是一个刚入门的前端小白,这也只是个人我的理解,也是第一次在掘金发布的本身文章,但愿大佬们不要嫌弃我