ES6中的class是如何实现的(附Babel编译的ES5代码详解)


Posted in Javascript onMay 17, 2019

序言

这篇文章主要讲解面试中的一个问题 - 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实现继承进行解析。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS 显示当前日期与时间的代码
Mar 24 Javascript
JSON 数据格式介绍
Jan 13 Javascript
Js+Flash实现访问剪切板操作
Nov 20 Javascript
手机端网页点击链接触发自动拨打或保存电话的示例代码
Aug 15 Javascript
seajs加载jquery时提示$ is not a function该怎么解决
Oct 23 Javascript
只需五句话搞定JavaScript作用域(经典)
Jul 26 Javascript
Bootstrop实现多级下拉菜单功能
Nov 24 Javascript
微信小程序 常见问题总结(4058,40013)及解决办法
Jan 11 Javascript
ionic2懒加载配置详解
Sep 01 Javascript
微信小程序获取用户信息并保存登录状态详解
May 10 Javascript
JS实现水平遍历和嵌套递归操作示例
Aug 15 Javascript
详解Vue后台管理系统开发日常总结(组件PageHeader)
Nov 01 Javascript
微信小程序云开发实现数据添加、查询和分页
May 17 #Javascript
js常用正则表达式集锦
May 17 #Javascript
Angular请求防抖处理第一次请求失效问题
May 17 #Javascript
vue cli 3.0 搭建项目的图文教程
May 17 #Javascript
小程序云开发如何实现图片上传及发表文字
May 17 #Javascript
tsconfig.json配置详解
May 17 #Javascript
小程序云函数调用API接口的方法
May 17 #Javascript
You might like
PHP错误Cannot use object of type stdClass as array in错误的解决办法
2014/06/12 PHP
php实现插入数组但不影响原有顺序的方法
2015/03/27 PHP
一款JavaScript压缩工具:X2JSCompactor
2007/06/13 Javascript
符合W3C网页标准的iframe标签的使用方法
2007/07/19 Javascript
不同浏览器的怪癖小结
2010/07/11 Javascript
基于jquery的兼容各种浏览器的iframe自适应高度的脚本
2010/08/13 Javascript
JavaScript的document对象和window对象详解
2010/12/30 Javascript
javascript 事件处理程序介绍
2012/06/27 Javascript
Extjs NumberField后面加单位实现思路
2013/07/30 Javascript
使用js实现关闭js弹出层的窗口
2014/02/10 Javascript
JS判断客户端是手机还是PC的2个代码
2014/04/12 Javascript
JavaScript中如何通过arguments对象实现对象的重载
2014/05/12 Javascript
JavaScript中具名函数的多种调用方式总结
2014/11/08 Javascript
js实现仿QQ秀换装效果的方法
2015/03/04 Javascript
基于Jquery实现焦点图淡出淡入效果
2015/11/30 Javascript
快速掌握WordPress中加载JavaScript脚本的方法
2015/12/17 Javascript
浅谈vue.js导入css库(elementUi)的方法
2018/03/09 Javascript
详解extract-text-webpack-plugin 的使用及安装
2018/06/12 Javascript
微信内置开发 iOS修改键盘换行为搜索的解决方案
2019/11/06 Javascript
手写Vue源码之数据劫持示例详解
2021/01/04 Vue.js
vue实现桌面向网页拖动文件的示例代码(可显示图片/音频/视频)
2021/03/01 Vue.js
[02:53]DOTA2英雄基础教程 山岭巨人小小
2013/12/09 DOTA
低版本中Python除法运算小技巧
2015/04/05 Python
Python学习之用pygal画世界地图实例
2017/12/07 Python
python实现连连看辅助(图像识别)
2020/03/25 Python
Python如何将装饰器定义为类
2020/07/30 Python
Python操作dict时避免出现KeyError的几种解决方法
2020/09/20 Python
PyCharm安装PyQt5及其工具(Qt Designer、PyUIC、PyRcc)的步骤详解
2020/11/02 Python
使用CSS3制作版头动画效果
2020/12/24 HTML / CSS
Pretty You London官网:英国拖鞋和睡衣品牌
2019/05/08 全球购物
你常见到的runtime exception
2016/09/05 面试题
交通法规咨询中心工作职责
2013/11/27 职场文书
车贷收入证明范本
2014/01/09 职场文书
财务部经理岗位职责
2014/02/03 职场文书
2014年重阳节敬老活动方案
2014/09/16 职场文书
使用Canvas绘制一个游戏人物属性图
2022/03/25 Javascript