Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript深入之从ECMAScript规范解读this #7

Open
mqyqingfeng opened this issue Apr 25, 2017 · 336 comments
Open

JavaScript深入之从ECMAScript规范解读this #7

mqyqingfeng opened this issue Apr 25, 2017 · 336 comments

Comments

@mqyqingfeng
Copy link
Owner

mqyqingfeng commented Apr 25, 2017

前言

在《JavaScript深入之执行上下文栈》中讲到,当JavaScript代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。

对于每个执行上下文,都有三个重要属性

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

今天重点讲讲 this,然而不好讲。

……

因为我们要从 ECMASciript5 规范开始讲起。

先奉上 ECMAScript 5.1 规范地址:

英文版:http:https://es5.github.io/#x15.1

中文版:http:https://yanhaijing.com/es5/#115

让我们开始了解规范吧!

Types

首先是第 8 章 Types:

Types are further subclassified into ECMAScript language types and specification types.

An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language. The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.

A specification type corresponds to meta-values that are used within algorithms to describe the semantics of ECMAScript language constructs and ECMAScript language types. The specification types are Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record.

我们简单的翻译一下:

ECMAScript 的类型分为语言类型和规范类型。

ECMAScript 语言类型是开发者直接使用 ECMAScript 可以操作的。其实就是我们常说的Undefined, Null, Boolean, String, Number, 和 Object。

而规范类型相当于 meta-values,是用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。规范类型包括:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。

没懂?没关系,我们只要知道在 ECMAScript 规范中还有一种只存在于规范中的类型,它们的作用是用来描述语言底层行为逻辑。

今天我们要讲的重点是便是其中的 Reference 类型。它与 this 的指向有着密切的关联。

Reference

那什么又是 Reference ?

让我们看 8.7 章 The Reference Specification Type:

The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operators.

所以 Reference 类型就是用来解释诸如 delete、typeof 以及赋值等操作行为的。

抄袭尤雨溪大大的话,就是:

这里的 Reference 是一个 Specification Type,也就是 “只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。

再看接下来的这段具体介绍 Reference 的内容:

A Reference is a resolved name binding.

A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag.

The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1).

A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.

这段讲述了 Reference 的构成,由三个组成部分,分别是:

  • base value
  • referenced name
  • strict reference

可是这些到底是什么呢?

我们简单的理解的话:

base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。

referenced name 就是属性的名称。

举个例子:

var foo = 1;

// 对应的Reference是:
var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};

再举个例子:

var foo = {
    bar: function () {
        return this;
    }
};
 
foo.bar(); // foo

// bar对应的Reference是:
var BarReference = {
    base: foo,
    propertyName: 'bar',
    strict: false
};

而且规范中还提供了获取 Reference 组成部分的方法,比如 GetBase 和 IsPropertyReference。

这两个方法很简单,简单看一看:

1.GetBase

GetBase(V). Returns the base value component of the reference V.

返回 reference 的 base value。

2.IsPropertyReference

IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.

简单的理解:如果 base value 是一个对象,就返回true。

GetValue

除此之外,紧接着在 8.7.1 章规范中就讲了一个用于从 Reference 类型获取对应值的方法: GetValue。

简单模拟 GetValue 的使用:

var foo = 1;

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};

GetValue(fooReference) // 1;

GetValue 返回对象属性真正的值,但是要注意:

调用 GetValue,返回的将是具体的值,而不再是一个 Reference

这个很重要,这个很重要,这个很重要。

如何确定this的值

关于 Reference 讲了那么多,为什么要讲 Reference 呢?到底 Reference 跟本文的主题 this 有哪些关联呢?如果你能耐心看完之前的内容,以下开始进入高能阶段:

看规范 11.2.3 Function Calls:

这里讲了当函数调用的时候,如何确定 this 的取值。

只看第一步、第六步、第七步:

1.Let ref be the result of evaluating MemberExpression.

6.If Type(ref) is Reference, then

  a.If IsPropertyReference(ref) is true, then
      i.Let thisValue be GetBase(ref).
  b.Else, the base of ref is an Environment Record
      i.Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).

7.Else, Type(ref) is not Reference.

  a. Let thisValue be undefined.

让我们描述一下:

1.计算 MemberExpression 的结果赋值给 ref

2.判断 ref 是不是一个 Reference 类型

2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)

2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)

2.3 如果 ref 不是 Reference,那么 this 的值为 undefined

具体分析

让我们一步一步看:

  1. 计算 MemberExpression 的结果赋值给 ref

什么是 MemberExpression?看规范 11.2 Left-Hand-Side Expressions:

MemberExpression :

  • PrimaryExpression // 原始表达式 可以参见《JavaScript权威指南第四章》
  • FunctionExpression // 函数定义表达式
  • MemberExpression [ Expression ] // 属性访问表达式
  • MemberExpression . IdentifierName // 属性访问表达式
  • new MemberExpression Arguments // 对象创建表达式

举个例子:

function foo() {
    console.log(this)
}

foo(); // MemberExpression 是 foo

function foo() {
    return function() {
        console.log(this)
    }
}

foo()(); // MemberExpression 是 foo()

var foo = {
    bar: function () {
        return this;
    }
}

foo.bar(); // MemberExpression 是 foo.bar

所以简单理解 MemberExpression 其实就是()左边的部分。

2.判断 ref 是不是一个 Reference 类型。

关键就在于看规范是如何处理各种 MemberExpression,返回的结果是不是一个Reference类型。

举最后一个例子:

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}

//示例1
console.log(foo.bar());
//示例2
console.log((foo.bar)());
//示例3
console.log((foo.bar = foo.bar)());
//示例4
console.log((false || foo.bar)());
//示例5
console.log((foo.bar, foo.bar)());

foo.bar()

在示例 1 中,MemberExpression 计算的结果是 foo.bar,那么 foo.bar 是不是一个 Reference 呢?

查看规范 11.2.1 Property Accessors,这里展示了一个计算的过程,什么都不管了,就看最后一步:

Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.

我们得知该表达式返回了一个 Reference 类型!

根据之前的内容,我们知道该值为:

var Reference = {
  base: foo,
  name: 'bar',
  strict: false
};

接下来按照 2.1 的判断流程走:

2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)

该值是 Reference 类型,那么 IsPropertyReference(ref) 的结果是多少呢?

前面我们已经铺垫了 IsPropertyReference 方法,如果 base value 是一个对象,结果返回 true。

base value 为 foo,是一个对象,所以 IsPropertyReference(ref) 结果为 true。

这个时候我们就可以确定 this 的值了:

this = GetBase(ref)

GetBase 也已经铺垫了,获得 base value 值,这个例子中就是foo,所以 this 的值就是 foo ,示例1的结果就是 2!

唉呀妈呀,为了证明 this 指向foo,真是累死我了!但是知道了原理,剩下的就更快了。

(foo.bar)()

看示例2:

console.log((foo.bar)());

foo.bar 被 () 包住,查看规范 11.1.6 The Grouping Operator

直接看结果部分:

Return the result of evaluating Expression. This may be of type Reference.

NOTE This algorithm does not apply GetValue to the result of evaluating Expression.

实际上 () 并没有对 MemberExpression 进行计算,所以其实跟示例 1 的结果是一样的。

(foo.bar = foo.bar)()

看示例3,有赋值操作符,查看规范 11.13.1 Simple Assignment ( = ):

计算的第三步:

3.Let rval be GetValue(rref).

因为使用了 GetValue,所以返回的值不是 Reference 类型,

按照之前讲的判断逻辑:

2.3 如果 ref 不是Reference,那么 this 的值为 undefined

this 为 undefined,非严格模式下,this 的值为 undefined 的时候,其值会被隐式转换为全局对象。

(false || foo.bar)()

看示例4,逻辑与算法,查看规范 11.11 Binary Logical Operators:

计算第二步:

2.Let lval be GetValue(lref).

因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined

(foo.bar, foo.bar)()

看示例5,逗号操作符,查看规范11.14 Comma Operator ( , )

计算第二步:

2.Call GetValue(lref).

因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined

揭晓结果

所以最后一个例子的结果是:

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}

//示例1
console.log(foo.bar()); // 2
//示例2
console.log((foo.bar)()); // 2
//示例3
console.log((foo.bar = foo.bar)()); // 1
//示例4
console.log((false || foo.bar)()); // 1
//示例5
console.log((foo.bar, foo.bar)()); // 1

注意:以上是在非严格模式下的结果,严格模式下因为 this 返回 undefined,所以示例 3 会报错。

补充

最最后,忘记了一个最最普通的情况:

function foo() {
    console.log(this)
}

foo(); 

MemberExpression 是 foo,解析标识符,查看规范 10.3.1 Identifier Resolution,会返回一个 Reference 类型的值:

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};

接下来进行判断:

2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)

因为 base value 是 EnvironmentRecord,并不是一个 Object 类型,还记得前面讲过的 base value 的取值可能吗? 只可能是 undefined, an Object, a Boolean, a String, a Number, 和 an environment record 中的一种。

IsPropertyReference(ref) 的结果为 false,进入下个判断:

2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)

base value 正是 Environment Record,所以会调用 ImplicitThisValue(ref)

查看规范 10.2.1.1.6,ImplicitThisValue 方法的介绍:该函数始终返回 undefined。

所以最后 this 的值就是 undefined。

多说一句

尽管我们可以简单的理解 this 为调用函数的对象,如果是这样的话,如何解释下面这个例子呢?

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}
console.log((false || foo.bar)()); // 1

此外,又如何确定调用函数的对象是谁呢?在写文章之初,我就面临着这些问题,最后还是放弃从多个情形下给大家讲解 this 指向的思路,而是追根溯源的从 ECMASciript 规范讲解 this 的指向,尽管从这个角度写起来和读起来都比较吃力,但是一旦多读几遍,明白原理,绝对会给你一个全新的视角看待 this 。而你也就能明白,尽管 foo() 和 (foo.bar = foo.bar)() 最后结果都指向了 undefined,但是两者从规范的角度上却有着本质的区别。

此篇讲解执行上下文的 this,即便不是很理解此篇的内容,依然不影响大家了解执行上下文这个主题下其他的内容。所以,依然可以安心的看下一篇文章。

下一篇文章

《JavaScript深入之执行上下文》

深入系列

JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

@littleluckly
Copy link

littleluckly commented May 18, 2017

下面一段代码的执行结果为1,why???,Foo()函数返回的this指window对吧,然后Foo().getName(),不就是指window.getName()吗,所以执行结果应该是5呀?求解答

function Foo(){
	getName = function(){
		console.log(1);					
        };
	return this
}
			
function getName(){
	console.log(5);
}

Foo().getName();

@mqyqingfeng
Copy link
Owner Author

@littleluckly this 确实是指向 window ,但是这道题的陷阱在于 Foo 函数执行的时候,里面的 getName 函数覆盖了外层的 getName 函数

@littleluckly
Copy link

@mqyqingfeng 对哦,Foo()函数里面也是个一个函数表达式,getName也是指向的全局。谢谢博主

@littleluckly
Copy link

littleluckly commented May 18, 2017

@mqyqingfeng 追问一下博主,如果代码改成如下这样,结果为3,请指教

		function Foo(){
				getName = function(){
					console.log(1);					
				};
				return this;
			}

			Foo.prototype.getName = function(){
				console.log(3);
			};

			function getName(){
				console.log(5);
			};
			new Foo().getName()//3

@mqyqingfeng
Copy link
Owner Author

@littleluckly 刚才去吃饭了,这道题考察的是运算符优先级问题,各运算符优先级可以查看这里

你会发现 成员访问 和 new (带参数列表)的优先级都为 19,相同等级下,遇到谁先执行谁,所以 new Foo().getName()相当于 (new Foo()).getName()

接下来的就比较简单了, new 返回一个对象,这个对象的原型指向 Foo.prototype,然后访问这个对象上的getName方法,自然是调用写在原型上的这个方法啦,结果也就是 3。

@littleluckly
Copy link

@mqyqingfeng 博主请务必一定接受一个抠脚大汉对你表示的感谢~~~么么哒

@mqyqingfeng
Copy link
Owner Author

@littleluckly 哈哈,感觉这是一个有味道的感谢,(~ ̄▽ ̄)~

@MissCuriosity
Copy link

= =没看懂,谢特,我再来看一遍。

@MissCuriosity
Copy link

我自己打印了一下,在foo.proto,foo.bar.__proto__中都没有GetValue,在全局直接使用GetValue也是报错得啊(GetValue is not defined),这一章真的是看得万脸懵逼啊!!

@MissCuriosity
Copy link

感觉看了三遍也没懂!!!

@Nikaple
Copy link

Nikaple commented May 27, 2017

@MissCuriosity GetValue, GetBase以及Reference类型等这篇文章提到的概念都属于浏览器底层的实现,只是为了从原理上来解读this指向问题的,自然在console里是无法使用的~

@mqyqingfeng
Copy link
Owner Author

@Nikaple 感谢回答,确实是这样的~
@MissCuriosity 回答的晚了,很抱歉,不知道你读规范受到了怎样的煎熬,实际上,我在学习 this 的时候,也深感艰难,要理解这篇文章的话,是要边跟着文章的思路边看规范的,而理解 this 的关键点在于通过查阅规范,判断表达式的结果是不是一个 Reference 类型。规范确实难懂,但若能克服对规范的恐惧,也是一个巨大的成长!与你相互勉励~

@sunsl516
Copy link

好了。博主。你成功把我讲懵逼了。

@mqyqingfeng
Copy link
Owner Author

@sunsl516 哈哈,这一篇不影响以后文章的阅读,你可以安心读接下来的文章,这篇文章是为了告诉大家还有一种讲解 this 的角度是从规范切入,如果有一天,你想知道从规范怎么解读 this,那再回来看这一篇文章哈~

@sunsl516
Copy link

嗯。 这个角度倒是很新颖。 这篇文章得反复琢磨才能搞懂。反正已经收藏了。后面再多看几遍。谢博主无私分享。@mqyqingfeng

@lynn1824
Copy link

太官方了,没看懂。
我知道的是:this一般有几种调用场景
var obj = {a: 1, b: function(){console.log(this);}}
1、作为对象调用时,指向该对象 obj.b(); // 指向obj
2、作为函数调用, var b = obj.b; b(); // 指向全局window
3、作为构造函数调用 var b = new Fun(); // this指向当前实例对象
4、作为call与apply调用 obj.b.apply(object, []); // this指向当前的object

@mqyqingfeng
Copy link
Owner Author

@lynn1824 感谢补充,我觉得从调用场景去讲 this 的指向是一个非常重要的总结,这在我们日常的使用中非常重要。然而我该如何解释 (obj.b)() (false || obj.b)() (foo.bar, foo.bar)()下 this 的指向呢?为什么 (obj.b)() 是作为对象调用, (false || obj.b)() 就是作为函数调用呢?不知道该如何很好的解释这些,才让我最终决定从规范的角度去讲一讲

@MrGoodBye
Copy link

@mqyqingfeng 感觉(false || obj.b)()像是一个自调用立执行函数,所以是函数调用模式.

@yangzhongxun
Copy link

看了两遍,有一点不太明白,具体分析的例子里面的后三问的 this 为什么不是 window,而是 undefined,如果是 undefined,那么 undefined.value 的值为什么是 2 呢?博主有时间了还请帮忙解释下,多谢!

@mqyqingfeng
Copy link
Owner Author

@MrGoodBye 哈哈,(obj.b)()也有两个括号呐……看规范的意思,(obj.b)()(false || obj.b)()的区别在于 ||运算符对值进行了计算,可是为什么进行了计算,this 的指向就发生了变化呢?哎呀,我们还是来看规范吧……😂

@mqyqingfeng
Copy link
Owner Author

@yangzhongxun 非严格模式下, this 的值如果为 undefined,默认指向全局对象

@yangzhongxun
Copy link

@mqyqingfeng 多谢博主!规范的确更加难懂,不过也是语言的根本,向博主学习!

@webLion200
Copy link

看的我是万脸蒙逼啊

@qray90
Copy link

qray90 commented Jul 11, 2022 via email

@zhuhuanxiong
Copy link

学到了

@philling
Copy link

看了四遍,终于看懂了~楼主牛逼!

@qray90
Copy link

qray90 commented Aug 24, 2022 via email

@Amy0104
Copy link

Amy0104 commented Sep 2, 2022

感谢楼主,感觉清晰了很多。

@i-am-zhengyufang
Copy link

我个人理解是因为innerFunction()的调用方式并不是通过对象.方法的方式调用的,尽管它在a的方法fn内部,因此this不会指向a,你可以看成一个普通函数调用,因此this指向最外层的window,得到windowsName

@shengyur
Copy link

shengyur commented Sep 14, 2022

有这两种场景:
第一种:

var a = 123
function foo1() {
    return a => {
        console.log(this.a)
    }
}
const obj1 = {
    a:2
}

const obj2 = {
    a:3
}

var bar = foo1.call(obj1)

bar.call(obj2)

第二种:

    var a = 123

    const foo2 = () => {
        return a => {
            console.log(this.a)
        }
    }
    const obj3 = {
        a:2
    }

    const obj4 = {
        a:3
    }

    var bar = foo2.call(obj3)

    bar.call(obj4)

运行出来的结果第一个是2,第二个是123, 没太明白为啥。

目前能解释的通的理解是 this 是调用函数的时候,是可以通过call绑定this的,且绑定无法被修改,但是对箭头函数调用call的时候,无法绑定this,因为箭头函数的this是由外层作用域决定的。所以第二种的this指向的是全局对象,浏览器环境中是window,所以是123

请教下大佬们,这个理解对嘛?

@jingaier
Copy link

jingaier commented Oct 11, 2022 via email

@qray90
Copy link

qray90 commented Oct 11, 2022 via email

@mrzsping
Copy link

为什么(false || foo.bar)()这里是因为第二步2.Let lval be GetValue(lref)使用了 GetValue而不是因为第4和5步返回的不是Reference 呢?

@coveyz
Copy link

coveyz commented Oct 31, 2022

@@

@jingaier
Copy link

jingaier commented Oct 31, 2022 via email

@qray90
Copy link

qray90 commented Oct 31, 2022 via email

@yetHandsome
Copy link

很硬核,我很喜欢,但是感觉好多概念还不是很清楚,可能需要多举几个例子才能看懂,我读了另一篇也写的不错的https://wangdoc.com/javascript/oop/this

@yetHandsome
Copy link

this
结论:
1.对象属性赋值方法,方法内 this 指向对象本身,
2.1 如果是变量赋值方法,方法内 this 指向 window
2.2 (上述可以认为是一个全局定义的函数赋值给变量,map、foreach 内部function 也可以认为是全局定义的function,故也是 window).
3.如果将对象属性定义的方法赋值给变量,方法内 this 指向 window.
4.变量找不到会去外层找,但是this不会,this找不到就是window.

例子:
1.
var o = {
f1: function () {
console.log('f1',this);
var f2 = function () {
console.log('f2',this);
}();
}
}

o.f1()

2.1
var f2 = function () {
console.log('f2',this);
}();

2.2
var a = [1,2]
a.forEach(function (item) {
console.log(item,this);
});

var o = {
f1: function () {
console.log('f1',this);
}
}

var f2 = o.f1
f2()

function a(){
var x = 1;
function b(){
console.log(x,this)
}
b()
}

a()

@DaphnisLi
Copy link

@

就是外面的函数声明提升了

@DaphnisLi
Copy link

@littleluckly 刚才去吃饭了,这道题考察的是运算符优先级问题,各运算符优先级可以查看这里

你会发现 成员访问 和 new (带参数列表)的优先级都为 19,相同等级下,遇到谁先执行谁,所以 new Foo().getName()相当于 (new Foo()).getName()

接下来的就比较简单了, new 返回一个对象,这个对象的原型指向 Foo.prototype,然后访问这个对象上的getName方法,自然是调用写在原型上的这个方法啦,结果也就是 3。

我觉得可以简单理解为,Foo 函数里面的 getName 在 new 的时候不会被 new 到实例中去,可以简单理解为丢失了,如果在前面加一个 this 变成 this.getName,那结果将会变成 1

@juanfu1997
Copy link

示例 4
false || foo.bar
和示例 3不都是使用了getBase,返回的值不是Reference吗?按理strict模式下,示例4也是会报错的吧

@Krue1
Copy link

Krue1 commented Jun 5, 2023

常看常新,写得太好了

@qray90
Copy link

qray90 commented Jun 5, 2023 via email

@jingaier
Copy link

jingaier commented Jun 5, 2023 via email

@qray90
Copy link

qray90 commented Jun 19, 2023 via email

@ShawLocke
Copy link

ShawLocke commented Jun 25, 2023

@mqyqingfeng 感谢作者,this的解释确实很不容易。我还记得当时我是看了 dmitry soshnikov 的系列blog( http:https://dmitrysoshnikov.com/ ),他是从 ES3 到 ES5 到 ES6讲的,我看了至少5次,后来我才开始看的 ES规范。建议没有基础的各位,不要一上来就看 ES 规范,真的很晦涩,需要很大的耐心。

@wmasfoe
Copy link

wmasfoe commented Jul 8, 2023

第一次刷已蒙蔽...
过段时间二刷

@lxfljw
Copy link

lxfljw commented Mar 13, 2024

补充一个总结:类似运算符的就会去做 getValue,得到的是一个 value 值,比如:(false || foo.bar)() || 就是运算符,所以 会 getValue 就得不到 reference,this 要求是 reference,所以严格模式下 getValue 后 this 就是 undefined。
其实场景 如 foo.bar = foo.bar. 这种赋值不是运算,所以得到的是 reference foo,this 就指向 foo。说白了这都是规范决定了,并没有什么特别逻辑性去推理,真要理解还是看规范。最后感谢博主总结,时隔多年还是好文章。

@qray90
Copy link

qray90 commented Mar 13, 2024 via email

@jingaier
Copy link

jingaier commented Mar 13, 2024 via email

@tinyblckc0000al
Copy link

花了一个半小时反复上下看,勉强理解了js判断this的过程,如果有理解不对的地方的话希望有人指出:

  1. 先分离出语句的MemberExpression,把它赋给ref
  2. 判断ref是否为Reference :主要看有没有父变量base value,自身namestring(这里其实不太清楚strict对reference判断是否有影响)。可以反向排查如果调用了GetValue,那么返回必定不是Reference。
    3.1 如果是Reference,且父变量是个对象,那么返回这个reference的Base value给this;
    3.2 其他情况this都为undefined,然后根据是否为严格模式考虑是否将this转换成顶级变量。

感觉对于正文中举的例子都能理解,不过像apply,call,new 这种怎么去分析啊?

ps:虽然有点为自己的愚笨感到丧气,但是看到大大的评论真的太温暖了,感谢您分享这样的文章!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests