《JavaScript框架设计》——2.2 加载器所在路径的探知

简介: 要加载一个模块,我们需要一个URL作为加载地址,一个script作为加载媒介。但用户在require时都用ID,因此我们需要一个将ID转换为URL的方法。思路很简单,强加个约定,URL的合成规则为。

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

2.2 加载器所在路径的探知

要加载一个模块,我们需要一个URL作为加载地址,一个script作为加载媒介。但用户在require时都用ID,因此我们需要一个将ID转换为URL的方法。思路很简单,强加个约定,URL的合成规则为。

basePath + 模块ID + ".js"
由于浏览器自上而下分析DOM,当浏览器在解析我们的JavaScript文件(这个JavaScript文件是指加载器)时,它就肯定DOM树中最后加入的script标签。因此,我们有下面这个方法。

function getBasePath() {
    var nodes = document.getElementsByTagName("script");
    var node = nodes[nodes.length - 1];
    var src = document.querySelector ? node.src : node.getAttribute("src", 4);
    return src;
}

这个能满足99%的需求。但如果我们不得不动态加载我们的加载器呢?在旧版本下IE就会折戟沉沙,这个不奇怪,许多常规方法在IE6、IE7、IE8都失效,除了API的差异性,还有它本身的各种Bug。这个我很难指出是什么,总之要解决。如下面的这个JavaScript片断。

<script>
    document.write('<script src="avalon.js"><\/script>');
    document.write('<script src="mass.js"><\/script>');
    document.write('<script src="http://common.cnblogs.com/script/jquery.js"><\/script>');
</script>

这时就需要用readyChange属性,微软在document、image、xhr、script等东西都拥有了这个属性,用来查看加载情况。

function getBasePath() {
    var nodes = document.getElementsByTagName("script");
    if (window.VBArray) {//如果是IE
        for (var i = 0, node; node = nodes[i++]; ) {
            if (node.readyState === "interactive") {
                break;
            }
        }
    } else {
        node = nodes[nodes.length - 1];
    }
    var src = document.querySelector ? node.src : node.getAttribute("src", 4);
    return src;
}

这样就搞定了。现在想一下能否优化。访问DOM比一般的JavaScript代码消耗高许多。这时我们可以利用Error对象。

function getBasePath() {
    try {
        a.b.c()
    } catch (e) {
        if (e.fileName) {//firefox
            return e.fileName;
        } else if (e.sourceURL) {//safari
            return e.sourceURL;
        }
    }
    var nodes = document.getElementsByTagName("script");
    if (window.VBArray) {//倒序查找更快
        for (var i = nodes.length, node; node = nodes[--i]; ) {
            if (node.readyState === "interactive") {
                break;
            }
        }
    } else {
        node = nodes[nodes.length - 1];
    }
    var src = document.querySelector ? node.src : node.getAttribute("src", 4);
    return src;
}

当然Opera与Chrome也有一些属性可以供我们提取,但比较复杂,这里就略去了。

为了方便以后使用,我们先将mass.js这个去掉吧。在现实中,为了防止缓存,这个后面可能带版本号、时间戮什么的,也要去掉。

url = url.replace(/[?#].*/, "").slice(0, url.lastIndexOf("/") + 1);
在mass并行加载器中,有一个getCurrentScript方法,它用于取得正在被解析的JavaScript文件的src,而不局限于加载器的JavaScript地址。这个对实现匿名模块非常有用。想象一下,我们有个require(["a","b","c","d","e"], callback),这些模块在define里面都没有ID,识别哪一个模块是对应哪个JavaScript文件加载非常麻烦。或者使用iframe,将各个模块的加载独立于一个沙箱环境中,或者使用一些变量做标识(这存在被覆盖的危险),因此mass再次发掘Error对象的私有属性。

function getCurrentScript(base) {//为true时相当于getBasePath
    var stack;
    try { //Firefox可以直接 var e = new Error("test"),但其他浏览器只会生成一个空Error
        a.b.c(); //强制报错,以便捕获e.stack
    } catch (e) { //Safari的错误对象只有line、sourceId、sourceURL
        stack = e.stack;
        if (!stack && window.opera) {
            //Opera 9没有e.stack,但有e.Backtrace,不能直接取得,需要对e对象转字符串进行抽取
            stack = (String(e).match(/of linked script \S+/g) || []).join(" ");
        }
    }
    if (stack) {
        /**e.stack最后一行在所有支持的浏览器大致如下。
         *Chrome23:
         * at http://113.93.50.63/data.js:4:1
         *Firefox17:
         *@http://113.93.50.63/query.js:4
         *@http://113.93.50.63/data.js:4
         *IE10:
         *  at Global code (http://113.93.50.63/data.js:4:1)
         *  //firefox4+ 可以用document.currentScript
         */
        stack = stack.split(/[@ ]/g).pop();   //取得最后一行,最后一个空格或@之后的部分
        stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "");                //去掉换行符
        return stack.replace(/(:\d+)?:\d+$/i, ""); //去掉行号与或许存在的出错字符起始位置
    }
    ///我们在动态加载模块时,节点都插入head中,因此只在head标签中寻找
    var nodes = (base ? document : head).getElementsByTagName("script");
    for (var i = nodes.length, node; node = nodes[--i]; ) {
        if ((base || node.className === moduleClass) && node.readyState === "interactive") {
            return node.className = node.src;
        }//如果此模块加载过就重写className
    }
}
相关文章
|
6天前
|
前端开发 JavaScript
前端 js 经典:数组常用方法总结
前端 js 经典:数组常用方法总结
15 0
|
1天前
|
JavaScript 前端开发 Java
前端知识点03(JS)
前端知识点概览:了解JS中的this指向,包括全局、函数、new、apply/call/bind及箭头函数的规则。理解script的async和defer属性对脚本加载和执行的影响。探讨setTimeout和setInterval的用法及其在性能上的考量。ES6与ES5的区别在于新语法特性,如let/const、箭头函数、模板字符串、模块化、类和继承等。此外,ES6还引入了Symbol、解构赋值、默认参数、Map/Set和Generator等功能。别忘了点赞和支持作者哦!
8 1
|
1天前
|
JavaScript 前端开发
前端面试02(JS)
本文是前端面试系列的第二篇,主要涵盖了JavaScript的基础知识,包括JS的组成(ECMAScript、DOM、BOM)、内置对象(如String、Array、Math、Date等)、数组操作方法、数据类型检测方法(typeof、instanceof、constructor、Object.prototype.toString.call)、闭包的概念及其特点、前端内存泄漏的原因和类型、事件委托的优势、基本数据类型与引用数据类型的差异、原型链的工作原理以及JS实现继承的多种方式(原型链、构造函数、组合继承等)。文章结尾鼓励读者点赞和支持作者。
7 1
|
2天前
|
JavaScript 前端开发 NoSQL
构建基于Node.js的全栈应用:从前端到后端的完整指南
【5月更文挑战第24天】本文是关于使用Node.js构建全栈应用的指南,涵盖前端(React或Vue)、后端(Node.js + Express)和数据库(MongoDB)的选型与实现。文章介绍了项目结构、前端组件化开发、后端API接口编写、前后端联调及部署上线的注意事项,帮助读者掌握全栈开发流程。
|
4天前
|
移动开发 JavaScript 前端开发
【热门话题】Vue.js:现代前端开发的轻量级框架之旅
Vue.js,由尤雨溪于2014年创建,是一个轻量级的前端框架,因其简洁API、高效渲染和组件系统深受全球开发者喜爱。本文探讨Vue的核心理念、技术架构、开发实践及在现代Web开发中的应用。Vue遵循渐进式框架思想,提供声明式编程、组件化和响应式数据绑定。技术上,它采用双向数据绑定、虚拟DOM和生命周期钩子。开发实践中,Vue CLI和Vuex、Vue Router分别加速开发和管理状态、路由。Vue不仅适用于单页应用,还支持多页应用、移动开发和跨平台项目,拥有丰富的社区生态和插件。随着Vue 3的推出,Vue将持续创新并影响前端开发领域。
26 0
|
6天前
|
前端开发 JavaScript
前端 JS 经典:函数管道
前端 JS 经典:函数管道
5 0
|
6天前
|
前端开发 JavaScript
前端 JS 经典:数组去重万能方法
前端 JS 经典:数组去重万能方法
10 0
|
6天前
|
缓存 JavaScript 前端开发
前端 JS 经典:CommonJs 规范
前端 JS 经典:CommonJs 规范
13 0
|
6天前
|
JavaScript 前端开发
前端 JS 经典:原型和原型链
前端 JS 经典:原型和原型链
12 0