js 的 this 指向问题(并非基础)

2018-12-21 19:59:09 +08:00
 wly19960911

今天我在主题里面看到有人指出,经过逻辑表达式之后运行,js 的 this 指向发生了变化。我不是很理解这种行为,js 引擎是把逻辑表达式执行赋值给临时变量?

a.b = function(){console.log(this)}
> ƒ (){console.log(this)}

(a.b)()
> {b: ƒ}

(true && a.b)()
> Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

(true ? a.b : null) ()
> Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

有人能回答嘛?

2261 次点击
所在节点    问与答
12 条回复
jecshcier
2018-12-21 20:25:11 +08:00
a.b 在执行的时候具有作用域,而在表达式中,实际上执行的事 a.b 和 true 的与的返回值,这个返回值是 function 本身,在执行这个 function 的时候,没有作用域,自然就指向了 window 本身
wly19960911
2018-12-21 20:40:38 +08:00
@jecshcier 这个应该不叫作用域而叫 context 吧,对于这种情况那我只能理解为逻辑表达式执行后内部类似赋值给临时变量丢失了 context ?
jecshcier
2018-12-21 20:49:48 +08:00
@wly19960911 抱歉我本来写的是上下文,纠结了一下改成了作用域反而改错了😅面壁。其实就是前者在执行的时候有上下文,后者执行的时候没有上下文。不妨写成这样,let test=a.b,test()返回的也是 window,这个其实就是基础啦😅
shyangs
2018-12-21 20:50:38 +08:00
(true && (function(){return a.b;})() )();
wly19960911
2018-12-21 20:54:18 +08:00
@jecshcier #3 主要是第一个表达式括号具有一定的误导性,到这么理解应该算是对的,说是基础但是没几个语言的逻辑表达式返回的是非 boolean 类型。
ayase252
2018-12-21 21:08:22 +08:00
有趣,在地铁上查了半天。
https://github.com/mqyqingfeng/Blog/issues/7
rabbbit
2018-12-21 21:11:48 +08:00
函数调用的表达式是 CallExpression : MemberExpression Arguments

Arguments 就是函数后面的那个括号, MemberExpression 就是括号前面的东西

例如 (foo.bar)(1) MemberExpression 是 (foo.bar), Arguments 是 (1)

函数调用时,执行如下过程(以下记为 过程 a)
```
1 令 ref 为执行 MemberExpression 的结果
2 令 func 为调用 GetValue(ref)的结果
3 令 argList 为执行 Arguments 的结果
4 如果 func 不是 Object,抛出 TypeError
5 如果 func 没有内部属性[[call]],抛出 TypeError
6 如果 ref 是 Reference
a 如果 ref 的基值是 Boolean String Number Object //作为属性调用 // Es6 新增 Symbol
i 令 thisValue 为调用 GetBase(ref)的结果
否则, ref 的基值是环境记录 //作为变量调用
i 令 thisValue 为调用 GetBase(ref). ImplicitThisValue 的结果 // ImplicitThisValue 通常返回 undefined,除非 provideThis 值为 true(with 语句)
否则,ref 不是 Reference
令 thisValue 为 undefined // 进入函数时, thisVaule 为 undefined 会让 this 指向 window 全局对象
8 return func.[[call]](thisValue, argList)
```
函数本质上就是个 Object,有个内部方法[[call]],调用函数,本质上就是调用函数的内部方法[[call]]
```
F.[[call]](thisValue ,argList)
```
这个 thisValue 就是 this 值[在进入执行环境时,如果 thisValue 为 undefind, 则令 this 为 Global Object(浏览器里就是 window)]

所以过程 a 的执行过程就是先运行 MemberExpression,然后判断返回值.如果不是 Reference 类型, 令 thisValue 为 undefined

---
另外解释下什么是 Reference

js5 的类型有 Number Object Function null undefined String Boolean Reference
这个 Reference 是个虚假的概念,实际上不存在.作用是用来描述 delete 等操作,Reference 由 3 部分组成 base value referenced name strict reference flag
例如 foo.bar 返回的是个 Reference 结构如下
```
{
baseValue: bar,
referenced name: 'foo'
strict: false
}

```
再举个例子 function() {var foo = 1; var bar = 2}

这里的 foo 也返回一个 Reference
```
{
baseValue: {foo:1, bar: 2} <-- 这个可以理解为所谓的作用域
referenced name: 'foo'
strict: false
}

```
---

有没有 this 取决于 MemberExpression,那么,(foo.bar)返回的是什么?

()是群组运算符, 表达式为 PrimaryExpression : ( Expression )
运行过程是
```
返回执行 Expression 的结果。这可能是一个 Reference。
```
https://www.w3.org/html/ig/zh/wiki/ES5/%E8%A1%A8%E8%BE%BE%E5%BC%8F#.E7.BE.A4.E7.BB.84.E8.BF.90.E7.AE.97.E7.AC.A6

也就是返回什么取决于括号里边的 Expression (foo.bar)就是 foo.bar , (false || foo.bar)就是 false|| foo.bar

||操作的定义在
https://www.w3.org/html/ig/zh/wiki/ES5/%E8%A1%A8%E8%BE%BE%E5%BC%8F#.E4.BA.8C.E5.85.83.E9.80.BB.E8.BE.91.E8.BF.90.E7.AE.97.E7.AC.A6

注意第 5 步返回的是 GetValue(rref),不是 Reference.
rabbbit
2018-12-21 21:30:07 +08:00
GetValue(v)这个内部函数的作用就是获取真实的那个值

文档这 https://www.w3.org/html/ig/zh/wiki/ES5/%E7%B1%BB%E5%9E%8B#GetValue
总结来说就是
如果不是 Reference,就直接返回 v.
如果 v 是 Reference,则 v 一定表示的是个对象 /环境记录项(<--可以理解成所谓的作用域)

如果 v 是表示对象的 Reference, 则调用内部方法 get
如果是环境记录项,则调用内部方法 GetBindingValue(标识符名, 是否是严格模式)

GetBindingValue(N,S)的文档在 https://www.w3.org/html/ig/zh/wiki/ES5/%E5%8F%AF%E6%89%A7%E8%A1%8C%E4%BB%A3%E7%A0%81%E4%B8%8E%E6%89%A7%E8%A1%8C%E7%8E%AF%E5%A2%83#GetBindingValue

总结来说就是
如果该变量未初始化,且不是严格模式,返回 undefined.否者报错
如果已初始化,则返回绑定的值(这步操作没说,我猜用的是 GetIdentifierReference)


往后看 GetIdentifierReference
https://www.w3.org/html/ig/zh/wiki/ES5/%E5%8F%AF%E6%89%A7%E8%A1%8C%E4%BB%A3%E7%A0%81%E4%B8%8E%E6%89%A7%E8%A1%8C%E7%8E%AF%E5%A2%83#GetIdentifierReference.28lex.2C_name.2C_strict.29

总结来说就是
在当前作用域找,没有就去递归去上一层找,直到全局对象 window,如果还没有,就返回 undefined
wly19960911
2018-12-21 21:30:36 +08:00
@rabbbit #7 我今天早上原来看的就是你写的啊,我晚上想找到那个讨论,搜不到你发的,结果就发了一贴,刚刚才翻到了你在那个主题下的回复,感谢长文回答。
rabbbit
2018-12-21 21:36:50 +08:00
更正
baseValue: bar,
referenced name: 'foo'
-->

baseValue: foo,
referenced name: 'bar'
heimeil
2018-12-21 21:42:24 +08:00
(a.b)() => [function].apply(a, arguments)

(true && a.b)() => (true && [function])() => [function].apply(undefined, arguments)

function 在定义的时候 this 并没有确定,后续执行可以通过 apply 和 call 动态指定,这里的 a.b 在表达式中需要取出了实际的 function 内联进去,而不是引用 a.b,经过表达式出来的已经和 a.b 没有关系了,以个人知识对实现的猜测。
codermagefox
2018-12-22 00:14:07 +08:00
@rabbbit 这个回答很🐂🍺了,感谢。之前看标准都没说的这么详细

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/519845

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX