http://www.web008.net

详细解剖大型H5单页面应用的核心技术点

前端的核心问题:体验与性能

用户体验与高性能是一个矛盾体,就好像软件的可维护性与高性能一样,为了追求易维护、易扩展或多或少会牺牲更多的性能为代价,当然这个只是相对而言 。回到主题,因为是跨平台,需要更多的考虑移动端的性能支持,这里不仅仅只某个功能,或某个效果或者动画,最大的问题就是“当量变堆积积累到一定程度总会产生质变”,这就是所谓的“量变产生质变”,移动设备分配给浏览器的资源总是有限的。举个例子:我们有制作一个2500页面的app应用,大概有几百兆的体积,这个不算夸张,如果是做epub甚至会出现1G以上的数据包,我们可以分解下会产生的问题

54.JavaScript 原型,原型链 ? 有什么特点?

  1. 原型对象也是普通的对象,是对象一个自带隐式的proto 属性,原型也有可能有自己的原型,如果一个原型对象的原型不为 null 的话,我们就称之为原型链
  2. 原型链是由一些用来继承和共享属性的对象组成的(有限的)对象链

  3. 怎么重构页面?

  4. 编写 CSS

  5. 让页面结构更合理化,提升用户体验
  6. 实现良好的页面效果和提升性能

页面的数据查询问题

在翻页一块强调了数据问题,那么数据会有什么问题?首先数据存储是用的sqlite存在本地的,不像传统的web页面,通过ajax获取服务器数据。当然如果是纯PC的情况下又不一样,这里讨论是移动端APP版本。html5引入Web SQL Database概念,所以前端也可以直接操作数据库了,是不是很6?完全跳出了服务端的挟持,前端开发者直接操作数据的CURD。

通过读取数据去是创建一个场景内容,但是这个数据速度的快慢是直接影响到用户体验的要素之一。一个页面涉及N多数据的的查询,可能关联很多表,几十上百条记录,如何优化?

数据查询方式
1:sql数据
拼sql语句是不行的,你可以试试一条SQL语句耗费的时间是多少?基本上1条语句就是100毫秒了,安卓下面实测
现在一个页面就可能存在几百条数据的关联,那么直接通过语句查询是行不通的

2:缓存哈希
把所有数据一次性读取出来,存在内存中,通过空间换时间,每次找内存中的缓存即可了。但是忽略一个问题,浏览器分配给每一个应用的内存是有限的,所以这样缓存的表数据一多,内存会溢出,应用直接崩

3: 缓存数据集
建立数据库的引用,缓存数据集SQLResultSetRowList了,可以直接result.rows.item(0) 通过索引下表找到对应的数据,这样只需要算出数据库中每一个id对应的下标索引就可以大大加快查询数据了。简单来说就是把查询的ID转化成数据库每条数据对应的索引数映射(从0开始),可以直接拿到这条数据,虽然前期的转化过程复杂,但是结果是美好的,数据问题也完美解决了。

优点:

网络标准统一、HTML5本身是由W3C推荐出来的。
多设备、跨平台
即时更新
提高可用性和改进用户的友好体验
有几个新的标签,有助于开发人员定义重要的内容
可以给站点带来更多的多媒体元素(音频和视频)
可以很好的代替Flash和Silverlight
被大量应用于移动应用程序和游戏

开发环境

  1. 基于ES6规范编写,加入了flow静态检测环境
  2. 开发调试基于webpack2,上线发布基于rollup+gulp
  3. 编写了全套基于node的build,开发、调试、压缩、发布

46.ie 各版本和 chrome 可以并行下载多少个资源

  1. IE6 2 个并发
  2. iE7 升级之后的 6个并发,之后版本也是 6 个
  3. Firefox,chrome也是6个

核心特性

  1. 单页面结构设计,采用ES6编写,加入了eslint检测与flow静态规则
  2. 采用面向对象设计,继承、封装、多态
    3. 设计模式,包含单例、工厂、策略、代理、迭代器、观察者、命令、中介、适配、装饰等等
    3. 管理上引入了场景的概念,按场景与容器分层,层层细分管理职责,尽量做到了高内聚低耦合
  3. 针对不同的设备不同的平台,提供了多种功能自动适配的方案,e.g. 显示区、图片尺寸、视频、音频、事件、动画等等
    5. 项目几乎大部分运用了目前前端所能接触的到一些H5、CSS3、JS所有发布的新的特性(webgl动画也有实现,性能问题暂未直接使用)

48. Flash、Ajax各自的优缺点,在使用中如何取舍?

Flash:

  1. Flash适合处理多媒体、矢量图形、访问机器
  2. 对CSS、处理文本上不足,不容易被搜索

Ajax:

  1. Ajax对CSS、文本支持很好,支持搜索
  2. 多媒体、矢量图形、机器访问不足

共同点:

  1. 与服务器的无刷新传递消息
  2. 可以检测用户离线和在线状态
  3. 操作DOM

  4. 请解释一下 JavaScript 的同源策略。

概念:
同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。它最早出自NetscapeNavigator2.0,其目的是防止某个文档或脚本从多个不同源装载。
这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议,指一段脚本只能读取来自同一来源的窗口和文档的属性。

为什么要有同源限制:
我们举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。

页面的管理与优化

面向对象设计一直是鼓励将行为分布到各个对象中去,把对象再划分更细的粒度,整个代码设计都会默认遵循这一点。场景页的切换,会将每个页面的滑动行为与坐标处理等,分解到每一个独立的页面中去,赋予每个场景页有独立的处理能力,如果让传统的父容器控制翻页的逻辑变化,那么父容器就需要控制3个页面的变化逻辑了,这里还包括了翻页、滑动、反弹等行为,这样代码耦合度与复杂度就高了(考虑下如果每个场景页的尺寸是不规则的?)。不管是场景页切换,还是场景内部管理,原则都是将行为分布在每个对象内部,让这些对象自己负责个自己的行为,这也正是面向对象设计的优点之处

善用设计模式

颗粒度的划分,可粗可细,这个根据自己设计,在xut.js项目中,可以把场景页看做一个对象,也可以把场景页内部的每个任务看做一个对象,甚至每个单独的DOM元素。在代码设计上很忌讳对象与对象直接关联,这样会产生对象之间的强耦合,但是实际上,每个对象与对象之间都可能产生错综复杂的关系,解耦的方式也有很多,比如通过观察,通过中介等等,之前强制加了下redux的思路,我只能说不是我的菜,这种单数据流的思路导致整个结构都改变了。OMG!

中介模式与观察者模式

页为独立单位,多个场景页之间的通讯会通过一个中介层,这里我称之为”page”管理层,其实上最复杂的组合情况下,会有4个管理层,一个page,一个master,一个浮动mater,一个浮动page,每个层都可以包含多组”场景页”,多个层之间可以做整体视觉差效果,可以做共享多页面等等….,多个管理层之间也会涉及交互的问题,如果对象与对象、页面与页面直接产生一对一或多对多的关系那么就直接产生了强耦合,久而久之会形成一种网状的交叉引用,当修改其中一个任意对象时,会难以避免引起其他的问题,所以在对象与对象之间交互通讯应该要增加一个中介对象,相关对象都通过中介对象来通讯,而不是互相引用

图片 1

如图,page层与master分别各自控制了3个场景页面组,2个层继续向上衍生中介层,层与中介之间可以通过发布订阅的方式再一次解耦,可以将page层作为为发布者,中介层作为订阅者,当page层发生改变,那么就会通知中介对象,由中介对象通知master层,引入中介后网状式的多对多的关系变成相对简单的一对多关系,如果增加新的的层级,只需要增加中介层对应的通讯控制就行了。可能感觉会比较迷惑2个模式太相像,其实是有区别的,可以看一篇文章吧 中介与观察者模式有何不同?

模板方法与钩子方法

es6中直接支持OOP的继承语法,也就是extends,我非常喜欢用这个特性,当然大多时候extends可以被mix-in的方式替换。在整个代码设计中,同一个类型,都会有相同的行为与不同的行为,那么就可以利用继承实现”模板方法”。在多任务分配中,所有任务都会继承一个抽象父类,定义流程接口,例如:处理数据、处理结构、绘制页面等等,所有的子类都实现了父类的接口,父类可以对子类进行流行控制与探测算法的处理。这样如果我们要往页面增加新的任务的时候,就需要实现这些抽象接口就OK了,不需要管具体的探测与流程控制,如果不同的任务有流程上的变动差异也可以用“钩子”的方式去实现不同的变化,把子类实现种相同的部分挪到父类中,不同的分布具体执行各自任务部分留在子类实现。这样就很体现了“泛型”的是思想。钩子方法也是非常常见的一种手段,这个我在JQuery源码中已经有很多分解了,xut.js也是到处可见hook

命令模式

在动态加载中提出了5个状态段处理的问题,例如:用户翻页发送请求给场景页中的每个对象,比如想让对象执行 “运行”、”停止”、”复位”、”销毁”等动作。其实用户翻页跟具体的对象其实是完全没有关联的,我并不希望把翻页的请求与每一个对象的状态直接关联,所以我希望有一种非常松耦合的方式处理程序,消除用户翻页与具体对象之间的直接耦合关系,那么我们可以把用户的请求处理的具体操作封装成commond对象,也就是命令对象。那么我们可以在任意时候去调用这个方法,那么这个方法就会执行针对对象状态的独立控制。这样的好处非常明显了,如果要做外部接口控制,那么接口只需要操控这个命令commond方法就可以了,直接解开了调用者与接收者之间的耦合关系

享元模式

这个比较麻烦点,使用过但是后来又改了,这里可以提及下,同样的用任务为例,一个场景页,如果创建了100个音频任务,那么意味着就是构建了100个音频对象,那么100个音频对象内部,其实会有相同的共享属性存在,比如传入音频类的类型,用Flash、用插件Phonegap、或者用HTML5去播放这个音频文件,那么这个类型的的属性其实任何对象都存在的,再回头看看享元模式的条件,大量使用相似的对象,造成了内存消耗,对象大多数状态可以改变为外部状态,剥离后可以用较少的共享对象取代大量对象。可以把音频的 文件的url,界面的配置等文件,等剥离成外部状态,并创建一个管理器保存。此时在去创建音频对象的时候,其实只有3个对象了,分别对应了3个类型的,Flash、用插件Phonegap、或者用HTML5对象。调用时通过传递外部管理器到每个音频对象中去组合成一个完整的对象,这样100个音频对象,减少的最多也只会存在3个了。当然这个麻烦的就是要区分内部状态与外部状态,增加额外的外部状态管理器,而且对象如果消耗不大,优化并不明显。

装饰模式与OCP

iscroll是前端使用频率很高的一个插件,在项目融合iscroll的过程中,其实会有一个这样的问题:iscroll作用之一是让对象区域上下或者左右滚动,这个滚动是会跟页面滑动形成冲突的,所以我们一般需要在iscroll停止用户事件传递与默认的浏览器行为。那么这样会出现一个弊端,如果iscroll是作用上下区域滚动,用户在iscroll的区域中如果想左右翻页此事就无法响应(翻页是全局控制,但是iscroll已经屏蔽了默认事件传递,全局无法得到通知)。如果iscroll的区域非常大,那么用户在整个页面上的体验感受就会是页面进入卡死状态,无法翻页,非常糟糕。

要解决这个问题,可以在iscroll左右翻页的时候让其响应全局滑动即可,因为iscroll是一个第三方插件,我们不应该去修改其内部结构,而是通过增加代码的的方式处理。iscroll插件被暴露几个事件监听接口scroll,scrollEnd,其实就是对内部处理用户操作行为的一个反馈,我们可以在这几个接口中扩展自己的代码,比如在用户滑动了,会触发scroll事件,我们可以在这个事件中调用全局翻页提供的方法让全局可以滑动,这里由于功能单一,我就提供下源码的截图,去掉了一些注释,保留了处理的方法

图片 2

通过扩展了iscroll提供的几个接口,不改变iscroll自身的情况下,动态的给iscroll对象添加了新的行为,实现了滑动、反弹、翻页的用户响应,这就是简单的装饰模式的体现。在没有改变iscroll内部源码的前提下,通过扩展的一些额外的代码就实现了新的功能,这样其实是遵循了”开放-封闭”的原则

简单工厂模式

这个是实用性很强的模式之一,正好上图的iscroll用到了就提及下。针对iscroll,遵循了”开放-封闭”的原则做了新功能的扩展,但是其实并不是任何时候都需要处理滑动、反弹、翻页行为的,所以应该对这个创建的接口做再一次的封装,实现这个接口的类来决定实例化哪个类

export default function IScroll(node, options, delegate) { if(delegate && config.hasTrackCode('swipe')) { options.stopPropagation = true options.probeType = 2 const iscroll = new iScroll(node, options) iscroll.on('scroll', function(e) { }) iscroll.on('scrollEnd', function(e) { }) return iscroll } else { /*其余滑动*/ return new iScroll(node, options) } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function IScroll(node, options, delegate) {
  if(delegate && config.hasTrackCode('swipe')) {
    options.stopPropagation = true
    options.probeType = 2
    const iscroll = new iScroll(node, options)
    iscroll.on('scroll', function(e) {
    })
    iscroll.on('scrollEnd', function(e) {
    })
    return iscroll
  } else {
    /*其余滑动*/
    return new iScroll(node, options)
  }
}

外部引入IScroll这个接口,只要传递不同的参数,内部来决定实例化哪个类。

其余优化

通过上面的一些优化手段,目前已经能满足现有的应用翻页性能了,优化是体现在各个细节上的

1. 引入对象池,利用空间换时间,共享了场景页的的重复的数据,尽量减少重复处理

2. 实现了多套事件机制,一套是全局用户收集派发用户行为(比如页面控制),一套针对hammer.js适配后支持独立对象事件绑定,实现多事件叠加嵌套的优先级处理

3. 实现全局事件机制中类似jquery的on的向上层层刷选委托处理,可以向全局注册很多不同类型的处理。例如:默认用户可以在页面上任意一个对象上滑动,如果对象有独立的事件,独立事件>全局事件优先级

  1. 简单实现了类似sizzle的嵌套闭包,增加数据筛选的速度与重复利用效率

5. 引入了vue早期batcher刷新思路,没有做虚拟dom,因为合并的文档碎片一次绘制,性能不会差

项目是基于自己的理解与实际运用的结晶,其中简单列举一些模式在项目中的运行,至于其余什么单列、适配器、迭代、策略等模式就很常用,这里就不多提及了。模式理解因人而异,或许与其实的理论有一点偏差,有则改之无则加勉。有人会说,这是强加模式上去,这属于推模式和过渡设计,我就只能呵呵,开始的代码其实并不多复杂,而且随着需求的不断变化,代码就会越来越朝着”模式”的方向进化了,因为你会觉得这样改是很比较合理的。模式本来就是在面向对象设计中提炼出来的可复用的设计技巧。所以很多时候,我们写出了带了模式的代码,只是自己不觉得而已。不是为了模式而模式,是为了更好的维护,更好的复用。当快速开发完全任务交付代码之后,之后会用更多的时间去考虑程序的延展性、健壮性,通过提升代码的可维护度从而提升工作效率,长期下来,这个是利大于弊的。

模式也并非一成不变的,实际开发中,为了使用上的便利就会牺牲维护度,比如我们最常用的jQuery,jQuery中的大多数方法同一个接口方法都会承载非常多的职责,例如:css方法,不仅可以以多种方式获取元素的样式,同时也支持多种方式设置样式值,最直接的就是违反了SRP单一职责原则,但是对于使用者来说简化了API的复杂度,也简化了用户的使用。利于弊得与失总是在不断的衡量与取舍。

50.GET和POST的区别,何时使用POST?

GET:一般用于信息获取,使用URL传递参数,对所发送信息的数量也有限制,一般在2000个字符
POST:一般用于修改服务器上的资源,对所发送的信息没有限制

GET方式需要使用Request.QueryString 来取得变量的值
POST方式通过Request.Form 来获取变量的值
也就是说Get 是通过地址栏来传值,而 Post 是通过提交表单来传值。

在以下情况中,请使用 POST 请求:

  1. 无法使用缓存文件(更新服务器上的文件或数据库)
  2. 向服务器发送大量数据(POST 没有数据量限制)
  3. 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

  4. 哪些地方会出现css阻塞,哪些地方会出现js阻塞?

js 的阻塞特性:所有浏览器在下载 JS 的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等。直到 JS 下载、解析、执行完毕后才开始继续并行下载其他资源并呈现内容。为了提高用户体验,新一代浏览器都支持并行下载 JS,但是 JS 下载仍然会阻塞其它资源的下载(例如.图片,css文件等)。
由于浏览器为了防止出现 JS 修改 DOM 树,需要重新构建DOM 树的情况,所以就会阻塞其他的下载和呈现。
嵌入 JS 会阻塞所有内容的呈现,而外部 JS 只会阻塞其后内容的显示,2 种方式都会阻塞其后资源的下载。也就是说外部样式不会阻塞外部脚本的加载,但会阻塞外部脚本的执行。

CSS 怎么会阻塞加载了?CSS 本来是可以并行下载的,在什么情况下会出现阻塞加载了(在测试观察中,IE6 下 CSS 都是阻塞加载)
当 CSS 后面跟着嵌入的 JS 的时候,该 CSS 就会出现阻塞后面资源下载的情况。而当把嵌入 JS 放到 CSS 前面,就不会出现阻塞的情况了。
根本原因:因为浏览器会维持 html 中 css 和 js 的顺序,样式表必须在嵌入的 JS 执行前先加载、解析完。而嵌入的 JS 会阻塞后面的资源加载,所以就会出现上面 CSS 阻塞下载的情况。

详细解剖大型H5单页面应用的核心技术点

2017/05/05 · CSS, HTML5, JavaScript · 单页

原文出处: 艾伦 Aaron   

阐述下项目 Xut.js 开发中一个比较核心的优化技术点,这是一套平台代码,并非某一个插件功能或者框架可以直接拿来使用,核心代码大概是6万行左右(不包含任何插件) 。这也并非一个开源项目,不能商业使用,只是为了作者开发方便同步修改代码而上传的源码

描述下,项目提出的概念“无需程序员编程”可批量制作app应用。分2大块,1块是客户端(PPT),默认扩展插件提供用户编辑的界面,平台会把设计逻辑与界面数据编译成前端数据资源包(前端能处理的js、css、图片等资源了),另一个大块就是纯前端部分(Xut.js),简单来说:前端通过读取数据包资源后,翻译出用户设计出的交互行为与可视效果。可以这样想象,苹果iTunes是一个平台,里面有超多的交互应用类型的app,每个app都是程序员开发的,现在每个app都可以通过我们这套平台生成了,但是实际上精细度与性能当然无法跟原生相比了,只能是尽可能的靠拢。在这种平台结构下前端的最大难点在于未知性,编辑数据的复杂度与数量是不可控的,可以想象下设计一个简单儿童类型的闯关游戏需要多少细节?项目介绍可以看我以前写过的一篇文章 Hybrid App技术批量制作APP应用与跨平台解决方案。

数据的未知性,会导致应用性能呈现反比例关系,当应用数据结构越复杂运行的实际性能越差。在这种设计下,一定会印证“墨菲定律”如果你担心某种情况发生,那么它就更有可能发生,在真机上开始大批量崩溃了。这篇文章我着重描述下项目前端方面“地基”的优化,好比建房,100层与200层的地基结构肯定是不一样的,只有地基建好了,房子才能建的更高。这里所涉及的问题以及角度只是个人观点与方案,篇幅有点长,有耐心可以看看。

上传了平台生成的一个简单的SPA单页面测试应用,简单看看 “猜谜语”

47.javascript里面的继承怎么实现,如何避免原型链上面的对象共享

用构造函数和原型链的混合模式去实现继承,避免对象共享可以参考经典的extend()函数,很多前端框架都有封装的,就是用一个空函数当做中间变量

在移动端app应用上,用户交互的行为一般就3种:滑动页面、点击跳转与组合形式

点击跳转:这个相对容易处理,而且方案也很多,页面为单位,可以单页面实现,通过路由支持,这样的框架很多了
滑动翻页:与点击跳转最大的不同点就是页面的“连续性”与“页面的无缝连接”
组合形式:点击跳转与滑动翻页的行为的组合方式

点击跳转看起来更像一个原生态的APP应用设计,但是我们是平台就需要对各种不同环境各种使用进行考量,重点分析下2500页面滑动翻页。

首先滑动翻页的“连续性”与“无缝连接”就让我只能选择单页面的设计实现(这里我们必须抛开所有原生的支持情况,因为我是前端)。由于博客园上传受限,简单录了一张gif效果图 动态+多任务超快翻页

53.Node.js 的适用场景

  1. 高并发
  2. 聊天
  3. 实时消息推送

郑重声明:本文版权归美高梅163888所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。