很早之前已经做过有关 Object
和 Array
的笔记了,这次主要是复习重点:
Object
:MDN-Object | Plumbiuの小屋Array
:MDN-Array | Plumbiuの小屋字符串可以认为是一个伪数组,所以可以解构赋值
const [a, b, c, d, e] = 'hello'
a // "h"
b // "b"
c // "c"
d // "d"
e // "e"
当然,类似数组的对象都有一个 length
属性,所以还可以解构字符串的 length
let { length: len } = 'hello'
len // 5
JavaScript 传统上只有 indexOf
方法,可以用来确认一个字符串是否包含在另一个字符串中。ES6 还提供了三种新方法:
includes()
:返回布尔值,表示是否找到了参数字符串startsWith()
:返回布尔值,表示参数字符串是否在原字符串的头部endsWith()
:返回布尔值,表示参数字符串是否在原字符串的尾部let s = 'Hello world!'
s.startsWith('Hello') // true
s.endWith('!') // true
s.includes('llo') .. true
三个方法都支持第二个参数,表示开始搜索的位置:
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
其中 endsWith
针对的是前 n
个字符,其他两个方法针对的是到第 n
个位置的字符串
repeat
返回一个新的字符串,表示将原来字符串重复 n
次
n <= -1
或者 n = Infinity
:报错(RangeError)n = 0
的情况注:第一种情况之所以是
n <= -1
是因为,当 n 介于 -1 到 0 时,会被等同于 0
该方法不会修改原来的字符串
'ha'.repeat(-2) // RangeError
'ha'.repeat(-0.9) // ""
'ha'.repeat(2.9) // "haha"
'ha'.repeat(NaN) // ""
'ha'.repeat('haha') // ""
'ha'.repeat('3') // "hahaha"
'ha'.repeat(2) // 'haha'
字符串补全长度功能,其中 padStart()
用于头部补全,padEnd()
用于尾部补全
instance.padStart(n, string)
及 instance.padEnd(n, string)
共接收两个参数,第一个参数补全后的长度,第二个参数是用来不全的字符串
n < instance.length
:补全不生效,返回原字符串n < string.length + instance.length
:截去超出部分的补全字符串(string)string
省略:默认空格补全'xxx'.padStart(2, 'ab') // "xxx"
'xxx'.padStart(5, '0123456789') // "xxx01"
'xxx'.padStart(5) // " xxx"
和 trim
类似,只不过 ES2019 对其进行了细分,trimStart()
用来去除头部空格,而 trimEnd()
用来去除尾部空格
三个方法均不会修改原来字符串
let s2 = ' abc '
console.log(s2.trim())
console.log(s2.trimStart())
console.log(s2.trimEnd())
我们可以在声明函数时,赋予默认值:
function log(x, y = 'hello') {
console.log(x, y)
}
log('hi')
log('hi', 'world')
log('hi', '')
ES6 引入了 rest 参数,形式如 ...rest
,用于获取函数多余参数,这样就不需要使用 arguments
对象了。
function add(...values) {
let sum = 0
for(var val of values) [
sum += val
]
console.log(typeof values) // object
return sum
}
add(2, 5, 3) // 10
注:rest 参数只能放在最后,例如下面的例子是错误的
function test(a, ...rest, b) {}
函数的 name
属性,返回该函数的函数名:
function foo {}
foo.name // "foo"
注:ES6 对这个属性做出了一些修改,如果将一个匿名函数赋值给一个变量,ES5 的
name
属性,会返回空字符串,ES6 则会返回实际的函数名
var f = function() {}
// ES5
f.name // ""
// ES6
f.name // "f"
Function
构造函数返回的函数实例,name
属性的值为 anonymous
(new Function).name // "anonymous"
bind
返回的函数,name
属性值会加上 bound
前缀
function foo() {}
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
和数组一样,对象也有扩展运算符(...
)
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }
console.log(x) // 1
console.log(y) // 2
console.log(z) // { a: 3, b: 4 }
如果对 null
或者 undefined
解构赋值,会报错
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误
结构赋值必须在最后,且只能存在一个
let { ...x, y, z } = obj // 句法错误
let { x, ...y, ...z } = obj // 句法错误
如果我们要访问对象内部的某个属性,往往需要判断一下该对象是否存在。比如,读取 message.body.user.firstName
安全的写法应该是:
const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default'
或者使用三元运算符 ?:
判断
const value = value ? value : undefined
这样的层层判断很麻烦,为了简化这种写法,ES2020 引入了“链判断运算符” ?.
const firstName = message?.body?.user?.firstName || 'default'
上面代码引入了 ?.
运算符,直接在链式调用的时候判断,左侧的对象如果为 null
或者 undefined
,则不再往下运算,而是返回 undefined
同时链判断运算符有三种写法:
obj?.prop
:对象属性obj?.[expr]
:同上func?.(...args)
:函数或对象方法的调用a?.b
// equal to
a == null ? undefined : a.b
a?.[x]
// equal to
a == null ? undefined : a[x]
// equal to
a == null ? undefined : a.b()
a?.()
// equal to
a == null ? undefined : a()
注:
- 如果
a?.b()
中的a.b
不是函数,不可调用,那么a?.b()
是会报错的。- 如果
a?.()
中的a
不是null
或者undefined
或者函数,那么a?.()
会报错
同时还有几个注意点:
a?.[++x]
// equal to
a == null ? undefined : a[++x]
如果 a
是 null
或者 undefined
,那么 x
不会进行递增运算(其他操作同理)
(a?.b).c
// equal to
(a == null ? undefined : a.b).c
不管 a
对象是否存在,圆括号后的 .c
总是会执行。
?.
运算符不要用括号
如果某个值是 null
或者 undefined
时,我们希望把它们指定为默认值,常见做法是使用或运算符 ||
指定默认值
const test = obj.test || 'none data'
但其实这样的写法是有问题的,如果 obj.test
为 false
、0、空字符串,默认值也会生效。为了避免这种情况,ES2020 引入了 ??
运算符,只有 obj.test
为 null
或者 undefined
时,默认值才会生效
const test = obj.test ?? 'none data'
??
存在优先级问题,如果与 &&
或者 ||
同时使用,不加括号的情况下,会报错
// 报错
t1 ?? t2 && t3
// ...
需要加上括号,才不会报错:
(t1 ?? t2) && t3
ES5 比较两个值是否相等,只有两个运算符:相等运算符 ==
和严格相等于算符 ===
。前者会自动转换数据类型,后者 NaN
不等于自身,以及 +0
全等于 -0
用于比较两个值是否严格相等(比 `)
console.log(NaN === NaN) // false
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(+0, -0)) // false
console.log(Object.is(1, 1)) // true
console.log(Object.is({}, {})) // false
console.log(Object.is({
foo: 'bar'
}, {
foo: 'bar'
})) // false
Object.assign
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)
const target = { a: 1 }
const source1 = { b: 2 }
const source2 = { c: 3 }
Object.assign(target, source1, source2)
console.log(target)
Object.assign
会直接返回改参数const obj = { a: 1 }
Object.assign(obj) === obj // true
typeof Object.assign(2) // "object"
但如果参数无法转换成对象,例如 undefined
和 null
则会报错
Object.assign(undefined) // 报错
Object.assign(null) // 报错
undefined
和 null
不在源对象位置,则不会报错let obj2 = { a: 1 }
console.log(Object.assign(obj2, undefined) === obj2) // true
console.log(Object.assign(obj2, null) === obj2) // true
const v1 = 'abc'
const v2 = true
const v3 = 10
const obj3 = Object.assign({}, v1, v2, v3)
console.log(obj3) // { "0": "a", "1": b, "2": "c", length: 3, [[PrimitiveValue]]: "abc" }
Object.assign
拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝枚举属性(enumerable: false
)Object.assign({b: 'c'}, Object.defineProperty({}, 'invisible', {
enumerable: false,
value: 'hello'
}))
上面代码中,Object.assign
要拷贝的对象只有一个不可枚举属性invisible
,这个属性并没有被拷贝进去。
Object.assign
拷贝。Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })// { a: 'b', Symbol(c): 'd' }
注意点:
Object.assign
方法实现的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性是对象,那么目标对象拷贝得到的是这个对象的引用
const obj4 = { a: { b: 1 } }
const obj5 = Object.assign({}, obj4)
obj4.a.b = 2
console.log(obj5.a.b)
修改 obj4
会影响到 obj5
,同时修改 obj5
也会影响到 obj4
Object.assign
遇到同名属性会替换
const obj6 = { a: 1, b: 2 }
const obj7 = { b: 3, c: 4 }
console.log(Object.assign(obj6, obj7)) // { a: 1, b: 3, c: 4 }
Object.assign
可以用来处理数组:
console.log(Object.assign([1, 2, 3], [4, 5])) // [4, 5, 3]
ES5 引入了 Object.keys()
:返回所有可遍历属性的键名(不含继承的)
var obj8 = { foo: 'bar', baz: 42 }
console.log(Object.keys(obj8))
ES2017 引入了跟 Object.keys
配套的 Object.values
和 Object.entries
,作为遍历对象的补充手段,供 for...of
循环使用
let { keys, values, entries } = Object
for(let key of keys(obj)) {
console.log(key) // "a", "b", "c"
}
for(let value of values(obj)) {
console.log(value) // 1, 2, 3
}
for(let [key, value] of entries(obj)) {
console.log([key, value]) // ["a", 1], ["b", 2], ["c", 3]
}
基本示例已经见过,这里说一下奇怪的用法:
const obj10 = { 10: 'a', 2: 'b', 7: 'c' }
console.log(Object.values(obj10)) // ["b", "c", "a"]
console.log(Object.values('foo')) // ['f', 'o', 'o']
console.log(Object.values(42)) // []
console.log(Object.values(true)) // []
基本用途是结合 for...of
循环遍历对象,还有其他用途:
Map
对象const obj11 = { foo: 'bar', baz: 42 }
const map = new Map(Object.entries(obj11))
console.log(map) // Map(2) { 'foo' => 'bar', 'baz' => 42 }