ES6中的class是如何实现的(附Babel编译的ES5代码详解)
序言
这篇文章主要讲解面试中的一个问题 - es6中的class语法的实现?
ecmascript 6 实现了class,class是一个语法糖,使得js的编码更清晰、更人性化、风格更接近面向对象的感觉;也使 ide 、编译期类型检查器、代码风格检查器等工具更方便地检测代码语法,做静态分析。同样的,这给没有类就缺点什么的软件开发工程师一个更低的门槛去接触js。
es6 class 的 es5 代码实现
javascript语言的传统方法是通过构造函数定义并生成新对象,这种写法和传统的面向对象语言差异较大。所以,es6引入了class这个概念作为对象的模板。
constructor
效果:es6创建一个class会默认添加constructor方法,并在new调用时自动调用该方法。
es5:
function person(name, age) { this.name = name; this.age = age; } person.prototype.tostring = function () { return '(' + this.name + ',' + this.age + ')'; } var p = new person('mia', 18); console.log(p);// person { name: 'mia', age: 18 }
es6:
class person { constructor(name, age) { this.name = name; this.age = age; } tostring() { return '(' + this.name + ',' + this.age + ')'; } } var p = new person('mia', 18); console.log(p);// person { name: 'mia', age: 18 }
es6的class中constructor是构造方法,对应的是es5中的构造函数person,this关键字则代表实例对象。
里面的class类可以看做是构造函数的另一种写法,由typeof person === 'function'为true;person === person.prototype.constructor为true可以得出,类的数据类型就是函数,类本身指向构造函数。也可以说class的底层依然是function构造函数。
类的公共方法都定义在类的prototype属性上。可以使用object.assign一次向类添加多个方法。
特别的:class的内部定义的方法都是不可枚举的(non-enumerable),这一点与es5的行为不一致。
es5:
object.keys(person.prototype); // ['tostring']
es6:
object.keys(person.prototype); // person {}
不可枚举的代码实现会在后面将es6代码用babel转码之后解析。
new调用
效果:class类必须使用new调用,否则会报错。
es5:
person()// undefined
es6:
person() // typeerror: class constructor person cannot be invoked without 'new'
实例的属性
效果:实例的属性是显式定义在this对象上,否则都是定义在原型上。类的所有实例共享一个原型对象,与es5行为一致。
es5:
function person() { this.grade = { count: 0 }; }
es6:
class person { constructor() { this.grade = { count: 0 }; } }
此外还要关注新提案,babel已经支持实例属性和静态属性新的写法。
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
注意:如果静态方法包含this关键字,指的是类。
es5:
function person() { } person.tosay = function () { return 'i love javascript.'; }; person.tosay(); // i love javascript.
es6:
class person { static tosay() { return 'i love javascript.'; } } person.tosay(); // i love javascript.
getter 和 setter
es6提供 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为,和es5行为一致。
es5:
function person(name) {} person.prototype = { get name() { return 'mia'; }, set name(newname) { console.log('new name:' + newname); } }
es6:
class person { get name() { return 'mia'; } set name(newname) { console.log('new name:' + newname); } }
es6 class 底层实现原理
下文主要用babel转码器分别对class中几个主要的方法进行转码,分析es5的实现方式。
babel是如何编译class的
将下面的代码使用babel转码器转换成es5代码,按照代码结构和功能分块进行讲解。
class person { constructor(name, age) { this.name = name; this.age = age; } tostring() { return '(' + this.name + ',' + this.age + ')'; } } var p = new person('mia', 18);
运行模式
"use strict";//class默认开启严格模式
私有函数:
js开发者在变量名或函数名前缀加下划线,一般表示私有。
前缀加下划线表示私有仅仅是一个约定俗成的习惯,澄清意图,并没有做其他处理。由于ecmascript草案中并没有定义私有变量的方法,所以在此限定之下仍可以在函数外或作用域外访问该函数或变量。
_instanceof和_classcallcheck的作用
检查声明的class类是否通过new的方式调用,否则会报错。
function _instanceof(left, right) { if (right != null && typeof symbol !== "undefined" && right[symbol.hasinstance]) { return right[symbol.hasinstance](left); } else { return left instanceof right; } } function _classcallcheck(instance, constructor) { if (!_instanceof(instance, constructor)) { throw new typeerror("cannot call a class as a function"); } }
_createclass和_defineproperties的作用
_createclass函数有三个参数,constructor是传入构造函数person,protoprops 是要添加到原型上的函数数组,staticprops 是要添加到构造函数本身的函数,即静态方法。这里的第二个和第三个参数是可以缺省的,会在_createclass 函数体内判断。
_createclass 函数的作用是收集公有函数和静态方法,将方法添加到构造函数或构造函数的原型中,并返回构造函数。
defineproperties 是将方法添加到构造函数或构造函数的原型中的主要逻辑,遍历函数数组,分别声明其描述符。若enumerable 没有被定义为true,则默认为fals,设置 configurable 为true。以上两个布尔值是为了限制 object.keys() 之类的方法被遍历到。如果存在 value,就为 descriptor 添加 value 和 writable 属性,如果不存在,就直接使用 get 和 set 属性。
最后,使用 object.defineproperty 方法为构造函数添加属性。
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); } } function _createclass(constructor, protoprops, staticprops) { if (protoprops) _defineproperties(constructor.prototype, protoprops); if (staticprops) _defineproperties(constructor, staticprops); return constructor; }
class类实现
var person = /*#__pure__*/ function () { function person(name, age) { _classcallcheck(this, person); this.name = name; this.age = age; } _createclass(person, [{ key: "tostring", value: function tostring() { return '(' + this.name + ',' + this.age + ')'; } }]); return person; }(); var p = new person('mia', 18);
解析:
不使用new调用时,this指向window,所以instance instanceof constructor为false,抛出异常。
通过调用_createclass函数,遍历函数数组。key为方法名,若有value说明是有具体的 function 声明,若无 value 说明使用了get 或 set 方法。
结尾
读到这相信大家对class的实现有了更深的理解。最近笔者一边在忙毕业设计,一边整理了这道阿里前端面试题的解析,评论区欢迎对class实现这一问题进行讨论。另外,class中的extend也是很有趣的实现,在下一篇文章会对class实现继承进行解析。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 这个民族曾经在华夏大地上繁衍2000年,却突然神秘消失
下一篇: 无人驾驶为什么这么火