V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
fourstring
V2EX  ›  JavaScript

请教一个关于 this 的问题

  •  
  •   fourstring · 2019-01-30 18:34:13 +08:00 · 4887 次点击
    这是一个创建于 2158 天前的主题,其中的信息可能已经有所发展或是发生改变。

    代码如下:

    var name="Window";
    var object={
        name:"object",
        getName:function (){
            var a= function(){
                return this.name;
            };
        	console.log(a());
        	return a;
        }
    }
    object.getName()();
    

    运行结果:

    Window
    Window
    

    我的问题在于console.log(a());这一行。return a后,是在全局作用域中执行的返回的匿名函数,所以this的值是window。但是我在getName函数中直接执行a()的话,根据this的定义“ this 指向函数运行时的执行环境对象”,而每个函数执行时都会创建一个自己的执行环境,那么a()执行时this的值应该是getName函数的环境对象,这样 a 函数内对this.name的引用就应该得到undefined。请问我的理解有什么问题呢?谢谢:)

    40 条回复    2019-03-01 09:03:15 +08:00
    4ark
        1
    4ark  
       2019-01-30 18:50:23 +08:00 via Android   ❤️ 1
    https://www.v2ex.com/t/529532 看看吧,另外贴代码的时候能不能带有格式啊,看着真糟心!
    4ark
        2
    4ark  
       2019-01-30 18:54:27 +08:00 via Android   ❤️ 1
    就你这段代码而言,a 并不属于任何对象,也没有通过 call 等方法调用,所有这样的函数 this 默认指向 Window,如果是严格默认下,就是 undefinde。
    4ark
        3
    4ark  
       2019-01-30 18:56:13 +08:00 via Android
    收回我说的第一句话,才发现我的文章代码也没有格式,为什么 V2EX 会这样??
    Trim21
        4
    Trim21  
       2019-01-30 19:08:06 +08:00 via Android
    @4ark 其实我这边手机看着两个帖子里的代码都有格式…
    4ark
        5
    4ark  
       2019-01-30 19:09:32 +08:00 via Android
    @Trim21 因为我用的是 v2er 的客户端,才发现这个问题,现在已卸载。
    4ark
        6
    4ark  
       2019-01-30 19:12:19 +08:00 via Android
    @Trim21 想问一下您手机端用的啥客户端?
    Trim21
        7
    Trim21  
       2019-01-30 19:13:33 +08:00 via Android
    @4ark chrome
    4ark
        8
    4ark  
       2019-01-30 19:14:31 +08:00 via Android
    @Trim21 使用起来体验没有客户端好,虽然不会有显示问题
    Nitroethane
        9
    Nitroethane  
       2019-01-30 19:15:59 +08:00 via Android
    @4ark 最新的 beta 版已经实现了代码格式化功能:
    https://imgur.com/a/0uF0P3M
    4ark
        10
    4ark  
       2019-01-30 19:20:31 +08:00 via Android
    @Nitroethane 求下载链接,感激不尽!
    4ark
        11
    4ark  
       2019-01-30 19:28:01 +08:00 via Android
    @Nitroethane 已在 play 商店搜到
    rabbbit
        12
    rabbbit  
       2019-01-30 19:41:38 +08:00   ❤️ 3
    this 是什么取决于如何调用
    本文中 a 函数的调用者是 a() 而非外层的 object.getName()

    当 a 函数被作为变量调用时, 会将内部方法 GetBase(ref). ImplicitThisValue 返回的值作为 thisArg(通常是 undefined)
    http://ecma-international.org/ecma-262/5.1/#sec-11.2.3

    在进入函数时
    1 如果是严格模式,把 this 指向 thisArg
    2 如果 thisArg 为 undefined,则将 this 指向 window
    http://ecma-international.org/ecma-262/5.1/#sec-10.4.3

    总之一句话,作为变量调用的函数里边的 this 都指向 window
    OSF2E
        13
    OSF2E  
       2019-01-30 19:54:13 +08:00
    《 Javascript 高级程序设计(第三版)》( P73 ) 4.2 执行环境与作用域
    rabbbit
        14
    rabbbit  
       2019-01-30 19:55:32 +08:00   ❤️ 1
    另外补充下
    ImplicitThisValue 通常返回 undefined, with 除外
    fourstring
        15
    fourstring  
    OP
       2019-01-30 19:59:42 +08:00
    @OSF2E #13 谢谢,我看的就是这本书。我之所以会认为 a()的结果是"object",就是因为这本书 5.5.4 里说 this “引用的是函数执行的环境对象”,然后 4.2 节说“每个函数都有自己的执行环境”,我看了下面别的 v 友回复以后,觉得应该是我对“执行环境”理解有误,但是这本书里确实没有明确讲解函数的执行环境究竟是什么(也就是不是词法环境)。
    fleam
        16
    fleam  
       2019-01-30 20:01:49 +08:00 via Android
    我总结的就是相关性就近原则,菜鸟飞过。。。
    X37B
        17
    X37B  
       2019-01-30 20:38:06 +08:00   ❤️ 1
    this(4 个规则):函数位置上的调用形式
    默认绑定
    独立调用 fn()
    this -->window "use strict" this --> undefined

    隐式绑定
    对象.对象.fn()
    this -->最近的调用者
    隐式丢失?
    使用隐式调用的形式进行传参 赋值;使用其他形式进行调用 this 的指向发生改变
    解决隐式丢失?
    硬绑定 bind 返回一个新的函数

    显示绑定
    apply
    call
    立即调用
    this --> 绑定的对象

    构造绑定
    new fn()
    this --> 构造出来的实例对象

    当一行代码出现多个绑定,this 绑定优先级:new --> 显示 --> 隐式 --> 默认
    shintendo
        18
    shintendo  
       2019-01-30 20:42:26 +08:00   ❤️ 1
    其实很简单,你的 this 在哪个函数里面?在 a 里面,那么 this 的值只取决于 a 被谁调用,跟其它什么 getName 没有任何关系。
    而所谓的 a 被谁调用,指的是“调用者.a()”这种调用,不是说 a()写在 getName 里面就是被 getName 调用了这种意思。
    fourstring
        19
    fourstring  
    OP
       2019-01-30 20:58:04 +08:00
    @X37B #17 请问知道这样的知识什么地方或者书能看到呢
    secondwtq
        20
    secondwtq  
       2019-01-30 21:17:41 +08:00   ❤️ 1
    @fourstring 犀牛书应该是这样理解语言设计者的意图的:词法作用域和 this 都属于函数运行环境 /上下文,现在的实践中,一般词法作用域是写代码时固定的,this 允许用户在运行时动态指定上下文,并且这东西和函数调用语法关联,因此也是一种“环境”

    实际上现在的 OO 语言对于 this 的实现基本就是函数参数,也就是非要说的话所有参数都是“环境”的一部分 ... 不过 OO 的语义上 this 确实有环境的意思

    词法作用域环境也可以动态绑定,用 eval 或者 with 就行,只不过现在没人用了
    OSF2E
        21
    OSF2E  
       2019-01-30 21:19:07 +08:00
    @fourstring

    首先,这部分内容要结合第 7 章的内容来理解。

    另外,这一节有一个关键概念是执行环境的变量对象( variable object ),每一个函数的执行环境( execution context )中所定义的变量和函数都保存在这个对象中。而 web 浏览器中最外层的执行环境(的变量对象)被认为是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。

    因此,你的代码中的匿名函数 a 在定义之初便自动“挂靠”到全局执行环境中的(解析器就是这么解析的,没有为什么),即该匿名函数是 window 对象的方法,所以 this 自然指向 window。

    可以使用 bind 方法改变该匿名函数“挂靠”的变量对象,代码如下:

    var a = function(){}.bind(this);
    rabbbit
        22
    rabbbit  
       2019-01-30 22:02:26 +08:00
    @OSF2E

    能深入讲讲"匿名函数 a 在定义之初便自动“挂靠”到全局执行环境中的"这句吗?
    没看懂意思,有哪本书提到过吗?
    rabbbit
        23
    rabbbit  
       2019-01-30 22:09:09 +08:00   ❤️ 1
    如果指的是第 7 章 182 页 "匿名函数的执行环境具有全局性" 这句话的话

    这句话是翻译错误,原文是

    ```
    Anonymous functions are not bound to an object in this context, meaning the this object points to window unless executing in strict mode (where this is undefined).
    ```
    翻译:在这个上下文(执行环境)中匿名函数并没有绑定到任何一个对象中,意味着 this 指向 window (除非这个上下文(执行环境)是在严格模式下执行的,而严格模式下该 this 指向 undefined )
    https://www.zhihu.com/question/21958425/answer/278063919
    palmers
        24
    palmers  
       2019-01-30 22:35:42 +08:00
    this 是动态的指向当前对象 并不会主动的缓存 所以 是 window 在 getName 函数中将 this 缓存起来就会得到你预料的结果了
    autoxbc
        25
    autoxbc  
       2019-01-30 23:12:17 +08:00   ❤️ 1
    个人觉得我的理解最接近本质

    记住唯一一条规则:this 是隐参数,在成员访问过程中,如果属性是函数,且不是箭头函数,宿主作为隐参数传递给属性

    这里的关键是成员访问,这是个在宿主对象上查找属性的过程,语句反应了这个过程,隐参数就会传递

    ---------------------
    console.log( a() )
    这里 a 是函数,但是查找 a 的过程不是成员访问,所以没有隐参数

    ---------------------
    既然 this 是隐参数,bind call apply 就是隐参数显式传递,这个是不是非常自然
    OSF2E
        26
    OSF2E  
       2019-01-31 00:47:57 +08:00
    @rabbbit @fourstring

    这句是纯个人理解,没有原文可以引用,我自己也觉得表达的不够准确。
    mamahaha
        27
    mamahaha  
       2019-01-31 01:08:20 +08:00   ❤️ 1
    这种问题你今天想明白了也许第二天又糊涂了,所以没必要浪费时间钻研,把它看成黑箱就得了
    yxcoder
        28
    yxcoder  
       2019-01-31 09:13:22 +08:00
    举个栗子:

    let obj = {
    getThis(){
    console.log(this);
    let func = function(){
    console.log(this);
    }
    func();
    }
    }
    obj.getThis();

    两个 console 分别会输出什么?
    yxcoder
        29
    yxcoder  
       2019-01-31 09:14:33 +08:00
    @yxcoder 这代码格式。。。哈哈
    X37B
        30
    X37B  
       2019-01-31 09:38:44 +08:00   ❤️ 1
    @fourstring https://github.com/getify/You-Dont-Know-JS
    <你不知道的 js>,国内有译本,分上中下,不过中册翻译的不好
    libook
        31
    libook  
       2019-01-31 10:52:34 +08:00   ❤️ 1
    “每个函数执行时都会创建一个自己的执行环境”的前提是使用 new 指令来执行函数。

    function a(){this.n=1;console.log(this.n);}

    你直接执行 a()的时候,此时没有一个新的对象创建,this 默认指向全局作用域,就像你直接 var x=1 然后发现 x 变成了 global ( window )的属性,是一样的。
    你执行 new a()的时候,依照 new 指令的原理,会创建一个新对象(类似于调用 Object.create(a.prototype)),然后再让 this 指向这个创建出来的对象,此时这个对象就是你所说的“自己的执行环境”。

    为了方便理解,我上面说得比较浅显,具体你可以去网上搜一下 new 指令的功能,这个是原型和原型链的思想。
    no1xsyzy
        32
    no1xsyzy  
       2019-01-31 11:43:34 +08:00
    this 是栈帧上的动态作用域!
    ——
    @libook 你可能没明白执行环境是什么意思?另一个名字叫帧( Frame )。
    或者是 (eval exp env) 的那个 env。
    其实说了这么多,其实就是说
    func(...params) := (apply func params (new-env))
    obj.func(...params) := (apply func params (assoc (('this obj)) (new-env)))
    func.call(obj) := (apply func '() (assoc (('this obj)) (new-env)))
    ian511
        33
    ian511  
       2019-01-31 12:02:19 +08:00
    哈哈看到标题就知道在问 js
    wly19960911
        34
    wly19960911  
       2019-01-31 13:45:28 +08:00
    /t/519845

    自己看吧
    libook
        35
    libook  
       2019-01-31 16:37:29 +08:00
    @no1xsyzy

    其实我是没听说过“执行环境”这个术语,也不知道应该对应哪个英文名称,所以为了帮助楼主解决问题,只能根据楼主的描述进行推测:

    楼主的“那么 a()执行时 this 的值应该是 getName 函数的环境对象”,显然楼主是期望 a 内部的 this 指向的是“ getName 函数的环境对象”即 object 对象,符合楼主预期的结果,输出应该是'object'字符串才对。
    所以我的判断是楼主说的“执行环境”就是指的是 this 指向的对象,@fourstring 楼主可以自己解释一下这个“执行环境”究竟是想说的什么。

    然后 Stack frame 应该指的是 JS 的 Call Stack 里的原理,我承认这方面我确实不懂,不过一方面我觉得楼主也未必能理解,另一方面楼主的问题在 ES 语法规范上应该就能解决,不至于挖掘到 JS 解释器的实现方式。
    no1xsyzy
        36
    no1xsyzy  
       2019-02-01 09:52:30 +08:00
    @libook 实际上这块的名称英文似乎也很混乱,因为似乎每个语言都会新加一个名称,甚至本身用多个名称…… 也可能是侧重方面的区别。
    大概 JS 里叫 Scope ?
    libook
        37
    libook  
       2019-02-01 10:46:18 +08:00
    @no1xsyzy 参考 MDN 英文的说法,https://developer.mozilla.org/en-US/docs/Glossary/Scope Scope 可能对应的是我们平时说的“作用域”。楼主的问题是 this 的问题,那么 this 是遵循原型链原理的,而原型链和作用域链貌似是 JS 里的两套独立体系。
    no1xsyzy
        38
    no1xsyzy  
       2019-02-01 11:27:13 +08:00
    @libook 一个变量就算是关键字也是有作用域的,而 this 是动态作用域(由调用方决定绑定到什么)而不是词法作用域(闭包)。你知道箭头函数不影响 this 吗?这实际上就是作用域的区别。
    所以这就是 JavaScript 中 this 为什么是个大问题,包括 #33 说的一看就知道是在说 JS。
    就是因为 JS 大量使用词法作用域以及广泛使用闭包——但却唯有 this 采用动态作用域。更糟糕的是采用的是隐式动态作用域。
    Python 的 self (约定上)采用显式动态作用域(作为参数传递)。
    C++ 根本没有词法作用域,似乎 lambda 也是手动传入环境的。
    billyangg
        39
    billyangg  
       2019-03-01 08:57:46 +08:00
    在 node 里为啥是 undefined ?
    billyangg
        40
    billyangg  
       2019-03-01 09:03:15 +08:00
    我觉得这个写法跟下面这种差不多:给 a 赋值 this 的指向就很明显了:

    ```js
    var name="Window";
    var object={
    name:"object",
    getName:function (){
    var a= function(){
    return this.name;
    };
    console.log(a());
    return a;
    }
    }
    var a = object.getName();
    a()
    ```

    ![TIM 截图 20190301090238.png]( https://i.loli.net/2019/03/01/5c7884cacf4c3.png)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2163 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 00:55 · PVG 08:55 · LAX 16:55 · JFK 19:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.