• 模块化的历史和模块加载器


    模块化的历史和模块加载器

    模块化的需求来源

    1. 前端代码日渐复杂,web应用越来越像桌面应用
    2. 复用别人的代码
    3. 更少的网络请求

    http://huangxuan.me/js-module-7day/#/

    实现模块化

    基本原理

    1. 函数作用域
    function m1(){
    	// 模块内容
    	// 这种方式污染全局变量
    }
    
    1. 对象写法
    var module1 = new Object({
    	_count = 0;
    	m1: function(){
    	}
    	// 模块成员会被暴露,内部状态可以被外部改写
    })
    
    1. IIFE
    var module1 = (function($){
    	var _count = 0;
    	var m1 = function(){
    		// ...
    	}
    	var _$body = $('body');
    	var foo = function(){
    		console.log(_$body);
    	}
    	return {
    		m1: m1,
    		foo: foo
    	}
    	// 
    })(jQuery)
    module1.foo();
    

    模块加载器

    js加载问题

    1. js文件加载的时候,页面会出现假死(使用defer或async就无法达到2中的要求)
    2. js文件的依赖关系需要通过script标签顺序去控制
    3. 如果模块过多且不打包,网络请求多

    模块加载器解决的问题

    1. 实现js文件的异步加载,避免网页失去响应
    2. 管理模块之间的依赖关系,便于代码的编写和维护

    模块加载器演变史

    require.js(AMD规范)

    使用require.js并指定入口文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <script src="js/require.js" data-main="js/main"></script>
    </body>
    </html>
    
    // 主模块(入口文件)的写法
    require.config({
      paths: {
        "jquery": 'jquery.min',
        "underscore": 'underscore.min',
        "backbone": 'backbone.min'
      }
    });
    
    require(['jquery', 'underscore','backbone'], function($, _, Backbone){
      console.log(_.min([1,10,2]))
    });
    
    require(['math'], function (math) {
      alert(math.min([10, 11]))
    });
    
    // 定义模块的方法
    define(['underscore'], function(_){
      var min = function (x, y){
        return _.min(x,y);
      };
      return {
        min: min
      }
    });
    

    此外,require.js提供了一个优化工具r.js,可以将模块打包,减少网络请求

    http://www.hangge.com/blog/cache/detail_1704.html

    require.js还有一些插件,可以将图片和文本打包

    sea.js(CMD规范)

    // 引入sea.js并且配置入口文件
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <div id="app">dfslljdslf</div>
    <script src="../sea-modules/seajs/seajs/2.2.0/sea.js"></script>
    <script>
        seajs.config({
          base: '../sea-modules/',
          alias: {
            'jquery': 'jquery/jquery/1.10.1/jquery.js'
          }
        })
    
        seajs.use('../static/test/main.js');
    </script>
    </body>
    </html>
    
    // 入口文件写法
    define(function (require, exports, module) {
      var test = require('./test');
      test.fadeOut();
    });
    
    // 定义模块方法
    define(function(require, exports, module){
      var $ = require('jquery');
      exports.fadeOut = function(){
        $('#app').fadeOut();
      }
    })
    
    区别

    写法上:require.js依赖前置,sea.js依赖就近,语法更像common.js
    加载和执行顺序上:require.js依赖提前执行,sea.js依赖懒执行,打日志可以看出来

    https://blog.csdn.net/zshake/article/details/53054722

    common.js(node.js服务器端)

    // 最终导出的是exports对象
    console.log("example.js");
    exports.message = "hi";
    exports.say = function (){
        console.log("hello");
    };
    
    // 使用require加载模块
    var example = require('./example.js');
    

    ES6 module

    ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

    // CommonJS模块
    let { stat, exists, readFile } = require('fs');
    
    // 等同于
    let _fs = require('fs');
    let stat = _fs.stat;
    let exists = _fs.exists;
    let readfile = _fs.readfile;
    

    上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
    ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

    // ES6模块
    import { stat, exists, readFile } from 'fs';
    

    上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
    由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。

    // export写法
    // 1.直接导出
    export var firstName = 'Michael';
    // 2.先声明后导出
    var firstName = 'Michael';
    export {firstName}
    // 3.导出函数或类
    export function multiply(x,y){
    	return x+y;
    }
    // 4.export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系
    export 1 // 报错
    var m = 1;
    export m; //报错
    export var m = 1; // 不报错
    export {m} // 不报错
    
    // import写法
    import {firstName, lastName, year} from './profile.js';
    import { lastName as surname } from './profile.js';
    // js后缀可以省略
    import {myMethod} from 'util';
    // import命令具有提升效果,会提升到整个模块的头部,首先执行
    foo();
    import { foo } from 'my_module';
    // 由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构
    import { 'f' + 'oo' } from 'my_module'; // 报错
    // import语句会执行所加载的模块,因此可以有下面的写法
    import 'lodash'; // 仅仅执行lodash模块,但是不输入任何值
    // 模块的整体加载
    // circle.js
    export function area(radius) {
      return Math.PI * radius * radius;
    }
    export function circumference(radius) {
      return 2 * Math.PI * radius;
    }
    
    import * as circle from './circle';
    console.log('圆面积:' + circle.area(4));
    console.log('圆周长:' + circle.circumference(14));
    
    // export default用法
    // export-default.js
    export default function () {
      console.log('foo');
    }
    // import-default.js
    import customName from './export-default'; // 不用括号
    customName(); // 'foo'
    // 一个模块只能有一个默认输出,因此export default命令只能使用一次
    

    import()方法
    在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。

    const path = './' + fileName;
    const myModual = require(path);
    

    上面的语句就是动态加载,require到底加载哪一个模块,只有运行时才知道。import命令做不到这一点。
    因此,有一个提案,建议引入import()函数,完成动态加载。

    import(specifier)
    

    上面代码中,import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。
    import()返回一个 Promise 对象。下面是一个例子。

    const main = document.querySelector('main');
    
    import(`./section-modules/${someVariable}.js`)
      .then(module => {
        module.loadPageInto(main);
      })
      .catch(err => {
        main.textContent = err.message;
      });
    

    import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。
    适用场景

    • 按需加载
    button.addEventListener('click', event => {
      import('./dialogBox.js')
      .then(dialogBox => {
        dialogBox.open();
      })
      .catch(error => {
        /* Error handling */
      })
    });
    
    • 条件加载
    if (condition) {
      import('moduleA').then(...);
    } else {
      import('moduleB').then(...);
    }
    
    • 动态模块路径
    import(f())  // 根据函数f的返回结果,加载不同的模块
    .then(...);
    

    注意点
    import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。

    // 解构赋值
    import('./myModule.js')
    .then(({export1, export2}) => {
      // ...·
    });
    // 同时加载多个模块
    Promise.all([
      import('./module1.js'),
      import('./module2.js'),
      import('./module3.js'),
    ])
    .then(([module1, module2, module3]) => {
       ···
    });
    

    http://es6.ruanyifeng.com/#docs/module

    打包工具

    require.js有类似r.js的打包工具,此外还有一些打包工具

    browserify

    让浏览器加载Nodejs模块

    $ browserify main.js -o bundle.js
    

    https://javascript.ruanyifeng.com/tool/browserify.html

    webpack

    可以打包各种文件资源,可支持各种模块规范

    webpack.config.js配置文件

    配置文件本身就是一个模块

    var path = require('path');
    var webpack = require('webpack');
    
    var devFlagPlugin = new webpack.DefinePlugin({
      _DEV_: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
    });
    
    module.exports={
      entry: {  // 入口文件,String/Array/Object
        bundle1: 'main1.jsx',
        bundle2: 'main2.jsx',
        bundle: 'main.jsx'
      },
      output:{   // 指定输出位置和文件名
        filename:'[name].js',  
        path: path.resolve(__dirname, './')
      },
      mode:'development', 
      module: { // loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
        rules: [
          {
            test: /.jsx?$/,
            exclude: /node_modules/,
            use:{
              loader: 'babel-loader',  // 处理ES6
              options: {
                presets: ['es2015', 'react']
              }
            }
          },
          {
            test: /.css$/,  // 处理css文件
            use: [
              {
                loader: 'style-loader',
              },
              {
                loader: 'css-loader',
                options: {
                  modules: true,
                }
              }
            ]
          },
          {
            test: /.(jpg|png)$/, // 解析图片模块
            use: {
              loader: 'url-loader',
              options: {
                limit: 8192
              }
            }
          }
        ]
      },
      plugins: [ // loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务,需要通过使用 new 操作符来创建它的一个实例
        devFlagPlugin,  // 根据node命令行参数设置变量_DEV_,_DEV_可以在任何模块使用
        new webpack.optimize.CommonsChunkPlugin({ // code splitting
          name: 'commons',
          filename: 'commons.js',
        })
      ]
    };
    
    // main.js
    const React = require('react');
    const ReactDOM = require('react-dom');
    var style = require('./app.css'); // 导入css文件模块(css module)
    
    var img1 = new Image();
    img1.src = require('./big.png'); // 导入图片资源
    document.body.appendChild(img1);
    if(_DEV_){
      document.write(new Date());
    }
    
    ReactDOM.render(
      <div>
        <h1 className={style.h1}>Hello, world!</h1>
        <h2 className="h2">Hello, webpack</h2>
      </div>,
      document.querySelector('#wrapper')
    );
    

    webpack.config.js还可以导出一个函数或promise对象,可以导出多种规范的模块,可以热替换,只更新修改的模块。
    webpack 从命令行或配置文件中定义的一个模块列表开始,处理你的应用程序。 从这些入口起点开始,webpack 递归地构建一个依赖图,这个依赖图包含着应用程序所需的每个模块,然后将所有这些模块打包为少量的 bundle - 通常只有一个 - 可由浏览器加载。

    https://www.webpackjs.com/concepts/
    https://github.com/ruanyf/webpack-demos#demo10-code-splitting-source
    http://www.ruanyifeng.com/blog/2012/10/javascript_module.html
    http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html
    http://www.ruanyifeng.com/blog/2012/11/require_js.html

  • 相关阅读:
    使用阿里云ECS安装HDFS的小问题
    退役回忆录嘤嘤嘤
    2018 ICPC北京 H ac自动机
    Educational Codeforces Round 54 (Rated for Div. 2) DE
    sa learning
    网络流learning
    Python模块logging
    Python模块unittest
    Linux /dev/shm
    Shell 字符串操作
  • 原文地址:https://www.cnblogs.com/woodyblog/p/9588544.html
Copyright © 2020-2023  润新知