对象属性配置和代理

一、存取器属性: settergetter

如果对象内某个属性比较重要, 比如 User 类中有一个 email 属性, 而开发者对 email 又有着严格的要求, 比如说 email 只能是 qq邮箱, 163邮箱, 126邮箱 ... 中的一种, 势必要进行检验。

class User {
    constructor() {
        this._email = "";
    }
    set email(value) {
        // give some validation
        this._email = value;
    }
    get email() {
        return this._email;
    }
}

通过这样构建一个 User 类, 其中每次调用 email 属性时 (读则调用 get, 写则调用 set), 返回的是实例内部的另外一个私有属性 _email。

let user1 = new User();
user1.email = "507160630@qq.com";
console.log(user1.email);

但是这样并不能阻止直接访问 _email, 属性前面加上下划线 _ 被约定俗成的表示私有属性, 因此 符号属性可以更好的解决此问题。

const USER_EMAIL = Symbol('email'); // 参数为一些描述信息, 可以不加。

# set方法就变成了这样
set email(value) {
    this[USER_EMAIL] = value;    // 去掉了 _email
}

同样这也无法避免被访问, 但是它的安全系数无疑大了很多。


存取器的 set get 并不是需要同时存在, 可以设置为只读 (还可以) 或只写 (不常见)。

二、对象属性的属性

  • 属性简介
const obj = { name: "Lisa" };
console.log(Object.getOwnPropertyDescriptor(obj, 'name'));
# 结果为
{ value: "Lisa", writable: true, enumerable: true, configurable: true };
# 从左到右表示 值 => 可写 => 可枚举 => 可配置
  • set、get
# 配置此属性为只读, 如果再赋值则会报错
Object.defineProperty(obj, 'foo', { writable: false });
# 添加新字段, 唯一在对象实例化后给属性添加 set get 的途径
Object.defineProperty(obj, 'color', {
    set: function(value){ this.color = value };
    get: function(){ return this.color };
});
Object.defineProperty(obj, 'color' { value: 'red'});
  • 可枚举
# 创建后设置是否可枚举
const arr = [1,2,3];
arr.sum = function() {console.log("hello")};
Object.defineProperty(arr, 'sum', {enumerable: false});
# 创建时设置是否可枚举
Object.defineProperty(arr, 'sum', {
    value: function() {consoel.log("hello")};
    enumerable: false;
});
# 等价写法, 函数不同 !!! (这个是复数形式)
Object.defineProperties(arr, {
    sum: {
        value: function() { console.log('hello') },
        emumerable: false
    },
    avg: {
        value: 100,
        enumerable: true
    }
});

三、对象保护

为了防止对象被无意间修改, JavaScript 提供了三种机制: 冻结, 封装, 阻止扩展。

操作常规对象冻结对象密封对象不可扩展对象
添加属性允许禁止禁止禁止
读取属性允许允许允许允许
设置属性值允许禁止允许允许
重新配置属性允许禁止禁止允许
删除属性允许禁止禁止允许
  • 冻结

冻结可以阻止对象的任何修改 (读取是没问题的)。

const appInfo = {
    company: 'white knight software, Inc.',
    version: '1.3.5'
}
Object.freeze(appInfo);
Object.isFrozen(appInfo);    // true
# 下面语句会报错
Object.defineProperty(appInfo, 'company', {enumerable: false});
  • 封装对象

封装对象能够阻止在对象上添加属性, 重新配置属性, 移除已有属性。

封装可用在类的实例上, 这时操作对象属性的方法仍然有效。

class Logger {
    constructor(name) {
        this.name = name;
        this.log = [];
    }
    add(entry) {
        this.log.push({
            log: entry,
            timestamp: Date.now()
        });
    }
}
const log1 = new Logger('Ha');

Object.seal(log1);
Object.isSealed(log1);    // true

log1.name = "Pi";        // OK
log1.age = 18;            // TypeError ...
delete log1.name;        // TypeError ...
  • 阻止扩展 (little use)

阻止对象被扩展, 防止给类添加新属性, 仍可以复制, 删除, 重新配置属性

// 仍然使用上面的例子
const log2 = new Logger('Yes');

Object.preventExtensions(log2);
Object.isExtensible(log2);            // false

log2.name = "Pi";                    // OK
log2.age = 18;                        // TypeError ...
delete log1.name;                    // Ok

# 这里只能重新配置已有属性, 若是新属性, 会报错
Object.defineProperty(log2, 'log', {enumerable: false});    // Ok

四、代理

代理是 ES6 新属性, 具有程序自我修改的能力 (元编程)

const coffee = {
    a: 1,
    b: 2
}
const _proxy = new Proxy(coffee, {
    get(target, key) {
        return target[key] || 0
    }
});
# 当访问 coffee 没有的属性时, 会自动返回 0
console.log(coffee.a);    // 1
console.log(coffee.c);     // undefined
console.log(_proxy.a);    // 1
console.log(_proxy.c);    // 0

添加新评论