天天看點

[Effective JavaScript 筆記]第6章:庫和API設計--個人總結

前言

又到了一章的總結,這章裡的内容。是把我從一個代碼的使用者,如何換位成一個代碼的編寫者。如何讓别人用自己的代碼更容易,不用去注意太多的無用細節,不用記住冗長的函數名。在使用API時怎樣避免使用者會出現了解的偏差。如何處理一些特殊敏感的值,參數如何設定可以更好地自說明,如何減少API對狀态的依賴,如何使API更加靈活,更利于使用者的編寫。下面一一展開介紹,對應的也會說明每條對應希望給到的是哪方面的建議!

第53條:保持一緻的約定

個人總結

不要去創造一個獨特的API,要使用一些大家慣用的詞彙,參數。比如使用統一的命名規範,相似屬性設定相同的屬性順序。

命名規範

有好多種,比如:

  • 構造函數首字母大寫。如MyClass
  • 普通函數,原型方法,變量,小寫字母開始,其他單詞首字母大寫。如compareFn,a.setValue(),goodName。
  • 常量及全局變量,為全部字母大寫。如PI=3.1415
  • 私有原型方法及屬性,為_開頭。如 _good, _getName()

    像以上的内容都是在js編寫過程中的慣用法,當看到上面的命名時,自然而然會知道表達的是什麼意思。

參數的設定

比如我們在使用jquery裡的一個函數時,一些函數在傳入一個參數,是取值操作。傳入二個參數時是付值操作。

$('.a').css('width');
$('.a').css('width',100);

$('a').attr('href');
$('a').attr('href','http://wengxuesong.cnblogs.com');
           

從上面也可以看出,這些函數在第一個參數傳入的都是一個可以設定和擷取的屬性值。而第二個參數都是一個要使用的值。當我們遇到類似的函數時,我們在使用的時候就可以很快使用了。比如

$('.b').data('name');
$('.b').data('name','wxs');
           

很快就知道上面這個的意思啦~

而且使用的函數都是很容易記憶并符合函數意義的簡單詞彙。

可以看出,上面這樣的一些設定,可以讓我們解決前言中提到的一些問題。

如何讓别人用自己的代碼更容易,不用去注意太多的無用細節,不用記住冗長的函數名

提示

  • 在變量命名和函數簽名中使用一緻的約定
  • 不要偏離使用者在他們的開發平台中很可能遇到的約定

第54條:将undefined看做“沒有值”

由于産生undefined的情況很多,是以在對待undefined的時候,想很好地區分它代表的真正的意義并不太容易。

産生undefined值的幾種情況

  • 變量聲明後沒有指派
  • 函數參數沒有傳入真正的實參數,形參的值
  • 函數運作沒有傳回值或直接return時,函數的運作結果的值
  • 對象不存在的屬性的值

不能把undefined做為函數判斷的特殊值進行處理,因為函數無法判定,這個參數是真正傳遞的為undefined,還是表達式運作的結果。要undefined隻做為一個“沒有值”來對待。如果要特殊處理一種情況,可以使用選項對象做為參數。

可選參數

  • 可選參數在未輸入值時是undefined。這時的arguments.length不計入這個參數。
  • 當可選參數傳入一個值為undefined的值時。這時的arguments.length會計入這個參數。
function Server(port,hostname){
  if(arguments.length < 2){
    hostname='localhost'
  }
  hostname=''+hostname;
  return 'http://'+hostname+':'+port;
}
var s1=new Server(80,'cnblogs.com');//"http://cnblogs.com:80"var s2=new Server(80);//"http://localhost:80"var s3=new Server(80,undefined);//"http://undefined:80"           

使用arguments.length來對函數可選參數進行判斷會導緻錯誤。應該對可選參數與undefined來比較來實作功能。

function Server(port,hostname){
  if(hostname===undefined){
    hostname='localhost'
  }
  hostname=''+hostname;
  return 'http://'+hostname+':'+port;
}
var s1=new Server(80,'cnblogs.com');//"http://cnblogs.com:80"var s2=new Server(80);//"http://localhost:80"var s3=new Server(80,undefined);//"http://localhost:80"           

隐式類型轉換

在隐式類型轉換為真時,undefined會轉換為false。但除undefined外還有很多值也可以轉換為false,如:null,""(空字元串),0和NaN。

在函數參數中使用真值測試時,設定預設值時。如果上面的0或空字元串為合法值時,就不能用真值來進行判斷。

function W(w){
  this.w=w||100;
}
var sw=new W(0);
sw.w;//100           

上面如果傳入0,則得到一個期望寬度為0的對象。

這種情況下必須顯示地判斷傳入的值是否為undefined。

function W(w){
  this.w=w===undefined?100:w;
}
var sw=new W(0);
sw.w;//0           

這條解決了如何去處理一些特殊敏感的值。

  • 避免使用undefined表示任何值
  • 使用描述性的字元串值或命名布爾屬性的對象,而不要使用undefined或null來代表特定應用标志
  • 提供參數預設值應當采用測試undefined的方式,而不是檢查arguments.length
  • 在允許0、NaN或空字元串為有效參數的地方,絕不要通過真值測試來實作參數預設值

第55條:接收關鍵字參數的選項對象

總結起來就是,當函數接收很多參數時,參數的順序無法改變(這裡也稱為位置參數),導緻有些不需要的值也得傳入,沒法處理多個參數可選的情況。比如

function aaa(a,b,c){
  //b,c都是可選的
}
aaa('a',,'c')
           

如上,如果我b不想傳遞隻想傳入a,c也必須傳入把b傳遞進去。要不裡面的代碼就會出錯。

上面的代碼也記憶也是一種挑戰,裡面的每個參數應該對應什麼樣的值。

面對這種情況,可以選擇使用選項對象。

選項對象

使用選項對象,選項對象裡的屬性名可以很好說明參數的作用。選項對象參數,隻處理非必選的參數。對于非必選參數,可以提供一些預設值。

  • 如果僅包括可選參數。可能會省略掉所有的參數,選項對象包括所有參數。
  • 如果有一個或兩個必選參數,使它們獨立于選項對象。

使用每一項值的對于選項對象的預設值的設定。需要檢測所有的非必選的參數項值。這個工作并不好做,需要去處理真值測試,或排除合理值的情況等問題。這樣做起來比使用位置參數的方法更加煩瑣。可以使用對象的覆寫處理方法,把對象覆寫的處理抽象出來,然後調用,這樣就可以使代碼的邏輯更加清晰。比如

function A(parent,message,opts){
  opts=extend({
    width:320,
    height:240
  });
  opts=extend({
    x:10,
    y:10,
    title:'alert',
    modal:false
  },opts);
  extend(this,opts);
}
           

抽象出來的擴充函數為

function extend(target,source){
  if(source){
    for(var key in source){
      var val=source[key];
      if(typeof val !== 'undefined'){
        target[key]=val;
      }
    }
  }
  return target;
}
           

一緻性是庫設計的目标,可以給API的使用者更好地預測它的功能及使用方法。

這條解決了使用API時怎樣避免使用者會出現了解的偏差。如何處理一些特殊敏感的值,參數如何設定可以更好地自說明。

  • 使用選項對象使得API更具有可讀性、更容易記憶
  • 所有通過選項對象提供的參數應當被視為可選的
  • 使用extend函數抽象出從選項對象中提取值的邏輯

第56條:避免不必要的狀态

API分為兩類:有狀态的和無狀态的。無狀态的API相當于純函數,行為隻取決于輸入,與程式所處的環境狀态無關。有狀态的方法,對于同一個方法可能傳回不同的結果。主要取決于方法所處的狀态。

無狀态的API更容易學習和使用,因為所有的行為都是函數本身決定的,隻要查詢相關函數的代碼就能知道輸出是否正确。

有狀态的API處于不同的狀态有可能會産生不同的結果。導緻在記憶和使用時都要記住額外的資訊。狀态的改變也常常會帶來代碼的耦合度更高。無狀态的API可以使代碼更加子產品化,避免與其它代碼産生耦合。

在設計API時,無狀态的API更好。

  • 盡可能地使用無狀态的API
  • 如果API是有狀态的,标示出每個操作與哪些狀态有關聯

第57條:使用結構類型設計靈活的接口

結構類型(鴨子類型):任何對象隻要具有預期的結構就屬于該類型。這個類似于強類型面向對象語言裡說的接口,也就是面向接口程式設計。

結構類型可以有利于單元測試,可以很容易去實作一個測試的資料結構。

結構類型也可以使代碼各部分解耦,代碼的依賴隻是通過結構類型。

在實作代碼時,不用去管結構類型最終的實作細節,隻要提供對應的方法及屬性,那麼程式就可以正常運作。

  • 使用結構類型來設計靈活的對象接口
  • 結構接口更靈活、更輕量,是以應該避免使用繼承
  • 針對單元測試,使用mock對象即接口的替代實作來提供可複驗的行為

第58條:區分數組對象和類數組對象

分離數組對象

數組對象和類數組對象,使用類型判斷可以直接區分。

var a=[];
var b={0:1,length:1};
var toString=({}).toString;
toString.call(a);//"[object Array]"
toString.call(b);//"[object Object]"           

這樣一目了然。但這與js的靈活的類數組對象的概念有争執,因為任何對象都可被視為數組,隻要它遵循正确的接口。上一條使用結構類型設計靈活的接口。靈活的結構隻要一個資料符合相應的接口,就可以把它視為正确的資料。

重載

重載兩種類型意味着必須有一種方法來區分兩種不同情況。如果出現了兩種情況的重疊區域,則無法對API進行重載。

API絕不應該重載與其他類型有重疊的類型。

Array.isArray函數

這個函數測試一個值是否是數組,而不管原型繼承。

var a={};
var b=[];
var c=10;
Array.isArray(a);//falseArray.isArray(b);//trueArray.isArray(c);//false           

可以直接區分,數組與類數組。

類數組轉化為數組

var slice=[].slice;
var b={0:10,1:'good',length:2};
slice.call(b);//[10,'good']           

如果對API的傳入參數有特殊指定要求的,需要在文檔中注明,API的重載也必須要注明,不同情況的參數要求。

  • 絕不重載與其他類型有重疊的結構類型
  • 當重載一個結構類型與其他類型時,先測試其他類型
  • 當重載其他對象類型時,接收真數組而不是類數組對象
  • 文檔标注你的API是否接收真數組或類數組值
  • 使用ES5提供的Array.isArray方法測試真數組

第59條:避免過度的強制轉換

重載和強制轉換

重載基于類型的判斷,而強制轉換使參數類型資訊丢失,導緻結果和預期不同。

在使用參數類型來作為重載依據時,應該避免強制轉換。

重載時通過對參數的類型進行強制要求,來實作API的設計,這樣代碼更謹慎。

防禦性程式設計

以額外的檢查來抵禦潛在的錯誤。抵禦所有的錯誤是不可能的。除js中提供的基本檢查工具外,可以通過編寫一些簡潔的檢查工具函數來輔助開發。

額外的代碼會影響程式的性能,也可以更早地捕獲錯誤。看具體情況來使用防禦性程式設計。

  • 避免強制轉換和重載的混用
  • 考慮防禦性地監視非預期的輸入

第60條:支援方法鍊

js裡自帶的方法鍊式調用,如字元串的replace方法

function escapeBasicHTML(str){
    return str.replace(/&/g,"&amp;")
              .replace(/< /g,"&lt;")
              .replace(/>/g,"&gt;")
              .replace(/"/g,"&quot;")
              .replace(/'/g,"&apos;");
}
           

數組方法

var users=records.map(function(record){
    return record.username;
})
.filter(function(username){
    return !!username;
})
.map(function(username){
    return username.toLowerCase();
});
           

這些都可以寫成傳統的方式,把每次的傳回值儲存到中間變量。

實作方法鍊的關鍵是,每次都傳回下一個方法的對象。

無狀态的API中,可以傳回一個新對象,則鍊式得到了自然的結果。像上面的replace方法,傳回的是一個新的字元串對象。

有狀态的API也值得使用,這時方法鍊被稱為流暢式。這個好舉例子,如jQuery裡的方法操作。

$('body').html('good').addClass('y');
           

方法鍊的書寫風格,需要代碼進行一定的處理才能支援(傳回對象自身)。方法鍊的代碼都可以改寫成傳統的風格。

  • 使用方法鍊來連接配接無狀态的操作
  • 通過在無狀态的方法中傳回新對象來支援方法鍊
  • 通過在有狀态的方法中傳回this來支援方法鍊

總結

縱觀這一章,從比寫具體代碼更高的一個層次給了指導。前前後後,每條都提出了,文檔很重要。有特殊要求和慣用法不同的都要文檔說明。

  • 在處理API接口命名時,保持命名約定一緻,不偏離使用者習慣。
  • 處理undefined時要特别對象,不能簡單處理。
  • 參數使用選項參數,說明性更強,更容易記憶和使用。
  • 設計無狀态的API接口,更容易子產品化和使用。
  • 面向結構類型程式設計,API更靈活。
  • 函數重載要注意不同情況,不能有重疊區域。
  • 強制轉換會破壞依賴類型判斷的重載。
  • 支援方法鍊,使代碼書寫起來更流暢。

倒數第二章了,還有一章就要完事了。這一章的内容雖然硬知識點沒有多少,但我看起來是這幾章裡就吃力的,因為找不到着力點。編碼的多少直接就決定了對這章各條的了解程度,這章可以在寫一些代碼後回來再反複看一下,掌握後就内化為自己的能力啦。

版權聲明

翻譯的文章,版權歸原作者所有,隻用于交流與學習的目的。

原創文章,版權歸作者所有,非商業轉載請注明出處,并保留原文的完整連結。

繼續閱讀