ES6 更易于继承的类语法的使用


Posted in Javascript onFebruary 11, 2019

和其它面向对象编程语言一样,ES6 正式定义了 class 类以及 extend 继承语法糖,并且支持静态、派生、抽象、迭代、单例等,而且根据 ES6 的新特性衍生出很多有趣的用法。

一、类的基本定义

基本所有面向对象的语言都支持类的封装与继承,那什么是类?

类是面向对象程序设计的基础,包含数据封装、数据操作以及传递消息的函数。类的实例称为对象。

ES5 之前通过函数来模拟类的实现如下:

// 构造函数
function Person(name) {
 this.name = name;
}
// 原型上的方法
Person.prototype.sayName = function(){
 console.log(this.name);
};
// new 一个实例
var friend = new Person("Jenny");

friend.sayName(); // Jenny
console.log(friend instanceof Person);  // true
console.log(friend instanceof Object);  // true

总结来说,定义一个类的思路如下:

1.需要构造函数封装数据
2.在原型上添加方法操作数据,
3.通过New创建实例

ES6 使用class关键字定义一个类,这个类有特殊的方法名[[Construct]]定义构造函数,在 new 创建实例时调用的就是[[Construct]],示例如下:

/*ES6*/
// 等价于 let Person = class {
class Person {
 // 构造函数
 constructor(name) {
  this.name = name;
 }
 // 等价于Person.prototype.sayName
 sayName() {
  console.log(this.name);
 }
}

console.log(typeof Person);  // function
console.log(typeof Person.prototype.sayName);  // function

let friend = new Person("Jenny");

friend.sayName(); // Jenny
console.log(friend instanceof Person);  // true
console.log(friend instanceof Object);  // true

上面的例子中class定义的类与自定义的函数模拟类功能上貌似没什么不同,但本质上还有很大差异的:

  • 函数声明可以被提升,但是class类声明与let类似,不能被提升;
  • 类声明自动运行在严格模式下,“use strict”;
  • 类中所有方法都是不可枚举的,enumerable 为 false。

二、更灵活的类

类和函数一样,是JavaScript的一等公民(可以传入函数、从函数返回、赋值),并且注意到类与对象字面量还有更多相似之处,这些特点可以扩展出类更灵活的定义与使用。

2.1 拥有访问器属性

对象的属性有数据属性和访问属性,类中也可以通过get、set关键字定义访问器属性:

class Person {
 constructor(name) {
  this.name = name;
 }

 get value () {
  return this.name + this.age
 }
 set value (num) {
  this.age = num
 }
}

let friend = new Person("Jenny");
// 调用的是 setter
friend.value = 18
// 调用的是 getter
console.log(friend.value) // Jenny18

2.2 可计算的成员名称

类似 ES6 对象字面量扩展的可计算属性名称,类也可以用[表达式]定义可计算成员名称,包括类中的方法和访问器属性:

let methodName = 'sayName'

class Person {
 constructor(name) {
  this.name = name;
 }

 [methodName + 'Default']() {
  console.log(this.name);
 }

 get [methodName]() {
  return this.name
 }

 set [methodName](str) {
  this.name = str
 }
}

let friend = new Person("Jenny");

// 方法
friend.sayNameDefault(); // Jenny
// 访问器属性
friend.sayName = 'lee'
console.log(friend.sayName) // lee

想进一步熟悉对象新特性可参考: 【ES6】对象的新功能与解构赋值

2.3 定义默认迭代器

ES6 中常用的集合对象(数组、Set/Map集合)和字符串都是可迭代对象,如果类是用来表示值这些可迭代对象的,那么定义一个默认迭代器会更有用。

ES6 通过给Symbol.iterator属性添加生成器的方式,定义默认迭代器:

class Person {
 constructor(name) {
  this.name = name;
 }

 *[Symbol.iterator]() {
  for (let item of this.name){
   yield item
  }
 }
}

var abbrName = new Person(new Set(['j', 'j', 'e', 'e', 'n', 'y', 'y', 'y',]))
for (let x of abbrName) {
 console.log(x); // j e n y
}
console.log(...abbrName) // j e n y

定义默认迭代器后类的实例就可以使用for-of循环和展开运算符(...)等迭代功能。

对以上迭代器内容感到困惑的可参考:【ES6】迭代器与可迭代对象

2.4 作为参数的类

类作为"一等公民”可以当参数使用传入函数中,当然也可以从函数中返回:

function createClass(className, val) {
 return new className(val)
}

let person = createClass(Person,'Jenny')
console.log(person) // Person { name: 'Jenny' }
console.log(typeof person) // object

2.5 创建单例

使用类语法创建单例的方式通过new立即调用类表达式:

let singleton = new class {
 constructor(name) {
  this.name = name;
 }
}('Jenny')
 
console.log(singleton.name) // Jenny

这里先创建匿名类表达式,然后 new 调用这个类表达式,并通过小括号立即执行,这种类语法创建的单例不会在作用域中暴露类的引用。

三、类的继承

回顾 ES6 之前如何实现继承?常用方式是通过原型链、构造函数以及组合继承等方式。

ES6 的类使用熟悉的extends关键字指定类继承的函数,并且可以通过surpe()方法访问父类的构造函数。

例如继承一个 Person 的类:

class Friend extends Person {
 constructor(name, phone){
  super(name)
  this.phone = phone
 }
}

let myfriend = new Friend('lee',2233)
console.log(myfriend) // Friend { name: 'lee', phone: 2233 }

Friend 继承了 Person,术语上称 Person 为基类,Friend 为派生类。

需要注意的是,surpe()只能在派生类中使用,它负责初始化 this,所以派生类使用 this 之前一定要用surpe()。

3.1 继承内建对象

ES6 的类继承可以继承内建对象(Array、Set、Map 等),继承后可以拥有基类的所有内建功能。例如:

class MyArray extends Array {
}

let arr = new MyArray(1, 2, 3, 4),
 subarr = arr.slice(1, 3)

console.log(arr.length) // 4
console.log(arr instanceof MyArray) // true
console.log(arr instanceof Array) // true
console.log(subarr instanceof MyArray) // true

注意到上例中,不仅 arr 是派生类 MyArray 的实例,subarr 也是派生类 MyArray 的实例,内建对象继承的实用之处是改变返回对象的类型。

浏览器引擎背后是通过[Symbol.species]属性实现这一行为,它被用于返回函数的静态访问器属性,内建对象定义了[Symbol.species]属性的有 Array、ArrayBuffer、Set、Map、Promise、RegExp、Typed arrays。

3.2 继承表达式的类

目前extends可以继承类和内建对象,但更强大的功能从表达式导出类!

这个表达式要求可以被解析为函数并具有[[Construct]]属性和原型,示例如下:

function Sup(val) {
 this.value = val
}

Sup.prototype.getVal = function () {
 return 'hello' + this.value
}

class Derived extends Sup {
 constructor(val) {
  super(val)
 }
}

let der = new Derived('world')
console.log(der) // Derived { value: 'world' }
console.log(der.getVal()) // helloworld

3.3 只能继承的抽象类

ES6 引入new.target元属性判断函数是否通过new关键字调用。类的构造函数也可以通过new.target确定类是如何被调用的。

可以通过new.target创建抽象类(不能实例化的类),例如:

class Abstract {
 constructor(){
  if(new.target === Abstract) {
   throw new Error('抽象类(不能直接实例化)')
  }
 }
}

class Instantiable extends Abstract {
 constructor() {
  super()
 }
}

// let abs = new Abstract() // Error: 抽象类(不能直接实例化)
 let abs = new Instantiable()
console.log(abs instanceof Abstract) // true

虽然不能直接使用 Abstract 抽象类创建实例,但是可以作为基类派生其它类。

四、类的静态成员

ES6 使用static关键字声明静态成员或方法。在类的方法或访问器属性前都可以使用static,唯一的限制是不能用于构造函数。

静态成员的作用是某些类成员的私有化,及不可在实例中访问,必须要直接在类上访问。

class Person {
 constructor(name) {
  this.name = name;
 }

 static create(name) {
  return new Person(name);
 }
}

let beauty = Person.create("Jenny");
// beauty.create('lee') // TypeError

如果基类有静态成员,那这些静态成员在派生类也可以使用。

例如将上例的 Person 作为基类,派生出 Friend 类并使用基类的静态方法create( ):

class Friend extends Person {
 constructor(name){
  super(name)
 }
}

var friend = Friend.create('lee')
console.log(friend instanceof Person) // true
console.log(friend instanceof Friend) // false

可以看出派生类依然可以使用基类的静态方法。

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

Javascript 相关文章推荐
枚举JavaScript对象的函数
Dec 22 Javascript
XML的代替者----JSON
Jul 21 Javascript
Javascript代码在页面加载时的执行顺序介绍
May 03 Javascript
jQuery Ajax中的事件详细介绍
Apr 16 Javascript
pace.js页面加载进度条插件
Sep 29 Javascript
基于canvas实现的钟摆效果完整实例
Jan 26 Javascript
ionic 上拉菜单(ActionSheet)实例代码
Jun 06 Javascript
JavaScript中的this陷阱的最全收集并整理(没有之一)
Feb 21 Javascript
ES6学习笔记之Set和Map数据结构详解
Apr 07 Javascript
JavaScript简单拖拽效果(1)
May 17 Javascript
JavaScript Event Loop相关原理解析
Jun 10 Javascript
vue 导航菜单刷新状态不消失,显示对应的路由界面操作
Aug 06 Javascript
总结4个方面优化Vue项目
Feb 11 #Javascript
JavaScript 九种跨域方式实现原理
Feb 11 #Javascript
ES6 迭代器与可迭代对象的实现
Feb 11 #Javascript
详解mpvue中小程序自定义导航组件开发指南
Feb 11 #Javascript
如何用JavaScript实现功能齐全的单链表详解
Feb 11 #Javascript
vue2.0实现的tab标签切换效果(内容可自定义)示例
Feb 11 #Javascript
vue实现动态显示与隐藏底部导航的方法分析
Feb 11 #Javascript
You might like
PHP实现网上点歌(二)
2006/10/09 PHP
解决PHP4.0 和 PHP5.0类构造函数的兼容问题
2013/08/01 PHP
PHP的拦截器实例分析
2014/11/03 PHP
PHP自带方法验证邮箱是否存在
2016/02/01 PHP
Laravel与CI框架中截取字符串函数
2016/05/08 PHP
在b/s开发中经常用到的javaScript技术
2006/08/23 Javascript
javascript GUID生成器实现代码
2009/10/31 Javascript
js鼠标左右键 键盘值小结
2010/06/11 Javascript
html+javascript实现可拖动可提交的弹出层对话框效果
2013/08/05 Javascript
jquery操作select大全
2014/04/25 Javascript
jQuery内置的AJAX功能和JSON的使用实例
2014/07/27 Javascript
打造个性化的功能强大的Jquery虚拟键盘(VirtualKeyboard)
2014/10/11 Javascript
JQuery中上下文选择器实现方法
2015/05/18 Javascript
JavaScript中使用自然对数ln的方法
2015/06/14 Javascript
在Ubuntu系统上安装Node.JS的教程
2015/10/15 Javascript
JavaScript学习总结之JS、AJAX应用
2016/01/29 Javascript
详解JavaScript中|单竖杠运算符的使用方法
2016/05/23 Javascript
jQuery实现的自动加载页面功能示例
2016/09/04 Javascript
简单实现js浮动框
2016/12/13 Javascript
jQuery设计思想
2017/03/07 Javascript
详细分析单线程JS执行问题
2017/11/22 Javascript
爬虫利器Puppeteer实战
2019/01/09 Javascript
node 标准输入流和输出流代码实例
2019/09/19 Javascript
JavaScript中的null和undefined用法解析
2019/09/30 Javascript
[59:42]Secret vs Alliacne 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
python实现连接mongodb的方法
2015/05/08 Python
python 请求服务器的实现代码(http请求和https请求)
2018/05/25 Python
python实现汽车管理系统
2018/11/30 Python
python zip,lambda,map函数代码实例
2020/04/04 Python
Python3 webservice接口测试代码详解
2020/06/23 Python
Python实现FTP文件定时自动下载的步骤
2020/12/19 Python
销售行政专员职责
2014/01/03 职场文书
学生实习介绍信
2014/01/15 职场文书
我的梦想演讲稿
2014/04/30 职场文书
SQL Server基本使用和简单的CRUD操作
2021/04/05 SQL Server
mybatis中sql语句CDATA标签的用法说明
2021/06/30 Java/Android