JavaScript中的this指向,面试官再问this就秀他一脸

本篇博文参考以下文章
https://www.cnblogs.com/w-yh/p/11913803.html
http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
https://www.cnblogs.com/pssp/p/5216085.html

this指向核心法典

函数执行时运行环境是谁,this就指向谁(当存在运行环境嵌套的时候,遵循就近原则)
核心法典这个时候不理解没关系,接下来我们一步一步拨开它的心

为什么会有this

首先需要明确,this是干嘛的。举个例子,创建一个animal对象

let animal = {
      name: 'Rabbit',
      speed: 10,
      run: function (name, speed) {
        console.log('The ' + name + ' runs with speed ' + speed + '.')
      }
    }

上面代码运行run这个函数的时候,每一次都需要给函数传递参数

animal.run(animal.name, animal.speed) //The Rabbit runs with speed 10.

如果是别的函数调用animal的run方法,那传参就传吧,毕竟不是一个系统,但是大多数情况下,都是animal自己使用自己的run方法,这样还得给自己传参,不符合程序思想(主要是因为懒)。

改进一下上面的代码,修改一下形参

let animal = {
      name: 'Rabbit',
      speed: 10,
      run: function (self) {
        console.log('The ' + self.name + ' runs with speed ' + self.speed + '.');
      }
}

这样再调用的时候,就只用传一个参数就可以了

animal.run(animal) //The Rabbit runs with speed 10.

然后呢,作为程序员,我们是善于研究的(还是因为懒)。这样还是需要传参,明明就是自己调用自己,凭什么要传参?
这里JS作者跟我们一样懒,所以增加一个this作为语法糖。那由于要考虑当别的地方调用run方法的时候也能按照我们预期的那样运行,所以this的指向的便不能仅仅是自身那么简单了,而是提升到了指向运行环境。所以最终代码会变成下面这个样子。

let animal = {
      name: 'Rabbit',
      speed: 10,
      run: function () {
        console.log('The ' + this.name + ' runs with speed ' + this.speed + '.');
      }
}

什么是运行环境

首先解释一下,为什么把this提升到运行环境,而不是函数或者对象自身。上面栗子🌰animal(Rabbit)有一个run的方法,现在有另一个animalB(Pig)也想用Rabbit的方法run跑一跑这个时候如果this是对象自身,那么实现的效果就是pig调用了rabbitrun方法,然后rabbit跑了,我们希望的是pig调用rabbitrun方法,然后pig跑了。

let animal = {
     name: 'Rabbit',
     speed: 10,
     run: function () {
         console.log('The ' + this.name + ' runs with speed ' + this.speed + '.');
     }
 }
 let animalB = {
     name: 'Pig',
     speed: 3,
     run: animal.run
 }
 animalB.run(); //The Pig runs with speed 3.

上面代码中虽然pig调用了rabbit的方法,但是最后输出的还是pig,这就是this的强大之处,指向当前函数运行时的运行环境。大家注意关注下面的代码
在这里插入图片描述
run方法是不是运行了,那运行环境是不是animalB提供的,所以this指向了animalB。

再看一下下面的例子

let animal = {
     firstname: 'Rabbit', //这里修改为firstname是为了和window中的name区别开来
     speed: 10,
     run: function () {
         console.log('The ' + this.firstname + ' runs with speed ' + this.speed + '.');
     }
 }
 let animalB = {
     firstname: 'Pig',
     speed: 3,
     run: animal.run
 }
 let sports = animalB.run;
 sports();//The undefined runs with speed undefined.

上面代码中先关注最后两行,倒数第二行,animalBrun方法赋值给了sports,问题来了,这里有没有运行环境?
答案是没有,这里只是给sports这个变量赋值,函数连运行都没有,所以根本不存在运行环境,只是一个简单的赋值。
然后再看倒数第一行,sports()运行了,那么存在运行环境了吧,运行环境是谁?
这里的运行环境是window,为什么是window
sports是定义在全局作用域当中的变量吧,全局作用域的运行环境就是window,这里打印undefined是因为window里面没有firstnamespeed导致的。
在这里插入图片描述
所以最开始我们的栗子🌰,可以写成

window.animalB.run();//The Pig runs with speed 3.

这里为什么运行环境不是window,这里animalB的运行环境是window,而run()的运行环境是animalB
你可以把运行环境理解为作用域。看一下我们文章最开始的一句话,
函数执行时运行环境是谁,this就指向谁(当存在运行环境嵌套的时候,遵循就近原则)就近原则就是这么来的。

从内存理解this

1.内存结构
let obj = {foo: 5};

上面过程中,JS引擎会先在内存中生成一个{foo: 1},然后把存放这个对象的内存地址赋值给obj
在这里插入图片描述
严格意义上来说,obj存放的是一个地址,浏览器引擎会拿到obj中的地址,从该地址读出原始对象,返回它的foo属性。
每个对象的属性都有四个属性描述符,用来存放该属性的一些特征(值,是否可写,是否可枚举,是否可配置)上面栗子🌰的实际保存方式如下。
在这里插入图片描述

2.函数

当属性的值是一个函数的时候,那么结构就又发生变化了
在这里插入图片描述
由于函数是一个单独的值,他可以在不同的环境下执行

let f = function () {};
let obj = {f:f};
f();//单独执行
obj.f();//obj环境执行
3.环境变量

JavaScript的运行机制里,允许在函数内部引用当前环境的其他变量。这就需要一种机制能够在函数体内部获取当前运行环境,this就应运而生,它涉及的目的就是在函数题内部,指代当前运行环境。

let f = function () {
	console.log(this.x)
};
let x = 1;
let obj = {
	f:f,
	x: 2
};

//单独执行
f();// 1

//obj环境执行
obj.f();// 2

f()的运行环境如下,粉色区域
在这里插入图片描述
obj.f()的运行环境如下粉色区域
在这里插入图片描述
注意上方红线,不同环境下对于function的引用不同,所以导致了获取到的this.x不同。

构造函数this

构造函数中的this,为什么其实例对象能拥有构造函数的属性呢,是因为在new的过程中,this的指向被改变了,指向了实例。下面这种情况比较特殊。

function Fn()  
{  
    this.name = 'rabbit';  
    return {};  
}
let f = new Fn;  
console.log(f.name); //undefined

这里因为return返回的是一个对象,所以this就指向这个对象,而不是实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值