关于 this 指向的问题你都清楚吗
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题汇总,速度收藏! 限时加入!程序员读者微信群,先到先得!

