mqyqingfeng / Blog

冴羽写博客的地方,预计写四个系列:JavaScript深入系列、JavaScript专题系列、ES6系列、React系列。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

JavaScript深入之从ECMAScript规范解读this

mqyqingfeng opened this issue · comments

前言

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

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

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

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

……

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

先奉上 ECMAScript 5.1 规范地址:

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

中文版:http://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,对作者也是一种鼓励。

下面一段代码的执行结果为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();

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

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

@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

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

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

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

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

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

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

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

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

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

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

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

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

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

太官方了,没看懂。
我知道的是: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

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

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

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

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

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

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

看的我是万脸蒙逼啊

@webLion200 哈哈,不影响以后文章的阅读,请安心的接着看~

所以,上面的demo,你只需要把:

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

改成:

var getName = function(){
    console.log(1);					
};

输出结果就也是5了。

@chenxiaochun 哈哈,确实是这样的,不过@littleluckly 那道题其实是一道面试题,就是专门挖了这个陷阱~

博主.前面都能看懂,就这一篇好多看不懂啊,我能怎么办我也很绝望啊

@Flying-Eagle2 所以我特地讲了 即便不是很理解此篇的内容,依然不影响大家了解执行上下文这个主题下其他的内容。所以,依然可以安心的看下一篇文章,我希望这篇文章能给大家带来一个新角度去看待 this ,暂时没有看懂,没有关系,以后突然想看了,那就再来啃一啃规范,那个时候也许就会看懂很多了~

@mqyqingfeng 恩恩,感谢博主哈

好文,从浏览器底层解读this指向,感谢博主~~~

非常感谢博主,能看到这篇文章绝对是我的幸运。事实上我之前看到过一篇类似的文章http://dmitrysoshnikov.com/ecmascript/chapter-3-this/
虽然能理解大部分,但是文中介绍 Reference 类型不够清晰(或者说我看不懂),然后我也有跟博主一样的疑惑,最后还是博主这篇文章让我彻底明白了,再次感谢。我看了博主的几篇文章,感觉捡到宝了(兴奋)。希望博主能写更多好文章让我这种小白能更深入的理解JS,感激不尽!

请问博主能不能讲一下浏览器的事件循环,task queue?job queue?以及渲染之类的?

@sterproton 谢谢夸奖哈,浏览器方面的内容现在还没有研究过,不过已经加入了待研究课题中,未来一定会写这些内容的~

这绝绝对对是我看到所有讲解this指向中最新颖的方式啦虽然大神的东东没看懂不过打算多看几遍

commented

大大呐 我也成功懵逼了 Mark着以后哪天脑子开窍了慢慢啃

commented

看的我整个人都**了

commented

又看了一遍,懂一点点了,爽

commented

道行太浅,以后再来读一遍

@ershing 没有什么问题哈~ 我就是想知道规范到底是怎么确定 this 的~

commented

(foo.bar)()
foo.bar返回的是

function () {
    return this.value;
}

感觉这应该是属于立即执行函数的范畴。
这样看运行的时候this不应该指向全局吗

博主人好温柔呀……

@wy1009 o(////▽////)q

能不能做一下伸手党,请问一下,如果是箭头函数的this指向,在规范中是如何规定的呢?><感激不尽!

@lynn1824 感谢回答哈~ @wy1009 我能说关于箭头函数的 this 在规范中是如何规定的,我还没有研究过吗……未来写到 ES6 系列的 arrow function,一定给你回复哈~

楼主标准看了多少?之前问了一些人,都说看标准浪费时间,然后就没有看啦

@YanLIU0822 标准我也并没有看过多少,我认为倒不需要专门去看规范,毕竟很多地方都很枯燥,有需要的时候,比如研究隐式类型转换的规则,就可以查看规范是如何定义的

此文让我就明白一个道理,就是还是要去看规范

到是博主另一片文章作用域链和上下文让我理解this了,其实说白了,this就是指向当前作用域上的[[scope]]

 value = 1;

function foo() {
    var value = 2;
    function dd() {
        return this.value;
    }
    return dd;
}

console.log(foo()()) // 1

请问下楼主,里的结果分析应该用词法作用域还是用本章讲的this呢,有点混乱

涉及到了 this,就是用本章的 this,如果没有,比如

 value = 1;

function foo() {
    var value = 2;
    function dd() {
        return value;
    }
    return dd;
}

console.log(foo()()) // 2

就根据词法作用域进行解释,此时的结果就变成了 2

@mqyqingfeng 那么this.value是在调用foo()时,就已经确定了吗,还是最后foo()()时确定的。不好意思,这个是我疑惑的地方,刚才表述不太清楚。

@fallenleaves409 this.value 是最后 foo()() 确定的,而 value 是函数创建的时候确定的~

@mqyqingfeng 十分感谢 = =

果然看不懂啊,果然道行不够

	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

博主,为什么最后new的实例不先去找构造函数里的固有方法,而是直接从原型上找

@pxz199381 因为构造函数里没有把方法绑定到实例上,如果Foo里是this.getName = ...就会输出1了。

@Nikaple OK,多谢了

var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());
这里object.getNameFunc()()的MemberExpression是object.getNameFunc(),那它是引用类型吗,如果是它的值是什么呀?@mqyqingfeng

是否考虑完结的弄成PDF?

受益匪浅

请问楼主,这里好像只是单纯从ECMAScript的角度去诠释this,那在event事件以及setTimeout之类的计时器中this是如何去定义的

@DarkYeahs 这个方面我还没有研究过,算是留个待补充的内容吧~

楼主,表示一脸懵逼,完全看不明白,深受打击啊

@jingaier 跳过这一篇哈~

厉害厉害,准备全部看一遍搞个总结什么的出来

@zanqianqvxifu 好呀,欢迎分享~

commented

首先说,第一个点是,第一次见这种直到this本质的规范文章,从来不知道,原来this是这样算出来的,第二点,前面有个说场景的那个同学,我也是基本是这么个认识,感觉足够了,可是看这篇文章
我有点不明白,比如,
console.log((foo.bar = foo.bar)());

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

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

像这种写法,(foo.bar = foo.bar)(),到底是啥意思?或者说,foo.bar=foo.bar左边括号里面,返回了啥?这是啥写法?(求解惑)
//而后面的,false || foo.bar,和false ,foo.bar,这2个地方,是一个表达式,分别是或表达,以及,逗号表达式,返回的内容,我知道,
//以前了解的this,也就是,所有这三种形式,都丢失了,this当前的上下文,变成了全局的this,也就是文章说的,undefined,或者说是,window对象
//但是为啥会丢失,看了这篇文章没找到答案,我的意思是说,能再大白话一点不,解释这里

后面三行,忽视掉,因为想起了,就是前面有个同学说的,其实就是,因为最终在全局环境中执行,
function () {
return this.value;
}
这里的this当然是window

@Ghohankawk foo.bar = foo.bar 就是取出 foo.bar 的值然后再赋值给 foo.bar,只是将自己的值再赋值给自己而已,返回的还是这个赋的值,也就是 foo.bar。

这些运算为什么会导致丢失呢?我也不知道,因为按照规范中的这些运算符的运算过程进行推导,最终得出的值并不是一个 Reference 类型,而它们不是 Reference 类型,判断 this 时就是会返回 undefined,可是为什么它们不返回一个 Reference 类型呢?是出于什么考虑的呢?我也不知道呀。

这篇文章算是给大家提供一个思路去判断 this 的值,不同于传统的根据场景和经验来判断,而是根据规范,切实可查的判断每个运算符的最终结果,根据是不是 Reference ,如果是 Reference 类型又怎样怎样,最终得出 this 值。

不得不说,真的很麻烦,哈哈~

再看个例子

var value = 1;

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

你可以尝试所有函数调用.形式是

(any expression return a funcion)();
/*
如果不绑定,调用结果的this一定是window 或者 global
因为在规范中定义的函数调用符号()前面的是memberexpression,
你用括号括起来,没有访问符,那这不就是一个全局成员?
*/

是不是可以这么说,用到了 || && , = ... 这些运算符,都会调用这个内置的getvalue。会导致它的this指向变化了。

@jxZhangLi 是的~ 不过为什么会调用这个内置的 getValue 方法,我也没有想清楚😂

commented

看过的关于this的指向确定里面,唯一从规范角度来确定this指向的文章。即便是你不知道的JavaScript系列上中对于this的指向也是偏经验的角度,靠经验的话的确无法解释为何最后一个例子返回值是1。来来回回看了此文十遍左右,中间部分来回翻对着下面例子来解释,中午算是能理解了。也尝试用规范来解释最后一个经验解释不了的例子,的确this指向了全局,返回1。规范实在看不下去,幸好作者将关键的重点都拎出来,看起来省很多力气。感谢!

看了这章感觉一脸懵逼~之前的this理解都是错误的么。。

@c690554125 想说一句, 《你不知道的js》中的this的讲解根本没有任何偏经验的言论, 他是告诉了你规则(这也是根据规范得来的),而且在帮助你理解的时候,作者特意强调了函数就是函数本身,它从来不属于任何对象,this的指向取决于调用形式,函数是引用类型,我不觉得最后一个例子和 var f = foo.bar; f(); 有任何本质上的区别。

@mqyqingfeng @Ghohankawk 这时因为foo.bar = foo.bar是个表达式呀, 而这个表达式返回的是右边那个foo.bar的值,也就是这个函数的引用(这是不加任何修饰的,你不知道的js中就是这样描述的,我也想不到更好的描述了...233),然后直接加()来调用,this自然就指向顶层对象了呀

@MinhowS 不不,你之前理解的也没有错误……只是角度不同罢了,如果这篇看不懂,没有关系的,接着往下看,剩下的比起这篇都很简单哦~

@c690554125 看这么多遍……真是辛苦了,棒棒哒~ o( ̄▽ ̄)d,希望带给你一个新的视角去理解 this ~

@sinkinlife 之所以说最后一个例子和 var f = foo.bar; f() 本质是不同的,是因为从规范的角度去看的话,两者其实走了不同的 this 判断分支。返回函数引用的这种理解方式也是可以的啦,规范算是从更具体的角度去表示,相辅相成~

commented

@sinkinlife 哦,看懂你的意思了,明白你要说的内容了,对,这个地方,你这么一说,我也稍微明白啦,这种表达方式对于理解也是可以的,其实就是和类似,你主动调用了,如,function a(){alert("han")},这种函数一样,即使你没有返回任何东西,函数也是默认返回了,一个undefined的值,这里,就是你说的,foo.bar=foo.bar,这个东西虽然是赋值,但是是有返回值的,一个道理,谢谢了

commented

即渊博又谦逊的博主!!!感谢博主的分享!感谢我看到了这么棒的文章!!!

这是我学JS这一个月看过最最最最难懂的文章,也是我遇到剖析的最深的文章,感谢博主的无私分享!!!

commented

call方法改变的是函数执行上下文还是this呢?

@yoky @jaweXU 这篇文章可能写的有些难懂,不要死磕,可以接着看下面的文章哈~

@Heroor 应该改变的是 this,但是函数执行上下文也包含了 this 的概念~

commented

@mqyqingfeng 我在网上其他文章里看到说,call,apply改变的是执行上下文,然后执行上下文创建时会建立this,所以this就跟着变了,这么理解是不是不对啊

感谢博主的分析,化繁为简,读完确实感觉判断this的思路清晰很多,只是要记住底层的方法还是要一些时间,一遍看下来还是要回头看很多次😂😂😂
另外关于示例4(false || foo.bar)()中的解释:

2.Let lval be GetValue(lref).

刚看的时候觉得“不对吖,这个逻辑运算符的计算结果不应该是右边的foo.bar么,跟lref应该没关系吧”,遂查看中文文档:

1. 令 lref 为解释执行 LogicalORExpression 的结果 .
2. 令 lval 为 GetValue(lref).
3. 如果 ToBoolean(lval) 为 true ,返回 lval.
4. 令 rref 为解释执行 LogicalANDExpression 的结果 .
5. 返回 GetValue(rref).

发现其实rref也做了一次GetValue()操作,这样的话就能解释为什么是输出undefined了,像示例4这种情况,博主只解释说lref的情况,理解容易出现偏差。
这里提个小小的建议,希望博主在解释的时候能尽量完整的说明产生这种结果的原因是什么。
再次感谢博主帮助分析底层逻辑结构,实在是获益良多。

@Heroor 没有呀,执行上下文包含了 this,你说改变了执行上下文也是可以的呀~

@inventer13 这里被我写漏了,感谢指出哈~

不明觉厉,看了博主其他文章,理清了很多困惑,感谢

commented
 var name=456;
    var p={
        name:123,
        say:{
           name:789,
           hello:function(){
               console.log(this.name)
           }
        }
    }
   p.say.hello();//789
   var b= p.say.hello;
   b();//456

我看到过你说过 上下文关系和声明位置有关和执行位置无关,可是上面这段代码又怎么解释呢?

@ps520 如果 console.log(name),name 的值跟声明的位置有关系,现在是 console.log(this.name),就相当于打印某个对象的属性一样,这时候就要看 this 是指向哪个对象了~

commented

@mqyqingfeng 大概明白了,谢谢回复。

strict reference 没有解释

var value = 1;

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

foo.bar() //2

var temp = foo.bar;
temp()//1

这种情况,感觉是=赋值引起的,就像上面说的(foo.bar = foo.bar)(),不知道这样理解对不对

@HuangQiii 如果是

foo.bar = foo.bar;
foo.bar();// 2

还是有区别的。

第一遍 ,一脸迷茫