JavaScript中的继承实现(1)

前言:学习过面向对象语言(java、c++)的童鞋都了解面向对象的概念,也肯定知道面向对象语言的特征:封装、继承和多态,但JavaScript并非面向对象,而是基于对象,这些概念我们无法直接应用到JavaScript的对象机制中,这节我们主要学习JavaScript开发者是如何曲线救国实现面向对象语言的继承特性

1.类的概述?

  类:类是面向对象语言的基础。类好比模型,比如说动物是一个类别很空泛不具体,拥有很多的特征,但是我们具体不知道它是会飞、爬、游。

  对象:那么对象好比是类的一个具体体现,比如狗这个动物就是真实存在的,所有的特征是固定的,会跑、会汪汪叫等。

  面向对象:面向对象更多强调的是数据和操作数据的行为是互相关联的,具体的实现就是将数据和关联的行为封装为一个类,基于类完成面向对象语言的开发。

  举例:表示单词或短语的字符通常称为字符串。但我们往往关心的不是数据本身,而是和数据关联的操作。所以就将数据(字符)和行为(计算长度、增删数据等)封装成了统一的String类,所有的字符串就是String类的一个实例。

2.何为继承?继承有什么好处?

  在现实生活中我们常会使用继承一词,比如:小鸟继承了妈妈的特征。可以继承妈妈的属性(羽毛、四肢等),也可以继承妈妈的行为(鸣叫、飞等),程序中的继承与其类似。

  继承:子类在不需要重写父类属性和方法的前提下继承了父类的属性和方法,并且可以直接调用它们。

  好处:提高了代码的复用性。

3.JavaScript中继承的实现

  在其他语言(java)中,子类继承父类得到只是父类的一个副本,类的继承实质上就是类的复制。但是在JavaScript中只有对象,对象之间不存在类之间复制的功能。那么JavaScript开发者又是如何实现面向对象语言的继承行为的。

  1>构造函数继承

    在子类中通过call/apply方法完成子类对父类的继承。

        // 定义名为Person的构造函数,构造函数只绑定属性name
        function Person(name) {
            this.name = name
            this.getName = function() {
                return this.name;
            }
        }

        // 定义构造函数Bar(),定义属性name和label
        function Student(name, hobby){
            // 通过call改变this的指向,继承Person中的属性name和方法getName()
            Person.call(this, name)
            this.hobby = hobby;
            this.getHobby = function () {
                return this.hobby
            }
        }
        var a = new Student("小明", "basketBall");
        console.log(a.getName());
        console.log(a.getHobby());

  上例中子类Student通过Person.call(this.name)继承了父类Person的name属性和getName()方法,我们在Student的实例中可以直接调用name属性和getName()方法。

  分析:<1>Person所有实例中的getName()方法行为都是一样的,每实例化一个对象都会定义一个行为相同的getName()方法,但我们希望所有实例都共享同一个getName()方法,所有getName()最好的方式是定义在原型上。

  缺点:

    <1>.每个实例都有相同行为的getName()方法造成内存的浪费。

    <2>.做不到代码的复用,最佳做法getName()方法所有实例共用同一个。

    <3>.造成父类多余属性的继承

  2>原型prototype模式继承

    函数都有prototype属性,在设计构造函数时,我们将不需要共享的属性和方法,定义在构造函数里面,需要共享的属性和方法,都放在prototype中;

        // 将不共享的name属性定义在Person函数中
        function Person(name) {
            this.name = name
        }
        // 将需要共享的getName方法定义在原型上
        Person.prototype.getName = function(){
            return this.name
        }

        // 定义构造函数Student(),定义属性name和label
        function Student(name, hobby){
            // 通过call改变this的指向,继承Person中的属性name
            Person.call(this, name)
            this.hobby = hobby;
        }
        // Student继承了Person中的方法
        Student.prototype = new Person()
        // 修正Student的构造函数的指向
        Student.prototype.constructor = Student
        // Student定义getHobby的方法
        Student.prototype.getHobby = function() {
            return this.hobby
        }

        var stu = new Student("小明", "basketBall");
        console.log(stu.getName());
        console.log(stu.getHobby());
        

  分析:

  1>.代码中Student.prototype = new Person(),Person实例赋值给Student.prototype,Student.prototype原先的值被删除掉。  2>.Student.prototype.constructor = Student做了什么呢?每个Prototype对象都有一个Constructor属性,如果没有Student.prototype = new Person(),    Student.prototype的constructor是指向Student的,执行这行后constructor指向了Person,更重要的是每个实例有一个constructor属性默认调用prototype中的constructor属性。     调用Student.prototype = new Person()后,stu.constructor也指向了Person。
        console.log(Student.prototype.constructor === Person)   // true
        console.log(stu.constructor === Person)                 // true
        console.log(stu.constructor === Student.prototype.constructor)  //true

    这就导致了继承链的混乱,因为stu是Student的实例,而不是Person的实例,所以我们需要手动修正Student.prototype.constructor = Student。

   3>.上例中对于Student.prototype = new Person(),我们可以使用Student.prototype = Person.prototype替换,与第一种相比不需要执行Person函数(提高性能)也不需要创建实例(节省内存),同时缩短了原型链的调用。

    缺点:导致Student.prototype和Person.prototype指向同一个对象引用,修改其中任意一个另一个就会被影响。比如:Student.prototype.constructor = Student会导致Person.prototype.constructor也是Student。

console.log(Person.prototype.constructor === Student)   //  true

  3>prototype模式改进

    由于Student.prototype = new Person()和Student.prototype = Person.prototype两种方式都存在缺点,这里我们使用空对象作为中介进行优化。

        /*
            1>F是空函数对于性能的损耗和内存的浪费忽略不计
            2>不会导致child.prototype和parent.prototype指向同一个引用
        */
        function extend (parent, child) {
            function F (){}
            F.prototype = parent.prototype
            child.prototype = new F()
            child.prototype.constructor = child
        }

        // 将不共享的name属性定义在Person函数中
        function Person(name) {
            this.name = name
        }
        // 将需要共享的getName方法
        Person.prototype.getName = function(){
            return this.name
        }

        // 定义构造函数Student(),定义属性name和hobby
        function Student(name, hobby){
            // 通过call改变this的指向,继承Person中的属性name和方法getName()
            Person.call(this, name)
            this.hobby = hobby;
        }

        extend(Person, Student)
        Student.prototype.getHobby = function() {
            return this.hobby
        }

        var stu = new Student("小明", "basketBall");
        console.log(stu.getName());             //小明
        console.log(stu.getHobby());            //basketball
        console.log(Student.prototype.constructor)  //Student
        console.log(Person.prototype.constructor)   //Person

  分析:

    1>上例中我们封装了extend方法,extend中我们还是用空构造函数F作为中介,避免了之前实现方式中的缺点。   

     1>F是空函数对于性能的损耗和内存的浪费忽略不计
      2>不会导致child.prototype和parent.prototype指向同一个引用

    2>对于我们自己封装的extend方法,我们其实可以使用ES5中提供的Object.create(proto,properties)代替,实现的效果是一样的。

    Student.prototype = Object.create(Person.prototype)

    注意:Object.create()方法的内部实现和我们自己封装的extend方法类似。

  

原文地址:https://www.cnblogs.com/diweikang/p/9181368.html

时间: 2024-10-23 20:17:51

JavaScript中的继承实现(1)的相关文章

JavaScript学习13 JavaScript中的继承

JavaScript学习13 JavaScript中的继承 继承第一种方式:对象冒充 <script type="text/javascript"> //继承第一种方式:对象冒充 function Parent(username) //父类对象 { this.username = username; //下面的代码最关键的部分就是将子对象的this传递给了父对象 this.sayHello = function() { alert(this.username); } } f

JavaScript中的继承(原型链)

一.原型链 ECMAScript中将原型链作为实现继承的主要方法,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法. 实例1: function SupType() { this.property = true; } SupType.prototype.getSupvalue = function() { return this.property; }; function SubType() { this.subproperty = false; } //原型对象等于一个类型的实例

javascript 中各种继承方式的优缺点 (转)

javascript中实现继承的方式有很多种,一般都是通过原型链和构造函数来实现.下面对各种实现方式进行分析,总结各自的优缺点. 一 原型继承 let Super = functioin(name) { this.name = name; this.setName = (newName) => { this.name = name; }; this.getName = () => { return this.name; } } let Sub = function(sex) { this.se

JavaScript中的继承机制

JavaScript到底是不是面向对象的?! 有人说是,有人说基于对象更准确,因为JavaScript没有类.不去管它,但JavaScript确实提供了一些面向对象的机制. 本文主要讨论JavaScript中的继承.继承,简单来说,就是让子类获得父类的属性和方法,以达到共享和复用的目的. 在开始继承之前,首先需要创建父类.为了后续讨论的需要,我分别为父类创建了私有对象.实例对象.引用对象和两个实例方法. 创建父类的代码如下: // 父类实例的构造函数 function Pet(name,soun

javascript 中实现继承的六种方式

javascript 中对于继承的描述: 许多面向对象语言都支持两种继承的方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.在 javascript 中由于函数没有签名也就无法实现接口继承,而只支持实现继承,而且实现继承主要通过原型链来实现的. 先引述下官方文档对于原型链的描述:其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.要理解这个概念要先弄清楚构造函数,原型,和实例的关系:每个构造函数(只要是函数)都有一个 prototype 属性该属性指向一

javascript中实现继承的几种方式

javascript中实现继承的几种方式 1.借用构造函数实现继承 function Parent1(){ this.name = "parent1" } function Child1(){ Parent1.call(this); this.type = "child1"; } 缺点:Child1无法继承Parent1的原型对象,并没有真正的实现继承(部分继承) 2.借用原型链实现继承 function Parent2(){ this.name = "p

JavaScript中的继承

在JavaScript中没有Java中的exends关键字,只能通过其他的方式来实现继承关系. 1) 对象冒充 1 function Parent(username) 2 { 3 this.username = username; 4 5 this.sayHello = function() 6 { 7 alert(this.username); 8 } 9 } 10 11 function Child(username, password) 12 { 13 //下面三行代码是就是实现了Chil

JavaScript 中的继承

继承是 OO 语言中的一个最为人津津乐道的概念.许多 OO 语言都支持两种继承方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.由于函数没有签名,在 ECMAScript 中无法实现接口继承. ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的. 组合继承时 JavaScript 中常用的继承模式,而寄生组合式继承克服了组合继承的缺点,被认为是引用类型最理想的继承范式. 以下是<JavaScript高级程序设计>中介绍的继承方式:

javascript中的继承实现

javascript虽然是一门面向对象的语言,但是它的继承机制从一开始设计的时候就不同于传统的其他面向对象语言,是基于原型的继承机制,但是在这种机制下,继承依然有一些不同的实现方式. 方法一:类式继承 所谓的类式继承就是指模仿传统面向对象语言的继承方式,继承与被继承的双方都是“类”,代码如下: 首先定义一个父类(或超类): 1 function Person(name){ 2 this.name=name; 3 } 4 5 Person.prototype.getName=function(){

【Javascript设计模式】第一课 Javascript中的继承

在Javascript中每个类有三个部分:     1.第一部分是构造函数内,这是供实例化对象复制用的. 2.第二部分是构造函数外,通过点语法添加的,这是供类使用的,实例化对象是访问不到的. 3.第三部分是类的原型中,实例化对象可以通过其原型链间接访问到,也是为供所有实例化对象所共用的. 一. 子类的原型对象 --类式继承 类式继承是最常见最简单的继承模式,类式继承用一句话概括就是"父类实例指向子类原型" /** * 声明一个父类 * @constructor */ var Super