186

我四处寻找如何使用该Object.defineProperty方法,但找不到任何像样的东西。

有人给了我这段代码

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
})

但我不明白。主要是,这get是我无法得到的(双关语)。它是如何工作的?

4

10 回答 10

526

既然你问了类似的问题,让我们一步一步来。它有点长,但它可以为您节省更多的时间,而不是我花在写这篇文章上的时间:

属性是一种 OOP 特性,旨在清晰地分离客户端代码。例如,在某些电子商店中,您可能有这样的对象:

function Product(name,price) {
  this.name = name;
  this.price = price;
  this.discount = 0;
}

var sneakers = new Product("Sneakers",20); // {name:"Sneakers",price:20,discount:0}
var tshirt = new Product("T-shirt",10);  // {name:"T-shirt",price:10,discount:0}

然后在您的客户代码(电子商店)中,您可以为您的产品添加折扣:

function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }

之后,网店老板可能会意识到折扣不能大于 80%。现在您需要在客户端代码中找到每次出现的折扣修改并添加一行

if(obj.discount>80) obj.discount = 80;

那么网店老板可能会进一步改变他的策略,比如“如果客户是经销商,最大折扣可以是90%”。而且您需要再次在多个地方进行更改,而且您需要记住在策略更改时更改这些行。这是一个糟糕的设计。这就是为什么封装是OOP的基本原则。如果构造函数是这样的:

function Product(name,price) {
  var _name=name, _price=price, _discount=0;
  this.getName = function() { return _name; }
  this.setName = function(value) { _name = value; }
  this.getPrice = function() { return _price; }
  this.setPrice = function(value) { _price = value; }
  this.getDiscount = function() { return _discount; }
  this.setDiscount = function(value) { _discount = value; } 
}

然后你可以改变getDiscount( accessor ) 和setDiscount( mutator ) 方法。问题是大多数成员的行为都像公共变量,只是折扣在这里需要特别注意。但是好的设计需要封装每个数据成员以保持代码的可扩展性。所以你需要添加很多什么都不做的代码。这也是一个糟糕的设计,一个样板的反模式。有时您不能只是稍后将字段重构为方法(eshop 代码可能会变大,或者某些第三方代码可能依赖于旧版本),因此这里的样板文件不那么邪恶。但是,它仍然是邪恶的。这就是为什么将属性引入许多语言的原因。您可以保留原始代码,只需将折扣成员转换为属性getset块:

function Product(name,price) {
  this.name = name;
  this.price = price;
//this.discount = 0; // <- remove this line and refactor with the code below
  var _discount; // private member
  Object.defineProperty(this,"discount",{
    get: function() { return _discount; },
    set: function(value) { _discount = value; if(_discount>80) _discount = 80; }
  });
}

// the client code
var sneakers = new Product("Sneakers",20);
sneakers.discount = 50; // 50, setter is called
sneakers.discount+= 20; // 70, setter is called
sneakers.discount+= 20; // 80, not 90!
alert(sneakers.discount); // getter is called

请注意最后一行:正确折扣值的责任已从客户代码(电子商店定义)转移到产品定义。产品负责保持其数据成员的一致性。如果代码的工作方式与我们的想法相同,那么(粗略地说)就是好的设计。

这么多关于属性。但是 javascript 与像 C# 这样的纯面向对象语言不同,并且对功能进行了不同的编码:

在 C#中,将字段转换为属性是一项重大更改,因此如果您的代码可能在单独编译的客户端中使用,则应将公共字段编码为自动实现的属性。

在 Javascript中,标准属性(具有上述 getter 和 setter 的数据成员)由访问器描述符(在您的问题中的链接中)定义。排他地,您可以使用数据描述符(因此您不能使用 ie并在同一属性上设置):

  • 访问器描述符= get + set(参见上面的示例)
    • get必须是一个函数;它的返回值用于读取属性;如果未指定,则默认为undefined,其行为类似于返回 undefined 的函数
    • set必须是一个函数;在给属性赋值时,它的参数用 RHS 填充;如果未指定,则默认为undefined,其行为类似于空函数
  • 数据描述符= 值 + 可写(参见下面的示例)
    • 默认未定义;如果可写可配置可枚举(见下文)为真,则该属性的行为类似于普通数据字段
    • 可写- 默认false;如果不是true,则该属性是只读的;尝试写入被忽略,没有错误*!

两个描述符都可以有以下成员:

  • 可配置- 默认false;如果不为真,则无法删除该属性;尝试删除被忽略,没有错误*!
  • 可枚举- 默认false;如果为真,它将被迭代for(var i in theObject);如果为 false,则不会被迭代,但仍可作为公共访问

* 除非在严格模式下- 在这种情况下,JS 会使用 TypeError 停止执行,除非它被try-catch 块捕获

要阅读这些设置,请使用Object.getOwnPropertyDescriptor().

通过示例学习:

var o = {};
Object.defineProperty(o,"test",{
  value: "a",
  configurable: true
});
console.log(Object.getOwnPropertyDescriptor(o,"test")); // check the settings    

for(var i in o) console.log(o[i]); // nothing, o.test is not enumerable
console.log(o.test); // "a"
o.test = "b"; // o.test is still "a", (is not writable, no error)
delete(o.test); // bye bye, o.test (was configurable)
o.test = "b"; // o.test is "b"
for(var i in o) console.log(o[i]); // "b", default fields are enumerable

如果您不希望允许客户端代码进行此类作弊,您可以通过三个级别的限制来限制对象:

  • Object.preventExtensions(yourObject)防止将新属性添加到yourObject。用于Object.isExtensible(<yourObject>)检查是否在对象上使用了该方法。预防很(阅读下文)。
  • Object.seal(yourObject)同上,属性不能被移除(有效设置configurable: false为所有属性)。用于Object.isSealed(<yourObject>)检测对象上的此特征。封印很(见下文)。
  • Object.freeze(yourObject)同上,属性不可更改(有效设置writable: false为所有带有数据描述符的属性)。Setter 的可写属性不受影响(因为它没有)。冻结是的:这意味着如果属性是对象,它的属性不会被冻结(如果你愿意,你应该执行类似“深度冻结”的操作,类似于深度复制 - 克隆)。用来Object.isFrozen(<yourObject>)检测它。

如果你只写几行有趣的东西,你就不需要为此烦恼。但是,如果您想编写游戏代码(正如您在链接问题中提到的那样),您应该关心良好的设计。尝试在 Google 上搜索有关反模式代码异味的信息。它将帮助您避免诸如“哦,我需要再次完全重写我的代码!”之类的情况。,如果您想大量编写代码,它可以为您节省数月的绝望。祝你好运。

于 2013-09-01T10:29:06.800 回答
27

get是当您尝试读取 value 时调用的函数player.health,例如:

console.log(player.health);

它实际上与以下内容没有太大区别:

player.getHealth = function(){
  return 10 + this.level*15;
}
console.log(player.getHealth());

设置 get 的反义词,当您分配值时将使用它。由于没有设置器,似乎不打算分配给玩家的健康:

player.health = 5; // Doesn't do anything, since there is no set function defined

一个非常简单的例子:

var player = {
  level: 5
};

Object.defineProperty(player, "health", {
  get: function() {
    return 10 + (player.level * 15);
  }
});

console.log(player.health); // 85
player.level++;
console.log(player.health); // 100

player.health = 5; // Does nothing
console.log(player.health); // 100

于 2013-08-30T04:00:06.547 回答
18

defineProperty是 Object 上的一种方法,它允许您配置属性以满足某些条件。这是一个简单的示例,其中一个员工对象具有两个属性 firstName 和 lastName,并通过覆盖对象上的toString方法来附加这两个属性。

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
employee.toString=function () {
    return this.firstName + " " + this.lastName;
};
console.log(employee.toString());

你会得到输出: Jameel Moideen

我将通过在对象上使用 defineProperty 来更改相同的代码

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: true,
    enumerable: true,
    configurable: true
});
console.log(employee.toString());

第一个参数是对象的名称,然后第二个参数是我们要添加的属性的名称,在我们的例子中是 toString,最后一个参数是 json 对象,它的值将是一个函数和三个可写、可枚举的参数和可配置的。现在我刚刚宣布一切都是真的。

如果你运行这个例子,你会得到输出:Jameel Moideen

让我们了解为什么我们需要可写、可枚举和可配置这三个属性。

可写

javascript 中非常烦人的部分之一是,如果您将 toString 属性更改为其他内容,例如

在此处输入图像描述

如果你再次运行它,一切都会中断。让我们将可写更改为假。如果再次运行相同的程序,您将获得正确的输出为 'Jameel Moideen' 。此属性将防止以后覆盖此属性。

可枚举的

如果您打印对象内的所有键,您可以看到包括 toString 在内的所有属性。

console.log(Object.keys(employee));

在此处输入图像描述

如果将 enumerable 设置为 false ,则可以对其他人隐藏 toString 属性。如果再次运行,您将获得 firstName,lastName

可配置

如果有人稍后重新定义对象,例如 enumerable 为 true 并运行它。可以看到 toString 属性又来了。

var employee = {
    firstName: "Jameel",
    lastName: "Moideen"
};
Object.defineProperty(employee, 'toString', {
    value: function () {
        return this.firstName + " " + this.lastName;
    },
    writable: false,
    enumerable: false,
    configurable: true
});

//change enumerable to false
Object.defineProperty(employee, 'toString', {

    enumerable: true
});
employee.toString="changed";
console.log(Object.keys(employee));

在此处输入图像描述

您可以通过将可配置设置为 false 来限制此行为。

此信息的原始参考来自我的个人博客

于 2017-05-27T18:28:57.400 回答
2

基本上,defineProperty是一种接受 3 个参数的方法 - 一个对象、一个属性和一个描述符。在这个特定的调用中发生的事情是对象的"health"属性player被分配到该玩家对象级别的 10 加 15 倍。

于 2013-08-30T04:02:36.337 回答
0

Object.defineProperty() 是一个全局函数..它在声明对象的函数内部不可用。您必须静态使用它...

于 2015-01-26T20:45:37.347 回答
0

是的,不再为 setup setter & getter 扩展功能这是我的示例Object.defineProperty(obj,name,func)

var obj = {};
['data', 'name'].forEach(function(name) {
    Object.defineProperty(obj, name, {
        get : function() {
            return 'setter & getter';
        }
    });
});


console.log(obj.data);
console.log(obj.name);
于 2014-07-30T18:07:10.040 回答
0

Object.defineProperty(Array.prototype, "last", {
  get: function() {
    if (this[this.length -1] == undefined) { return [] }
    else { return this[this.length -1] }
  }
});

console.log([1,2,3,4].last) //returns 4

于 2020-04-22T07:06:00.910 回答
0

import { CSSProperties } from 'react'
import { BLACK, BLUE, GREY_DARK, WHITE } from '../colours'

export const COLOR_ACCENT = BLUE
export const COLOR_DEFAULT = BLACK
export const FAMILY = "'Segoe UI', sans-serif"
export const SIZE_LARGE = '26px'
export const SIZE_MEDIUM = '20px'
export const WEIGHT = 400

type Font = {
  color: string,
  size: string,
  accent: Font,
  default: Font,
  light: Font,
  neutral: Font,
  xsmall: Font,
  small: Font,
  medium: Font,
  large: Font,
  xlarge: Font,
  xxlarge: Font
} & (() => CSSProperties)

function font (this: Font): CSSProperties {
  const css = {
    color: this.color,
    fontFamily: FAMILY,
    fontSize: this.size,
    fontWeight: WEIGHT
  }
  delete this.color
  delete this.size
  return css
}

const dp = (type: 'color' | 'size', name: string, value: string) => {
  Object.defineProperty(font, name, { get () {
    this[type] = value
    return this
  }})
}

dp('color', 'accent', COLOR_ACCENT)
dp('color', 'default', COLOR_DEFAULT)
dp('color', 'light', COLOR_LIGHT)
dp('color', 'neutral', COLOR_NEUTRAL)
dp('size', 'xsmall', SIZE_XSMALL)
dp('size', 'small', SIZE_SMALL)
dp('size', 'medium', SIZE_MEDIUM)

export default font as Font

于 2019-02-24T20:44:34.810 回答
0

概括:

Object.defineProperty(player, "health", {
    get: function () {
        return 10 + ( player.level * 15 );
    }
});

Object.defineProperty用于在播放器对象上创建新属性。Object.defineProperty是一个原生存在于 JS 运行时环境中的函数,它采用以下参数:

Object.defineProperty(obj, prop, descriptor)

  1. 我们要在其上定义新属性的对象
  2. 我们要定义的新属性的名称
  3. 描述符对象

描述符对象是有趣的部分。在这里,我们可以定义以下内容:

  1. 可配置 <boolean>:如果true 属性描述符可以更改并且可以从对象中删除该属性。如果可配置是无法更改false传入的描述符属性。Object.defineProperty
  2. Writable <boolean>:如果true可以使用赋值运算符覆盖属性。
  3. Enumerable <boolean>:如果属性可以在循环true 中迭代。for...in此外,当使用该Object.keys功能时,该键也会出现。如果属性是,它们将不会使用循环false进行迭代,并且在使用.for..inObject.keys
  4. get <function>:需要属性时调用的函数。调用此函数而不是直接给出值,并将返回值作为属性的值给出
  5. set <function>:分配属性时调用的函数。调用此函数而不是设置直接值,并使用返回的值来设置属性的值。

例子:

const player = {
  level: 10
};

Object.defineProperty(player, "health", {
  configurable: true,
  enumerable: false,
  get: function() {
    console.log('Inside the get function');
    return 10 + (player.level * 15);
  }
});

console.log(player.health);
// the get function is called and the return value is returned as a value

for (let prop in player) {
  console.log(prop);
  // only prop is logged here, health is not logged because is not an iterable property.
  // This is because we set the enumerable to false when defining the property
}

于 2018-09-12T11:26:16.977 回答
0

直接在对象上定义新属性,或修改对象上的现有属性,并返回该对象。

注意:您直接在 Object 构造函数上调用此方法,而不是在 Object 类型的实例上调用。

   const object1 = {};
   Object.defineProperty(object1, 'property1', {
      value: 42,
      writable: false, //If its false can't modify value using equal symbol
      enumerable: false, // If its false can't able to get value in Object.keys and for in loop
      configurable: false //if its false, can't able to modify value using defineproperty while writable in false
   });

在此处输入图像描述

关于定义属性的简单解释。

示例代码:https ://jsfiddle.net/manoj_antony32/pu5n61fs/

于 2019-07-30T13:31:55.997 回答