Javascript 对象初探

119 阅读4分钟

对象的属性

对象就是一些无序属性的集合,Javascript对象规定了这些属性的内部特性,对象中定义的每一个属性都要遵守这些内部特性的约束。

对象的属性的特性有两种:数据属性,访问器属性

数据属性

  • [[Writable]]:属性的值是否可以被修改,默认为 true
  • [[Enumerable]]:属性是否可被遍历
  • [[Value]]:表示属性的值,默认为 undefined
  • [[Configurable]]:属性是否可被重新定义或被修改特性,默认为true

访问器属性

  • [[Get]] :在读取属性的值时调用的函数,默认值为undefined
  • [[Set]] :在写入属性的值时调用的函数,默认值为undefined

查看属性的特性

为了表示这些特性与普通属性的区别,ECMAScript规范把它们都包裹在一对方括号中。我们可以用 Object.getOwnPropertyDescriptor() 来查看一个对象中某一个属性的特性:

const obj = {
  msg: 'Hello World'
}

// 第一个参数是要查看的对象,第二个参数是要查看的属性,写成字符串形式
console.log(Object.getOwnPropertyDescriptor(obj, 'msg'))

打印的结果

常用的对象内置方法

Object.hasOwnProperty()

用来检查对象自身是否含有给定的属性(非继承而来的),它是Object.prototype 对象上的方法,所有对象都会继承这个方法。返回一个布尔值,表示检查的结果

const person = {
  msg: 'Hello World'
}

person.hasOwnProperty('msg') // true

person.hasOwnProperty('name') // false

可以看到,msg 是 person 含有的属性,所以返回 true;person 没有 name 这个属性,故而返回 false

Object.defineProperty & Object.defineProperties

我们有时候真的需要自定义这些属性的特性,比如让它不可修改,或者让它不可被外界访问到。Object.defineProperty() 方法就可以做这些事情

const obj = {
  msg: 'Hello World'
}

Object.defineProperty(obj, 'msg', {
	writable: false
})

obj.msg = 'Good Morning'

console.log(obj.msg) // 'Hello World'

上面的代码通过 Object.defineProperty() 将 obj 对象的 msg 属性的 writable 特性设置为 false,表示它不可以被修改,所以再对 msg 赋值将是无效的

使用 delete 操作符可以删除对象的一个属性,它的返回值是布尔,表示删除是否成功

const obj = {
  msg: 'Hello World'
}

Object.defineProperty(obj, 'msg', {
	configurable: true
})

// 使用 delete 删除 msg 属性
delete obj.msg

console.log(delete obj.msg) // true
console.log(obj.msg) // undefined

注意: configurable 一旦设置为 false ,状态就冻结了,此后再也无法修改其值

const obj = {
  msg: 'Hello World'
}

Object.defineProperty(obj, 'msg', {
  configurable: false
})

// 使用 delete 删除 msg 属性将会失败
delete obj.msg
console.log(delete obj.msg) // false

// 试图改回 configurable 的值
Object.defineProperty(obj, 'msg', {
  configurable: true
})

// 删除操作会报错,因为 configurable 的值已经被冻结为 false
delete obj.msg
console.log(delete obj.msg) 
// Uncaught TypeError: Cannot redefine property: msg at Function.defineProperty

Object.defineProperties() 方法,property变成了复数形式,顾名思义是用来一次性定义多个属性的特性,下面是它的用法

const person = {
  msg: 'Hello World'
  name: 'Aelly',
  age: 28
}

Object.defineProperty(person, {
	'msg': {
    	value: 'Hello',
    	writable: true
  	},
  	'age': {
    	value: 25,
    	writable: false
  	},
  	'name': {
    	value: 'Aelly',
    	writable: false,
    	configurable: false
  	},
})

Object.keys() & Object.getOwnPropertyNames

这两个方法的作用都是遍历对象的属性,返回属性名组成的数组,它们的不同之处在于:Object.keys 只遍历可枚举的属性(enumerable 特性的值为 true),而 Object.getOwnPropertyNames 却可以遍历到不可枚举的属性

const obj = {
  age: 2020,
  msg: 'Hello World',
  color: 'red'
}
console.log(Object.keys(obj)) // ["age", "msg", "color"]
console.log(Object.getOwnPropertyNames(obj)) // ["age", "msg", "color"]


const arr = [1, 2, 3, 4]
console.log(Object.keys(arr)) // ["0", "1", "2", "3"]
console.log(Object.getOwnPropertyNames(arr)) // ["0", "1", "2", "3", "length"]

function f(){
    const name = "Hello World"
    const age = 2020
}

console.log(Object.keys(f)) // []

console.log(Object.getOwnPropertyNames(f)) 
// ["length", "name", "arguments", "caller", "prototype"]

对于纯对象(Obejct)来说,这两个方法返回的结果无差别,不过对于数组(也是对象)来说,Object.getOwnPropertyNames 却遍历到了 length 属性;再看对函数遍历的结果,Object.getOwnPropertyNames返回了许多函数内部独有的不可被外部遍历到的属性

Object.getPrototypeOf()

获取一个对象的原型,返回值就是该对象的 prototype 对象

const obj = {
  msg: 'Hello World'
}

console.log(Object.getPrototypeOf(obj))

打印结果

Object.create()

以一个对象为原型来创建另一个对象,相当于复制了其所有属性到另一个对象

const Person = {
  age: 2020,
  msg: 'Hello World',
  color: 'red'
}

const Aelly = Object.create(Person)

console.log(Person)
console.log(Aelly)

通过查看打印结果我们可以发现,对象 Aelly 的 __ proto__ 属性正是对象 Person。基于 Object.create() 的这个功能,在面向对象编程中,常常用它来实现类式继承

Object.assign()

用于将一个或者多个对象的自身的(非继承而来的)可枚举的属性浅复制到另一个目标对象;第一个参数是目标对象,后面接受一个参数列表,表示源对象。它的返回值就是完成了浅复制操作后的目标对象

const target = {}

const source1 = {
  age: 2020,
  msg: 'Hello World',
  color: 'red'
}

const source2 = {
  name: 'Andy',
  color: 'Orange'
}

const result = Object.assign(target, source1, source2)

console.log(result) // {age: 2020, msg: "Hello World", color: "Orange", name: "Andy"}

如果源对象中存在与目标对象同名的属性,那么目标对象中的这个属性将会被覆盖,上面代码中,color 的值就被覆盖了

Object.assign() 复制的仅仅是值,而不是值的引用:

const result = Object.assign(target, source1, source2)

console.log(result) // {age: 2020, msg: "Hello World", color: "Orange", name: "Andy"}

source1.name = 'AKSJFKSFFLSNVKKFSDLJFFFEFNS'

console.log(result)

最后一个 console.log() 打印出来的值将不会发生任何变化,你可以去控制台实操一下

本文参考了 MDN 文档和《Javascript标准参考教程》,代码全部亲手实现,如有错误,欢迎指出!