上周的某一天,和一位同樣是前端技術極度愛好的開發者朋友聊天,他在提出了一個問題,他寫的vue程式為什麼在dev模式運行良好,而在production模式就直接報錯了。這讓我感到驚訝,還有這麼神奇的事情。今就把這個歷險記道給大伙聽聽,看能從中學習到什麼? 一、還原現場 朋友在看到我的驚訝後,分分就把他 ...
上周的某一天,和一位同樣是前端技術極度愛好的開發者朋友聊天,他在提出了一個問題,他寫的vue程式為什麼在dev模式運行良好,而在production模式就直接報錯了。這讓我感到驚訝,還有這麼神奇的事情。今就把這個歷險記道給大伙聽聽,看能從中學習到什麼?
一、還原現場
朋友在看到我的驚訝後,分分就把他出錯的demo發給了,本地運行,事故現場重現:
二、排查嫌疑對象
既然現象是必現,要麼是自己的代碼出了問題,要麼就是vue有Bug(心裡莫名的偷笑,大伙都懂的)。
2.1 代碼文件結構和源碼展示
從代碼結構看,沒有好說的。就是用vue-cli創建的模板開發項目,其保增加了service層而已。經過我多年來的經驗,將嫌疑放到了service/index.js和components/HellowWorld.vue兩個文件上。
- service/index.js代碼
let item = {}; item.result = 22222; item.do = callBack => { return callBack({ result: this.a.result }); }; export default item;
- HelloWorld.vue代碼(只展示了js代碼部分)
import service from "@/service/index" export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } }, mounted () { service.do(item => { this.msg = item.result }) } }
簡要邏輯說明:
- HelloWorld.vue引用了service/index.js文件
- service/index.js中定義了do方法,且接受一個callback參數(使用了es6的箭頭函數)
- HelloWorld.vue在mounted方法中調用了do方法,且傳入了一個函數表達式(也使用了箭頭函數)
2.2 報錯位置偵查
通過運行結果對比圖,可以看出production模式下的運行是有報錯,在達里我們放大他的報錯位置:
看到這裡,你是否有想破口大罵的衝動,怎麼會this.a.result呢,這代碼明顯有錯誤吧。然後我迅速查閱了他給的demo代碼,見service/index.js中的do方法,確實是怎麼樣寫的。立刻,我略帶鄙視的口吻質問我的那位朋友,你這個幾年的代碼白寫了吧,居然能犯這麼低級的錯誤。我直接把這個錯誤現場圖扔給了他。
馬上,他回了一個更為鄙視的表情,那為什麼我的dev模式能正常運行呢。我立即無語且尷尬。因為確實他的dev模式運行是正常的,只有在production模式下才出的問題啊。
2.3 重點分析嫌疑對象
經過上述的分析和折騰,我們可以初步確定問題點就在service/index.js中do方法中和this上。也就是說在dev模式下這個this.a上是有result這個屬性的,而在production模式下this連這個a屬性都沒有了。
作為老鳥的我,突然想到,dev模式和production模式都是運行在有sourcemap的的情況下的。這很不利用我們看編譯後的代碼。於是,我關閉了chrome瀏覽器的sourcemap功能,兩種模式下代碼如下:
- dev模式下的運行代碼:
註意三個紅框處的代碼,webpack在dev模式下代碼文件是沒有合併成一個文件的,而是遵行commanJs規範,進行模式化載入的,而他對這個service/index.js這個模式導出時,用的名稱正是a。也就是解釋了在dev模式下this.a為什麼會有效,他的this.a.result有值,則是因為他雖然是單文件模式化載入,但其文件中的js代碼還是被bable做了轉換,將箭頭運算符轉換為了es5可執行的代碼。所以this.a.result是有值的。
- production模式下的運行代碼:
看這段代碼是否有些頭大,其實從中我們只需要關心l這個變數的值,經測試發現,他的值不service/index.js中導出的對象,而是瀏覽器全局對象window。這就是為什麼production模式下的代碼不能正常運行的問題了。
三、我的推理和總結
通過上述分析,可以大致推理出webpack在dev模式下是按照commonJs模式將各個文件獨立模式化載入和引用,而Build之後,各個文件模塊被合併成了一個,且對servcie/index.js進行了直接導出。再中上箭頭函數對this指向的處理,就造成了this.a無效了。
在這個demo中就算把service/index.js中的this指向處理好了,但其值還是會正常顯示,原因在於vue組件中的mounted方法中也用到了箭頭函數,其this的指向在運行時也會不正確。具體解釋如下:
註意,不要在實例屬性或者回調函數中(如 vm.$watch('a', newVal => this.myMethod()))使用箭頭函數。因為箭頭函數綁定父級上下文,所以 this 不會像預想的一樣是 Vue 實例,而且 this.myMethod 是未被定義的。
vue官網說明地址:https://cn.vuejs.org/v2/guide/instance.html
3.1 原因總結
- this.a為什麼可以訪問,是因為webpack的dev下編譯是單個文件模式化引用導致的
- vue組件的mounted中用不可使用箭頭函數,這個vue的特性,官網可見。
- 示例代碼下載