【问题标题】:Javascript: Do I need to put this.var for every variable in an object?Javascript:我是否需要为对象中的每个变量添加 this.var?
【发布时间】:2012-11-16 14:34:00
【问题描述】:

在我最熟悉的语言 C++ 中,通常会这样声明一个对象:

class foo
{
public:
    int bar;
    int getBar() { return bar; }
}

调用getBar() 工作正常(忽略bar 可能未初始化的事实)。 getBar() 中的变量barfoo 类的范围内,所以我不需要说this->bar,除非我真的需要明确我指的是类'bar而不是一个参数。

现在,我正在尝试开始使用 Javascript 中的 OOP。所以,我查找了如何定义类并尝试了同样的事情:

function foo()
{
     this.bar = 0;
     this.getBar = function() { return bar; }
}

它给了我bar is undefined。将bar 更改为this.bar 可以解决此问题,但是对每个变量都这样做会使我的代码变得很混乱。这对每个变量都是必要的吗?由于我找不到任何与此相关的问题,这让我觉得我在做一些根本错误的事情。


编辑:对,所以,我从 cmets 得到的是 this.bar,一个对象的属性,引用了不同于 bar 的东西,一个局部变量。有人能说出这究竟是为什么,就范围和对象而言,以及是否有另一种方法来定义不需要这样做的对象?

【问题讨论】:

  • .bar 是对象属性,而不是变量。所以是的,要从对象中获取属性,您需要通过对该对象的一些引用来做到这一点。如果this 引用了您的对象,那么this.bar 将为您提供该属性。
  • this.barbar 不同。但是你可以做var bar = this.bar 这将使bar 引用this.bar
  • @Loktar 但前提是this.bar 是一个对象或数组...
  • @Loktar:为了让 OP 清楚,它会得到一个当前值的副本。
  • @user1689607 如果它是原始值,它将获得其当前值的副本,或者如果它是对象或数组,它将获得对象的“引用副本”。

标签: javascript scope this


【解决方案1】:

JavaScript 没有基于 classes 类的对象模型。它使用更强大的原型继承,可以模仿类,但不太适合它。一切都是对象,对象[可以]从其他对象继承。

构造函数只是一个将属性分配给新创建的对象的函数。该对象(通过调用new keyword 创建)可以通过this keyword(函数本地)引用。

方法也只是一个在对象上调用的函数——同样this指向该对象。至少当该函数作为对象的属性被调用时,使用member operator(点、括号)。这会给新手带来很多困惑,因为如果您传递该函数(例如传递给事件侦听器),它将与访问它的对象“分离”。

现在继承在哪里? “类”的实例继承自同一个原型对象。方法被定义为该对象的函数属性(而不是每个实例一个函数),您调用它们的实例只是继承该属性。

例子:

function Foo() {
    this.bar = "foo"; // creating a property on the instance
}
Foo.prototype.foo = 0; // of course you also can define other values to inherit
Foo.prototype.getBar = function() {
    // quite useless
    return this.bar;
}

var foo = new Foo; // creates an object which inherits from Foo.prototype,
                   // applies the Foo constructor on it and assigns it to the var
foo.getBar(); // "foo" - the inherited function is applied on the object and
              // returns its "bar" property
foo.bar; // "foo" - we could have done this easier.
foo[foo.bar]; // 0 - access the "foo" property, which is inherited
foo.foo = 1;  // and now overwrite it by creating an own property of foo
foo[foo.getBar()]; // 1 - gets the overwritten property value. Notice that
(new Foo).foo;     // is still 0

所以,我们确实只使用了该对象的属性并且对此感到满意。但它们都是“公共的”,可以覆盖/更改/删除!如果这不重要,你很幸运。您可以通过在属性名称前加上下划线来表示属性的“私有性”,但这只是对其他开发人员的提示,可能不会被遵守(尤其是错误时)。

因此,聪明的头脑找到了一种解决方案,它使用构造函数作为闭包,允许创建私有“属性”。 javascript函数的每次执行都会为局部变量创建一个新的变量环境,一旦执行完成,可能会被垃圾收集。在该范围内声明的每个函数也可以访问这些变量,并且只要可以调用这些函数(例如通过事件侦听器),环境就必须保持不变。因此,通过从构造函数中导出本地定义的函数,您可以使用只能由这些函数访问的局部变量来保留该变量环境。

让我们看看它的实际效果:

function Foo() {
    var bar = "foo"; // a local variable
    this.getBar = function getter() {
        return bar; // accesses the local variable
    }; // the assignment to a property makes it available to outside
}

var foo = new Foo; // an object with one method, inheriting from a [currently] empty prototype
foo.getBar(); // "foo" - receives us the value of the "bar" variable in the constructor

这个在构造函数中定义的 getter 函数现在被称为“特权方法”,因为它可以访问“私有”(本地)“属性”(变量)。 bar 的值永远不会改变。当然,你也可以为它声明一个 setter 函数,然后你可以添加一些验证等。

请注意,原型对象上的方法无法访问构造函数的局部变量,但它们可能会使用特权方法。让我们添加一个:

Foo.prototype.getFooBar = function() {
    return this.getBar() + "bar"; // access the "getBar" function on "this" instance
}
// the inheritance is dynamic, so we can use it on our existing foo object
foo.getFooBar(); // "foobar" - concatenated the "bar" value with a custom suffix

因此,您可以将这两种方法结合起来。请注意,特权方法需要更多内存,因为您创建具有不同作用域链(但代码相同)的不同函数对象。如果您要创建数量惊人的实例,则应仅在原型上定义方法。

当您设置从一个“类”到另一个“类”的继承时,它会变得更加复杂 - 基本上您必须使子原型对象继承自父对象,并将父构造函数应用于子实例以创建“私有属性”。看看Correct javascript inheritancePrivate variables in inherited prototypesDefine Private field Members and Inheritance in JAVASCRIPT module patternHow to implement inheritance in JS Revealing prototype pattern?

【讨论】:

  • 这对我理解这里到底发生了什么很有帮助。真的,最初的动机只是让我对必须将this 放在所有内容之前感到恼火,但真正理解为什么我必须这样做是好的。
  • ECMAScript 中没有“成员运算符”。正确的术语是“property accessor notation”。
  • @PointedEars:当然,这是官方术语。但是“会员运营商”(似乎是由 MDN 创造的)真的是错误的或非描述性的吗?当然是数学意义上的运算符,而不是二进制 ES 运算符(第 11.5-11.11 节)
  • 用词不当可能源于 David Flanagan 的“JavaScript:权威指南”(“权威”),他实际上将[...] 列为“数组运算符”。是的,那错的。考虑可能的“操作数”。运算符优先级是什么,尤其是[]?我刚刚编辑了那篇明显错误的 MDN 文章,并建议将其移出“Operators”命名空间。您应该更新您的链接并在一段时间后重新检查它。
  • array 运算符是“确定性”错误,是的 :-) Operator precedence 对我来说很有意义,给予它尽可能高的优先级(在分组运算符之后)。尽管如此,非常感谢改进 MDN :-) 请注意,operators pagelists 列出了 get/setletthis 甚至 function 作为运算符: -/
【解决方案2】:

明确地说this.foo 意味着(正如您已经理解的那样)您对this 引用的当前对象的属性foo 感兴趣。因此,如果您使用:this.foo = 'bar';,您将设置this 引用的当前对象的属性foo 等于bar

JavaScript 中的 this 关键字并不总是与 C++ 中的含义相同。这里我可以举个例子:

function Person(name) {
   this.name = name;
   console.log(this); //Developer {language: "js", name: "foo"} if called by Developer
}

function Developer(name, language) {
   this.language = language;
   Person.call(this, name);
}

var dev = new Developer('foo', 'js');

在上面的示例中,我们使用函数Developer 的上下文调用函数Person,因此this 引用了将由Developer 创建的对象。正如您可能从console.log 结果中看到的那样,this 来自Developer。通过call 方法的第一个参数,我们指定了调用函数的上下文。

如果您不使用this,您创建的属性将是一个局部变量。你可能知道 JavaScript 有函数作用域,所以这就是为什么变量是局部的,只对声明它的函数可见(当然所有它都是在父函数内部声明的子函数)。这是一个例子:

function foo() {
    var bar = 'foobar';
    this.getBar = function () {
        return bar;
    }
}

var f = new foo();
console.log(f.getBar());  //'foobar'

当您使用var 关键字时,这是正确的。这意味着如果您忘记了 var,那么您将 bar 定义为局部变量,不幸的是,bar 将变为全局变量。

function foo() {
    bar = 'foobar';
    this.getBar = function () {
        return bar;
    }
}

var f = new foo();
console.log(window.bar);  //'foobar'

正是本地范围可以帮助您实现隐私和封装,这是 OOP 的最大好处之一。

现实世界的例子:

function ShoppingCart() {
    var items = [];

    this.getPrice = function () {
       var total = 0;
       for (var i = 0; i < items.length; i += 1) {
          total += items[i].price;
       }
       return total;
    }

    this.addItem = function (item) {
        items.push(item);
    }

    this.checkOut = function () {
        var serializedItems = JSON.strigify(items);
        //send request to the server...
    }
}

var cart = new ShoppingCart();
cart.addItem({ price: 10, type: 'T-shirt' });
cart.addItem({ price: 20, type: 'Pants' });
console.log(cart.getPrice()); //30

JavaScript 作用域的另一个好处是Module Pattern。 在模块模式中,您可以使用 JavaScript 的本地功能范围来模拟隐私。使用这种方法,您可以同时拥有私有属性和方法。这是一个例子:

var module = (function {

    var privateProperty = 42;

    function privateMethod() {
        console.log('I\'m private');
    }
    return {

       publicMethod: function () {
           console.log('I\'m public!');
           console.log('I\'ll call a private method!');
           privateMethod();
       },

       publicProperty: 1.68,

       getPrivateProperty: function () {
           return privateProperty;
       },

       usePublicProperty: function () {
           console.log('I\'ll get a public property...' + this.publicProperty);
       }

    }
}());

module.privateMethod(); //TypeError
module.publicProperty(); //1.68
module.usePublicProperty(); //I'll get a public property...1.68
module.getPrivateProperty(); //42
module.publicMethod(); 
/*
 * I'm public!
 * I'll call a private method!
 * I'm private
 */

parentless 包装匿名函数的语法有点奇怪,但暂时忘记它(它只是在初始化后执行函数)。从使用示例中可以看出该功能,但好处主要是提供了一个简单的公共接口,它不会让您参与所有实现细节。有关该模式的更详细说明,您可以查看我在上面放置的链接。


我希望通过this :-) 信息可以帮助您了解 JavaScript 的一些基本主题。

【讨论】:

    【解决方案3】:
    function Foo() {
      this.bar = 0;
      this.getBar = function () { return this.bar };
    }
    

    当您使用 new 关键字调用上述函数时 - 像这样......

    var foo = new Foo();
    

    ... - 发生了一些事情:

    1) 创建一个对象
    2) 使用引用该对象的 this 关键字执行该函数。
    3) 该对象被返回。

    foo,那么,就变成了这个对象:

    {
        bar: 0,
        getBar: function () { return this.bar; }
    };
    

    那么,为什么不这样做呢:

    var foo = {
        bar: 0,
        getBar: function () { return this.bar; }
    };
    

    如果它只是一个简单的对象,你会的。

    但是使用构造函数创建一个对象(这就是它的名称)在创建多个“相同”对象方面为我们提供了很大的优势。

    请看,在 javascript 中,所有函数都是使用原型属性 [an object] 创建的,并且使用该函数创建的所有对象(通过使用 new 关键字调用它)都链接到该原型对象。这就是它如此酷的原因——你可以在原型对象中存储所有常用的方法(和属性,如果你想的话),并节省大量内存。它是这样工作的:

    function Foo( bar, bob ) {
       this.bar = bar;
       this.bob = bob;
    }
    
    Foo.prototype.calculate = function () {
      // 'this' points not to the 'prototype' object 
      // as you could've expect, but to the objects
      // created by calling Foo with the new keyword.
      // This is what makes it work.
      return this.bar - this.bob;  
    };
    
    var foo1 = new Foo(9, 5);
    var foo2 = new Foo(13, 3);
    var result1 = foo1.calculate();
    var result2 = foo2.calculate();
    
    console.log(result1); //logs 4
    console.log(result2); //logs 10
    

    就是这样!

    【讨论】:

      【解决方案4】:

      为了更接近 JavaScript 中的 OOP,您可能需要了解模块设计模式(例如,描述为 here)。

      基于闭包效果,此模式允许在您的对象中模拟私有属性。

      使用“私有”属性,您可以通过其标识符直接引用它们(即,在构造函数中没有 this 关键字)。

      但无论如何,JS 中的闭包和设计模式 - 一个高级主题。所以,要熟悉基础知识(在前面提到的书中也有解释)。

      【讨论】:

      • 回过头来翻阅这篇文章(它的长度确实可以成为一本书,但它似乎没有出版)非常非常有帮助。感谢您让我注意到这一点。
      • @Chaosed0 永远欢迎!没有多少关于 JS 设计模式的书籍/文章值得一读,但这一篇很好。
      【解决方案5】:

      在 javascript 中,this 总是指函数的所有者对象。例如,如果你在一个页面中定义你的函数foo(),那么owner就是javascript对象windows;或者如果你在 html 元素 &lt;body&gt; 上定义了 foo(),那么所有者就是 html 元素体;同样如果你定义了元素&lt;a&gt;的onclick函数,那么所有者就是锚。

      在您的情况下,您在开始时将属性 bar 分配给“所有者”对象并尝试返回局部变量 bar

      由于您从未定义任何局部变量bar,因此它给您,因为 bar 未定义。

      理想情况下,如果您想返回零值,您的代码应该将变量定义为var bar;

      【讨论】:

        【解决方案6】:

        this 类似于对象(变量或函数)的公共访问修饰符,而 var 是私有访问修饰符

        例子

        var x = {}; 
        x.hello = function(){
            var k = 'Hello World'; 
           this.m = 'Hello JavaScript'; 
        }
        
        var t = new x.hello(); 
        console.log(t.k); //undefined
        console.log(t.m); //Hello JavaScript
        

        【讨论】:

          猜你喜欢
          • 2012-11-05
          • 1970-01-01
          • 2017-08-09
          • 2014-06-20
          • 2021-11-21
          • 2017-05-13
          • 1970-01-01
          • 2011-09-01
          • 2020-02-14
          相关资源
          最近更新 更多