资讯专栏INFORMATION COLUMN

javascript:原型链继承和构造函数继承

missonce / 1940人阅读

摘要:构造函数继承在子类的构造函数中,通过或的形式,调用父类构造函数,以实现继承。所以,其实多带带使用原型链继承或者借用构造函数继承都有自己很大的缺点,最好的办法是,将两者结合一起使用,发挥各自的优势。使指向自己而不是指向构造函数

原型链继承

子类的所有实例都共享着原型上的所有属性和方法。通过子类实例,可以访问原型上的属性,但是,不能重写原型上的属性。

//定义一个学生类
function Student(stuID, schoolName) {
    this.stuID = stuID;
    this.schoolName = schoolName;
}
//所有学生都有这样一个特征
Student.prototype.characteristic = "年轻有朝气";
            
var stu1 = new Student(1001,"第一小学");
console.log(stu1.stuID);                //1001
console.log(stu1.characteristic);       //"年轻有朝气"
            
//重写characteristic
stu1.characteristic = "活泼可爱";
console.log(stu1.characteristic);       //"活泼可爱"

var stu2 = new Student(1002,"第一小学");
console.log(stu2.characteristic);       //"年轻有朝气"
console.log(Student.prototype);         //{characteristic: "年轻有朝气"}

上面这段代码表明:
通过stu1.characteristic = "活泼可爱";并没有改变原型上的属性值。

当实例中,存在和原型中同名的属性时,会自动屏蔽原型上的同名属性。stu1.characteristic = = "活泼可爱" 实际上是给实例stu1添加了一个本地属性 characteristic,所以当我们再次访问stu1.characteristic 时,访问的是实例的本地属性,而不是原型上的characteristic属性(它因和本地属性名同名已经被屏蔽了)。

原型上的 characteristic 值是一个基本类型的值,如果是一个引用类型呢?这其中又会有一堆小九九。

其实原型上任何类型的值,都不会被实例所重写。在实例上设置与原型上同名属性的值,只会在实例上创建一个同名的本地属性。但是,原型上引用类型的值可以通过实例进行修改,而且所有的实例访问到的该引用类型的值也会随之改变。(不是很明白,既然引用类型的值都能被修改了,那么为什么还说不会被实例重写??难道修改!= 重写)

//定义一个学生类
function Student(stuID, schoolName) {
    this.stuID = stuID;
    this.schoolName = schoolName;
}
//所有学生都有这样一个特征
Student.prototype.characteristic = "年轻有朝气";
Student.prototype.examItems = ["语文","数学","英语"];  //考试项目


var stu1 = new Student(1001, "第一小学");
console.log(stu1.examItems); //["语文","数学","英语"]

//修改examItems
stu1.examItems.push("科学");
console.log(stu1.examItems); //["语文","数学","英语","科学"]

var stu2 = new Student(1002, "第一小学");
console.log(stu2.examItems); //["语文","数学","英语","科学"]

原型上任何类型的属性值都不会通过实例被重写,但是引用类型的属性值会受到实例的影响而修改。

构造函数继承

在子类的构造函数中,通过 apply( ) 或 call( )的形式,调用父类构造函数,以实现继承。

//定义一个超类/父类: 人
function Person (name, age) {
    //人都有姓名,年龄,会吃饭,会睡觉
    //传入出生年份 year,自动计算年龄
    this.name = name;
    this.age = age;
    this.eat = function () {
        alert("吃饭");
    }
    this.sleep = function () {
        alert("睡觉");
    }
}

//定义一个子类: 学生
//学生Student也是人,自然要继承超类 Person 的所有属性和方法
//学生都应当有姓名、年龄、会吃饭、会睡觉
//当然学生也有自己的一些属性:学号,学校名称等,和方法,比如都要去做一件事:写作业
function Student (stuID, schoolName, name, age) {
    this.stuID = stuID;
    this.schoolName = schoolName;
    //用call调用 Person,以实现继承
    Person.call(this, name, age);
}

Student.prototype.doHomework = function () {
    alert("做作业");
}

//实例化一个学生
var stu1 = new Student(1001, "第一小学", "王宝宝",20);
console.log(stu1.stuID);       //1001
console.log(stu1.schoolName);  //"第一小学"
console.log(stu1.name);        //"王宝宝"
console.log(stu1.age);         //20
stu1.eat();                    //"吃饭"
stu1.sleep();                  //"睡觉"
stu1.doHomework();             //"做作业"

在子类构造函数中,我们通过 call 的方式调用了父类构造函数 Person实现了继承。别忘了,函数只不过是一段可以在特定作用域执行代码的特殊对象,我们可以通过 call 方法指定函数的作用域。

在 stu1 = new Student() 构造函数时,Student 内部 this 的值指向的是 stu1, 所以 this.stuID =stu1.stuID, 所以 Person.call(this, name, age) 就相当于Person.call(stu1, "王宝宝", 20),就相当于 stu1.Person("王宝宝",20)。最后,stu1 去调用 Person 方法时,Person 内部的 this 指向就指向了 stu1。那么Person 内部this 上的所有属性和方法,都被拷贝到了stu1上。

总之,在子类函数中,通过call() 方法调用父类函数后,子类实例 stu1, 可以访问到 Student 构造函数和 Person 构造函数里的所有属性和方法。这样就实现了子类向父类的继承。

缺点

这种形式的继承,每个子类实例都会拷贝一份父类构造函数中的方法,作为实例自己的方法,比如 eat()。这样做,有几个缺点:

  1. 每个实例都拷贝一份,占用内存大,尤其是方法过多的时候。

  2. 方法都作为了实例自己的方法,当需求改变,要改动其中的一个方法时,之前所有的实例,他们的该方法都不能及时作出更新。只有后面的实例才能访问到新方法。

所以,其实多带带使用原型链继承或者借用构造函数继承都有自己很大的缺点,最好的办法是,将两者结合一起使用,发挥各自的优势。

例如:

function OSTAccountDC() {
    OSTBaseDC.call(this);
}

OSTAccountDC.prototype = new OSTBaseDC(); //通过OSTBaseDC创建一个实例,避免改变父类构造函数的属性,而只是在本地创建一个属性。

OSTAccountDC.prototype.constructor = OSTAccountDC; //使this指向自己OSTAccountDC,而不是指向构造函数OSTBaseDC

OSTAccountDC.prototype.accountRequest = function(callback){
    // do something
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93754.html

相关文章

  • JavaScript系列--浅析原型继承

    摘要:综上所述有原型链继承,构造函数继承经典继承,组合继承,寄生继承,寄生组合继承五种方法,寄生组合式继承,集寄生式继承和组合继承的优点于一身是实现基于类型继承的最有效方法。 一、前言 继承是面向对象(OOP)语言中的一个最为人津津乐道的概念。许多面对对象(OOP)语言都支持两种继承方式::接口继承 和 实现继承 。 接口继承只继承方法签名,而实现继承则继承实际的方法。由于js中方法没有签名...

    draveness 评论0 收藏0
  • JavaScript面向对象编程——继承

    摘要:想要解决这样的问题的话,可以借助构造函数也可以叫做伪造对象或经典继承。通过借助构造函数实现对实例对象的属性和继承。 原型链 原型链是什么 构造函数或构造器具有prototype属性 对象具有__proto__属性 这就是之前学习的原型如果构造函数或对象A A的原型指向构造函数或对象B B的原型在指向构造函数或对象C 以此类推 最终的构造函数或对象的原型指向Object的原型 由此形成一...

    desdik 评论0 收藏0
  • 彻底理解Javascript中的原型继承

    摘要:在节中,我们学习到了通过构造函数创建对象的三个重要步骤,其中的一步是把构造函数的对象设置为创建对象的原型。利用而不是直接用创建一个实例对象的目的是,减少一次调用父构造函数的执行。 JavaScript语言不像面向对象的编程语言中有类的概念,所以也就没有类之间直接的继承,JavaScript中只有对象,使用函数模拟类,基于对象之间的原型链来实现继承关系,ES6的语法中新增了class关键...

    ziwenxie 评论0 收藏0
  • 深入javascript——原型继承

    摘要:在使用原型链实现继承时有一些需要我们注意的地方注意继承后的变化。在了解原型链时,不要忽略掉在末端还有默认的对象,这也是我们能在所有对象中使用等对象内置方法的原因。 在上一篇post中,介绍了原型的概念,了解到在javascript中构造函数、原型对象、实例三个好基友之间的关系:每一个构造函数都有一个守护神——原型对象,原型对象心里面也存着一个构造函数的位置,两情相悦,而实例呢却又...

    UCloud 评论0 收藏0
  • 彻底搞懂JavaScript中的继承

    摘要:这正是我们想要的太棒了毫不意外的,这种继承的方式被称为构造函数继承,在中是一种关键的实现的继承方法,相信你已经很好的掌握了。 你应该知道,JavaScript是一门基于原型链的语言,而我们今天的主题 -- 继承就和原型链这一概念息息相关。甚至可以说,所谓的原型链就是一条继承链。有些困惑了吗?接着看下去吧。 一、构造函数,原型属性与实例对象 要搞清楚如何在JavaScript中实现继承,...

    _ivan 评论0 收藏0
  • JavaScript面向对象---原型继承

    摘要:因为这造成了继承链的紊乱,因为的实例是由构造函数创建的,现在其属性却指向了为了避免这一现象,就必须在替换对象之后,为新的对象加上属性,使其指向原来的构造函数。这个函数接收两个参数子类型构造函数和超类型构造函数。 最近一直在研究js面向对象,原型链继承是一个难点,下面是我对继承的理解以下文章借鉴自CSDN季诗筱的博客 原型链继承的基本概念: ES中描述了原型链的概念,并将原型链作为实现...

    vspiders 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<