require js

Require原理

在require中,根据AMD(Asynchronous Module Definition)的思想,即异步模块加载机制,其思想就是把代码分为一个一个的模块来分块加载,这样无疑可以提高代码的重用。

在整个require中,主要的方法就两个:require和define,我们先来聊聊require

require作为主函数来引入我们的“模块”,require会从自身的存储中去查找对应的defined模块,如果没有找到,则这时这个模块有可以存在三种状态:loading, enabling, defining,这就是require中要注意的地方,如果模块还没有被加载,那么它的这三种状态出现的时机是:

loading 文件还没有加载完毕

enabling 对该模块的依赖进行加载和模块化

defining 对正在处理的模块进行加载,并运行模块中的callback

js是单线程操作,拿过来直接加载不就完了吗? 先看require的load方法的主要代码:

req.load = function (context, moduleName, url) {

var config = (context && context.config) || {},

node;

if (isBrowser) {

//create a async script element

node = req.createNode(config, moduleName, url);

//add Events [onreadystatechange,load,error]

//set url for loading

node.src = url;

//insert script element to head and start load

currentlyAddingScript = node;

if (baseElement) {

head.insertBefore(node, baseElement);

} else {

head.appendChild(node);

}

currentlyAddingScript = null;

return node;

} else if (isWebWorker) {

//do something

}

};

req.createNode = function (config, moduleName, url) {

var node = config.xhtml ?

document.createElementNS(‘http://www.w3.org/1999/xhtml‘, ‘html:script‘) :

document.createElement(‘script‘);

node.type = config.scriptType || ‘text/javascript‘;

node.charset = ‘utf-8‘;

node.async = true;

return node;

};

require使用的是script标签去拿js,细心的同学会注意到node上设定了 async 属性(异步加载script标签),并且在标签上绑定了load等事件,当文件loading完成后,则要做的主要工作是执行 completeLoad 事件函数,但是要注意的是这时候把script加载完成后,立即执行的是script标签内部的内容,执行完后才触发的 completeLoad事件,而在我们的模块里面,一定要用define函数来对模块进行定义,所以这里我们先穿插着来讲讲define干了什么

define

define顾名思义是去定义一个模块,但它并不是单纯的去定义

define = function (name, deps, callback) {

var node,

context;

//do for multiple constructor

......

//If no name, and callback is a function, then figure out if it a

//CommonJS thing with dependencies.

if (!deps && isFunction(callback)) {

deps = [];

//Remove comments from the callback string,

//look for require calls, and pull them into the dependencies,

//but only if there are function args.

if (callback.length) {

callback

.toString()

.replace(commentRegExp, ‘‘)

.replace(cjsRequireRegExp, function (match, dep) {

deps.push(dep);

});

deps = (callback.length === 1 ? [‘require‘] : [‘require‘, ‘exports‘, ‘module‘]).concat(deps);

}

}

//If in IE 6-8 and hit an anonymous define() call, do the interactive work.

if (useInteractive) {

node = currentlyAddingScript || getInteractiveScript();

if (node) {

if (!name) {

name = node.getAttribute(‘data-requiremodule‘);

}

context = contexts[node.getAttribute(‘data-requirecontext‘)];

}

}

//add to queue line

if (context) {

context.defQueue.push([name, deps, callback]);

context.defQueueMap[name] = true;

} else {

globalDefQueue.push([name, deps, callback]);

}

};

这就是define函数,代码不是很多,但是新奇的东西却是有一个!!!那就是代码中对 callback.toString() 文本来进行 正则匹配 ,我们看看这两个replace中的正则表达式是什么样的

commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg;

cjsRequireRegExp = /[^.]\s*require\s*\(\s*["‘]([^‘"\s]+)["‘]\s*\)/g;

第一个正则是用来支掉callback中的注释的,而第二个正则是用来匹配callback.toString() 文本中的 require(.....) ,并将 ..... 这个字段push到queue中,这个方法是不是很变态?现在让我们来接着回到require的completeLoad 函数

require回归

rquire的compeleteLoad函数又做了什么

completeLoad : function (moduleName) {

var found,

args,

mod,

shim = getOwn(config.shim, moduleName) || {},

shExports = shim.exports;

takeGlobalQueue();

while (defQueue.length) {

args = defQueue.shift();

if (args[0] === null) {

args[0] = moduleName;

//If already found an anonymous module and bound it

//to this name, then this is some other anon module

//waiting for its completeLoad to fire.

if (found) {

break;

}

found = true;

} else if (args[0] === moduleName) {

//Found matching define call for this script!

found = true;

}

callGetModule(args);

}

context.defQueueMap = {};

//Do this after the cycle of callGetModule in case the result

//of those calls/init calls changes the registry.

mod = getOwn(registry, moduleName);

if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) {

if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {

if (hasPathFallback(moduleName)) {

return;

} else {

return onError(makeError(‘nodefine‘,

‘No define call for ‘ + moduleName,

null,

[moduleName]));

}

} else {

//A script that does not call define(), so just simulate

//the call for it.

callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);

}

}

checkLoaded();

}

这个函数主要是去做了从queue中拿出来define里push进去的字符串,并调用callGetModule 去调用模块, callGetModule 又去做了什么

function callGetModule(args) {

//Skip modules already defined.

if (!hasProp(defined, args[0])) {

getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);

}

}

在require内部,有一个 defined 全局变量来储存已经定义好的模块,如果这个模块目前没有定义,那就再做下面的 makeModuleMap ,这个方法则是用来实现对当前module信息的组装,并生成一个Map,它将会返回以下的值:

return {

prefix : prefix,

name : normalizedName,

parentMap : parentModuleMap,

unnormalized : !!suffix,

url : url,

originalName : originalName,

isDefine : isDefine,

id : (prefix ?

prefix + ‘!‘ + normalizedName :

normalizedName) + suffix

};

然后再去调用 getModule ,这也是require里面来组装module的主要方法,在require内部定义了 Module类 ,而这个方法则会为当前的 ModuleMap ,其中包含了这个模块的路径等信息。这里要注意的是getModule方法里面拥有一个 基于全局context的registry变量 ,这里则是用来保存根据ModuleMap来实例化的Module,并将其保存在了 registry 变量中(立即保存的Module只是一个空壳,后面实例中介绍),后面会介绍代码的重用如何实现的。

我们直接来看看Module类是长什么样的:

Module = function (map) {

this.events = getOwn(undefEvents, map.id) || {};

this.map = map;

this.shim = getOwn(config.shim, map.id);

this.depExports = [];

this.depMaps = [];

this.depMatched = [];

this.pluginMaps = {};

this.depCount = 0;

};

Module.prototype = {

//init Module

init : function (depMaps, factory, errback, options) {},

//define dependencies

defineDep : function (i, depExports) {},

//call require for plugins

fetch : function () {},

//use script to load js

load : function () {},

//Checks if the module is ready to define itself, and if so, define it.

check : function () {},

//call Plugins if them exist and defines them

callPlugin : function () {},

//enable dependencies and call defineDep

enable : function () {},

//register event

on : function (name, cb) {},

//trigger event

emit : function (name, evt) {}

}

new一个Module后,使用init来对Module对象进行初始化,并主要传入其的依赖数组和工厂化方法。这这么多的方法里,主要的两个方法则是 enable 和 check 方法,这两个方法是对方法,当init里调用enable后,下来将要进行的就是一个不断重复的过程,但是过程的主角在一直改变。

递归

上面说的这个过程那就是在初始化Model的时候去查找它的依赖,再去 load方法异步地去请求依赖 ,而依赖又是一个个Module,又会再对自己自身的依赖的依赖进行查找。由于这个过程都是异步进行的,所以都是通过事件监听回调来完成调用的,我们来举下面的例子:

  • A 的依赖有 B C
  • B 的依赖有 C D
  • C 的依赖有 A B

这是一个很绕的例子,如A,B,C都有自己的方法,而我们在实现时都互相调用了各自的方法,我们姑且不讨论这种情况的现实性。

当如果我去 require("A") 时,require去查找 defined 中是否有A模块,如果没有,则去调用 makeModuleMap 来为即将调用的模块实例一个 ModuleMap 并加入到defined中,再用ModuleMap实例化一个 Module 加入到registry中,但是这时候的Module是一个空壳,它是只存储了一些模块相关的依赖等,模块里的exports或者callback是还没有被嵌进来,因为这个文件根本没有被加载呀!

注册时触发 Module.init 方法去异步加载文件(使用script)。加载完毕后,触发A里的define函数,define函数通过参数或callback里查找A模块需要的依赖,即B和C模块,将B,C加入到A的依赖数组中。这时则触发 completeLoad 函数,这时complete再去从queue中遍历,调用 callGetModule 去查找B、C模块,这时则会创建B和C模块的ModuleMap,根据ModuleMap去实例化空壳Module,( 调用异步load加载,再触发define等,继续查找依赖………… ),再接下来会做 checkLoaded ,我们看看这个函数:

function checkLoaded() {

var err,

usingPathFallback,

waitInterval = config.waitSeconds * 1000,

//It is possible to disable the wait interval by using waitSeconds of 0.

expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),

noLoads = [],

reqCalls = [],

stillLoading = false,

needCycleCheck = true;

//Do not bother if this call was a result of a cycle break.

if (inCheckLoaded) {

return;

}

inCheckLoaded = true;

//Figure out the state of all the modules.

eachProp(enabledRegistry, function (mod) {

var map = mod.map,

modId = map.id;

//Skip things that are not enabled or in error state.

if (!mod.enabled) {

return;

}

if (!map.isDefine) {

reqCalls.push(mod);

}

if (!mod.error) {

//If the module should be executed, and it has not

//been inited and time is up, remember it.

if (!mod.inited && expired) {

if (hasPathFallback(modId)) {

usingPathFallback = true;

stillLoading = true;

} else {

noLoads.push(modId);

removeScript(modId);

}

} else if (!mod.inited && mod.fetched && map.isDefine) {

stillLoading = true;

if (!map.prefix) {

//No reason to keep looking for unfinished

//loading. If the only stillLoading is a

//plugin resource though, keep going,

//because it may be that a plugin resource

//is waiting on a non-plugin cycle.

return (needCycleCheck = false);

}

}

}

});

if (expired && noLoads.length) {

//If wait time expired, throw error of unloaded modules.

err = makeError(‘timeout‘, ‘Load timeout for modules: ‘ + noLoads, null, noLoads);

err.contextName = context.contextName;

return onError(err);

}

//Not expired, check for a cycle.

if (needCycleCheck) {

each(reqCalls, function (mod) {

breakCycle(mod, {}, {});

});

}

if ((!expired || usingPathFallback) && stillLoading) {

//Something is still waiting to load. Wait for it, but only

//if a timeout is not already in effect.

if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {

checkLoadedTimeoutId = setTimeout(function () {

checkLoadedTimeoutId = 0;

checkLoaded();

}, 50);

}

}

inCheckLoaded = false;

}

这个函数对所有已在 registry 中的Module进行遍历,并来判断其是否已经完成了定义(定义是在 Module.check 函数里完成的,定义完成后 ModuleMap.isDefined = true ,并将其从registry中删除,其会将真正的模块内容注入到对应的defined中),注意这里有一个重要的地方, checkoutLoadTimeoutId 是一个间隔为50ms的setTimeout函数,即当在加载的时候会不断轮询去查看所有模块是否已经加载好了,因为所有的模块都是异步进行加载的,所以这样可以完全保证所有模块进行完全加载,并进行了过期设定。

接着上面的例子讲,当加载B模块时,会去查找A和C模块,这时候A模块是已经加载的,但是不能确定C是否已经加载好,但是这时的C模块空壳Module已经加入到了registry中,所以这时会像上面去轮询C模块是否加载, C模块不加载好,是无法对B模块进行注入的,B模块在这一阶段仍是那一个registry里的空壳Module ,直至C模块已经定义,B模块的depCount成为0,才可以继续运行去注入自己。在对模块进行define的时候,用上了defining,是为了防止内部的factory进行加工时,再去尝试去define这个Module,就像一个圈一样,掐断了它。

这就是整个require工作的流程,其中主要使用了异步加载,所以让这个思想变得异常的复杂,但是带来的却是性能上的优化,需要我们注意的是:

在使用require时,我们需要注意依赖包的引入,如果我们把B的改成 define("B",[],callback) ,这时 B是没有callback依赖预读 ,那么我们在引入A模块的时候异步加载了B和C模块,但是B模块里使用了C模块的方法,这里的B是直接运行的, 并不去检测其的依赖包是否加载完毕 ,所以这时的B运行时碰到 require("C") 时,C模块是否加载好是不确定的,这时候代码会不会出问题就是网速的问题了……………………

小心

我们在使用时要小心define()的用法:

  • define(name, dependencies, callback)

    将依赖写在参数dependencies中,这样require时会对里面的依赖进行加载,加载完后才会执行callback

  • define(name, callback) 

    直接在callback中require依赖,会对 callback.toString() 进行正则查找require(....) ,同样加载查找出的所有依赖并加载完后执行callback

  • define(callback) 

    同上

使用时千万不能在第一种情况下直接require依赖,这样并不能保障该模块是否已被定义下执行了callback

时间: 2024-10-13 01:41:09

require js的相关文章

require.js的简单使用

<script src="js/require.js"></script> <script src="js/require.js" data-main="js/main"></script> require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){ // some code here }); 使用require.

require.js JQ

require.js和sea.js的作用都是一样的. 为了解决两大问题,第一实现js文件的异步加载,避免网页失去响应,第二管理模块之间的依赖性. 基本的模板 define(function(require,exports,module){ exports.getStyle = function (obj,name){ //你初始的模块 } }) define(function(require,exports,module){ var get = require('get');//引入初始模块(基

Javascript模块化编程(三):require.js的用法

作者: 阮一峰 日期: 2012年11月 7日 这个系列的第一部分和第二部分,介绍了Javascript模块原型和理论概念,今天介绍如何将它们用于实战. 我采用的是一个非常流行的库require.js. 一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代码,相信很多人都见过. <script src="1.js"></s

require.js的AMD规范详解

require.js使用简介 在web刚开始发展的蛮荒时代,一个页面中只需要加载一个或少量的js文件,不存在模块,也不存在冲突之类的问题,但随着web项目的发展,它越来越大,js文件动辄几十个,怎么加载就成为了一个问题,要为浏览器的性能考虑,还有各个js文件(模块)的依赖关系.require.js的出现就是为了解决这样的问题. 1.实现js文件的异步加载,避免网页失去响应. 2.管理模块之间的依赖性,便于代码的编写和维护. require.js加载 使用require.js的第一步,是先去官方网

require.js 的使用

一.为什么要用require.js 在同一个页面要加载多个js文件时,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长: 其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载, 当依赖关系很复杂的时候,代码的编写和维护都会变得困难. 例如: <script src="1.js"></script>  <script src="2.js"&g

Require.js、Angular.js整合

Require.js 详见: 初识Require.js 解密Require.js Angular.js 详见: 初识Angular.js之爱恨情仇 整合Require.js.Angular.js 配置 在Requirejs中配置angular.js. require-main.js // 配置 requirejs.config({ ... paths: { 'jquery': 'libs/jquery-2.1.3/jquery.min', 'angular': 'libs/angular-1.3

【转】require.js学习笔记(二)

require.js遵循AMD规范,通过define定义模块,require异步加载模块,一个js文件即一个模块. 一.模块加载require1.加载符合AMD规范模块 HTML: <script src="js/require.js" data-main="js/main"></script> MAIN.JS require.config({ baseUrl: "js/lib", paths: { "jquer

require.js的用法

关于拖延症的话题我在Hacker News上不断的看到有人提出来(你也读了,不是吗?),感觉有必要将我是如何跟拖延症做斗争的方法分享给大家.然而,我这里说的主要是针对程序员/美工,但其实任何人都可以使用.首先最重要的-. 它不是那些老套陈旧的动机心理学扯谈. 我并不是说那些传统的应对拖延症的方法理论不对,只是对我无效.当正经历极度消沉的时候,我通常听到的理论的最后一句话是"You just DO IT!".我有很多的事情要去做.但我不会去阅读你那400页的治疗拖延症手册,也不会执行你那

require.js 源码解读——配置默认上下文

首先,我们先来简单说一下,require.js的原理: 1.载入模块? 2.通过模块名解析出模块信息,以及计算出URL? 3.通过创建SCRIPT的形式把模块加载到页面中.? 4.判断被加载的脚本,如果发现它有依赖就去加载依赖模块.如果不依赖其它模块,就直接执行factory方法 ?5.等所有脚本都被加载完毕就执行加载完成之后的回调函数. 从今天起,我们跟着我们简单的例子,通过跟踪代码,来了解require.js的源码. 1 <!DOCTYPE html> 2 <html lang=&q

require js 将config和入口函数分开写

原文地址 https://github.com/jrburke/requirejs/issues/354 Area there any plans to standardize/recommend a particular approach? If there is, I would suggest the following: // homepage.html <script data-id="requirejs" data-logic="home" dat