本篇主要爲閱讀源碼做一些準備,相關代碼參考了網上相關資料。
本篇主要講述數據的動態動態綁定與更新:
1:數據的動態綁定與更新核心方法是Object.defineProperty(),下面是相關介紹。
Object.definePropety(obj,property,descriptor)
/
* obj 需要操作的對象
* property 需要修改的屬性
* descriptor 屬性描述符,屬性描述符有兩種類型的取值 1:數據描述符,2:存取描述符,屬性描述符是這兩者之一,不能同時存在
*
數據描述符和存取描述符均具有的可選鍵有:
configurable:(true||false) 默認false, 當且僅當該屬性值爲true時,屬性描述符的內容才能被改變
enumerable: (true||false) 默認false, 當該屬性爲true時,操作的(property)屬性纔是枚舉類型
數據描述符獨有的屬性
value: 默認爲undefined 值爲property屬性對應的值。
writable: 默認false property是否可寫入,僅當爲true時,操作的屬性才能進行賦值操作
存取描述符獨有的屬性
get: 默認值undefined。給property提供geter方法的屬性,當訪問property時,就會觸發get屬性,
set: 默認值undefined。 給property提供setter方法的屬性,當修改property時會觸發該方法。
/
2:這裏我們想實現和vue一樣的格式:
window.onload = function(){
let app = new Vue ({
el:'#app', //根節點
data:{ //數據
number:1,
count:1
},
methods:{ //方法
addN: function(){
this.number+=2
},
addC: function(){
this.count++
}
}
}) }
首先我們需要先定義函數Vue()
//新建Vue函數
function Vue (options){
//初始化實例
this._init(options);
};
Vue.prototype._init = function (options){
this.$el = document.querySelector(options.el); //根節點
this.$data = options.data; //數據
this.$methods = options.methods; //方法
this._var = {}; //爲動態更新數據設置的變量
this._obverse(this.$data); //爲數據設置get set 方法
this._directive(this.$el); // 循環遍歷文檔,找尋v指令進行初始化
};
再看_obverse()方法:
Vue.prototype._obverse = function(data){
//循環數據$data,對每個數據執行Object.defineProperty()
var _this = this;
Object.keys(data).forEach((item)=>{
if(data.hasOwnProperty(item)){
var value = data[item];
_this._var.dire= [];
if(typeof value === 'Object'){
this._obverse(value)
}
Object.defineProperty (data,item,{
configurable: true,
enumerable: true,
get(){
return value
},
set(newValue){
if(newValue!=value){
value = newValue;
//需要明白的是input中的數據對應的是實例data的數據,然後通過v指定標記進行恰當的賦值操作
//如何讓一個確定的節點與數據發生關係,方法是給實例設置變量,將節點作爲對象添加到變量中,然後
//通過某一標記進行循環判斷即可獲得節點。
_this._var.dire.forEach(item=>{
item._update();
})
}
}
})
}
})
};
接着看_directive()方法 : 該方法循環遍歷整個文檔節點,然後找出所有v指令,並對各個指令進行邏輯賦值,v-click增加點擊事件,v-model增加input事件, v-bind:進行數據顯示。
//對v指令進行邏輯賦值
Vue.prototype._directive = function (el) {
let _this = this;
let root = el.children
//childNodes與children的區別
let node=Array.prototype.slice.call(root);
node.forEach((item)=>{
if(item.children.length>0){
_this._directive(item);
}
if(item.hasAttribute('v-click')){
//v-click觸發點擊事件
let attrValue = item.getAttribute('v-click');
item.onclick = function(){
//調用實例的methods方法
_this.$methods[attrValue].apply(_this.$data)
}
}
if(item.hasAttribute('v-model')&&(item.nodeName === 'INPUT'||item.nodeName === 'TEXTAREA')){
//v-model觸發input事件
let attrValue = item.getAttribute('v-model'); //自執行函數爲了初始化input值
item.addEventListener('input',(function(){
let value = item.Value;
_this._var.dire.push(new Node({
content:'value',
name: attrValue,
node: item,
vm: _this
}));
return function(){
_this.$data[attrValue] = this.value==''?0:parseInt(this.value);
}
})(),false)
}
if(item.hasAttribute('v-bind')){
let attrValue = item.getAttribute('v-bind');
//查詢實例對象的數據對節點進行賦值
_this._var.dire.push(new Node({
content:'innerHTML',
name: attrValue,
node: item,
vm: _this
}))
// item.innerHTML = _this.$data[attrValue]
}
})
}
這裏有一個問題:如何對某一節點進行數據的動態交互呢?
要想解決該問題可以將某一節點設置數據需要的必要條件保存下來,然後供vue數據使用,這也是Node函數存在的理由。
//設置節點對象
function Node(opt){
this.$content = opt.content; //內容輸出到哪裏 innerHTML 或者 value
this.$name = opt.name; //節點對應的屬性值 v-bind= 'name'
this.$node = opt.node; //節點本身
this._this = opt.vm // Vue實例this
this._update();
}
Node.prototype._update = function(){
//通過該函數對節點內容進行賦值,由該句話可知我們所需要的節點信息
this.$node[this.$content] = this._this.$data[this.$name]
}
最後說明一下大致流程:
文檔加載完畢 > 執行_init()函數進行初始化>>首先執行_obverse()方法遍歷整個data數據,爲每個數據添加get與set方法,注意
set方法內循環保存有節點信息的數組來對含有v-bind指令的節點進行數據展示。>>接着執行_directive()方法,該方法遍歷整個文檔節點,當發現節點存在v-click就綁定onclick事件與vue實例方法相關聯,當發現v-model就綁定input事件,注意該句柄是一個
自執行函數,目的爲了使該節點初始時就有值,之後又返回一個函數作爲input事件該函數是爲了觸發數據的set方法,
當發現v-bind時,將節點信息保存到數組中同時會運行update()初始化節點的值。
最後附上完整代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue源碼一</title>
</head>
<body>
<div id="app">
<form>
<input type="text" v-model="number" class="in">
<button type="button" v-click="addN">增2</button>
</form>
<h3 v-bind="number"></h3>
<form>
<input type="text" v-model="count" class="in">
<button type="button" v-click="addC">增1</button>
</form>
<h3 v-bind="count"></h3>
</div>
<script>
//新建Vue函數
function Vue (options){
this._init(options);
};
Vue.prototype._init = function (options){
this.$el = document.querySelector(options.el);
this.$data = options.data;
this.$methods = options.methods;
//定義觀察者函數
this._var = {};
this._obverse(this.$data);
this._directive(this.$el);
};
Vue.prototype._obverse = function(data){
//循環數據$data
var _this = this;
Object.keys(data).forEach((item)=>{
console.log(item)
if(data.hasOwnProperty(item)){
var value = data[item];
_this._var.dire= [];
if(typeof value === 'Object'){
this._obverse(value)
}
Object.defineProperty (data,item,{
configurable: true,
enumerable: true,
get(){
return value
},
set(newValue){
if(newValue!=value){
value = newValue;
//編寫代碼對h3中的數據進行實時監控
//需要明白的是input中的數據對應的是實例data的數據,然後通過v指定標記進行恰當的賦值操作
//如何讓一個確定的節點與數據發生關係,方法是給實例設置變量,將節點作爲對象添加到變量中,然後
//通過某一標記進行循環判斷即可獲得節點。
_this._var.dire.forEach(item=>{
item._update();
})
}
}
})
}
})
};
//對v指令進行邏輯賦值
Vue.prototype._directive = function (el) {
let _this = this;
let root = el.children
//childNodes與children的區別
let node=Array.prototype.slice.call(root); //抓化爲真正的數組
node.forEach((item)=>{
if(item.children.length>0){
_this._directive(item);
}
if(item.hasAttribute('v-click')){
//v-click觸發點擊事件
let attrValue = item.getAttribute('v-click');
item.onclick = function(){
console.log(1111111111)
//調用實例的methods方法
_this.$methods[attrValue].apply(_this.$data)
}
}
if(item.hasAttribute('v-model')&&(item.nodeName === 'INPUT'||item.nodeName === 'TEXTAREA')){
//v-model觸發input事件
let attrValue = item.getAttribute('v-model'); //自執行函數爲了初始化input值
item.addEventListener('input',(function(){
let value = item.Value;
_this._var.dire.push(new Node({
content:'value',
name: attrValue,
node: item,
vm: _this
}));
return function(){
_this.$data[attrValue] = this.value==''?0:parseInt(this.value);
}
})(),false)
}
if(item.hasAttribute('v-bind')){
let attrValue = item.getAttribute('v-bind');
//查詢實例對象的數據對節點進行賦值
_this._var.dire.push(new Node({
content:'innerHTML',
name: attrValue,
node: item,
vm: _this
}))
// item.innerHTML = _this.$data[attrValue]
}
})
}
//設置節點對象
function Node(opt){
this.$content = opt.content; //內容輸出到哪裏 innerHTML 或者 value
this.$name = opt.name; //節點對應的屬性值 v-bind= 'name'
this.$node = opt.node; //節點本身
this._this = opt.vm // Vue實例this
this._update();
}
Node.prototype._update = function(){
//通過該函數對節點內容進行賦值
this.$node[this.$content] = this._this.$data[this.$name]
}
window.onload = function(){
let app = new Vue ({
el:'#app',
data:{
number:1,
count:1
},
methods:{
addN: function(){
this.number+=2
},
addC: function(){
this.count++
}
}
})
}
</script>
</body>
</html>