Object.defineProperty()详解

Posted 孙群

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Object.defineProperty()详解相关的知识,希望对你有一定的参考价值。

目录

为什么要使用Object.defineProperty()?

我们对一个Object对象设置属性时,一般是通过对象的.操作符或者[]操作符直接赋值的,例如obj1.a = 1 或 obj1['a'] = 1,通过这种方式添加的属性后续可以更改属性值,并且默认该属性是可枚举的,即通过for (const key in obj1) 或 obj1.keys()均可访问到属性。如果我们想在新增属性后不允许再更改属性值或者将该属性设置为非枚举属性,那我们该如何处理呢?

此时我们就需要使用静态方法Object.defineProperty(obj, prop, descriptor),其可以通过定义属性的元数据信息精确地控制属性的行为。

方法签名

Object.defineProperty(obj, prop, descriptor)
  • 参数
    • obj
      Object,Required,要在其上定义属性的对象。
    • prop
      String|Symbol,Required,要定义或修改的属性的名称。
    • descriptor
      Object,Required,将被定义或修改的属性描述符。
  • 返回值
    Object,返回被传递给函数的对象obj。

方法描述

该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,能够在属性枚举期间呈现出来(for...in 或 Object.keys 方法), 这些属性的值可以被改变,也可以被删除。方法Object.defineProperty()允许修改默认的属性元数据配置。我们可以认为使用Object.defineProperty()定义的属性在使用上更加严格。

属性描述符

Object.defineProperty(obj, prop, descriptor)中的参数descriptor就是属性描述符,就是定义属性行为的元数据信息。属性描述符有两种主要形式:数据描述符存取描述符数据描述符是一个Boolean类型的元数据属性,值为true或false,用于定义对属性的某种操作行为是允许还是禁止。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一,即descriptor要么是数据描述符,要么是存取描述符,不能同时包含数据描述符和存取描述符。

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

    • configurable
      当且仅当该属性的 configurable 为 true 时,才能够再次修改该属性的属性描述符,同时该属性也能从对应的对象上被删除。默认为 false。
    • enumerable
      当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
  • 数据描述符除了具有configurableenumerable这两个元数据键值外,还具有以下可选键值:

    • value
      该属性的初始值。可以是任何有效的 javascript 值(数值,对象,函数等)。默认为 undefined。
    • writable
      当且仅当该属性的writable为true时,该属性才能被写入值。默认为 false。
  • 存取描述符除了具有configurableenumerable这两个元数据键值外,还具有以下可选键值:

    • get
      一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。默认为 undefined。
    • set
      一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined。
  • 描述符可同时具有的键值

    configurableenumerablevaluewritablegetset
    数据描述符YesYesYesYesNoNo
    存取描述符YesYesNoNoYesYes

    如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。

示例

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

    • 向对象中添加一个属性并设置数据描述符

      	const obj1 = ;
      
      	// 向对象obj1中添加一个属性a,并设置数据描述符的示例
      	Object.defineProperty(obj1, "a", 
          	value: 1,
          	writable: true,
          	enumerable: true,
          	configurable: true
      	);
      
      	// 对象obj1有了属性a,其值为1
      	console.log(obj1.a) // 输出: 1,表示属性a的【value: 1】默认值已生效
      
    • 向对象中添加一个属性并设置存取描述符

      	const obj1 = ;
      
      	// 向对象中添加一个属性b,并设置存取描述符的示例
      	var bValue;
      	Object.defineProperty(obj1, "b", 
         		get: function () 
              	return bValue;
          	,
          	set: function (newValue) 
              	bValue = newValue;
          	,
          	enumerable: true,
          	configurable: true
      	);
      
      	obj1.b = 2;
      
      	// 对象obj1有了属性b,其值为2
      	console.log(obj1.b); // 输出: 2,表示属性b的getter和setter均生效
      
    • 数据描述符和存取描述符不能混合使用

      	const obj1 = ;
      
      	// 数据描述符和存取描述符不能混合使用
      	// 抛出异常: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
      	Object.defineProperty(obj1, "c", 
          	value: 1,
          	get: function () 
              	return 2;
          	
      	);
      
  • writable
    writable设置为false时(默认值即是false),该属性被定义为只读属性,即只能读取该属性值,不能给该属性写入值。

    	const obj1 = ;
    
    	Object.defineProperty(obj1, 'a', 
        	value: 1, // 用于定义a的默认值为1
        	writable: true // 用于定义属性a可写
    	);
    
    	Object.defineProperty(obj1, 'b', 
        	value: 2, // 用于定义b的默认值为2
        	writable: false // 用于定义属性b不可写
    	);
    
    	obj1.a = 100; // 向属性a中写入值
    	console.log(obj1.a); // 输出: 100,表示属性a的【writable: true】已生效
    
    	// 严格模式下抛出异常 TypeError: Cannot assign to read only property 'b' of object '#<Object>'
    	// 非严格模式不抛出异常,但是不生效
    	obj1.b = 200; // 向属性b中写入值
    	console.log(obj1.b); // 输出: 1
    
  • enumerable
    enumerable定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

    	const obj1 = ;
    
    	Object.defineProperty(obj1, 'a', 
        	value: 1, // 用于定义a的默认值为1
        	enumerable: true // 用于定义属性a可以被枚举
    	);
    
    	Object.defineProperty(obj1, 'b', 
        	value: 2, // 用于定义b的默认值为2
        	enumerable: false // 用于定义属性b不可以被枚举
    	);
    
    	for (let key in obj1) 
        	console.log(key); // 输出: 'a'
    	
    
    	console.log(Object.keys(obj1)); // 输出: ['a']
    	console.log('a' in obj1); // 输出: true
    	console.log('b' in obj1); // 输出: true
    
  • get和set

    • 设置getter和setter的普通示例

         const obj1 = ;
      
         let v = null;
      
         Object.defineProperty(obj1, 'a', 
             get: function() 
                 return v;
             ,
             set: function(newValue) 
                 v = newValue;
             
         );
      
         obj1.a = 1;
         console.log(obj1.a); // 输出: 1,表示属性a的getter和setter均生效
      
    • 继承属性

         function Person() 
      
         // 在Person的原型上定义属性name,并设置getter以及setter,这样Person类的示例均能访问name属性,且实例的name属性互相隔离
         Object.defineProperty(Person.prototype, 'name', 
             get: function() 
                 return this._name;
             ,
             set: function(newValue) 
                 this._name = newValue;
             
         );
      
         const a = new Person();
         const b = new Person();
      
         a.name = 'zhangsan';
         b.name = 'lisi';
      
         console.log(a.name); // 输出: 'zhangsan'
         console.log(b.name); // 输入: 'lisi'
      
  • configurable
    configurable特性表示对象的属性是否可以被删除,以及能否通过再次调用Object.defineProperty()更改其他属性描述符的配置。

    • configurable用于表示对象的属性是否可以被删除的示例

          // 'use strict';
          
      	const obj1 = ;
      
      	Object.defineProperty(obj1, 'a', 
          	value: 1, // 用于定义a的默认值为1
          	configurable: true
      	);
      
      	Object.defineProperty(obj1, 'b', 
          	value: 2, // 用于定义b的默认值为2
          	configurable: false
      	);
      
      	delete obj1.a; // 从对象obj1中删除属性a
      	console.log(obj1.a); // 输出: undefined,表示删除属性a成功
      
      	// 严格模式下抛出异常 TypeError: Cannot delete property 'b' of #<Object>
          // 非严格模式下不会抛出异常,但是不生效
      	delete obj1.b; // 从对象obj2中删除属性b
      	console.log(obj1.b); // 输出: 2,表示删除属性b失败
      
    • configurable值为false表示属性描述符不能被再次修改的示例

          // 'use strict';
      
          const obj1 = ;
      
          Object.defineProperty(obj1, 'a', 
              value: 1, // 用于定义a的默认值为1
              configurable: false,
              writable: false, // 将属性a定义为只读不可写
              enumerable: false // 将属性a定义为不可枚举
          );
      
          // 严格模式会抛出异常
          // 非严格模式不会抛出异常,但不生效
          obj1.a = 100;
          console.log(obj1.a); // 输出: 1
          console.log(Object.keys(obj1)); // 输出: []
      
          // 抛出异常TypeError: Cannot redefine property: a
          // 说明当上次对属性a调用Object.defineProperty()设置configurable为false后,a的属性描述符不能被再次修改,即不能再次对属性a调用Object.defineProperty()方法
          Object.defineProperty(obj1, 'a', 
              value: 2, // 用于定义a的默认值为1
              configurable: true,
              writable: true, // 重新将属性a定义为可写
              enumerable: true // 重新将属性a定义为可枚举
          );
      
          // 由于上面抛出异常,以下代码均无法执行
          // console.log(obj1.a);
          // obj1.a = 200;
          // console.log(obj1.a);
          // console.log(Object.keys(obj1));
      
    • configurable值为true表示属性描述符可以被再次修改的示例

          // 'use strict';
      
          const obj1 = ;
      
          Object.defineProperty(obj1, 'a', 
              value: 1, // 用于定义a的默认值为1
              configurable: true, // 表示可以对属性a再次调用Object.defineProperty()方法
          	writable: false, // 将属性a定义为只读不可写
          	enumerable: false // 将属性a定义为不可枚举
      	);
      
      	obj1.a = 100;
      	console.log(obj1.a); // 输出: 1
      	console.log(Object.keys(obj1)); // 输出: []
      
      	Object.defineProperty(obj1, 'a', 
          	value: 2, // 用于定义a的默认值为2
          	configurable: true,
          	writable: true, // 重新将属性a定义为可写
          	enumerable: true // 重新将属性a定义为可枚举
      	);
      
      	console.log(obj1.a); // 输出: 2,表示【writable: true】生效
      
      	obj1.a = 200;
      	console.log(obj1.a); // 输出: 200,表示【writable: true】生效
      	console.log(Object.keys(obj1)); // 输出: ['a'],表示【enumerable: true】生效
      	
      	let bValue = null;
          Object.defineProperty(obj1, 'b', 
              configurable: true, // 表示可以对属性a再次调用Object.defineProperty()方法
          	get() 
                  return bValue;
              ,
              set(newValue) 
                  bValue = newValue;
              
          );
      
          obj1.b = 'abcd';
          console.log(obj1.b); // 输出: 'abcd'
      
          Object.defineProperty(obj1, 'b', 
              configurable: true, // 表示可以对属性a再次调用Object.defineProperty()方法
          	get() 
                  return bValue.toString().toUpperCase();
              ,
              set(newValue) 
                  bValue = newValue + '_' + newValue;
              
          );
      
          obj1.b = 'efgh';
      
          console.log(obj1.b); // 输出: 'EFGH_EFGH',表示新的setter和getter已经生效
      

Object.defineProperties()

  • 描述
    静态方法Object.defineProperty(obj, prop, descriptor)只能每次对一个属性设置元数据配置信息,即每次只能对一个属性设置getter和setter,为了能够对多个属性同时设置元数据配置信息,Object还提供了静态方法Object.defineProperties(obj, props)。参数props是包含多个key-value的键值对对象,其中key是就是静态方法Object.defineProperty(obj, prop, descriptor)中的 prop 参数,value就是静态方法Object.defineProperty(obj, prop, descriptor)中的 descriptor 参数。
  • 示例
        const obj1 = 
            firstName: '',
            lastName: '',
            _job: ''
        ;
    
        Object.defineProperties(obj1, 
            fullName: 
                get: function () 
                    return this.firstName + ' ' + this.lastName;
                ,
    
                set: function(v) 
                    const names = v.split(' ');
                    this.firstName = names[0];
                    this.lastName = names[1];
                
            ,
    
            job: 
                get: function() 
                    return this._job.toString().toUpperCase();
                ,
    
                set: function(v) 
                    this._job = v;
                
            
        );
    
        obj1.fullName = 'Jackie Chan';
        console.log(obj1.fullName); // 输出: 'Jackie Chan'
        console.log(obj1.firstName); // 输出: 'Jackie'
        console.log(obj1.lastName); // 输出: 'Chan'
    
        obj1.job = 'web developer';
        console.log(obj1.job); // 输出: 'WEB DEVELOPER'
    

浏览器兼容性

Object.defineProperty()Object.defineProperties()
主流现代浏览器以及IE9+支持,IE8虽然实现了该方法,但只能在 DOM 对象上使用且存在诸多限制。该方法在规范ECMAScript Latest Draft (ECMA-262)中被定义。主流现代浏览器以及IE9+支持,在规范ECMAScript Latest Draft (ECMA-262)中被定义。

参考

MDN Object.defineProperty()
MDN Object.defineProperties()
StackOverflow Object.defineProperty polyfill
StackOverflow JavaScript getter support in IE8

以上是关于Object.defineProperty()详解的主要内容,如果未能解决你的问题,请参考以下文章

Object.defineProperty()详解

Vue2.X监听data变化的核心API—Object.defineProperty详解

数据劫持详解(JavaScript)

数据响应式详解(JavaScript)

ES5的常用对象方法详解

理解Object.defineProperty()