JavaScript-Proxy & 数据劫持

JavaScript-Proxy & 数据劫持

引:

作为一名参与前端业务的开发者,在了解框架的同时也应了解框架背后的原理
在Vue、React等框架的使用中,双向绑定是最常使用的API之一,其基本思想是使用JS实现数据劫持

数据劫持:

简单来说就是在数据改变时对数据进行监听(处理)
其中Proxy是数据劫持的一种方案

Proxy:

我们可以按照如下方式定义一个 Proxy

const testProxy = new Proxy(target, handler);

target:
要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler:
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 testProxy 的行为
当外界每次对obj进⾏操作时,就会执⾏handler对象的⽅法
handler中常⽤的对象⽅法如下:

  1. get(target, propKey, receiver)
  2. set(target, propKey, value, receiver)
  3. has(target, propKey)
  4. construct(target, args):
  5. apply(target, object, args)

接下来上一个简单的操作用例:

// Proxy是es6新增的,不支持旧版IE
const carInfo = {
  brand: '梅赛德斯',
  model: 'c500',
  price: 5000000,
  handlePrice: 0
}

function editPrice(dom){
	testProxy.price = dom.value;
}
function handlePrice(dom){
	testProxy.handlePrice = dom.value;
}

window.onload = () =>{
	document.getElementById('brand').innerHTML = testProxy.brand;
	document.getElementById('model').innerHTML = testProxy.model;
	document.getElementById('price').innerHTML = testProxy.price;
}

const testProxy = new Proxy(carInfo, {
  // 获取对象属性值时触发
  // target:原对象,key:对应属性
  get: function(target, key) {
    console.log(`车辆信息:${key} 被访问`)
	return target[key];
  },
  // 改变/赋值对象属性值时触发
  // target:原对象,key:对应属性,newValue:改变的值
  set: function(target, key, newValue) {
	if (key === "price"){ // 对价格进行拦截校验
		if (typeof newValue !== "number") {
			alert("价格字段必须为Number类型") // 异常后输入框数值初始化
			document.getElementById('price').value = target[key]
			return 
		}else if (newValue < 300000){
			alert("价格不能低于300000")
			document.getElementById('price').value = target[key]
		}else {
			target[key] = newValue;
			console.log(`车辆信息:${key} 已被更改`)
		}  
	} else if (key === 'handlePrice') {
		testProxy.price = target['price'] + parseInt(newValue)
		console.log(`价格已被更改 ${testProxy.price}`)
		document.getElementById('handlePrice').value = 0;
		document.getElementById('price').innerHTML = target['price'];
	} else {
		target[key] = newValue;
		console.log(`车辆信息:${key} 已被更改`)
	}
    // Proxy仅作为 => 代理 如需要改变原数据,则需要做此赋值操作
	// target[key] = newValue 
	
  }
})
testProxy.brand = '大众',
testProxy.price = 300001

控制台输出:
在这里插入图片描述
据此,我们了解到了Proxy的基本使用,Proxy可以监听数据的get和set操作
根据此特性我们其实可以应用到很多业务场景
get:
1、特殊数据读取、访问埋点
2、特殊数据读取/访问时更改格式 50 => 50m/kg/min…
set:
1、表单校验/拦截
2、双向绑定
3、数据保护

校验demo(实现了一个简易的双向绑定):

// Proxy是es6新增的,不支持旧版IE
const carInfo = {
  brand: '梅赛德斯',
  model: 'c500',
  price: 5000000,
  handlePrice: 0,
  handlePriceFlag: false
}

function editPrice(dom){
	testProxy.price = dom.value;
}
function handlePrice(dom){
	testProxy.handlePrice = dom.value;
}
function edit(dom){
	testProxy.handlePriceFlag = true;
	testProxy.handlePrice = dom.value;
}

window.onload = () =>{
	document.getElementById('brand').innerHTML = testProxy.brand;
	document.getElementById('model').innerHTML = testProxy.model;
	document.getElementById('price').innerHTML = testProxy.price;
}

const testProxy = new Proxy(carInfo, {
  // 获取对象属性值时触发
  // target:原对象,key:对应属性
  get: function(target, key) {
    console.log(`车辆信息:${key} 被访问`)
	return target[key];
  },
  // 改变/赋值对象属性值时触发
  // target:原对象,key:对应属性,newValue:改变的值
  set: function(target, key, newValue) {
	if (key === "price"){ // 对价格进行拦截校验
		if (typeof newValue !== "number") {
			alert("价格字段必须为Number类型") // 异常后输入框数值初始化
			document.getElementById('price').value = target[key]
			return 
		}else if (newValue < 300000){
			alert("价格不能低于300000")
			document.getElementById('price').value = target[key]
		}else {
			target[key] = newValue;
			console.log(`车辆信息:${key} 已被更改`)
		}  
	} else if (key === 'handlePrice') {
		console.log(`价格已被更改 ${testProxy.price}`)
		document.getElementById('handlePriceValue').innerHTML = newValue;
	} else if (key === 'handlePriceFlag' && newValue === true){
		testProxy.price = target['price'] + (parseInt(target['handlePrice']) || 0);
		console.log('testProxy.price', testProxy.price);
		document.getElementById('price').innerHTML = testProxy.price;
		document.getElementById('handlePrice').value = 0;
		testProxy.handlePriceFlag = false;
	} else {
		target[key] = newValue;
		console.log(`车辆信息:${key} 已被更改`)
	}
	
    // Proxy仅作为 => 代理 如需要改变原数据,则需要做此赋值操作
	// target[key] = newValue
	 
	// 设置新值,保存至全局
	const rets = Reflect.set(target, key, newValue)
	return rets
  }
})
testProxy.brand = '大众';
testProxy.price = 300001;

效果:
![![demo输出](https://img-blog.csdnimg.cn/524c23db367b43f9aadc23245700be68.png](https://img-blog.csdnimg.cn/4d3be95680a04d949b35baec29d16a81.png

Object.defineProperty:

在JS中数据劫持的方案不止只有Proxy,其中 Object.defineProperty 也是一种方案
该方案在Vue2的数据绑定中进行了广泛应用,虽然其有一些局限性不如Proxy灵活(Object.defineProperty操作对象需要递归,略嫌麻烦),但该API对较旧版IE适配,这是一个比较大的优势
这块底层玩意儿我当时写毕业论文的时候深入学习过,所以在此就不过多解释了,偷个懒搬砖搬过来吧(节选)

Vue-数据双向绑定(v-model)

Vue中的双向绑定功能在工程开发中十分常见。所谓双向绑定,即为数据与模型(model)之间的绑定。在最常见的开发中,我们可以在input上通过v-model绑定data中的某个已定义的数据源,此时当我们在input中进行写入删除操作时,data中的绑定的数据将随之input中的值而改变。
双向绑定十分常见并且实用,但我们使用原生js实现此功能却并没有想象中简单。在Vue中v-model是由v-bind+v-on实现的,其中v-bind绑定属性,v-on绑定事件。
图中两个input效果是等同的
数据在v-on与v-bind中相互指向,使用$event.target.value指向当前dom的value属性值。综上,双向绑定可以说仅仅只是一个语法糖。即view更新data,data也更新view。接下来将使用原生写法尝试实现。
首先可以明确,我们要想实现双向绑定,必须要能够监听数据的实时变化。在原生js开发中,使用视图更新数据十分常见且容易,只需要绑定事件监听即可。但关键点应是利用数据的变化而改变视图,我们必须要清楚地了解数据什么时候发生了变化、变化了什么。
在js原生的方法Object.defineProperty( )中有一个set函数,当对象数据发生了变化即会触发。我们可以利用这个特性去监听并且触发函数。再反观.vue文件的构成中,data就是一个对象,我们可以使用递归方法遍历这个对象的属性值,进行Object.defineProperty( )处理。

创建空对象:
在这里插入图片描述
方法函数:
在这里插入图片描述
控制台输出:
在这里插入图片描述
尝试手动改变对象值:
在这里插入图片描述
set函数:
在这里插入图片描述
控制台结果 :
在这里插入图片描述

Vue中的双向绑定功能在工程开发中十分常见。所谓双向绑定,即为数据与模型(model)之间的绑定。在最常见的开发中,我们可以在input上通过v-model绑定data中的某个已定义的数据源,此时当我们在input中进行写入删除操作时,data中的绑定的数据将随之input中的值而改变。
由以上分析可以得出Vue的数据响应式和Object.defineProperty( )是分不开的。即Vue利用其set函数进行数据劫持。
当然,正式的Vue功能并没有完全实现,仍还缺少订阅的问题,正式的功能是由初始化时调用get函数并进行判断操作,最后必须进行解析操作识别指令在DOM中正式绑定。至此双向绑定才算正式完成

总结:

1、框架的原理理解起来并不难,难的原因是自己认为难而不去了解
2、底层的东西应该广泛深入学习,不应该抱有能用就行的心态

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值