70

我想知道什么时候应该使用

Object.defineProperty

为对象创建新属性。我知道我可以设置类似的东西

enumerable: false

但是你什么时候真的需要这个?如果你只是设置一个属性

myObject.myprop = 5;

它的描述符都设置为true,对吗?当你们对 .defineProperty() 使用相当冗长的调用以及出于什么原因时,我实际上更好奇。

4

10 回答 10

48

Object.defineProperty主要用于设置具有特定属性描述符的属性(例如只读(常量)、可枚举性(在for (.. in ..)循环中不显示属性、getter、setter)。

"use strict";
var myObj = {}; // Create object
// Set property (+descriptor)
Object.defineProperty(myObj, 'myprop', {
    value: 5,
    writable: false
});
console.log(myObj.myprop);// 5
myObj.myprop = 1;         // In strict mode: TypeError: myObj.myprop is read-only

例子

此方法使用属性扩展Object原型。仅定义了 getter,并且可枚举性设置为false

Object.defineProperty(Object.prototype, '__CLASS__', {
    get: function() {
        return Object.prototype.toString.call(this);
    },
    enumerable: false // = Default
});
Object.keys({});           // []
console.log([].__CLASS__); // "[object Array]"
于 2012-04-11T12:24:43.720 回答
29

在我的经验中,很少使用像“可枚举”这样的功能。主要用例是计算属性:

var myObj = {};

myObj.width = 20;
myObj.height = 20;

Object.defineProperty(myObj, 'area', {
    get: function() {
        return this.width*this.height;
    }
});
console.log(myObj.area);
于 2014-01-10T13:23:53.663 回答
18

使用 Object.defineProperty 的一个非常好的理由是,它允许您循环遍历对象中的函数作为计算属性,它执行函数而不是返回函数的主体。

例如:

var myObj = {};

myObj.width = 20;
myObj.height = 20;

Object.defineProperty(myObj, 'area', {
    get: function() {
        return this.width*this.height;
    },
    enumerable: true
});

for (var key in myObj) {
  if (myObj.hasOwnProperty(key)) {
    console.log(key + " -> " + myObj[key]);
  }
}
//width -> 20, height -> 20, area -> 400

与将函数作为属性添加到对象字面量相比:

var myObj = {};

myObj.width = 20;
myObj.height = 20;

myObj.area = function() {
       return this.width*this.height;
    };

for (var key in myObj) {
  if (myObj.hasOwnProperty(key)) {
    console.log(key + " -> " + myObj[key]);
  }
}
// width -> 20, height -> 20, area -> function() { return this.width*this.height;}

确保将 enumerable 属性设置为 true 以便循环遍历它。

于 2016-05-06T11:37:51.613 回答
6

例如,这就是 Vue.js 跟踪data对象变化的方式:

当您将纯 JavaScript 对象作为选项传递给 Vue 实例时data,Vue 将遍历其所有属性并将它们转换为getter/settersusing Object.defineProperty。这是一个仅限 ES5 且不可填充的功能,这就是 Vue 不支持 IE8 及更低版本的原因。

getter/setter 对用户是不可见的,但在底层,它们使 Vue 能够在访问或修改属性时执行依赖跟踪和更改通知。

[...]

请记住,即使是 Vue.js 的超薄和基本版本也会使用更多的东西Object.defineProperty,但主要功能来自于它:

Vue.js 的反应周期

在这里你可以看到一篇文章,作者实现了类似 Vue.js 的最小 PoC 版本:https ://medium.com/js-dojo/understand-vue-reactivity-implementation-step-by-step-599c3d51cd6c

这里有一个演讲(西班牙语),演讲者在解释 Vue.js 中的反应性时构建了类似的东西:https://www.youtube.com/watch? v=axXwWU-L7RM

于 2019-08-13T22:20:56.530 回答
3

概括:

在 Javascript 中,对象是键值对的集合。 Object.defineProperty()是一个可以在对象上定义新属性并可以设置属性的以下属性的函数:

  • value <any>与键关联的值
  • writable <boolean>:如果 writable 设置为true可以通过为其分配新值来更新该属性。如果设置为false您无法更改该值。
  • enumerable <boolean>如果 enumerable 设置为trueProperty 可以通过for..in循环访问。此外,返回的唯一可枚举属性键Object.keys()
  • 可配置<boolean>如果可配置设置为false您无法更改属性属性(值/可写/可枚举/可配置),也因为您无法更改值,您无法使用delete运算符将​​其删除。

例子:

let obj = {};


Object.defineProperty(obj, 'prop1', {
      value: 1,
      writable: false,
      enumerable: false,
      configurable: false
});   // create a new property (key=prop1, value=1)


Object.defineProperty(obj, 'prop2', {
      value: 2,
      writable: true,
      enumerable: true,
      configurable: true
});  // create a new property (key=prop2, value=2)


console.log(obj.prop1, obj.prop2); // both props exists

for(const props in obj) {
  console.log(props);
  // only logs prop2 because writable is true in prop2 and false in prop1
}


obj.prop1 = 100;
obj.prop2 = 100;
console.log(obj.prop1, obj.prop2);
// only prop2 is changed because prop2 is writable, prop1 is not


delete obj.prop1;
delete obj.prop2;

console.log(obj.prop1, obj.prop2);
// only prop2 is deleted because prop2 is configurable and prop1 is not

于 2018-09-04T18:26:08.413 回答
2

Object.defineProperty 防止您不小心将值分配给原型链中的某个键。使用此方法,您只分配给该特定对象级别(而不是原型链中的任何键)。

例如:有一个对象{key1: value1, key2: value2},你不知道它的原型链,或者你错误地错过了它,那么原型链中的某个地方有一些属性“颜色”-

使用点(。)分配 -

此操作将为原型链中的键“颜色”赋值(如果键存在于某处),您会发现对象没有任何变化,如 . obj.color='蓝色'; // obj 保持与 {key1: value1, key2: value2} 相同

使用 Object.defineProperty 方法-

Object.defineProperty(obj, 'color', {
  value: 'blue'
});

// 现在 obj 看起来像{key1: value1, key2: value2, color: 'blue'}. 它将属性添加到同一级别。然后您可以使用方法安全地迭代Object.hasOwnProperty()

于 2018-11-10T12:11:54.173 回答
2

我见过的一个简洁的用例defineProperty是库向用户提供一个错误属性,如果在某个时间间隔内没有访问它,您将自己记录错误。例如:

let logErrorTimeoutId = setTimeout(() => {
  if (error) {
    console.error('Unhandled (in <your library>)', error.stack || error);
  }
}, 10);

Object.defineProperty(data, 'error', {
    configurable: true,
    enumerable: true,
    get: () => {
      clearTimeout(logErrorTimeoutId);
      return error;
    },
  });

此代码的来源:https ://github.com/apollographql/react-apollo/blob/ddd3d8faabf135dca691d20ce8ab0bc24ccc414e/src/graphql.tsx#L510

于 2017-06-22T18:38:32.357 回答
1

一个很好的用途是当您需要以优雅的方式进行一些拦截或应用经典的 Observer/Observable 模式时:

https://www.monterrail.com/blog/2016/how-to-build-a-reactive-engine-in-javascript-part-1-observable-objects

于 2017-07-06T14:30:51.397 回答
0

一个非常有用的案例是监视对某些事物的更改并对其采取行动。这很容易,因为您可以在设置值时触发回调函数。这是一个基本的例子。

您有一个Player可以播放或不播放的对象。您希望在它开始播放和停止播放时立即发生某些事情。

function Player(){}
Object.defineProperty(Player.prototype, 'is_playing', {
  get(){
    return this.stored_is_playing;  // note: this.is_playing would result in an endless loop
  },
  set(newVal){
    this.stored_is_playing = newVal;
    if (newVal === true) {
      showPauseButton();
    } else {
      showPlayButton();
    }
  }
});
const cdplayer = new Player();
cdplayer.is_playing = true; // showPauseButton fires 

此答案与此处的其他几个答案相关,这些答案是获取更多信息的好起点,但无需遵循外部链接即可阅读有关库或编程范例的信息。

于 2019-09-28T19:21:43.640 回答
-2

@杰拉德辛普森

如果 'area' 应该是可枚举的,它也可以在没有 Object.defineProperty 的情况下编写。

var myObj = {
    get area() { return this.width * this.height }
};

myObj.width = 20;
myObj.height = 20;

for (var key in myObj) {
  if (myObj.hasOwnProperty(key)) {
    console.log(key + " -> " + myObj[key]);
  }
}

//area -> 400, width -> 20, height -> 20
于 2016-12-11T11:22:29.720 回答