js学习笔记 3-02 面向对象的程序设计--对象的继承
本笔记转贴 简书 ==minxuan==(http://www.jianshu.com/p/0045cd01e0be)的文章
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了“null”意外)
- 所有的引用类型(数组、对象、函数),都有一个__proto__属性([[prototype]]隐式原型),属性值是一个普通的对象
- 所有的函数,都有一个 prototype 属性(显示原型),属性值也是一个普通的对象
- 所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的 ”prototype“ 属性值
构造函数 原型对象 实例 关系
对象的继承 一、原型链继承function SuperType(){
this.superproperty = true;
}
SuperType.prototype.getSuperValue = function(){
return this.superproperty;
}
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType(); //原型链继承
SubType.prototype.getSubValue =function(){
return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue());//true
通过 ==SubType.prototype = new SuperType()==,实现了以下三点:
A:重写了SubType的原型,让子类原型和子类构造函数之间断开联系。
B:子类原型是父类的实例,其原型指针[[prototype]]指向了父类的原型对象,这样子类就可以沿着原型链访问到父类的方法getSuperValue()。
C:子类原型是父类实例,通过父类构造函数,子类原型继承了父类的属性superproperty。
最终,子类继承了父类的方法和属性。
原型链继承的问题:
二、借用构造函数父类的实例属性成了子类的原型属性,如上面的superproperty,会被子类所有实例共享。该属性是基本类型值时没有问题,但如果是引用类型值(比如数组),那么修改实例1的该属性(比如向数组push一个新值),实例2也会跟着改变。
也就是说,实例们只有共性,不能保持个性。
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this,"Nicholas");
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas"
alert(instance.age); //29
通过 ==SuperType.call(this)==
- 在子类构造函数中调用了父类的构造函数
- 创建子类实例会执行子类的构造函数(含父类的构造函数),也就完成了继承。
- 当然,子类和父类的原型是没有关系的,子类实例不能访问父类原型对象中的属性和方法。
构造函数继承的问题:
三、组合继承方法在构造函数中定义,无法实现函数复用。比如父类中有一个方法getName(),则每次创建子类实例的时候,都要创建一个新的getName(),通过instance1.getName() !== instance.getName()就可以验证这一点。
这就是说,实例们保持了个性,却不能共享方法。
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name,age){
//继承属性
SuperType.call(this,name); //第二次调用SuperTyper()
this.age = age;
}
//继承方法
SubType.prototype = new SuperType(); //第一次调用SuperTyper()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType("Nicholas",29);
instance1.colors.push("black");
alert(instance1.colors); // "red","blue","green","black"
instance1.sayName(); //"Nicholas"
instance1.sayAge(); //29
var instance2 = new SubType("Greg",27);
alert(instance2.colors); // "red","blue","green"
instance2.sayName(); //"Greg"
instance2.sayAge(); //27
使用原型链实现对父类原型属性和方法的继承,而通过借用构造函数实现对父类实例属性的继承;结合两者的优点,让实例们即保持个性,又共享方法。
组合继承的问题:
四、原型式继承两次调用父类的构造函数。第一次(A):SubType.prototype=new SuperType(),子类原型对象取得了父类的实例属性。
第二次(B):SuperType.call(this),创建子类实例时,调用父类构造函数,重写实例属性,屏蔽了原型对象上同名属性。
function object(o){
function F(){};
F.prototype = o;
return new F();
}
var person = {
name:"Nicholas",
friends:["Shelby","Court","Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
- 以一个对象为基础,通过object()函数进行浅复制,再将得到的对象实例加以修改。
- 可以看到,这种继承方法是没有父类和子类的,只是通过复制对象来得到副本。
ES5有 Object.create() 方法,是object()的规范化,可以传入两个参数:要复制的对象和额外的属性对象(如{name:{value:Greg}},这种方式会覆盖基础对象上的同名属性)。
var obj = Object.create(o);//obj原型对象[[prototype]]是o的一个副本;
原型式继承的问题:
五、寄生式继承和原型链继承一样,继承的属性由所有实例共享,改动一个实例原型对象上的引用类型属性的值时,所有实例对该属性的取值都会改变。
function object(o){
function F(){};
F.prototype = o;
return new F();
}
function createAnother(original){
var clone = object(original);//object()函数创建对象
clone.sayHi = function(){ //增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name:"Nicholas";
friends:["Shelby","Court","Van"];
} //基础对象
var anotherPerson = createAnother(person); //新对象
anotherPerson.sayHi(); //"hi"
//anotherPerson原型对象(anotherPerson.__proto__)是person的一个副本,所以anotherPerson.__proto__上有person的所有属性和方法
//同时anotherPerson还有了自身的属性和方法 如 sayHi()
六、寄生组合式继承寄生式和原型式方法相同,都是复制一个基础对象来得到新对象,不同的是它将对象实例的修改放到也放到函数中,将整个过程(创建、增强、返回)封装了起来。
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; //指定对象
}
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name,age){
//继承属性
SuperType.call(this,name); //只调用一次SuperTyper()
this.age = age;
}
//继承方法
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
}
对象继承 例结顾名思义,这种继承模式就是寄生式(复制)+组合式(原型链+构造函数),将几种方法组合起来。==解决了组合式继承两次调用父类构造函数的问题==。(白话一点就是子类的原型对象不必再从父类的构造函数实例过来一些会被子类自己屏蔽的属性和方法啦)
(慕课网-前端跳槽面试必备技巧)
//借助构造函数实现继承
function Parent1 () {
this.name = 'parent1';
}
Parent1.prototype.say = function () {
};
function Child1 () {
Parent1.call(this);
this.type = 'child1';
}
console.log(new Child1(), new Child1().say());
// 缺点:子类的实例对象无法继承父类的原型对象上的属性和方法
//借助原型链实现继承
function Parent2 () {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function Child2 () {
this.type = 'child2';
}
Child2.prototype = new Parent2();
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play);
s1.play.push(4);
//缺点:改变一个子类实例原型对象([[prototype]])上引用类型的值,会影响子类所有实例原型对象([[prototype]])上该引用类型属性的值
// 组合方式
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3 () {
Parent3.call(this);//第二次调用Parent3
this.type = 'child3';
}
Child3.prototype = new Parent3();//第一次调用Parent3
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);
//缺点:会调用两次父类构造函数
//组合继承的优化1
function Parent4 () {
this.name = 'parent4';
this.play = [1, 2, 3];
}
function Child4 () {
Parent4.call(this);
this.type = 'child4';
}
Child4.prototype = Parent4.prototype;//子构造函数原型对象是父构造函数原型对象的一个副本
var s5 = new Child4();
var s6 = new Parent4();
console.log(s5 instanceof Child4, s5 instanceof Parent4);
console.log(s5.constructor, s6.constructor);
Child4.prototype.constructor = Child4;
console.log(s5.constructor, s6.constructor);
//缺点:无法区分实例是由哪个构造函数直接实例来的, 如果改动 Child4.prototype.constructor 则也会影响父类原型对象的constructor(因为子构造函数原型对象是父构造函数原型对象的一个副本)
//组合继承的优化2
function Parent5 () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5 () {
Parent5.call(this);
this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);//子构造函数原型对象的[[prototype]]是父构造函数原型对象的一个副本
Child5.prototype.constructor = Child5;//不会影响父构造函数原型对象的constructor
共同学习,写下你的评论
评论加载中...
作者其他优质文章