閱讀sea.js源碼小結

sea.js想解決的問題

  1. 惱人的命名衝突node

  2. 煩瑣的文件依賴程序員

對應帶來的好處 Sea.js 帶來的兩大好處:

  1. 經過 exports 暴露接口。這意味着不須要命名空間了,更不須要全局變量。這是一種完全的命名衝突解決方案。數組

  2. 經過 require 引入依賴。這可讓依賴內置,開發者只需關心當前模塊的依賴,其餘事情 Sea.js 都會自動處理好。對模塊開發者來講,這是一種很好的 關注度分離,能讓程序員更多地享受編碼的樂趣。緩存

API速查

1. seajs.config
2. seajs.use
3. define
4. require
5. require.async
6. exports
7. module.exports

sea.js的執行過程

啓動

script標籤引入sea.js文件,seajs.config(data)啓動配置函數,config函數會會合並全部config配置,seajs.use = function(ids, callback),啓用主腳本異步

運行過程

主腳本啓動以後,首先利用request模塊請求主腳本(生成script標籤插入head標籤中),而後根據正則解析模塊define的依賴,並對依賴遞歸解析其依賴。
在運行過程當中,經過監聽發佈者模式,系統內置了8個事件,可用於開發插件。async

resolve       -- 將 id 解析成爲 uri 時觸發
load          -- 開始加載文件時觸發
fetch         -- 具體獲取某個 uri 時觸發
request       -- 發送請求時觸發
define         -- 執行 define 方法時觸發
exec         -- 執行 module.factory 時觸發
config         -- 調用 seajs.config 時觸發
error          -- 加載腳本文件出現 404 或其餘錯誤時觸發

全局掛載

全部相關數據最後所有掛載在window.seajs下,包括方法及模塊數據。函數

小知識點

exports與module.exports

exports 僅僅是 module.exports 的一個引用。在 factory 內部給 exports 從新賦值時,並不會改變 module.exports 的值。所以給 exports 賦值是無效的,不能用來更改模塊接口。fetch

//源碼以下
// Exec factory
var factory = mod.factory;

var exports = isFunction(factory) ?
  factory.call(mod.exports = {}, require, mod.exports, mod) :
  factory

關於動態依賴

有時會但願可使用 require 來進行條件加載:ui

if (todayIsWeekend)
  require("play");
else
  require("work");

但請牢記,從靜態分析的角度來看,這個模塊同時依賴 play 和 work 兩個模塊,加載器會把這兩個模塊文件都下載下來。 這種狀況下,推薦使用 require.async 來進行條件加載。編碼

//sea.js源碼以下
require.async = function(ids, callback) { //可傳入回調函數
  Module.use(ids, callback, uri + "_async_" + cid())  //——async_英語標識這個腳本是異步加載的,cid用於清除緩存
  return require //返回require方便鏈式調用
}

在開發時,Sea.js 是如何知道一個模塊的具體依賴呢?

a.js

define(function(require, exports) {
  var b = require('./b');
  var c = require('./c');
});

Sea.js 在運行 define 時,接受 factory 參數,能夠經過 factory.toString() 拿到源碼,再經過正則匹配 require 的方式來獲得依賴信息。依賴信息是一個數組,好比上面 a.js 的依賴數組是:['./b', './c']

//源碼以下

// Parse dependencies according to the module factory code
if (!isArray(deps) && isFunction(factory)) {  
  deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString()) //parseDependencies是利用正則解析依賴的一個函數
}

時間出發函數Emit

// Emit event, firing all bound callbacks. Callbacks receive the same
// arguments as `emit` does, apart from the event name
var emit = seajs.emit = function(name, data) {
  var list = events[name]

  if (list) {
    // Copy callback lists to prevent modification
    list = list.slice()

    // Execute event callbacks, use index because it's the faster.
    for(var i = 0, len = list.length; i < len; i++) {
      list[i](data)
    }
  }

  return seajs
}

主要看這個部分list = list.slice(),註釋是防止拷貝該時間的回調函數,防止修改,困惑了一下。

緣由是Javascript中賦值時,對於引用數據類型,都是傳地址。
因此這裏,若是想防止觸發事件的過程當中回調函數被更改,必須對這個list數組進行拷貝,而並不是只是將list指向events[name]的地址。

根據debug值配置是否刪除動態插入的腳本

// Remove the script to reduce memory leak
      if (!data.debug) {
        head.removeChild(node)
      }

這裏思考了蠻久,爲何能夠刪除動態插入的腳本?這樣腳本還會生效嗎?

首先,必須瞭解計算機內存分爲

  1. 靜態數據區 (用來存放程序中初始化的全局變量的一塊內存區域)

  2. 代碼區 (一般用來存放執行代碼的一塊內存區域)

  3. 棧區 (棧在進程運行時產生,一個進程有一個進程棧。棧用來存儲程序臨時存放的局部變量,即函數內定義的變量 不包括static 類型的。函數被調用時,他的形參也會被壓棧。

  4. 堆區 (用於存放進程運行中被動態分配的內存段,它的大小而且不固定,可動態擴展。當進程調用malloc等分配內存時,新分配的內存被動態的添加到堆上(堆被擴大),當利用free等函數釋放內存時,被釋放的‘ 內存從堆中剔除)

這些在Javascript中都被屏蔽了,大部分時候咱們都不須要考慮,可是若是要深刻了解的話,則是必需要知道的知識。

首先HTML文檔中的JS腳本在計算機中做爲指令被讀入內存,以後開始執行,CPU開始一條一條指令讀取,好比,讀取到var cool = "wilson"時,就會在內存中分配一個6字符大小的內存,一個function也同樣會在內存中佔據必定大小。因此,當指令所有運行完以後,指令自己其實已經沒有用了,可是仍然給佔據了一部份內存。
當你點擊按鈕觸發一個回調函數時,並不是去讀取指令,而是讀取內存中這個回調函數的地址。因此刪除這些動態加載的JS文件是沒有問題的。

ID 和路徑匹配原則

所謂 ID 和路徑匹配原則 是指,使用 seajs.use 或 require 進行引用的文件,若是是具名模塊(即定義了 ID 的模塊),會把 ID 和 seajs.use 的路徑名進行匹配,若是一致,則正確執行模塊返回結果。反之,則返回 null。

對 module.exports 的賦值須要同步執行,不能放在回調函數裏。下面這樣是不行的

// x.js
define(function(require, exports, module) {

  // 錯誤用法
  setTimeout(function() {
    module.exports = { a: "hello" };
  }, 0);

});

//在 y.js 裏有調用到上面的 x.js:

// y.js
define(function(require, exports, module) {

  var x = require('./x');

  // 沒法馬上獲得模塊 x 的屬性 a
  console.log(x.a); // undefined

});

WilsonLiu's blog首發地址:http://blog.wilsonliu.cn

相關文章
相關標籤/搜索