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 相关文章推荐
推荐自用 Javascript 缩图函数 (onDOMLoaded)……
Oct 23 Javascript
心扬JS分页函数代码
Sep 10 Javascript
jQuery不间断滚动效果(模拟百度新闻支持文字/图片/垂直滚动)
Feb 05 Javascript
sencha touch 模仿tabpanel导航栏TabBar的实例代码
Oct 24 Javascript
解决window.opener=null;window.close(),只支持IE6不支持IE7,IE8的问题
Jan 14 Javascript
JavaScript中string转换成number介绍
Dec 31 Javascript
Javascript writable特性介绍
Feb 27 Javascript
JavaScript实现的SHA-1加密算法完整实例
Feb 02 Javascript
javascript简单判断输入内容是否合法的方法
May 11 Javascript
BootStrapTable 单选及取值的实现方法
Jan 10 Javascript
layui的面包屑或者表单不显示的解决方法
Sep 05 Javascript
vue-resource 拦截器interceptors使用详解
Jan 18 Vue.js
微信小程序云开发实现数据添加、查询和分页
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 is_file()和is_dir()用于遍历目录时用法注意事项
2010/03/02 PHP
了解Joomla 这款来自国外的php网站管理系统
2010/03/11 PHP
基于php无限分类的深入理解
2013/06/02 PHP
PHP实现定时执行任务的方法
2014/10/05 PHP
PHP开发注意事项总结
2015/02/04 PHP
分享PHP计算两个日期相差天数的代码
2015/12/23 PHP
PHP正则获取页面所有图片地址
2016/03/23 PHP
PHP crc32()函数讲解
2019/02/14 PHP
PHP 文件写入和读取操作实例详解【必看篇】
2019/11/04 PHP
Laravel 验证码认证学习记录小结
2019/12/20 PHP
使用jQuery简化Ajax开发 Ajax开发入门
2009/10/14 Javascript
使用Javascript简单实现图片无缝滚动
2014/12/05 Javascript
SpringMVC返回json数据的三种方式
2015/12/10 Javascript
浅析JavaScript声明变量
2015/12/21 Javascript
jQuery实现网页顶部固定导航效果代码
2015/12/24 Javascript
基于javascript实现右下角浮动广告效果
2016/01/08 Javascript
浅析jQuery中使用$所引发的问题
2016/05/29 Javascript
微信小程序商品到详情的实现
2017/06/27 Javascript
jQuery实现ajax回调函数带入参数的方法示例
2018/06/26 jQuery
js实现登录拖拽窗口
2020/02/10 Javascript
微信浏览器左上角返回按钮监听的实现
2020/03/04 Javascript
Python连接DB2数据库
2016/08/27 Python
python实现redis三种cas事务操作
2017/12/19 Python
python互斥锁、加锁、同步机制、异步通信知识总结
2018/02/11 Python
Python 忽略warning的输出方法
2018/10/18 Python
Python将json文件写入ES数据库的方法
2019/04/10 Python
Yankee Candle官网:美国最畅销蜡烛品牌之一
2020/01/05 全球购物
银行实习生的自我评价
2013/12/09 职场文书
父母对孩子说的话
2014/04/12 职场文书
2014党委书记四风对照检查材料思想汇报
2014/09/21 职场文书
同学会邀请函模板
2015/01/30 职场文书
吧主申请感言怎么写
2015/08/03 职场文书
网吧员工管理制度
2015/08/05 职场文书
幼儿园教师教学反思
2016/03/02 职场文书
Redisson实现Redis分布式锁的几种方式
2021/08/07 Redis
浅谈MySQL中的六种日志
2022/03/23 MySQL