zoukankan      html  css  js  c++  java
  • javascript中继承(一)-----原型链继承的个人理解

    【寒暄】好久没有更新博客了,说来话长,因为我下定决心要从一个后台程序员转为Front End,其间走过了一段漫长而艰辛的时光,今天跟大家分享下自己对javascript中原型链继承的理解。

    总的说来,js中的常用的继承方式可以分为两种,一种是原型链式继承,这也是本文要谈的重点;另外一种是借用构造函数继承,这方面的理解,我将在下次的博客中更新。好了,闲话不多说,进入正题。

    一,关于原型

    首先,我们不得不解释下原型的概念:我们创建的每一个函数都有一个原型属性,即prototype,这个属性是一个指针,指向原型对象。
    1 function Person(){}//这里我们声明一个函数Person,js中函数是对象,也是构造函数
    2 console.log(Person.prototype)//打印一下Person对象的原型,会出现什么呢?如下图所示:


    大家在图中看到了,Person对象的原型拥有一个constructor,它指向Person的构造函数,即Person本身,另外一个属性是__proto__属性,这个属性我会在后文中说明。

    到这里,大家肯定会明白了,一个对象建立后,会产生一个局部的“小链式结构”,即Person对象拥有一个prototype属性,这个属性指向原型对象,在原型对象中又有一个构造器constructor,指向构造函数。用一张图来说明:



    那么,原型对象的作用是什么呢?这个原型对象包含由特定类型的实例共享的属性和方法。大家要注意共享这两个字,用一段代码解释下

     1 function Person(){
     2   this.name="bob" //这是一个实例属性
     3 }
     4 Person.prototype.eat=function(){ //给对象的原型对象添加一个eat的方法,接下来,new的实例会共享这个方法
     5   return "food";
     6 }
     7 var p1=new Person();  //这里究竟发生生了什么?
     8 p1.eat()//->food
     9 var p2=new Person();
    10 p2.eat()//->food,所以只要是Person的对象,他们都会共享原型对象的方法,当然,p1.name也会共享Person的实例属性,因为p1是Person的一个实例 

    好了,到这里原型的概念我们已经讲完了,大家或许会疑问,上面的new一个Person实例的过程中究竟发生了什么呢?为什么这个实例能够访问到原型对象中的方法?其实,在这个过程过程中,p1实例拥有了一个指针,这个指针指向构造函数的原型对象。此时原型对象中的方法自然能够被实例所访问。用一张图来说明下:

    这里,我们总结下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象拥有一个指向构造函数的指针,而实例拥有一个指向原型对象的内部指针(这就是前面所提到的[[Prototype]],即__proto__,要注意的是这个__proto__属性在chrome浏览器中是可以看到的,而在大部分浏览器是隐藏的!)

    二,关于原型链继承

    好了,说了这么多终于到回到我们的主角了【原型链】,提出一个思考:如果我们让原型对象等于另外一个对象的实例,将会有一个什么样的结果呢?先看下面一段代码

     1 function Person(){
     2   this.name="bob";  
     3 }
     4 Person.prototype.eat=function(){
     5   return "food";
     6 }
     7 function Student(){}
     8 Student.prototype=new Person();//将Person实例赋给Student的原型对象
     9 var one=new Student();
    10 one.name//bob
    11 one.eat()//food,Student的实例能访问到Person对象的实例方法,也能访问到其原型属性中的方法

    以上就是原型链继承的一种基本模式,那么我们怎么解释这样的原理呢?之前说过,对象的实例拥有一个指向原型对象的指针,那么student的原型对象拥有了Person对象实例后,自然也拥有一个指向Person原型对象的指针。此时,我们再new一个Student实例one时,one实例包含一个指向Student原型的指针,而Student.prototype拥有一个指向Person原型对象的指针,Person原型本身包含一个指向自身构造函数的指针。这样一来,就构成了实例与原型的链条。这就是所谓的原型链的概念!

    用一张图描绘一下上面讲的情况:

    大家注意一下,这里的one对象的constructor现在指向谁呢?它并不指向Student,因为Student的原型指向另一个对象--Person的原型,而这个原型对象的constructor指向的是Person。

    三,原型链方法的改写及注意的问题

    有时候,子类型需要改写超类型当中的方法,或者添加新的方法,一定要注意给原型添加代码一定要放在继承语句(即替换原型语句)的后面,
    function Person(){
      this.name="bob" ;
    }
    Person.prototype.eat=function(){
      return "food";
    }
    function Student(){}
    //Student.prototype.eat=function(){ 
    //  return "food1";       
    //}           
    //注意如果更改原型语句的代码放在替换之前,那么下面one.eat()的结果将仍然是food
    //,原因很简单,前面对prototype对象的修改,在后面的替换一句中被Person实例对象覆盖了
    //,换句话说,就是现在的prototype实例中仍旧是以前的eat方法
    Student.prototype=new Person();
    Student.prototype.eat=function(){
      return "food1";
    }
    var one=new Student();
    console.log(one.eat());//food1
    但是大家要注意一下一种情况,在通过原型链继承时,不能通过对象字面量个方式来更新原型对象
     1 function Person(){
     2   this.name="bob" ;
     3 }
     4 Person.prototype.eat=function(){
     5   return "food";
     6 }
     7 function Student(){}
     8 
     9 Student.prototype=new Person();
    10 Student.prototype={
    11   run:function(){
    12     return "run";
    13   }
    14 };
    15 var one=new Student();
    16 console.log(one.eat());//Uncaught TypeError: undefined is not a function 
    在上面的代码中,把Person的实例赋给Student的原型,接下来又把原型改写成另一个对象字面量,现在原型包含的是Object实例,不再是Person实例,因此原型链已经被切断了,也就是说Student和Person没关系了。

    但是思考下面一段代码,我这样改写,会切断原型链吗?
     1 function Person(){
     2   this.name="bob" ;
     3 }
     4 Person.prototype.eat=function(){
     5   return "food";
     6 }
     7 function Student(){}
     8 
     9 Student.prototype=new Person();
    10 Student.prototype.constructor=Student;//把Student原型对象中原本指向Person构造函数的对象强行指向到Student
    11 var one=new Student();
    12 console.log(one.eat());//food
    从代码运行的情况来看,这个动作并没有切断原型链的继承,原因何在?
    大家一定要主要实例和原型对象是通过[[Prototype]]来实现关系链接的,换句话说,实例里面的[[Prototype]]指针指向原型对象,这里我改写了constructor后,并没有将[[Prototype]]的指向改变,当然也就没有改变整个原型链的继承关系!这一点要非常注意!!!

    四,如何确定原型和实例关系

    可以通过两种方式来确定原型和实例之间的关系,第一种是instanceof操作符,这个操作符用来测试实例与原型链中出现过的构造函数,看下面的一段代码
     1 function Person(){
     2   this.name="bob" ;
     3 }
     4 Person.prototype.eat=function(){
     5   return "food";
     6 }
     7 function Student(){}
     8 
     9 Student.prototype=new Person();
    10 Student.prototype.constructor=Student;
    11 var one=new Student();
    12 var person=new Person();
    13 console.log(one instanceof Student);//true
    14 console.log(one instanceof Person);//true
    15 console.log(person instanceof Person);//true
    16 console.log(person instanceof Student);//false
    最后一个出现了false,什么原因,instanceof的工作原理是什么呢?
    A intanceof B,它的工作原理是,检测对象B的prototype指向的对象是否出现在Ad对象的[[Prototype]]链上,换句话说,A对象的原型链继承线路上,有没有B的存在。最后一个例子中Student的原型对象指向Person的原型对象,person实例也是指向Person的原型对象,而Person的原型对象指向了Person的构造函数本身,所以这条person对象的原型链路上,并没有出现Student的原型对象,故最后一个person不是Student的实例!

     
    那么,第二种检测方式是isPrototypeOf()方法,同理,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。
    function Person(){
      this.name="bob" ;
    }
    Person.prototype.eat=function(){
      return "food";
    }
    function Student(){}
    
    Student.prototype=new Person();
    
    var one=new Student();
    var person=new Person();
    console.log(Student.prototype.isPrototypeOf(one));//true
    console.log(Person.prototype.isPrototypeOf(one));//true
    console.log(Person.prototype.isPrototypeOf(person));//true
    console.log(Student.prototype.isPrototypeOf(person));//false

    五,总结

    以上就是原型连接继承及需要注意的问题,原型链继承固然很强大,但是也有一些问题,比如共享的原型属性容易被修改,在创建子类型的实例时,不能向超类传参数,等等,在下一次的分享中,我将谈一下自己学习‘借用构造函数实现继承’的心得!
    【感谢】:本人是刚学习前端的菜鸟,以上都是自己的有些学习体会,讲的啰嗦和不对的地方,大家可以给我留言,要知道这篇博文我整整花了一个晚上的时间完成,希望能给那些正在学习js的同学一些参考。感谢《js高级程序设计》,以及《javascript类型检测》这篇文章!
    PS:本文中的图片水印,是本人csdn的博客搬家到cnblogs所致,版权均归属本人!

  • 相关阅读:
    hdu 1998 奇数阶魔方(找规律+模拟)
    巧用百度Site App新组件为企业官网打造移动站
    POJ 1276 Cash Machine
    Unity3D中使用MiniJson解析json的例子
    设计模式读书笔记-----单例模式
    android 常用资料
    Objective-C之run loop详解
    icon 图标下载
    揭开Html 标签的面纱,忘不了的html .
    12157
  • 原文地址:https://www.cnblogs.com/rookiebob/p/3749389.html
Copyright © 2011-2022 走看看