《JavaScript框架设计》——2.4 define方法

简介: 我们再来看define方法,打个比方,它与require的关系就是内应外合。define是应,require是合。require拥有加载器90%的调度资源,以围城姿态攻打我们封闭的JavaScript模块。

本节书摘来自异步社区《JavaScript框架设计》一书中的第2章,第2.4节,作者:司徒正美著,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.4 define方法

我们再来看define方法,打个比方,它与require的关系就是内应外合。define是应,require是合。require拥有加载器90%的调度资源,以围城姿态攻打我们封闭的JavaScript模块。JavaScript模块则由一个“内鬼”define来看守城门。当require发出请求,define就打开城门,模块被兼并到mass Framework的“庞大帝国内”!

define有3个参数,前面两个为可选,事实上这里的ID没什么用,就是给开发者看的,它还是用getCurrentScript方法得到script节点路径做ID,deps没有就补上一个空数组。在mass中,先根据浏览器的情况对模块进行分级,一些模块是专门用于打补丁的,只对IE6、IE7、IE8有用,但对Chrome没有用,这个我们在require时就自动排除它。但合并后,我们不得不把它们都加上(因为不知道面对的是什么浏览器),mass设置一个参数,用于忽略执行这个补丁模块的工厂函数,保证框架用浏览器的原生API执行。这个参数就设置在define中,为一个布尔,如果为true就跳过。

此外,define还要考虑循环依赖的问题。比如说加载A,要依赖B与C,加载B,要依赖A与C,这时A与B就循环依赖了。A与B在判定各自的deps中的键值都是2时才执行,结果都无法执行了。

define的源码:

window.define = $.define = function(id, deps, factory) {
    var args = $.slice(arguments);
    if (typeof id === "string") {
        var _id = args.shift();
    }
    if (typeof args[0] === "boolean") { //用于文件合并,在标准浏览器中跳过补丁模块
        if (args[0]) {
            return;
        }
        args.shift();
    }
    if (typeof args[0] === "function") {
        args.unshift([]);
    } //上线合并后能直接得到模块ID,否则寻找当前正在解析中的script节点的src作为模块ID
    //现在除了Safari外,我们都能直接通过getCurrentScript一步到位得到当前执行的script节点
    //Safari可通过onload+delay闭包组合解决
    id = modules[id] && modules[id].state >= 1 ? _id : getCurrentScript();
    factory = args[1];
    factory.id = _id; //用于调试
    factory.delay = function(id) {
        args.push(id);
        var isCycle = true;
        try {
            isCycle = checkCycle(modules[id].deps, id);
        } catch (e) {
        }
        if (isCycle) {
            $.error(id + "模块与之前的某些模块存在循环依赖");
        }
        delete factory.delay; //释放内存
        require.apply(null, args); //0,1,2 --> 1,2,0
    };
    if (id) {
        factory.delay(id, args);
    } else { //先进先出
        factorys.push(factory);
    }
};

checkCycle方法:

function checkCycle(deps, nick) {
    //检测是否存在循环依赖
    for (var id in deps) {
        if (deps[id] === "司徒正美" && modules[id].state !== 2 && (id === nick || checkCycle(modules[id].deps, nick))) {
                return true;
         }
      }
}

define与require互相调用的示意如图2.1所示。

screenshot

▲图2.1

至此整个加载器就完成了。现在的难点转为我们如何判定当前模块的加载情况,如何知道它的模块名,如何排查它的对应的链接是否有效。为此mass动用一个modules对象,两个数组(loadings与factory)。小小一个加载器,里面的注释就提及到许多兼容性问题,真正与DOM打交道还没有开始呢!

最后看一下mass加载器,加载自己框架的ajax、node模块一共做了多少事吧!

require("ajax, node", function($) {
    $.log("加载完成!")
})

Firefox20会加载这么多模块,如图2.2所示。

screenshot

▲图2.2

IE10在IE7模式下会加载如下模块,如图2.3所示。

screenshot

▲图2.3

由于使用了模块化,我们就可以分级,对旧版本IE使用体积更庞大的query模块,其他则使用query_neo,并且多了许多补丁模块。

此外,mass加载器会把加载过程全部记录下来,大家可以直接在Firefox看到这些消息,如图2.4所示。

screenshot

▲图2.4

模块加载器会让我们的前端开发变得更为工业化,维护调试都非常方便,强烈建立大家在工作时使用。现在国内主流的几个加载器seajs、requirejs、mass、oyejs都是比较好的选择。

相关文章
|
2月前
|
自然语言处理 JavaScript 网络架构
js开发:请解释什么是ES6的箭头函数,以及它与传统函数的区别。
ES6的箭头函数以`=>`定义,简化了函数写法,具有简洁语法和词法作用域的`this`。它无`arguments`对象,不能用作构造函数,不支持`Generator`,且不改变`this`、`super`、`new.target`绑定。适用于简短表达式,常用于异步编程和高阶函数。
20 5
|
2月前
|
程序员 编译器 C++
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用(一)
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用
47 0
|
15天前
|
前端开发 开发者
【专栏】BEM(Block-Element-Modifier)是一种前端命名规范和架构方法,旨在创建清晰、可维护的代码结构。
【4月更文挑战第29天】BEM(Block-Element-Modifier)是一种前端命名规范和架构方法,旨在创建清晰、可维护的代码结构。它包括Block(独立功能单元)、Element(Block的子元素)和Modifier(表示状态或变体)。BEM的特点包括命名一致性、模块化设计、清晰结构和可复用性,适用于代码组织、样式管理、组件化开发和团队协作。虽然命名较长和学习成本是其局限性,但BEM在提升代码质量和效率方面具有显著优势,是前端开发的重要工具。
|
2月前
|
设计模式 开发框架 编译器
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用(二)
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用
38 0
|
2月前
|
算法 IDE 程序员
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用(三)
【深入探究Qt内部架构】QObject、事件循环与Q_OBJECT宏的协同作用
56 5
|
5月前
|
JavaScript 前端开发 网络架构
JavaScript开发中ES6+新特性:解释箭头函数的作用以及它与普通函数的区别。
JavaScript开发中ES6+新特性:解释箭头函数的作用以及它与普通函数的区别。
43 1
|
7月前
|
前端开发
【前端验证】对uvm_info宏的进一步封装尝试
【前端验证】对uvm_info宏的进一步封装尝试
|
安全 编译器 Linux
【C/C++】你了解预处理吗?带你深度剖析#define定义宏
【C/C++】你了解预处理吗?带你深度剖析#define定义宏
150 0
|
编译器
chapter 7 用函数实现模块化设计(上)
chapter 7 用函数实现模块化设计
92 0
chapter 7 用函数实现模块化设计(上)