以前学习 vue 的时候,一直没刨根问底过。在看到网上这类文章比较多,良莠不齐的质量有时候看的一头雾水。固然也有不错的文章,可是终究是别人的理解。因而写一篇关于本身的理解记录下来,亲身实践才能收获更多!vue
在说明以前,咱们先了解一个 Object.defineProperty()
。引用 MDN 上的权威介绍 developer.mozilla.org/zh-CN/docs/… :设计模式
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。缓存语法安全
Object.defineProperty(obj, prop, descriptor) 复制代码
参数bash
obj
函数要定义属性的对象。学习
prop
ui要定义或修改的属性的名称或
Symbol
。spa
descriptor
设计要定义或修改的属性描述符。
返回值
被传递给函数的对象。
在了解了这个以后,咱们就能够用它来实现一个响应式的初级样子。
const TestObject = {
name:"",
age:10
}
let tempValue = '';
Object.defineProperty(TestObject,'name',{
get:function (){
console.log('我被获取了,我能够在这里搞点事情!')
return tempValue;
},
set:function (newValue){
console.log('我被写入了,我能够在这里搞点事情!')
tempValue = newValue
}
})
复制代码
此时使用 TestObject.name
方法可让 get 和 set 里面的对应生效。
我想看到这里,聪明的同窗可能会有一个疑问,为啥要搞一个 tempValue ?我直接 TestObject.name
在 get 和 set 里面赋值不行么?就像这样:
const TestObject = {
name:"",
age:10
}
Object.defineProperty(TestObject,'name',{
get:function (){
console.log('我被获取了,我能够在这里搞点事情!')
return TestObject.name;
},
set:function (newValue){
console.log('我被写入了,我能够在这里搞点事情!')
TestObject.name = newValue
}
})
复制代码
真正运行的时候,其实会发现,陷入了死循环,这里你们切忌要避免坑!
咱们重读MDN上的文档能够发现,get 和 set 自己就是在获取和设置的时候触发的函数,在里面写了 TestObject.name ,那么就会继续调用 set ,而后继续 TestObject.name ,继续 set ,继续.....无限循环。因此使用一个临时变量在外面,是比较安全的作法。
在前面基础上,咱们如今能够开始接管整个对象了,逻辑很是简单,套个循环,上代码:
function ProxyObj(obj){
Object.keys(obj).forEach(key=>{
DefineObj(obj,key,obj[key])
})
}
function DefineObj(obj,key,value){
Object.defineProperty(obj,key,{
get:function (){
console.log('我被获取了,我能够在这里搞点事情!')
return value;
},
set:function (newValue){
console.log('我被写入了,我能够在这里搞点事情!')
value = newValue
}
})
}
const TestObject = {
name:"",
age:10
}
ProxyObj(TestObject)
复制代码
这时,聪明的同窗又会有疑问了?为何要建立两个 function ,ProxyObj 和 DefineObj 不能堆在一个里面么?就像这样
function ProxyObj(obj){
Object.keys(obj).forEach(key=>{
Object.defineProperty(obj,key,{
get:function (){
console.log('我被获取了,我能够在这里搞点事情!')
return obj[key];
},
set:function (newValue){
console.log('我被写入了,我能够在这里搞点事情!')
obj[key] = newValue
}
})
})
}
const TestObject = {
name:"",
age:10
}
ProxyObj(TestObject)
复制代码
不能,其实缘由跟以前提到的问题同样,存在死循环的问题。那为何咱们拆分开来就不存在呢?由于这里的 DefineObj 传入的形参。用咱们了解到的 js 基础知识来解释,一个函数内的形参,至关因而函数内预设的变量,通常状况下(也会有二般状况)这个变量的生命周期仅在函数内。因此利用了这个特性,案例巧妙的将形参 value 拿出来传递和赋值,就不存在无限死循环的问题了。
划重点:使用 Object.defineProperty
切忌不要陷入到死循环当中!
终于来到了收集依赖环节,这块也是我以前一直没有想通的地方。直到次日醒来朦胧中看着屏幕前的代码,忽然如有所思了,话很少说,直接来看看需求,对于需求都没有很是清晰的概念,直接上代码有点晕。
const TestObject = {
name:"",
age:10
}
watcher(TestObject,'type',()=>{
return TestObject.age>18?'成年人':'未成年人';
})
复制代码
咱们须要实现这个watcher函数,里面能够填入对象,而且设置关注的对象 type
属性,此时该属性尚未在对象身上,咱们须要赋予一下,当 age
变化之后,type
也要对应着改变一下。这就是咱们所说的依赖收集需求。
咱们这样简单实现一下:
function watcher(obj,key,cb){
Object.defineProperty(obj,key,{
get:function (){
const val = cb();
return val;
},
set:function(newValue){
console.log('该属性是被用于去自动计算的哦~不要人工赋值!')
}
})
}
const TestObject = {
name:"",
age:10
}
watcher(TestObject,'type',()=>{
return TestObject.age>18?'成年人':'未成年人';
})
console.log(TestObject.type)//未成年人
TestObject.age=19;
console.log(TestObject.type)//成年人
复制代码
能够看到,咱们经过这样的方式,就简单的实现了 TestObject
属性上的依赖计算属性 。
可是问题又来了,我若是不去读取 type 它是不会主动更新的。如何作到 age 变化之后,type 自动更新呢?
在前面的基础上,咱们先定义一个依赖更新时候的函数 updateTodo
此时代码变这样:
let target = '';
function watcher(obj,key,cb){
function updateTodo(){
const val = cb();
console.log('更新啦',val)
}
Object.defineProperty(obj,key,{
get:function (){
target = updateTodo;//重点代码
const val = cb();//重点代码
target = '';//重点代码
return val;
},
set:function(newValue){
console.log('该属性是被用于去自动计算的哦~不要人工赋值!')
}
})
}
const TestObject = {
name:"",
age:10
}
watcher(TestObject,'type',()=>{
return TestObject.age>18?'成年人':'未成年人';//重点代码
})
console.log(TestObject.type)
TestObject.age = 19;
console.log(TestObject.type)
复制代码
会发现咱们顺便在全局还定义一个target,储存当前 callback,这里是我以为最重要的部分,必定要认真看里面的这段,思考一下,我单独拿出来:
target = updateTodo;
const val = cb();
target = '';
复制代码
callback函数长这样:
()=>{
return TestObject.age>18?'成年人':'未成年人';
}
复制代码
可能你们会有疑问,target 赋一下有重置是什么骚操做?有何意义?
注意, callback 里面有一个 TestObject.age
!这个一旦被访问,它的 get 函数就被调用了!那么此刻,前面target里面储存的 updateTodo 函数,是否是就能够在 get 里面取到了呢?
因此在 cb() 执行完以后,实际上里面就有机会收集依赖了,target 就是这个做用,做为一个临时的 callback 缓存着,那么咱们的需求也很好解决了,只须要在这里进行一次依赖的收集和释放便可!
这里的疑问解决了之后,就开始直接上代码吧!后面就比较好理解,跟咱们前面的接管对象章节作一个合并,直接展现完整代码,细细品味,很是有意思:
function ProxyObj(obj){
Object.keys(obj).forEach(key=>{
DefineObj(obj,key,obj[key])
})
}
function DefineObj(obj,key,value){
let deps = [];
Object.defineProperty(obj,key,{
get:function (){
console.log('我被获取了,我能够在这里搞点事情!')
//依赖收集
if(target && !deps.includes(target)){//判断是否存在或重复依赖
deps.push(target)
}
return value;
},
set:function (newValue){
console.log('我被写入了,我能够在这里搞点事情!')
value = newValue;
deps.forEach(fn=>{//依赖释放
fn()
})
}
})
}
function watcher(obj,key,cb){
function updateTodo(){
const val = cb();
console.log('更新啦',val)
}
Object.defineProperty(obj,key,{
get:function (){
target = updateTodo;
const val = cb();
target='';
return val;
},
set:function(newValue){
console.log('该属性是被用于去自动计算的哦~不要人工赋值!')
}
})
}
let target = '';
const TestObject = {
name:"",
age:10
}
ProxyObj(TestObject)
watcher(TestObject,'type',()=>{
return TestObject.age>18?'成年人':'未成年人';
})
console.log(TestObject.type)
TestObject.age=19;
console.log(TestObject.type)
复制代码
固然,若是你对设计模式和一些代码整洁有要求,看到这里你可能会以为很是不爽,没有关系,尝试着本身来封装下吧,或者参看 Vue 内的源码部分。