拿捏js中this指向

123 阅读5分钟

拿捏js中this指向

大一暑假:2022-8-9

this很灵活,认真读完这一篇,你对this的理解八九不离十了

1.想要理解this,先记住以下两点

1.this永远指向一个对象。

2.this的指向完全取决于函数执行时的运行环境。

为什么这么说呢?其中还是涉及很大的奥秘的,且听我慢慢分析。

2.js中的内存结构

js中为什么会有this,跟内存中的数据结构有关系。

举个小栗子来说吧:

let obj = {foo:5}

上面的代码将一个对象赋值给objjavascript引擎首先会在内存中,生成一个对象{obj:5},然后把这个对象的内存地址赋值给变量obj

img

也就是说,变量obj实际是一个地址。如果想要读取obj.foojs引擎先从obj拿到地址,然后再从该地址读出原始的对象,返回它的foo属性。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举个例子,上面例子的foo属性,实际上是以下面的形式保存的。

img

{
  foo: {
    [[value]]: 5
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}

foo属性的值保存在属性描述对象的value属性中。

问题是属性的值是一个函数。

比如:

let obj = {foo:function(){}}

这时,js会将函数单独保存在内存中,然后再将函数的地址赋给foo属性的value属性。

img

{
  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.fobj环境下执行,所以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指向windowthis.xthis.y等同于window.xwindow.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

第一个函数fobj1环境下执行,this指向obj1,所以this.x等同于obj1.x,打印1。

我相信第一个大家都没问题,第二个就有点疑惑了,有人可能会这样想:obj2.f明明是调用obj1.ff的运行环境还在obj1

中,应该打印1才是。这样想的小伙伴说你是强盗也不为过。

举个几乎不会出现的例子来说:小明向小红借了100rmb,然后花了却不还。小红问为什么不还?小明说:我花的是你的钱,不是我的钱,为什么要还?

活生生的强调逻辑!

相同道理,生活中不会出现,js中也是如此,obj2中的f执行了obj1中的ff函数的执行环境是谁呢?当然是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指向具体的实例对象,也就是fnfn中存在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的认知有一个新的理解。

借用黑客帝国中先知说的一句话结尾吧:

"你来到这里不是为了做选择,你早已选择了。你来到这里的目的,是为了了解你为什么这样做选择。"

参考:JavaScript 的 this 原理 - 阮一峰的网络日志 (ruanyifeng.com)

彻底搞懂JavaScript中的this指向问题 - 知乎 (zhihu.com)