RequireJs 源码解读及思考
写在前面:
最近做的一个项目,用的require和backbone,对两者的使用已经很熟悉了,但是一直都有好奇他们怎么实现的,一直寻思着读读源码。现在项目结束,终于有机会好好研究一下。
本文重要解读requirejs的源码,backbone源码分析将会随后给出。
行文思路:
- requirejs 基本介绍
- requirejs使用后的几个好奇
- requirejs源码解读
requirejs基本介绍
由于篇幅有限,这里不会详解requirejs的使用和api,建议读者朋友自己去用几次,再详读api.
简介
简单来说,requirejs是一个遵循 AMD(Asynchronous Module Definition)规范的模块载入框架。
使用requirejs的好处是:异步加载、模块化、依赖管理
使用
引入:
<script data-main="/path/to/init" src="/path/to/require.js"></script>
这里的data-main是整个项目的入口.
定义模块:
} : function (fn) { fn(); };
req.onError = defaultOnError;
req.createNode = function (config, moduleName, url) {};
req.load = function (context, moduleName, url) {};
define = function (name, deps, callback) {};
req.exec = function (text) {};
req(cfg);
? [) &&
!isOpera) {
useInteractive = true;
node.attachEvent('onreadystatechange', context.onScriptLoad);
} else {
node.addEventListener('load', context.onScriptLoad, false);
node.addEventListener('error', context.onScriptError, false);
}
node.src = url;
// 兼容IE6-8, script可能在append之前执行, 所有把noe绑定在currentAddingScript中,防止其他地方改变这个值
currentlyAddingScript = node;
if (baseElement) {
// 这里baseElement是getElementsByName('base'); 现在一般都执行else了。
head.insertBefore(node, baseElement);
} else {
head.appendChild(node);
}
currentlyAddingScript = null;
return node;
} else if (isWebWorker) {
// 如果是web worker。。不懂
try {
importScripts(url);
context.completeLoad(moduleName);
} catch (e) {
context.onError(makeError('importscripts',
'importScripts failed for ' +
moduleName + ' at ' + url,
e,
[moduleName]));
}
}
};
这里可以看出第一个问题的原因了.引入data-*的作用是用来移除匹配用的.或则IE低版本中修正contextName和moduleName. 这里req.createNode和context.onScriptLoad是其他地方定义的,接下来看req.createNope:
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;
};
这里可以解决最后一个问题,通过appendChild, node.async实现异步加载的。
当node加载完毕后会调用context.onScriptLoad, 看看做了什么:
if (evt.type === 'load' ||
(readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
interactiveScript = null;
// getScriptData()找evet对应的script, 提取data-requiremodule就知道mod的name了。
var data = getScriptData(evt);
context.completeLoad(data.id);
}
}
再看context.completeLoad:
var found, args, mod,
shim = getOwn(config.shim, moduleName) || {},
shExports = shim.exports;
// 把globalQueue 转换到 context.defQueue(define收集到的mod集合)
takeGlobalQueue();
while (defQueue.length) {
args = defQueue.shift();
if (args[0] === null) {
args[0] = moduleName;
// 如果当前的defModule是匿名define的(arg[0]=null), 把当前moduleName给他,并标记找到
if (found) {
break;
}
found = true;
} else if (args[0] === moduleName) {
// 非匿名define
found = true;
}
// callGetModule较长, 作用是实例化一个context.Module对象并初始化, 放入registry数组中表示可用.
callGetModule(args);
}
// 获取刚才实例化的Module对象
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 {
callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);
}
}
// 检查loaded情况,超过时间的就remove掉,并加入noLoads数组
checkLoaded();
}
可以看到,当script加载完毕后,只做了一件事:实例化context.Module对象,并暴露给registry供调用.
require 实现
req = requirejs = function (deps, callback, errback, optional) {
var context, config,
contextName = defContextName;
// 第一个参数不是模块依赖表达
if (!isArray(deps) && typeof deps !== 'string') {
// deps is a config object
config = deps;
if (isArray(callback)) {
// 如果有依赖模块则调整参数, 第二个参数是deps
deps = callback;
callback = errback;
errback = optional;
} else {
deps = [];
}
}
if (config && config.context) {
contextName = config.context;
}
context = getOwn(contexts, contextName);
if (!context) {
context = contexts[contextName] = req.s.newContext(contextName);
}
if (config) {
context.configure(config);
}
// 以上获取正确的context,contextName
return context.require(deps, callback, errback);
};
一看,结果什么都没做,做的事还在context.require()里面。 在context对象中:
context.require = context.makeRequire();
我们需要的require结果是context.makeRequire这个函数返回的闭包:
options = options || {};
function localRequire(deps, callback, errback) {
var id, map, requireMod;
if (options.enableBuildCallback && callback && isFunction(callback)) {
callback.__requireJsBuild = true;
}
if (typeof deps === 'string') {
if (isFunction(callback)) {
// 非法调用
return onError(makeError('requireargs', 'Invalid require call'), errback);
}
// 如果require的是require|exports|module 则直接调用handlers定义的
if (relMap && hasProp(handlers, deps)) {
return handlers[deps](registry[relMap.id]);
}
if (req.get) {
return req.get(context, deps, relMap, localRequire);
}
// 通过require的模块名Normalize成需要的moduleMap对象
map = makeModuleMap(deps, relMap, false, true);
id = map.id;
if (!hasProp(defined, id)) {
return onError(makeError('notloaded', 'Module name "' +
id +
'" has not been loaded yet for context: ' +
contextName +
(relMap ? '' : '. Use require([])')));
}
// 返回require的模块的返回值。
return defined[id];
}
// 把globalQueue 转换到 context.defQueue,并把defQueue的每一个都实例化一个context.Module对象并初始化, 放入registry数组中表示可用.
intakeDefines();
// nextTick 使用的是setTimeOut.如果没有则是回调函数
// 本次require结束后把所有deps标记为needing to be loaded.
context.nextTick(function () {
intakeDefines();
requireMod = getModule(makeModuleMap(null, relMap));
requireMod.skipMap = options.skipMap;
requireMod.init(deps, callback, errback, {
enabled: true
});
checkLoaded();
});
return localRequire;
}
..
return localRequire;
}
如果直接require模块, 会返回此模块的返回值;否则会把他加入到context.defQueue, 初始化后等待调用; 比如直接:
var util = require('./path/to/util');
会直接返回util模块返回值; 而如果:
require(['jquery', 'backbone'], function($, Backbone){});
就会执行intakeDefines()
和nextTick()
;
总结
花时间读读源码,对以前使用require
时的那些做法想通了,知道那样做的原因和结果,以后用着肯定也会顺手多了。
学习框架源码可以让自己用的有谱、大胆, 更多的时候是学习高手的代码组织, 编码风格,设计思想, 对自己提升帮助很大~
总结下自己研读源码方式,希望对读者有所帮助: 项目中用熟 -> 源码如何布局组织-> demo进入源码,log验证猜想-> 看别人分析 -> 总结
玉伯说 RequireJS
是没有明显的 bug,SeaJS
是明显没有 bug, 以后一定研究下seajs
,看看如何明显没有bug.
RequireJs 源码解读及思考的更多相关文章
- 温故而知新 Volley源码解读与思考
相比新的网络请求框架Volley真的很落后,一无是处吗,要知道Volley是由google官方推出的,虽然推出的时间很久了,但是其中依然有值得学习的地方. 从命名我们就能看出一些端倪,volley中 ...
- BackBone 源码解读及思考
说明 前段时间略忙,终于找到时间看看backbone代码. 正如知友们说的那样,backbone简单.随性. 代码简单的看一眼,就能知道作者的思路.因为简单,所以随性,可以很自由的和其他类库大搭配使用 ...
- Spark Streaming源码解读之流数据不断接收和全生命周期彻底研究和思考
本节的主要内容: 一.数据接受架构和设计模式 二.接受数据的源码解读 Spark Streaming不断持续的接收数据,具有Receiver的Spark 应用程序的考虑. Receiver和Drive ...
- 15、Spark Streaming源码解读之No Receivers彻底思考
在前几期文章里讲了带Receiver的Spark Streaming 应用的相关源码解读,但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Appr ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- underscore 源码解读之 bind 方法的实现
自从进入七月以来,我的 underscore 源码解读系列 更新缓慢,再这样下去,今年更完的目标似乎要落空,赶紧写一篇压压惊. 前文 跟大家简单介绍了下 ES5 中的 bind 方法以及使用场景(没读 ...
- 线程本地变量ThreadLocal源码解读
一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...
- seajs 源码解读
之前面试时老问一个问题seajs 是怎么加载js 文件的 在网上找一些资料,觉得这个写的不错就转载了,记录一下,也学习一下 seajs 源码解读 seajs 简单介绍 seajs是前端应用模块化开发的 ...
- SDWebImage源码解读之SDWebImageDownloader
SDWebImage源码解读之SDWebImageDownloader 第八篇 前言 SDWebImageDownloader这个类非常简单,作者的设计思路也很清晰,但是我想在这说点题外话. 如果有人 ...
随机推荐
- 中文价格识别为数字 java代码
运行效果: public class VoicePriceRecognition { private final static String NOT_HAS_PRICE_CONTENT="n ...
- Debussy的安装与使用
1.概述 Debussy是NOVAS Software, Inc ( 思源科技 )发展的HDL Debug & Analysis tool,这套软体主要不是用来跑模拟或看波形,它最强大的功能是 ...
- 【转】python面向对象中的元类
type() 动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的. 比方说我们要定义一个Hello的class,就写一个hello.py模块: class Hel ...
- Android:日常学习笔记(4)——探究活动(1)
Android:日常学习笔记(4)——探究活动 什么是活动: 活动是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互. 手动创建活动 创建空活动 1.新建活动时选择Add ...
- [POI2007]驾驶考试egz
题目 BZOJ 神仙题,可比那些氵紫题有意思多了 做法 \(i\)能作为起始点,当\(i\)能到达\(1\)~\(i-1\)和\(i+1\)~\(n\) 这样处理显然会麻烦,因为要从每个点都特判一次 ...
- Bürkert 流体控制系统 (8611 型通用调节器)
Type Description High-Tech Made EasyThe new universal controller eCONTROL Type 8611 brings an essent ...
- R和Python小数的保留
R: 1.保留几位有效数字: signif(x,digits) 2.保留几位小数: round(x,digits) Python: 1.“%.2f”%a
- Django 组合索引
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join( ...
- iOS_Quartz 2D绘图
目 录: 一.基础知识掌握 二.Quartz 2D绘图基础:CGContextRef实现简单地绘制图形 三.CGContextRef实现文字.图片.基于路径的图形绘制 四.在内存中绘制位图 五.添加 ...
- iOS_AFNetWorking框架分析
网络 — 你的程序离开了它就不能生存下去!苹果的Foundation framework中的NSURLConnection又非常难以理解, 不过这里有一个可以使用的替代品:AFNetworking.A ...