前端乾貨之JS最佳實踐

wangduanduan發表於2019-02-16

持續更新地址 https://wdd.js.org/js-best-pr…

1. 風格

一千個讀者有一千個哈姆雷特,每個人都有自己的code style。我也曾為了要不要加分號給同事鬧個臉紅脖子粗,實際上有必要嗎? 其實JavaScript已經有了比較流行的幾個風格

我自己使用的是JavaScript Standard Style, 我之所以使用這個,是因為它有一些工具。可以讓你寫完程式碼後,一旦儲存,就自動幫你把你的風格的程式碼修正成標準分割,而不是死記硬背應該怎麼寫。看完這個頁面,你就應該立馬愛上JavaScript Standard Style , 如果你用vscode, 恰好你有寫vue, 你想在.vue檔案中使用standard風格,那麼你需要看看這篇文章

2. 可維護性

很多時候,我們不是從零開始,開發新程式碼。而是去維護別人的程式碼,以他人的工作成果為基礎。確保自己的程式碼可維護,是贈人玫瑰,手留餘香的好事。一方面讓別人看的舒服,另一方面也防止自己長時間沒看過自己的程式碼,自己都難以理解。

2.1. 什麼是可維護程式碼

可維護的程式碼的一些特徵

  • 可理解易於理解程式碼的用途
  • 可適應資料的變化,不需要完全重寫程式碼
  • 可擴充套件要考慮未來對核心功能的擴充套件
  • 可除錯給出足夠的資訊,讓除錯的時候,確定問題所在
  • 不可分割函式的功能要單一,功能粒度不可分割,可複用性增強

2.2. 程式碼約定

2.2.1. 可讀性

  • 統一的縮排方式
  • 註釋
  • 空白行

2.2.1.1. 縮排:

  • 一般使用4個空格
  • 不用製表符的原因是它在不同編輯器裡顯示效果不同

2.2.1.2. 註釋:哪些地方需要註釋?

  • 函式和方法
  • 大段程式碼
  • 複雜的演算法
  • hack

2.2.1.3. 空白行:哪些地方需要空白行?

  • 方法之間
  • 方法裡的區域性變數和第一個語句之間
  • 單行或者多行註釋
  • 方法內衣個邏輯單元之間
// Good
if (wl && wl.length) {

    for (i = 0, l = wl.length; i < l; ++i) {
        p = wl[i];
        type = Y.Lang.type(r[p]);
        
        if (s.hasOwnProperty(p)) {
        
            if (merge && type == `object`) {
                Y.mix(r[p], s[p]);
            } else if (ov || !(p in r)) {
                r[p] = s[p];
            }
        }
    }
}

2.2.2. 變數名和函式名

There are only two hard problem in Computer Science cache invalidation and naming things.—Phil Karlton

  • 駝峰式命名
  • 變數名以名詞開頭
  • 方法名以動詞開頭
  • 常量全部大寫
  • 建構函式以大寫字母開頭
  • jQuery物件以”$”符號開頭
  • 自定義事件處理函式以“on”開頭
// Good
var count = 10;
var myName = "wdd";
var found = true;

// Bad: Easily confused with functions
var getCount = 10;
var isFound = true;

// Good
function getName() {
    return myName;
}

// Bad: Easily confused with variable
function theName() {
    return myName;
}

// Bad:
var btnOfSubmit = $(`#submit`);

// Good:
var $btnOfSubmit = $(`#submit`);

// Bad:給App新增一個處理聊天事件的函式,一般都是和websocket服務端推送訊息相關
App.addMethod(`createChat`,function(res){
    App.log(res);
});
// Bad: 此處呼叫,這裡很容易誤以為這個函式是處理建立聊天的邏輯函式
App.createChat();

// Good: 
App.addMethod(`onCreateChat`,function(res){
    App.log(res);
});
// Good:此處呼叫
App.onCreateChat();

變數命名不僅僅是一種科學,更是一種藝術。總之,要短小精悍,見名知意。有些名詞可以反應出變數的型別。

2.2.2.1. 變數名

名詞 資料型別含義
count, length,size 數值
name, title,message 字串
i, j, k 用來迴圈
car,person,student,user 物件
success,fail 布林值
payload post資料的請求體
method 請求方式

2.2.2.2. 函式名

動詞 含義
can Function returns a boolean
has Function returns a boolean
is Function returns a boolean
get Function returns a nonboolean
set  Function is used to save a value

2.2.2.3. 一些與函式名搭配的常用動詞

動詞 用法
send 傳送
resend 重發
validate 驗證
query 查詢
create 建立
add 新增
delete 刪除
remove 移除
insert 插入
update 更新,編輯
copy 複製
render 渲染
close 關閉
open 開啟
clear 清除
edit 編輯
query 查詢
on 當事件發生
list 渲染一個列表,如使用者列表renderUsersList()
content 渲染內容,如使用者詳情的頁面 renderUserContent()

2.2.2.4. 介面常用的動詞

對於http請求的最常用的四種方法,get,post,put,delete,有一些常用的名詞與其對應

含義 請求方法 詞語 栗子
增加 post create createUser,createCall
刪除 delete delete deleteUser
修改 put update updateUser,updateProfile
查詢 get get,query getUser,queryUser(無條件查詢使用get,有條件查詢使用query)

2.2.2.5. 學會使用單複數命名函式

函式名 含義
getUser() 獲取一個使用者,一般是通過唯一的id來獲取
getUsers() 獲取一組使用者,一般是通過一些條件來獲取
createUser() 建立一個使用者
createUsers() 建立一組使用者

2.2.2.6. 常量

var MAX_COUNT = 10;
var URL = "http://www.nczonline.net/";

2.2.2.7. 建構函式

// Good
function Person(name) {
    this.name = name;
}
Person.prototype.sayName = function() {
    alert(this.name);
};
var me = new Person("wdd");

2.2.2.8. 底層http請求介面函式

  • 建議使用“_”開頭,例如App._getUsers();而對於介面函式的封裝,例如App.getUsers(),內部邏輯呼叫App._getUsers();

2.2.3. 檔名

  • 全部使用小寫字母
  • 單詞之間的間隔使用“-”

eg:

app-main.js
app-event.js
app-user-manger.js

2.2.4. 檔案歸類

自己寫的js檔案最好和引用的一些第三方js分別放置在不同的資料夾下。

2.2.5. 千萬別用alert

alert的缺點

  • 如果你用alert來顯示提醒訊息,那麼使用者除了點選alert上的的確定按鈕外,就只能點選上面的關閉,或者選擇禁止再選擇對話方塊,除此以外什麼都不能操作。
  • 有些瀏覽器如果禁止了alert的選項,那麼你的alert是不會顯示的
  • 如果你在try catch語句裡使用alert,那麼console裡將不會輸出錯誤資訊,你都沒辦法檢視錯誤的詳細原因,以及儲出錯的位置。

更優雅的提醒方式

  • console.log() 普通提示訊息
  • console.error() 錯誤提示訊息
  • console.info() 資訊提示訊息
  • console.warn() 警告提示訊息

2.3. 鬆散耦合

  • html檔案中儘可能避免寫js語句
  • 儘量避免在js更改某個css類的屬性,而使用更改類的方法
  • 不要在css中寫js的表示式
  • 解耦應用邏輯和事件處理程式

2.3.1. 將應用邏輯和事件處理程式的解耦

//一般事件訂閱的寫法,以jQuery的寫法為栗子
$(document).on(`click`,`#btn-get-users`,function(event){
    event.stopPropagation();
    
    //下面的省略號表示執行獲取所有用於並顯示在頁面上的邏輯
    // Bad
    ...
    ...
    ...
    //
});

如果增加了需求,當點選另外一個按鈕的時候,也要執行獲取所有使用者並顯示在頁面上,那麼上面省略的程式碼又要複製一份。如果介面有改動,那麼需要在兩個不同的地方都要修改。
所以,應該這樣。

$(document).on(`click`,`#btn-get-users`,function(event){
    event.stopPropagation();
    
    //將應用邏輯分離在其他個函式中
    // Good
    App.getUsers();
    App.renderUsers();
});

2.3.2. 鬆散解耦規則

  • 不要將event物件傳給其他方法,只傳遞來自event物件中的某些資料
  • 任何事件處理程式都應該只處理事件,然後把處理轉交給應用邏輯。

2.3.3. 將非同步請求和資料處理解耦

// Bad
ReqApi.tenant.queryUsers({},function(res){
    if(!res.success){
        console.error(res);
        return;
    }
    
    //對資料的處理
    ...
    ...
    ...
});    

上面程式碼對資料的處理直接寫死在非同步請求裡面,如果換了一個請求,但是資料處理方式是一樣的,那麼又要複製一遍資料處理的程式碼。最好的方式是將資料處理模組化成為一個函式。

// Good
ReqApi.tenant.queryUsers({},function(res){
    if(!res.success){
        console.error(res);
        return;
    }
    
    //對資料的處理
    App.renderUsers(res.data);
}); 

非同步請求只處理請求,不處理資料。函式的功能要專一,功能粒度不可分割。

2.3.4. 不要將某個變數寫死在函式中,儘量使用引數傳遞進來

如果你需要一個函式去驗證輸入框是否是空,如下。這種方式就會繫結死了這個只能驗證id為test的輸入框,換成其他的就不行

// bad
function checkInputIsEmpty(){
    var value = $(`#test`).val();
    if(value){
        return true;
    }
    else{
        return false;
    }
}

// good 
function isEmptyInput(id){
    var value = $(`#`+id).val();
    if(value){
        return true;
    }
    else{
        return false;
    }
}

2.4. 程式設計實踐

2.4.1. 尊總物件所有權

javascript動態性質是的幾乎任何東西在任何時間都能更改,這樣就很容易覆寫了一些預設的方法。導致一些災難性的後果。如果你不負責或者維護某個物件,那麼你就不能對它進行修改。

  • 不要為例項或原型新增屬性
  • 不要為例項或者原型新增方法
  • 不要重定義存已存在的方法

2.4.2. 避免全域性變數

// Bad 兩個全域性變數
var name = "wdd";
funtion getName(){
    console.log(name);
}

// Good 一個全域性變數
var App = {
    name:"wdd",
    sayName:funtion(){
        console.log(this.name);//如果這個函式當做回撥數使用,這個this可能指向window,
    }
};

單一的全域性變數便是名稱空間的概念,例如雅虎的YUI,jQuery的$等。

2.4.3. 避免與null進行比較

funtion sortArray(values){
    // 避免
    if(values != null){
        values.sort(comparator);
    }
}
function sortArray(values){
    // 推薦
    if(values instanceof Array){
        values.sort(compartor);
    }
}

2.4.3.1. 與null進行比較的程式碼,可以用以下技術進行替換

  • 如果值是一個應用型別,使用instanceof操作符,檢查其建構函式
  • 如果值是基本型別,使用typeof檢查其型別
  • 如果是希望物件包含某個特定的方法名,則只用typeof操作符確保指定名字的方法存在於物件上。

程式碼中與null比較越少,就越容易確定程式碼的目的,消除不必要的錯誤。

2.4.4. 從程式碼中分離配置檔案

配置資料是一些硬程式碼(hardcoded),看下面的栗子

function validate(value){
    if(!value){
        alert(`Invalid value`);
        location.href = `/errors/invalid.php`;
    }
}

上面程式碼裡有兩個配置資料,一個是UI字串(`Invalid value`),另一個是一個Url(`/error/invalid.php`)。如果你把他們寫死在程式碼裡,那麼如果當你需要修改這些地方的時候,那麼你必須一處一處的檢查並修改,而且還可能會遺漏。

2.4.4.1. 所以第一步是要區分,哪些程式碼應該寫成配置檔案的形式?

  • 顯示在UI元素中的字串
  • URL
  • 一些重複的唯一值
  • 一些設定變數
  • 任何可能改變的值

2.4.4.2. 一些例子

var Config = {
    "MSG_INVALID_VALUE":"Invalid value",
    "URL_INVALID":"/errors/invalid.php"
}

2.4.5. 除錯資訊開關

在開發過程中,可能隨處留下幾個console.log,或者alert語句,這些語句在開發過程中是很有價值的。但是專案一旦進入生產環境,過多的console.log可能影響到瀏覽器的執行效率,過多的alert會降低程式的使用者體驗。而我們最好不要在進入生產環境前,一處一處像掃雷一樣刪除或者註釋掉這些除錯語句。

最好的方式是設定一個開關。

//全域性命令空間
var App = {
    debug:true,
    log:function(msg){
        if(debug){
            console.log(msg);
        }
    },
    alert:function(msg){
        if(debug){
            alert(msg);
        }
    }
};

//使用
App.log(`獲取使用者資訊成功`);
App.alert(`密碼不匹配`);

//關閉日誌輸出與alert
App.debug = false;

2.4.6. 使用jQuery Promise

沒使用promise之前的回撥函式寫法

// bad:沒使用promise之前的回撥函式寫法
function sendRequest(req,successCallback,errorCallback){
    var inputData = req.data || {};
    inputData = JSON.stringify(inputData);
    $.ajax({
        url:req.base+req.destination,
        type:req.type || "get",
        headers:{
            sessionId:session.id
        },
        data:inputData,
        dataType:"json",
        contentType : `application/json; charset=UTF-8`,
        success:function(data){
            successCallback(data);
        },
        error:function(data){
            console.error(data);
            errorCallback(data);
        }
    });
}

//呼叫
sendRequest(req,function(res){
    ...
},function(res){
    ...
});

使用promise之後

function sendRequest(req){
    var dfd = $.Deferred();
    var inputData = req.data || {};
    inputData = JSON.stringify(inputData);
    $.ajax({
        url:req.base+req.destination,
        type:req.type || "get",
        headers:{
            sessionId:session.id
        },
        data:inputData,
        dataType:"json",
        contentType : `application/json; charset=UTF-8`,
        success:function(data){
            dfd.resolve(data);
        },
        error:function(data){
            dfd.reject(data);
        }
    });
    
    return dfd.promise();
}

//呼叫
sendRequest(req)
.done(function(){
    //請求成功
    ...
})
.fail(function(){
    //請求失敗
    ...
});

2.4.7. 顯示錯誤提醒,不要給後端介面背鍋

假如前端要去介面獲取使用者資訊並顯示出來,如果你的請求格式是正確的,但是介面返回400以上的錯誤,你必須通過提醒來告知測試,這個錯誤是介面的返回錯誤,而不是前端的邏輯錯誤。

2.4.8. REST化介面請求

對資源的操作包括獲取、建立、修改和刪除資源,這些操作正好對應HTTP協議提供的GET、POST、PUT和DELETE方法。

對應方式

請求型別 介面字首
GET .get,
POST .create 或者 .get
PUT .update
DELETE .delete

說明

  • 有些介面雖然是獲取某一個資源,但是它使用的卻是POST請求,所以建議使用.get比較好

示例:

// 與使用者相關的介面
App.api.user = {};

// 獲取一個使用者: 一般來說是一個指定的Id,例如userId
App.api.user.getUser = function(){
    ...
};

// 獲取一組使用者: 一般來說是一些條件,獲取條件下的使用者,篩選符合條件的使用者
App.api.user.getUsers = function(){
    ...
};

// 建立一個使用者
App.api.user.createUser = function(){
    
};

// 建立一組使用者
App.api.user.createUsers = function(){
    
};

// 更新一個使用者
App.api.user.updateUser = function(){
    
};

// 更新一組使用者
App.api.user.updateUsers = function(){
    
};

// 更新一個使用者
App.api.user.updateUser = function(){
    
};

// 更新一組使用者
App.api.user.updateUsers = function(){
    
};

// 刪除一個使用者
App.api.user.deleteUser = function(){
    
};

// 刪除一組使用者
App.api.user.deleteUsers = function(){
    
};

3. 效能

3.1. 注意作用域

  • 避免全域性查詢
  • 避免with語句

3.2. 選擇正確的方法

  • 優化迴圈

    • 減值迭代:從最大值開始,在迴圈中不斷減值的迭代器更加高效
    • 簡化終止條件:由於每次迴圈過程都會計算終止條件,所以必須保證它儘可能快。也就是避免其他屬性查詢
    • 簡化迴圈體:由於迴圈體是執行最多的,所以要確保其最大限度地優化。
  • 展開迴圈
  • 避免雙重解釋:
// **Bad** 某些程式碼求值
eval("alert(`hello`)");

// **Bad** 建立新函式
var sayHi = new Function("alert(`hello`)");

// **Bad** 設定超時
setTimeout("alert(`hello`)");
  • 效能的其他注意事項

    • 原生方法較快
    • switch語句較快:可以適當的替換ifelse語句case 的分支不要超過128條
    • 位運算子較快

3.3. 最小化語句數

3.3.1. 多個變數宣告(廢棄)

// 方式1:Bad
var count = 5;
var name = `wdd`;
var sex = `male`;
var age = 10;

// 方式2:Good
var count = 5,
    name = `wdd`,
    sex = `male`,
    age = 10;

2017-03-07 理論上方式2可能要比方式1效能高一點。但是我在實際使用中,這個快一點幾乎是沒什麼感受的。就像你無法感受到小草的生長一樣。反而可讀性更為重要。所以,每行最好只定義一個變數,並且每行都有一個var,並用分號結尾。

3.3.2. 插入迭代值

// Good
var name = values[i++];

3.3.3. 使用陣列和物件字面量

// Good
var values = [`a`,`b`,`c`];

var person = {
    name:`wdd`,
    age:10
};

只要有可能,儘量使用陣列和物件字面量的表示式來消除不必要的語句

3.4. 優化DOM互動

在JavaScript各個方面中,DOM無疑是最慢的一部分。DOM操作與互動要消耗大量的時間。因為他們往往需要重新渲染整個頁面或者某一部分。進一步說,看似細微的操作也可能花很久來執行。因為DOM要處理非常多的資訊。理解如何優化與DOM的互動可以極大的提高指令碼完成的速度。

  • 使用dom快取技術
  • 最小化現場更新
  • 使用innerHTML插入大段html
  • 使用事件代理

3.4.1. Dom快取技術

呼叫頻率非常高的dom查詢,可以將DOM快取在於一個變數中

// 最簡單的dom快取

var domCache = {};

function myGetElement(tag){
    return domCache[tag] = domCache[tag] || $(tag);
}

3.5. 避免過長的屬性查詢,設定一個快捷方式

// 先看下面的極端情況
app.user.mother.parent.home.name = `wdd`
app.user.mother.parent.home.adderess = `上海`
app.user.mother.parent.home.weather = `晴天`

// 更優雅的方式
var home = app.user.mother.parent.home;
home.name = `wdd`;
home.address = `上海`,
home.weather = `晴天`

注意
使用上面的方式是有前提的,必須保證app.user.mather.parent.home是一個物件,因為物件是傳遞的引用。如果他的型別是一個基本型別,例如:number,string,boolean,那麼複製操作僅僅是值傳遞,新定義的home的改變,並不會影響到app.user.mather.parent.home的改變。

4. 快捷方式

4.1. 字串轉數字

+`4.1` === 4.1

4.2. 數字轉字元

4.1+`` === `4.1`

4.3. 字串取整

`4.99` | 0 === 4

5. 通用編碼原則

建議讀者自行擴充套件

  • DRY(dont`t repeat yoursele: 不要重複你自己)
  • 高內聚低耦合
  • 開放閉合
  • 最小意外
  • 單一職責(single responsibility)

6. 高階技巧

6.1. 安全型別檢測

  • javascript內建型別檢測並不可靠
  • safari某些版本(<4)typeof正規表示式返回為function

建議使用Object.prototype.toString.call()方法檢測資料型別

function isArray(value){
    return Object.prototype.toString.call(value) === "[object Array]";
}

function isFunction(value){
    return Object.prototype.toString.call(value) === "[object Function]";
}

function isRegExp(value){
    return Object.prototype.toString.call(value) === "[object RegExp]";
}

function isNativeJSON(){
    return window.JSON && Object.prototype.toString.call(JSON) === "[object JSON]";
}

對於ie中一COM物件形式實現的任何函式,isFunction都返回false,因為他們並非原生的javascript函式。

在web開發中,能夠區分原生與非原生的物件非常重要。只有這樣才能確切知道某個物件是否有哪些功能

以上所有的正確性的前提是:Object.prototype.toString沒有被修改過

6.2. 作用域安全的建構函式

function Person(name){
    this.name = name;
}

//使用new來建立一個物件
var one = new Person(`wdd`);

//直接呼叫建構函式
Person();

由於this是執行時分配的,如果你使用new來操作,this指向的就是one。如果直接呼叫建構函式,那麼this會指向全域性物件window,然後你的程式碼就會覆蓋window的原生name。如果有其他地方使用過window.name, 那麼你的函式將會埋下一個深藏的bug。

那麼,如何才能建立一個作用域安全的建構函式?

function Person(name){
    if(this instanceof Person){
        this.name = name;
    }
    else{
        return new Person(name);
    }
}

6.3. 惰性載入函式

假設有一個方法X,在A類瀏覽器裡叫A,在b類瀏覽器裡叫B,有些瀏覽器並沒有這個方法,你想實現一個跨瀏覽器的方法。

惰性載入函式的思想是:在函式內部改變函式自身的執行邏輯

function X(){
    if(A){
        return new A();
    }
    else{
        if(B){
            return new B();
        }
        else{
            throw new Error(`no A or B`);
        }
    }
}

換一種寫法

function X(){
    if(A){
        X = function(){
            return new A();
        };
    }
    else{
        if(B){
            X = function(){
                return new B();
            };
        }
        else{
            throw new Error(`no A or B`);
        }
    }
    
    return new X();
}

6.4. 防篡改物件

6.4.1. 不可擴充套件物件 Object.preventExtensions

// 下面程式碼在谷歌瀏覽器中執行
> var person = {name: `wdd`};
undefined
> Object.preventExtensions(person);
Object {name: "wdd"}
> person.age = 10
10
> person
Object {name: "wdd"}
> Object.isExtensible(person)
false

6.4.2. 密封物件Object.seal

密封物件不可擴充套件,並且不能刪除物件的屬性或者方法。但是屬性值可以修改。

> var one = {name: `hihi`}
undefined
> Object.seal(one)
Object {name: "hihi"}
> one.age = 12
12
> one
Object {name: "hihi"}
> delete one.name
false
> one
Object {name: "hihi"}

6.4.3. 凍結物件 Object.freeze

最嚴格的防篡改就是凍結物件。物件不可擴充套件,而且密封,不能修改。只能訪問。

6.5. 高階定時器

6.5.1. 函式節流

函式節流的思想是:某些程式碼不可以沒有間斷的連續重複執行

var processor = {
    timeoutId: null,

    // 實際進行處理的方法
    performProcessing: function(){
        ...
    },

    // 初始化呼叫方法
    process: function(){
        clearTimeout(this.timeoutId);

        var that = this;

        this.timeoutId = setTimeout(function(){
            that.performProcessing();
        }, 100);
    }
}

// 嘗試開始執行
processor.process();

6.5.2. 中央定時器

頁面如果有十個區域要動態顯示當前時間,一般來說,可以用10個定時來實現。其實一箇中央定時器就可以搞定。

中央定時器動畫 demo地址:http://wangduanduan.coding.me…

var timers = {
        timerId: 0,
        timers: [],
        add: function(fn){
            this.timers.push(fn);
        },
        start: function(){
            if(this.timerId){
                return;
            }

            (function runNext(){
                if(timers.timers.length > 0){
                    for(var i=0; i < timers.timers.length ; i++){
                        if(timers.timers[i]() === false){
                            timers.timers.splice(i, 1);
                            i--;
                        }
                    }

                    timers.timerId = setTimeout(runNext, 16);
                }
            })();
        },
        stop: function(){
            clearTimeout(timers.timerId);
            this.timerId = 0;
        }
    };

7. 函數語言程式設計

推薦閱讀:JS函數語言程式設計中文版

8. HTML的告誡

  • 使用input的時候,一定要加上maxlength屬性。(你以為只需要輸入一個名字的地方,使用者可能複製一篇文章放進去。)
  • 從input取值的時候,最好去除一下首尾空格

9. ajax的告誡

ajax在使用的時候,例如點選按鈕,獲取某個列表。需要注意以下方面

  1. ajax請求還沒有結束時,按鈕一定要disabled,防止多次點選。請求結束時,才去掉按鈕的disabled屬性。
  2. 請求沒結束的時候,一定要顯示一個gif的動畫,告訴使用者請求還在loading。不要讓使用者以為這垃圾程式又卡死了。
  3. 請求的結果如果是空的,一定要告訴使用者: 很抱歉,暫時沒有查詢到相關記錄之類的話語。不要給一個空白頁面給使用者。
  4. 最好考慮到請求報錯的情況,給出友好的錯誤提醒。

10. 程式碼整潔之道

10.1. 函式整潔

  • 儘量將所有程式碼封裝在函式中,不要暴露全域性變數
  • 每個函式的函式體中,程式碼行越少越好,最好一個函式中就一句程式碼

11. 工程化與模組化

11.1. 前端構建工具必不可少

11.1.1. webpack

11.1.2. rollup

11.1.3. parcel

12. 協議 TCP IP HTTP

如果你認為前端不需要關於協議的知識,那麼你就是大錯特錯了。其實不僅僅是前端,所有的開發者都應該學習底層的協議。因為他們是網際網路通訊的基石。

推薦三本必讀的書籍

或者你一也可以看看關於協議方面的一些問題,以及如果你遇到過,你是否知道如何解決:

13. 推薦深度閱讀

13.1. 推薦閱讀技術書籍

13.2. 推薦閱讀線上文章

13.3. 技術之外

14. 參考文獻

相關文章