Образец прототипа Грокча в стиле Крокфорда; ищу элегантное решение - PullRequest
4 голосов
/ 08 апреля 2011

Я часто использую образец прототипа Крокфорда при написании программ на JavaScript. Я думал, что понял все «ошибки», но обнаружил один, о котором раньше не думал. Я хотел бы знать, есть ли у кого-нибудь лучший способ справиться с этим.

Вот простой пример:

// Here's the parent object
var MyObject = {
    registry: {},
    flatAttribute: null,
    create: function () {
        var o, F = function () {};

        F.prototype = this;
        o = new F();
        return o;
    }
};

// instance is an empty object that inherits
// from MyObject
var instance = MyObject.create();

// Attributes can be set on instance without modifying MyObject
instance.flatAttribute = "This is going to be applied to the instance";

// registry doesn't exist on instance, but it exists on
// instance.prototype. MyObject's registry attribute gets
// dug up the prototype chain and altered. It's not possible
// to tell that's happening just by examining this line.
instance.registry.newAttribute = "This is going to be applied to the prototype";

// Inspecting the parent object
// prints "null"
console.log(MyObject.flatAttribute);
// prints "This is going to be applied to the prototype"
console.log(MyObject.registry.newAttribute);

Я хочу быть уверен, что любые изменения, внесенные в экземпляр, не распространяются на изменение наследования. Это не тот случай, когда атрибут является объектом, и я устанавливаю вложенное свойство.

Решение состоит в том, чтобы повторно инициализировать все атрибуты объекта в экземпляре. Однако одно из заявленных преимуществ использования этого шаблона - удаление кода повторной инициализации из конструктора. Я думаю о клонировании всех атрибутов объекта родительского объекта и установке их в экземпляре в функции create ():

{ create: function () {
    var o, a, F = function () {};

    F.prototype = this;
    o = new F();
    for (a in this) {
        if (this.hasOwnProperty(a) && typeof this[a] === 'object') {
            // obviously deepclone would need to be implemented
            o[a] = deepclone(this[a]);
        }
    }
    return o;
} };

Есть ли лучший способ?

Ответы [ 4 ]

2 голосов
/ 08 апреля 2011

Существует очень простое решение для обеспечения того, чтобы они были только переменными экземпляра, а именно использование ключевого слова this в конструкторе.

var MyObject = {
    flatAttribute: null,
    create: function () {
        var o, F = function () {
           this.registry  = {}
        };

        F.prototype = this;
        o = new F();
        return o;
   }
};

это гарантирует, что все свойства "instance.registry. *" Являются локальными для экземпляра, потому что порядок поиска для объектов javascript следующий.

object -> prototype -> parent prototype ...

, добавивпеременная экземпляра в функции конструктора с именем «registry», которая всегда будет найдена первой.

другое решение, которое я считаю более изящным, - это не использовать конструкторы Крокфорда (стиль Java) и использовать макет, которыйотражает объектную систему javascripts более естественно.большинство этих ошибок связано с несоответствием между практикой и языком.

// instance stuff
var F = function () {
   this.registry  = {}
};

F.prototype = {
    // static attributes here
    flatAttribute: null,
    methodA: function(){ 
        // code here 'this' is instance object
        this.att = 'blah';
    }
};

var instanceA = new F();
instanceA.registry['A'] = 'hi';
var instanceB = new F();
instanceB.registry['B'] = 'hello';

instanceA.registry.A == 'hi'; // true
instanceB.registry.B == 'hello'; // true
F.prototype.registry == undefined; // true
0 голосов
/ 05 мая 2013

Мне всегда нравится помнить, что object.Create - это один из вариантов, а не единственный способ достижения неклассического наследования в javascript.

Для себя я всегда считаю, что Object.create работает лучше всего.когда я хочу наследовать элементы из цепочки прототипов родительских объектов (то есть методы, которые я хотел бы иметь возможность применять к наследующему объекту).

-

Для простого наследования "Собственного имущества" Object.create в основном не требуется.Когда я хочу наследовать собственные свойства, я предпочитаю использовать популярные шаблоны Mixin & Extend (которые просто копируют собственные свойства одного объекта в другой, не беспокоясь о прототипе или "новом"),

В книге Стояна Стефанова "Шаблоны Javascript" он приводит пример функции глубокого расширения, которая выполняет то, что вы ищетерекурсивно и включает поддержку свойств, которые являются массивами, а также стандартными объектами ключ / значение:

function extendDeep(parent, child){
    var i,
        toStr = Object.prototype.toString,
        astr = "[object Array]";

    child = child || {};

    for (i in parent) {
        if (parent.hasOwnProperty(i)) {
            if (typeof parent[i] === "object") {
                child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
                extendDeep(parent[i], child[i]);
            } else { 
                child[i] = parent[i];
            }
        }
    }
    return child;
}

Если вы используете jQuery, jQuery.extend () имеет необязательный параметр "глубокий аргумент, который позволяет вам расширить объект почти идентичным способом.

0 голосов
/ 08 апреля 2011

я думаю, что вы используете прототип наследования для имитации a классического , объектно-ориентированного наследования.

Что вы пытаетесь сделать остановить поиск метода-прототипа, который ограничивает его выразительность , так зачем его использовать?Вы можете добиться того же эффекта, используя этот функциональный шаблон :

var MyObject = function() {
    // Declare here shared vars
    var global = "All instances shares me!";

    return {
        'create': function() {
            var flatAttribute;
            var register = {};

            return {
                // Declare here public getters/setters
                'register': (function() {
                    return register;
                })(),
                'flatAttribute': (function() {
                    return flatAttribute;
                })(),
                'global': (function() {
                    return global;
                })()
            };
        }
    };
}();


var instance1 = MyObject.create();
var instance2 = MyObject.create();

instance1.register.newAttr = "This is local to instance1";
instance2.register.newAttr = "This is local to instance2";
// Print local (instance) var
console.log(instance1.register.newAttr);
console.log(instance2.register.newAttr);
// Print global var
console.log(instance1.global);
console.log(instance2.global);

Код на jsFiddle

0 голосов
/ 08 апреля 2011

Это даст вам ожидаемый результат?Здесь я не использую литерал Object, а создаю мгновенно созданную функцию конструктора для родительского объекта (Base):

var Base = ( function(){
     function MyObject(){
     this.registry = {},
     this.flatAttribute = null;
     if (!MyObject.prototype.create)   
      MyObject.prototype.create = function(){
       return new this.constructor();
      };
     }
     return new MyObject;
    } )(),
// create 2 instances from Base
instance1 = Base.create(),
instance2 = Base.create();

// assign a property to instance1.registry
instance1.registry.something = 'blabla';

// do the instance properties really belong to the instance?
console.log(instance1.registry.something); //=>  'blabla'
console.log(instance2.registry.something === undefined); //=>  true

Но все это немного виртуально.Если вы не хотите использовать оператор new (я думаю, что это была полная идея), следующий способ предлагает вам сделать это без необходимости создания метода:

function Base2(){
     if (!(this instanceof Base2)){
         return new Base2;
     }
     this.registry = {}, 
     this.flatAttribute = null;
     if (!Base2.prototype.someMethod){
         var proto = Base2.prototype;
         proto.someMethod = function(){};
         //...etc
     }
}
//now the following does the same as before:
var  instance1 = Base2(),
     instance2 = Base2();

// assign a property to instance1.registry
instance1.registry.something = 'blabla';

// do the instance properties really belong to the instance?
console.log(instance1.registry.something); //=>  'blabla'
console.log(instance2.registry.something === undefined); //=>  true

Пример в jsfiddle

...