(精華2020年5月17日更新) vue實戰篇 手寫vue底層原始碼
MYvue.js 主要作用監聽屬性變化
class MYvue {
constructor(options) {
this.$options = options;
this.$data = options.data;
//資料劫持
this.observe(this.$data);
this.$el = options.el;
//包含Watcher建立
new Complie(options.el, this)
}
//資料劫持
observe(data) {
if (!data || typeof data != 'object') {
return;
}
Object.keys(data).forEach((key) => {
//讓資料可觀測
//this.$data.test 改變
this.defineReactive(data, key, data[key]);
//this.test 代理改變
this.proxyData(key);
})
}
//讓資料可觀測
defineReactive(obj, key, value) {
var dept = new Dep();
//value值為物件遞迴遍歷
this.observe(value);
Object.defineProperty(obj, key, {
get() {
//屬性被讀取了,將來需要新增訂閱
console.log('我被讀取了');
//將Dep.target(當前的watcher物件存入Dep的deps)
Dep.target && dept.addDep(Dep.target)
return value;
},
set(newVal) {
if (newVal == value) {
return;
}
value = newVal;
console.log(key + '屬性更行了,他更新的值是' + newVal);
//如果我被改變, 我將來會在這裡通知的
dept.notify(); //變化的資料,讓watcher的update方法執行
},
enumerable: true,
configurable: true
})
}
//代理data中的屬性到vue例項上
proxyData(key) {
Object.defineProperty(this, key, {
get() {
return this.$data[key]
},
set(newVal) {
this.$data[key] = newVal;
}
})
}
}
//Dep 用來管理wather,管理者的角色
class Dep {
constructor() {
//存放所有的依賴(watcher),一個watcher對應一個屬性
this.deps = [];
}
//收集訂閱者
addDep(sub) {
this.deps.push(sub);
}
//通知訂閱更新
notify() {
this.deps.forEach((sub) => {
//你要更新了
sub.update() //update是watchr裡面的一個函式
})
}
}
//監聽器物件
class Watcher {
constructor(vm, key, cb) {
// vm :Vue例項化物件
// key: 需要監聽的屬性
// cb: 是Watccher繫結的更新函式
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this;//this指Watcher本身
this.vm[key] // 觸發getter ,新增依賴
Dep.target = null;
}
update() {
console.log('你的屬性要更新了');
this.cb.call(this.vm, this.vm[this.key]);
}
}
Complie.js 把屬性變化重新渲染html
class Complie {
constructor(el, vm) {
//遍歷節點
this.$el = document.querySelector(el);
this.$vm = vm;
//編譯
if (this.$el) {
//轉換內容為片段fragment
this.$fragment = this.node2Fragment(this.$el);
//執行編譯
this.replaceTemplate(this.$fragment)
//將編譯完的html結果追加到$el
this.$el.appendChild(this.$fragment);
}
}
node2Fragment(el) {
// createDocumentFragment 用來建立虛擬dom節點
var frag = document.createDocumentFragment();
let child;
//講el中所有的元素搬家到frag
while (el.firstChild && (child = el.firstChild)) {
frag.appendChild(child);
}
return frag;
}
//對el立面的內容進行替換
// 對el裡面的內容進行替換
replaceTemplate(frag) {
// console.log('frag.childNodes',frag.childNodes);
var childNodes = Array.from(frag.childNodes);
if (childNodes.length == 0) return;
childNodes.forEach(node => {
let txt = node.textContent;
let reg = /\{\{(.*?)\}\}/g; // 正則匹配{{}}
// 只讀屬性 Node.nodeType 表示的是該節點的型別
// nodeType 屬性可用來區分不同型別的節點,比如 元素, 文字 和 註釋。
// 即是文字節點又有大括號的情況{{}}
if (node.nodeType === 3 && reg.test(txt)) {
// console.log(RegExp.$1); // 匹配到的第一個分組 如: a.b, c
let arr = RegExp.$1.split('.');
let valTest = this.$vm;
arr.forEach(key => {
valTest = valTest[key]; // 如this.a.b
});
// 用trim方法去除一下首尾空格
node.textContent = txt.replace(reg, valTest).trim();
// 監聽變化,第二步加的
// 給Watcher再新增兩個引數,用來取新的值(newVal)給回撥函式傳參
new Watcher(this.$vm, RegExp.$1, newVal => {
//這裡是有屬性改變的時候會更新資料
node.textContent = txt.replace(reg, newVal).trim();
});
}
// v-modle資料的雙向繫結
if (node.nodeType === 1) { // 元素節點
let nodeAttr = Array.from(node.attributes); // 獲取dom上的所有屬性,是個類陣列
nodeAttr.length > 0 && nodeAttr.forEach(attr => {
let name = attr.name; // v-model type
let exp = attr.value; // c text
if (name.includes('v-')) {
node.value = this.$vm[exp]; // this.c 為 2
}
// oninput .onclick
node.addEventListener('input', e => {
let newVal = e.target.value;
// 相當於給this.c賦了一個新值
// 而值的改變會呼叫set,set中又會呼叫notify,notify中呼叫watcher的update方法實現了更新
this.$vm[exp] = newVal;
});
});
}
// 如果還有子節點,繼續遞迴replaceTemplate
if (node.childNodes && node.childNodes.length) {
this.replaceTemplate(node);
}
});
}
}
使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script src="./MYvue.js"></script>
<script src="./compile.js"></script>
<body>
<div id="app">
<h1>{{song}}</h1>
<p>主打歌為{{album.theme}} </p>
<p> <span>作詞人為{{singer}}等人。</span><em>qq</em></p>
<input type="text" v-model="song">
</div>
<script>
// 寫法和Vue一樣
let MYvue = new MYvue({
el: '#app',
data: {
song: '飛啊飛啊',
album: {
name: '一眼萬年',
theme: '天外飛仙'
},
singer: '胡歌'
}
});
</script>
</body>
</html>
相關文章
- (精華2020年5月21日更新) vue實戰篇 實時通訊websocket的封裝結合vue的使用VueWeb封裝
- (精華2020年5月8日更新) vue教程篇 vue-router路由的使用Vue路由
- (精華2020年5月4日更新) vue教程篇 事件簡寫和事件物件$eventVue事件物件
- (精華2020年5月8日更新) vue教程篇 vue-router路由的許可權控制Vue路由
- (精華)2020年7月12日 vue 手搭腳手架vue-cliVue
- (精華2020年5月20日更新) vue教程篇 父子元件相互傳參Vue元件
- 手寫Vue2.0原始碼(八)-元件原理Vue原始碼元件
- (精華2020年5月4日更新) vue教程篇 計算屬性computed的使用Vue
- (精華2020年5月4日更新) vue教程篇 v-show和v-if的使用Vue
- Vue實戰篇(Vue仿今日頭條)Vue
- (精華2020年5月31日更新) react基礎篇 手寫ssr服務端渲染React服務端
- (精華2020年5月4日更新) vue教程篇 簡單小結(1)-使用者管理Vue
- Vue中的底層原理Vue
- iOS窺探KVO底層實現實戰篇iOS
- [譯] Vue Router 實戰手冊Vue
- 急速 debug 實戰三(Node - webpack外掛,babel外掛,vue原始碼篇)WebBabelVue原始碼
- vue - Vue腳手架(終結篇)/ vue動畫Vue動畫
- 手寫vue路由Vue路由
- Vue底層架構及其應用Vue架構
- vue實戰-元件編寫-todolist元件Vue元件
- Vue 外掛編寫與實戰Vue
- Vue 原始碼解讀(4)—— 非同步更新Vue原始碼非同步
- (精華)2020年7月14日 vue vue-router動態路由的實現許可權控制Vue路由
- (精華)2020年7月16日 vue vue-count-to數字滾動外掛Vue
- 第143篇:手寫vue-router,實現router-viewVueView
- (精華)2020年7月18日 vue clipboard複製剪下Vue
- (精華)2020年7月17日 vue mixins的使用Vue
- HasMap 底層原始碼分析ASM原始碼
- vue-cli原始碼分析(試探篇)Vue原始碼
- 細談 vue - 抽象元件實戰篇Vue抽象元件
- 深入vue2.0底層思想–模板渲染Vue
- (精華)2020年7月18日 vue element-ui實現動態表格VueUI
- Seata原始碼分析(一). AT模式底層實現原始碼模式
- Vue.js 原始碼實現Vue.js原始碼
- spring原始碼解析 (七) 事務底層原始碼實現Spring原始碼
- Vue原始碼解析:Vue例項Vue原始碼
- (精華)2020年7月18日 vue element-ui實現表格可編輯VueUI
- (精華)2020年7月18日 vue element-ui實現表格拖動排序VueUI排序