let 和 const
1 | var a= []; |
暂时性死区的本质就是只要进入当前作用域 所要使用的变量就已经存在 但是不可获取 只有等到声明变量的那一行代码出现 才可以获取和使用变量。
块级作用域
ES6 允许块级作用域声明函数
- 函数声明类似于
var
即会提升到全局作用域或者函数作用域的头部 - 函数声明还会提升到所在块级作用域的头部
do 表达式
1 | let x = do { |
const
const
实际上保证的并不是变量的值不得改动 而是变量指向的那个内存地址不得改动(对于一个复合类型的数据 变量指向的内存地址保存的只是一个指针 const 只能保证这个指针是固定的 不能保证它指向的数据结构是不可变的)
变量的结构赋值
数组的解构赋值
ES6 内部使用严格相等运算符判断一个位置是否有值 所以 如果一个数组成员不严格等于
undefined
默认值是不会生效的
1 | let [x=1] = [undefined]; // x = 1 |
对象的解构赋值
数组的元素是按次序排列的 变量的取值是由他的位置决定的 而对象的属性没有次序 变量必须与属性同名才能取到正确的值 0
默认值生效的条件是对象的属性值严格等于undefined
如果解构失败 那么变量的值等于undefined
如果解构的是嵌套的对象 而且子对象所在的父属性不存在 那么就会报错
1 | let { |
1 | let arr = [1, 2, 3]; |
数值和布尔值的解构赋值
解构赋值时 只要等号右边的值不是对象或数组 就会将其转为对象 由于undefined
和null
无法转为对象 所以对他们进行解构赋值时都会报错
解构的用途
- 交换变量的值
1 | let x = 1; |
- 从函数返回多个值
- 函数参数的命名
- 提取
JSON
数据 - 函数参数的默认值
- 遍历
Map
结构
1 | for (let [key] of map) { |
- 输入模块的指定方法
字符串的扩展
includes() startsWith() endsWith()
includes()
: 返回布尔值 表示是否找到了字符串startsWith()
: 返回布尔值 表示参数字符串是否在源字符串的头部endsWith():
返回布尔值 表示参数字符串是否在源字符串的头部
repeat()
返回一个新字符串 将原来的字符串重复几次
padStart() padEnd()
1 | 'x'.padStart(5, 'ab')//'ababx' 最小长度以及补全的字符串 |
模板字符串
模板字符串默认会将其他字符串转移 导致无法嵌入其他语言
String.raw()
充当模板字符串的处理函数(返回一个连反斜线都被转转义的字符串)
1 | String.raw`Hi\n${5+3}`;/// Hi \\nS ! |
正则的扩展
字符串的正则方法
数值的扩展
Number.isFinite():判断一个数是不是有限的
Number.isNaN():判断一个数是不是 NaN
只对数值有效 对非数值一律返回 false
Number.EPSILON
极小的常量 实质是一个可以接受的误差范围
可用于为浮点数设置一个误差范围
1 | function withinErrorMargin(left, right) { |
Math 对象的新增
Math.trunc()
用于取出一个数的小数部分 返回整数部分(先内部使用 Number 再转为数值 对于空值或者无法截取整数的值 返回 NaN)
1 | Math.trunc = Math.trunc || (x) => x < 0? Math.ceil(x): Math.floor(x) |
Math.sign()
判断一个数到底是正数负数还是零 先转为数值
函数的扩展
参数默认值
参数默认值不是传值的 而是每次都重新计算默认表达式的值 也就是默认表达式其实是惰性求值的
1 | let x = 99; |
函数的length
返回没有设置默认值的参数个数
作用域
一旦设置了默认值 函数进行声明式初始化 参会会形成一个单独的作用域 等到初始化结束 这个作用域就会消失 这和不设置参数的默认行为不一样
1 | let x = 1; |
1 | var x = 1; |
箭头函数
- 函数体内的
this
就是定义时所在的对象 而不是使用时所在的对象this
指向的固定化并不是因为箭头函数内部由绑定 this 的机制 而是因为箭头函数根本没有自己的this
导致内部的this
就是外层代码块的this
- 不可以当作构造函数
- 因为它没有
this
所以不能用作构造函数
- 因为它没有
- 不可以使用
arguments
对象 可以用rest
参数代替 - 不可以使用
yield
命令 因此箭头函数不能也能做Generator
函数
绑定 this
使用(::)
会自动将左边的对象作为上下文环境(即this
对象)绑定到右边的函数上
1 | foo::bar; // bar.bind(foo); |
可以链式调用
尾调用优化
函数调用会在内部形成一个调用记录(调用帧)保存调用位置和内部变量等信息 所有调用帧就形成一个调用栈 尾调用由于是函数的最后一步操作 所以不需要保留外层函数的调用栈 因为调用位置 内部变量等信息都不会再用到 所以直接用内层函数的调用帧取代外层函数的即可。
1 | function f() { |
尾调用优化即指保留内层函数的调用帧 如果函数都是尾调用 那么可以做到每次执行时调用帧只有一项 可以大大节省内存
只有不再用到外层函数的内部变量 内层函数的调用帧才可以取代外层函数的调用帧 否则无法进行尾调用优化
尾递归
1 | function factorial (n) { |
递归函数的改写
- 在尾递归函数之外再提供一个正常形式的函数
1 | function tailFactorial(n, total) { |
- 使用 ES6 的函数默认值
1 | function factorial(n, total = 1) { |
一旦使用递归 则最好使用尾递归
ES2017 提议可以在最后一个参数后面加逗号 减少后期更改时的提交信息的冗余
数组的扩展
解构赋值
1 | const [first, ...rest] = []; //first:undefined ; rest: [] |
如果将扩展运算符用于数组赋值 则只能将其作为参数的最后一位
任何Iterator
接口的对象 都可以使用扩展运算符来转为真正的数组
1 | var nodeList = document.querySelectorAll("div"); |
Array.from()
将两类对象转为真正的数组:类似数组的对象(array-like object
)和可遍历(iterable
)对象
Array.from
的第二个参数类似于map
方法 将每个元素处理后放入返回的数组
1 | Array.from(new Set(arr)); //数组去重 |
Array.of()
将一组值转换为数组
copyWithin()
在当前数组内部将指定位置的成员复制到其他位置(会覆盖原有裁员) 然后返回数组 即这个方法会修改当前数组
Array.prototype.copyWithin(target, start = 0, end = this.length)
1 | //将3号位复制到0号位 |
find()和 findIndex()
找到符合条件值以及对应的索引
fill()
使用一个定制填充一个数组
entries()、keys()、values()
includes()
Map
结构的has
方法是用来查找键名的Set
结构的has
方法是用来查找值的
数组的空位
空位不是 undefined 一个位置的值等于 undefined 依然是有值的 空位是没有任何值的 in 运算符可以说明这点
ES6 规定将空位转为undefined
对象的扩展
Object.is()
与全等运算符差不多 但是NaN
等于NaN +0 != -0
Object.assign()
将源对象的所有可枚举属性复制到目标对象
如果目标对象和源对象有同名属性或者多个源有同名属性 则后面的属性会覆盖前面的属性
如果只有一个参数 则Object.assign
直接返回参数 如果该参数不是对象 则先转成对象再返回 如果是null
或者 undefined 则报错
源对象(非首参数)位置的参数会先转成对象 不能则跳过 粗了字符串会以数组的形式复制到目标对象 其他值不会产生效果
Object.assign
复制的属性是有限的 只复制源对象的自身属性(不复制继承属性) 也不复制不可枚举的属性
Object.assign
是浅复制 不是深复制
对于嵌套的对象 一旦遇到同名属性 Object.assign
的处理方法是替换而不是添加
1 | var target = { a: { b: "c", d: "e" } }; |
Object.assign 可以用来处理数组 但是会把数组当作对象来处理
用途
- 为对象添加属性
1 | class Point { |
- 为对象添加方法
1 | Object . assign (SomeClass . prototype , { |
- 克隆对象
1 | function clone(origin) { |
- 合并多个对象
1 | const merge = (target, ...source) => Object.assign(target, ...source) |
- 为属性指定默认值
由于存在深复制的问题 DEFAULTS 对象和 options 对象的所有属性都只能是简单类型 而不能指向另一个对象 否则将导致 DEFAULTS 对象该属性不起作用
属性的遍历
for...in
- 遍历对象自身和继承的可枚举属性(不含
Symbol
属性)
- 遍历对象自身和继承的可枚举属性(不含
Object.keys(obj)
- 返回一个包含自身(不包含
Symbo
l 以及继承)的可枚举属性
- 返回一个包含自身(不包含
Object.getOwnPropertyNames(obj)
- 包含自身(不包含
Symbol
但是包含不可枚举属性)的数组
- 包含自身(不包含
Object.getOwnPropertySymbols(obj)
- 包含自身的所有
Symbol
属性
- 包含自身的所有
Reflect.ownKeys(obj)
- 包含自身的所有属性
遍历规则
- 首先遍历所有属性名为数值的属性 按照数字排序
- 其次遍历所有属性名为字符串的属性 按照生成时间遍历
- 最后遍历所有属性名为
Symbo
l 的属性 按照生成时间排序
Object.setPrototypeOf()
设置一个对象的prototype
对象 返回参数本身
Object.getPrototypeOf()
读取一个对象的prototype
对象
如果参数不是对象 则自动转换为对象 如果是undefined
或null
则直接报错
Objecr.keys() Object.values() Object.entries()
O
bject.keys()
:包含自身(不包含继承)的所有可遍历属性Object.values()
:如果参数不是对象 则会先将其转为对象 对于数值或者布尔值则返回空数组Object.entries()
:输出非Symbol
值的属性- ‘可以将对象转为真正的
Map
结构
1
2
3var obj = { foo: "bar", baz: 40 };
var map = new Map(Object.entries(obj));
map; //{foo: 'bar', baz: 40}- ‘可以将对象转为真正的
对象的扩展运算符
解构赋值的复制是浅复制 即如果一个键的值是复合类型的值(数组、对象、函数) 那么解构赋值复制的是这个值的引用 而不是这个值的副本
解构赋值不会复制继承自原型对象的属性
1 | //克隆完整对象 |
Object.assign
总是复制一个属性的值 而不会复制它背后的赋值方法或取值方法
Object.getOwnPropertyDescriptions
配合Object.defineProperties
可以实现正确复制
1 | const source = { |
Null 传到运算符(?.
)
相当于判空操作
Symbol
Symbol
函数不能使用 new 命令 否则会报错 因为Symbol
是一个原始类型的值 不是对象 所以不能添加属性 是一种类似于字符串的数据类型
Symbol
函数的参数只表示对当前Symbol
值的描述 因此相同参数的Symbol
函数的返回值是不相等的
Symbol
值不能与其他值进行比较 否则会报错 Symbol
值可以转为字符串或者布尔值 但是不能转为数值
作为属性名的 Symbol
Symbol
值可以作为标识符用于对象的属性名 复制某一个键不小心被重写或者覆盖
1 | const mySymbol = Symbol(); |
Symbol
值作为属性名时 该属性还是公开属性 不是私有属性
常量使用 Symbol 值的最大好处就是其他任何值不可能有相同的值了 因此可以保证 switch 语句按设计的方式工作
用处
消除魔法字符串
1 | const shapeType = { |
属性名的遍历
Symbol
作为属性名 不会被for...in for...of
以及Object.keys() Object.getOwnPropertyNames()
返回 但是不是私有属性 可以使用Object.getOwnPropertySymbols
获取对象的所有Symbol
属性名
Reflect.owbKeys()
可以返回所有的类型的键名 包括常规键名和 Symbol 键名
以
Symbol
值作为名称的属性不会被常规方法遍历得到 可以用这个特性为对象定义一些非私有但又希望只用于内部的方法
1 | const size = Symbol("size"); |
Symbol.for() Symbol.keyFor()
接收一个字符串作为参数 然后搜索有没有以该参数作为名称的Symbol
有就返回这个Symbol
值 没有就新建一个以该字符串为名称的Symbol
值
1 | const s1 = Symbol.for("foo"); |
1 | const s1 = Symbol.for("foo"); |
Symbol.for
为Symbol
值登记的名字是全局环境的 可以在不同的iframe
或service worker
中取到同一个值
模块的 Singleton 模式
Singleton 模式指的是 调用一个类并且在任何时候都返回同一个实例
1 | //mod.js |
内置的 Symbol 值
Symbol.hasInstance
使用instanceof
运算符时会调用这个方法 判断该对象是否为某个构造函数的实例
1 | foo instanceof Foo 实际上调用了Foo[Symbol.hasInstance](foo) |
Symbol.isConcatSpreadable
Symbol.species
Symbol.match
Symbol.replace
Symbol.search
Symbol.split
Symbol.iterator
Symbol.toPrimitive
Symbol.toStringTag
Symbol.unscopables
Set 和 Map
Set
Set
:一组不会重复的数组
1 | [...new Set(array)]; //数组去重 |
向Set
加入值时不会发生类型转换 Set
内部判断两个值是否相等时使用的算法类似于精确运算符 但是NaN
等于自身 (两个对象总是不相等的)
Set
的遍历顺序就是插入顺序 这个特性非常有用 比如使用Set
保存一个回调函数列表 调用时能保证按照添加顺序调用
遍历的应用
扩展运算符(...)
内部使用for...of
循环 也可以使用Set
结构
1 | let set = new Set(["red", "blue"]); |
1 | //无法直接在遍历操作中同步改变Set结构 一种是利用原Set映射出一个新的结构 然后赋值给原来的Set结构 另一种是利用Array.from方法 |
WaekSet
WeakSet
的成员只能是对象 而不能是其他类型的值WeakSet
中的对象都是弱引用 即垃圾回收机制不考虑WeakSet
对该对象的引用(如果其他对象不再引用该对象 那么垃圾回收机制就会自动回收该对象所占用的内存 不考虑是否还存在于WeakSet
中)WeakSet
不能遍历 因为成员都是弱引用 随时可能消失 遍历机制无法保证成员存在 很可能刚刚遍历结束 成员就获取不到了
WeakSet
的一个用处是储存 DOM 节点 而不用担心这些节点从文档移除时会语法内存泄漏
Map
Map
的键可以是任何数据类型Map
的键实际上适合内存绑定的 只要内存不一样 就视为两个键 这就解决了同名属性的问题 我们扩展别人的库是 如果使用对象作为键名 不 i 用担心自己的属性和原作者的属性同名
只有对同一对象的引用
Map
结构才将其视为同一个键
1 | const map = new Map(); |
1 | //如果Map的键是一个简单类型的值(数字、字符串、布尔值) 只要两个值严格相等 Map就将其视为一个键 将NaN视为一个键 |
Map
的遍历顺序就是插入顺序
1 | //Map转为JSON |
WeakMap
如果想要往对象中添加数据又不想干扰垃圾回收机制 便可以使用WeakMap
比如在网页的DOM
元素上添加数据时就可以用WeakMap
结构 当该DOM
元素被清除时 其对应的WeakMap
记录就会自动清除
WeakMap
弱引用的只是键名而不是键值 键值依然是正常引用的
将监听函数反正该WeakMap
里面 一旦DOM
对象消失 与它绑定的函数也会自动消失
WeakMap
也可以用来部署私有属性
Proxy
要使
Proxy
起作用 必须针对Proxy
实例进行操作 而不是针对目标对象进行操作
Proxy
实例也可以作为其他对象的原型对象
1 | const proxy = new Proxy( |
get(target, propKey, receiver):
拦截对象属性的读取
1 | //get方法可以继承 |
set(target, propKey, value, receiver
):拦截的对象属性的设置
如果一个属性不可配置或不可写 则该属性不能被代理 通过Proxy
对象访问该属性将会报错
1 | //有时候 我们会在对象上设置内部属性 属性名的第一个字符使用下划线开头 表示这些属性不应该被外部使用 结合get和set方法可以防止这些内部属性被外部读/写 |
如果目标对象自身的某个属性不可写也不可配置 那么 set 不得改变这个属性的值 只能返回同样的值 否则报错
has(target, propKey):
拦截propKey in proxy
的操作
1 | //使用has方法隐藏了某些属性 使其不被in运算符发现 |
如果原对象不可配置或禁止扩展 那么has
拦截会报错
has
方法拦截的是Has Property
操作,而不是HasOwnProperty
操作,即has
方法 不判断一个属性是对象自身的属性还是继承的属性 对for...in
不生效
deleteProperty(target, propKey)
:拦截delete proxy[propKey
]的操作ownKeys(target)
:拦截Object.getOwnPropertyNames(proxy) Object.getOwnPropertySymbols(proxy) Object.keys(proxy)
返回一个数组 该方法返回目标对象自身属性的属性名 而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性getOwnPropertyDescriptor(target, propKey)
:拦截Object.getOwnPropertyDescriptor(proxy, propKey)
defineProperty(target, propKey, propDesc):
拦截Object defineProperty(proxy propKey,
propDesc 〕、 Object define Properties(proxy, propDescs)
preventExtensions(target):
拦截Object preventExtensions proxy
getPrototypeOf(target)
isExtensible(target)
setPrototypeOf(target, proto)
:拦截Object setPrototypeOf proxy proto )
, 返回一个布尔值。 果目标对象是 函数 那么还有两 操作可以拦截。apply(target, object, args)
apply
方法拦截函数的调用/call/apply
操作(直接调用Reflect.apply
方法也会被拦截)
1 | //三个参数 目标对象 目标对象的上下文对象 目标对象的参数数组 |
construct( target, args)
:拦截Proxy
实例作为构造函数调用的操作 ,比ηew proxy ( . . . arg)
1 | target:目标对象 args:构建函数的参数对象 |
this 问题
在Proxy
代理的情况下 目标对象内部的this
关键字会指向Proxy
代理
1 | const target = { |
有些元素对象的内部属性只有通过正确的this
才能获取 所以Proxy
也无法代理这些原生对象的属性
1 | const target = new Date(); |
实例:web 服务的客户端
Proxy
对象可以拦截目标对象的任意属性 所以它很适合编写 Web 服务的客户端
1 | const service = createWebSer rice (’ http://example . com/data ’); |
Proxy
还可以用来实现数据库的ORM层
Reflect
- 将
Object
对象的一些明显属于语言内部的方法(Object.defineProperty
)放到Reflect
对象上 - 修改某些
Object
方法的返回结果 让其变得合理
1 | //旧写法 |
- 让
object
操作编程函数行为
1 | //旧写法 |
Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就 能在Reflect
对象上找到对应的方法。这就使Proxy
对象可以方便地调用对应的Reflect
方法来完成默认行为,作为修改行为的基础。也就是说,无论Proxy
怎么修改默认行为,我们 总可以在Reflect
上获取默认行为。
1 | Proxy(target, { |
使用 Proxy 实现观察者模式
1 | const queuedObservers = new Set(); //观察者函数容器 |
Promise
- 对象的状态不受外界影响
- 一旦状态改变就不会再变 任何时候都可以得到这个结果
- 无法取消
Promise
一旦新建就会立即执行 无法中途取消 - 如果不设置回调函数
Promise
内部抛出的错误不会反映到外部 - 处于
Pendding
状态时 无法得知目前进展到哪一阶段 - 如果某些事件不断重复发生 使用
Stream
模式更好
1 | //Promise新建后会立即执行 |
调用resolve
或reject
并不会终结Promise
的参数函数的执行
1 | new Promise((resolve, reject) => { |
1 | var promise = new Promise(function (resolve , reject) { |
Promise
对象的错误具有冒泡性质 会一直向后传递知道被捕获为止 即错误总会被下一个catch
捕获
不要再 then 中定义reject
状态的回调函数 而应该总是使用catch
方法
如果没有指定 catch 方法指定错误处理的回调函数 Promise 对象抛出的错误不会被传递到外层代码 即不会有任何反应