流行的 JavaScript 继承模式

Posted

技术标签:

【中文标题】流行的 JavaScript 继承模式【英文标题】:Popular JavaScript Inheritance Patterns 【发布时间】:2010-12-11 07:54:06 【问题描述】:

我正在编写一个关于 TDD javascript 的 ebook on GitHub,我想知道我是否遗漏了任何流行的继承模式。如果您知道任何其他模式,我很乐意看到它们。他们应该有以下几点:

    经过时间考验 - 在实际应用中使用 应提供源代码。应该尽可能直截了当和迂腐。 当然要正确且有效。

我这样做的原因是,对于我们许多人来说,JavaScript 中的对象继承似乎很难理解。我的 JavaScript 继承章节基本上是对以下内容的学习辅助:Crockford 的 Good Parts 和 Zakas 的 Professional JavaScript for Web Developers。

以下是我目前的模式:

// 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

【问题讨论】:

我在很多不同的项目中尝试了很多技术。我最喜欢的是使用 Object.create() 来实例化基本原型。查看我的博客:ncombo.wordpress.com/2013/07/11/… 【参考方案1】:

请参阅How to "properly" create a custom object in JavaScript? 了解摘要。 (不妨把它链接起来,因为我浪费了这么多时间打字!)

这个:

Dog.prototype = new Animal();

通常会被避免。您可以在示例/教程代码中看到它,但它是一个可怕的混乱,因为它基于实例的类,并且以错误的方式构造了一个实例:name 未定义。任何更复杂的构造函数都会对这种事情感到不安。

Object.prototype.inherit=

是一种更好的构造方法,但是将任何东西原型化到Object 被认为是非常糟糕的品味。它冒着将对象用作琐碎地图并破坏其他代码的风险。您可以将此辅助函数放在其他地方,例如。 Function.prototype.subclass.

prototype.constructor

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

【讨论】:

感谢您的反馈。我同意 Object.prototype.inherit 并且我想我在我的文章中提到了这一点,但我至少会将它添加到代码中的 cmets 和/或将其放入包装函数中。谢谢你抓住它! Dog.prototype = new Animal() 我在后面的模式中给出了替代方案。感谢您的意见鲍勃。 我继续更新了代码(并编辑了上面的代码清单)以使用辅助对象而不是破坏对象。 是的,我回去阅读了我的文章,然后我说:'当然没有什么需要你对 Object 进行修补——如果你愿意,你可以创建一个返回此类功能的包装器。简直是懒惰!!!谢谢你让我“做对了”!【参考方案2】:

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

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

【讨论】:

【参考方案3】:

这里有一个有趣的模式值得一提:JavaScript 构造函数可以返回任何对象(不一定是this)。可以创建一个构造函数,它返回一个代理对象,其中包含对“真实”实例对象的“真实”方法的代理方法。这听起来可能很复杂,但事实并非如此;这是一个代码sn-p:

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/

【讨论】:

【参考方案4】:

我的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。

【讨论】:

嗨克里斯托夫!谢谢。因此,您的派生方法实际上与我的继承方法相同,只是它返回一个闭包并调用“自身”;该模式还使用构造函数窃取 - 我认为它本质上是一种组合继承模式(原型和构造函数窃取结合)。我需要再看一下其他模式 - 我想我今晚会玩它并报告;-)感谢分享克里斯托夫。【参考方案5】:

在这里聚会迟到了,但我有 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
 //

【讨论】:

以上是关于流行的 JavaScript 继承模式的主要内容,如果未能解决你的问题,请参考以下文章

前端面试基本---JavaScript严格模式

Typescript 和 Javascript之间的区别

JavaScript继承基础讲解,原型链借用构造函数混合模式原型式继承寄生式继承寄生组合式继承

javascript设计模式-继承

JavaScript 对象

javascript中的继承模式