// 如何讓實現響應式的呢? let obj = {}; let name = 'zhangsan'; Object.defineProperties(obj, name, {get : function() { console.log('name' , name) }, set : function() { console.log('name' , name) }}) // 1. 關鍵是理解Object.defineProperty // 2. 將data的屬性代理到vm上面的 let mv = {}; let data = { price: 100, name: 'zhangsan' }; for (let key in data) { (function (key) { Object.defineProperty(mv, key, { get: function () { console.log('get val'); return data[key]; }, set: function (val) { console.log('set val'); data[key] = val; } }) })(key); }
<div id="app"> <div> <input v-model="title"> <button v-on:click="add">submit</button> </div> <ul> <li v-for="item in list">{{item}}</li> </ul> </div>
// 1(*****). 模板實際上就是一個字符串………………(vue中的模板的本質) // 2. 模板有邏輯,如v-if, v-for // 3. 與html格式很像,可是有很大的區別 // 4. 最終仍是要轉換爲html來顯示 // 5(*****). 模板最終必須轉換成JS代碼,由於: // (1)有邏輯(v-if v-for):必須用JS才能實現(圖靈完備) // (2) 轉換成HTML來渲染頁面,必須用JS才能實現 // (3) 所以,模板最終要轉換成爲一個JS函數(render函數)
var obj = { name: 'zhangsan', age: 20, getAddress(){ alert('shanghai'); } } // 不使用with function fn() { alert(obj.name); alert(obj.age); obj.getAddress(); } // 使用with(代碼不易維護!!!) function fn1() { with(obj){ alert(name); alert(age); getAddress(); } } fn(); fn1();
<div id='app'> <p>{{price}}</p> </div> // 使用with限制這個做用域裏面的this with(this) { return _c( // this._c 'div', { attrs: {"id" : "app"} // id=app }, [ _c('p', [_v(_s(price))]) // this._c('p', [_v(_s(price))]) ] ) } // 實現一個本身的render函數 var vm = new Vue({ el: '#app', data: { price: 100 } }); function render() { with (vm) { return _c( 'div', { attrs: {'id': 'app'} }, [ _c('p', [_v(_s(price))]) ] ); } } function render() { return vm._c( 'div', { attrs: {'id': 'app'} }, [ // vm._v 轉換爲一個文本節點 // vm._s 轉換爲一個字符串 // vm._c 轉換爲一個DOM節點 vm._c('p', [vm._v(vm._s(price))]) ] ); }
<div id="app"> <div> <input type="text" v-model="title"> <button @click="add">submit</button> </div> <div> <ul> <li v-for="item in list">{{item}}</li> </ul> </div> </div>
with (this) { // this 就是vm return _c( 'div', {attrs: {"id": "app"}}, [ _c('div', [ _c('input', { directives: [{ name: "model", rawName: "v-model", value: (title), expression: "title" }], attrs: {"type": "text"}, domProps: {"value": (title)}, on: { "input": function ($event) { if ($event.target.composing) return; title = $event.target.value } } }), _v(" "), _c('button', { on: { "click": add } }, [_v("submit")] ) ] ), _v(" "), _c('div', [ _c( 'ul', // 這裏返回的是一個數組(li標籤組成的數組) _l((list), function (item) { return _c('li', [_v(_s(item))]) }), 0 ) ] ) ] ) } // view ---> data ---> 使用input的事件綁定 ---> 更新頁面數據到data // data ---> view ---> defineProperty ---> 同步數據到頁面
vm._update(vnode) { const prevNode = vm._vnode; vm._vnode = vnode; if (!prevNode) { // 首次渲染的時候 vm.$el = vm.__patch__(vm.$el, vnode); } else{ vm.$el = vm.__patch__(prevNode, vnode); } } // 開始更新vue組件(修改data的屬性的時候,Object.defineProperty) function updateComponent() { vm._update(vm._render()); }
******(問題總結)html
<template></template> --->>> render 函數
[!NOTE]vue
- with函數的使用
- 模板中的全部信息都被render函數包含
- 模板中用到的data中的屬性,都變成了JS變量
- 模板中的v-model v-for v-on都變成了JS邏輯
- render函數返回vnode
- Object.defineProperty
- 將data的屬性代理到vm上
with(vm) { }
[!NOTE]node
- 初次渲染,執行updateComponent, 執行vm._render()
- 執行render函數,會訪問到vm.list和vm.title屬性
- 會被響應式的get方法監聽到(Object.defineProperty)
Object.defineProperty(mv, key, { get: function() { return data[key]; } })
- 執行updateComponent, 會執行vdom的patch方法
- patch將vnode渲染成DOM,初次渲染完成
[!NOTE]react
- data中有不少屬性,有些會被用到,有些可能不被用到
- 被用到的會走到get, 不被用到的不會走到get
- 未走到get中的屬性,set的時候咱們也無需關心
- 避免沒必要要的重複渲染(關鍵點)
vm._update(vnode) { const prevNode = vm._vnode; vm._vnode = vnode; if (!prevNode) { // 首次渲染的時候 vm.$el = vm.__patch__(vm.$el, vnode); } else{ vm.$el = vm.__patch__(prevNode, vnode); } } // 開始更新vue組件(修改data的屬性的時候,Object.defineProperty) function updateComponent() { vm._update(vm._render()); }
Object.defineProperty(mv, key, { set: function(newVal) { data[key] = newVal; // 開始執行 updateComponnet() } })
[!NOTE]jquery
- 修改屬性,被響應式的set監聽到
- set中執行updateComponnet
- updateComponent從新執行vm._render()
- 生成的vnode和preVnode, 經過patch進行對比
- 渲染到html中去
- Object.defineProperty
- data的屬性代理到vm上面(with)
給data添加新屬性的時候vm.$set(vm.info,'newKey','newValue')webpack
data上面屬性值是數組的時候,須要用數組的方法操做數組,而不能經過index或者length屬性去操做數組,由於監聽不到屬性操做的動做。ios
[!NOTE]
上面說了vm負責讓數據變了,視圖能自動發生變化。這麼神奇魔術背後的原理是Object.defineProperty。其實就是屬性的讀取和設置操做都進行了監聽,當有這樣的操做的時候,進行某種動做。來一個demo玩下。web
// 對obj上面的屬性進行讀取和設置監聽 let obj = { name:'huahua', age:18 } function observer(obj){ if(typeof obj === 'object'){ for (const key in obj) { defineReactive(obj,key,obj[key]) } } } // get的return的值纔是最終你讀取到的值。因此設的值是爲讀取準備的。 // set傳的參數是設置的值,注意這裏不要有obj.name = newVal 這樣又觸發set監聽,會死循環的。 function defineReactive(obj,key,value){ Object.defineProperty(obj,key,{ get:function(){ console.log('你在讀取') // happy的話這邊能夠value++,這樣你發現讀取的值始終比設置的大一個,由於return就是讀取到的值 return value }, set:function(newVal){ console.log('數據更新了') value = newVal } }) } observer(obj) obj.age = 2 console.log(obj.age)
[!NOTE]
在瀏覽器執行的時候,控制檯隨手也能夠obj.name="hua1"相似的操做,發現都監聽到了。可是若是更深一步,obj.name={firstname:'hua',lastname:'piaoliang'};obj.name.lastname='o'就不能監聽到屬性修改了。由於並無將新的賦值對象監聽其屬性。因此函數須要改進。面試
須要在defineReactive的第一行加上observer(value)。設置值的時候若是是對象的話,也須要將這個對象數據劫持。同理,set那邊也須要加這行。ajax
function defineReactive(obj,key,value){ // 注意這裏!!!!!!! observer(value) Object.defineProperty(obj,key,{ get:function(){ return value }, set:function(newVal){ // 注意這裏!!!!!!! observer(newVal) console.log('數據更新了') value = newVal } }) }
若是obj.name=[1,2,3];obj.name.push(4)發現又沒有通知了,這是由於Object.defineProperty不支持監聽數組變化。因此須要重寫數組上面的方法。
// 把數組上大部分方法重寫了,這裏不一一列舉。可是若是你 [1,2].length--,這是捕捉不到的 let arr = ['push','slice','split'] arr.forEach(method=>{ let oldPush = Array.property[method] Array.property[method] = function(value){ console.log('數據更新') oldPush.call(this, value) } })
vue.js 是採用數據劫持結合發佈者-訂閱者模式的方式,經過 Object.defineProperty()來劫持各個屬性的 setter,getter,在數據變更時發佈消息給訂閱者,觸發相應的監聽回調。
第一步:須要 observe 的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 setter 和 getter 這樣的話,給這個對象的某個值賦值,就會觸發 setter,那麼就能監聽到了數據變化
第二步:compile 解析模板指令,將模板中的變量替換成數據,而後初始化渲染頁面視圖,並將每一個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變更,收到通知,更新視圖
第四步:MVVM 做爲數據綁定的入口,整合 Observer、Compile 和 Watcher 三者,經過 Observer 來監聽本身的 model 數據變化,經過 Compile 來解析編譯模板指令,最終利用 Watcher 搭起 Observer 和 Compile 之間的通訊橋樑,達到數據變化 -> 視圖更新;視圖交互變化(input) -> 數據 model 變動的雙向綁定效果。
當屬性值是數組,數組變化的時候,跟蹤不到變化。由於數組雖然是對象,可是Object.defineProperty不支持數組,因此vue改寫了數組的全部方法,當調用數組方法的時候,就調動變更事件。可是不能經過屬性或者索引控制數組,好比length,index。
[!NOTE]
總結:data上,綁定全部屬性避免後期加新屬性。若是是數組,只能經過數組方法修改數組。以下例子,控制檯vm.arr--發現視圖並不會變化,vm.arr.push(4)就能變化
<div id="app">{{msg}}{{arr}}</div> <script src="node_modules/vue/dist/vue.js"></script> <script> let vm = new Vue({ el:'#app', // template加上以後會替換掉#app這個標籤 // template:'<h1>en</h1>', data:{msg:'msg',arr:[1,2,3]} }) vm.msg = 'msg' </script>
總共分爲 8 個階段建立前/後,載入前/後,更新前/後,銷燬前/後。
//父組件經過標籤上面定義傳值 <template> <Main :obj="data"></Main> </template> <script> //引入子組件 import Main form "./main" exprot default{ name:"parent", data(){ return { data:"我要向子組件傳遞數據" } }, //初始化組件 components:{ Main } } </script> //子組件經過props方法接受數據 <template> <div>{{data}}</div> </template> <script> exprot default{ name:"son", //接受父組件傳值 props:["data"] } </script>
//子組件經過$emit方法傳遞參數 <template> <div v-on:click="events"></div> </template> <script> //引入子組件 import Main form "./main" exprot default{ methods:{ events:function(){ } } } </script> // <template> <div>{{data}}</div> </template> <script> exprot default{ name:"son", //接受父組件傳值 props:["data"] } </script>
vue-router 模塊的 router-link 組件。
在實際項目中咱們會碰到多層嵌套的組件組合而成,可是咱們如何實現嵌套路由呢?所以咱們須要在 VueRouter 的參數中使用 children 配置,這樣就能夠很好的實現路由嵌套。
index.html,只有一個路由出口
<div id="app"> <!-- router-view 路由出口, 路由匹配到的組件將渲染在這裏 --> <router-view></router-view> </div>
main.js,路由的重定向,就會在頁面一加載的時候,就會將 home 組件顯示出來,由於重定向指向了 home 組件,redirect 的指向與 path 的必須一致。children 裏面是子路由,固然子路由裏面還能夠繼續嵌套子路由。
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) //引入兩個組件 import home from "./home.vue" import game from "./game.vue" //定義路由 const routes = [ { path: "/", redirect: "/home" },//重定向,指向了home組件 { path: "/home", component: home, children: [ { path: "/home/game", component: game } ] } ] //建立路由實例 const router = new VueRouter({routes}) new Vue({ el: '#app', data: { }, methods: { }, router })
home.vue,點擊顯示就會將子路由顯示在出來,子路由的出口必須在父路由裏面,不然子路由沒法顯示。
<router-link :to="index">
router.push('index')
webpack 中提供了 require.ensure()來實現按需加載。之前引入路由是經過 import 這樣的方式引入,改成 const 定義的方式進行引入。
import home from '../../common/home.vue'
const home = r => require.ensure( [], () => r (require('../../common/home.vue')))
vue 框架中狀態管理。在 main.js 引入 store,注入。新建了一個目錄 store,….. export 。場景有:單頁應用中,組件之間的狀態。音樂播放、登陸狀態、加入購物車
// 新建 store.js import vue from 'vue' import vuex form 'vuex' vue.use(vuex) export default new vuex.store({ //...code }) //main.js import store from './store' ...
有 5 種,分別是 state、getter、mutation、action、module
若是請求來的數據不是要被其餘組件公用,僅僅在請求的組件內使用,就不須要放入 vuex 的 state 裏
若是被其餘地方複用,請將請求放入 action 裏,方便複用,幷包裝成 promise 返回
vuex 僅僅是做爲 vue 的一個插件而存在,不像 Redux,MobX 等庫能夠應用於全部框架,vuex 只能使用在 vue 上,很大的程度是由於其高度依賴於 vue 的 computed 依賴檢測系統以及其插件系統,
vuex 總體思想誕生於 flux,可其的實現方式完徹底全的使用了 vue 自身的響應式設計,依賴監聽、依賴收集都屬於 vue 對對象 Property set get 方法的代理劫持。最後一句話結束 vuex 工做原理,vuex 中的 store 本質就是沒有 template 的隱藏着的 vue 組件;
Vue.use(Vuex) 方法執行的是 install 方法,它實現了 Vue 實例對象的 init 方法封裝和注入,使傳入的 store 對象被設置到 Vue 上下文環境的\(store 中。所以在 Vue Component 任意地方都可以經過 this.\)store 訪問到該 store。
在 store 構造方法中有 makeLocalContext 方法,全部 module 都會有一個 local context,根據配置時的 path 進行匹配。因此執行如 dispatch('submitOrder', payload)這類 action 時,默認的拿到都是 module 的 local state,若是要訪問最外層或者是其餘 module 的 state,只能從 rootState 按照 path 路徑逐步進行訪問。
store 初始化時,全部配置的 action 和 mutation 以及 getters 均被封裝過。在執行如 dispatch('submitOrder', payload)的時候,actions 中 type 爲 submitOrder 的全部處理方法都是被封裝後的,其第一個參數爲當前的 store 對象,因此可以獲取到 { dispatch, commit, state, rootState } 等數據。
Vuex 中修改 state 的惟一渠道就是執行 commit('xx', payload) 方法,其底層經過執行 this._withCommit(fn) 設置_committing 標誌變量爲 true,而後才能修改 state,修改完畢還須要還原_committing 變量。外部修改雖然可以直接修改 state,可是並無修改_committing 標誌位,因此只要 watch 一下 state,state change 時判斷是否_committing 值爲 true,便可判斷修改的合法性。
devtoolPlugin 中提供了此功能。由於 dev 模式下全部的 state change 都會被記錄下來,'時空穿梭' 功能其實就是將當前的 state 替換爲記錄中某個時刻的 state 狀態,利用 store.replaceState(targetState) 方法將執行 this._vm.state = state 實現。
v-if(判斷是否隱藏)、v-for(把數據遍歷出來)、v-bind(綁定屬性)、v-model(實現雙向綁定)
[!NOTE]
思路:使用Vue的router.beforeEach鉤子函數結合axios的攔截器功能來實現。