the super tiny vue.js(200行原始碼)
Online demo:http://yangxiaofu.com/deep-in-vue/src/the-super-tiny-vue.html github: https://github.com/xiaofuzi/deep-in-vue/blob/master/src/the-super-tiny-vue.js es6版本:https://github.com/xiaofuzi/re-vue
the super tiny vue.js. 程式碼總共200行左右(去掉註釋)
簡介:一個迷你vue庫,雖然小但功能全面,可以作為想了解vue背後思想以及想學習vue原始碼而又不知如何入手的入門學習資料。
特性: * 資料響應式更新 * 指令模板 * MVVM * 輕量級
功能解讀
<templete>
<div id='app'>
<div>
<input v-model='counter' />
<button v-on-click='add'>add</button>
<p v-text='counter'></p>
</div>
</div>
var vm = new Vue({
id: 'counter',
data: {
counter: 1
},
methods: {
add: function () {
this.counter += 1;
}
}
})
如上為一段模板以及js指令碼,我們所要實現的目標就是將 vm 例項與id為app的DOM節點關聯起來,當更改vm data 的counter屬性的時候, input的值和p標籤的文字會響應式的改變,method中的add方法則和button的click事件繫結。 簡單的說就是, 當點選button按鈕的時候,觸發button的點選事件回撥函式add,在add方法中使counter加1,counter變化後模板中的input 和p標籤會自動更新。vm與模板之間是如何關聯的則是通過 v-model、v-on-click、v-text這樣的指令宣告的。
實現思路詳解
- 查詢含指令的節點
- 對查詢所得的節點進行指令解析、指令所對應的實現與節點繫結、 節點指令值所對應的data屬性與前一步關聯的指令實現繫結、data屬性值通過setter通知關聯的指令進行更新操作
- 含指令的每一個節點單獨執行第二步
- 繫結操作完成後,初始化vm例項屬性值
指令節點查詢
首先來看第一步,含指令節點的查詢,因為指令宣告是以屬性的形式,所以可以通過屬性選擇器來進行查詢,如下所示:
<input v-model='counter' type='text' />
則可通過 querySelectorAll('[v-model]') 查詢即可。
root = this.$el = document.getElementById(opts.el),
els = this.$els = root.querySelectorAll(getDirSelectors(Directives))
root對於根節點,els對應於模板內含指令的節點。
指令解析,繫結
1.指令解析 同樣以
<input v-model='counter' type='text' />
為例,解析即得到var directive = { name: 'v-model', value: 'counter' }
name對應指令名,value對應指令值。
2.指令對應實現與當前節點的繫結(bindDirective) 指令實現可簡單分為函式或是包含update函式的物件,如下便是
v-text
指令的實現程式碼:text: function (el, value) { el.textContent = value || ''; }
指令與節點的繫結即將該函式與節點繫結起來,即該函式負責該節點的更新操作,
v-text
的功能是更新文字值,所以如上所示 更改節點的textContent屬性值。- 響應式資料與節點的繫結(bindAccessors) 響應式資料這裡拆分為 data 和 methods 物件,分別用來儲存資料值和方法。
var vm = new Vue({ id: 'counter', data: { counter: 1 }, methods: { add: function () { this.counter += 1; } } })
我們上面解析得到 v-model 對於的指令值為 counter,所以這裡將data中的counter與當前節點繫結。
通過2、3兩步實現了型別與 textDirective->el<-data.counter 的關聯,當data.counter發生set(具體檢視defineProperty set 用法)操作時, data.counter得知自己被改變了,所以通知el元素需要進行更新操作,el則使用與其關聯的指令(textDirective)對自身進行更新操作,從而實現了資料的 響應式。
- textDirective
- el
- data.counter 這三個是繫結的主體,資料發生更改,通知節點需要更新,節點通過指令更新自己。
4.其它相關操作
var prefix = 'v'; var Directives = { /** *對應於 v-text 指令 */ text: function (el, value) { el.textContent = value || ''; }, /** *對應於 v-model 指令 */ model: function (el, value, dirAgr, dir, vm, key) { let eventName = 'keyup'; el.value = value || ''; /** * 事件繫結控制 */ if (el.handlers && el.handlers[eventName]) { el.removeEventListener(eventName, el.handlers[eventName]); } else { el.handlers = {}; } el.handlers[eventName] = function (e) { vm[key] = e.target.value; } el.addEventListener(eventName, el.handlers[eventName]); }, on: { update: function (el, handler, eventName, directive) { if (!directive.handlers) { directive.handlers = {} }
} } } /** * MiniVue */ function TinyVue (opts) { /** * root/this.$el: 根節點 * els: 指令節點 * bindings: 指令與data關聯的橋樑 */ var self = this, root = this.$el = document.getElementById(opts.el), els = this.$els = root.querySelectorAll(getDirSelectors(Directives)), bindings = {}; /** * 指令處理 */ [].forEach.call(els, processNode); processNode(root); /** * vm響應式資料初始化 */ let _data = extend(opts.data, opts.methods); for (var key in bindings) { if (bindings.hasOwnProperty(key)) { self[key] = _data[key]; } } /** * ready methods */ if (opts.ready && typeof opts.ready == 'function') { this.ready = opts.ready; this.ready(); } function processNode (el) { getAttributes(el.attributes).forEach(function (attr) { var directive = parseDirective(attr); if (directive) { bindDirective(self, el, bindings, directive); } }) } } /************************************************************** * @privete * helper methods */ /** * 獲取節點屬性 * 'v-text'='counter' => {name: v-text, value: 'counter'} */ function getAttributes (attributes) { return [].map.call(attributes, function (attr) { return { name: attr.name, value: attr.value } }) } /** * 返回指令選擇器,便於指令節點的查詢 */ function getDirSelectors (directives) { /** * 支援的事件指令 */ let eventArr = ['click', 'change', 'blur']; return Object.keys(directives).map(function (directive) { /** * text => 'v-text' */ return '[' + prefix + '-' + directive + ']'; }).join() + ',' + eventArr.map(function (eventName) { return '[' + prefix + '-on-' + eventName + ']'; }).join(); } /** * 節點指令繫結 */ function bindDirective (vm, el, bindings, directive) { //從節點屬性中移除指令宣告 el.removeAttribute(directive.attr.value); /** * v-text='counter' * v-model='counter' * data = { counter: 1 } * 這裡的 counter 即指令的 key */ var key = directive.key, binding = bindings[key]; if (!binding) { /** * value 即 counter 對應的值 * directives 即 key 所繫結的相關指令 如: bindings['counter'] = { value: 1, directives: [textDirective, modelDirective] } */ bindings[key] = binding = { value: '', directives: [] } } directive.el = el; binding.directives.push(directive); //避免重複定義 if (!vm.hasOwnProperty(key)) { /** * get/set 操作繫結 */ bindAccessors(vm, key, binding); } } /** * get/set 繫結指令更新操作 */ function bindAccessors (vm, key, binding) { Object.defineProperty(vm, key, { get: function () { return binding.value; }, set: function (value) { binding.value = value; binding.directives.forEach(function (directive) { directive.update( directive.el, value, directive.argument, directive, vm, key ) }) } }) } function parseDirective (attr) { if (attr.name.indexOf(prefix) === -1) return ; /** * 指令解析 v-on-click='onClick' 這裡的指令名稱為 'on', 'click'為指令的引數,onClick 為key */ //移除 'v-' 字首, 提取指令名稱、指令引數 var directiveStr = attr.name.slice(prefix.length + 1), argIndex = directiveStr.indexOf('-'), directiveName = argIndex === -1 ? directiveStr : directiveStr.slice(0, argIndex), directiveDef = Directives[directiveName], arg = argIndex === -1 ? null : directiveStr.slice(argIndex + 1); /** * 指令表示式解析,即 v-text='counter' counter的解析 * 這裡暫時只考慮包含key的情況 */ var key = attr.value; return directiveDef ? { attr: attr, key: key, dirname: directiveName, definition: directiveDef, argument: arg, /** * 指令本身是一個函式的情況下,更新函式即它本身,否則呼叫它的update方法 */ update: typeof directiveDef === 'function' ? directiveDef : directiveDef.update } : null; } /** * 物件合併 */ function extend (child, parent) { parent = parent || {}; child = child || {}; for(var key in parent) { if (parent.hasOwnProperty(key)) { child[key] = parent[key]; } } return child; }var handlers = directive.handlers; if (handlers[eventName]) { //繫結新的事件前移除原繫結的事件函式 el.removeEventListener(eventName, handlers[eventName]); } //繫結新的事件函式 if (handler) { handler = handler.bind(el); el.addEventListener(eventName, handler); handlers[eventName] = handler; }
相關文章
- vue.js原始碼Vue.js原始碼
- Netty原始碼—六、tiny、small記憶體分配Netty原始碼記憶體
- Vue.js 原始碼實現Vue.js原始碼
- Vue.js 原始碼構建Vue.js原始碼
- Vue.js原始碼——事件機制Vue.js原始碼事件
- Vue.js 原始碼目錄設計Vue.js原始碼
- 深入Vue.js從原始碼開始(一)Vue.js原始碼
- 200 行 Python 程式碼做個換臉程式(附原始碼)Python原始碼
- 從template到DOM(Vue.js原始碼角度看內部執行機制)Vue.js原始碼
- Vue.js原始碼解析-Vue初始化流程Vue.js原始碼
- Vue.js原始碼解析-從scripts指令碼看vue構建Vue.js原始碼指令碼
- 從Vue.js原始碼角度再看資料繫結Vue.js原始碼
- Vue.js原始碼學習三 —— 事件 Event 學習Vue.js原始碼事件
- 從Vue.js原始碼看nextTick機制Vue.js原始碼
- ext2檔案系統super.c原始碼分析(Linux 2.6.24)原始碼Linux
- tiny-spring分析Spring
- [Vue.js進階]從原始碼角度剖析vue-router(上)Vue.js原始碼
- Vue.js 原始碼學習五 —— provide 和 inject 學習Vue.js原始碼IDE
- Windows2000原始碼下載 (轉)Windows原始碼
- [Vue.js進階]從原始碼角度剖析Vue的生命週期Vue.js原始碼
- Vue.js原始碼學習一 —— 資料選項 State 學習Vue.js原始碼
- Zookeeper原始碼分析(一) ----- 原始碼執行環境搭建原始碼
- [Vue.js進階]從原始碼角度剖析計算屬性的原理Vue.js原始碼
- Vue.js原始碼解析-Vue初始化流程之動態建立DOMVue.js原始碼
- 從Vue.js原始碼看非同步更新DOM策略及nextTickVue.js原始碼非同步
- 執行流程原始碼分析原始碼
- JavaScript superJavaScript
- Tiny Core Linux 安裝配置Linux
- 10分鐘快速精通rollup.js——Vue.js原始碼打包原理深度分析Vue.js原始碼
- 深入Mybatis原始碼——執行流程MyBatis原始碼
- 執行緒池原始碼探究執行緒原始碼
- 執行緒池原始碼分析執行緒原始碼
- Mybatis執行流程原始碼分析MyBatis原始碼
- python superPython
- 騰訊物聯TencentOS tiny上雲初探CentOS
- 原始碼原始碼原始碼樹品原始碼原始碼
- Vue.js 元件編碼規範Vue.js元件
- 70行實現Promise核心原始碼Promise原始碼