Популярные образцы наследования JavaScript - PullRequest
16 голосов
/ 29 октября 2009

Я работаю над книгой на GitHub по TDD JavaScript, и мне интересно, не хватает ли я каких-либо популярных шаблонов наследования. Если вы знаете о каких-либо дополнительных шаблонах, я бы хотел их увидеть. Они должны иметь следующее:

  1. Проверено временем - используется в реальных приложениях
  2. Исходный код должен быть предоставлен. Должно быть как можно более прямым и педантичным.
  3. Конечно будь корректным и работающим.

Причина, по которой я это делаю, заключается в том, что многим из нас довольно трудно унаследовать наследование объектов в JavaScript. Моя глава по наследованию JavaScript в основном служит учебным пособием для: Хороших деталей Крокфорда и Профессионального JavaScript для веб-разработчиков Zakas.

Вот шаблоны, которые у меня есть до сих пор:

// Pseudoclassical Inheritance
    function Animal(name) {
        this.name = name;
        this.arr = [1,2,3];
    };
    Animal.prototype = {
        constructor: Animal,
        whoAmI: function() { return "I am " + this.name + "!\n"; }
    };

    function Dog(name, breed) {
        this.name = name;
        this.breed = breed;
    };
    Dog.prototype = new Animal();
    Dog.prototype.getBreed = function() {
        return this.breed;
    };
    Dog.prototype.bark = function() {
        return 'ruff ruff';
    };

    // Combination Inheritance
    function Parent(name) {
        this.name = name;
        this.arr = [1,2,3];
    };
    Parent.prototype = {
        constructor: Parent,
        toString: function() { return "My name is " + this.name; }
    };
    function Child(name, age) {
        Parent.call(this, name);
        this.age = age;
    };

    Child.prototype = new Parent();

    Child.prototype.getAge = function() {
        return this.age;
    };

    // Prototypal Inheritance
    var helper = { // Thanks to Bob Vince for reminding me NOT to clobber Object!

        inherit: function(p) {
        NewObj = function(){};
        NewObj.prototype = p;
        return new NewObj(); 
        },
        inheritPrototype: function(subType, superType) {
        var prototype = helper.inherit(superType.prototype);
        prototype.constructor = subType;
        subType.prototype = prototype;
        }
    };

    function SubType(name, age) {
        Parent.call(this, name);
        this.age = age;    
    };
    //Child.prototype = new Parent();   // Gets replaced by:
    helper.inheritPrototype(SubType, Parent);  
    SubType.prototype.getAge = function() {
        return this.age;
    };

    // Functional - Durable Pattern
    function super_func(blueprint) { 
        var obj = {};
        obj.getName = function() { return blueprint.name; };
        obj.getAge  = function() { return blueprint.age; };
        obj.getFoo  = function() { return blueprint.foo; };
        obj.getBar  = function() { return blueprint.bar; };
        return obj;
    };
    function sub_func(blueprint) {
        blueprint.name = blueprint.name || "Crockford's Place";
        supr = super_func(blueprint);
        supr.coolAugment = function() { return "I give a fresh new perspective on things!" };
        return supr;    
    };

А для тех, кто заинтересован, вот тесты jspec (извините, но Markdown или что-то еще, что они немного искажают формат):

describe 'JavaScript Inheritance Tests'
    before_each
    animal = new Animal("Onyx")
    dog = new Dog("Sebastian", "Lab")

    person = { password : 'secret', toString : function(){ return '<Person>' } }
    stub(person, 'toString').and_return('Original toString method!')    
    end
    describe 'Pseudoclassical Inheritance Creation'
    it 'should create parent and child object using pseudoclassical inheritance'
        animal.constructor.should.eql Animal
        // dog.constructor.should.eql Dog // Nope: expected Animal to eql Dog
        dog.constructor.should.eql Animal 
        animal.should.be_a Animal 
        dog.should.be_a Animal
        // dog.should.be_a Dog // Nope! We severed the original prototype pointer and now point to Animal!
        dog.should.be_an_instance_of Animal
        dog.should.be_an_instance_of Dog 
        (animal instanceof Dog).should.be_false
    end
    it 'should behave such that child inherits methods and instance variables defined in parent'
        animal.whoAmI().should.match /I am Onyx.*/ 
        dog.whoAmI().should.match /Sebastian.*/
        animal.should.respond_to 'whoAmI'
        dog.should.respond_to 'whoAmI'
        dog.should.have_prop 'name'
    end
    it 'should behave such that methods and instance variables added to child are NOT available to parent'
        dog.bark().should.match /Ruff Ruff/i
        dog.should.have_property 'breed'
        dog.should.respond_to 'bark'
        // animal.should.have_prop 'breed' // Of course not!
        // animal.should.respond_to 'bark' // Of course not!
    end
    it 'should behave such that reference variables on the parent are "staticy" to all child instances'
        dog.arr.should.eql([1,2,3]) 
        dog.arr.push(4)
        dog.arr.should.eql([1,2,3,4]) 
        spike = new Dog("Spike", "Pitbull")
        spike.arr.should.eql([1,2,3,4]) 
        spike.arr.push(5)
        rover = new Dog("Rover", "German Sheppard")
        spike.arr.should.eql([1,2,3,4,5])
        rover.arr.should.eql([1,2,3,4,5])
        dog.arr.should.eql([1,2,3,4,5])
    end 
    end

    describe 'Combination Inheritance Solves Static Prototype Properties Issue'
    it 'should maintain separate state for each child object'
        child_1 = new Child("David", 21)
        child_2 = new Child("Peter", 32)
        child_1.arr.push(999)
        child_2.arr.push(333)
        child_1.arr.should.eql([1,2,3,999])
        child_2.arr.should.eql([1,2,3,333])
        child_1.getAge().should.eql 21
        child_1.should.be_a Parent
    end
    end

    describe 'Prototypal Inheritance'
    it 'should inherit properties from parent'
        person.toString().should.match /Original toString.*/i
        person.password.should.eql 'secret'
        joe = helper.inherit(person)
        joe.password.should.eql 'secret'
        joe.password = 'letmein'
        joe.password.should.eql 'letmein'
        person.password.should.eql 'secret'
    end
    end

    describe 'Parisitic Combination Inheritance'
    it 'should use inheritPrototype (to call parent constructor once) and still work as expected'
        sub = new SubType("Nicholas Zakas", 29)
        sub.toString().should.match /.*Nicholas Zakas/
        sub.getAge().should.eql 29
        charlie = new SubType("Charlie Brown", 69)
        charlie.arr.should.eql([1,2,3])
        charlie.arr.push(999)
        charlie.arr.should.eql([1,2,3,999])
        sub.arr.should.eql([1,2,3]) 
        sub.should.be_an_instance_of SubType
        charlie.should.be_an_instance_of SubType
        (sub instanceof SubType).should.eql true 
        (sub instanceof Parent).should.eql true 
    end
    end

    describe 'Functional Durable Inheritance'
    it 'should hide private variables'
        sup = new super_func( {name: "Superfly Douglas", age: 39, foo: "foo", bar: "bar"} )
        sup.getName().should.eql 'Superfly Douglas'
        sup.name.should.be_undefined
        sup.getAge().should.eql 39 
        sup.age.should.be_undefined
        sup.getFoo().should.eql 'foo'
        sup.foo.should.be_undefined
    end

    it 'should create a descendent object that inherits properties while maintaining privacy'
        sub = new sub_func( {name: "Submarine", age: 1, foo: "food", bar: "barfly"} )
        sub.getName().should.eql 'Submarine'
        sub.name.should.be_undefined
        sub.getAge().should.eql 1 
        sub.age.should.be_undefined
        sub.getFoo().should.eql 'food'
        sub.foo.should.be_undefined 
        sub.getBar().should.eql 'barfly'
        sub.bar.should.be_undefined 
        sub.coolAugment().should.match /.*fresh new perspective.*/
        //sub.should.be_an_instance_of super_func NOPE!
        //sub.should.be_an_instance_of sub_func   NOPE!
        sub.should.be_an_instance_of Object 
    end
    end

end

Спасибо всем! О, и если вы хотите проверить мое эссе / книгу, я хотел бы получить обратную связь: TDD JavaScript в репозитории GitHub

Ответы [ 5 ]

8 голосов
/ 29 октября 2009

См. Как "правильно" создать пользовательский объект в JavaScript? для краткого изложения. (Можно также связать это, так как я потратил так много времени, печатая его!)

это:

Dog.prototype = new Animal ();

как правило, следует избегать. Вы видите это в примере / учебном коде, но это ужасный беспорядок, потому что он основывает класс на экземпляре, а экземпляр создан неверным образом: name не определено. Любой более сложный конструктор будет расстраиваться из-за такого рода вещей.

Object.prototype.inherit =

Это лучший подход для построения, но создание прототипа чего-либо в Object считается очень плохим вкусом. Это рискует испортить использование объектов в качестве тривиальных карт и взломать другой код. Вы можете поместить эту вспомогательную функцию в другом месте, например. Function.prototype.subclass.

prototype.constructor

Лично я бы хотел этого избежать, потому что constructor имеет особое значение в JavaScript (как реализовано в Firefox и некоторых других браузерах; не в JScript IE), и это значение не то, что constructor делает здесь, и то, что вы бы хотели ожидать, что любая такая собственность будет делать; это сбивает с толку и почти всегда лучше избегать. Поэтому, если включить ссылку на функцию конструктора в экземпляр в системе классов, я бы предпочел назвать это как-то иначе.

1 голос
/ 26 ноября 2012

Здесь стоит упомянуть интересную схему: конструктор JavaScript может возвращать любой объект (не обязательно this ). Можно создать функцию конструктора, которая возвращает прокси-объект, который содержит прокси-методы для «реальных» методов «реального» объекта экземпляра. Это может показаться сложным, но это не так; Вот фрагмент кода:

var MyClass = function() {
    var instanceObj = this;
    var proxyObj = {
        myPublicMethod: function() {
            return instanceObj.myPublicMethod.apply(instanceObj, arguments);
        }
    }
    return proxyObj;
};
MyClass.prototype = {
    _myPrivateMethod: function() {
        ...
    },
    myPublicMethod: function() {
        ...
    }
};

Приятно то, что создание прокси может быть автоматизировано, если мы определим соглашение для именования защищенных методов. Я создал небольшую библиотеку, которая делает именно это: http://idya.github.com/oolib/

1 голос
/ 15 декабря 2010

Сотрудник в моей предыдущей компании разработал библиотеку для выполнения Java как наследование http://www.uselesspickles.com/class_library/. Я думаю, что это более сексуально, чем предложения Раджендры, синтаксис выглядит чище.

Я написал статью, в которой демонстрируются разные способы ее решения, но при этом избегаю известных плохих практик. http://js -bits.blogspot.com / 2010/08 / javascript-inheritance-done-right.html , это если вы не хотите загружать библиотеку, а просто хотите скопировать, вставить некоторый код что вы можете улучшить, чтобы делать то, что вам нужно.

0 голосов
/ 05 января 2017

Опоздал на вечеринку, но у меня есть 2 замечания.

1) Пожалуйста, не сообщайте людям о наследовании путем создания объектов супертипа. Это считается плохой практикой по нескольким причинам. Во-первых, это принципиальная ошибка. Вы создаете экземпляры объектов только для того, чтобы использовать их методы и ничего не делать с экземпляром как таковым. Правильный способ сделать это - использовать метод Object.prototype.inherit. Кроме того, этот метод заставляет вас оставить аргумент функции конструктора супертипа пустым, что может привести к ошибке при строгих обстоятельствах.

2) Вы забыли упомянуть шаблон кражи конструктора.

function Supertype(name){
 this.name=name;
 this.sayName = function(){console.log(this.name);};
} 

function Subtype(name){
 //inherit by using (stealing) supertype constructor function
 Supertype(name);

 // child specific properties
 //
}
0 голосов
/ 29 октября 2009

В моей папке dev/web/stuff лежат как минимум полдюжины реализаций различных шаблонов наследования, но в основном это игрушки.

В действительности я иногда использую следующую тонкую оболочку над подходом JavaScript по умолчанию для псевдоклассов, чтобы облегчить наследование:

Function.prototype.derive = (function() {
    function Dummy() {}
    return function() {
        Dummy.prototype = this.prototype;
        return new Dummy;
    };
})();

Пример кода:

function Pet(owner, type, name) {
    this.owner = owner;
    this.type = type;
    this.name = name;
}

Pet.prototype.toString = function() {
    return this.owner + '\'s ' + this.type + ' ' + this.name;
};

function Cat(owner, name) {
    Pet.call(this, owner, 'cat', name);
}

Cat.prototype = Pet.derive();

var souris = new Cat('Christoph', 'Souris');

Еще одним интересным является следующее, которое автоматически добавляет фабричные методы к подходящему прототипу:

var Proto = new (function() {
    function Dummy() {}

    this.clone = function() {
        Dummy.prototype = this;
        return new Dummy;
    };

    this.init = function() {};

    this.create = function() {
        var obj = this.clone();
        this.init.apply(obj, arguments);
        return obj;
    };
});

Пример кода:

var Pet = Proto.clone();

Pet.init = function(owner, type, name) {
    this.owner = owner;
    this.type = type;
    this.name = name;
};

Pet.toString = function() {
    return this.owner + '\'s ' + this.type + ' ' + this.name;
};

Cat = Pet.clone();

Cat.init = function(owner, name) {
    Pet.init.call(this, owner, 'cat', name);
};

// use factory method
var filou = Cat.create('Christoph', 'Filou');

// use cloning (the proper prototypal approach)
var red = filou.clone();
red.name = 'Red';

Вы уже видели мою реализацию классов .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...