2017年5月28日

2017年5月16日

SVG 原地旋轉

SVG 物體旋轉的時候是以 <svg></svg> 的左上角當作圓心進行旋轉,因此當我們直接對 svg 內的元素進行 rotate 時,常常不是我們想要的效果。若我們想要讓物體在原地進行選轉的話,可以使用一些技巧。

操作範例

可以搭配範例看下面的說明,SVG 原地旋轉 @ Codepen

STEP 0: 原本方形的位置

假設我們原本在 <svg></svg> 中畫一個方形:
<svg>
    <!--   原本方形的位置(400, 300)   -->
    <rect x="400" y="300" width="40" height="30" stroke="#41b883" stroke-width="2" fill="#41CEC0"></rect>
</svg>

STEP 1: 先讓物體進行位移(translate)把圓心移到原物體中心點

為了讓這個方形能在原地旋轉,我們要把旋轉的圓心透過 transalte 移動到方形的中心點:
我們可以計算出方形的中心點是:
// x' = x + width / 2
x' = 400 + 40 / 2 = 420
// y' = y + height / 2
y' = 300 + 30 / 2 = 315
因此我們可以寫 transform="transalte(420, 315)"
<!--  STEP1: 先移動旋轉圓心的位置    -->
<!--   x' = x + width /2  -->
<!--   y' = y + height / 2   -->
<!--   translate(400 + 40/2, 300 + 30/2)   -->
<rect x="400" y="300" width="40" height="30" stroke="#41b883" stroke-width="2" fill="#41CEC0" transform="translate(420, 315)"></rect>

STEP 2: 選擇想要旋轉的角度

接著看你需要旋轉的角度,使用 rotate,在這裡我旋轉 45 度。
<!--   STEP2: 進行旋轉   -->
<!--   rotate(45)   -->
<rect x="400" y="300" width="40" height="30" stroke="#41b883" stroke-width="2" fill="#41CEC0" transform="translate(420, 315) rotate(45)"></rect>

STEP 3: 將方形移回原本的位置

最後,我們一樣透過 translate ,把原本位移的距離在扣回去,就可以讓旋轉後的物體回到原本的位置:
<!--   STEP3: 移回原本的位置   -->
<!--   translate(-420, -315)   -->
<rect x="400" y="300" width="40" height="30" stroke="#41b883" stroke-width="2" fill="#41CEC0" transform="translate(420, 315) rotate(45) translate(-420, -315)"></rect>
這樣就完成了原地旋轉。

其實如果你知道怎麼計算旋轉的中心點後,也可以一器合成的把它寫在 rorate 裡面,對多數人來說,這樣的作法也比較直觀好想像,我們只需要在 rotate(角度, 旋轉中心X, 旋轉中心Y) 裡面給定旋轉的中心點就可以了。
像是這樣子:
<!--   直接把旋轉中心點給在 rotate 之中   -->
<!--   rotate(45, 420, 315)   -->
<rect x="400" y="300" width="40" height="30" stroke="#41b883" stroke-width="2" fill="#41CEC0" transform=" rotate(45, 420, 315)"></rect>
一樣可以達到原地旋轉的效果,但是如果你以後看到那種先位移再旋轉,之後再位移的作法,你也可以知道它背後的意義和邏輯了。

2017年5月14日

[掘竅] 為什麼畫面沒有隨資料更新 - Vue 響應式原理(Reactivity)

之前在在一開始接觸框架的時候,不是很清楚響應式原理到底是什麼,一直會把它和 responsive 這個東西搞混,因為兩個在中文翻起來都是響應式,官網雖然有提及這個部分的說明,但小時候不懂事就給它忽略過去了。
最近在用 Vue 做一些東西的時候,才慢慢瞭解到 Reactivity 的意思和重要性,在使用 Vue 的過程中,我們會發現當我們修改 JavaScript 的資料時,畫面就會自動的更新產生變化,之所以能這麼方便操作,都是由於 Vue 背後的響應式系統(reactivity system),可見 Reactivity 在 Vue 中有多重要!!
也因此,錯誤的使用可能會導致 Vue 的 data 不會有即時更新的效果。
在 Vue 當中,每個組件實例都有相對應的 watcher 實例物件 ,它會紀錄組件渲染過程中所有被觸碰到的屬性,當這些屬性的 setter 被觸發時,就會通知 watcher,進而促使組件重新渲染。
然而,有些情況下你可能會發現即使已經為 JavaScript 中的資料重新設值時,畫面卻沒有重新渲染,這有可能是受限於 JavaScript 物件底層的一些限制,讓我們來看看什麼情況下會導致資料更新了,畫面卻沒有重新渲染:

物件部分:一開始沒有被註冊到的物件不會響應式更新

這是一開始在使用 Vue 或搭配 AJAX 取得資料時很容易疏忽的部分。假設我們現在要做一個計數器,分別記錄不同動物的數量,當我點擊按鈕的時候,該動物的數量就會增加,template 的部分像這樣:
<!-- template -->
<button @click="addCount('dog')">addDogs</button>
<button @click="addCount('cat')">addCats</button>
<button @click="addCount('penguin')">addPenguin</button>
<p>dog {{ counter.dog }}, cat {{ counter.cat }}, penguin {{ counter.penguin }}</p>
在一開始的時,我們在 datacounter 屬性中註冊了 counter.dogcounter.cat 這兩個屬性,但是並沒有把 penguin 給註冊進去,我們可能想說等在 created Hook 中再把資料送進去(通常因為是要透過 AJAX 取的資料內容),順便建立 penguin 屬性就好,因此這時候 JavaScript 的部分像這樣:
new Vue({
  el: '#unregister-object',
  data: {
    // 一開始沒有註冊 penguin,只註冊了 dog 和 cat
    counter: {
      dog: 0,
      cat: 0
    }
  },
  methods: {
    addCount(name) {
      this.counter[name] ++
    }
  },
  created() {
    // 在 created 的時候才建立 penguin 順便設值為 0
    this.counter.penguin = 0
  },
  updated() {
    // 讓我們可以知道組件有被更新
    console.log('view updated')
  }
})
可以點這裡操作範例 @ Codepen
這時候你會發現一個現象,從 console 中我們可以看到,當你按了 addDogsaddCats 時,這個組件會立即的更新。可是當你按 addPenguin 時,畫面卻毫無反應,也不會更新。你可能會以為資料沒有被設定進去,可是如果你又回去點addDogsaddCats 讓畫面更新時,你會發現其實 penguin 的資料有被設定進去,只是沒有產生響應式的變化重新渲染畫面:
所以碰到這樣的問題,我們可以怎麼做呢?

方法一:在 data 中要把所有需要響應式的資料設定進去

如果你希望你的資料能夠在變更時讓畫面重新渲染,達到響應式的效果(Reactive),那麼最簡單的方式是在一開始的 data 中就把資料設定進去,在這裡我們就只要把 penguin 加到 data 中就可以,像是這樣:
data: {
  counter: {
    dog: 0,
    cat: 0,
    penguin: 0       // 一開始就把要響應式變化的資料設定進來
  }
}

方法二:動態添加新的響應式屬性

除了上面的方式,我們也可以透過 vm.$set(object, key, value) 動態新增響應式的屬性,那麼我們只需要在原本的 created(){...} 中改成:
// 也可以使用 Vue.set(object, key, value)
created() {
  // 在 created 的時候才建立 penguin 順便設值為 0
  // 使用 $set 動態新增響應式組件
  this.$set(this.counter, 'penguin', 0)
}
如此當 penguin 被點擊時,一樣會更新組件,重新渲染頁面。

方法三:建立新的物件

另一種當我們要一次更改或新增較多的屬性時,可以透過 Object.assign() 的方法重新建立一個新的物件讓 Vue 去監控,因此,在 created 中可以寫成:
created () {
  // 透過建立新的物件來新增物件的屬性
  this.counter = Object.assign({}, this.counter, {
    penguin: 0,
    dog: 5
  })
}

陣列部分:利用陣列索引直接設值時

剛剛提到的主要是物件的部分,陣列同樣的也會在某些情況下沒有辦法產生響應式的變化,因此如果我們希望畫面會隨著資料能夠有響應式的變化時,應該特別留意和避免。
Template 如下,主要是列出一個動物清單,當我按下 Change Animal 時,它會根據我所輸入的內容以陣列索引的方式變更陣列中的資料內容。另外我們多寫了一個 forceUpdate 的按鈕,點下去之後可以強制更新 vue 組件。
<!-- template -->
<div id="change-by-array-index" v-cloak="v-cloak">
  <h3>利用索引值變更 index 0 的值</h3>
  <input type="text" v-model="animal"/>
  <button v-on:click="changeAnimal">Change Animal</button>
  <ul>
    <li v-for="item in animals" v-bind:key="item">{{ item }}</li>
  </ul>
  <button v-on:click="$forceUpdate()">forceUpdate</button>
</div>
JS 的部分有 changeAnimal 這個 function 會把使用者填寫的資料以陣列索引的方式代入 animals[0]
new Vue({
  el: '#change-by-array-index',
  data: {
    animal: '',
    animals: ['<YOURAnimal>', 'cat', 'penguin', 'bird', 'rabbit']
  },
  methods: {
    changeAnimal () {
      // 當利用陣列索引直接設置值時
      this.animals[0] = this.animal
    }
  }
})
可以點這裡操作範例 @ Codepen
這時候會碰到跟剛剛物件時一樣的問題,就是當我填完 input 內容,按下 changeAnimal 的按鍵時,畫面沒有任何反應,這是因為 以陣列索引值的方式修改資料內容時,Vue 無法監測到 ,因此雖然資料已經代進去了,但是 Vue 不會更新。
和剛剛類似,你可以透過按下 forceUpdate 這個按鈕來讓 Vue 強制更新,強制更新後你就會發現,畫面更新成你剛剛填入的資料內容,表示其實剛剛是有把資料設定到 Vue 當中,只是 Vue 沒有監測到,因此沒有觸發組件去做 update

方法一:使用 Vue 可觀察到的陣列方法

在 Vue 中包含一組可以觀察陣列的方法,而這些方法將能促使畫面重新渲染。這些方法包含:push()pop()shift()unshift()splice()sort()reverse()
因此,回到剛剛的例子上,我們可以使用 arr.splice(startIndex, deleteCount, addItem) 這個方法來把陣列中的內容抽換掉,因此我們可以把 methods 改成:
methods: {
  changeAnimal () {
    // 利用 arr.splice(startIndex, deleteCount, addItem)
    this.animals.splice(0, 1, this.animal)
  }
}

方法二:使用 vm.$set

和物件類似,我們也可以使用 vm.$set(array, index, value) 來達到響應式修改資料內容,我們可以把 methods 改成:
methods: {
  changeAnimal () {
  // 利用 vm.$set(array, index, value) 方法
    this.$set(this.animals, 0, this.animal)
  }
}
如此一樣可以達到響應式變換陣列的資料內容。

瞭解 Vue Reactivity 的原理

See the Pen [Demo] Vue Reactivity in Vanilla JavaScript by PJCHEN (@PJCHENder) on CodePen.

總結

當你對於 Vue 的響應式原理(Reactivity System)有了更多的瞭解後,相信你可以少踩到一些莫名其妙或不必要的坑。
操作範例:

參考資料

2017年5月13日

深入淺出瞭解 JavaScript 閉包(closure)


雖然之前在 Udemy 上課時有整理了幾篇關於閉包的筆記,但其實當時對於閉包的概念和使用上還是不很清楚,只是大概知道有這個概念。
前陣子在 Treehouse 上課時,聽到了另一個用來說明閉包的例子,我覺得講解的非常清楚,在這裡做個筆記記錄一下。
個人覺得先瞭解閉包的應用,接著在回去看之前上 Udemy 時整理的和閉包有關的文章時,在理解上更有幫助。

不使用閉包(closure)的情況

在 JavaScript 中,global variable 的錯用可能會使得我們的程式碼出現不可預期的錯誤。
假設我們現在要做一個計數的程式,一開始我們想要先寫一個給狗的計數函式:
// 狗的計數程式
var count = 0

function countDogs () {
  count += 1
  console.log(count + ' dog(s)')
}

countDogs()    // 1 dog(s)
countDogs()    // 2 dog(s)
countDogs()    // 3 dog(s)
接著繼續寫程式的其他部分,當寫到程式的後面時,我發現我也需要寫貓的計數程式,於是我又開始寫了貓的計數程式:
// 狗的計數函式
var count = 0

function countDogs () {
  count += 1
  console.log(count + ' dog(s)')
}


// 中間是其他程式碼...

// 貓的計數函式
var count = 0

function countCats () {
  count += 1
  console.log(count + ' cat(s)')
}

countCats()    // 1 cat(s)
countCats()    // 2 cat(s)
countCats()    // 3 cat(s)

乍看之下運作上好像沒有問題,當我執行 countDogs()countCats(),都會讓 count 增加,然而問題在於當我在不注意的情況下把 counter 這個變數建立在了全域的環境底下時,不論是執行 countDogs() 或是 countCats() 時,都是用到了全域的 count 變數,這使得當我執行下面的程式時,他沒有辦法分辨現在到底是在對狗計數還是對貓計數,進而導致把貓的數量和狗的數量交錯計算的情錯誤況:
var count = 0

function countDogs () {
  count += 1
  console.log(count + ' dog(s)')
}


// 中間是其他程式碼...

var count = 0

function countCats () {
  count += 1
  console.log(count + ' cat(s)')
}

countCats()    // 1 cat(s)
countCats()    // 2 cat(s)
countCats()    // 3 cat(s)

countDogs()    // 4 dog(s),我希望是 1 dog(s)
countDogs()    // 5 dog(s),我希望是 2 dog(s)

countCats()    // 6 cat(s),我希望是 4 cat(s)

透過閉包讓 function 能夠有 private 變數

從上面的例子我們知道,如果錯誤的使用全域變數,程式很容易會出現一些莫名其妙的 bug ,這時候我們就可以利用閉包(closure)的作法,讓函式有自己私有變數,簡單來說就是 countDogs 裡面能有一個計算 dogs 的 count 變數;而 countCats 裡面也能有一個計算 cats 的 count 變數,兩者是不會互相干擾的。
為了達到這樣的效果,我們就要建立閉包,讓變數保留在該函式中而不會被外在環境干擾。
改成閉包的寫法會像這樣:
function dogHouse () {
  var count = 0
  function countDogs () {
    count += 1
    console.log(count + ' dogs')
  }
  return countDogs
}

const countDogs = dogHouse()
countDogs()    // "1 dogs"
countDogs()    // "2 dogs"
countDogs()    // "3 dogs"
這樣我們就將專門計算狗的變數 count 關閉在 dogHouse 這個函式中,上面這是閉包的基本寫法,當你看到一個 function 內 return 了另一個 function,通常就是有用到閉包的概念
從程式碼中我們可以看到在 dogHouse 這個函式中裡面的 countDogs() 才是我們真正執行計數的函式:
而在 dogHouse 這個函式中存在 count 這個變數,由於 JavaScript 變數會被縮限在函式的執行環境中,因此這個 count 的值只有在 dogHouse 裡面才能被取用,在 dogHouse 函式外是取用不到這個值的。
最後因為我們要能夠執行在 dogHouse 中真正核心 countDogs() 這個函式,因此我們會在最後把這個函式給 return 出來,好讓我們可以在外面去呼叫到 dogHouse 裡面的這個 countDogs() 函式:
接著,當我們在使用閉包時,我們先把存在 dogHouse 裡面的 countDogs 拿出來用,並一樣命名為 countDogs(這裡變數名稱可以自己取),因此當我執行全域中的 countDogs 時,實際上會執行的是 dogHouse 裡面的 countDogs 函式:
上面的例子就是一個很基本的閉包的寫法,一個 function 裡面包了另一個 function,同時會 return 裡面的 function 讓我們可以在外面使用到它。
我們可以把我們最一開始的程式碼都改成使用閉包的寫法:
function dogHouse () {
  var count = 0
  function countDogs () {
    count += 1
    console.log(count + ' dogs')
  }
  return countDogs
}

function catHouse () {
  var count = 0
  function countCats () {
    count += 1
    console.log(count + ' cats')
  }
  return countCats
}

const countDogs = dogHouse()
const countCats = catHouse()

countDogs()    // "1 dogs"
countDogs()    // "2 dogs"
countDogs()    // "3 dogs"

countCats()    // "1 cats"
countCats()    // "2 cats"

countDogs()    // "4 dogs"
當我們正確的使用閉包時,雖然一樣都是使用 count 來計數,但是是在不同執行環境內的 count 因此也不會相互干擾。

進一步瞭解和使用閉包

另外,甚至在運用的是同一個 dogHouse 時,變數間也都是獨立的執行環境不會干擾,例如:
function dogHouse () {
  var count = 0
  function countDogs () {
    count += 1
    console.log(count + ' dogs')
  }
  return countDogs
}

// 雖然都是使用 dogHouse ,但是各是不同的執行環境
// 因此彼此的變數不會互相干擾

var countGolden = dogHouse()
var countPug = dogHouse()
var countPuppy = dogHouse()

countGolden()     // 1 dogs
countGolden()     // 2 dogs

countPug()        // 1 dogs
countPuppy()      // 1 dogs

countGolden()     // 3 dogs
countPug()        // 2 dogs

將參數代入閉包中

但是這麼做你可能覺得不夠清楚,因為都是叫做 dogs,這時候我們一樣可以把外面的變數透過函式的參數代入閉包中,像是下面這樣,回傳的結果就清楚多了:
// 透過函式的參數將值代入閉包中
function dogHouse (name) {
  var count = 0
  function countDogs () {
    count += 1
    console.log(count + ' ' + name)
  }
  return countDogs
}

// 同樣是使用 dogHouse 但是使用不同的參數
var countGolden = dogHouse('Golden')
var countPug = dogHouse('Pug')
var countPuppy = dogHouse('Puppy')

// 結果更清楚了
countGolden()     // 1 Golden
countGolden()     // 2 Golden

countPug()        // 1 Pug
countPuppy()      // 1 Puppy

countGolden()     // 3 Golden
countPug()        // 2 Pug

進一步簡化程式

直接 return function

接著,如果我們熟悉在閉包中會 return 一個 function 出來,我們就可以不必為裡面的函式命名,而是用匿名函式的方式直接把它回傳出來。
因此寫法可以簡化成這樣:
function dogHouse () {
  var count = 0
  // 把原本 countDogs 函式改成匿名函式直接放進來
  return function () {
    count += 1
    console.log(count + ' dogs')
  }
}

function catHouse () {
  var count = 0
  // 把原本 countCats 函式改成匿名函式直接放進來
  return function () {
    count += 1
    console.log(count + ' cats')
  }
}
然後我們剛剛有提到,可以透過函式參數的方式把值代入閉包當中,因此實際上我們只需要一個 counter ,在不同的時間點給它參數區分就好。這樣子不管你是要記錄哪一種動物都很方便,而且程式碼也相當簡潔:
function createCounter (name) {
  var count = 0
  return function () {
    count++
    console.log(count + ' ' + name)
  }
}

const dogCounter = createCounter('dog')
const catCounter = createCounter('cat')
const birdCounter = createCounter('bird')

dogCounter()     // 1 dog
dogCounter()     // 2 dog
catCounter()     // 1 cat
catCounter()     // 2 cat
birdCounter()    // 1 brid
dogCounter()     // 3 dog
catCounter()     // 3 cat

閉包的實際應用

在 HTML 中我們先建立三個按鍵
<button id="first">First</button>
<button id="second">Second</button>
<button id="third">Third</button>
接著我們希望能夠讓這三個按鈕被點擊的時候可以回傳按鈕的文字到 console 上,因此直覺上我們可能會這樣寫:
var buttons = document.getElementsByTagName('button')

for (var i = 0; i < buttons.length; i ++) {
  // buttonName 暴露於 global environment
  var buttonName =  buttons[i].innerHTML
   buttons[i].addEventListener('click', function () {
    console.log(buttonName)
  })
}
這時候可能會預期點選不同的按鈕時,會根據每個 button 內容的不同而得到不同的結果。但是實際執行後,你會發現回傳的結果都是 “Third”。
操作範例 @ JSBin
之所以會這樣是因為 JavaScript 是在按鍵被點擊的時候才會執行 addEventListener 裡面的 callback function ,這時候它發現要使用到 buttonName 這個變數,於是它向外層去尋找,這時候它會找到的已經是處於 global 的 buttonName,而值會是 ‘Third’。
往外找的行為並不是 closure 而是 global environment 和 scope chain 的概念。可參考:[筆記] JavaScript中Scope Chain和outer environment的概念
因為 buttonName 這個變數是暴露在 global environment,所以他不會被保存在 function 中。我們若想把這個變數保存在 function 內,我們就可以使用到上面所講述的 closure 的概念:
// 建立一個閉包把資料存在這個 function 當中
function saveButtonName (buttonName){
  // buttonName 被儲存在閉包當中
  var buttonName = buttonName
  return function(){
    console.log(buttonName)
  }
}

var buttons = document.getElementsByTagName('button')
for (var i = 0; i < buttons.length; i ++) {
  var buttonName =  buttons[i].innerHTML
   buttons[i].addEventListener('click', saveButtonName(buttonName))
}
這時候我們就可以把跑迴圈時的 buttonName 的值存在閉包當中,當按鈕被點擊時會執行 saveButtonName 這個 function,而它在這個 function 裡面就可以找到 buttonName 的值,因此他不需要在到外層的 global 去找 buttonName 的值。
於是我們就可以得到正確的值。(如果你好奇現在 global 中 buttonName 的值是多少,可以透過在 global 環境 console 出來看看)。

使用 let

我們剛剛有提到,一開始的程式碼之所以會跑出非預期的結果,是因為我們的 buttonName 這個變數是處在 global environment ,再跑迴圈的過程中,它的值會一直被重新覆蓋。
在 ES6 中提出了新的用來定義變數的關鍵字 let ,簡單來說,透過 let 它可以幫我們把所定義的變數縮限在 block scoped 中,也就是變數的作用域只有在 { } 內,因此要解決上面程式碼的問題,我們也可以透過 let 來避免 buttonName 這個變數跑到 global variable 被重複覆蓋。
寫法如下:
// 使用 ES6 寫法
for (var i = 0; i < buttons.length; i ++) {
  let buttonName =  buttons[i].innerHTML
   buttons[i].addEventListener('click', saveButtonName(buttonName))
}
如此一樣能得到我們想要的結果
若想對 let 有更多的瞭解,可以參考
[筆記] JavaScript ES6 中使用block-scoped 的 let 宣告變數
希望看完這篇文章後,你能對於閉包有更清楚的認識,接著你可以看這四篇關於 closure 的筆記,相信你會對閉包有更深入的瞭解。

參考資料

2017年5月9日

那些關於 Vue 的小細節 - Computed 中 getter 和 setter 觸發的時間點




文章撰寫時使用 vue@2.3.2
在 Vue 中 computed 是經常會使用到的屬性,因為在 Vue 中透過 computed 會 cache 住沒有改變的資料,因此正確且適當的使用 computed 將可以減少資料重新運算的次數,讓網頁的效能提升。
但是在使用的過程中,有時候會發現 computed 怎麼樣就是不被觸發,這當中有些細節是我們可以進一步瞭解的。

computed 的基本觀念

在 Vue 中,computed 的屬性可以被視為像是 data 一樣,可以讀取和設值,因此在 computed 中可以分成 getter(讀取) 和 setter(設值),在沒有寫 setter 的情況下,computed 預設只有 getter ,也就是只能讀取,不能改變設值。
雖然說 computed 內的屬性可以被視為像是 data 一樣,但在使用上,一般還是會讓 computed 類似唯讀的狀態,也就是去處理 `data` 資料,然後把它吐出來使用。
另外,在 getter 中,要記得搭配使用 return 來把值返回出來。
基本的寫法如下:
預設只有 getter 的 computed
    
new Vue({
    computed: {
        computedData: function () {
            return // ...
        }
    }
})
    
有 setter 和 getter 的 computed
    
new Vue({
    computed: {
        computedData: {
            get: function () {
                return // ...
            },
            set: function () {
                // ...
            }
        }
    }
})
    

程式範例連結

下面的部分包含許多程式說明,你可以開啟這份 Codepen ,然後開啟 console 視窗搭配閱讀。

一般情況下 getter 觸發的時間點

在一般的情況下,我們可以這樣使用 computed (只有 getter)來更新資料,你可以直接打開 Codepen 的第一部分,或者程式碼如下:
    
    
<!-- template -->
<div id="computed-basic" v-cloak="v-cloak"> 
    <h3 class="title-border">一般情況 computed getter 被觸發的時間點</h3>
    <form class="pure-form">
      <input type="text" v-model="firstName"/>
      <input type="text" v-model="lastName"/>
    </form>
    <p>{{fullName}}</p>
</div>

    
    
// js: computed-basic
new Vue({
    el: '#computed-basic',
    data: {
        firstName: 'PJ',
        lastName: 'Chen'
    },
    computed: {
        fullName () {
            console.log('computed getter')
            return this.firstName + ' ' + this.lastName
        }
    },
    updated () {
        console.log('updated')
    }
})
    
在這個情況下,我們只要輸入 input 的內容,改變了 this.firstNamethis.lastName 時,就會觸發 getter,也就是說,computed 的 getter 會觀察被寫在裡面的資料,一般來說,當被觀察的資料改變時,這個 getter 就會被觸發
聽起來非常合理,但是我們用 console.log() 看一下,分別看 computed getterupdated 的時間點,你會發現,當我們在 input 輸入資料,會觸發 computed,同時也會觸發這個 vm 的 updated
img

getter 的例外情況:資料變更但 getter 不會被觸發

剛剛我們提到當 getter 裡面被觀察的資料有變更時,就會觸發 computed,但這個說其實並不完全正確,有些時候畫面更新了,資料變更了,但其實不會觸發 computed 裡面的 getter。
如果我們把 template 中的 fullName 拿掉,換成 firstName 和 lastName 時(程式碼如下):
     
<p> firstName: {{ firstName }}, lastName: {{ lastName }} </p>
    
也就是當我們的 template 中沒有馬上用到這個 computed 的資料時(這裡的話就是指 fullName),那麼 Vue 不知道你要用到 fullName ,因此即使我們變更了 this.firstNamethis.lastName,依然不會觸發 getter 。
我們可以在 console 中看到,firstName 和 lastName 資料變更的情況下,只會一直得到 updated 而已,computed 中的 getter 並不會被觸發。
img

一般情況下 setter 觸發的時間點

接著,讓我們來看一下在一般的情況下, computedsetter 什麼時候會被觸發:

<!-- template -->
 <div id="computed-setter-basic" v-cloak="v-cloak"> 
    <h3 class="title-border">一般情況 computed setter 被觸發的時間點</h3>
    <form class="pure-form">
      <input type="text" v-model="fullName"/>
    </form>
    <p>firstName: {{ firstName }} <br/> lastName: {{ lastName }}</p>
 </div>



// js
// computed-setter-basic
new Vue({
    el: '#computed-setter-basic',
    data: {
        firstName: 'PJ',
        lastName: 'Chen'
    },
    computed: {
        fullName: {
            get () {
                console.log('computed getter')
                return this.firstName + ' ' + this.lastName
            },
            set (value) {
                console.log('computed setter')
                this.firstName = value.split(' ')[0]
                this.lastName = value.split(' ')[1]
            }
        }
    },
    updated () {
        console.log('updated')
    }
})

template 中,我們可以看到,我們的 input 是直接綁 v-model="fullName" ,因此他會直接去修改 fullName 的值,而當前 fullName 是 computed 中的一個屬性,我們說過 computed 中的屬性就和 data 類似可以取值(getter)和設值(setter),這時候因為我們要對 fullName 設值,自然就會對應到 fullName 裡面的 setter(如果沒有設定 setter 是無法對 fullName 設值的)。
簡單來說,當 computed 的屬性要被設值時,就會觸發 setter,從 console 中我們也可以看到,當我在 input 中輸入內容時,fullName 會改變,fullName 改變的情況會觸發 setter ,接著,因為我的 setter 中所做的事會變更到 getter 中所觀察的資料,這時候才又觸發 getter 執行,最後重新 updated 畫面。也就是從 setter -> getter -> updated 這樣的過程,如下圖所示:
img

觸發 setter 不必然會觸發 getter

在上面的例子中,我們會先觸發 setter , 接著觸發 getter,最後 updated 畫面。但是其實 getter 會被觸發是因為我們在 setter 中變更到了被 getter 所觀察的資料。也就是說,如果我們的 setter 在執行時,並不會觸發 getter 所觀察的資料的話,那麼 getter 就不會被觸發。
例如,當我把上面程式碼的 setter 中對於資料的變更註解掉時:

set (value) {
    console.log('computed setter')
    // this.firstName = value.split(' ')[0]
    // this.lastName = value.split(' ')[1]
}
那麼即時我們在 input 中輸入內容,都只會觸發 setter 而不會觸發 getter。換句話說,setter 和 getter 是獨立觸發的,兩個被觸發的時間點是不同的。
img

總結

在這篇文章中,我們進一步瞭解了 Vue computed 中的 gettersetter,有幾個重點可以整理一下
  1. getter 和 setter 彼次觸發的時間點是獨立的。 getter 在大部分的時候是當內部觀察的資料有改變時會被觸發;setter 則是當被觀察的物件本身有改變時會被觸發。
  2. getter 在畫面中沒有使用到被觀察的物件時,不會被觸發。
這篇的內容主要是根據自身的經驗和理解,如果有任何錯誤,都歡迎不吝告知,以避免錯誤的知識傳遞,謝謝!

5/10 更新,感謝網友@Aysh Su ( 聖涵 / 亞所 ) 修正觀念錯誤

2017年5月4日

[VSCode] 手殘常常打錯字 Typo?讓 Code SpellChecker 省下你找錯字的時間。


本文章同步刊登於 PJCHENder 前端網頁資源站

今天要來和大家分享一款 VSCode 的套件。
(PS. 我個人不只用 VSCode,只是最近剛好開發有用 VSCode,而且切換編輯器使用,比較有新鮮感,寫起來不易疲累XD)。


實在是不分享好像卡在喉嚨不太舒服壓!


其實在下載使用前,我覺得這不過就一款普通拼字檢查套件。


但在使用過後,才發現原來我花了很多時間在無意義的拼字錯誤上。


因為你慢慢的會發現,其實大多數的變數、屬性名稱等等的都是以有意義的英文字命名的,沒有意義變數名稱非常少。


因此,你可以省下很多時間在找不知道哪裡來的錯誤,說真的有時候 tranistion 這種拼錯字,很難發現。


下載之後,點一下下方的狀態列


接著再按下啟動,就可以了


有些比較特別的單字,你還是需要加入到專案字典中,特別是和專案名稱(通常是自創的單字)有關的字


加入字典的方式非常簡單




Code Spellchecker for VSCode

圖片中使用的 icon 版權聲明:
1. Heimlich Maneuver by Luis Prado from the Noun Project
2. bleeding by BomSymbol from the Noun Project
3. Smoke Inhalation by Luis Prado from the Noun Project
4. praying by Gan Khoon Lay from the Noun Project