讲清Object.defineProperty()方法 PS: Vue@2.x 底层实现**数据劫持**就是调用该方法实现的。

[学习文档地址]

​​​​​​https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

目录

[学习文档地址]

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

Object.defineProperty()工作原理

语法

参数

返回值

方法描述

属性描述符

它们共享以下可选键

数据描述符还具有以下可选键值:

访问器描述符还具有以下可选键值:

示例

其他方法

Object.defineProperties()

浏览器兼容性

参见


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

该方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。

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

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

Object.defineProperty()工作原理

1.首先,检查传入的参数是否为对象。如果不是对象,则抛出 TypeError

2.接着,创建或修改指定对象的属性,需要传入三个参数:目标对象 obj、属性名 prop 和属性描述符 descriptor

3.属性描述符 descriptor 是一个包含属性特性的对象,可以包括 valuewritableenumerableconfigurableget 和 set 等属性。

4.根据属性描述符中的设置,对属性进行定义或修改。例如,可以设置属性的值、可写性、可枚举性和可配置性等。

5.创建或修改属性后,返回目标对象

语法

Object.defineProperty(obj, prop, descriptor)

参数

obj

要定义属性的对象。

prop

一个字符串或 Symbol,指定了要定义或修改的属性键。

descriptor

要定义或修改的属性的描述符。

返回值

传入函数的对象,其指定的属性已被添加或修改

方法描述


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

属性描述符

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

数据描述符和访问器描述符都是对象。

它们共享以下可选键

(请注意:在使用 Object.defineProperty() 定义属性的情况下,下述所有键都是默认值):

当设置为 false 时,

  • 该属性的类型不能在数据属性和访问器属性之间更改,且
  • 该属性不可被删除,且
  • 其描述符的其他属性也不能被更改(但是,如果它是一个可写的数据描述符,则 value 可以被更改,writable 可以更改为 false)。

默认值为 false

数据描述符还具有以下可选键值:

value

与属性相关联的值。可以是任何有效的 JavaScript 值(数字、对象、函数等)。默认值为 undefined。

writable

如果与属性相关联的值可以使用赋值运算符更改,则为 true默认值为 false

访问器描述符还具有以下可选键值:

get

用作属性 getter 的函数,如果没有 getter 则为 undefined  。当访问该属性时,将不带参地调用此函数,并将 this 设置为通过该属性访问的对象(因为可能存在继承关系,这可能不是定义该属性的对象)。返回值将被用作该属性的值。默认值为  undefined 。

set

用作属性 setter 的函数,如果没有 setter 则为 undefined 。当该属性被赋值时,将调用此函数,并带有一个参数(要赋给该属性的值),并将 this 设置为通过该属性分配的对象。默认值为 undefined 。

如果描述符没有 valuewritableget 和 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'
    

    浏览器兼容性

    Report problems with this compatibility data on GitHub
    desktopmobileserver

    Chrome

    Edge

    Firefox

    Opera

    Safari

    Chrome Android

    Firefox for Android

    Opera Android

    Safari on iOS

    Samsung Internet

    WebView Android

    Deno

    Node.js

    defineProperty

    5

    Toggle history

    12

    Toggle history

    4

    Toggle history

    11.6

    Toggle history

    5.1

    footnote

    Toggle history

    18

    Toggle history

    4

    Toggle history

    12

    Toggle history

    6

    footnote

    Toggle history

    1.0

    Toggle history

    4.4

    Toggle history

    1.0

    Toggle history

    0.10.0

    Toggle history

参见

  • 44
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值