Requirejs

Posted songjp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Requirejs相关的知识,希望对你有一定的参考价值。

一.规范

为了更好的性能,建议html中没有内联的脚本,只引用requirejs来加载脚本。

<script data-main="./main.js" src="./require.js"></script>  

data-main属性告诉requirejs,加载完require.js再去加载main.js

二.加载js文件

  • baseUrl: requirejs相对于baseUrl来加载脚本
<script data-main="scripts/main.js" src="scripts/require.js"></script>

以上代码将baseUrl设置为"scripts" 目录, 并且main.js会有一个moduleID为 ‘main‘

baseUrl 也能通过RequireJS config配置,如果没有配置且没有使用data-main属性,默认将使用包含当前HTML页面的目录路径作为baseUrl

RequireJS默认所有的依赖都是脚本,因此可以省去后缀 .js ,当解析当该模块时,路径会自动加上后缀。

当你想直接引用一个脚本,而不是通过 "baseUrl + path"的方式来寻找时,只需要module ID有以下三个特征,requirejs查找该模块时就不会加上"baseUrl + path",只会被当做相对于当前HTML的一个路径:

  • 以 .js 结尾
  • 以 / 开头
  • 包含http,https协议

以下路径区分app.js 以及 第三方库library.js:

文件夹

|-- index.html
|-- js/
    |-- app/
        |-- sub.js
    |-- lib/
        |-- jquery.js
        |-- canvas.js
    |-- app.js
    |-- require.js

index.html

<script data-main="js/app.js" src="js/require.js"></script>

app.js

require.config({
    // 默认从js/lib加载模块
    baseUrl: ‘js/lib‘,
    // 以app开头的模块,从js/app 加载
    // paths属性是相对baseUrl来说的:js/lib/../app = js/app
    paths: {
        app: ‘../app‘
    }

});

// 实际的逻辑代码
requirejs([‘jquery‘, ‘canvas‘, ‘app/sub‘],
function   ($,        canvas,   sub) {
  // 这些模块的return值,依次被注入
});

理想的情况是所有加载的模块都是通过define() 定义的规范模块,当加载非规范模块时,需要使用shim 配置(jquery3.1已支持define)。

js/lib/myLib.js

function myFub(){
    console.log("i am not amd module");
}

js/app.js

require.config({
  baseUrl: ‘js/lib‘,
  paths:{
    app: ‘../app‘,
  },
  shim:{
  }
})
require([‘jquery‘,‘myLib‘],function(jquery,myLib){
  console.log(myLib);  // undefined
  console.log(myFun);      // function 
})

此时myLib 为undefined, 因为myLib.js 不符合AMD规范,但是打印 myFun是有值的,即全局范围内,myFun是有定义的,但是不能注入到myLib变量中。

此时通过配置shim,将变量注入到myLib中:

shim:{
    ‘myLib‘:{
        deps:[],   // 声明依赖
        exports:‘myFun‘   // myLib中的某个全局变量,将该变量暴露出去作为myLib的值
    }
}

这样
require([‘jquery‘,‘myLib‘],function(jquery,myLib){
  console.log(myLib);  // myLib被注入为 myFun的值 function
  console.log(myFun);      // function 
})

三. data-main入口点

data-main中设置的脚本是异步加载的:

<script data-main="scripts/main" src="scripts/require.js"></script>
<script src="scripts/other.js"></script>

// main.js

require.config({
    paths: {
        foo: ‘libs/foo-1.1.3‘
    }
});

// other.js

require( [‘foo‘], function( foo ) {
});

data-main表明,在require.js加载完之后去异步加载main.js,此时会继续执行other.js,这导致可能会先执行other.js,此时require(‘foo‘) 实际上是去加载 (baseUrl: scripts/) scripts/foo.js,而不是加载 scripts/libs/foo.js,因为此时main.js可能还未执行。

四. 定义模块

模块定义了一个作用域来避免全局名称污染:

// myLib.js

define(function(){
  var a = {
    color:‘black‘,
    size:"unsize"
  };
  return a;
})

此时只能通过require加载myLib.js, 全局下window.a为undefined。

  1. 简单键值对, 如果一个模块没有任何依赖,仅含值对。
define({
    name:"conan",
    size:"small"
})
  1. 函数式定义,没有依赖,但是需要做初始化工作。
define(function(){
    // 初始化
    return {
        name:‘conan‘,
        size:"small"
    }
})
  1. 存在依赖,第一个参数是依赖的名称数组,第二个参数是函数,其中参数为对应依赖返回的值。
define(["./lib1", "./lib2"], function(lib1, lib2) {
        return {
            name: "conan",
            size: "small",
            addToCart: function() {
                lib1.decrement(this);
                lib2.add(this);
            }
        }
    }
);
  1. 模块不一定返回对象,任何函数的返回值都是允许的,比如返回函数
define(["./lib1", "./lib2"], function(lib1, lib2) {
        return  function(title){
            console.log(title);
        }
    }
);
  1. 命名模块
define(‘foo/title‘,[‘my/lib1‘,‘my/lib2‘], function(lib1, lib2){
    return {
        
    }
})

当显式指定模块名称的时候,使得模块更不具备移植性,当把文件移到别的目录,就得重命名。因此最好避免对模块命名,交给优化工具生成,优化工具需要生产模块名将多个模块打包成一个包加快浏览器载入速度。

  1. 注意事项

如果需要在define内部使用类似require(‘./relative/name‘)加载一个相对路径的模块,记得把require本身作为一个依赖注入,同时相对路径的模块也是作为依赖:

// myLib2.js

define({
  name :"lib123"
})

// myLib.js

define([‘require‘,‘../app/myLib2‘],function(require){
  var lib2 = require(‘../app/myLib2‘);
  console.log(lib2);  // {name : "lib123"}
  var a = {
    name:‘conan‘,
    size:"small"
  };
  return a;
})

事实证明这样也行

define([‘../app/myLib2‘],function(){
  var lib2 = require(‘../app/myLib2‘);  // 全局require
  console.log(lib2);  // {name : "lib123"}
  var a = {
    name:‘conan‘,
    size:"small"
  };
  return a;
})

// 目录

|-- index.html
|-- js/
    |-- app/
        |-- sub.js
        |-- myLib2.js
    |-- lib/
        |-- jquery.js
        |-- myLib.js
    |-- app.js
    |-- require.js

生成相对于模块的URL地址:当需要生成一个相对于该模块的URL地址时,可以将 require 作为依赖注入??,调用require.toUrl()

define([‘require‘],function(require){
  var lib2 = require.toUrl(‘../app/myLib2‘);
  console.log(lib2);      // js/lib/../app/myLib2
  var a = {
    name:‘conan‘,
    size:"small"
  };
  return a;
})

同上,不将require注入也行, 则使用全局的require。

define(function(){
  var lib2 = require.toUrl(‘../app/myLib2‘);
  console.log(lib2);      // js/lib/../app/myLib2
  var a = {
    name:‘conan‘,
    size:"small"
  };
  return a;
})

五. 配置

map

如有两类模块需要使用不同版本的 foo 模块:

requirejs.config({
    map: {
        ‘some/newmodule‘: {
            ‘foo‘: ‘foo1.2‘
        },
        ‘some/oldmodule‘: {
            ‘foo‘: ‘foo1.0‘
        }
    }
});

// 目录

|-- foo1.0.js
|-- foo1.2.js
|-- some/
    |-- newmodule.js
    |-- oldmodule.js

当 some/newmodule 调用 require(‘foo‘)时,将获取foo1.2.js

当 some/oldmodule 调用 require(‘foo‘)时,将获取foo1.0.js

六. 插件 Dom Ready

某个模块需要操作dom时,需要在DOM加载完成之后再执行模块。

// 第一种方式:回调
require([‘domReady‘], function (domReady) {
  domReady(function () {
    //This function is called once the DOM is ready.
    //It will be safe to query the DOM and manipulate
    //DOM nodes in this function.
  });
});

上诉代码也可以使用下面的 loader plugin 书写方式,并建议采用下述方式。

第二种方式:loader plugin
require([‘domReady!‘], function (doc) {
    // 在此操作dom
    //document.
});

注意: 如果document需要一段时间来加载(也许是因为页面较大,或加载了较大的js脚本阻塞了DOM计算),使用domReady作为loader plugin可能会导致RequireJS“超时”错。

如果这是个问题,则考虑增加waitSeconds配置项的值,或在require()使用domReady()调用(将其当做是一个模块)。

七. 命名模块

前面一直在无视命名模块的概念,直到遇到一个bug

require.config({
  baseUrl: ‘js/lib‘,
  paths:{
    app: ‘../app‘,
    jquery:‘https://cdn.bootcss.com/jquery/3.3.1/jquery‘               ---- 1
  }
})

require([‘jquery‘], function ($) {                                     ---- 2
  console.log($);  // 能打印出jquery对象
})

而如果将1,2处的jquery改为 jquery1或者其它字符串,则会报错。原因是因为 cdn上的jquery.js本身有这么一段代码:

if ( typeof define === "function" && define.amd ) {
    define( "jquery", [], function() {
        return jQuery;
    } );
}

即该模块定义的是命名模块,不能以其它名称去require。

以下是一些命名模块的例子

//目录

js/
|--  app/
|--  lib/
     |-- lib.js
|-- app.js
|-- require.js 

// lib.js 该文件中定义了一个命名模块和一个匿名模块

define([],function(){          //匿名模块
  return {
    name:"noNameModule"
  }
})

define("namedModule",[],function(){   // 有名模块
  return {
    name:"namedModule"
  }
})

// app.js

require.config({
  baseUrl: ‘js/lib‘,
  paths:{
    app: ‘../app‘,
  }
})
require([‘lib‘],function(lib){   // 寻找 js/lib/lib.js,找到匿名模块的输出
  console.log(lib);   // noNameModule
})
require([‘namedModule‘], function (l) {  // 寻找 js/lib/nameModule.js, 会报错
  console.log(l); // 前面寻找了 js/lib/lib.js 中有nameModule模块,所以会输出 namedModule
})

// 情况一

require.config({
  baseUrl: ‘js/lib‘,
  paths:{
    app: ‘../app‘,
  }
})
require([‘namedModule‘], function (l) {  // 寻找 js/lib/nameModule.js, 会报错
  console.log(l); // 没有输出
})
require([‘lib‘],function(lib){   // 寻找 js/lib/lib.js,找到匿名模块的输出
  console.log(lib);   // noNameModule
})

// 情况二

require.config({
  baseUrl: ‘js/lib‘,
  paths:{
    app: ‘../app‘,
  }
})

require([‘lib‘],function(lib){   // 寻找 js/lib/lib.js,找到匿名模块的输出
  console.log(lib);   // noNameModule
  require([‘namedModule‘], function (l) {  // 寻找 js/lib/lib.js 里的namedModule, 不会报错
      console.log(l); // namedModule
  })
})

以上是关于Requirejs的主要内容,如果未能解决你的问题,请参考以下文章

requirejs

RequireJS

requireJS文件夹

RequireJS和AMD规范

requireJS

requirejs - 将多个文件组合成一个不依赖于 requirejs 的 js 文件