手把手教你寫一個 Javascript 框架:項目結構

ThorstenGri 9年前發布 | 12K 次閱讀 中間件 JavaScript開發 JavaScript

過去幾個月中,RisingStack 的 JavaScript 工程師 Bertalan Miklos 編寫了新一代客戶端框架 NX 。Bertalan 將通過 編寫 JavaScript 框架 系列文章與我們分享他在編寫框架過程中的收獲:

本章將展示 NX 的項目結構,并講述如何解決可擴展性、依賴注入以及私有變量等方面的一些困難。

本系列章節如下:

  1. 項目結構(正是本文)
  2. 執行調度(Execution timing)
  3. 沙箱求值
  4. 數據綁定簡介
  5. ES6 Proxy 實現數據綁定
  6. 自定義元素
  7. 客戶端路由

項目結構

沒有放之四海而皆準的項目結構,但有一些基本準則。感興趣的同學可以看下我們的 Node Hero 系列中的《 Node.js 項目結構教程 》這一章。

NX 框架概覽

NX 的目標是成為一個開源社區驅動的易于擴展的項目。項目特點如下:

  • 包含現代客戶端框架必須的所有特性;
  • 除 polyfill 外,沒有任何外部依賴;
  • 代碼總量 3000 行;
  • 沒有代碼多于 300 行的模塊;
  • 單個特性模塊依賴不超過 3 個。

項目各模塊依賴關系如下圖所示:

這種結構為典型框架開發難題提供了一種解決方案。

  • 擴展性
  • 依賴注入
  • 私有變量

可擴展性實現

社區驅動項目必須易于擴展。故項目的核心部分應當小巧,并擁有一個預定義的依賴處理系統。前者確保項目易于理解,后者則保證框架穩定。

本節先聚焦于實現小巧的內核。

現代框架應當擁有的主要特性就是創建自定義元素并將其應用于 DOM 的能力。NX 的核心只有一個 component 函數,它的工作正在于此這個函數允許用戶配置、注冊一個新類型的元素。

component(config)
  .register('comp-name')

注冊的 comp-name 是空組件類型,可以按照預期在 DOM 中實例化。

<comp-name></comp-name>

下一步是保證能使用新特性擴展組件。為保持簡潔、可擴展,這些新特性不應該污染核心部分。這時候使用依賴注入就很方便了。

利用中間件實現依賴注入(DI)

依賴注入是一種設計模式,在這種模式中,一個或多個依賴或服務被注入到或引用傳遞給一個獨立對象。

DI 解決了硬性依賴,卻引入了新問題。使用者需要知道如何配置、注入依賴。大多客戶端框架都將這些工作交給 DI 容器,幫助開發者完成。

DI 容器指的是知道如何實例化、配置對象的對象。

另外一種方式則是中間件 DI 模式,這在服務端得到廣泛應用(如 Express、Koa 等)。其中的奧秘在于,所有可注入的依賴(中間件)擁有相同的接口,以相同方式注入。這種方法則無需 DI 容器。

為保持簡潔,我決定采用中間件模式。若你曾使用過 Express,以下代碼自然不會陌生:

component()
  .use(paint) // inject paint middleware
  .use(resize) // inject resize middleware
  .register('comp-name')

function paint (elem, state, next) {
  // elem is the component instance, set it up or extend it here
  elem.style.color = 'red'
  // then call next to run the next middleware (resize)
  next()
}

function resize (elem, state, next) {
  elem.style.width = '100 px'
  next()
}

中間件在新的組件實例插入 DOM 時執行,通常會給實例擴展一些新特性。如若不同庫擴展相同對象,則將導致名稱沖突。暴露私有變量會加劇問題,并可能被其他人意外利用。

公開 API 小巧玲瓏,其余部分隱身不見,正是避免問題的優秀方案。

處理私有變量

JavaScript 中需要通過函數作用域來實現私有變量。需要使用跨作用域私有變量時,人們習慣使用 _ 前綴來標志,并將其公開暴露。這可以避免意外使用,但無法解決命名沖突。更好的辦法是使用 ES6 的 Symbol 基本數據類型。

Symbol 是一種唯一的、不可變的數據類型,可用作對象屬性標識符。

下面的代碼展示了 symbol 的實際使用:

const color = Symbol()

// a middleware
function colorize (elem, state, next) {
  elem[color] = 'red'
  next()
}

這樣一來,通過 color symbol (以及元素 elem)就能獲取 red 。 red 的私有程度,可由 color symbol 暴露的不同程度控制。合理數量的私有變量,通過中心存儲讀取,是一種優雅的解決方案。

// symbols module
exports.private = {
  color: Symbol('color from colorize')
}
exports.public = {}

index.js 如下:

// main module
const symbols = require('./symbols')
exports.symbols = symbols.public

在項目內部,所有模塊都可訪問 symbol 存儲對象,但私有部分不會對外暴露。公有部分則可用于對外部開發者暴露一些低層次特性。這就避免了意外使用,因為開發者需要明確引入需要使用的 symbol。此外,symbol 引用也不會像字符串一樣產出沖突,是故不會產生命名沖突。

以下幾點概括了不同場景下的用法:

1. 公有變量

正常使用.

function (elem, state, next) {
  elem.publicText = 'Hello World!'
  next()
}

2. 私有變量

項目私有的跨作用域變量,應當在私有 symbol 對象中加入一個 symbol key。

// symbols module
exports.private = {
  text: Symbol('private text')
}
exports.public = {}

并在需要的地方引入。

const private = require('symbols').private

function (elem, state, next) {
  elem[private.text] = 'Hello World!'
  next()
}

3. 半私有變量

低層次 API 的變量,應當在公有 symbol 對象中加入一個 symbol key。

// symbols module
exports.private = {
  text: Symbol('private text')
}
exports.public = {
  text: Symbol('exposed text')
}

并在需要的地方引入。

const exposed = require('symbols').public

function (elem, state, next) {
  elem[exposed.text] = 'Hello World!'
  next()
}

 

 

來自:http://www.zcfy.cc/article/writing-a-javascript-framework-project-structuring-risingstack-1675.html

 

 本文由用戶 ThorstenGri 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!