JavaScript面向对象的程序设计——“对象继承”的注意要点 - 前端学习笔记
继承
继承分为接口继承和实现继承;接口继承只继承方法签名,实现继承则继承实际的方法;由于函数没有签名,所以ECMAScript 中没有接口继承,只能依靠原型链来实现实现继承。
原型链
基本思想是利用原型链让一个引用类型继承另一个引用类型的属性和方法:
如:Person2.prototype 是Person1.prototype 的继承,那么(“--->” 意为“指向”):
Person2.prototype - [[Prototype]] ---> Person1.prototypePerson2 - prototype ---> Person2person2 - [[Prototype]] ---> Person2.prototype//Person2.prototype 无 constructorPerson1.prototype - cunstructor ---> Person1Person1 - prototype ---> Person1.prototypeperson1 - [[Prototype]] ---> Person1.prototype
具体方法
具体如何继承呢:
function People(){}; //原始的People.prototype.sayWorld = function(){ return "World people"};function Person(){}; //继承的Person.prototype = new People(); //这里应该注意要先继承Person.prototype.sayNation = function(){ //然后再修改prototype return "Chinese people"};var person = new Person(); //实例console.log(person.sayNation()); //Chinese peopleconsole.log(person.sayWorld()); //World people
一定要注意!!!给原型添加方法的代码一定要放在替换原型的语句之后!!!
再举个例子:
function WorldPeople(){};WorldPeople.prototype = { constructor: WorldPeople, color: "", say: function(){ return "People in the Earth." }, friends: ["Oliver","Alice","Troy"]};function Chinese(){};Chinese.prototype = new WorldPeople();Chinese.prototype.color = "yellow";Chinese.prototype.say = function(){ return "i am Chinese."};var person1 = new Chinese();console.log(person1.friends.toString());console.log(person1.color);console.log(person1.say()); /*[Log] Oliver,Alice,Troy (repetition.html, line 163)[Log] yellow (repetition.html, line 164)[Log] i am Chinese. (repetition.html, line 165)*/
确定原型和实例的关系
用instanceof
操作符和insPrototypeOf()
方法即可,如:
console.log(person1 instanceof Object); //true;console.log(person1 instanceof WorldPeople); //true;console.log(person1 instanceof Chinese); //true;console.log(Object.prototype.isPrototypeOf(person1)); //trueconsole.log(WorldPeople.prototype.isPrototypeOf(person1)); //trueconsole.log(Chinese.prototype.isPrototypeOf(person1)); //true
谨慎定义方法
一定要记得,给原型添加方法的代码一定要放在替换原型的语句之后!
还要记得,通过原型链实现继承时,不能使用字面两创建原型方法,因为这样会重写原型链!
原型链的问题
包含引用类型值的原型,该属性会被所有实例共享;
在创建子类型的实例时,不能向超类型的构造函数中传递参数;
对于第一种问题:
function People(){}People.prototype.friends = ["Alice","Oliver"];function Person(){};Person.prototype = new People();var person1 = new Person();var person2 = new People();person1.friends.push("Troy");console.log(person1.friends);console.log(person2.friends); //两者完全相同
有什么解决办法呢:
借用构造函数(不推荐使用)
被称为“借用构造函数”的技术或伪造对象或经典继承。如:
function People(){ this.friends = ["Alice","Oliver"];}function Person(){ People.call(this); //继承了People}//这里就不必写Person.prototype = new People()var person1 = new Person();var person2 = new Person();person1.friends.push("Troy");console.log(person1.friends); //["Alice", "Oliver", "Troy"]console.log(person2.friends); //["Alice", "Oliver"]
该方法的主要优势就是可以在子类型构造函数中向超类型构造函数传递参数。
又如:
function SuperType(name){ this.name = name;}function SubType(){ SuperType.call(this,"Oliver"); //这里不仅仅继承了SuperType,而且还向它传递了参数 this.age = 18;}var person = new SubType();console.log(person.name); //Oliverconsole.log(person.age); //18
由于函数不可复用等问题,不推荐使用。
组合继承(最常用的模式)
也叫做伪经典继承。如:
//不通用的属性function SuperType(name){ this.name = name; this.colors = ["Blue","Red","Black"];}//通用的方法SuperType.prototype.sayName = function(){ return (this.name);};//继承属性并新增属性function SubType(name,age){ SuperType.call(this,name);//继承属性 this.age = age;//新增属性}//继承方法并新增方法SubType.prototype = new SuperType();//继承方法SubType.prototype.constructor = SubType;SubType.prototype.sayAge = function(){//新增方法 return (this.age);};var person1 = new SubType("Oliver",18);var person2 = new SubType("Troy",24);person1.colors.pop();console.log(person1.colors);console.log(person2.colors);console.log(person1.sayName() + person1.sayAge());console.log(person2.sayName() + person2.sayAge());/*[Log] ["Blue", "Red"] (repetition.html, line 255)[Log] ["Blue", "Red", "Black"] (repetition.html, line 256)[Log] Oliver18 (repetition.html, line 257)[Log] Troy24 (repetition.html, line 258)*/
最常用的方法。再举个例子:
function People(name,age){ this.name = name; this.age = age; this.friends = [];}People.prototype.friendsList = function(){ document.write(this.friends.toString());};function Person(name,age,color,job){ People.call(this,name,age); this.color = color; this.job = job;}Person.prototype = new People();Person.prototype.constructor = Person;Person.prototype.sayInfo = function(){ document.write(this.name + this.age + this.color + this.job);};var person1 = new Person("Oliver",18,"yellow","Hero");person1.friends.push("Alice");person1.sayInfo(); //Oliver18yellowHeroperson1.friendsList(); //Alicevar person2 = new Person("Troy",24,"White","Fighter");person2.friends.push("Oliver","Islan");person2.sayInfo(); //Troy24WhiteFighterperson2.friendsList(); //Oliver,Islan
平时用这个方法已经足够。又如:
function Cars(name){ this.name = name; this.hasColor = ["blue","black"];}Cars.prototype.sayName = function(){ console.log(this.name);};function Car(name,color){ Cars.call(this,name); this.color = color;}Car.prototype = new Cars();Car.prototype.constructor = Car;Car.prototype.sayColor = function(){ console.log(this.color);};var benz = new Car("Benz-C200","Black");benz.hasColor.push("red");benz.sayName();benz.sayColor();console.log(benz.hasColor);var benz2 = new Car("Benz-C180","White");benz2.hasColor.push("white");benz2.sayName();benz2.sayColor();console.log(benz2.hasColor); /*[Log] Benz-C200 (repetition.html, line 309)[Log] Black (repetition.html, line 319)[Log] ["blue", "black", "red"] (repetition.html, line 325)[Log] Benz-C180 (repetition.html, line 309)[Log] White (repetition.html, line 319)[Log] ["blue", "black", "white"] (repetition.html, line 330)*/
结合创建对象和继承对象,来一个比较吧:
重要!
重要!
重要!
//组合使用构造函数模式和原型模式-创建对象function Person(name,age){ this.name = name; this.age = age; this.friendsList = ["Alice","Islan"];}Person.prototype.friends = function(){ console.log(this.friendsList.toString());};var person1 = new Person("Oliver",18);var person2 = new Person("Troy",24);person1.friendsList.pop();person1.friends(); //Aliceperson2.friends(); //Alice,Islan
//组合继承-继承对象function Person(name,age){ this.name = name; this.age = age; this.friendsList = ["Alice","Islan"];}Person.prototype.friends = function(){ console.log(this.friendsList.toString());};function Info(name,age,job){ Person.call(this,name,age); this.job = job;}Info.prototype = new Person();Info.prototype.constructor = Info;Info.prototype.sayJob = function(){ console.log(this.job);};var person1 = new Info("Oliver",18,"Master");var person2 = new Info("Troy",24,"Hero");person1.friendsList.pop();person1.friends(); //Aliceperson2.friends(); //Alice,Islanperson1.sayJob(); //Masterperson2.sayJob(); //Hero
对比一下,就可以看出,继承属性主要应用到call 操作符给超类型构造函数传递参数;而继承方法则要注意不可使用字面量语法。
以上
原型式继承
通常只是想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。共享相应的引用类型的值的属性。
语法是:
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: "Oliver", friends: ["Alice","Islan"]};var anotherPerson = object(person);anotherPerson.name = "Troy";anotherPerson.friends.push("Ellen");var yetAnotherPerson = object(person);yetAnotherPerson.name = "Ellen";yetAnotherPerson.friends.push("Troy","Oliver");console.log(person.friends);
又如:
function object(o){ function F(){}; F.prototype = o; return new F();}var person = { name: "Oliver", friends: ["Alice","Islan"]};var anotherPerson = object(person);anotherPerson.name = "Troy";anotherPerson.friends.push("Oliver");console.log(person.friends);["Alice", "Islan", "Oliver"]
这种方法比较简单,只是想让person 和anotherPerson 保持类似并共享引用类型的值的属性。
寄生式继承(不能做到函数复用而导致效率降低)
创建一个封装继承过程的函数而已:
function createAnotherObj(obj){ var clone = obj; clone.sayHi = function(){ console.log("hi"); }; return clone;}var person = { name: "Troy", friends: ["Alice"]};var anotherObj = createAnotherObj(person);anotherObj.sayHi();anotherObj.name = "Oliver";anotherObj.friends.push("Ellen");console.log(person.friends);console.log(anotherObj.friends); //两个完全一样
寄生组合式继承(最理想的继承范式)
基本逻辑就是首先创建一个超类型原型的一个副本;然后为副本添加constructor 属性;最后把副本赋值给子类型的原型。如:
function inheritPrototype(SubType,SuperType){ var prototype = Object(SuperType.prototype); prototype.constructor = SubType; SubType.prototype = prototype;}function SuperType(name){ this.name = name; this.color = ["red","yellow"];}SuperType.prototype.list = function(){ console.log(this.color.toString());};function SubType(name,age){ SuperType.call(this,name); this.age = age;}inheritPrototype(SubType,SuperType);SubType.prototype.sayName = function(){ console.log(this.name);};var type1 = new SubType("Oliver",18);var type2 = new SubType("Troy",24)type2.color.pop();type1.list(); //red,yellowtype2.list(); //red
应该常用这种模式,比较完善。