官方文档:developer.mozilla.org/zh-CN/docs/…
一、Object.assign()
1、Object.assign()是ES6新出的方法,它会将一个或多个源对象中的可枚举 自有属性,复制到目标对象,返回修改后的目标对象
const target = { a: 1 }
const source1 = { b: 2 }
const source2 = { c: 3 }
const res = Object.assign(target, source1, source2)
console.log(res) // {a: 1, b: 2, c: 3}
console.log(target) // {a: 1, b: 2, c: 3}
console.log(Object.is(res, target)) // true
参数:
- target:目标对象
- source:源对象
2、如果目标对象和源对象中有同名属性,后面的覆盖前面的
const target = { a: 1 }
const source1 = { b: 2, a: 10 }
const source2 = { c: 3, a: 20 }
const res = Object.assign(target, source1, source2) // 相当于const res1 = { ...target, ...source1, ...source2 }
console.log(res) // {a: 20, b: 2, c: 3}
console.log(target) // {a: 20, b: 2, c: 3}
3、原型上的属性不会被拷贝
const person = { name: 'xx' }
Object.prototype.age = 10
const res = Object.assign({}, person)
console.log(res) // {name: 'xx'}
可以通过hasOwnProperty判断该属性是否是自有属性:
console.log(person.hasOwnProperty('name')) // true
console.log(person.hasOwnProperty('age')) // false
注:for in会遍历到原型上的属性,for of不会遍历到原型上的属性
4、不可枚举属性不会被拷贝
const person = { name: 'xx' }
Object.defineProperty(person, 'age', {
value: 10,
// enumerable: true // enumerable默认为false
})
const res = Object.assign({}, person)
console.log(res) // {name: 'xx'}
可以通过propertyIsEnumerable判断该属性是否是可枚举属性:
console.log(person.propertyIsEnumerable('name')) // true
console.log(person.propertyIsEnumerable('age')) // false
注:for in无法遍历到不可枚举属性,for of可以遍历到不可枚举属性
5、属性名为Symbol的属性,会被拷贝
const person = { [Symbol('xx')]: 'xx' }
const res = Object.assign({}, person)
console.log(res) // {Symbol(xx): 'xx'}
6、Object.assgin()是浅拷贝,假如源对象是一个对象的引用,它只会拷贝其引用值
const person = { name: 'xx', son: [{ name: 'a', age: 10 }] }
const res = Object.assign({}, person)
person.son[0].age = 100
console.log(person) // { name: 'xx', son: [{ name: 'a', age: 100 }] }
console.log(res) // { name: 'xx', son: [{ name: 'a', age: 100 }] }
7、对于数组的处理
const target = [1, 2, 3]
const source = [4, 5]
Object.assign(target, source)
console.log(target) // [4, 5, 3]
Object.assgin()会将[1, 2, 3]视为{0: 1, 1: 2, 2: 3},因此source中的0号属性4就会替代target中的0号属性1
8、Object.assgin()的常见场景
1、为ES5原型对象添加方法(ES6的原型方法写在类中,为ES5的构造函数添加原型属性,代码看起来会很清晰):
function Person() {}
Object.assign(Person.prototype, {
sex: '男',
eat() {
console.log('吃饭')
}
})
2、构造函数中,给实例绑定属性时,使用Object.assign()会减少一些代码行数
function Person(name, age) {
// this.name = name
// this.age = age
Object.assign(this, { name, age })
}
二、Object.create()
1、Object.create()是ES6新出的方法,把现有对象作为原型,创建一个新对象
const animal = {
name: 'xx'
}
const cat = Object.create(animal)
console.log(cat) // {}
console.log(cat.__proto__) // {name: 'xx'}
其中,const cat = Object.create(animal)相当于
const cat = {}
cat.__proto__ = animal
2、当传入null时,新对象是没有原型属性的
const obj = Object.create(null)
相当于:
const obj = {}
obj.__proto__ = null
3、Object.create()的第二个参数
第二个参数默认是undefined,可以传入一个对象,对象中的属性作为返回的新对象的自有属性。这些属性的写法对应于Object.defineProperties()的第二个参数
const person = Object.create(
{},
{ age: { value: 20 }, height: { value: 175 } }
)
console.log(person) // {age: 20, height: 175}
由于configurable、enumerable、writable默认值都是false,所以age和height都是不可删除、不可枚举、不可修改的
三、Object.defineProperty() 和 Object.defineProperties()
1、Object.defineProperty()是ES5的方法,为一个对象添加一个新属性或某个属性
- 参数一:需要操作的对象
- 参数二:需要操作的属性
- 参数三:属性描述对象
var person = { name: '小明' }
Object.defineProperty(person, 'age', { value: 10 })
console.log(person) // {name: '小明', age: 10}
2、属性描述对象中的6个属性
-
value、get、set的默认值是undefined
-
configurable、enumerable、writable的默认值是false
Object.defineProperty(obj, 'name', { value: '小明' })
相当于:
Object.defineProperty(obj, 'name', {
value: '小明',
configurable: false,
enumerable: false,
writable: false
})
obj.age = 18相当于:
Object.defineProperty(obj, 'age', {
value: 18,
configurable: true,
enumerable: true,
writable: true
})
1、value
var person = { name: '小明' }
Object.defineProperty(person, 'age', {})
Object.defineProperty(person, 'eat', {
value: function () {
console.log('吃东西')
}
})
console.log(person) // {name: '小明', age: undefined, eat: f}
person.eat() // 吃东西
2、configurable
当设置configurable为true时,该属性可以被delete删除
var person = { name: '小明' }
Object.defineProperty(person, 'age', {
value: 10,
configurable: true
})
console.log(person) // {name: '小明', age: 10}
delete person.age
console.log(person) // {name: '小明'}
3、enumerable
当设置configurable为true时,该属性便是可枚举的,可以通过Object.keys()获取可枚举属性的数组,也可以通过for in遍历来查看哪些属性是可枚举的
var person = { name: '小明' }
Object.defineProperty(person, 'age', {
value: 10,
enumerable: true
})
console.log(Object.keys(person)) // ['name', 'age']
不可枚举属性,依然可以通过person['age']或person.age访问
4、writable
当设置configurable为true时,该属性才可以重新赋值,否则赋值操作会被忽略
var person = { name: '小明' }
Object.defineProperty(person, 'age', {
value: 10,
writable: true
})
console.log(person) // {name: '小明', age: 10}
person.age = 20
console.log(person) // {name: '小明', age: 20}
综上,定义一个空对象var person = {},如果设置person.age = 10,其本质是:
Object.defineProperty(person, 'age', {
value: 1,
configurable: true,
enumerable: true,
writable: true
})
5、get和set
var person = {}
var value = 'xx'
Object.defineProperty(person, 'name', {
get() {
console.log('我被获取了')
return value
},
set(val) {
console.log('我被设置了')
window.value = val
}
})
person.name = '张三'
console.log(person.name)
- get和set不是必须成对出现,可以只写一个
- get和set有一个出现时,就不能再设置value和writable了
- vue的数据绑定使用的就是Object.defineProperty()
- 当需要一个变量表示多个含义时,就需要用到Object.defineProperty(),如
a == 1 && a == 2 && a == 3返回true
3、Object.defineProperties
Object.defineProperties(obj, {
name: { value: '小明' },
age: { value: 18 }
})
四、Object.is()
1、Object.is()是ES6的方法,用于判断两个值是否为同一个值
Object.is(undefined, undefined)
Object.is(1, 2)
2、Object.is()和===的区别
当比较+0和-0时,当比较NaN和NaN时,Object.is()和===的结果不同,除此以外和===保持一致
3、Object.is()的实现
Object.defineProperty(Object, 'is', {
value: function (x, y) {
if (x === y) {
return x !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
}
})
五、Object.getOwnPropertyDescriptor() 和 Object.getOwnPropertyDescriptors()
1、概念:该方法传入2个参数,返回一个对象上自有属性的属性描述对象,如果属性名是原型上的或者不存在时返回undefined
const obj = {
name: '小明'
}
obj.__proto__.a = 'a'
const des = Object.getOwnPropertyDescriptor(obj, 'name')
const des1 = Object.getOwnPropertyDescriptor(obj, 'a') // undefined,原型上的属性无法获取
console.log(des) // {value: '小明', writable: true, enumerable: true, configurable: true}
2、注意事项
在ES5中,第一个参数不传对象的话会报错;在ES6中,第一个参数会强制转换为对象:
console.log(Object.getOwnPropertyDescriptor('abc', 0)) // {value: 'a', writable: false, enumerable: true, configurable: false}
3、Object.getOwnPropertyDescriptors()
和Object.getOwnPropertyDescriptor()相比,不用传入属性名,获取的是所有的自由属性的属性描述对象集合
const obj = {
name: '小明',
age: 18
}
obj.__proto__.a = 'a'
console.log(Object.getOwnPropertyDescriptor(obj, 'name')) // {value: '小明', writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptors(obj)) // {name: {value: '小明', writable: true, enumerable: true, configurable: true}, age: {value: 18, writable: true, enumerable: true, configurable: true}}
六、Object.getPrototypeOf()/Object.setPrototypeOf()/Object.prototype.isPrototypeOf
1、Object.getPrototypeOf()
概念:该方法返回一个对象的原型对象(内部[[prototype]]属性的值),如果没有继承属性,返回null
const obj = {
name: '小明',
age: 18
}
console.log(obj)
console.log(Object.getPrototypeOf(obj))
console.log(Object.getPrototypeOf(obj) === obj.__proto__) // true
使用obj作为原型创建person对象,person对象的原型对象就是obj:
const person = Object.create(obj)
console.log(Object.getPrototypeOf(person) === obj) // true
没有继承属性的对象,获取该对象的原型为null:
const empty = Object.create(null)
console.log(Object.getPrototypeOf(empty)) // null
2、Object.setPrototypeOf()
1、概念:该方法将一个对象(obj)的原型设置为另一个对象或null,再返回obj
const obj = {
name: '小明',
age: 18
}
console.log(Animal.prototype.isPrototypeOf(cat)) // true
console.log(Object.getPrototypeOf(obj)) // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Object.setPrototypeOf(obj, { a: 'a' })) // {name: '小明', age: 18}
console.log(Animal.prototype.isPrototypeOf(cat)) // false
console.log(Object.getPrototypeOf(obj)) // {a: 'a'}
console.log(obj) // {name: '小明', age: 18}
console.log(obj.a) // a
其中,Object.setPrototypeOf(obj, { a: 'a' })相当于obj.__proto__ = { a: 'a' }
将obj的原型设置为null时,obj便不具有任何的原型:
Object.setPrototypeOf(obj, null)
2、注意事项
- getPrototypeOf可以实现伪类继承,但是从性能和可读性的角度还是要使用extends
- 出于安全考虑,某些内置对象的原型在设计之初就设计成不可变的,如:proxy、window、location、Object.prototype,使用getPrototypeOf更改其原型会报错
3、Object.prototype.isPrototypeOf()
使用原型对象调用该实例方法,判断原型是否在该对象的原型链上
function Person() {}
function Programmer() {}
Programmer.prototype = Object.create(Person.prototype)
const p1 = new Programmer()
console.log(Programmer.prototype.isPrototypeOf(p1)) // true
console.log(Person.prototype.isPrototypeOf(p1)) // true
console.log(Object.prototype.isPrototypeOf(p1)) // true
console.log(p1 instanceof Programmer) // true
console.log(p1 instanceof Person) // true
console.log(p1 instanceof Object) // true
七、Object.prototype.hasOwnProperty() 和 Object.hasOwn()
1、概念:判断一个属性是不是该对象的自有属性,尽管某个属性是symbol类型或者该属性是不可枚举的,只要它是该对象的自有属性那么返回true,如果是原型上的属性或者不存在这个属性返回false
const s = Symbol()
const obj = {
name: '小明',
0: 0,
'-1': -1,
[s]: Symbol()
}
obj.__proto__.a = 'a'
Object.defineProperty(obj, 'age', { value: 18 })
console.log(obj) // {0: 0, name: '小明', -1: -1, age: 18, Symbol(): Symbol()}
console.log(obj.hasOwnProperty('name')) // true
console.log(obj.hasOwnProperty('0')) // true
console.log(obj.hasOwnProperty('-1')) // true
console.log(obj.hasOwnProperty(s)) // true symbol类型的自有属性返回true
console.log(obj.hasOwnProperty('age')) // true 不可枚举的自由属性返回true
console.log(obj.hasOwnProperty('a')) // false
2、hasOwnProperty常常用来和for...in搭配使用
由于in操作符会读取到原型上的属性,所以使用hasOwnProperty可以过滤掉原型属性:
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log('自有属性', key, obj[key])
} else {
console.log('原型上的属性', key, obj[key])
}
}
3、使用hasOwnProperty作为属性名
在obj对象追加一个属性:
hasOwnProperty: () => 100
那么通过hasOwnProperty判断是不是自有属性都会返回100:
console.log(obj.hasOwnProperty('name')) // 100
console.log(obj.hasOwnProperty('0')) // 100
console.log(obj.hasOwnProperty('-1')) // 100
console.log(obj.hasOwnProperty(s)) // 100
console.log(obj.hasOwnProperty('age')) // 100
console.log(obj.hasOwnProperty('a')) // 100
如果要避免这种情况,你得使用原型链上真正的hasOwnProperty方法:
console.log({}.hasOwnProperty.call(obj, 'name')) // true
console.log({}.hasOwnProperty.call(obj, '0')) // true
console.log({}.hasOwnProperty.call(obj, '-1')) // true
console.log({}.hasOwnProperty.call(obj, s)) // true
console.log({}.hasOwnProperty.call(obj, 'age')) // true
console.log({}.hasOwnProperty.call(obj, 'a')) // false
或者使用Object原型上的hasOwnProperty方法:
console.log(Object.prototype.hasOwnProperty.call(obj, s)) // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'hasOwnProperty')) // true
所以在for...in循环中的if判断那样写是不严谨的,你应该这样写:
if (Object.prototype.hasOwnProperty.call(obj, key))
4、Object.hasOwn()
Object.hasOwn()就是来解决Object.prototype.hasOwnProperty()不够严谨的地方的,MDN建议使用此方法替代Object.prototype.hasOwnProperty()。
Object.prototype.hasOwnProperty()问题展示:
const obj = Object.create(null)
obj.name = '小明'
console.log(obj.hasOwnProperty('name')) // Uncaught TypeError: obj.hasOwnProperty is not a function
报错了,这是因为hasOwnProperty是原型上的方法,而Object.create(null)是没有原型的。还有上面的使用hasOwnProperty作为属性名,这两种情况下,使用hasOwnProperty不够严谨,当然了你可以通过调用外部对象上(或Object原型上)的hasOwnProperty方法,但是这没有Object.hasOwn()直观
八、Object.keys()/Object.values()/Object.entries()/Object.fromEntries()
1、for...in遍历时的顺序
for...in遍历对象时,会首先找到对象中的非负整数属性,将这一部分的属性按照升序遍历,再找到其他的属性,按照创建时的顺序遍历出来
2、Object.keys()
Object.keys()返回该对象自身属性并且是可枚举的属性组成的数组,数组中属性名的排序和for...in一致
const s = Symbol()
const obj = {
name: '小明',
0: 0,
'-1': -1,
[s]: Symbol(),
100: 100,
50: 50
}
obj.__proto__.a = 'a'
Object.defineProperty(obj, 'age', { value: 18 })
for (const key in obj) {
console.log(key)
}
console.log(Object.keys(obj)) // ['0', '50', '100', 'name', '-1']
- for...in会遍历到原型上的属性,但Object.keys()并不会获取到原型上的属性
- for...in和Object.keys()都不会获取到symbol类型的属性名
- for...in和Object.keys()都不会获取到不可枚举属性
3、Object.values()
Object.values()和Object.keys()遍历范围一致:
console.log(Object.values(obj)) // [0, 50, 100, '小明', -1]
4、Object.entries()
Object.entries()和Object.keys()遍历范围一致,但是Object.entries()返回的是一个二维数组:
const entries = Object.entries(obj)
console.log(entries) // [['0', 0], ['50', 50], ['100', 100], ['name', '小明'], ['-1', -1]]
使用场景:
- 如果for...of需要使用到下标可以考虑使用Object.entries()
- 如果需要将obj转为快速map,可以通过Object.entries(),但是你需要时刻注意,Object.entries()是拿不到原型上的属性和symbol值作为属性的属性的
const map = new Map(Object.entries(obj))
console.log(map) // Map(5) {'0' => 0, '50' => 50, '100' => 100, 'name' => '小明', '-1' => -1}
5、Object.fromEntries()(ES10)
将键值对列表(通常是二维数组)转换为一个对象,是Object.entries()的逆操作
- 将数组转换为对象:
const o = Object.fromEntries(entries)
console.log(o) // {0: 0, 50: 50, 100: 100, name: '小明', -1: -1}
- 将map转换为对象:
const map = new Map(Object.entries(obj))
const o = Object.fromEntries(map)
console.log(o) // {0: 0, 50: 50, 100: 100, name: '小明', -1: -1}
- 和
new URLSearchParams搭配将查询字符串转为对象:
const queryStr = 'userId=1001&username=张三'
const searchParams = new URLSearchParams(queryStr)
const params = Object.fromEntries(searchParams)
console.log(params) // {userId: '1001', username: '张三'}
6、总结
- Object.keys()/Object.values()/Object.entries()分别获取对象中的
自有键、值、键和值的集合,原型上的属性无法获取到,并且它们自身的symbol键也是无法获取到的 - 这三个方法的顺序和for...in保持一致
- 这三个方法都无法获取到不可枚举属性
九、Object.getOwnPropertyNames()
该方法和Object.keys()相比唯一的区别是,它可以获取到不可枚举属性:
const s = Symbol()
const obj = {
name: '小明',
0: 0,
'-1': -1,
[s]: Symbol(),
100: 100,
50: 50
}
obj.__proto__.a = 'a'
Object.defineProperty(obj, 'age', { value: 18 })
console.log(Object.keys(obj)) // ['0', '50', '100', 'name', '-1']
console.log(Object.getOwnPropertyNames(obj)) // ['0', '50', '100', 'name', '-1', 'age']
- 同样地,Object.getOwnPropertyNames也无法获取到原型上的属性,也无法获取到symbol键,顺序和for...in一致
- 但是Object.getOwnPropertyNames能够获取到不可枚举属性
使用场景:只获取不可枚举的属性
const a = Object.keys(obj)
const b = Object.getOwnPropertyNames(obj)
const res = b.filter((n) => !a.includes(n))
console.log(res) // ['age']
十、Object.getOwnPropertySymbols()
该方法返回对象自有属性中symbol键组成的数组
const s1 = Symbol(1)
const s2 = Symbol(2)
const s3 = Symbol.for(3)
const s4 = Symbol.for(4)
const obj = {
name: '小明',
0: 0,
[s1]: Symbol(),
[s2]: Symbol(),
[s3]: Symbol()
}
obj.__proto__[s4] = 400
const arr = Object.getOwnPropertySymbols(obj)
console.log(arr) // [Symbol(1), Symbol(2), Symbol(3)]
前面所提到的for...in、Object.keys()/Object.values()/Object.entries()、Object.getOwnPropertyNames()都是无法获取到symbol键的,只有Object.getOwnPropertySymbols()能获取到
十一、Object.freeze()/Object.isFrozen()
1、Object.freeze()
使用Object.freeze可以冻结一个对象,返回这个对象。被冻结的对象不可以再改变了,具体包括:
- 不可以往这个对象中再添加属性
- 不可以修改已有属性
- 不可以删除已有属性
- 该对象已有属性的可枚举性、可配置性、可写性不可以被修改
- 该对象的原型不可以被修改,但是可以修改原型上的某个属性
const obj = {
name: '张三',
age: 18
}
const newObj = Object.freeze(obj)
console.log(newObj) // {name: '张三', age: 18}
console.log(obj === newObj) // true
在严格模式下,当冻结了一个对象后,这些操作会报错:
obj.gender = '男' // Uncaught TypeError: Cannot add property gender, object is not extensible
obj.age = 20 // Uncaught TypeError: Cannot assign to read only property 'age' of object '#<Object>'
delete obj.age // Uncaught TypeError: Cannot delete property 'age' of #<Object>
非严格模式下也会报错:
Object.defineProperty(obj, 'age', { value: 20, writable: false }) // Uncaught TypeError: Cannot redefine property: age at Function.defineProperty (<anonymous>)
obj.__proto__ = null // Uncaught TypeError: #<Object> is not extensible at set __proto__ [as __proto__] (<anonymous>)
obj.__proto__.valueOf = null // 这是允许的
当一个对象被冻结后,我们只能读取对象的属性或者遍历这个对象:
obj.name // 张三
Object.keys(obj) // ['name', 'age']
2、Object.isFrozen()
判断一个对象是否被冻结
console.log(Object.isFrozen(obj)) // false
Object.freeze(obj)
console.log(Object.isFrozen(obj)) // true
十二、Object.seal()/Object.isSealed()
1、Object.seal()
使用Object.seal可以密封一个对象,返回这个对象。被密封的对象和被冻结的对象只有一个区别:被密封的对象可以改变属性值
2、Object.isSealed()
判断一个对象是否被密封
console.log(Object.isSealed(obj)) // false
Object.seal(obj)
console.log(Object.isSealed(obj)) // true
十三、Object.preventExtensions()/Object.isExtensible()
1、Object.preventExtensions()
使用Object.preventExtensions可以使一个对象变得不可扩展,返回这个对象
const obj = {
name: '张三',
age: 18
}
const newObj = Object.preventExtensions(obj)
console.log(obj) // {name: '张三', age: 18}
console.log(obj === newObj) // true
设置不可扩展后,严格模式下此句代码会报错:
obj.gender = '男' // Uncaught TypeError: Cannot add property gender, object is not extensible
但是要删除某个属性或者操作原型上的属性,或者改变已有属性的可枚举性、可配置性、可写性,都是允许的:
delete obj.name
obj.__proto__.a = 'a'
Object.defineProperty(obj, 'age', { value: 19 })
2、Object.isExtensible()
判断一个对象是否可扩展
console.log(Object.isExtensible(obj)) // true
Object.preventExtensions(obj)
console.log(Object.isExtensible(obj)) // false
如果一个对象被冻结了,那么这个对象一定是不可扩展的;但是一个对象不可扩展,它不一定是被冻结了
对比:
| Object.freeze | Object.seal | Object.preventExtensions | |
|---|---|---|---|
| 是否可以添加新属性 | 否 | 否 | 否 |
| 是否可以修改已有属性 | 否 | 是 | 否 |
| 是否可以删除已有属性 | 否 | 否 | 是 |
| 是否可以修改已有属性的可枚举性、可配置性、可写性 | 否 | 否 | 是 |
| 是否可以修改原型 | 否 | 否 | 是 |