자바스크립트 상속에 대해 설명하기로 약속했는데 지금까지 미뤄지고 있습니다. 더 이상 고민하지 않고 바로 본론으로 들어가겠습니다.
상속을 이해하고 싶다면 이미 객체지향 JavaScript에 대해 어느 정도 이해하고 있다는 뜻입니다. 그래도 이해가 되지 않는다면 "객체지향 JS 기본 설명, 팩토리 모드, 생성자 모드, 프로토타입 모드, 혼합 모드, 동적 프로토타입 모드》, JavaScript 상속을 완료하기 위해 일반적으로 사용되는 방법에 대해 이야기해 보겠습니다.
프로토타입 체인
JavaScript에서 상속을 구현하는 가장 간단한 방법은 프로토타입 체인을 사용하는 것입니다. 하위 유형의 프로토타입을 상위 유형의 인스턴스, 즉 "subtype.prototype = new parent type();"으로 지정하면 됩니다. 구현 방법은 다음과 같습니다.
// 为父类型创建构造函数 function SuperType() { this.name = ['wuyuchang', 'Jack', 'Tim']; this.property = true; } // 为父类型添加方法 SuperType.prototype.getSuerperValue = function() { return this.property; } // 为子类型创建构造函数 function SubType() { this.test = ['h1', 'h2', 'h3', 'h4']; this.subproperty = false; } // 实现继承的关键步骤,子类型的原型指向父类型的实例 SubType.prototype = new SuperType(); // 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,则方法为空 SubType.prototype.getSubValue = function() { return this.subproperty; } /* 以下为测试代码示例 */ var instance1 = new SubType(); instance1.name.push('wyc'); instance1.test.push('h5'); alert(instance1.getSuerperValue()); // true alert(instance1.getSubValue()); // false alert(instance1.name); // wuyuchang,Jack,Tim,wyc alert(instance1.test); // h1,h2,h3,h4,h5 var instance2 = new SubType(); alert(instance2.name); // wuyuchang,Jack,Tim,wyc alert(instance2.test); // h1,h2,h3,h4
위 코드는 프로토타입 체인을 통해 구현한 단순 상속임을 알 수 있지만, 테스트 코드 예시에는 여전히 몇 가지 문제가 남아 있습니다. 제 블로그 글 "객체지향 JS 기본 설명, 팩토리 모드, 생성자 모드, 프로토타입 모드, 하이브리드 모드, 동적 프로토타입 모드"를 본 아이들은 반드시 프로토타입 체인코드의 존재를 알고 있어야 한다고 믿습니다첫 번째 문제는 하위 유형의 프로토타입이 상위 유형의 인스턴스, 즉 하위 유형의 프로토타입에 포함된 상위 유형의 속성이므로 참조 유형 값의 프로토타입 속성이 모든 인스턴스에서 공유됩니다 . 위 코드의 instance1.name.push('wyc');는 이 문제의 존재를 증명할 수 있습니다. 프로토타입 체인의 두 번째 문제는 하위 유형의 인스턴스를 생성할 때 매개변수를 상위 유형의 생성자에 전달할 수 없다는 것입니다. 따라서 실제 개발에서는 프로토타입 체인만 단독으로 사용하는 경우가 거의 없습니다.
빌드 생성자
프로토타입 체인에 존재하는 두 가지 문제를 해결하기 위해 개발자들은 프로토타입 체인에 존재하는 문제를 해결하기 위해 차용 생성자라는 기술을 사용하기 시작했습니다. 이 기술의 구현 아이디어도 매우 간단합니다. 하위 유형의 생성자 내에서 상위 유형의 생성자를 호출하기만 하면 됩니다. 잊지 마세요. 함수는 특정 환경에서 코드를 실행하는 객체일 뿐이므로 apply() 또는 call() 메서드를 통해 생성자를 실행할 수 있습니다. 코드는 다음과 같습니다.
// 为父类型创建构造函数 function SuperType(name) { this.name = name; this.color = ['pink', 'yellow']; this.property = true; this.testFun = function() { alert('http://tools.jb51.net/'); } } // 为父类型添加方法 SuperType.prototype.getSuerperValue = function() { return this.property; } // 为子类型创建构造函数 function SubType(name) { SuperType.call(this, name); this.test = ['h1', 'h2', 'h3', 'h4']; this.subproperty = false; } // 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,则方法为空 SubType.prototype.getSubValue = function() { return this.subproperty; } /* 以下为测试代码示例 */ var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']); instance1.name.push('hello'); instance1.test.push('h5'); instance1.color.push('blue'); instance1.testFun(); // http://tools.jb51.net/ alert(instance1.name); // wuyuchang,Jack,Nick,hello // alert(instance1.getSuerperValue()); // error 报错 alert(instance1.test); // h1,h2,h3,h4,h5 alert(instance1.getSubValue()); // false alert(instance1.color); // pink,yellow,blue var instance2 = new SubType('wyc'); instance2.testFun(); // http://tools.jb51.net/ alert(instance2.name); // wyc // alert(instance2.getSuerperValue()); // error 报错 alert(instance2.test); // h1,h2,h3,h4 alert(instance2.getSubValue()); // false alert(instance2.color); // pink,yellow
위 코드에서 하위 유형 SubType의 생성자는 상위 유형 "SuperType.call(this, name);"을 호출하여 속성 상속을 구현하는 것을 볼 수 있습니다. 하위 유형의 인스턴스가 전달되었지만 새로운 문제가 다시 발생합니다. 상위 유형의 생성자 testFun에 메서드를 정의하고 상위 유형의 프로토타입에 getSuperValue라는 메서드를 정의한 것을 볼 수 있습니다. 그러나 이 하위 유형을 인스턴스화한 후에도 여전히 상위 유형의 프로토타입에 정의된 getSuperValue 메소드를 호출할 수 없습니다. 이는 상위 유형의 생성자 메소드인 testFun만 호출할 수 있습니다. . 이는 객체를 생성할 때 생성자 패턴만 사용하여 함수를 재사용할 수 없게 만드는 것과 같습니다. 이러한 문제점을 고려하여 생성자를 빌리는 기법은 단독으로 사용되는 경우가 거의 없다.
결합 상속(프로토타입 체인 차용 생성자)
이름에서 알 수 있듯이 결합 상속은 프로토타입 체인을 사용하는 것과 생성자를 차용하는 것의 장점을 결합한 패턴입니다. 구현도 매우 간단하기 때문에 양쪽의 장점, 즉 프로토타입 체인 상속 방식과 생성자 상속 속성 을 확실히 결합한 것입니다. 구체적인 코드 구현은 다음과 같습니다.
// 为父类型创建构造函数 function SuperType(name) { this.name = name; this.color = ['pink', 'yellow']; this.property = true; this.testFun = function() { alert('http://tools.jb51.net/'); } } // 为父类型添加方法 SuperType.prototype.getSuerperValue = function() { return this.property; } // 为子类型创建构造函数 function SubType(name) { SuperType.call(this, name); this.test = ['h1', 'h2', 'h3', 'h4']; this.subproperty = false; } SubType.prototype = new SuperType(); // 在此处给子类型添加方法,一定要在实现继承之后,否则会在将指针指向父类型的实例,则方法为空 SubType.prototype.getSubValue = function() { return this.subproperty; } /* 以下为测试代码示例 */ var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']); instance1.name.push('hello'); instance1.test.push('h5'); instance1.color.push('blue'); instance1.testFun(); // http://tools.jb51.net/ alert(instance1.name); // wuyuchang,Jack,Nick,hello alert(instance1.getSuerperValue()); // true alert(instance1.test); // h1,h2,h3,h4,h5 alert(instance1.getSubValue()); // false alert(instance1.color); // pink,yellow,blue var instance2 = new SubType('wyc'); instance2.testFun(); // http://tools.jb51.net/ alert(instance2.name); // wyc alert(instance2.getSuerperValue()); // true alert(instance2.test); // h1,h2,h3,h4 alert(instance2.getSubValue()); // false alert(instance2.color); // pink,yellow
위 코드는 SuperType.call(this, name);을 통해 상위 유형의 속성을 상속하고 SubType.prototype = new SuperType();을 통해 상위 유형의 메서드를 상속합니다. 위의 코드는 프로토타입 체인과 빌린 생성자에서 발생하는 문제를 편리하게 해결하며 JavaScript에서 가장 일반적으로 사용되는 인스턴스 상속 방법이 되었습니다. 하지만 혼합 모드에도 단점이 있는 것은 아닙니다. 위의 코드에서 메서드를 상속할 때 상위 유형의 속성이 실제로 상속되는 것을 볼 수 있습니다. 그러나 이때 참조 유형이 공유되므로 이를 호출합니다. 하위 유형의 생성자에서 두 번 발생합니다. 부모 유형의 생성자는 부모 유형의 속성을 상속하고 프로토타입에 상속된 속성을 덮어씁니다. 분명히 생성자를 두 번 호출할 필요가 없지만 해결할 수 있는 방법이 있습니까? 이 문제를 해결할 때 다음 두 가지 패턴을 살펴보겠습니다.
프로토타입 상속
프로토타입 상속의 구현 방법은 일반 상속의 구현 방법과 다릅니다. 프로토타입 상속은 엄밀한 의미에서 생성자를 사용하지 않고, 대신 프로토타입을 사용하여 사용자 정의 유형을 생성하지 않고 기존 객체를 기반으로 새 객체를 생성합니다. . 구체적인 코드는 다음과 같습니다.
function object(o) { function F() {} F.prototype = o; return new F(); }
코드 예:
/* 原型式继承 */ function object(o) { function F() {} F.prototype = o; return new F(); } var person = { name : 'wuyuchang', friends : ['wyc', 'Nicholas', 'Tim'] } var anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Bob'); var anotherPerson2 = object(person); anotherPerson2.name = 'Jack'; anotherPerson2.friends.push('Rose'); alert(person.friends); // wyc,Nicholas,Tim,Bob,Rose
기생 상속
/* 寄生式继承 */ function createAnother(original) { var clone = object(original); clone.sayHi = function() { alert('hi'); } return clone; }
사용 예:
/* 原型式继承 */ function object(o) { function F() {} F.prototype = o; return new F(); } /* 寄生式继承 */ function createAnother(original) { var clone = object(original); clone.sayHi = function() { alert('hi'); } return clone; } var person = { name : 'wuyuchang', friends : ['wyc', 'Nicholas', 'Rose'] } var anotherPerson = createAnother(person); anotherPerson.sayHi();
寄生组合式继承
前面说过了JavaScrip中组合模式实现继承的缺点,现在我们就来解决它的缺点,实现思路是,对于构造函数继承属性,而原型链的混成形式继承方法,即不用在继承方法的时候实例化父类型的构造函数。代码如下:
function object(o) { function F() {} F.prototype = o; return new F(); } /* 寄生组合式继承 */ function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; }
而在使用时只需要将组合模式中的“SubType.prototype = new SuperType();”这行代码替换成inheritPrototype(subType, superType);即可。寄生组合式继承的高效率体现在它只调用了一次父类型构造函数,避免了创建不必要的或多余的属性。与此同时,原型链还能保持不变,因此,还能够正常使用instanceof和isPrototypeof()。这也是目前来说最理想的继承方式了,目前也在向这种模式转型。(YUI也使用了这种模式。)
此博文参考《JavaScript高级程序设计第3版》,代码为经过改写,更具体,并加了注释使大家更易懂。如对JS继承方面有独到见解的童鞋不别吝啬,回复您的见解供大家参考!