博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于seajs加载模块的入口脚本
阅读量:7065 次
发布时间:2019-06-28

本文共 10058 字,大约阅读时间需要 33 分钟。

目录

思路

发现问题

解决等待加载seajs的问题

解决脚本按依赖加载的问题

实现autoload

加载嵌入页面的脚本

完整的entry.js


项目第二版本开始开发的时候,我觉得应该对脚本干点什么。因为之前的脚本引用太多,太杂,且不说引用的第三方脚本,也不说每个页面自己的业务脚本,就项目组自己写的,项目的公共脚本都有十余个,不好好管理真是不行。那么很自然的,是RequireJS,SeaJS之类的加载工具。本文不纠结如何选择,反正最终选择了SeaJS。

其实引入seajs,除了想把脚本模块化之外,还有一个很重要的目的,就是希望在每个页面都只需要引入很少,甚至1个脚本,就能根据需要引入其它的脚本,不用再在一个页面文件中写大量的 <script ...></script> 标签了。

记得之前seajs可以在引入的时候自动加载config,然后再根据标签中的一个 data-xxx 配置项加载页面的入口脚本,不过我在 seajs 2 的文档中没发现有这样的用法,可能是由于某些原因取消了吧。所以我准备自己写一个脚本,这个脚本要干这么些事情:


思路

  • 这个脚本命名为 entry.js

  • 首先加载sea.js,并调用 seasj.config 进行基本的配置

  • 然后加载所有(或绝大部分)页面都需要加载的公共脚本,比如jquery、jquery-easyui等

  • 最后根据当前页面的地址加载对应的页面脚本

    1. 如果页面不需要自己的业务脚本,不加载

    2. 如果页面需要自己的的业务脚本,且脚本是独立文件,则按页面路径放置在 js 目录中相应位置,自动加载

    3. 如果页面有业务脚本不是嵌入式脚本,需要在所有公共脚本加载完成之后调用。

根据这个思路,entry.js 大概应该这样写

// @(#) entry.js(function() {    // 通过document.write加载sea.js    document.write('');    // seajs.config({...})    seajs.config({        alias: {            "jquery": "jquery-1.11.0.min",            "easyui": "/js/easyui/jquery.easyui.min",            "easyui-zh": "/js/easyui/locale/easyui-lang-zh_CN",            "loading": "jquery.loading.js"        },        preload: [            "jquery"        ]    });    // 使用seajs加载共享模块    var loadCommons = function() {        seasj.use("jquery");        seasj.use("easyui");        seasj.use("easyui-zh");        seasj.use("loading");    };    loadCommons();    // 加载页面对应的脚本    var autoload = function() {        // TODO 加载页面对应的脚本    };    autoload();)();

发现问题

现在问题出现了

  1. seajs.config会报错 seasj is not defined

  2. 绕过第1个问题之后,loadCommons 也会出问题,会报告 jQuery is not defined

  3. autoload的实现得想个方便又易于实现的办法

  4. 嵌入页面的脚本该如何加载

分析原因,在document.write之后,浏览器开始加载sea.js,但是脚本没等浏览器加载sea.js完成,就开始执行 seajs.config(),所以出现 seajs 未定义的错误。同理,seajs.use 同时加载几个模块,压根儿没顾及它们之间的依赖关系(本来也没法定义依赖关系)


解决等待加载seajs的问题

对于第1个问题,上面说到绕过,其实就是通过 setTimeout 来延时加载绕过的。修改后的代码结构如下:

// @(#) entry.js(function() {    var config = {        // 这里是seajs.config中的定义        // 为了方便修改配置,把这个配置对象定义提前        // 内容略    };    // 通过document.write加载sea.js    document.write('');    // 使用seajs加载共享模块    var loadCommons = function() {        // 略    };    // 加载页面对应的脚本    var autoload = function() {        // TODO 加载页面对应的脚本    };    // 定义 main 函数等待 seajs 加载完成,    // 简单的通过判断是否存在 seajs 对象来判断其是否加载完成    var main = function () {        // 如果没有加载完成,等待50毫秒再试        if (typeof (seajs) === "undefined") {            setTimeout(main, 50);            return;        }        seajs.config(config);        loadCommons();    // 现在这个函数内部还有问题        autoload()        // 现在这里加载autoload也有问题    };    main();)();


解决脚本按依赖加载的问题

下面来解决脚本依赖的问题,分析依赖关系

  • easyui-zh依赖easyui

  • easyui和loading依赖jquery

  • autuload()依赖loadCommons()

一开始想通过seajs的define来解决这个问题,但尝试了下面两种办法都不行,

 
// 方法一,不行,因为几个require会同步加载,一样无序define(function(require) {    require("jquery");    require("easyui");    require("easyui-zh");    require("loading");    autuload();});

// 方法二,也不行,因为依赖的几个模块也是同步加载,无序define("autoload", ["jquery", "easyui", "easyui-zh", "loading"], autload);

最后想到用seajs.use来解决这个问题,虽然代码看起来有点难过

seajs.use("jquery", function() {    seajs.use("easyui", function() {        seajs.use(["easyui-zh", "loading"], autoload);    });});

现在这都算好的,如果依赖树过长的话,这个代码看来肯定会想死的心都有了。如果能把这个调用平面化就好了……于是继续想办法,终于想到了用队列方式来处理,于是定义了这么个东西

// 通过链式调用按顺序加载依赖的js库(使用seajs.use)   var useQueue = function () {       return (function () {           var queue = [];           return {               add: function (p) {                   // 按顺序添加一个模块(名称)                   queue.push(p);                   return this;               },               addRange: function (a) {                   // 按顺序并入(追加)一个模块(名称)列表                   queue = queue.concat(a);                   return this;               },               run: function (callback) {                   // 使用递归的方式,从队列中依次加载模块                   // 幸好javascript的function是对象                   var go = function () {                       if (queue.length == 0) {                           callback();                       } else {                           var p = queue.shift();                           seajs.use(p, go);                       }                   };                   go();               }           };       })();   };

这个方法的关键在 run() 方法里,这里定义了一个加载调用函数 go(),它每次从队列中取出一个元素给 seajs.use() 加载,而 seajs.use() 加载完成之后会递归调用 go() 继续处理队列后面的元素。

代码整体搞复杂了,但是加载这里简单了(这算不算强迫症)

useQueue().add("jquery")        .add("easyui")        .add("easyui-zh")        .add("loading")        .run(function () {    autoload();});

考虑了詥队列可能会经常修改,把依赖队列配置到 config 对象中,

var config {    // 上面是seajs.config所需要的配置    dependQueue: [        "jquery",        "easyui",        "easyui-zh",        [            "loading"            // 这里用数组是为了后面可能添加与loading平级的其它脚本            // 反正 seajs.use 的第1个参数可以是单个模块名,也可以是一个模块名的数组        ]    ]}

这是后面的加载部分

useQueue().addRange(config.dependQueue).run(function() {    autoLoad();});


 

实现autoload

autoload() 中需要检查当前页面的配置,是否需要加载页面对应的脚本文件,如果要加载,是指定路径还是自动计算路径?

考虑到页面在加载entry.js的时候就需要做好这些决定,而这个配置只能是在页面中,不能写在 entry.js 中,还要考虑不给页面增加负担,一般能想到的办法是在<body> 标签中加个属性来配置,而更好的办法,是加在引入 entry.js<script> 标签中。

从这个 <script> 标签我们很容易得到需要的信息——只需要解析 data-autoload 属性就行了,这件事用jquery来干就是一句话的事情。为了更快的定位到这个 <script>,可以约定为它加一个ID:js-entry

分析 data-autoload 的逻辑是这样:

  1. 如果有定义data-autoload,但没有属性值,或属性值为空,表示自动计算脚本路径

  2. 如果有定义data-autoload,且有值,则其值是需要加载的页面业务脚本路径

  3. 如果没有定义data-autoload,表示不加载页面业务脚本

代码实现

var autoLoad = function () {    var script = $("#js-entry");    if (script.length == 0) {        // 这里简单处理了,没有定义js-entry,就不自动加载        // 如果不怕麻烦,这种情况下还可以去分析得到当前script标签        return;    }    var url = script.data("autoload");    if (typeof url !== "string") {        // 没有定义data-autoload        return;    }    if (!url) {        // data-autoload没有属性值,或属性值为空字符串        // 根据当前页面URL分析计算得到脚本的URL        var subPath = window.location.href.replace(/^.*\/\/.+\//, "/");        subPath = subPath.replace(/\.[^.]+$/, "");        url = "/js/pages" + subPath;    }    seajs.use(url);};


加载嵌入页面的脚本

页面需要的脚本很少的时候,完全没有必要为页面去创建一个脚本文件,这时候需要在页面内嵌入业务脚本代码。加载嵌入脚本大概应该是这样

在执行到页面嵌入脚本的时候,entry.js 有可能还在加载依赖库,甚至有可能还在加载 sea.js。那么这个时候嵌入脚本就不能很好的运行。怎么办?

执行嵌入脚本的时候唯一明确的是 entry.jssetTimeout 之外的代码已经执行完了。那么这里可以定义一个函数,通过回调的方式来执行页面嵌入脚本。就像这样

// entry.js$(function() {    // .....    var callback;    var page = function(fun) {        callback = fun;    };    // .....    window.page = page;});

剩下的问题就是,如何保证 callback() 在所有依赖脚本加载完成之后调用。也许这样可以这样

useQueue().addRange(config.dependQueue).run(function() {    autoLoad();    if (typeof callback == "function") {        callback();    }});

但实际上这样并不保险……因为,万一,entry.js加载脚本的速度嗖嗖的,就有可能出现调用 callback 的时候,页面上的 page() 还没执行,也就是说,callback 还是 undefined

如果能用 jQuery.Deferred 来处理,这个事情就好办了,问题是有页面上调用 page() 的时候 jQuery 有可能还没加载出来……只好简单的自己实现一个了

var page = (function () {    var isReady = false;    var func;    return {        define: function (callback) {            func = callback;            if (isReady) {                func.call(this);            }        },        run: function () {            isReady = true;            if (typeof func === "function") {                func.call(this);            }        }    };})();// ......var main = function () {    // .....    useQueue().addRange(config.dependQueue).run(function () {        autoLoad();        page.run();    });};

相应的页面代码要改改


完整的entry.js

/** * Entry javascript for pages * http://jamesfanc.blog.51cto.com/ * * @requires [seajs](http://seajs.org/) * @requires [jquery](http://jquery.com/) * @author [James Fancy](mailto:jamesfancy@126.com) * * Copyright 2014 James Fancy */(function () {    var config = {        alias: {            "jquery": "jquery-1.11.0.min",            "easyui": "/js/easyui/jquery.easyui.min",            "easyui-zh": "/js/easyui/locale/easyui-lang-zh_CN",            "loading": "jquery.loading.js"        },        preload: [            "jquery"        ],        dependQueue: [            "jquery",            "easyui",            "easyui-zh",            [                "loading"            ]        ]    };    if (typeof (seajs) === "undefined") {        document.write('\n');    }    // 定义page对象,    // 可以使用page.define(callback)来定义页面脚本    // callback会在依赖项加载完成之后调用    var page = (function () {        var isReady = false;        var func;        return {            define: function (callback) {                func = callback;                if (isReady) {                    func.call(this);                }            },            run: function () {                isReady = true;                if (typeof func === "function") {                    func.call(this);                }            }        };    })();    var autoLoad = function () {        var script = $("#js-entry");        if (script.length == 0) {            return;        }        var url = script.data("autoload");        if (typeof url !== "string") {            return;        }        if (!url) {            var subPath = window.location.href.replace(/^.*\/\/.+\//, "/");            subPath = subPath.replace(/\.[^.]+$/, "");            url = "/js/pages" + subPath;        }        seajs.use(url);    };    // 通过链式调用按顺序加载依赖的js库(使用seajs.use)    var useQueue = function () {        return (function () {            var queue = [];            return {                addRange: function (a) {                    queue = queue.concat(a);                    return this;                },                add: function (p) {                    queue.push(p);                    return this;                },                run: function (callback) {                    var go = function () {                        if (queue.length == 0) {                            callback();                        } else {                            var p = queue.shift();                            seajs.use(p, go);                        }                    };                    go();                }            };        })();    };    var main = function () {        if (typeof (seajs) === "undefined") {            setTimeout(main, 50);            return;        }        seajs.config(config);        useQueue().addRange(config.dependQueue).run(function () {            autoLoad();            page.run();        });    };    main();    window.page = page;})();

转载地址:http://sejll.baihongyu.com/

你可能感兴趣的文章
前端面试每日3+1(周汇总2019.05.05)
查看>>
SQL Join 图示
查看>>
RPA:制造业的下一个改变者
查看>>
VSCode Python开发环境配置
查看>>
208道 java 高频面试题和答案
查看>>
nginx反向代理配置
查看>>
MySQL学习笔记 初学基础篇
查看>>
一步步教你用 CSS 为 SVG 添加过滤器
查看>>
TeeChart Pro VCL/FMX教程(一):入门——构建图表
查看>>
微服务架构 SpringCloud(二)Eureka(服务注册和服务发现基础篇)
查看>>
oracle RAC的客户端HA配置
查看>>
VsCode编辑器
查看>>
spring cloud开发、部署注意事项
查看>>
又一款基于BCH开发出来的社交软件BlockPress
查看>>
ttlsa教程系列之mongodb——(五)mongodb架构-复制原理&复制集
查看>>
虚拟主机通过修改.htaccess将入口重定向到public文件夹
查看>>
nginx快速安装
查看>>
Kinect for windows的脸部识别
查看>>
MySQL 运维笔记(一)—— 终止高负载SQL
查看>>
Mysql导出表结构及表数据 mysqldump用法_无需整理
查看>>