相關基礎知識點html
// 能夠讓 任意函數/方法 成功臨時指定成對象的方法進行調用 - call/applyvue
// 1. 根據僞數組生成 真數組node
const lis = document.getElementsByTagName("li");git
const arr = [].slice.call(lis); 數組
const relArr = Array.from(lis); // ES6語法app
// 2. node.nodeType // 獲得節點類型函數
// 3. 給對象添加屬性,指定 屬性描述符:存取描述符 get(),set() - 數據描述符ui
Object.defineProperty(ojb, propertyName, {this
get(){return this.firstNAme+"-"+this.lastNAme},編碼
set(newVAlue){const names=newValue.split("-");this.firstNAme=names[0];this.lastNAme=names[1]},
})
// 4. Object.keys(obj); // 獲得對象自身可枚舉屬性組成的數組
// 5. obj.hasOwnProperty("name") // 判斷 obj 對象自身屬性是否包含 name
// 6. DocumentFragment 文檔碎片(高效更新多個節點)
內存中用來存儲多個節點對象 的容器,不與任何 頁面/頁面節點 對應
思路: 先將節點複製到內存中,在內存中修改完後,一次性添加到頁面中
// 1. 建立一個內存中 節點容器
const fragment = document.createDocumentFragment();
// 2. 取出 div 中全部節點,轉移到 fragment 中
const div = document.getElementById("demo");
let child; // newPos = appendChild(child) 會將 child 從原來位置取下來,放到 newPos 的最後位置
while(child=div.firstChild){fragment.appendChild(child)}
// 3. 在內存中遍歷修改
const nodes = fragment.children[0].childNodes;
Array.prototype.slice.call(nodes).forEach(node=>{
if(node.nodeType === 1){
node.textContent = "丘魔的兵"
}
})
// 4. 一次性添加到頁面
div.appendChild(fragment);
在 git 上,有個開源項目,剖析了 vue 原理
爲達到簡化編碼的目的,
內部實現的關鍵點: 經過 Object.defineProperty(vm, key, {}) 實現
對於 data 中狀態的數據,經過 ViewModel 實例 來進行 代理讀/代理寫 操做
---------------------------------------------------------------
function Vue(configObj){
var me = this;
// 1. 保存 配置對象 到 ViewModel
this.$configObj = configObj;
// 2. 保存 data 對象到 ViewModel 和 變量 data 上
var data = this._data = this.$configObj.data;
// 3. 遍歷 data 中全部屬性, 實現數據代理
Object.keys(data).forEach(function (key) {
me._proxy(key);
});
// 實現數據綁定
observe(data, this);
// 實現模版解析
this.$compile = new Compile(options.el || document.body, this)
}
Vue.prototype = {
//
_proxy: function(key){
var vm = this;
// 給 vm 添加指定屬性,使用 屬性描述符 (設置 getter/setter)
Object.defineProperty(vm, key, {
configurable: false,
enumerable: true,
get: function proxyGetter(){ // 代理讀
return vm._data[key]
},
set: function proxySetter(newValue){ // 代理寫
vm._data[key] = newValue
}
})
},
$watch: function (key, cb, options) {
new Watcher(this, key, cb);
}
}
this.$compile = new Compile(options.el || document.body, this)
function Compile(el, vm) { this.$vm = vm; this.$el = this.isElementNode(el) ? el : document.querySelector(el); if (this.$el) { // 總體流程: this.$fragment = this.node2Fragment(this.$el); // 1. 將 目標元素節點 拷貝到 DocumentFragment this.init(); // 2. 內存中編譯 DocumentFragment this.$el.appendChild(this.$fragment); // 3. 將 DocumentFragment 追加到目標元素節點 } } Compile.prototype = { node2Fragment: function(el) { var fragment = document.createDocumentFragment(), child; // 將原生節點拷貝到fragment while (child = el.firstChild) { fragment.appendChild(child); } return fragment; }, init: function() { this.compileElement(this.$fragment); // 傳入待處理節點,進行編譯處理 }, compileElement: function(el) { var childNodes = el.childNodes, me = this; [].slice.call(childNodes).forEach(function(node) { var text = node.textContent; // 換行 var reg = /\{\{(.*)\}\}/; // 匹配 {{模板表達式}} if (me.isElementNode(node)) { me.compile(node); } else if (me.isTextNode(node) && reg.test(text)) { // 若是是 {{模板表達式}}
// 則 解析 成node.textContent = 模板表達式,無論匹配成功幾個表達式,老是隻生效第一個
me.compileText(node, RegExp.$1);
}
if (node.childNodes && node.childNodes.length) { // 若是還有子節點,則遞歸編譯 me.compileElement(node); } }); }, compile: function(node) { var nodeAttrs = node.attributes, me = this; [].slice.call(nodeAttrs).forEach(function(attr) { var attrName = attr.name; if (me.isDirective(attrName)) { var exp = attr.value; // 獲得表達式 即 回調函數名 var dir = attrName.substring(2); // 獲得指令 // 事件指令 if (me.isEventDirective(dir)) { compileUtil.eventHandler(node, me.$vm, exp, dir); // 普通指令 } else { compileUtil[dir] && compileUtil[dir](node, me.$vm, exp); } node.removeAttribute(attrName); } }); }, compileText: function(node, exp) { compileUtil.text(node, this.$vm, exp); }, isDirective: function(attr) { return attr.indexOf('v-') == 0; }, isEventDirective: function(dir) { return dir.indexOf('on') === 0; }, isElementNode: function(node) { return node.nodeType == 1; }, isTextNode: function(node) { return node.nodeType == 3; } }; // 用於編譯的方法 - 指令處理集合 var compileUtil = { text: function(node, vm, exp) { // 編譯 v-text 或者 {{模板表達式}} this.bind(node, vm, exp, 'text'); }, html: function(node, vm, exp) { // v-html this.bind(node, vm, exp, 'html'); }, model: function(node, vm, exp) { // v-model this.bind(node, vm, exp, 'model'); var me = this, val = this._getVMVal(vm, exp); node.addEventListener('input', function(e) { var newValue = e.target.value; if (val === newValue) { return; } me._setVMVal(vm, exp, newValue); val = newValue; }); }, class: function(node, vm, exp) { // v-bind:class this.bind(node, vm, exp, 'class'); }, bind: function(node, vm, exp, dir) { var updaterFn = updater[dir + 'Updater']; // 根據指令名 獲得一個用於更新節點的更新函數 updaterFn && updaterFn(node, this._getVMVal(vm, exp)); // 獲得指定表達式對應的值
// 解析每一個表達式後 - 爲表達式建立對應的 watcher 對象(事件表達式除外) new Watcher(vm, exp, function(value, oldValue) { updaterFn && updaterFn(node, value, oldValue); // 更新節點 }); }, // 事件處理 eventHandler: function(node, vm, exp, dir) { var eventType = dir.split(':')[1], fn = vm.$options.methods && vm.$options.methods[exp]; if (eventType && fn) { node.addEventListener(eventType, fn.bind(vm), false); } }, _getVMVal: function(vm, exp) { var val = vm._data; exp = exp.split('.'); exp.forEach(function(k) { val = val[k]; }); return val; }, _setVMVal: function(vm, exp, value) { var val = vm._data; exp = exp.split('.'); exp.forEach(function(k, i) { // 非最後一個key,更新val的值 if (i < exp.length - 1) { val = val[k]; } else { val[k] = value; } }); } }; // 更新節點的方法集 var updater = { textUpdater: function(node, value) { // v-text 或者 {{模板表達式}} node.textContent = typeof value == 'undefined' ? '' : value; }, htmlUpdater: function(node, value) { // v-html node.innerHTML = typeof value == 'undefined' ? '' : value; }, classUpdater: function(node, value, oldValue) { // v-bind:class var className = node.className; className = className.replace(oldValue, '').replace(/\s$/, ''); var space = className && String(value) ? ' ' : ''; node.className = className + space + value; }, modelUpdater: function(node, value, oldValue) { // v-model node.value = typeof value == 'undefined' ? '' : value; } };
使用 數據劫持技術 實現數據綁定
思想: 經過 defineProperty() 來監視 data 中全部狀態屬性(任意層次)的變化,一旦變化了,界面會實時更新
Observer
用來對 data 中數據進行監視 的構造函數
爲每一個 data 數據設置 getter 和 setter ,並配置 dep 對象
Compile
用來解析模板頁面對象的構造函數
利用 compile 對象解析模板頁面
每解析一個表達式都會建立一個 watcher 對象,並創建 watcher 和 dep 的關係
1 個 watcher 對應 n>=1 個 dep -------- 假設表達式是 2 層表達式 mom.son 時,當前 watcher 就對應 2 個 dep
1 個 dep 對應 n>=0 個 watcher -------- 沒有一個表達式用到這個屬性; 只有一個表達式用到這個屬性; 多個表達式用到這個屬性
每一個 dep 都有惟一的 id
subs 包含 n 個對應 watcher 的數組 ---- subcribes 的簡寫
function Observer(data) { this.data = data; this.walk(data); } Observer.prototype = { walk: function(data) { var me = this; Object.keys(data).forEach(function(key) { me.convert(key, data[key]); }); }, convert: function(key, val) { // 給每一個對象設置 響應式屬性 this.defineReactive(this.data, key, val); }, defineReactive: function(data, key, val) { // 對某個屬性進行劫持 var dep = new Dep(); // 在每一個屬性進行劫持前建立一個對應的 dep 對象 var childObj = observe(val); // 遞歸實現屬性下全部層次屬性的劫持 Object.defineProperty(data, key, { enumerable: true, // 可枚舉 configurable: false, // 不能再define get: function() { if (Dep.target) { dep.depend(); } return val; }, set: function(newVal) { if (newVal === val) { return; } val = newVal; childObj = observe(newVal); // 新的值是object的話,進行監聽 dep.notify();// 通知訂閱者 } }); } }; function observe(value, vm) { if (!value || typeof value !== 'object') { return; } return new Observer(value); }; var uid = 0; function Dep() { this.id = uid++; this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, depend: function() { Dep.target.addDep(this); }, removeSub: function(sub) { var index = this.subs.indexOf(sub); if (index != -1) { this.subs.splice(index, 1); } }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); } }; Dep.target = null;
5
5
5
55
5
5