index.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<input type="text" v-model="word">
<p v-bind="word"></p>
<button v-click="sayHi">change model</button>
</div>
</body>
<script src="myMvvm.js"></script>
<script src="watcher.js"></script>
</html>
<script>
var app= new myMvvm({
el: '#app',
data: {
word:"hello world"
},
methods:{
sayHi:function () {
this.word="改變後"
console.log("點擊了")
}
}
})
</script>
watcher.js
/*
* watcher,用來綁定更新函數,實現對DOM元素的更新:
* 由_obverse對數據監聽而觸發對DOM更新.
* 本函數包含對某個DOM的某個更新的任務和方法
*
* */
function watcher(name,el,vm,exp,attr){
this.name=name;//指令名稱--這裏尚未用到
this.el=el;//指令對應的DOM元素
this.vm=vm;//指令所屬的myMVVM實例
this.exp = exp;//指令對應的值--也就是指令所對應的data中的屬性名
this.attr=attr;//綁定的屬性值。好比innerHTML
/*
* 更新DOM
* 好比 H3.innerHTML = this.data.number; 當number改變時,會觸發這個update函數,保證對應的DOM內容進行了更新。
* */
this.update=function(){
/*
* /好比 H3.innerHTML = this.data.number; 當number改變時
* */
this.el[this.attr]=this.vm.$data[this.exp];
}
//實例該對象的時候執行更新dom方法
this.update();
}
myMvvm.js
var myMvvm=function(options){
this.init(options);
}
myMvvm.prototype.init=function(options){
/*
* _binding保存着model與view的映射關係,也就是咱們前面定義的Watcher的實例。當model改變時,咱們會觸發其中的指令類更新,保證view也能實時更新
* this._binding={
* 'data中的某值':{
* _directives[watcher.....]
* }
* }
* 也就是說:data中的某值發生了變化,就遍歷data中的某值對應_directives,執行每一個watcher裏面的更新方法來對DOM更新。實現一個值變化,多個dom跟着改變。
* */
this._binding={}
//---------------------
this.$options=options;
this.$el=document.querySelector(options.el);
this.$data=options.data;
this.$methods=options.methods;
this._obverse(this.$data);
this._compile(this.$el);
}
//監聽數據發生變化
myMvvm.prototype._obverse=function(data){
//若是data數據爲空
if(!data){
return ;
}
if(typeof data !=="object"){
throw new Error("data必須是個對象");
}
var self=this;
/*
* 遍歷data中全部屬性
* Object.keys(obj)函數返回一個由一個給定對象的自身可枚舉屬性組成的數組
* */
Object.keys(data).forEach(function (v,k) {
/*
* 遞歸執行本observer函數.
* 判斷當前屬性對應的值可能仍是個對象。因此遞歸。
* 本功能不完善,因此這個遞歸函數沒用,由於本功能沒對data數據中的對象進行完善,也就是本功能還不支持data屬性的值爲對象
* */
if(data[v] &&(typeof data[v]=='object')){self._obverse(data[v]);return;}
//當前屬性的值
var oldValue=data[v];
/*
* 按照前面的數據
* _binding = {
* word:{
* _directives: [...watch實例]
* }
* }
* 這裏是先聲明一個空數組,之後全部和data中某值有關係的都會追加到相應的_directives中。
* 爲什麼要在這裏聲明?
* 由於這裏要根據_binding的key必須爲data中的屬性(鍵)
* 那這個數組什麼時候纔有東西呢?
* 解析指令(好比v-bind)的時候push進去的,由於解析指令的時候,會解析每個dom,而後解析出你的是點擊事件仍是修改文本內容。
* 而後在生成watcher追加進去這個數組-之後全部和data中某值發生改變只要循環執行這個數組中watcher的update更新方法就能夠更新全部和這個data中的某值相關聯的dom
* */
self._binding[v]={
_directives:[]
}
//獲取本data某屬性對應的_directives
var binding= self._binding[v];
/*爲對象全部屬性添加set和get方法--這樣可監聽數據變化
* 不熟悉object.defineProperty方法的看:https://my.oschina.net/u/3112095/blog/1797618
* */
Object.defineProperty(data,v,{
enumerable: true, // 可枚舉--可被for-in和Object.keys()枚舉。
configurable: false, // 目標屬性不能被刪除和不能修再被改特性
get:function () {
//發現這個oldValue若是替換成data[v]會形成堆棧溢出。
return oldValue;
},
set:function(value){
if(oldValue==value)return;
console.log("監聽到--設置值,值變化了");
//發現這個oldValue若是替換成data[v]會形成堆棧溢出。
oldValue=value;
// 當data中某屬性改變時,觸發_binding['某值']._directives 中的綁定的Watcher類的更新--這樣實現一個data中的值發生改變,和它相關聯的dom更新。
binding._directives.forEach(function(item){
item.update();
})
}
});
})
}
//解析指令(v-bind,v-model,v-click)等,並在過程當中對view和model進行綁定
//這裏參數root爲id爲app的Element元素,也就是咱們的根元素
//這裏的解析指令只作了簡單的解析--也就是data中的屬性不支持對象。
myMvvm.prototype._compile=function (root) {
var _this=this;
var nodes=root.children;
//遍歷全部節點
for(var i=0;i<nodes.length;i++){
var node=nodes[i];
//若是子節點還有節點,遞歸調用本函數
//由於這個demo沒作解析data中屬性是對象的狀況--因此這個暫時沒什麼用
if(node.children.length){this._compile(node)}
//若是有v-click屬性,就給他添加監聽
if(node.hasAttribute("v-click")){
node.onclick=(function(){
var attrVal=node.getAttribute("v-click");
//bind是使data的做用域與method函數的做用域保持一致
return _this.$methods[attrVal].bind(_this.$data);
})()
}
//v-model
if(node.hasAttribute('v-model')&&(node.tagName=="INPUT" ||node.tagName=="TEXTAREA")){
var attrVal=node.getAttribute('v-model');
_this._binding[attrVal]._directives.push(new watcher(
'input',
node,
_this,
attrVal,
'value'
));
node.addEventListener('input',function () {
_this.$data[attrVal]=this.value;
console.log(_this.$data[attrVal])
})
}
//v-bind 若是有v-bind屬性,咱們只要使node的值及時更新爲data中word的值便可
if(node.hasAttribute("v-bind")){
var attrVal=node.getAttribute("v-bind");
_this._binding[attrVal]._directives.push(new watcher(
'text',
node,
_this,
attrVal,
'innerHTML'
))
}
}
}