本内容使用vue2.6进行分析。
Vue.js是一款MVVM框架,上手快速简单易用,通过响应式在修改数据的时候更新视图。
那些内卷比较严重的同学(面试八卦文)都知道vue.js基于Object.defineProperty做的响应式原理依赖。
思路原理 数据的可观察
observe 观察者模式,几乎是所有的高级软件的一个通用思路,不要为了卷而卷。大家学完之后要考虑你用观察者模式能用到什么地方。
function observe(value, cb) {
Object.keys(value).forEach(
(key) => defineReactive(value, key, value[key] , cb)
)
}
function defineReactive (obj, key, val, cb) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
/*....依赖收集等....*/
/*Github:https://github.com/answershuto*/
return val
},
set:newVal=> {
val = newVal;
cb();/*订阅者收到消息的回调*/
}
})
}
class Vue {
constructor(options) {
this._data = options.data;
observe(this._data, options.render)
}
}
let app = new Vue({
el: '#app',
data: {
text: 'text',
text2: 'text2'
},
render(){
console.log("render");
}
})
为了便于理解,首先考虑一种最简单的情况,不考虑数组等情况,代码如上所示。在initData中会调用observe这个函数将Vue的数据设置成observable的。当_data数据发生改变的时候就会触发set,对订阅者进行回调(在这里是render)。
那么问题来了,需要对app._data.text操作才会触发set。为了偷懒,我们需要一种方便的方法通过app.text直接设置就能触发set对视图进行重绘。那么就需要用到代理。
为什么这么设计
大家想想如果没有Object.defineProperty你会怎么去做这个操作?
我们先看一个简单的例子,拥有一个objec的对象,现在我们有一个render的html操作
let object = {
data :{
test : 0
}
};
object.data.test = 1;
render(object);
当我们每一次修改了objcet.data的时候就会去执行render重新渲染render操作。 这个时候就比较好玩了,如果你的系统非常庞大,然后你丢给一个团队去调用的时候。 你想每次都手动执行一次render操作吗?
上面的方式已经知道问题了,这个时候。我们就会想,可以通过什么方式进行数据的修改是做一点事情呢?在近十年被oop的编程思维影响下,无疑我们第一反应是java的get/set操作。
于是就有了以下的设计操作
class Controller {
private data;
constructor() {
}
setData(data:any) {
this.data = { ...this.data, ...data };
cb();/*订阅者收到消息的回调*/
}
}
# 这就有点早期React的那味道,this.setState( { xxx: xxxx} )
这个时候,还是感觉有点多余,于是我们会想有没有类似php的魔法方法呢?我只需需要执行this.xxx 就会帮我触发类似this.setState的操作呢?于是有了Vue的Object.defineProperty。
PS PHP 魔法方法的js实现
PHP 里面有 __set __get 可以让我们做到 this->handle() 在我们的js使用代理模式也能做到
alert(proxy.version); 弹出 “1.0”,
alert(proxy.age); 弹出 “none”。sn对象 没有age 属性。
我们还可以设置不让修改某个属性,对属性修改的拦截:
var proxy = new Proxy(new Object,{
get(target,name){
if(name in target){
return target[name]
}
else{
// 这里写正则做自己想做的事情吧
if((name as string).lastIndexOf('()') > -1){
return handleFunc()
}
}
},
set(target,name,value){
if(name == 'version'){
alert()
return false;
}
target[name] = value
return true;
}
})
好了今天的分享先到这里吧。