zoukankan      html  css  js  c++  java
  • JS继承的实现方式 原型 原型链 prototype和_proto_的区别

    7.13 JS继承的实现方法

    标签(空格分隔): JS继承的实现方法


    JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。

    js继承的实现方式

    js继承的实现方式有6种

    1.原型链继承
    2.构造继承
    3.实例继承
    4.拷贝继承
    5.组合继承
    6.寄生组合继承

    既然要实现继承,那么首先我们得有一个父类。代码如下:

        //定义一个动物类
        function Animal(name){
            //属性
            this.name = name || 'Animal';
            //实例方法
            this.sleep = function(){
                console.log(this.name + '正在睡觉');
            }
        }
        //原型方法
        Animal.prototype.eat = function(food){
            console.log(this.name + '正在吃:' +food);
        }
    

    1、原型链继承

    核心:将父类的实例作为子类的原型

        function Cat(){
        }
        Cat.prototype = new Animal();
        Cat.prototype.name = 'cat';
    
        //Test Code
        var cat = new Cat();
        console.log(cat.name); //cat
        console.log(cat.eat('fish')); //cat正在吃:fish
        console.log(cat.sleep());  //cat正在睡觉
        console.log(cat instanceof Animal); //true
        console.log(cat instanceof Cat); //true
    

    特点:

    1.非常纯粹的继承关系,实例是子类的实例,也是父类的实例

    2.父类新增的原型方法/原型属性,子类都能访问到

    3.简单,易于实现

    缺点:

    1.要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

    2.无法实现多继承

    3.来自原型对象的引用属性是所有实例共享的

    4.创建子类实例时,无法向父类构造函数传参

    需要注意的两点:

    1.别忘记默认的原型:

    所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的
    所有的函数的默认原型都是Object的实例,因此默认的原型都会包含一个内部指针,指向Object.prototype。这也正是所有的自定义类型都会继承toString(),valueOf()等默认方法的根本原因。

    2.确定原型和实例的关系

    可以通过两种方式来确定原型和实例之间的关系。

    • 使用instanceof操作符
      只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true.(格式:alert(instance instanceof Object);//true)

    instanceof 用于判断一个变量是否某个对象的实例

    • 使用isPrototypeOf()方法
      同样,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此isPrototypeOf()方法也会返回true.(格式:alert(Object.prototype.isPrototypeOf(instance));//true)

    3.谨慎地定义方法

    子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎么样,给原型添加方法的代码一定要放在替换原型的语句之后。例子如下:

     function SuperType(){
            this.prototype = true;
        }
    
     SuperType.prototype.getSuperValue = function(){
            return this.property;
        };
    
     function SubType(){
            this.subproperty = false;
        }
    
     //继承了SuperType
     SubType.prototype = new SuperType();
    
     //添加新方法
     SubType.prototype.getSubValue = function(){
            return this.subproperty;
        };
    
     //重写超类型中的方法
    
     SubType.prototype.getSuperValue = function(){
            return false;
        };
    
     var instance = new SubType();
     alert(instance.getSuperValue());
    

    不能使用字面量创建原型方法,因为这样就会重写原型链。例子如下:

     function SuperType(){
            this.property = true;
        }
    
        SuperType.prototype.getSuperValue = function(){
            return this.property;
        };
        function SubType(){
            this.subproperty = false;
        }
        //继承了SuperType
        SubType.prototype = new SuperType();
    
        //使用字面量添加新方法,会导致上一行代码无效
    
        SubType.prototype = {
            getSubValue : function(){
                return this.subproperty;
            },
            someOtherMethod : function(){
                return false;
            }
    
        };
    
        var instance  = new SubType();
        alert(instance.getSuperValue());//error;
    

    2、构造函数

    核心:使用父类的构造函数来增强子类的实例,等于是复制父类的实例属性给子类(没用到原型)

        function Cat(){
            Animal.call(this);//继承了Animal
            this.name = name || 'Tom';
        }
        //Test Code
        var cat = new Cat();
        console.log(cat.name);
        console.log(cat.sleep());
        console.log(cat instanceof Animal);
        console.log(cat instanceof Cat);
    

    特点:

    1.解决了1中,子类实例共享父类引用属性的实例

    2.创建子类实例时,可以向父类传递参数

    3.可以实现多继承(call多个父类对象)

    举例:

    输入图片说明

    apply举例:

    输入图片说明

    倒数第二句:通过apply方法,改变了Parent的指向,此时Parent的新指向为Child的实例,并且调用child的getName()方法。

    去掉最后一句,Parent.apply(child,[child.getName()]);调用的顺序为:Child(‘张’)、getName()、Parent(‘张’),由于没人任何console.log,所以不会打印任何信息、

    apply实现原理:改变函数内部的函数上下文this,使它指向传入函数的具体对象

    缺点:

    1.实例并不是父类的实例,只是子类的实例

    2.只能继承父类的实例属性和方法,不能继承原型属性方法

    3.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

    注意:

    传递参数

    相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数
    例子:

        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
    

    3.实例继承

    核心:为父类实例添加新特性,作为子类实例返回。

        function Cat(name){
            var instance = new Animal();
            instance.name = name || 'Tom';
            return instance;
        }
    
        //Test Code
        var cat = new Cat();
        console.log(cat.name);
        console.log(cat.sleep());
        console.log(cat instanceof Animal);//true
        console.log(cat instanceof Cat);//false
    

    特点:

    1. 不限制调用方式,不管是new 子类()还是 子类(),返回的对象都具有相同的效果
    2. 实例是父类的实例,不是子类的实例
    3. 不支持多继承

    4.拷贝继承

        function Cat(){
            var animal = new Animal();
            for(var p in animal){
                Cat.prototype[p] = animal[p];
            }
            Cat.prototype.name = name || 'Tom';
        }
    
        //Test Code
    
        var cat = new Cat();
        console.log(cat.name); //Tom
        console.log(cat.sleep()); //Tom正在睡觉
        console.log(cat instanceof Animal); //false
        console.log(cat instanceof Cat);//true
    

    特点:

    1. 支持多继承

    缺点:

    1. 效率较低,内存占用高(因为要拷贝父类的属性)
    2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

    5.组合继承

    核心:通过调用父类构造,继承父类的属性并保留传参的有点,然后通过将父类实例作为子类原型,实现函数复用。

            function Animal(name){
            //属性
            this.name = name || 'Animal';
            //实例方法
            this.sleep = function(){
                console.log(this.name + '正在睡觉');
            }
        }
    
        function Cat(name){
            Animal.call(this);
            this.name = name || 'Tom';
        }
        Cat.prototype = new Animal();
    
        //Test Code
        var cat = new Cat();
        console.log(cat.name);//Tom
        console.log(cat.sleep());//Tom正在睡觉
        console.log(cat instanceof Animal);//true
        console.log(cat instanceof Cat);//true
    

    特点:

    1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
    2. 既是子类的实例,也是父类的实例
    3. 不存在引用属性共享问题
    4. 可传参
    1. 函数可复用

    缺点:

    1.调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

    6.寄生组合式继承

    核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造函数的时候,就不会初始化两次实例方法/属性,避免组合继承的缺点

        function Cat(name){
            Animal.call(this);
            this.name = name || 'Tom';
        }
        (function(){
            //创建一个没有实例方法的类
            var Super = function(){};
            Super.prototype = Animal.prototype;
            //将实例作为子类的原型
            Cat.prototype = new Super();
        })();
    
        //Test Code
        var cat = new Cat();
        console.log(cat.name);//Tom
        console.log(cat.sleep());//Tom 正在睡觉
        console.log(cat instanceof Animal);//true
        console.log(cat instanceof Cat);//true
    

    特点:堪称完美

    缺点:实现较为复杂

    原型链

    prototype和__proto__的概念

    prototype是函数的一个属性(每个函数都有一个prototype属性),这个属性是一个指针,指向一个对象。它是显示修改对象的原型的属性。

    __proto__是一个对象拥有的内置属性(请注意:prototype是函数的内置属性,__proto__是对象的内置属性),是JS内部使用寻找原型链的属性。

    用chrome和FF都可以访问到对象的__proto__属性,IE不可以。

    拓展

    我们可以直接获取一个对象的[[prototype]]链。

    在ES5中,标准的方法是:Object.getPrototypeOf(a);

    可以验证一下,这个对象引用和我们想象的是不是一样的:

    Object.getprototypeOf(a) === Foo.prototype;//true

    绝大多数(不是所有!)浏览器也支持一种非标准的方法来访问内部[[prototype]]属性:

    a._proto_ === Foo.prototype;//true

    这里就要使用__proto__属性来链接到原型(也就是Person.prototype)进行查找。最终在原型上找到了age属性。

    原型

    JS中的对象有一种特殊的[[prototype]]内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时[[prototype]]属性都会被赋一个非空的值。

      var myObject = {
        a : 2;
      };
      myObject.a;//2
    

    [[prototype]]引用的用处:
    当你试图引用对象的属性时会触发[[Get]]操作,比如myObject.a
    对于默认的[[Get]]操作来说,第一步是检查对象本身是否有这个属性,如果有的话就使用它。

    但是如果a不在myObject中,就需要使用对象的[[prototype]]链。
    对于默认的[[Get]]操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的[[prototype]]链。

     var anotherObject = {
        a:2;
     };
     //创建一个关联到anotherObject的对象
     
     var myObject = Object.create(anotherObject);
     myObject.a;//2
    

    现在myObject对象的[[prototype]]关联到了anotherObject.显然myObject.a并不存在,但是尽管如此,属性访问仍然成功的(在anotherObject中)找到了值2.
    但是,如果anotherObject中也找不到a并且[[prototype]]链不为空的话,就会继续查找下去。

  • 相关阅读:
    mac安装搜狗
    idea的阿里代码规范检查
    记录windows10闪屏
    github访问不了
    线程安全问题例子
    简单负载均衡工具类
    git将远程分支回归到指定版本
    minio的使用
    php禁止浏览器使用缓存页面的方法
    百度seo
  • 原文地址:https://www.cnblogs.com/hixxcom/p/7172984.html
Copyright © 2011-2022 走看看