流行的JavaScript继承模式

时间:2009-10-29 16:41:24

标签: javascript inheritance design-patterns

我正在开发关于TDD JavaScript的ebook on GitHub,我想知道我是否缺少任何流行的继承模式。如果您知道任何其他模式,我很乐意看到它们。他们应该有以下几点:

  1. 经过时间测试 - 在真实应用中使用
  2. 应提供源代码。应该尽可能直接和迂腐。
  3. 当然是正确和有效的。
  4. 我这样做的原因是,似乎JavaScript中的对象继承对我们很多人来说都很难理解。我的JavaScript继承章节基本上是一个学习辅助工具:Crockford的Good Parts和Zakas的Web开发人员专业JavaScript。

    以下是我目前的模式:

    // 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 at GitHub repo

5 个答案:

答案 0 :(得分:8)

有关摘要,请参阅How to "properly" create a custom object in JavaScript?。 (也可以链接它,因为我浪费了很多时间输入它!)

这样:

  

Dog.prototype = new Animal();

通常会避免使用

。你在example / tutorial代码中看到它,但它是一个可怕的混乱,因为它是基于一个实例的类,以及以错误方式构造的实例:name未定义。任何更复杂的构造函数都会对此类事情感到不安。

  

Object.prototype.inherit =

是一种更好的构建方法,但将任何内容原型化为Object被认为是非常差的品味。它可能会将对象的使用视为琐碎的地图并破坏其他代码。您可以将此辅助函数放在其他位置,例如。 Function.prototype.subclass

  

prototype.constructor

我个人倾向于避免,因为constructor在JavaScript中具有特殊含义(在Firefox和其他一些浏览器中实现;而不是IE的JScript),这意味着constructor不是{{1}}的意思也不是你期望任何这样的财产做什么;它令人困惑,几乎总是最好避免。因此,如果在类系统的实例中包含指向构造函数的链接,我更愿意将其命名为其他内容。

答案 1 :(得分:1)

我以前公司的一位同事开发了一个像继承http://www.uselesspickles.com/class_library/这样的java库。我认为它比Rajendra的建议更性感,语法看起来更干净。

我写了一篇文章,展示了解决它的不同方法,但确保避免了已知的不良做法。 http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html,如果你不想下载一个库,只是想复制粘贴一些你可以改进的代码来做你需要的代码。

答案 2 :(得分:1)

这里有一个值得一提的有趣模式: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/

答案 3 :(得分:0)

我的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';

您已already seen implementation of classes

答案 4 :(得分:0)

来到这里的晚会,但我有2分要做。

1)请不要通过创建超类型对象来通知人们继承。出于某些原因,这被认为是不好的做法。首先,它是一个原则性的错误。您实例化对象只是为了使用它们的方法而不是对实例本身做任何事情。完成此操作的正确方法是使用Object.prototype.inherit方法。此外,此方法强制您将超类型构造函数参数留空,这可能会在严格的情况下调用错误。

2)你忘了提到构造函数窃取模式。

$this->before(function () {
    $this['dispatcher']->addListener($eventName, $callback);
});