篇前声明:为了不涉及业务细节,篇内信息统一以某游戏,某功能代替

前不久,某游戏准备内测客户端,开发人员测试过程中发现某功能突然不灵了,之前的测试一切ok,没有发现任何异常,第一反应是,游戏内浏览器都是自己包装的,是不是做了什么改造,触发了某个盲点。

游戏方表示浏览器还是以前包装的Chromium,不过还真有不同的,就是UA改了,而且不是在原UA后加的后缀标识,而是完全替换,使用了游戏名称做UA,问题应该就在这里了,从代码上来看,不会触发任何雷区,理论上不会有问题,如果有问题,极有可能出现在框架中,YUI3的底层逻辑。

顺便抱怨一下,游戏内测试本身就是一个蛋痛的事情,基本都是自己封装浏览器,即使使用同样的内核,也有可能包装方式不同引出不同的坑,下载了一个游戏,使用fiddler神器代理js测试,js又一直被缓存,游戏内也不知道如何清缓存,代理不到,只能转而代理html页面,js加随机数时间戳。

第一步:YUI组件加载完毕打log

YUI({combine: true}).use('node', 'io', 'xml', function (Y) {Y.log("init");
});

测试结果:没有log,验证了我的想法,影响YUI组件加载,框架层级的

第二步:增减组件测试

测试结果:刚开始毫无规律,确实有部分组件去掉以后可以正常加载,貌似有共同点,但又看不出明确的共同点,跟其他同事沟通,貌似这些组件加载了皮肤,单独加载其它自带皮肤的组件,果然有问题,而不带皮肤就ok

第三步:YUI组件加载打log观察

测试结果:果然,竟然… 只加载了皮肤,没有加载js

这个时候才想起来看抓包… 发现组件真的没加载

第四步:YUI组件加载代码分析

先来看YUI加载过程

从这个抓包图来看,分为四步,yui种子文件—》loader组件配置文件—》组件皮肤—》组件js代码

1. 加载yui.js

可以理解为种子文件,它将负责后续yui的模块加载,当执行YUI.use引用模块时,开始加载loader文件(如果只引用了种子文字,但没有写js去use方法,实际上不会加载模块的)

YUI的_init方法对loader的配置,_init:

config.base = YUI.config.base || Y.Env.getBase(/^(.*)yui\/yui([\.\-].*)js(\?.*)?$/,  /^(.*\?)(.*\&)(.*)yui\/yui[\.\-].*js(\?.*)?$/);
config.loaderPath = YUI.config.loaderPath || 'loader/loader' + (filter || '-min.') + 'js';

use方法代码片段,开始加载loader

handleBoot = function() {Y._loading = false;queue.running = false;Env.bootstrapped = true;if (Y._attach(['loader'])) {Y.use.apply(Y, args);}
};if (G_ENV._bootstrapping) {queue.add(handleBoot);
} else {G_ENV._bootstrapping = true;var url = config.base + config.loaderPath;url += (url.indexOf("?") > -1 ? "&" : "?") + "v=" + Y.Env.CACHE_VERSION;Y.Get.script(url, {onEnd: handleBoot});
}

2. 加载loader.js

yui3为按需加载,Loader相当于所有组件的一个配置文件(yui最新版本已将loader直接放入yui.js,减少一个请求),配置内容包括所有的模块,模块的依赖项,模块的皮肤等,如果模块有皮肤,会优先加载,模块有依赖模块,也需要同时加载

我们来简单看一下loader的代码

"console": {"requires": ["yui-log","widget","substitute"],"skinnable": true
}

requires代表它依赖的其它模块,skinnable则代表它需要皮肤

模块添加时有这么一句代码 ,如果有皮肤,则添加皮肤

if (o.skinnable) {this._addSkin(this.skin.defaultSkin, i, name);
}_addSkin: function(skin, mod, parent) {var mdef, pkg, name,info = this.moduleInfo,sinf = this.skin,ext  = info[mod] && info[mod].ext;// Add a module definition for the module-specific skin cssif (mod) {name = this.formatSkin(skin, mod);if (!info[name]) {mdef = info[mod];pkg = mdef.pkg || mod;this.addModule({name:  name,group: mdef.group,type:  'css',after: sinf.after,after_map: YArray.hash(sinf.after),path:  (parent || pkg) + '/' + sinf.base + skin + '/' + mod + '.css',ext:   ext});}}return name;
}

看一下addskin方法,会根据配置的基准路径寻找该皮肤文件,默认是该组件所在文件夹下面的assets/skins/sam下的同名css文件

3. Loader加载成功

重新回到yui.Js的user方法中来看,此时,该判断就会生效,便拿到了所有loader对象,获取到所有的组件加载项args

if (boot && len && Y.Loader) {Y._loading = true;loader = getLoader(Y);loader.onEnd = handleLoader;loader.context = Y;// loader.attaching = args;loader.data = args;loader.require((fetchCSS) ? missing : args);loader.insert(null, (fetchCSS) ? null : 'js');
}

一路追踪到loader.js中的loadNext,我们使用了合并的方式,该方法会将同类资源文件合并加载(js or css),然后调用Y.Get.js or Y.Get. css进行资源文件的加载,由此再追踪到yui.js中的_next方法

4. 组件加载

此时,开始进行组件加载,并跟踪加载流程

if (q.type === "script") {n = _scriptNode(url, w, q.attributes);
} else {n = _linkNode(url, w, q.attributes);
}
// track this node's load progress
_track(q.type, n, id, url, w, q.url.length);

创建script or link节点

// add it to the head or insert it before 'insertBefore'.  Work around IE
// bug if there is a base tag.
insertBefore = q.insertBefore || d.getElementsByTagName('base')[0];if (insertBefore) {s = _get(insertBefore, id);if (s) {s.parentNode.insertBefore(n, s);}
} else {h.appendChild(n);
}

添加到页面上,这里皮肤css与js并不是完全的并行加载,对于webkit与gecko内核,直接开始加载下一个资源文件,即js

if ((ua.webkit || ua.gecko) && q.type === "css") {_next(id, url);
}

我们看一下_track方法

if (ua.ie) {n.onreadystatechange = function() {var rs = this.readyState;if ("loaded" === rs || "complete" === rs) {n.onreadystatechange = null;f(id, url);}};
// webkit prior to 3.x is no longer supported
} else if (ua.webkit) {if (type === "script") {// Safari 3.x supports the load event for script nodes (DOM2)n.addEventListener("load", function() {f(id, url);});}// FireFox and Opera support onload (but not DOM2 in FF) handlers for
// script nodes.  Opera, but not FF, supports the onload event for link
// nodes.
} else {n.onload = function() {f(id, url);};n.onerror = function(e) {_fail(id, e + ": " + url);};
}

ie浏览器通过readystatechange进行判断并加载后续的资源文件,其它浏览器如webkit則监听onload(即使监听失败,_next方法中可直接执行js加载)

我们加载的模块是console,但是只加载了css,js不见了,如下图,我不是webkit,我不是gecko,我也不是IE,然后就进入了死胡同

由于UA变更,没有将其判断为webkit,_next方法没有执行,加载完css后无法直接加载js,故只能依靠track方法,track方法判断其不属于IE浏览器,直接进入了else分支,但由于浏览器本身是Chromium内核,link节点的onload方法无法触发。进入了死万劫不复之地。

解决方法也比较简单,对于非css请求,依赖_trace方法触发回调其它js加载(js加载需要严格时序保证),而css请求,不依赖_trace,直接调用_next触发其它资源加载

该方法改动较小,风险较小,即使后续有新的UA变更或浏览器更新等,仍可以保证正常使用,缺点在于,如果加载过程中发生网络波动等异常,有可能出现样式闪动的问题。

转载于:https://www.cnblogs.com/v-rockyli/p/3639930.html

【原】从一个bug浅谈YUI3组件的资源加载相关推荐

  1. 浅谈设备、驱动的加载和匹配

    要了解Linux设备驱动,首先要理解linux的bus.device.driver三个概念. Bus就是总线,除了我们通常知道的i2c.spi.usb等总线之外,Linux中还有一个很重要的总线pla ...

  2. 浅谈Android中的异步加载之ListView中图片的缓存及优化三

    隔了很久没写博客,现在必须快速脉动回来.今天我还是接着上一个多线程中的异步加载系列中的最后一个使用异步加载实现ListView中的图片缓存及其优化.具体来说这次是一个综合Demo.但是个人觉得里面还算 ...

  3. picker多选 vant_浅谈vant组件Picker 选择器选单选问题

    浅谈vant组件Picker 选择器选单选问题 1.写遮罩 2.定义data 3.写事件 4.效果图 补充知识:vue使用vant编辑用户性别 我就废话不多说了,大家还是直接看代码吧~ v-model ...

  4. unity 异步加载网络图片_一个非常好用的AssetBundle资源加载器

    Loxodon Framework Bundle是一个非常好用的AssetBundle加载器,也是一个AssetBundle冗余分析工具.它能够自动管理AssetBundle之间复杂的依赖关系,它通过 ...

  5. 【Flutter】Image 组件 ( 配置本地 gif 图片资源 | 本地资源加载 placeholder )

    文章目录 一.配置本地 gif 图片资源 二.本地资源加载 Placeholder 三.完整代码示例 四.相关资源 一.配置本地 gif 图片资源 配置 assets 图片资源 : 将 gif 图片拷 ...

  6. 一个可以拖拽的异步按需加载树

    最近完成了一个可以拖拽的异步按需加载树,顾名思义,这个树,至少支持以下三个功能. 1,节点可以拖拽(项目需要,已设置为只允许同级节点拖拽). 2,异步加载(使用ajax加载数据,没啥好说的). 3,按 ...

  7. react实现异步插件_React-loadable实现组件进行异步加载

    React 项目打包时,如果不进行异步组件的处理,那么所有页面所需要的 js 都在同一文件中(bundle.js),整个js文件很大,从而导致首屏加载时间过长.所有,可以对组件进行异步加载处理,可以使 ...

  8. web前端高级React - React从入门到进阶之组件的懒加载及上下文Context

    第二部分:React进阶 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...

  9. 【Bug档案01】Spring Boot的控制器+thymeleaf模板 -使用中出现静态资源加载路径不当的问题 -解决时间:3h

    [Bug档案01]Spring Boot的控制器+thymeleaf模板 -使用中出现静态资源加载路径不当的问题 -解决时间:3h 参考文章: (1)[Bug档案01]Spring Boot的控制器+ ...

  10. 好轮子收藏:一个支持几乎所有流行格式的图像加载库stb_image.h

    是在opengl教程网站上看到的,挺好用. stb_image.h一个非常流行的单头文件图像加载库,它能够加载大部分流行的文件格式,并且能够很简单得整合到你的工程之中. 下载地址:stb/stb_im ...

最新文章

  1. linux7内核优化,centos7 系统内核、网络等优化(适用高并发)
  2. 基于关联规则(Variational Autoencoders)疾病预测系统实战:(pyspark FPGrowth实现频繁项集挖掘、最后给出预测模型topK准确率和召回率)
  3. php iis mysql windows2003,Windows Server 2003 IIS6.0+PHP5(FastCGI)+MySQL5环境搭建教程 | 系统运维...
  4. Gitlab环境快速部署(RPM包方式安装)
  5. [HNOI2009]最小圈 (二分答案+负环)
  6. 上千个电脑文件怎么搬?用Python一键复制移动
  7. 服务器端与客户端TCP连接入门(三:多线程)
  8. 一封电子邮件的发送和接收的主要步骤
  9. Learn Python the first day.
  10. leetcode707:设计链表(增删差)
  11. 简单版:带干扰线的图形验证码生成
  12. html左右分隔可调整,CSS实现可拖拽分割面板
  13. iPhone SDK开发基础之 OpenFlow编程
  14. 基于PaddlePaddle实现声纹识别
  15. 弘辽科技:淘宝商品入池怎么设置?技巧有哪些?
  16. C++-线程的join和detach
  17. vue-cli-service build 如何环境设置,打出不同环境的包
  18. 教育类小程序APP开发
  19. 2020春季学期哈工大软件构造学习心得一
  20. php mvc例子,PHP_ThinkPHP的MVC开发机制实例解析,ThinkPHP是目前国内应用非常广 - phpStudy...

热门文章

  1. Linux命令大全(超详细版)
  2. 计算机累论文的数据字典怎么写,毕业论文中数据字典应该这样写
  3. 远程连接工具rdcman
  4. C语言取反(~)简单理解
  5. Web前端开发技术包括哪些?
  6. 2021年高压电工模拟考试系统及高压电工考试试题
  7. BP神经网络代码示例
  8. php emoji base64,PHP处理字符中的emoji表情
  9. EC Final 2019 题解
  10. 【单片机仿真】(二)keil 安装教程