具有构造函数和原型的Object.defineProperty

时间:2016-05-10 08:57:19

标签: javascript properties

我刚刚发现了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" Alexander"

但我对此非常困惑,例如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方法,因为代码对我来说非常干净,我可以将它用于继承。但是,如果为每个对象重新创建函数,我可能需要考虑第二种方法吗?

2 个答案:

答案 0 :(得分:1)

  

有人可以解释哪种方法适合我吗?

根本不要使用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;
  

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

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

答案 1 :(得分:1)

我参加聚会可能有点晚了,但我想在这里指出一些小事。

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

我是这样理解你的问题

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

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

javascript 对象定义的原型

现代 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??请参阅 herethere,以及 there

打开引擎盖

不要在构造函数中创建 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 vs 实例原型

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 的实例)上的属性时,引擎会像这样查找该属性:

<头>
检查位置 说明
this.$HERE$ 在您当地的房产
this.__proto__.$HERE$ __proto__ 是对 Base.prototype 的引用。
this.__proto__.__proto__.$HERE$ 这将是您的类原型。
this.__proto__.__proto__.__proto__.$HERE$ 这将是您的祖父类原型。
...然后我们继续搜索原型链,直到没有更多的原型。 搜索是使用Object.prototype.hasOwnProperty完成的,这意味着即使值为undefined,搜索也会停止。
<块引用>

__proto__ 是一个魔法属性,它已被弃用,取而代之的是 Object.getPrototypeOf()。为简洁起见,我使用它。

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

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

// 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。然后您增加该值,并将它分配给您的实例< /em>.

您开始于:

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

调用:

instance.incrementValue();

你最终得到:

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

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

带有继承的老派类定义

这个类拥有一切:

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

看哪,“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;
})();