在网页开发中,一般数据想要渲染到页面中,需要依赖于操作DOM更新。 Vue
和React
则是使用数据驱动视图,也就是数据改变,DOM也相应完成变化。
而数据变化更新DOM也分为侵入式和非侵入式
Vue
属于非侵入式,React
和小程序
数据变化输入侵入式。
侵入式设计,就是设计者将框架功能“推”给客户端;
而非侵入式设计,则是设计者将客户端的功能“拿”到框架中用
侵入式设计带来的最大缺陷是,代码需要依赖框架的代码,如果把框架拿掉或者换一个框架,就需要重新修改代码
下面的例子中,vue
改变a的值没有调用其他的API,而react
和小程序
则调用了setState
和setData
的API
Vue:
this.a++;
React:
this.setState({
a: this.state.a + 1
});
小程序:
this.setData({
a: this.state.a + 1
});
当然,本文的重点在于Object.defineProperty。
在上面说到,在React
和小程序当中,因为调用API方法改变数值,所以界面改变也很好理解,API方法对应会改变DOM。
那么Vue
改变数值时是非侵入式,那么界面应该响应数值改变而改变?
在这里就需要提到Object.defineProperty
数据代理了,MDN地址:developer.mozilla.org/zh-CN/docs/…
这是一个JavaScript引擎的API,可以来检测对象属性的变化
在vue3中使用proxy代替
defineProperty
,但是proxy具有兼容性问题,且无法polyfill (基本上IE浏览器可以放弃)
之前在《前端面试中有趣的题目》 中也有用到过该方法去定义一个const
Object.defineProperty
中具有get和set方法,当Vue去改变a值时,a属性已经被set给劫持了。(get和set都需要变量进行周转)
例子: 在下面会看到例子当中obj对象存在一个_a
属性,这是为了对应之后设置a
的get
和set
,现在修改obj.a
的数值,也能够打印console
中的语句。如果在set
方法中添加updateView
更新视图方法,那么就能够简单的达到了数据同步的效果
let obj = {
_a: 0
}
Object.defineProperty(obj, 'a', {
get() {
return this._a;
},
set(n) {
console.log('设置a属性的值' + n);
this._a = n;
// 触发更新视图
//updateView();
}
})
obj.a = 0; // 设置a属性的值0
obj.a++; // 设置a属性的值1
obj.a++; // 设置a属性的值2
obj.a += 10; // 设置a属性的值12
console.log(obj.a); // 12
先定义一个等一下要测试的数据
let personInfo = {
name: 'zhangsan',
age: 20,
info: {
address: '江苏'
}
}
在上面的基础上,可以对其进行封装
function defineProperty(object, key, data) {
if (!data) {
data = object[key]
}
Object.defineProperty(object, key, {
get() {
return data;
},
set(val) {
if (data != val) {
console.log(`${key}的原值${data}被更新为:${val}`)
data = val;
}
}
})
}
对其中的name
属性调用方法进行代理,能够在控制台看到name的原值zhangsan被更新为:lisi
被打印
defineProperty(personInfo, 'name');
personInfo.name = 'lisi' // name的原值zhangsan被更新为:lisi
并且在此封装好的基础上,可以再写一个方法,对对象进行分析,遍历对象各个属性放入方法代理
function observe(obj) {
if (typeof obj != 'object') {
return ;
}
for(let key in obj) {
observeReactive(obj, key);
}
}
这样一来层次不深的属性
基本已经被defineProperty代理了
observe(personInfo);
personInfo.age = 30; // age的原值20被更新为:30
不过如果是内部还有层次的就不行了,比如下面的例子,就没有打印出来
personInfo.info.address = '浙江';
那么就对属性再做一次判断,如果还是对象,继续遍历
function defineProperty(object, key, data) {
if (!data) {
data = object[key]
}
// 递归
if (typeof data == 'object') {
observe(data);
}
Object.defineProperty(object, key, {
get() {
return data;
},
set(val) {
if (data != val) {
console.log(`${key}的原值${data}被更新为:${val}`)
data = val;
// 新增加对象也要observe
if (typeof val == 'object') {
observe(data);
}
}
}
})
}
这样一来address的原值江苏被更新为:浙江
就能够被打印出来了
Object.defineProperty
还是有挺多不足的
Vue.set
和Vue.delete
)push
、shift
等方法生效生效,无法原生监听数组,需要特殊处理