目录

---------

很早之前已经做过有关 ObjectArray 的笔记了,这次主要是复习重点:

字符串

解构赋值

字符串可以认为是一个伪数组,所以可以解构赋值

Javascript
const [a, b, c, d, e] = 'hello'
a // "h"
b // "b"
c // "c"
d // "d"
e // "e"

当然,类似数组的对象都有一个 length 属性,所以还可以解构字符串的 length

Javascript
let { length: len } = 'hello'
len // 5

实例新增方法

includes()、startsWidth()、endsWith()

JavaScript 传统上只有 indexOf 方法,可以用来确认一个字符串是否包含在另一个字符串中。ES6 还提供了三种新方法:

Javascript
let s = 'Hello world!'
s.startsWith('Hello') // true
s.endWith('!') // true
s.includes('llo') .. true

三个方法都支持第二个参数,表示开始搜索的位置:

Javascript
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

其中 endsWith 针对的是前 n 个字符,其他两个方法针对的是到第 n 个位置的字符串

repeat()

repeat 返回一个新的字符串,表示将原来字符串重复 n

注:第一种情况之所以是 n <= -1 是因为,当 n 介于 -1 到 0 时,会被等同于 0

该方法不会修改原来的字符串

Javascript
'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()

字符串补全长度功能,其中 padStart() 用于头部补全,padEnd() 用于尾部补全

instance.padStart(n, string)instance.padEnd(n, string)共接收两个参数,第一个参数补全后的长度,第二个参数是用来不全的字符串

Javascript
'xxx'.padStart(2, 'ab') // "xxx"
'xxx'.padStart(5, '0123456789') // "xxx01"
'xxx'.padStart(5) // "  xxx"

trimStart()、trimEnd()

trim 类似,只不过 ES2019 对其进行了细分,trimStart() 用来去除头部空格,而 trimEnd() 用来去除尾部空格

三个方法均不会修改原来字符串

Javascript
let s2 = '  abc  '
console.log(s2.trim())
console.log(s2.trimStart())
console.log(s2.trimEnd())

函数

函数默认值

我们可以在声明函数时,赋予默认值:

Javascript
function log(x, y = 'hello') {
  	console.log(x, y)
}
log('hi')
log('hi', 'world')
log('hi', '')

rest 参数

ES6 引入了 rest 参数,形式如 ...rest,用于获取函数多余参数,这样就不需要使用 arguments 对象了。

Javascript
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 参数只能放在最后,例如下面的例子是错误的

Javascript
function test(a, ...rest, b) {}

name 属性

函数的 name 属性,返回该函数的函数名:

Javascript
function foo {}
foo.name // "foo"

注:ES6 对这个属性做出了一些修改,如果将一个匿名函数赋值给一个变量,ES5 的 name 属性,会返回空字符串,ES6 则会返回实际的函数名

Javascript
var f = function() {}

// ES5
f.name // ""
// ES6
f.name // "f"

Function 构造函数返回的函数实例,name 属性的值为 anonymous

Javascript
(new Function).name // "anonymous"

bind 返回的函数,name 属性值会加上 bound 前缀

Javascript
function foo() {}
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "

对象扩展

对象的扩展运算符

和数组一样,对象也有扩展运算符(...)

Javascript
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 解构赋值,会报错

Javascript
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误

结构赋值必须在最后,且只能存在一个

Javascript
let { ...x, y, z } = obj // 句法错误
let { x, ...y, ...z } = obj // 句法错误

链判断运算符

如果我们要访问对象内部的某个属性,往往需要判断一下该对象是否存在。比如,读取 message.body.user.firstName 安全的写法应该是:

Javascript
const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default'

或者使用三元运算符 ?:判断

Javascript
const value = value ? value : undefined

这样的层层判断很麻烦,为了简化这种写法,ES2020 引入了“链判断运算符” ?.

Javascript
const firstName = message?.body?.user?.firstName || 'default'

上面代码引入了 ?. 运算符,直接在链式调用的时候判断,左侧的对象如果为 null 或者 undefined,则不再往下运算,而是返回 undefined

同时链判断运算符有三种写法:

Javascript
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?.() 会报错

同时还有几个注意点:

  1. 短路机制
Javascript
a?.[++x]
// equal to
a == null ? undefined : a[++x]

如果 anull 或者 undefined,那么 x 不会进行递增运算(其他操作同理)

  1. 括号的影响
Javascript
(a?.b).c
// equal to
(a == null ? undefined : a.b).c

不管 a 对象是否存在,圆括号后的 .c 总是会执行。

?. 运算符不要用括号

Null 判断运算符

如果某个值是 null 或者 undefined 时,我们希望把它们指定为默认值,常见做法是使用或运算符 || 指定默认值

Javascript
const test = obj.test || 'none data'

但其实这样的写法是有问题的,如果 obj.testfalse、0、空字符串,默认值也会生效。为了避免这种情况,ES2020 引入了 ?? 运算符,只有 obj.testnull 或者 undefined 时,默认值才会生效

Javascript
const test = obj.test ?? 'none data'

?? 存在优先级问题,如果与 && 或者 || 同时使用,不加括号的情况下,会报错

Javascript
// 报错
t1 ?? t2 && t3
// ...

需要加上括号,才不会报错:

Javascript
(t1 ?? t2) && t3

对象新增方法

Object.is()

ES5 比较两个值是否相等,只有两个运算符:相等运算符 == 和严格相等于算符 === 。前者会自动转换数据类型,后者 NaN 不等于自身,以及 +0 全等于 -0

用于比较两个值是否严格相等(比 `)

Javascript
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()

Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)

Javascript
const target = { a: 1 }
const source1 = { b: 2 }
const source2 = { c: 3 }
Object.assign(target, source1, source2)
console.log(target)
Javascript
const obj = { a: 1 }
Object.assign(obj) === obj // true
Javascript
typeof Object.assign(2) // "object"

但如果参数无法转换成对象,例如 undefinednull 则会报错

Javascript
Object.assign(undefined) // 报错
Object.assign(null)			 // 报错
Javascript
let obj2 = { a: 1 }
console.log(Object.assign(obj2, undefined) === obj2) // true
console.log(Object.assign(obj2, null) === obj2) // true
Javascript
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" }
Javascript
Object.assign({b: 'c'}, Object.defineProperty({}, 'invisible', {
  enumerable: false,
  value: 'hello'
}))

上面代码中,Object.assign要拷贝的对象只有一个不可枚举属性invisible,这个属性并没有被拷贝进去。

Javascript
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })// { a: 'b', Symbol(c): 'd' }

注意点:

  1. 浅拷贝

Object.assign 方法实现的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性是对象,那么目标对象拷贝得到的是这个对象的引用

Javascript
const obj4 = { a: { b: 1 } }
const obj5 = Object.assign({}, obj4)
obj4.a.b = 2
console.log(obj5.a.b)

修改 obj4 会影响到 obj5,同时修改 obj5 也会影响到 obj4

  1. 同名属性替换

Object.assign 遇到同名属性会替换

Javascript
const obj6 = { a: 1, b: 2 }
const obj7 = { b: 3, c: 4 }
console.log(Object.assign(obj6, obj7)) // { a: 1, b: 3, c: 4 }
  1. 数组的处理

Object.assign 可以用来处理数组:

Javascript
console.log(Object.assign([1, 2, 3], [4, 5])) // [4, 5, 3]

Object.keys()、Object.values()、object.entries()

ES5 引入了 Object.keys():返回所有可遍历属性的键名(不含继承的)

Javascript
var obj8 = { foo: 'bar', baz: 42 }
console.log(Object.keys(obj8))

ES2017 引入了跟 Object.keys 配套的 Object.valuesObject.entries,作为遍历对象的补充手段,供 for...of 循环使用

Javascript
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]
}

Object.values()

基本示例已经见过,这里说一下奇怪的用法:

  1. 数组作为参数
Javascript
const obj10 = { 10: 'a', 2: 'b', 7: 'c' }
console.log(Object.values(obj10)) // ["b", "c", "a"]
  1. 字符串作为参数
Javascript
console.log(Object.values('foo')) // ['f', 'o', 'o']
  1. 不是对象的作为参数
Javascript
console.log(Object.values(42)) // []
console.log(Object.values(true)) // []

Object.entries()

基本用途是结合 for...of 循环遍历对象,还有其他用途:

  1. 将对象转换为 Map 对象
Javascript
const obj11 = { foo: 'bar', baz: 42 }
const map = new Map(Object.entries(obj11))
console.log(map) // Map(2) { 'foo' => 'bar', 'baz' => 42 }