【问题标题】:JS defineProperty and prototypeJS 定义属性和原型
【发布时间】:2012-12-27 00:03:53
【问题描述】:

如您所知,我们可以在 JS 中使用 defineProperty() 定义 getter 和 setter。我在尝试使用 defineProperty() 扩展我的课程时遇到了困难。

这是一个示例代码:

我有一个必须添加到对象的字段数组

fields = ["id", "name", "last_login"]

我还有一个要修改的类

var User = (function(){
    // constructor
    function User(id, name){
        this.id     = id
        this.name   = name
    }
    return User;
})();

还有一个使用defineProperty()向类添加字段的函数

var define_fields = function (fields){
    fields.forEach(function(field_name){
        var value = null
        Object.defineProperty(User.prototype, field_name, {
            get: function(){ return value }
            set: function(new_value){
                /* some business logic goes here */
                value = new_value
            }
        })
    })
};

运行define_fields() 后,我的字段位于User 的实例中

define_fields(fields);
user1 = new User(1, "Thomas")
user2 = new User(2, "John")

但是这些属性的值是相同的

console.log(user2.id, user2.name) // 2, John
console.log(user1.id, user1.name) // 2, John

在这种情况下,有什么方法可以让defineProperty() 正常工作吗? 如果我理解问题出在value 上,它对于 类的每个实例,但我不知道如何修复它。提前致谢 为您解答。

更新: 这种方式抛出“RangeError:超出最大调用堆栈大小”

var define_fields = function (fields){
    fields.forEach(function(field_name){
        Object.defineProperty(User.prototype, field_name, {
            get: function(){ return this[field_name] }
            set: function(new_value){
                /* some business logic goes here */
                this[field_name] = new_value
            }
        })
    })
};

【问题讨论】:

  • 一定喜欢 JavaScript。有一刻你以为你掌握了所有可以用它表达的东西的窍门,然后它证明你犯了可怕的错误。
  • 在生产中一定会害怕 js。永远不会接受修复不是我写的 js 应用程序的工作!

标签: javascript prototype defineproperty


【解决方案1】:

请不要实现任何其他版本,因为它会占用您应用中的所有内存:

var Player = function(){this.__gold = 0};

Player.prototype = {

    get gold(){
        return this.__gold * 2;
    },



    set gold(gold){
        this.__gold = gold;
    },
};

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

如果实例化 10000 个对象:

  • 用我的方法:你的内存中只有两个函数;
  • 使用其他方法:10000 * 2 = 20000 个函数在内存中;

【讨论】:

【解决方案2】:

在他回答三分钟后,我得出了与Mikhail Kraynov 相同的结论。每次调用构造函数时,该解决方案都会定义新属性。我想知道,正如你所问的,是否有一种方法可以将 getter 和 setter 放入原型中。这是我想出的:

var User = (function () {
  function User (id, nam) {
    Object.defineProperty (this, '__',  // Define property for field values   
       { value: {} });

    this.id = id;
    this.nam = nam;
  }

  (function define_fields (fields){
    fields.forEach (function (field_name) {
      Object.defineProperty (User.prototype, field_name, {
        get: function () { return this.__ [field_name]; },
        set: function (new_value) {
               // some business logic goes here 
               this.__[field_name] = new_value;
             }
      });
    });
  }) (fields);

  return User;
}) ();  

在这个解决方案中,我在原型中定义了字段 getter 和 setter,但在每个保存字段值的实例中引用了一个(隐藏的)属性。

在这里查看小提琴:http://jsfiddle.net/Ca7yq

我在小提琴中添加了更多代码以显示对属性枚举的一些影响:http://jsfiddle.net/Ca7yq/1/

【讨论】:

  • 太棒了!正是我需要的:)
  • 不客气。这确实具有奇怪的效果,即具有相同构造函数的对象不一定具有任何共同的属性。一个有趣的事实,它肯定在某个地方有一个很棒的应用程序
  • 我会保密我的应用程序 :) 稍后它将在 Chrome 网上应用店中发布,也许它的一些源代码会在 GitHub 上。
  • 很好的解决方案!我只是想补充一点,您可以在User 构造函数中添加User.prototype.toJSON = function() { return this.__; },这样您就可以直接JSON.stringify(user1) 而不必JSON.stringify(user1.__)。如果您正在使用对对象进行字符串化的持久层,那么您也许可以将对象传递给保存方法!
  • This article 是为类似的上下文(ES6 类)编写的。 WeakMap 方法很适合这里。
【解决方案3】:

在我看来,当您为原型定义属性时,所有实例都共享该属性。所以正确的变体可能是

var User = (function(){
// constructor
function User(id, name){
    this.id     = id
    this.name   = name

    Object.defineProperty(this, "name", {
        get: function(){ return name },
        set: function(new_value){
            //Some business logic, upperCase, for example
            new_value = new_value.toUpperCase();
            name = new_value
        }
    })
}
return User;
})();

【讨论】:

  • 是的,我想到了这个解决方案。但是会带来一些其他的问题。假设我有一个抽象类Model 和一个继承自ModelUser。所以我需要一些可以自动为User 类创建所需方法的东西。我不想在每次创建新模型时都编写这些方法的定义。例如,稍后我将拥有 AlbumSong 类,它们也将继承自 Model
  • 这不会耗尽你所有的记忆吗?它将为每个实例创建新函数。
【解决方案4】:

当您在所有用户实例的 prototype 对象上定义属性时,所有这些对象将共享相同的 value 变量。如果这不是您想要的,您将需要在每个用户实例上分别调用 defineFields - 在构造函数中:

function User(id, name){
    this.define_fields(["name", "id"]);
    this.id     = id
    this.name   = name
}
User.prototype.define_fields = function(fields) {
    var user = this;
    fields.forEach(function(field_name) {
        var value;
        Object.defineProperty(user, field_name, {
            get: function(){ return value; },
            set: function(new_value){
                /* some business logic goes here */
                value = new_value;
            }
        });
    });
};

【讨论】:

  • 这个共享是 ES 规范的一部分吗?在我弄清楚这一点之前,我很困惑。
  • 是的,行为是在 ES5 中指定的(结合关于原型、闭包等的部分)
【解决方案5】:

此解决方案没有额外的内存消耗。您更新的代码已接近。你只需要使用 this.props[field_name] 而不是直接 this[field_name]。

请注意,defineProperty 调用已替换为 Object.create

Js Fiddle http://jsfiddle.net/amuzalevskyi/65hnpad8/

// util
function createFieldDeclaration(fields) {
    var decl = {};
    for (var i = 0; i < fields.length; i++) {
        (function(fieldName) {
            decl[fieldName] = {
                get: function () {
                    return this.props[fieldName];
                },
                set: function (value) {
                    this.props[fieldName] = value;
                }
            }
        })(fields[i]);
    }
    return decl;
}

// class definition
function User(id, name) {
    this.props = {};
    this.id = id;
    this.name = name;
}
User.prototype = Object.create(Object.prototype, createFieldDeclaration(['id','name']));

// tests
var Alex = new User(0, 'Alex'),
    Andrey = new User(1, 'Andrey');

document.write(Alex.name + '<br/>'); // Alex
document.write(Andrey.name + '<br/>'); // Andrey

Alex.name = "Alexander";
document.write(Alex.name + '<br/>'); // Alexander
document.write(Andrey.name + '<br/>'); //Andrey

【讨论】:

    【解决方案6】:

    从接受的答案中,我意识到我们在这里尝试做的是定义私有实例变量。这些变量应该在实例(this)上,而不是在原型对象上。通常我们通过在属性名称前加上下划线来命名私有变量。

    var Vehicle = {};
    Object.defineProperty(Vehicle, "make", {
        get: function() { return this._make; }
        set: function(value) { this._make = value; }
    });
    
    function Car(m) { this.make = m; }    //this will set the private var _make
    Car.prototype = Vehicle;
    

    接受的答案基本上将所有私有变量放在一个容器中,这实际上更好。

    【讨论】:

    • 你完全正确。范围内的本地(私有)变量是更有效和安全的方式。但是还有一个问题:如何动态定义变量?我只想拥有一个类,例如客户端上的Model,然后我将执行MyCustomModel extends Model 之类的操作,Model 将处理特定类的属性定义。您可以将 Rails ActiveModel 作为我想做的参考。
    • eval 不是动态定义变量的好方法:)
    • 我认为您正在尝试做一些 Javascript 杂技:)。对于局部变量,我认为我们别无选择,只能使用 eval。在我的一个程序中,我做: var expr=""; for (var name in vars) expr+="var "+name+"=vars[name];\n";评估(表达式); ...使用本地变量。不确定如何在继承上下文中使用它
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-23
    • 1970-01-01
    • 2016-11-13
    • 2015-01-29
    • 2023-03-16
    • 1970-01-01
    相关资源
    最近更新 更多