• 我看原型链


    前言

      最近呢,又把  javascript高级程序设计(第三版)中原型、原型链读了一遍。原来读这一部分,一步一步的跟着书中对例子的分析,似懂非懂的点着头。哦!原来这样!  

      现在已经工作一年了,在面试的时候被问到的频率也是相当大,而我也是知其然而不知其所以然。有笔试遇到这个题,干脆画一个原型链的图,心里想着:“面试官您就看图自己意会吧”!霍霍,其实是自己用文字描述不出来***^_^***

    构造函数、原型、实例之间的关系

      要说原型链,那就要搞清楚构造函数、原型和实例之间的关系。书中有对他们之间的关系做简要概括,但是我在刚开始看这部分的时候,对这里的理解很是费劲,最近有个朋友也问到我这部分,说她读到这里就读不懂了。书中说“每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针”。

    什么是构造函数

      那什么是构造函数呢?在javascript中Object、Array都是构造函数(原生构造函数)。构造函数可以用来创建特定类型的对象,像Object、Array就是特定的类型对象。另外构造函数的命名要以大写字母开头,这是借鉴了其他OO(Object Oriented,面向对象)语言,主要是为了区别其他函数。创建构造函数的实例必须要使用new操作符,这也是构造函数与其他函数的唯一区别,否则这个构造函数跟其他普通函数也无异了。

    function Person(){}           //定义一个空构造函数
    var person = new Person();    //创建构造函数的一个实例,person指向构造函数的作用域

     

    什么是原型对象

      每个函数都有一个原型对象。函数的prototype属性指向这个函数的原型对象,所有的原型对象也都会自动获得一个constructor属性,并且这个属性包含的指针指向prototype所在函数。也就是说,在创建一个函数的时候,这个函数的原型对象也就被创建了,prototype指向这个原型。

      比如上面创建的Person构造函数,Person.prototype指向Person的原型对象,Person.prototype.construbtor又指向Person函数

    什么是实例

      实例实例,那不就是一个实实在在的例子!!!我们经常会听到“实例化一个对象”这句话。上面代码中new Person()就是实例化了一个对象,person就是一个实例,一个实实在在的例子,一个人。只不过这个人是我们不认识的一个人,没有年龄,没有姓名,没有性别等等。这不是我们要讨论的事情,现在我们再说实例与构造函数,原型对象之间的关系。

      当创建person这个实例的时候,同时也被创建了一个指向构造函数原型对象的指针,这个指针叫[[Prototype],]注意是指向构造函数的原型,而不是函数本身!!!!!在浏览器中我们打开控制台的时候可以看到-proto-属性,这个属性就是指向原型对象的指针。

      经过分析之后再重读那句话:每个构造函数(Person)都有一个原型对象(Person.prototype),原型对象都包含一个指向构造函数的指针(这个指针叫constructor,是在创建构造函数的时候原型对象自动获取的),而实例(person)都包含一个指向原型对象的内部指针(这个指针叫[[Prototype]],我们没法访问这个指针)。搞清楚这三者之间的关系,我们再看原型链那就很容易理解了。

    谈谈原型链继承

      书中把原型链放到继承这一节来讲,说明原型链可以实现继承。并且还是实现继承的主要方法。原理就是:利用原型对象让一个引用类型继承另一个引用类型的属性和方法。

      那么我们要怎么利用原型对象来实现继承呢?从上面 “什么是实例 ” 这一小节的图可以看出来,实例是指向原型对象的,既然这样,那这个实例就可以访问到原型和构造函数中的属性和方法。因为实例的指针指向原型,而原型的constructor属性包含的指针又指向构造函数。一层一层的通过向上查找,最终访问到构造函数与其原型的属性和方法。

      现在假如有个超类SuperType和一个子类SubType,子类要继承父类的属性和方法,正如上面所提到的,实例指向原型对象,从而能访问其属性和方法。那我们把子类的原型对象作为超类SuperType的一个实例,这样子类SubType就能访问到超类的属性和方法。

    function SuperType () {
        this.property = true;
    }
    SuperType.prototype.getSuperValue = function() {
        return this.property;
    };
    
    function SubType () {
        this.subproperty = false;
    }
    SubType.prototype = new SuperType();     //子类SubType的原型对象是超类SuperType的实例,这句是实现继承的关键一句
    
    SubType.prototype.getSubValue=function(){
        return this.subproperty;
    };
    var instance = new SubType();        //子类的实例instance

      运行上面那段代码,打开控制台。从监听实例instance开始,一层一层往下看。我们可以看到实例的指针指向SubType的原型,包含一个原型方法getSubvalue和超类的属性property(这是通过原型链访问到的),还有一个指针指向超类SuperType的原型对象。可以看到SuperType原型包含一个自动获取的constructor属性,属性中的指针指向构造函数SuperType,还有一个超类原型方法getSuperValue和一个指向Object原型的指针。

      就这样,通过将超类的实例赋值给子类的原型,从而实现了继承。这个继承实现的本质其实是重写子类的原型对象。以上关系可用下图表示:

      因为子类SubType的原型是超类的实例,所以,子类的原型指向超类的原型的指针是实例的指针[[Prototype]]。这样,子类除了拥有自己的属性和方法之拥有超类的属性和方法。而他们之间的关系是:子类实例(instance)指向子类的原型(SubType.prototype),子类的的原型又指向超类的原型(SuperType.prototype)。

    定义方法注意点

      如果要用原型链实现继承,那么在给子类定义方法时有两点需要注意:

      一、一定要在替换原型语句之后给子类原型定义方法

      这是因为实现继承的本质就是重写子类的原型,如果在替换原型语句之前给子类定义方法,那么这些方法都被定义在原始原型上了,重写原型之后,定义的这些方法将无法访问。

    function SuperType () {
        this.property = true;
    }
    SuperType.prototype.getSuperValue = function() {
        return this.property;
    };
    
    function SubType () {
        this.subproperty = false;
    }
    SubType.prototype = new SuperType();     //这一句就是替换原型语句
    SubType.prototype.getSubValue=function(){  //给子类定义原型方法getSubValue
        return this.subproperty;
    };

    var instance = new SubType(); 

      二、给子类定义原型方法不能使用对象字面量

      对象字面量也是一个对象,如果使用它给子类定义方法将会导致继承失效。因为这样做对破坏子类与超类之间的连接关系,子类原型指针不再指向超类原型,而是指向了Object对象。

    function SuperType () {
        this.property = true;
    }
    SuperType.prototype.getSuperValue = function() {
        return this.property;
    };
    
    function SubType () {
        this. = false;
    }
    SubType.prototype = new SuperType();     //这一句就是替换原型语句
    
    //使用对象字面量定义原型方法,会导致继承失败,上一行代码无效
    SubType.prototype = {
      getSubValue : function (){
        return this.subproperty;
    }
    };
    
    var instance = new SubType(); 

    不用对象字面量定义原型方法

    使用对象字面量定义原型方法

    原型链的缺点

      原型链的缺点主要有两点:

      一、包含引用类型值的原型属性会被所有实例共享,其中一个实例修改了引用类型的属性,会导致其他实例中的这个属性也被改变。例如:

    function SuperType(){
      this.color=['red','blue'];
    }
    function SubType(){
      
    }
    
    //继承SuperType
    SubType.prototype = new SuperType();
    
    var instance1 = new SubType();
    instance1.color.push("green");
    console.log(instance1.color); //"red,blue,green"
    
    var instance2 = new SuperType();
    console.log(instance2.color) //"red,blue,green"

      二、在创建子类实例的时候,不能向超类的构造函数传参。由于所有的子类都共享超类的属性,所以给超类传参不能保证不影响其他子类

      由于以上两点缺点,在实际开发中很少单独使用原型链。

  • 相关阅读:
    不同地区Android开发者使用哪些设备测试APP?
    IIS6、IIS7.5设置网站默认首页方法(Directory Listing Denied)
    大数据正在改变我们的生活
    如何在MySQL中使用explain查询SQL的执行计划?
    整合spring cloud云架构
    对SQL 优化,提升性能!
    传统分布式架构如何进行容器化升级
    Android开发实践:Android.mk模板
    如何从代码层面优化系统性能
    德国又出超实用的厨房神器
  • 原文地址:https://www.cnblogs.com/miss-radish/p/4781489.html
Copyright © 2020-2023  润新知