Skip to content

Latest commit

 

History

History
179 lines (127 loc) · 13.2 KB

20140722.md

File metadata and controls

179 lines (127 loc) · 13.2 KB

#阿里前端学习笔记(三)#

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。