本次任務 html
不少文章都寫過他們兩個的區別前端
可是這些區別只是表面上的, 我來展現一個有趣的👇vue
<div id="boss"> <div>1</div> <div>2</div> <div>3</div> </div> <script> let oD = document.getElementById('boss'); // 正常執行 oD.childNodes.forEach(element => {console.log(element); }); // 報錯 oD.childNodes.map(element => { console.log(element); }); </script>
oDs.childNodes 並非個數組, 他仍然是僞數組, 可是他可使用forEach, 這個挺唬人的, 第一反應是這兩個遍歷方法在實現的方式上是否是有什麼不一樣, 但轉念一想感受本身想歪了, 答案實際上是 oDs.childNodes這個僞數組造成的時候, 往身上掛了個forEach...
經過上面的問題我有了些思考node
綜上所述, 仍是用forEach保險!
可是就想用map怎麼辦那?
1: slice的原理就是一個一個的循環放入一個新數組;webpack
let slice = Array.prototype.slice; slice.call(oD.childNodes).map(()=>{})
2: 擴展運算符原理不太同樣, 但他同樣能夠把全部元素都拿出來, 接下來咱們就對他進行死磕.ios
[...oD.childNodes].map(()=>{})
這個神奇的語法其實有不少門道的, 咱們來一塊兒死磕一下吧.
下面代碼會正確執行, 對象放入對象確定沒問題git
let obj = {a:1,b:2}, result = {...obj}; console.log(result)
下面代碼會報錯, 由於缺乏iterable.github
let obj = {a:1,b:2,length:2}, result = [...obj]; console.log(result)
緣由是'擴展運算符'不知道該怎麼擴展他, 咱們要告訴如何擴展才能夠正確的執行.
Symbol.iterator是Symbol身上的屬性, 而iterable的key就是它.web
let obj = { '0': 'a', '1': 'b', length: 2 }; obj[Symbol.iterator] = function() { let n = -1, _this = this, len = this.length; // 必須有返回值 // 而且返回值必須是對象 return { // 必須有next next: function() { n++; if (n < len) { return { value: _this[n], // 返回的值, 這個能夠隨便控制 done: false // 爲true就是結束, 爲false就是繼續 }; } else { return { done: true }; } } }; }; result = [...obj]; console.log(result);
上面的方法能夠知足個人要求了, 可是寫法上真的不敢恭維, 代碼量太多了..
因此我更推薦採用第二種方式利用Genertorexpress
let obj = { '0': 'a', '1': 'b', length: 2 }; obj[Symbol.iterator] = function*() { let n = -1, len = this.length; while (len !== ++n) { yield this[n]; } }; result = [...obj]; console.log(result);
😺整個世界都清爽了.
這個神奇的屬性作了不少不少神奇的事情, 能夠說如今的前端若是不會用它的話真徹底說不過去了...
功能
監控對象的某個屬性, 能夠對取值與賦值作出相應, 屬於'元編程'
第一個參數是 監控對象
第二個參數是 key
第三個參數必須是一個對象, 也能夠理解成config對象
好比 obj.name 這個會觸發get函數
obj.name = 'lulu' 這個會觸發set屬性, 可是要注意, 這個不會觸發get
這些動做都可以被監控到, 那咱們就能夠爲因此爲了哈哈哈哈哈
let obj = { name: 'a' }; function proxyObj(obj, name, val) { Object.defineProperty(obj, name, { enumerable: true, // 描述屬性是否會出如今for in 或者 Object.keys()的遍歷中 configurable: true, // 描述屬性是否配置,以及能否刪除 get() { return val; }, set(newVal) { val = newVal; } }); } proxyObj(obj,'name',obj['name']) console.log((obj.name = 2));
缺點
當前本套工程裏面, 使用data裏面的數據須要this.$data.xxx, 咱們把它變成this.xxx就能夠直接訪問的形式.
cc_vue/src/index.js
constructor(options) { // 1: 無論你傳啥, 我都放進來, 方便之後的擴展; // ... -------新加的 // 2: 把$data掛在vm身上, 用戶能夠直接this.xxx獲取到值 this.proxyVm(this.$data); -------新加的 // end new Compiler(this.$el, this); }
/** * @method 把某個對象的值, 代理到目標對象上 * @param { data } 想要被代理的對象 * @param { target } 代理到誰身上 */ proxyVm(data = {}, target = this) { // 默認就掛在框架的實例上 for (let key in data) { Object.defineProperty(target, key, { enumerable: true, configurable: true, get() { return data[key]; }, set(newVal) { if (newVal !== data[key]) { data[key] = newVal; } } }); } }
這樣之後再有訪問數據的操做就能夠直接this.了
以前對模板取值的操做要改一下啦, 很簡單的就是去掉$data
cc_vue/src/CompileUtil.js
getVal(vm, expression) { let result, __whoToVar = ''; for (let i in vm.$data) { // data下期作代理, 而且去掉原型上的屬性 let item = vm.$data[i]; if (typeof item === 'function') { __whoToVar += `function ${i}(...arg){return vm['${i}'].call(vm,...arg)}`; } else { __whoToVar += `let ${i}=vm['${i}'];`; } } __whoToVar = `${__whoToVar}result=${expression}`; eval(__whoToVar); return result; },
這裏比較核心, 因此咱們直接單獨抽離出一個'劫持模塊'.
當前步驟只是添加了劫持, 關於具體劫持以後幹什麼, 請看下一條
cc_vue/src/Observer.js
class Observer { constructor(data) { // 我只是負責初始化 this.data = data; this.observer(data); } /** * @method 針對對象進行觀察 * @param { data } 要觀察的對象 */ observer(data) { // 循環拿出對象身上的全部值, 進行監控 if (data && typeof data === 'object'&& !Array.isArray(data)) { for (let key in data) { this.defineReactive(data, key, data[key]); } } } /** * @method 進行雙向綁定,每一個值之後的操做動做,都會反應到這裏. * @param { obj } 要觀察的對象 * @param { key } 要觀察的對象 * @param { value } 要觀察的對象 */ defineReactive(obj, key, value) { // 由於data數據可能會很深, 因此必須遞歸 this.observer(obj[key]); let _this = this; Object.defineProperty(obj, key, { configurable: true, // 可改變可刪除 enumerable: true, // 可枚舉 get() { return value; }, set(newVal) { if (value !== newVal) { // 若是用戶傳進來的新值是個對象, 那就從新觀察他 _this.observer(newVal); value = newVal; } } }); } }
固然要在index裏面啓動這個模塊
cc_vue/src/index.js
class C { constructor(options) { // 1: 無論你傳啥, 我都放進來, 方便之後的擴展; for (let key in options) { this['$' + key] = options[key]; } // 2: 劫持data上面的操做 new Observer(this.$data); // ....
'訂閱發佈'屬因而vue比較核心的功能了, 這裏也稍微有一點繞, 你們一塊兒慢慢梳理.
如今data數據的改動已經被劫持, 思路梳理以下:
cc_vue/src/Watch.js
發佈訂閱, 這個類很簡單, 只是實現了兩個功能, 讓如隊列與執行隊列
export class Dep { constructor() { this.subs = []; // 把訂閱者所有放在這裏 } /** * @method 添加方法進訂閱隊列. */ addSub(w) { this.subs.push(w); } /** * @method 發佈信息,通知全部訂閱者. */ notify() { this.subs.forEach(w => w.update()); } }
cc_vue/src/Watch.js
觀察者, 就是他稍微有點繞
export class Watcher { // vm 實例 // expr 執行的表達式 // cb 回調函數, 也就是變量更新時執行的方法 constructor(vm, expr, cb) { this.vm = vm; this.expr = expr; this.cb = cb; // 這裏取一下當前的value, 之後每次變化都對比一下oldvalue, 防止無用的更新 this.oldValue = this.getOld(); } /** * @method 只有第一次的取值會調用他,對老值的記錄,以及被訂閱. */ getOld() { // 他只會被調用一次 // Dep是引用類型, 它身上的值固然能夠傳遞 Dep.target = this; // 這個this指的就是watch本身 // 獲取到這個值當前的value let value = CompileUtil.getVal(this.vm, this.expr.trim()); // 操做完要制空 Dep.target = null; // 給oldvalue賦值 return value; } /** * @method 更新值. */ update() { // 拿到新的value, 先比一比, 有變化再更新 let newVal = CompileUtil.getVal(this.vm, this.expr.trim()); if (newVal !== this.oldValue) { this.cb(); } } }
Dep.target = this; 這句是點睛之筆, 咱們來使用一下
cc_vue/src/CompileUtil.js
// 在解析模板的時候添加一個watch text(node, expr, vm) { let content = expr.replace(/\{\{(.+?)\}\}/g, ($0, $1) => { // 由於模板只解析一次, 因此不用擔憂他被重複new new Watcher(vm, $1, () => { // 這裏的callback就是具體的更新操做 this.updater.textUpdater(node, this.getContentValue(vm, expr)); }); return this.getVal(vm, $1); }); this.updater.textUpdater(node, content); },
getContentValue 獲取元素內的全部文本信息
有的人會問爲何要把文本信息全更新, 而不是隻獲取變化的文本, 那是由於 不少時候我很會寫出這樣的代碼 <p>{{a}}--{{b}}</p>, 那咱們沒法只單獨改變b的樣子, 由於咱們操做的是 p標籤的textContent屬性
getContentValue(vm, expr) { return expr.replace(/\{\{(.+?)\}\}/g, ($0, $1) => { $1 = $1.trim(); return this.getVal(vm, $1); }); },
上面的代碼, 咱們在解析text文本的時候放入了一個watch, 那麼這個watch被new的一瞬間會執行getOldvalue方法, 那麼就有了以下代碼
cc_vue/src/Observer.js
defineReactive(obj, key, value) { this.observer(obj[key]); // 1: 劫持某一個值的時候, 建立一個dep實例 let dep = new Dep(); let _this = this; Object.defineProperty(obj, key, { configurable: true, enumerable: true, get() { // 2: 獲取值的時候, 查看Dep這個類上面是否有target參數 // 這個參數是咱們獲取oldval時候掛上去的watch類 // 若是有的話, 調用把這個watch類放入訂閱者裏面 Dep.target && dep.addSub(Dep.target); return value; }, set(newVal) { if (value !== newVal) { _this.observer(newVal); value = newVal; // 3: 每次更新數據, 都執行發佈者 dep.notify() } } }); }
其實想想dep與watch也能夠寫成一個class, 可是寫成兩個更貼合設計模式.
實驗
新建第二個文件夾, 專門用來檢測雙向數據綁定
cc_vue/use/2:雙向綁定
<div id="app"> <p>n: {{n}} </p> <p>n+m: {{n+m}} </p> </div>
let vm = new C({ el: '#app', data: { n: 1, m: 2 } }); // 每秒給變一下n的值, n只要在屏幕上跟着發生變化就是成功了 setInterval(() => { vm.n += 1; }, 1000);
webpack方面配置調整一下
new HtmlWebpackPlugin({ filename: 'index.html', template: path.resolve(__dirname, '../use/2:雙向綁定/index.html'),
有興趣的朋友能夠試驗一下個人工程的效果,
此次實現的只是初步的綁定操做.
下一集:
你們均可以一塊兒交流, 共同窗習,共同進步, 早日實現自我價值!!
github:尚未star,期待您的支持
我的技術博客:我的博客
更多文章,ui庫的編寫文章列表 文章地址