【重学前端】es6中class做了什么
2020-05-19 本文已影响0人
刘小胖LJX
前言
在JavaScript中不论是es5之前利用function定义一个对象的构造方法还是es6利用class定义类,都可以实现对象的实例化
- es5中定义一个对象
// es5
function Parent () {
this.name = 'parent'
this.age = 22
this.work = function () {
console.log('the ' + this.name + ' is working')
}
}
Parent.prototype.speak = function () {
console.log('hello!')
}
Parent.prototype.color = 'yello'
- es6中定义一个对象
class Parent {
constructor () {
this.name = 'parent'
this.age = 22
}
work () {
console.log('the ' + this.name + ' is working')
}
}
Parent.prototype.speak = function () {
console.log('hello!')
}
Parent.prototype.color = 'yello'
class Son extends Parent {
constructor () {
super()
this.sonName = 'son'
}
}
const son = new Son()
上述两种方法是等效的,都可以通过关键字new进行实例化
es6的class做了什么
我们利用babel转码工具对es6的class进行转码得到以下es5代码
'use strict';
// _createClass
var _createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
// _possibleConstructorReturn
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
// _inherits
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
// _classCallCheck
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
// Parent 类声明
var Parent = function () {
function Parent() {
_classCallCheck(this, Parent);
this.name = 'parent';
this.age = 22;
}
_createClass(Parent, [{
key: 'work',
value: function work() {
console.log('the ' + this.name + ' is working');
}
}]);
return Parent;
}();
Parent.prototype.speak = function () {
console.log('hello!');
};
Parent.prototype.color = 'yello';
// Son类声明(继承Parent)
var Son = function (_Parent) {
_inherits(Son, _Parent);
function Son() {
_classCallCheck(this, Son);
var _this = _possibleConstructorReturn(this, (Son.__proto__ || Object.getPrototypeOf(Son)).call(this));
_this.sonName = 'son';
return _this;
}
return Son;
}(Parent);
// Son类实例化
var son = new Son();
从以上代码可以看出,es6的class转码后分为以下几个部分
- _createClass
- _classCallCheck
- _inherits继承
- _possibleConstructorReturn(super 方法)
- Parent定义
- Parent原型方法及属性定义
- Son类定义(继承Parent)
- Son实例化
_createClass 方法
核心:通过一个闭包+立即执行函数,内部维护一个私有的defineProperties方法,遍历传入的自定义对象属性列表,调用Object.defineProperty给类添加方法,将静态方法添加到构造函数上,将非静态的方法添加到构造函数的原型对象上
- 代码解析
var _createClass = function () {
/**
* 创建class方法
* @param {*} target 目标对象
* @param {*} props 属性 Array<{ key: string, value: Function | String | Number | ...}>
*/
function defineProperties(target, props) {
// 遍历传入的属性列表,调用Object.defineProperty向目标对象上添加定义属性
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
// 属性描述符 enumerable 是否可枚举
descriptor.enumerable = descriptor.enumerable || false;
// 属性描述符 configurable 该属性的属性描述符是否可改变
descriptor.configurable = true;
// 属性描述符 writable 如果设置值, writable为true : value可以被赋值运算符修改
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
// 暴露一个方法,接收参数
/**
*
* @param {*} Constructor // 目标对象
* @param {*} protoProps // 非静态方法
* @param {*} staticProps // 静态方法
*/
return function (Constructor, protoProps, staticProps) {
// 非静态方法,添加到构造函数(对象)的原型对象上
if (protoProps) defineProperties(Constructor.prototype, protoProps);
// 静态方法,添加到构造函数上
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
- js对象的属性描述符(JSPropertyDescriptor)
Field Name | Description |
---|---|
getter |
get 语法为属性绑定一个函数,每当查询该属性时便调用对应的函数,查询的结构为该函数的返回值 |
setter |
如果试着改变一个属性的值,那么对应的 setter 函数将被执行 |
value |
描述指定属性的值 , 可以是任何有效的 Javascript 值(函数 , 对象 , 字符串 ...). |
configurable |
当且仅当该属性的 configurable 为 true 时,该属性 描述符 才能够被改变, 同时该属性也能从对应的对象上被删除. |
enumerable |
描述指定的属性是否是 可枚举 的. |
writable |
当且仅当该属性的 writable 为 true 时, value 才能被赋值运算符改变。 |
_classCallCheck方法解析
/**
* 防止类的构造函数以普通函数的方式调用
* 判断构造函数是否存在于传入实例的原型链上(实例是构造函数的实例对象)
* @param {*} instance 实例
* @param {*} Constructor 构造函数
*/
function _classCallCheck(instance, Constructor) {
// instance 即传入的this对象,判断Constructor是否存在于this的原型链上
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
new的原理
/**
* 创建一个new操作符
* @param {*} Con 构造函数
* @param {...any} args 忘构造函数中传的参数
*/
function createNew(Con, ...args) {
let obj = {} // 创建一个对象,因为new操作符会返回一个对象
Object.setPrototypeOf(obj, Con.prototype) // 将对象与构造函数原型链接起来
// obj.__proto__ = Con.prototype // 等价于上面的写法
let result = Con.apply(obj, args) // 将构造函数中的this指向这个对象,并传递参数
return result instanceof Object ? result : obj
}
如果使用普通函数的调用方式,则调用时不会修改设置实例的原型,则以上判断就会出行false,就可以限制class 类以普通函数的方式进行调用
_inherits实现继承
/**
* _inherits实现继承
* @param {*} subClass 子类
* @param {*} superClass 父类
*/
function _inherits(subClass, superClass) {
// 判断父类,既不是函数方法也不是null的情况报错
// null是所有对象的原型链的顶端
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
// 基于父类创建子类的原型对象
subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });
// 判断父类及环境是否存在Object.setPrototypeOf api
// 将父类设置为子类的原型 继承父类
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
_possibleConstructorReturn(super 方法)
结合Son中的调用分析
function Son() {
_classCallCheck(this, Son);
var _this = _possibleConstructorReturn(this, (Son.__proto__ || Object.getPrototypeOf(Son)).call(this));
_this.sonName = 'son';
return _this;
}
/**
* _possibleConstructorReturn 修改this的指向
* @param {*} self // 子类的this
* @param {*} call // 子类的原型(父类)
*/
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
// 修改this指向,将子类的原型指向父类
return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
Parent定义方法解析
var Parent = function () {
/**
* 声明一个构造函数方法,定义Parent类
*/
function Parent() {
// 判断原型(防止以函数的方式调用)
_classCallCheck(this, Parent);
// 属性定义
this.name = 'parent';
this.age = 22;
}
// 调用_createClass方法定义Parent类上的方法
_createClass(Parent, [{
key: 'work',
value: function work() {
console.log('the ' + this.name + ' is working');
}
}]);
// 返回声明的Parent类
return Parent;
}();
// 定义原型链上的属性和方法
Parent.prototype.speak = function () {
console.log('hello!');
};
Parent.prototype.color = 'yello';
补充:es5中的构造函数于es6中的class有什么区别
- ES6语法定义的class类,不能被提前调用,无法执行预解析;ES5的function函数可以提前调用,但是只有属性没有方法
- class内部会启用严格模式
- class内部不可以使用未声明的对象
- es5构造函数中可以
- class的所有方法都是不可枚举的
- class必须使用new关键词调用
- class 的继承有两条继承链
- 一条是: 子类的proto 指向父类
- 另一条: 子类prototype属性的proto属性指向父类的prototype属性.
- es6的子类可以通过proto属性找到父类,而es5的子类通过proto找到Function.prototype
// es5
function Super() {}
function Sub() {}
Sub.prototype = new Super()
Sub.prototype.constructor = Sub
var sub = new Sub()
console.log( Sub.__proto__ === Function.prototype) // true
// es6
class Super{}
class Sub extends Super {}
let sub = new Sub()
console.log( Sub.__proto__ === Super)// true