共计 6907 个字符,预计需要花费 18 分钟才能阅读完成。
本系列博客为 ES6 基础语法的使用及总结,如有错误,欢迎指正。
重学 ES6 之出来混迟早要还的(五)主要包括 ES6 模块化 、ES6 的继承、 遍历器 等。
其他笔记:
重学 ES6 之出来混迟早要还的(一)
重学 ES6 之出来混迟早要还的(二)
重学 ES6 之出来混迟早要还的(三)
重学 ES6 之出来混迟早要还的(四)
ES6 模块化(ES6 Module)
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
在过去为了支持 JS 模块化,可以使用类、立即执行函数或者第三方插件 (RequireJS、seaJS) 来实现模块化
前端模块化开发
1.AMD 和 CMD
(1) AMD
全称:Asynchronous Module Definition(异步模块定义)
- AMD 规范是 require.js 推广的、对模块定义的规范
- 非同步加载模块,允许指定回调函数
- AMD 在使用模块之前将依赖模块全部加载完成
(2) CMD
全称:Common Module Definition(通用模块定义)
- CMD 规范是 SeaJS 推广的、对模块定义的规范
- 就近依赖,需要时再进行加载,所以执行顺序和书写顺序一致
(3) AMD 和 CMD 二者异同
- 相同:AMD 和 CMD 都是浏览器端的 js 模块化规范
-
异同:
- AMD 提前执行,CMD 延迟执行
- AMD 推崇依赖前置,CMD 推崇依赖就近
- require.js 遵循 AMD 规范,SeaJS 遵循 CMD 规范
- AMD 受网络等因素执行顺序可能和书写顺序不一样(使用模块之前将依赖模块全部加载完成),CMD 执行顺序和书写顺序一致
2.CommonJS
CommonJS 是服务端模块的规范,NodeJS 采用了这个规范。CommonJS 规范同步加载模块,也就是:只有加载完成,才能执行后面的操作。
3.AMD 和 CommonJS 的比较
- AMD 非同步加载模块;CommonJS 规范同步加载模块
- AMD 推荐风格是通过
module transport
规范暴露接口,即通过返回一个对象来暴露模块接口;CommonJS 的风格是通过对module.exports
或exports
的属性赋值来达到暴露模块对象的目的
ES6 模块化
模块功能主要由两个命令构成:export
和 import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
export 命令
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。
1. 常规导出
// 分开导出
export let firstName = 'Michael';
export let lastName = 'Jackson';
export let year = 1958;
// 一次性导出
let firstName = 'Michael';
let lastName = 'Jackson';
let year = 1958;
export {firstName, lastName, year};
2. 通常情况下,export
输出的变量就是本来的名字,但是可以使用 as
关键字重命名。
变量名被修改后原有变量名自动失效
function v1() { ...}
function v2() { ...}
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
export default 命令
1.export default
命令,为模块指定默认输出。
export default function () {console.log('foo');
}
上面代码是一个模块文件,它的默认输出是一个函数。
其他模块加载该模块时,import
命令可以为该匿名函数指定任意名字,不需要知道原模块输出的函数名。
2. 注意点
- 一个模块只能使用一次默认导出, 多次无效
- 默认导出时, 导入的名称可以和导出的名称不一致
import 命令
使用 export
命令定义了模块的对外接口以后,其他 JS 文件就可以通过 import
命令加载这个模块。
1. 常规导入
import
命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块对外接口的名称相同。
import {firstName, lastName, year} from '那个 js 文件的路径';
2. 如果想为输入的变量重新取一个名字,import
命令要使用 as
关键字,将输入的变量重命名。
import {lastName as surname} from '那个 js 文件的路径';
3. 注意点
-
import
后面的from
指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js 路径可以省略。 - 接收导入变量名必须和导出变量名一致(根本上是解构赋值)
- 其他模块加载默认模块时,
import
命令可以为该匿名函数指定任意名字;不需要知道原模块输出的函数名。 - 需要注意的是,加载默认模块时 import 命令后面,不使用大括号。
// export-default.js
export default function () {console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
- 如果多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。
ES6 的继承
ES6 类和对象
Class 基本语法
1. 在 ES6 之前通过构造函数来定义一个类
function Person(myName, myAge) {
// 实例属性
this.name = myName;
this.age = myAge;
// 实例方法
this.say = function () {console.log(this.name, this.age);
}
// 静态属性
Person.num = 666;
// 静态方法
Person.run = function () {console.log("run");
}
}
let p = new Person("zs", 18); // 创建一个 Person 实例
p.say(); // 调用实例方法,访问实例属性
console.log(Person.num); // 访问静态属性
Person.run(); // 调用静态方法
实例属性 / 实例方法
通过实例对象访问的属性,称之为实例属性;
通过实例对象调用的方法,称之为实例方法。静态属性 / 静态方法
通过构造函数访问的属性,称之为静态属性;
通过构造函数调用的方法,称之为静态方法。
2.ES6 引入了 Class(类)这个概念,作为对象的模板。
通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
2.1 定义类的两种方式
// 方式一
class User{···}
// 方式二
const List = class{···};
2.2 类是一种特殊的函数
- 函数存在提升,但是类不存在提升,在定义之前调用类会报错
-
typeof
检查类返回function
class User{ }
console.log(typeof User); //function
2.3 上面的代码用 ES6 的“类”改写,就是下面这样:
class Person{constructor(myName, myAge){
this.name = myName;
this.age = myAge;
}
// 定义实例方法
say(){console.log(this.name, this.age);
}
// 定义静态方法
static run() {console.log("run");
}
}
let p = new Person("zs", 18); // 创建一个 Person 实例
p.say(); // 调用实例方法
Person.run(); // 调用静态方法
3.constructor
方法
constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。
-
constructor
意义:类初始化时候执行的函数 - 作用:初始化属性, 传入所需要的参数
- 返回值:自动返回实例对象
- 实例属性都需要在
constructor
中添加
4. 在类里面定义方法注意点
- 定义“类”的方法的时候,不需要加上 function 关键字,直接把函数定义放进去。
- 方法之间不需要逗号分隔,加了会报错。
-
static
关键字表示定义静态方法;ES6 明确规定,Class 内部只有静态方法,没有静态属性。 -
实例方法写在
constructor
外面,默认是添加到原型上面的class Point {constructor(){// ...} toString(){// ...} toValue(){// ...} } // 等同于 Point.prototype = {toString(){}, toValue(){} };
(以上上面代码为例打印 p 这个对象,可以发现 say()方法存在在原型上)
- 类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
ES6 继承
ES6 之前的继承
1. 两个关键步骤
- 在子类中通过 call/apply 方法借助父类的构造函数
- 将子类的原型对象设置为父类的实例对象
2. 三句关键代码
function Person(myName, myAge) {
this.name = myName;
this.age = myAge;
}
Person.prototype.say = function () {console.log(this.name, this.age);
};
function Student(myName, myAge, myScore) {
// 1. 在子类中通过 call/apply 方法借助父类的构造函数
Person.call(this, myName, myAge);
this.score = myScore;
this.study = function () {console.log("day day up");
}
}
// 2. 将子类的原型对象设置为父类的实例对象
Student.prototype = new Person();
Student.prototype.constructor = Student;
let stu = new Student("zs", 18, 99);
stu.say();
ES6 继承
1.Class 之间可以通过 extends
关键字实现继承
- 格式:
class b extends a
- 意思:定义了一个 b 类,该类通过 extends 关键字,继承了 a 类的所有属性和方法。
2.super
关键字
- 表示父类的构造函数,用来新建父类的 this 对象。
- 子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。
- 注意,super 虽然代表了父类 a 的构造函数,但是返回的是子类 b 的实例,即 super 内部的 this 指的是 b
3.ES6 实现继承
class Person{constructor(myName, myAge){
this.name = myName;
this.age = myAge;
}
say(){console.log(this.name, this.age);
}
}
// 以下代码的含义: 告诉浏览器将来 Student 这个类需要继承于 Person 这个类
class Student extends Person{constructor(myName, myAge, myScore){super(myName, myAge);
this.score = myScore;
}
study(){console.log("day day up");
}
}
let stu = new Student("zs", 18, 98);
stu.say();
4. 小结
ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先创造父类的实例对象 this(所以必须先调用 super 方法),然后再用子类的构造函数修改 this。
Object.getPrototypeOf()
Object.getPrototypeOf 方法可以用来从子类上获取父类。
在 ES6 中可以使用这个方法判断,一个类是否继承了另一个类。
console.log(Object.getPrototypeOf(Student) === Person); //true
遍历器 Iterator
1. 什么是遍历器
遍历器是一个对象,该对象里面有一个 next 方法会返回给我们需要的数据
[可遍历对象就是部署了 [Symbol.iterator] 属性的对象]
2.[Symbol.iterator]
- 可遍历对象都有一个叫做
[Symbol.iterator]
的属性
const arr = ['Ann','Bob','Charlie','Dolly'];
console.log(arr);
-
[Symbol.iterator]
的属性会返回一个函数 -
[Symbol.iterator]
返回的函数执行之后会返回一个新对象Array Iterator {}
,该对象中又一个名称叫做 next 的方法
const iterator = arr[Symbol.iterator]();
console.log(iterator);
- next 方法每次执行都会返回一个对象;该对象中存储了当前取出的数据和是否取完了的标记
let res = iterator.next();
console.log(res);
let res2 = iterator.next();
console.log(res2);
let res3 = iterator.next();
console.log(res3);
let res4 = iterator.next();
console.log(res4);
- 数据遍历完之后,随后再调用一直返回
undefined
和true
let res5 = iterator.next();
console.log(res5);
let res6 = iterator.next();
console.log(res6);
3. 一点区别
第三篇说到,Array.prototype.entries();
和 [Symbol.iterator]();
返回的都是新的 Array Iterator 对象,二者等价
console.log(arr.entries());
console.log(arr[Symbol.iterator]());
二者的返回的都是新的 Array Iterator{}
对象,细微的区别在于:
- 使用
.entries()
返回的Array Iterator{}
对象,再调用 next()时,返回给我们的数据中的 value 是以数组 (即包含索引) 的形式
- 使用
[Symbol.iterator]();
返回的Array Iterator{}
对象,再调用 next()时,value 返回的就是那个值
4. 其他方法返回Array Iterator{}
4.1 .keys
方法
顾名思义,它的 Array Iterator{}
的next()
方法返回的是索引
let iterator = arr.keys();
console.log(iterator);
let res = iterator.next();
console.log(res);
let res2 = iterator.next();
console.log(res2);
let res3 = iterator.next();
console.log(res3);
4.2 .values()
方法
顾名思义,它的 Array Iterator{}
的next()
方法返回的是值
let iterator = arr.values();
console.log(iterator); //Array Iterator {}
let res = iterator.next();
console.log(res);
let res2 = iterator.next();
console.log(res2);
let res3 = iterator.next();
console.log(res3);
5. 实现一个遍历器
(抄的,这个代码太优雅了,必须分享)
Array.prototype.myIterator = function () {
let i = 0;
let items = this;
return {next(){
const done = i >= items.length;
const value = done ? undefined : items[i++];
return {
value,
done
}
}
}
};
本博客部分内容 ES6 模块化和 ES6 继承参考了这些:
http://caibaojian.com/es6/mod…
http://caibaojian.com/es6/cla…