# Object.defineProperty

使用字面量或者new Object声明的对象,不能进行精准控制:比如这个属性是否是可以通过delete删除的?这个属性是否在for-in遍历的时被遍历出来呢

如果想要对一个属性进行比较精准的操作控制,那么就可以使用属性描述符。

  • 通过属性描述符可以精准的添加或修改对象的属性;
  • 属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改;

Object.defineProperty(obj, prop, descriptor):会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

+ obj:要定义属性的对象。
+ prop:要定义或修改的属性的名称或 Symbol。
+ descriptor:要定义或修改的属性描述符。

+ 返回值:被传递给函数的对象

在调用 Object.defineProperty() 时, configurable 、 enumerable 和 writable 的值如果不指定,则都默认为 false 。

  1. 场景
var dreamapple = {
    firstName: 'dream',
    lastName: 'apple'
};

要给dreamapple添加一个fullName属性,当dreamapple的firstName或者lastName发生变化的时候,fullName也要随之变化;当设置了fullName的值的时候,那么相应的它的firstName和lastName也随之发生变化?

  • vue可以使用计算属性
<script>
	computed: {
	  fullName: {
		// getter
		get: function () {
		  return this.firstName + ' ' + this.lastName
		},
		// setter
		set: function (newValue) {
		  var names = newValue.split(' ')
		  this.firstName = names[0]
		  this.lastName = names[names.length - 1]
		}
	  }
	}
</script>
  • ES5实现方法

比较简单的做法就是给这个对象的属性fullName设置一个 getter 和一个setter. get/set后不接冒号,没有function作为参数

var dreamapple = {
    firstName: 'dream',
    lastName: 'apple',
    get fullName() {
        return this.firstName + ' ' + this.lastName;
    },
    set fullName(fullName) {
        var names = fullName.trim().split(' ');
        if(2 === names.length) {
            this.firstName = names[0];
            this.lastName = names[1];
        }
    }
};

dreamapple.firstName = 'Dream';
dreamapple.lastName = 'Apple';
console.log(dreamapple.fullName); // Dream Apple

dreamapple.fullName = 'Jams King';
console.log(dreamapple.firstName); // Jams
console.log(dreamapple.lastName); // King
  • 这里的get和set写法是语法糖,等同于利用Object.defineProperty。
var obj = {
  _age: 18,
  _eating: function() {},
// 等同于下面Object.defineProperties中设置的age
// [[这样写configurable和enumerable则默认为true]]
//   set age(value) {
//     this._age = value
//   },
//   get age() {
//     return this._age
//   }
}

Object.defineProperties(obj, {
  name: {
    configurable: true,
    enumerable: true,
    writable: true,
    value: "why"
  },
  age: {
    configurable: true,
    enumerable: true, // 这种写法默认则为false
    get: function() {
      return this._age
    },
    set: function(value) {
      this._age = value
    }
  }
})

obj.age = 19
console.log(obj.age)

console.log(obj)

  • Object.defineProperty() 包括数据描述符和存取描述符, 属性描述符必须是数据描述符或者存取描述符两种形式之一,不能同时是两者 两者均具有以下两种键值:
  1. configurable:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符;默认为false;当设置为true,再设为false后是不可逆转的。

  2. enumerable:当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。enumerable和writable设置完false还是可以改为true

	let obj = {}
	Object.defineProperty(obj,'k',{
		configurable:true,
		value:30
	})
	console.log(delete obj.k,1)
    //当设置为false之后,无法在改为true,会报错:
    //Cannot redefine property: k  at Function.defineProperty (<anonymous>)
	Object.defineProperty(obj,'k',{
		configurable:false,
		value:30
	})
	console.log(delete obj.k,2)
	Object.defineProperty(obj,'k',{
		configurable:true,
		value:30
	})
	console.log(delete obj.k,3)
	console.log(Object.keys(obj))
	console.log(obj)
	// 当configurable设置为false之后,自然就无法再转为存取属性描述符
	Object.defineProperty(obj,'k',{
		set(){
			this.f =1999
		},
		get(){
			console.log(1)
		}
	})
	
	obj.k =1000
	console.log(obj)
  • value 该属性对应的值,可以是任何有效的JavaScript值(数值,对象,函数等),默认为undefined.
var dream = {};
Object.defineProperty(dream, 'name', {
    value: 'dreamlost'
});

console.log(dream.name); // dreamlost
dream.name = 'finish'; // 修改name属性
console.log(dream.name); // 并不是finish,依旧是dreamlost

只有当属性的writable修饰为true时,我们这个属性才可以被修改.

var dream = {};
Object.defineProperty(dream, 'name', {
    value: 'dreamapple',
    writable: true
});

console.log(dream.name); // dreamapple
dream.name = 'apple'; // 修改name属性
console.log(dream.name); // apple

enumerable 这个特性决定了我们定义的属性是否是可枚举的类型,默认是false;只有我们把它设置为true的时候这个属性才可以使用for(prop in obj)和Object.keys()中枚举出来。

var dream = {};
Object.defineProperty(dream, 'a', {
    value: 1,
    enumerable: false // 不可枚举
});
Object.defineProperty(dream, 'b', {
    value: 2,
    enumerable: true // 可枚举
});

// 只会输出 b
for(prop in dream) {
    console.log(prop);
}

console.log(Object.keys(dream)); // ['b']

console.log(dream.propertyIsEnumerable('a')); // false
console.log(dream.propertyIsEnumerable('b')); // true

configurable 这个特性决定了对象的属性是否可以被删除,以及除writable特性外的其它特性是否可以被修改。

var dream = {};
Object.defineProperty(dream, 'c', {
    value: 3,
    configurable: false
});
 //throws a TypeError
Object.defineProperty(dream, 'c', {
    configurable: true
});
 //throws a TypeError
Object.defineProperty(dream, 'c', {
    writable: true
});
 //won't throws a TypeError
Object.defineProperty(dream, 'c', {
    writable: false
});
delete dream.c; // 属性不可以被删除
console.log(dream.c); // 3 

configurable为总开关,第一次设置 false 之后,第二次什么设置也不行了,在调用Object.defineProperty时,configurable,enumerable,writable的值如果不指定,默认false,且一旦configurable为false后,再也不能去使用Object.defineProperty修改任何配置。否则报错。

  • defineProperty的另一种方式
    • get 一个给属性提供getter的方法,如果没有getter则为undefined;该方法返回值被用作属性值,默认为undefined.
    • set 一个给属性提供setter的方法,如果没有setter则为undefined;该方法将接受唯一参数,并将该参数的新值分配给该属性,默认为undefined.
var dream = {};
Object.defineProperty(dream, 'fullName', {
    enumerable: false,
	// writable:true,
    get: function () {
        return this.firstName + ' ' + this.lastName;
    },
    set: function (fullName) {
        var names = fullName.trim().split(' ');
        if (2 === names.length) {
			console.log(names)
			//["li", "mei"]
            this.firstName = names[0];
            this.lastName = names[1];
        }
    }
});
dream.fullName="li mei"
console.log(dream.fullName)//li mei
console.log(dream.propertyIsEnumerable("lastName"))//true
console.log(dream)//{firstName: "li", lastName: "mei"}
for(var i in dream){
	console.log(i)//firstName lastName
}

提示

value和get,set是不可以共存的,就是说你定义了value后就不能够再定义get,set特性了.

  • 场景
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <title> 功能加1 </title>
</head>
<body>
<span id="container">1</span>
<button id="button">点击加 1</button>
<script type="text/javascript">
(function(){
	var root = this;
	function watch(obj, name, func){
		var value = obj[name];

		Object.defineProperty(obj, name, {
			get: function() {
				return value;
			},
			set: function(newValue) {
				value = newValue;
				func(value)
			}
		});

		if (value) obj[name] = value
	}

	this.watch = watch;
})()
var obj = {
    value: 1
}

watch(obj, "value", function(newvalue){
    document.getElementById('container').innerHTML = newvalue;
})

document.getElementById('button').addEventListener("click", function(){
    obj.value += 1
});




</script>
</body>
</html>
	function reactive(data,key,val){
			if(typeof val === 'object'){
				console.log(val) //{ff: 1}
				observe(val)
			}
			Object.defineProperty(data,key,{
				get(){
					console.warn(`get`,key)
					return val
				},
				set(newVal){
					console.log(`set`,val)
					val = newVal
				}
			})
	}
	
	
	function observe(obj){
		Object.keys(obj).forEach(key=>{
			reactive(obj,key,obj[key])
		})
	}
	
	let obj ={a:1,b:30,c:{ff:1}}
	observe(obj)
	
	obj.c.ff=1000 // 触发一次 get  再触发set去改值
	// vue.html:20 get c
	// set 1
	
	console.warn(obj.c) //{ff: 1000}

参考文献 (opens new window)

最后更新: 2/10/2023, 8:23:41 PM