ES6
# ES6
[TOC]
# 一、模块化的使用和编译环境
# 1.1 模块化的基本语法
# 1.1.1 export语法
// util1.js
// 用于指定模块的默认输出,一个模块只能有一个默认输出。
export default var a = 100;
export function foo {
console.log('util1-foo');
}
2
3
4
5
6
7
// util2.js
export var myUtil2 = 'this is util2';
export function fn1() {
console.log('util2-fn1');
}
export function fn2() {
console.log('util2-fn2');
}
2
3
4
5
6
7
8
9
10
// index.js
// 有了默认输出之后,其他模块加载该模块时,import命令可以为该匿名变量/函数,起任意的名字
import util1 from './util1.js'
import { fn1, fn2 } from './util2.js'
console.log(util1);
fn1();
fn2();
2
3
4
5
6
7
8
# 1.1.1.1 各种导入导出的区别
参考教程:exports、module.exports 和 export、export default 到底是咋回事 (opens new window)
require: node和es6都支持。
export / import: 只有es6支持。
module.exports / exports: 只有node支持。
# 1.1.1.1.1 node模块
- 遵循的是CommonJS规范。
CommonJS
定义的模块分为: 模块标识(module
)、模块定义(exports
) 、模块引用(require
)
- exports = module.exports = {}
- 在一个node执行一个文件时,会给这个文件内生成一个
exports
和module
对象,而module
又有一个exports
属性。 - 他们都指向同一块{}内存区域。
- 在一个node执行一个文件时,会给这个文件内生成一个
# 1.1.1.1.2 es6模块
- export 和 export default的区别
- 均可用于导出常量、函数、文件、模块,但只有export能直接导出变量表达式。
- 一个文件中,export可以多个,而export default只能有一个。
- 导入export导出的内容需要加{},export default则不需要。
# 1.1.1.1.3 ES6模块和CommonJS模块的差异
- CommonJS模块输出的是值的复制,ES6模块输出的是值的引用。
- CommonJS输出的是值的复制,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值,除非写成取值器函数。
- ES6是动态引用,不会缓存值,而是动态地去被加载的模块取值。
- CommonJS模块是运行时加载,ES6模块是编译时输出接口。
- CommonJS加载的是一个对象(即module.exports属性),该对象只有在脚本运行结束时才会生成。
- ES6模块不是对象,它的对外接口只是一个静态定义,在代码静态解析阶段就会生成。
- CommonJS模块是单个值导出,ES6模块可以导出多个。
- Common JS是动态语法可以写在判断里,ES6模块静态语法只能写在顶层。
- Common JS的this是当前模块,ES6模块的this是undefined。
# 1.1.2 AMD、CMD、CommonJs和ES6对比
- AMD是RequireJS在推广过程中对模块定义的规范化产出。
- CMD是SeaJS在推广过程中对模块定义的规范化产出。
- CommonJS规范 - module.exports(服务端才支持,浏览器不支持)
# 1.2 开发环境配置
# 1.2.1 babel
Babel is a JavaScript compiler.
# 1.2.2 webpack
# 1.2.3 rollup
vue、react 都是通过 rollup 来打包的,能尽量简化代码,优化冗余内容。
# 1.3 JS众多模块化标准发展
- 没有模块化
- AMD 成为标准,require.js(CMD)
- 前端打包工具,使得 nodejs 模块化可以被使用
- ES6 出现,想统一现在所有模块化标准
- nodejs 积极支持,浏览器尚未统一
- 可以自造 lib,但不要自造标准
# 二、Class与JS构造函数的区别
- Class 在语法上更加贴合面向对象的写法
- Class 实现继承更加易读、易理解
- 更易于写 java 等后端语言的使用
- 本质还是语法糖,使用 prototype
# Class和普通构造函数有何区别
我们经常会用ES6中的Class来代替JS中的构造函数做开发。
- Class 在语法上更加贴合面向对象的写法
- Class 实现继承更加易读、易理解
- 更易于写 java 等后端语言的使用
- 本质还是语法糖,使用 prototype
# 三、异步操作
# 3.1 Promise的用法
- new Promise 实例,而且要 return
- new Promise 时要传入函数,函数有 resolve reject 两个参数
- 成功时执行 reslove()失败时执行 reject()
- then 监听结果
function loadImg(src, callback, fail) {
var img = document.createElement('img')
img.onload = function () {
callback(img)
}
img.onerror = function () {
fail()
}
img.src = src
}
var src = 'http://www.imooc.com/static/img/index/logo_new.png'
loadImg(src, function (img) {
console.log(img.width)
}, function () {
console.log('failed')
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function loadImg(src) {
const promise = new Promise(function (resolve, reject {
var img = document.createElement('img')
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject()
}
img.src = src
})
return promise
}
var src = 'http://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(function (img) {
console.log(img.width)
}, function () {
console.log('failed')
})
result.then(function (img) {
console.log(img.height)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 四、其他常用共能
# 4.1 let/const
# 4.1.1 let
# 4.1.1.1 适合用于for循环的计数器
for 循环的特别之处:是指循环变量的那一部分是一个父作用域,而循环体内部是单独的子作用域。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
2
3
4
5
6
7
由于JS事件执行机制,for循环内部执行时,循环已经走完了。变量 i 是 var 声明的,在全局范围内有效,所以全局只有一个变量 i 。每一次循环变量 i 的值都会发生变化。
console.log(i)中的 i 指向全局的 i 。
let a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
2
3
4
5
6
7
变量 i 是 let 声明的,只在本轮循环有效。每一次循环的 i 都是一个新的变量,存在循环内的块级作用域。
虽然每一轮循环的变量 i 都是重新声明的,但由于JS引擎内部会记住上一轮循环的值,初始化本轮变量 i 时,都会在上一轮循环的基础上进行计算。
ps: 也可以用闭包去解决。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function (num) {
return function () {
console.log(num)
};
}(i);
}
a[6]();
2
3
4
5
6
7
8
9
# 4.1.2 const
const声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。例如,在引用内容是对象的情况下,这意味着可以改变对象的内容(例如,其参数)。
const a = { x: 1 };
a.x = 2;
console.log(a); // { x: 2 }
2
3
# 4.2 多行字符串/模板变量
// ES5
var name = 'zhangsan', age = 20, html = '';
html += '<div>';
html += ' <p>' + name + '</p>';
html += ' <p>' + age + '</p>';
html += '</div>';
// ES6
const name = 'zhangsan', age = 20;
const html = `<div>
<p>${name}</p>
<p>${age}</p>
</div>`;
console.log(html);
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4.3 解构赋值
// ES5
var obj = {a: 100, b: 200}
var a = obj.a
var b = obj.b
var arr = ['xxx', 'yyy', 'zzz']
var x = arr[0]
//ES6
const obj = {a: 10, b: 20, c: 30}
const {a, c} = obj
console.log(a) // 10
console.log(c) // 30
const arr = ['xxx', 'yyy', 'zzz']
const [x, y, z] = arr
console.log(x) // xxx
console.log(y) // yyy
console.log(z) // zzz
// 函数参数默认值
// 两者的区别在于:前者是分别给两个变量分别默认值,后者是给整个对象一个默认值
function m1({x = 0, y = 0} = {}) {}
function m2({x,y} = {x: 0, y: 0}) {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 4.4 块级作用域
let myname= '小林'
{ // 暂时性死区
console.log(myname) // Uncaught ReferenceError
let myname= '大林'
}
2
3
4
5
# 4.5 函数的扩展
# 4.5.1 默认参数
function fn (a, b) {
b = b || 0;
}
// ES6
function fn (a, b=0) {}
2
3
4
5
6
# 4.5.2 rest参数
- 形式为
...变量名
,用来获取函数多余的参数。 - rest参数搭配的变量是一个数组,该变量将多余的参数放入其中。故rest参数只能作为最后一个参数,否则会报错。
// arguments变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest参数写法
const sortNumbers = (...numbers) => numbers.sort();
2
3
4
5
6
7
# 4.5.3 箭头函数
- 函数体内的this绑定的是定义时所在的定义域,而不是运行时所在的定义域。
- 箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以不能用作构造函数。
- 函数体内不可以使用arguments对象,因为arguments对象在函数体内不存在。如果要用,可以用rest参数代替。
// 箭头函数内部的arguments其实就是函数foo的arguments变量
function foo() {
setTimeout(() => {
console.log('args:', arguments);
}, 100);
}
foo(2, 4, 6, 8);
// args: [2, 4, 6, 8]
2
3
4
5
6
7
8
9
# 4.7 Set和Map数据结构
- 共同点:
- 本身是构造函数
- 属性:size
- 操作方法:delete(val) has(val) clear()
- 不同点:set.add() map.get() map.set()
- 遍历方法:keys() values() entries() forEach()
# 4.7.1 Set(集合)
- 新的数据结构,类似数组,但是成员的值都是唯一的,没有重复。
- Set本身是一个构造函数,用来生成Set数据结构。
let s = new Set();
typeof s; // "object"
2
const obj = { 1: "a", 2: "b", 3: "c" };
const set = new Set([1, 2, 3, 4, 5]);
const arr = [1, 2, 3, 4, 5];
obj.hasOwnProperty("1"); // true
obj.hasOwnProperty(1); // true
set.has("1"); // false
set.has(1); // true
arr.hasOwnProperty("1"); // true
arr.hasOwnProperty(1); // true
2
3
4
5
6
7
8
9
10
所有对象键(不包括Symbols
)都会被存储为字符串,即使你没有给定字符串类型的键。 这就是为什么obj.hasOwnProperty('1')
也返回true
。
上面的说法不适用于Set
。 在我们的Set
中没有“1”
:set.has('1')
返回false
。 它有数字类型1
,set.has(1)
返回true
。
# 4.7.2 Map(字典)
# 4.7.3 WeakSet 和 WeakMap
- WeakSet
- 成员都是对象
- 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
- 不能遍历,方法有add、delete、has
- WeakMap
- 只接受对象作为键名(null除外),不接受其他类型的值作为键名
- 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
- 不能遍历,方法有get、set、has、delete
# 4.8 扩展运算符
...
将一个数组转为用逗号分隔的参数序列。
# 4.8.1 替代数组的apply方法
let args = [0, 1, 2];
f.apply(null, args);
// 等价于
f(...args);
2
3
4
# 4.8.1.1 求最大最小值
Math.max(...args);
Math.min(...args);
2
# 4.8.2 合并数组
const _arr = [0, 1, 2];
const arr = [..._arr];
// arr => [0, 1, 2]
2
3
# 4.8.3 将有Iterator接口对象转换为真正的数组
[...'hello'] // ['h','e','l','l','o']
[...new Set([0,1,1,null,null])] // [0,1,null]
2