什么时候使用Object.defineProperty()


67

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

Object.defineProperty

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

enumerable: false

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

myObject.myprop = 5;

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

Answers:


45

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使用属性扩展原型。仅定义了吸气剂,可枚举性设置为false

Object.defineProperty(Object.prototype, '__CLASS__', {
    get: function() {
        return Object.prototype.toString.call(this);
    },
    enumerable: false // = Default
});
Object.keys({});           // []
console.log([].__CLASS__); // "[object Array]"

好,我明白了。但这有意义吗?大多数时候,我希望所有描述符都设置为true,所以我可以按照旧的方式设置属性,对吗?.defineProperty还有其他优势吗?
Andre Meinhold

@RobW:您也可以创建getterssetters而无需使用.defineProperty
jAndy 2012年

@jAndy但是该属性将在for (.. in ..)循环中Object.prototype.__defineGetter__('lol',function(){return 3});for(var i in [])alert(i);显示:shows "lol"Object.defineProperty可以用于定义的getter / setter这不会在显示for (.. in ..)循环。
罗伯W

@RobW:我更多地考虑使用var foo = { get lol() { return 5;} };,但是问题仍然存在。
jAndy 2012年

13
@Andre:如果您看不到它们的用例,那么您就不需要它们了……当您需要它们时,您就会知道;)
Felix Kling

26

根据我的经验,很少使用“可枚举”之类的功能。主要用例是计算属性:

var myObj = {};

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

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

在我看来,就像您竭尽全力使该属性全部成为语义上非常灰色的区域的名称一样;
aaaaaa 2015年

@aaaaaa什么?灰色地带?
Pascalius

2
我只是不了解提供计算属性的用例,而不是简单地使其成为函数。例如,myObj.area = function(){返回this.width * this.height; }
aaaaaa 2015年

8
该函数是完全有效的,但是在某些情况下,您更喜欢属性而不是函数。
Pascalius

14
主要用途是,可以使用具有包装自定义逻辑的getter / setter定义单个属性,而不是使用两个独立的函数(例如:getFoo(),setFoo()),然后在单个对象上使用标准赋值运算符成员(例如:x = myObj.Foo或myObj.Foo = x)。这使您的代码更简单,并且在您希望防止序列化为JSON时发出值时非常有用。(例如:您的对象具有许多您不想通过网络发送的特定于UI的属性...)总而言之,这有助于JS更紧密地遵循标准的OO原则
Joshua Barker

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,以便循环遍历它。


5

例如,这就是Vue.js跟踪data对象中的更改的方式

当你传递一个普通的JavaScript对象到Vue的实例作为其data选项,Vue公司将走过它的所有属性并将其转换为getter/setters使用Object.defineProperty。这是仅ES5且不可调整的功能,这就是Vue不支持IE8及以下版本的原因。

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

[...]

请记住,即使是超薄且基本的Vue.js版本也将使用不仅仅是Object.defineProperty,但主要功能来自于此:

Vue.js的反应周期

在这里,您可以看到一篇文章,作者在其中实现了Vue.js之类的最低PoC版本: https

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


3

概要:

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

  • <any>与键关联的值
  • writable <boolean>如果将writable设置为true,则可以通过为其分配新值来更新该属性。如果设置为false,则无法更改该值。
  • enumerable <boolean>如果将enumerable设置为trueProperty,则可以通过for..in循环访问。此外,唯一返回的可枚举属性键Object.keys()
  • configurable <boolean>如果将configurable设置为,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


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


2

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

例如:有一个像 {key1: value1, key2: value2}而您不完全了解其原型链,或者错误地错过了它,并且原型链中某处有一些“颜色”属性,

使用dot(。)分配-

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

使用Object.defineProperty方法-

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

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


安尼丝(Anish)恭喜您获得第一个答案。非常详细的说明。:您可能会发现语法高亮显示在你的答案有帮助meta.stackoverflow.com/questions/274371/...
蒂姆·奥格威


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 

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


-2

@杰拉德·辛普森

如果“区域”应该是可枚举的,则也可以不使用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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.