zoukankan      html  css  js  c++  java
  • 前端01前端模块化IIFE,commonjs,AMD,UMD,ES6 Module规范超详细讲解

     

     

    https://saas.51cto.com/learner.html#/topic/detail?id=1348233985432469504

    https://saas.51cto.com/learner.html#/   手机企业yw-syw51saascto.com/

    https://www.cnblogs.com/xinxihua/p/14552132.html 前端05vscode+Vue 引入 ECharts

     

    https://www.cnblogs.com/xinxihua/p/14552105.html 前端04测试一个的项目

     

    https://www.cnblogs.com/xinxihua/p/14551901.html 前端3vue+mysql连接实例

     

    https://www.cnblogs.com/xinxihua/p/14551802.html 前端01前端模块化IIFE,commonjs,AMD,UMD,ES6 Module规范超详细讲解

     

    https://www.cnblogs.com/xinxihua/p/14551691.html 前端02vue+vscode+nodejs webpack开发环境搭建

     

     https://www.w3school.com.cn/jquery/jquery_ref_dom_element_methods.asp   Jquey

    jQuery 参考手册 - DOM 元素方法

     

    https://www.cnblogs.com/xiayizhan/p/8707127.html    SpringBoot-thymeleaf模板集成

    https://git-scm.com/download/gui/windows
    https://desktop.github.com/
    VSCode-SpringBoot与ECharts前后端分离图表绘制+Vue.js图片懒加载路径二次刷新过程演示
    https://www.bilibili.com/video/av458169809/
    VSCode-SpringBoot与ECharts前后端分离图表绘制+Vue.js图片懒加载路径二次刷新过程演示


    # -*- coding:utf-8 -*-
    #D:Python365djangoDemos0604hellobabyviews_foreign.py
    # D:Python365djangoDemos0604hellobabyviews.py
    from django.shortcuts import render
    from django.http import HttpResponse
    from __future__ import unicode_liternals
    from . import models
    from django.db.models.aggregates import *
    from django.db.models import F,Q

    #D:Python365djangoDemos0604hellobabyviews_foreign.py

    d:
    cd D:Python365djangoDemos0604
    @start iexplore http://127.0.0.1
    python manage.py runserver 127.0.0.1:80
    @start iexplore http://127.0.0.1:80/admin

    @pause
    exit

    常用的命令:
    --npm install 名字
    --npm uninstall 名字
    --npm version 查看版本信息
    --npm upgrade 包名 更新已经下载的包


    Visual Studio Code

    D: encentmicrosoftVS[Visual Studio Code

    npm init
    npm init -g
    npm run
    nrm
    nrm ls
    node -v
    npm -v
    d:
    cd D: encentnodejs
    mkdir node_global
    mkdir node_cache
    npm config set prefix "D: encentnodejs ode_global"
    d:
    cd D: encentnodejs
    mkdir node_global
    mkdir node_cache
    npm config set prefix "D: encentnodejs ode_global"
    npm config set cache "D: encentnodejs ode_cache"
    npm list -global
    npm list
    npm ls
    npm config list
    npm config set registry=http://registry.npm.taobao.org
    npm config list
    npm config get registry
    npm vue info
    npm info
    npm info vue06
    npm install npm -g
    npm -v
    doskey /HISTORY
    npm list -global

    d:
    cd D: encentnodejs
    vue init webpack vue20
    ##########要等待项目生成##########
    vue init webpack vue21
    ##########要等待项目生成##########
    vue init webpack vue22
    ##########要等待项目生成##########
    cd vue20
    npm install
    npm run dev
    ###########

    前端01前端模块化IIFE,commonjs,AMD,UMD,ES6 Module规范超详细讲解

    前端模块化IIFE,commonjs,AMD,UMD,ES6 Module规范超详细讲解

     

    为什么前端需要模块化

    在没有模块化的时候,多个脚本引入页面,会造成诸多问题,比如:

    • 多人协同开发的时候,系统中可能会引入很多js脚本,这些js会定义诸多全局变量,这时候很容易出现变量名覆盖的问题
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <script type="text/javascript">
            var info = "这是功能A";
        </script>
        <script type="text/javascript">
            var info = "这是功能B";
        </script>
        <script>
            console.log(info); // 这是功能B
        </script>
    </body>
    </html>
    

    上面的例子中可以看到 第一个js中定义的变量info的值被第二个js中的变量所覆盖

    • 当脚本之间存在依赖关系的时候,单纯的引用script看不出js之间的依赖,可读性很差
    <html>
    <!-- 此处省略head -->
    <body>
        <script type="text/javascript">
            function getMessage(){
                return "这是一条message"
            }
        </script>
        <script type="text/javascript">
            function showMessage(){
                console.log(getMessage());
            }
        </script>
        <script>
            showMessage(); // 这是一条message
        </script>
    </body>
    </html>
    

    如果第一个脚本没有引入,那么执行就会抛错,也就是说第二个脚本是依赖第一个脚本的,但是这个依赖关系这里看不出来

    什么是模块

    模块我理解为就是一个局部作用域,这个局部作用域内部定义了一些局部变量和方法,然后对外提供接口供外部调用,比如:

    var moduleA = {
        name : "A"
    }
    var moduleB = {
        name : "B"
    }
    
    console.log(moduleA.name); // A
    

    这里就可以看成是定义了两个最简单的模块,我们可以通过模块去访问各自的变量

    是什么IIFE

    IIFE(Immediately Ivoked Function Expression),即立即执行函数表达式,所谓立即执行,就是声明一个函数,声明完了立即执行

    var IIFE = function(){
        // ...
    }
    IIFE();
    

    这样是立即执行但是肯定会有一个问题,函数名冲突了怎么办?所以有了我们最常见的写法,声明一个自执行匿名函数

    (function(){
        // ...
    })()
    

    如果看过jquery的一些插件的源码的话经常能看到这样的代码

    (function($){
        // ...
    })(jQuery)
    

    这里其实就是表明 这个模块依赖了jquery

    举个栗子

    定义模块A和模块B,模块B依赖模块A

    • js文件
    // 模块A moduleA.js
    (function(window){
        var name = "module A";
        // 对外暴露对象moduleA
        window.moduleA = {
            getName(){
                return name;
            }
        }
    })(window)
    
    // 模块B moduleB.js
    (function(window, moduleA){
        // 对外暴露对象moduleB
        window.moduleB = {
            showFirstModuleName(){
                console.log(moduleA.getName());
            }
        }
    })(window, moduleA)
    
    // main.js
    (function(moduleB){
        console.log(moduleB.showFirstModuleName());
    })(moduleB)
    
    • html文件中
    <html>
    <!-- 此处省略head -->
    <body>
        <script type="text/javascript" type="./moduleA.js"></script>
        <script type="text/javascript" type="./moduleB.js"></script>
        <script type="text/javascript" type="./main.js"></script>
    </body>
    </html>
    

    上述例子展示了如何用IIFE来定义模块,这样写有几个缺点

    • 定义了3个模块,那么就引入了3个js脚本,那如果有更多模块呢,那就意味着很页面加载时会像服务器发起多次http请求,这是不好的
    • html中script的标签顺序是固定的,因为模块main依赖moduleB,moduleB依赖moduleA,所以moduleA必须先声明,这样在moduleB的IIFE执行时候才能正常,不然会抛处ReferenceError

    模块化标准

    Commonjs

    nodejs采用的模块化标准,commonjs使用方法require来引入模块,这里require()接收的参数是模块名或者是模块文件的路径,如果是模块名的话,require会到node_modules中去找对应名称的模块来加载

    const _ = require("lodash");
    

    这里就引入了一个名为lodash的模块,那么一个模块应该如何对外提供接口呢?
    commonjs提供两种方式对外暴露接口

    // 第一种module.exports
    const name = "张三";
    module.exports = {
        getName(){
            return name
        }
    }
    
    // 第二种
    const name = "张三"
    exports.getName = function(){
        return name;
    }
    

    其实本质上,模块对外暴露的就是exports这个对象,module.exports =这种写法,相当于直接给exports对象赋值,而export. name这种写法其实就是给exports对象上添加了一个名为"name"的方法

    特征

    • 在node运行时执行
    • require是对值的拷贝
    // moduleA.js
    let count = 1;
    
    // 异步让count++
    setTimeout(()=>{
        count++;
    });
    
    exports.count = count;
    
    // main.js
    const {count} = require("./moduleA.js");
    
    // 同步打印count
    console.log(count); // 打印值为1
    
    // 异步打印count
    setTimeout(()=>{
        console.log(count); // 打印值为1
    });
    

    可见改变了moduleA中的count,并不影响main.js中引入的值

    • 不做特殊处理(webpack打包)commonjs只能运行在node环境,浏览器环境不能直接使用,window上没有定义require这个方法,所以解释脚本的时候就会抛处ReferenceError
    • commonjs是同步加载模块,在node环境中require引入一个模块的时候,这个过程是同步的,必须等模块加载完才能继续后续操作

    IIFE中的例子用commonjs实现

    上述IIFE中的例子,用commonjs来实现就看起来就更清晰:

    // 模块A moduleA.js
    const name = "module A"
    module.exports = {
        getName(){
            return name;
        }
    }
    
    // 模块B moduleB.js
    const {getName} = require("./moduleA.js"); // 引入moduleA
    
    exports.showFirstModuleName = function(){
        console.log(getName());
    }
    
    // main.js
    const moduleB = require("./moduleB.js");
    
    moduleB.showFirstModuleName(); // module A
    

    上文中讲commonjs的特性的时候提到过,不能直接在浏览器中运行,所以我们需要先使用打包用具(webpack等工具,以后的文章中会写)把js打包处理成浏览器能直接运行的bundle.js,在引入到html中

    <html>
    <!-- 此处省略head -->
    <body>
        <script type="text/javascript" type="./dist/bundle.js"></script>
    </body>
    </html>
    

    或者直接在用node运行main:

    -> node main.js
    

    AMD和RequireJS

    全称Asynchronous Module Definition异步模块定义,与commonjs不同AMD是完全针对浏览器的模块化定义,AMD加载模块是异步的

    如何定义一个模块

    AMD规范中定义模块用到方法define,还是以之前的例子来举例,先来定义一个没有依赖的模块moduleA

    // 定义一个moduleA.js
    define(function(){
        var name = "module A"
        return {
            getName(){
                return name
            }
        }
    })
    

    这里define只接受了一个回调函数作为参数,这个回调是不是与IIFE有点相似,再来定义一个依赖moduleA的moduleB

    // 定义一个moduleB.js
    define(["moduleA"], function(moduleA){
        return {
            showFirstModuleName(){
                console.log(moduleA.getName());
            }
        }
    });
    

    这里define的第一个参数是一个数组,数组里面放的是当前定义的模块所依赖的模块的名字,而后面回调函数接收的参数就是对应的模块了,也许看到这里你会想问,为什么这里只写一个模块名“moduleA”就能找到对应的moduleA.js的文件了呢?后面会讲

    如何在入口文件引入模块

    我们已经实现了moduleA.js和moduleB.js接下来要实现入口main.js,AMD的标准中,引入模块需要用到方法require,看到这你可能会有疑问,前面说commonjs的时候,不是说了window对象上没定义require吗?这里就不得不提到一个库,那就是RequireJS

    RequireJS is a JavaScript file and module loader.

    官网介绍RequireJS是一个js文件和模块的加载器,提供了加载和定义模块的api,当在页面中引入了RequireJS之后,我们便能够在全局调用define和require,下面来实现main.js

    // 实现main.js
    require(["moduleB"], function(moduleB){
        moduleB.showFirstModuleName();
    });
    

    三个js文件都写好了,我们该如何引入到页面中呢?查看RequireJS官网已经给出了答案

    <html>
    <!-- 此处省略head -->
    <body>
        <!--引入requirejs并且在这里指定入口文件的地址-->
        <script data-main="js/main.js" src="js/require.js"></script>
    </body>
    </html>
    

    要通过script引入requirejs,然后需要为标签加一个属性data-main来指定入口文件

    使用RequireJS需要的配置

    前面介绍用define来定义一个模块的时候,直接传“模块名”似乎就能找到对应的文件,这一块是在哪实现的呢?其实在使用RequireJS之前还需要为它做一个配置

    // main.js
    require.config({
        paths : {
            // key为模块名称, value为模块的路径
            "moduleA" : "./moduleA",
            "moduleB" : "./moduleB"
        }
    });
    
    require(["moduleB"], function(moduleB){
        moduleB.showFirstModuleName();
    });
    

    这个配置中的属性paths应该说是一目了然,看了就能明白,为什么引入的时候只写模块名就能找到对应路径了吧,不过这里有一项要注意的是,路径后面不能跟.js文件后缀名,更多的配置项请参考RequireJS官网

    ES6 module

    es6提出了新的模块化方案,这个方案应该也是现在最流行的。通过关键字export value来暴露模块,通过import moduleName from path来引入模块,是不是看起来很简单?但是其实这里还有很多细节

    如何运行

    • 浏览器端是不能直接运行的,需要先用babel将es6语法转译成es5(把import转译成了require),然后再使用打包工具打包,最后在页面中引入
    • node端在某个版本后有办法直接运行了(抱歉没查是哪个版本),首先js文件的后缀名都要改成.mjs,然后再命令行直接运行node --experimental-modules main.mjs

    多次暴露

    模块可以多次调用export来暴露任何值

    // moduleA.mjs
    // 暴露一个变量
    export let name = "张三"
    
    // 暴露一个方法
    export function getName(){
        return name;
    }
    
    export function setName(newName){
        name = newName;
    }
    
    // main.mjs
    import {name, getName, setName} from "./moduleA";
    
    console.log(name); // 张三
    setName("李四");
    console.log(getName()); // 李四
    

    这里import后面必须跟结构赋值如果写成下面这样,会输出undefined

    import moduleA from "./moduleA"
    
    console.log(moduleA); // undefined;在node环境下运行会报错
    

    那如果模块分别暴露的方法有很多怎么办呢,这时候结构赋值不是要写很多个方法?其实还可以这样引入

    import * as moduleA from "./moduleA";
    
    console.log(moduleA.name); // 张三
    moduleA.setName("李四");
    console.log(moduleA.getName()); // 李四
    

    默认暴露

    es6还提供了一种暴露方法叫默认暴露,默认暴露即export default value这里的value可以是任何值,为什么上面举得import的反例,引入结果会是undefined呢,再看一个例子

    // moduleA.mjs
    export default {
        name : 张三,
        setName(newName){
            this.name = newName;
        },
        getName(){
            return this.name;
        }
    }
    
    // main.mjs
    import moduleA from "./moduleA"
    
    console.log(moduleA);  // { name: '张三', setName: [Function: setName], getName: [Function: getName] }
    

    这里其实就能看出来,直接引入给moduleA赋值的其实是export default value后面的value

    UMD

    UMD全称为Universal Module Definition,也就是通用模块定义,为什么叫通用呢,我们怎么描述一个模块是通用的呢?举个例子,假如现在我的项目使用的是amd模块规范,那么现在我引入了一个用commonjs规范写的模块,能正常运行吗?肯定不行的,而UMD就是解决了这个问题。

    特点

    umd所谓的通用,就是兼容了commonjs和amd规范,这意味着无论是在commonjs规范的项目中,还是amd规范的项目中,都可以直接引用umd规范的模块使用(牛逼!)

    原理

    原理其实就是在模块中去判断全局是否存在exports和define,如果存在exports,那么以commonjs的方式暴露模块,如果存在define那么以amd的方式暴露模块

    (function(window, factory){
        if(typeof exports === "objects"){
            // commonjs
            module.exports = factory();
        }else if(typeof define === "function" && define.amd){
            // amd
            define(factory);
        }else{
            window.moduleA = factory();
        }
    })(window, function(){
        // 返回module
        let modlueA = {
            name : "张三",
            setName(newName){
                thie.name = newName;
            },
            getName(){
                return this.name;
            }
        }
        return modlueA;s
    })
    
     
  • 相关阅读:
    内网邮件服务器搭建
    solr的命令
    solr的post.jar
    Java IO(四--字符流基本使用
    Java IO(三)--字节流基本使用
    Java IO(二)--RandomAccessFile基本使用
    Java集合(三)--Collection、Collections和Arrays
    Java集合(二)--Iterator和Iterable
    Java IO(一)--File类
    Java基础(十四)--装箱、拆箱详解
  • 原文地址:https://www.cnblogs.com/xinxihua/p/14551802.html
Copyright © 2011-2022 走看看