别再用self=this、that=this了,代码太难读懂!
1. 前言
JavaScript中的this可能是当年设计的时候存在着设计缺陷,在ES6中能使用()=>
这个高端的箭头函数就尽量使用箭头函数,箭头函数在其他语言中还有个高大上的名字Lambda
表达式。
PS:nodejs
环境中没有window
对象
2. this指向
对于this的指向,我的理解就是记住一句话,如果没有使用apply和call还有箭头函数的情况下,this指向最后一次调用它的对象,如果最后一次调用它的是函数,则在严格模式指向undefined
非严格模式下指向全局变量window
。
下面的例子全部为非严格模式。
var a = 1;var obj = { a: 2, b: function() { return this.a; }};console.log(obj.b()); //2
上面的例子应该很清晰,因为最后一次调用b
方法的是obj
这个对象,而对象obj
里面a的值为2,所以这个例子会输出2。
那么我们加大一下难度看下面的例子。
var a = 1;var obj = { a: 2, b: function() { return this.a; }};var t = obj.b;console.log(t());console.log(obj.b());console.log(t() === obj.b());console.log(t === obj.b);
这个时候我们新加入一个变量t
,将b
方法赋值给t
,大家思考一下这个时候函数t
会输出什么。
神奇的事情发生了!
console.log(t()); //1console.log(obj.b()); //2console.log(t() === obj.b()); //falseconsole.log(t === obj.b); //true
函数t
输出的结果是1。
因为这次调用方法的是函数t
而他的对象为window
,因为对象window
上的a
值为1,故t
的最后输出结果为1。
下面这个例子也同样。
var a = 1;var obj = { a: 2, b: function() { function fun() { return this.a; } console.log(fun()); }};obj.b(); //1
因为调用fun
方法的是方法b
,这时最后一次调用fun
的不为对象,则this指向全局变量window
。
3. 箭头函数
- 箭头函数会捕获其所在上下文的
this
值,作为自己的this
值。 - 箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为
undefined
。
3.1 解决的痛点
在ES5
语法中使用this有时候会很难以预测this的指向,从而产生bug,这个问题到了现在框架时代大量通过类进行封装时变得尤为明显。如果学习过react,就会发现将父组件的函数传递给子组件使用时,this指向总是个头疼的问题。
虽然使用call,apply,bind可以解决这部分痛点,但是需要额外的代码,不仅增大了工作量,而且增加了维护成本。
3.2 简单题目
var a = 1;var obj = { a: 2, b: () =>{ return this.a; }};console.log(obj.b()); //1
因为b是通过箭头函数进行声明,则它的this就指向了obj
的this,而obj
的this就是全局变量window
。
3.3 中等题目
var a = 1;var obj = { a: 2, b: function () { var fun = () => { return this.a; }; console.log(fun()); },};obj.b(); //2
因为箭头函数会找上下文的this,所以这里找到的上文就是方法b
的this,即为对象obj
。
var a = 1;var obj = { a: 2, b: () => { var fun = () => { return this.a; }; console.log(fun()); },};obj.b(); //1
箭头函数是没有this的,这个时候方法fun
就逐层寻找this,最终找到的是对象obj
的this。
4. call,apply,bind
apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数
bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。
-
apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:
B.apply(A, arguments);
即A对象应用B对象的方法。 -
call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:
B.call(A, args1,args2);
即A对象调用B对象的方法。 -
bind:除了返回是函数以外,它的参数和call一样。
4.1 箭头函数
this是不能改变的,所以对箭头函数使用call,apply,bind
是没有效果的
var a = 1;var obj = { a: 2, b: function () { var fun = () => { return this.a; }; console.log(fun.call(a)); },};obj.b(); //2
4.2 特殊情况
var a = 1;var obj = { a: 2, b: function () { var fun = () => { return this.a; }; console.log(fun()); },};var obj1 = { a: 3,};obj.b.call(obj1); //3
箭头函数的this虽然不能被改变,但是可以改变它上下文的this。
5. 一大堆题
既然上面都讲了那么多,那么我们来看一下下面的题:
var age = 10;var person = { age: 20, getAge() { var age = 30; return this.age; //20 }};console.log(age, age * 2); //10 20console.log(person.getAge()); //20var b = person.getAge;console.log(b()); //10console.log(person.getAge()); //20console.log((1, person.getAge)()); //10console.log((1, person.getAge.bind(person))()); //20console.log((person.getAge, person.getAge)()); //10console.log((person.getAge = person.getAge)()); //10console.log(person.getAge.call()); //10console.log(person.getAge.call(person)); //20function getAge2() { this.age = 40; console.log(person.getAge());}getAge2(); //20console.log(age); //node环境和浏览器环境不一样,因为浏览器环境中有window对象function getAge3() { this.age = 50; this.getAge4 = () => { console.log(person.getAge.call(this)); //50 };}console.log(new getAge3().getAge4());console.log(age); //10function getAge4() { this.age = 60; this.getAge5 = () => { console.log(person.getAge.call(this)); //60 };}console.log(new getAge4().getAge5()); //undefinedconsole.log(age); //10var age2 = 10;var person2 = { age2: 20, getAge2: () => { var age2 = 30; return this.age2; }};console.log(person2.getAge2.call()); //10console.log(person2.getAge2.call(person2)); //10
6. 别再用self=this、that=this
文章到这里就应该完了,但是!现在已经进入新时代了啊!可以使用箭头函数尽量的避免使用const self = this
这种代码,之前我不太明白为什么React和Vue要进入函数式编程时代,直到我阅读到一个代码中有const self = this
这种代码,光说无凭,我们直接来看一眼下面的代码:
<template> <div> <button @click="click">点我</button> <div>{{ center }}</div> </div></template><script>export default { name: "test", data() { // 万恶之源 const self = this const self = this; return { self: 100, center: 50, test: { click: () => { console.log(self.self); // 这里的this指的什么? this.center = 10; console.log(this.center); }, }, }; }, methods: { click() { this.test.click(); }, },};</script>
<style scoped></style>
可以看到这段代码,又是self又是this的,这仅仅是我从项目代码中截取后简化的一段,原代码更加复杂,下面我们来看一眼原项目中test那个位置是怎么写的:
amapEvents: { // 这里用了箭头函数 click: (e) => { let { lng, lat } = e.lnglat; // 这里的self其实就是this console.log(self.plugin); self.plugin[1]; let address; // 这里没有用箭头函数 self.plugin[1].getAddress([lng, lat], function (status, result) { if (status === "complete" && result.info === "OK") { if (result && result.regeocode) { // 这里的self就不是this了,因为上面没有使用箭头函数 self.address = result.regeocode.formattedAddress; address = self.address; console.log("地址:" + self.address); self.$nextTick(); } } }); this.center = [lng, lat]; this.address = address; },},
眼花嘛?头晕嘛?这项目上又是this又是self的,又是用了箭头函数又是不用箭头函数的,错综复杂的调用关系,**最关键的是它还将函数放在data里面!太不守代码基本法了,所以在项目中最好都使用箭头函数,因为箭头函数的this会逐层查找。**反正我用了箭头函数后,再也不用纠结this的问题了!