闲聊——浅谈前端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可能过几年它就会被取代也是有可能,可是如今若是你说你不会,那就请赶快恶补吧,它会带你飞的。

以上分析结合本身工做经验之谈,有些可能说大呢,说错了你们就一笑而过,不对的地方也但愿诚恳指出。

相关文章
相关标签/搜索