js 模拟es6 实现继承

原文链接: js 模拟es6 实现继承

上一篇: js 继承

下一篇: js 实现继承的几种方式对比

https://zhuanlan.zhihu.com/p/57336944

ES6 extends 继承做了什么操作

我们先看看这段包含静态方法的 ES6 继承代码:

// ES6
class Parent{
    constructor(name){
        this.name = name;
    }
    static sayHello(){
        console.log('hello');
    }
    sayName(){
        console.log('my name is ' + this.name);
        return this.name;
    }
}
class Child extends Parent{
    constructor(name, age){
        super(name);
        this.age = age;
    }
    sayAge(){
        console.log('my age is ' + this.age);
        return this.age;
    }
}
let parent = new Parent('Parent');
let child = new Child('Child', 18);
console.log('parent: ', parent); // parent:  Parent {name: "Parent"}
Parent.sayHello(); // hello
parent.sayName(); // my name is Parent
console.log('child: ', child); // child:  Child {name: "Child", age: 18}
Child.sayHello(); // hello
child.sayName(); // my name is Child
child.sayAge(); // my age is 18

其中这段代码里有两条原型链,不信看具体代码。

// 1、构造器原型链
Child.__proto__ === Parent; // true
Parent.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
// 2、实例原型链
child.__proto__ === Child.prototype; // true
Child.prototype.__proto__ === Parent.prototype; // true
Parent.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true

一图胜千言,笔者也画了一张图表示,如图所示:

9099004f295346304aa94d583d2c9250cac.jpg


结合代码和图可以知道。
ES6 extends 继承,主要就是:

    1. 把子类构造函数( Child )的原型( __proto__ )指向了父类构造函数( Parent ),
    2. 把子类实例 child 的原型对象( Child.prototype ) 的原型( __proto__ )指向了父类 parent 的原型对象( Parent.prototype )。这两点也就是图中用不同颜色标记的两条线。
    3. 子类构造函数 Child 继承了父类构造函数 Preant 的里的属性。使用 super 调用的( ES5 则用 call 或者 apply 调用传参)。
      也就是图中用不同颜色标记的两条线。

看过《JavaScript高级程序设计-第3版》 章节 6.3继承 的读者应该知道,这 2和3小点 ,正是 寄生组合式继承 ,书中例子没有 第1小点
1和2小点 都是相对于设置了 __proto__ 链接。那问题来了,什么可以设置了 __proto__ 链接呢。

new Object.create Object.setPrototypeOf 可以设置 __proto__

说明一下, __proto__ 这种写法是浏览器厂商自己的实现。
再结合一下图和代码看一下的 new new 出来的实例的__proto__指向构造函数的 prototype ,这就是 new 做的事情。
摘抄一下之前写过文章的一段。 面试官问:能否模拟实现JS的new操作符 ,有兴趣的读者可以点击查看。

new 做了什么:

  1. 创建了一个全新的对象。
  2. 这个对象会被执行 [[Prototype]] (也就是 __proto__ )链接。
  3. 生成的新对象会绑定到函数调用的 this
  4. 通过 new 创建的每个对象将最终被 [[Prototype]] 链接到这个函数的 prototype 对象上。
  5. 如果函数没有返回对象类型 Object (包含 Functoin , Array , Date , RegExg , Error ),那么 new 表达式中的函数调用会自动返回这个新的对象。

Object.create ES5提供的

Object.create(proto, [propertiesObject])
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是 undefined )。对于不支持 ES5 的浏览器, MDN 上提供了 ployfill 方案。
MDN Object.create()

// 简版:也正是应用了new会设置__proto__链接的原理。
if(typeof Object.create !== 'function'){
    Object.create = function(proto){
        function F() {}
        F.prototype = proto;
        return new F();
    }
}

Object.setPrototypeOf ES6提供的

Object.setPrototypeOf MDN

Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部 [[Prototype]] 属性)到另一个对象或 null
Object.setPrototypeOf(obj, prototype)

`ployfill`
// 仅适用于Chrome和FireFox,在IE中不工作:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
  obj.__proto__ = proto;
  return obj; 
}

nodejs 源码就是利用这个实现继承的工具函数的。
nodejs utils inherits

function inherits(ctor, superCtor) {
  if (ctor === undefined || ctor === null)
    throw new ERR_INVALID_ARG_TYPE('ctor', 'Function', ctor);

  if (superCtor === undefined || superCtor === null)
    throw new ERR_INVALID_ARG_TYPE('superCtor', 'Function', superCtor);

  if (superCtor.prototype === undefined) {
    throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',
                                   'Object', superCtor.prototype);
  }
  Object.defineProperty(ctor, 'super_', {
    value: superCtor,
    writable: true,
    configurable: true
  });
  Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
}

ES6 extends ES5 版本实现

知道了 ES6 extends 继承做了什么操作和设置 __proto__ 的知识点后,把上面 ES6 例子的用 ES5 就比较容易实现了,也就是说 实现寄生组合式继承 ,简版代码就是:

// ES5 实现ES6 extends的例子
function Parent(name) {
  this.name = name;
}

Parent.sayHello = function () {
  console.log('hello');
}
Parent.prototype.sayName = function () {
  console.log('my name is ' + this.name);
  return this.name;
}

function Child(name, age) {
  // 相当于super
  Parent.call(this, name);
  this.age = age;
}

function _inherits(Child, Parent) {
  // Object.create
  Child.prototype = Object.create(Parent.prototype);
  // __proto__
  // Child.prototype.__proto__ = Parent.prototype;
  Child.prototype.constructor = Child;
  // ES6
  // Object.setPrototypeOf(Child, Parent);
  // __proto__
  Child.__proto__ = Parent;
}

_inherits(Child, Parent);
Child.prototype.sayAge = function () {
  console.log('my age is ' + this.age);
  return this.age;
}
let parent = new Parent('Parent');
let child = new Child('Child', 18);
console.log('parent: ', parent); // parent:  Parent {name: "Parent"}
Parent.sayHello(); // hello
parent.sayName(); // my name is Parent
console.log('child: ', child); // child:  Child {name: "Child", age: 18}
Child.sayHello(); // hello
child.sayName(); // my name is Child
child.sayAge(); // my age is 18

es5 的简单实现

function Extend(child, father) {
  child.prototype = Object.create(father.prototype)
  child.prototype.constructor = child
  child.__proto__ = father
}


function Animal(name) {
  this.name = name
}

Animal.sayAni = function () {
  console.log('Animal sayAni', this.name)
}
Animal.prototype.sayName = function () {
  console.log('Animal sayName', this.name)
}

function Dog(name, age) {
  // 调用父类构造函数, 初始化父类属性
  Animal.call(this, name)
  this.age = age
}

// 先实现继承
Extend(Dog, Animal)
// 在拓展子类方法
Dog.prototype.sayHello = function () {
  console.log('Dog say', this.name, this.age)
}


let a = new Animal('ani')
let d = new Dog('dog', 11)

a.sayName() // Animal sayName ani
Animal.sayAni() // Animal sayAni Animal
d.sayHello() // Dog say dog 11
d.sayName() // Animal sayName dog
Dog.sayAni() // Animal sayAni Dog

推荐阅读JS继承相关的书籍章节

《JavaScript高级程序设计第3版》-第6章 面向对象的程序设计,6种继承的方案,分别是原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承。 图灵社区本书地址 ,后文放出 github 链接,里面包含这几种继承的代码 demo

《JavaScript面向对象编程第2版》-第6章 继承,12种继承的方案。1.原型链法(仿传统)、2.仅从原型继承法、3.临时构造器法、4.原型属性拷贝法、5.全属性拷贝法(即浅拷贝法)、6.深拷贝法、7.原型继承法、8.扩展与增强模式、9.多重继承法、10.寄生继承法、11.构造器借用法、12.构造器借用与属性拷贝法。

ES6标准入门-第21章class的继承

ef=" https://oshotokill.gitbooks.io/understandinges6-simplified-chinese/content/chapter_9.html ">《深入理解ES6》-第9章 JavaScript 中的类

《你不知道的 JavaScript -上卷》第6章 行为委托和附录A ES6中的class

总结

继承对于JS来说就是父类拥有的方法和属性、静态方法等,子类也要拥有。子类中可以利用原型链查找,也可以在子类调用父类,或者从父类拷贝一份到子类等方案。
继承方法可以有很多,重点在于必须理解并熟
悉这些对象、原型以及构造器的工作方式,剩下的就简单了。 寄生组合式继承 是开发者使用比较多的。
回顾寄生组合式继承。主要就是三点:

    1. 子类构造函数的 __proto__ 指向父类构造器,继承父类的静态方法
    2. 子类构造函数的 prototype __proto__ 指向父类构造器的 prototype ,继承父类的方法。
    3. 子类构造器里调用父类构造器,继承父类的属性。
      行文到此,文章就基本写完了。文章代码和图片等资源放在这里 github inhert 和demo es6-extends ,结合 console、source 面板查看更佳。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值