<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] # Vue 雙向綁定原理 參考: [鏈接1](https://juejin.im/post/5acc17cb51882555745a03f8) [鏈接2](https://www.cnblogs.com/canfoo/p/6891868.html) [鏈接3](https://yuchengkai.cn/docs/frontend/framework.html#%E6%95%B0%E6%8D%AE%E5%8A%AB%E6%8C%81) [鏈接4](https://www.cnblogs.com/kidney/p/6052935.html?utm_source=gold_browser_extension) 首先明確下雙向綁定的概念: - 單向綁定指的是 Model(模型)更新時,View(視圖)會自動更新 - 如果反過來 View 更新時 Model 的數據也能自動更新,那就是雙向綁定 也就是說,我們只要滿足上述條件就算實現雙向綁定了,那么下面的代碼就是最簡單的雙向綁定: ```html <!DOCTYPE html> <html lang="en"> <head> <title>Document</title> </head> <body> <input type="text" id="a"> <span id="b"></span> <script> const obj = {} Object.defineProperty(obj, 'attr', { set: function (newVal) { document.getElementById('a').value = newVal document.getElementById('b').innerHTML = newVal } }) document.addEventListener('keyup', function (e) { obj.attr = e.target.value }) </script> </body> </html> ``` 我們在輸入框輸入文字時,JavaScript 代碼中的數據會發生變化;在控制臺顯式地修改 obj.attr 的值,視圖也會相應地更新,所以說這是一個極簡的雙向綁定。 鏈接 4 還提到了 Object.defineProperty 的幾個要點: - 讀取或設置訪問器屬性的值,實際上是調用其內部特性:get 和 set 函數 - get 和 set 方法內部的 this 都指向 obj,這意味著其可以操作對象內部的值 - 訪問器屬性的會"覆蓋"同名的普通屬性,因為訪問器屬性會被優先訪問,與其同名的普通屬性則會被忽略。 下面再看 vue 是如何雙向綁定的,這里不再做具體分析了(可以參考上面的鏈接)。我用一句話、一張圖、一段代碼來整理自己的思路: ***** <span style="font-size: 20px; color:#42b383" >一句(比較長)的話</span> vue 的雙向綁定采用數據劫持結合發布-訂閱模式實現,數據劫持即使用 Object.defineProperty 把傳入的 data 選項(一個 JavaScript 對象)的屬性轉換為 getter / setter,發布-訂閱即模板解析過程中,與渲染相關的數據屬性會添加相應的 Watcher,該屬性的 setter 觸發時就會通知對應的 Watcher 更新視圖。 <span style="font-size: 20px; color:#42b383" >盜一張圖 -.-</span> ![](https://box.kancloud.cn/dee4be024ce54050fa58dd8a55c2c2a5_978x552.png) <span style="font-size: 20px; color:#42b383" >再剽一段代碼 -.-</span> 代碼來源:[https://github.com/bison1994/two-way-data-binding/blob/master/index.html](https://github.com/bison1994/two-way-data-binding/blob/master/index.html) ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Two-way-data-binding</title> </head> <body> <div id="app"> <input type="text" v-model="text"> {{ text }} </div> <script> // 這里是劫持效果是 this.xxx -> this.$data.xxx function observe (obj, vm) { Object.keys(obj).forEach(function (key) { defineReactive(vm, key, obj[key]); }) } function defineReactive (obj, key, val) { var dep = new Dep(); Object.defineProperty(obj, key, { get: function () { // 添加訂閱者 watcher 到主題對象 Dep if (Dep.target) dep.addSub(Dep.target); return val }, set: function (newVal) { if (newVal === val) return val = newVal; // 作為發布者發出通知 dep.notify(); } }); } // 編譯 DOM 結構,用文檔片段的形式存儲,然后將編譯后的 DOM 掛載到綁定的 el 上 function nodeToFragment (node, vm) { var flag = document.createDocumentFragment(); var child; // appendChild 方法有個隱蔽的地方,就是調用以后 child 會從原來 DOM 中移除 // 所以,第二次循環時,node.firstChild 已經不再是之前的第一個子元素了 while (child = node.firstChild) { compile(child, vm); flag.appendChild(child); } return flag } function compile (node, vm) { var reg = /\{\{(.*)\}\}/; // 節點類型為元素 if (node.nodeType === 1) { var attr = node.attributes; // 解析屬性 for (var i = 0; i < attr.length; i++) { if (attr[i].nodeName == 'v-model') { // 該特性的名稱 var name = attr[i].nodeValue; // 獲取 v-model 綁定的屬性名 node.addEventListener('input', function (e) { // 給相應的 data 屬性賦值,進而觸發該屬性的 set 方法 vm[name] = e.target.value; }); node.removeAttribute('v-model'); } }; new Watcher(vm, node, name, 'input'); } // 節點類型為 text if (node.nodeType === 3) { if (reg.test(node.nodeValue)) { // 有{{}} var name = RegExp.$1; // 獲取匹配到的字符串 name = name.trim(); new Watcher(vm, node, name, 'text'); } } } // nodeType 準確來說應該是事件類型,比如 v-model v-bind 歸為一類 function Watcher (vm, node, name, nodeType) { Dep.target = this; // 全局變量 this.name = name; this.node = node; this.vm = vm; this.nodeType = nodeType; this.update(); // 更新視圖,即修改相應其監聽的 DOM 節點的某個特性值 Dep.target = null; } Watcher.prototype = { update: function () { this.get(); if (this.nodeType == 'text') { this.node.nodeValue = this.value; } if (this.nodeType == 'input') { this.node.value = this.value; } }, // 獲取 data 中的屬性值 get: function () { this.value = this.vm[this.name]; // 觸發相應屬性的 get } } function Dep () { this.subs = [] } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); } } function Vue (options) { this.data = options.data; var data = this.data; observe(data, this); var id = options.el; var dom = nodeToFragment(document.getElementById(id), this); // 編譯完成后,將 dom 返回到 app 中 document.getElementById(id).appendChild(dom); } var vm = new Vue({ el: 'app', data: { text: 'hello world' } }) </script> </body> </html> ``` # vue 的 nextTick 是如何實現的? 參考鏈接: [https://mp.weixin.qq.com/s/mCcW4OYj3p3471ghMBylBw](https://mp.weixin.qq.com/s/mCcW4OYj3p3471ghMBylBw) [https://segmentfault.com/a/1190000013314893](https://segmentfault.com/a/1190000013314893) - nextTick 的用途? 該 API 可以在 DOM 更新完畢后執行一個回調,其可以確保我們操作的是更新后的 DOM ```js // 修改數據 vm.msg = 'Hello' // DOM 還沒有更新 Vue.nextTick(function () { // DOM 更新了 }) ``` - 如何檢測 DOM 的更新并確保回調是在 DOM 更新后執行? 1. vue 用異步隊列的方式來控制 DOM 更新和 nextTick 回調先后執行 2. microtask 因為其高優先級特性,能確保隊列中的微任務在一次事件循環前被執行完畢 3. 因為兼容性問題,vue 不得不做了 microtask 向 macrotask 的降級方案 來看下面這段代碼: ```html <div id="example"> <div ref="test">{{test}}</div> <button @click="handleClick">tet</button> </div> ``` ```js var vm = new Vue({ el: '#example', data: { test: 'begin', }, methods: { handleClick() { this.test = 'end' + this.test; // 這里確保 DOM 更新,你可以試試 this.test = 'end' 會發現第二次點擊時會輸出 1 promise 2 3 console.log('1') setTimeout(() => { // macroTask console.log('3') }, 0); Promise.resolve().then(function() { //microTask console.log('promise!') }) this.$nextTick(function () { console.log('2') }) } } }) ``` 在 Chrome 下,這段代碼會輸出 `1、2、promise、3` DOM 更新其實就是生成一個 Watcher 隊列,最后會調用我們的 nextTick 函數(具體見鏈接2的分析) ```js export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { //通過 pending 來判斷是否已經有 timerFunc 這個函數在事件循環的任務隊列等待被執行 pending = true timerFunc() // 把回調作為 microTask 或 macroTask 參與到事件循環 } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } ``` 這里面通過對 `pending` 的判斷來檢測是否已經有 `timerFunc` 這個函數在事件循環的任務隊列等待被執行。如果存在的話,那么是不會再重復執行的。 最后異步執行 `flushCallbacks` 時又會把 `pending` 置為 `false`。 ```js // 執行所有 callbacks function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } ``` 所以回到我們的例子: ```js handleClick() { this.test = 'end'; console.log('1') setTimeout(() => { // macroTask console.log('3') }, 0); Promise.resolve().then(function() { //microTask console.log('promise!') }); this.$nextTick(function () { console.log('2') }); } ``` 代碼中,`this.test = 'end'` 必然會觸發 `watcher` 進行視圖的重新渲染,而我們在文章的 `Watcher` 一節中(鏈接2)就已經有提到會調用 `nextTick` 函數,一開始 `pending` 變量肯定就是 `false`,因此它會被修改為 `true` 并且執行 `timerFunc`。之后執行 `this.$nextTick` 其實還是調用的 `nextTick` 函數,只不過此時的 `pending` 為 `true` 說明 `timerFunc` 已經被生成,所以 `this.$nextTick(fn)` 只是把傳入的 `fn` 置入 `callbacks` 之中。此時的 `callbacks` 有兩個 `function` 成員,一個是 `flushSchedulerQueue`,另外一個就是 `this.$nextTick()` 的回調。 因此,上面這段代碼中,在 `Chrome` 下,有一個 `macroTask` 和兩個 `microTask`。一個`macroTask`就是`setTimeout`,兩個`microTask`:分別是`Vue`的`timerFunc`(其中先后執行`flushSchedulerQueue`和`function() {console.log('2')}`)、代碼中的`Promise.resolve().then()`。 最后我們貼出 timeFunc 的代碼來看看其降級策略: ```js // vue@2.6.10 /src/core/util/next-tick.js // The nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // Use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // Fallback to setImmediate. // Techinically it leverages the (macro) task queue, // but it is still a better choice than setTimeout. timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } } // 降級策略:Native Promise -> MutationObserver -> setImmediate -> setTimeout // 雖然參考鏈接中有說用 MessageChannel 但是這里的 Vue 源碼中沒看到? 2019.7.31 ``` # 聊聊 keep-alive 參考鏈接:[https://segmentfault.com/a/1190000011978825](https://segmentfault.com/a/1190000011978825) [https://juejin.im/post/5cce49036fb9a031eb58a8f9](https://juejin.im/post/5cce49036fb9a031eb58a8f9) ## keep-alive 內置組件的用途? `<keep-alive>` 包裹動態組件時,會緩存不活動的組件實例,而不是銷毀它們。`<keep-alive>`是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現在父組件鏈中。 當組件在 `<keep-alive>` 內被切換,它的 `activated` 和 `deactivated` 這兩個生命周期鉤子函數將會被對應執行。 - Props: - `include`\- 字符串或正則表達式。只有名稱匹配的組件會被緩存。 - `exclude`\- 字符串或正則表達式。任何名稱匹配的組件都不會被緩存。 - `max`\- 數字。最多可以緩存多少組件實例。 應用場景:避免組件的反復重建和渲染,保存用戶狀態等。 ## 為什么 keep-alive 組件自身不會被渲染? Vue 在初始化生命周期的時候,為組件實例建立父子關系會根據 `abstract` 屬性決定是否忽略某個組件。在 keep-alive 中,設置了 `abstract: true`,那 Vue 就會跳過該組件實例。 ## keep-alive 組件包裹的組件是如何使用緩存的? ```js // src/core/vdom/patch.js function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { let i = vnode.data if (isDef(i)) { const isReactivated = isDef(vnode.componentInstance) && i.keepAlive if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */) } if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) // 將緩存的DOM(vnode.elm)插入父元素中 if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } } ``` 在首次加載被包裹組件時,由 `keep-alive.js` 中的 `render` 函數可知,`vnode.componentInstance` 的值是 `undefined`,`keepAlive` 的值是 `true`,因為 keep-alive 組件作為父組件,它的 `render` 函數會先于被包裹組件執行;那么就只執行到 `i(vnode, false /* hydrating */)`,后面的邏輯不再執行; ***** 再次訪問被包裹組件時,`vnode.componentInstance` 的值就是已經緩存的組件實例,那么會執行 `insert(parentElm, vnode.elm, refElm)` 邏輯,這樣就直接把上一次的 DOM 插入到了父元素中。 ## 如何做到避免組件的重復創建? 一般的組件,每一次加載都會有完整的生命周期,即生命周期里面對應的鉤子函數都會被觸發,為什么被 keep-alive 包裹的組件卻不是呢? 因為被緩存的組件實例會為其設置 keepAlive = true,而在初始化組件鉤子函數中: ```js // src/core/vdom/create-component.js const componentVNodeHooks = { init (vnode: VNodeWithData, hydrating: boolean): ?boolean { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) child.$mount(hydrating ? vnode.elm : undefined, hydrating) } } // ... } ``` 可以看出,當 vnode.componentInstance 和 keepAlive 同時為 truly 值時,不再進入 $mount 過程,那 mounted 之前的所有鉤子函數(beforeCreate、created、mounted)都不再執行。 ## activated 與 deactivated 鉤子 在 patch 的階段,最后會執行 invokeInsertHook 函數,而這個函數就是去調用組件實例(VNode)自身的 insert 鉤子: ```js // src/core/vdom/patch.js function invokeInsertHook (vnode, queue, initial) { if (isTrue(initial) && isDef(vnode.parent)) { vnode.parent.data.pendingInsert = queue } else { for (let i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]) // 調用 VNode 自身的 insert 鉤子函數 } } } ``` 再看`insert`鉤子: ```js // src/core/vdom/create-component.js const componentVNodeHooks = { // init() insert (vnode: MountedComponentVNode) { const { context, componentInstance } = vnode if (!componentInstance._isMounted) { componentInstance._isMounted = true callHook(componentInstance, 'mounted') } if (vnode.data.keepAlive) { if (context._isMounted) { queueActivatedComponent(componentInstance) } else { activateChildComponent(componentInstance, true /* direct */) } } // ... } ``` 在這個鉤子里面,調用了`activateChildComponent`函數遞歸地去執行所有子組件的`activated`鉤子函數: ```js // src/core/instance/lifecycle.js export function activateChildComponent (vm: Component, direct?: boolean) { if (direct) { vm._directInactive = false if (isInInactiveTree(vm)) { return } } else if (vm._directInactive) { return } if (vm._inactive || vm._inactive === null) { vm._inactive = false for (let i = 0; i < vm.$children.length; i++) { activateChildComponent(vm.$children[i]) } callHook(vm, 'activated') } } ``` 相反地,`deactivated`鉤子函數也是一樣的原理,在組件實例(VNode)的 `destroy` 鉤子函數中調用`deactivateChildComponent`函數。 # vue-router 實現淺析 參考:[https://zhuanlan.zhihu.com/p/27588422](https://zhuanlan.zhihu.com/p/27588422) "更新視圖但不重新請求頁面" 是前端路由原理的核心之一,目前在瀏覽器環境中這一功能的實現主要有兩種方式: * 利用 URL 中的 hash(“#”) * 利用 History interface 在 HTML5 中新增的方法 ## Hash 模式 `http://www.example.com/index.html#print` \# 符號本身以及它后面的字符稱之為 hash,可通過 window.location.hash 屬性讀取。它具有如下特點: - hash 雖然出現在 URL 中,但不會被包括在 HTTP 請求中。它是用來指導瀏覽器動作的,對服務器端完全無用,因此,改變 hash 不會重新加載頁面 - 可以為 hash 的改變添加監聽事件: ```js window.addEventListener("hashchange", funcRef, false) ``` - 每一次改變 hash(window.location.hash),都會在瀏覽器的訪問歷史中增加一個記錄 路由操作主要就是 push 和 replace,push 是將新的路由添加到瀏覽器歷史記錄棧的棧頂,replace 是替換當前棧頂。 函數觸發順序: ```js 1 $router.push() // 調用方法 2 HashHistory.push() // 設置 hash 并添加到瀏覽器歷史記錄(添加到棧頂)(window.location.hash= XXX) 3 History.transitionTo() // 監測更新,更新則調用 History.updateRoute() 4 History.updateRoute() // 更新路由 5 {app._route= route} // 替換當前app路由 6 vm.render() // 更新視圖 ``` ## History 模式 更改了 API,可以直接操作瀏覽器歷史記錄棧 1.push:與 hash 模式類似,只是將 window.hash 改為 history.pushState 2.replace:與 hash 模式類似,只是將 window.replace 改為 history.replaceState 3.監聽地址變化:在 HTML5History 的構造函數中監聽 popState(window.onpopstate) # vuex 實現淺析 參考:[https://www.jianshu.com/p/d95a7b8afa06](https://www.jianshu.com/p/d95a7b8afa06) vuex 僅僅是作為 vue 的一個插件而存在,不像 Redux,MobX 等庫可以應用于所有框架, vuex 只能使用在 vue 上,很大的程度是因為其高度依賴于 vue 的 computed 依賴檢測系統以及其插件系統。 每一個 vue 插件都需要有一個公開的 install 方法,vuex 也不例外。其調用了一下 applyMixin 方法,該方法主要作用就是在所有組件的 **beforeCreate** 生命周期注入了設置 **this.$store** 這樣一個對象。 ```js // src/mixins.js // 對應applyMixin方法 export default function (Vue) { const version = Number(Vue.version.split('.')[0]) if (version >= 2) { Vue.mixin({ beforeCreate: vuexInit }) } else { const _init = Vue.prototype._init Vue.prototype._init = function (options = {}) { options.init = options.init ? [vuexInit].concat(options.init) : vuexInit _init.call(this, options) } } /** * Vuex init hook, injected into each instances init hooks list. */ function vuexInit () { const options = this.$options // store injection if (options.store) { this.$store = typeof options.store === 'function' ? options.store() : options.store } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store } } } ``` Vuex 的構造函數中有如下一個方法: ```js // src/store.js function resetStoreVM (store, state, hot) { // 省略無關代碼 Vue.config.silent = true store._vm = new Vue({ data: { $$state: state }, computed }) } ``` 其本質就是將我們傳入的 state 作為一個隱藏的 vue 組件的 data,也就是說,我們的 commit 操作,本質上其實是修改這個組件的 data 值,結合上文的 computed,修改被 **defineReactive** 代理的對象值后,會將其收集到的依賴的 **watcher** 中的 **dirty** 設置為 true,等到下一次訪問該 watcher 中的值后重新獲取最新值。 這樣就能解釋了為什么 vuex 中的 state 的對象屬性必須提前定義好,如果該 **state** 中途增加**一個屬性**,因為該**屬性**沒有被 **defineReactive**,所以其依賴系統沒有檢測到,自然不能更新。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看