概览
ES6,全称ECMAScript 6.0,是JavaScript的下一版本标准,2015.06发布。
ES6主要是为了解决ES5的先天不足,比如JavaScript里没有类的概念、内置集合对象Map和Set、迭代器、异步编程等。
特性
1 声明与表达式
1.1 let 与 const
let 和 const 是在ES6中新增的两个关键字。
let声明的变量只在let命令所在的代码块内有效,不能重复声明, 不存在变量提升。
var 定义的变量是在全局范围内有效,可以重复声明,存在变量提升。
{
console.log(a) // ReferenceError: Cannot access 'a' before initialization
let a = 0
console.log(a) // 0
let a = 1 // SyntaxError: Identifier 'a' has already been declared
}
console.log(a) // ReferenceError: a is not defined
const声明一个只读的常量,一旦声明,常量的值就不能改变。意味着一旦声明就必须初始化,否则会报错。
const PI = "3.1415926";
console.log(PI) // 3.1415926
const MY_AGE; // SyntaxError: Missing initializer in const declaration
const 保证的不是变量的值不变,而是保证变量所指向的内存地址所保存的数据不允许改动。
1.2 解构赋值
解构赋值是对赋值运算符的扩展,是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段的获取。
数组模型解构(Array)
- 基本
let [a,b,c] = [1,2,3]
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
- 可嵌套
let [a,[[b],c]] = [1,[[2],3]]
- 可忽略
let [a, ,b] = [1,2,3]
- 不完全解构
let [a = 1, b] = []
console.log(a) // a = 1
console.log(b) // b = undefined
- 剩余运算符
let [a, ...b] = [1,2,3]
console.log(a) // a = 1
console.log(b) // b = [2,3]
- 字符串等
let [a,b,c,d,e] = 'hello'
console.log(a) // a = 'h'
console.log(b) // a = 'e'
console.log(c) // a = 'l'
console.log(d) // a = 'l'
console.log(e) // a = 'o'
- 解构默认值
let [a = 2] = [undefined]
console.log(a) // a = 2
当解构模式有匹配结果,且匹配结果是undefined时,会触发默认值作为返回值
let [a = 3, b = a] = []; // a = 3, b = 3 let [a = 3, b = a] = [1]; // a = 1, b = 1 let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
对象模型解构(Object)
- 基本
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }
console.log(foo) // foo = 'aaa'
console.log(bar) // bar = 'bbb'
其它可参考数组模型的解构
1.3 Symbol
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
- 基本用法
let sy = Symbol('kk')
console.log(sy) // Symbol(kk)
typeof(sy) // 'symbol'
//相同参数 Symbol() 返回的值不相等
let sy1 = Symbol('kk')
sy === sy1 // false
- 注意事项
- Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
- Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问
- 不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回
- 要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到
- Symbol.for()
类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1; // false
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2; // true
- Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1); // "Yellow"
2 内置对象
2.1 Map 与 Set
Map
Map 对象保存键值对。任何值(对象或者原始值)都可以作为一个键或者一个值。
Maps 和 Objects 的区别:
- 一个Object的键只能是字符串或者是Symbol, 但一个Map的键可以是任意值。
- Map中的键值是有序的(FIFO原则),而添加到对象中的键值则不是。
- Map中的键值对个数可以从size属性获取,而Object的键值对个数只能手动计算。
- Object都有自己的原型,原型链上的键名有可能和你自己在对象上设置的键名产生冲突。
-
基本使用
let myMap = new Map() myMap.set('key','value') // key is string myMap.get('key') // value myMap.set(NaN, 'not a number') // key is NaN
-
Map的迭代
for ... of
let myMap = new Map() myMap.set(0, 'zero') myMap.set(1, 'one') for(let [key,value] of myMap){ console.log(key + ' = ' + value) }
forEach()
myMap.forEach(function(key,value){ console.log(key + ' = ' + value) })
-
Map对象的操作
Map 与 Array 的转换
let vkArr = [['key1','value1'],['key2','value2']] let myMap = new Map(vkArr) // 将一个二维数组转换成 Map 对象 let outArr = Array.from(myMap) // 将一个Map 转换成 Array
Map 的克隆
let originalMap = new Map([['key1','value1'],['key2','value2']]) let cloneMap = new Map(originalMap) console.log(originalMap === cloneMap) // false, Map对象构造函数生成实例,迭代出新的对象
Map 的合并
let first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]) let second = new Map([[1, 'uno'], [2, 'dos']]) let merged = new Map(...first, ...second) //合并Map对象时,如果键值重复,则后面的覆盖前面的
Set
Set对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要注意:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在Set中只能存一个,不重复;
-
基本使用
let mySet = new Set() mySet.add(1) mySet.add(5) mySet.add('string')
-
类型转换
let mySet = new Set(['value1','value2','value3']) // Array 转 Set let myArray = [...mySet] // 用 ...操作符 将Set 转 Array //string 转 set let mySet = new Set('hello') // Set(4) {'h','e','l','l','o'}
-
Set 对象作用
//数组去重 let mySet = new Set([1,2,3,4,4]) [...mySet] //[1,2,3,4] //并集 let a = new Set([1,2,3]) let b = new Set([4,3,2]) let union = new Set([...a, ...b]) // Set(4) {1,2,3,4} //交集 let intersect = new Set([...a].filter(x => b.has(x))) // Set(2) {2,3} //差集 let difference = new Set([...a].filter(x => !b.has(x))) // {1}
2.2 Proxy 与 Reflect
Proxy(代理)
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些额外的操作。‘
-
基本用法
let target = { name: 'Tom', age: 24 } let handler = { get: function(target, key) { console.log('getting ' + key); return target[key]; // 不是target.key }, set: function(target, key, value) { console.log('setting ' + key); target[key] = value; } } let proxy = new Proxy(target, handler) proxy.name // 实际执行 handler.get proxy.age = 25 // 实际执行 handler.set
Reflect(映射)
Reflect 可以用于获取目标对象的行为,与Object类似,但是更易读,为操作对象提供了一个种更优雅的方式。它的方法与Proxy是对应的。
let exam = {
name: "Tom",
age: 24,
get info(){
return this.name + this.age;
}
}
Reflect.get(exam, 'name'); // "Tom"
Reflect.set(exam, 'age', 25); // true
组合使用
Reflect 对象的方法与Proxy对象的方法是一一对应的,所以Proxy对象的方法可以通过调用Reflect对象的方法获取默认行为,然后进行额外操作。
let target = {
name: 'Tom',
age: 24
}
let handler = {
get: function (target, key) {
console.log('getting ' + key);
// return target[key];
return Reflect.get(target,key);
},
set: function (target, key, value) {
console.log('setting ' + key + ' value: ' + value);
// target[key] = value;
Reflect.set(target,key,value);
}
}
let proxy = new Proxy(target,handler)
proxy.age = 33
console.log('name is '+ proxy.name)
2.3 字符串
扩展的方法
方法名 | 方法描述 | 示例 |
---|---|---|
includes() | 返回boolean,判断是否找到参数字符串 | 'hello'.includes('llo') // true |
startsWith() | 返回boolean,判断参数字符串是否在原字符串的头部 | 'hello'.startsWith('he') // true |
endsWith() | 返回boolean,判断参数字符串是否在原字符串的尾部 | 'hello'.endsWith('o') //true |
indexOf() | 返回字串的位置 | 'hello'.indexOf('h') // 0 |
lastIndexOf() | 返回字串最后出现的位置 | 'hello'.lastIndexOf('h') // 4 |
repeat() | 返回新字符串,表示将字符串重复指定次数 | 'hello,'.repeat(2) // 'hello,hello,' |
padStart() | 返回新字符串,用参数字符从头部(左侧)补全字符串 | 'h'.padStart(5,'o') // 'ooooh' |
padEnd() | 返回新字符串,用参数字符从尾部(右侧)补全字符串 | 'h'.padEnd(5,'o') // 'hoooo' |
模板字符串
模板字符串相当于加强版的字符串,用反引号`,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
let name = 'Mike'
let age = 24
let info = `My name is ${name}, I am ${age + 1} years old next year.`
2.4 数值
N/A
2.5 对象
对象字面量
-
属性的简洁表示法。允许对象的属性直接写变量,这时候属性名就是变量名,属性值就是变量值。
const age = 12 const name = 'Amy' const person = {age, name} console.log(person) // {age: 12, name: 'Amy'}
-
方法名也可以简写
const person = { sayHi(){ console.log('Hi')} } //等同于 const person = { sayHi: function(){ console.log('Hi')} }
-
属性名表达式
const obj = { ['hel' + 'lo']() { return 'Hi'} } obj.hello()
注意:属性的简洁表示法和属性名表达式不能同时使用,否则会报错
对象的拓展运算符
拓展运算符 (...) 用于取出参数对象所有可遍历属性然后拷贝到当前对象
-
基本用法
let person = {name: 'Amy', age: 12} let someone = {...person} console.log(someone) // {name: 'Amy', age: 12}
-
用于合并两个对象
let age = {age: 12} let name = {name: 'Amy'} let person = {...age, ...name}
自定义的属性和拓展运算符对象里面属性相同的时候,自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
对象的新方法
-
Object.assign(target, source, ...)
用于将源对象的所有可枚举属性复制到目标对象中。
let target = {a: 1} let obj1 = {b: 2} let obj2 = {c: 3} Object.assign(target,obj1, obj2) console.log(target) // {a:1, b:2, c:3}
a. 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
b. 如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象后返回。
c. assign 的属性拷贝是浅拷贝
-
Object.is(value1, value2)
用于比较两个值是否严格相等,与 (===) 基本类似。
Object.is('q','q') // true Object.is([1],[1]) // false 对象类型要同时比较地址和值 Object.is({q: 1},{q: 1}) //false 对象类型要同时比较地址和值
与 (===) 的区别
// 1. +0 不等于 -0 Object.is(+0, -1) // false +0 === -0 // true // 2. NaN 等于本身 Object.is(NaN, NaN) // true NaN === NaN // false
2.6 数组
数组的创建
-
Array.of()
将参数中所有值作为元素形成数组。
console.log(Array.of(1,2,3,4)) // [1,2,3,4] //参数值可为不同类型 console.log(Array.of(1,'2',true)) // [1, '2', true] //参数为空时返回空数组 console.log(Array.of()) // []
-
Array.from()
将类数组对象或可迭代对象转化为数组
// 参数为数组,返回与原数组一样的数组 console.log(Arrays.from([1,2])) // [1, 2] //参数含空位 console.log(Arrays.from([1, ,3])) // [1, undefined, 3]
-
类数组
一个类数组对象必须含有 length 属性,且元素属性名必须是数组或可转换为数值的字符。
let arr = Array.from({ 0: '1', 1: '2', 2: '3', length: 3 }) console.log(arr) //['1','2','3']
-
转换可迭代对象
// 转换 map // 转换 set // 转换 string let arr = Array.from(map | set | string)
扩展的方法
方法名 | 描述 | 示例 |
---|---|---|
find() | 查找数组中符合条件的元素,若有多个,返回第一个元素 | Array.from(1,2,3).find(x=>x>2) // 3 |
findIndex() | 查找数组中符合条件的元素的索引,若有多个,返回第一个 | Array.from(1,2,3).findIndex(x=>x>2) // 2 |
fill() | 将一定范围索引的数组元素填充为单个指定的值 | Array.from(1,2,3,4).fill(0,1,2) // [1,0,3,4] |
copyWithin() | 将一定范围索引的数组元素修改为此数组另一指定范围索引的元素 | Array.from(1,2,3,4).copyWithin(0,2,4) // [3,4,3,4] |
entries() | 遍历键值对 | N/A |
keys() | 遍历键名 | N/A |
values() | 遍历键值 | N/A |
includes() | 数组是否包含指定值 | N/A |
flat() | 嵌套数组转一维数组 | [1,[2,3]].flat() // [1,2,3] |
flatMap() | 先对数组中每个元素进行了处理,再对数组执行flat() | [1,[2,3]].flatMap(x=>x*2) // [2,4,6] |
数组缓冲区
数组缓冲区是内存中的一段地址。实际字节数在创建时确定,之后只可修改其中的数据,不可修改大小。
let buffer = ArrayBuffer(10)
console.log(buffer.byteLength) // 10
-
视图
视图是用来操作内存的接口。视图可以操作数组缓冲区或缓冲区字节的子集,并按照其中一种数值数据类型来读取和写入数据。
DataView 类型是一种通用的数组缓冲区视图,其支持所有8种数值型数据类型。
//默认DataView可操作缓冲区的全部内容 let buffer = new ArrayBuffer(10) let dataView = new DataView(buffer) dataView.setInt8(0,1) console.log(dataView.getInt8(0)) //1 //通过设置偏移量和长度指定DataView可操作的字节范围 let dataView1 = new DataView(buffer, 0, 3) dataView1.setInt8(5,1) // RangeError
定型数组
数组缓冲区特定类型的视图。可以强制使用特定的数据类型,而不是使用通用的DataView对象来操作数组缓冲区。
let buffer = new ArrayBuffer(10)
let view = new Int8Array(buffer)
console.log(view.byteLength) // 10
length 属性不可写,如果尝试修改这个值,在非严格模式下会直接忽略该操作,在严格模式下会抛出错误。
扩展运算符
- 复制数组
let arr = [1,2],
arr1 = [...arr]
console.log(arr1) //[1,2]
//合并数组
console.log(...[1,2],...[3,4]) // [1,2,3,4]
3 运算符与语句
3.1 函数
-
函数参数的扩展
默认参数
function fn(name, age = 17){ console.log(name + ', ' + age)} fn('Amy', 18) // Amy, 18 fn('Amy') // Amy, 17
a. 使用函数的默认参数时,不允许有同名参数。
b. 只有在未传递参数,或者参数为undefined时,才会使用默认参数,null值被认为是有效的值传递。
c. 函数参数默认值存在暂时性死区,在函数参数默认值表达式中,还未初始化赋值的参数值无法作为其它参数的默认值。
不定参数
不定参数用来表示不确定的参数个数,形如, ...变量名,由...加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。
function fn(...values){ console.log(values.length)} fn(1, 2) // 2 fn(1,2,3,4) // 4
-
箭头函数
箭头函数提供了一种更加简洁的函数书写方式。
let f = v => v // 等价于 let f = function(v){ console.log(v)} f(1) // 1
当箭头函数没有参数或者有多个参数时,要用()括起来。
当箭头函数有多行语句,用{} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略{} 结果自动返回。
let f = (a,b) => { let result = a + b return result } f(6,2) // 8
当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
let f = (id,name) = ({ id: id, name: name}) f(6,2) // {id: 6, name: 2}
没有 this, super, arguments和new.target 绑定。
箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时对象。
-
适用场景
N/A
-
不适用场景
N/A
3.2 class 类
class(类)作为对象的模板被引入,可以通过class关键字定义类。其实class的本质就是function,它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
// 匿名类
let Example = class {
constructor(a){
this.a = a
}
}
// 命名类
let Example = class Example{
constructor(a){
this.a = a
}
}
a. 类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
b. 类中方法不需要 function 关键字
c. 方法间不能加分号。
-
类的主体 (以下属性、方法、实例化,与其它任一编程语言都是相通的,不再作解释)
属性
静态属性
公共属性
实例属性
name属性
方法
constructor方法
静态方法
原型方法 实例方法
类的实例化
new
实例化对象
-
decorator
decorator 是一个函数,用来修饰类的行为,在代码编译时产生作用。
类修饰
function testable(target){ target.isTestable = true } @testable class Example{} Example.isTestable // true
上面的例子添加的是静态属性,若要添加实例属性,在类的 prototype 上操作即可。
方法修饰
class Example{ @writable sum(a, b) {return a + b} } function writable(target, name, descriptor){ descriptor.writable = false return descriptor }
修饰器执行顺序: 由外向内进入,由内向外执行。
-
封装与继承
getter / setter
class Example{ constructor(a, b) { this.a = a; // 实例化时调用 set 方法 this.b = b; } get a(){ console.log('getter'); return this.a; } set a(a){ console.log('setter'); this.a = a; // 自身递归调用 } } let exam = new Example(1,2); // 不断输出 setter ,最终导致 RangeError
extends
class Child extends Father{ ... }
Super
子类constructor方法中必须有super,且必须出现在this之前
class Father { constructor() {} } class Child extends Father { constructor() {} // or // constructor(a) { // this.a = a; // super(); // } } let test = new Child();
不可继承常规对象
var Father = { // ... } class Child extends Father{ // ... } // Uncaught TypeError: Class extends value #<Object> is not a constructor or null
3.3 ES6 模块
ES6引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6的模块分为导出(export) @与导入(import) 两个模块
-
特点
a. ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict。
b. 模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
c. 每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
d. 每一个模块只加载一次(单例),若再去加载同目录下的同文件,直接从内存中读取。
-
export 与 import
模块导入导出各种类型的变量,如字符串,数值,函数,类。
a. 导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。
b. 不仅能导出声明还能导出引用(函数)。
c. export 命令可以出现在模块的任何位置,但必须处于模块顶层。
d.import 命令会提升到整个模块的头部,首先执行。
import 命令的特点:
只读属性
单例模式
export default 命令:
a. 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
b. export default 中的 default 是对应的导出接口变量。
c. 通过 export 方式导出,在导入时要加{ },export default 则不需要。
d. export default 向外暴露的成员,可以使用任意变量来接收。
-
复合使用
export 与 import 可以在同一模块中使用,使用特点:
a. 可以将导出接口改名,包括default。
b. 复合使用 export 与 import ,也可以导出全部,当前模块导出的接口会覆盖继承导出的。
4 异步编程
4.1 Promise 对象
Promise是异步编程的一种解决方案。从语法上讲,Promise是一个对象,从它可以获取异步操作的消息。
Promise 状态
Promise 异步操作有三种状态:pending(进行中), fulfilled(已成功), rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。
const p1 = new Promise(function(resolve,reject){
resolve('success1');
resolve('success2');
});
const p2 = new Promise(function(resolve,reject){
resolve('success3');
reject('reject');
});
p1.then(function(value){
console.log(value); // success1
});
p2.then(function(value){
console.log(value); // success3
});
状态的缺点
a. 无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。
b. 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
c. 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
then 方法
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
const p = new Promise(function(resolve,reject){
resolve('success');
});
p.then(function(value){
console.log(value);
});
console.log('first');
// first
// success
简便的Promise链式编程最好保持扁平化,不要嵌套Promise。
注意总是返回或终止Promise链。
4.2 Generator 函数
ES6新引入的 Generator函数,可以通过yield关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
-
Generator 函数组成
Generator 有两个区分于普通函数的部分:
a. 一是在function 后面,函数名之前有个 *
b. 函数内部有 yield 表达式。
function* func(){ console.log("one"); yield '1'; console.log("two"); yield '2'; console.log("three"); return '3'; }
-
执行机制
调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
let f = func() f.next() // one // {value: "1", done: false} f.next() // two // {value: "2", done: false} f.next() // three // {value: "3", done: true} f.next() // {value: undefined, done: true}
4.3 Async 函数
async 是 ES7 才有的与异步操作有关的关键字,和Promise, Generator 有很大关联的。
async 函数返回一个 Promise对象,可以使用 then 方法添加回调函数。
async function helloAsync(){
return 'hello async'
}
console.log(helloAsync()) // Promise {<resolved>: 'helloAsync'}
helloAsync().then(v=>{
console.log(v) // hello async
})
async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会暂停执行,等到触发的异步操作完成后,恢复async 函数的执行并返回解析值。
await 关键字仅在 async function 中有效。
function testAwait(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("testAwait");
resolve();
}, 1000);
});
}
async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsync
await 针对所跟不同表达式的处理方式:
- Promise 对象: await 会暂停执行,等待 Promise 对象 resolve, 然后恢复async函数的执行并返回解析值。
- 非Promise对象: 直接返回对应的值。
总结
N/A