JavaScript面向对象系列:三、理解对象

js中的对象是动态的,可以在代码执行的任意时刻发生改变。基于类的语言会根据类的定义锁定对象,js对象没有这种限制。js编程的一大重点就是管理对象。

当一个属性第一次被添加给对象时,js在对象上调用一个名为 [[Put]] 的内部方法。 [[Put]] 方法会在对象上创建一个新的节点来保存属性们就像第一次在哈希表上添加一个键一样。这个操作不仅指定了初始值,也定义了属性的一些特征。调用 [[Put]] 的结果是在对象上创建了一个自有属性。一个自有属性表明仅仅该指定对象实例拥有该属性。该属性被直接保存在实力内,对该属性的所有操作都必须通过该对象进项。

当一个已有的属性被赋予一个新值的时候,调用一个名为 [[Set]] 的方法。该方法将属性的当前方法替换为新值。

var person = {
name:"laowang" //调用 [[Put]] 方法
};
person.age = 25; //调用 [[Put]] 方法

person.name = "xiaowang"; //调用 [[Set]] 方法

属性探测

不要使用一下模式探测属性是否存在

if(person.age){
//todo
}

因为当person.age 为一个对象、非空字符串、非零数字、true时,会判断为真。当person为 null、undefined、0、false、NaN或空字符串会判断为假。

更可靠的属性探测方式是使用 in 操作符。

例如

console.log("name" in person); //true
console.log("sex" in person); //false

使用 in 操作符,在大多数情况下是属性探测的最好的办法。它还有一个额外的好处是不会评估属性的值。当此类评估会导致性能问题或者错误时,这一点很重要。

在某些情况下,希望检查一个属性是不是自有属性。

in 操作符会检查自有属性和原型属性,所以不能检查出正确结果。

另一个方法是 hasOwnProperty方法,这个方法只有在属性存在并且是自有属性时才返回true。

例如

var person = {
name:"laowang" //调用 [[Put]] 方法
};

console.log("name" in person); //true
console.log(person.hasOwnProperty("name")); //true

console.log("toString" in person); //true
console.log(person.hasOwnProperty("toString")); //false

##删除对象属性

使用 delete 操作符可以将属性从一个对象中删除。将对象的一个属性设置为null并不能将属性从对象中彻底删除,这种做法只会将原值替换为null。

使用 delete 操作符会调用对象内部名为 [[Delete]] 的内部方法。相当于从哈希表中移除了一个键值对。

属性枚举

对象内部属性 [[Enumerable]] 设置为true,则属性可以循环遍历。可以使用 for-in 操作符来遍历一个对象。es5中新增 Object.keys() 来遍历对象的键,返回一个键组成的数组。

这两种操作符循环遍历有区别, for-in 操作符会遍历自有属性和原型属性,Object.keys() 只会遍历自由属性。

并不是所有属性都是可枚举的,实际上,对象大部分原生方法的 [[Enumerable]] 设置为false,可以使用 propertyIsEnumerable() 方法来检查一个属性是否为可枚举,每个对象都有这个方法。

访问器属性

getset 属性时访问器属性,可以定义属性被读取或者写入时候的行为。这两个属性可以只设置其中一个,如果只设置 get 这个属性变为只读,如果只设置 set 这个属性变为只写,这两种情况在严格模式下会报错.

例如

var person = {
name:"laowang", //调用 [[Put]] 方法
set name(value){
console.log('set');
this.name = value;
},
get name(){
console.log('get');
return this.name;
}
};

person.name; //get
person.name = 'xiaowang'; //set

##通用特征

有两个属性是数据和访问器都具有的。一个是 [[Enumerable]] ,决定了你是否可以遍历该属性。另一个是
[[Configurable]] ,决定了该属性是否可配置.可以随时删除或改变一个可配置属性。改变属性特征,可以使用 Object.defineProperty() 方法。

例如

var person = {
name:"laowang",
};

Object.defineProperty(person,"name",{
enumerable:false
});

console.log("name" in person); //true
console.log(person.propertyIsEnumerable("name")); //false

var propertites = Object.keys(person);
console.log(propertites.length); //0

Object.defineProperty(person,"name",{
configurable:false
});

delete person.name;
console.log("name" in person); //true
console.log(person.name); //laowang

Object.defineProperty(person,"name",{
configurable:true
}); //报错

一个属性被设置为不可配置了,就不能再修改了。无法将一个不可配置属性变成可配置属性 。删除一个不可配置属性,非严格模式下会失败,严格模式下会报错。

数据属性特征

数据属性拥有两个访问器属性不具备的特征:

第一个是 [[Value]] ,包含属性的值,在对象上创建属性时该特征被自动赋值。所有的属性的值都保存在 [[Value]] 中,即使值是一个函数。

第二个是 [[Writable]] ,该特种是一个布尔值,只是该属性是否可以写入,所有的属性默认是可写的。

例如

var person = {};

Object.defineProperty(person,"name",{
value:"laowang"
});

console.log("name" in person); //true
console.log(person.propertyIsEnumerable("name")); //false

delete person.name;
console.log("name" in person); //true

person.name = "xiaowang";
console.log(person.name); //laowang

使用 Object.defineProperty() 定义新的属性时,一定要为所有特征指定一个值,否则布尔值类型的特征会被默认设置为false。上面这个name就是不可枚举,不可配置,不可写。

定义多重属性

使用 Object.defineProperty() 可以定义一个属性的特征。使用 Object.defineProperties() 可以定义多个属性的特征。

例如

var person = {};

Object.defineProperties(person,{
name:{
value:"laowang",
configurable:true
},
age:{
value:25,
configurable:false,
enumerable:false
}
});

获取属性特征

使用 Object.getOwnPropertyDescriptor() 可以获取一个属性的所有特征

例如

var person = {
name:"laowang"
};

var descriptor = Object.getOwnPropertyDescriptor(person,"name");

console.log(descriptor.enumerable); //true
console.log(descriptor.configurate); //true
console.log(descriptor.writable); //true
console.log(descriptor.value); //laowang

对象禁止扩展

对象也有内部特征,禁止扩展方法是 Object.preventExtensions() 。可以用 Object.isExtensible() 来检查对象的 [[Extensible]] 的值。

例如

var person = {
name:"laowang"
};

console.log(Object.isExtensible(peson)); //true

Object.preventExtensions(peson);
console.log(Object.isExtensible(peson)); //false

person.sayName = function (){
console.log(this.name);
}

console.log("sayName" in person); //false

对象封印

对象封印时创建不可扩展对象的第二种方法。对象被封印后是不可扩展的并且所有属性都不可配置。不仅不能给对象添加新属性,也不能删除属性或改变其类型(从数据属性编程访问器属性或者相反)。一个对象被封印,则只能读写它的属性。

使用 Object.seal() 方法来封印一个对象。对象的 [[Extensible]] 被设置为false。 其余所有属性的 [[Configurate]] 特征被设置为false。可以使用 Object.isSealed() 来判断一个对象是否被封印。

例如

var person = {
name:"laowang"
};

console.log(Object.isExtensible(peson)); //true
console.log(Object.isSealed(peson)); //false

Object.seal(peson);
console.log(Object.isExtensible(peson)); //false
console.log(Object.isSealed(peson)); //true

person.sayName = function (){
console.log(this.name);
}

console.log("sayName" in person); //false

person.name = "xiaowang";
console.log(person.name); //xiaowang

delete person.name;
console.log("name" in person); //true
console.log(person.name); //xiaowang

var descriptor = Object.getOwnPropertyDescriptor(person,"name");

console.log(descriptor.enumerable); //true
console.log(descriptor.configurate); //false

对象冻结

创建不可扩展对象的最后一种方法是冻结对象。如果一个对象被冻结,则不能在对象上添加或者删除属性,不能改变属性类型,也不能写入任何数据属性。被冻结对象是一个数据属性都为只读的封印对象。被冻结的对象无法解冻。使用 Object.freeze() 来冻结一个对象,使用 Object.isFrozen() 来判断一个对象是否被冻结。

例如

var person = {
name:"laowang"
};

console.log(Object.isExtensible(peson)); //true
console.log(Object.isSealed(peson)); //false
console.log(Object.isFrozen(peson)); //false

Object.freeze(peson);
console.log(Object.isExtensible(peson)); //false
console.log(Object.isSealed(peson)); //true
console.log(Object.isFrozen(peson)); //true

person.sayName = function (){
console.log(this.name);
}

console.log("sayName" in person); //false

person.name = "xiaowang";
console.log(person.name); //laowang

delete person.name;
console.log("name" in person); //true
console.log(person.name); //laowang

var descriptor = Object.getOwnPropertyDescriptor(person,"name");

console.log(descriptor.writable); //false
console.log(descriptor.configurate); //false

总结

属性有两种类型:数据属性、访问器属性。数据属性可以保存值,访问器属性不保存值。

发表评论

电子邮件地址不会被公开。 必填项已用*标注