这一章,估计是js最操蛋的一部分内容。

现代方法:

  1. 简介
  2. Object.getPrototypeOf()
  3. super 关键字
  4. 类的 prototype 属性和__proto__属性
  5. 原生构造函数的继承
  6. Mixin 模式的实现

远古方法:

  * 《Javascript面向对象编程(一):封装》【可略,已看】

  * 《Javascript面向对象编程(二):构造函数的继承》

  * 《Javascript面向对象编程(三):非构造函数的继承》


热身一

调用Object.create(..) 会凭空创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你指定的对象。

既然是新对象,也就没有必要让o1的属性继承Base里的this.a。

热身二

  1. var animal = function() {};
  2. var dog = function() {};  // 本质是 new Function(),
  3.  
  4. animal.price = 2000;   // 原型预备役,先赋值
  5. dog.prototype = animal;  // want to share properties in animal
  6. console.log(dog.price)   // undefined, 其实没有“继承”到
    /**
    * 原型链是依赖于__proto__,而不是prototype!
    * 所以,dog.prototype赋值,有屁用!
    */

  7. 原理:
    dog's __proto__ ----> dog的构造函数的原型,也就是Function的原型
    dog.__proto__ == Function.prototype
  8.  
  9. Function.prototype没有price, 当然dog.price就没有继承到东西
  10.  
  11.  
  12. var tidy = new dog();
  13. console.log(tidy.price)  // 2000, 反而“继承”到了
  14.  
  15. Jeff: tidy.__proto__ == dog.prototype == animal

可见,通过_proto__,让tidy与dog有关;但dog与animal之间却没有建立起这层关系。

也就是说:dog不能继承,但dog的实例反而能继承animal的属性。


对象之间的"继承"的五种方法

父类 - 构造函数形式

  1. function Animal(){
  2.   this.species = "动物";
  3. }

一、 构造函数绑定

Ref: [JS] Topic - hijack this by "apply" and "call"

  1.   function Cat(name,color){
  2.  
  3.     Animal.apply(this, arguments);  // 如果是当前对象中没有的属性,就改变this,在"父类"中找
  4.  
  5.     this.name = name;
  6.     this.color = color;
  7.   }
  8.  
  9.   var cat1 = new Cat("大毛","黄色");
  10.   alert(cat1.species); // 动物

二、 prototype模式

  1.   Cat.prototype = new Animal(); // 同理“热身二”
  2.  
  3. /**
    * Cat.prototype.constructor 指向变化: Cat --> Animal
    */
  4.   Cat.prototype.constructor = Cat; // 有必要么?但是,第二行又是什么意思呢?
  5.   var cat1 = new Cat("大毛","黄色");
  6.   alert(cat1.species); // 动物

极为重要的一点:prototype改变后,需要再调整回来,为了安全,避免继承链的紊乱。

原型对象的constructor属性,原本就是指向正确的,

现在Cat.prototype都变为了new的新对象,那么也要至少保证Cat.prototype.constructor仍然是指向Cat的。

这个潜规则。

三、 直接继承prototype

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。

缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。

  1. function Animal(){ }
  2. Animal.prototype.species = "动物";
  3.  
  4. Cat.prototype = Animal.prototype;
  5.  
  6. ...
    ...

四、 利用空对象作为中介

由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。

  1. var F = function(){};       // F是空对象,所以几乎不占内存
  2. F.prototype = Animal.prototype; // 这里的技巧: 利用空对象 做了过渡
  3.  
  4. Cat.prototype = new F();   // 不会影响到Animal的prototype对象
  5.  
  6. Cat.prototype.constructor = Cat;

封装成一个函数,便于使用。

  1.   function extend(Child, Parent) {
  2.     var F = function(){};
  3.     F.prototype = Parent.prototype;
  4.     Child.prototype = new F();
  5.     Child.prototype.constructor = Child;
  6.     Child.uber = Parent.prototype;
  7.   }

为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层")这等于在子对象上打开一条通道,可以直接调用父对象的方法。

这一行放在这里,只是为了实现继承的完备性,纯属备用性质。

使用方式:

  1.   extend(Cat,Animal);
  2.   var cat1 = new Cat("大毛","黄色");
  3.   alert(cat1.species); // 动物

五、 拷贝继承

  1.   function Animal(){}
  2.   Animal.prototype.species = "动物";
  3.  
  4.   function extend2(Child, Parent) {
  5.  
  6.     var p = Parent.prototype;
  7.     var c = Child.prototype;
  8.  
  9.     for (var i in p) {
  10.       c[i] = p[i];
  11.     }
  12.  
  13.     c.uber = p;
  14.   }
  15.  
  16.   extend2(Cat, Animal);
  17.   var cat1 = new Cat("大毛","黄色");
  18.   alert(cat1.species); // 动物


父类 - 普通对象形式

  1. var Chinese = {
        nation:'中国'
    };

两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现"继承"。

可以使用:非构造函数"的继承。

一、object()方法

  1. function object(o) {
  2.   function F() {}
  3.   F.prototype = o;  // 把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。
  4.   return new F();
  5. }
  6.  
  7. 1. 第一步先在父对象的基础上,生成子对象
    var Doctor = object(Chinese);    // 这里没有在外部显式的new,跟构造函数的继承有点用法的区别
  8.  
  9. 2. 然后,再加上子对象本身的属性
    Doctor.career = '医生';
  10.  
  11. 3. 子对象已经继承了父对象的属性
    alert(Doctor.nation); //中国

二、浅拷贝

这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

详见原链接:Javascript面向对象编程(三):非构造函数的继承

  1.   function extendCopy(p) {
  2.     var c = {};
  3.     for (var i in p) {
  4.       c[i] = p[i];
  5.     }
  6.  
  7.     c.uber = p;
  8.     return c;
  9.   }
  10.  
  11.   var Doctor = extendCopy(Chinese);
  12.   Doctor.career = '医生';
  13.   alert(Doctor.nation); // 中国

三、深拷贝

目前,jQuery库使用的就是这种继承方法。

  1.   function deepCopy(p, c) {
  2.  
  3.     var c = c || {};
  4.  
  5.     for (var i in p) {
  6.  
  7.       if (typeof p[i] === 'object') {
  8.  
  9.         c[i] = (p[i].constructor === Array) ? [] : {};
  10.  
  11.         deepCopy(p[i], c[i]);
  12.  
  13.       } else {
  14.  
  15.          c[i] = p[i];
  16.  
  17.       }
  18.     }
  19.  
  20.     return c;
  21.   }

使用方式:

  1. var Doctor = deepCopy(Chinese);
  2.  
  3. // 现在,给父对象加一个属性,值为数组。然后,在子对象上修改这个属性:
  4. Chinese.birthPlaces = ['北京','上海','香港'];
  5. Doctor.birthPlaces.push('厦门');
  6.  
  7. // 这时,父对象就不会受到影响了。
  8. alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
  9. alert(Chinese.birthPlaces); //北京, 上海, 香港

让我们来瞧瞧,有了Class类之后会如何?


1. 子类需要得到this对象

2. 子类必须在constructor方法中调用super方法

3. 只有调用super之后,才可以使用this关键字,否则会报错

4. 父类的静态方法,也会被子类继承

Parent类:

  1. class Point {
  2. constructor(x, y) {
  3. this.x = x;
  4. this.y = y;
  5. }
  6. }

Child类:

  1. class ColorPoint extends Point {
  2. constructor(x, y, color) {    // 会被默认添加
  3. super(x, y); // 调用父类的constructor(x, y), 这里的x,y是父类的,所以要super中作为了参数
  4. this.color = color;
  5. }
  6.  
  7. toString() {
  8. return this.color + ' ' + super.toString(); // 调用父类的toString()
  9. }
  10. }

Object.getPrototypeOf方法

可以用来从子类上获取父类

  1. Object.getPrototypeOf(ColorPoint) === Point
  2. // true

super 关键字

注意,

super虽然代表了父类A的构造函数,但是返回的是子类B的实例。

super指向父类的原型对象。

super内部的this指的是B,因此super()在这里相当于:

  1. A.prototype.constructor.call(this)。
  • super不是指向父类!

例一:super.p无法调用到this.p = 2

例二:只能调用父类的prototype上

例三:this的不变性【子类普通方法中通过super调用父类的方法时,虽然是父类方法内部的this,却其实是指向当前的子类实例】

例四:超级变态,带我娓娓道来!

  1. class A {
  2. constructor() {
  3. this.x = 1;
  4. }
  5. }
  6.  
  7. class B extends A {
  8. constructor() {
  9. super();
  10. this.x = 2;
  11. super.x = 3;  // 其实是把上面的x变了,因为在这种情况下,super.x == this.x
  12. console.log(super.x); // undefined
  13. console.log(this.x); //
  14. }
  15. }
  16.  
  17. let b = new B();

附加题:对于super.x的设计已经无语。

  • super指向父类!

例五:用在静态方法之中,这时super将指向父类,而不是父类的原型对象。

  1. class Parent {
  2. static myMethod(msg) {
  3. console.log('static', msg);
  4. }
  5.  
  6. myMethod(msg) {
  7. console.log('instance', msg);
  8. }
  9. }
  10.  
  11. -------------------------------------
  12.  
  13. class Child extends Parent {
  14. static myMethod(msg) {
  15. super.myMethod(msg);
  16. }
  17.  
  18. myMethod(msg) {
  19. super.myMethod(msg);
  20. }
  21. }
  22.  
  23. Child.myMethod(1); // static 1
  24.  
  25. var child = new Child();
  26. child.myMethod(2); // instance 2

类的 prototype 属性和__proto__属性

Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

这算是子类沿着线索找寻父类的一个办法。

  1. class A {
  2. }
  3.  
  4. class B extends A {
  5. }
  6.  
  7. B.__proto__ === A // true (1)构造函数的继承
  8. B.prototype.__proto__ === A.prototype // true (2)方法的继承 

貌似是讲述本质,实现原理。但哥不是很关心。

  1. class A {
  2. }
  3.  
  4. class B {
  5. }
  6.  
  7. // B 的实例 继承 A 的实例
  8. Object.setPrototypeOf(B.prototype, A.prototype);  // ---->
  9.  
  10. // B 继承 A 的静态属性
  11. Object.setPrototypeOf(B, A);
  12.  
  13. const b = new B();

解释:

  1. Object.setPrototypeOf = function (obj, proto) {
  2. obj.__proto__ = proto;
  3. return obj;
  4. }

这两条继承链,可以这样理解:【记住这个就可以了】

(1) 作为一个对象,子类(B)的原型(__proto__属性)是父类(A);

(2) 作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。

extends 的继承目标

先复习一下:

第一种特殊情况,子类继承Object类。

第二种特殊情况,不存在任何继承。

第三种特殊情况,子类继承null。

原生构造函数的继承

  1. class MyArray extends Array {
  2. constructor(...args) {
  3. super(...args);
  4. }
  5. }
  6.  
  7. var arr = new MyArray();
  8. arr[0] = 12;
  9. arr.length //
  10.  
  11. arr.length = 0;
  12. arr[0] // undefined

ECMAScript 的原生构造函数大致有下面这些。

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()

自定义Error子类的例子,可以用来定制报错时的行为。

  1. class ExtendableError extends Error {
  2. constructor(message) {
  3. super();
  4. this.message = message;
  5. this.stack = (new Error()).stack;
  6. this.name = this.constructor.name;
  7. }
  8. }
  9.  
  10. class MyError extends ExtendableError {
  11. constructor(m) {
  12. super(m);
  13. }
  14. }
  15.  
  16. var myerror = new MyError('ll');
  17. myerror.message // "ll"
  18. myerror instanceof Error // true
  19. myerror.name // "MyError"
  20. myerror.stack
  21. // Error
  22. // at MyError.ExtendableError
  23. // ...

Mixin 模式的实现

Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。

  1. const a = {
  2. a: 'a'
  3. };
  4. const b = {
  5. b: 'b'
  6. };
  7. const c = {...a, ...b}; // {a: 'a', b: 'b'}

下面是一个更完备的实现,将多个类的接口“混入”(mix in)另一个类。

  1. function mix(...mixins) {
  2. class Mix {}
  3.  
  4. for (let mixin of mixins) {
  5. copyProperties(Mix, mixin); // 拷贝实例属性
  6. copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
  7. }
  8.  
  9. return Mix;
  10. }
  11.  
  12. function copyProperties(target, source) {
  13. for (let key of Reflect.ownKeys(source)) {
  14. if ( key !== "constructor"
  15. && key !== "prototype"
  16. && key !== "name"
  17. ) {
  18. let desc = Object.getOwnPropertyDescriptor(source, key);
  19. Object.defineProperty(target, key, desc);
  20. }
  21. }
  22. }

有点难,没看懂。

(完)

[JS] ECMAScript 6 - Inheritance : compare with c#的更多相关文章

  1. [JS] ECMAScript 6 - Variable : compare with c#

    前言 范围包括:ECMAScript 新功能以及对象. 当前的主要目的就是,JS的学习 --> ECMAScript 6 入门 let 命令 js 因为let, i的范围限制在了循环中. var ...

  2. [JS] ECMAScript 6 - Class : compare with c#

    Ref: Class 的基本语法 Ref: Class 的基本继承 许多面向对象的语言都有修饰器(Decorator)函数,用来修改类的行为.目前,有一个提案将这项功能,引入了 ECMAScript. ...

  3. [JS] ECMAScript 6 - Prototype : compare with c#

    开胃菜 prototype 对象 JavaScript 语言的继承则是通过“原型对象”(prototype). function Cat(name, color) { // <----构造函数 ...

  4. [JS] ECMAScript 6 - Async : compare with c#

    一段引言: Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大. 它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对 ...

  5. [JS] ECMAScript 6 - Array : compare with c#

    扩展运算符(spread) 先复习下 rest 参数. (1) argument模式,但不够好. // https://blog.csdn.net/weixin_39723544/article/de ...

  6. [JS] ECMAScript 6 - Object : compare with c#

    Ref: 对象的扩展 Outline: 属性的简洁表示法 属性名表达式 方法的 name 属性 Object.is() Object.assign() 属性的可枚举性和遍历 Object.getOwn ...

  7. [JS] ECMAScript 6 - String, Number, Function : compare with c#

    字符串的扩展 正则的扩展 数值的扩展 函数的扩展 字符串的扩展 js 字符的 Unicode 表示法 codePointAt() String.fromCodePoint() 字符串的遍历器接口 at ...

  8. [JS] ECMAScript 6 - Set & Map : compare with c#

    Ref: Set 和 Map 数据结构 Day 0 - 1所学

  9. [Node.js] ECMAScript 6中的生成器及koa小析

    原文地址:http://www.moye.me/2014/11/10/ecmascript-6-generator/ 引子 老听人说 koa大法好,这两天我也赶了把时髦:用 n 安上了node 0.1 ...

随机推荐

  1. 几种Unity运行平台的判断

    这里就介绍几种常见的,也是便于使用的几种平台判断的方法. 1.先说第一种,也是我用的顺手的一个.利用RuntimePlatform判断,API上的解释是[The platform applicatio ...

  2. __x__(2)0905第二天__计算机软件和硬件

    计算机(Computer)由硬件和软件组件,没有软件的计算机称为 裸机, 计算机的软件包括操作系统(OS)和应用软件(Software). 操作系统(Operating System,简称OS) 是管 ...

  3. 使用Doxygen + graphviz生成Unity 3d的UGUI类图

    下载软件 1) Graphviz,下载地址:http://download.csdn.net/detail/u010953266/8591169 为什么不用官网?一是下载速度慢,二是下载到本地的文件貌 ...

  4. windows环境下面批量新建文件夹

    md D:批量新建文件夹\2026 md D:批量新建文件夹\2030 md D:批量新建文件夹\2032 md D:批量新建文件夹\1835 md D:批量新建文件夹\1832 电脑桌面新建文档 - ...

  5. Win7下MongoDB的安装和使用

    Win7下MongoDB的安装和使用 1.下载: http://www.mongodb.org/downloads 2.安装: 安装目录为 D:\mongodb\MongoDB 2.6 Standar ...

  6. 八幅漫画理解使用 JSON Web Token 设计单点登录系统

    原文出处: John Wu 上次在<JSON Web Token – 在Web应用间安全地传递信息>中我提到了JSON Web Token可以用来设计单点登录系统.我尝试用八幅漫画先让大家 ...

  7. 【Yaml】Yaml学习笔记

    转载:https://blog.csdn.net/moshenglv/article/details/52084899 YAML何许物也?在XML泛滥的情况下,YAML的出现的确让人眼前一亮,在初步学 ...

  8. 原创:vsphere概念深入系列三:vSphere命令行管理

    假设无法近距离接触物理主机,只能远程命令行管理,. 以下命令行可以起到点作用. 首先需要安装vSphere CLI工具. 启动后界面: 1.查看datastore内容 所有命令行工具都可以加上-ser ...

  9. postgresql ltree类型

    最近一个月使用Postgresql的时候,经常遇到ltree的数据,感觉有些别扭,可是有绕不过去.今天决心整理一下,以后使用方便一些. 一.简介 ltree是Postgresql的一个扩展类型,由两位 ...

  10. 【转发】centos 7开启FTP以及添加用户配置权限,只允许访问自身目录,不能跳转根目录

    1.切换到root用户 2.查看是否安装vsftp,我这个是已经安装的. [root@localhost vsftpd]# rpm -qa |grep vsftpd vsftpd-3.0.2-11.e ...