拿捏js中this指向
大一暑假:2022-8-9
this很灵活,认真读完这一篇,你对this的理解八九不离十了
1.想要理解this,先记住以下两点
1.this永远指向一个对象。
2.this的指向完全取决于函数执行时的运行环境。
为什么这么说呢?其中还是涉及很大的奥秘的,且听我慢慢分析。
2.js中的内存结构
js中为什么会有this,跟内存中的数据结构有关系。
举个小栗子来说吧:
let obj = {foo:5}
上面的代码将一个对象赋值给obj。javascript引擎首先会在内存中,生成一个对象{obj:5},然后把这个对象的内存地址赋值给变量obj。
也就是说,变量obj实际是一个地址。如果想要读取obj.foo,js引擎先从obj拿到地址,然后再从该地址读出原始的对象,返回它的foo属性。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举个例子,上面例子的foo属性,实际上是以下面的形式保存的。
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
foo属性的值保存在属性描述对象的value属性中。
问题是属性的值是一个函数。
比如:
let obj = {foo:function(){}}
这时,js会将函数单独保存在内存中,然后再将函数的地址赋给foo属性的value属性。
{
foo: {
[[value]]: 函数的地址
...
}
}
由于函数是一个单独的值,所以它可以在不同的环境中执行。
let f = function(){}
let obj = ={f:f}
//单独执行
f();
//在obj环境下执行
obj.f();
Javascript允许在函数体内部,引用当前环境的其它变量。
function f(){
console.log(x)
}
上面代码中,函数体内使用了变量x。该变量由运行环境提供。
现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
function(){
console.log(this.x)
}
上面代码中,函数体中的this.x就是指当前运行环境中的x。
var x = 1;
let obj ={
x:2,
f:function(){
console.log(this.x)
}
}
function f(){
console.log(this.x)
}
f();//1
obj.f()//2
上面代码中,第一个函数f在全局环境下执行,this.x就是指向全局环境的x。
obj.f在obj环境下执行,所以this.x就是指向obj.x。
3.不同函数下的this
知道了上述原理,就不难发现,this几乎都会出现在函数中,指函数执行时当前的运行环境。所以在下就特意为大家整理了以下几种常见函数中this的指向,帮助大家更好的理解并掌握this。
1.普通函数:指向window
let x = 1;
var y = 2;
function f(){
console.log(this.x)
console.log(this.y)
}
f();
//打印 :undefined
//2
首先f的执行环境为window,所以this指向window,this.x、this.y等同于window.x、window.y。
那为什么this.x不是1呢?
var声明的y变量会被js引擎挂在到js顶层属性中,也就是window属性中,所以window.y是2。然而在es6出现的let声明的变量,不会被挂载到window属性中,this.x还是等同于window.x,但是由于window上没有x这个属性,所以就打印undefined了。
2.对象方法:指向调用该方法的对象
let obj1 ={
x:1,
f:function(){
console.log(this.x)
}
}
let obj2 ={
x:2,
f:obj1.f
}
obj1.f() //1
obj2.f()//2
第一个函数f在obj1环境下执行,this指向obj1,所以this.x等同于obj1.x,打印1。
我相信第一个大家都没问题,第二个就有点疑惑了,有人可能会这样想:obj2.f明明是调用obj1.f,f的运行环境还在obj1
中,应该打印1才是。这样想的小伙伴说你是强盗也不为过。
举个几乎不会出现的例子来说:小明向小红借了100rmb,然后花了却不还。小红问为什么不还?小明说:我花的是你的钱,不是我的钱,为什么要还?
活生生的强调逻辑!
相同道理,生活中不会出现,js中也是如此,obj2中的f执行了obj1中的f,f函数的执行环境是谁呢?当然是obj2了,跟obj1已经没有半毛钱的关系了。
因此,对象方法中this指向调用该方法的对象。
3.构造函数:指向实例对象
function F(x){
this.x=x
this.f=function(){
console.log(this.x)
}
}
let fn = new F(2)
fn.f()//2
这里的this指向具体的实例对象,也就是fn,fn中存在x属性:2,this.x也就是fn.x,输出2了。
4.绑定事件函数 :指向绑定事件的元素
<button>点击</button>
const btn = document.querySelector("button");
btn.onclick = function () {
console.log(this);
};
//打印<button>点击</button>
5.回调函数
对于回调函数中的this对象。对于普通函数,this指向调用时所在的对象(即window对象)。对于箭头函数,this指向定义生效时所在的对象。
普通函数:
var id = 10;
function foo() {
setTimeout(function () {
console.log(this.id);
}, 1000);
}
foo(); //10
foo.call({ id: 30 }); //10
上面都输出10,this指向window对象,原因是1s后,回调函数执行,此时回调函数所在对象为window。
箭头函数:
var id = 10;
function foo() {
setTimeout(()=> {
console.log(this.id);
}, 1000);
}
foo();//10
foo.call({ id: 30 });//30
第一个回调函数输出10,定义时this就已经确认了指向,指向window。
第二个回调函数输出30,与上述不同的是,这个回调函数的this在定义时已经重新指向为{id:30}这个对象了。
6.立即调用函数 :window
(function(){console.log(this)})() //window
注意严格模式下this
"use strict";
console.log(this); //window
function f() {
console.log(this); //undefined
}
f();
- 在严格模式下,在全局作用域中,
this指向window对象 - 在严格模式下,全局作用域函数中
this等于undefined
希望大家认真读完会对this的认知有一个新的理解。
借用黑客帝国中先知说的一句话结尾吧:
"你来到这里不是为了做选择,你早已选择了。你来到这里的目的,是为了了解你为什么这样做选择。"