资讯专栏INFORMATION COLUMN

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

Richard_Gao / 1333人阅读

摘要:但好在还给我们提供了一个方法,每一个对象都有这样一个方法,专门用来判断某个属性是否是该对象的私有属性。如果你想要用对象字面形式,你只能在创建对象时定义访问器属性。在中,我们使用冻结一个对象,并且使用来判断一个对象是否被冻结。

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

不出你所料,对象属性自然也有其相应的特征属性,但是这个话题有点复杂,让我们先从简单的说起,对象属性的分类。

面对一个复杂的事物,寻找其内在共性,妥善分类往往是快速认知该事物的捷径,这与程序员“将难以解决的大问题拆解为可以解决的小问题”的思维有异曲同工之妙。

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

按来源分类

私有属性

原型属性

JavaScript是一门基于原型链的语言,对象继承是节省内存空间,避免代码重复,逻辑混乱的好方法。而对象继承对于属性而言则带来一个问题,即我们需要区分某对象内的属性,究竟是对象自有的(私有属性),还是继承于其他对象(继承属性),无论是进行属性遍历还是对属性进行操作,我们都需要谨慎的思考这个问题。

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

情景一: 属性查找

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

let obj = {
    x: 1,
}

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

但是遗憾的是,in操作符会检查所有的私有属性和原型属性,因此你并不能通过in操作符知道该属性的真正来源。

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

我们终于得到了我们想要的。太棒了。

小结:

查找属性(不区分属性来源):in操作符

查找私有属性:对象的.hasOwnProperty()方法

情景二: 属性枚举

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

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

可枚举:这牵扯到我们很快要谈到的属性特征属性(有点拗口是吧:))

会遍历原型属性:这样当一个对象的继承链很长而我们又只关心对象的私有属性时就会变得非常麻烦

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

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

因此,你可以基于是否需要一个数组,是否只需要对象自有属性来判断使用哪一种方法。

小结:

枚举属性(不区分属性来源):for-in循环

只枚举私有属性,且返回数组: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]]特征属性,对象属性要复杂的多:

因为所有对象属性都具有:

[[Enumerable]]特征属性:决定一个属性是否可以被遍历;

[[Configurable]]特征属性:决定一个属性是否可以被配置

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

[[Value]]特征属性:即属性的值;

[[Writable]]特征属性:值为布朗类型,决定该属性值是否可以写入;

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

[[Get]]特征属性:即为getter函数内容;

[[Set]]特征属性:即为setter函数内容;

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

[[Enumerable]]

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

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

let obj = {
    x: 1,
}

console.log(obj.propertyIsEnumerable("x"))   // true
[[Configurable]]

可配置是指:

删除操作;

属性类型变更操作(从数据属性变为访问器属性,或者相反):

使用Object.defineProperty()方法配置属性(别着急,我们之后会着重讲解这个方法);

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

配置特征属性

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

该方法接收三个参数:

拥有该属性的对象

属性名(字符串)

包含需要设置的特征的属性描述对象(属性描述对象具有和特征属性额同名的属性,但是名字中不包含中括号)

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

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()方法使一个对象不仅不可扩展,其所有的属性都不可配置,也就是说,对于一个被封印的对象,你不能:

添加新属性;

删除属性或改变属性类型;

当一个对象被封印时,你只能读写它已有的属性。另外,我们可以通过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!喜欢这篇文章吗?别忘了在下方? 点赞让我知道。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90051.html

相关文章

  • 关于JavaScript对象所不知道的事(一)- 先谈对象

    摘要:对象与属性让我们保持耐心,再梳理一下对象与属性的关系对象是属性的集合,当对象的属性是函数时,我们将其称之为方法。 这篇博文的主要目的是为了填坑,很久之前我发表了一篇名为关于JavaScript对象中的一切(一) — 对象属性的文章,想要谈一谈JavaScript对象,可那时只是贴了一张关于这个主题的思维导图,今天我会针对这一主题进行展开,将JavaScript对象一些平常不太常用的知识...

    mykurisu 评论0 收藏0
  • 前端面试所不知道系列

    摘要:请注意是创建一个全局对象的属性,而不是声明了一个全局变量。由于变量声明自带不可删除属性,比较跟,前者是变量声明,带不可删除属性,因此无法被删除后者为全局变量的一个属性,因此可以从全局变量中删除。下期预告前端面试你所不知道系列伪类和伪元素 写在开始 又到了一年的伊始,很多人可能因为各种原因想换一份工作,而找工作难免遇到各种各样头痛的面试题,于是我打算写一个系列,关于面试中最常见或者前端一...

    Julylovin 评论0 收藏0
  • 所不知道的JSON.stringify

    摘要:已经逐渐替代被全世界的开发者广泛使用。函数将一个对象转换成文本化的。不能被文本化的属性会被忽略。和例外情况在数组中,不可被的元素用填充。自从年双十一正式上线,累计处理了亿错误事件,得到了金山软件等众多知名用户的认可。 译者按: 老司机们,你知道JSON.stringify还有第二个和第三个可选参数吗?它们是什么呢? 原文: What you didn’t know about JSO...

    piapia 评论0 收藏0
  • 所不知道 ❌ Console

    前言 console.log 可以是在日常 Web 开发中最常用的方法了,但是你应该知道 console 比你想象的强。 1.凡人视角 打印字符串 代码: console.log(I am a 凡人); 打印提示消息 代码: console.info(Yes, you are a 凡人); 打印警告消息 代码: console.warn(凡人你居然敢窥视我); 打印错误消息 代码: console...

    lovXin 评论0 收藏0
  • 所不知道 ❌ Console

    前言 console.log 可以是在日常 Web 开发中最常用的方法了,但是你应该知道 console 比你想象的强。 1.凡人视角 打印字符串 代码: console.log(I am a 凡人); 打印提示消息 代码: console.info(Yes, you are a 凡人); 打印警告消息 代码: console.warn(凡人你居然敢窥视我); 打印错误消息 代码: console...

    scq000 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<