从14年8月份入职这家公司来,掐指一算专职前端这个职位也快8个月了,这里就这几个月自己做前端的一些工作写些心得出来和大家分享分享; 由于之前公司没有前端,所以我们所做的系统前端这块也算是比较混乱
从14年8月份入职这家公司来,掐指一算专职前端这个职位也快8个月了,这里就这几个月自己做前端的一些工作写些心得出来和大家分享分享;
由于之前公司没有前端,所以我们所做的系统前端这块也算是比较混乱,各种ace,bootstrap乱入,测试小妹妹都只能望洋兴叹了。
FirstBlood,先简介一下系统的结构,大体如下:
如图所示,外层A页面分为3个区域:导航,logo以及一个iframe页面,通过导航控制iframe的url切换内容。
SecondBlood,用seajs来管理js代码,模块化组件:
seajs的引用是放在外层A页面里面的,当seajs加载完毕后,再将各个组件返回的构造函数或者对象添加到A页面的window对象。这一点很重要,因为我们可以直接在B页面通过A的window对象访问到这些方法,这也就意味着我们在这些B页面里面不需要再引用任何资源而又能去调用这些方法!
作为一个前端,当后台同事问你调用组件要引用哪些资源的时候,你可以自豪的告诉他:
Hey,man, needn't!
那么这样做,可以带来如下好处:
B页面不用再引入额外的资源,加载的速度更快;
我们知道,如果我们在B页面里面打开一个div,该div的范围只能是在B页面内,无法在全屏范围内拖动。但是如果该div是在加载A页面里面的就可以全屏了。这一点再用户体验上可以带来一个质的飞越!
同时,我们在封装组件的时候一定要尽量减少后台同事在B页面所写的js代码量。
在A页面中,我们的代码如下:
//curwin 表示A页面的window对象, 而topWindow做一个A页面window对象的一个属性,用来做确定其身份。var curWin = this, topWindow = true;seajs.config({ base: "XXXX", //这里是js文件的路径 alias: { xxxxx //这里是一些组件的具体名称已经其文件路径。 }});//_ 是undersore.js,一个js方法库, 而main则是一些初始化方法的包含文件。seajs.use(['_', 'main'], function(_, main){ main.init(curWin); //开始初始化});
以上就是A页面所需的代码。
因为系统的后台是 niubility 的 java,所以我们的B页面都会引入一个名为 common-topwin.jsp 的页面(后面再揭晓代码),通过这个公共文件的引入,如果我们要调用某个组件的话只需这样既可:
topwin.xxxx(papapa);
ThirdBlood,如何解耦
某日,我正在码代码,项目组java负责人跑来跟我说为什么我们的页面(B页面)单独的运行时候会报错呢?What?要单独运行?不在框架里面玩了?o, shit!不在框架里面运行肯定报错呀,seajs都没有了不报错才怪了。
这个时候你会怎么想? “哎呀,这是不是需求变了?” “那以前写的代码怎么办呢?” papapapa,这完全就是晴天霹雳呀!
这个时候,对,就是这个时候,我们需要 “静静” !
也就是这个时候我们需要做一件事情,让B页面独立运行的时候自身重新构造一个seajs,让它重新装X重新飞。看看B页面的结构:
<html><head></head><!-- 公用资源的引入 --><body></body><script> //这个时候,topWin就是个定时炸弹,如果代码运行到这里topWin没有XXX方法就只能坐以待毙了。topWin.xxx </script></html>
正如前面所说的,我们这边项目的习惯是把公用引入是放到Body前面的,所以我需要在 那个位置 写一个代码来重新构造seajs.
此时,我们所面临的难题:
组件的 js 和 css 没有引入,而且我们也不知道当前页面需要的是哪些组件,因此我们也无法确认其需要的资源。
如果我们成功的动态添加进1所说的资源,那么在页面代码执行到topWin.xxx的时候很有可能资源代码没有加载完成。也就意味着XXX方法还是没法执行。
具体组件的js代码依赖,某些组件又是由多个子组件组成,加大了难度。
正如我们经常所说的XXX不哭,站起来XX,虽然有难度,那么能够解决的话我们也就能做出来!
Trust me , trust yourself!
据说,雅虎还是哪个公司做了一个前端优化:将整个站点JS代码分解,页面最初只加载必要的JS,等load后再去加载其他的。而重点时 如果用户这个时候点击了某个按钮 而相应该按钮的代码却还没有加载下来。这个时候用一个伪方法将其记录下来等其真正资源加载完毕后再执行! 那么, so 这个就是整个问题的核心了,也是最终的解决思路。下面是具体的代码实现:
//定义一些变量,其中 dynamicCss和dynamicJs是一些资源文件名,这里省去具体的内容var curWin = this, topWin = getTopWin(), logAdd = false, link, seaTag, treeDepend = false, treeRead = false, seaRead = false, dynamicCss = ['', '', ''], dynamicJs = ['', '', '', '', ''];//以topWin的dialog组件名判断当前页面是否在独立运行。if('function' != typeof topWin.dialog){ topWin = {}; //重置topWin这个对象, var seaTag = document.createElement('script'), headTag = curWin.document.getElementsByTagName('head')[0], //oprateRemenber用来记录组件被执行或者初始化 oprateRemenber = { use : [], dialog : [], partAndPerson : [], defaultDialog : [] } //从这个地方开始,我们就先把topWin的真实方式通过同名的伪方法先定义一次, topWin.defaultDialog = function(context){ var tempDialog = {}, len = oprateRemenber.defaultDialog.length; oprateRemenber.defaultDialog.push({ tag : tempDialog, context : context }); return function(config){ var func = oprateRemenber.defaultDialog[len].tag; return func(config); }; } topWin.dialog = function(config){ var tempDialog = {}; oprateRemenber.dialog.push({ tag : tempDialog, config : config }) return tempDialog; }; topWin.partAndPerson = function(config){ var that = this; oprateRemenber.partAndPerson.push({ tag : that, config : config }); if(!treeDepend) { dynamicTreeJs(0) treeDepend = true; } } topWin.seajs = {}; topWin.seajs.use = function(){ oprateRemenber.use.push(arguments); } //seajs的动态插入 seaTag.type = 'text/javascript'; seaTag.src = "XXX/sea.js?sqnm="+ Math.random(); headTag.appendChild(seaTag); //seajs加载完毕后判断时间,注意ie下面的兼容性问题 seaTag.onload = seaTag.onreadystatechange = function(){ if( ! this.readyState || this.readyState=='loaded' || this.readyState=='complete'){ //seajs加载完毕,开始执行已记录的方法 seaReady(); } }; function seaReady(){ //如果本代码是放在head里面的那就等 window加载完毕后再执行。 if(undefined == this.document || null == this.document.body){ setTimeout(arguments.callee, 300); return; } seajs.config({ base: XXXX, alias: { XXXXX } }); //正式使用seajs的use方法执行最终的方法 seajs.use('main', function(main){ var j, len, curAttr, curOpreate; main.init(curWin); topWin = curWin; for(var attr in oprateRemenber) { switch(attr){ case 'defaultDialog': curAttr = oprateRemenber.defaultDialog; for(j = 0, len = curAttr.length; j < len; j++) { curOpreate = curAttr[j]; //console.log(curOpreate.context); curOpreate.tag = topWin.defaultDialog(curOpreate.context); } break; case 'dialog': curAttr = oprateRemenber.dialog; for(j = 0, len = curAttr.length; j < len; j++) { curOpreate = curAttr[j]; curOpreate.tag = topWin.dialog(curOpreate.config) } break; case 'partAndPerson': if(treeRead) { curAttr = oprateRemenber.partAndPerson; for(j = 0, len = curAttr.length; j < len; j++) { curOpreate = curAttr[j]; curOpreate.tag = new topWin.partAndPerson(curOpreate.config) } } else{ seaRead = true; } break; case 'use': curAttr = oprateRemenber.use; for(j = 0, len = curAttr.length; j < len; j++) { if(0 == j) addCss(headTag, 'validate'); topWin.seajs.use(curAttr[j][0], curAttr[j][1]); } break; } } }); } for(var i = 0, len = dynamicCss.length; i < len; i++) { addCss(headTag, dynamicCss[i]) }}function dynamicTreeJs(sqnm){ if(0 == sqnm) { addCss(headTag, '${resource_static_css_url}/jquery.mCustomScrollbar'); } var dynamicScript; dynamicScript = document.createElement('script'); dynamicScript.type = 'text/javascript'; dynamicScript.src = '${resource_static_js_url}/' + dynamicJs[sqnm++] + '.js'; headTag.appendChild(dynamicScript); dynamicScript.onload = function(){ if(undefined != dynamicJs[sqnm]){ dynamicTreeJs(sqnm); } else { treeRead = true; if(seaRead) { curAttr = oprateRemenber.partAndPerson; for(j = 0, len = curAttr.length; j < len; j++) { curDialog = curAttr[j]; curDialog.tag = new topWin.partAndPerson(curDialog.config) } } } }};//添加CSS的一个方法function addCss(tag, cssHref) { var link = document.createElement('link'); link.type = 'text/css'; link.setAttribute('href', cssHref + '.css'); link.setAttribute('rel', 'stylesheet'); tag.appendChild(link);}//这个方法就是用来获取A页面window对象的方法,应该还有更好的方案。function getTopWin(){ var temp = false, curWin = this; if(undefined != this.topWindow) return this; while(!temp){ curWin = curWin.parent; if(curWin.parent === curWin) { return curWin; } temp = curWin.topWindow || false; } return curWin;}
通过这样一个操作,我们就把B页面脱离框架独立运行的问题解决了!
LastBlood,结束语
写到这里,感谢您的阅读,如果有必要,我会把自己封装的组件在后面的blog写出来和大家分享。3QS!