閒聊——淺談前端js模塊化演變

function時代

  前端這幾年發展太快了,我學習的速度都跟不上演變的速度了(門派太多了,後臺都是大牛公司支撐相似於facebook的react、google的angular,angular的1.0還沒怎麼用過項目,網上查閱2.0的正式版就要出來,書寫方法大改,思惟架構都有很大的改變,真是難爲了如今的前端)。2010年第一次接觸前端js,仍是從asp.net拖控件中接觸,再接着就是我大學那個時代最出名的傳智播客視頻教學,那個時候課餘時間全去看這個視頻了,對着教學一個一個的敲,依稀的記得好定義了好多function,如今想一想仍是頗有趣,心想反正就幾個方法也不會重複。javascript

Object,Function的prototype時代

  隨着前端的代碼越寫越多(本身偶爾會和朋友一塊兒接點外包或幫老師作點項目,畢竟學了不用,永遠也只是紙上談兵),發現本身詞窮了,本身定義的function名稱太多了(方法重名但是會覆蓋以前的方法),這個時候怎麼辦了,在學校最好一點是什麼,就是幾乎每一個學校都有圖書館,你都能找到本身想要的書籍,由於我自己大學主專業是學習C#的,js這種弱類型語言不停的寫function太不美觀了(自黑一下,其實我是讓人討人厭的處女座),圖書館有不少js設計模式的書籍,js高級編程,寫的都很不錯,到如今項目中也都沒有徹底的用到,有些可能用到了吧,但是都不記得這些專業術語名詞(如今什麼設計模式,什麼新技術的縮寫名詞太多了,有些雖然用過,可就是就不住。記不住又不行,有些面試官就是喜歡問,不知道你可就慘了,有些更好笑的是考官在網上現查的,而後當即來問你,'哈哈,其實我也作過這種事')。雖然我是一個大懶人但是看多了,至少也是會記住一兩個,用Function的prototype和arguments去搞面向對象仍是頗有趣的,由於是弱類型,因此不少寫法更靈活。若是你想說你更懶,那你就Object裏面定義一大頓方法也行,其實這種寫法在如今也是很流行,好比咱們經常使用的工具類。css

閉包時代

  好的程序員不是一我的獨自敲代碼,閉門造車,咱們多去查看新技術、新框架、新組建(不過這也確實難爲不少人,畢竟互聯網是一個多姿多彩的世界。幸虧隨着時代的進步,咱們看書學技術的途徑也愈來愈多了,剛開始我關注過一些有趣技術網站的qq帳號,天天均可以在咱們逛QQ空間的時候,看到幾條頗有趣翻譯的外國技術分享文章,再後來有朋友推薦我去看各大公司技術論壇網站,不過這個沒看多久,太多了,好壞難分,重疊的太多了。移動互聯網發展愈來愈快,微信火得一塌糊塗,幾個微信前端公共帳號仍是不錯的,天天都會推薦一篇不錯的文章,公共號爲’前端早讀課‘,’前端大全‘),我的認爲好的程序員都應該有拿來主義和創新精神,因此咱們引用的框架愈來愈多,好多框架都是一個js文件,它們都怕本身的代碼被污染,因此大多數用到了閉包,那什麼是閉包了,閉包有什麼好處呢,網上有好多,在這裏我也囉嗦幾句。html

閉包的好處:前端

1. 邏輯連續,當閉包做爲另外一個函數調用的參數時,避免你脫離當前邏輯而單獨編寫額外邏輯。
2. 方便調用上下文的局部變量。
3. 增強封裝性,第2點的延伸,能夠達到對變量的保護做用。java

閉包的壞處:node

閉包有一個很是嚴重的問題,那就是內存浪費問題,這個內存浪費不只僅由於它常駐內存,更重要的是,對閉包的使用不當會形成無效內存的產生。react

優化注意:jquery

1)因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。
2)閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象(object)使用,把閉包看成它的公用方法(Public Method),把內部變量看成它的私有屬性(private value),這時必定要當心,不要隨便webpack

只要你搞前端,或者搞web幾乎都有人問你,概率幾乎達到80%,這個時候推薦一篇博客(深刻理解JavaScript系列(2):揭祕命名函數表達式),這但是湯姆大叔,自己很是的佩服他,他寫的js設計模式和js注意事項確實不錯,就在博客園中,也很感謝這些人在博客園中,咱們大學生活才更幸福。git

js模塊化和MVC

  Extjs或sencha touch這兩個都是同一家公司的,也是我用到的最先的前端模塊化和MVC,sencha touch我用的也是最多的,由於移動端畢竟火爆。

  說的它的好處吧:

  1.靈活架構,焦點分離
  2.方便模塊間組合、分解
  3.方便單個模塊功能調試、升級
  4.多人協做互不干擾

  mvc是後端說的最多的術語,若是你如今還不懂你最好快點去惡補下,由於如今不少形形色色的前端MVC層出不窮,

  MVC開始是存在於桌面程序中的,M是指業務模型,V是指用戶界面,C則是控制器,使用MVC的目的是將M和V的實現代碼分離,從而使同一個程序可使用不一樣的表現形式。好比一批統計數據能夠分別用柱狀圖、餅圖來表示。C存在的目的則是確保M和V的同步,一旦M改變,V應該同步更新。

  MVC 是一種使用 MVC(Model View Controller 模型-視圖-控制器)設計建立 Web 應用程序的模式:

Model(模型)表示應用程序核心(好比數據庫記錄列表)。
View(視圖)顯示數據(數據庫記錄)。
Controller(控制器)處理輸入(寫入數據庫記錄)。
MVC 模式同時提供了對 HTML、CSS 和 JavaScript 的徹底控制。
Model(模型)是應用程序中用於處理應用程序數據邏輯的部分。
  一般模型對象負責在數據庫中存取數據。
View(視圖)是應用程序中處理數據顯示的部分。
  一般視圖是依據模型數據建立的。
Controller(控制器)是應用程序中處理用戶交互的部分。
  一般控制器負責從視圖讀取數據,控制用戶輸入,並向模型發送數據。
MVC 分層有助於管理複雜的應用程序,由於您能夠在一個時間內專門關注一個方面。例如,您能夠在不依賴業務邏輯的狀況下專一於視圖設計。同時也讓應用程序的測試更加容易。
MVC 分層同時也簡化了分組開發。不一樣的開發人員可同時開發視圖、控制器邏輯和業務邏輯。

  囉嗦一點說一下sencha這家js框架公司的好處和弊端吧,他們提供的框架有很好的樣式體系,基於sass寫的,能夠隨便咱們改變樣式風格,提供的sencha.cmd第一次讓我感受到了前端的工程化,提供了代碼的壓縮、代碼驗證、代碼模塊的依賴合併。組件豐富,說了這麼多你們感受必定很爽吧。可是因爲他提供很是多的組件,因此致使js代碼過多,雖然有些咱們能夠自行配置篩選壓縮,它的依賴壓縮不是所有根據配置,大部門和文件夾裏面的文件有關,必須不停的移除或添加,反正就是很麻煩,特別是版本的升級時候,真是想吐了。有時由於壓縮的緣故還會報一些下錯。

AMD和CMD時代

   說來前面的大框架咱們必定想用到更加輕便的模塊框架了,這個時候必定要說說兩派的表明框架RequireJS和SeaJs,引用一下github上的專業術語吧,我的也通過一段時間的驗證。

相同之處

RequireJS 和 Sea.js 都是模塊加載器,倡導模塊化開發理念,核心價值是讓 JavaScript 的模塊化開發變得簡單天然。

不一樣之處

二者的主要區別以下:

  1. 定位有差別。RequireJS 想成爲瀏覽器端的模塊加載器,同時也想成爲 Rhino / Node 等環境的模塊加載器。Sea.js 則專一於 Web 瀏覽器端,同時經過 Node 擴展的方式能夠很方便跑在 Node 環境中。

  2. 遵循的規範不一樣。RequireJS 遵循 AMD(異步模塊定義)規範,Sea.js 遵循 CMD (通用模塊定義)規範。規範的不一樣,致使了二者 API 不一樣。Sea.js 更貼近 CommonJS Modules/1.1 和 Node Modules 規範。

  3. 推廣理念有差別。RequireJS 在嘗試讓第三方類庫修改自身來支持 RequireJS,目前只有少數社區採納。Sea.js 不強推,採用自主封裝的方式來「海納百川」,目前已有較成熟的封裝策略。

  4. 對開發調試的支持有差別。Sea.js 很是關注代碼的開發調試,有 nocache、debug 等用於調試的插件。RequireJS 無這方面的明顯支持。

  5. 插件機制不一樣。RequireJS 採起的是在源碼中預留接口的形式,插件類型比較單一。Sea.js 採起的是通用事件機制,插件類型更豐富。

總之,若是說 RequireJS 是 Prototype 類庫的話,則 Sea.js 致力於成爲 jQuery 類庫。

webpack時代

  Seajs我沒怎麼用過項目,因此也就不說了,RequireJS 用過,那時候用的.net的mvc5.0中的一個第三方插件,幫我省去了不少的配置文件,由於若是咱們用nodejs的話通常要本身書寫R.js,用來配置一些處理流程,強大的.net插件幫我省去了好多,但是每一個模塊你都的定義,而且在配置文件中書寫出來,每一個模塊都要寫初始模塊定義,爲何我會這麼去說話一個框架了,由於不斷的學習用到了更好的webpack,如下是個人觀點(有些解釋術語參考了其它博客)

說說一下webpack的優勢吧:

1.require.js的全部功能它都有
2.編繹過程更快,由於require.js會去處理不須要的文件
3.還有一個額外的好處就是你不須要再作一個封裝的函數,require.js中你得這樣

define(['jquery'], function(jquery){})

4.如今你須要一個很大的封裝去定義每一個模塊,而後你須要在在require.js的配製文件中將每一個模塊的路徑都配出來,用過requirejs都會遇到的好繁瑣

require.config({
    baseUrl: '/scripts',
    paths: {
        'facebook'          : '//connect.facebook.net/en_US/all',
        // 'facebook'       : '//connect.facebook.net/en_US/sdk/debug'
        'requirejs'         : '../bower_components/requirejs/require',
        'react'             : '../bower_components/react/react-with-addons',
        'underscore'        : '../bower_components/lodash/dist/lodash',
        'futures-requirejs' : '../bower_components/futures-requirejs/future',
        'jquery'            : '../bower_components/jquery/jquery',
        // 'phaser'         : '../bower_components/phaser/build/phaser',
        'phaser.filters'    : '../bower_components/phaser/filters/',
        'phaser'            : '../thirdParty/phaser/Phaser',
        'snap'              : '../bower_components/Snap.svg/dist/snap.svg',
        'proton'            : '../thirdParty/Proton',
        'copyProperties'    : '../thirdParty/copyProperties',
        'flux'              : '../bower_components/flux/dist/Flux',
        'eventEmitter'      : '../bower_components/eventEmitter/EventEmitter',
        'pixi'              : '../bower_components/pixi/bin/pixi',
        'crossroads'        : '../bower_components/crossroads/dist/crossroads',
        'signals'           : '../bower_components/js-signals/dist/signals',
        'hasher'            : '../bower_components/hasher/dist/js/hasher',
        'async'             : '../bower_components/async/lib/async',
        'socket.io-client'  : '../bower_components/socket.io-client/dist/socket.io',
        'html2canvas'       : '../bower_components/html2canvas/build/html2canvas.min',
        'hammer'            : '../bower_components/hammerjs/hammer',
        'touch-emulator'    : '../bower_components/hammer-touchemulator/touch-emulator',
        'moment'            : '../bower_components/moment/moment',
        // 'famous'         : '../bower_components/famous',
        'tinygradient'      : '../bower_components/tinygradient/tinygradient',
        'page'              : '../bower_components/page/index',
        // 'faker'          : '../bower_components/faker/dist/faker',
        'faker'             : '../thirdParty/Faker',
        'perlin'            : '../thirdParty/Perlin',
        'tinycolor'         : '../vendors/tinycolor',
        // 'flux'           : '../../node_modules/flux/index',
        'client'            : './',
        'errors'            : './errors',
        'server'            : '../../server',
    },
    packages: [{
        name     : 'API2',
        location : '../bower_components/api2/src/',
        main     : 'API'
    }],
    shim: {
        'phaser.filters/Fire': {
            deps: ['phaser'],
        },
        'page': {
            exports: 'page'
        },
        'snap' : {
            exports: 'Snap'
        },
        'html2canvas' : {
            exports: 'html2canvas'
        },
        'facebook' : {
            exports: 'FB'
        },
        // 'underscore': {
        //     deps: [],
        //     exports: '_'
        // },
        'phaser': {
            exports: 'Phaser'
        },
        'pixi': {
            exports: 'PIXI'
        },
        'hammer': {
            exports: 'Hammer'
        },
        'touch-emulator': {
            exports: 'TouchEmulator'
        },
        'proton': {
            exports: 'Proton'
        },
        'moment': {
            exports: 'moment'
        }
    }
});
View Code

 

如下是webpack的一個文件配置,

var path = require("path");
var webpack = require("webpack");
var ExtractTextPlugin = require("extract-text-webpack-plugin")

module.exports = {
  // context: path.join(__dirname),
  entry: {
    index: './app/scripts/index.js',
    page1: './app/scripts/page1.js',
    page3: './app/scripts/page3.js'
  },
  output: {
    path: path.join(__dirname, '_dist'),
    filename: './scripts/[name]-bundle.js',
    chunkFilename: "./scripts/[id]-chunk.js",
   // library:'appConfig'  //主要應用jsonp命名從新定義
  },
  module: {
    loaders: [{
        test: /\.css$/,
        loader: ExtractTextPlugin.extract("style-loader", "css-loader")
      }, {
        test: /\.less$/,
        loader: 'style-loader!css-loader!less-loader'
      }, {
        test: /\.woff$/,
        loader: 'url-loader?prefix=font/&limit=5000'
      }, {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          compact: false
        }
      }
    ]
  },
  'html-minify-loader': {
    empty: true, // KEEP empty attributes
    cdata: true, // KEEP CDATA from scripts
    comments: true // KEEP comments
  },
  resolve: {
    root: [path.join(__dirname, "bower_components"), path.join(__dirname, 'app', 'scripts')],
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "commons",
      filename: "./scripts/commons.js",
      chunks: ["page1", "page2", "page3"]
    }),
    new ExtractTextPlugin("./styles/[name].css"),
    new webpack.ResolverPlugin(
      new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
    )
  ]
};
View Code

 

咱們能夠發現少了好多引用模塊js文件路徑引用,可能會有人問,它怎麼知道我要引用那些js,在nodejs裏有一個有趣的插件brower(這東西你若是沒有用到,你老闆可能會說你仍是一個初級菜鳥)

Bower 是twitter 推出的一款包管理工具,基於nodejs的模塊化思想,把功能分散到各個模塊中,讓模塊和模塊之間存在聯繫,經過 Bower 來管理模塊間的這種聯繫

包管理工具通常有如下的功能:

註冊機制:每一個包須要肯定一個惟一的 ID 使得搜索和下載的時候可以正確匹配,因此包管理工具須要維護註冊信息,能夠依賴其餘平臺。
文件存儲:肯定文件存放的位置,下載的時候能夠找到,固然這個地址在網絡上是可訪問的。
上傳下載:這是工具的主要功能,能提升包使用的便利性。好比想用 jquery 只須要 install 一下就能夠了,不用處處找下載。上傳並非必備的,根據文件存儲的位置而定,但須要有必定的機制保障。
依賴分析:這也是包管理工具主要解決的問題之一,既然包之間是有聯繫的,那麼下載的時候就須要處理他們之間的依賴。下載一個包的時候也須要下載依賴的包

可能會有人會問,我雖然將這些包下了下來,那我仍是不知道那些模塊位置,我該若是引用了,webpack一個插件輕鬆解決這個問題:如下是配置文件

resolve: {
  root: [path.join(__dirname, "bower_components"), path.join(__dirname, 'app', 'scripts')],
},
plugins: [
  new webpack.ResolverPlugin(
    new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
  )
]

 

那若是調用了

var $ = require("jquery");

 

是否是感受很方便了,還有一些新手可能會吐槽,js原本就是弱類型語言,框架那麼多,智能提示是否是不好,特別是玩微軟那套玩多了的人必定會問,在此推薦如下webstorm、sublime、atom它們上面的插件仍是不錯的

5.requirejs它是異步依賴加載的,說白了就是,只要你用到了,它絕對會一次性加載完,就算你初始化沒有用到,它也會加載,若是你把它壓縮,文件就好大了,最後就一個文件。你也能夠破壞它的原則,按傳統的寫法單獨加載,不過就總體就不是很美觀了。可能有些解決的插件,我沒用到也是有可能的,這裏也就很少說了。

webpakc就很好解決了,它提供的插件能夠自動分析,根據你提過js文件主入口,自動分析,可合成多個js文件,可是它不會所有加載,按照你的須要一次加載,這樣就不會出現頁面剛初始化就加載沒必要要的js文件,提升頁面速度(雖然能夠靠用體力解決,說白了就是注意寫法,不過好痛苦)

以上是它們的對比,再說說它的私有特性吧

1. 對 CommonJS 、 AMD 、ES6的語法作了兼容
2. 對js、css、圖片等資源文件都支持打包(css均可以合成多個css文件包,好爽,不再是sb似的所有加載了,sass和less雖然也是模塊化的加載合併,但是css和js分離的關聯不大,這裏的css能夠和js有更大的關聯,更細緻區分加載的js)
3. 串聯式模塊加載器以及插件機制,讓其具備更好的靈活性和擴展性,例如提供對CoffeeScript、ES6的支持
4. 有獨立的配置文件webpack.config.js
5. 能夠將代碼切割成不一樣的chunk,實現按需加載,下降了初始化時間
6. 支持 SourceUrls 和 SourceMaps,易於調試
7. 具備強大的Plugin接口,大可能是內部插件,使用起來比較靈活
8.webpack 使用異步 IO 並具備多級緩存。這使得 webpack 很快且在增量編譯上更加快

在補充一個特別的屬性吧

雙服務器模式

項目開發中,僅有一臺靜態服務器是不能知足需求的,咱們須要另啓一臺web服務器,且將靜態服務器集成到web服務器中,就可使用webpack的打包和加載功能。咱們只須要修改一下配置文件就能夠實現服務器的集成。

 entry: [ './src/page/main.js', 'webpack/hot/dev-server', 'webpack-dev-server/client?http://127.0.0.1:8080' ] output: { path: __dirname, filename: '[name].js', publicPath: "http://127.0.0.1:8080/assets/" } plugins: [ new webpack.HotModuleReplacementPlugin() ]

若是在開發中啓動兩個服務器並非一個很好地選擇,webpack提供了一箇中間件webpack-dev-middleware,但其只能在生產環境中使用,能夠實如今內存中實時打包生成虛擬文件,供瀏覽器訪問以及調試。使用方式以下:

var webpackDevMiddleware = require("webpack-dev-middleware"); var webpack = require("webpack"); var compiler = webpack({ // configuration output: { path: '/' } }); app.use(webpackDevMiddleware(compiler, { // options }));

  有些好處可能沒有說到,webpack可能過幾年它就會被取代也是有可能,可是如今若是你說你不會,那就請趕快惡補吧,它會帶你飛的。

以上分析結合本身工做經驗之談,有些可能說大呢,說錯了你們就一笑而過,不對的地方也但願誠懇指出。

相關文章
相關標籤/搜索