前端乾貨之JS最佳實踐

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

1. 風格

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

我本身使用的是JavaScript Standard Style, 我之因此使用這個,是由於它有一些工具。可讓你寫完代碼後,一旦保存,就自動幫你把你的風格的代碼修正成標準分割,而不是死記硬背應該怎麼寫。看完這個頁面,你就應該立馬愛上JavaScript Standard Style , 若是你用vscode, 剛好你有寫vue, 你想在.vue文件中使用standard風格,那麼你須要看看這篇文章php

2. 可維護性

不少時候,咱們不是從零開始,開發新代碼。而是去維護別人的代碼,以他人的工做成果爲基礎。確保本身的代碼可維護,是贈人玫瑰,手留餘香的好事。一方面讓別人看的舒服,另外一方面也防止本身長時間沒看過本身的代碼,本身都難以理解。

2.1. 什麼是可維護代碼

可維護的代碼的一些特徵css

  • 可理解易於理解代碼的用途
  • 可適應數據的變化,不須要徹底重寫代碼
  • 可擴展要考慮將來對核心功能的擴展
  • 可調試給出足夠的信息,讓調試的時候,肯定問題所在
  • 不可分割函數的功能要單一,功能粒度不可分割,可複用性加強

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,有一些經常使用的名詞與其對應html

含義 請求方法 詞語 栗子
增長 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分別放置在不一樣的文件夾下。vue

2.2.5. 千萬別用alert

alert的缺點java

  • 若是你用alert來顯示提醒消息,那麼用戶除了點擊alert上的的肯定按鈕外,就只能點擊上面的關閉,或者選擇禁止再選擇對話框,除此之外什麼都不能操做。
  • 有些瀏覽器若是禁止了alert的選項,那麼你的alert是不會顯示的
  • 若是你在try catch語句裏使用alert,那麼console裏將不會輸出錯誤信息,你都沒辦法查看錯誤的詳細緣由,以及儲出錯的位置。

更優雅的提醒方式webpack

  • 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
    ...
    ...
    ...
    //
});

若是增長了需求,當點擊另一個按鈕的時候,也要執行獲取全部用戶並顯示在頁面上,那麼上面省略的代碼又要複製一份。若是接口有改動,那麼須要在兩個不一樣的地方都要修改。
因此,應該這樣。git

$(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;
    }
    
    //對數據的處理
    ...
    ...
    ...
});

上面代碼對數據的處理直接寫死在異步請求裏面,若是換了一個請求,可是數據處理方式是同樣的,那麼又要複製一遍數據處理的代碼。最好的方式是將數據處理模塊化成爲一個函數。github

// 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. 參考文獻

相關文章
相關標籤/搜索