深入理解JavaScript之对象

227 阅读3分钟

前情提要

众所周知,JavaScript是一门弱类型语言,但它同样是一门面向对象的语言,准确点说是一门基于原型的面向对象的语言。

什么是对象 ?

ECMA-262规范把对象定义为:无序属性的集合,其属性值可以包含基本类型值、对象或者方法。通俗点讲,对象是一组键值对的集合。键表示属性名称,值表示属性的值。

对象的属性

对象有两个属性:数据属性和访问器属性

数据属性

数据属性具有4个描述行为的特性,因为这些特性是内部值,所以ECMA-262规范将其放在了两对方括号中。

  • [[Configurable]]: 表示属性能否删除而重新定义,或者是否可以修改为访问器属性,默认值为true。
  • [[Enumerable]]: 表示属性是否可枚举,可枚举的属性能够通过for...in循环返回,默认值为true。
  • [[Writable]]: 表示属性值能否被修改,默认值为true。
  • [[Value]]: 表示属性的真实值,属性的读取和写入均通过此属性完成,默认值为undefined。 如果需要修改数据属性默认的特性,则必须使用Object.defineProperty()方法。比如:
// 此时name和age均具有Configurable、Enumerable、Writable和Value属性。其中Configurable,Enumerable,Writable默认为true。
let mag = {
    name: "张三",
    age: 18
}
msg.name = "李四";
console.log(msg.name); // 李四
Object.defineProperty(msg, 'name', {
    writable: false
})
msg.name = "王二麻子";
console.log(msg.name); // 李四
Object.defineProperty(msg, 'name', {
    enumerable: false
})
for(let key in msg) {
    console.log(key);
} // age

访问器属性

访问器属性同样具有4个特性:

  • [[Configurable]]: 表示属性能否删除而重新定义,或者是否可以修改为访问器属性,默认值为true。
  • [[Enumerable]]: 表示属性是否可枚举,可枚举的属性能够通过for...in循环返回,默认值为true。
  • [[Get]]: 在读取属性值时调用的函数(一般称为getter()函数),负责返回有效的值,默认值为undefined。
  • [[Set]]: 在写入属性值时调用的函数(一般称为setter()函数),负责处理数据,默认值为undefined。 同样的,如果要修改访问器属性的特性,也必须使用Object.defineProperty()函数。 getter()方法和setter()方法的存在在一定程度上可以实现对象的私有属性,私有属性不对外暴露。如果想要读取和写入私有属性的值,则需要通过设置额外属性的getter()方法和setter()方法来实现。比如:
let obj = {
    _age: 10
}
Object.defineProperty(obj, "age", {
    get() {
        return this._age;
    },
    set(val) {
        if (val > 10) {
            this._age = val;
            console.log('更新成功');
        }
    }
})
console.log(obj.age); // 10
obj.age = 4;
console.log(obj.age); // 10
obj.age = 100; // 更新成功
console.log(obj.age); // 100

Object.defineProperty()的其他兄弟

  1. Object.defineProperties() 可以同时修改对象里多个属性的数据属性和访问器属性
let obj = {
    name: "王小二",
    age: 18
}
Object.defineProperties(obj, {
    // 数据属性
    name: {
        value: "改头换面",
        writable: false
    },
    age: {
        value: 25,
        writable: true
    },
    // 访问器属性
    name: {
        get() {
            return this.name;
        },
        set(val) {
            this.name = val;
        }
    }
})
  1. Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
let obj = {
    name: '小明'
}
let attr = Object.getOwnPropertyDescriptor(obj, "name");
console.log(attr);
// 正常情况下是:
{
    configurabletrue
    enumerabletrue
    value"小明"
    writabletrue
}
如果没有getter,或者setter,获取时会返回undefined

兼容性

并不是所有的浏览器都支持Object.defineProperty()或者Object.defineProperties();以及Object.getOwnPropertyDescriptor() Object.defineProperty()不受支持时,可以通过defineGetter() 和 **defineSetter()**来构造访问器属性的get和set特性。
同时Object.getOwnPropertyDescriptor()可以用 lookupGetterlookupSetter来获取getter和setter

var obj = {
    _num: 2004,
    age: 18
};
obj.__defineGetter__('num', function() {
    return this._num;
});
obj.__defineSetter__('num', function(newValue) {
    if (newValue > 2004) {
        this._num = newValue;
        this.age += newValue - 2004;
    }
})
obj.num = 2010; // 写入属性
console.log(obj.age, obj.num); // -> 24  2010