|
本帖最后由 w1017894741 于 2017-3-5 02:18 编辑
案件主要讲述的是: windowDialog和他身上的 forceRefreshOnOpen 属性的故事
1 描述: 做项目的时候发现了一个bug,一开始懵懵懂懂弄不明白是哪里的问题,结果静心仔细想了想,加上测试终于弄明白了大致的原理和解决方案;
下面的是有bug的演示
1.1 简单点的演示
代码是这样子的:- define(function(require) {
- var $ = require("jquery");
- var justep = require("$UI/system/lib/justep");
- var $input2 = $("input", ".scontainer");
- var Model = function() {
- this.callParent();
- };
- Model.prototype.modelParamsReceive = function(event) {
- $input2.each(function(index, ele) {
- this.oninput = function(e) {
- console.log("我是第二页的第" + (index + 1) + "个input,你在写我");
- }
- });
- };
- Model.prototype.button1Click = function(event) {
- this.comp("windowDialog1").open({
- "src" : require.toUrl("$UI/TEST/oninput/oninput.w")
- });
- };
- return Model;
- });
复制代码
1.2 复杂点的演示
代码就不贴了,有点多...
2 案例:
如果父页面的windowDialog组件设置了打开页面时强制刷新属性为true:- forceRefreshOnOpen 设置为true
复制代码 那么返回上页再次进入时将不会注册当前页面中的input事件,导致事件处理函数失效
3 调试:
设置了强制刷新属性后,再次进入页面时,这些全局的变量将不会再次被初始化.
4 猜测:
4.1 页面强制刷新后,dom对象会重新生成(或改变)
4.2 页面强制刷新后,对应的js文件不会重新初始化,只是通过事件机制执行某些事件,但除此外的一些全局变量和方法都不会重新初始化,所以之前取到的jquery对象和当前文档结构中的jQuery对象不同
5 测试:
5.1 由于js中不能直接获得变量的引用地址,所以只能通过下面的方式来进行测试
5.2 为了验证上面的猜测,我特意在model组件的onParamsReceive事件里对 全局变量$input2中的第一个input 与 重新取了一次本页面的第一个input进行全等比较,查看所引用的对象的堆地址是否相同.
- console.log($input2[0] === $("input", ".scontainer")[0]);
复制代码
6 测试结果:
6.1 第一次进入页面,由于都是第一次取到,所以肯定是相同的,比较结果为:true 然后返回上一页,重新进入
6.2 这次$input2变量中存的是第一次进入页面取到的input,$("input", ".scontainer")[0]表达式取到的是第二次进入页面重新获取的input,比较结果为:false
6.3 上述2次操作和结果证明了我上面的猜测
7 结论:
7.1 虽然这个问题是由windowDialog组件产生的,但这其实也不能算是windowDialog组件的bug
7.2 反正我是使用windowDialog组件进行页面跳转的,如果你用的也是这个,可能需要慎重对待forceRefreshOnOpen这个属性了,如果你业务上要求要刷新打开的页面,那就得小心了
7.3 以上的问题不仅仅是input事件特有,如果根据原理来推论,使用这种方的操作都会有相同的问题.
"自从喝了强身追风酒,腰也不酸了,腿也不疼了".
----广告乱入
8 本来是为了优化性能,所以才事先把所有的input在全局区域缓存起来,结果却是搬起石头砸自己的脚,好在找到了原因和解决方案.
9 项目这么紧,我还浪费时间在研究这件事上面,我想我一定是疯了,可是忍不住啊 ^0.0^
10 解决方案: 一种解决方案就是: 在能确定的每次都会执行的情境下去获取, 见11.4
11 还有一点值得提一下,每次打开页面通过jquery获取input的时候,都会在栈区中开辟内存来存储变量:
由于全局变量的生存期很长,有时要考虑到性能优化.所以可以有如下的事情发生:
11.1 变量代表的如果是引用类型,这个变量在栈区中会有一个类似键值对的东西:
在堆区中是真正存放这个对象的地方,它有: 1 唯一地址
2 实际的内容,这些内容占据着相应的内存空间
"如果将一个引用类型的变量设置为null,javascript的垃圾回收机制就会把这个对象在堆中销毁,并回收它所占用的内存空间"
11.2 推测:
通过上面说的这个原理可以推测一下这样一个事实:
- 可以将变量比喻成在 栈区(小国联盟) 中有一根线牵着 堆区(小国) 中对象的实际内容.当把对象设置为null(踢出小国联盟)时,就是切断了这根线,这时这个对象在堆中 "唇亡齿寒",就被强大的 javascript垃圾回收帝国给 干掉(销毁),然 吞并(内存释放) 了
复制代码
都是这么画图的,不信你看:
11.3 如果按照10中的方案来做的话,还是会增加程序的性能负担,因为每次都在jquery遍历文档树来获取需要的内容,那我想能不能优化一下?
11.4 按照 11.2 的推测我有这样一个想法:
11.4.1 建立联盟: 全局声明变量(先在所有小国的外面建立这个联盟)
11.4.2 按需结盟: 第一次进入页面,需要获取到input,来进行事件绑定(当需要你参加进来拯救世界的时候就和你联盟) - Model.prototype.modelParamsReceive = function(event){
- $input2 = $("input", ".scontainer");//在需要时,为已声明的全局变量赋值
复制代码
11.4.3 按利结盟: 第二次进入页面,因为这时候页面里的input对象不再是第一次进入页面时获取的对象了(虽然你们长得一样,可你们还是不同的人,所以只能抛弃你了. 说白了就是这里用不到你了,你自己去玩吧)
代码还是这么写:
- Model.prototype.modelParamsReceive = function(event){
- $input2 = $("input", ".scontainer");//为已声明的全局变量赋值
- }
复制代码
(1) 戏剧化的描述好似更像是政治斗争,利益绑定,这个真的是不是我不单纯,而是这个过程就是介样子滴
(2) 实际上在这里和新盟友结盟的时候发生了两件事情:
1 全局变量 $input2(小国联盟) 和 $("input", ".scontainer") ( 新的小伙伴) 搭伙吃饭啦 2 旧对象(老盟友)被抛弃了(栈区和堆区的线被切断了),结果它被 垃圾回收帝国 给干掉(销毁)然后吞并(内存释放)了
"我虽然无心害你,但你却因我而死. 额其实这么说也矛盾了,因为我就是要让你被干掉,不然浏览器跑程序累不累,本着能轻松一点就轻松一点的态度干掉你,我也是给别人打工的,千万不要怪我啊,阿弥陀佛!!!!"
12 喝完"强身追风酒"以后,我乘着醉意改了一下代码,效果变成了这样子:
12.1 简单点的演示
代码是这样的:
- define(function(require) {
- var $ = require("jquery");
- var justep = require("$UI/system/lib/justep");
- var $input2;
- var Model = function() {
- this.callParent();
- };
- Model.prototype.modelParamsReceive = function(event) {
- $input2 = $("input", ".scontainer");
- $input2.each(function(index, ele) {
- this.oninput = function(e) {
- console.log("我是第二页的第" + (index + 1) + "个input,你在写我");
- }
- });
- };
- Model.prototype.button1Click = function(event) {
- this.comp("windowDialog1").open({
- "src" : require.toUrl("$UI/TEST/oninput/oninput.w")
- });
- };
- return Model;
- });
复制代码
12.2 复杂点的演示
困了,不作了!
以上纯属个人观点:
如有错误,恳请指正!!!
"如有雷同,纯属倒霉!"
--此句话来源于网络
参考:
JavaScript 中的对象引用
|
评分
-
查看全部评分
|