这次我们好好聊一聊对象这个东西,本次说的不是array,也不是function,而是object

基础概念

对象是一种特殊的数据类型,这种数据类型还有其他的很多叫法,比如“散列”,“散列表”,“字典”,“关联数组”。

对象按照创建者的角度,可以分为三类:
内置对象:是由javascript语言本身自定义的对象,大多数是一些预定好的构造函数类,比如ArrayDateFunction
宿主对象:是指javascript解释器所嵌入的宿主环境定义的对象,比如HTMLElement就是宿主对象。
自定义对象:是指由开发者在代码中所创建的对象。

我们知道对象的表达方式是:'属性':'值'

属性按照来源不同,可以分为两类:
自有属性:是指直接在对象中定义的属性。
继承属性:是指在对象原型链中的属性。

与此同时,对象的属性还具有一些特性:
可写:代表是否可以设置该对象所对应的该属性的值。
可枚举:代表是否可以通过api枚举出该属性。
可配置:代表是否可以删除或修改该属性。

记住上述概念,有助于我们进行下一步的理解。

创建对象

创建对象有三种方式:
第一种:对象直接量

var o = {}
var obj = {
    a: 1,
    'b': 2,
    'min title': 3
}

第二种:通过new创建对象

var o = new Object()

第三种:Object.create()
该方法支持传入两个参数,第一个参数是对象的原型(就是所创建出的对象的_proto_的指向),第二个参数是对属性进一步的描述(该参数可选,参数内容会在后面详解

clipboard.png

以下三种方式是等价的:
clipboard.png

检测对象原型的方法除了instanceof之外,还有一个isPrototypeOf(),我们来看一下使用:

clipboard.png

对象的getter和setter

我们首先再明确一个概念:
我们常见的{a: 1}中的a叫做数据属性,除此之外还有一个叫做存取器属性,存取器属性的值的读取和设置,都是由getter和setter控制的。

var o = {
    p: 0,
    set q(value) {
        this.p = value
    },
    get q() {
        return this.p+=100
    }
}
o.q = 1
console.log(o.q)    // => 101
console.log(o.q)    // => 201

其中,对象中的函数定义没有使用function关键字,使用的而是getset关键字

处理可以这样定义存取器属性,我们还可以利用其他的方法定义存取器属性,就是我们熟知的Object.defineProperty()Object.definePeoperties()

首先,我们介绍一下Object.defineProperty()定义数据属性的方式

var o = {}
// 定义属性
Object.defineProperty(
    o, 
    'x', 
    {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: true
    }
)
// 修改属性
Object.defineProperty(
    o, 
    'x', 
    {
        value: 2,
        writable: false
    }
)

这个函数共有三个参数:
第一个参数是,需要加属性的对象。

第二个参数是,添加的属性的名称。

第三个参数是定义的配置项:
第一个配置就是这个属性所对应的值。
剩余三个配置就是对应到文章一开始所提到属性三个特性可写可枚举可配置
这四个配置项都不是必须填写。假如对于新创建的属性,value不填,默认值就是undefined,其他配置项的缺省值是false。假如对于修改已有的属性来说,不填写的配置项就不做修改。

我们再看一下Object.defineProperty()定义存取器属性的方式
其中,需要注意的是,在定义存取器属性时无法定义,valuewritable配置项,因为定义的getset从某种意义上代替了前两个配置项。

var o = {y: 1}
Object.defineProperty(
    o, 
    'x', 
    {
        set(value) {
            this.y = value
        },
        get() {
            return this.y+=100
        },
        enumerable: true,
        configurable: true
    }
)

Object.defineProperty只能单个定义或修改对象属性,Object.defineProperties提供了批量解决的办法,如下:

var o = {}
Object.defineProperties(
    o,  
    {
        x: {
            set(value) {
                this.y = value
            },
            get() {
                return this.y+=100
            },
            enumerable: true,
            configurable: true
        },
        y: {
            value: 1,
            writable: false,
            enumerable: true,
            configurable: true
        }
    }
)

Object.definePropertyObject.defineProperties对于属性的修改是有规则和要求的,如下:

  1. 如果对象是不可扩展的,则可以编辑已有的自有属性,但不能给它添加新属性。
  2. 如果属性是不可配置的,则不能修改它的可配置性和可枚举性。
  3. 如果存取器属性是不可配置的,则不能修改其getter和setter方法,也不能将它转换为数据属性。
  4. 如果数据属性是不可配置的,则不能将它转换为存取器属性。
  5. 如果数据属性是不可配置的,则不能将它的可写性从false修改为true,但可以从true修改为false。
  6. 如果数据属性是不可配置且不可写的,则不能修改它的值。然而可配置但不可写属性的值是可以修改的(实际上是先将它标记为可写的,然后修改它的值,最后转换为不可写的)。

在这里,我们再看一下上面的Object.create方法,Object.create的第二个参数是和Object.defineProperties第二个参数一样的,缺省值同样为undefinedfalse

没有提供选项去配置属性特性的方法,这些属性默认都是true,例如:new关键字和对象直接量的方式创建对象,以及最上面对象直接量的方式设置getter和setter

对象的扩展性

对象的扩展性是什么,其实就是指对象能否添加新的属性,默认情况下所有的内置对象和自定义对象都是可扩展的。除非我们人为的改变它:

Object.preventExtesions()可以传入一个参数,就是你要取消扩展功能的对象,操作后,这个对象会不能扩展(即不能添加新属性),且不能恢复,操作只影响该对象本身,不对其他原型链操作产生影响。
我们可以利用Object.esExtensible()判断这个对象是否可扩展,传入一个对象,返回布尔值。

Object.seal()可以将对象封闭,效果和上面的Object.preventExtesions()一样,增加的效果是将这个对象的自有属性设置为不可配置(即将configurable设为false),同样不能解封。
我们可以利用Object.isSealed()来判断这个对象是否封闭,传入一个对象,返回布尔值。

Object.freeze()可以将对象冰冻,效果和上面的Object.seal()一样,增加的效果是将这个对象的自有属性设置为不可写(即将writable设为false),同样不能解冻。
Object.isFrozen()可以判断这个对象是否冰冻。

根据属性查询值

根据属性查询值的方式我们当然是众所周知了,[].,当然他们也可以设置和修改可写性为true的自有属性值

var o = {}
Object.defineProperties(
    o,  
    {
        x: {
            value: 1,
            writable: true,
            enumerable: true,
            configurable: true
        },
        y: {
            value: 1,
            writable: false,
            enumerable: true,
            configurable: true
        }
    }
)

o.x    // => 1
o['x'] = 5
o.x    // => 5
o.y = 5
o.y    // => 1

删除属性

delete可以用户删除对象属性,能删除的属性只是该对象的自有属性属性配置性为true的属性

让人感到意外的是,delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性。

clipboard.png

检测属性是否存在

检测属性共有三种方式,in操作符,hasOwnPropertypropertyIsEnumerable

in可以检测对象的自有属性继承属性,不受属性特性的影响

var o = {x: 1}
'x' in o    // true
'y' in o    // false
'toString' in o    // true

hasOwnProperty只能检测对象的自有属性,不能检测继承属性,不受属性特性的影响

var o = {x: 1}
o.hasOwnProperty('x')    // true
o.hasOwnProperty('y')    // false
o.hasOwnProperty('toString')    // false

propertyIsEnumerable只能检测自有属性,且要求这个属性的可枚举性为true

枚举全部属性

首先第一个方法是for/in,这个方法可以枚举出当前对象可枚举(属性枚举性为true)的自有属性和继承属性

var o = Object.create({z: 1})

Object.defineProperties(
    o,  
    {
        x: {
            value: 1,
            writable: true,
            enumerable: true,
            configurable: true
        },
        y: {
            value: 1,
            writable: false,
            enumerable: false,
            configurable: true
        }
    }
)

for (p in o) {console.log(p)}    // => x, z

第二个方法是Object.keys(),这个方法可以枚举出当前对象的可枚举(属性枚举性为true)的自有属性,返回值是一个数组

var o = Object.create({z: 1})

Object.defineProperties(
    o,  
    {
        x: {
            value: 1,
            writable: true,
            enumerable: true,
            configurable: true
        },
        y: {
            value: 1,
            writable: false,
            enumerable: false,
            configurable: true
        }
    }
)

Object.keys(o)    // => ['x']

第三个方法是Object.getOwnPropertyNames,它可以枚举出当前对象所有的自有属性(不受枚举性影响)

Object.defineProperties(
    o,  
    {
        x: {
            value: 1,
            writable: true,
            enumerable: true,
            configurable: true
        },
        y: {
            value: 1,
            writable: false,
            enumerable: false,
            configurable: true
        }
    }
)

Object.getOwnPropertyNames(o)    // => ['x', 'y']

对象的序列化和反序列化

JSON.stringify()JSON.parse()


bradwang
174 声望41 粉丝

既迷茫又渴望,就是一个普普通通的程序员