JavaScript中的Object.defineProperty()和defineProperties()

文章同步到githubhtml

ECMAS-262第5版在定義只有內部採用的特性時,提供了描述了屬性特徵的幾種屬性。ECMAScript對象中目前存在的屬性描述符主要有兩種,數據描述符(數據屬性)和存取描述符(訪問器屬性),數據描述符是一個擁有可寫或不可寫值的屬性。存取描述符是由一對 getter-setter 函數功能來描述的屬性。

Object的definePropertydefineProperties這兩個方法在js中的重要性十分重要,主要功能就是用來定義或修改這些內部屬性,與之相對應的getOwnPropertyDescriptorgetOwnPropertyDescriptors就是獲取這行內部屬性的描述。vue

下面文章我先介紹數據描述符和存取描述符的屬性表明的含義,而後簡單介紹以上四個方法的基本功能,這些若是瞭解可直接跳過,最後我會舉例擴展及說明各內部屬性在各類場景下產生的實際效果,那纔是這篇文章的核心內容。本文章關於概念性的描述仍是會盡可能使用《javaScript高級教程》、MDN網站等概念,保證準確和易於你們理解,講解部分則結合我的理解和舉例說明。java

數據(數據描述符)屬性

數據屬性有4個描述內部屬性的特性git

[[Configurable]]

表示可否經過delete刪除此屬性,可否修改屬性的特性,或可否修改把屬性修改成訪問器屬性,若是直接使用字面量定義對象,默認值爲truegithub

[[Enumerable]]

表示該屬性是否可枚舉,便是否經過for-in循環或Object.keys()返回屬性,若是直接使用字面量定義對象,默認值爲truesegmentfault

[[Writable]]

可否修改屬性的值,若是直接使用字面量定義對象,默認值爲true函數

[[Value]]

該屬性對應的值,默認爲undefined網站

訪問器(存取描述符)屬性

訪問器屬性也有4個描述內部屬性的特性spa

[[Configurable]]

和數據屬性的[[Configurable]]同樣,表示可否經過delete刪除此屬性,可否修改屬性的特性,或可否修改把屬性修改成訪問器屬性,若是直接使用字面量定義對象,默認值爲true雙向綁定

[[Enumerable]]

和數據屬性的[[Configurable]]同樣,表示該屬性是否可枚舉,便是否經過for-in循環或Object.keys()返回屬性,若是直接使用字面量定義對象,默認值爲true

[[Get]]

一個給屬性提供 getter 的方法(訪問對象屬性時調用的函數,返回值就是當前屬性的值),若是沒有 getter 則爲 undefined。該方法返回值被用做屬性值。默認爲 undefined

[[Set]]

一個給屬性提供 setter 的方法(給對象屬性設置值時調用的函數),若是沒有 setter 則爲 undefined。該方法將接受惟一參數,並將該參數的新值分配給該屬性。默認爲 undefined

建立/修改/獲取屬性的方法

Object.defineProperty()

功能:
方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。若是不指定configurable, writable, enumerable ,則這些屬性默認值爲false,若是不指定value, get, set,則這些屬性默認值爲undefined

語法: Object.defineProperty(obj, prop, descriptor)

obj: 須要被操做的目標對象
prop: 目標對象須要定義或修改的屬性的名稱
descriptor: 將被定義或修改的屬性的描述符

var obj = new Object();

Object.defineProperty(obj, 'name', {
    configurable: false,
    writable: true,
    enumerable: true,
    value: '張三'
})

console.log(obj.name)  //張三

Object.defineProperties()

功能:
方法直接在一個對象上定義一個或多個新的屬性或修改現有屬性,並返回該對象。

語法: Object.defineProperties(obj, props)

obj: 將要被添加屬性或修改屬性的對象
props: 該對象的一個或多個鍵值對定義了將要爲對象添加或修改的屬性的具體配置

var obj = new Object();
Object.defineProperties(obj, {
    name: {
        value: '張三',
        configurable: false,
        writable: true,
        enumerable: true
    },
    age: {
        value: 18,
        configurable: true
    }
})

console.log(obj.name, obj.age) // 張三, 18

Object.getOwnPropertyDescriptor()

功能:
該方法返回指定對象上一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該對象的屬性,不須要從原型鏈上進行查找的屬性)

語法: Object.getOwnPropertyDescriptor(obj, prop)

obj: 須要查找的目標對象
prop: 目標對象內屬性名稱

var person = {
    name: '張三',
    age: 18
}

var desc = Object.getOwnPropertyDescriptor(person, 'name'); 
console.log(desc)  結果以下
// {
//     configurable: true,
//     enumerable: true,
//     writable: true,
//     value: "張三"
// }

Object. getOwnPropertyDescriptors()

功能:
所指定對象的全部自身屬性的描述符,若是沒有任何自身屬性,則返回空對象。

語法: Object.getOwnPropertyDescriptors(obj)

obj: 須要查找的目標對象

var person = {
    name: '張三',
    age: 18
}
var desc = Object.getOwnPropertyDescriptors(person, 'name');
console.log(desc) 
//{
//    configurable: true,
//    enumerable: true,
//    value: '張三',
//    writable: true
//}

各類場景下描述符屬性的的擴展現例講解

. configurable

若是設置configurable屬性爲false,則不可以使用delete操做符(在嚴格模式下拋出錯誤), 修改全部內部屬性值會拋出錯誤,在《javaScript高級教程中》說只能夠改變writable的值,如今改變writable的值也會拋出錯誤

在對象中添加一個數據描述符屬性

var person = {};

Object.defineProperty(person, 'name', {
    configurable: false,
    value: 'John'
}) ;

delete person.name   // 嚴格模式下拋出錯誤

console.log(person.name)  // 'John'  沒有刪除

Object.defineProperty(person, 'name', {
    configurable: true  //報錯
});

Object.defineProperty(person, 'name', {
    enumerable: 2  //報錯
});

Object.defineProperty(person, 'name', {
    writable: true  //報錯
});

Object.defineProperty(person, 'name', {
    value: 2  //報錯
});

注意:
以上是最開始定義屬性描述符時,writabl默認爲false,纔會出現上述效果,若是writable定義爲true, 則能夠修改[[writable]]和[[value]]屬性值,修改另外兩個屬性值報錯

var obj = {};

Object.defineProperty(obj, 'a', {
    configurable: false,
    writable: true,
    value: 1
});

Object.defineProperty(obj, 'a', {
    // configurable: true, //報錯
    // enumerable: true,  //報錯
    writable: false,
    value: 2
});
var d = Object.getOwnPropertyDescriptor(obj, 'a')
console.log(d);
// {
//     value: 2, 
//     writable: false, 
// }

在對象中添加存取描述符屬性

var obj = {};
var aValue; //若是不初始化變量, 不給下面的a屬性設置值,直接讀取會報錯aValue is not defined
var b;
Object.defineProperty(obj, 'a', {
    configurable : true,
    enumerable : true,
    get: function() {
        return aValue
    },
    set: function(newValue) {
        aValue = newValue;
        b = newValue + 1
    }
})
console.log(b) // undefined
console.log(obj.a)  // undefined, 當讀取屬性值時,調用get方法,返回undefined
obj.a = 2;  // 當設置屬性值時,調用set方法,aValue爲2

console.log(obj.a) // 2  讀取屬性值,調用get方法,此時aValue爲2
console.log(b) // 3  再給obj.a賦值時,執行set方法,b的值被修改成2,額外說一句,vue中的計算屬性就是利用setter來實現的

注意:
1.getter和setter能夠不一樣時使用,但在嚴格模式下只其中一個,會拋出錯誤
2.數據描述符與存取描述符不可混用,會拋出錯誤

var obj = {};
Object.defineProperty(obj, 'a', {
    value: 'a1',
    get: function() {
       return 'a2'
    }    
});

3.全局環境下:

var a = 1;  // a屬於window, 至關於window.a

讓咱們來看看a的描述符屬性

var d = Object.getOwnPropertyDescriptor(window, 'a');
console.log(d)
// {
//     configurable: false,
//     value: 1,
//     writable: true,
//     enumerable: true
// }

在來看一下另外一種不適用var聲明的方式初始化a變量

a = 1; //a至關於window的一個屬性, window.a

再來看看此時a的描述符屬性

var d = Object.getOwnPropertyDescriptor(window, 'a');
console.log(d)
// {
//     configurable: true,   // 此時configurable屬性值爲true
//     value: 1,
//     writable: true,
//     enumerable: true
// }

注意:

只有使用var, let等操做符纔是定義變量,而不使用var,直接a=1;,這樣a的含義爲window的一個屬性,並非咱們所說的變量的概念。使用 var定義的任何變量,其configurable屬性值都爲false,定義對象也是同樣

var b = {
    name: 'bbb'
}
var d = Object.getOwnPropertyDescriptor(window, 'b');
console.log(d)
// {
//     configurable: false
//     value: 1,
//     writable: true,
//     enumerable: true
// }

可是這裏須要說明的一點是,使用字面量定義的對象,該對象內部的屬性的數據描述符屬性都爲true

var b = {
    name: 'bbb'
}
var d = Object.getOwnPropertyDescriptor(b, 'name');
console.log(d)
// {
//     configurable: true
//     writable: true,
//     enumerable: true
//     value: 'bbb'
// }

Writable

當writable爲false(而且configurable爲true),[[value]]能夠經過defineeProperty修改, 但不能直接賦值修改

var obj = {};

Object.defineProperty(obj, 'a', {
    configurable: true,
    enumerable: false,
    writable: false,
    value: 1
});

Object.defineProperty(obj, 'a', {
    configurable: false,
    enumerable: true,
    writable: false ,
    value: 2
});
var d = Object.getOwnPropertyDescriptor(obj, 'a')

console.log(d); // 結果以下
// {
//     value: 2, 
//     writable: false, 
//     enumerable: true, 
//     configurable: false
// }


可是若是直接複製修改
var obj = {}

Object.defineProperty(obj, 'a', {
    configurable: true,
    enumerable: false,
    writable: false,
    value: 1
});
obj.a=2;
var d = Object.getOwnPropertyDescriptor(obj, 'a')

console.log(d); // 結果以下

// {
//     value: 1,  // 沒有作出修改
//     writable: false, 
//     enumerable: true, 
//     configurable: false
// }

Enumerable

直接上例子

var obj = {};
Object.defineProperties(obj, {
    a: {
        value: 1,
        enumerable: false
    }, 
    b: {
        value: 2,
        enumerable: true
    },
    c: {
        value: 3,
        enumerable: false
    }
})

obj.d = 4;

//等同於

//Object.defineProperty(obj, 'd', {
//    configurable: true,
//    enumerable: true,
//    writable: true,
//    value: 4
//})

for(var key in obj) {
    console.log(key);  
    // 打印一次b, 一次d, a和c屬性enumerable爲false,不可被枚舉
} 

var arr = Object.keys(obj);
console.log(arr);  // ['b', 'd']

get和set

簡易的數據雙向綁定

在線demo地址: http://www.sunzhaoye.com/demo...

html代碼:

<body>
    <p>
        input1=><input type="text" id="input1">
    </p>
    <p>
        input2=>
        <input type="text" id="input2">
    </p>
    <div>
        我每次比input1的值加1=>
        <span id="span"></span>
    </div>
</body>

js代碼:

var oInput1 = document.getElementById('input1');
var oInput2 = document.getElementById('input2');
var oSpan = document.getElementById('span');
var obj = {};
Object.defineProperties(obj, {
    val1: {
        configurable: true,
        get: function() {
            oInput1.value = 0;
            oInput2.value = 0;
            oSpan.innerHTML = 0;
            return 0
        },
        set: function(newValue) {
            oInput2.value = newValue;
            oSpan.innerHTML = Number(newValue) ? Number(newValue) : 0
        }
    },
    val2: {
        configurable: true,
        get: function() {
            oInput1.value = 0;
            oInput2.value = 0;
            oSpan.innerHTML = 0;
            return 0
        },
        set: function(newValue) {
            oInput1.value = newValue;
            oSpan.innerHTML = Number(newValue)+1;
        }
    }
})
oInput1.value = obj.val1;
oInput1.addEventListener('keyup', function() {
    obj.val1 = oInput1.value;
}, false)
oInput2.addEventListener('keyup', function() {
    obj.val2 = oInput2.value;
}, false)

總結

終於到了最後了,就不具體梳理總結了。雖然咱們在開過過程當中不怎麼使用幾種方法,但理解以後對於咱們理解js中對象有很大幫助,對後續進步也頗有幫助,好比vue的實現原理等。我的能力有限,還但願你們發現問題後能多多指點,共同進步。

相關文章
相關標籤/搜索