this
是一个比较迷惑人的东西,尽管你对this
有很多的了解,但是面试题里面考察this
指向,总会让你有种猜谜的感觉,知道一些,但是还是会出错,或许你猜对了,但是又好像解释不太清楚。
嗯,不只你一个人这样,很多人都是这样,包括我自己,本质上就是面试埋下的坑,让你跳进去,你想跳过去,那还是不太容易,真正对知识的理解与应用,绝不只是停留在概念与理念,也不是为了完成一道面试题,答不对也没关系,如果面试官给你耐心解释了这道题,那也是一次不错的学习机会。
正文开始...
在阅读本文之前,主要会从以下几点对this
的思考
this
是什么时候产生的this
在函数中的指向问题箭头函数
中this
this
的指向方案为了了解this
,我们先看下this
,新建一个index.html
与1.js
console.log(this, Object.getPrototypeOf(this));
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>this</title>
</head>
<body>
<div id="app"></div>
<script src="./1.js"></script>
</body>
</html>
当我们在浏览器打开时,我们会发现this
是一个window
对象
如果我们在终端
直接运行1.js
呢
{} [Object: null prototype] {}
在node
环境下,全局的this
居然是一个{}
对象
this
现在我们在js
的最顶部使用use strict
采用严格模式。
我们在函数内部写一个this
"use strict"
console.log(this, Object.getPrototypeOf(this));
var publicName = "Maic";
function hello() {
console.log(this) // undefined
console.log(this.publicName) // undefined
}
hello();
在严格模式
下函数内部会是undefined
,并且访问publicName
会直接报错
为啥use strict
严格模式下全局this
无法访问
于是查找资料寻得,严格模式主要有以下特征
delete
删除对象的属性this
不再指向全局对象还有其他的更多的参考js-script[1]
在这之前我们很基础的了解到在非严格模式下this
指向的是window
或者{}
对象,在普通函数中this
的指向是window
全局对象
而你通常会看到this
的指向并不都是指向全局对象,而是动态变化的,正因为它会变化,所以令人十分费脑壳
this
指向
function hello() {
console.log(this) // window
// console.log(this.publicName);
}
hello();
在普通函数内部this
指向的是window
对象
this
指向
...
function Person() {
this.age = 10;
this.name = 'Web技术学苑';
console.log(this, '111')
}
const person = new Person();
console.log(person, '222'); // Person { age: 10, name: 'Web技术学苑' }
至此你会发现,构造函数内部的this
居然就是实例化的那个对象person
const userInfo = {
publicName: 'Jack',
getName: function () {
console.log(this.name, '--useInfo') // Jack
}
}
userInfo.getName();
不出意外打印都知道肯定publicName
肯定是Jack
,内部的this
也是指向userInfo
但是如果改成下面这种呢
var publicName = "Maic";
const userInfo = {
publicName: 'Jack',
getName: () => {
console.log(this.publicName, '---useInfo')
}
}
userInfo.getName();
这是一个很迷惑的问题,箭头函数不是没有自己的this
吗,而且这里是userInfo.getName()
这不是一个隐式调用吗?应也是userInfo
这个对象才对,但是并不是,当改成箭头函数后,内部的this
居然变成了全局的window
对象了
我们看下babel
对上面一段代码编译成es5
的代码
es6代码
var publicName = 'Maic';
const userInfo = {
publicName: 'Jack',
getName: () => {
console.log(this.publicName, '---useInfo')
}
}
userInfo.getName();
编译后的代码,大概就是下面这样的了
var _this = this;
var publicName = "Maic";
var userInfo = {
publicName: "Jack",
getName: function getName() {
console.log(_this.publicName, "---useInfo");
}
};
userInfo.getName();
其实箭头函数是非常迷惑人的,而且外面是一个被调用的是一个对象,所以时常会给人一种幻觉,我们常听到一句this
指向的是被调用的那个对象,那么这里箭头函数
的this
指向的是window
,而const
定义的变量会被转换成var
那怎么能让getName
指向的是本身自己的useInfo
呢
var publicName = 'Maic';
const userInfo = {
publicName: 'Jack',
getName: function(){
console.log(this.publicName, '---useInfo') // Jack
}
}
userInfo.getName();
你看当我把箭头函数改成普通函数,这个普通函数内部的this
就指向userInfo
了
this
指向被调用的那个对象貌似这句话后又在此时好像又是正确的
我们接下来看下下面一种情况
var publicName = 'Maic';
const userInfo = {
publicName: 'Jack',
getName: function(){
console.log(this.publicName, '---useInfo') // Jack
}
}
var user = userInfo.getName;
user();
那么此时getName
内部的this
又是谁呢?
此时你会发现打印的是Maic
此时会发现this
指向的是window
,也就是说指向的那个被调用者
,那被调用者
是谁?
上面那段代码同等于下面,你仔细看
var publicName = 'Maic'; // var 定义,实际上等同于window.publicName = publicName
function getName () {
console.log(this.publicName, '---useInfo') // Jack
}
const userInfo = {
publicName: 'Jack',
getName
}
// var user = userInfo.getName;
// or 等价于
// window.user = userInfo.getName;
// or 进一步等价
window.user = function getName () {
console.log(this.publicName, '---useInfo') // Jack
}
// user();
// or 等价于
window.user();
所以你现在是不是很清晰明白this
指向的也是被调用的那个对象window
了
但是有一点必须申明,必须在非严格模式下,此时的this
才会指向window
。
在这之前我们了解到非严格模式下
this
指向的是window
对象this
指向的是实例化的那个对象this
指向的是全局对象,如果不是那么指向的是被调用本身的那个对象我们再来看下那些面试题中很迷惑的this
var user = {
name: 'Maic',
a: {
name: 'Tom',
b: function () {
console.log(this.name)
}
}
}
console.log(user.a.b()) // Tom
没错,你看到的这个打印是Tom
,这里直接调用的是b
这个方法,被调用的是user.a
这个对象,所以在b
这个方法内部的this
指向了a
对象
如果是箭头函数呢
var name = "Maic";
...
var user = {
name: 'Jack',
a: {
name: 'Tom',
b: () => {
console.log(this.name)
}
}
}
console.log(user.a.b()) // Maic
我们会发现通过babel
转换后会是这样的
var _this = this;
var user = {
name: "Jack",
a: {
name: "Tom",
b: function b() {
console.log(_this.name);
}
}
};
所以依然箭头函数内部依然是个全局对象window
我们接下来看一道真实的面试题
var obj = {
a: 1,
b: function () {
console.log(this.a)
},
c: () => {
console.log(this.a)
}
}
var a = 2;
var objb = obj.b;
var objc = {
a: 3
}
objc.b = obj.b;
const t = objc.b;
obj.b(); // 1
obj.c(); // 2
objb(); // 2
objc.b(); // 3
obj.b.call(null); // 2
obj.b.call(objc); // 3
t() // 2
我想信绝大大部分第一个obj.b()
肯定是可以正确答出来,但是后面的貌似有些迷惑人,时常会让你掉进坑里
我们先看结论打印的依次肯定是
1
2
2
3
2
3
2
obj.b()
的调用实际上在之前例子已经有讲,b
方法是一个普通方法,内部this
指向的就是被调用的obj
对象,所以此时内部访问的a
属性就是对象obj
var objb = obj.b
,当我们看到这样的代码时,其实这段代码可以拆分以下
function b() {
console.log(this.b)
}
window.objb = b;
本质上就是将对象obj
的一个方法b
赋值给了window.objb
的一个属性
所以objb()
的调用也是window.objb()
,objb
方法内部this
自然指向的就是window
对象,而我们用var a = 2
这个默认会绑定在window
对象上
obj.c()
,因为c
是一个箭头函数,所以内部的this
就是指向的全局对象
obj.b.call(null)
这个null
是非常迷惑人,通常来说call
不是改变函数内部this
的指向吗,但是这里,如果call(null)
实际上会默认指向window
对象
objc.b()
这打印的是3,其实与objb
的赋值有异曲同工之笔
...
var objc = {
a: 3
}
objc.b = obj.b;
本质上就在objc
动态的新增了一个属性b
,而这个属性b
赋值了一个方法,也就是下面这样
objc.b = function() {
console.log(this.a)
}
objc.b() // 3
如果是const t = objc.b
,至此你会发现,当我们执行t()
时,此时打印的却是2
那是因为const t
定义的变量会编译成var
从而t变量变成一个全局的window对象下的属性,本质上等价下面
...
// const t = objc.b
var a = 2;
/*
等价于下面
var t = function() {
console.log(this.a)
}
*/
// 本质上就是
window.t = function() {
console.log(this.a)
}
this
var nobj = {
name: '1',
a: {
name: '2',
b: {
name: '3',
c: function () {
console.log(this.name)
}
}
}
}
console.log(nobj.a.b.c()); //3
以上的结果是3,实际上我们从之前案例中明白,非严格模式下this
指向被调用
那个对象
所以你可以把上面那段代码看成下面这样
...
console.log((nobj.a.b).c()); //3
//or 相当于
/*
*
var n = nobj.a.b;
n.c()
*/
这个相信很多小伙伴已经耳熟能祥了,call
,apply
,bind
,能手撕call
,apply
,bind
的文章已经不计其数
这里就只讲解如何使用,以及他们在业务中的一些具体使用场景
用一段伪代码举证以下
// index.vue
import configOption from './config'
export default {
name: 'index',
computed: {
optionsBtnGroup() {
return configOption.call(this)
}
},
methods: {
handleEdit(id) {
console.log(id)
},
handleDelete(id) {
console.log(id)
}
}
}
对应的template
可能就是下面这样几个按钮
<div>
<a href="javascript:void(0)" v-for="(item, index) in optionsBtnGroup" :key="index" @click="item.handle(item.id)">{{item.text}}</a>
</div>
我们再来看下config.js
export default () => {
const options = [
{
text: '编辑',
id: 123,
handle: (id) => {
this.handleEdit(id)
}
},
{
text: '删除',
id: 234,
handle: (id) => {
this.handleDelete(id)
}
}
]
}
正因为在计算属性中用了call
所以在config.js
中才能访问外部methods
的方法,有些人看到这样的代码肯定会说,两个按钮这么搞配置,代码反而多了这么多,还不如模版上放两个按钮完事
是的,确实是,当我们为了使用call
而使用反而增加了业务代码的维护成本,正常情况还是建议不要写出上面那段坏代码的味道
,我们只要明白在什么时候可以用,什么可以不用就行,不要为了使用而使用,反而本末倒置。
但是有时候如果业务复杂,你想隔离业务的耦合,达到通用,call
能帮你减少不少代码量
apply
也是可以改变this
对象
const userInfo = {
publicName: 'Jack',
getName: () => {
console.log(this.publicName, '---useInfo')
}
}
function test(...args) {
console.log(args); // ['hello', 'world']
console.log(this.publicName);
}
test.apply(userInfo, ['hello', 'world'])
apply
会立即执行该函数,如果传入的首个参数是null
或者undefined
,那么此时内部this
指向的是window
另外还有一个方法可以让函数立即执行,也能改变当前函数this
指向
...
var publicName = 'Maic';
function test(...args) {
console.log(args);
console.log(this.publicName);
}
Reflect.apply(test, {publicName: 'aaa'}, [1,2,3]) // aaa [1,2,3]
Reflect.apply(test, window, ['a', 'b', 'c']) // Maic ['a', 'b', 'c']
这也是可以改变this
指向,不过会返回一个新函数,我们常常在react
中发现这样用bind
显示绑定方案。
我们写个简单的例子,尝试改变页面背景,切换body肤色
document.body.addEventListener('click', function () {
console.log(this) // body
if (this.style.backgroundColor === 'red') {
this.style.backgroundColor = 'green'
} else {
this.style.backgroundColor = 'red';
}
})
可以切换背景肤色
以上貌似没有问题,但是你可能会写这样的代码
document.body.addEventListener('click', () => {
console.log(this)
if (this.style.backgroundColor === 'red') {
this.style.backgroundColor = 'green'
} else {
this.style.backgroundColor = 'red';
}
})
此时内部的this
一定指向的window
,而且内部访问style
报错
于是你会改成这样
const fn = function () {
if (this.style.backgroundColor === 'red') {
this.style.backgroundColor = 'green'
} else {
this.style.backgroundColor = 'red';
}
}
document.body.addEventListener('click', fn)
是的,这样是可以的,本质上就是一个fn
的形参,内部this
指向仍然是document.body
于是为了借助bind
,你可以这么做
const body = document.body;
const fn = function () {
if (this.style.backgroundColor === 'red') {
this.style.backgroundColor = 'green'
} else {
this.style.backgroundColor = 'red';
}
}.bind(body)
body.addEventListener('click', fn)
这么做也是ok的
不知道你有没有疑问,为什不像下面这么做呢?
const body = document.body;
const fn = function () {
if (this.style.backgroundColor === 'red') {
this.style.backgroundColor = 'green'
} else {
this.style.backgroundColor = 'red';
}
}
body.addEventListener('click', fn.bind(this))
如果你仔细看下,其实fn
内部this
指向是window
,所以这是一个常会犯的错误。
还有为啥不是像下面这样
const body = document.body;
const fn = function () {
if (this.style.backgroundColor === 'red') {
this.style.backgroundColor = 'green'
} else {
this.style.backgroundColor = 'red';
}
}
body.addEventListener('click', fn.bind(body))
以上功能没有任何问题,但是我们每次点击都会调用bind,从而返回一个新的函数,所以这种方式虽然效果一样,但是性能远不如第一种,为了更好理解,你可以写成下面这样
const body = document.body;
const fn = function () {
if (this.style.backgroundColor === 'red') {
this.style.backgroundColor = 'green'
} else {
this.style.backgroundColor = 'red';
}
}
const callback = fn.bind(body)
body.addEventListener('click', callback)
this
怎么产生的,通常情况this
在非严格模式下,指向的是全局window
对象,在严格模式下,普通函数内的this
不是全局对象this
指向问题,正常情况this
指向的是被调用的那个对象,但是如果是箭头函数,那么指向的是全局对象window
bind
,call
, apply
改变this
指向[1]js-script: https://www.runoob.com/js/js-strict.html
[2]code example: https://github.com/maicFir/lessonNote/tree/master/javascript/05-this
[3]this: https://wangdoc.com/javascript/oop/this.html
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/mnMf2jl7I79TEtGDS6gMuQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。