童鞋們都應(yīng)該知道Vue2.x很重要的特性(數(shù)據(jù)雙向綁定、虛擬DOM)瘸恼,所以在面試的時(shí)候就經(jīng)常有面試官問(wèn)小白同學(xué)說(shuō)數(shù)據(jù)雙向綁定原理是什么劣挫,虛擬DOM是什么,給我實(shí)現(xiàn)一個(gè)唄东帅。所以給大家展示一個(gè)最簡(jiǎn)的雙向綁定的案例压固,也參考了網(wǎng)上一些資料。至于虛擬DOM的實(shí)現(xiàn)后面再說(shuō)吧靠闭,網(wǎng)上資料也很多很全因?yàn)楫吘箁eact都出來(lái)好久了帐我。(都知道網(wǎng)上資料一大堆坎炼,大都不著調(diào)。)直接上代碼@辜Rス狻!
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="msg">
<span>{{msg}}</span>
</div>
<script src="mvvm/watcher.js"></script>
<script src="mvvm/observer.js"></script>
<script src="mvvm/compile.js"></script>
<script src="mvvm/index.js"></script>
<script>
let vm = new Vue({
el: "app",
data: {
msg: 'hello world'
}
})
</script>
</body>
</html>
- index.js
// 這個(gè)文件為入口文件芬为,也就是Vue的構(gòu)造函數(shù)
function Vue(options) {
// 傳遞過(guò)來(lái)的對(duì)象
this.data = options.data;
this.id = options.el;
// 1.第一步先將data中所有的數(shù)據(jù)進(jìn)行監(jiān)聽(tīng)
// 這個(gè)函數(shù)一般使用遞歸的方式完成所有屬性的監(jiān)聽(tīng)
observer(this.data, this);
// 2.第二步將所有DOM節(jié)點(diǎn)的翻譯出來(lái)抢肛,也就是說(shuō)將v-model,
// {{}}等翻譯成你想要的數(shù)據(jù)碳柱。其次還有將v-model的數(shù)據(jù)進(jìn)行監(jiān)聽(tīng)捡絮,使用觀察者模式,完成雙向綁定
getAllNode(document.getElementById(this.id), this);
}
- observer.js
// 定義一個(gè)pubsub,這個(gè)作用是將所有的觀察者加入其中莲镣,
// 并且出發(fā)事件
function pubsub() {
this.subs = [];
}
pubsub.prototype = {
// 將需要觀察的數(shù)據(jù)加入subs中
addSub: function(sub){
this.subs.push(sub);
},
// 執(zhí)行觀察的數(shù)據(jù)上綁定的事件update事件福稳。
pub: function(){
console.log(this.subs);
this.subs.forEach(function(sub){
sub.update();
})
}
}
// 將數(shù)據(jù)都進(jìn)行監(jiān)聽(tīng)
function active(obj, key, val) {
var pubsub1 = new pubsub();
Object.defineProperty(obj.data, key, {
// getter,如果獲取數(shù)據(jù)時(shí)瑞侮,會(huì)判斷是有需要觀察的數(shù)據(jù)的圆,如果有就添加到subs中,沒(méi)有不添加
// Pubsub是一個(gè)全局的變量半火,這個(gè)變量必須是全局才能判斷是有需要觀察的數(shù)據(jù)
get() {
if(Pubsub.target) {
// 添加訂閱
pubsub1.addSub(Pubsub.target);
}
return val;
},
set(newVal) {
// 如果數(shù)據(jù)被setter越妈,那么就涉及到及時(shí)的更新數(shù)據(jù),
// 這時(shí)只需要進(jìn)行發(fā)布事件钮糖,觀察的數(shù)據(jù)就會(huì)執(zhí)行update函數(shù)來(lái)執(zhí)行更新操作
if(val == newVal) {
return;
}
val = newVal;
// 發(fā)布
pubsub1.pub();
}
})
}
// 監(jiān)聽(tīng)data中所有的數(shù)據(jù)
function observer(obj, vm) {
// obj = data
// vm = 實(shí)例對(duì)象
for(var key in obj) {
active(vm, key, obj[key]);
}
}
- comiple.js
// 獲取到所有節(jié)點(diǎn)梅掠,并且進(jìn)行翻譯
function getAllNode(node, vm) {
console.log(vm);
var length = node.childNodes.length;
for(var i = 0; i < length; i++) {
compile(node.childNodes[i], vm)
}
}
// 翻譯
function compile(node, vm) {
// 匹配到{{}},將其中的值進(jìn)行觀察。
var reg = /\{\{(.*)\}\}/;
// 如果節(jié)點(diǎn)存在并且節(jié)點(diǎn)類型為1時(shí)(查看一下為1時(shí)一般都是什么節(jié)點(diǎn))
if(node!=undefined && node.nodeType == 1) {
// 獲取到節(jié)點(diǎn)的屬性
var attr = node.attributes;
if(attr.length) {
// 對(duì)節(jié)點(diǎn)的屬性循環(huán)處理
for(var i = 0; i < attr.length; i++) {
// 如果為v-model時(shí)店归,進(jìn)行處理
if(attr[i].nodeName == "v-model") {
// 獲取到v-model里面的寫的變量名
var name = attr[i].nodeValue;
// 給該input增加事件處理阎抒,如果內(nèi)容改變,并及時(shí)更新data中的數(shù)據(jù)
node.addEventListener('input', function(e) {
vm.data[name] = e.target.value;
})
// 修改dom上的數(shù)據(jù)消痛,并移除指令
console.log(vm.data[name]);
node.value = vm.data[name];
node.removeAttribute('v-model')
}
}
}else {
// 匹配到{{}}的dom節(jié)點(diǎn)
if(reg.test(node.outerText)) {
var name = RegExp.$1;
// 拿到變量的名稱
name = name.trim();
// 將變量加入到watcher中
new Watcher(vm, node, name)
}
}
}
}
- watcher.js
// 全局的變量且叁,這個(gè)變量來(lái)控制是否當(dāng)前有觀察的數(shù)據(jù)
var Pubsub = {
target: null
}
// 觀察者定義
function Watcher(vm, node, name) {
Pubsub.target = this;
this.name = name;
this.node = node;
this.vm = vm;
// 將數(shù)據(jù)觀察時(shí)就要進(jìn)行更新操作一次
this.update();
Pubsub.target = null;
}
Watcher.prototype = {
// 將更新的數(shù)據(jù)渲染到頁(yè)面中去
update() {
this.node.innerHTML = this.vm.data[this.name];
}
}
大家想測(cè)試,就將五個(gè)文件的代碼復(fù)制下來(lái)秩伞,運(yùn)行一下逞带。
運(yùn)行的同時(shí)給大家配一張圖,讓大家容易理解纱新。(這張是盜圖展氓,不知哪位大神用visio畫的,這里我就直接引用了)