前言
在JavaScript之继承与原型链一文中讲到了继承与原型链相关的基础知识,本文将讲解ES6中的extends是如何实现的。
ES6 extends 做了哪些操作
我们先来看下使用extends常见的写法
class Parent {
constructor(name) {
this.name = name;
}
static speak(msg) {
console.log(msg);
}
walk() {
console.log(this.name + ' is walking');
}
}
class Child extends Parent {
constructor(name, gender) {
super(name);
this.gender = gender;
}
sayGender() {
console.log('my gender is ' + this.gender);
}
}
let parent = new Parent('Parent');
let child = new Child('Child', 'male');
console.log(parent); // {name: 'Parent'}
Parent.speak('Hi'); // Hi
parent.walk(); // Parent is walking
console.log(child); // {name: 'Child', gender: 'male'}
Child.speak('Welcome'); // Welcome
child.walk(); // Child is walking
child.sayGender(); // my gender is male
我们在父类Parent中定义了构建函数,静态方法speak和方法walk,在子类Child中定义了构建函数,在构造函数中调用了父类的构造函数,并且在子类实例上新增了一个属性gender,另外,子类中新增了一个方法sayGender。
通过上面一系列的测试的打印结果来看,子类继承了父类的静态方法,父类实例拥有的属性和方法,子类实例也同样拥有。
我们详细展开一下父类实例parent和子类实例child的数据结构。
parent展开如下:
可以看到,name是parent实例上的属性,而walk则是parent的原型对象上的方法。
child展开如下:
可以看出,name和gender是child实例上的属性,sayGender是child的原型对象上的方法,walk是child的原型对象的原型对象(Child.prototype.__proto__)上的方法。
这表明Child.prototype.__proto__等于Parent.prototype,另外Child.__proto__等于Parent,我们可以打印验证一下。
console.log(Child.prototype.__proto__ === Parent.prototype); // true
console.log(Child.__proto__ === Parent); // true
我们可以用下面这张图来表示它们之间的关系,可以看出其中的原型链:
extends的ES5版本实现
通过上面这张关系图,我们就可以写出extends的es5版本,如下所示:
function Parent(name) {
this.name = name;
}
Parent.speak = function(msg) {
console.log(msg);
}
Parent.prototype.walk = function() {
console.log(this.name + ' is walking');
}
function Child(name, gender) {
// 调用父类构造函数,通过call修改this
Parent.call(this, name);
this.gender = gender;
}
Child.prototype.sayGender = function() {
console.log('my gender is ' + this.gender);
}
// 使用Object.setPrototypeOf
Object.setPrototypeOf(Child.prototype, Parent.prototype);
Object.setPrototypeOf(Child, Parent);
在实际的开发过程中,babel会将ES6 extends的相关代码转换成类似上面这段ES5代码。当然babel的转换代码会更加严谨,包含了很多的polyfill的代码。