在ES5继承的实现非常有趣的,由于没有传统面向对象类的概念,Javascript利用原型链的特性来实现继承,这其中有很多的属性指向和需要注意的地方。

原型链的特点和实现已经在之前的一篇整理说过了,就是通过将子类构造函数的原型作为父类构造函数的实例,这样就连通了子类-子类原型-父类,原型链的特点就是逐层查找,从子类开始一直往上直到所有对象的原型Object.prototype,找到属性方法之后就会停止查找,所以下层的属性方法会覆盖上层。

一个基本的基于原型链的继承过程大概是这样的:

  1. //先来个父类,带些属性
  2. function Super(){
  3. this.flag = true;
  4. }
  5. //为了提高复用性,方法绑定在父类原型属性上
  6. Super.prototype.getFlag = function(){
  7. return this.flag;
  8. }
  9. //来个子类
  10. function Sub(){
  11. this.subFlag = false;
  12. }
  13. //实现继承
  14. Sub.prototype = new Super;
  15. //给子类添加子类特有的方法,注意顺序要在继承之后
  16. Sub.prototype.getSubFlag = function(){
  17. return this.subFlag;
  18. }
  19. //构造实例
  20. var es5 = new Sub;

原型链实现的继承主要有几个问题:
1、本来我们为了构造函数属性的封装私有性,方法的复用性,提倡将属性声明在构造函数内,而将方法绑定在原型对象上,但是现在子类的原型是父类的一个实例,自然父类的属性就变成子类原型的属性了;
这就会带来一个问题,我们知道构造函数的原型属性在所有构造的实例中是共享的,所以原型中属性的改变会反应到所有的实例上,这就违背了我们想要属性私有化的初衷;
2、创建子类的实例时,不能向父类的构造函数传递参数

  1. function Super(){
  2. this.flag = true;
  3. }
  4. function Sub(){
  5. this.subFlag = false;
  6. }
  7. Sub.prototype = new Super;
  8. var obj = new Sub();
  9. obj.flag = flase; //修改之后,由于是原型上的属性,之后创建的所有实例都会受到影响
  10. var obj_2 = new Sub();
  11. console.log(obj.flag) //false;

为了解决以上两个问题,有一个叫借用构造函数的方法
只需要在子类构造函数内部使用apply或者call来调用父类的函数即可在实现属性继承的同时,又能传递参数,又能让实例不互相影响

  1. function Super(){
  2. this.flag = true;
  3. }
  4. function Sub(){
  5. Super.call(this) //如果父类可以需要接收参数,这里也可以直接传递
  6. }
  7. var obj = new Sub();
  8. obj.flag = flase;
  9. var obj_2 = new Sub();
  10. console.log(obj.flag) //依然是true,不会相互影响

结合借用构造函数和原型链的方法,可以实现比较完美的继承方法,可以称为组合继承:

  1. function Super(){
  2. this.flag = true;
  3. }
  4. Super.prototype.getFlag = function(){
  5. return this.flag; //继承方法
  6. }
  7. function Sub(){
  8. this.subFlag = flase
  9. Super.call(this) //继承属性
  10. }
  11. Sub.prototype = new Super;
  12. var obj = new Sub();
  13. Super.prototype.getSubFlag = function(){
  14. return this.flag;
  15. }

这里还有个小问题,Sub.prototype = new Super; 会导致Sub.prototype的constructor指向Super;
然而constructor的定义是要指向原型属性对应的构造函数的,Sub.prototype是Sub构造函数的原型,所以应该添加一句纠正:
Sub.prototype.constructor = Sub;

看完ES5的实现,再来看看ES6的继承实现方法,其内部其实也是ES5组合继承的方式,通过call借用构造函数,在A类构造函数中调用相关属性,再用原型链的连接实现方法的继承

  1. class B extends A {
  2. constructor() {
  3. return A.call(this); //继承属性
  4. }
  5. }
  6. A.prototype = new B; //继承方法

ES6封装了class,extends关键字来实现继承,内部的实现原理其实依然是基于上面所讲的原型链,不过进过一层封装后,Javascript的继承得以更加简洁优雅地实现

  1. class ColorPoint extends Point {
  2. constructor(x, y, color) {
  3. super(x, y); // 等同于parent.constructor(x, y)
  4. this.color = color;
  5. }
  6. toString() {
  7. return this.color + ' ' + super.toString(); // 等同于parent.toString()
  8. }
  9. }

通过constructor来定义构造函数,用super调用父类的属性方法

ES6中Class充当了ES5中,构造函数在继承实现过程中的作用
同样有原型属性prototype,以及在ES5中用来指向构造函数原型的__proto__属性,这个属性在ES6中的指向有一些主动的修改。
一个继承语句同时存在两条继承链:一条实现属性继承,一条实现方法继承。

  1. class A extends B {}
  2. A.__proto__ === B; //继承属性
  3. A.prototype.__proto__ === B.prototype; //继承方法

ES6的子类的__proto__是父类,子类的原型的__proto__是父类的原型
第二条继承链理解起来没有什么问题,对应到ES5中的A.prototype = new B;A.prototype作为B构造的实例,指向构造函数B的原型B.prototype,

但是在ES5中A.__proto__是指向Function.prototype的,因为每一个构造函数其实都是Function这个对象构造的,ES6中子类的__proto__指向父类可以实现属性的继承,在ES5中在没有用借用继承的时候由于父类属性被子类原型继承,所有的子类实例实际上都是同一个属性引用。

在ES6中实现了子类继承父类属性,在构造实例的时候会直接拿到子类的属性,不需要查找到原型属性上面去,ES6新的静态方法和静态属性(只能在构造函数上访问)也是通过这样类的直接继承来实现,至于普通复用方法还是放到原型链上,道理和实现和ES5是一样的。

此外我认为这里修改A.__proto__的指向是有意区分ES6中继承和实例化,同时建立子类和父类直接的关系,ES5的子类的构造函数通过子类的原型与父类的构造函数连接,不存在直接的关系;

可以这么说,在ES5继承和构造实例,ES6构造实例的时候可以理解__proto__原型指针是用来指向构造函数的原型的,但是在ES6继承中,__proto__指继承自哪个类或原型,在A继承B之后,构造一个实例 var obj = new A; 会发现它所有的属性指向都是和ES5一致的。

有个有趣的地方:ES6继承是在父类创建this对象,在子类constructor中来修饰父类的this,ES5是在子类创建this,将父类的属性方法绑定到子类,由于原生的构造函数(Function,Array等)没有this,子类无法通过call/apply(this)获得其内部属性,所以在ES5无法继承,ES6实现后可以为原生构造函数封装一些有趣的接口,比方说阮一峰老师的这个给Array实例封装一个版本记录和回滚的方法:

  1. class VersionedArray extends Array {
  2. constructor() {
  3. super();
  4. this.history = [[]];
  5. }
  6. commit() {
  7. this.history.push(this.slice());
  8. }
  9. revert() {
  10. this.splice(0, this.length, ...this.history[this.history.length - 1]);
  11. }
  12. }
  13. var x = new VersionedArray();
  14. x.push(1);
  15. x.push(2);
  16. x // [1, 2]
  17. x.history // [[]]
  18. x.commit();
  19. x.history // [[], [1, 2]]
  20. x.push(3);
  21. x // [1, 2, 3]
  22. x.revert();
  23. x // [1, 2]

最后做一个ES5和ES6的继承小结:
ES5最经典的继承方法是用组合继承的方式,原型链继承方法,借用函数继承属性,ES6也是基于这样的方式,但是封装了更优雅简洁的api,让Javascript越来越强大,修改了一些属性指向,规范了继承的操作,区分开了继承实现和实例构造,此外ES6继承还能实现更多的继承需求和场景。

文/Daguo(简书作者)
原文链接:http://www.jianshu.com/p/342966fdf816

ES5和ES6中对于继承的实现方法的更多相关文章

  1. ES5和ES6中的继承

    看到一篇写的非常好的关于js继承的文章,其中对构造函数.原型.实例之间的关系的描述十分透彻,故转载作者文章以随时学习,并供大家共同进步! ES5 ES5中的继承,看图: function Super( ...

  2. ES5和ES6中的继承 图解

    Javascript中的继承一直是个比较麻烦的问题,prototype.constructor.__proto__在构造函数,实例和原型之间有的 复杂的关系,不仔细捋下很难记得牢固.ES6中又新增了c ...

  3. ES5与ES6中的继承

    ES5继承在ES5中没有类的概念,所以一般都是基于原型链继承,具体的继承方法有以下几种: 父类: function Father (name) { this.name = name || 'sam' ...

  4. 浅谈es5和es6中的继承

    首先给大家介绍下在es5中构造函数的继承 function A(){ 2 //构造函数A 3 this.name="我是A函数"; 4 } 5 6 A.prototype={ 7 ...

  5. 前端面试之ES6中的继承!

    前端面试之ES6中的继承! ES6之前并没有给我们提供 extends继承.我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承. 1 call() 两个作用: 1 调用这个函数! 2 修改函数 ...

  6. es6中一些基本的使用方法

    es6中一些基本的使用方法 const 定义常量 let 块级变量 用let定义的变量只在块当中起作用,离开变量外界的块(括号)就会被销毁. 模板字面量 用于字符串拼接和写模板,使用 ` (反引号,左 ...

  7. JS创建对象、继承原型、ES6中class继承

    面向对象编程:java中对象的两个基本概念:1.类:类是对象的模板,比如说Leader 这个是泛称领导,并不特指谁.2:实例:实例是根据类创建的对象,根据类Leader可以创建出很多实例:liyi,y ...

  8. 在ES5实现ES6中的Object.is方法

    ES6中对象的扩展里面添加了一个Object.is方法,用于比较两个值是否严格相等.内部计算与 === 行为基本一致.那么我们怎么在不支持这个方法的ES5中实现呢? 首先我们需要搞清楚两点,1:Obj ...

  9. ES5和ES6中关于import & export的书写方式的区别

    ES6中输出变量的写法 情景1:单个变量 输出 export const less = 'less' 引用 import {less} from '../index.js' 情景2:多个变量 输出: ...

随机推荐

  1. pbfunc外部函数扩展应用-在Powerbuilder中进行Http的GET、POST操作

    利用PBFunc扩展函数进行Http的操作时,需要对n_pbfunc_http的以下几个函数进行参数设置: of_set_URL(...)//要进行GET或POST的url,必须 of_set_Con ...

  2. java war run

    #!/bin/bashdate=`date +'%Y%m%d %T'`pid=`ps -ef |grep Credit | grep -v grep|awk '{print $2}'`damocles ...

  3. Ajax的实现

    一.JavaScript的ajax //Ajax var xhr; if(window.XMLHttpRequest){ //除IE外的浏览器 xhr = new XMLHttpRequest() } ...

  4. SharePoint 数据迁移解决方案

    前言:说来惭愧,我们的SharePoint内网门户跑了2年,不堪重负,数据量也不是很大,库有60GB左右,数据量几万条,总之由于各种原因吧,网站速度非常慢,具体问题研究了很久,也无从解决,所有考虑用N ...

  5. 【读书笔记】iOS-防止通讯协议被轻易破解的方法

    开发者可以选择类似Protobuf之类的二进制通讯协议或者自己实现通讯协议,对于传输的内容进行一定程度的加密,以增加黑客破解协议的难度. 参考资料: <iOS开发进阶> --唐巧

  6. ShellExecuteA()&MessageBoxA()

    #include<windows.h> #include<stdlib.h> void main() { ) { ShellExecuteA(, , , ); //0代表系统启 ...

  7. CoreAnimation-03-隐式动画

    简介 每个UI控件,默认自动创建一个图层(根图层),即每个UI控件对应于至少一个图层 可以手动创建图层,这些图层为非根图层 对非根图层的某些属性(标记为Animatable的属性)进行修改,默认会自动 ...

  8. Mac上的软件使用介绍

    目录大纲: Drop to GIF Parallels Desktop 1.Drop to GIF 功能:将视频文件可以生产动态图gif文件 网址在github上:https://github.com ...

  9. iOS之UI--主流框架的搭建--仿制QQ的UI框架

    使用XCode搭建多个控制器界面,一般在实际开发中建议超过四个控制器界面使用纯代码. 下面的实例其实已经超过了四个,总结详细步骤的目的,主要是更熟悉XCode的StoryBoard使用细节. 先直接上 ...

  10. Matlab2014下载和破解方法,以及Matlab很好的学习网站

    ISO镜像下载地址链接: http://pan.baidu.com/s/1i31bu5J 密码: obo1  单独破解文件下载链接: http://pan.baidu.com/s/1c0CGQsw 密 ...