js 的 defineProperty 数据劫持

我们知道,在Vue中我们操作某个属性时vue可以实时响应,这是因为当一个vue实例被创建出来之后,它会生成一个Observer观察者实例,然后对vue中的对象属性进行遍历,通过defineProperty将它们转换为了getter和setter,当我们操作某个数据时,就会触发getter和setter,从而完成一个监听操作。

下面我们详细解释一下defineProperty以及它的优劣所在:

Object.defineProperty(obj,"",{})

defineProperty总共有三个参数,第一个参数表示我们将在哪个对象上进行操作

第二个参数表示为要操作的是哪个字段,注意,这个参数是一个字符格式的

第三个参数为一个对象,它用来设置我们操作的这个属性需要哪些特性

一、defineProperty的第三个参数总共有六个属性可以设置,第一个属性为  value

let obj = {
	age:19,
};

Object.defineProperty(obj,"name",{
	value:"zhangsan"
})
obj.name = 666;
console.log(obj.name);

这个属性代表我们设置的这个name字段的值为 "zhangsan" , 注意,现在我们只设置了一个属性【value】,在默认情况下这就是一个只读字段,我们对他进行修改不会有效,因此将他赋值为 666 不会有任何效果。

它的结果仍然是张三

二、 writable

这个属性代表的是这个name字段是否是可写字段,在默认我们不设置的情况下它为false,即不可写,因此上面我们对这个字段进行赋值就无效

let obj = {
	age:19,
};

Object.defineProperty(obj,"name",{
	value:"zhangsan",
	writable:true,
})
obj.name = 666;
console.log(obj.name);

现在我们将它设置为true,因此此时它是可写属性,所有修改就会有效了

 

三、enumerable

这个属性用于设置我们定义的这个name字段是否可以被枚举,默认我们不设置的情况下它为false,即不可枚举,因此当我们去查询这个键值时查询不到

(枚举的意思就是是否可以被列举,被查询出来)

let obj = {
	age:19,
};

Object.defineProperty(obj,"name",{
	value:"zhangsan",
	writable:true,
})
obj.name = 666;

console.log(Object.keys(obj));
//Object.keys方法用于查询此对象中的所有字段,并用一个数组返回

此时因为我们没有设置,因此默认情况下它是查不到这个name字段的

只能查到 age字段,并没有name字段,但若我们将enumerable属性设置为true

let obj = {
	age:19,
};

Object.defineProperty(obj,"name",{
	value:"zhangsan",
	writable:true,
	enumerable:true,
})
obj.name = 666;

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

此时它就可以查到这个name字段了。

 

四、configurable

这个属性用于设置该字段是否可以被删除,或者是否可以更改它的特性,为false表示不能删除和更改,true则表示允许删除和更改;它的默认值是false,即不允许删除和更改特性。

let obj = {
	age:19,
};

Object.defineProperty(obj,"name",{
	value:"zhangsan",
	writable:true,
	enumerable:true,
	configurable:false
})
//delete 关键字用于删除某个对象中的某个属性
delete obj.name;
console.log(obj);

此时我们执行delete是无效的,因为它现在是不可删除的,此时这个name字段仍然存在

并且为false时我们不能再次更改这个字段的特性,否则会报错;

let obj = {
	age:19,
};

Object.defineProperty(obj,"name",{
	value:"zhangsan",
	writable:true,
	enumerable:true,
	configurable:false
})

//此处我再次对它的特性进行更改,将writable和enumerable设置为false
Object.defineProperty(obj,"name",{
	value:"zhangsan",
	writable:false,
	enumerable:false,
	configurable:false
})

但如果configurable为true时,则又是另一种效果:

let obj = {
	age:19,
};

Object.defineProperty(obj,"name",{
	value:"zhangsan",
	writable:true,
	enumerable:true,
	configurable:true
})

delete obj.name

console.log(obj)

此时我们可以看到,name字段已经被删除了,并且这个时候我们再去更改它的特性它也不会报错了。

 

五、get和set

这两个属性用于监听字段的读写操作,也是用的最多的两个属性,注意,这两个属性不能和writable以及value属性同时出现,否则会报错。

当我们去操作这个字段时,它就会触发get方法和set方法,从而实现监听操作

let obj = {
	age:19,
};

let tempName = ""

Object.defineProperty(obj,"name",{
	get(){
		console.log("读取了这个属性")
		return tempName;
	},
	set(val){
		console.log("设置了这个属性")
		tempName = val;
	}
})

obj.name = 999;

console.log(obj.name)

 

使用get和set我么可以做一些有趣的事,比如我们可以让一个变量同时等于多个值

let obj = {
	age:19,
};

let tempName = 0;

Object.defineProperty(obj,"name",{
	get(){
		tempName++;
		return tempName;
	},
	set(val){
		tempName = val;
	}
})

console.log(obj.name===1 && obj.name===2 && obj.name===3)

 

缺点

defineProperty无法针对整个对象,它只能给对象的某一个属性进行监听,如果我们想监听整个操作,必须使用遍历方法对整个对象进行遍历,一个一个的对他们执行监听操作,这么做起来比较麻烦。

let data = {
	name:"张三",
	age:"19",
	gender:"男",
}

let temp = {}
Object.keys(data).forEach(key=>{
	temp[key] = data[key]
	Object.defineProperty(data,key,{
		get(){
			console.log(`获取了${key}`)
			return temp[key]
		},
		set(val){
			console.log(`设置了${key}`)
			temp[key] = val;
		}
	})
})

console.log(data.name)
console.log(data.age)
console.log(data.gender)

其次,第二个问题在于它无法监听一些数组api的操作

push,sort,pop,unshift,shift,splice,reverse

这七个api它监听不到;

let arr = [1,2,3,4,5]

Object.defineProperty(window,"data",{
	get(){
		console.log("获取了数组")
		return arr;
	},
	set(val){
		console.log("设置了数组")
	}
})

data.push("12312312")

当我们执行push时,它只触发了get方法,但是set方法却并没有被触发,因为它无法监听push方法的操作;

在Vue中能监听到是因为它自己重写了这些方法,它其实使用了函数劫持的方式,自己对这些方法进行了重写,事实上它对我们在data中定义的数组进行了原型链重写,将其指向了vue自己定义的数组方法上,从而让这些重写的方法来进行数据劫持,这样当我们调用数组api时就可以通知依赖更新。如果数组中还包含了引用类型,它还会对引用类型再次递归遍历进行监控,这样就实现了检测数组的变化;

一个简单实现:

let methods = ["push","pop","splice","sort","unshift","shift","reverse"];

let ArrayM = [];

methods.forEach(method=>{
	
	let original = Array.prototype[method]
	
	ArrayM[method] = function(){
		console.log(`调用了${method}`);
		
		return original.apply(this,arguments)
	}
	
})

let list = [];
list.__proto__ = ArrayM;

list.push(123);

list.reverse();

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值