dmq / mvvm Goto Github PK
View Code? Open in Web Editor NEW剖析vue实现原理,自己动手实现mvvm
剖析vue实现原理,自己动手实现mvvm
Watcher.js文件
get: function() {
Dep.target = this;
var value = this.getter.call(this.vm, this.vm);
Dep.target = null;
return value;
},
这里的Dep.target为什么要再次重置为null?
demo中的例子:
<div id="mvvm-app">
<input type="text" v-model="someStr">
<input type="text" v-model="child.someStr">
<!-- <p v-class="className" class="abc">
{{someStr}}
<span v-text="child.someStr"></span>
</p> -->
<p>{{getHelloWord}}</p>
<p v-html="child.htmlStr"></p>
<button v-on:click="clickBtn">change model</button>
<br>
{{getHelloWord}} {{getHelloWord}} <!-- 如果这样设置会出现错误, -->
<br>
</div>
错误如下:
watcher.js:50 Uncaught TypeError: Cannot read property 'call' of undefined
at Watcher.get (http://localhost:63342/mvvm/js/watcher.js:50:32)
at Watcher (http://localhost:63342/mvvm/js/watcher.js:13:23)
at Object.bind (http://localhost:63342/mvvm/js/compile.js:128:9)
at Object.text (http://localhost:63342/mvvm/js/compile.js:96:14)
at Compile.compileText (http://localhost:63342/mvvm/js/compile.js:73:21)
at http://localhost:63342/mvvm/js/compile.js:41:20
at Array.forEach (native)
at Compile.compileElement (http://localhost:63342/mvvm/js/compile.js:33:35)
at Compile.init (http://localhost:63342/mvvm/js/compile.js:26:14)
at Compile (http://localhost:63342/mvvm/js/compile.js:7:14)
而且如果在 Element 是这样设置的
<div>Hello ,{{someStr}} </div>
, 你的做法会将{{model}}填充整个节点,只留下<div>{{someStr}} </div>
请问项目中的watcher.js和compile.js中的call函数是什么意思啊,没有找到call函数的实现啊。也看不懂。
还有一个问题就是为什么 child.name 是个新值,之前的 setter、dep 都已经失效?
想请教你是如何实现UI层 与 data的绑定的
优化了一下,通过参数控制是否触发属性的get , 把订阅者(watcher)添加到订阅器(keyDep)中。这样会比较好理解, 另外个人觉得 在闭包中保存 dep 对新手不太好理解, 可以提取出来 用统一的发布订阅中心维护 订阅器和相应的订阅者
observer.js
get: function () {
KeyDep.current_watcher && keydep.addSub(KeyDep.current_watcher)
return val;
},
watcher.js
function Watcher(vm,attr_val,cb){
this.vm = vm;
this.attr_val = attr_val;
this.cb = cb;
this.value = this.get(this) //触发get 把 自己(订阅者)添加到 相应的订阅器中
}
Watcher.prototype = {
contructor: Watcher,
update(){
var newValue = this.get(null); //再次触发get 不再往 相应的订阅器中 添加自己(订阅者)
var oldValue = this.value;
if(newValue !== oldValue){
this.value = newValue;
this.cb.call(this.vm,newValue,oldValue)
}
},
get(_this){
KeyDep.current_watcher = _this;
var value = compileUtil._getVMVal(this.vm,this.attr_val);
KeyDep.current_watcher = null;
return value
}
}
get里有 if (Dep.target) { dep.depend()} 这段,而后面又直接设置 Dep.target = null 这是为了以后用的吗?看不懂
看代码的时候发现 每一个动态node节点(携带指令、或者{{text}} 文本节点) 都会生成一个Watcher,这个Watcher 绑定了对应的节点和更新函数,当更新data中的数据时,调用dep.notify(), 触发Watcher里面的update方法,再触发绑定的更新函数,从而实现节点的更新。
但是今天看Vue2.0源码的时候发现,每个Vue实例只会绑定一个Watcher,当调用dep.notify(), Watcher做的事情是创建一个新的虚拟节点,再通过虚拟节点的diff来更新试图,和简易版的MVVM是有差别,不知道理解的对不对。
项目里的一个watcher对应一个指令,而vue源码里是一个watcher对应一个vm,依赖变化触发mountComponent,patch vdom,之所以不一样是因为vue完全使用vdom的原因吗
多谢提供的分享,但是Watcher存在一个问题
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
// 此处为了触发属性的getter,从而在dep添加自己,结合Observer更易理解
this.value = this.get();
}
Watcher.prototype = {
update: function() {
this.run(); // 属性值变化收到通知
},
run: function() {
var value = this.get(); // 取到最新值
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal); // 执行Compile中绑定的回调,更新视图
}
},
get: function() {
Dep.target = this; // 将当前订阅者指向自己
var value = this.vm[exp]; // 触发getter,添加自己到属性订阅器中
Dep.target = null; // 添加完毕,重置
return value;
}
};
如果每次触发更新,都会把Watcher加入观察者中,存在部分性能问题
可以这样
function Watcher(vm, exp, cb) {
Dep.target = this; // 将当前订阅者指向自己
this.cb = cb;
this.vm = vm;
this.exp = exp;
// 此处为了触发属性的getter,从而在dep添加自己,结合Observer更易理解
this.value = this.get();
Dep.target = null; // 添加完毕,重置
}
Watcher.prototype = {
update: function() {
this.run(); // 属性值变化收到通知
},
run: function() {
var value = this.get(); // 取到最新值
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal); // 执行Compile中绑定的回调,更新视图
}
},
get: function() {
var value = this.vm[exp]; // 触发getter,添加自己到属性订阅器中
return value;
}
};
只有每次初始化的时候才会加入观察者
//非最后一个key,更新val的值
if (i < exp.length - 1) {
val = val[k]
} else {
val[k] = value
}
这里没看懂为什么要加上这个条件,什么原因造成不可以直接val[k] = value,可以举个例子吗?
非常感谢!@DMQ
可以看下react,这样是不是可以大大提高解析
比如我在页面上
<script>
new MVVM({
el: '#todoist_app',
data: {
input_due_date: '00月00日'
},
methods: {
}
})
document.body.appendChild('<div>{{input_due_date}}</div>')
</script>
在这种情况下,没有渲染input_due_date
属性变量变化是怎样触发计算属性变化的,这块没看明白,请教下
test
data.index = 1
for(var i=0;i<10000;i++) {
vm.index = i;
}
//update=》run函数执行10000次,dom执行10000次,这个怎么解决?
大佬, 能解答一下,为什么 Object.defineProperty 劫持一下 computed 中的属性,就可以实现 computed 的功能呢,
computed: {
fullname () {
return this.firstname + this.lastname
}
}
firstname 或 lastname 更新怎么触发的 fullname, 这一块不是很明白, 为什么 lastname 更新时 set 方法里的 dep.notify(); 这个 dep 是 跟 fullname 相关的。
大佬方便微信或其他聊天工具联系一下吗
发现了个不知算不算问题的小问题,就是如果直接将一个对象赋值给构造函数Function的prototype,导致Function.prototype.constructor指向Object了。
如:
function Dep() {
.....
}
Dep.prototype = {
......
}
let dep = new Dep();
console.log(dep.constructor) //Object
// 将原生节点拷贝到fragment
while (child = el.firstChild) {
fragment.appendChild(child);
}
这里是个无限循环,或许可以改为:
for (child = el.firstChild; child != null; child = child.nextSibling) {
fragment.appendChild(child);
}
这个不是赋值语句吗? 不应该是判断语句吗 ==
或者 ===
您好 , 您关于vue双向数据绑定的文章写得灰常好!!!
有小地方还不太理解,能具体讲讲compile初始化的时候watcher有什么用处以及修改input里value值时watcher的调用情况吗?
非常感谢!!!
订阅者的update()方法有点不理解,
复现步骤
run: function() {
this.cb.call(this.vm);
}
// 生成一个data对象
var data = { a: 'test', b: {c:'test2'} }
// 用data去实例化MVVM
var vm = new MVVM({ data })
// 生成一个观察者,去观察data.b.c的变化,并传入回调函数
new Watcher(vm, '_data.b.c', ()=>console.log('观察到数据c变化,开始执行操作'))
vm._data.b.c = 'test3' // 观察到数据c变化,开始执行操作
vm._data.b = {} // 观察到数据c变化,开始执行操作
给c赋值,监听到变化,这是正常的。
但是给b赋值,他也执行c的回调就不对了。原因是,在parseGetter中,我们想获得b.c,就会先执行b的get, 再执行c的get,但是他的watcher都是同一个。
有什么好的solution吗?我看vue的实现,也没看到怎么处理这个循环绑定watcher的
我这里用中介者模式实现了一下,不知道有没有什么利弊可以说明一下?
https://github.com/ziyi2/mvvm
configurable 为 true 时,该属性描述符才能够被改变,
configurable 为 false时, 才不能被define
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, 'ob', this);
if (Array.isArray(value)) {
var augment = hasProto
? protoAugment
: copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value);
}
};
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
};
这是尤大的写法
你是直接用字面量的方式进行重写
在学习你的代码的过程中发现按照你的写法显示walk 为定义
源代码如下:
node2Fragment: function (el) { var fragment = document.createDocumentFragment(), child; // 将原生节点拷贝到fragment while (child = el.firstChild) { fragment.appendChild(child); } }
上面代码中,el每次循环将第一个子节点赋值给child时,el不需要删除对应的子节点吗?
数据代理那部分没有做深层遍历
例如:{{someStr+child.someStr}},这种直接报错,应该是挺常见的,computed可以解决。
如果我扩展了for指令,表达式里会用到局部的index变量,就没办法解析了。
基于Proxy实现的简易版: buejs
function Compile(el, vm) {
this.$vm = vm;
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
if (this.$el) {
this.$fragment = this.node2Fragment(this.$el);
// 上面的代码添加了一个this.$el 的文档碎片,
this.init();
this.$el.appendChild(this.$fragment);
// 这里把文档碎片添加到了父元素上, 为什么父元素不会出现两个一样的子节点呀?
// 因为 this.$el 本来就有一些子节点, 后面有添加了一份 文档碎片节点, 这里感觉很疑惑,希望你帮忙解答一下,谢谢~
}
}
希望能多讲一点帮助理解
留意到Dep类中有一个removeSub方法,但是并没有调用?
在Watcher函数中有一个判断
if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = this.parseGetter(expOrFn) }
我看示例里并没有为function的类型啊,什么情况下这里会触发function的逻辑呢?非常感谢
Dep.target = watcherInstance
标记订阅者是当前watcher
实例,强行触发属性定义的getter
方法
为什么通过获取watcher
实例,会触发data
里面的属性的getter
方法呢?
希望有人帮我解答下呀。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.