ES6教程
ES6 是 JavaScript 语言的下一代标准,使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言
ES6是有史以来最实质的升级,特性涵盖范围甚广,小到受欢迎的语法糖,例如箭头函数(arrow functions)和简单的字符串插值(string interpolation),大到烧脑的新概念,例如代理(proxies)和生成器(generators);它将彻底改变程序员们编写JS代码的方式。
ps:es6是基础走向进阶必须学的
变量声明let、const
代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。
1 | // 代码块内,在声明变量 PI 之前使用它会报错。 |
let
let 声明的变量只在 let 命令所在的代码块内有效,只能声明一次,可以重新赋值,重复声明会报错;不存在变量提升,在声明变量前调用,会报错!
for 循环计数器很适合用 let,使用var就需要立即执行函数,let不需要
1 | console.log(a); //ReferenceError: a is not defined |
const
const 声明一个只读的常量,一旦声明,常量的值就不能改变。声明必须初始化,否则会报错
const 保证的不是该变量值不得改动,而是变量指向的内存地址所保存的数据不得改动。
对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量;
对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的,并不能确保改变量的结构不变
区别
6点区别:
变量提升、暂时性死区、块级作用域、重复声明、修改声明变量、使用
解构赋值
解构赋值是对赋值运算符的扩展。解构的源,解构赋值表达式的右边部分。解构的目标,解构赋值表达式的左边部分。
数组结构的解构赋值
1 | 1. 可嵌套 |
对象模型的解构赋值
- 对象模型的解构赋值需要保持 键名统一
1 | 1. 可嵌套可忽略 |
Symbol
新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
- Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。
- Symbol 接受一个字符串作为参数,为新创建的 Symbol 提供描述
- 相同参数生成的 symbol 值不相等
Symbol 常作为属性名来使用,每一个 Symbol 的值都是不相等的,可以保证属性不重名
Symbol 作为对象属性名时不能用.运算符,要用方括号,因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
1 | let syObject = {}; |
Symbol.for 方法可以检测上下文中是否已经存在使用该方法且相同参数创建的 symbol 值,如果存在则返回已经存在的值,如果不存在则新建。
1 | const s1 = Symbol.for('foo'); |
需要注意的是:
Symbol 值作为属性名时,不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。
如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
使用场景
- 当一个复杂对象中含有多个属性的时候,很容易将某个属性名覆盖掉,利用 Symbol 值作为属性名可以很好的避免这一现象
- 模拟类的私有方法,ES6 中的类是没有 private 关键字来声明类的私有方法和私有变量的,但是我们可以利用 Symbol 的唯一性来模拟。
Map 与 Set
Map
Map 对象保存键值对,任何值(对象或者原始值) 都可以作为一个键或一个值。如果你需要“键值对”的数据结构,Map比Object更合适。
Maps 和 Objects 的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
Map的构建
map的键可以是字符串、对象、函数、NaN
Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键,在扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
1 | var myMap = new Map(); |
!!!虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。
1 | var myMap = new Map(); |
作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
1 | var map = new Map([ |
只有对同一个对象的引用,Map结构才将其视为同一个键
1 | var map = new Map(); |
Map 实例属性方法
- size属性
- set(key, value)
- get(key)
- **has(key)**,返回布尔值
- **delete(key)**,返回布尔值
- **clear()**,没有返回值
Map 遍历方法
for…of
对 Map 进行遍历键值对
1 | for (var [key, value] of myMap) { |
对 Map 进行遍历键对、值对
1 | for (var key of myMap.keys()) { |
结合数组方法
Map结构转为数组结构,比较快速的方法是结合使用扩展运算符(...
),结合数组的map
方法、filter
方法,可以实现Map的遍历和过滤(Map本身没有map
和filter
方法)。
1 | let map0 = new Map() |
forEach
Map还有一个forEach
方法,与数组的forEach
方法类似,也可以实现遍历。forEach方法还可以接受第二个参数,用来绑定this
1 | var reporter = { |
Map对象的操作
Map和Array的转换
Map转为数组最方便的方法,就是使用扩展运算符(…),也可以使用 Array.from 函数
1 | var kvArray = [["key1", "value1"], ["key2", "value2"]]; |
Map 的克隆
1 | var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]); |
Map 的合并
1 | var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]); var second = new Map([[1, 'uno'], [2, 'dos']]); // 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three var merged = new Map([...first, ...second]); |
Set
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
set判断值唯一性,也会考虑值的类型,相当用===
常用于去除数组重复成员
1 | 1. 方法一 |
Set属性方法
属性
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
方法
add(value)
:添加某个值,返回Set结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除所有成员,没有返回值。
遍历操作
keys()
,values()
,entries()
,forEach()
Set结构键名和键值是同一个值,所以key
方法和value
方法的行为完全一致
1 | for (let x of new Set(['red', 'green', 'blue'])) { |
Set 中的特殊值
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
类型转换
1 | // Array 转 Set new Set() |
weakset和weakmap
待补充
Proxy 与 Reflect
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API
- Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
- Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。
作为构造函数,Proxy
接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy
的介入,操作原来要访问的就是这个对象;
第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。
1 | let target = { |
Proxy 支持的拦截操作
- target,目标对象 ;propKey,即key;receiver, Proxy 实例本身
- ctx 表示目标对象上下文,args 表示目标对象的参数数组。
1 | get |
1 | function sub(a, b){ |
Reflect 的好处
- target[key] = value,没有返回值,Reflect 有返回值,可以看设置是否成功
- 原来set get 都挂载在Object上,不方便做统一管理
- Reflect 和 Proxy 使用方法上一一对应
字符串
子串的识别
1 | let string = "apple,banana,orange"; |
字符串重复
repeat():返回新的字符串,表示将字符串重复指定次数返回。
如果参数是小数,向下取整;参数是 负数、infinity 会报错;参数是 NaN,等同于 repeat 零次;传入字符串,先将字符串转化为数字
字符串补全
padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。
padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。
- 参数:第一个参数是生成字符串最小长度,第二个参数是用来补全的字符串,若空用空格填充
- 如果指定的长度小于或者等于原字符串的长度,则返回原字符串
- 如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串
1 | console.log("h".padStart(5,"o")); // "ooooh" |
模板字符串
用反引号 ` 除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中通过 ${} 的方式加入变量、表达式、函数
模板字符串中的换行和空格都是会被保留的
1 | function f(){ |
标签模板
模板字符串可以紧跟在一个函数后面,该函数将被调用来处理这个模板字符串,这种称为“标签模板”功能。
该函数依次会接收到多个参数
- 所有静态文字都作为数组传递给第一个参数。
- 占位符表达式的所有值都作为其余参数传递。
- 变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。
1 | function tag(stringArr, value1, value2) { |
数值
二进制表示法新写法: 前缀 0b 或 0B;八进制表示法新写法: 前缀 0o 或 0O
1 | console.log(0b11 === 3); // true |
对象
对象字面量
1 | // 属性的简洁表示法: ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值 |
拓展运算符
拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象,是一个非常常用的方法!
拓展运算符 后跟null、undefined、空对象,不会报错
1 | // 基本用法 |
对象新方法
Object.assign(target, source_1, ···)
用法:将源对象的所有可枚举属性复制到目标对象中。
同名属性,遵循后面覆盖前面的原则
如果只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
- null 和 undefined 放在第一个参数时,不能转化为对象,会报错
- null 和 undefined 放在第二个参数或后面,会忽略而不会报错
1
2
3
4
5Object.assign(3); // Number {3}
typeof Object.assign(3); // "object"
Object.assign(null); // TypeError: Cannot convert undefined or null to object
Object.assign(1,undefined); // Number {1}assign 的属性拷贝是浅拷贝,如果源对象某个属性值是对象,那么拷贝得到的是这个对象的引用
数组的处理
1 | Object.assign([2,3], [5]); // [5,3] |
Object.is(value1, value2)
用来比较两个值是否严格相等,与(===)基本类似
与(===)的两点区别:
1 | //一是+0不等于-0 |
数组
Array.of
方法用于将一组值,转换为数组
Array.from
方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象
1 | // Array.of() 将参数中所有值作为元素形成数组。 |
新增方法
- copyWithin()
- find()、findIndex()
- fill(),使用给定值,填充一个数组,第二、三个参数,指定填充的起始、结束位置
- entries(),keys(),values(),遍历键值对,键 为 index,值为 value
- includes()
- flat(),flatMap(),将数组扁平化处理,返回一个新数组,对原数据没有影响
函数
增加了默认参数,只有在未传递参数或undefined时,才使用默认参数,null被认为是有效的参数值
不定参数,用来表示不确定参数个数,形如,…变量名
1 | function f(...values){ |
箭头函数
- 箭头函数严格模式下this也指向window
- 箭头函数 忽略任何形式的this指向改变,静态this指向
- 箭头函数 不是个 构造器,不能new
1 | 参数 => 函数体 |
- 箭头函数 没有 this、super、arguments 和 new.target 绑定
- this,指向window
- 使用arguments ,会报错
- 不可以作为构造函数,不能使用 new 命令,否则会报错
箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象
class类
class 的本质是 function,可以看作一个语法糖,让对象原型的写法更加清晰,是构造函数的另一种写法
- class (类)作为对象的模板被引入,可以通过 class 关键字定义类;
- 类 不可以重复声明,类定义不会被提升
- 类里面的方法不加function关键字 方法与方法之间不用,号隔开
constructor关键字
- constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法
- 一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。类包含多个 constructor 的方法,则抛出 SyntaxError错误
1 | class Person{ |
静态方法
不需要通过实例对象,可以直接通过类来调用的方法,其中的 this 指向类本身
- 静态方法可以被子类继承
- 静态方法可以通过类名调用,不能通过实例对象调用,否则会报错
1 | class Person { |
setter 和 getter
1 | class Person { |
super
super关键字用于访问和调用 父类上的函数,可以调用父类的构造函数 也可以调用父类的普通函数
- super在调用父类的构造函数时,会将父类构造函数的属性方法绑定到子类实例的构造对象中!super(),相当于执行了 new Father();
- super方法必须放在子类构造函数this调用之前,不然new Father()会覆盖掉this调用生成的对象
- super() 里面有参数,相当于给父类的构造函数传参数
extends
子类可以继承父类中的一些 方法和属性
模块
模块化,为了确定模块的依赖关系,以及输入和输出的变量。
ES6 的模块化分为导出(export) @与导入(import)两个模块。
特点
ES6 的模块自动开启严格模式
模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。
export、import、as
- export 命令可以出现在模块的任何位置,但必需处于模块顶层。
- import 命令会提升到整个模块的头部,首先执行。
- as 对于模板内部变量重命名,可以导入重命名 也可以导出重命名
1 | /*-----export [test.js]-----*/ |
import
只读属性:可以改写 import 变量类型为对象的属性值,不能改写 import 中基本类型的值。
单例模式:多次重复执行同一句 import 语句,那么只会执行一次
静态执行特性:import 是静态执行,所以不能使用表达式和变量。
1
2import { "f" + "oo" } from "methods";
// error
export default 命令
- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
- export default 中的 default 是对应的导出接口变量。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 向外暴露的成员,可以使用任意变量来接收。
1 | var a = "My name is Tom!"; |
Promise对象
Promise是解决异步流程化的一种手段,从语法上说,Promise 是一个对象
Promise 有三种状态:pending(进行中)、resolved(已成功)和 rejected(已失败)
- Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变
Promise 是构造函数,需要new,只有一个函数参数 excutor执行器,执行器有两个参数,resolve接收成功,reject接收失败
promise接受then方法调用,接受两个函数参数,第一个函数接收resolve的参数,第二个参数接收reject参数
excutor是同步执行,then是异步调用,在 JavaScript 事件队列的当前运行完成之前,then回调函数永远不会被调用
通过 .then 形式添加的回调函数,是可以链式调用的,中间需要return出去
浏览器中不能终止的 Promise 链里的 rejection,建议后面都跟上 .catch(error => console.log(error));
1 | const p = new Promise(function(resolve,reject){ |
Generator 函数
Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
Generator 有两个区分于普通函数的部分:
- 函数名之前有个 * ; * 用来表示函数为 Generator 函数
- 函数内部有 yield 表达式,用来定义函数内部的状态
Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
1 | function* func(){ |
async 函数
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数
1 | async function helloAsync(){ |
await
await 操作符用于等待一个 Promise 对象,async 函数中遇到 await 就会先暂停执行,等到触发 操作完成后,恢复 async 函数的执行并返回解析值
await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误