《JavaScript框架设计》——2.3 require方法

2.3 require方法

require方法的作用是当依赖列表都加载完毕,执行用户回调。因此这里有个加载的过程,整个加载过程分为以下几步。

(1)取得依赖列表的第一个ID,转换为URL。无论是通过basePath+ID+".js",还是以映射的方式直接得到。

(2)检测此模块有没有加载过,或正在被加载。因此我们需要一个对象来保持所有模块的加载情况。当用户从来没有加载过此节点时,就进入加载流程。

(3)创建script节点,绑定onerror、onload、onreadychange等事件判定加载成功与否,然后添加href并插入DOM树,开始加载。

(4)将模块的URL,依赖列表等构建成一个对象,放到检测队列中,在上面的事件触发时进行检测。

在mass的加载器,它支持允许第一个参数为字符串,然后内部按空格或逗号切分为ID数组,以及做去重处理,其他都一样。
mass在这基础上做了扩展。

(1)模块ID本来就是URL的简体,因此可以包含斜线(/),并以/划分为多项。

(2)模块ID应该是以符合变量的规则的字符串组成,第一个项可以是“.”或“..”。这些都是URL的基本的规则,表示当前目录与父目录。

(3)模块ID的未尾可以包含“.js”,但如果它是指向一个CSS文件,那么必须以CSS结尾。

(4)如果以“/”或“./”开头,表示它与加载它的模块在同一目录。

(5)如果以“..”开头,表示它在加载它的模块的上一级目录,如果存在多个“..”,就要向上找。

(6)如果模块ID是“mass”,不做转换与加载,这表示mass框架的种子模块,也就是加载器的所在模块。

(7)如果模块ID是“ready”,不做转换与加载,这用于延迟用户回调到DOM树建完后执行。避免出现domReady的回调函数与模块的回调函数出现套嵌。

(8)如果情况就直接在前面按上basePath——加载器所在的目录!

除了这些情况外,我们通常还用到映射,就是允许用户在事前用一个方法,把ID与完整的URL对应好,这样就直接拿。mass称之为别名机制。ID只是给用户用的,框架还是URL做加载或其他检测。此外,AMD还发展一种shim技术,shim就是垫片的意思,目的是让不符合AMD定义的JavaScript文件也能无缝切入我们的加载器系统。

如这个是普通的别名机制:

require.config({
    alias: {
        'lang': 'http://common.cnblogs.com/script/mass/lang.js',
        'css': 'http://common.cnblogs.com/script/mass/css.js'
    }
});

而对于jQuery或其插件,我们需要shim机制:

require.config({
    alias: {
        'jquery': {
            src: 'http://common.cnblogs.com/scriptjquery.js',
            exports: "$"
        },
        'jquery.tooltip': {
            src: 'http://common.cnblogs.com/script/ui/tooltip.js',
            exports: "$",
            deps: ["jquery"]
        }
    }
});

下面是require的源码:

window.require = $.require = function(list, factory, parent) {
    // 用于检测它的依赖是否都为2
    var deps = {},
            // 用于保存依赖模块的返回值
            args = [],
            // 需要安装的模块数
            dn = 0,
            // 已安装完的模块数
            cn = 0,
            id = parent || "callback" + setTimeout("1");
    parent = parent || basepath;//basepath为加载器的路径
    String(list).replace($.rword, function(el) {
        var url = loadJSCSS(el, parent)
        if (url) {
            dn++;
            if (modules[url] && modules[url].state === 2) {
                cn++;
            }
            if (!deps[url]) {
                args.push(url);
                deps[url] = "司徒正美"; //去重
            }
        }
    });
    modules[id] = {//创建一个对象,记录模块的加载情况与其他信息
        id: id,
        factory: factory,
        deps: deps,
        args: args,
        state: 1
    };
    if (dn === cn) { //如果需要安装的等于已安装好的
        fireFactory(id, args, factory); //安装到框架中
    } else {
        //放到检测队列中,等待checkDeps处理
        loadings.unshift(id);
    }
    checkDeps();
};

每require一次,相当于把当前的用户回调当成一个不用加载的匿名模块,ID是随机生成,回调是否执行,要待到deps对象里面所有值都为2。

require里有三个重要方法:loadJSCSS,它用于转换ID为URL,后面再调用loadJS, loadCSS,或再调用require方法;fireFactory,就是执行用户回调,我们的最终目的;checkDeps,检测依赖是否都安装好,安装好就执行fireFactory。

function loadJSCSS(url, parent, ret, shim) {
    //1. 特别处理mass|ready标识符
    if (/^(mass|ready)$/.test(url)) {
        return url;
    }
    //2. 转化为完整路径
    if ($.config.alias[url]) {//别名机制
        ret = $.config.alias[url];
        if (typeof ret === "object") {
            shim = ret;
            ret = ret.src;
        }
    } else {
        if (/^(\w+)(\d)?:.*/.test(url)) { //如果本来就是完整路径
            ret = url;
        } else {
            parent = parent.substr(0, parent.lastIndexOf('/'));
            var tmp = url.charAt(0);
            if (tmp !== "." && tmp !== "/") { //相对于根路径
                ret = basepath + url;
            } else if (url.slice(0, 2) === "./") { //相对于兄弟路径
                ret = parent + url.slice(1);
            } else if (url.slice(0, 2) === "..") { //相对于父路径
                var arr = parent.replace(/\/$/, "").split("/");
                tmp = url.replace(/\.\.\//g, function() {
                    arr.pop();
                    return "";
                });
                ret = arr.join("/") + "/" + tmp;
            } else if (tmp === "/") {
                ret = parent + url;//相对于兄弟路径
            } else {
                $.error("不符合模块标识规则: " + url);
            }
        }
    }
    var src = ret.replace(/[?#].*/, ""),
            ext;
    if (/\.(css|js)$/.test(src)) {
        ext = RegExp.$1;
    }
    if (!ext) { //如果没有后缀名,加上后缀名
        src += ".js";
        ext = "js";
    }
    //3. 开始加载JS或CSS
    if (ext === "js") {
        if (!modules[src]) { //如果之前没有加载过
            modules[src] = {
                id: src,
                parent: parent,
                exports: {}
            };
            if (shim) {//shim机制
                require(shim.deps || "", function() {
                    loadJS(src, function() {
                        modules[src].state = 2;
                        modules[src].exports = typeof shim.exports === "function" ?
                                shim.exports() : window[shim.exports];
                        checkDeps();
                    });
                });
            } else {
                loadJS(src);
            }
        }
        return src;
    } else {
        loadCSS(src);
    }
}

注意,上面的modules[src]是以完整路径做ID的,它对应的对象没有state属性,表示其正在加载中。

loadJS与loadCSS方法就比较纯粹了,不过loadJS会做个死链检测checkFail。

function loadJS(url, callback) {
    //通过script节点加载目标模块
    var node = DOC.createElement("script");
    node.className = moduleClass; //让getCurrentScript只处理类名为moduleClass的script节点
    node[W3C ? "onload" : "onreadystatechange"] = function() {
        if (W3C || /loaded|complete/i.test(node.readyState)) {
            //factorys里面装着define方法的工厂函数(define(id?,deps?, factory))
            var factory = factorys.pop();
            factory && factory.delay(node.src);
            if (callback) {
                callback();
            }
            if (checkFail(node, false, !W3C)) {
                $.log("已成功加载 " + node.src, 7);
            }
        }
    };
    node.onerror = function() {
        checkFail(node, true);
    };
    //插入到head的第一个节点前,防止IE6下head标签没闭合前使用appendChild抛错
    node.src = url;
    head.insertBefore(node, head.firstChild);
}

checkFail方法主要是用于开发调试。有3个参数。node——script节点,onError——是否为onerror触发,fuckIE——对应旧版本IE的hack。思路是,JavaScript文件从加载到解析到执行需要一个过程,在interact阶段,我们的JavaScript代码已经有些部分可以执行了,这时我们将模块对象的state改为1,如果还是undefined,我们就可识别它为死链。不过,此hack对不是以AMD定义的JavaScript模块无效,因为将state改1的逻辑是由define方法执行的。如果判定是死链,我们就把此节点移除。

function checkFail(node, onError, fuckIE) {
    var id = node.src;//检测是否死链
    node.onload = node.onreadystatechange = node.onerror = null;
    if (onError || (fuckIE && !modules[id].state)) {
        setTimeout(function() {
            head.removeChild(node);
        });
        $.log("加载 " + id + " 失败" + onError + " " + (!modules[id].state), 7);
    } else {
        return true;
    }
}

checkDeps方法会在用户加载模块之前及script.onload后各执行一次,检测模块的依赖情况,如果模块没有任何依赖或state都为2了,我们调用fireFactory方法。

function checkDeps() {
    loop: for (var i = loadings.length, id; id = loadings[--i]; ) {
        var obj = modules[id],  deps = obj.deps;
        for (var key in deps) {
            if (hasOwn.call(deps, key) && modules[key].state !== 2) {
                continue loop;
            }
        }
        //如果deps是空对象或者其依赖的模块的状态都是2
        if (obj.state !== 2) {
            loadings.splice(i, 1);//必须先移除再安装,防止在IE下DOM树建完后手动刷新页面,会多次执行它
            fireFactory(obj.id, obj.args, obj.factory);
            checkDeps();//如果成功,则再执行一次,以防有些模块就差本模块没有安装好
        }
    }
}

历经千辛万苦,我们终于到达fireFactory方法。它的工作是从modules中收集各模块的返回值,执行factory, 完成模块的安装。

function fireFactory(id, deps, factory) {
    for (var i = 0, array = [], d; d = deps[i++]; ) {
        array.push(modules[d].exports);
    }
    var module = Object(modules[id]),
            ret = factory.apply(global, array);
    module.state = 2;
    if (ret !== void 0) {
        modules[id].exports = ret;
    }
    return ret;
}
时间: 2024-10-30 17:01:06

《JavaScript框架设计》——2.3 require方法的相关文章

javascript框架设计读书笔记之模块加载系统_javascript技巧

模块加载,其实就是把js分成很多个模块,便于开发和维护.因此加载很多js模块的时候,需要动态的加载,以便提高用户体验. 在介绍模块加载库之前,先介绍一个方法. 动态加载js方法: 复制代码 代码如下: function loadJs(url , callback){ var node = document.createElement("script");       node[window.addEventListener ? "onload":"onre

javascript框架设计之框架分类及主要功能

这篇文章主要介绍了javascript框架设计之框架分类及主要功能的相关资料,需要的朋友可以参考下 从内部架构和理念划分,目前JavaScript框架可以划分为5类. 第一种是以命名空间为导向的类库或框架,如果创建一个数组用new Array(),生成一个对象用new Object(),完全的java风格,因此,我们以某一对象为跟,不断为它添加对象和二级对象属性来组织代码,如金字塔般垒起来,早期代表YUI,EXT(so,不是有活力的公司都还用它们) 第二种是以类工厂为导向的框架.著名的有Prot

javascript框架设计之类工厂

 这篇文章主要介绍了javascript框架设计之类工厂的相关资料,非常浅显易懂,有需要的小伙伴可以查看下.     类与继承在javascript的出现,说明javascript已经达到大规模开发的门槛了,在之前是ECMAScript4,就试图引入类,模块等东西,但由于过分引入太多的特性,搞得javascript乌烟瘴气,导致被否决.不过只是把类延时到ES6.到目前为止,javascript还没有正真意义上的类.不过我们可以模拟类,曾近一段时间,类工厂是框架的标配,本章会介绍各种类实现,方便大

javascript框架设计之框架分类及主要功能_javascript技巧

从内部架构和理念划分,目前JavaScript框架可以划分为5类. 第一种是以命名空间为导向的类库或框架,如果创建一个数组用new Array(),生成一个对象用new Object(),完全的java风格,因此,我们以某一对象为跟,不断为它添加对象和二级对象属性来组织代码,如金字塔般垒起来,早期代表YUI,EXT(so,不是有活力的公司都还用它们) 第二种是以类工厂为导向的框架.著名的有Prototype,还有mootools.Base2.Ten,它们基本上除了最基本的命名空间,其它模块都是一

javascript框架设计之类工厂_javascript技巧

类与继承在javascript的出现,说明javascript已经达到大规模开发的门槛了,在之前是ECMAScript4,就试图引入类,模块等东西,但由于过分引入太多的特性,搞得javascript乌烟瘴气,导致被否决.不过只是把类延时到ES6.到目前为止,javascript还没有正真意义上的类.不过我们可以模拟类,曾近一段时间,类工厂是框架的标配,本章会介绍各种类实现,方便大家在自己的框架中或选择时自己喜欢的那一类风格. 1.javascript对类的支持 在其它语言中 ,类的实例都要通过构

javascript框架设计读书笔记之种子模块_javascript技巧

1.命名空间: js里面的命名空间就是使用对象的属性来扩展的.比如,用户定义一个A对象,A对象下面有B属性和C属性,同时B属性和C属性又是对象.因此A={B:{},C:{}},这时用户就可以在B对象和C对象中定义一样的方法,属性了.因此B和C就属于不同的命名空间.我们调用B,C对象里面的方法,就可以通过A.B.like(),A.C.like()调用了.当然A属于window对象中的属性. 但是有一种情况,比如:boke.jsp页面引入了jquery.js以及prototype.js(他们都会在w

《JavaScript框架设计》——导读

前言 首先说明一下,本书虽是讲解框架设计,但写个框架不是很深奥的事情,重点是建立更为完整的前端知识树.只有自己尝试写个框架,才有机会接触像原型.作用域.事件代理.缓存系统.定时器等深层知识,也才有机会了解applyElement.swapNode.importNode.removeNode.replaceNode.insertAdjacentHTML.createContextualFragment.runtimeStyle等偏门API,也才会知晓像getElementById.getEleme

《JavaScript框架设计》——第 1 章 种子模块 1.1命名空间

第 1 章 种子模块 种子模块也叫核心模块,是框架的最先执行的部分.即便像jQuery那样的单文件函数库,它的内部也分许多模块,必然有一些模块冲在前面立即执行,有一些模块只有用到才执行,也有一些模块可有可无,存在感比较弱,只在特定浏览器下才运行. 种子模块就是其中的急先锋,它里面的方法不一定要求个个神通广大,设计优良,但一定极具扩展性,常用,稳定.扩展性是指通过它们能将其他模块的方法包进来,让种子像大树一样成长:常用是指绝大多数模块都用到它们,防止做重复工作:稳定是指不能轻易在以后版本就给去掉,

《JavaScript框架设计》——第 1 章 种子模块1.1 命名空间

第 1 章 种子模块 种子模块也叫核心模块,是框架的最先执行的部分.即便像jQuery那样的单文件函数库,它的内部也分许多模块,必然有一些模块冲在前面立即执行,有一些模块只有用到才执行,也有一些模块可有可无,存在感比较弱,只在特定浏览器下才运行. 种子模块就是其中的急先锋,它里面的方法不一定要求个个神通广大,设计优良,但一定极具扩展性,常用,稳定.扩展性是指通过它们能将其他模块的方法包进来,让种子像大树一样成长:常用是指绝大多数模块都用到它们,防止做重复工作:稳定是指不能轻易在以后版本就给去掉,