ECMAScript中有两类属性,分别是数据属性与访问器属性。
1.1 数据属性
数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的 特性。
-
[[Configurable]]
:表示能否通过delete
删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。 -
[[Enumerable]]
:表示能否通过for-in循环返回属性。 -
[[Writable]]
:表示能否修改属性的值。 -
[[Value]]
:包含这个属性的数据值。
通过
new object
或者对象字面量方式创建对象定义的属性,他们的[[Configurable]]
、[[Enumerable]]
、[[Writable]]
特征均为true
,而[[Value]]
值被设置为指定的值。
举例
//对象字面量
var cat = {
name: "TOM",
age:"2"
}
console.log(Object.getOwnPropertyDescriptor(cat,"name"))
//输出结果:{ value: 'TOM', writable: true, enumerable: true, configurable: true }
//getOwnPropertyDescriptor可以获取指定属性的描述
Object.defineProperty(cat,"name",{writable:false})
console.log("修改前",cat.name)
cat.name= "panghu"
console.log("修改后",cat.name)
//修改前 TOM
//修改后 TOM
使用Object.defineProperty()可以修改默认属性。包含三个参数:属性所在对象,属性名称,描述符对象。
Object.defineProperty(cat,"name",{writable:false})
以上代码将name
属性的[[writable]]
特性值设为false
,表示该属性只可读不可修改。如果修改name的值,非严格模式下,赋值操作会被忽略,在严格模式下,会抛出错误。
1.2 访问器属性
访问器属性有如下 4 个特性。
-
[[Configurable]]
:特性同1.1数据属性的该特征值 -
[[Enumerable]]
:特性同1.1数据属性的该特征值 -
[[Get]]
:在读取属性时调用的函数。默认值为 undefined。 -
[[Set]]
:在写入属性时调用的函数。默认值为 undefined。
访问器属性不能直接定义,必须使用 Object.defineProperty()
来定义
var dog = {
_age: "2",
edition:1
}
Object.defineProperty(dog,"age",{
get:function(){
return this._age
},
set:function(newAge){
this._age = newAge
this.edition+=1
}
})
console.log(dog.age) //2
dog.age=3
console.log("age:"+dog.age+" editor:"+dog.edition)//age:3 editor:2
console.log(Object.getOwnPropertyDescriptor(dog,"age"))
//{
//get: [Function: get],
//set: [Function: set],
//enumerable: false, 属性默认不可枚举
//configurable: false 描述符默认不可修改,属性不可删除
//}
以上代码定义了_age
与edition
两个数据属性,同时定义了age
这个访问器属性。age
访问器属性包含get
与set
两个方法,get
方法返回_age
属性的值,set
方法通过计算来确定正确的版本。
数据属性和访问器属性共享configurable
和enumerable
(默认值是指在使用 Object.defineProperty()
定义属性时的默认值):
-
configurable
当且仅当该属性的
configurable
键值为true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为false
。 -
enumerable
当且仅当该属性的
enumerable
键值为true
时,该属性才会出现在对象的枚举属性中。 默认为false
。
1.3 访问器属性实现双向绑定
了解了这些基本概念,不禁有一个疑问,访问器属性有什么作用?
访问器属性使用的常见方式,即设置一个值会导致其他属性发生变化,该思想是vue双向绑定数据实现的核心内涵。
下面通过一个实例了解一下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input id="input"/></br> //input输入框
<script>
let inputNode = document.getElementById('input');
let person = {}
Object.defineProperty(person, 'name' ,{ //为person.name定义访问器属性的内容
configurable: true,
get: function () {
console.log('person.name.get():'+ inputNode.value)
return inputNode.value
},
set: function (newValue) {
console.log('person.name.set():' + newValue)
inputNode.value = newValue
}
})
inputNode.oninput = function () {
//为input输入框添加监听方法,input输入值发生改变时,将输入值赋值给person.name
console.log('inputNode.oninput: ' + inputNode.value)
person.name = inputNode.value; //将input输入框的数据与perosn对象的name属性值绑定
console.log('person.name: '+ person.name)
}
</script>
</body>
</html>
在输入框中输入232
输出:
10:25:06.509 inputNode.oninput: 2 at study_property.html:24
10:25:06.522 person.name.set():2 at study_property.html:19
10:25:06.524 person.name.get():2 at study_property.html:15
10:25:06.531 person.name: 2 at study_property.html:26
10:25:07.111 inputNode.oninput: 23 at study_property.html:24
10:25:07.122 person.name.set():23 at study_property.html:19
10:25:07.123 person.name.get():23 at study_property.html:15
10:25:07.133 person.name: 23 at study_property.html:26
10:25:09.035 inputNode.oninput: 232 at study_property.html:24
10:25:09.051 person.name.set():232 at study_property.html:19
10:25:09.052 person.name.get():232 at study_property.html:15
10:25:09.067 person.name: 232 at study_property.html:26
以上代码实现了一个将input
输入框的数据与perosn
对象的name
属性值绑定的实例,input
输入框内的数据发生改变会同步影响perosn.name
的值,而对perosn.name
的任何修改也会同步到input
输入框内
2.1 Writable 属性
当 writable
属性设置为 false
时,该属性被称为“不可写的”。它不能被重新赋值。
var o = {}; // 创建一个新对象
Object.defineProperty(o, 'a', {
value: 37,
writable: false
});
console.log(o.a); // 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)非严格模式下不会报错
console.log(o.a); // 37 对象的a属性的值不能被修改,因为属性Writable为false
//strict mode 严格模式
(function() {
'use strict';
var o = {};
Object.defineProperty(o, 'b', {
value: 2,
writable: false
});
o.b = 3; // throws TypeError: "b" is read-only 报错b属性只读,不能修改
return o.b; // returns 2 without the line above
}());
2.2 enumerable属性
enumerable
定义了对象的属性是否可以在 for...in
循环和 Object.keys()
中被枚举。
var o = {};
Object.defineProperty(o, "a", { value : 1, enumerable: true });
Object.defineProperty(o, "b", { value : 2, enumerable: false });
Object.defineProperty(o, "c", { value : 3 }); // enumerable 默认为 false
o.d = 4; // 如果使用直接赋值的方式创建对象的属性,则 enumerable 为 true
for (var i in o) {
console.log(i);
}
// a d
// b c 不可以被枚举
console.log(Object.keys(o)); // ['a', 'd'] // b c 不可以被枚举
console.log(o.propertyIsEnumerable('a')); // true
console.log(o.propertyIsEnumerable('b')); // false
console.log(o.propertyIsEnumerable('c')); // false
console.log(o.propertyIsEnumerable('d')); // true
2.3 Configurable 属性
configurable
特性表示对象的属性是否可以被删除,以及除 value
和 writable
特性外的其他特性是否可以被修改。
var o = {};
Object.defineProperty(o, 'a', {
value:1,
configurable: false
});
console.log('o.a: ',o.a) // o.a: 1
delete o.a
console.log('o.a: ',o.a) // o.a: 1
把[[Configurable]]
特性设置为false后,属性不能从对象中删除,此时调用delete在非严格模式下什么也不会发生,在严格模式下会抛出错误。
var o = {};
Object.defineProperty(o, 'a', {
value:1,
configurable: false
});
Object.defineProperty(o, 'a', {
value:1,
writable: false,
enumerable:false
});
console.log(Object.getOwnPropertyDescriptor(o,"a"))
//输出{ value: 1, writable: false, enumerable: false, configurable: false }
var o = {};
Object.defineProperty(o, 'a', {
value:1,
writable: false,
enumerable:false,
configurable:false
});
Object.defineProperty(o, 'a', {
writable: true,
enumerable:true,
});
console.log(Object.getOwnPropertyDescriptor(o,"a")) //TypeError: Cannot redefine property: a
[[Configurable]]
特性设置为false
后,如果[[Writable]][[enumerable]]
以前为true
,则还可以将其修改为false
,如果[[Writable]][[enumerable]]
以前为false
,则不可修改。