V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
wly19960911
V2EX  ›  问与答

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

  •  
  •   wly19960911 · 2018-12-21 19:59:09 +08:00 · 2329 次点击
    这是一个创建于 2172 天前的主题,其中的信息可能已经有所发展或是发生改变。

    今天我在主题里面看到有人指出,经过逻辑表达式之后运行,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, …}
    

    有人能回答嘛?

    第 1 条附言  ·  2018-12-21 21:18:40 +08:00
    感谢 6、7L 的回复。

    原来使用的时候根本没有注意过 Reference 的概念,学习了,鉴于 v2 回复没有 markdown 格式,7L 格式看起来不太好,我就 append 6L 给出的链接吧。

    https://github.com/mqyqingfeng/Blog/issues/7
    12 条回复    2018-12-22 00:14:07 +08:00
    jecshcier
        1
    jecshcier  
       2018-12-21 20:25:11 +08:00 via iPhone
    a.b 在执行的时候具有作用域,而在表达式中,实际上执行的事 a.b 和 true 的与的返回值,这个返回值是 function 本身,在执行这个 function 的时候,没有作用域,自然就指向了 window 本身
    wly19960911
        2
    wly19960911  
    OP
       2018-12-21 20:40:38 +08:00 via Android
    @jecshcier 这个应该不叫作用域而叫 context 吧,对于这种情况那我只能理解为逻辑表达式执行后内部类似赋值给临时变量丢失了 context ?
    jecshcier
        3
    jecshcier  
       2018-12-21 20:49:48 +08:00 via iPhone
    @wly19960911 抱歉我本来写的是上下文,纠结了一下改成了作用域反而改错了😅面壁。其实就是前者在执行的时候有上下文,后者执行的时候没有上下文。不妨写成这样,let test=a.b,test()返回的也是 window,这个其实就是基础啦😅
    shyangs
        4
    shyangs  
       2018-12-21 20:50:38 +08:00
    (true && (function(){return a.b;})() )();
    wly19960911
        5
    wly19960911  
    OP
       2018-12-21 20:54:18 +08:00
    @jecshcier #3 主要是第一个表达式括号具有一定的误导性,到这么理解应该算是对的,说是基础但是没几个语言的逻辑表达式返回的是非 boolean 类型。
    ayase252
        6
    ayase252  
       2018-12-21 21:08:22 +08:00 via iPhone   ❤️ 1
    有趣,在地铁上查了半天。
    https://github.com/mqyqingfeng/Blog/issues/7
    rabbbit
        7
    rabbbit  
       2018-12-21 21:11:48 +08:00   ❤️ 1
    函数调用的表达式是 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
        8
    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
        9
    wly19960911  
    OP
       2018-12-21 21:30:36 +08:00
    @rabbbit #7 我今天早上原来看的就是你写的啊,我晚上想找到那个讨论,搜不到你发的,结果就发了一贴,刚刚才翻到了你在那个主题下的回复,感谢长文回答。
    rabbbit
        10
    rabbbit  
       2018-12-21 21:36:50 +08:00
    更正
    baseValue: bar,
    referenced name: 'foo'
    -->

    baseValue: foo,
    referenced name: 'bar'
    heimeil
        11
    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
        12
    codermagefox  
       2018-12-22 00:14:07 +08:00 via iPhone
    @rabbbit 这个回答很🐂🍺了,感谢。之前看标准都没说的这么详细
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1177 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 23:30 · PVG 07:30 · LAX 15:30 · JFK 18:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.