数值的扩展 date:(2019.07.17)
二进制和八进制表示法
Number.isFinite(), Number.isNaN()
Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true
- 它们与传统的全局方法isFinite()和isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。
Number.parseInt(), Number.parseFloat()
- ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变,这样做的目的,是逐步减少全局性方法,使得语言逐步模块化
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true
Number.isInteger()
- 用来判断一个数值是否为整数
- JavaScript 内部,整数和浮点数采用的是同样的储存方法,所以 25 和 25.0 被视为同一个值
- 如果参数不是数值,Number.isInteger返回false
Number.EPSILON
- 它表示 1 与大于 1 的最小浮点数之间的差
function withinErrorMargin (left, right) {
return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}
0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true
上面的代码为浮点数运算,部署了一个误差检查函数
Math 对象的扩展
ES6 在 Math 对象上新增了 17 个与数学相关的方法。所有这些方法都是静态方法,只能在 Math 对象上调用
- Math.trunc方法用于去除一个数的小数部分,返回整数部分,对于非数值,Math.trunc内部使用Number方法将其先转为数值,对于空值和无法截取整数的值,返回NaN
Math.trunc = Math.trunc || function(x) {
return x < 0 ? Math.ceil(x) : Math.floor(x);
};
- Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值
它会返回五种值。
参数为正数,返回+1;
参数为负数,返回-1;
参数为 0,返回0;
参数为-0,返回-0;
其他值,返回NaN
Math.sign('') // 0
Math.sign(true) // +1
Math.sign(false) // 0
Math.sign(null) // 0
Math.sign('9') // +1
Math.sign('foo') // NaN
Math.sign() // NaN
Math.sign(undefined)
- Math.cbrt方法用于计算一个数的立方根
- Math.hypot方法返回所有参数的平方和的平方根
指数运算符
- ES2016 新增了一个指数运算符(**)
2 ** 2 // 4
2 ** 3 // 8
- 指数运算符可以与等号结合,形成一个新的赋值运算符(**=)
数组的扩展
1.扩展运算符
含义
- 扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3])
// 1 2 3
- 该运算符主要用于函数调用
function add(x, y) {
return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42
- 扩展运算符后面还可以放置表达式
- 如果扩展运算符后面是一个空数组,则不产生任何效果
- 只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错
- 替代函数的 apply 方法
扩展运算符的应用
- 复制数组,只是复制了指向底层数据结构的指针
- 合并数组,但是浅拷贝
- 与解构赋值结合
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
- 如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
- 扩展运算符还可以将字符串转为真正的数组
- 实现了 Iterator 接口的对象,都可以用扩展运算符转为真正的数组
- Map 和 Set 结构,Generator 函数,都可以使用扩展运算符
2.Array.from()
- Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)
- 只要是部署了 Iterator 接口的数据结构,Array.from都能将其转为数组
- 如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组
- Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组
3.Array.of()
- Array.of方法用于将一组值,转换为数组
- 这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
- Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载
4.数组实例的copyWithin()
- 在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组,会修改当前数组
Array.prototype.copyWithin(target, start = 0, end = this.length)
- 它接受三个参数:
- target(必需):从该位置开始替换数据。如果为负值,表示倒数。
- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
- 这三个参数都应该是数值,如果不是,会自动转为数值。
[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]
5.数组实例的find()和findIndex()
- 数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
[1, 4, -5, 10].find((n) => n < 0)
// -5
- find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
- 数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
- find函数接收了第二个参数person对象,回调函数中的this对象指向person对象
- 这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足
6.数组实例的fill()
- fill方法使用给定值,填充一个数组
- fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
- fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
7.数组实例的entries(),keys()和values()
- 用于遍历数组
- 可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历
8.数组实例的includes()
- 方法返回一个布尔值,表示某个数组是否包含给定的值,该方法的第二个参数表示搜索的起始位置
9.数组实例的flat(),flatMap()
- 数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组,该方法返回一个新数组,对原数据没有影响
- 默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1
[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]] [1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5]
- 如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数
- 如果原数组有空位,flat()方法会跳过空位。
[1, 2, , 4, 5].flat() // [1, 2, 4, 5]
- 遍历函数返回的是一个双层的数组,但是默认只能展开一层,因此flatMap()返回的还是一个嵌套数组
10.数组的空位
- 空位不是undefined,一个位置的值等于undefined,依然是有值的,空位是没有任何值,in运算符可以说明这一点
- Array.from方法会将数组的空位,转为undefined
- 扩展运算符(...)也会将空位转为undefined
- copyWithin()会连空位一起拷贝
- fill()会将空位视为正常的数组位置
- for...of循环也会遍历空位
- entries()、keys()、values()、find()和findIndex()会将空位处理成undefined
TDD作业
Number API
<p align="center" style="display:inline-block">
<p>Number->Number.isInteger()</p>
<img src="./img/TDD-Number.isInteger.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Number->Number.isNaN()</p>
<img src="./img/TDD-Number.isNaN.png" height="150">
</p>
Array API
<p align="center" style="display:inline-block">
<p> Array->Array.from()</p>
<img src="./img/TDD-Array.from.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Array->Array.from()</p>
<img src="./img/TDD-Array.from.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Array->Array.of()</p>
<img src="./img/TDD-Array.of.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Array->Array.fill()</p>
<img src="./img/TDD-Array.fill.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Array->Array.find()</p>
<img src="./img/TDD-Array.find.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Array->Array.findIndex()</p>
<img src="./img/TDD-Array.findIndex.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Array->Array.entries()</p>
<img src="./img/TDD-Array.entries.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Array->Array.keys()</p>
<img src="./img/TDD-Array.keys.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Array->Array.values()</p>
<img src="./img/TDD-Array.values.png" height="150">
</p>
图区:
date:(2019.07.18)
let const
- for循环的计数器很适合let命令
- let声明不存在变量提升
- let const声明变量,暂存性死区(TDZ)
- 暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
- 不允许出现重复声明
块级作用域
- 为什么需要块级作用域?
- 内层变量可能会覆盖外层变量
- 用来计数的循环变量泄露为全局变量
- es6的块级作用域
- ES6 允许块级作用域的任意嵌套
- 使得立即执行函数不那么必要了
- 块级作用域与函数声明
- ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明,浏览器没有遵守这个规定
- ES6 的块级作用域必须有大括号
const命令
- 基本用法
- const声明一个只读的常量。一旦声明,常量的值就不能改变
- const一旦声明变量,就必须立即初始化
- 只在声明所在的块级作用域内有效
- 声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用
- 不可重复声明
- 本质
- const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动
- 可以用const什么{}和[]且赋值不会报错
- 想将对象冻结,应该使用Object.freeze方法
- ES6 声明变量的六种方法
- var、function、let、const、import、class,ES6 一共有 6 种声明变量的方法。
顶层对象的属性
- 顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的
- var命令和function命令声明的全局变量,依旧是顶层对象的属性
- let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩
globalThis对象
- JavaScript 语言存在一个顶层对象,它提供全局环境,但是,顶层对象在各种实现里面是不统一的
- 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window
- 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self
- Node 里面,顶层对象是global,但其他环境都不支持
字符串的扩展
1.字符Unicode表示法
- ES6 加强了对 Unicode 的支持,允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。
"\u0061"
// "a"
- JavaScript 共有 6 种方法可以表示一个字符。
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
2.字符串的遍历器接口
- ES6 为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
3.直接输入U+2028和U+2029
-
javaScript 字符串允许直接输入字符,以及输入字符的转义形式
'中' === '\u4e2d' // true
javaScript 规定有5个字符,不能在字符串里面直接使用,只能使用转义形式。
U+005C:反斜杠(reverse solidus)
U+000D:回车(carriage return)
U+2028:行分隔符(line separator)
U+2029:段分隔符(paragraph separator)
U+000A:换行符(line feed)
4.JSON.stringify()的改造
5.模板字符串
- 模板字符串(template string)是增强版的字符串,用反引号(`)标识,它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
- 模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中,使用trim方法消除它
- 模板字符串中嵌入变量,需要将变量名写在${}之中,大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性
let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"
- 模板字符串之中还能调用函数
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
- 模板字符串甚至还能嵌套
6.实例:模板编译
- 使用<%...%>放置 JavaScript 代码,使用<%= ... %>输出 JavaScript 表达式
7.标签模板
- 模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能
- 标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数
- 如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
8.模板字符串的限制
函数的扩展
1.函数参数的默认值
基本用法
- ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面
- 参数变量是默认声明的,所以不能用let或const再次声明
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
- 使用参数默认值时,函数不能有同名参数
- 一个容易忽略的地方是,参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的
与解构赋值默认值结合使用
- 参数默认值可以与解构赋值的默认值,结合起来使用
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
- 上面代码指定,如果没有提供参数,函数foo的参数默认为一个空对象
参数默认值的位置
- 通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数,如果非尾部的参数设置默认值,实际上这个参数是没法省略的
- 如果传入undefined,将触发该参数等于默认值,null则没有这个效果
函数的 length 属性
-
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真
(function(...args) {}).length // 0
如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
(function(a, b=1, c){}).length
1
(function(a, b, c=1){}).length
2
(function(a, b, c){}).length
3
作用域
- 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域
- 如果参数的默认值是一个函数,该函数的作用域也遵守这个规则
应用
- 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo()
// Error: Missing parameter
- 参数的默认值不是在定义时执行,而是在运行时执行
rest参数
- ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中
- rest 参数之后不能再有其他参数
- 函数的length属性,不包括 rest 参数
严格模式
- ES2016 规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式
- 两种方法可以规避这种限制。第一种是设定全局性的严格模式
- 第二种是把函数包在一个无参数的立即执行函数里面
name 属性
- 函数的name属性,返回该函数的函数名
- 变量f等于一个匿名函数,ES5 和 ES6 的name属性返回的值不一样
- 如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
- Function构造函数返回的函数实例,name属性的值为anonymous
- bind返回的函数,name属性值会加上bound前缀
箭头函数
=>
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回
如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错
-
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了
let fn = () => void doesNotReturn();
箭头函数可以与变量解构结合使用
箭头函数的一个用处是简化回调函数
使用注意点
箭头函数有几个使用注意点。
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的
除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target
由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向
不适用场合
- 第一个场合是定义对象的方法,且该方法内部包括this
- 第二个场合是需要动态this的时候,也不应使用箭头函数
嵌套的箭头函数
尾调用优化
- 尾调用(Tail Call)是函数式编程的一个重要概念,指某个函数的最后一步是调用另一个函数
- 以下三种情况,都不属于尾调用
// 情况一
function f(x){
let y = g(x);
return y;
}
// 情况二
function f(x){
return g(x) + 1;
}
// 情况三
function f(x){
g(x);
}
- 尾调用不一定出现在函数尾部,只要是最后一步操作即可
尾递归
函数参数的尾逗号
- ES2017 允许函数的最后一个参数有尾逗号
Unicode
<p align="center" style="display:inline-block">
<p> Unicode->In strings</p>
<img src="./img/TDD-unicode.png" height="150">
</p>
Template strings
<p align="center" style="display:inline-block">
<p> Template strings->basics</p>
<img src="./img/TDD-templateString-basics.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Template strings->Multiline</p>
<img src="./img/TDD-template-multiline.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Template strings->Tagged template strings</p>
<img src="./img/TDD-templateTagged.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Template strings->raw</p>
<img src="./img/TDD-template-String.raw.png" height="150">
</p>
Symbol
<p align="center" style="display:inline-block">
<p> Symbol->Symbol.for</p>
<img src="./img/TDD-symbol.for.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Symbol->Symbol.keyfor</p>
<img src="./img/TDD-symbol.keyfor.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Symbol->Basics</p>
<img src="./img/TDD-symbol-basics.png" height="150">
</p>
Object API
<p align="center" style="display:inline-block">
<p> Symbol->Object.is()</p>
<img src="./img/TDD-Object.is.png" height="150">
</p>
Modules
<p align="center" style="display:inline-block">
<p> Modules->import</p>
<img src="./img/TDD-module-import.png" height="150">
</p>
Default Parameters
<p align="center" style="display:inline-block">
<p> Default Parameters->Basics</p>
<img src="./img/TDD-DefaultParameters.png" height="150">
</p>
Spread operator
<p align="center" style="display:inline-block">
<p> Spread operator->With Array</p>
<img src="./img/TDD-spreaddestructuring.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Spread operator->With String</p>
<img src="./img/TDD-spread-string.png" height="150">
</p>
Rest operator
<p align="center" style="display:inline-block">
<p> Rest operator->As parameter
</p>
<img src="./img/TDD-rest-parameter.png" height="150">
</p>
<p align="center" style="display:inline-block">
<p> Rest operator->With destructuring</p>
<img src="./img/TDD-spread-string.png" height="150">
</p>
图片区域:
date(07.22)
变量的解构赋值
数组的解构赋值
- 基本用法
- 按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
- 本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值
- 解构不成功,变量的值就等于undefined
- 不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功
- 如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
- 事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值
- 默认值
- ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效
- 默认值是一个表达式,那么这个表达式是惰性求值
- 默认值可以引用解构赋值的其他变量,但该变量必须已经声明
对象的解构赋值
- 简介
- 对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
- 如果解构失败,变量的值等于undefined
- 将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多
let { log, sin, cos } = Math;
- 如果变量名与属性名不一致,必须写成下面这样
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
- 对象的解构赋值可以取到继承的属性
- 解构也可以用于嵌套结构的对象
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"
- 注意,这时p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]
- 默认值
- 对象的解构也可以指定默认值
- 默认值生效的条件是,对象的属性值严格等于undefined
- 注意点
- 如果要将一个已经声明的变量用于解构赋值,必须非常小心
- 解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式
- 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;//方括号这种写法,属于“属性名表达式”
first // 1
last // 3
- 字符串的解构赋值
- 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
- 类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值
let {length : len} = 'hello';
len // 5
- 数值和布尔值的解构赋值
- 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错
- 函数参数的解构赋值
- undefined就会触发函数参数的默认值
- 圆括号问题
- es6规则:只要有可能导致解构的歧义,就不得使用圆括号;阮一峰建议只要有可能,就不要在模式中放置圆括号
- 不能使用圆括号的情况
- 变量声明语句
// 全部报错
let [(a)] = [1];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};
let { o: ({ p: p }) } = { o: { p: 2 } };
- 函数参数
// 报错
function f([(z)]) { return z; }
// 报错
function f([z,(x)]) { return x; }
- 赋值语句的模式
- 可以使用圆括号的情况
- 赋值语句的非模式部分,可以使用圆括号
- 用途
- 交换变量的值
- 从函数返回多个值
- 函数参数的定义
- JSON数据的提取
- 函数参数的默认值
- 遍历map数组
- 任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
- 如果只想获取键名,或者只想获取键值,可以写成下面这样。
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
- 输入模块的指定方法
class表达式
与函数一样,类也可以使用表达式的形式定义
const myClass = new Me{
getClassName(){
return Me.name;
}
}
set
var s = new set();
s.add()
不会添加重复值
Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化
set可以可以去重(包括NaN)
两个对象总是不相等的
Set结构实例有一下属性:
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
Set实例分为两大类,操作方法和遍历方法
Set.prototype.add(value):添加某个值,返回 Set 结构本身。
Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
Set.prototype.clear():清除所有成员,没有返回值。
Array.from方法可以将 Set 结构转为数组
数组去重:Array.from(new Set(array))
Set.prototype.keys():返回键名的遍历器
Set.prototype.values():返回键值的遍历器
Set.prototype.entries():返回键值对的遍历器
Set.prototype.forEach():使用回调函数遍历每个成员
for of;map;filter;
因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
Set和Map都可以用来生成新的 Map
size 属性
Map.prototype.set(key, value)
Map.prototype.get(key)
Map.prototype.has(key)
Map.prototype.delete(key)
Map.prototype.clear()
Map.prototype.keys():返回键名的遍历器。
Map.prototype.values():返回键值的遍历器。
Map.prototype.entries():返回所有成员的遍历器。
Map.prototype.forEach():遍历 Map 的所有成员
iterator
1. Iterator(遍历器)的概念
- 遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
- Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费
- Iterator 的遍历过程是这样的
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
2. 默认 Iterator 接口
3. 调用 Iterator 接口的场合
4. 字符串的 Iterator 接口
5. Iterator 接口与 Generator 函数
6. 遍历器对象的 return(),throw()
7. for...of 循环
promise
1.Promise的含义
- Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大
- 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理
- Promise对象有以下两个特点。
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
- Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段
2.基本用法
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
- Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署
- resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
- reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去
- Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数
promise.then(function(value) {
// success
}, function(error) {
// failure
});
- then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数
- Promise 新建后就会立即执行。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
- Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出
- resolve函数的参数除了正常的值以外,还可能是另一个 Promise
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
- p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作
- 注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行
3.Promise.prototype.then()
- Promise 实例具有then方法,是定义在原型对象Promise.prototype上的,它的作用是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数,then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例),因此可以采用链式写法,即then方法后面再调用另一个then方法
4.Promise.prototype.catch()
- Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
//等同于
promise.catch(function(error) {
console.log(error);
});
// Error: test
// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
- 比较上面两种写法,可以发现reject方法的作用,等同于抛出错误。
- 如果 Promise 状态已经变成resolved,再抛出错误是无效的
- Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获
- 一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
- 第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数
5.Promise.prototype.finally()
- inally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
- 不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数
- finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果
6.Promise.all()
- Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.all([p1, p2, p3]);
- Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
- p的状态由p1、p2、p3决定,分成两种情况。
- (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数;(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数
7.Promise.race()
- Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
- 上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数
8.Promise.resolve()
- 有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用
- Promise.resolve方法的参数分成四种情况。
参数是一个 Promise 实例
- 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
参数是一个thenable对象
- thenable对象指的是具有then方法的对象,比如下面这个对象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
- Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
- 参数不是具有then方法的对象,或根本就不是对象
- 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
- 不带有任何参数
- Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象,所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。
9.Promise.reject()
- Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected
- 注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数
10.应用
- 异步加载图片
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
- Ajax 操作
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
- getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误