iT邦幫忙

2023 iThome 鐵人賽

DAY 4
0
Vue.js

業主說給你30天學會Vue系列 第 4

V04_在Vue之前_必備的JS基礎(1)_模組 module 的操作

  • 分享至 

  • xImage
  •  

V04_在Vue之前_必備的JS基礎(1)_模組module的操作

今天來好好探索一下,在vue的框架中,常見的JS的語法及概念
前面這幾篇的學習,算是在打好基礎
等到正式進入vue等程式時,就能駕輕就熟
可以專注在vue的程式碼的編寫上

首先要了解的是JS的模組的概念
先來參考 MDN對於JS Module 的說明
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

** MDN 是一個非常完整且詳細的網站技術說明文件的平台,可以算是網站技術的百科全書,
如果可以好好善用這個平台的學習資源,在網站技術上一定會變得很專業。 **

這是MDN官網的介紹
MDN Web Docs is an open-source, collaborative project documenting Web platform technologies, including CSS, HTML, JavaScript, and Web APIs. We also provide an extensive set of learning resources for beginning developers and students.

MDN 是Mozilla Developer Network 的縮寫,而 Mozilla 是一個自由軟體社群,前身是Netscape,
旗下的瀏覽器產品是 Firefox,其核心引擎是 Gecko

JS Module, export, import

接著回到 JS Module,或是 ES Module (ESM)
主要是引入了 export 及 import 的語法

先從官網的github下載範例程式
https://github.com/mdn/js-examples

解壓縮後,先參考這個目錄的檔案 /js-examples-main/module-examples/basic-modules
目錄架構為

- index.html
- main.js
- modules
  - canvas.js
  - square.js

由於作為模組的js與一般網頁的js,副檔名都是js,
為了避免混淆,模組的js會另外儲存在 modules的目錄下

先看一下 canvas.js 的程式碼

function create(id, parent, width, height) {
  let divWrapper = document.createElement('div');
  let canvasElem = document.createElement('canvas');
  parent.appendChild(divWrapper);
  divWrapper.appendChild(canvasElem);

  divWrapper.id = id;
  canvasElem.width = width;
  canvasElem.height = height;

  let ctx = canvasElem.getContext('2d');

  return {
    ctx: ctx,
    id: id
  };
}

function createReportList(wrapperId) {
  let list = document.createElement('ul');
  list.id = wrapperId + '-reporter';

  let canvasWrapper = document.getElementById(wrapperId);
  canvasWrapper.appendChild(list);

  return list.id;
}

export { create, createReportList };

這裡有2個function, 分別為 create 及 createReportList
透過 export { create, createReportList };
將這個模組的 2個function 匯出
如果function是有傳回值的話,可以透過 return 回傳

接著是 square.js的程式碼

const name = 'square';

function draw(ctx, length, x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x, y, length, length);

  return {
    length: length,
    x: x,
    y: y,
    color: color
  };
}

function random(min, max) {
   let num = Math.floor(Math.random() * (max - min)) + min;
   return num;
}

function reportArea(length, listId) {
  let listItem = document.createElement('li');
  listItem.textContent = `${name} area is ${length * length}px squared.`

  let list = document.getElementById(listId);
  list.appendChild(listItem);
}

function reportPerimeter(length, listId) {
  let listItem = document.createElement('li');
  listItem.textContent = `${name} perimeter is ${length * 4}px.`

  let list = document.getElementById(listId);
  list.appendChild(listItem);
}

function randomSquare(ctx) {
  let color1 = random(0, 255);
  let color2 = random(0, 255);
  let color3 = random(0, 255);
  let color = `rgb(${color1},${color2},${color3})`
  ctx.fillStyle = color;

  let x = random(0, 480);
  let y = random(0, 320);
  let length = random(10, 100);
  ctx.fillRect(x, y, length, length);

  return {
    length: length,
    x: x,
    y: y,
    color: color
  };
}

export { name, draw, reportArea, reportPerimeter };
export default randomSquare;

其中看到 function 有 draw, random, reportArea, reportPerimeter, randomSquare
還有一個 const name,

然後要匯出的是
export { name, draw, reportArea, reportPerimeter };

所以只有 random 這個function 沒有匯出,當作是模組內部使用的function

export default randomSquare;

是指 如果只有列一個function要匯出的話,例如randomSquare,就可以不用另加大括號,
而是用 export default randomSquare 就可以了

同樣 在匯入的部份,參考main.js程式碼如下

import { create, createReportList } from './modules/canvas.js';
import { name, draw, reportArea, reportPerimeter } from './modules/square.js';
import randomSquare from './modules/square.js';

let myCanvas = create('myCanvas', document.body, 480, 320);
let reportList = createReportList(myCanvas.id);

let square1 = draw(myCanvas.ctx, 50, 50, 100, 'blue');
reportArea(square1.length, reportList);
reportPerimeter(square1.length, reportList);

// Use the default
let square2 = randomSquare(myCanvas.ctx);
export { name, draw, reportArea, reportPerimeter };
export default randomSquare;

至於這2種用法,可能一時看不出使用時機的差別,
但是可以在更多的範例中,慢慢理解 export default 的差別,

就像 為何不寫成

export { name, draw, reportArea, reportPerimeter, randomSquare };

還要將randomSquare獨立出來寫在 export default 中。

接著是 import的部份,可以與export對照來看

import { create, createReportList } from './modules/canvas.js';
import { name, draw, reportArea, reportPerimeter } from './modules/square.js';
import randomSquare from './modules/square.js';

同樣地,設定成export default randomSquare; 的function
在import時,也是不用大括號

//-------------------------------

此外,還有在export及import也有提供設定別名或是重新命名的語法
可以在export端重新命名, 語法如下

// inside module.js
export { function1 as newFunctionName, function2 as anotherNewFunctionName };

// inside main.js
import { newFunctionName, anotherNewFunctionName } from "./modules/module.js";

在匯出時
function1 as newFunctionName 是指將原本 function1 重新命名為 newFunctionName
function2 as anotherNewFunctionName 是指將原本 function2 重新命名為 anotherNewFunctionName

在匯入時
可以直接引入 newFunctionName, anotherNewFunctionName

另外也可以在import端重新命名
語法如下

// inside module.js
export { function1, function2 };

// inside main.js
import {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName,
} from "./modules/module.js";

舉例來說, 有3個模組 square.js, circle.js, triangle.js
可參考這個目錄的檔案 /js-examples-main/module-examples/module-objects
目錄架構為

- index.html
- main.js
- modules
  - canvas.js
  - circle.js
  - square.js
  - triangle.js

其中 circle.js的程式碼如下

const name = 'circle';

function degToRad(degrees) {
  return degrees * Math.PI / 180;
};

function draw(ctx, radius, x, y, color) {
  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.arc(x, y, radius, degToRad(0), degToRad(360), false);
  ctx.fill();

  return {
    radius: radius,
    x: x,
    y: y,
    color: color
  };
}

function reportArea(radius, listId) {
  let listItem = document.createElement('li');
  listItem.textContent = `${name} area is ${Math.round(Math.PI * (radius * radius))}px squared.`

  let list = document.getElementById(listId);
  list.appendChild(listItem);
}

function reportPerimeter(radius, listId) {
  let listItem = document.createElement('li');
  listItem.textContent = `${name} circumference is ${Math.round(2 * Math.PI * radius)}px.`

  let list = document.getElementById(listId);
  list.appendChild(listItem);
}

export { name, draw, reportArea, reportPerimeter };

這3個模組都有相同名稱的function要 export

export { name, draw, reportArea, reportPerimeter };

在import時,就會出現

import { name, draw, reportArea, reportPerimeter } from "./modules/square.js";
import { name, draw, reportArea, reportPerimeter } from "./modules/circle.js";
import { name, draw, reportArea, reportPerimeter } from "./modules/triangle.js";

在執行時產生錯誤,
因此就可以改寫為

import {
  name as squareName,
  draw as drawSquare,
  reportArea as reportSquareArea,
  reportPerimeter as reportSquarePerimeter,
} from "./modules/square.js";

import {
  name as circleName,
  draw as drawCircle,
  reportArea as reportCircleArea,
  reportPerimeter as reportCirclePerimeter,
} from "./modules/circle.js";

import {
  name as triangleName,
  draw as drawTriangle,
  reportArea as reportTriangleArea,
  reportPerimeter as reportTrianglePerimeter,
} from "./modules/triangle.js";

當然也可以在export處理重新命名,
不過這就要哪一種比較不會產生混淆,或是不好整合的問題
在多人專案時因為無法預期其他import會不會有名稱衝突的問題,
比較難在export端處理重新命名,
因此在import端處理重新命名,就可以依據程式的名稱衝突狀況進行調整。

//--------
如果要將模組的function全部匯入的話,可以使用這個語法

import * as Module from "./modules/module.js";

Module.function1();
Module.function2();

但僅限有列在export的function

示範如下

import * as Canvas from "./modules/canvas.js";
import * as Square from "./modules/square.js";
import * as Circle from "./modules/circle.js";
import * as Triangle from "./modules/triangle.js";

const square1 = Square.draw(myCanvas.ctx, 50, 50, 100, "blue");
Square.reportArea(square1.length, reportList);
Square.reportPerimeter(square1.length, reportList);

//-----------
除了使用模組Module之外,也有提供將類別class匯出的方法
參考這個目錄的檔案 /js-examples-main/module-examples/classes

其中 circle.js 的程式碼變成

function degToRad(degrees) {
  return degrees * Math.PI / 180;
}

class Circle {
  constructor(ctx, listId, radius, x, y, color) {
    this.ctx = ctx;
    this.listId = listId;
    this.radius = radius;
    this.x = x;
    this.y = y;
    this.color = color;
    this.name = 'circle';
  }

  draw() {
    this.ctx.fillStyle = this.color;
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, this.radius, degToRad(0), degToRad(360), false);
    this.ctx.fill();
  }

  reportArea() {
    let listItem = document.createElement('li');
    listItem.textContent = `${this.name} area is ${Math.round(Math.PI * (this.radius * this.radius))}px squared.`

    let list = document.getElementById(this.listId);
    list.appendChild(listItem);
  }

  reportPerimeter() {
    let listItem = document.createElement('li');
    listItem.textContent = `${this.name} circumference is ${Math.round(2 * Math.PI * this.radius)}px.`

    let list = document.getElementById(this.listId);
    list.appendChild(listItem);
  }
}

export { Circle };

import端的程式 main.js 程式碼如下

import { Canvas } from './modules/canvas.js';

import { Square } from './modules/square.js';
import { Circle } from './modules/circle.js';
import { Triangle } from './modules/triangle.js';

// create the canvas and reporting list
let myCanvas = new Canvas('myCanvas', document.body, 480, 320);
myCanvas.create();
myCanvas.createReportList();

// draw a square
let square1 = new Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
square1.draw();
square1.reportArea();
square1.reportPerimeter();

// draw a circle
let circle1 = new Circle(myCanvas.ctx, myCanvas.listId, 75, 200, 100, 'green');
circle1.draw();
circle1.reportArea();
circle1.reportPerimeter();

// draw a triangle
let triangle1 = new Triangle(myCanvas.ctx, myCanvas.listId, 100, 75, 190, 'yellow');
triangle1.draw();
triangle1.reportArea();
triangle1.reportPerimeter();`

可以看到export與import的對應語法

export { Circle } ->
import { Circle } from './modules/circle.js';

整理一下module與class的語法

module 的流程
circle.js為模組本身的檔案
-> export { name, draw, reportArea, reportPerimeter };
-> import * as Circle from "./modules/circle.js";
-> const circle1 = Circle.draw(myCanvas.ctx, 50, 50, 100, "blue");
class 的流程
在circle.js中 的 class Circle
-> export { Circle };
-> import { Circle } from './modules/circle.js';
-> let circle1 = new Circle(myCanvas.ctx, myCanvas.listId, 75, 200, 100, 'green'); 
-> circle1.draw();

以上是常見的模組用法

//----------------------------------

接著是 直接在網頁上透過使用模組的用法

例如原本是如下的程式碼

import { name as squareName, draw } from "./modules/shapes/square.js";
import { name as circleName } from "https://example.com/shapes/circle.js";

可以在index.html中,加上

<script type="importmap">
  {
    "imports": {
      "square": "./module/shapes/square.js",
      "circle": "https://example.com/shapes/circle.js"
    }
  }
</script>

其中在<script>中,加上 type="importmap" 屬性,就可以設定import的內容
接著在main.js中,就可以改成

import { name as squareName, draw } from "square";
import { name as circleName } from "circle";

等於是一種路徑的map對照或是指示module 匯入的路徑, 稱為 module specifier 模組指示詞
等於將原本的
import { name as squareName, draw } from "./modules/shapes/square.js";

經過 <script type="importmap"> 的設定後
可以改寫成
import { name as squareName, draw } from "square";

有關 <script type="importmap"> 的部份,可以參考MDN官網的說明
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap

整理一下<script>的用法

<script>  <script> 一般的javascript程式編寫

<script type="application/javascript" src="myscript.js"></script> 載入外部js程式

<script type="module" src="main.js"></script> 載入模組

<script type="module"> 模組程式編寫

<script type="importmap"> 設定 module specifier,模組路徑的map對照

上一篇
V03_直接在網頁上使用Vue
下一篇
V05_在Vue之前_必備的JS基礎(2)_陣列Array的操作
系列文
業主說給你30天學會Vue31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言