分析
- 劫持数据
- 指令解析
- 数据或View发生变化时更新
实践
数据劫持是由Object.defineProperty()来实现
var Observe = function(data){
Object.keys(data).forEach(key=>{
var dep = new Dep();
Object.defineProperty(data,key,{
get:function(){
if(Dep.target){
// 将watcher和dep联系起来
Dep.target.addDep(dep)
}
return data[key];
},
set:function(newVal){
// 对数据的赋值操作
// 需要更新View
// 由Dep进行桥接
data[key] = newVal;
dep.notify()
}
})
})
}
当取值时var val = data.key时将触发getter
当赋值时data.key = val时,将触发setter
2. 指令解析就是对DOM的自定义属性和Mustache语法进行解析。同时我们还需要初始化View
<div id="app">
{{msg}}
<span v-text="msg"></span>
<input type="text" v-model="msg" />
</div>
我们需要解析{{msg}}、v-text、v-model
var Compile = function(el,data){
this._el = document.querySelector(el);
this._data = data;
this.compileDOM(this._el)
}
Compile.prototype = {
compileDOM:function(el){
// 匹配 Mustache
var regExp = /\{\{(\w+)\}\}/;
var childNodes = el.childNodes;
Array.from(childNodes).forEach(node=>{
if(node.nodeType === 1){
this.parseAttrs(node);
}else if(node.nodeType === 3 && regExp.test(node.textContent)){
Parse.text(node,this._data,RegExp.$1)
}
if(node.childNodes && node.childNodes.length){
this.compileDOM(node)
}
})
},
parseAttrs:function(node){
var attrs = node.attributes;
[].slice.call(attrs).forEach(attr=>{
// 属性名
var attrName = attr.name;
if(attrName.startsWith('v-')){
var path = attrName.substring(2);
// 属性值 对应数据中的key值
var attrVal = attr.value;
Parse[path](node,this._data,attrVal)
}
})
}
}
初始化View
var Parse = {
text:function(node,data,key){
// 这里进行了取值操作
node.textContent = data[key];
// 当数据发生变化 更新View
new Watcher(data,key,function(newVal){
node.textContent = newVal;
})
},
model:function(node,data,key){
// 这里进行了取值操作
node.value = data[key];
// 绑定监听函数
node.addEventListener('input',function(){
// 这里进行了赋值操作 View发生了变化
data[key] = this.value;
});
// 当数据发生变化 更新View
new Watcher(data,key,function(newVal){
node.value = newVal;
})
}
}
至此解析指令并初始化View完成,然而当数据和View还没有任何联系,接下来就是让他们产生联系
var uid = 0;
var Dep = function(){
this.id = uid++;
this.subs = [];
}
Dep.target = null;
Dep.prototype = {
addWatcher:function(watcher){
// 绑定一个数据的watcher
this.subs.push(watcher)
},
notify:function(){
// 赋值操作会触发notify
// 再由Dep触发绑定的Watchers更新
this.subs.forEach(watcher=>{
watcher.update();
})
}
}
Dep作为数据和Watcher的桥接,取值操作时绑定此数据的watcher,赋值操作时触发watcher的更新
var Watcher = function(data,key,cb){
this._data = data;
this._key = key;
this._cb = cb;
this.deps = {};
// 获取数据
// 将Watcher和Dep关联起来
this.value = this.get()
}
Watcher.prototype = {
get:function(){
Dep.target = this;
// 这里进行了取值操作
var val = this._data[this._key];
Dep.target = null;
return val;
},
update:function(){
var newVal = this.get();
if(this.value !== newVal){
this.value = newVal;
this._cb(newVal);
}
},
addDep:function(dep){
if(!this.deps[dep.id]){
this.deps[dep.id] = dep;
dep.addWatcher(this);
}
}
}
Watcher数据的View的桥梁,一条数据对应一个watcher
View发生变化,触发数据变化,watcher在更新View
最后就是整合了
var Mvvm = function(opts){
this._el = opts.el;
this._data = opts.data;
Object.keys(this._data).forEach(key=>{
Object.defineProperty(this,key,{
get:function(){
return this._data[key]
},
set:function(newVal){
this._data[key] = newVal;
}
})
})
new Observer(this._data);
new Compile(this._el,this)
}
参考文档 合格前端系列
PS:虽然如今大火,但是一直没有仔细研究过,粗略的看过后发现被问到并不能回答上来,只能自己写一遍!欢迎指正!