咱们带着问题去阅读本文:javascript
先来看一个prototype的例子:html
function Person (name, sex) {
this.name = name
this.sex = sex
}
function Man (name) {
this.name = name
}
Man.prototype = new Person('', 'male')
let Jy = new Man('Jy')
console.log(Jy.name, Jy.sex) // Jy, male
复制代码
这是咱们使用原型的一个很简单的例子,Person具备名字和性别,Man是一个性别为男的Person,Jy是一个Man。咱们先记住这一个例子,下面将使用class重写这个例子。前端
Tips: new, this等是Brendan Eich使之更像Java的OOP而加上的,有兴趣的读者能够自行查阅相关信息。java
class Person {
constructor (name, sex) {
this.name = name
this.sex = sex
}
}
class Man extends Person {
constructor (name) {
super('', 'male')
this.name = name
}
}
let Jy = new Man('Jy')
console.log(Jy.name, Jy.sex) // Jy, 'male'
复制代码
咱们经过重写这个例子,采用了class、constructor、extends、super 这些单词,接下来就具体来讲说ES6规范中对它们作了什么。express
在ES6以前,JS对象其实就是属性的集合,而属性则是一组键值对(key, value),key能够是String or Symbol, value包括数据属性特征值和访问器特征值。babel
你说普通的属性还好,不还有对象下面的方法吗?怎么就变成了属性的集合呢?app
其实在ES5规范中出现的method的定义是“function that is the value of a property”,是对象的函数属性而已,不能称之为方法,直到ES6出现,规范中才有Method Definitions。函数
咱们能想到的在ES3有关OOP的东西: prototype、new、 this、 constructor、 instanceof, 甚至不是规范的 __proto__
属性。oop
所幸的是在ES5中咱们增长了不少方法来补全它,使之完备:ui
再来看一段代码:
let obj = {
name: 'Jy',
speak () { // Note: it's not speak: function () {}
console.log(this.name, super.name)
}
}
obj.speak() // Jy, undefined
Object.setPrototypeOf(obj, { name: 'super' })
obj.speak() // Jy, super
let speak = obj.speak
speak() // undefined, super
复制代码
obj.speak在ES6中定义已是Method了,它具备属性[[homeObject]],homeObject指向方法被调用的对象(代码中指的是obj), 它是绑定在对象中的Internal Slots,也就是你不能去修改,就至关于写死了。
那么homeObject有什么用呢?它跟super密切相关,当解析到super这一关键字的时候就会找homeObject的prototype。
简单来讲,总结为下面两条公式:
Note: homeObject是静态绑定在internal slots中的,而super是动态查找的。
讲完super,咱们来说讲extends和constructor
class A extends B { }
class A extends B {
constructor (...args) {
super(args)
}
}
class C extends null { }
复制代码
extends主要作了如下两件事:
若是父类是null, 则执行Object.setPrototypeOf(C.prototype, null)
上述代码的第一和第二部分区别在于有没有显示声明constructor, 那么这两段代码是否等价呢?答案是等价的。
规范中就是这么定义的:
代码的第三部分是继承了null, 它不会报语法错误,可是咱们没法new一个C出来,缘由是new的时候会调用null的constructor,而null没有constructor。
看到这里,ES6的class oop, 规范声明都是使用原型来操做,因此咱们是否是能够说class是原型的语法糖了?
咱们实际项目中多采用babel来编译ES六、7的代码,因此这节咱们就来分析如下babel编译后的代码,其中会省略一些报错、类型检测的一些相关代码来更好地呈现使用原型来实现OOP的主题。
编译前:
class A extends B {}
console.log(new A)
复制代码
编译后:
"use strict";
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
var A =
/*#__PURE__*/
function (_B) {
_inherits(A, _B);
function A() {
return _getPrototypeOf(A).apply(this, arguments);
}
return A;
}(B);
console.log(new A());
复制代码
咱们重点看_inherits 方法,跟咱们上述说的extends作的两件事是同样的:
只不过它采用的是Object.create方法,这两个方法的区别能够去MDN上查看。
再看function A内部,其实就是执行了B的构造器函数来达到super(arguments)的效果, 这个与规范:若是没有显示声明constructor会自动加上constructor是一致的。
至此,咱们终于理解了为何class是原型的语法糖以及如何使用原型来实现class这一语法糖。
但切记咱们使用原型的目的并非来模拟class oop的,prototype based的oop应该用prototype去理解而不是class。
ES6的class oop 是不完备的 ,例如abstract class 、interface、private等都尚未,不过有些功能已经在提案中了,你们能够拥抱它,或者TypeScript是个不错的选择,若是你的项目中使用到了TS, 欢迎你到评论区分享你的感觉。