【问题标题】:Object method using between lexical scope or this-binding?在词法范围或 this 绑定之间使用对象方法?
【发布时间】:2019-01-08 08:25:26
【问题描述】:

我无法确定是什么概念解释了为什么对象的属性“count”的值保留在下面的代码中。

我已阅读并查看了 Getify 的 You Don't Know JS 中的 this 和对象原型部分 以及他们解释lexical this的部分。 但是,我无法理解下面的代码。 是词法作用域吗? 还是 this 绑定允许保留 count 的值?

下面是示例代码:

var obj = {
    count: 0,
    method: function() {
        console.log("in method: " + this.count)
        return this.count++;
    },
}

// here is where I have issue, when the method is invoked as a function
for (var i = 0; i<10; i++) {
    console.log(obj.method()) // invoked as a function
}

// I've left this small block in for convenience
// I have no trouble with understanding why this block outputs what it outputs
for (var i = 0; i<10; i++) {
    console.log(obj.method) // "gets its value (a reference to a function) and then logs that" from TJ Crowder
}

我希望对 obj.method() 的第一个方法调用的输出能够输出

// 0
// in method 0
// 1
// in method 1
// 2
.
.
.
// 10
// in method 10

我对输出的内容没有任何问题。我的问题又是,是词法作用域吗? 还是 this 绑定允许保留 count 的值?

感谢您抽出宝贵时间提供帮助。

编辑 1 在下面 Tj Crowder 的帖子的帮助下,我编辑了代码 sn-p 以清除错误,因为它偏离了我的问题。

【问题讨论】:

  • 也许有助于区分不同的日志调用?然后你可以看到哪个打印哪个输出
  • 你没有从method()函数返回任何东西,所以它打印undefined。想做return this.count;
  • 我已经编辑了 sn-p 来表达我的问题的初衷。我最初提供的代码在根本问题和您的回复方面具有误导性,我深表歉意并感谢您的回复。

标签: javascript this lexical-scope


【解决方案1】:

这个问题与this范围无关。 :-) 您会看到undefineds,因为method 不返回任何内容,因此调用它会产生值undefined,您通过console.log 记录该值。要使其返回count 的值,您需要添加return

method: function() {
    console.log(this.count)
    return this.count++;
//  ^^^^^^
},

这将返回 this.count 的值,因为它在增量之前(这似乎是您对预期输出的期望)。

现场示例:

var obj = {
    count: 0,
    method: function() {
        console.log("in method: " + this.count)
        return this.count++;
    },
    timeOutMethod: function() { // I understand here we explicitly bind this, no problem here
        setTimeout(function() {
            console.log(this.count++)
        }.bind(this), 100)
    }
}

// here is where I have issue, when the method is invoked as a function
for (var i = 0; i<10; i++) {
    console.log(obj.method()) // invoked as a function
}
.as-console-wrapper {
  max-height: 100% !important;
}

另外,关于这个:

for (var i = 0; i<10; i++) {
    console.log(obj.method) // invoked as a property
}

根本不调用 method,它只是获取它的值(对函数的引用)然后记录它(你会在控制台中看到函数的一些表示)。

【讨论】:

  • 额头一巴掌!不提供返回值当然可以解释为什么我变得不确定。谢谢!另外,您已经解决了我一段时间以来的困惑 - 我对访问属性并将其记录到控制台时会发生什么以及如何准确解释它感到困惑。
【解决方案2】:

它是绑定

范围 是关于哪些变量是可访问的以及语言对您隐藏哪些变量的概念。在机器语言中,所有内存地址都是可读写的,因此在机器语言和一些汇编语言中,范围的概念并不存在(所有变量基本上都是全局的)。后来的语言通过引入函数引入了全局变量与局部变量的概念。这个概念进一步演变成闭包——创建多个作用域实例的能力。

绑定是哪个属性/属性属于哪个对象的概念。在 Java 和 C++ 等实现早期绑定的语言中,绑定的概念仅控制方法访问属性的方式(通常这允许语言不需要“this”关键字)。后期绑定语言的规则稍微复杂一些,因为绑定是在运行时而不是编译时确定的。 Javascript 不仅是后期绑定,而且是动态的——允许程序员使用 Function.prototype.call() 之类的东西来改变 this 指向的对象。并在运行时将一个对象的方法分配给另一个对象(例如b.foo = a.foo

【讨论】:

  • 感谢您在编程语言中使用范围和绑定的历史背景。我重新阅读了关于“this”和词法作用域的资源,希望正确理解了它的运作方式。我发布了一个解释,我认为可能是正确的假设是允许访问 this.count 属性的机制。您是否愿意查看一下,看看您同意或不同意什么以及如何改进我的解释?
【解决方案3】:

TL;DR 将属性count 绑定到this 值的机制是隐式绑定 (Getify's You Don't Know JS), Implicit Binding

首先如果我在解释中得出了错误的结论,您是否愿意提供您同意或不同意的内容,并分享我应该如何考虑这个问题?我正在寻求改进,谢谢!

解释:obj.method() 被调用时,我们有一个this.count 的属性查找。如果我们在调用obj.method() 时检查调用站点,obj 是变量存在于全局范围内。 obj 的属性通过原型链访问。当我们执行this.count 的属性查找时,我们尝试访问obj 中的属性。当没有找到该属性时,我们查找该属性的原型链。在我们通过“this”绑定访问count 属性时,obj.count 包含/拥有属性count

我的问题的原因:我混合了 this-binding 如何在箭头-fcns 中运行的概念、词法范围以及对“this”的解释过于字面化。

示例

以下是让我感到困惑的代码示例。 我的问题源于混淆了 this-binding 如何在不同样式的代码中运行的约定。我混淆了以下概念:

  1. this 关键字如何在箭头函数中运行(即词法范围)

    function wait() {
        setTimeout(() => {
            console.log(this) // lexically bound
        }, 100);
    }
    
    wait();
    
    function foo() {
        // console.log(this) - will output {a: 2}
        return (a) => {
            console.log(this.a) // 'this' value is adopted from foo's lexical scope
        }
    }
    
    var obj = {
        a: 2
    }
    
    var obj2 = {
        a: 3
    }
    
    // foo is 'this'-bound to obj1
    // bar, a reference to the returned arrow-function will also be 'this'-bound to obj1
    // the arrow function binding cannot be overridden
    var bar = foo.call(obj) 
    bar.call(obj2)
    
  2. 不是了解this 的真正工作原理,而是使用词法范围作为一种解决方法。在这里,data.count 是递增的

    function foo() {
        return data.count++; // the obj property data.count is accessed via global scope
    }
    
    var data = {
        count: 0,
    }
    
    for (var i = 0; i<10; i++) {
        foo()
    }
    
    console.log(data.count)
    
  3. 最后,另一个误解可能来自对this-binding 的过于直白的解释。我引用的 sn-p 来自 Getify 的书

    function foo(num) {
        console.log( "foo: " + num );
    
        // keep track of how many times `foo` is called
        this.count++;
    }
    
    foo.count = 0;
    
    var i;
    
    for (i=0; i<10; i++) {
        if (i > 5) {
            foo( i );
        }
    }
    // foo: 6
    // foo: 7
    // foo: 8
    // foo: 9
    
    // how many times was `foo` called?
    console.log( foo.count ); // 0 -- not what we expected
    

旁白:感谢@TJ Crowder 和@slebetman 帮助澄清关于范围和this-binding 的其他误解

【讨论】:

  • 你真的需要研究对象是如何在 javascript 中工作的。 foo() 是一个函数,虽然是的,但函数也是对象,在 foo 中包含 this.count 不会将属性 count 添加到函数 foo。相反,它尝试从附加的对象foo 访问属性count。例如,如果您调用bar.foo(),它将访问bar.count。如果您调用foo(),它将是全局对象的属性count,如果在浏览器中执行,它将是window.count,而不是foo.count(node.js 更复杂)
  • 请记住,绑定和作用域完全不相关。范围是关于变量隐藏(局部 vs 全局 vs 闭包)——它是关于函数的行为方式。绑定是关于对象(而不是函数)的结构
  • 另外,你真的应该更多地研究闭包(你似乎认为你需要解决的问题并称之为“词法范围”)。如果您了解全局变量和局部变量之间的区别,则更容易理解闭包。
  • 另一条评论。当你想解释绑定时,不要再考虑上下文(因为你在这里使用它)。上下文仅涉及范围。您需要考虑继承链。看,就像我说的,它们之间根本没有关系。 (是的,在 ECMAScript 标准中,他们使用具有两种不同含义的“上下文”一词,但对象的“上下文”与“全局”一词无关)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-07-06
  • 2021-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-03
  • 1970-01-01
相关资源
最近更新 更多