Appearance
对象扩展
简洁表示
属性简写:属性名和变量名一致时
js
// ES5
{x: x, y: y}
// ES6
{x, y}方法简写:去掉:,省略function
js
// ES5
method: function() {
return "Hello!";
}
// ES6
method() {
return "Hello!";
}简写对象方法不能作构造函数
tip:对象的简洁表示法,每组键值对会打印对象名,更清晰
js
let user = { name: 'test' }
let foo = { bar: 'baz' }
console.log(user, foo)
// {name: "test"} {bar: "baz"}
console.log({ user, foo })
// {user: {name: "test"}, foo: {bar: "baz"}}属性名表达式
属性名
ES5 - 只能用 标识符定义属性
ES6 - 允许使用表达式定义属性,方法名同样适用
js
// ES5
obj.foo = true
obj = {
foo: true,
abc: 123
}
// ES6
obj['a' + 'bc'] = 123
obj = {
[propKey]: true,
['a' + 'bc']: 123,
['h' + 'ello']() {
return 'hi'
}
}表达式与简洁表示不能同时使用
js
// 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };
// 正确
const foo = 'bar';
const baz = { [foo]: 'abc'};属性名表达式为对象,默认转为 [object Object]
js
const keyA = { a: 1 }
const keyB = { b: 2 }
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
}
myObject // Object {[object Object]: "valueB"}keyB 会覆盖 keyA
方法 name 属性
函数的name属性,返回函数名。
对象方法
js
const person = {
sayName() {
console.log('hello!')
}
}
person.sayName.name // "sayName"get,set 函数
js
const obj = {
get foo() {},
set foo(x) {}
}
obj.foo.name // TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo')
descriptor.get.name // "get foo"
descriptor.set.name // "set foobind 函数、构造函数
js
new Function().name // "anonymous"
var doSomething = function () {
// ...
}
doSomething.bind().name // "bound doSomething"symbol
js
const key1 = Symbol('description')
const key2 = Symbol()
let obj = {
[key1]() {},
[key2]() {}
}
obj[key1].name // "[description]"
obj[key2].name // ""属性枚举性和遍历
可枚举性
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor:可以获取该属性的描述对象。
js
let obj = { foo: 123 }
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true, // 可枚举性
// configurable: true
// }enumerable为false,可以禁用对该属性遍历。
toString和length属性的enumerable都是false,因此for...in不会遍历到这两个继承自原型的属性。
| for...in | 遍历自身属性和继承的可枚举属性 |
| Object.keys() | 遍历自身可枚举属性键名 |
| JSON.stringify() | 串行化自身可枚举属性 |
| Object.assign() | 拷贝自身可枚举属性 |
引入继承的属性会让问题复杂化,大多时候用
Object.keys()代替for...in
ES6 规定,所有 class 的原型的方法都是不可枚举的
js
Object.getOwnPropertyDescriptor(
class {
foo() {}
}.prototype,
'foo'
).enumerable
// false属性遍历(5 种)
for...in:循环遍历对象自身可枚举属性和继承可枚举属性
- 自身可枚举属性、继承可枚举属性
- 不含自身不可枚举、Symbol 属性
Object.keys:返回一个数组,包括对象自身可枚举属性的键名
- 自身可枚举属性
- 不含自身不可枚举属性、继承可枚举属性、Symbol 属性
Object.getOwnPropertyNames:返回一个数组,包含对象自身的所有属性的键名
- 自身所有属性
- 不含继承可枚举属性、不含 Symbol 属性
Object.getOwnPropertySymbols:返回一个数组,包含对象自身的所有 Symbol 属性的键名
- 自身所有 Symbol 属性
Reflect.ownKeys:返回一个数组,包含对象自身的所有键名
- 自身属性(不管枚举性)、Symbol 属性
- 不含继承属性
属性遍历的次序规则
- 首先遍历所有数值键,按照数值升序排列
- 其次遍历所有字符串键,按照加入时间升序排列
- 最后遍历所有Symbol键,按照加入时间升序排列
super 关键字
this:总是指向函数所在的当前对象
ES6 - super:指向当前对象的原型对象
js
const proto = {
foo: 'hello',
print() {
console.log(this.foo)
}
}
const obj = {
foo: 'world',
find() {
console.log(super.foo)
},
print() {
super.print()
}
}
Object.setPrototypeOf(obj, proto)
obj.find() // "hello"
obj.print() // "world"说明:super.print()指向proto中print(),但是绑定的this还是obj
JavaScript 引擎内部super.foo等同于
js
super.foo // 属性
super.foo() // 方法
// 等价写法
Object.getPrototypeOf(this).foo
Object.getPrototypeOf(this).foo.call(this)getPrototypeOf:返回指定对象原型
注意:
super 关键字表示原型对象,只能用在对象方法
以下三种写法super都未用在对象方法
对象方法:只有简写的形式能定义对象方法
js
// 报错 用在属性中
const obj = {
foo: super.foo
}
// 报错 用在函数中,赋值给foo属性
const obj = {
foo: () => super.foo
}
// 报错 ...
const obj = {
foo: function () {
return super.foo
}
}对象扩展运算符
解构赋值
对象的解构赋值:将目标对象自身所有可遍历、但尚未被读取的属性,分配到指定的对象上面
- 不能解构 null 和 undefined
- 扩展运算符必须在末尾
- 解构赋值为浅拷贝
- 不能复制继承自原型对象属性
- 扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式
js
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };// 局部解构
// 1
let { ...z } = null; // 运行时错误
let { ...z } = undefined; // 运行时错误
// 2
let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
// 4
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o; // 1 {}
let { y, z } = newObj; // undefined 3
// 5
let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contextsx为解构赋值,可以读取继承属性,y和z为扩展运算符解构赋值,不能取到继承属性
扩展运算符
对象的扩展运算符(...):取出参数对象的所有可遍历属性,拷贝到当前对象之中。
- 数组是特殊对象,扩展运算符可用于数组
- 空对象,无效果
- 不是对象自动转为对象
- 字符串,自动转为类似数组对象
- 等效于
Object.assign(),浅拷贝
js
// 等同于 {...Object(1)}
{...1} // {}
{...true} // {}
{...undefined} // {}
{...null} // {}
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
// 合并对象
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);Object.assign:只拷贝了对象实例属性
以下三种拷贝完整对象
js
// 写法一
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
}
// 写法二
const clone2 = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj)
// 写法三
const clone3 = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))
__proto__属性在非浏览器的环境不一定部署,推荐写法二、三
对象同名属性规则:后面的属性会覆盖前面的属性
js
let x = 1,
y = 2,
aWithOverrides = { ...a, x, y }
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 })应用:修改部分属性
js
let newVersion = {
...previousVersion,
name: 'New Name' // Override the name property
}自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
js
let aWithDefaults = { x: 1, y: 2, ...a }
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a)与数组相同,后面可跟表达式
js
const obj = {
...(x > 1 ? { a: 1 } : {}),
b: 2
}会执行取值函数get
js
let a = {
get x() {
throw new Error('not throw yet')
}
}
let aWithXGetter = { ...a } // 报错AggregateError 错误对象
ES2021 - 为了配合新增的Promise.any()方法,还引入一个新的错误对象AggregateError
作用:封装了多个错误对象,单一操作可以抛出多个错误
js
AggregateError(errors[, message])- errors(必): 数组,它的每个成员都是一个错误对象
- message(选):字符串,表示 AggregateError 抛出时的提示信息
js
const error = new AggregateError(
[
new Error('ERROR_11112'),
new TypeError('First name must be a string'),
new RangeError('Transaction value must be at least 1'),
new URIError('User profile link must be https')
],
'Transaction cannot be processed'
)对象的新增方法
Object.is()
ES5 比较值,==或===
==:会自动转换数据类型===:NaN不等于自身,+0等于-0
ES6 - Object.is():比较两个值是否严格相等。与===基本一致,唯一不同 +0 不等于 -0
js
;+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // truepolyfill
js
Object.defineProperty(Object, 'is', {
value: function (x, y) {
if (x === y) {
// 针对+0 不等于 -0的情况
// 1 / -0 -Infinity
// 1 / +0 +Infinity
return x !== 0 || 1 / x === 1 / y
}
// 针对NaN的情况
return x !== x && y !== y
},
configurable: true,
enumerable: false,
writable: true
})Object.assign()
Object.assign():将源自身对象所有可枚举属性(含Symbol),复制到目标对象
参数
第一参数:目标对象
- 只有一个参数,直接返回
- 不是对象转换为对象,
undefined和null无法转为对象,报错
后面参数:源对象
- 不是对象转换为对象(数组也是)
- 字符串会以数组形式拷贝,其他值(Boolean,Number,undefined、null)忽略
注意
- 浅拷贝
- 同名参数,源对象覆盖目标对象,后面源对象覆盖前面源对象
- 数组处理
js
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]- 取值函数处理:先取值再处理
js
const source = {
get foo() {
return 1
}
}
const target = {}
Object.assign(target, source)
// { foo: 1 }用途
- 为对象添加属性
- 为对象添加方法
- 克隆对象
js
// 只能克隆自身值,无法保证继承链
function clone(origin) {
return Object.assign({}, origin)
}js
function clone(origin) {
let originProto = Object.getPrototypeOf(origin)
return Object.assign(Object.create(originProto), origin)
}- 合并对象
- 指定默认值
getOwnPropertyDescriptors()
ES5 - Object.getOwnPropertyDescriptor():返回某个对象属性的描述对象(descriptor)。
ES2017 - Object.getOwnPropertyDescriptors():返回指定对象所有自身属性的描述对象
polyfill
js
function getOwnPropertyDescriptors(obj) {
const result = {}
for (let key of Reflect.ownKeys(obj)) {
result[key] = Object.getOwnPropertyDescriptor(obj, key)
}
return result
}问题:Object.assign()无法正确拷贝 get 和 set 属性。Object.assign 只会拷贝属性值,不会赋值、取值方法
js
const source = {
set foo(value) {
console.log(value)
}
}
const target1 = {}
Object.assign(target1, source)
Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
// writable: true,
// enumerable: true,
// configurable: true }解决:defineProperties + getOwnPropertyDescriptors
js
const source = {
set foo(value) {
console.log(value)
}
}
const target2 = {}
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source))
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
// set: [Function: set foo],
// enumerable: true,
// configurable: true }配合Object.create()方法,将对象属性克隆到新对象(浅拷贝)
js
const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))对象继承
ES5
js
const obj = {
__proto__: prot,
foo: 123
}ES6
js
const obj = Object.create(prot)
obj.foo = 123
// 或者
const obj = Object.assign(Object.create(prot), {
foo: 123
})另一种写法
js
const obj = Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo: 123
})
)实现混入模式
js
let mix = (object) => ({
with: (...mixins) => mixins.reduce((c, mixin) => Object.create(c, Object.getOwnPropertyDescriptors(mixin)), object)
})
// multiple mixins example
let a = { a: 'a' }
let b = { b: 'b' }
let c = { c: 'c' }
let d = mix(c).with(a, b)
d.c // "c"
d.b // "b"
d.a // "a"__proto__
__proto__:读取或设置当前对象原型对象,实际是调用Object.prototype.__proto__
js
// es5 的写法
const obj = {
method: function() { ... }
};
obj.__proto__ = someOtherObj;
// es6 的写法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };实现如下
js
Object.defineProperty(Object.prototype, '__proto__', {
get() {
let _thisObj = Object(this)
return Object.getPrototypeOf(_thisObj)
},
set(proto) {
if (this === undefined || this === null) {
throw new TypeError()
}
if (!isObject(this)) {
return undefined
}
if (!isObject(proto)) {
return undefined
}
let status = Reflect.setPrototypeOf(this, proto)
if (!status) {
throw new TypeError()
}
}
})
function isObject(value) {
return Object(value) === value
}setPrototypeOf()
与__proto__相同,设置对象原型对象(推荐)
js
Object.setPrototypeOf(object, prototype)
// 等效
function setPrototypeOf(obj, proto) {
obj.__proto__ = proto
return obj
}说明
- 第一参数不是对象,会自动转对象,此函数返回值为第一参数,所以操作无效。
js
Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('foo', {}) === 'foo' // true
Object.setPrototypeOf(true, {}) === true // true- 第一参数为
undefined,null会报错
js
Object.setPrototypeOf(undefined, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefinedgetPrototypeOf()
读取一个对象的原型对象
不是对象,转对象,undefined、null报错
js
Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // truekeys()、values()、entries()
ES5 - Object.keys:返回一个数组,成员是键名。(可遍历属性、非 Symbol、不含继承)
ES2017 - Object.values:返回一个数组,成员是键值。(可遍历属性、非 Symbol、不含继承)
说明
- 会过滤属性名为 Symbol 值的属性
js
Object.values({ [Symbol()]: 123, foo: 'abc' })
// ['abc']- 值为字符串,返回字符串组成的数组
js
Object.values('foo')
// ['f', 'o', 'o']- 数值、布尔值,返回空数组(不是对象转对象,而这两者包装对象都不会添加非继承属性)
Object.entries():返回一个数组,成员是键名 + 键值(可遍历属性、非 Symbol、不含继承)
js
const obj = { foo: 'bar', baz: 42 }
const map = new Map(Object.entries(obj))
map // Map { foo: "bar", baz: 42 }polyfill
js
// Generator函数的版本
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]]
}
}
// 非Generator函数的版本
function entries(obj) {
let arr = []
for (let key of Object.keys(obj)) {
arr.push([key, obj[key]])
}
return arr
}formEntries()
Object.fromEntries():Object.entries()的逆操作,用于将一个键值对数组转为对象。
应用:将 Map 结构转为对象
js
const entries = new Map([
['foo', 'bar'],
['baz', 42]
])
Object.fromEntries(entries)
// { foo: "bar", baz: 42 }
const map = new Map().set('foo', true).set('bar', false)
Object.fromEntries(map)
// { foo: true, bar: false }配个URLSearchParams对象,查询字符串转对象
js
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }js
var paramsString = 'q=URLUtils.searchParams&topic=api'
var searchParams = new URLSearchParams(paramsString)
for (let p of searchParams) {
console.log(p)
}
searchParams.has('topic') === true // true
searchParams.get('topic') === 'api' // true
searchParams.getAll('topic') // ["api"]
searchParams.get('foo') === null // true
searchParams.append('topic', 'webdev')
searchParams.toString() // "q=URLUtils.searchParams&topic=api&topic=webdev"
searchParams.set('topic', 'More webdev')
searchParams.toString() // "q=URLUtils.searchParams&topic=More+webdev"
searchParams.delete('topic')
searchParams.toString() // "q=URLUtils.searchParams