为了账号安全,请及时绑定邮箱和手机立即绑定

js学习笔记 3-02 面向对象的程序设计--对象的继承

标签:
JavaScript

本笔记转贴 简书 ==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
点击查看更多内容
2人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消