#阿里前端学习笔记(三)#
2014-07-22 by niuzhixiang
##汽车票项目h5-car搜索模块源码阅读总结##
###搜索首页与关联页面的切换###
./index.html
中,首先预先下载选择城市页面../suggestion-list/index.html
和搜索结果页面../searchlist/index.html
,然后使用./index.js
模块,核心代码如下:
(function(){
......
//提前下载后续页面
docElem.transition('preload', '../suggestion-list/index_withbar.html');
docElem.transition('preload', '../searchlist/index_withbar.html');
});
//使用./index.js模块
KISSY.use("h5-car/pages/search/index", function (S, Search) {
var search = new Search({
suggestionListingURL:'../suggestion-list/index_withbar.html',
searchListingURL:'../searchlist/index_withbar.html'
});
});
./index.js
模块的核心代码如下:
......
var Search = BaseClass.extend({
initializer:function(){
var self = this;
/*初始化当前模块的属性*/
self.initAttr();
/*绑定页面事件,包括pagebeforehide、pagehide、pagebeforeshow等整个页面
切换时的事件*/
self.bindPageEvents();
/*绑定页面元素事件,包括选择起点/终点的click事件(此时切换到选择城市页面);
选择日期时的click事件(此时呼出日历组件);提交表单的click事件(此时
切换到搜索结果页面)
self.bindEvent();
/*填充默认值,包括出发/到达城市、日期*/
self.initValue();
self.router();
// 埋点
self.pagePoint && clearTimeout(self.pagePoint);
self.pagePoint = setTimeout(function() {
self.sendPoint('Page', 'Bus','');
}, 800);
//self.bindTransition();
},
......
在self.bindEvent()
方法中,绑定了出发城市和到达城市div元素的click事件,当点击“出发城市”或“到达城市”时,click事件的回调函数中会调用Zepto的transition.js
组件的transition()
方法,代码如下:
//netWorkOnline()方法继承自base-class
self.netWorkOnline(function(){
//如果联网,则调用self.transition()方法,传入要切换到的页面url(选择城市页面)
self.transition(suggestionListingURL + '?cityFrom=arr&depCity=' +
encodeURIComponent(depCityTotalVal));
},function(){
//如果未联网,报错
self.notification.toast('网络开小差啦...请稍后再试试');
});
transition()
方法定义在transition.js
组件中,代码如下:
//transtion()方法赋给了Zepto元素的原型,因此所有Zepto元素都具有了该方法
$.fn.transition = function(method) {
//如果传入的method参数是在methods对象中定义的方法名,则调用该方法
if (methods[method])
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
//如果传入的method参数并非一个方法名,调用methods对象中定义的to()方法
else if (typeof method === 'object' || !method)
return methods.to.apply(this, arguments);
else
throw 'Method ' + method + ' does not exist';
};
其中methods对象中定义的方法如下:
var methods = {
/*初始化一些选项*/
options : function(options) {...},
/*初始化并渲染页面,并且绑定window对象的hashchange事件,当当前页面url的hash值
变化时,会进行一系列操作,并调用changePage()方法完成页面的切换*/
init : function(eventData, targetPage, title) {...},
/*修改当前页面url的hash值为要切换到的页面相对url*/
to : function(page, transition, reverse) {...},
/*当window对象的hashchange事件触发时被调用,完成页面的切换。该方法中进行了一系列
操作,并调用了perform()方法完成页面切换的动画效果*/
changePage : function(to, transition, back, top) {...},
/*采用Ajax下载指定的页面,在搜索页面index.html中被调用,用于预先下载要切换的页面*/
preload: function(what) {...},
/*完成页面切换的动画效果*/
perform : function(from, to, transition, back, top, changeEventData) {...}
}
至此,可以得到页面切换的完整流程:
- 当用户点击搜索首页
./index.html
的“出发城市”或“到达城市”元素时,触发其click事件(click事件的回调函数在./index.js
中的bindEvent()
方法中进行了定义)。 - click事件的回调函数中调用了transition组件中定义的
transition()
方法,进而调用了methods.to()
方法,methods.to()
方法修改了当前页面url的hash值。 - url的hash值被修改后,当前页面的url就从
http://demo.com/pages/search/index_withbar.html?ks-debug
变为http://demo.com/pages/search/index_withbar.html?ks-debug#../suggestion-list/index_withbar.html?cityFrom=arr&depCity=%E4%B8%8A%E6%B5%B7%E5%B8%82
- hash值改变后,触发了
methods.init()
方法中绑定在window对象上的hashchange事件。 - window对象的hashchange事件触发后,其回调函数中调用了
methods.changePage()
方法进行页面的切换,其中还调用了methods.perform()
方法实现页面切换的动画效果。 - 至此,页面切换完成。
注意:上面展示了从搜索首页到选择城市页面的切换,而从搜索首页到搜索结果页面的切换原理是类似的。
另外,任何页面源代码的阅读都可以遵循以上的方式进行,总结如下:
- 先从模块的index.html入手,看这张页面使用了哪个kissy模块;
- 然后再去看这个模块的index.js文件,主要是initializer()中,都调用了其他哪些方法;
- 进而再逐一去看这些方法,一般包括事件绑定方法、数据请求方法、页面渲染方法(包括Juicer模板编译)等几类;
- 接着再一层层往下地看这些方法都调用了底层的哪些方法(或哪些kissy组件),这些底层的方法又调用了更底层的哪些方法...以此类推。
- 最终将整个页面源代码看明白、理解透彻。
###日历组件###
- 在页面初始化(也即调用
./index.js
模块的initializer()
方法)时,会执行./index.js
中的router()
方法,该方法会调用base-class
模块的calendarInit()
方法创建并初始化一个日历控件(即一个Calendar对象),但并不呈现。 - 在搜索首页
./index.html
中点击选择日期的div元素后,会触发该元素click事件的回调函数(在./index.js
中的bindEvent()
方法中对该事件进行了绑定)。 - 回调函数中会将页面初始化时创建的日历控件(即Calendar对象)显示在页面上。
###筛选控件###
其原理与日历控件类似,都是一个widgets组件(此处是Filter对象),直接引用即可。
##jSQL##
jSQL是用JavaScript实现的一套模仿SQL数据库的数据操作方法库。它的目的是让前端程序员更加专注于业务需求和逻辑,而将数据的排序、筛选等通用性操作与业务逻辑分离。
jSQL的典型使用场景:客户端从服务端获得数据后,将其先存储在jSQL创建的“模拟”数据库中(位于内存中),然后根据页面的业务需求(例如按某某属性从高到低排序,或者按某种条件进行二次筛选),使用jSQL提供的API(语法与SQL语法很类似)从原始数据中进行查询,最后将查询结果渲染在页面上。这样就实现了将数据的逻辑操作(排序、筛选等)与前端呈现完全分离,非常优雅。
jSQL的更多资料请查看jSQL官方网站
##ttid##
主要是运营同学使用,用来统计用户访问渠道(例如 H5页面 or PC网页),例如用我的iPhone打开“汽车票”搜索页面时,页面url为http://h5.m.taobao.com/trip/h5-car/search/index_withbar.html?ttid=201200%40laiwang_iphone_5.2.0
,在url后面挂着的参数ttid表明了本次请求的来源是iPhone手机(此外还包括一些其他信息)。对于前端同学来说,一般无需关心ttid的取值,直接透传即可(也就是说前端在做页面跳转时,url上始终携带着ttid参数)。
##埋点##
埋点是为了让后台可以随时随地记录用户都产生了哪些行为,例如点击按钮、选择日期、点击超链接等行为都可以进行埋点。在需要埋点的地方调用base-class
模块的sendPoint(type, page, action)
方法即可(该方法底层调用了tracker
模块的sendPoint(str)
方法),sendPoint()
方法的三个参数分别为用户行为类型、用户行为发生的页面、用户的行为名称。
##图片base64加密##
如果在HTML页面中引用体积较小的图片,不必通过<img src=".." />
这种方式去图片的存储路径加载该图片了,而是可以直接将图片进行Base64编码,将编码后的图片数据直接放到网页里,例如以下代码就将图片数据直接放在网页里,而不用专门去图片存放路径下载了,这样做可以节省流量。
<img class="realname-cert-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJEAAAAgCAMAAAAlkGrBAAAAtFBMVEUAAAAdluwdluwdlusclesdlescjeL///9JpvK/2fnW5vtLnfDr8/2ny/depfFEofFYsvREovE1le8/nfBvrfJOqvKNvPQ6mfBTrvNQrPO00vh+tfP1+f7L3/qaxPXh7PxBn/FGo/FLqPJHpPE3l+88m/BVsPMdluxMqPJWsPM9nPBZs/RRrPNYs/RatPRZtPR/wvJAn/E2ou/9/v9XrfNUp/JAnPD7/f9No/FIofFBnfD+//9MgKVDAAAAB3RSTlMAAPb9+v4JrawDTgAABAdJREFUeF7tl+eOIkkQhPdsljXtvcGbmdk9b9//va6zqppuaJBWnE4anTZ+IBSi4CMrMhM+WB3ekT58hTp8fDf6z4i+EB13Ol+6QRglN5a5C2D+JdGPP9w6RdVRvQAiFbD4CjTXPDAeNgxGjlNg3f1EsTdPE+V6RVApQEWcLsXKMwGCD87qYmkKsn/FonAASby7KkHqPFDhRYqbJ4kMp6W0qkFIJ+rLYjQFaNApqTo6oogNlr3JEKBdj28yuLRvYSZJnq1RCEtVhQUK5GR1O0eURCUA42Yici6yp4INqqEeHgXSPk0kqyt5ohMCUet0g2Vr5O1a7T3RFK8ShMqiQRTo8JiRz8lRQUhxj6iSTZWRVSmAF0WBRuGLUerBQLIymLoqaNVmurUJlBMfNKrv9drhpztAAIi0IML7F20mAUI0LNFGscEUMkqCFKDpT/52tNbRej8l27m91v3qZNvvEdHh67ebwrwgEABZJJuxmMsaQDQgMptdVhXrdnBaCsBUirl5dUApo5QyG7O9AihXPu0NpU25Qsj0LhFW6O2mMC+/EFgQuVBWNN1uZS1KJEIjSrD2Ig50WkONQH5KEjkFP4+xdLPmaHpbXktENDFzovmVEfj9Z0RCMrh3a4YLgFpypUrXUD6pKWZzE+NHeSCkSC/BT9JLAwaDy4DqkaiTksqrZB++mfg8yAh0Xs5sEktkajUFFk02UmxUB1D2BLXCb50nPvi+Xn4MJoWmMyIUi+ZE81R7lAloUSOTk6wV0Eb4Nb3lNx2GqZNOpY+xJfKjEtKxeldErKrSKrm5tZcL0pmA098ItCDKOQ9RVRjWIEMrzonNVwdzNf0V0bYGYb0lEdVFUtwm+9vvRsP8OiKR35YTEiO8lMhIVjE0WOWFBZyIjMEYpQAqnxElSPSg176fZdv8MWVoSbQqBXPCpnISLIoFAFDEKKw0nRMRziMGbFs70xPtUkz950zItzMZgZZEp1UWWSmAumu3+DSLkkwAVbM3vyZKhawA0o0EiI8jEetALogebJGXPwmQvz4tiCblOq5B2DGw3WCuiVI6f0QkAEoB3c4M3B2OBp/zelYjs39MhEhncvZASyJTkF1MARdla5nsktzvHUboZYn8YAQomd29JMY2T9wAB6qwfqE7xFXwiMgjPNr9xx2vZGfnIPlIMux30QZmmXok8mOIxY3fvYH0K0ZTpiRcKXzy18jOtXgT93Z1bba44iQZiWrmNYbYcNbiGbd7Td+4sZDraN12bFInsieJjhUImnLksTIb3tR+FOPMjLwuAz2J1sOZxh947Zua+7PrXTRplz39GzIIM50YZ/sP2W7GJRzqfIz+/E9JoBDII3H36qWeIHpe+5lh/tf/IL8QvSP9A09AaaYm3f3LAAAAAElFTkSuQmCC"/>
注意,如果图片体积较大,就不建议采用Base64编码了,因为图片数据量太大了不容易放在网页里。
##Sass##
Sass是一个与Less很像的CSS预编译工具,需要运行在Ruby环境下。详见Sass官方网站
##学习经验##
###断点调试和抓包工具的结合使用###
在做前端开发的时候,尤其是阅读别人写的源代码的时候,可以结合断点调试和抓包工具一起使用。
- 断点调试(在断点处写
debugger;
)可以追踪JavaScript方法的调用情况,跟踪变量等等。 - 抓包工具可以查看访问一个页面时都开启了哪些网络请求,以及每个网络请求的详细信息。抓包工具相当于一个加强版的Chrome Network控制台。
###JS中DOM操作的性能优化###
使用JavaScript进行DOM元素的修改等操作是非常消耗性能的,因为在修改DOM节点之后,浏览器都需要进行一次页面结构的重新布局(reflow)和页面元素的重新描绘(repaint),这两个过程需要耗费大量资源(事实上,reflow的代价要比repaint大很多:repaint只需要重新描绘元素,而reflow需要重新计算元素的布局,这会影响到子元素的布局,甚至父元素的布局)。因此在JS代码中应尽量减少DOM操作。一般有以下几个优化方法:
- 集中修改样式。例如要修改一个ul元素下的所有li元素的样式,不要在每一次for循环中都操作一次DOM,而是可以将修改后的元素放到一个临时变量里,for循环结束后通过这个临时变量进行一次DOM操作,相当于这一次DOM操作就批量地修改了多个元素的样式。
- 尽量通过修改元素的class值来修改样式。如果需要对一个DOM元素修改多个样式,最好直接修改该元素的class名称(在CSS中指定这个class的具体样式),这样的话一次DOM操作就可以统一修改多个样式了。
- 在修改DOM元素之前先将其隐藏(设为
display:none
),这样该元素的修改就不会触发浏览器的reflow(因为该元素是隐藏的,它的改变不需要引起页面结构的重新布局)。修改完成以后再将该元素重新显示。 - 将要修改的DOM元素设为
position:absolute/fixed
,这样的话该元素就从普通文档流中脱离,该元素发生改变的话,浏览器只需要repaint该元素即可,而对普通文档流没有任何影响,因此无需reflow。