Object的静态方法

305 阅读13分钟

官方文档: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)

image.png

相当于:

      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的方法,为一个对象添加一个新属性或某个属性

  1. 参数一:需要操作的对象
  2. 参数二:需要操作的属性
  3. 参数三:属性描述对象
      var person = { name: '小明' }
      Object.defineProperty(person, 'age', { value: 10 })

      console.log(person) // {name: '小明', age: 10}

2、属性描述对象中的6个属性

  1. value、get、set的默认值是undefined

  2. 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)
  1. get和set不是必须成对出现,可以只写一个
  2. get和set有一个出现时,就不能再设置value和writable了
  3. vue的数据绑定使用的就是Object.defineProperty()
  4. 当需要一个变量表示多个含义时,就需要用到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()和===的结果不同,除此以外和===保持一致

image.png

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

image.png 使用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)

image.png

2、注意事项

  1. getPrototypeOf可以实现伪类继承,但是从性能和可读性的角度还是要使用extends
  2. 出于安全考虑,某些内置对象的原型在设计之初就设计成不可变的,如: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])
        }
      }

image.png

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']
  1. for...in会遍历到原型上的属性,但Object.keys()并不会获取到原型上的属性
  2. for...in和Object.keys()都不会获取到symbol类型的属性名
  3. 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]]

使用场景:

  1. 如果for...of需要使用到下标可以考虑使用Object.entries()
  2. 如果需要将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()的逆操作

  1. 将数组转换为对象:
      const o = Object.fromEntries(entries)
      console.log(o) // {0: 0, 50: 50, 100: 100, name: '小明', -1: -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}
  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、总结

  1. Object.keys()/Object.values()/Object.entries()分别获取对象中的自有键、值、键和值的集合,原型上的属性无法获取到,并且它们自身的symbol键也是无法获取到的
  2. 这三个方法的顺序和for...in保持一致
  3. 这三个方法都无法获取到不可枚举属性

九、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']
  1. 同样地,Object.getOwnPropertyNames也无法获取到原型上的属性,也无法获取到symbol键,顺序和for...in一致
  2. 但是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可以冻结一个对象,返回这个对象。被冻结的对象不可以再改变了,具体包括:

  1. 不可以往这个对象中再添加属性
  2. 不可以修改已有属性
  3. 不可以删除已有属性
  4. 该对象已有属性的可枚举性、可配置性、可写性不可以被修改
  5. 该对象的原型不可以被修改,但是可以修改原型上的某个属性
      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.freezeObject.sealObject.preventExtensions
是否可以添加新属性
是否可以修改已有属性
是否可以删除已有属性
是否可以修改已有属性的可枚举性、可配置性、可写性
是否可以修改原型