目录html
1.序言react
ECMAScript 2015(ES6) 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法(class)不会为JavaScript引入新的面向对象的继承模型。git
2.class 是一个特殊的函数es6
ES6 的 class 主要提供了更多方便的语法去建立老式的构造器函数。咱们能够经过 typeof 获得其类型:github
class People { constructor(name) { this.name = name; } } console.log(typeof People) // function
那 class 声明的类究竟是一个什么样的函数呢?咱们能够经过在线工具 ES6 to ES5 来分析 class 背后真正的实现。express
3.class 的工做原理浏览器
下面经过多组代码对比,来解析 class 声明的类将转化成什么样的函数。babel
第一组:用 class 声明一个空类app
ES6的语法:函数
class People {}
这里提出两个问题:
1.class 声明的类与函数声明不同,不会提高(即便用必须在声明以后),这是为何?
console.log(People) // ReferenceError class People {}
在浏览器中运行报错,以下图:
2.不能直接像函数调用同样调用类People()
,必须经过 new 调用类,如 new People()
,这又是为何?
class People {} People() // TypeError
在浏览器中运行报错,以下图:
转化为ES5:
"use strict"; function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } // 判断 Constructor.prototype 是否出如今 instance 实例对象的原型链上 function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var People = function People() { // 检查是否经过 new 调用 _classCallCheck(this, People); };
针对上面提到的两个问题,咱们均可以用转化后的 ES5 代码来解答:
对于问题1,咱们能够看到 class 声明的类转化为的是一个函数表达式,而且用变量 People 保存函数表达式的值,而函数表达式只能在代码执行阶段建立并且不存在于变量对象中,因此若是在 class 声明类以前使用,就至关于在给变量 People 赋值以前使用,此时使用是没有意义的,由于其值为 undefined,直接使用反而会报错。因此 ES6 就规定了在类声明以前访问类会抛出 ReferenceError 错误(类没有定义)。
对于问题2,咱们能够看到 People 函数表达式中,执行了 _classCallCheck 函数,其做用就是保证 People 函数必须经过 new 调用。若是直接调用 People(),因为是严格模式下执行,此时的 this 为 undefined,调用 _instanceof 函数检查继承关系其返回值必然为 false,因此必然会抛出 TypeError 错误。
补充:类声明和类表达式的主体都执行在严格模式下。好比,构造函数,静态方法,原型方法,getter和setter都在严格模式下执行。
第二组:给类添加公共字段和私有字段
ES6的语法:
class People { #id = 1 // 私有字段,约定以单个的`#`字符为开头 name = 'Tom' // 公共字段 }
转化为ES5:
... // 将类的公共字段映射为实例对象的属性 function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var People = function People() { _classCallCheck(this, People); // 初始化私有字段 _id.set(this, { writable: true, value: 1 }); // 将类的公共字段映射为实例对象的属性 _defineProperty(this, "name", 'Tom'); }; // 转化后的私有字段(会自动检查命名冲突) var _id = new WeakMap();
对比转化先后的代码能够看出:
对于私有字段,在使用 class 声明私有字段时,约定是以字符 '#' 为开头,转化后则将标识符中的 '#' 替换为 '_',而且单独用一个 WeakMap 类型的变量来替代类的私有字段,声明在函数表达式后面(也会自动检查命名冲突),这样就保证了类的实例对象没法直接经过属性访问到私有字段(私有字段根本就没有在实例对象的属性中)。
对于公共字段,则是经过 _defineProperty 函数将类的公共字段映射为实例对象的属性,若是是首次设置,还会经过 Object.defineProperty 函数来进行初始化,设置属性的可枚举性(enumerable)、可配置性(configurable)、可写性(writable)
第三组:给类添加构造函数与实例属性
ES6的语法:
class People { #id = 1 // 私有字段,约定以单个的`#`字符为开头 name = 'Tom' // 公共字段 constructor(id, name, age) { this.#id = id this.name = name this.age = age // 实例属性 age } }
转化为ES5:
... // 设置(修改)类的私有字段 function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to set private field on non-instance"); } if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } return value; } var People = function People(id, name, age) { _classCallCheck(this, People); _id.set(this, { writable: true, value: 1 }); _defineProperty(this, "name", 'Tom'); // constructor 从这开始执行 _classPrivateFieldSet(this, _id, id); this.name = name; this.age = age; }; var _id = new WeakMap();
对比转化先后的代码能够看出:
类的构造函数(constructor)里面的代码的执行时机是在字段定义(字段映射为实例对象的属性)以后。而对私有字段的赋值(修改)是专门经过 _classPrivateFieldSet 函数来实现的。
第四组:给类添加原型方法和静态方法
ES6的语法:
class People { #id = 1 name = 'Tom' constructor(id, name, age) { this.#id = id this.name = name this.age = age } // 原型方法 getName() { return this.name } // 静态方法 static sayHello() { console.log('hello') } }
转化为ES5:
... // 设置对象的属性 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); } } // 将类的方法映射到构造函数的原型(Constructor.prototype)的属性上 // 将类的静态方法映射到构造函数(Constructor)的属性上 function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var People = function () { function People(id, name, age) { // ... } // 设置类的方法和静态方法 _createClass(People, [{ key: "getName", value: function getName() { return this.name; } }], [{ key: "sayHello", value: function sayHello() { console.log('hello'); } }]); return People; }(); var _id = new WeakMap();
对比一下第三组和第四组转化后的代码,能够明显发现:
类的字段经过 _defineProperty 函数映射到实例对象(this)的属性上。
类的方法则经过 _createClass 函数映射到构造函数的原型(Constructor.prototype)的属性上,
类的静态方也经过 _createClass 函数映射到构造函数(Constructor)的属性上。
第五组:类的继承
ES6的语法:
// 父类(superClass) class People {} // 子类(subClass)继承父类 class Man extends People {}
转化为ES5:
... var People = function People() { _classCallCheck(this, People); }; var Man = function (_People) { // Man 继承 _People _inherits(Man, _People); // 获取 Man 的父类的构造函数 var _super = _createSuper(Man); function Man() { _classCallCheck(this, Man); // 实现了父类构造函数的调用, 子类的 this 继承父类的 this 上的属性 return _super.apply(this, arguments); } return Man; }(People);
在 _inherits 函数中,实现了原型链和静态属性的继承:
// 实现继承关系 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } // Object.create(proto, propertiesObject) 方法 // 建立一个新对象,使用 proto 来提供新建立的对象的__proto__ // 将 propertiesObject 的属性添加到新建立对象的不可枚举(默认)属性(即其自身定义的属性,而不是其原型链上的枚举属性) subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } // 设置对象 o 的原型(即 __proto__ 属性)为 p function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
1.经过 Object.create
函数调用可知:
(1)subClass.prototype.__proto__ === superClass.prototype
,至关于实现了原型链的继承
(2)subClass.prototype.constructor === subClass
,代表 subClass 构造函数的显示原型对象(prototype)的 constructor 属性指向原构造函数
2.经过调用 _setPrototypeOf(subClass, superClass)
可知:
(1)subClass.__proto__ === superClass
,至关于实现了静态属性的继承
在 Man 构造函数中,经过调用其父类的构造函数(_super),实现了子类的 this 继承父类的 this 上的属性:
// 得到父类的构造函数 function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } // 判断 call 的类型,返回合适的 Constructor function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } // 断言 selft 是否初始化 function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } // 判断是否可否使用 Reflect function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } // 获取 o 对象的原型(__proto__) function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
从上述可知 class 继承的实现主要包含三部分:
4.class 继承的原型链关系
实例代码:
class People { constructor(name) { this.name = name } } class Man extends People { constructor(name, sex) { super(name) this.sex = sex } } var man = new Man('Tom', 'M')
根据上面分析所知道的类(class)的继承的实现原理,并结合 深刻理解JS中的对象(一):原型、原型链和构造函数 中所提到的构造函数的原型链关系,可得示例代码的完整原型链关系以下图:
5.参考