2009-10-29 7 views
16

나는 TDD 자바 스크립트에 ebook on GitHub에서 일하고 있는데, 나는 대중적인 상속 패턴을 놓치고 있는지 궁금해. 추가 패턴을 알고 있다면 그 패턴을보고 싶습니다. 그들은 다음이 있어야합니다인기있는 자바 스크립트 상속 패턴

  1. 시간 테스트 -
  2. 소스 코드를 제공해야 실제 응용 프로그램에 사용됩니다. 가능한 한 솔직하고 똑똑해야합니다.
  3. 물론 정확하고 올바르게 작동해야합니다.

내가 이것을하고있는 이유는 자바 스크립트에서 객체 상속이 매우 어려웠던 것으로 보인다는 점입니다. 내 JavaScript 상속 장은 기본적으로 Crockford의 Good Parts 및 Zakas의 웹 개발자 용 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 테스트 (죄송하지만 마크 다운 또는 무엇이든 그들이 사용하고있는 미치게 형식 조금)입니다 : 여기

내가 지금까지 가지고있는 패턴이다
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

+0

많은 프로젝트에서 많은 기술을 시도했습니다. 필자가 가장 좋아하는 것은 Object.create()를 사용하여 기본 프로토 타입을 인스턴스화하는 것입니다. 내 블로그를 확인하십시오 : http://ncombo.wordpress.com/2013/07/11/javascript-inheritance-done-right/ – Jon

답변

8

이 요약 How to "properly" create a custom object in JavaScript?를 참조하십시오. (내가 너무 많은 시간 그것을 입력을 낭비하기 때문에뿐만 아니라, 그것을 연결할 수 있음!)

이 :

Dog.prototype = 새로운 동물을();

은 일반적으로 피해야합니다. 당신은 예제/튜토리얼 코드에서 그것을 보았습니다,하지만 그것은 인스턴스에 클래스를 기반으로하고 있기 때문에 무서운 혼란스럽고 잘못된 방법으로 생성 된 인스턴스입니다 : name은 정의되지 않았습니다. 더 복잡한 생성자는 그런 종류의 일에서 화를 낼 것입니다.

Object.prototype.inherit =

는 건설을위한 더 나은 방법이지만, Object에 아무것도 프로토 타입은 매우 가난한 맛 간주됩니다. 사소한 맵으로 오브젝트를 엉망으로 만들거나 다른 코드를 깨뜨릴 위험이 있습니다. 이 도우미 함수를 다른 곳에 넣을 수 있습니다. Function.prototype.subclass.

prototype.constructor를

개인적으로

(파이어 폭스와 다른 브라우저에서 구현 된;하지 IE의 JScript를) constructor 자바 스크립트에서 특별한 의미를 가지고 있기 때문에 내가 피하기 위해 경향이, 그 의미는 아니다 어떤 constructor이 여기에서 무엇을 기대합니까? 그것은 혼란스럽고 거의 항상 최고의 피할 수 있습니다. 따라서 클래스 시스템의 인스턴스에 생성자 함수에 대한 링크를 포함하면 다른 이름으로 지정하는 것이 좋습니다.

+0

의견에 감사드립니다. 나는 Object.prototype.inherit에 동의하며 에세이에서이 문제를 언급한다고 생각하지만 적어도 코드 주석에 추가하거나 래퍼 함수에 넣을 것입니다. 그것을 잡아 주셔서 감사합니다! Dog.prototype = new Animal() 나중의 패턴에서 대안을 제시합니다. 귀하의 의견을 보내 주셔서 감사합니다. – Rob

+0

나는 Object를 clobbering하는 것과는 반대로 helper 객체를 사용하기 위해 코드를 업데이트했다 (위의 코드 목록을 편집했다). – Rob

+0

그래, 돌아가서 나의 에세이를 읽었고 나는 '물론 원숭이 패치 객체를 필요로하는 것은 아무것도 없다. 원한다면 그러한 기능을 반환하는 래퍼를 만들 수있다.' 그냥 평범한 게으른 !!! 나를 "옳은 일"으로 만들어 주셔서 감사합니다! – Rob

0

dev/web/stuff 폴더에 다양한 상속 패턴이 적어도 6 개 이상 구현되어 있지만 대부분 장난감입니다.

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 seenimplementation of classes을했습니다.

+0

안녕하세요 Christoph! 감사. 따라서 파생 메소드는 실제로 상속 메소드와 동일합니다. 단, 클로저를 반환하고 '자체'를 호출한다는 점만 다릅니다. 패턴은 또한 constructor stealing을 사용합니다 - 저는 이것이 본질적으로 조합 상속 패턴 (prototypal 및 constructor 훔친 결합)이라고 생각합니다. 나는 조금 더 오랫동안 다른 패턴을 쳐다보아야 할 것입니다 - 저는 오늘 밤 그걸 가지고 놀고 다시보고 할 것입니다 ;-) Christoph를 공유해 주셔서 감사합니다. – Rob

1

내 회사의 동료가 java와 같은 상속을 수행 할 라이브러리 http://www.uselesspickles.com/class_library/을 개발했습니다. Rajendra의 제안보다 더 섹시하다고 생각합니다. 구문이 더 깨끗해 보입니다.

나는 그것에 접근하는 여러 가지 방법을 보여주는 기사를 썼지 만, 알려진 나쁜 습관을 피하는 것을 확실히 한 기사를 썼다. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html, 라이브러리를 다운로드하고 싶지는 않지만 단지 필요한 코드를 복사하여 붙여 넣기 만하면됩니다.

1

JavaScript 생성자는 어떤 객체라도 반환 할 수 있습니다 (반드시 일 필요는 없습니다). "실제"인스턴스 객체의 "실제"메소드에 프록시 메소드가 포함 된 프록시 객체를 반환하는 생성자 함수를 만들 수 있습니다. 이것은 복잡하게 들릴지 모르지만 그렇지 않습니다. 여기에 코드입니다 :

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/

0

여기에 늦어도 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 
// 
} 
관련 문제