简述建设iis网站的基本过程6,安全的网站建设,手机网站开发实例,网站培训机构有哪些本文由尚妆前端开发工程师欲休撰写 本文发表于尚妆博客#xff0c;欢迎订阅#xff01; 移动端开发在某些场景中有着特殊需求#xff0c;如为了提高用户体验和加快响应速度#xff0c;常常在部分工程采用SPA架构。传统的单页应用基于url的hash值进行路由#xff0c;这种实…  本文由尚妆前端开发工程师欲休撰写 本文发表于尚妆博客欢迎订阅  移动端开发在某些场景中有着特殊需求如为了提高用户体验和加快响应速度常常在部分工程采用SPA架构。传统的单页应用基于url的hash值进行路由这种实现不存在兼容性问题但是缺点也有--针对不支持onhashchange属性的IE6-7需要设置定时器不断检查hash值改变性能上并不是很友好。 而如今在移动端开发中HTML5规范给我们提供了一个History接口使用该接口可以自由操纵历史记录。本文并不详细介绍History接口而是探究History接口如何影响浏览器历史堆栈并且利用这个规律应用到具体的实际业务中提出两种历史记录保存策略使路由逻辑更清晰让SPA更容易。 History API回顾 HTML5 History API包括2个方法history.pushState()和history.replaceState()和1个事件window.onpopstate。 pushState history.pushState(stateObject, title, url)包括三个参数。 第一个参数用于存储该url对应的状态对象该对象可在onpopstate事件中获取也可在history对象中获取。 第二个参数是标题目前浏览器并未实现。 第三个参数则是设定的url。一般设置为相对路径如果设置为绝对路径时需要保证同源。 pushState函数向浏览器的历史堆栈压入一个url为设定值的记录并改变历史堆栈的当前指针至栈顶。 在这里笔者使用历史堆栈和当前指针用以说明浏览器对历史记录的管理策略。文档中并没有使用这样的词汇笔者为了更形象的介绍接口对浏览器历史记录的影响使用这样的描述如有不当之处请及时指出不过目前以这套模型为基础的逻辑实现中并未出现悖论。 replaceState 该接口与pushState参数相同含义也相同。唯一的区别在于replaceState是替换浏览器历史堆栈的当前历史记录为设定的url。需要注意的是replaceState不会改动浏览器历史堆栈的当前指针。 onpopstate 该事件是window的属性。该事件会在调用浏览器的前进、后退以及执行history.forward、history.back、和history.go触发因为这些操作有一个共性即修改了历史堆栈的当前指针。在不改变document的前提下一旦当前指针改变则会触发onpopstate事件。 History API与业务实践 最常见的单页应用场景列表页、商品详情页以及其内部的其他链接入口如图片页、评论页及其推荐其他商品详情页。以上提到的已经涉及到了4个单独业务逻辑页面推荐的商品可复用商品详情页逻辑分别是列表、详情、图片详情和评论。将这4个页面合并到一个页面中这就是最简单的SPA。为了用户的良好体验必须设计合理的交互逻辑最直观的就是浏览器或手机app、微信公众号的后退和前进必须合乎业务逻辑特点。因此这就涉及到了History API的使用也牵扯到浏览器的历史记录管理。  上图为具体的逻辑示意图。在列表页点击其中一个商品这里是商品1进入详情页。详情页包括了该商品的轮播图、商品的图片详情入口、评论入口和推荐的其他商品入口。接下来进行如下操作进入图片详情页后退至详情页再进入评论页后退至商品1详情页再由推荐商品入口进入商品9详情页同样在商品9详情页进入图片详情页和评论页再后退至商品9详情页由推荐商品入口进入商品34详情页再进行类似操作。最后保证在商品34图片详情页或评论页可以顺利后退至最初的商品列表页。 上文中加粗的“后退”意味着使用浏览器后退按钮或者使用手机自带的返回再或者使用页面上提供的后退按钮。 这样一个很细小的需求但是一旦真正放手去做却不是那么容易。仅仅根据History API的2个函数和1个事件去盲目的尝试实现这属于盲人摸象鲁棒性不高。不清楚浏览器的历史记录管理策略不了解当前页面的历史记录数量此种情况若要实现上述场景就有些麻烦。所以在具体动手写业务代码之前需要搞懂History的pushState和replaceState具体如何影响历史记录栈。 探究浏览器历史记录策略与History API的关系 由于浏览器并未针对每个页面的历史记录提供具体访问的接口因此所有的测试都是黑盒。但是在移动端的中大都是webkit内核其webcore的具体实现也都相近因此该节得出的结论完全可以在移动端使用。 尽管无法访问当前页的历史记录栈但是浏览器却提供了history.length属性它标明了当前历史记录栈的个数。该值会帮助我们更好地分析History API对历史记录栈的影响。 上图为测试实例。其中白色箭头意味着点击该链接并执行pushState操作即操作1黑色箭头则执行浏览器后退红色的圆点为历史记录栈中的当前指针而每个项则为历史记录栈历史记录的个数则为其子项的数量。 初始在第一个搜索列表页执行操作1后历史堆栈数量增加当前指针上移一位至26788.html同理在执行3次操作1历史堆栈递增3个当前指针仍在栈顶即78099.html此后进行浏览器后退历史堆栈数量不变当前指针下移一位至8819.html在此处再执行操作1栈顶元素改变当前指针移至栈顶历史堆栈数量不变继续执行操作1栈顶元素改变指针移至栈顶历史堆栈数量加一执行浏览器后退栈顶元素不变指针下移一位至8128.html历史堆栈数量不变执行浏览器后退栈顶元素不变指针下移一位至8819.html历史堆栈数量不变执行浏览器后退栈顶元素不变指针下移一位至8128.html历史堆栈数量不变执行浏览器后退栈顶元素不变指针下移一位至26788.html历史堆栈数量不变执行操作1栈顶元素变为9721.html指针上移至栈顶历史堆栈数量变为3执行操作1栈顶元素变为8387.html指针上移至栈顶历史堆栈数量变为4执行浏览器后退栈顶元素不变指针下移一位至9721.html历史堆栈数量不变执行浏览器后退栈顶元素不变指针下移一位至26788.html历史堆栈数量不变执行浏览器后退栈顶元素不变指针下移一位至search.html历史堆栈数量不变执行操作1栈顶元素变为xxx.html指针上移至栈顶历史堆栈数量变为2... 至此实验结束。虽然这里仅仅列出了这一个测试用例但是其实笔者做了更多更复杂的测试并且平台涉及了pc和移动端的浏览器、微信和原生webview结果都一样。这一系列测试说明了很多问题总结之一句话则是 **浏览器针对每个页面维护一个History栈。执行pushState函数可压入设定的url至栈顶同时修改当前指针当执行back操作时history栈大小并不会改变history.length不变仅仅移动当前指针的位置若当前指针在history栈的中间位置非栈顶此时执行pushState会改变history栈的大小。总结pushState的规律可发现当前指针在history栈顶部时执行pushState会增加history栈大小若current指针不在栈顶则会在当前指针所在位置添加项。执行back操作并不修改history栈大小因此可以通过back和forward在当前大小的history栈中自由移动。** 掌握这个规律就知道如何维护历史记录就知道在什么状态下需要pushState。回到最初的需求产品经理规定从商品34的评论页按后退按钮可以到达最初的列表页但是他并没有详细规定如何后退。在这里就会有2中实现方式 每一次后退会回到上次的访问地方。如在商品34的评论页会后退至商品34的详情页再后退则会回到商品9的详情页直至回到列表页。总共维护三层历史记录第一层栈底为列表页第二层为详情页第三层栈顶为评论页或图片详情页。在该种实现下由商品34的评论页第一次后退至商品34的详情页第二次后退至列表页。针对第一种其实实现最为简单因为这完全是由浏览器默认控制历史记录堆栈而我们只需在合适的时机调用pushState将url插入到堆栈然后在onpopstate处理函数中监听对应的时间即可 window.addEventListener(popstate, function (e) {console.log(popstate)// 后退前进至商品详情页异步加载数据并渲染if(e.state  e.state.indexOf(/shop/sku/) ! -1){ajaxDetail(e.state,true);}else// 后退前进至评论页异步加载数据渲染if(e.state  e.state.indexOf(/shop/comment/commentList.html) ! -1){ajaxComment(e.state,true);}else// 后退前进至图片详情页异步加载数据渲染if(e.state  e.state.indexOf(/shop/item/pictext/) ! -1){ajaxPic(e.state,true);}else// 后退前进至列表页隐藏浮层if(e.state  e.state.indexOf(/search/) ! -1){// 隐藏spa的浮层$(.spa-container).css(zIndex,-1);}}); 针对第二种实现则是本文的重点。毕竟由浏览器默认维护的历史堆栈在某些业务场景中并不匹配因此需要开发者自己维护一个历史记录栈。在本次实现中由于总共涉及4张页面的显示因此我们设定了3层历史堆栈这很好理解。 为了构建这样的历史记录栈在主页面即列表页中需要额外添加两条历史记录。这是由于默认打开列表页时当前页面的url已加入历史记录栈中 function push(state){history.pushState(state, null, location.pathname  location.search);}// abc用于标示初始列表页history.replaceState(abc,null,location.pathname  location.search)// 压入两条历史记录push();push(); 这样打开列表页后就会创建3个历史记录并且这3个历史记录的url都为列表页的url这与后面的操作并无影响。 在列表页中打开详情页需要做额外的处理。由于按照我们设计的历史记录栈第二层应该为详情页而此时在初始化后历史记录栈的当前指针已指向栈顶元素因此需要将当前指针下移一位。这里就需要history.back来完成。 $(.item-list).on(click,a,handler);// 异步加载详情数据
var handler  function(e,isScrollXClick){var a  this;ajaxDetail($(a).attr(href),isScrollXClick);return false;
};var isScrollXClick;/*** params: url 请求路径 isScrollXClick: 是否点击推荐商品**/var ajaxDetail  function(url,isScrollXClick){$.ajax({url: /api  url,success: function(data){......if(!isScrollXClick){console.log(I am back!)// 在代码中进行back or forward并不会立即出发popstate事件以v8引擎为例在执行back之后// 的大概18us之后会触发事件而此时如果立即通过replaceState修改url则会造成失败修改的是// history stack栈顶的url.// 这里通过异步执行replaceState兼容history.back();}// 异步触发setTimeout(function(){history.replaceState(url, null, url);})// 针对推荐栏的商品循环绑定事件此处用事件代理优化$(#J_PDSlider).on(click,a,function(e){isScrollXClick  1;handler.call(this,e,isScrollXClick);return false;});},error: function(xhr, type){alert(Ajax error!)}})}; 在此处实现通过isScrollXClick变量判断是否点击的是推荐商品如果不是则需要执行back操作下移指针。此时指针是指在第二层但是浏览器和第二层历史记录的url仍为初始化设定的url因此需要修改在这里异步修改当前url。 之所以异步执行replaceState是由于webkit触发popState事件决定的。在代码中执行history.back 或者history.forward并不会立即返回也不会立即触发popState事件。由于没有阅读webkit的源码因此无从推测执行back或者forward后具体需要额外做什么操作它们之间有着10us级别的间隔因此此处必须使用setTimeout实现异步改变url。 在具体开发过程中这个问题困扰着笔者好几天终于在一次调试过程中发现浏览器url的变动才联想到可能是由事件触发的时间差导致。 对于图片详情和评论的逻辑处理则和上文类似无需多言。 最后一次后退需要回到列表页而在初始化阶段我们给列表页设置了state为“abc”特殊的标示该路由因此在popState事件处理中我们就可以根据该项回到初始页 window.addEventListener(popstate, function (e) {if(e.state  e.state.indexOf(/shop/sku/) ! -1){ajaxDetail(e.state,true);}else if(e.state  e.state.indexOf(abc) ! -1){// 隐藏spa的浮层$(.spa-container).css(zIndex,-1);push();push();}}); 如果回到初始页隐藏浮层同时在执行2次push操作。根据上节发现的规律在初始页执行2次push操作会在当前指针位置重新添加2个历史记录当前指针指向栈顶元素历史记录栈的数量不变仍为3。这样就完成了简单的由开发者自定义维护历史堆栈的spa系统。 回顾 之所以会写这篇文章完全是出于偶然由于实际项目的各种需求我们不应该仅仅将眼光停留在使用API的层面上。另外在开发过程中遇到难以解决的问题需要提出各种合理的设想并用详实的实验证明在得到相对应的结论后需要利用该结论去例证其他场景这样才能确保解决方案的可靠性。目前网络上或者书籍中并未提供任何手动维护历史记录堆栈的方法也未明确指出History API与浏览器历史记录之间如何影响因此本文对于旨在利用History API实现spa的开发者而言还是有些指导意义的。