核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持。
数据劫持 -->给对象扩展属性 --> 属性设置
1、Object.defineProperty( )里面传入三个参数。
function defineProperty () {
var _obj = {}
Object.defineProperty(_obj, 'a', {
value: 1
})
return _obj
}
var obj = defineProperty()
console.log(obj); //{a:1}
2、Object.defineProperty( )里面传入两个参数。
第二个参数中属性值有四个可选属性:
1、value:属性值
2、writeable:是否可写,默认为false
3、enumerable:是否可枚举,默认为false
4、configurable:是否可配置,默认为false
function defineProperty () {
var _obj = {}
Object.defineProperty(_obj, {
a: {
// 设置属性值
value:1,
// 属性值是否可写,默认为false
writeable:true,
// 是否可枚举,默认为false
enumerable:true,
// 是否可配置,默认为false
configurable:true
}
})
return _obj
}
var obj = defineProperty()
console.log(obj); //{a:1}
3、Object.defineProperty( )里面传入两个参数,第二个参数中包含get方法和set方法时:
get方法:在每一次获取属性值时,都会先走get方法,我们可以在返回该属性值之前,做一些事情;
set方法:在每一次改变属性值时,都会先走set方法,相应的,我们也可以在这里做一些事情;
function defineProperty () {
var _obj = {}
Object.defineProperty(_obj, {
firstVal: {
get () {
console.log(`获取${firstVal}的值`);
return firstVal
},
set (newVal) {
firstVal = newVal
console.log(`修改了${firstVal}的值`);
}
},
secondVal: {
get () {
console.log(`获取${secondVal}的值`);
return secondVal
},
set (newVal) {
secondVal = newVal
console.log(`修改了${secondVal}的值`);
}
}
})
return _obj
}
var obj = defineProperty()
console.log(obj);
Demo练习(计算器):
index.html
Document
0
index.js
// 定义一个计算类
class Compute {
plus (a, b) {
return a + b
}
minus (a, b) {
return a - b
}
mul (a, b) {
return a * b
}
div (a, b) {
return a / b
}
}
// 继承计算类
class Calculator extends Compute {
constructor() {
super()
// 获取元素
const oCal = document.getElementsByClassName('j_calculator')[0]
this.fInput = oCal.getElementsByTagName('input')[0]
this.sInput = oCal.getElementsByTagName('input')[1]
this.oBtnGroup = oCal.getElementsByClassName('btn-group')[0]
this.oBtnItems = this.oBtnGroup.getElementsByTagName('button')
this.oResult = oCal.getElementsByClassName('result')[0]
// 使用defineProperty定义响应式数据
this.data = this.defineData()
this.btnInx = 0
}
// 初始化函数
init () {
this.bindEvent()
}
// 事件绑定函数
bindEvent () {
this.oBtnGroup.addEventListener('click', this.onFieldBtnClick.bind(this), false);
// 两个input输入框绑定同一个事件函数
this.fInput.addEventListener('input', this.onInputClick.bind(this), false);
this.sInput.addEventListener('input', this.onInputClick.bind(this), false);
}
// 切换加减乘除按钮,并加颜色
onFieldBtnClick (ev) {
const e = ev || window.event,
tar = e.target || e.srcElement,
tagName = tar.tagName.toLowerCase()
tagName === 'button' && this.fieldUpdate(tar)
}
// 切换计算按钮时进行计算
fieldUpdate (target) {
this.oBtnItems[this.btnInx].className = ''
this.btnInx = [].indexOf.call(this.oBtnItems, target)
target.className += 'current'
// 修改当前点击的按钮的属性,从而引发响应式
this.data.field = target.getAttribute('data-field')
}
// 两个input输入框事件函数
onInputClick (ev) {
const e = ev || window.event,
tar = e.target || e.srcElement,
className = tar.className,
val = Number(tar.value.replace(/\s+/g, '')) || 0;
// 判断是哪一个输入框
switch (className) {
case 'f-input':
this.data.fNumber = val
break;
case 's-input':
this.data.sNumber = val
break;
default:
break;
}
}
// 定义响应式数据
defineData () {
let _obj = {},
fNumber = 0,
sNumber = 0,
field = 'plus'
// this指向改变,所以提前保存this
const self = this
Object.defineProperties(_obj, {
fNumber: {
get () {
console.log('正在读取该值');
return fNumber
},
set (newVal) {
fNumber = newVal
self.calculatorResult(fNumber, sNumber, field)
console.log('"fNumber" the value has change');
}
},
sNumber: {
get () {
console.log('正在读取该值');
return sNumber
},
set (newVal) {
sNumber = newVal
self.calculatorResult(fNumber, sNumber, field)
console.log('"sNumber" the value has change');
}
},
field: {
get () {
console.log('正在读取该值');
return field
},
set (newVal) {
field = newVal
self.calculatorResult(fNumber, sNumber, field)
console.log('"field" the value has change');
}
}
})
return _obj
}
// 当输入值时进行计算
calculatorResult (fNumber, sNumber, field) {
this.oResult.innerHTML = this[field](fNumber, sNumber)
}
}
new Calculator().init()
核心:vue3的数据响应式,是使用了ES6新增的Proxy代理对象,Proxy本身就是一个构造函数。
var myObj = {
a: 1,
b: 2
}
// 定义响应式数据
let proxy = new Proxy(myObj, {
//当读取对象某个属性时调用
get (target, prop) {
console.log(`有人读取了对象的${prop}属性`)
// 第一种形式
// return target[prop]
// 第二种形式,使用Reflect函数
// Reflect函数是一个全局函数,里面封装了许多方法,可以直接调用
return Reflect.get(target, prop)
},
//当修改对象的某个属性或追加某个属性时调用
set (target, prop, value) {
console.log(`有人修改了对象的${prop}属性`)
// target[prop] = value
Reflect.set(target, prop, value)
},
//当删除对象的某个属性时调用
// 代理Proxy里面还有许多方法,可查阅资料学习
deleteProperty (target, prop) {
console.log(`有人删除了对象身上的${propName}属性`)
return Reflect.deleteProperty(target, prop)
}
})
console.log(proxy);
console.log(proxy.a);
proxy.b = 3
console.log(proxy.b);
最后总结:
1、defineProperty是对一个空对象进行设置属性,在读取和改变属性值的拦截一下,可以实现一些其他的逻辑处理。不支持函数拦截;
2、Proxy是对一个完整的对象进行代理,相当于拷贝了一份,当我们对原数据进行属性值操作时,这个代理对象会挡在前面对数据进行操作。支持对象代理,数组代理和函数代理。