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

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

一,关于原型

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

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

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

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

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

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

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

二,关于原型链继承

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

 function Person(){
this.name="bob";
}
Person.prototype.eat=function(){
return "food";
}
function Student(){}
Student.prototype=new Person();//将Person实例赋给Student的原型对象
var one=new Student();
one.name//bob
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

但是大家要注意一下一种情况,在通过原型链继承时,不能通过对象字面量个方式来更新原型对象

 function Person(){
this.name="bob" ;
}
Person.prototype.eat=function(){
return "food";
}
function Student(){} Student.prototype=new Person();
Student.prototype={
run:function(){
return "run";
}
};
var one=new Student();
console.log(one.eat());//Uncaught TypeError: undefined is not a function
在上面的代码中,把Person的实例赋给Student的原型,接下来又把原型改写成另一个对象字面量,现在原型包含的是Object实例,不再是Person实例,因此原型链已经被切断了,也就是说Student和Person没关系了。

但是思考下面一段代码,我这样改写,会切断原型链吗?
 function Person(){
this.name="bob" ;
}
Person.prototype.eat=function(){
return "food";
}
function Student(){} Student.prototype=new Person();
Student.prototype.constructor=Student;//把Student原型对象中原本指向Person构造函数的对象强行指向到Student
var one=new Student();
console.log(one.eat());//food

从代码运行的情况来看,这个动作并没有切断原型链的继承,原因何在?

大家一定要主要实例和原型对象是通过[[Prototype]]来实现关系链接的,换句话说,实例里面的[[Prototype]]指针指向原型对象,这里我改写了constructor后,并没有将[[Prototype]]的指向改变,当然也就没有改变整个原型链的继承关系!这一点要非常注意!!!

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

可以通过两种方式来确定原型和实例之间的关系,第一种是instanceof操作符,这个操作符用来测试实例与原型链中出现过的构造函数,看下面的一段代码
 function Person(){
this.name="bob" ;
}
Person.prototype.eat=function(){
return "food";
}
function Student(){} Student.prototype=new Person();
Student.prototype.constructor=Student;
var one=new Student();
var person=new Person();
console.log(one instanceof Student);//true
console.log(one instanceof Person);//true
console.log(person instanceof Person);//true
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所致,版权均归属本人!

javascript中继承(一)-----原型链继承的个人理解的更多相关文章

  1. 一步步学习javascript基础篇(5):面向对象设计之对象继承(原型链继承)

    上一篇介绍了对象创建的几种基本方式,今天我们看分析下对象的继承. 一.原型链继承 1.通过设置prototype指向“父类”的实例来实现继承. function Obj1() { this.name1 ...

  2. JavaScript 随笔2 面向对象 原型链 继承

    第六章 面向对象的程序设计 1.创建对象的几种方式 A)工厂模式 function CreatObj(name,sex,age){ this.name=name; this.sex=sex; this ...

  3. Typescript中的类 Es5中的类和静态方法和继承(原型链继承、对象冒充继承、原型链+对象冒充组合继承)

    <!doctype html> <html> <head> <meta charset="utf-8"> <meta name ...

  4. js继承之原型链继承

    面向对象编程都会涉及到继承这个概念,JS中实现继承的方式主要是通过原型链的方法. 一.构造函数.原型与实例之间的关系 每创建一个函数,该函数就会自动带有一个 prototype 属性.该属性是个指针, ...

  5. 三张图搞懂JavaScript的原型对象与原型链 / js继承,各种继承的优缺点(原型链继承,组合继承,寄生组合继承)

    摘自:https://www.cnblogs.com/shuiyi/p/5305435.html 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__pro ...

  6. 一篇JavaScript技术栈带你了解继承和原型链

    作者 | Jeskson 来源 | 达达前端小酒馆 1 在学习JavaScript中,我们知道它是一种灵活的语言,具有面向对象,函数式风格的编程模式,面向对象具有两点要记住,三大特性,六大原则. 那么 ...

  7. 深入浅出JavaScript之原型链&继承

    Javascript语言的继承机制,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instanc ...

  8. JS中原型链继承

    当我们通过构造函数A来实现一项功能的时候,而构造函数B中需要用到构造函数A中的属性或者方法,如果我们对B中的属性或者方法进行重写就会出现冗杂的代码,同时写出来也很是麻烦.而在js中每个函数都有个原型, ...

  9. JavaScript之继承(原型链)

    JavaScript之继承(原型链) 我们知道继承是oo语言中不可缺少的一部分,对于JavaScript也是如此.一般的继承有两种方式:其一,接口继承,只继承方法的签名:其二,实现继承,继承实际的方法 ...

随机推荐

  1. mysql 格式化时间

    SELECT phone,chang, msg, linkid, DATE_FORMAT(mo_time, '%Y%m%d%H%i%s') FROM mo http://www.w3school.co ...

  2. php或js判断网站访问者来自手机或者pc

    php或js判断网站访问者来自手机或者pc机 2013年9月26日,在弄wtuonline的时候为了区分用户是来自手机版浏览器还是pc,针对不同平台选择不同的网站版本,最终总结如下:         ...

  3. 开源web终端ssh解决方案-gateone简介

    好久都没来写博客,最近忙啥去了呢? 一是忙于saltstack的二次开发,二是云计算的学习研究中,所以就一直没写东西,今天给大家介绍个工具. 1. 首先来说一下为什么要 web ssh? 许多人不是说 ...

  4. 6.html5分组元素

    何为分组元素,首先先看下面这个例子: <span>scolia<span>scolia</span></span> <span>scolia ...

  5. 自学Python三 Python中的屠龙刀(续)

    装饰器: 在函数代码功能运行期间动态增加功能的方式叫做装饰器(Decorator).它对一个函数或者类进行再加工. 我们先定义两个函数,一个计算两数和,一个计算两数差. >>> de ...

  6. 【转】C#中Invoke的用法

    在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界 ...

  7. MongoDB分片简单实例

    分片 在Mongodb里面存在另一种集群,就是分片技术,可以满足MongoDB数据量大量增长的需求. 当MongoDB存储海量的数据时,一台机器可能不足以存储数据也足以提供可接受的读写吞吐量.这时,我 ...

  8. 深度理解依赖注入(Dependence Injection)

    前面的话:提到依赖注入,大家都会想到老马那篇经典的文章.其实,本文就是相当于对那篇文章的解读.所以,如果您对原文已经有了非常深刻的理解,完全不需要再看此文:但是,如果您和笔者一样,以前曾经看过,似乎看 ...

  9. Android 文档之viewAnimator

    一.结构 public class ViewAnimator extends FrameLayout java.lang.Object android.view.View android.view.V ...

  10. Karaf 依赖equinox and felix,karaf 本Apache的很多项目作为基础框架

    6月17日是Apache Karaf作为Apache顶级项目.Karaf是个运行时包,包含了一个OSGi框架(Equinox或Felix).一个命令shell(Felix Gogo)及默认情况下内置的 ...