【问题标题】:how to use javascript Object.defineProperty如何使用javascript Object.defineProperty
【发布时间】:2023-03-16 09:25:01
【问题描述】:

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

有人给我this snippet of code

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

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

【问题讨论】:

标签: javascript object defineproperty


【解决方案1】:

既然你问了similar question,那就一步步来吧。它有点长,但它可能会比我花在写这篇文章上的时间来节省你更多的时间:

Property 是一种 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#中,将字段转换为属性是breaking change,因此如果您的代码可能在单独编译的客户端中使用,则应将公共字段编码为Auto-Implemented Properties

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

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

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

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

* 除非在 strict mode - 在这种情况下 JS 会停止执行 TypeError 除非它被 try-catch block 捕获

要阅读这些设置,请使用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(&lt;yourObject&gt;) 检查是否在对象上使用了该方法。预防是浅的(阅读下文)。
  • Object.seal(yourObject) 同上,属性不能被移除(有效地将configurable: false设置为所有属性)。使用Object.isSealed(&lt;yourObject&gt;) 检测对象上的此功能。印章(请阅读下文)。
  • Object.freeze(yourObject) 同上,属性不能改变(有效地将writable: false设置为所有带有数据描述符的属性)。 Setter 的可写属性不受影响(因为它没有)。冻结是:这意味着如果属性是对象,它的属性不会被冻结(如果你愿意,你应该执行类似“深度冻结”的操作,类似于deep copy - cloning)。使用Object.isFrozen(&lt;yourObject&gt;) 进行检测。

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

【讨论】:

  • 这部分很清楚。 function Product(name,price) { this.name = name; this.price = price; var _discount; // private member Object.defineProperty(this,"discount",{ get: function() { return _discount; }, set: function(value) { _discount = value; if(_discount&gt;80) _discount = 80; } }); } 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
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    注意:您直接在 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/

    【讨论】:

      【解决方案4】:

      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 对象,它的值将是一个函数和三个参数可写、可枚举和可配置。现在我只是将所有内容都声明为 true。

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

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

      可写

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

      如果你再次运行它,一切都会中断。 让我们将 writeable 更改为 false。如果再次运行相同的程序,您将获得正确的输出为“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 来限制此行为。

      Orginal reference of this information is from my personal Blog

      【讨论】:

      • 我知道你的博客上有这个,只是把它贴在这里,但至少要知道这一点:屏幕截图在 SO 上并不流行。您不能复制粘贴代码进行尝试,搜索引擎或辅助技术将看不到代码。
      • @JacqueGoupil 你是对的。我将通过添加代码而不是屏幕截图来更新
      【解决方案5】:

      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

      【讨论】:

        【解决方案6】:

        总结:

        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. 可配置 &lt;boolean&gt;:如果true,属性描述符可能会被更改,并且属性可能会从对象中删除。如果可配置为false,则Object.defineProperty 中传递的描述符属性无法更改。
        2. 可写 &lt;boolean&gt;:如果true,则可以使用赋值运算符覆盖该属性。
        3. 可枚举 &lt;boolean&gt;:如果true,则可以在for...in 循环中迭代该属性。此外,当使用Object.keys 功能时,该键将出现。如果属性为 false,则不会使用 for..in 循环对其进行迭代,并且在使用 Object.keys 时不会显示。
        4. get &lt;function&gt; : 需要属性时调用的函数。调用此函数而不是直接给出值,并将返回值作为属性的值给出
        5. set &lt;function&gt; :每当分配属性时调用的函数。调用此函数并使用返回值来设置属性的值,而不是设置直接值。

        示例:

        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
        }

        【讨论】:

          【解决方案7】:

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

          console.log(player.health);
          

          实际上并没有太大的不同:

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

          get 的反义词是 set,当你赋值给 value 时会用到。由于没有二传手,似乎不打算分配给玩家的健康:

          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

          【讨论】:

          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

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

            【讨论】:

              【解决方案10】:

              是的,不再为 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);
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2022-06-10
                • 2011-10-31
                • 1970-01-01
                • 1970-01-01
                • 2012-01-16
                • 1970-01-01
                相关资源
                最近更新 更多