vue實現數據雙向綁定主要是:採用數據劫持結合發佈者-訂閱者模式的方式,經過Object.defineProperty()來劫持各個屬性的setter,getter,在數據變更時發佈消息給訂閱者,觸發相應監聽回調。當把一個普通 Javascript 對象傳給 Vue 實例來做爲它的 data 選項時,Vue 將遍歷它的屬性,用 Object.defineProperty 將它們轉爲 getter/setter。用戶看不到 getter/setter,可是在內部它們讓 Vue 追蹤依賴,在屬性被訪問和修改時通知變化。javascript
vue的數據雙向綁定 將MVVM做爲數據綁定的入口,整合Observer,Compile和Watcher三者,經過Observer來監聽本身的model的數據變化,經過Compile來解析編譯模板指令(vue中是用來解析 {{}}),最終利用watcher搭起observer和Compile之間的通訊橋樑,達到數據變化 —>視圖更新;視圖交互變化(input)—>數據model變動雙向綁定效果。html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <body> <div id="app"> <input type="text" id="txt"> <p id="show"></p> </div> </body> <script type="text/javascript"> var obj = {} Object.defineProperty(obj, 'txt', { get: function () { return obj }, set: function (newValue) { document.getElementById('txt').value = newValue document.getElementById('show').innerHTML = newValue } }) document.addEventListener('keyup', function (e) { obj.txt = e.target.value }) </script> </body> </html>
beforeCreate(建立前) 在數據觀測和初始化事件還未開始
created(建立後) 完成數據觀測,屬性和方法的運算,初始化事件,$el屬性尚未顯示出來
beforeMount(載入前) 在掛載開始以前被調用,相關的render函數首次被調用。實例已完成如下的配置:編譯模板,把data裏面的數據和模板生成html。注意此時尚未掛載html到頁面上。
mounted(載入後) 在el 被新建立的 vm.$el 替換,並掛載到實例上去以後調用。實例已完成如下的配置:用上面編譯好的html內容替換el屬性指向的DOM對象。完成模板中的html渲染到html頁面中。此過程當中進行ajax交互。
beforeUpdate(更新前) 在數據更新以前調用,發生在虛擬DOM從新渲染和打補丁以前。能夠在該鉤子中進一步地更改狀態,不會觸發附加的重渲染過程。
updated(更新後) 在因爲數據更改致使的虛擬DOM從新渲染和打補丁以後調用。調用時,組件DOM已經更新,因此能夠執行依賴於DOM的操做。然而在大多數狀況下,應該避免在此期間更改狀態,由於這可能會致使更新無限循環。該鉤子在服務器端渲染期間不被調用。
beforeDestroy(銷燬前) 在實例銷燬以前調用。實例仍然徹底可用。
destroyed(銷燬後) 在實例銷燬以後調用。調用後,全部的事件監聽器會被移除,全部的子實例也會被銷燬。該鉤子在服務器端渲染期間不被調用。前端
同步:vue
import Page from
'@/components/page'
// 同步方式引入
java
父子組件的執行順序爲,node
父組件beforeCreated es6
父組件created ajax
父組件beforeMountedvuex
子組件beforeCreated segmentfault
子組件created
子組件beforeMounted
孫組件beforeCreated
孫組件created
孫組件beforeMounted
孫組件mounted
子組件mounted
父組件mounted
異步:
const Page = () => import (
'@/components/page'
)
// 異步引入
父組件beforeCreated
父組件created
父組件beforeMounted
父組件mounted
子組件beforeCreated
子組件created
子組件beforeMounted
子組件mounted
孫組件beforeCreated
孫組件created
孫組件beforeMounted
孫組件mounted
beforeDestroy鉤子函數在實例銷燬以前調用。在這一步,實例仍然徹底可用。
destroyed鉤子函數在Vue 實例銷燬後調用。調用後,Vue 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬(也就是說子組件也會觸發相應的函數)。這裏的銷燬並不指代'抹去',而是表示'解綁'。
銷燬時beforeDestory函數的傳遞順序爲由父到子,destory的傳遞順序爲由子到父。
答:會觸發 下面這幾個beforeCreate, created, beforeMount, mounted 。
.vue.js的兩個核心是什麼?
答:數據驅動、組件系統
性能優化 內存減小
兼容低,沒錯,IE11都不支持Proxy
vue3的整個數據監聽系統都進行了重構,由es5的Object.defineProperty改成了es6的proxy。尤大說,這個新的數據監聽系統帶來了初始化速度加倍同時內存佔用減半的效果。
Vue 2.x裏,是經過 遞歸 + 遍歷 data
對象來實現對數據的監控的,
Vue 3.x 使用Proxy。
這裏面有兩點須要強調下:
一、Object.defineProperty須要遍歷全部的屬性,這就形成了若是vue對象的data/computed/props中的數據規模龐大,那麼遍歷起來就會慢不少。
二、一樣,若是vue對象的data/computed/props中的數據規模龐大,那麼Object.defineProperty須要監聽全部的屬性的變化,那麼佔用內存就會很大。
Object.defineProperty VS Proxy
Object.definePropety的缺點
除了上面講,在數據量龐大的狀況下Object.defineProperty的兩個缺點外,Object.defineProperty還有如下缺點。
一、沒法監聽es6的Set、WeakSet、Map、WeakMap的變化;
二、沒法監聽Class類型的數據;
三、屬性的新加或者刪除也沒法監聽;
四、數組元素的增長和刪除也沒法監聽 (性能考慮,因此沒法監聽 )
Proxy應運而生
針對Object.defineProperty的缺點,Proxy都可以完美得解決,它惟一的缺點就是,對IE不友好,因此vue3在檢測到若是是使用IE的狀況下(沒錯,IE11都不支持Proxy),會自動降級爲Object.defineProperty的數據監聽系統。因此若是是IE用戶,那麼就享受不到速度加倍,內存減半的體驗了。
Object.defineProperty
初始化
const data = {} for(let i = 0; i <= 100000; i++) { data['pro' + i] = i } function defineReactive(data, property) { let value = data[property] Object.defineProperty(data, property, { get() { // console.log(`讀取${property}的值爲${value}`) return value }, set(newVal) { // console.log(`更新${property}的值爲${newVal}`) } }) } for(let property in data) { defineReactive(data, property) }
Proxy
初始化
const data = {} for(let i = 0; i <= 100000; i++) { data['pro' + i] = i } var proxyData = new Proxy(data, { get(target, property, receiver) { // console.log(`讀取${property}的值爲${target[property]}`) return Reflect.get(target, property, receiver); }, set(target, property, value, receiver) { // console.log(`更新${property}的值爲${value}`) return Reflect.set(target, property, value, receiver); } })
ES6
原生提供的 Proxy
語法很簡單,用法以下:
let proxy = new Proxy(target, handler);
let obj = { a : 1 } let proxyObj = new Proxy(obj,{ get : function (target,prop) { return prop in target ? target[prop] : 0 }, set : function (target,prop,value) { target[prop] = 888; } }) console.log(proxyObj.a); // 1 console.log(proxyObj.b); // 0 proxyObj.a = 666; console.log(proxyObj.a) // 888
下面的例子 給予proxy的vue demo
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>簡單版mvvm</title> </head> <body> <div id="app"> <h1>開發語言:{{language}}</h1> <h2>組成部分:</h2> <ul> <li>{{makeUp.one}}</li> <li>{{makeUp.two}}</li> <li>{{makeUp.three}}</li> </ul> <h2>描述:</h2> <p>{{describe}}</p> <p>計算屬性:{{sum}}</p> <input placeholder="123" v-module="language" /> </div> <script> function Mvvm(options = {}) { this.$options = options let data = this._data = this.$options.data let vm = initVm.call(this) initObserve.call(this, data) initComputed.call(this) new Compile(this.$options.el, vm) mounted.call(this._vm) return this._vm } function initVm () { this._vm = new Proxy(this, { get: (target, key, receiver) => { return this[key] || this._data[key] || this._computed[key] }, set: (target, key, value) => { return Reflect.set(this._data, key, value) } }) return this._vm } function initObserve(data) { this._data = observe(data) } function observe(data) { if (!data || typeof data !== 'object') return data return new Observe(data) } class Observe { constructor(data) { this.dep = new Dep() for (let key in data) { data[key] = observe(data[key]) } return this.proxy(data) } proxy(data) { let dep = this.dep return new Proxy(data, { get: (target, prop, receiver) => { if (Dep.target) { dep.addSub(Dep.target) } return Reflect.get(target, prop, receiver) }, set: (target, prop, value) => { const result = Reflect.set(target, prop, observe(value)) dep.notify() return result } }) } } class Compile{ constructor (el, vm) { this.vm = vm this.element = document.querySelector(el) this.fragment = document.createDocumentFragment() this.init() } init() { let element = this.element this.fragment.append(element) this.replace(this.fragment) document.body.appendChild(this.fragment) } replace(frag) { let vm = this.vm Array.from(frag.childNodes).forEach(node => { let txt = node.textContent let reg = /\{\{(.*?)\}\}/g if (node.nodeType === 1) { let nodeAttr = node.attributes; Array.from(nodeAttr).forEach(attr => { let name = attr.name let exp = attr.value if (name.includes('v-')){ node.value = vm[exp] node.addEventListener('input', e => { let newVal = e.target.value // 至關於給this.a賦了一個新值 // 而值的改變會調用set,set中又會調用notify,notify中調用watcher的update方法實現了更新 vm[exp] = newVal }) } }); } else if (node.nodeType === 3 && reg.test(txt)) { replaceTxt() function replaceTxt() { node.textContent = txt.replace(reg, (matched, placeholder) => { new Watcher(vm, placeholder, replaceTxt); // 監聽變化,進行匹配替換內容 return placeholder.split('.').reduce((val, key) => { return val[key] }, vm) }) } } if (node.childNodes && node.childNodes.length) { this.replace(node) } }) } } class Dep { constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } notify() { this.subs.filter(item => typeof item !== 'string').forEach(sub => sub.update()) } } class Watcher { constructor (vm, exp, fn) { this.fn = fn this.vm = vm this.exp = exp Dep.exp = exp Dep.target = this let arr = exp.split('.') let val = vm arr.forEach(key => { val = val[key] }) Dep.target = null } update() { let exp = this.exp let arr = exp.split('.') let val = this.vm arr.forEach(key => { val = val[key] }) this.fn(val) } } function initComputed() { let vm = this let computed = this.$options.computed vm._computed = {} if (!computed) return Object.keys(computed).forEach(key => { this._computed[key] = computed[key].call(this._vm) new Watcher(this._vm, key, val => { this._computed[key] = computed[key].call(this._vm) }) }) } function mounted() { let mounted = this.$options.mounted mounted && mounted.call(this) } // 寫法和Vue同樣 let mvvm = new Mvvm({ el: '#app', data: { language: 'Javascript', makeUp: { one: 'ECMAScript', two: '文檔對象模型(DOM)', three: '瀏覽器對象模型(BOM)' }, describe: '沒什麼產品是寫不了的', a: 1, b: 2 }, computed: { sum() { return this.a + this.b } }, mounted() { console.log('i am mounted', this.a) } }) </script> </body> </html>
參考 http://www.javashuo.com/article/p-hfhawqpd-ez.html
hash模式:在瀏覽器中符號「#」,#以及#後面的字符稱之爲hash,用window.location.hash讀取;
特色:hash雖然在URL中,但不被包括在HTTP請求中;用來指導瀏覽器動做,對服務端安全無用,hash不會重加載頁面。
hash 模式下,僅 hash 符號以前的內容會被包含在請求中,如 http://www.xxx.com,所以對於後端來講,即便沒有作到對路由的全覆蓋,也不會返回 404 錯誤。
history模式:history採用HTML5的新特性;且提供了兩個新方法:pushState(),replaceState()能夠對瀏覽器歷史記錄棧進行修改,以及popState事件的監聽到狀態變動。
history 模式下,前端的 URL 必須和實際向後端發起請求的 URL 一致,如 http://www.xxx.com/items/id。後端若是缺乏對 /items/id 的路由處理,將返回 404 錯誤。Vue-Router 官網裏如此描述:「不過這種模式要玩好,還須要後臺配置支持……因此呢,你要在服務端增長一個覆蓋全部狀況的候選資源:若是 URL 匹配不到任何靜態資源,則應該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。」
vue經常使用的修飾符?
答:.prevent: 提交事件再也不重載頁面;.stop: 阻止單擊事件冒泡;.self: 當事件發生在該元素自己而不是子元素的時候會觸發;.capture: 事件偵聽,事件發生的時候會調用
vue等單頁面應用及其優缺點
答:優勢:Vue 的目標是經過儘量簡單的 API 實現響應的數據綁定和組合的視圖組件,核心是一個響應的數據綁定系統。MVVM、數據驅動、組件化、輕量、簡潔、高效、快速、模塊友好。
缺點:不支持低版本的瀏覽器,最低只支持到IE9;不利於SEO的優化(若是要支持SEO,建議經過服務端來進行渲染組件);第一次加載首頁耗時相對長一些;不可使用瀏覽器的導航按鈕須要自行實現前進、後退。
props: { // 基礎類型檢測 (`null` 意思是任何類型均可以) propA: Number, // 多種類型 propB: [String, Number], // 必傳且是字符串 propC: { type: String, required: true }, // 數字,有默認值 propD: { type: Number, default: 100 }, // 數組/對象的默認值應當由一個工廠函數返回 propE: { type: Object, default: function () { return { message: 'hello' } } }, // 自定義驗證函數 propF: { validator: function (value) { return value > 10 } } }
refAge: { type: Number, default: 0 }, refName: { type: String, default: '' }, hotDataLoading: { type: Boolean, default: false }, hotData: { type: Array, default: () => { return [] } }, getParams: { type: Function, default: () => () => {} }, meta: { type: Object, default: () => ({}) }
組件內的導航鉤子主要有這三種:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。他們是直接在路由組件內部直接進行定義的
const File = { template: `<div>This is file</div>`, beforeRouteEnter(to, from, next) { // do someting // 在渲染該組件的對應路由被 confirm 前調用 }, beforeRouteUpdate(to, from, next) { // do someting // 在當前路由改變,可是依然渲染該組件是調用 }, beforeRouteLeave(to, from ,next) { // do someting // 導航離開該組件的對應路由時被調用 } }
beforeRouteEnter 不能獲取組件實例 this,由於當守衛執行前,組件實例被沒有被建立出來,剩下兩個鉤子則能夠正常獲取組件實例 this
少數狀況,咱們不知道要 加載那些組件,隨着業務動態添加,好比 接口動態加載,根據須要加載組件
<button @click="asdfn">切換組件</button>
掛載的元素地方
<div ref="xxx"></div>
註冊組件腳本
registerComponent(templateName,yourNeedEl){ var self=this; return import(`@/components/${templateName}.vue`).then((component) => { console.log("component",component) const cpt = Vue.extend(component.default); new cpt({ el: yourNeedEl }); }); },
組件調用
asdfn(){ console.log("tabContent2"); var ele=this.$refs.xxx; this.registerComponent("tabContent2",ele) },
記得引入
import Vue from 'vue'
1、computer
當頁面中有某些數據依賴其餘數據進行變更的時候,可使用計算屬性。
<p id="app"> {{fullName}} </p> <script> var vm = new Vue({ el: '#app', data: { firstName: 'Foo', lastName: 'Bar', }, computed: { fullName: function () { return this.firstName + ' ' + this.lastName } } }) </script>
須要注意的是,就算在data中沒有直接聲明出要計算的變量,也能夠直接在computed中寫入。
計算屬性默認只有getter,能夠在須要的時候本身設定setter:
// ... computed: { fullName: { // getter get: function () { return this.firstName + ' ' + this.lastName }, // setter set: function (newValue) { var names = newValue.split(' ') this.firstName = names[0] this.lastName = names[names.length - 1] } } }
這個時候在控制檯直接運行vm.fullName = ‘bibi wang’,相應的firstName和lastName也會改變。
適用場景:
2、watch
watch和computed很類似,watch用於觀察和監聽頁面上的vue實例,固然在大部分狀況下咱們都會使用computed,但若是要在數據變化的同時進行異步操做或者是比較大的開銷,那麼watch爲最佳選擇。watch爲一個對象,鍵是須要觀察的表達式,值是對應回調函數。值也能夠是方法名,或者包含選項的對象。直接引用文檔例子
var vm = new Vue({ el: '#app', data: { firstName: 'Foo', lastName: 'Bar', fullName: 'Foo Bar' }, watch: { firstName: function (val) { this.fullName = val + ' ' + this.lastName }, lastName: function (val) { this.fullName = this.firstName + ' ' + val } } })
若是在data中沒有相應的屬性的話,是不能watch的,這點和computed不同。
適用場景
3、methods
方法,跟前面的都不同,咱們一般在這裏面寫入方法,只要調用就會從新執行一次,相應的有一些觸發條件,在某些時候methods和computed看不出來具體的差異,可是一旦在運算量比較複雜的頁面中,就會體現出不同。
須要注意的是,computed是具備緩存的,這就意味着只要計算屬性的依賴沒有進行相應的數據更新,那麼computed會直接從緩存中獲取值,屢次訪問都會返回以前的計算結果。
var MyComponent = Vue.extend({ data: function() { return { message: '快速錄入信息', taskUrl: '', message2: '查看詳情>>', telPhone:'撥打電話', } }, // props:['data'], props:['myname'], template: '<a @click="show(message)">{{message }}</a>', methods: { show(message) { var self=this; // console.log('地圖氣泡 哈哈哈',message,this.message2,this.myProps,this.myType); // this.$router.push({path: '/detail', query:defaults }); //快速錄入信息點 // this.myRouter.push({ path: '/PositioningAndPublishing', query: { 'testId': this.props.id } }) if(this.myType=="go_addEvent"){ var highwayId=this.myProps.highwayId;//K1289+820 var highwayDirectionId=this.myProps.highwayDirectionId;// var positionCode=this.myProps.positionCode;// var cache=+new Date; self.bus.$emit('upPositionNumber', positionCode) // 觸發事件sh //this.$emit("upDataPoint",this.myProps) var path='/PositioningAndPublishing'; var query={ "testId": self.myProps.id , "highwayId": highwayId , "highwayDirectionId": highwayDirectionId , "positionCode": positionCode, "cache": cache } console.log(path,"0myProps",query); self.myRouter.push({ path: path, replace: true, query:query }) } //查看詳情 // this.myRouter.push({ path: '/evnetWork', query: { 'testId': this.props.id } }) // evnetWork?testId='+id+'" if(this.myType=="go_Detail"){ var cache=+new Date; this.myRouter.push({ path: '/evnetWork',replace: true, query: { 'testId': this.myProps.id ,'cache': cache }}) } }, }, created(){ //this.message=this.message2; if(this.myType=="go_addEvent"){ this.message="快速錄入信息" } //查看詳情 // this.myRouter.push({ path: '/evnetWork', query: { 'testId': this.props.id } }) // evnetWork?testId='+id+'" if(this.myType=="go_Detail"){ this.message="查看詳情>>" } //console.log("cre33ated",this,this.message); }, mounted() { //console.log("TextIconOverlay",new BMapLib.TextIconOverlay()); }, beforeDestroy () { this.bus.$off('upPositionNumber'); this.bus.$off('upTestId'); // 觸發事件sh }, });
調用
var component = new MyComponent().$mount();
infoBox =new BMapLib.InfoBox(map,content,opts); infoBox.enableAutoPan(); infoBox.addEventListener('open',function(type, target, point){ //窗口打開是,隱藏自帶的關閉按鈕 //console.log(type,target,point); document.querySelectorAll('.boxClass')[0].clientLeft; document.querySelectorAll('.boxClass')[0].classList.add("myps1sss"); setTimeout(()=>{ // console.log("component go_addEvent",component) console.log("dat222a2",data2.positionCode); component.myProps=data2; component.myRouter=self.$router; component.myType="go_addEvent"; component.message="快速錄入信息"; document.querySelectorAll('.luxx')[0].appendChild(component.$el); },100) })
self.lookTimeer=setTimeout(()=>{ if(document.querySelector('.jshows')){ document.querySelector('.jshows').removeEventListener("click",looksss); document.querySelector('.jshows').addEventListener("click",looksss) } },200)