关于JavaScript对象,你所不知道的事(二)- 再说属性

说完了对象那些不经常使用的冷知识,是时候来看看JavaScript中对象属性有哪些有意思的东西了。程序员

不出你所料,对象属性天然也有其相应的特征属性,可是这个话题有点复杂,让咱们先从简单的提及,对象属性的分类。数组

面对一个复杂的事物,寻找其内在共性,妥善分类每每是快速认知该事物的捷径,这与程序员“将难以解决的大问题拆解为能够解决的小问题”的思惟有殊途同归之妙。函数

那么,对象的属性根据不一样的维度,能够如何分类呢?你或许想不到,居然有如此多的分类方法,而不一样的类别,有牵扯出特定的方法解决这一类别的某些问题。让咱们看看吧:工具

按来源分类

  • 私有属性
  • 原型属性

JavaScript是一门基于原型链的语言,对象继承是节省内存空间,避免代码重复,逻辑混乱的好方法。而对象继承对于属性而言则带来一个问题,即咱们须要区分某对象内的属性,到底是对象自有的(私有属性),仍是继承于其余对象(继承属性),不管是进行属性遍历仍是对属性进行操做,咱们都须要谨慎的思考这个问题。性能

让咱们举例两个典型场景看看JavaScript是如何帮助咱们解决这个问题的:this

情景一: 属性查找

有时候,咱们须要查找某个对象是否有某个属性,再进一步决定是否要执行下一步操做,JavaScript提供给咱们的查找工具是in操做符,in操做符用以在给定对象中查找一个给定名称的属性,若是找到则返回true值。实际上,in操做符就是在哈希表中查找一个键是否存在(还记的咱们的蓝色章鱼吗,in操做符只是检查章鱼是否有那只触角,并不关心触角上拿着的卡片上写了什么,更不会随着卡片的地址去读取值,这对于提高性能尤为重要),让咱们看看代码示例:code

let obj = {
    x: 1,
}

console.log('x' in obj)   // true
console.log('y' in obj)   // false

可是遗憾的是,in操做符会检查全部的私有属性和原型属性,所以你并不能经过in操做符知道该属性的真正来源。对象

但好在JavaScript还给咱们提供了一个.hasOwnProperty()方法,每个对象都有这样一个方法,专门用来判断某个属性是不是该对象的私有属性。继承

咱们终于获得了咱们想要的。太棒了。ip

小结:

  1. 查找属性(不区分属性来源):in操做符
  2. 查找私有属性:对象的.hasOwnProperty()方法

情景二: 属性枚举

有些时候,咱们想要得到一个对象内全部属性的键或值(或者所有都要),这时咱们就要枚举一个对象内的全部属性,一般,咱们会使用for-in循环去实现这一点。

然而,很不巧的是,for-in循环会遍历全部可枚举的原型属性,注意这里有两点须要进一步说明:

  1. 可枚举:这牵扯到咱们很快要谈到的属性特征属性(有点拗口是吧:))
  2. 会遍历原型属性:这样当一个对象的继承链很长而咱们又只关心对象的私有属性时就会变得很是麻烦

固然,你能够在for-in循环中,再使用咱们刚提到的.hasOwnProperty()方法,可是JavaScript给予了咱们更好的选择:使用Object.keys()方法:

Object.keys()方法是ECMAScript5引入的方法,它能够获取可枚举属性的名字的数组,而且它只返回对象的自有属性。

所以,你能够基因而否须要一个数组,是否只须要对象自有属性来判断使用哪种方法。

小结:

  1. 枚举属性(不区分属性来源):for-in循环
  2. 只枚举私有属性,且返回数组:Object.keys()方法

按做用分类

  • 数据属性
  • 访问器属性

你也许不多据说过这样的分类方式,由于咱们几乎都在使用数据属性,让我来简要说明这两种类型的属性的区别:

数据属性包含了一个值,咱们以前提到的对象的内部方法[[Put]]的默认行为就是建立数据属性:

let obj = {
    x: 1,   // x 是数据属性
}

访问器属性不包含值,而是定义了一个当属性被读取时调用的函数(称为“getter”)和一个当属性被写入时调用的函数(称为“setter”):

let obj = {
    x: 1,
    
    get y() {
        return 2
    }

    set y() {
        return 3
    }
}

console.log(obj.y)   // 2

之因此访问器属性不多见到是由于咱们不多须要在进行属性赋值或读取操做时触发一些行为,不过反过来讲,若是这偏偏是你面临的场景,就大胆的使用吧。


对象属性的特征属性

绕了一大圈,终于能够回到正题,谈谈属性的特征属性了,相较于对象只有一个孤零零的[[Extensiable]]特征属性,对象属性要复杂的多:

由于全部对象属性都具备:

  1. [[Enumerable]]特征属性:决定一个属性是否能够被遍历;
  2. [[Configurable]]特征属性:决定一个属性是否能够被配置

而只有数据属性有如下两个属性:

  1. [[Value]]特征属性:即属性的值;
  2. [[Writable]]特征属性:值为布朗类型,决定该属性值是否能够写入;

而只有访问器属性有如下两个属性:

  1. [[Get]]特征属性:即为getter函数内容;
  2. [[Set]]特征属性:即为setter函数内容;

让咱们先来看看这些特征属性的意义,再来谈谈如何配置这些特征属性:

[[Enumerable]]

并非全部的属性都是可枚举的,实际上,对象的大部分原生方法的[[Enumerable]]特征属性的值都被设置为false(因此使用for-in循环时,不会遍历出一大堆你不须要的内容),那咱们该如何判断一个属性是不是可枚举的呢?

JavaScript为咱们提供了.propertyIsEnumerable()方法去检查一个属性是否可枚举,像.hasOwnProperty()方法同样,每一个对象都拥有这个方法:

let obj = {
    x: 1,
}

console.log(obj.propertyIsEnumerable('x'))   // true

[[Configurable]]

可配置是指:

  1. 删除操做;
  2. 属性类型变动操做(从数据属性变为访问器属性,或者相反):
  3. 使用Object.defineProperty()方法配置属性(别着急,咱们以后会着重讲解这个方法);

所以,当你设置某个属性的[[Configurable]]特征属性为false时,以上三种操做就都不能正确执行。


配置特征属性

是时候讲解JavaScript为咱们提供的配置属性特征属性的方法了:Object.defineProperty()

该方法接收三个参数:

  1. 拥有该属性的对象
  2. 属性名(字符串)
  3. 包含须要设置的特征的属性描述对象(属性描述对象具备和特征属性额同名的属性,可是名字中不包含中括号)

让咱们看看该方法的实际用法:

let obj = {
    x: 1,
}

Object.defineProperty(obj, 'x', {
    enumerable: false,
})

console.log('x' in obj)   // true
console.log(obj.propertyIsEnumerable('x'))   // false

咱们经过Object.defineProperty()方法使obj对象的x属性为不可遍历的,在以后的检测中,咱们看到控制台输入属性存在,但不可遍历。

让咱们再看看数据属性配置特征属性的示例:

let obj = {
    x: 1,
}

Object.defineProperty(obj, 'x', {
    value: 2,
    enumerable: true,
    configurable: true,
    writable: true,
})

console.log(obj.x)   // 2

// 注意咱们所作的实际上彻底等同于如下这段代码

let obj = {
    x: 2,
}

下面是访问器属性配置特征属性的示例:

let obj = {
    x: 1,
}

Object.defineProperty(obj, 'x', {
    get: function() {
        console.log('reading...')
        return 1
    },
    set: function(value) {
        console.log('setting...')
        this.x = value
    },
    enumerable: true,
    configurable: true,
})

使用访问器属性特征属性比使用对象字面形式定义访问器属性的优点在于,你能够为已有的对象定义这些属性。若是你想要用对象字面形式,你只能在建立对象时定义访问器属性。

须要注意的是,一旦你决定使用Object.defineProperty()方法配置属性的特征属性,你须要完整在配置对象中列出enumerable属性与configurable属性,由于在默认状况下,这些属性的值皆为false,这可能不是你想要的。


定义多重属性

当你须要配置一个对象的多个属性时,你须要使用Object.defineProperties()方法,其用法以下:

let obj = {
    x: 1,
}

Object.defineProperties(obj, {
    x: {
        value: 2,
        enumerable: true,
        configurable: true,
        writable: true,
    },
    y: {
        get: function() {
            console.log('reading...')
            return 1
        },
        set: function(value) {
            console.log('setting...')
            this.x = value
        },
    },
})

获取属性特征属性

目前为止,咱们提到了属性的全部特征属性,以及如何设置,最后,让咱们看看JavaScript为咱们提供的查看属性特征属性的方法:Object.getOwnPropertyDescriptor()。其用法以下:

let obj = {
    x: 1,
}

const descriptor = Object.getOwnPropertyDescriptor(obj, 'x')

console.log(descriptor.enumerable)   // true
console.log(descriptor.configurable)   //true
console.log(descriptor.writable)   // true
console.log(descriptor.value)   // 1

能够看到,该方法接收两个参数,一个目标对象以及想要获取特征属性的属性名,该函数会返回一个特征属性描述对象,包含属性特征属性的全部信息。


难以置信,咱们终于讲完了属性的全部特征属性。看到这里的你也值得为本身鼓掌?

先休息一会吧,而后咱们看看最后的一个主题(还记的咱们上一章提到的封闭对象吗?),定义禁止修改的对象


对象封印与对象冻结

对象封印

对象封印是指,经过使用Object.seal()方法使一个对象不只不可扩展,其全部的属性都不可配置,也就是说,对于一个被封印的对象,你不能:

  1. 添加新属性;
  2. 删除属性或改变属性类型;

当一个对象被封印时,你只能读写它已有的属性。另外,咱们能够经过Object.isSealed()方法检验一个对象是否为被封印对象。

代码以下:

let obj = {
    x: 1,
}

console.log(Object.isExtensible(obj))   // true
console.log(Object.isSealed(obj))    // false

// 封印对象
Object.seal(obj)

console.log(Object.isExtensible(obj))   // false
console.log(Object.isSealed(obj))    // true

obj.y = 2

console.log('y' in obj)   // false

obj.x = 3

console.log(obj.x)   // 3

delete obj.x
console.log(obj.x)   // 3

对象冻结

让咱们好好想一想对象封印都作了些什么,它使咱们不能添加属性,只能对已有的属性进行读写操做,但却没法改变已有属性的特征属性,也没法删除已有属性,咱们的对象的封闭性已经很是强了。

对象冻结则更近一步,将对象属性的操做限制为只读,它更像是一个对象某一时刻的快照,除了看以外咱们不能对它有任何操做。

在JavaScript中,咱们使用Object.freeze()冻结一个对象,而且使用Object.isFrozen()来判断一个对象是否被冻结。


终于结束了,让咱们简短回顾一下咱们在本章中都讲了些什么:

  • 首先,咱们讲到了属性的分类:

    • 按来源分:私有属性原型属性
    • 按做用分:数据属性访问器属性
  • 其次,咱们谈到了属性的特征属性:

    • 共有特征属性:[[Enumerable]]`[[Configurable]]
    • 数据属性特征属性:[[Value]][[Writable]]
    • 访问器属性特征属性:[[Set]][[Get]]
  • 以及:

    • 配置属性特征属性的方法:Object.defineProperty()
    • 定义多重属性特征属性的方法:Object.defineProperties()
    • 获取属性特征属性的方法:Object.getOwnPropertyDescriptor()
  • 最后,咱们介绍了定义更加封闭对象的两种方式:

    • 对象封印:Object.seal()Object.isSealed()方法用于检验一个对象是否被封印
    • 对象冻结:Object.freeze()Object.isFrozen()方法用来判断一个对象是否被冻结

大功告成!你如今已经和我同样彻底了解JavaScript对象了,Good Job?

? Hey!喜欢这篇文章吗?别忘了在下方? 点赞让我知道。

相关文章
相关标签/搜索