Blog A Paranoid Guy

深入理解JS对象


前言

理解JavaScript的对象运作方式对以后理解整个JS很有帮助,下面开始介绍。

定义属性

当一个属性第一次被添加给对象时,JavaScript在对象上调用一个名为[[Put]]的内部方法。[[Put]]方法会在对象上创建一个新节点来保存属性。调用[[Put]]的结果是在对象上创建了一个自有属性

当一个已有的属性被赋予一个新值时,调用的是一个名为[[Set]]的方法

属性探测

检查对象是否已有一个属性,最可靠的方法是使用 in 操作符:'属性' + in + 对象in 操作符会检查自有属性和原型属性;考虑性能问题,可以选择所有对象都拥有的hasOwnProperty()方法,该方法在给定属性存在且为自有属性时返回ture

var ob = new Object();

ob.name = 'mnichangxin';

Object.prototype.age = 20; //原型添加属性

/* 属性探测 */
console.log('name' in ob); //true
console.log('age' in ob); //true
console.log(ob.hasOwnProperty('name')); //true
console.log(ob.hasOwnProperty('age')); //false

删除属性

使用delete操作符:delete + 属性,成功时返回true

/* 删除属性 */
delete ob.name;
console.log(ob.hasOwnProperty('name')); //false

属性枚举

可枚举属性的内部特征[[Enumerable]]都被设置为true,可以用for-in循环遍历它们;如果只需要获取一个对象的属性列表数组,可以用Object.keys()方法。它们的区别是:for-in循环同时会遍历原型属性和自有属性,而Object.keys()只返回自有属性

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

/* 属性枚举 */

//for-in循环
for(property in ob) {
    console.log('Name:' + property);
    console.log('Value:' + ob[property]);
}

//Object.keys()方法
var propertyList = Object.keys(ob);

for(var i = 0; i < propertyList.length; ++i) {
    console.log('Name:' + propertyList[i]);
    console.log('Value:' + ob[propertyList[i]]);
}

//判断是否可以枚举
console.log(ob.propertyIsEnumerable('name')); //true

属性类型

属性有两种类型:数据属性和访问器属性。

[[Put]]方法的默认行为是创建数据属性;访问器属性不包含值而是定义了一个当属性被读取时调用的函数(称为getter)和一个当属性被写入时调用的函数(称为setter)。并不一定要同时定义getter和setter,可以选择定义其中之一(只读或只写)


属性特征

通用特征

有两个特征是数据和访问器属性都具有的。一个是[[Enumerable]],决定了你是否可以遍历该属性;另一个是[[Configurable]],决定该属性是否可以配置。声明的所有属性默认都是可枚举、可配置的

Object.defineProperty(ob, 'name', {
    enumerable: false,
    configurable: false
});
console.log(ob.propertyIsEnumerable('name')); //false
delete ob.name; //试图删除会报错

数据属性特征

数据属性拥有两个特有的特征:第一个是[[Value]],包含属性的值;第二个是[[Writable]],指示该属性是否可以写入,默认可写。当Object.defineProperty()被调用时,首先检查属性是否存在。如果不存在,将根据属性描述对象指定的特征创建。用此方法时,一定要为所有的特征指定一个值,否则所有布尔类型默认设置为false

//数据属性特征值
Object.defineProperty(ob, 'name', {
    value: 'world',
    writable: true,
    enumerable: true,
    configurable: true
});
for(property in ob) {
    console.log('Name:' + property);
    console.log('Value:' + ob[property]);
}

访问器属性特征

访问器特有的属性特征:[[Get]][[Set]]

字面形式:

var person1 = {
    _name'MR',
}
get name() {
    console.log('reading name');
    return this._name;
}
set name() {
    console.log('setting name to %s', value);
    this._name = value;
}

对象形式:

//访问器属性特征值
ob._name = 'MR';
Object.defineProperty(ob, 'name', {
    get: function() {
        console.log('reading name');
        return this._name;
    },
    set: function(value) {
        console.log('setting name to %s', value);
        this._name = value;
    },
    enumerable: true,
    configurable: true
});

定义多重属性

可以为一个对象同时定义多个属性,使用Object.defineProperties()方法,该方法接受两个参数:需要改变的对象和一个包含所有属性的对象

//定义多重属性
Object.defineProperties(ob, {
     _name: {
        value: 'world',
        enumerable: true,
        configurable: true,
        writable: true
     },
     name: {
        get: function() {
            console.log('reading name');
            return this._name;
        },
        set: function(value) {
            console.log('setting name to %s',value);
            this._name = value;
        },
        enumerable: true,
        configurable: true
     }
});

获取属性特征

使用Object.getOwnPropertyDescriptor()方法,只可用于自有属性。接受两个参数:对象和属性名。如果属性存在,它会返回一个属性描述对象

//获取属性特征
var des = Object.getOwnPropertyDescriptor(ob, 'name');
console.log(des.enumerable);
console.log(des.configurable);

禁止修改对象

禁止扩展

第一种方法是用Object.preventExtensions()创建一个不可扩展的对象。该方法只接受一个参数,就是传入的对象。可以用Object.isExtensible()检查[[Extensionable]]的值

//禁止扩展
Object.preventExtensions(ob);
ob.month = 4;
console.log(ob.month); //undefined

对象封印

第二种方法是用Object.seal()方法封印一个对象。该方法被调用时,[[Extensible]]的特征被设置为false,其所有属性的[[Configurable]]特征被设为false,只能读写它的属性。可以用Object.isSealed()判断一个对象是否被封印。

Object.seal(ob);
ob.month = 4;
console.log(ob.month); //undefined

对象冻结

第三种方法是用Object.freeze()来冻结一个对象。被冻结对象是一个数据属性都为只读的被封印对象,被冻结对象无法解冻。可以用Object.isFrozen()判断一个对象是否被冻结。

Object.freeze(ob);
console.log(ob.month);
console.log(ob.month); //undefined

下一篇 JavaScript原型

Comments