zoukankan      html  css  js  c++  java
  • 面向对象

    内容介绍:

    • 类的声明
    • 生成实例
    • 继承的几种方式

    类的声明与实例化 

    /**
     * 类的声明
     */
    function Animal(){
        this.name = 'name';
    }
    /**
     * ES6中类的声明
     */
    class Animal2{
        constructor(){
            this.name = 'name';
        }
    }

     实例化

      用new运算符来实例化

    /**
     *
     * 实例化
     */
    new Animal();
    new Animal2();

     继承的几种方式

      一、借助构造函数实现继承

        代码实现如下:

    function Parent(name){
        this.name = name;//实例基本属性
        this.like = ['apple','banana'];//实例引用属性
        this.eat = function(){ //实例函数(引用属性)
            console.log(this.name + ' like ' + this.like.join(' '));
        };
    }
    //原型方法
    Parent.prototype.say = function(){
        console.log(this.name + 'say hi');
    };
    function Child(name){
        Parent.call(this,name); //核心 call也可以用apply代替
        this.type = 'child1';
    }
    var child1 = new Child('张三');
    var child2 = new Child('李四');
    child2.like.push('orange');
    
    child1.eat(); //张三 is like apple banana
    child2.eat(); //李四 is like apple banana orange
    child1.say(); //child1.say is not a function

      实现继承的原理:在子类构造函数Child中执行 Parent.call(this) 这句代码;即在子类的构造函数Child中执行父类构造函数Parent,同时改变运行的上下文(this指向),使this指向了Child。从而父类的实例属性会挂载到子类上去,如此便实现了继承。等于是把父类的实例属性和方法复制一份给子类实例装上。

      该继承方法的缺点:

        1.只能继承父类的实例属性和方法,不能继承父类的原型属性和方法。所以上面例子中child1就获取不到say方法

        2.无法实现函数共享,每new一次,Child的实例属性和方法都会开辟内存空间,比较浪费内存,缺乏效率。

      为了解决内存消耗问题,下面介绍一下原型链继承

      二 、原型链继承

       实现继承的原理:每个构造函数都有prototype属性指向该构造函数的原型(原型对象),这个原型(原型对象)上的属性和方法都会被该构造函数的实例所继承。

      代码如下:

    function Parent(name){
        this.name = name;//实例基本属性
        this.like = ['apple','banana'];//实例引用属性
    }
    //原型方法
    Parent.prototype.eat = function(){
        console.log(this.name + ' like ' + this.like.join(' '));
    };
    function Child(name){
        this.type = 'child1';
    }
    Child.prototype = new Parent();//核心
    Child.prototype.constructor = Child;//修复构造函数的指向
    
    var child1 = new Child('张三');
    var child2 = new Child('李四');
    child2.like.push('orange');
    
    child1.eat(); //undefined like apple banana orange
    child2.eat(); //undefined like apple banana orange

      原型链继承解决了构造函数继承的内存消耗问题,但也出现了新的问题:

      该继承方法的缺点:

        1. 创建子类的实例时,无法向父类构造函数传递参数(上例子中Child中的name 无法传给Parent)。

        2.  如果在实例中修改了原型上的属性,那么原型的属性就会被修改,所有继承自该原型的类的属性都会一起改变。

          上面代码中,只改变了child2这个实例的like属性,因为该属性是它原型的属性,即把原型的属性更改了,所以实例child1的值也改变了。

      三、组合方式

        借鉴上面两种方式的优缺点,采用构造函数和原型链结合方式

        代码如下:

    function Parent(name){
        this.name = name;//实例基本属性
        this.like = ['apple','banana'];//实例引用属性
    }
    //原型方法
    Parent.prototype.eat = function(){
        console.log(this.name + ' like ' + this.like.join(' '));
    };
    function Child(name){
        Parent.call(this,name); //核心
        this.type = 'child1';
    }
    Child.prototype = new Parent();//核心 子类的原型指向父类
    Child.prototype.constructor= Child;//修复构造函数的指向
    
    var child1 = new Child('张三');
    var child2 = new Child('李四');
    child2.like.push('orange');
    
    child1.eat(); //张三 like apple banana
    child2.eat(); //李四 like apple banana orange

       实现继承的原理:

        通过Parent.call(this,name); 可以继承父类的基本属性和引用属性 ,并且可以传参数;

        通过 Child.prototype = new Parent();  将子类的原型指向父类的实例,这样子类的实例(即new child())就继承了父类的所有属性和方法(包括实例属性/方法和原型属性/方法)。

      优点:

        1. 修改子类实例的属性时,不会引起父类的属性的变化

        2. 可以向父类传参数了

      缺点:

        1. 创建子类的实例的时候,父类构造函数执行了两次

          上面代码中,在构造函数继承(即Parent.call(this,name))的时候已经执行了一次Parent,父类Parent的属性已经在子类Child里运行了(Child已经有了Parent的属性和方法,不包括原型上的属性和方法)。外面原型链继承的时候(即Child.prototpe= new Parent())就没有必要再执行一次Parent了,只需要把原型上的属性和方法继承了就可以了。

      四、组合方式的优化1(推荐)

        组合方式中为了解决构造函数继承的缺点(即父类的原型中的属性继承不了),所以把子类的原型指向了父类的实例。但是父类的属性在构造函数继承时子类中就已经存在了,子类只缺少原型上的属性和方法。所以,我们进一步优化,把子类的原型指向父类的原型。这样就避免了福类构造函数的二次执行

        代码如下:

    function Parent(name){
        this.name = name;//实例基本属性
        this.like = ['apple','banana'];//实例引用属性
    }
    //原型方法
    Parent.prototype.eat = function(){
        console.log(this.name + ' like ' + this.like.join(' '));
    };
    function Child(name){
        Parent.call(this,name); //核心
        this.type = 'child1';
    }
    Child.prototype = Parent.prototype;//核心 子类的原型指向父类的原型
    Child.prototype.constructor= Child;//修复构造函数的指向
    
    var child1 = new Child('张三');
    var child2 = new Child('李四');
    child2.like.push('orange');
    
    child1.eat(); //张三 like apple banana
    child2.eat(); //李四 like apple banana orange

     缺点:

      上例中的代码Child.prototype = Parent.prototype;即把子类的原型指向了父类的原型,此时子类的原型的构造函数就是父类的原型的构造函数,而父类的原型的构造函数是Parent,所以Child.prototype和Parent.prototypr共用一个构造函数Parent。显然不是我们想要的,所以我们执行了Child.ptototype.constructor = Child;即把子类的构造函数指向自己。但是这样做同时也把父类的构造函数指向了子类。如下面的代码:

    console.log(child1.__proto__.constructor ===  Child); //true
    console.log( new Parent().__proto__.constructor ===  Child);//true
    console.log( new Parent().__proto__.constructor ===  Parent);//false

    通过上面代码可以看出,子类Child的实例的构造函数是Child;父类Parent的实例的构造函数也是Child。

      五、组合方式优化2(推荐)

        实现的原理:通过Object.cerate(obj)方法,该方法创建了一个新的对象,并且这个新的对象继承了指定的对象obj。类似于:

    //将Object.create()的实现原理 封装成一个函数
    function create(obj){
        function F(){}; //创建一个构造函数
        F.prototype = obj; //对象obj 赋给 F的原型
        return new F(); //返回构造函数的实例,这样F的实例就会继承obj的所有属性和方法
    }

        代码实现如下:

    function Parent(name){
        this.name = name;//实例基本属性
        this.like = ['apple','banana'];//实例引用属性
    }
    //原型方法
    Parent.prototype.eat = function(){
        console.log(this.name + ' like ' + this.like.join(' '));
    };
    function Child(name){
        Parent.call(this,name); //核心
        this.type = 'child1';
    }
    Child.prototype = Object.create(Parent.prototype);//核心 子类的原型指向Object.create 创建的中间对象
    Child.prototype.constructor = Child;//把Child的原型的构造函数的指向Child
    
    var child1 = new Child('张三');
    var child2 = new Child('李四');
    child2.like.push('orange');
    
    child1.eat(); //张三 like apple banana
    child2.eat(); //李四 like apple banana orange
    console.log(child1.__proto__.constructor ===  Child); //true
    console.log(new Parent().__proto__.constructor ===  Parent);//true

    代码Child.prototype = Object.create(Parent.prototype); 即把子类的原型指向了Object.create(Parent.prototype)创建的新对象,该对象继承了Parent.prototype的属性和方法。这样子类和父类构造函数就分离开了。然后再把子类的原型的构造函数指向自己。

    有上面的代码可以看出,子类实例的构造函数指向了子类Child,父类的实例的构造函数指向了父类Parent。这正是我们想要的结果。

     

          

        

      

      

  • 相关阅读:
    7大python 深度学习框架的描述及优缺点绍
    nodejs 和 js
    python操redis
    python ConfigParser 的小技巧
    RHEL 7.3修改网卡命名规则为ethX
    redhat7.3配置163 yum源
    分区脚本(fdisk)
    linux之sed用法
    linux awk命令详解
    Python 装饰器学习
  • 原文地址:https://www.cnblogs.com/yangkangkang/p/8746876.html
Copyright © 2011-2022 走看看