js_Object.defineproperty()

本 post 仿自 MDN

MDN_js_Object.defineProperty

在看 vue 的时候,遇到这个方法,所以拿出来看了下,棒棒哒!

Object.defineProperty() static method

Object.defineProperty() 方法会直接在一个对象上定义(add)一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

Syntax

Object.defineProperty(obj, propName, descriptor)

params

Obj: 要在其上定义属性的对象。

prop: 要定义或修改的属性的名称

descriptor: 将被定义或修改的属性描述符

Note: 在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty 是定义key为Symbol的属性的方法之一。

Decription

该方法允许精确添加或修改对象的属性。通过赋值来添加的普通属性会创建在属性枚举期间显示的属性(for…in 或 Object.keys 方法), 这些值可以被改变,也可以被删除。这种方法允许这些额外的细节从默认值改变。默认情况下,使用Object.defineProperty()添加的属性值是不可变的。

Property Descriptor

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。

数据描述符和存取描述符均具有以下可选键值:

  1. configurable: 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
// 不可配置
var o = {};
Object.defineProperty(o, 'demo', {
    configurable: true,
    enumerable: false,
    value: 13
});

// delete o.demo  ---   false
// o.demo  ----  13
  1. enumerable: 当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中(也就是说可以遍历出来,可以使用Object.keys获得key)。默认为 false。
// 不可枚举
var o = {};
Object.defineProperty(o, 'demo', {
    configurable: true,
    enumerable: false,
    value: 13
});

// o {demo: 13}
// o.demo   13
// Object.keys(o)  --- []
// for (var k in o) {console.log(o[k])}  --- undefined

数据描述符同时具有以下可选键值:

  1. value: 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。

  2. writable: 当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。

    • Object 本身有个 length 属性成员 应该就是 不可写的(只读的)
var o = {};
Object.defineProperty(o, 'demo', {
    configurable: true,
    enumerable: true,
    value: 13,
    writable: false
})

// o.demo --- 13
// o.demo = 10 --- 10
// o.demo --- 13

存取描述符同时具有以下可选键值:

  1. get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。

  2. set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。

如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。

记住,这些选项不一定是自身属性,如果是继承来的也要考虑。为了确认保留这些默认值,你可能要在这之前冻结 Object.prototype,明确指定所有的选项,或者将proto属性指向null。


// 使用 __proto__
var obj = {};
var descriptor = Object.create(null); // 没有继承的属性
// 默认没有 enumerable,没有 configurable,没有 writable 
// 默认全为 false
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);

// 显式
// 与上述的写法效果是一样的
Object.defineProperty(obj, 'key', {
    enumerable: false,
    configurable: false,
    writable: false,
    value: 'static'
});

// 循环使用同一对象
function withValue(value) {
    // 对象没有的话就赋值一次
    var d = withValue.d || (withValue.d = {
        enumerable: false,
        configurable: false,
        writable: false,
        value: null
    });
    d.value = value;
    return d;
}

Object.defineProperty(obj, 'key', withValue('static'));


// 如果 freeze 可用, 防止代码添加或删除对象原型的属性
// (value, get, set, enumerable, writable, configurable)
(Object.freeze||Object)(Object.prototype);

examples

创建(新增)属性

新增属性的时候 也是 2(公共属性) + 2(描述符) mode

如果对象中不存在指定的属性,Object.defineProperty()就创建这个属性。当描述符中省略某些字段时,这些字段将使用它们的默认值。拥有布尔值的字段的默认值都是false。value,get和set字段的默认值为undefined。一个没有get/set/value/writable定义的属性被称为“通用的”,并被“键入”为一个数据描述符

var o = {}; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
// 一个普通的属性成员添加 boolean 值为 true
Object.defineProperty(o, 'a', {
    configurable: true,
    enumerable: true,
    value: 13,
    writable: true
});

// 在对象中添加一个属性与存取描述符的示例

var bValue;
Object.defineProperty(o, "b", {
    get: function () {
        return bValue;
    },
    set: function (newValue) {
        bValue = newValue;
    },
    enumerable: true,
    configurable: true
});
o.b = 38;
// o.b的值现在总是与bValue相同,除非重新定义o.b

// o.b的值现在总是与bValue相同,除非重新定义o.b

修改属性

如果属性已经存在,Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。如果旧描述符将其configurable 属性设置为false,则该属性被认为是“不可配置的”,并且没有属性可以被改变(除了单向改变 writable 为 false)。当属性不可配置时,不能在数据和访问器属性类型之间切换

当试图改变不可配置属性(configurable,enumerable)(除了writable 属性之外)的值时会抛出{jsxref(“TypeError”)}},除非当前值和新值相同。

writable 属性

严格模式下会报错, 非严格模式下,赋值 writable: false 属性不会报错

当writable属性设置为false时,该属性被称为“不可写”。它不能被重新赋值。

var o = {}; // Creates a new object

Object.defineProperty(o, 'a', {
  value: 37,
  writable: false
});

console.log(o.a); // logs 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); // logs 37. The assignment didn't work.

// strict mode
// 在严格模式下, writable 为 false ,赋值的时候会 throw errors;
(function () {
    'use strict';
    var o = {};
    Object.defineProperty(o, 'a', {
        value: 2,
        writable: false;
    });
    o.b = 3;  // Object.defineProperty.html:46 Uncaught TypeError: Cannot assign to read only property 'b' of object '#<Object>'
    return o.b;
}());

Enumerable 特性

enumerable定义了对象的属性是否可以在 for…in 循环和 Object.keys() 中被枚举。

var o = {}; // Creates a new object

Object.defineProperty(o, 'a', {
  value: 37,
  writable: false
});

console.log(o.a); // logs 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); // logs 37. The assignment didn't work.

// strict mode
// 在严格模式下, writable 为 false ,赋值的时候会 throw errors;
(function () {
    'use strict';
    var o = {};
    Object.defineProperty(o, 'a', {
        value: 2,
        writable: false;
    });
    o.b = 3;  // Object.defineProperty.html:46 Uncaught TypeError: Cannot assign to read only property 'b' of object '#<Object>'
    return o.b;
}());

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
 });

 o.d = 4;

for (var k in o) {
    console.log(o[k]);

}
// 1 , 4

Object.keys(o); // ["a", "d"]

o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
o.propertyIsEnumerable('d'); // true

Configurable 特性

configurable特性表示:

在 chrome 下, writable 特性也不能修改

  1. 对象的属性是否可以被删除,
  2. 以及除writable特性外的其他特性是否可以被修改。
var o = {};
Object.defineProperty(o , 'a', {
    get: function () {
        return 1;
    },
    configurable: false
});

Object.defineProperty(o, 'a', {
    configurable: true
}); // VM8936:1 Uncaught TypeError: Cannot redefine property: a

Object.defineProperty(o, 'a', {
    enumerable: true
}); //  Uncaught TypeError: Cannot redefine property: a

Object.defineProperty(o, 'a', {
    set: function () {}
}); // VM8999:2 Uncaught TypeError: Cannot redefine property: a

Object.defineProperty(o, 'a', {
    get: function () {
        return 1;
    }
}); // Uncaught TypeError: Cannot redefine property: a
Object.defineProperty(o, 'a', {
    value: 12
}); // VM9003:1 Uncaught TypeError: Cannot redefine property: a

Object.defineProperty(o, 'a', {
    writable: true
}); // VM9023:1 Uncaught TypeError: Cannot redefine property: a

console.log(o.a); // 1
delete o.a; // false
console.log(o.a); // 1

如果o.a的configurable属性为true,则不会抛出任何错误,并且该属性将在最后被删除。

添加多个属性和默认值

考虑特性被赋予的默认特性值非常重要,通常,使用点运算符Object.defineProperty()为对象的属性赋值时,数据描述符中的属性默认值是不同的,如下例所示。

var o = {};
o.a = 1;
// 等价于

Object.defineProperty(o, 'a', {
    configurable: true,
    enumerable: true,
    value: 1,
    writable: true
});

// 另一方面,

Object.defineProperty(o, 'a',{ value: 1});
// 等价于
// 因为 配置对象中, 所有的boolean 值默认为 false
Object.defineProperty(o, 'a', {
    configurable: false,
    enumerable: false,
    value: 1,
    writable: false
});

一般的 Setters 和 Getters

下面的例子展示了如何实现一个自存档对象。 当设置temperature 属性时,archive 数组会获取日志条目。

// 1. temp archive 变量
// 2. 对象 set 时候 保存
// 3. 将保存的东西 获取到
function Archive () {
    // 感觉 temperature 就像共享变量一样
    var temperature = null;
    var archiver = [];
    Object.defineProperty(this, 'temperature', {
        get: function () {
            console.log('gettor!');
            return temperature;
        },
        set: function (value) {
            temperature = value;
            archiver.push({val: temperature});
        }
    });
    this.getArchiver = function () { return archiver};
}
var instance = new Archive();
console.log(instance.temperature);
instance.temperature = 11;
instance.temperature = 12;
instance.getArchiver();

// gettor
// [{val: 11},{val: 12}]

或者

var pattern = {
    get: function () {
        return 'I alway return this string,whatever you have assigned';
    },
    set: function () {
        this.myname = 'this is my name string';
    }
};
function TestDefineSetAndGet () {
    Object.defineProperty(this, 'myproperty', pattern);
}

var instance = new TestDefineSetAndGet();
// writable 只能和 value 一起使用
// 所以说 存取器描述符定义 的属性都是可以 读写的
instance.myproperty = 'test';

console.log(instance.myproperty); 
// 'I alway return this string,whatever you have assigned'
console.log(instance.myname);
// 'this is my name string'

set 和 get 简单理解:

  1. 在 访问对象属性的时候,对应的属性 gettor 会被调用,得到属性值
  2. 在对对象属性 写(赋值)的时候,对应的 settor 会对调用,属性被重新赋值
  3. settor 和 gettor 是两个钩子函数, 很好用
  4. set 和 get 中 this 指向 被定义的对象

猜你喜欢

转载自blog.csdn.net/palmer_kai/article/details/80183823