【问题标题】:Object.defineProperty with constructor function and prototype带有构造函数和原型的 Object.defineProperty
【发布时间】:2016-09-05 03:27:33
【问题描述】:

我刚刚发现了 Object.defineProperty,由于我最熟悉 C#,我想在我的构造函数中使用访问器属性,例如:

function Base(id) {
   var _id = id;

   Object.defineProperty(this,"ID",{
       get: function() { return _id; },
       set: function(value) { _id = value; }
   })
}

function Derived(id, name) {
   var _name = name;

   Base.call(this,id);

   Object.defineProperty(this,"Name",{
      get: function() { return _name; },
      set: function(value) { _name = value; }
   })
}

Derived.prototype = Object.create(Base.prototype);
Derived.constructor = Derived;

var b = new Base(2);
var d = new Derived(4,"Alexander");

console.log(b.ID);
console.log(d.ID, d.Name);
d.ID = 100;
console.log(d.ID, d.Name);

打印出来:

2
4 “亚历山大”
100 “亚历山大”

但我对此感到非常困惑,例如this answer with a very high score 鼓励使用上述方法,而this answer 表示它将耗尽所有内存,因为将为我实例化的每个对象重新创建函数。它建议采用以下方法:

var Base = function(id){this.__id = id}
Player.prototype = {
   get ID(){
      return this.__id;
   },
   set ID(value){
      this.__id = value;
   }
}

var p = new Player();
p.ID = 2;
alert(p.ID); // 4

但是,这种方法也创建了另一个公共属性 __id,这对我来说似乎不太理想(我的示例中的属性是“特权”,因为它显然在 javascript 中调用,因此不需要额外的公共属性)。

谁能解释一下哪种方法适合我?现在我在 javascript 文档丛林中完全迷失了。我非常喜欢Object.defineProperty 方法,因为我觉得代码很干净,我可以将它与继承一起使用。但是,如果确实为每个对象重新创建函数,我可能需要考虑第二种方法?

【问题讨论】:

  • 欢迎使用 JavaScript!虽然我没有什么可以支持它(因此是评论,而不是答案),但现代 JS 引擎非常好 - 虽然它似乎您在第一个示例中创建了多个函数,因为规范 说就是这样,我一点也不惊讶引擎内部足够聪明,可以重用内存中的“相同”功能。引擎只需要让它看起来符合规范 - 在内部,他们可以做任何他们想做的事情来完成工作。
  • 我第二个詹姆斯索普。除此之外,AFAIK prototypes 内存重用在 90 年代更为相关,当时内存仍然非常稀疏。这些天来,除非您创建成百上千个实例,否则不会有太多收获。 (不过,我也没有来源支持这一点)。
  • 如果你可以公开暴露你的对象的私有部分,并且可以阻止自己在 3 个月后直接访问它们以修复那个烦人的错误,那也很好 :)跨度>
  • 您在支持 ES 类的环境中吗?这会改善你的情况吗?
  • 您必须选择:与公共数据共享的方法,或与私有数据的复制方法。请参阅javascript - accessing private member variables from prototype-defined functions,没有完美的方法可以做到这一点。

标签: javascript properties


【解决方案1】:

我参加聚会可能有点晚了,但我想在此指出几件小事。

首先,您在构造函数中定义自己的属性,这对于实例特定属性和不应与其他实例共享的属性很好,但不适用于 getter、setter 和方法。相反,您应该在函数的原型上定义它们。

我理解你这样的问题

你如何制作真正的私人财产?

这很简单,并且适用于所有浏览器。首先我们将详细介绍 Object.defineProperty,以及派生的 Object.create、Object.defineProperties。

javascript object定义的prototype

现代 javascript 允许使用一些“语法糖”来声明类,但每个自豪的开发人员都应该了解它的真正工作原理。

首先没有类。 Javascript没有类。它有原型,它们不是类似类,所以在我天真的固执中,我上了几门课来寻找真相,我反对又反对——我一直到了谷底。没有课程。

you 成为对象。如果youyour prototype 无法回答问题,则系统会询问您的prototypes´ prototype,然后会询问您的prototypes´ prototypes´ prototype,然后继续进行,直到没有更多原型可以询问为止。

所有这些原型仍然是对象。该算法说明了这一点:

// Friendly version
function askQuestion(askee, question) {
  do {
    if (askee.hasOwnProperty(question)) {
      return askee[question];
    }
  } while (askee = Object.getPrototypeOf(askee))
}

ECMAScript 6(无聊)

为了说明“现代 javascript”的语法,我将离题:

class Tortoise extends Turtle {

  constructor() {
    while (this.__proto__) {
      this.__proto__ = Object.getPrototypeOf(this.__proto__);
    }
  }

  #privateProperty = "This isn't a comment"

  get __proto__() { return Object.getPrototypeOf(this); }

  set __proto__(to) { Object.setPrototypeOf(this, to); }

}

这不适用于所有浏览器。它也不适用于任何浏览器。只是为了展示语法。

有人声称上面的内容只是老式 javascript 之上的语法糖,但是当涉及到扩展原生对象时,它怀疑它所包含的内容比表面上看到的要多。

即使“现代 javascript”(ECMAScript 6)允许我们像上面那样编写类,你也应该理解它

此外,现代 javascript 与 Internet Explorer 11 的顽固性相结合,迫使我们使用 babel,这是一个非常棒的工具,具有令人难以置信的强大功能,令人难以置信的先进和灵活,这种工具甚至存在的可能性,仅仅是巧合,是无限不可能的。

工具的存在就是上帝存在的证明,我很遗憾地说,也证明了上帝不存在。


WTF??请参阅 heretherethere

打开引擎盖

不要在构造函数中创建 REUSABLE 属性。一个被许多实例使用的函数不应该在构造函数中赋值。

真实版

function Base(id) {
  // GOOD
  this.id = id;

  // GOOD, because we need to create a NEW array for each
  this.tags = [];

  // Okay, but could also just be in the prototype
  this.numberOfInteractions = 0;

  // BAD
  this.didInteract = function() {
    this.numberOfInteractions++;
  }
}

糖衣

class Base {
  constructor(id) {
    // GOOD
    this.id = id;

    // GOOD, because we need to create a NEW array for each
    this.tags = [];

    // Okay, but could also just be in the prototype
    this.numberOfInteractions = 0;

    // BAD
    this.didInteract = function() {
      this.numberOfInteractions++;
    }
  }
}

改进的真实版本

function Base(id) {
  this.id = id;
  this.tags = [];
}
Base.prototype.numberOfInteractions = 0;
Base.prototype.didInteract = function() {
  this.numberOfInteractions++;
}

改进的糖衣版本

如果你坚持吃糖,写完代码后喜欢写更多的代码行和一些额外的劳动,你可以安装 babel 并像下面这样编写代码。

它会使脚本文件稍大且速度较慢 - 除非您真的不需要支持所有浏览器。

class Base {

  constructor(id) {
    this.id = id;
    this.tags = [];
  }

  numberOfInteractions = 0;

  didInteract() {
    this.numberOfInteractions++;
  }

}

继承ABC

EcmaScript 中的继承非常简单,只要你真正了解它!

TLDR:如果你想让上面的类扩展另一个类,你可以这样做:

Object.setPrototypeOf(Base.prototype, Parent.prototype);

这应该适用于任何地方。它本质上是Base.prototype.__proto__ = Parent.prototype

Base.prototype实例原型

javascript 中的所有对象都有一个实例原型实例原型用于搜索对象属性的“默认值”。

function MyConstructor() { /* example function or "class" */ }

上述语句创建了一个名为MyConstructor对象,它有一个实例原型,它是对Function.prototype 的引用。同时也是一个可以调用的函数。

这很重要:

MyConstructor instanceof Function;
// is TRUE because
Object.getPrototypeOf(MyConstructor) === Function.prototype

// this is NOT TRUE
MyConstructor.prototype === Function.prototype

因为这种细微的差别

var myInstance = new MyConstructor();

myInstance instanceof MyConstructor;
// this is TRUE because
Object.getPrototypeOf(myInstance) === MyInstance.prototype

现在,MyConstructor.prototype 只是一个空对象(它有一个引用Object.prototype实例原型)。

访问属性

在 javascript 中,对象只有属性。它们没有方法,但具有指向函数的属性

当您尝试访问对象(Base 的实例)上的属性时,引擎会像这样查找该属性:

Checked location Description
this.$HERE$ On your local properties
this.__proto__.$HERE$ __proto__ is a reference to Base.prototype.
this.__proto__.__proto__.$HERE$ This would be your parent class prototype.
this.__proto__.__proto__.__proto__.$HERE$ This would be your grand-parent class prototype.
...and on we go, searching through the prototype chain, until there are no more prototypes. The search is done using Object.prototype.hasOwnProperty, which means that even if the value is undefined, the search will stop.

__proto__ 是一个神奇的属性,已被Object.getPrototypeOf() 弃用。为了简洁起见,我使用它。

通过原型链找到的任何属性,在返回给您之前都将绑定this

接下来是继承和方法定义。这里有两种思想流派;一个用Object.create创建的新对象覆盖Base.prototype,然后继续创建方法:

// This works (but it overwrites the Base.prototype object)
Base.prototype = Object.create(ParentClass);

// Declare a method
Base.prototype.incrementMyValue = function() {
    this.myValue++;
}

// A default value
Base.prototype.myValue = 123

// A getter
Object.defineProperty(Base.prototype, 'getMyValue', {get: function() { 
  return myValue;
}});

在上面的代码中,我想指出的是,当你访问instance.myValue时,它是未定义的,所以扫描原型链,你会得到123

如果您第一次调用instance.incrementMyValue(),原型链将被扫描并返回 123。然后您递增该值,并将其分配给 您的实例.

你开始于:

instance // no .myValue exists
instance.__proto__.myValue = 123

调用:

instance.incrementValue();

你最终得到:

instance.myValue = 124;
instance.__proto__.myValue = 123

默认值仍然存在,但被实例本地myValue 属性覆盖。

old-school 类继承定义

这门课什么都有:

  • 私有财产
  • 私有静态属性
  • 公共属性
  • 公共静态属性

看,“Charm”类,借用我希望发布的 Charm.js 库中的想法:

var Derived = (function(){
  /* Wrapped in a function, so we keep things private*/

  /**
   * CONSTRUCTOR
   */
  function Derived(id, name) {
    Base.call(this, id);               // Calling the parent constructor
    private(this).name = name;         // Setting a TRUE private property
  }

  /**
   * PRIVATE STATIC PROPERTIES
   */
  var thisIsPrivateAndShared = 0;

  /**
   * PUBLIC STATIC PROPERTIES
   */
  Derived.thisIsPublicAndShared = 0;

  /**
   * PRIVATE NON-STATIC PROPERTIES THROUGH WeakMap
   */
  var secrets = new WeakMap();
  function private(for) {
    var private = secrets.get(for);
    if (!private) {
      private = {};
      secrets.set(for, private);
    }
    return private;
  }

  /**
   * Building the prototype
   */
  Derived.prototype = Object.create(

    /**
     * EXTEND Base.prototype (instead of Object.prototype)
     */
    Base.prototype,

    /**
     * Declare getters, setters, methods etc (or see below)
     */
    {
      /**
       * GETTERS AND SETTERS FOR THE PRIVATE PROPERTY 'name'
       */
      name: {
        get: function() {                // getter
          return private(this).name;
        },
        set: function(value) {           // setter
          private(this).name = value;
        }
      },

      /**
       * A PUBLIC METHOD
       */
      method: {value: function() {

      }},

      /**
       * A PUBLIC PROPERTY WITH A DEFAULT VALUE
       */
      age: {value: 42, writable: true}
    });

    /**
     * I am too lazy to write property descriptors,
     * unless I want a getter/setter/private properties,
     * so I do this:
     */
    Derived.prototype.lessWorkMethod = function() {
    };
  }
  return Derived;
})();

【讨论】:

    【解决方案2】:

    谁能解释一下哪种方法适合我?

    根本不要使用Object.defineProperty。您在这里绝对不需要属性描述符,并且您的 getter 和 setter 没有做任何特别的事情。只需使用一个简单的普通属性。它将比您关心的其他任何事情都更快、更好地优化。

    function Base(id) {
        this.ID = id;
    }
    
    function Derived(id, name) {
        Base.call(this,id);
        this.Name = name;
    }
    
    Derived.prototype = Object.create(Base.prototype);
    Derived.prototype.constructor = Derived;
    

    如果确实为每个对象重新创建函数,我可能需要考虑第二种方法?

    是的,确实如此,但可以忽略不计。您不应该过早地进行微优化。您将知道何时真正需要它,然后您仍然可以轻松地交换实现。在此之前,请使用简洁的代码。

    【讨论】:

    • 在我的实际应用程序中,很多东西交织在一起,所以我当然需要属性,如果不清楚,我只是举了一个简单的例子抱歉。在 cmets 讨论之后,我完全同意你的第二点。定义属性的第一种方法非常优雅且易读,所以我将继续使用它:)
    • 知道您可以改用干净的代码总是令人欣慰的。您将知道何时必须进行微优化。即,如果您需要动态创建大量对象。
    • 如果我们在某些情况下需要定义属性怎么办?
    • @zwcloud 如果需要的话,是否定义它们?
    • @zwcloud 你可能想ask a complete question 展示你需要什么(以及为了什么)以及你如何尝试使用Object.defineProperty
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多