如何解决网页脚本执行顺序与声明顺序不同的问题
2018-03-22 19:15
351 查看
我们开发网页界面时,偶尔会遇到这样的问题:
浏览器执行脚本的顺序,与源码中声明的顺序不同
是的,确实是这样。
这并不是浏览器出了问题,而是客观存在,需要我们前端工程师要解决的问题。
这种现象有多种原因可以解释,例如:
先加载的脚本体量大,后加载的脚本体量小;
先加载的脚本在加载时,恰好服务端“卡”了一下;
网络延迟等
通常的解决办法,是将有先后顺序要求的脚本合并为一个脚本。合并方式又有:
发布时动态合并;
在工程源码中手动静态合并;
我推荐“发布时动态合并”。因为源码合并,破坏了工程的可读性。
但我今天要提出另一种方式,那就是:
允许方法先调用,后声明
具体来讲,就是引入一个“管家”,由管家实现延迟调用问题:
如果调用的时候,方法已经存在,则取出定义的方法体并执行;
如果方法并不存在,则暂存调用信息(参数等),等待方法声明;
方法声明时,如果存在暂存的调用信息,则依次执行。
当然,管家脚本要预先加载就绪,而且方法的调用方式,要更换为通过管家来调用。否则管家“管不住”。
完整代码如下所示(module.js 的 Github源码):
浏览器执行脚本的顺序,与源码中声明的顺序不同
是的,确实是这样。
这并不是浏览器出了问题,而是客观存在,需要我们前端工程师要解决的问题。
这种现象有多种原因可以解释,例如:
先加载的脚本体量大,后加载的脚本体量小;
先加载的脚本在加载时,恰好服务端“卡”了一下;
网络延迟等
通常的解决办法,是将有先后顺序要求的脚本合并为一个脚本。合并方式又有:
发布时动态合并;
在工程源码中手动静态合并;
我推荐“发布时动态合并”。因为源码合并,破坏了工程的可读性。
但我今天要提出另一种方式,那就是:
允许方法先调用,后声明
具体来讲,就是引入一个“管家”,由管家实现延迟调用问题:
如果调用的时候,方法已经存在,则取出定义的方法体并执行;
如果方法并不存在,则暂存调用信息(参数等),等待方法声明;
方法声明时,如果存在暂存的调用信息,则依次执行。
当然,管家脚本要预先加载就绪,而且方法的调用方式,要更换为通过管家来调用。否则管家“管不住”。
完整代码如下所示(module.js 的 Github源码):
;(function(){ var attachContext = window; /** * 简化的defineProperty方法定义,用于兼容IE8 */ ;(function(){ var rIE = /\bMSIE\s+((\d+)(\.\d+)*)\b/i; var ieMajorVersion = rIE.exec(navigator.userAgent); if(null != ieMajorVersion){ ieMajorVersion = parseInt(ieMajorVersion[2]); if(ieMajorVersion <= 8) Object.defineProperty = function(obj, name, opt){ obj[name] = opt.value; }; } })(); var modules = {};/* 所有创建的模块。key存放名称,value存放对象 */ /** * 设定参数默认值 */ var setDftValue = function(ops, dftOps){ ops = ops || {}; dftOps = dftOps || {}; /* 参数不存在时,从默认参数中读取并赋值 */ for(var p in dftOps) if(!(p in ops)) ops[p] = dftOps[p]; return ops; }; /** * 方法体元数据 */ var metaset = { context: null,/* 方法执行上下文(this上下文) */ defer: true/* 调用的方法不存在时,是否挂起,直至对应的方法被创建时再触发 */ }; /** * @constructor * 模块类 * @param ops {Json} 参数配置 * @param ops.name {String} 模块名称 */ var Module = function(ops){ ops = setDftValue(ops, {name: null}); if(null == ops.name) throw new Error("No name specified"); /* 名称唯一性检查 */ if(ops.name in modules) throw new Error("Module of name: " + ops.name + " exists already"); /* 保留引用 */ modules[ops.name] = this; /** 提供的方法集合。key {String}:方法名称,value {Function}:方法体 */ var services = {}; /** * 调用方法时,方法不存在从而记录下来的需要延迟触发的调用。 * key {String}:方法名称 * value {Json}:调用信息 * value.meta {Json}:调用元数据 * value.data {Any}:调用时需要传递的参数 */ var deferedCalls = {}; /** 获取模块名称 */ Object.defineProperty(this, "getName", {value: function(){ return ops.name; }, configurable: false, enumerable: true, writable: false}); /** * 判断是否含有特定名称的方法 * @param name 方法名 */ Object.defineProperty(this, "has", {value: function(name){ return name in services; }, configurable: false, enumerable: true, writable: false}); /** * 定义方法 * @param name {String} 方法名 * @param func {Function} 方法体 */ Object.defineProperty(this, "define", {value: function(name, func){ if(null == name || "" == name.replace(/(^\s+)|(\s+$)/g, "")) throw new Error("Function name can not be null or empty"); /* 检查方法是否存在 */ if(name in services) throw new Error("Function of name: " + name + " exists already"); if(typeof func !== "function") throw new TypeError(String(func) + " is not a valid function"); services[name] = func; /* 触发挂起的调用 */ deferedCalls[name] = deferedCalls[name] || []; if(deferedCalls[name].length > 0){ console.log("Function of name: " + name + " is defined, calling " + deferedCalls[name].length + " deferred callbacks"); setTimeout(function(){ for(var i = 0; i < deferedCalls[name].length; i++){ var call = deferedCalls[name][i]; func.call(call.meta.context, {data: call.data}); } delete deferedCalls[name]; }, 0); } return this; }, configurable: false, enumerable: true, writable: false}); /** * 调用方法 * @param name {String} 方法名 * @param meta {Json} 方法元数据配置 * @param data {Json} 要传递的参数 */ Object.defineProperty(this, "call", {value: function(name, data, meta){ meta = setDftValue(meta, metaset); /* 判断方法是否存在,如果存在则立即调用,否则延迟触发(直至方法被创建后) */ if(name in services){ services[name].call(meta.context, {data: data}); }else if(meta.defer){ console.warn("Function of name: " + name + " does not exist, calling in a deferred mode"); deferedCalls[name] = deferedCalls[name] || []; deferedCalls[name].push({meta: meta, data: data}); }else throw new Error("Function of name: " + name + " does not exist"); return this; }, configurable: false, enumerable: true, writable: false}); }; /** * 引用模块,如果指定名称的模块不存在则自动创建 * @param name {String} 模块名称 */ Object.defineProperty(Module, "ofName", {value: function(name){ if(name in modules) return modules[name]; return new Module({name: name}); }, configurable: false, enumerable: true, writable: false}); attachContext.Module = Module; })();
相关文章推荐
- 绝对酷,如何解决asp.net中javascript脚本的问题(使用服务器控件执行客户端脚本)
- 绝对酷,如何解决asp.net中javascript脚本的问题(使用服务器控件执行客户端脚本)
- asp.net关于Page_Load方法和执行js脚本顺序的不同所带来的问题
- document.write 方式引入外部 JS 文件导致脚本程序执行顺序不同以及 DOM 树更新延迟问题
- linux定时执行,脚本问题解决汇总
- 解决SQLPLUS执行脚本时显示用户名密码问题
- 如何解决linux下编译环境,运行环境不同的问题
- asp.net2.0中网页引用js脚本无效问题的解决..
- 向同一个servlet发多个不同请求,如何解决同步问题?
- 如何解决宽带连接正常,QQ也能连接上但网页打不开的问题
- 如何解决css样式表在不同浏览器中显示效果不同的问题
- 解决夜间需要不停重启机器的自动化测试脚本执行问题
- 关于如何解决Global.asax中Session_Start不执行的问题
- 解决IE无法执行脚本的问题
- 如何解决linux下编译环境,运行环境不同的问题
- 不同database排序方式,不同语言,如何解决国际化编程问题
- 如何解决下载的CHM文件无法显示网页问题
- 解决Mozilla Firefox浏览网页时JS、JavaScript脚本失效问题
- 如何解决在firefox中浏览IIS网页需要验证的问题
- 解决执行Oracle控制脚本时遇到的 “cat: /etc/oratab: 没有那个文件或目录”的问题