1、解析dom、fragment编译,初始化new watcher
2 ,数据劫持,Object.defineProperty(obj,key,{
configurable:true,// 可以配置
enumerable:true, // 可以枚举
get:function(){return value}, // 添加wacher,添加订阅者
set:function(newValue){ return newValue} // noticfy:执行,更新数据
})
3, 发布订阅模式:
什么是发布订阅者模式呢?举个例子:大爷我要传授们手艺,我这里收了很多徒弟,为了节约时间,我将他们记录在我的邮件里,等我准备好资料,在我这里留底的人员进行群发
接下里开始代码实现的过程
1,创建一个简单的应用,
<div id="mvvm-app">
<input type="text" v-model="word">
<p>{{word}}</p>
<button v-on:click="sayHi">change model</button>
</div>
<script src="./js/observer.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/compile.js"></script>
<script src="./js/mvvm.js"></script>
<script>
var vm = new MVVM({
el: '#mvvm-app',
data: {
word: 'Hello World!'
},
methods: {
sayHi: function() {
this.word = 'Hi, everybody!';
}
}
});
</script>
2,我们先初始化将模板中的 {{}}替换,创建compile函数进行编译,替换
function MVVM(options) {
this.$options = options; // this是挂载vm上的,防止和data里面取值冲突,我们这里定义属性的,传递的data,用一些特定的字符来区别,按照我们在 var data = this._data = this.$options.data;// 源码上注解,我们的data采用,_data,我们的属性采用$options observe(data, this); this.$compile = new Compile(options.el || document.body, this) }
3,我们要开始实现我们的Compile函数
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.
init();
this.
$el.
appendChild(
this.
$fragment);
}
}
Compile.
prototype = {
node2Fragment
:
function(
el) {
var
fragment =
document.
createDocumentFragment(),
child;
// 将原生节点拷贝到fragment
while (
child =
el.
firstChild) {
fragment.
appendChild(
child);
}
return
fragment;
},
init
:
function() {
this.
compileElement(
this.
$fragment);
},
compileElement
:
function(
el) {
var
childNodes =
el.
childNodes,
me =
this;
[].
slice.
call(
childNodes).
forEach(
function(
node) {
var
text =
node.
textContent;
var
reg =
/
\{\{
(
.
*
)
\}\}
/;
if (
me.
isElementNode(
node)) {
me.
compile(
node);
}
else
if (
me.
isTextNode(
node) &&
reg.
test(
text)) {
me.
compileText(
node,
RegExp.
$1);
}
if (
node.
childNodes &&
node.
childNodes.
length) {
me.
compileElement(
node);
}
});
},
compile
:
function(
node) {
var
nodeAttrs =
node.
attributes,
me =
this;
[].
slice.
call(
nodeAttrs).
forEach(
function(
attr) {
var
attrName =
attr.
name;
if (
me.
isDirective(
attrName)) {
var
exp =
attr.
value;
var
dir =
attrName.
substring(
2);
// 事件指令
if (
me.
isEventDirective(
dir)) {
compileUtil.
eventHandler(
node,
me.
$vm,
exp,
dir);
// 普通指令
}
else {
compileUtil[
dir] &&
compileUtil[
dir](
node,
me.
$vm,
exp);
}
node.
removeAttribute(
attrName);
}
});
},
compileText
:
function(
node,
exp) {
compileUtil.
text(
node,
this.
$vm,
exp);
},
isDirective
:
function(
attr) {
return
attr.
indexOf(
'v-') ==
0;
},
isEventDirective
:
function(
dir) {
return
dir.
indexOf(
'on') ===
0;
},
isElementNode
:
function(
node) {
return
node.
nodeType ==
1;
},
isTextNode
:
function(
node) {
return
node.
nodeType ==
3;
}
};
// 指令处理集合
var
compileUtil = {
text
:
function(
node,
vm,
exp) {
this.
bind(
node,
vm,
exp,
'text');
},
html
:
function(
node,
vm,
exp) {
this.
bind(
node,
vm,
exp,
'html');
},
model
:
function(
node,
vm,
exp) {
this.
bind(
node,
vm,
exp,
'model');
var
me =
this,
val =
this.
_getVMVal(
vm,
exp);
node.
addEventListener(
'input',
function(
e) {
var
newValue =
e.
target.
value;
if (
val ===
newValue) {
return;
}
me.
_setVMVal(
vm,
exp,
newValue);
val =
newValue;
});
},
class
:
function(
node,
vm,
exp) {
this.
bind(
node,
vm,
exp,
'class');
},
bind
:
function(
node,
vm,
exp,
dir) {
var
updaterFn =
updater[
dir +
'Updater'];
updaterFn &&
updaterFn(
node,
this.
_getVMVal(
vm,
exp));
new
Watcher(
vm,
exp,
function(
value,
oldValue) {
updaterFn &&
updaterFn(
node,
value,
oldValue);
});
},
// 事件处理
eventHandler
:
function(
node,
vm,
exp,
dir) {
var
eventType =
dir.
split(
':')[
1],
fn =
vm.
$options.
methods &&
vm.
$options.
methods[
exp];
if (
eventType &&
fn) {
node.
addEventListener(
eventType,
fn.
bind(
vm),
false);
}
},
_getVMVal
:
function(
vm,
exp) {
var
val =
vm;
exp =
exp.
split(
'.');
exp.
forEach(
function(
k) {
val =
val[
k];
});
return
val;
},
_setVMVal
:
function(
vm,
exp,
value) {
var
val =
vm;
exp =
exp.
split(
'.');
exp.
forEach(
function(
k,
i) {
// 非最后一个key,更新val的值
if (
i <
exp.
length -
1) {
val =
val[
k];
}
else {
val[
k] =
value;
}
});
}
};
var
updater = {
textUpdater
:
function(
node,
value) {
node.
textContent =
typeof
value ==
'undefined' ?
'' :
value;
},
htmlUpdater
:
function(
node,
value) {
node.
innerHTML =
typeof
value ==
'undefined' ?
'' :
value;
},
classUpdater
:
function(
node,
value,
oldValue) {
var
className =
node.
className;
className =
className.
replace(
oldValue,
'').
replace(
/\s
$
/,
'');
var
space =
className &&
String(
value) ?
' ' :
'';
node.
className =
className +
space +
value;
},
modelUpdater
:
function(
node,
value,
oldValue) {
node.
value =
typeof
value ==
'undefined' ?
'' :
value;
}
};
实现watcher
function
Watcher(
vm,
expOrFn,
cb) {
this.
cb =
cb;
this.
vm =
vm;
this.
expOrFn =
expOrFn;
this.
depIds = {};
if (
typeof
expOrFn ===
'function') {
this.
getter =
expOrFn;
}
else {
this.
getter =
this.
parseGetter(
expOrFn);
}
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);
}
},
addDep
:
function(
dep) {
// 1. 每次调用run()的时候会触发相应属性的getter
// getter里面会触发dep.depend(),继而触发这里的addDep
// 2. 假如相应属性的dep.id已经在当前watcher的depIds里,说明不是一个新的属性,仅仅是改变了其值而已
// 则不需要将当前watcher添加到该属性的dep里
// 3. 假如相应属性是新的属性,则将当前watcher添加到新属性的dep里
// 如通过 vm.child = {name: 'a'} 改变了 child.name 的值,child.name 就是个新属性
// 则需要将当前watcher(child.name)加入到新的 child.name 的dep里
// 因为此时 child.name 是个新值,之前的 setter、dep 都已经失效,如果不把 watcher 加入到新的 child.name 的dep中
// 通过 child.name = xxx 赋值的时候,对应的 watcher 就收不到通知,等于失效了
// 4. 每个子属性的watcher在添加到子属性的dep的同时,也会添加到父属性的dep
// 监听子属性的同时监听父属性的变更,这样,父属性改变时,子属性的watcher也能收到通知进行update
// 这一步是在 this.get() --> this.getVMVal() 里面完成,forEach时会从父级开始取值,间接调用了它的getter
// 触发了addDep(), 在整个forEach过程,当前wacher都会加入到每个父级过程属性的dep
// 例如:当前watcher的是'child.child.name', 那么child, child.child, child.child.name这三个属性的dep都会加入当前watcher
if (!
this.
depIds.
hasOwnProperty(
dep.
id)) {
dep.
addSub(
this);
this.
depIds[
dep.
id] =
dep;
}
},
get
:
function() {
Dep.
target =
this;
var
value =
this.
getter.
call(
this.
vm,
this.
vm);
Dep.
target =
null;
return
value;
},
parseGetter
:
function(
exp) {
if (
/
[^
\w.$
]
/.
test(
exp))
return;
var
exps =
exp.
split(
'.');
return
function(
obj) {
for (
var
i =
0,
len =
exps.
length;
i <
len;
i++) {
if (!
obj)
return;
obj =
obj[
exps[
i]];
}
return
obj;
}
}
};
function
Observer(
data) {
this.
data =
data;
this.
walk(
data);
}
Observer.
prototype = {
walk
:
function(
data) {
var
me =
this;
Object.
keys(
data).
forEach(
function(
key) {
me.
convert(
key,
data[
key]);
});
},
convert
:
function(
key,
val) {
this.
defineReactive(
this.
data,
key,
val);
},
defineReactive
:
function(
data,
key,
val) {
var
dep =
new
Dep();
var
childObj =
observe(
val);
Object.
defineProperty(
data,
key, {
enumerable:
true,
// 可枚举
configurable:
false,
// 不能再define
get
:
function() {
if (
Dep.
target) {
dep.
depend();
}
return
val;
},
set
:
function(
newVal) {
if (
newVal ===
val) {
return;
}
val =
newVal;
// 新的值是object的话,进行监听
childObj =
observe(
newVal);
// 通知订阅者
dep.
notify();
}
});
}
};
function
observe(
value,
vm) {
if (!
value ||
typeof
value !==
'object') {
return;
}
return
new
Observer(
value);
};
var
uid =
0;
function
Dep() {
this.
id =
uid++;
this.
subs = [];
}
Dep.
prototype = {
addSub
:
function(
sub) {
this.
subs.
push(
sub);
},
depend
:
function() {
Dep.
target.
addDep(
this);
},
removeSub
:
function(
sub) {
var
index =
this.
subs.
indexOf(
sub);
if (
index != -
1) {
this.
subs.
splice(
index,
1);
}
},
notify
:
function() {
this.
subs.
forEach(
function(
sub) {
sub.
update();
});
}
};
Dep.
target =
null;
整体实现此路,需要一张图演示实现思路
图片上传不好使了,上传一个图片,沾上图片链接
https://wx2.sinaimg.cn/mw690/9f27f7e9ly1g2v1spxutbj20s00860tp.jpg