2021.08.31開始 運行時的源碼在vue/dist/vue.runtime.esm.js里面,不過有些非函數的定義,打印不出來(看看vue源碼斷點怎么打比較合適)。看官方文檔時候,直接百度中文的那段文字,經常搜不出來,可以先根據官方文檔或自己定位到對應的代碼段,然后搜索代碼段的變量名,就能搜到了
1.官方文檔 模板語法-插值-使用javascript表達式 最后,有這么一句話:模板表達式都被放在沙盒中,只能訪問全局變量的一個白名單,如 Math
和 Date
。你不應該在模板表達式中試圖訪問用戶定義的全局變量。
開始我以為是vue.prototype.$aa = 1,不能訪問這個$aa。看了源碼之后,才明白。全局對象指的是Array、Object這類的js關鍵字,當然也可以自己定義。比如我寫個js文件 export 1,然后在vue文件中import aaa from ./js文件,在模板語法中使用{{aaa}},就會報錯。因為模板表達式的沙盒里沒有aaa這個變量。想使用的話可以把它掛載到vueprototype上或者直接賦值給當前vue實例的data中的一個屬性
function makeMap ( str, expectsLowerCase ) { var map = Object.create(null); var list = str.split(','); for (var i = 0; i < list.length; i++) { map[list[i]] = true; } return expectsLowerCase ? function (val) { return map[val.toLowerCase()]; } : function (val) { return map[val]; } }
var allowedGlobals = makeMap( // 'Infinity,undefined,NaN,isFinite,isNaN,' + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,' + 'require' // for Webpack/Browserify );
var hasHandler = { has: function has (target, key) { //target就是當前vue實例,key就是要解析的值 var has = key in target; //對象的in方法,只要key在target的原型鏈上,就會返回true,所以設置vue.prototype.$aa = 1之后,使用$aa是沒問題的
var isAllowed = allowedGlobals(key) || (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); //這里判斷的是全局變量或者以_
開頭的屬性,這么做是由於渲染函數中會包含很多以_
開頭的內部方法,如渲染函數里遇到的_c
、_v
等等 if (!has && !isAllowed) { if (key in target.$data) { warnReservedPrefix(target, key); } else { warnNonPresent(target, key); } } return has || !isAllowed } };
最后的判斷!has
我們可以理解為你訪問了一個沒有定義在實例對象上(或原型鏈上)的屬性,所以這個時候提示錯誤信息是合理,但是即便!has
成立也不一定要提示錯誤信息,因為必須要滿足!isAllowed
,也就是說當你訪問了一個雖然不在實例對象上(或原型鏈上)的屬性,但如果你訪問的是全局對象那么也是被允許的。這樣我們就可以在模板中使用全局對象了
2.在谷歌瀏覽器的控制台,打印一些數據時。發現同樣的數據,顯示的格式經常是不一樣的,有時是data:{...},有時是data:{a:1},有時是data:{__ob__:Observer}。
經過我的驗證,發現沒被掛載到vue實例上的(被淺拷貝的也算),會直接顯示數據data:{a:1},被掛載到vue實例上並且對象長度小於等於4的,會顯示data:{__ob__:Observer},其余顯示的都是data:{...}
拓展:給vue data屬性的對象添加屬性時,有以下幾種情況。
const a = {content:{a:1,b:2,c:3,d:4,e:5}} 情況一 this.form = a; 這樣相當於將a的地址賦值給this.form,這時不管是打印a 還是this.form都是一樣的,不會去判斷對象長度是多少,點開查看(調用getter)之前都是顯示{...} 情況二 this.form = {...a}或者this.form = Object.assign({},this.form,a). 這時this.form===a是false,他倆這時的引用類型屬性是共用一個地址,但是基本類型的值是互不干擾的。 打印a,會判斷長度,小於等於4的顯示{__ob__:Observer},大於4的顯示{...}; 打印this.form,不會判斷長度,統一顯示{...} 情況三 this.form = JSON.parse(JSON.stringify(a)) //深拷貝 這時打印a,會直接顯示所有的值,不會有{...}和{__ob__:Observer},因為它沒有被vue影響 打印this.form,不會判斷長度,統一顯示{...} 但是,以上三種情況,當我直接打印this.form.a時,都會顯示一個{__ob__:Observer}。
具體原因要等以后看源碼再了解了(2021.09.07)
3.當你設置 vm.someData = 'new value'
,該組件不會立即重新渲染(這里指的是html里的dom元素 不會重新渲染)。當刷新隊列時,組件會在下一個事件循環“tick”中更新。多數情況我們不需要關心這個過程,但是如果你想基於更新后的 DOM 狀態來做點什么,這就可能會有些棘手。雖然 Vue.js 通常鼓勵開發人員使用“數據驅動”的方式思考,避免直接接觸 DOM,但是有時我們必須要這么做。為了在數據變化之后等待 Vue 完成更新 DOM,可以在數據變化之后立即使用 Vue.nextTick(callback)
。這樣回調函數將在 DOM 更新完成后被調用。
因為 $nextTick()
返回一個 Promise
對象,所以你可以使用新的 ES2017 async/await 語法完成相同的事情:
methods: { updateMessage: async function () { this.message = '已更新' console.log(this.$el.textContent) // => '未更新' await this.$nextTick() console.log(this.$el.textContent) // => '已更新' } }
當我使用vue檢測不到的方法來增減對象和數組時,可能是數據驅動不了視圖,也可能是數據驅動了視圖,但通過this.$refs.textContent是獲取不到更新后的dom的,這時候就要用$nextTick了。(具體原因要等源碼的研究了 2021.09.07)
4.VUE事件修飾符 https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6
注意: 特殊的系統修飾鍵有ctrl、alt 、shift、 meta(window鍵),例如 @key.alt.67 //alt+c @click.ctrl //ctrl+click
修飾鍵與常規按鍵不同,在和 keyup
事件一起用時,事件觸發時修飾鍵必須處於按下狀態。換句話說,只有在按住 ctrl
的情況下釋放其它按鍵,才能觸發 keyup.ctrl
。而單單釋放 ctrl
也不會觸發事件。如果你想要這樣的行為,請為 ctrl
換用 keyCode
:keyup.17
2.5.0新增的exact修飾符 允許你控制由精確的系統修飾符組合觸發的事件。
<!-- 即使 Alt 或 Shift 被一同按下時也會觸發。只監聽ctrl,不管同時有其他幾個按鍵在按 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的時候才觸發 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 沒有任何系統修飾符被按下的時候才觸發 -->
<button v-on:click.exact="onClick">A</button>
鼠標修飾符有 .left .right .middle
5.復選框<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
// 當選中時 vm.toggle === 'yes' // 當沒有選中時 vm.toggle === 'no'
這里的 true-value
和 false-value
attribute 並不會影響輸入控件的 value
attribute,因為瀏覽器在提交表單時並不會包含未被選中的復選框。如果要確保表單中這兩個值中的一個能夠被提交,(即“yes”或“no”),請換用單選按鈕。
翻譯:比如有這樣的頁面
<input type="checkbox" v-model="picked" :true-value="value1" :false-value="value2"> <label> 復選框 </label> <p> {{picked}} </p> <p>{{value1}} </p> <p>{{value2}} </p>
data: { picked:false, value1:123, value2:345 }
那么剛進頁面時,picked的值就是false,這個value1和value2的值是不會影響picked的值的。
但是一旦對這個chekcbox做了勾選或取消勾選的操作,這個picked的值就會變成123或者456。
拓展:如果換成radio的話,剛進頁面是一樣的,但是一旦做了操作,picked的值就會變成null。
6.閱讀vuex官方文檔時,對於rootState一直無法理解,官方文檔對於它的定義是 根節點狀態。但是有rootState.count這種用法,而我打印的時候,rootState底下就是幾個module對象,沒有.count這一層級。
原因:參考了一些文檔,發現.count確實是根節點的狀態,並且想獲取這個層級的值,就要在new Vuex.Store()時給根節點賦一個state對象。像這樣
const store = new Vuex.Store({ state: { count: 1, }, modules: { a: moduleA, b: moduleB, }, })
這時候在moduleA的action或者getters里面打印rootState,里面就有count和a、b兩個modules里面的state對象。
7.Vue 深入響應式原理說明:Vue 不能檢測數組和對象的一些變化,也就是說數據更新時視圖不會更新。但是實際使用中發現還是更新了。
原因: 查了一些文檔,沒發現回答。自己又測試了一下,發現官方文檔寫的是沒有問題的。操作的時候,如果只有arr.length=1或者arr[0] = 1這種操作時,不會觸發視圖更新。但是如果有其他的能被vue檢測到的數據更新(this.count='xx),就會順帶把vue檢測不到的數據變動也一起更新了,這樣就實現了類似arr.length = 1也被vue檢測到的效果。但是這樣只更新了視圖,並沒有給這個新數據加上getter和setter,在控制台打印可以看到,有getter和setter的會默認不顯示,沒有的會直接顯示出來。
壞處:目前能想到的壞處,就是雖然視圖更新了,但是vue的watch是監聽不到數據變化的。因為這個數據沒有setter,watch是靠監聽setter來實現的。
export default { data() { return { items: ['a', 'b', 'c'],
count:1 } }, methods: { hah() { this.items.length = 2 // 不是響應性的,沒有被vue檢測到
//this.count = 'xx' //是響應性的,會觸發vue更新 setTimeout(() => { this.items.shift() },3000) }, }, watch: { items(new,old) { console.log(new,old) // 1,3 ,vue只能監聽到一開始的長度3和shift之后的長度1 } } }
源碼解析:簡單打斷點看了一下,應該是this.count觸發了它自己的set和watch.然后又觸發了vue的$nextTick和render方法,vue就順帶把當前實例的數據都更新到視圖上了。但是並沒有給新屬性加上setter和getter,也沒有觸發其他屬性的watch。
經驗:以后對於vue檢測不到的變動,還是都用this.$set來操作吧。因為不可能每次都正好有別的屬性在更新,萬一一開始有,后面又拿掉了那個更新的屬性操作,vue就檢測不到更新了,這是在埋bug.
8.vue官方文檔說到父組件引用子組件時的標簽名和props在標簽上的使用,是這樣的
使用 kebab-case Vue.component('my-component-name', { /* ... */ })
當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在引用這個自定義元素時使用 kebab-case,例如 <my-component-name>
。
使用PascalCase Vue.component('MyComponentName', { /* ... */ })
當使用 PascalCase (首字母大寫命名) 定義一個組件時,你在引用這個自定義元素時兩種命名法都可以使用。也就是說 <my-component-name>
和 <MyComponentName>
都是可接受的。注意,盡管如此,直接在 DOM (即非字符串的模板) 中使用時只有 kebab-case 是有效的。
重點在最后一句,我在.vue文件中直接使用<MyComponent userName="a"/>是可用的,這是為啥?
原因:vue官方文檔經常提到的字符串模板和非字符串模板,其實是這樣定義的
1.字符串模板就是寫在vue中的template中定義的模板,如.vue的單文件組件模板和定義組件時template屬性值的模板。字符串模板不會在頁面初始化參與頁面的渲染,會被vue進行解析編譯之后再被瀏覽器渲染,所以不受限於html結構和標簽的命名。
Vue.component('MyComponentA', { template: '<div MyId="123"><MyComponentB>hello, world</MyComponentB></div>' }) <div id="app"> <MyComponentA></MyComponentA> </div>
2.dom模板(或非字符串模板、Html模板)就是寫在html文件中,一打開就會被瀏覽器進行解析渲染的,所以要遵循html結構和標簽的命名,否則瀏覽器不解析也就不能獲取內容了。
下面的例子不會被正確渲染, 會被解析成mycomponent,但是注冊的vue的組件是MyComponent,因此無法渲染。
<!DOCTYPE <html> <head> <meta charset="utf-8"> <title>Vue Component</title> </head> <body> <div id="app"> Hello Vue <MyComponent></MyComponent> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script > //全局注冊 Vue.component('MyComponent', { template: '<div>組件類容</div>' }); new Vue ({ el: '#app' }); </script> </body> </html>
所以,下面的例子就可以正常顯示了:
<!DOCTYPE <html> <head> <meta charset="utf-8"> <title>Vue Component</title> </head> <body> <div id="app"> Hello Vue <my-component></my-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script > //全局注冊 Vue.component('my-component', { template: '<div>組件類容</div>' }); new Vue ({ el: '#app' }); </script> </body> </html>
因為html對大小寫不敏感,所以在DOM模板中使用組件必須使用kebab-case命名法(短橫線命名)。
因此,對於組件名稱的命名,可參考如下實現:
/*-- 在單文件組件、JSX和字符串模板中 --*/ <MyComponent/> /*-- 在 DOM 模板中 --*/ <my-component></my-component> 或者 /*-- 在所有地方 --*/ <my-component></my-component>
9,vuex官方文檔說只能通過mutation來修改state,而實際使用中,發現this.$store.state.a = 132是可以生效的,並且其他組件也都可以訪問,只不過它沒有被設置set和get,想要這兩個用this.$set就可以了。不過在vuex的嚴格模式下會報錯。
開啟嚴格模式,僅需在創建 store 的時候傳入strict: true
const store = new Vuex.Store({ // ... strict: true 或者
strict: process.env.NODE_ENV !== 'production'
})
在嚴格模式下,無論何時發生了狀態變更且不是由 mutation 函數引起的,將會拋出錯誤。這能保證所有的狀態變更都能被調試工具跟蹤到。
總結:為了防止vuex的數據改變來源難以追蹤,使用中統一用mutation來改變state。也可以在開發過程中開啟vuex嚴格模式(生產環境中必須關閉)。