關於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!喜歡這篇文章嗎?別忘了在下方? 點贊讓我知道。

相關文章
相關標籤/搜索