0%

在使用 Nuxt.js 開啟專案後,會自動生成不少文件檔案,今天就來介紹幾樣基礎設定以及檔案的運用吧!

了解 Nuxt 路徑引用規則

  • ~以及@ :根目錄
    • (注意:nuxt.conf.js、非 nuxt 結構的 js 檔 除外)
  • 波浪或小老鼠 :從根目錄 搜尋assets 資料夾

    以下也一樣
    import test from "@/assets/js/test.js"
  1. ./:自己的目錄
  • nuxt.conf.js、以及非 nuxt 結構要這樣撰寫
  1. 放在 static 資料夾裡的引用方式(直接斜線)<img src="/demo.png" alt="">
  2. 補充:
    2.4 nuxt 圖片處理 - webpack 相關設定
  • 在asset 加入 img 資料夾(將圖片放在這裡)
    <img src="@/assets/img/demo.png" alt="">

  • 放在css作背景圖
    *background: url(~assets/img/demo.png); //nuxt css 引入圖片不能加斜線

    • 也不能將波浪改為@
      :::info
      原本波浪斜線方式:
      background: url(~/assets/img/demo.png);
      :::

nuxt.config.js 設定

  • vue cli3 設定vue 專案是透過是 main.js/ vue.config.js 這兩隻檔案。
    • main.js 為程式的進入點
  • nuxt 則統一用 nuxt.conf.js 做為設定。
    • 要編輯這份文件達到專案需求
    • 如 wedpack 是放在 build

  • 此外在此檔案可以做標題、引入 CDN 等基礎設置
    • head,meta 可以做表頭的設定
    • script 可以引入 JS 的檔案或 CDN 資源
  • css 全域設定,也同樣放在這裡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'myNuxt',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: '網站前後端、網頁設計、程式語言網站' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
],
script:
[
{ src: "/jquery.min.js"},
{ src: "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/js/all.js" }
]

},

引用 static 靜態資源的檔案

  • static資料夾:靜態資源資料夾
    • 其中要連接該檔案路徑要用斜線
    • 這裡面的檔案不會被打包和優化

套件安裝!

  1. 最簡單方式是使用打包後的 CDN 或 js 檔案
    • 在head 的 script 放入 CDN
  2. 使用 nuxt plugins 自包 js 套件 或 vue 套件 → 較麻煩
  3. 使用 nuxt modules 來引入套件
    • 套件較少
  • nuxt module 主要是將 plugin 在封裝一層出來。(就是 nuxt 專有的套件),如果對安裝 plugins 沒把握,就找有 nuxt modules 的套件來安裝。

練習1包裝一般的 vue 套件

vuejs-datepicker

1、npm install vuejs-datepicker

  • 若直接依照官網來安裝,並在檔案引入會發生錯誤
  • 所以 install 完之後要再執行下面動作
    2、plugins 新增一個檔案:datepicker.js → 撰寫套件
  • 在 plugins 資料夾,新增檔案
    1
    2
    3
    4
    5
    import Vue from 'vue'
    import Datepicker from 'vuejs-datepicker';

    Vue.component('Datepicker', Datepicker)

3、nuxt.config.js 安裝 plugin → 可以設定該套件只在 客戶端 運作

  • 可以將插件安裝在客戶端或是 server 端
    1
    { src: '~/plugins/datepicker.js', mode: 'client' },
  • 要使用時,再放入頁面

    練習2 安裝一般的 js 套件

    GSAP
  1. npm install gsap
  2. plugins 新增一個檔案:gsap.js → 撰寫套件
    1
    2
    3
    4
    5
    import Vue from 'vue'
    import gsap from "gsap";

    Vue.prototype.$gsap = gsap

  3. nuxt.config.js 安裝 plugin
    1
    2
    3
    4
    plugins: [
    { src: '~/plugins/gsap.js', mode: 'client' },
    ],

  • create() 會在前端執行\後端執行

練習3 nuxt 安裝

axios modules

  1. npm install @nuxtjs/axios
  2. plugins 新增一個檔案:axios.js
  • 如同前面的練習,到 nuxt.config.js
    1
    2
    3
    4
    modules: [
    '@nuxtjs/axios',
    ],

製作網站,都會希望自己的網站可以被搜尋引擎找得到,並且可以在使用者輸入關鍵字後,可以在頭幾個項目中就能關注到自己的網站!
而在使用 Vue.js 框架,因為是使用 SPA 架構無法做到 SEO (search engine optimization) 的效果,別人無法輕易的找到我們所製作的網站。
今天要介紹的 Nuxt.js 是一個基於 Vue.js、用來處理伺服器渲染(Server-side rendering(SSR))的框架。透過 SSR 我們可以做到更好的 SEO 效果。

本文首要任務: 認識 SPA 與 SSR 的差異

SPA (Single Page Application) 架構

  • 讓一些後端的工作,分擔到前端 (如: router 設定)
  • 流程為:使用者開了瀏覽器 -> 向後端server要資料,因為改為SPA 架構,所以提供的會是SPA 首頁(會載入JS\編譯好的CSS\一頁空殼的HTML網頁),此空殼經過 Vue 編譯產出 DOM 物件,讓瀏覽器印出。
  • 讓回應速度更快,使用者在轉換頁面時得到更好的體驗。
    • SPA(Single Page Application)意思是僅有一個頁面的應用程式,也就是說網頁不需跳轉頁面就可以達到基本的建立、讀取、修改、刪除資料功能。
  • 後端負責調整API 邏輯\架構

    SPA 缺點

  • SEO差(SPA 一開始吐的是空殼),GOOGLE 爬不到頁面資訊
  • SMO差,FB爬不到頁面資訊
  • 首屏速度慢,一開始畫面會空白或閃爍
    • 發出請求資訊到實際上呈現完整頁面時間

SSR (Server Side Rendering)

流程為:使用者開了瀏覽器 -> 向後端server要資料,因為改為SSR,所以會提供一整個頁面(不會是畫面空白閃爍)

  • 希望頁面可以將網頁整個東西呈現,並讓搜尋引擎查得到
  • 處理 SEO(針對搜尋引擎)
  • title
  • meta tag
    • 全域 <head> 設定
    • 單頁 <head> 設定
  • description
  • 處理 SMO (針對 Social media)
    • twitter\youtube\Line…

      有更多的彈性設定

      Nuxt 有三種模式,可以幫你解決問題

  1. Universal: SSR + CSR
  • 全域設定 mode: ‘Universal’
  1. SPA: 不跑SSR
  • 全域設定 mode: ‘spa’ 或執行 nuxt-spa
  1. Static Generated (Pre Rendering): 產生靜態頁面
  • nuxt run generate
    • 預先將頁面轉成靜態頁面
    • 如要拉10樣商品,就先拉10個頁面

希望本篇的扼要介紹能夠讓讀者快速知道其中的差異。

參考資料:
SSR — Nuxt.js 超入門
Vue Nuxt 介紹與實作範例

之前的實作練習都沒有機會使用到 Vuex,總是覺得它的資料存取、取得或是管理,對於剛接觸Vue 的我來說還是相對複雜,而且剛開始接觸 Vue 總要先練習下 props \ emit 資料傳遞才行啊~
既然已經會使用基礎的資料傳遞後,那就要近一步實作 Vuex 狀態管理

實作功能說明

  • 資料的取得來自 JSONPlaceholder
    • 從Vuex來實作資料的取得、刪除、更新
  • 針對目前取得的資料進行筆數的篩選
  • 區分未完成事項、以及完成事項
  • 可透過點擊方塊改變色塊
  • 接下來~會製作 3個 component ,將元件放到 App.vue

成品

開始前別忘了先建立 module

Vuex 檔案的處理

  • 這裡開專案直接有建立 vuex
  • 進入 store > index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createStore } from 'vuex';
import todos from './modules/todos';


export default createStore({
state: {
},
mutations: {
},
actions: {
},
modules: {
todos,
}
})

建立 modules 資料夾

  • store > modules > todo.js
    • 從這裡的資料取得並傳遞到 Todos.vue
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import axios from 'axios';

      const state = {};
      const getters = {};
      const actions = {};
      const mutations = {};

      export default {
      state,
      getters,
      actions,
      mutations
      }

      接下來要嘗試來串取資料

  • 取得假資料
    jsonplaceholder-todo

    在 todo.js 來製作資料的串接

  • 首先state這裡要先建立空陣列,getters 則是建立資料取得的函式
  • 在action 來發出請求,取得回應,並傳給 mutations
    • mutations 是改變資料的地方
  • 引入axios,使用 Asyc await 使用 get 來串接資料
    • 將串到的資料 commit 到 mutations,改變原本todos資料內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import axios from 'axios';

const state = {
todos: []
};
const getters = {
getTodos: (state) => state.todos,
};
const actions = {
async fetchTodos({ commit }) {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos");
// console.log(res.data);
commit('setTodos', res.data);
}
};
const mutations = {
setTodos: (state, todos) => {
state.todos = todos;
}
};

export default {
state,
getters,
actions,
mutations
}

建立 Todos.vue

  • 此元件主要是呈現所有的 Todo list

    取得 vuex 中的資料

  • 引入 mapGetters, mapActions

  • 將要從 getters 拿到的陣列,放到computed做資料的取得

    • 將該陣列getTodos 放到 v-for
  • 串接的 action 使用 mapActions,於 methods中執行,而此函式要在created階段運行

  • 如此一來就可以,從 Vuex 中得到資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<h3>Todos</h3>
<div class="todos">
<div class="todo" v-for="todo in getTodos" :key="todo.id">
{{ todo.title }}
</div>
</div>
</template>

<script>
import { mapGetters, mapActions } from "vuex";
export default {
name: "Todos",
methods:{
...mapActions(["fetchTodos"])
},
computed: mapGetters(["getTodos"]),
created() {
this.fetchTodos();
},
};
</script>

製作加入新 todo 功能

  • 建立一個新的 component => AddTodo.vue
  • 在 AddTodo 這個元件,製作 輸入框
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <template>
    <div>
    <h3>Add Todo</h3>
    <div class="add">
    <form>
    <input type="text" placeholder="Add Todo..." v-model="title" />
    <input type="submit" value="Submit" />
    </form>
    </div>
    </div>
    </template>
    <script>
    export default {
    name: "AddTodo",
    };
    </script>

    到 module > todo.js

  • 使用 async 與 axios 傳入資料
    • 第二個參數為 title
    • post 的網址一樣,後面接的參數為title, complete: false (any new todo that it’s not going to completed)
1
2
3
4
5
6
7
8
9
10
11
12
const actions = {
async fetchTodos({ commit }) {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos");
// console.log(res.data);
commit('setTodos', res.data);
},
async addTodo({ commit }, title) {
const res = await axios.post("https://jsonplaceholder.typicode.com/todos", { title, completed: false });
commit('addNewTodo', res.data);
}

};
  • 而在action 中使用axios 來新增資料,要將這必新資料 push 到 todo的陣列中
    • 存入的該資料要放在,陣列的最前面所以使用 unshift
      1
      2
      3
      4
      5
      6
      7
      8
      const mutations = {
      setTodos: (state, todos) => {
      state.todos = todos;
      },
      addNewTodo: (state, todo) => {
      state.todos.unshift(todo);
      }
      };

到 AddTodo.vue 來呼叫此 action

  • import mapActions
1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<h3>Add Todo</h3>
<div class="add">
<form @submit="onSubmit">
<input type="text" placeholder="Add Todo..." v-model="title" />
<input type="submit" value="Submit" />
</form>
</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<script>
import { mapActions } from "vuex";
export default {
name: "AddTodo",
data() {
return {
title: "",
};
},
method: {
...mapActions(["addTodo"]),
onSubmit(e) {
e.preventDefault();
//點擊後呼叫action中的加入todo函式,並傳入title資料
this.addTodo(this.title);
},
},
};
</script>


這裡要特別提出說明

因為是使用線上的虛擬資料,他不允許我們可以存入新資料到他的API裡面
所以可以看到下方的錯誤顯示(重複 id)
而此在重新整理之後,存入的資料會消失


製作刪除功能

  • 以id作為參數,來依此刪除
  • 不用儲存response資料
    • 在 async deleteTodo ,製作 by id 刪除
    • 到 mutations => 使用 filter 存下資料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import axios from 'axios';

const state = {
todos: []
};
const getters = {
getTodos: (state) => state.todos,
};
const actions = {
async fetchTodos({ commit }) {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos");
// console.log(res.data);
commit('setTodos', res.data);
},
async addTodo({ commit }, title) {
const res = await axios.post("https://jsonplaceholder.typicode.com/todos", { title, completed: false });
commit('addNewTodo', res.data);
},
async deleteTodo({ commit }, id) {
await axios.delete(`https://jsonplaceholder.typicode.com/todos/${id}`);
commit('removeTodo', id)
}

};
const mutations = {
setTodos: (state, todos) => {
state.todos = todos;
},
addNewTodo: (state, todo) => {
state.todos.unshift(todo);
},
removeTodo: (state, id) => {
state.todos = state.todos.filter((todo) => todo.id !== id);
//使用filter 將,不是id的篩選出來
}
};

export default {
state,
getters,
actions,
mutations
}

  • 回到 Todos.vue
  • 在 template 放入垃圾桶的 icon
    • 執行刪除的功能,在 icon 綁定刪除事件deleteTodo()
    • deleteTodo()就是來自於 methods 取得的事件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <template>
      <h3>Todos</h3>
      <div class="todos">
      <div class="todo" v-for="todo in getTodos" :key="todo.id">
      {{ todo.title }}
      <i class="fas fa-trash-alt" @click="deleteTodo(todo.id)"></i>
      </div>
      </div>
      </template>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <script>
      import { mapGetters, mapActions } from "vuex";
      export default {
      name: "Todos",
      methods: {
      ...mapActions(["fetchTodos", "deleteTodo"]),
      },
      computed: mapGetters(["getTodos"]),
      created() {
      this.fetchTodos();
      },
      };
      </script>


製作篩選器功能

  • 當 FilterTodo.vue 這裡的篩選器執行的時候,會執行 action中的函式,進行篩選
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <style scoped>
    select {
    margin-top: 20px;
    padding: 6px;
    border: #ef9a9a 1px solid;
    }
    </style>
    <template>
    <div>
    Filter todos:
    <select>
    <option value="200">200</option>
    <option value="100">100</option>
    <option value="50">50</option>
    <option value="30">30</option>
    <option value="20">20</option>
    <option value="10">10</option>
    </select>
    </div>
    </template>
    1
    2
    3
    4
    5
    <script>
    export default {
    name: "FilterTodos",
    };
    </script>

將該元件綁到 App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div id="app">
<div class="container">
<AddTodo />
<FilterTodos />
<Todos />
</div>
</div>
<!-- <router-view /> -->
</template>

<script>

import Todos from "@/components/Todos.vue";
import AddTodo from "@/components/AddTodo.vue";
import FilterTodos from "@/components/FilterTodos.vue";

export default {
name: "app",
components: {
Todos,
AddTodo,
FilterTodos,
},

};
</script>

到 module > todo.js 來製作 actions

  • 要傳入 option 的 value
    • 第二個參數為事件
    • 在 axios 的 get 放入 url,接上取得的參數
    • commit 直接傳到 setTodos ,將串接到的資料透過 mutations 存到 todos
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      import axios from 'axios';

      const state = {
      todos: []
      };
      const getters = {
      getTodos: (state) => state.todos,
      };
      const actions = {

      async filterTodos({ commit }, e) {
      //取得下拉選單的值
      //console.log(e.target.value);
      let selected = parseInt(e.target.value);
      const res = await axios.get(`https://jsonplaceholder.typicode.com/todos?_limit=${selected}`);
      commit('setTodos', res.data);
      }

      };
      const mutations = {
      setTodos: (state, todos) => {
      state.todos = todos;
      },
      addNewTodo: (state, todo) => {
      state.todos.unshift(todo);
      },
      removeTodo: (state, id) => {
      state.todos = state.todos.filter((todo) => todo.id !== id);
      //使用filter 將,不是id的篩選出來
      }
      };

      export default {
      state,
      getters,
      actions,
      mutations
      }

  • 將action 綁到 FilterTodos.vue
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    <template>
    <div>
    Filter todos:
    <select @change="filterTodos($event)">
    <option value="200">200</option>
    <option value="100">100</option>
    <option value="50">50</option>
    <option value="30">30</option>
    <option value="20">20</option>
    <option value="10">10</option>
    </select>
    </div>
    </template>

    <script>
    import { mapActions } from "vuex";
    export default {
    name: "FilterTodos",
    methods: {
    ...mapActions(["filterTodos"]),
    },
    };
    </script>


todos 完成與否,狀態的改變

  • 點擊兩下來更改狀態
    • 當要更新資料,要使用put,傳入updTodo物件資料
      • 將該資料傳到mutation
    • 接著到 mutation 製作更新的動作
    • 要取得目前的index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import axios from 'axios';

const state = {
todos: []
};
const getters = {
getTodos: (state) => state.todos,
};
const actions = {

async updataTodo({ commit }, updTodo) {
const res = await axios.put(`https://jsonplaceholder.typicode.com/todos/${updTodo.id}`,updTodo);
commit('renewTodo', res.data);
},

};
const mutations = {

renewTodo: (state, updTodo) => {
console.log(updTodo);
//我們是要在同一筆資料上更新
const index = state.todos.findIndex(todo => todo.id === updTodo.id);
//以下簡單確認 indx 是否有存在
if (index !== -1) {
state.todos.splice(index, 1, updTodo);
}
}
};

export default {
state,
getters,
actions,
mutations
}

回到 Todos.vue

  • template 插入legend
  • 引入 updataTodo到 methods
  • onDblclick()建立一個變數,它包含的物件內容為id、title、completed,其中我只要要改變的值是 complete true\false
    • 這裡的 id,title 都一樣。complete 則設定狀態的改變
    • 最後在呼叫一次 updataTodo() 傳入新的物件
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <template>
      <h3>Todos</h3>
      <div class="legend">
      <span>雙重點擊來標示已完成的事項</span>
      <span> <span class="incomplete-box"></span> = 未完成 </span>
      <span> <span class="complete-box"></span> = 已完成 </span>
      </div>
      <div class="todos">
      <div
      @dblclick="onDblclick(todo)"
      class="todo"
      v-for="todo in getTodos"
      :key="todo.id"
      :class="{ 'is-complete': todo.completed }"
      >
      {{ todo.title }}
      <i class="fas fa-trash-alt" @click="deleteTodo(todo.id)"></i>
      </div>
      </div>
      </template>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25

      <script>
      import { mapGetters, mapActions } from "vuex";
      export default {
      name: "Todos",
      methods: {
      ...mapActions(["fetchTodos", "deleteTodo", "updataTodo"]),
      onDblclick(todo) {
      const updTodo = {
      id: todo.id,
      title: todo.title,
      completed: !todo.completed,
      };
      this.updataTodo(updTodo);
      },
      },
      computed: mapGetters(["getTodos"]),
      created() {
      this.fetchTodos();
      },
      };
      </script>



參考資料:
[Vue.js] Vuex 學習筆記 (5) - getters 的核心概念
Vuex
Using this.$store.dispatch vs using mapActions

試想今天有一個網頁,上方的導覽列有不同的分類,帶領你到該項目的分頁中
那我們該如何實現,將各個項目的內容一一呈現

簡單分別製作 component

首先:component的命名要用大寫

  • 在 component 中
  • 在 App.vue
    讓vue知道有個component可以被使用,透過ES6的解構放入

    將引入的component放到template

將其他組件引入

補充說明:

修改路徑:
用~@來指定圖片路徑:@在vuecli是代表特殊意義,就會直接從src這個資料夾底下開始查找;而波浪符號,代表現在在css之中使用這個功能

  • 也就是在專案中,即便路徑複雜,只要在路徑最前面加上@就會直接進去src

之所以會撰寫這篇文,是因為面試過程被考倒的觀念,才發現自己過去從沒注意到這部分啊,那就來寫篇文章來認識它們之中的傳遞方式。

首先,先從型別的認識開始

在Javascript分兩大類,一種是基本型別(primitive type),另一種是物件(Object)。

  • Primitive type (以純值的形式存在)
    Boolean
    Null
    Undefined
    Number
    BigInt
    String
    Symbol(於 ECMAScript 6 新定義)
  • Object
    物件型別指的是可能由零或多種不同型別 (包括純值與物件) 所組合成,例如object,array, function, map

知道型別後,可以簡易的分類:

  • primitive type會是 pass by value,
  • object 是 pass by reference。

接下來來觀察,它們之間不同

pass by value

範例1:

1
2
3
4
5
6
7
let x=10;
let y=x;

x=20;
console.log(x); //20
console.log(y); //10

  • 注意:x和y是兩個獨立變數 (先記著這點)
    • 值會存入該變數
      var y = x; 看起來會像是y的內容要複製x,但可以的話要理解為,變數 y 是去建立了一個新的值,然後將變數 x 的內容複製了一份過來。
  • 因為兩的變數,各自獨立,所以當變數 x 的內容後來經過更新變成 20 之後,變數 y 的內容依舊保持原來的 10 而不受影響。

範例2

1
2
3
4
5
6
7
8
9
10
11
var num=3;
console.log("num start:",num);

function passByValue(func_num){
func_num=5;
}

passByValue(num);

console.log("num end:", num);

結果:

1
2
3
num start:3
num end: 3

  • 先是宣告新變數
  • 隨後建立passByValue() 函式
  • 呼叫passByValue(num) 複製變數num的值,3傳入passByValue(func_num)
    • 一開始 值會是3
    • 遇到func_num=5; => 將值改為 5
  • 但因為出去了函式範圍(scope),最終的值 num end:3

pass by refrence

範例

1
2
3
4
5
6
7
8
let x={value:10};
let y=x;

x.value=20;
console.log(x); //{value:20}
console.log(y); //{value:20}
console.log( x === y ); //true

拆解說明一下

  • 當宣告一個物件
  • JavaScript 會在記憶體的某處建立起一個物件 (圖右側),然後再將這個 x變數指向新生成的物件

  • 接著,當我們宣告了第二個變數 y ,並且透過 = 將y 指向 x 的位置。
  • 接著當我們更新了 x.value 的內容後, y.value 的內容也被更新了。

範例2

1
2
3
4
5
6
7
8
9
10
11
12
var obj1={item:"unchanged"};

console.log("obj1 start:",obj1);

function passByReference(ref){
ref.item= "changed";
}

passByReference(obj1);

console.log("obj end", obj1);

結果

1
2
3
obj1 start:{item: "unchanged"}
obj1 end:{item:"changed"}

  • 當執行passByReference(obj1) 。想像他是個地址(0x0016),進入函式中將地址複製,傳入。
    • 此時他的value 是個地址(0x0016)
  • 進入函式,遇到ref.item
    • de-reference ,進入原本的記憶體位置,找到item,並改變他的值

在不一般情況下,基本型別是 pass by value,而物件型別是 pass by reference的方式,但總有例外的時候。

pass by sharing

1
2
3
4
5
6
7
8
9
10
11
12
var obj1={item:"unchanged"};

console.log("obj1 start:",obj1);

function passBySharing(ref){
ref={ item: "changed"};
}

passBySharing(obj1);

console.log("obj end", obj1);

1
2
3
obj1 start:{item: "unchanged"}
obj1 end:{item:"unchanged"}

  • 傳入之前start 沒有改變
  • 呼叫函式,並進入passBySharing(),還是複製地址,傳入
  • 遇到ref={ item: "changed"};,會直接覆蓋地址(有點類似pass by value)
    • 這並不是直接更改物件特性

最後,來說說 JavaScript 屬於?

看了多篇文章,實在也是有點混亂,該認為三種形式都有呢?還是就是Pass by sharing、Pass by reference呢? 那例外情形又該如何解釋?

所以這邊的結尾,直接引用Kuro、Huli老師文章的資訊,供給大家參考。

從Kuro Hsu 的文章
提及認為 JavaScript 應該更屬於 Pass by sharing 的形式。

  • JavaScript 不屬於單純的傳值或傳址。

參考 ECMA-262-3 in detail. Chapter 8. Evaluation strategy

Regardless of usage concept of reference in this case, this strategy should not be confused with the “call by reference” discussed above. The value of the argument is not a direct alias, but the copy of the address.
由於在 JavaScript 的物件類型是可變的 (mutable),當物件更新時,會影響到所有引用這個物件的變數與其副本,修改時會變動到原本的參考,但當賦與新值時,會產生新的實體參考。

另外在 Huli 的文章中:

依據細分程度的不同,下面幾句話都是正確的:
JavaScript 裡面只有 pass by value
JavaScript 的 primitive type 是 pass by value,object 是 pass by sharing


最終來個綜合練習:

相信在最後的這個練習,可以更清楚,pass by value,pass by reference,pass by sharing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function changeStuff(num,obj,obj2){
num=num*10;
obj.item="changed";
obj={item:"changed"};
}

var num=10;
var obj={item: "unchanged"};
var obj2= {item: "unchanged"};

changeStuff(num, obj, obj2);
console.log(num);
console.log(obj.item);
console.log(obj.item);
1
console=> 10 "changed" "unchanged"

參考資料:
JS基本觀念:call by value 還是reference 又或是 sharing?
重新認識 JavaScript: Day 05 JavaScript 是「傳值」或「傳址」?
Tech Talk: Pass By Sharing with Javascript
深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?

延續 Wilson Ren課程來認識常見的排列方法!

排列的演算法介紹

  • 在JS中,就有內建 array.sort()
    • 同樣在其他程式語言,都會有內建的 sorting function
    • 但還是需要知道他們是如何運作
  • 總共有 6 種 sorting
  • 此篇文章會以前面兩種為主

Bubble Sort

  • 冒泡排序
  • 會比較相鄰的元素,如果順序不對會互換element
  • 是相當簡單的演算法,在現實世界很少拿來使用,最常用在教學
  • 而像在python、java 他們內建的排序演算法,都不是用冒泡排序,多是用 quicksort,merge sort(比較複雜、但有效率)

    範例說明

  • 比較array的最後兩個數字 => 發現順序不對就對調
  • 對調之後,再往下兩個元素
    • 發現順序正確,不需更動
  • 不更動之後,再往下兩個

    以此類推…
  • 小結:將找到的最小值,推到最左邊

要如何做 Bubble Sort

  • 先從一個陣列中2個數值比較開始思考

    1
    2
    3
    for j from A.length-1 to 1;
    if A[j] < A[j-i] ;
    swap A[j] and A[j-i]
  • 虛擬碼

  • i => sorted elements

  • j => adjacent elements(j是相對i而來)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function bubbleSort(arr){
    for(let i=0;i<arr.length-2;i++){
    for(let j=arr.length-1;j>=i+1;j--){
    if (arr[j]<arr[j-1]){
    //swap arr[j] and arr[j-1]
    let temp=arr[j];
    arr[j] = arr[j-1];
    arr[j-1] = temp;
    }
    }
    }
    console.log(arr);
    }

    bubbleSort([4,1,5,2,7]);
  • 加入隨機的數字,組成新的陣列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function bubbleSort(arr) {
    let step = 0;
    for (let i = 0; i <= arr.length - 2; i++) {
    for (let j = arr.length - 1; j >= i + 1; j--) {
    if (arr[j] < arr[j - 1]) {
    // swap arr[j] and arr[j - 1]
    let temp = arr[j];
    arr[j] = arr[j - 1];
    arr[j - 1] = temp;
    step++;
    }
    }
    }
    console.log("It takes " + step + " steps to complete.");
    console.log(arr);
    }

    let test = [];

    for (let i = 0; i < 100; i++) {
    test.push(Math.floor(Math.random() * 100));
    }

    bubbleSort(test);

    Big O of Bubble Sort

  • 最糟情況下: 由大到小,要排成 由小到大 => 他交換的次數會是 (n-1)+(n-2)+(n-3)+…+(n-n)次

  • 最好的情況: 本身的arr就接近 小到大

    • 優化語法
    • 發現沒有任何elements被交換,就可以停止迴圈
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      function bubbleSort(arr){
      for(let i=0;i<arr.length-2;i++){
      let swapping=false;
      for(let j=arr.length-1;j>=i+1;j--){
      if (arr[j]<arr[j-1]){
      //swap arr[j] and arr[j-1]
      let temp=arr[j];
      arr[j] = arr[j-1];
      arr[j-1] = temp;
      swapping=true;
      }
      }
      }
      if (swapping==false){
      break;
      }
      }
  • 平均情況還是用到 nested for loop

    • O(n^2)

Insertion Sort

  • 效率比 bubble sort 好一些
  • 理論上, 都是 O(n^2)
  • 不斷做插入的動作

    範例說明

  • 先認為這條arr長度是1
  • 從陣列最前面開始,要將1插入4這個arr上

  • 數字2,要和左邊的數字作比較

  • 數字3去比較

虛擬碼

1
2
3
index 0,1,2,3,4
value 1,2,3,4,0

  • 檢查這條arr的 index=1
  • 設定key=A[j],並將key插入 sorted sequence
    • j 的前一格為 i
  • 確認 i 有沒有大於key => 也就是對於key而言,要不斷地和它左邊的值比較
    • 如果左邊的值比key大,就要讓他們互換
1
2
3
4
5
6
7
index 0,1,2,3,4
value 1,2,3,4 (i),0 (j key)

while i>0 && A[i] > key
A[i+1] = A[i]
i -= 1

  • 語法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let unsorted = [14, -4, 17, 6, 22, 1, -5];

insertionSort(unsorted);

function insertionSort(arr) {
for (let j = 1; j <= arr.length - 1; j++) {
let key = arr[j];
i = j - 1;
while (i >= 0 && arr[i] > key) {
arr[i + 1] = arr[i];
i -= 1;
}
arr[i + 1] = key;
}

console.log(arr);
return arr;
}

Big O

常常看到 YouTube 演算法造成頻道經營的難度、或是facebook演算法而使得行銷曝光度的改變,但始終對演算法這個名詞沒有認識。
藉由 Wilson Ren課程

什麼是演算法?

用以解決問題而可以逐步執行的步驟或程序。

來看看現實生活中的演算法

  • Google Map 如何找到最短路徑
  • YouTube 推薦給你,認為你有興趣的影片
  • FB\IG 的加好友、追蹤推薦

演算法比較

有兩個演算法都可以完成目標任務,那我們會如何取決誰比較好?

  • 哪個演算法執行速度快?
  • 所佔用電腦的記憶體資源少?

    時間?

    首先,在計時演算法所耗時的部分:
  • 幫演算法做計時,是不實際的事情
    • 同一台電腦在執行同一任務,所執行的時間會不同
    • 不同電腦、CPU處理速度不一樣

      應該考慮,複雜度 Complexity

  • 複雜度分為兩種:時間複雜度、空間複雜度 (在本文多是討論時間複雜度)
  • 要如何計算時間複雜度?
    • 加、減、乘、除、comparison ,這些每一個都可以被算作一個 operation
    • Complexity: 在所寫的演算法中,總共用到多少 operations(運算子)
    • 使用 function 來顯示 Complexity 和 input size 的關係。

Big O Notation

  1. 是一個工具,用來描述當你的值不斷擴大時,f(n)值會去哪裡
  2. 為最壞情況的打算。他會展示一個演算法複雜度的趨勢

計算 Big O 的值

  1. Constant doesn’t matter : 常數它並不重要
    • f(n)=3n :3為常數、n為變數
  2. Small Terms don’t matter
    • fn= 3n^2 + 6n + 4 => 只需保留到fn= 3n^2
  3. Logarithm Base doesn’t matter

    範例:

  4. f(n)=3n
    答案:O(n)
  5. f(n)=13n^3 + 6n +7
    答案:O(n^3)
  6. f(n)=4log₂n
    答案:O(logn)
  7. f(n)=5
    答案:O(1)

演算法常見 Big O 的值

由好至差

  1. O(1)
  2. O(logn)
  3. O(n)
  4. O(nlogn)
  5. O(n^2)
  6. O(n^3)
  • 很多sorting值會是 O(nlogn)
  • 盡量讓演算法可以達到3、4的值

圖示:

emit 實作練習

  • 透過點擊add按鈕,觸發外層元件數值的改變
    • 先定義外層接收資料方式
    • 定義內層的 $emit 觸發方法
    • 使用 v-on 的方式觸發外層方法(口訣:前內、後外)

外層元件

  • 定義接收方法:當內層傳給你的時候,要做什麼事
    • addNumber()使num 增加1
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const app = Vue.createApp({
      data() {
      return {
      num: 0,
      text: ''
      };
      },
      methods: {
      addNumber() {
      console.log('addnumber');
      this.num++;
      },

      }
      });

內層元件

  • 在方法內建立函式,來觸發資料傳遞
  • 並將click綁到內部元件的按鈕上。使點擊時候,會觸發$emit
    • emit 名稱emit-num
      1
      2
      3
      4
      5
      6
      7
      8
      9
      app.component('button-counter', {
      methods: {
      click() {
      console.log('inner,click');
      this.$emit('emit-num');
      }
      },
      template: `<button type="button" @click="click">add</button>`
      });

建立內外層元件的溝通橋樑

  • 使用 v-on 的方式觸發外層方法(口訣:前內、後外)
    • 在 div內放入子元件button-counter
    • :emit-num="addNumber"
1
2
3
4
<div id="app">
{{ num }}
<button-counter v-on:emit-num="addNumber"></button-counter>
</div>

練習二,資料接收

外層元件

  • 接收方法為 getData 接收 text
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const app = Vue.createApp({
    data() {
    return {
    text: ''
    };
    },
    methods: {

    getData(value) {
    console.log('getData', text);
    this.text = value;
    }
    }
    });

內層元件

  • 建立觸發傳遞的方式
    • 將內層元件text: '內部資料' 傳遞到外層
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      app.component('button-text', {
      data() {
      return {
      text: '內部資料',
      }
      },
      methods: {
      emitText() {
      this.emit('emit-text', this.text)
      }
      },
      template: `<button type="button" @click="emitText">emit data</button>`
      });

      建立內外橋樑

      1
      2
      3
      <h3>傳遞資料狀態</h3>
      內部傳來的文字:{{ text }}<br>
      <button-text @emit-text="getData"></button-text>

emit 命名

與 props 命名一樣需要注意:

  • 在內層若以駝峰命名 emitText
    • 也可以一開始內層就是用- 來命名即可,如emit-text
      1
      2
      3
      emitText() {
      this.emit('emitText', this.text)
      }
  • 到了要綁定的 template 上,emit-text
    1
    <button-text @emit-text="getData"></button-text>

因為Vue每個元件都是各自獨立,所以我們無法在各自元件去調整資料,而直接修改另一個元件的資料。
所以,要使元件之間可以相互溝通,就需要使用資料傳遞方式。
在傳遞方式:

  • 外層傳遞內層 : props
  • 內層向外傳遞 : emit
    當外層元件,要將資料往內層元件丟時,此過程就是使用props。

傳遞資料的方式:

靜態資料傳入

  • 預期將外層傳入一個url的變數,傳進給內層使用
    • photo為內部元件,在template 我希望能夠取得外層傳入的圖片(urlimg)
    • 故在 props 以此命名,並將它綁到 template
      1
      2
      3
      4
      app.component('photo', {
      props: ['url'],
      template: `<img :src="url" class="img-thumbnail" alt>`
      });
  • 將上方的元件資料,加到外層元件:像是加入html屬性一樣
    1
    2
    3
    <photo
    url="https://images.unsplash.com/photo-1605784401368-5af1d9d6c4dc?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=600&q=80">
    </photo>

動態資源

  • 使用 v-bind,綁定內部元件的props ,使資料可以跟外層元件連動
  • 記得使用口訣: 前內、後外 =>前面就是props內的名稱,後面就是外層元件的名稱
    1
    2
    3
    <h3>動態資源</h3>
    <p>技巧:前內、後外</p>
    <photo v-bind:urlimg="imgUrl"></photo>

資料傳遞為 單向數據流

  • 外部所定義的資料,當往內層傳遞,是單向性
  • 不能試圖使用v-model或各種方式,來改變props傳入的內容
    • 以下範例:在子元件 photo2 放入 input綁定 v-model
    • 當嘗試在輸入框修改網址,會出現錯誤
1
2
//外層元件綁定 url
<photo2 :url="imgUrl"></photo2>
1
2
3
4
5
6
7
8
9
10
11
12
13
const app = Vue.createApp({
data() {
return {
imgUrl: 'https://images.unsplash.com/photo-1605784401368-5af1d9d6c4dc?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=600&q=80',
};
},
});

app.component('photo2', {
props: ['url'],
template: `<img :src="url" class="img-thumbnail" alt><br>
<input type="text" v-model="url"> {{ url }}`
})

在內部元件,要為props來命名

在命名上有些要注意的地方

  • 首字母大寫: PostData、SetItems
  • 駝峰命名法: postData、setItems
    要注意:在 HTML 中使用時必須使用 kebab-case (短橫線分隔)且應該為小寫。
1
2
3
4
5
6
7
8
9
10
11
<div id="app">

<photo3 :super-url="imgUrl" ></photo3>
</div>

<script>
Vue.component("photo3", {
props: ["superUrl"],
template: `<img :src="superUrl" class="img-thumbnail alt >"`
});
</script>

定義 Props 型別驗證

  • 使用型別驗證,會改用大括號,裡面放入props名稱,並用物件形式加入設定的內容
    • props:{ propC: { type: String, required: true, } }
  • 其中,可以針對該props 來設定:
    • type型別 : 可以是 String、Number、Boolean、Array、Object、Date、Function、Symbol
    • default :如果該 prop 沒有接收到傳入的值,就會使用 default 的值作為預設值。
    • required : 是否為必填項,如果設為 true 則表示必須要有值傳入,若沒有,就會出現錯誤提示。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      app.component('props-validation', {
      props: {
      // 單一型別檢查,可接受的型別 String, Number, Object, Boolean, Function(在 Vue 中可使用 Function 驗證型別)
      // null, undefined 會直接通過驗證
      propA: Function,

      // 多個型別檢查
      propB: [String, Number],

      // 必要值
      propC: {
      type: String,
      required: true,
      },

      // 預設值
      propD: {
      type: Number,
      default: 300
      },

      // 自訂函式
      propE: {
      type: Object,
      default() {
      return {
      money: 300
      }
      }
      },

      // 自訂驗證
      propF: {
      validator(value) {
      return value > 1000
      }
      },
      },
      六角學院
      [DAY12]跟 Vue.js 認識的30天 - Vue 模組資料傳遞(props)

修飾符有分為:
1.按件修飾符
2.滑鼠修飾符
3.事件修飾符

codepen

按鍵修飾符

1-1. 別名修飾

  • 在特定按鍵,按下去的時候觸發
    • .enter, .tab, .delete, .esc, .space, .up, .down, .left, .righ
  • 使用.enter只有再按下 enter 鍵才會觸發
1
2
3
4
5
<div id="app">
<h6 class="mt-3">別名修飾</h6>
<input type="text" class="form-control" v-model="text" @keyup.enter="trigger('enter')">

</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
Vue.createApp({

data(){
return {

}
},
methods: {
trigger: function(name) {
console.log(name, '此事件被觸發了')
},
}
}).mount('#app')

1-2. 相應按鍵時才觸發的監聽器

  • 僅在按下相應按鍵時才觸發鼠標或鍵盤事件的監聽器
  • .ctrl, .alt, .shift, .meta
  • 此範例為 @keyup.shift.enter
    1
    2
    <h6 class="mt-3">相應按鍵時才觸發的監聽器</h6>
    <input type="text" class="form-control" v-model="text" @keyup.shift.enter="trigger('shift + Enter')">
  1. 滑鼠修飾符
    .left 只當點擊鼠標左鍵時觸發。
    .right 只當點擊鼠標右鍵時觸發。
    .middle 只當點擊鼠標中鍵時觸發。
  • 按下滑鼠右鍵
    <span class="box" @click.right="trigger('right button')">
1
2
3
4
5
6
<h4>滑鼠修飾符</h4>
<h6 class="mt-3">滑鼠修飾符</h6>
<div class="p-3 bg-primary">
<span class="box" @click.right="trigger('right button')">
</span>
</div>

  1. 事件修飾符
  • 不會限定,是使用滑鼠還是鍵盤,是針對事件本身來進行修飾
    .stop - 調用 event.stopPropagation()
    .prevent - 調用 event.preventDefault()
    .capture - 添加事件偵聽器時使用 capture 模式。
    .self - 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。
    .once - 只觸發一次回調。
  • 常用的是 <a>外部連結:移除預設事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <h4>事件修飾符</h4>
    <ul>
    <li>.stop - 調用 event.stopPropagation()。</li>
    <li><strong>.prevent - 調用 event.preventDefault()。</strong></li>
    <li>.capture - 添加事件偵聽器時使用 capture 模式。</li>
    <li>.self - 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。</li>
    <li>.once - 只觸發一次回調。</li>
    </ul>
    <a href="https://www.google.com/" @click.prevent="trigger('prevent')">加入 Prevent</a>

資料來源:
六角學院 - Vue 3.js