ES6

# ES6

[TOC]

# 一、模块化的使用和编译环境

# 1.1 模块化的基本语法

# 1.1.1 export语法

// util1.js
// 用于指定模块的默认输出,一个模块只能有一个默认输出。
export default var a = 100;

export function foo {
    console.log('util1-foo');
}
1
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');
}
1
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();
1
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执行一个文件时,会给这个文件内生成一个 exportsmodule对象,而module又有一个exports属性。
    • 他们都指向同一块{}内存区域。
# 1.1.1.1.2 es6模块
  • export 和 export default的区别
    1. 均可用于导出常量、函数、文件、模块,但只有export能直接导出变量表达式
    2. 一个文件中,export可以多个,而export default只能有一个。
    3. 导入export导出的内容需要加{},export default则不需要。
# 1.1.1.1.3 ES6模块和CommonJS模块的差异
  1. CommonJS模块输出的是值的复制,ES6模块输出的是值的引用。
    • CommonJS输出的是值的复制,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值,除非写成取值器函数。
    • ES6是动态引用,不会缓存值,而是动态地去被加载的模块取值。
  2. CommonJS模块是运行时加载,ES6模块是编译时输出接口。
    • CommonJS加载的是一个对象(即module.exports属性),该对象只有在脚本运行结束时才会生成。
    • ES6模块不是对象,它的对外接口只是一个静态定义,在代码静态解析阶段就会生成。
  3. CommonJS模块是单个值导出,ES6模块可以导出多个。
  4. Common JS是动态语法可以写在判断里,ES6模块静态语法只能写在顶层。
  5. 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')
})
1
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)
})
1
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
1
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
1
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]();
1
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 }
1
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);
1
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}) {}
1
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= '大林'
}
1
2
3
4
5

# 4.5 函数的扩展

# 4.5.1 默认参数

function fn (a, b) {
    b = b || 0;
}

// ES6
function fn (a, b=0) {}
1
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();
1
2
3
4
5
6
7

# 4.5.3 箭头函数

  1. 函数体内的this绑定的是定义时所在的定义域,而不是运行时所在的定义域。
    • 箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以不能用作构造函数。
  2. 函数体内不可以使用arguments对象,因为arguments对象在函数体内不存在。如果要用,可以用rest参数代替。
// 箭头函数内部的arguments其实就是函数foo的arguments变量
function foo() {
    setTimeout(() => {
        console.log('args:', arguments);
    }, 100);
}

foo(2, 4, 6, 8);
// args: [2, 4, 6, 8]
1
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"
1
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
1
2
3
4
5
6
7
8
9
10

所有对象键(不包括Symbols)都会被存储为字符串,即使你没有给定字符串类型的键。 这就是为什么obj.hasOwnProperty('1')也返回true

上面的说法不适用于Set。 在我们的Set中没有“1”set.has('1')返回false。 它有数字类型1set.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);
1
2
3
4
# 4.8.1.1 求最大最小值
Math.max(...args);
Math.min(...args);
1
2

# 4.8.2 合并数组

const _arr = [0, 1, 2];
const arr = [..._arr];
// arr => [0, 1, 2]
1
2
3

# 4.8.3 将有Iterator接口对象转换为真正的数组

[...'hello']	// ['h','e','l','l','o']
[...new Set([0,1,1,null,null])]	// [0,1,null]
1
2