# 你不知道的 JavaScript(中卷)

# 类型和语法
# 七种内置类型
TIP
ES5 6 种
ES6 新增 1 种
- number(数字类型)
- string(字符串类型)
- bool(布尔类型)
- null(空值)
- undefined(未定义)
- object(对象)
- symbol(符号)
# 数组
TIP
数组可以存放任意类型的值:数字,字符串,布尔,对象,甚至是另一个数组。
# 稀疏数组
TIP
稀疏数组:数组中含有空白或者空缺单元的数组。
// 稀疏数组
var arr = []
arr[0] = 0
arr[2] = 2
console.log(arr[1]) // 输出 undefined
console.log(arr.length) // 输出 3
2
3
4
5
6
# 类数组
TIP
类数组:与数组的结果类似,但并不是真正的数组。
// 类数组的案例
function foo() {
// arguments就是类数组
console.log(arguments)
}
foo('a', 'b', 'c')
2
3
4
5
6
# 类数组转成真正的数组
TIP
Array.prototype.slice()工具函数转Array.from()ES6 新方法转
// 类数组转真正数组的案例
function foo() {
var arr = Array.prototype.slice.call(arguments)
var arr2 = Array.from(arguments)
arr.push(3)
arr2.push(4)
// 输出[1, 2, 3]
console.log(arr)
// 输出[1, 2, 4]
console.log(arr2)
}
foo(1, 2)
2
3
4
5
6
7
8
9
10
11
12
# 字符串和字符数组
# 相似之处
- 都是类数组
- 都有
length属性 - 都有
indexOf,concat方法
// 字符串和字符的相似之处
var a = 'foo'
var b = ['f', 'o', 'o']
console.log(a.length) // 输出3
console.log(b.length) // 输出3
console.log(a.indexOf('o')) // 输出1
console.log(b.indexOf('o')) // 输出1
var c = a.concat('bar')
var d = b.concat(['b', 'a', 'r'])
console.log(c) // 输出foobar
console.log(d) // 输出['f','o','o','b','a','r']
2
3
4
5
6
7
8
9
10
11
12
13
# 为什么字符串不可变
TIP
字符串不可变:意思是字符串的成员函数不会改变原始的值,而是创建并返回一个新的字符串。
// 字符串的不可变性
var a = 'foo'
var b = a.concat('bar')
console.log(b) // 输出foobar
console.log(a) // 输出foo
var c = a.toUpperCase()
console.log(c) // 输出FOO
console.log(a) // 输出foo
2
3
4
5
6
7
8
9
# 字符串借用数组的方法
// 字符串借用数组的方法
var a = 'foo'
var b = Array.prototype.join.call(a, '-')
console.log(b) // 输出f-o-o
var c = Array.prototype.map
.call(a, function(v) {
return v.toUpperCase() + '.'
})
.join('')
console.log(c) // 输出F.O.O.
2
3
4
5
6
7
8
9
10
# 字符串借用数组方法反转
// 最暴力的字符串反转方法
var a = 'why'
var b = a
.split('')
.reverse()
.join('')
console.log(b) // 输出yhw
2
3
4
5
6
7
# 十种原生函数
TIP
ES5 9 种
ES6 新增 1 种
- String()
- Number()
- Boolean()
- Array()
- Function()
- Object()
- RegExp()
- Date()
- Error()
- Symbol() (ES6 新增)
# 原生函数的运用
// 原生函数的运用
var strObj = new String('abc')
var str = 'abc'
console.log(typeof str) // 输出string
console.log(typeof strObj) // 输出Object
console.log(str) // 输出abc
console.log(strObj) // 输出String {0:'a',1:'b',2:'c',length:3}
2
3
4
5
6
7
8
9
TIP
再次强调:利用原生函数创建的是一个封装对象,而非其基本类型的值
# [[CLASS]]内部属性
TIP
- 所有
typeof返回object的对象,都有一个内部属性,这个属性无法访问,一般通过Object.prototype.toString.call()访问 null和undefined虽然没有其对应的原生构造函数,但其内部[[CLASS]]依然是Null和Undefined
// 内部属性
console.log(Object.prototype.toString.call([1, 2, 3])) // 输出[object Array]
console.log(Object.prototype.toString.call('abc')) // 输出[object String]
console.log(Object.prototype.toString.call(12)) // 输出[object Number]
console.log(Object.prototype.toString.call(null)) // 输出[object Null]
console.log(Object.prototype.toString.call(undefined)) // 输出[object Undefined]
2
3
4
5
6
# 封装对象的封装和拆封
TIP
- 基本类型值会自动封装成一个封装对象
- 封装对象永远返回的是一个对象,其布尔值为
true,即使是对false值进行的封装的对象,其布尔值也是true - 获取封装对象中的基本类型值,用
valueOf()函数
// 封装对象的封装和拆封
var str = 'abc'
console.log(str.length) // 输出3
console.log(str.toUpperCase()) // 输出ABC
var flag = new Boolean(false)
if (!flag) {
console.log('执行不到这里,因为flag为封装对象,其布尔值为true')
}
var number = new Number(123)
console.log(str.valueOf()) // 输出abc
console.log(number.valueOf()) // 输出123
2
3
4
5
6
7
8
9
10
11
12
13
# 强制类型转换
TIP
显示强制类型转换:发生在静态类型语言的编译阶段
隐式强制类型转换:发生在动态语言的运行时
// 两种强制类型转换
var a = 42
var b = a + '' // 隐式强制类型转换
var c = String(a) // 显示强制类型转换
2
3
4
# ToString 类型转换规则
TIP
- 数字按照规则转换成对应的字符串格式
null转换成字符串nullundefined转换成字符串undefined- 布尔值转换成对应的字符串格式,
true转换成字符串true,false转换成字符串false - 数组,将所有单元以
,号连接起来 - 对象,如果对象没有重新定义其
toString()方法,返回其对应的内部属性[[CLASS]]的值,如[object Object]
// ToString类型转换的规则
var a = String(42)
var b = String(null)
var c = String(undefined)
var d = String(true)
var e = String([1, 2, 3])
var f = {
name: 'www',
age: 12,
toString: function() {
return this.age
}
}
var h = {
name: 'AAA',
age: 11
}
console.log(a) // 输出42
console.log(b) // 输出null
console.log(c) // 输出undefined
console.log(d) // 输出true
console.log(e) // 输出1,2,3
console.log(f.toString()) // 输出12,f对象重新定义了toString()方法
console.log(h.toString()) // 输出[object Object],返回的是h对象的内部属性[[CLASS]]的值
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# JSON 字符串化
TIP
- 数字字符串化为其对应的字符串格式
- 字符串字符串化为其对应的字符串格式,但有两对引号,其中一对是字符串本身的
- 布尔字符串为其对应的字符串格式
null字符串化为"null"- 数组字符串化为其对应的字符串化格式,例如
[1,2,3]字符串化为"[1,2,3]" undefined、函数function和symbol会自动忽略,变成undefined,如果在数组中,则返回null以保证数组中单元位置不变
// JSON字符串化
var a = 42
var b = 'abc'
var c = true
var d = null
var e = [1, 2, 3]
console.log(JSON.stringify(a)) // 输出"42"
console.log(JSON.stringify(b)) // 输出""abc""
console.log(JSON.stringify(c)) // 输出"true"
console.log(JSON.stringify(d)) // 输出"null"
console.log(JSON.stringify(e)) // 输出"[1,2,3]"
var f = undefined
var h = [1, undefined, function() {}, 4]
var i = {
name: 'why',
age: 12,
sayHello: function() {
consoel.log(this.age)
}
}
console.log(JSON.stringify(f)) // 忽略,变成undefined
console.log(JSON.stringify(h)) // 忽略数组第二项,第三项 输出"[1,null,null,4]"
console.log(JSON.stringify(i)) // 忽略对象中的方法,sayHello属性不字符串化,输出"{"name":'why',"age": 12}"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# JSON 字符串化对象
TIP
- 未定义其
toJSON方法,则按正常规则进行序列化 - 定义了其
toJSON方法,则按具体定义toJSON()方法的返回值来进行序列化
注意
对象的字符串化方法toJSON()并不是直接返回字符串化的值,而是返回一个能够被 JSON 字符串化的一个 JSON 安全的值,最后通过JSON.stringify()来字符串化
// 对象的字符串化
var obj1 = {
name: 'why',
age: 12
}
var obj2 = {
name: 'why',
age: 12,
toJSON: function() {
// 只字符串化name属性,age属性不
return {
name: this.name
}
}
}
console.log(JSON.stringify(obj1)) // 输出{"name":"why","age":12}
console.log(JSON.stringify(obj2)) // 输出{"name":"why"}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# JSON 字符串化参数运用
TIP
- 第一个可选参数:可以是一个数组或者一个函数,用来指定对象序列化过程中,哪些属性应该被处理,哪些属性应该被排斥。当参数为数组时,只序列化数组中的属性;当参数为函数时,函数返回什么就序列化什么。
- 第二个可选参数:用来指定输出的缩进格式,不常用
// JSON字符串格式化参数的运用
var obj = {
a: 42,
b: '42',
c: true,
d: [1, 2, 3]
}
var result1 = JSON.stringify(obj, ['a', 'b', 'd'])
var result2 = JSON.stringify(obj, function(key, value) {
if (key !== 'c') {
return value
}
})
console.log(result1) // 输出{"a":"42","b":"42","d":"[1,2,3]"}}(给什么输出什么)
console.log(result2) // 输出{"a":"42","b":"42","d":"[1,2,3]"}}(返回什么输出什么)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ToNumber 转换规则
TIP
true转换为 1false转换为 0- 空字符串转换为 0,非空字符串转换为 1
- null 转换为 0,undefined 转换为 NaN
- 对象或者数组,先查找
valueOf()再查找toString(),都没有则报TypeError错误
// ToNumber转换规则
var a = true
var b = false
var c = null
var d = undefined
var e = {
a: 42,
valueOf: function() {
return this.a
},
toString: function() {
return 24
}
}
var f = {
a: 42,
toString: function() {
return this.a
}
}
var h = [1, 2, 3]
h.toString = function() {
return this.join('')
}
console.log(Number(a)) // 输出1
console.log(Number(b)) // 输出0
console.log(Number(c)) // 输出0
console.log(Number(d)) // 输出NaN
console.log(Number(e)) // 输出42,先判断valueOf()
console.log(Number(f)) // 输出42,没有valueOf()时,判断toString()
console.log(Number(h)) // 输出123
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
27
28
29
30
31
32
# ToBoolean 转换规则
TIP
假值:可以被强制类型转换为 false 的值
真值:其他
JS 规范假值表
undefinednullfalse+0 -0和 NaN- 空字符串
// ToBoolean转换规则
var a = Boolean(42)
var b = Boolean(0)
var c = Boolean(null)
var d = Boolean(undefined)
var e = Boolean('')
var f = Boolean('0')
var g = Boolean('false')
var h = Boolean(NaN)
var i = Boolean([])
var j = Boolean({})
var k = Boolean(function() {
console.log('k')
})
console.log(a) // 输出true
console.log(b) // 输出false
console.log(c) // 输出false
console.log(d) // 输出false
console.log(e) // 输出false
console.log(f) // 输出true
console.log(g) // 输出true
console.log(h) // 输出fasle
console.log(i) // 输出true
console.log(j) // 输出true
console.log(k) // 输出true
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
# 显示强制类型转换(字符串与数字之间)
TIP
- 数字转字符串用
String()方法,没有使用new - 字符串转数字用
Number()方法,没有使用new - 其他方法
// 字符串与数字之间的强制类型转换
var a = 42
var b = String(a)
var c = '3.14'
var d = Number(c)
console.log(b) // 输出"42"
console.log(d) // 输出3.14
2
3
4
5
6
7
8
9
# 字符串与数字之间转换的其他方法
// 字符串与数字之间转换的其他方法
var a = 42
var b = a.toString() // 隐式类型转换,创建一个封装对象
var c = '3.14'
var d = +c // 一元运算符,显示转换为数字类型
console.log(b) // 输出"42"
console.log(d) // 输出3.14
2
3
4
5
6
7
8
9
# 显示转换为布尔值
TIP
- 其他类型转换为布尔值时,使用
Boolean(),没有使用new操作符 - 其他方法
// 其他类型值转换为布尔值
var a = 0
var b = '0'
var c = null
var d = undefined
var e = []
var f = {}
var h = ''
var i = 'false'
console.log(Boolean(a)) //输出false
console.log(Boolean(b)) //输出true
console.log(Boolean(c)) //输出false
console.log(Boolean(d)) //输出false
console.log(Boolean(e)) //输出true
console.log(Boolean(f)) //输出true
console.log(Boolean(h)) //输出false
console.log(Boolean(i)) //输出true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 显示转换为布尔值的其他方法
// 显示转换为布尔值的其他方法
var a = []
var b = ''
var c = !a // !一元运算符,显示强制类型转换,将其真值转换为假值,假值转换为真值
var d = !b
var e = !!a // !!二元运算符,在!一元运算符的基础上,把值再次取反
var f = !!b
console.log(c) // 输出false
console.log(d) // 输出true
console.log(e) // 输出true
console.log(f) // 输出false
2
3
4
5
6
7
8
9
10
11
12
13
14
# 隐式强制类型转换
# 隐式强制类型转换形式一:运算符
TIP
- 运算符
+可以隐式将数字转换为字符串 - 运算符
-可以隐式的将字符串转换为数字
// 隐式强制类型转换形式一:运算符
var a = 42
var b = '0'
var c = a + b
var d = c - 0
console.log(c) // 输出"420"
console.log(d) // 输出420
var e = [1, 2]
var f = [3, 4]
var g = e + f
// 输出1,23,4 数组相加时,会隐式的调用数组的valueOf()或者toString()
// e.toString() => "1,2"
// f.toString() => "3,4"
console.log(g)
// 提问一 i==j 或者j==i中 是数字转换成字符串再进行比较;还是字符串转换成数字,再进行比较
// 解答:ES5规范定义:数字==字符串 判断是,是字符串转换成数字,再和数字比较
var i = 42
var j = '42'
console.log(i == j)
console.log(j == i)
// 提问二:数字==布尔值时,是数字转出布尔值,再进行判断;还是布尔值转成数字,再进行判断
// 解答:ES5规范:数字==布尔值时,是布尔值转换成对应的数字,再和数字比较(true:1,false:0)
var p = true
var q = 0
console.log(p == q)
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
27
28
29
# 隐式强制类型转换形式一:隐式转换为布尔值
隐式转换为布尔值的情况
if语句的条件判断for循环的第二条条件判断语句do..while和while循环的循环判断条件?:三元运算符||和&&逻辑运算符
注意
||和&&逻辑操作符返回的不一定是布尔值,而是两个数中的其中一个数
// || 和 && 逻辑运算符的返回值
var a = 42
var b = 'abc'
var c = false
console.log(a && b) // 输出"abc",a为真,直接返回第二个数,不管第二个数为真还是为假
console.log(b && a) // 输出42,规则同上
console.log(a || b) // 输出42,a为真,直接返回第一个数,不管第二个数为真还是为假
console.log(b || a) // 输出"abc",规则同上
console.log(c && b) // 输出false,c为假,直接返回第一个数,不管第二个数为真还是为假
console.log(c || b) // 输出"abc",c为假,直接返回第二个数,不管是真还是假
2
3
4
5
6
7
8
9
10
11
12
13
// 三元运算符和逻辑操作符的大致关系
var a = 42
var b = false
// a||b 大致相当于 a?a:b
console.log(a || b) // 输出42
console.log(a ? a : b) // 输出42
// a&&b 大致相当于 a?b:a
console.log(a && b) // 输出false
console.log(a ? b : a) // 输出false
2
3
4
5
6
7
8
9
10
11
# 隐式强制类型转换形式一:符号
TIP
==宽松相等:只判断值是否相等===严格相等不仅判断值,还要判断类型
// 隐式强制类型转换形式一:符号
var a = 42
var b = '42'
console.log(a == b) // 输出true
console.log(a === b) // 输出false,a为数字类型,b为字符串类型
2
3
4
5
6
# 逻辑运算符&&扩展运用
# 提问
// 提问:如何让下面的判断成立,输出ok
if (a == 1 && a == 2 && a == 3) {
console.log('ok')
}
2
3
4
# 解答
技巧点
改写Number.prototype.valueOf方法,已达到每一次调用valueOf方法时,自增其变量
// 情况一:当a为数字时
var i = 0
// 情况一关键点:改写Number.prototype.valueOf方法
Number.prototype.valueOf = function() {
return i++
}
var a = new Number()
if (a == 1 && a == 2 && a == 3) {
console.log('ok')
}
// 情况二:当a为对象时
// 情况二关键点:定义对象自己的valueOf方法
var a = {
b: 1,
valueOf: function() {
return this.b++
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('ok')
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 异步
# 分块的程序
TIP
- 现在执行的块程序
- 将来执行的块程序
// 分块的程序
function now() {
return 21
}
function later() {
answer = answer * 2
console.log('later:' + answer)
}
var answer = now()
setTimeout(function() {
later()
}, 1000)
// 现在执行的块:
function now() {
return 21
}
function later() {}
var answer = now()
setTimeout(function() {
later()
}, 1000)
// 将来执行的块:
answer = answer * 2
console.log('later:' + answer)
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
# 无法理解的console.log函数
console.log函数到底是异步的还是同步的?
TIP
console.log函数并不是JavaCcript的正式一部分,它是宿主环境添加到JavaScript中的- 在
Node.js环境下,它是严格的同步的 - 在浏览器下,正常情况下是'同步'的,非正常情况下是异步的。
// '无法理解的'`console.log`函数
var a = {
index: 1
}
console.log(a)
a.index++
console.log(a)
// 有些浏览器在某些环境下,会把console.log 等I/O操作放在后台执行,意味着a.index++
// 操作执行完毕后,才执行两个console.log函数,造成输出结果是:{index:2}
2
3
4
5
6
7
8
9
10
# 为什么setTimeout不精准
setTimeout设置一个定时器,在指定的时间之后才放入事件循环队列中,而如果队列中有很多个项目,秉承队列先进先出的原则,需要等到它前面所有的项目都执行完毕后,才执行定时器里的函数,这就造成了定时器往往并不是一个精准的时间。
# ES6
# let 和 const
# let和const的共同点
- 只在自己声明的作用域中有效
- 一旦声明,则不能重复声明同一个变量
- 不再像
var声明那样存在变量提升
var bar = 1
{
var foo = 2
let baz = 3
const PI = 3.1415
PI = 3.1415926 //报错
}
console.log(foo) //输出2
console.log(baz) //报错
2
3
4
5
6
7
8
9
10
# 扩展/收缩运算符
TIP
ES6 新运算符...,称为扩展或者收缩,具体作用取决于到底如何使用
// ...的扩展
function foo(x, y, z) {
console.log(x, y, z) // 输出1,2,3
}
var arr = [1, 2, 3]
foo(...arr) // 扩展数组:ES6写法
foo.apply(null, arr) // 扩展数组:ES5写法
// ...的收缩
// 1.收集参数:ES6写法
function bar(...arr) {
console.log(arr) // 输出1,2,3,4,5
}
// 2.收集参数:ES5写法
function foo() {
var args = Array.prototype.slice.call(arguments)
console.log(args) // 输出1,2,3,4,5
}
bar(1, 2, 3, 4, 5)
foo(1, 2, 3, 4, 5)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 参数设置默认值
TIP
- ES6 参数默认值可以是普通的赋值
- ES6 参数默认值可以是一个表达式
- ES6 参数默认值可以是一个函数调用的返回值
// ES5设置参数默认值和ES6设置参数默认值
function foo(x, y) {
x = x || 11
y = y || 22
console.log(x + y)
}
foo() // 输出33
foo(5, undefined) // 输出27
foo(undefined, 6) // 输出17
foo(null, 6) // 输出17
foo(0, 10) // 弊端:参数无法传递0,输出21
// ES5改进
function bar(x, y) {
x = x != undefined ? x : 11
y = y != undefined ? y : 22
console.log(x + y)
}
bar() // 输出33
bar(5, undefined) // 输出27
bar(undefined, 6) // 输出17
bar(null, 6) // 输出17
bar(0, 10) // 输出10
// ES6方法
function baz(x = 11, y = 22) {
console.log(x + y)
}
baz() // 输出33
baz(5, undefined) // 输出27
baz(undefined, 6) // 输出17
baz(null, 6) // 输出6(与预期结果不一致,是因为null被强制转换成了0,实际baz(0,6))
baz(0, 10) // 输出10
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
27
28
29
30
31
32
33
# ES6 参数默认表达式
TIP
- 参数默认表达式中的参数是惰性的,意味着他们只在运行时才会发生,即其值省略或者为
undefined - 默认表达式中的参数也是有作用域的,在参数列表中有的,不会向外层获取。
// ES6参数默认表达式
function foo(x = y + 3, z = bar(x)) {
console.log(x, z)
}
function bar(val) {
return val + 1
}
var y = 5
foo(5) // 输出11 (5+6)
foo(undefined, 10) // 输出18 (8+10)
var w = 1,
z = 2
function baz(x = w + 1, y = x + 1, z = z + 1) {
console.log(x, y, z)
}
baz() // 报错,参数列表中有z,不在取外层的z,而z = z + 1时,右边的z没有赋值就使用,即报错
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 解构
常用解构方式
- 对象解构
- 数组解构
// 常用解构方式:解构对象 or 解构数组
// ES6之前的获取返回数组和返回对象的方式
function foo() {
return [1, 2, 3]
}
function bar() {
return {
X: 4,
Y: 5,
Z: 6
}
}
var arr = foo()
var a = arr[0]
var b = arr[1]
var c = arr[2]
var obj = bar()
var x = obj.X
var y = obj.Y
var z = obj.Z
console.log(a, b, c) // 输出1,2,3
console.log(x, y, z) // 输出4,5,6
// ES6之后获取返回数组和返回对象的方式
var [A, B, C] = foo()
var { X, Y, Z } = bar()
console.log(A, B, c) // 输出1,2,3
console.log(X, Y, Z) // 输出4,5,6
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
27
28
29
# 解构赋值技巧一:属性要一一对应
TIP
解构赋值{x,y,z} = {x:1,y:2,z:3}中,左侧省略了x:,y:以及z:
// 解构赋值技巧一:属性要一一对应
var { x, y, z } = { x: 1, y: 2, z: 3 } // 简写方式
var { x: x, y: y, z: z } = { x: 1, y: 2, z: 3 } // 完整写法
var { x: X, y: Y, z: Z } = { x: 1, y: 2, z: 3 } // 属性一一对应是指,左侧的属性必须同右侧的一致
2
3
4
# 解构赋值技巧二:交换两个变量的值
// 解构赋值技巧二:交换两个变量的值
var x = 10,
y = 20
;[y, x] = [x, y]
console.log(x, y) // 输出20,10
2
3
4
5
# 解构赋值技巧三:快速提取对象中的属性或者对象中的对象(数组)
// 解构赋值技巧三:快速提取对象中的属性或者对象中的对象(数组)
var {
a: { x, y },
a
} = { a: { x: 1, y: 2 } }
console.log(x, y) // 输出1,2
console.log(a) // 输出{x:1,y:2}
2
3
4
5
6
7
# 解构赋值技巧四:按需提取
TIP
往往在用解构赋值时,有一些对象或者数组的值,是我们不必要的,可以通过占位的形式绕过它,不提取
// 解构赋值技巧四:按需提取
function foo() {
return [1, 2, 3]
}
function bar() {
return {
X: 4,
Y: 5,
Z: 6
}
}
var [, a] = foo()
console.log(a) // 只提取第二个数,输出2
var { X, Z } = bar()
console.log(X, Z) // 只提取X属性和Z属性的值,输出4,6
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 解构赋值技巧五:解构的默认值
TIP
解构默认值是指:在解构的过程中,发生解构失败(左侧需要而右侧没有)时,左侧赋值一个默认的值
// 解构赋值技巧五:解构的默认值
function foo() {
return [1, 2, 3]
}
function bar() {
return {
X: 4,
Y: 5,
Z: 6
}
}
var [a = 1, b = 1, c = 1, d = 4] = foo()
console.log(a, b, c, d) // 输出1,2,3,4 foo函数返回的数组中只有三个,d应用默认值4
var { X = 1, Y = 2, Z = 3, K = 0 } = bar()
console.log(X, Y, Z, K) // 输出4,5,6,0 bar返回的对象中,没有K属性,K应用默认值0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 解构赋值技巧六:解构参数
// 解构赋值技巧六:解构参数
function foo([x, y]) {
console.log(x, y)
}
foo([1, 2]) // 输出1,2
foo([2]) // 输出2,undefined
foo([]) // 输出undefined,undefined
function bar({ x, y }) {
console.log(x, y)
}
bar({ x: 3, y: 4 }) // 输出3,4
bar({ x: 3 }) // 输出3,undefined
bar({}) // 输出undefined,undefined
2
3
4
5
6
7
8
9
10
11
12
13
14
# 对象字面量扩展
# 简洁写法
TIP
- 属性的简洁写法:当属性和标识符同名是,可以省略,只写一个,例如
x:x => x - 方法的简洁写法
// 简洁写法
var x = 2,
y = 3
var obj = {
x: x,
y: y
}
var myObj = {
x,
y
}
console.log(myObj.x) // 输出2
console.log(myObj.y) // 输出3
var foo = {
sayHello: function sayHello() {
console.log('Hello')
},
sayHi: function sayHi() {
console.log('Hi')
}
}
var newFoo = {
sayHello() {
console.log('hello')
},
sayHi() {
console.log('Hi')
}
}
newFoo.sayHello() // 输出Hello
newFoo.sayHi() // 输出Hi
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
27
28
29
30
31
32
# 字符串模板
TIP
${内容}:字符串模板里的内容可以是变量、函数调用以及表达式
// 字符串模板
var name = 'why'
var age = 23
var address = '广州'
// ES5拼接字符串
var str = '我叫:' + name + ',我的年龄是:' + age + ',我的地址是:' + address
// ES6模板字符串
var newStr = `我叫:${name},我的年龄是:${age},我的地址是:${address}`
console.log(str) // 输出:我叫:why,我的年龄是:23,我的地址是:广州
console.log(newStr) // 输出:我叫:why,我的年龄是:23,我的地址是:广州
2
3
4
5
6
7
8
9
10
11
12
13
# 箭头函数
箭头函数使用规则
- 如果只是一个简单的计算并且
return计算结果,那么可以使用箭头函数 - 如果主要依赖于
var self = this中的self来工作 - 其他复杂的情况,慎用箭头函数
# 箭头函数的使用场景一
// 箭头函数的使用场景一:没有参数时
var foo = function() {
return 12
}
var bar = () => 12
console.log(foo()) // 输出12
console.log(bar()) // 输出12
2
3
4
5
6
7
# 箭头函数的使用场景二
// 箭头函数的使用场景二:只有一个参数时
var foo = function(x) {
return x + 1
}
var bar = x => x + 1
console.log(foo(1)) // 输出2
console.log(bar(1)) // 输出2
2
3
4
5
6
7
# 箭头函数的使用场景三
// 箭头函数的使用场景三:有多个参数时
var sum1 = function(x, y) {
return x + y
}
var sum2 = (x, y) => x + y // 完整:return x+y;
var result1 = sum1(1, 2)
var result2 = sum2(1, 2)
console.log(result1) // 输出3
console.log(result2) // 输出3
2
3
4
5
6
7
8
9
# 箭头函数的使用场景四
// 箭头函数的使用场景四:遍历
var a = [1, 2, 3, 4, 5]
a = a.map(v => v + 1)
console.log(a) // 输出2,3,4,5,6
2
3
4
# 箭头函数的使用场景五
// 箭头函数的使用场景五:不改变this
var controller = {
makeRequest: function() {
var self = this
setTimeout(function() {
self.sayHello()
}, 0)
},
makeResponse: function() {
setTimeout(() => {
this.sayHello()
}, 0)
},
sayHello() {
console.log('Hello')
}
}
controller.makeRequest() // 输出Hello
controller.makeResponse() // 输出Hello
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 迭代器
TIP
定义:迭代器是一种有序的、连续的、基于拉取的用于消耗数据的组织方式
// 迭代器
var arr = [1, 2, 3]
var it = arr[Symbol.iterator]()
it.next() // 输出{value: 1, done: false}
it.next() // 输出{value: 2, done: false}
it.next() // 输出{value: 3, done: false}
it.next() // 输出{value: undefined, done: true},true代表已迭代完毕
2
3
4
5
6
7
# 迭代器可选接口
TIP
return:向迭代器发出一个信号,表明消费者代码已经完毕,不会再从其中提取任何值。throw:向迭代器抛出一个异常/错误
# 迭代器与循环
// 迭代器与循环
for (v of it) {
console.log(v)
}
// for-of循环的等价形式
for (var v, res; (res = it.next()) && !res.done; ) {
v = res.value
console.log(v)
}
2
3
4
5
6
7
8
9
10
# 自定义迭代器
// 自定义斐波拉契数组迭代器
var Fib = {
[Symbol.iterator]() {
var n1 = 2,
n2 = 1
return {
[Symbol.iterator]() {
return this
},
next() {
var current = n2
n2 = n1
n1 = n2 + current
return {
value: current,
done: false
}
},
return(v) {
return {
value: v,
done: true
}
}
}
}
}
// 输出1 1 2 3 5 8 13 21 34 55
for (var v of Fib) {
console.log(v)
if (v > 50) {
break
}
}
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
27
28
29
30
31
32
33
34
35
# 生成器
在ES6之前,一个函数一旦开始执行,将不会被中断,一直到函数执行完毕;在ES6之后,由于生成器的存在,函数可以暂停自身,待到合适的机会再开始运行。
// 生成器函数的语法: *号的位置可以随意
function* foo() {}
function* foo() {}
function* foo() {}
function* foo() {}
2
3
4
5
# 运行生成器
TIP
生成器函数同普通函数一样,可以传参,可以调用,不同是的,调用并不会向普通函数一样立即执行
// 运行生成器
function* foo(x, y) {
console.log(x + y)
}
var it = foo(5, 10)
it.next() // it.next()运行生成器函数里的代码
// 输出:15
// 输出:{value: undefined, done: true}
2
3
4
5
6
7
8
# yield 生成器暂停点
TIP
yield表示生成器函数遇到此处时,暂停,待生成器恢复时再运行其后的代码yield在同一个生成器函数中可以出现多次yield不只是一个暂停点,还可以是一个表达式
// yield生成器暂停点
function* foo() {
var x = 10
var y = 20
yield
var x = x + y
console.log(x)
}
var it = foo()
it.next() // 输出{value: undefined, done: false},表示yield执行的代码已全部执行,生成器已暂停,待下一次next时,再运行其后的代码
it.next() // 输出30 {value: undefined, done: true},true表示生成器已运行完毕
2
3
4
5
6
7
8
9
10
11
// yield表达式
function* foo() {
var x = 10
var y = 20
var z = yield x + y // 发出x+y的值30,接收参数33并赋值给z
console.log('z:' + z)
}
var it = foo()
it.next() // 输出{value: 30, done: false}
it.next(33) // 输出z:33 {value: undefined, done: true}
function* bar() {
var arr = [yield 1, yield 2, yield 3]
console.log(arr, yield 4)
}
var itBar = bar()
itBar.next() // 输出{value: 1, done: false}
itBar.next() // 输出{value: 2, done: false}
itBar.next() // 输出{value: 3, done: false}
itBar.next() // 输出{value: 4, done: false}
itBar.next() // 输出[undefined,undefined,undefined],undefined {value: undefined, done: true}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# yield*委托
TIP
yield*委托的行为和yield相同yield*委托是把生成器控制委托给一个iterator,这个iterator迭代完毕即意味着生成器迭代完毕
// yield*委托
function* foo() {
yield* [1, 2, 3]
}
var it = foo()
it.next() // 输出{value: 1, done: false}
it.next() // 输出{value: 2, done: false}
it.next() // 输出{value: 3, done: false}
it.next() // 输出{value: undefined, done: true}
2
3
4
5
6
7
8
9
# Promise
TIP
Promise不是对回调的替代Promise只能被决议一次,其后的决议会被忽略Promise决议只有两种结果:完成或拒绝
// 构造Promise
var promise = new Promise((resolve,reject) {
// ..
// resolve():完成
// reject():拒绝
})
2
3
4
5
6
# Promise 与回调的对比
// Promise与回调的对比
function ajax(url,cb) {
// ...
}
ajax('www.baidu.com',function handle(error,contents) {
if(err) {
// 处理错误
} else {
// 处理成功
}
})
function ajax(url) {
return new Promise((resolve,reject) {
// ...
})
}
ajax(url).then((contents) => {
// 处理成功
}, (err) = {
// 处理错误
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Promise.all()
TIP
Promise.all()函数接受一个或多个值的数组- 只有当数组中全部被决议(完成或拒绝)才返回
// Promise.all()
var p1 = Promise.resolve(1)
var p2 = new Promise(resolve => {
setTimeout(() => {
resolve(2)
}, 100)
})
var v3 = 3
var p4 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err')
}, 10)
})
Promise.all([p1, p2, v3]).then(res => {
console.log(res) // 输出[1,2,3]
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Promise.race()
TIP
Promise.race()函数接受一个或多个值的数组- 数组中只要有一个被决议,函数就返回
// Promise.race()
var p1 = Promise.resolve(1)
var p2 = new Promise(resolve => {
setTimeout(() => {
resolve(2)
}, 100)
})
var v3 = 3
var p4 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err')
}, 10)
})
Promise.race([p2, p1, v3]).then(res => {
console.log(res) // 输出1
})
Promise.race([p2, p4]).then(
res => {
console.log(res) // 不会执行到这里
},
err => {
console.log(err) // 输出err
}
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Map 和 WeakMap
# Map 结构
TIP
对象是创建无序键值对数据结构映射的主要机制
在 ES6 之前,对象的属性只能是字符串
在 ES6 之后,Map结构允许使用对象、数组等作为键
set():新增一个 map 结构的数据get(key):根据键获取值size:获取 map 结构的长度delete(key):根据指定的键删除has(key):判断指定的键是否存在于 map 结构中keys()遍历,values()遍历,entries()键值对遍历clear清空 map 结构
// Map结构
var map = new Map()
var x = { id: 1 },
y = { id: 2 }
// 设置map数据
map.set(x, 'bar')
map.set(y, 'foo')
// 获取map数据
console.log(map.get(x)) // 输出bar
console.log(map.get(y)) // 输出foo
// 获取map结构的长度
console.log(map.size) // 输出2
// 根据指定键删除map数据
map.delete(x)
// 根据指定的键判断是否存在于map结构中
console.log(map.has(x)) // 输出false
// 遍历map键
for (var key of map.keys()) {
console.log(key) // 输出{id:2}
}
// 遍历map值
for (var value of map.values()) {
console.log(value) // 输出foo
}
// 遍历map键值对
for (var item of map.entries()) {
console.log(item[0]) // 输出y
console.log(item[1]) // 输出{id:2}
}
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
27
28
29
30
31
32
33
34
35
36
37
# WeakMap
TIP
- 表现形式基本与
Map结构类似 - 没有
clear()方法和size属性 - 不暴露任何键、值和迭代器,因此无法进行
keys()、values()和entries()遍历 - 同
Map结构最明显的区别是,键必须是对象,如果键是可以被 GC 的,那么Map结构对应的值也会被 GC
var map = new Map();
var x = { id: 1 },
var y = { id: 2 },
var z = { id: 3 }
map.set(x,y,z);
x = null; // {id:1}可以被GC
y = null; // {id:2}可以被GC
2
3
4
5
6
7
8
# Set 和 WeakSet
# Set 结构
TIP
Set是一个集合,它里面的值是唯一的,重复添加会被忽略(Set结构不允许强制类型转换,1和"1"被认为是两个不同的值)
add():添加新值size:获取Set结构的长度delete():根据指定的键删除has():判断指定的键是否存在Set集合中keys()遍历、values()遍历、entries()遍历clear():清空Set结构
// Set结构
var set = new Set()
var x = { id: 1 }
var y = { id: 2 }
var a = 1
var b = '1'
var c = true
// 添加Set数据
set.add(x)
set.add(y)
set.add(a)
set.add(b)
set.add(c)
// 获取Set数据的长度
console.log(set.size) // 输出5
// 删除Set数据
set.delete(c)
// 判断某个值是否存在Set结构中
console.log(set.has(c)) // 输出false
// 遍历Set的键
for (var key of set.keys()) {
console.log(key) // 输出{id:1} {id:2} 1 "1"
}
// 遍历Set的值
for (var value of set.values()) {
console.log(value) // 输出{id:1} {id:2} 1 "1"
}
// 遍历Set的键值对
for (var item of set.entries()) {
console.log(item[0]) // 输出 {id:1} {id:2} 1 "1"
console.log(item[1]) // 输出 {id:1} {id:2} 1 "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
27
28
29
30
31
32
33
34
35
36
37
38
39
# Set 集合的运用
// Set集合的运用:数组的去重、并集、交集、差集
var set1 = new Set([1, 2, 1, 3, 4, 5])
var set2 = new Set([4, 5, 6, 7])
console.log(Array.from(set1)) // 去重:输出1,2,3,4,5
var union = new Set([...set1, ...set2])
console.log(Array.from(union)) // 并集:输出1,2,3,4,5,6,7
var intec = new Set([...set1].filter(x => set2.has(x)))
console.log(Array.from(intec)) // 交集:输出4,5
var diff1 = new Set([...set1].filter(x => !set2.has(x)))
console.log(Array.from(diff1)) // 差集(set1-set2):1,2,3
var diff2 = new Set([...set2].filter(x => !set1.has(x)))
console.log(Array.from(diff2)) // 差集(set2-set1):6,7
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# WeakSet
TIP
WeakSet基本表现同Set类似WeakSet必须是对象
# Array 新增 API
TIP
Array.of()新的数组构造器Array.from()将类数组对象转换成真正的数组copyWithin()复制数组中一部分内容到同一个数组的另一个位置,覆盖原来位置的值fill()填充数组find()在数组中查找某个值是否存在findIndex()在数组中某个值的索引keys()遍历,values()遍历以及entries()遍历
# Array.of()
TIP
Array.of(3)构造一个长度为 1,第一个值为 3 的数组,而不会像Array(3)那样构造一个长度为 3 空槽数组
// Array.of()
var a = Array(3)
console.log(a.length) // 输出3
console.log(a[0]) // 输出undefined
var b = Array.of(3)
var c = Array.of(1, 2, 3)
console.log(b.length) // 输出1
console.log(b[0]) // 输出3
console.log(c) // 输出[1,2,3]
2
3
4
5
6
7
8
9
10
# Array.from
TIP
常见的类数组有:arguments、classList以及自定义的类数组对象
var arrLike = {
0: '1',
1: true,
2: 23,
length: 3
}
// ES5做法
var arr1 = Array.prototype.slice.call(arrLike)
console.log(arr1) // 输出["1",true,23]
// ES6做法
var arr2 = Array.from(arrLike)
console.log(arr2) // 输出["1",true,23]
2
3
4
5
6
7
8
9
10
11
12
13
# Array.from 第二个参数的运用
// Array.from第二个参数的运用
var arrLike = {
0: true,
1: 12,
2: 'foo',
length: 3
}
var arr = Array.from(arrLike, (value, index) => {
if (typeof value === 'boolean') {
return !value
} else if (typeof value === 'string') {
return value.toUpperCase()
} else {
return value
}
})
console.log(arr) // 输出[false,12,'FOO']
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# copyWithin
TIP
target(必填):要复制到的索引start(可选):复制开始的索引,默认是 0end(可选):复制结束的索引,默认是数组的长度
// copyWithin
console.log([1, 2, 3, 4, 5].copyWithin(3)) // 输出[1,2,3,1,2]
console.log([1, 2, 3, 4, 5].copyWithin(1, 2)) // 输出[1,3,4,5,5]
console.log([1, 2, 3, 4, 5].copyWithin(1, 2, 3)) // 输出[1,3,3,4,5]
2
3
4
# fill
TIP
fill用指定值完全或者部分填充已存在的数组start(可选):填充的起始位置end(可选):填充的结束位置
// fill
var arr = Array(4).fill('www')
console.log(arr) // 输出['www','www','www','www']
var arr1 = Array(4).fill(42, 1, 2)
console.log(arr1) // 输出[undefined,42,undefined,undefined]
2
3
4
5
6
# find 和 findIndex
TIP
find和findIndex参数都是一个回调函数- 回调函数的参数分别是
value,index以及array
// find和findIndex
var arr = [1, 2, 3, 4, 5]
var flag = arr.find((value, index, arr) => {
return value == 2
})
console.log(flag) // 输出true
var index = arr.findIndex((value, index, arr) => {
return value == 2
})
console.log(index) // 输出1
2
3
4
5
6
7
8
9
10
11
# Object 新增 API
TIP
Object.is()执行比===比较更严格的值比较Object.getOwnPropertySymbols()从对象中获取所有符号属性Object.setPrototypeOf()设置对象的原型委托Object.assign()把其他对象的属性赋值到目标对象上
# Object.is
TIP
如果不是严格要是别NaN和-0的话,还是建议使用===比较
// Object.is()
var x = NaN
var y = 0
var z = -0
console.log(x === x) // 输出false
console.log(y === z) // 输出true
console.log(Object.is(x, x)) // 输出true
console.log(Object.is(y, z)) // 输出false
2
3
4
5
6
7
8
# Object.getOwnPropertySymbols
// Object.getOwnPropertySymbols()
var obj = {
age: 42,
[Symbol('name')]: 'why',
[Symbol('isMan')]: true
}
console.log(Object.getOwnPropertySymbols(obj)) // 输出[Symbol(name),Symbol(isMan)]
2
3
4
5
6
7
# Object.setPrototypeOf
// Object.setPrototypeOf()
var obj1 = {
foo() {
console.log('obj1')
},
sayHello() {
console.log('hello,world')
}
}
var obj2 = {}
Object.setPrototypeOf(obj2, obj1)
obj2.foo() // 输出obj1
obj2.sayHello() // 输出hello,world
2
3
4
5
6
7
8
9
10
11
12
13
# Object.assign
TIP
Object.assign()是浅拷贝- 只会拷贝对象本身中可枚举的属性或者符号
- 通过
=进行简单赋值操作 - 相同的属性的值以最后一个为准
var obj1 = { a: 1 }
var obj2 = { b: 2 }
var obj3 = { a: 100, c: 3 }
Object.defineProperty(obj1, 'a1', {
value: 11,
enumerable: true,
configurable: true,
writable: true
})
Object.defineProperty(obj2, 'b', {
enumerable: false
})
Object.defineProperty(obj3, Symbol('c1'), {
value: 33,
enumerable: true,
configurable: true,
writable: true
})
var result = Object.assign({}, obj1, obj2, obj3)
console.log(result) // 输出{ a:100,a1:11,c:3,Symbol(c1):33 }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Number 新增 API
TIP
Number.MAX_SAFE_INTEGER能够被安全表达的最大整数:2^53-1Number.MIN_SAFE_INTEGER能够被安全表达的最小整数:-(2^53-1)Number.isNaN()判断是否等于NaN,是返回true,否则返回falseNumber.isFinite()判断是否是一个有效的数字(非无限的)Number.isInteger()判断是否是一个整数
# Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER
// Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER
console.log(Number.MAX_SAFE_INTEGER) // 输出9007199254740991
console.log(Number.MIN_SAFE_INTEGER) // 输出-9007199254740991
2
3
# Number.isNaN
TIP
- 全局函数
isNaN()有缺陷,只要是非数字就返回true,即使是NaN Number.isNaN()判断的是NaN,只有判断传入的是NaN才返回true
// Number.isNaN()
var a = NaN
var b = 'NaN'
var c = 42
console.log(isNaN(a)) // 输出true
console.log(isNaN(b)) // 输出true isNaN()做了强制类型转换
console.log(isNaN(c)) // 输出false
console.log(Number.isNaN(a)) // 输出true
console.log(Number.isNaN(b)) // 输出false
console.log(Number.isNaN(c)) // 输出false
2
3
4
5
6
7
8
9
10
11
12
# Number.isFinite()
TIP
- 全局函数
isFinite()会对参数进行强制类型转换 Number.isFinite()则不会对参数进行强制类型转换
// Number.isFinite()
var a = NaN
var b = Infinity
var c = '42'
console.log(isFinite(a)) // 输出false
console.log(isFinite(b)) // 输出false
console.log(isFinite(c)) // 输入true
console.log(Number.isFinite(a)) // 输出false
console.log(Number.isFinite(b)) // 输出false
console.log(Number.isFinite(c)) // 输出false
2
3
4
5
6
7
8
9
10
11
12
# Number.isInteger
TIP
42,42.0以及42.没有什么区别
// Number.isInteger
var num1 = 42
var num2 = 42.0
var num3 = 42
var num4 = 42.2
var num5 = '42'
console.log(Number.isInteger(num1)) // 输出true
console.log(Number.isInteger(num2)) // 输出true
console.log(Number.isInteger(num3)) // 输出true
console.log(Number.isInteger(num4)) // 输出false
console.log(Number.isInteger(num5)) // 输出false
2
3
4
5
6
7
8
9
10
11
# String 新增 API
TIP
repeat()复制字符串,参数为要复制的次数startsWith()判断字符串是否以指定字符串开头endsWith()判断字符串是否以指定字符串结尾includes()判断字符串是否包含指定的字符串
# repeat
var str = 'why'
var newStr = str.repeat(3)
console.log(newStr) // 输出whywhywhy
2
3
# startsWith、endsWith 以及 includes
var str = 'www . baidu . com/why'
console.log(str.startsWith('www')) // 输出true
console.log(str.endsWith('why')) // 输出true
console.log(str.includes('/')) // 输出true
2
3
4
5