关于 this 指向的问题你都清楚吗
共 3969字,需浏览 8分钟
·
2020-09-04 23:12
JavaScript 中的this
关键字在开发中的角色是非常灵活的,基本上可以指代一切调用者,也正因为如此,成为了 JS 相关的面试中的重要考查点。对它的原理和使用的梳理还是很有必要的。
一句话总结
虽然this
使用灵活多变,但是我们依然可以找出一定的规律进行概括。一句话总结,那就是 this
指向的是函数运行时的所在的环境对象。也有其他说取决于调用函数时的执行上下文,或者取决于函数调用位置的,虽然没错但个人觉得并不及这一句精炼。有了抽象概括,接着再来实际的场景中看看这一结论是如何得以体现的。
全局环境下的函数调用
通常来说,全局环境一般指浏览器和node
环境,而关于this
的考察一般是在浏览器环境中, 对于如下代码:
var a = 1;
function fr() {
console.info("this.a", this.a);
console.info("this", this);
}
fr();
在浏览器的环境中的输出是:
1
Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
可见,直接函数调用的时候,this
指向环境对象(浏览器也就是window
)。不过,严格模式下上面代码的this
是undefined
,this.a
会直接报错。
如果是在Node
环境中呢?
undefined
Object [global] {...
Node
环境下,的this
指向的是全局global
, a
变量并没有写到全局,所以this.a
是undefined
。
因实际中关于考察 this 的考察大多指浏览器环境,后面对Node
环境只作简单描述,不过多展开。
函数作为对象的方法调用
函数经常被挂载到一个对象上面,通过对象属性的方式进行调用。
const obj = {
name: "te",
say: function () {
console.info("Hi !");
console.info("this=", this);
},
};
obj.say();
// Hi !
// this= { name: 'te', say: [Function: say] }
可见,函数作为对象的一个属性来调用的时候,this
是指向这个上级对象的。此时函数运行所在的环境对象也是这个上级obj
对象。
我们对上面的obj
对象不做修改,对调用方式做简单修改:
const s = obj.say;
s();
此时的输出是:
Hi !
VM872:5 this= Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
可以看到函数say
中的this
指向已经发生了变化,不再指向obj
对象,而是window
这个全局对象。是不是看起来有一点不可思议?
这是因为obj.say
函数被赋值给s
之后,对s
的调用其实是在全局环境中的,所以函数内部的this
又成了全局的window
。
构造函数中的 this
看到这里估计会有声音说,构造函数中的this
不是最简单的吗,指向当前返回的新对象,但真的是这样吗?
关于new
操作符创建新的对象的过程可以简单描述为:
创建新的空对象 为该对象添加属性和方法,并使构造函数的 this
指向新对象。返回该新对象
在实际使用new
创建对象的时候,一般不会在构造函数中手动指定返回值,因为new
是会返回一个符合预期的对象的。不过如果手动给构造函数添加了return,那么还是要注意区分的:
显式 return
一个非对象的值,得到的是创建出的对象实例。
function ori() {
this.name = "A";
return;
}
const obj = new ori();
console.info("", obj);
// ori { name: 'A' }
并没有返回值(相当于undefined),得到的是对象实例。这里return
数值类型和字符串的话,构造函数的结果都是对象实例。
return
一个对象,得到的就是该对象,相应的this也指向这个对象。
function ori() {
this.name = "A";
const inner = {
name: "B",
};
return inner;
}
const obj = new ori();
console.info("", obj);
// { name: 'B' }
可以看到,这时的返回的对象已经是inner
对象。所以,构造函数中的返回对象的this
也是要视具体的return
来决定的。
bind/call/apply 方法
三个方法都是可以改变对应函数的this
指向的,bind
方法通过该函数调用并且返回一个新的函数,新的函数已经修改了this
指向,只有调用这个新的函数,函数体才会被执行。而call/apply
方法是会直接绑定this
并进行调用的,调用的时候,函数内部的this
指向call/apply
的第一个参数,两者的区分主要在于传参的格式上,其他深入点可以自行补充。
三者可以这样转换:
const obj = {};
fun.call(obj, "arg1","arg2",...);
相当于:
const obj = {};
fun.call(obj, ["arg1","arg2",...]);
用bind
则需要手动调用一次:
const obj = {};
fun.bind(target, "arg1","arg2",...)();
关于修改原函数中的this
绑定,可以参考如下:
const personA = {
name: "A",
say: function () {
console.info("my name is " + this.name);
},
};
const personB = { name: "B" };
personA.say.call(personB);
// my name is B
最初是personA
在执行自身的say
方法,怎么就输出B
了呢,因为call
方法将personA.say
函数中的this
绑定到了personB
,故输出的时候,this.name
也就是personB
的name
。
箭头函数的 this
箭头函数作为ES6
的特殊化函数,this
的绑定比较特殊,不适用于上面根据运行时环境决定,只能通过定义的作用域链,也就是定义的外层的环境来决定。更加浅显一点来说,箭头函数的this
指向父作用域,否则就是全局对象,call/apply/bind
方法都是不能改变箭头函数的this
指向的。
const name = "xx";
const personA = {
name: "A",
say: () => {
console.info("my name is " + this);
},
};
personA.say();
// my name is [object Window]
可以看到,父级对象personA
的环境就是全局,say
中的this
也就是全局window
。
const name = "xx";
const personA = {
name: "A",
say: function () {
let name = "In";
return () => {
console.info("my name is " + this.name);
};
},
};
personA.say()();
// my name is A
箭头函数的父级是say
对应的匿名函数,所在的环境是在对象personA
中,故personA.say()
得到的箭头函数中的this
就是指向personA
的,所以最终的this.name
就是A
。
现在可以看看call
方法对箭头函数this
的影响:
const personA = {
name: "A",
say: function () {
return () => {
console.info("my name is " + this.name);
};
},
};
const personB = { name: "B" };
personA.say.call(personB)();
personA.say().call(personB);
// my name is B
// my name is A
先对personA.say
调用call
,say
对应的匿名函数的this
指向被改变为personB
,say
对应的匿名函数的this
取决于父级匿名函数this
,调用时this
也指向personB
,故得到的this.name
是B
。
而执行personA.say()
之后,已经得到了箭头函数,此时再去调用call
方法的话,是无法改变箭头函数中的this
指向的,故this.name
依然还是A
以上便是对JavaScript中this相关的总结,觉得不错的给点个赞~
-End-
最近热文:
一周内B站疯转5.6W次,好东西呀! TikTok软件,请务必谨慎使用! 美国如果把根域名服务器封了,中国会从网络上消失?
超全递归技巧整理,这次一起拿下递归
LeetCode1-20题汇总,速度收藏!
限时加入!程序员读者微信群,先到先得!
最近热文:
一周内B站疯转5.6W次,好东西呀! TikTok软件,请务必谨慎使用! 美国如果把根域名服务器封了,中国会从网络上消失? 超全递归技巧整理,这次一起拿下递归 LeetCode1-20题汇总,速度收藏! 限时加入!程序员读者微信群,先到先得!