class 是一个特殊的函数
参考
1.序言
ECMAScript 2015(ES6) 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法(class)不会为JavaScript引入新的面向对象的继承模型。
2.class 是一个特殊的函数
ES6 的 class 主要提供了更多方便的语法去创建老式的构造器函数。我们可以通过 typeof 得到其类型:
class People { constructor(name) { this.name = name; } } console.log(typeof People) // function那 class 声明的类到底是一个什么样的函数呢?我们可以通过在线工具 来分析 class 背后真正的实现。
3.class 的工作原理
下面通过多组代码对比,来解析 class 声明的类将转化成什么样的函数。
第一组:用 class 声明一个空类
ES6的语法:
class People {}这里提出两个问题:
class 声明的类与函数声明不一样,不会提升(即使用必须在声明之后),这是为什么?
console.log(People) // ReferenceError class People {}在浏览器中运行报错,如下图:
不能直接像函数调用一样调用类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 声明私有字段时,约定是以字符 '#' 为开头,转化后则将标识符中的 '#' 替换为 '_',并且单独用一个 的变量来替代类的私有字段,声明在函数表达式后面(也会自动检查命名冲突),这样就保证了类的实例对象无法直接通过属性访问到私有字段(私有字段根本就没有在实例对象的属性中)。
对于公共字段,则是通过 _defineProperty 函数将类的公共字段映射为实例对象的属性,如果是首次设置,还会通过Object.defineProperty来进行初始化,设置属性的可枚举性(enumerable)、可配置性(configurable)、可写性(writable)
第三组:给类添加构造函数与实例属性