这篇文章主要讲解面试某大厂遇到的一个问题 - ES6中的class语法的实现?前端
ECMAScript 6 实现了class,class是一个语法糖,使得js的编码更清晰、更人性化、风格更接近面向对象的感受;也使 IDE 、编译期类型检查器、代码风格检查器等工具更方便地检测代码语法,作静态分析。一样的,这给没有类就缺点什么的软件开发工程师一个更低的门槛去接触js。面试
JavaScript语言的传统方法是经过构造函数定义并生成新对象,这种写法和传统的面向对象语言差别较大。因此,ES6引入了Class这个概念做为对象的模板。数组
效果:ES6建立一个class会默认添加constructor
方法,并在new调用时自动调用该方法。bash
ES5:babel
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构造函数。ui
类的公共方法都定义在类的prototype属性上。可使用
Object.assign
一次向类添加多个方法。this
特别的:class的内部定义的方法都是不可枚举的(non-enumerable),这一点与ES5的行为不一致。编码
ES5:
Object.keys(Person.prototype); // ['toString']
复制代码
ES6:
Object.keys(Person.prototype); // Person {}
复制代码
不可枚举的代码实现会在后面将ES6代码用Babel转码以后解析。
效果: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.
复制代码
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);
}
}
复制代码
其余class特性:
下文主要用babel转码器分别对class中几个主要的方法进行转码,分析ES5的实现方式。 将下面的代码使用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;
}
复制代码
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);
复制代码
解析:
instance instanceof Constructor
为false
,抛出异常。读到这相信你们对class的实现有了更深的理解。笔者也是一边在忙毕业设计,一边整理了这道阿里前端面试题的解析。但愿你们有所收获。
评论区欢迎对ES6 class实现原理相关问题进行讨论。另外,class中的extend
也是颇有趣的实现,在下一篇文章会对class实现继承进行解析。