實作一個 JavaScript 迷你框架 (一)

前言

在這系列的課程,我們將總結 JavaScript 全攻略:克服JS的奇怪部分 學習到的知識,並仿效 jQuery 原始碼的架構,來打造一個簡單的 Framwork,取名為 Greetr

關於 jQuery的架構可以參考

需求

當我們在打造一個 framework 時,須事先規劃好這個 framework 要具備哪些功能,而不是一股腦的就打開程式編輯器開始寫,以下是這個框架的功能。

  • 當我們告訴它我們的姓(lastname)名(firstname)還有選擇的語言(language)時,它可以用正式(formal)和非正式(informal)的方式和我們打招呼。

  • 支援英文(English)和中文(Chinese)兩種語言。

  • 是一個可重複使用的 library/framework,也就是說,每一個安裝此 framework 的人可以直接使用,不會和它原本程式碼有所衝突。

  • 和 jQuery只需要輸入 $() 一樣,我們可以使用 G$( )來建立物件。

  • 支援 jQuery,可以把 Greetr 產生的訊息直接顯示於 HTML 中。


HTML

1
2
3
4
5
6
7
8
9
<html>
<head>
</head>
<body>
<script src="jquery-3.0.0.js"></script>
<script src="greetr.js"></script>
<script src="app.js"></script>
</body>
</html>

安全的程式碼 IIFEs

參考:PJChender - 為什麼我們要用IIFEs(Immediately Invoked Functions Expressions)

先前的課程有提到知名的框架通常都會使用 IIFE,避免程式碼使用者和框架的程式碼互相汙染。

1
2
3
4
5
(function(global, $) {

// the content of framwork

})(window, jQuery)

同時,因為我們要讓我們的 framework 能夠取用或改變到全域環境的內容,同時還要支援 jQuery ,因此在參數的地方,我們會帶入 windowjQuery 的核心 。

補充:我們還可以加上 ; 在最前面,防止前一份 Framwork 或程式碼結尾沒有加上分號而導致的問題。

1
;(function(global, $) { ... }

不使用 new 就可以建立物件

參考:[筆記] 跟著JQuery原始碼一起學習程式設計

我們可以利用 return 一個 function constructor 的方式來達到這樣的效果,如下面程式碼中第 1 部分所示,而第 2 部分,才是我們真正的函數建構子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
;(function (global, $) {

/* 1. new放在這個函數中 */
var Greetr = function (firstName, lastName, language) {
return new Greetr.init(firstName, lastName, language);
}

/* 2. 函數建構子 */
Greetr.init = function (firstName, lastName, language) {
this.firstName = firstName;
this.lastName =lastName;
this.language = language;
}

})(window, jQuery)

建立函數建構子的預設值

接著,我們要在函式裡面為 firstname, lastnamelanguage 來建立預設值

[筆記] JavaScript中coercion的實際使用–建立函式預設值(default value)
[筆記] 談談JavaScript中的”this”和它的bug

1
2
3
4
5
6
7
8
Greetr.init = function (firstName, lastName, language) {
/* 1 */
var self = this
/* 2 */
self.firstName = firstName || '';
self.lastName = lastName || '';
self.language = language || 'ch';
}

ES6還可以這麼做

1
Greetr.init = function (firstName = '', lastName = '', language = 'ch') { }

為了避免 this 可能在後面使用時碰到一些問題,所以在第 1 部分的地方,我們用 var self = this 這樣的方法,來避免 this 在後續操作上可能會碰到的問題;在第 2 部分的地方,我們則是透過 JavaScript 中強制轉換coercion的特性,使用 OR operator || 的方式來達到預設值的效果。


建立函數建構子的原型(prototype)

參考:JS的原型繼承(方法1) - 函數建構子 與「new」 該篇文章中的函數建構子和prototype的建立部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
;(function (global, $) {

var Greetr = function (firstName, lastName, language) {
return new Greetr.init(firstName, lastName, language);
}

/* 1 */
Greetr.prototype = {
// 方法寫在這
}

Greetr.init = function (firstName, lastName, language) {
var self = this
self.firstName = firstName || '';
self.lastName =lastName || '';
self.language = language || 'ch';
}

/* 2 */
Greetr.init.prototype = Greetr.prototype

})(window, jQuery)

我們的函數建構子是 Greetr.init,如果要設定相關方法,照理說應該要設定在Greetr.init.prototype,但是為了讓程式碼更好閱讀,我們要將方法設置在 Greetr.prototype 內,如 /* 1 */
再來我們還必須讓 Greetr.init 建構子 所建立的物件實體 instance 可以參考到 Greetr.prototype 內的方法,我們可以使用/* 2 */ 這行程式碼來達到目的。


使用 G$( ) 即可快速建立物件

就像在 jQuery 中,我們可以使用 jQuery()$() 來選取DOM,在這裡,我們希望我們可以使用 Greetr()G$() 這兩種方式來建立物件。

我們只需要加上這一行,就可以達到這樣的效果了。

1
G$ = Greetr

讓全域環境也可以取用

如果是上面的寫法,只有在這個IFFE內才能取用到 G$()Greetr(),因為就詞彙環境來說,全域物件裡並沒有這個變數。

1
global.G$ = global.Greetr = Greetr

加上Global,是為了讓全域都可以使用這些功能 ,如此一來 G$()Greetr() 也可以在全域環境下使用了。


測試

目前為止的 Greetr.js 程式碼如下,已經可以簡單使用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
;(function(global, $){

var Greetr = function(firstname, lastname, language){
return new Greetr.init(firstname, lastname, language);
}

Greetr.prototype = {}

Greetr.init = function(firstname, lastname, language){

var self = this;
self.firstname = firstname || '';
self.lastname = lastname || '';
self.language = language || 'EN';

}

Greetr.init.prototype = Greetr.prototype;

global.Greetr = global.G$ = Greetr;

})(window, jQuery)

試著在 app.js,建立一個物件:

var a = G$('Dylan', 'Liu', 'EN')

結果


資料來源: