起步软件技术论坛
搜索
 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 2727|回复: 9

[分享] 由windowDialog引发的血案

  [复制链接]

104

主题

369

帖子

1173

积分

金牌会员

王小二

Rank: 6Rank: 6

积分
1173
QQ
发表于 2017-3-5 02:01:39 | 显示全部楼层 |阅读模式
本帖最后由 w1017894741 于 2017-3-5 02:18 编辑

案件主要讲述的是:      windowDialog和他身上的 forceRefreshOnOpen 属性的故事


1 描述: 做项目的时候发现了一个bug,一开始懵懵懂懂弄不明白是哪里的问题,结果静心仔细想了想,加上测试终于弄明白了大致的原理和解决方案;
下面的是有bug的演示
   1.1 简单点的演示

         1111.gif

       代码是这样子的:
  1. define(function(require) {
  2.         var $ = require("jquery");
  3.         var justep = require("$UI/system/lib/justep");

  4.         var $input2 = $("input", ".scontainer");

  5.         var Model = function() {
  6.                 this.callParent();
  7.         };

  8.         Model.prototype.modelParamsReceive = function(event) {
  9.                 $input2.each(function(index, ele) {
  10.                         this.oninput = function(e) {
  11.                                 console.log("我是第二页的第" + (index + 1) + "个input,你在写我");
  12.                         }
  13.                 });
  14.         };

  15.         Model.prototype.button1Click = function(event) {
  16.                 this.comp("windowDialog1").open({
  17.                         "src" : require.toUrl("$UI/TEST/oninput/oninput.w")
  18.                 });
  19.         };

  20.         return Model;
  21. });
复制代码





    1.2 复杂点的演示

       2222.gif
        代码就不贴了,有点多...

2 案例:
      如果父页面的windowDialog组件设置了打开页面时强制刷新属性为true:
  1. forceRefreshOnOpen 设置为true
复制代码
那么返回上页再次进入时将不会注册当前页面中的input事件,导致事件处理函数失效

3 调试:
     设置了强制刷新属性后,再次进入页面时,这些全局的变量将不会再次被初始化.

4 猜测:
    4.1 页面强制刷新后,dom对象会重新生成(或改变)
    4.2 页面强制刷新后,对应的js文件不会重新初始化,只是通过事件机制执行某些事件,但除此外的一些全局变量和方法都不会重新初始化,所以之前取到的jquery对象和当前文档结构中的jQuery对象不同

5 测试:
   5.1 由于js中不能直接获得变量的引用地址,所以只能通过下面的方式来进行测试
   5.2 为了验证上面的猜测,我特意在model组件的onParamsReceive事件里对 全局变量$input2中的第一个input 与 重新取了一次本页面的第一个input进行全等比较,查看所引用的对象的堆地址是否相同.
  1. 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. 变量名称:堆中的实际地址
复制代码

          在堆区中是真正存放这个对象的地方,它有:            1 唯一地址
            2 实际的内容,这些内容占据着相应的内存空间

    "如果将一个引用类型的变量设置为null,javascript的垃圾回收机制就会把这个对象在堆中销毁,并回收它所占用的内存空间"

    11.2 推测:
               通过上面说的这个原理可以推测一下这样一个事实:  
  1. 可以将变量比喻成在 栈区(小国联盟) 中有一根线牵着 堆区(小国) 中对象的实际内容.当把对象设置为null(踢出小国联盟)时,就是切断了这根线,这时这个对象在堆中 "唇亡齿寒",就被强大的 javascript垃圾回收帝国给 干掉(销毁),然  吞并(内存释放) 了
复制代码

   都是这么画图的,不信你看:


    123.png


    11.3 如果按照10中的方案来做的话,还是会增加程序的性能负担,因为每次都在jquery遍历文档树来获取需要的内容,那我想能不能优化一下?

    11.4 按照 11.2 的推测我有这样一个想法:
         11.4.1  建立联盟: 全局声明变量(先在所有小国的外面建立这个联盟)
  1. var $input2;
复制代码

        11.4.2  按需结盟: 第一次进入页面,需要获取到input,来进行事件绑定(当需要你参加进来拯救世界的时候就和你联盟)                       
  1. Model.prototype.modelParamsReceive = function(event){
  2.         $input2 = $("input", ".scontainer");//在需要时,为已声明的全局变量赋值
复制代码

        11.4.3 按利结盟: 第二次进入页面,因为这时候页面里的input对象不再是第一次进入页面时获取的对象了(虽然你们长得一样,可你们还是不同的人,所以只能抛弃你了. 说白了就是这里用不到你了,你自己去玩吧)
                                       
        代码还是这么写:         
  1. Model.prototype.modelParamsReceive = function(event){
  2.         $input2 = $("input", ".scontainer");//为已声明的全局变量赋值
  3. }        
复制代码

        (1) 戏剧化的描述好似更像是政治斗争,利益绑定,这个真的是不是我不单纯,而是这个过程就是介样子滴
        (2) 实际上在这里和新盟友结盟的时候发生了两件事情:
               1 全局变量 $input2(小国联盟) 和 $("input", ".scontainer") ( 新的小伙伴) 搭伙吃饭啦               2 旧对象(老盟友)被抛弃了(栈区和堆区的线被切断了),结果它被 垃圾回收帝国 给干掉(销毁)然后吞并(内存释放)了


"我虽然无心害你,但你却因我而死. 额其实这么说也矛盾了,因为我就是要让你被干掉,不然浏览器跑程序累不累,本着能轻松一点就轻松一点的态度干掉你,我也是给别人打工的,千万不要怪我啊,阿弥陀佛!!!!"


12 喝完"强身追风酒"以后,我乘着醉意改了一下代码,效果变成了这样子:
     12.1 简单点的演示

         11111.gif

   代码是这样的:
  
  1. define(function(require) {
  2.         var $ = require("jquery");
  3.         var justep = require("$UI/system/lib/justep");

  4.         var $input2;

  5.         var Model = function() {
  6.                 this.callParent();
  7.         };

  8.         Model.prototype.modelParamsReceive = function(event) {
  9.                 $input2 = $("input", ".scontainer");
  10.                 $input2.each(function(index, ele) {
  11.                         this.oninput = function(e) {
  12.                                 console.log("我是第二页的第" + (index + 1) + "个input,你在写我");
  13.                         }
  14.                 });
  15.         };

  16.         Model.prototype.button1Click = function(event) {
  17.                 this.comp("windowDialog1").open({
  18.                         "src" : require.toUrl("$UI/TEST/oninput/oninput.w")
  19.                 });
  20.         };

  21.         return Model;
  22. });
复制代码



     12.2 复杂点的演示

    22222.gif


                        
        困了,不了!

以上纯属个人观点:
        如有错误,恳请指正!!!
        "如有雷同,纯属倒霉!"
                                --此句话来源于网络



参考:
JavaScript 中的对象引用




评分

参与人数 1威望 +20 收起 理由
Masion + 20 神马都是浮云

查看全部评分

<a href="#自我介绍"/>点点点</a>

104

主题

369

帖子

1173

积分

金牌会员

王小二

Rank: 6Rank: 6

积分
1173
QQ
 楼主| 发表于 2017-3-5 02:19:15 | 显示全部楼层
:L:L:L:L:L:L:L:L:L:L
<a href="#自我介绍"/>点点点</a>
回复 支持 反对

使用道具 举报

39

主题

168

帖子

421

积分

中级会员

Rank: 3Rank: 3

积分
421
QQ
发表于 2017-3-5 07:42:36 | 显示全部楼层
回复

使用道具 举报

104

主题

369

帖子

1173

积分

金牌会员

王小二

Rank: 6Rank: 6

积分
1173
QQ
 楼主| 发表于 2017-3-6 02:32:56 | 显示全部楼层
<a href="#自我介绍"/>点点点</a>
回复 支持 反对

使用道具 举报

4

主题

27

帖子

72

积分

初级会员

Rank: 2

积分
72
QQ
发表于 2017-3-6 11:52:21 | 显示全部楼层
牛逼
回复

使用道具 举报

19

主题

71

帖子

238

积分

中级会员

Rank: 3Rank: 3

积分
238
QQ
发表于 2017-3-6 14:08:28 | 显示全部楼层
高手。厉害。
话说,想要用好 起步的平台,前端h5 css js jquery 等是不是都要精通呢?
回复 支持 反对

使用道具 举报

104

主题

369

帖子

1173

积分

金牌会员

王小二

Rank: 6Rank: 6

积分
1173
QQ
 楼主| 发表于 2017-3-6 23:10:50 | 显示全部楼层
zcbwz 发表于 2017-3-6 14:08
高手。厉害。
话说,想要用好 起步的平台,前端h5 css js jquery 等是不是都要精通呢? ...

未必要很熟练,但起码要有一定的底子,起码有点想法知道怎么问问题,怎么搜索为题.
我觉得吧,学习额外的东西,不仅可以提高解题能力,更重要的是可以丰富解题思路.
技术好不好,api用的熟不熟练都是暂时的.重要的是思路.
<a href="#自我介绍"/>点点点</a>
回复 支持 反对

使用道具 举报

104

主题

369

帖子

1173

积分

金牌会员

王小二

Rank: 6Rank: 6

积分
1173
QQ
 楼主| 发表于 2017-3-6 23:30:08 | 显示全部楼层
帖子里面其实有些地方表述的也不合理,真正起到优化的地方应该是这里:
1 首先刚进入页面的时候是需要重新获取一下,这是不可避免的
2 优化的是,这种获取不需要再其他的事件中再次进行(真正节省的是这个部分的性能).
   因为事件执行是有顺序的(有时还要考虑到异步),只要选择一个对的事件,当其他事件需要使用时可以直接使用这个全局变量.

3 这里的全局变量其实只是相对于当前js上下文而言,并不是真正的全局变量,原因是模块化规范中,内部属性对外界的直接联系是封闭的,这么说其实也不完全准确,关键还是得看写法.
    就当前写法,如:
   
  1. define(function(require){
  2.           var Model = {};
  3.          
  4.          return Model;
  5.      });
复制代码


   如果按上面的这种写法,想要实现真正的全局变量,我这里有两种想法
     (1) 一个方法是挂载window对象上,如: window.a = 1;
     (2) 把变量声明在 define(...) 外面
<a href="#自我介绍"/>点点点</a>
回复 支持 反对

使用道具 举报

97

主题

580

帖子

1359

积分

金牌会员

Rank: 6Rank: 6

积分
1359
QQ
发表于 2017-3-9 10:35:22 | 显示全部楼层
你有当讲师的潜质。。。能把枯燥的代码说的如此清新,资质当当的啊
传说中路过
回复 支持 反对

使用道具 举报

104

主题

369

帖子

1173

积分

金牌会员

王小二

Rank: 6Rank: 6

积分
1173
QQ
 楼主| 发表于 2017-3-9 16:39:11 | 显示全部楼层
carbinechun 发表于 2017-3-9 10:35
你有当讲师的潜质。。。能把枯燥的代码说的如此清新,资质当当的啊

被张鑫旭逗比大神荼毒已深
<a href="#自我介绍"/>点点点</a>
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|X3技术论坛|Justep Inc.    

GMT+8, 2024-5-4 07:58 , Processed in 0.091044 second(s), 27 queries .

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表