首頁  >  文章  >  web前端  >  設計與建立自己的JavaScript程式庫:提示與技巧

設計與建立自己的JavaScript程式庫:提示與技巧

黄舟
黄舟原創
2017-02-22 13:48:531324瀏覽

程式碼庫:我們一直在使用它們。程式碼庫是開發者把他們會在專案中使用到的程式碼打包起來形成的,這總是可以節省時間和避免重複造輪子。擁有一個可重複使用的包,不管是開源的還是閉源的,總比重複構建一樣特性的包或者從過去的項目中手動複製粘貼要好。

更多來自作者的文章

  • Stop Maiming Bodies: The Perils of Pixel Font-Size

  • ES2016: Should the Future of JavaScript Be Developer-Driven?

#除了被包裝好的程式碼,還可以更精確的形容程式碼庫嗎?除了少數的例外,程式碼庫通常只是一個文件,或是在同一個資料夾裡的幾個文件。它的程式碼應該可以單 獨保存和在你的專案中正常使用。庫文件允許你根據項目的不同來調整結構或行為。想像一下只能透過USB介面進行通訊的USB設備。一些設備,例如滑鼠和 鍵盤,可以透過設備提供的介面來進行配置。

在這篇文章,我會解釋如何建立庫檔案。儘管大部分的方法可以應用到其他語言,但這篇文章主要講述的是建立JavaScript庫檔案。

為什麼要建立自己的Javascript函式庫?

首先和最重要的,函式庫檔案可以讓現有的程式碼方便的重複利用。你不需要挖出陳舊的項目來複製文件,只需要引入庫文件。這也可以讓你的應用程式元件化,讓應用程式的程式碼庫更小更容易維護。

设计和构建你自己的JavaScript代码库:提示与技巧

Christ Church Library (source)

任何可以讓實作一個具體的功能更容易或可以重複使用的抽象的程式碼,都可以被打包進去庫文件。 jQuery就是一個有趣的例子。儘管jQuery的API有大量的簡化的DOM API,在跨瀏覽器DOM操作比較困難的過去有著相當重要的意義。

如果一個開源專案變得流行並且讓許多開發者使用它,人們很有可能透過提出問題或貢獻程式碼來參加它的開發。不管怎樣,都對函式庫和依賴它的項目有所幫助。

一個受歡迎的開源函式庫也會帶來很好的機會。公司可能會對你的工作品質表示認可並給你offer。可能公司會要求你把你的專案整合進他們的應用。畢竟,沒人可以比你更了解你的專案。

當然它可能只是一個習慣——享受敲碼,幫助他人並在這個過程中學習和成長。你可以提升你的極限和嘗試新的東西。

範圍和目標

在寫下第一行程式碼之前,你需要確定你的函式庫的功能-你需要設定一個目標。透過這個目標,你可以專注於你想利用這個函式庫來解決的問題。要牢記你的程式碼庫的原本的形式在解決問題上要更容易使用和記憶。 API越簡單,使用者學習你的程式碼庫就更容易。介紹一個Unix的設計哲學:

只做一件事情並把它做好

問下你自己:你的程式碼庫解決了什麼問題?你打算怎麼取解決它?你會一個人完成全部工作,還是引入別人的程式碼庫?

不管程式碼庫體積有多大,試著製作一個路線圖。列出你想要的每一個特性,並將它們盡可能的打散,知道你有一個小巧但是能解決問題的程式碼庫,就像 minimum viable product。這會成為你的第一個版本。從這裡開始,你可以在每一個新特性建立里程碑。從本質上來說,你把你的專案變成了比特級的程式碼區塊,讓每個特性完成的更好和更有趣。相信我,這會讓你保持狀態良好。

API設計

在我看來,我想以使用者的角度來開發我的程式碼庫。你可以稱呼它為以使用者為中心的設計。在本質上,你正在創造你的程式碼庫的大綱,給予它更多的思考和讓選擇它的人使用起來更方便。同時,你需要思考在什麼地方需要可以定制,這會在這篇文章的後面討論。

終極的API測試是嘗試自己的技術,在你的專案中使用你的程式碼庫。試著用你的程式碼庫取代之前的程式碼,看看它是否滿足了你想要的功能。試著讓你的程式碼庫盡可能的直觀,讓它可以更靈活的在邊界條件下使用,還有客製化(在之後的文章會講述)。

這是一個關於使用者代理字串的程式碼庫的大綱的可能會有樣子:

// Start with empty UserAgent string var userAgent = new UserAgent; 
// Create and add first product: EvilCorpBrowser/1.2 (X11; Linux; en-us) var application = new UserAgent.Product('EvilCorpBrowser', '1.2');
application.setComment('X11', 'Linux', 'en-us');
userAgent.addProduct(application); // Create and add second product: Blink/20420101 var engine = new UserAgent.Product('Blink', '20420101');
userAgent.addProduct(engine); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 userAgent.toString(); 
// Make some more changes to engine product engine.setComment('Hello World'); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 (Hello World) userAgent.toString();

根據你的程式碼的複雜度,你可能會在組織結構上花費一些時間。利用設計模式是組織你的程式碼庫的好方法,甚至可以解決一些技術問題。這也能避免加入新特性所帶來的大面積重構。

靈活性和自訂性

靈活性是讓程式碼庫變得強大的要素,但是確定可以自訂與不能自訂的界限是很困難的。 chart.js 和D3.js是很好的例子。這兩個程式碼庫都是用來進行資料視覺化的。 Chart.js可以很簡單的創建不同形式的內建圖表。但如果你想對影像進行更多的掌控,D3.js才是你需要的。

有好幾種方法可以把控制權交給使用者:配置,暴露公共方法,透過回呼和事件。

配置程式碼庫通常在初始化之前完成。但是一些程式碼庫允許尼在運行時對配置進行修改。配置通常被細小的部分限制,只有為了之後的使用而修改它們的數值才是被允許的。

// Configure at initialization var userAgent = new UserAgent({
  commentSeparator: ';' }); // Run-time configuration using a public method userAgent.setOption('commentSeparator', '-'); 
  // Run-time configuration using a public property userAgent.commentSeparator = '-';

方法通常是暴露给实例使用的,比如说从实例中获取数据,或者设置实例的数据和执行操作。

var userAgent = new UserAgent; // A getter to retrieve comments from all products userAgent.getComments(); 
// An action to shuffle the order of all products userAgent.shuffleProducts();

回调通常是在公共的方法中被传递的,通常在异步操作后执行用户的代码。

var userAgent = new UserAgent;

userAgent.doAsyncThing(function asyncThingDone() { // Run code after async thing is done });

事件有很多种可能。有点像回调,除了增加事件句柄是不应该触发操作的。事件通常用于监听,你可能会猜到,这可是事件!更像回调的是,你可以提供更多的信息和返回一个数值给代码库去进行操作。

var userAgent = new UserAgent; // Validate a product on addition userAgent.on('product.add', function onProductAdd(e, product) { 
var shouldAddProduct = product.toString().length < 5; // Tell the library to add the product or not return shouldAddProduct;
});

在一些例子中,你可能允许用户对你的代码库进行扩展。因此,你需要暴露一些公共方法或者属性来让用户填充,像Angular的模块 (angular.module('myModule'))和Jquery的 fn(jQuery.fn.myPlugin)或者什么都不做,只是简单的让用户获取你的代码库的命名空间:

// AngryUserAgent module // Has access to UserAgent namespace (function AngryUserAgent(UserAgent) { 
// Create new method .toAngryString() UserAgent.prototype.toAngryString = function() { return this.toString().toUpperCase();
  };

})(UserAgent); // Application code var userAgent = new UserAgent; // ... // EVILCORPBROWSER/1.2 (X11; LINUX; EN-US) BLINK/20420101 userAgent.toAngryString();

类似的,这允许你重写方法。

// AngryUserAgent module (function AngryUserAgent(UserAgent) { // Store old .toString() method for later use var _toString = UserAgent.prototype.toString; 
// Overwrite .toString() UserAgent.prototype.toString = function() { return _toString.call(this).toUpperCase();

  };

})(UserAgent); var userAgent = new UserAgent; // ... // EVILCORPBROWSER/1.2 (X11; LINUX; EN-US) BLINK/20420101 userAgent.toString();

在后面的例子中,允许你的用户获取代码库的命名空间,让你在对扩展和插件的定义方面上的控制变小了。为了让插件遵循一些约定,你可以(或者是应该)写下文档。

测试

对测试驱动开发(test-driven development)来 说,写下大纲是良好的开始。简单来说,指的是在你写实际的代码库之前,在你写下测试准则的时候。如果测试检查的是你的代码特性是否跟期待的一样,以及你在 写代码库之前写测试,这就是行为驱动开发。不管怎样,如果你的测试覆盖了你的代码库的每一个特性,而且你的代码通过了所有的测试。你可以确定你的代码是可 以正常工作的。

Jani Hartikainen讲述了如何利用Mocha来进行单元测试 Unit Test Your JavaScript Using Mocha and Chai。在使用Jsmine,Travis,Karma测试JavaScript (Testing JavaScript with Jasmine, Travis, and Karma)这篇文章中,Tim Evko展示了怎么通过另一个叫做Jasmine的框架来设置良好的测试流程。这两个测试框架都是非常流行的,但还有适应别的需求的其他框架。

我在这篇文章前面撰写的大纲,已经讲述了它期待怎样的输出。这是一切测试的开始:从期望出发。关于我的代码库的一个Jasmine测试像是这样:

describe(&#39;Basic usage&#39;, function () {
  it(&#39;should generate a single product&#39;, function () { // Create a single product var product = new UserAgent.Product(&#39;EvilCorpBrowser&#39;, &#39;1.2&#39;);
    product.setComment(&#39;X11&#39;, &#39;Linux&#39;, &#39;en-us&#39;);

    expect(product.toString())
      .toBe(&#39;EvilCorpBrowser/1.2 (X11; Linux; en-us)&#39;);
  });

  it(&#39;should combine several products&#39;, function () { var userAgent = new UserAgent; 
  // Create and add first product var application = new UserAgent.Product(&#39;EvilCorpBrowser&#39;, &#39;1.2&#39;);
    application.setComment(&#39;X11&#39;, &#39;Linux&#39;, &#39;en-us&#39;);
    userAgent.addProduct(application); // Create and add second product var engine = new UserAgent.Product(&#39;Blink&#39;, &#39;20420101&#39;);
    userAgent.addProduct(engine);

    expect(userAgent.toString())
      .toBe(&#39;EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101&#39;);
  });

  it(&#39;should update products correctly&#39;, function () { var userAgent = new UserAgent; 
  // Create and add first product var application = new UserAgent.Product(&#39;EvilCorpBrowser&#39;, &#39;1.2&#39;);
    application.setComment(&#39;X11&#39;, &#39;Linux&#39;, &#39;en-us&#39;);
    userAgent.addProduct(application); // Update first product application.setComment(&#39;X11&#39;, &#39;Linux&#39;, &#39;nl-nl&#39;);

    expect(userAgent.toString())
      .toBe(&#39;EvilCorpBrowser/1.2 (X11; Linux; nl-nl)&#39;);
  });
});

一旦你对你的API设计的第一个版本完全满意,是时候开始思考结构和你的代码库应该如何被使用。

模块加载器兼容性

你或许使用过模块加载器。使用你的代码库的开发者有可能使用加载器,所以你会希望自己的代码库与模块加载器是兼容的。但兼容哪一个呢?应该怎么从CommonJS,RequireJS,AMD和其他加载器中挑选呢?

實際上,你不需要挑選!通用模組定義(UMD)是一個目標就是支援多種載入器的規則。你可以在網路上找到不同風格的程式碼段,或從這裡 UMD GitHub repository學習並讓它與你的程式碼庫相容。從其中的某個模板開始,或者使用你喜歡的構建工具add UMD with your favorite build tool,你就不必再擔心模組加載器的問題了。

如果你希望使用ES2015的 import/export 語法,我建議使用Babel和Babel’s UMD plugin將程式碼轉換成ES5。透過這個方法你可以在你的專案中使用ES2015,同時產生相容性良好的程式碼庫。

文件

我完全贊成在每個專案中使用文件。但這通常牽涉到大量的工作,導致編寫文件被推遲和在最後被遺忘。

基本資訊

文件的編寫應當從專案的名字和描述之類基本的資訊開始。這會對別人明白你的程式碼庫做了什麼和是否對他們有用有所幫助。

你可以提供像是適用範圍和目標之類的信息來更好的給用戶提供信息,而提供路線圖可以讓他們明白在未來可能會有什麼新變化以及他們可以提供怎樣的幫助。

API,教學與範例

當然,你需要確保使用者知道如何去使用你的程式碼庫。這從API文件開始。教程和例子是很好的補充,但編寫他們會是一個龐大的工作。然而,內嵌文件Inline documentation不會這樣。以下是一些利用JSDoc的,可以被解析和轉換成文檔頁的註釋

元任務

一些用戶想對你的程式碼庫作出改進。在大多數情況中,會是貢獻程式碼,但有一些會創建一個私人使用的定製版本。對於這些用戶,為類似建立程式碼庫,運行測試,生成,轉換和下載資料之類的元任務提供文件很有幫助的。

貢獻

當你對你的程式碼庫進行開源,得到程式碼貢獻是很有幫助的。為了引導貢獻者,你可以增加一些關於貢獻程式碼的步驟和需要滿足的標準的文件。這會對你審閱和接納貢獻的程式碼和他們正確的貢獻代碼有所幫助。

授權許可

最後一點,使用授權許可。從技術上講,即時你沒有選擇任何一種技術許可,你的程式碼庫仍然是擁有版權的,但不是每個人都知道這一點。

我發現 ChooseALicense.com這個網站可以讓你一個不需要成為法律專家也能挑選授權許可。在挑選授權許可之後,只需要在專案的根目錄下新增 LICENSE.txt 檔案。

将它打包和发布到包管理器

对一个好的代码库来说,版本是很重要的。如果你想要加入重大的变化,用户可能需要保留他们现在正在使用的版本。

Semantic Versioning是流行的版本命名标准,或者叫它SemVer。SemVer版本包括三个数字,每一个代表不同程度的改变:重大改变,微小的改变和补丁

在你的Git仓库加入版本和发布

如果你有一个git仓库,你可以在你的仓库添加版本数字。你可以把它想象成你的仓库的快照。我们也叫它标签 Tags。可以通过开启终端和输入下面的文字来创造标签:

# git tag -a [version] -m [version message]
git tag -a v1.2.0 -m "Awesome Library v1.2.0"

很多类似GitHub的服务会提供关于所有版本的概览和提供它们的下载链接。

发布一个通用仓库

npm

许多编程语言自带有包管理器,或者是第三方包管理器。这可以允许我们下载关于这些语言的特定代码库。比如PHP的Composer 和Ruby的RubyGems

Node.js,一种独立的JavaScript引擎,拥有 npm,如果你对npm不熟悉,我们有一个很好的教程beginner’s guide。

默认情况下,你的npm包会发布为公共包。不要害怕,你也可以发布私有包 private packages, 设置一个私有的注册private registry, 或者根本不发布avoid publishing.

为了发布你的包,你的项目需要有一个 package.json 文件。你可以手动或者交互问答的方式来创建。通过输入下面的代码来开始问答:

`npm init`

这个版本属性需要跟你的git标签吻合。另外,请确定有README.md 文件。像是GitHub,npm在你的包的展示页使用它。

之后,你可以通过输入下面的代码来发布你的包:

`npm publish`

就是这样!你已经成功发布了你的npm包。

Bower

几年前,有另一个叫做Bower的包管理器。这个包管理器,实际上不是为了特定的语言准备的,而是为了互联网准备的。你可以发现大部分是前端资源。在Bower发布你的包的关键一点是你的代码库是否跟它兼容。

如果你对Bower不熟悉,我们也有一个教程beginner’s guide 。

跟npm一样,你也可以设置一个私有仓库private repository。你可以通过问答的方式避免发布。

有趣的是,在最近的一两年,很多人转为使用npm管理前端资源。近段npm包主要是跟JavaScript相关,大部分的前端资源也发布在了npm上。不管怎样,Bower仍然流行。我明确的推荐你在Bower上发布你的包。

我有提到Bower实际上是npm的一种模块,并最初是得到它的启发吗?它们的命令是很相似的,通过输入下面的代码产生bower.json文件:

`bower init`

跟npm init类似,指令是很直白的,最后,发布你的包:

`bower register awesomelib http://www.php.cn/`

像是把你的代码库放到了野外,任何人可以在他们的Node项目或者网络上使用它!

总结

核心的产品是库文件。确定它解决了问题,容易和适合使用,你会使得你的团队或者许多开发者变得高兴。

我提到的很多任务都是自动化的,比如:运行测试,创建标签,在package.json升级版本或者在npm或者bower重发布你的包。 这是你像Travis CI 或 Jenkins一样踏入持续集成和使用工具的开始。我之前提到的文章 article by Tim Evko 也讲述到了这点。

你构建和发布代码库了吗?请在下面的评论区分享!

 

以上就是设计和构建你自己的JavaScript代码库:提示与技巧的内容,更多相关内容请关注PHP中文网(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn