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 相关文章推荐
input+select(multiple) 实现下拉框输入值
May 21 Javascript
js Html结构转字符串形式显示代码
Nov 15 Javascript
封装的jquery翻页滚动(示例代码)
Nov 18 Javascript
Javascript中3个需要注意的运算符
Apr 02 Javascript
js实现不重复导入的方法
Mar 02 Javascript
javaScript事件机制兼容【详细整理】
Jul 23 Javascript
Angular2搜索和重置按钮过场动画
May 24 Javascript
vue实现某元素吸顶或固定位置显示(监听滚动事件)
Dec 13 Javascript
小程序图片长按识别功能的实现方法
Aug 30 Javascript
Vue cli3 库模式搭建组件库并发布到 npm的流程
Oct 12 Javascript
使用element-ui的el-menu导航选中后刷新页面保持当前选中状态
Jul 19 Javascript
原生javascript如何实现共享onload事件
Jul 03 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
使用array_map简单搞定PHP删除文件、删除目录
2014/10/29 PHP
php检查日期函数checkdate用法实例
2015/03/19 PHP
php计算整个目录大小的方法
2015/06/01 PHP
PHP getNamespaces()函数讲解
2019/02/03 PHP
Code:loadScript( )加载js的功能函数
2007/02/02 Javascript
javascript XML数据显示为HTML一例
2008/12/23 Javascript
给jQuery方法添加回调函数一款插件的应用
2013/01/21 Javascript
简略说明Javascript中的= =(等于)与= = =(全等于)区别
2013/04/16 Javascript
JavaScript函数详解
2015/02/27 Javascript
解决bootstrap中modal遇到Esc键无法关闭页面
2015/03/09 Javascript
JavaScript从数组中删除指定值元素的方法
2015/03/18 Javascript
jquery中ready()函数执行的时机和window的load事件比较
2015/06/22 Javascript
探讨JavaScript标签位置的存放与功能有无关系
2016/01/15 Javascript
AngularJS仿苹果滑屏删除控件
2016/01/18 Javascript
手把手搭建安装基于windows的Vue.js运行环境
2017/06/12 Javascript
jquery UI实现autocomplete在获取焦点时得到显示列表功能示例
2019/06/04 jQuery
no-vnc和node.js实现web远程桌面的完整步骤
2019/08/11 Javascript
linux 下以二进制的方式安装 nodejs
2020/02/12 NodeJs
JavaScript代码异常监控实现过程详解
2020/02/17 Javascript
javascript前端实现多视频上传
2020/12/13 Javascript
[48:27]EG vs Liquid 2018国际邀请赛淘汰赛BO3 第二场 8.25
2018/08/29 DOTA
python 图片验证码代码
2008/12/07 Python
python实现多线程采集的2个代码例子
2014/07/07 Python
python脚本实现分析dns日志并对受访域名排行
2014/09/18 Python
Python如何应用cx_Oracle获取oracle中的clob字段问题
2019/08/27 Python
postman传递当前时间戳实例详解
2019/09/14 Python
Python代码块及缓存机制原理详解
2019/12/13 Python
HTML5 Canvas图像模糊完美解决办法
2018/02/06 HTML / CSS
法国在线药房:DoctiPharma
2020/10/21 全球购物
会计电算化专业毕业生推荐信
2013/12/24 职场文书
2014年六一儿童节演讲稿
2014/05/23 职场文书
干部作风建设个人剖析材料
2014/10/11 职场文书
2014年幼儿园个人工作总结
2014/11/10 职场文书
办公室文员岗位职责
2015/02/04 职场文书
教您:房贷工资收入证明应该怎么写?
2019/08/19 职场文书
Golang 链表的学习和使用
2022/04/19 Golang