ES6中提供了类似于传统面向对象语言的Class类概念来定义类用来创建对象,使得创建对象的方式更加简便。同时也在语言层面提供了Module模块机制来实现模块化,有利于开发大型复杂的项目。本篇笔记主要介绍这两个知识点的相关内容。
一、class定义类
1.传统创建对象的方式
面向对象编程将我们的世界抽象为一个个的对象,传统的面向对象语言Java、C++中提供了class类的概念来定义对象。但是在传统的JavaScript语言中并没有class类,而是通过直接定义构造函数,然后通过new关键字创建对象。
//定义Vehicle构造函数
var Vehicle = function (){
this.price = 1000;
};
//通过new关键字创建对象
var v = new Vehicle();
v.price // 1000
关于JavaScript对象的更详细描述可以参考这里
2.class基本语法与特性
为了使对象创建的写法更加接近于面向对象的样式,提供更加清晰的对象原型写法,ES6引入了Class类来作为对象模板,其功能和语法基本接近于传统语言的Class类。但就本质而言,class相当于语法糖,对ES5的构造函数作了进一步的包装,因此继承了许多函数的特性。
下面是用class定义类
class A{
constructor(x, y){
this.x = x;
this.y = y;
}
}
let a = new A(1,2);
下面简单介绍class类的一些特性:
- 类的数据类型就是函数,类本身指向其构造函数
- 类的所有方法都定义在类的prototype属性上,关于prototype属性不太了解的童鞋可以参阅这里
- ES6的类内部定义的方法是不可枚举的,而ES5是可枚举的。
- Class不存在变量提升,因此必须先定义后使用
- ES6的Class类也有name属性,返回紧跟在class关键字后面的类名
- 在类中定义的函数不需要使用function关键字,末尾也不用跟逗号,否则会报错
constructor方法
ES6中Class默认提供了constructor方法,类似于Java中的构造方法,该方法在通过new创建对象时会自动调用。一个类必须有constructor()方法,如果没有显式定义那么会默认添加一个空的方法:constructor(){}.
constructor()方法默认返回该类的实例对象(this)对象,当然也可以通过return语句自行指定返回另一个对象。
实例对象
ES6通过new命令创建实例对象的方式与ES5一样。其属性如果没有通过this显式定义在实例对象上,则全部定义在原型(Class)对象上。
ES6中所有的实例对象共享一个原型对象,这一点与ES5相同。
class表达式
Class也可以通过表达式的方式定义
//下面定义的类其类名是MyClass而不是M,M只在内部有用指代当前类
const Myclass = class M{
constructor(){}
}
3.Class类的继承
和传统面向对象一样,ES6的Class类也可以通过extends关键字实现继承。
class Point{
constructor(x, y){}
}
class ColorPoint extends Point{
constructor(x, y, color){
super(x, y);
this.color = color;
}
toString(){
//这里通过super调用父类的toString方法
return this.color + ":" + super.toString();
}
}
几点注意:
- 必须在子类的constructor方法中调用super方法,否则创建实例会报错。另外必须在super之后才能使用this.因为子类没有自己的this对象,是继承的父类的this对象
- 子类实例的构建是基于对父类实例的加工,只有super才能返回父类实例.
- 作为一个对象,子类的原型(proto属性)是父类。作为一个构造函数,子类的原型(prototype属性)是父类的实例
- super关键字代表的是父类的实例
- 如果在类中的某个方法前面加上*就表示该方法是一个Generator方法
设值与取值
Class内部可以通过get和set关键字来对某个属性值进行存值函数和取值函数。从而拦截该属性的存取行为
class MyClass{
constructor(){
}
get distanse(){
return 12;
}
set distanse(value){
console.log(value);
}
}
let m = new MyClass();
m.distanse = 123;
var dis = m.distanse;
静态方法与静态属性
类作为实例的原型,其定义的所有方法都会被实例继承,如果在方法前加上static关键字则意味着该方法不会被实例继承,只能通过类调用。如果通过实例调用会报错。另外父类的静态方法可以被子类继承通过子类调用并且可以通过super调用。
ES6规定Class内部没有静态属性只有静态方法,在ES7中有关于静态属性的提案,通过Babel转码可以使用
//ES6静态属性读写方法
class Foo{}
Foo.prop = 1;
let p = Foo.prop;
//ES7规定的实例属性写法
Class MyClass1{
myProp = 11;
constructor(){
console.log(this.prop);
}
}
//ES7规定的静态属性写法
class MyClass2{
static staticProp = 12;
constructor(){
console.log(MyClass2.prop);
}
}
4.原生构造函数的继承
在ES5中,是通过先新建子类的实例对象this,在将父类的属性添加到子类上,由于父类的内部属性无法获取,因此我们无法继承原生的构造函数。但是在ES6中是先创建父类的实例对象this,然后在用子类的构造函数修饰this,因此我们可以继承父类所有的行为。因此我们可以继承已有的原生构造函数并定义自己的数据结构。
下面是ES6标准入门中一个自定义Error的例子
class ExtendableError extends Error{
constructor(message){
super();
this.message = message;
this.stack = (new Error()).stack;
this.name = this.constructor.name;
}
}
class MyError extends ExtendableError{
constructor(m){
super(m);
}
}
原生构造函数如下:
- Boolean(), Number(), String(), Array(), Date()
- Function(), RegExp(), Error(), Object()
5.new.target属性
ES6为new命令引入了new.target属性,在构造函数中返回new命令所作用的构造函数,如果构造函数不是通过new命令调用则返回undefined.另外子类继承父类时new.target会返回子类。
通过上述特性可以确保构造函数只被new命令调用或者写出只能通过子类继承才能使用的类
class MyClass1{
constructor(){
if(new.target !== undefined){
//...
}else{
throw new Error("只能通过new命令调用");
}
}
}
class MyClass2{
constructor(){
if(new.target === MyClass2){
throw new Error("本类不能实例化");
}else{
//...
}
}
}
二、Module模块化
在ES6之前JavaScript一直没有模块体系,无法将大型程序拆分为互相依赖的小文件在根据需要拼装起来。社区提供了CommonJS和AMD规范来实现浏览器和服务的模块化。ES6在语言层面上提供了Module实现模块功能。
有关JavaScriptm模块化和CommonJS/AMD规范可以参阅阮一峰老师的这篇文章
简单来说,ES6提供的模块功能主要是通过export命令显式静态指定输入的代码,然后通过import命令静态引入。在
编译时就确定模块的依赖关系以及输入输出的变量。
1.export命令与import命令
ES6模块功能主要是由export命令和import命令构成。export命令用来规定模块的对外接口;import命令用于输入其他模块所提供的功能。示例如下:
//test.js
export var name = "tom";
let person1 = "p1";
let person2 = "p2";
export {person1, person2};
function f1(){}
export {f1 as func1};
关于export的几点注意:
- export可以对外输出变量、函数和类
- 一个模块就是一个独立的文件,export命令可以出现在模块的任一位置,但必须位于模块层级。如果处于块级作用域则会报错。import也是如此。
- 通常情况下export输出的变量就是其变量名,但可以通过as进行重命名
- export语句输出的值是动态绑定,绑定其所在的模块
通过上面的export命令对外输出后,就可以在其他JS文件中通过import命令引入加载该模块
import {name, person1, person2} from './test';
function printData(){
console.log(name + person1);
}
关于import命令的几点注意:
- import命令具有提升效果,会提升到整个模块的头部首先执行
- import接受一个对象(用大括号表示),里面的指定从外部模块导入的变量名,必须与被导入模块中的对外接口名称相同。当然也可以使用as对其进行重命名
2.模块的整体加载
上面我们是单独指定每一个变量进行加载导入。另外也可以通过星号*或者module命令实现模块的整体加载
//math.js
export funciont sum(x, y){
return x+y;
}
export funciont sub(x, y){
return x-y;
}
//通过*整体加载,使用as指定对象名称
import * as math from './math'
console.log(math.sum(4,5));
//module命令可以代替import实现整体加载,后跟一个变量表示模块定义在该变量上
module math from './math';
console.log(math.sub(4,5));
3.export default 命令
该命令用来指定模块的默认输出。本质上来说就是输出一个叫做default的变量或者方法,然后系统允许你为它起任何名字
//test.js
export default function fun(){
console.log('foo');
}
//引入
import customFun from './test';
customFun();//foo
//可以同时输入默认方法和其他的方法变量
//import customFun,{otherMethod} from './test';
几点注意:
- 一个模块只能有一个默认输出,因此export default只能使用一次
- 从代码可以看出,引入默认输出的import命令后面不需要跟大括号。
- 如果输出默认的值只有将值跟在export default之后。
- 该命令也可以用来输出类
4.模块继承
模块之间可以继承的,可以在原有模块的基础上再次输出自定义的变量和默认方法。
//有circle模块,circleplus模块继承该模块
export * from circle;//输出circle模块的内容
//也可以指定输出 export {area as circleArea} from 'circle';
export var pi = 3.14;
export default function(value){
Math.exp(value);
}
//加载
module math from 'circleplus';//整体加载
import exp from 'circleplus';//加载默认方法为exp
4.ES6模块加载性质与循环加载
ES6的模块加载对外输出的是值的引用。这么CommonJS对外输出值的拷贝有着本质的不同。
ES6模块加载机制:在遇到import命令时不会去执行模块,而是生成一个动态的只读引用,等到需要的时候在到模块中取值。因此ES6模块是动态引用,并且不会缓存值,模块里的变量绑定其所在的模块。换句话说就是加载模块的值与模块实时的运算值相同,完全反映其模块内部的变化。
//lib.js
export let counter = 3;
export function initCounter(){
counter ++;
}
//引入
import {counter, initCounter} from './lib';
console.log(counter);//3
initCounter();
//这里输出的是4,会随着模块的执行而变化。如果是CommonJS则输出3。从这一点来看ES6提供了模块机制要优于CommonJ
console.log(counter);//4
需要注意的是,ES6输入的模块是一个符号引用,这个变量指向的地址是只读的,对它重新赋地址值会报错。
循环加载
循环加载指的是a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。CommonJS是值得拷贝,如果加载时代码没有执行则就没有值,会出现。ES6模块是动态引用,只要引用存在,代码就能执行。
以下代码使用Module模块机制可以执行,但是如果使用CommonJS就会报错。
代码示例:
//even.js
import {odd} from './odd';
export var counter = 0;
export funxtion(n){
counter ++;
return n == 0 || odd(n-1);
}
//odd.js
import {even} from './even';
export function odd(n){
return n!=0 && even(n-1);
}
以上就是关于Class和Module的相关简记,希望对看到的同学有所帮助。