前言
间隔上一篇js的继承系列曾经过来了四年,时不时还有新的读者评论和回复,开心之余也想着更新一下内容,因为过后的内容里没有波及到es6的 extend
实现,所以当初抽空补上。 当然,如果是0根底的同学或者对于根本的继承有些忘记的同学,能够先回顾一下前两篇:
详解js中的继承(一)
详解js中的继承(二)
注释
根底回顾 & 准备常识
为了使前面的学习过程更丝滑,在开始之前,一起再回顾一下这个构造函数-原型对象-实例模型:
当拜访 a
的属性时,会先从a
自身的属性(或办法)去找,如果找不到,会沿着 __proto__
属性找到原型对象A.prototype
,在原型对象上查找对应的属性(或办法);如果再找不到,持续沿着原型对象的__proto__
持续找,这也就是最早咱们介绍过的原型链的内容。
function A (){ this.type = 'A'}const a = new A();
当然,图上的原型链能够持续找,咱们晓得 A
尽管是函数,然而实质也是 Object
,沿着__proto__
属性 一直上溯,最终会返回 null
;
a.__proto__ === A.prototype; // truea.__proto__.__proto__ === Object.prototype; // truea.__proto__.__proto__.__proto__ === null; // true
extend实现源码解析
进入正题, 学过 es6
的同学都晓得,能够通过关键字 extend
间接实现继承,比方:
// 首先创立一个Animal类class Animal { name: string; constructor(theName: string) { this.name = theName; }; move(distanceInMeters: number = 0) { console.log(`Animal moved ${distanceInMeters}m.`); }}// 子类Dog继承于Animalclass Dog extends Animal { age: number; constructor(name: string, age: number) { super(name); this.age = age; } bark() { console.log('Woof! Woof!'); }}const dog = new Dog('wangwang', 12);dog.bark();// 'Woof! Woof!'dog.move(10);//`Animal moved 10m.`
那么这个 extend
到底做了哪些事件呢? 这里借助装置 typescript
这个 npm
包,而后在本地运行 tsc [文件门路]
,把ts以及es6的代码转换成原生js的代码来进行钻研,(当然也有个毛病是转换的代码为了谋求代码极简 有时可能会影响可读性 比方 undefined
写作 void 0
之类的),下面的代码转换之后长这样:
// 第一局部var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); };})();// 第二局部// 首先创立一个Animal类var Animal = /** @class */ (function () { function Animal(theName) { this.name = theName; } ; Animal.prototype.move = function (distanceInMeters) { if (distanceInMeters === void 0) { distanceInMeters = 0; } console.log("Animal moved ".concat(distanceInMeters, "m.")); }; return Animal;}());// 第三局部// 子类Dog继承于Animalvar Dog = /** @class */ (function (_super) { __extends(Dog, _super); function Dog(name, age) { var _this = _super.call(this, name) || this; _this.age = age; return _this; } Dog.prototype.bark = function () { console.log('Woof! Woof!'); }; Dog.prototype.move = function (distanceInMeters) { if (distanceInMeters === void 0) { distanceInMeters = 5; } console.log("Dog moved ".concat(distanceInMeters, "m.")); }; return Dog;}(Animal));// 第四局部 无需解析var dog = new Dog('wangwang', 12);dog.bark(); // 'Woof! Woof!'dog.move(10); // Dog moved 10m.
代码看起来有些简单,咱们依照代码正文里,各局部内容复杂程度从简略到简单进行剖析:
- 先看第二局部,首先是用匿名立刻执行函数(IIFE)包裹了一层,这一点咱们在聊闭包的时候说过,这样写的益处是防止净化到全局命名空间;而后在外部,就是之前第一篇说过的构造函数-原型对象的经典模型-- 属性放在构造函数里,办法绑定在原型对象上, 所以这一部分其实就是 es6的
Class
对应的原生js写法; 第三局部,
Dog
类的写法和第二局部大体雷同,然而还是有几处区别:_super.call(this, name)
,_super
代表父类,所以这一步是应用父类的构造函数生成一个对象,之后再依据本身的构造函数,批改该对象;__extends
办法,也是本文的核心内容。
最初来介绍第一局部,也就是
__extends
的具体实现。这部分的外层也是一个简略的防止反复定义以及匿名立刻执行函数(IIFE),这一点就不赘述了。 核心内容是extendStatics
的实现:首先介绍下
Object.setPrototypeOf
这个办法,这个办法的作用是为某个对象从新指定原型,用法如下:Object.setPrototypeOf(d, b) // 等价于d.__proto__ = b;
后续每个
||
分隔符前面,都能够了解为一种polyfill
写法,只是为了兼容不同的执行环境;接下来返回一个新的函数,后面提到,间接转换过去的可能有点艰涩,所以我在这里略微整顿成可读性更强的写法:
return function (d, b) {// 当b不是构造函数或者null时,抛出谬误 if (typeof b !== "function" && b !== null) { throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); } // 批改d原型链指向 extendStatics(d, b); // 模仿原型链的继承 function Temp() { this.constructor = d; } if(b === null){ d.prototype = {}; // Object.create(null) 此时返回一个新的空对象{} } else { Temp.prototype = b.prototype; var temp = new Temp(); d.prototype = temp; }};此处第一个 `if` 比拟好了解,不多解释;
接下来的 extendStatics(d, b)
也介绍了成果是 d.__proto__ = b;
再接着就是比拟有意思了,为了不便大家看懂,还是画一下相干的关系图:
首先, d和b
各自独立(当然)这里请留神!!!,咱们用大写字母B和D分表示意b和d的构造函数,而b和d自身也可能还是一个函数,也还有本人对应的原型对象,只是图上没有标出。(眼神不太好或者不太认真的同学务必要认真 否则很容易了解出错)
举个例子,前文的 Animal
对应图上的b, 那么 B
则对应 Function
, 即 Animal.__proto__ = Function.prototype
, 然而与此同时,Animal
还有本人的原型对象Animal.protptype
:
执行extendStatics(d, b)
后,原型关系如下(D的构造函数和原型对象变成不可拜访了,所以用灰色示意):
再接着 执行以下代码之后:
function Temp() { this.constructor = d; }Temp.prototype = b.prototype;var temp = new Temp(); d.prototype = temp;
结构图如下:
从图上能够看到,这个长期变量temp
最初变成了d
的原型对象, 同时也是一个b的实例。 这一点和咱们最早学过的原型链继承其实是相似的,区别在于多了一个 d.__proto__ = b
.
那么,如果执行 var dog = new Dog('wangwang', 12);
其实,这里的 Dog
就对应上图的 d
, dog
的原型链其实就是 dog.__proto__ === temp
,再向上也就是 b.prototype
,天然也就能够调用到定义在b.prototype
的办法了。
自测环节
那么在实现 extend
之后,答复几个问题,测试下本人的了解水平。
Q1: 首先,属性是怎么继承的,和ES5有何区别?
A1: extend是通过调用父类的办法创立初始对象,在此基础上,再依据子类的构造函数对该对象进行调整; ES5 的继承(组合继承),本质是先发明子类的实例对象 this
,再利用 call
或者 apply
,将父类的属性增加到 this
.
Q2: dog
是如何调用到 move
办法的?
A2: 这个问题其实就是后面刚刚剖析的原型链模型,办法的查找程序是: dog.move(不存在) > dog.__proto__(temp变量).move (不存在) > dog.__proto__.__proto__.move (找到)
Q3: 多进去的d.__proto__ = b
有何作用?
A3: 能够继承父类的静态方法,例如增加办法: Animail.sayHello = function() {console.log('hello')};
,那么Dog.sayHello()
同样失效,能够参照上图进行了解,查找程序: d.hello(不存在) > d.__proto__.hello (找到)
小结
本文是继承系列的后续文章,次要针对ES6
里Extend
做个简略的源码剖析和原理介绍,最要害的还是原型链的图解局部,心愿能对读者有帮忙。
欢送大家关注专栏,也心愿大家对于青睐的文章,可能不吝点赞和珍藏,对于行文格调和内容有任何意见的,都欢送私信交换。
(想来外企的小伙伴欢送私信或者增加主页联系方式征询详情~)