SegmentFault go for front end最新的文章
2018-04-13T10:56:38+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
淘宝新势力周H5性能优化实战
https://segmentfault.com/a/1190000014359615
2018-04-13T10:56:38+08:00
2018-04-13T10:56:38+08:00
kukuv2
https://segmentfault.com/u/kukuv2
37
<h2>前言</h2>
<p>淘宝新势力周(春上新)是命运石kimi链路(H5链路)第一次承接S级大促,面对S级大促内容丰富且复杂的页面需求,kimi链路遇到了一些性能问题,在未进行性能优化之前,搭建出来的页面,业务方普遍反馈页面卡顿严重,无法滑动。</p>
<p>因为时髦女装会场是反馈比较严重的页面之一,所以我以时髦女装会场为例子,介绍下这次性能优化的具体方案。时髦女装会场页面模块在18个左右,页面上的img标签数量在200左右,页面总长度 31208px,以iPhone6页面一屏736px为标准,总共能分为42.4屏左右。为什么我要特别把img标签写出来呢?因为这次的性能卡顿主要的原因是因为错误使用图片懒加载引起的。</p>
<h2>通过performance图排查性能问题</h2>
<p>现代的web性能优化离不开chrome devtool里performance的帮助,我们先来看一张未优化之前 performance的截图</p>
<p><img src="/img/remote/1460000014359618?w=1614&h=1462" alt="未优化前" title="未优化前"></p>
<p>这张performance图我们主要看三个部分:第一个是最上面FPS红线的部分,红线代表着这段时间内未达到60FPS每帧;第二部分是Frames的耗时,勾选了Screenshots后我们能看到每帧的耗时;第三部分是下面函数耗时,我们能从函数耗时里分析出来到底是哪段代码block住了页面渲染,导致卡帧。</p>
<p>从上面的图可以看到最长的一帧耗时3.37秒,这导致FPS都快接近0了。</p>
<p><img src="/img/remote/1460000014359619?w=1962&h=720" alt="" title=""></p>
<p>把函数耗时图拉大分析里面耗时最长的函数,可以看到耗时最长的函数是inview函数,这个函数是图片懒加载库里面检查当前图片是否在屏幕中间的函数。</p>
<p>图片懒加载库的基本逻辑是:当调用初始化函数时立即检查当前页面上所有未真正加载的图片,判断是否需要加载。当页面进行滑动时,重复检查所有图片的逻辑。</p>
<h2>这次性能问题的原因和解决方案</h2>
<p>卡顿掉帧的原因:这次搭建出来的页面使用的是外包同学开发的业务模块,在模块内部手动调用了lazyLoad初始函数,所以每初始化一个模块就会立即检查所有未加载图片,当页面上图片数量不断增长的时候,inview函数的耗时也不断增加,检查一个图片是否在页面的耗时是2ms~5ms,如果页面中有100个图片未加载当页面滑动时每一次检查会耗时200ms~500ms,如果检查是同步操作的话,掉帧几乎无法避免。</p>
<p>优化方案:之前的其他链路的优化方案是模块懒加载,然后lazyload统一调用,但是因为这次离上线时间较紧张,让外包返工改模块风险较大,于是有另外的一个优化方案:图片懒加载库的异步化,只要避免函数执行耗时过长阻塞渲染,就能避免卡帧,假设我们有100张图片,我们分多批次进行检查,避免一次检查所有图片阻塞渲染。另外针对模块初始化时频繁的检查所有图片的问题,我们给这段逻辑加上debounce函数和图片缓存队列。</p>
<h2>优化的过程</h2>
<h3>优化1.0:</h3>
<p>在我接手之前,有一版优化是将模块的渲染通过setTimeout函数改成异步的;这个优化是几乎没有效果的,优化后页面依然卡顿掉帧,因为这个优化并没有找到页面卡顿的原因。起码也应该将setTimeout改成RAF。当然模块的延时加载并不能解决卡顿问题,但是模块的懒加载能解决一部分问题。下面我们看一张使用模块懒加载后的performance图</p>
<p><img src="/img/remote/1460000014359620?w=1330&h=1406" alt="" title=""></p>
<p>模块懒加载后,一长条红色块已经变成了短条的红色块,但是因为模块内部单独使用图片懒加载导致频繁检查所有图片是否在可视范围内的问题还是没有得到解决,最长的一帧达到855ms,依然存在掉帧。</p>
<h3>优化2.0:</h3>
<p>图片懒加载异步版本:通过对图片懒加载库的改造,1、初始化时加上debound优化和图片缓存队列,2、分批检查图片。我们在看一下优化后的performance图</p>
<p><img src="/img/remote/1460000014359621?w=1560&h=1386" alt="" title=""></p>
<p>红色的条块也消失,看下面函数执行变的又长有尖,这是因为检查图片的操作变成异步分批了。</p>
<h3>图片懒加载库改造时遇到的问题:</h3>
<p>在将图片懒加载改造成异步的时候遇到了一个问题,就和Java多线程一样,很多时候异步我们也希望是有序的异步。</p>
<p>分批检查的有序是比较容易保证的,将图片分成多批,一批一批进行,再最后一批结束任务。但是问题出在分批检查和图片懒加载模块初始化存在交替运行的情况,而这两个任务都会改变一个变量。如果不能解决这个问题,就会出现图片有时候能正常加载,有时候加载不出来的情况。所以有说法是,大部分偶现的问题都是异步并行的问题。</p>
<p>解决这个问题的思路也比较常见,就是通过锁,当一个操作异步变量的任务开启,我们的锁自增1,完成异步任务时自减,图片懒加载库的图片缓存初始队列等到异步锁释放后再进行检查,否则存入缓存队列,等待下一帧再检查。</p>
<h2>总结</h2>
<p>优化过后,对应常见的机型基本能保证页面流畅不卡顿。chrome的performance图基本上和真机操作的情况保持一致,如果performance出现掉帧,那iPhone6s上和android上基本也会出现掉帧,但是iPhone7以上的机器却可能感受不明显。通过performance能够快速定位掉帧的问题,通过解决这些问题实质性的优化页面性能,而不是通过猜测进行无效优化。</p>
Vue的异构
https://segmentfault.com/a/1190000011134784
2017-09-12T17:22:50+08:00
2017-09-12T17:22:50+08:00
kukuv2
https://segmentfault.com/u/kukuv2
3
<h2>Vue的异构</h2>
<p>组件化是至上而下的,一旦一个页面从某个Dom节点开始逐渐话之后,相当于从这个Dom节点开始所有的子辈节点都被组件化框架所接管了,这是因为Vue或React这类组件化框架都有一个重要的特性:那就是 数据=》视图 的一一对应关系,所以在被组件化框架所接管的区域内,所以的Dom操作都应该有组件化框架来完成,如果中间有其他的Dom操作,比如jQuery修改Dom节点元素,会破坏 数据=》视图 这个一一对应关系。</p>
<p>但是事实上,我们有大量的网页系统和框架并不是基于组件化框架所搭建的,所以很多时候我们必须拿出异构的方案。这里的异构指的是 组件化框架和其他框架共存的情况。</p>
<p>我们主要介绍Vue异构方法,实际上Vue提供的computed、watch 、directive等对异构提供了很大的帮助。所以Vue的异构相对其他组件化框架来说更加容易理解和操作。</p>
<p>组件化异构的核心思想就是在不破坏 数据=》视图 的前提下,将非组件化的代码封装成类组件化的代码。</p>
<h3>不属于异构的情况</h3>
<p>在列举Vue的异构方法之前,我们要分清楚什么情况才称之为异构,并不是引入了任意js库就叫异构。只有引入其他js库,并且我们使用该库去操作Vue所接管的Dom节点时才称之为异构。</p>
<h4>一个不是异构的例子</h4>
<p>我们从github上下载Vue的源代码,里面有一些实用的例子在example文件夹里,其中有一个《elastic-header》的例子,我们能看到这个例子里引用了dynamic.js这个js库来实现平滑的动画效果。那这里面算不算异构能,需不需要我们采取什么特殊处理呢?会不会破坏 数据=》视图 的映射关系呢?其实是不会的!<br><img src="https://sfault-image.b0.upaiyun.com/265/470/2654701985-59b7a69b9aefc_articlex" alt="Alt text" title="Alt text"><br>我们看这里面使用dynamics.js的方法,dynamics被当成一个工具库来使用,并没有直接操作Dom,而是改变Vue里面对象的数值,最终是由Vue去操作Dom,所以并不会破坏 数据=》视图 的映射关系,所以我们并不需要担心引入这类的库与Vue混用会产生问题。</p>
<p>下面我们对Vue的异构方法做一些分类,不同的异构需求关注的重心也不太一样。</p>
<h3>通过封装成Vue组件的方式实现异构</h3>
<p>在Vue的example库里就有一个Vue异构的例子,在example/select2这个目录下。这个例子是Vue与jQuery的插件select2进行异构的例子 ,并且是通过将select2封装成Vue组件的方式实现异构的。</p>
<pre><code class="htmlbars"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue.js wrapper component example (jquery plugin: select2)</title>
<!-- Delete ".min" for console warnings in development -->
<script src="../../dist/vue.min.js"></script>
<script src="https://unpkg.com/jquery"></script>
<script src="https://unpkg.com/select2@4.0.3"></script>
<link href="https://unpkg.com/select2@4.0.3/dist/css/select2.min.css" rel="stylesheet">
<style>
html, body {
font: 13px/18px sans-serif;
}
select {
min-width: 300px;
}
</style>
</head>
<body>
<div id="el">
</div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<p>Selected: {{ selected }}</p>
<select2 :options="options" v-model="selected">
<option disabled value="0">Select one</option>
</select2>
</div>
</script>
<script type="text/x-template" id="select2-template">
<select>
<slot></slot>
</select>
</script>
<script>
Vue.component('select2', {
props: ['options', 'value'],
template: '#select2-template',
mounted: function () {
var vm = this
$(this.$el)
.val(this.value)
// init select2
.select2({ data: this.options })
// emit event on change.
.on('change', function () {
vm.$emit('input', this.value)
})
},
watch: {
value: function (value) {
// update value
$(this.$el).val(value).trigger('change')
},
options: function (options) {
// update options
$(this.$el).select2({ data: options })
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
})
var vm = new Vue({
el: '#el',
template: '#demo-template',
data: {
selected: 1,
options: [
{ id: 1, text: 'Hello' },
{ id: 2, text: 'World' }
]
}
})
</script>
</body>
</html></code></pre>
<p>这里面最关键的代码就是 组件的mounted方法和watch方法,异构最关键的事情有三件</p>
<ul>
<li>Vue要初始化其他库(组件化异构的时候一般在mounted方法里,因为这个时候能拿到Dom元素)</li>
<li>当Vue数据发生变化的时候要调用其他库的方法更新页面</li>
<li>当其他库的数据方法发生变化的时候要对Vue中的数据重新赋值,保证 数据=》视图这个对应关系不变</li>
</ul>
<pre><code class="javascript">$(this.$el)
.val(this.value)
// 在mounted方法里初始化 select2库
.select2({ data: this.options })
// 当其他库的数据方法发生变化的时候要对Vue中的数据重新赋值,保证 数据=》视图这个对应关系不变
.on('change', function () {
vm.$emit('input', this.value)
})</code></pre>
<pre><code class="javascript">watch: {
// 当Vue数据发生变化的时候要调用其他库的方法更新页面
value: function (value) {
// update value
$(this.$el).val(value).trigger('change')
},
options: function (options) {
// update options
$(this.$el).select2({ data: options })
}
},</code></pre>
<h3>通过directive的方式实现异构</h3>
<p>下面我们将官方提供的例子修改一下,改成由 directive的方式来实现异构。因为指令在Vue2里能力已经被极大的削弱,但也依然具备能够实现异构的能力</p>
<pre><code class="htmlbars"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue.js wrapper component example (jquery plugin: select2)</title>
<!-- Delete ".min" for console warnings in development -->
<script src="../../dist/vue.min.js"></script>
<script src="https://unpkg.com/jquery"></script>
<script src="https://unpkg.com/select2@4.0.3"></script>
<link href="https://unpkg.com/select2@4.0.3/dist/css/select2.min.css"
rel="stylesheet">
<style>
html, oubody {
font: 13px/18px sans-serif;
}
select {
min-width: 300px;
}
</style>
</head>
<body>
<div id="el"></div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template"
id="demo-template">
<div>
<p>Selected: {{ selected }}</p>
// 使用v-select指令
<select v-select="{options:options,onChange:onChange,value:selected}"></select>
</div>
</script>
<script>
Vue.directive('select', {
// 当绑定元素插入到 DOM 中。
bind: function (el, value) {
console.log('bind ' + arguments);
},
inserted: function (el, binding) {
var value = binding.value
// 聚焦元素
$(el)
.val(value.value)
// init select2
.select2({data: value.options})
// emit event on change.
.on('select2:select', function (evt) {
var id = evt.params.data.id
value.onChange(id)
})
console.log('inserted ' + arguments);
},
update: function (el, binding) {
var val = $(el).val()
var value = binding.value.value
if (val !== value) {
$(el).val(value).trigger('change')
}
},
componentUpdated: function () {
console.log('componentUpdated ' + arguments);
},
unbind: function () {
console.log('unbind ' + arguments);
}
})
var vm = new Vue({
el: '#el',
template: '#demo-template',
data: {
selected: 1,
options: [
{
id: 1,
text: 'Hello'
},
{
id: 2,
text: 'World'
}
]
},
methods: {
onChange: function (val) {
debugger;
this.selected = val
}
}
})
</script>
</body>
</html></code></pre>
<p>因为指令无法直接获取到vm,所以我们通过binding里传入onChange方法来更改组件的值。</p>
<p>这里面最关键的代码就是指令的inserted方法和update方法</p>
<ul>
<li>指令要初始化其他库(在inserted方法里,因为这时候能获取到对应的Dom节点)</li>
<li>当Vue数据发生变化的时候要调用其他库的方法更新页面 (在update方法里调用select2库的更新方法)</li>
<li>当其他库的数据方法发生变化的时候要对Vue中的数据重新赋值,保证 数据=》视图这个对应关系不变(注册事件监听,调用指令的binding.value值里的onChange方法)</li>
</ul>
<h3>循环嵌套Vue组件</h3>
<p>想象中很复杂的异构其实只要把数据到视图的逻辑都理清楚,其实实现起来非常容易。</p>
<p>我们在想象一个应用场景,如果这时候需要在select2这个jQuery插件库中再插入 Vue组件应该怎么处理呢?</p>
<p>首先在由其他库中生成的Dom节点中插入Vue组件,只能通过 new Vue()方法去重新初始化Vue组件。但是需要注意一点的是组件是一个可以多实例的概念,所以我们不能简单的给嵌套Vue组件一个Id,因为当多实例的情况下通过这个Id能取到不只一个Dom节点,所以我们最佳方式是通过Dom节点树向下寻找,直到找到具体拿来初始化的对应元素,可以通过 jQuery 的 find方法,也能通过生成 uuid的方式来生成唯一的Id,通过这些方式来保证组件在多实例的情况下运行起来也不会出现问题。</p>
Vue项目的自动化测试
https://segmentfault.com/a/1190000011062084
2017-09-07T17:33:33+08:00
2017-09-07T17:33:33+08:00
kukuv2
https://segmentfault.com/u/kukuv2
39
<h2>Vue项目的自动化测试</h2>
<p>说到自动化测试,许多开发团队都是听说过、尝试过,但最后都止步于尝试,不能将TDD(测试驱动开发)、BDD(行为驱动开发)的完整流程贯彻到项目中。思考其中的原因:终究还是成本抵不上收益。</p>
<p>很多后端开发人员可能写过很多自动化的单元测试代码,但是对前端测试一头雾水。这是因为相对于后端开发人员的自动化单元测试,前端的自动化测试成本更高。</p>
<p>自动化测试就是通过自动化脚本将一个又一个测试用例串起来,每个测试用例都要模拟环境、模拟输入、然后断言输出。前端自动化最难的地方就是模拟环境、模拟输入和断言输出了!<br>我们可以试想一下现实中的使用场景:</p>
<p>模拟环境:首先前端代码是跑在不同的终端环境上的,纯粹的使用某台机子的运行环境进行模拟是无法发现真正存在的问题。所以我们的测试用例必须跑在真实的环境下,这里面包括不同的机器:Android、ios、pc、macbook;不同的系统:window10、window8、linux、mac;不同的运行载体:IE、safari、chrome、firefox、Opera、Android webview、UIWebview、WKWebview;不同网络环境:WiFi、4G、3G、offline</p>
<p>模拟输入:前端的输入不好模拟,在PC上有鼠标click,double Click、drag、mouseDown、mouseOver、input等等,在mobile上有swipe、tap、scroll、摇一摇、屏幕翻转等。相对于后端的单元测试,前端的输入种类繁多,每一种模拟起来都十分复杂,而且很多bug隐藏在几种连贯的输入之后才会复现。</p>
<p>断言输出:前端的断言不是简单的判断值是否相等,很多情况是即使值相等、效果完全不一样。<br>很多展示效果更是不能通过简单的断言来检测,比如区域是否能滑动,输入时键盘是否正确弹起等。</p>
<p>当你跨越千山万水把上面的问题解决了,测试用例写好了,功能代码写好,完美!然后UE跑过来和你说那个这根线往左边移动一像素的时候,你会瞬间崩溃。可能这一像素你很多测试用例都得重写。所以前端自动化测试的成本真不一定抵得上收益。</p>
<p>但是困难不代表解决不了,部分场景不适合不代表所有场景都不适合!</p>
<p>正因为面临这么多的困难,我们的前端社区开发出了很多工具帮我们解决这些问题。本章节主要是结合Vue这个框架介绍前端自动化测试的一些工具和方法。</p>
<p>我们使用vue-cli去新建一个vue的新项目,在这个项目中开启默认的unit tests和e2e tests</p>
<pre><code class="bash">bogon:work xiaorenhui$ vue init webpack vueExample
? Project name vue-example
? Project description A Vue.js project
? Author kukuv <kukuv>
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? No
? Setup unit tests with Karma + Mocha? Yes
? Setup e2e tests with Nightwatch? Yes</code></pre>
<p>下面列举下这个新项目中涉及到的一些开源项目:</p>
<ul>
<li>
<p>karma</p>
<ul><li>Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner)。该工具可用于测试所有主流Web浏览器,也可集成到CI(Continuous integration)工具,也可和其他代码编辑器一起使用。这个测试工具的一个强大特性就是,它可以监控(Watch)文件的变化,然后自行执行,通过console.log显示测试结果。</li></ul>
</li>
<li>
<p>Mocha</p>
<ul><li>mocha是一款功能丰富的javascript单元测试框架,它既可以运行在nodejs环境中,也可以运行在浏览器环境中。</li></ul>
</li>
<li>
<p>Nightwatch</p>
<ul><li>Nightwatch是一套基于Node.js的测试框架,使用Selenium WebDriver API以将Web应用测试自动化。它提供了简单的语法,支持使用JavaScript和CSS选择器,来编写运行在Selenium服务器上的端到端测试。</li></ul>
</li>
<li>
<p>phantomjs</p>
<ul><li>一个基于webkit内核的无头浏览器,即没有UI界面,即它就是一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现。</li></ul>
</li>
<li>
<p>sinon-chai</p>
<ul><li>sinon-chai是 sinon和chai这两个断言库的结合,提供丰富的断言方法</li></ul>
</li>
</ul>
<p>很多人看到这么多新名词一定头晕,心想一个单元测试咋需要懂这么多东西。情况是上面只是单元测试框架的一小部分、还有许多框架没有列出来。正因为前端的自动化测试面临着许多问题,所以我们才有这么多的框架来帮忙解决问题。</p>
<h3>unit tests</h3>
<p>我们先来分析一下这个项目中的unit tests,这里面用到了 Karma、Mocha、sinon-chai、phantomjs。项目中已经有一个默认的单元测试例子。karma作为测试执行过程管理工具把Mocha、sinon-chai、phantomjs等框架组织起来。Mocha用来描述测试用例、sinon-chai用来断言、然后使用phamtomjs作为运行环境来跑测试用例。</p>
<p>npm install 将依赖的库都安装好,这里面phantomjs的依赖会比较难装,如果你之间没有安装过phantom,因为phantom比较大,而且加上国内的网络环境等原因。如果phantomjs装不上可以尝试使用chrome作为运行环境,这需要安装 "karma-chrome-launcher",需要修改配置文件。</p>
<p>然后 npm run unit 跑一下unit tests,如果提示权限问题就 使用sudo 来提升下权限。跑完后我们看一下目录结构</p>
<pre><code class="bash">└── unit
├── coverage 代码覆盖率报告,src下面的index.html可以直接用浏览器打开
│ ├── lcov-report
│ │ ├── base.css
│ │ ├── index.html
│ │ ├── prettify.css
│ │ ├── prettify.js
│ │ ├── sort-arrow-sprite.png
│ │ ├── sorter.js
│ │ └── src
│ │ ├── App.vue.html
│ │ ├── components
│ │ │ ├── Hello.vue.html
│ │ │ └── index.html
│ │ └── index.html
│ └── lcov.info
├── index.js 运行测试用例前先加载的文件,方便统计代码覆盖率
├── karma.conf.js karma的配置文件
└── specs 所有的测试用例都放在这里
└── Hello.spec.js</code></pre>
<pre><code class="javascript">// 加载所有的测试用例、 testsContext.keys().forEach(testsContext)这种写法是webpack中的加载目录下所有文件的写法
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)
// 加载所有代码文件,方便统计代码覆盖率
const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)</code></pre>
<pre><code class="javascript">config.set({
// 在几个环境里跑你的测试用例
// browsers: ['PhantomJS','Chrome'],
browsers: ['Chrome'],
// 默认加载几个框架
frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
// 使用那些汇报框架
reporters: ['spec', 'coverage'],
// 预加载文件
files: ['./index.js'],
// 预处理
preprocessors: {
'./index.js': ['webpack', 'sourcemap']
},
// webpack 配置
webpack: webpackConfig,
webpackMiddleware: {
noInfo: true
},
// coverage 配置
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
}
})</code></pre>
<p>上面使用的插件例如 mocha、spec、coverage除了karma默认自带的都需要你在npm<br>上安装对应的插件,例如以下</p>
<pre><code class="javascript"> "karma": "^1.4.1",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "^1.1.1",
"karma-mocha": "^1.3.0",
"karma-phantomjs-launcher": "^1.0.2",
"karma-phantomjs-shim": "^1.4.0",
"karma-sinon-chai": "^1.3.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.31",
"karma-webpack": "^2.0.2",</code></pre>
<pre><code class="bash">> vue-exampl@1.0.0 unit /Users/xiaorenhui/work/vueExample
> cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run
07 09 2017 12:08:13.004:INFO [karma]: Karma v1.7.0 server started at http://0.0.0.0:9876/
07 09 2017 12:08:13.007:INFO [launcher]: Launching browser Chrome with unlimited concurrency
07 09 2017 12:08:13.015:INFO [launcher]: Starting browser Chrome
07 09 2017 12:08:15.475:INFO [Chrome 60.0.3112 (Mac OS X 10.12.3)]: Connected on socket qDaxr51TuQCfQBcVAAAA with id 73077049
INFO LOG: 'Download the Vue Devtools extension for a better development experience:
https://github.com/vuejs/vue-devtools'
LOG LOG: 'data'
Hello.vue
✓ should render correct contents
Chrome 60.0.3112 (Mac OS X 10.12.3): Executed 1 of 1 SUCCESS (0.024 secs / 0.011 secs)
TOTAL: 1 SUCCESS
=============================== Coverage summary ===============================
Statements : 60% ( 3/5 )
Branches : 50% ( 1/2 )
Functions : 0% ( 0/1 )
Lines : 60% ( 3/5 )
================================================================================</code></pre>
<p>我修改了一下Hello.vue这个组件,可以看到coverage 里精确的显示了测试代码的覆盖率,下面是我做的修改</p>
<pre><code class="javascript">export default {
name: 'hello',
data () {
console.log('data');
function aa() {
}
if(false){
console.log('data aa');
}
return {
msg: 'Welcome to Your Vue.js App'
}
},
methods:{
aa(){
console.log('methods aa');
}
}
}</code></pre>
<p><img src="/img/bVUzUh?w=1423&h=187" alt="" title=""><br>打开reporter下面的index.html我们可以看到代码覆盖的具体情况。<br>点开Hello.vue更有直观的方式展示哪些代码被覆盖了,哪些没有。<br><img src="/img/bVUzUI?w=474&h=363" alt="图片描述" title="图片描述"></p>
<h3>e2e测试</h3>
<p>既然我们已经有了单元测试,那e2e测试有和单元测试有什么区别呢?Nightwatch是前端e2e测试的一个有代表性的框架。单元测试TDD的粒度很细,我们会为许多函数、方法去写单元测试,而e2e更接近BDD。直白点说,就是TDD的测试单元是一个个函数、方法,而BDD测试的单元是一个个预期的行为表现。e2e做的事情就是打开浏览器,并且真正的访问我们最终的页面,然后在这个真实的浏览器、真实的页面中我们去做各种断言,而单元测试不会要去我们去访问最终的页面,单元测试要保证的是一个个单元是没有问题的,但这些单元组合起来跑在页面上是否有问题,不是单元测试能够保证的,尤其是在前端这种模拟环境、模拟输入非常复杂的领域中,这是单元测试的短板,而e2e测试就是用来解决这些短板的。</p>
<p>我们来看看项目中使用Nightwatch来进行e2e测试的例子</p>
<p>首先看一下目录</p>
<pre><code class="bash">├── e2e
│ ├── custom-assertions
│ │ └── elementCount.js 自定义的断言方法
│ ├── nightwatch.conf.js nightwatch的配置文件
│ ├── reports
│ │ ├── CHROME_60.0.3112.101_Mac\ OS\ X_test.xml
│ │ └── CHROME_60.0.3112.113_Mac\ OS\ X_test.xml
│ ├── runner.js bootstrap文件,起我们的页面server和nightwatch文件
│ └── specs
│ └── test.js 测试用例
</code></pre>
<p><img src="/img/bVUzUN?w=799&h=336" alt="图片描述" title="图片描述"><br>selenium是一个用java写的e2e测试工具集,它的API被纳入 w3c的webDriver Api中, nightWatch是对selenium的一个nodejs封装。所有我们需要再配置文件中配置selenium。</p>
<pre><code class="javascript"> src_folders: ['test/e2e/specs'],
output_folder: 'test/e2e/reports',
custom_assertions_path: ['test/e2e/custom-assertions'],
// 对selenium的配置
selenium: {
start_process: true,
server_path: require('selenium-server').path,
host: '127.0.0.1',
port: 4444,
cli_args: {
'webdriver.chrome.driver': require('chromedriver').path
}
},
// 测试环境的配置
test_settings: {
default: {
selenium_port: 4444,
selenium_host: 'localhost',
silent: true,
globals: {
devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
}
},
chrome: {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true
}
},
firefox: {
desiredCapabilities: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true
}
}
}</code></pre>
<p>下面的runner需要先起一个我们的网页服务然后再起nightWatch服务</p>
<pre><code class="javascript">var server = require('../../build/dev-server.js')
server.ready.then(() => {
// 2. run the nightwatch test suite against it
// to run in additional browsers:
// 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
// 2. add it to the --env flag below
// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
// For more information on Nightwatch's config file, see
// http://nightwatchjs.org/guide#settings-file
var opts = process.argv.slice(2)
console.log(opts);
if (opts.indexOf('--config') === -1) {
opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
}
if (opts.indexOf('--env') === -1) {
opts = opts.concat(['--env', 'chrome,firefox'])
}
var spawn = require('cross-spawn')
var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
runner.on('exit', function (code) {
server.close()
process.exit(code)
})
runner.on('error', function (err) {
server.close()
throw err
})
})
</code></pre>
<p>sudo npm run e2e后</p>
<pre><code class="javascript">> node test/e2e/runner.js
> Starting dev server...
Starting to optimize CSS...
> Listening at http://localhost:8080
[]
Starting selenium server... started - PID: 74459
[Test] Test Suite
=====================
Running: default e2e tests
✔ Element <#app> was visible after 81 milliseconds.
✔ Testing if element <.hello> is present.
✔ Testing if element <h1> contains text: "Welcome to Your Vue.js App".
✔ Testing if element <img> has count: 1
OK. 4 assertions passed. (3.951s)</code></pre>
<p>在控制台上我们能看到各种断言的结果</p>
javascript典型内存泄漏及chrome的排查方法
https://segmentfault.com/a/1190000008901861
2017-03-31T11:18:02+08:00
2017-03-31T11:18:02+08:00
kukuv2
https://segmentfault.com/u/kukuv2
38
<h2>javascript的内存泄漏</h2>
<p>对于JavaScript这门语言的使用者来说,大多数的使用者的内存管理意识都不强。因为JavaScript一直以来都只作为在网页上使用的脚本语言,而网页往往都不会长时间的运行,所以使用者对JavaScript的运行时长和内存控制都比较漠视。但随着Spa(单页应用)、node.js服务端程序和各种js工具的诞生,我们需要重新重视JavaScript的内存管理。</p>
<h3>内存泄漏的定义</h3>
<blockquote><p>指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。</p></blockquote>
<h3>JavaScript的内存管理</h3>
<p>首先JavaScript是一个有Garbage Collection 的语言,也就是我们不需要手动的回收内存。不同的JavaScript引擎有不同的垃圾回收机制,这里我们主要以V8这个被广泛使用的JavaScript引擎为主。</p>
<h4>JavaScript内存分配和回收的关键词:GC根、作用域</h4>
<p>GC根:一般指全局且不会被垃圾回收的对象,比如:window、document或者是页面上存在的dom元素。JavaScript的垃圾回收算法会判断某块对象内存是否是GC根可达(存在一条由GC根对象到该对象的引用),如果不是那这块内存将会被标记回收。</p>
<p>作用域:在JavaScript的作用域里,我们能够新建对象来分配内存。比如说调用函数,函数执行的过程中就会创建一块作用域,如果是创建的是作用域内的局部对象,当作用域运行结束后,所有的局部对象(GC根无法触及)都会被标记回收,在JavaScript中能引起作用域分配的有函数调用、with和全局作用域。</p>
<h4>作用域的分类:局部作用域、全局作用域、闭包作用域</h4>
<h5>局部作用域</h5>
<p>函数调用会创建局部作用域,在局部作用域中的新建的对象,如果函数运行结束后,该对象没有作用域外部的引用,那该对象将会标记回收</p>
<h5>全局作用域</h5>
<p>每个JavaScript进程都会有一个全局作用域,全局作用域上的引用的对象都是常驻内存的,直到进程退出内存才会自动释放。<br>手动释放全局作用域上的引用的对象有两种方式:</p>
<ul><li><p>global.foo = undefined</p></li></ul>
<blockquote><p>重新赋值改变引用</p></blockquote>
<ul><li><p>delete global.foo</p></li></ul>
<blockquote><p>删除对象属性</p></blockquote>
<h5>闭包作用域</h5>
<p>在JavaScript语言中有闭包的概念,闭包指的是包含自由变量的代码块、自由变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。</p>
<pre><code class="javascript">var closure = (function(){
//这里是闭包的作用域
var i = 0 // i就是自由变量
return function(){
console.log(i++)
}
})()</code></pre>
<p>闭包作用域会保持对自由变量的引用。上面代码的引用链就是:</p>
<pre><code class="javascript">window -> closure -> i</code></pre>
<p>闭包作用域还有一个重要的概念,闭包对象是当前作用域中的所有内部函数作用域共享的,并且这个当前作用域的闭包对象中除了包含一条指向上一层作用域闭包对象的引用外,其余的存储的变量引用一定是当前作用域中的所有内部函数作用域中使用到的变量</p>
<h3>常见的几种内存泄漏的方式及使用chrome dev tools的排查方法</h3>
<h5>用全局变量缓存数据</h5>
<p>将全局变量作为缓存数据的一种方式,将之后要用到的数据都挂载到全局变量上,用完之后也不手动释放内存(因为全局变量引用的对象,垃圾回收机制不会自动回收),全局变量逐渐就积累了一些不用的对象,导致内存泄漏</p>
<pre><code class="javascript"> var x = [];
function createSomeNodes() {
var div;
var i = 10000;
var frag = document.createDocumentFragment();
for (; i > 0; i--) {
div = document.createElement("div");
div.appendChild(document.createTextNode(i + " - " + new Date().toTimeString()));
frag.appendChild(div);
}
document.getElementById("nodes").appendChild(frag);
}
function grow() {
x.push(new Array(1000000).join('x'));
createSomeNodes();
setTimeout(grow, 1000);
}
grow()</code></pre>
<p>上面的代码贴一张 timeline的截图<br><img src="/img/bVLvWb?w=1374&h=433" alt="图片描述" title="图片描述"><br>主要看memory区域,通过分析代码我们可以知道页面上的dom节点是不断增加的,所以memory里绿色的线(代表dom nodes)也是不断升高的;而代表js heap的蓝色的线是有升有降,当整体趋势是逐渐升高,这是因为js 有内存回收机制,每当内存回收的时候蓝色的线就会下降,但是存在部分内存一直得不到释放,所以蓝色的线逐渐升高</p>
<h5>js错误引用DOM元素</h5>
<pre><code class="javascript"> var nodes = '';
(function () {
var item = {
name:new Array(1000000).join('x')
}
nodes = document.getElementById("nodes")
nodes.item = item
nodes.parentElement.removeChild(nodes)
})()</code></pre>
<p>这里的dom元素虽然已经从页面上移除了,但是js中仍然保存这对该dom元素的引用。<br>因为这段代码是只执行一次的,所以用timeline视图会很难分析出来是否存在内存泄漏,所以我们可以用 chrome dev tool 的 profile tab里的heap snapshot 工具来分析。<br>上面的代码贴一张 heap snapshot 的summary模式的截图<br><img src="/img/bVLvVx?w=1431&h=266" alt="clipboard.png" title="clipboard.png"></p>
<p>通过constructor的filter功能,我们把上面代码中创建的长字符串找出来,可以看到代码运行结束后,内存中的长字符串依然没有被垃圾回收掉。<br>顺带提一下的是右边红框里的shadow size和 retainer size的含义</p>
<ul>
<li><p>shadow size 指的是对象本地的大小</p></li>
<li><p>retainer size 指的是对象所引用内存的大小,回收该对象是会将他引用的内存也一并回收,所以retainer size 指代的是回收内存后会释放出来的内存大小</p></li>
</ul>
<p>上面我们可以看到 长字符串本身的shadow size和retainer size是一样大的,这是引用长字符串没有引用其他的对象,如果有引用其他对象,那shadow size 和retainer size将不一致。</p>
<h5>闭包循环引用</h5>
<pre><code class="javascript">(function(){
var theThing = null
var replaceThing = function () {
var originalThing = theThing
var unused = function () {
if (originalThing)
console.log("hi")
}
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function someMethod() {
console.log('someMessage')
}
};
};
setInterval(replaceThing,100)
})()</code></pre>
<p>首先我们明确一下,unused是一个闭包,因为它引用了自由变量 originalThing,虽然它被没有使用,但v8引擎并不会把它优化掉,因为 JavaScript里存在eval函数,所以v8引擎并不会随便优化掉暂时没有使用的函数。</p>
<p>theThing 引用了someMethod,someMethod这个函数作用域隐式的和unused这个闭包共享一个闭包上下文。所以someMethod也引用了originalThing这个自由变量。</p>
<p>这里面的引用链是:</p>
<pre><code class="javascript">GCHandler -> replaceThing -> theThing -> someMethod -> originalThing -> someMethod(old) -> originalThing(older)-> someMethod(older)</code></pre>
<p>随着setInterval的不断执行,这条引用链是不会断的,所以内存会不断泄漏,直致程序崩溃。<br>因为是闭包作用域引起的内存泄漏,这时候最好的选择是使用 chrome的heap snapshot的container视图,我们通过container视图能清楚的看到这条不断泄漏内存的引用链<br><img src="/img/bVLvU6?w=1435&h=624" alt="clipboard.png" title="clipboard.png"></p>
<blockquote><p>由于作者水平有限,文中如有错误还望指出,谢谢!</p></blockquote>
<p>参考文档:</p>
<blockquote><p><a href="https://link.segmentfault.com/?enc=cEcriAZVrXYm1h0FnBLrCg%3D%3D.xhbIAr31zLg7vRagVXQaXjY2tMXiiUtRmZhe7ZF0XByIfy%2Bsxm40nIEmIe%2FCRSNwDq8h3gnFepkSQwxCwXO8JPSu94rd1J%2BCK8aab%2BmWIkVb82cGChk4ivWy2xRFE7%2FLhThZpogigP8ZQ272Tz1C2G%2F9eiZnDQmSoXAIoMPxnVxXUL5aYmLTfg9tT4ipt%2BaEuyuJqsjq5mzdJ1fgnyOCUVh7HiJvYK7xWor9FwZTkXw%3D" rel="nofollow">百科内存泄漏介绍</a><br><a href="https://link.segmentfault.com/?enc=5IixQGAHmqL3lGfY6Pm1UA%3D%3D.tDO9Kz%2B%2B1keisQngLeKZQ3we%2FS%2FlWtjqVY%2BTSt6N%2BsGF7hVJvu0iKSM9ML8Rd0yS8hcaAV20LKBA0bYMnGpH3tjUsGaMYOOZbsIvZTDWn78%3D" rel="nofollow">chrome devtolls</a><br>深入浅出nodejs<br><a href="https://link.segmentfault.com/?enc=S2TT3bt3iqqe15fqQy9uLg%3D%3D.er1Eb2SXpyUR641EdMMwZncoNJOniQjKcCeAkCoh1yh32vsCMN38CVHCGrY0lpxGt7HlKX9xcdQAZkOjK7JsDg%3D%3D" rel="nofollow">node-interview</a></p></blockquote>
源码解析 —— Vue的响应式数据流
https://segmentfault.com/a/1190000007604830
2016-11-25T22:10:34+08:00
2016-11-25T22:10:34+08:00
kukuv2
https://segmentfault.com/u/kukuv2
5
<h2>Vue、React介绍</h2>
<p>目前前端社区比较推崇的框架有Vue 和 React,公司内部许多端都自发的将原有的老技术方案(widget + jQuery)迁移到 Vue / React上了。<br>我觉得Vue / React 有以下几点优势</p>
<ul>
<li><p>首先它们都有完整的组件化方案</p></li>
<li><p>virtual Dom (前端性能提升利器)</p></li>
<li><p>成熟的社区生态</p></li>
</ul>
<h3>介绍一个Vue例子</h3>
<p><img src="/img/bVF4v5?w=351&h=573" alt="图片描述" title="图片描述"></p>
<p>上面的例子我们初始化了一个vue组件,当我们改变这个组件的状态时,页面的内容也会随之改变,这中间并不需要我们手动的去操作页面上的dom元素。<br><img src="/img/bVF4wc?w=201&h=373" alt="图片描述" title="图片描述"></p>
<p>同时我们注意到Vue提供了一个语法糖 ——watch,这个就是我们今天要讲的 <code>Vue响应式数据流</code>的主角!代码很简单,就是组件的状态 name 改变的时候我们输出一句话 “name change”。<br>下面我们会向大家解释清楚为什么这个 watch 这么重要,以及它和 Vue的响应式数据流有什么关系。</p>
<h2>Vue的响应式数据流的优势在哪?</h2>
<p>Vue 和 React 都是前端的组件化框架,功能上大同小异,本质上就是借助virtual Dom帮助开发者管理混乱的Dom,并提供给开发者像操作状态机一样操作页面的能力。</p>
<p>但是Vue的virtual Dom 不是普通的 virtual Dom</p>
<blockquote><p>Vue 2.0 的实现有与众不同的地方。和 Vue 的响应式系统结合在一起之后,它可以让你不必做任何事就获得完全优化的重渲染。由于每个组件都会在渲染时追踪其响应依赖,所以系统精确地知道应该何时重渲染、应该重渲染哪些组件。不需要 <code>shouldComponentUpdate</code>,也不需要 immutable 数据 - it just works . —— 尤雨溪</p></blockquote>
<p>我们看一下第三方的性能分析:<br><img src="/img/bVF4wj?w=600&h=344" alt="图片描述" title="图片描述"></p>
<p>除了性能,最大的优势是减轻了开发者的负担,开发者大多数情况下不需要依赖 <code>shouldComponentUpdate</code>,也不需要依赖 immutable 数据去判断组件是否需要重新渲染,Vue会帮你做好这件事。</p>
<p>举个例子来说明这两个的virtual dom的不同之处:</p>
<blockquote><p>开发者就像一个老师,Vue和React这两个学生要做的事就是根据老师给出的长宽画出对应的长方形。每当老师改变给出的长和宽时,Vue能够自己发现长和宽变没变,需不需要重新画;React则需要老师告诉它长和宽变了,需要重新画了。</p></blockquote>
<h2>预备知识:Object.defineProperty 与 订阅发布设计模式</h2>
<h3>Object.defineProperty</h3>
<p>JavaScript 提供一个非常强大的方法 Object.defineProperty,它可以定义当某一个值访问和赋值时会先执行自定义的钩子方法。</p>
<h4>一个简单的Object.defineProperty例子</h4>
<pre><code class="javascript">var obj = {};
var initValue = 'hello';
Object.defineProperty(obj,"newKey",{
get:function (){
//当获取值的时候触发的函数
return initValue;
},
set:function (value){
//当设置值的时候触发的函数,设置的新值通过参数value拿到
console.log(value)
initValue = value;
}
});
//获取值
console.log( obj.newKey ); //hello
//设置值
obj.newKey = 'change value'; //change value</code></pre>
<p>这个方法给予JavaScript开发一种面向切面编程的能力,使用该方法我们能够隐式、自然的控制属性的访问和赋值。</p>
<h3>订阅发布设计模式</h3>
<p><img src="/img/bVF4wu?w=788&h=310" alt="图片描述" title="图片描述"></p>
<p>订阅发布是一个非常常见的设计模式,原理也非常简单就是订阅者订阅信息,然后发布者发布信息通知订阅者更新。</p>
<h2>Vue 源码</h2>
<p>前面铺垫这么多就是希望大家能理解接下来要讲的响应式数据流。</p>
<h3>Vue的初始化</h3>
<p><img src="/img/bVF4wv?w=882&h=519" alt="图片描述" title="图片描述"><br>如上图,Vue的初始化会执行一系列的方法,这里我们主要介绍Vue的initState 方法。<br>prop和data都是组件的属性,prop通常上是父组件传递下来的,data是组件自身定义的,Vue不推荐你去改组件传递下来的prop,因为那样会带来不必要的复杂度。</p>
<h3>Observer</h3>
<p>Prop和data 的最终归宿都是递归执行 defineReactive方法。<br><img src="/img/bVF4ww?w=794&h=342" alt="图片描述" title="图片描述"></p>
<p>那defineReactive方法做了什么呢?<br><img src="/img/bVF4wy?w=553&h=597" alt="图片描述" title="图片描述"></p>
<p>defineReactive会用 Object.defineProperty将组件的每个属性都包装一下,这样谁访问了这些属性,谁重新赋值了这些属性我们都能追踪到了。<br>Vue里面有一个 Observe类,所有的prop子属性和data本身都会带有一个Observer对象,Observer的构造函数<br><img src="/img/bVF4wz?w=510&h=362" alt="图片描述" title="图片描述"><br>在控制台我们可以看到每个属性下都有__ob__,这说明这个属性已经被包装成 Observer对象了,所以的访问和赋值都能给追踪到,这里面也保存着所有订阅该Observer的订阅者Watcher。</p>
<h3>Watcher</h3>
<p>我们看一下Watcher的构造函数<br><img src="/img/bVF4wA?w=631&h=574" alt="图片描述" title="图片描述"></p>
<p>Watcher支持 watch 一个表达式或者是一个方法。Watcher在构造的时候会先获取一次expOrFn的值,下面我们把expOrFn称为Watcher的Getter。</p>
<h3>Dep</h3>
<p>还有一个关键的类是Dep,这个类会帮助我们的属性记录下所有的Watcher,每个属性都有自己的Dep实例,同时Vue的Watcher访问的属性的时候 Dep会作为一个全局变量将自身的target属性指向访问的Wathcer。会执行下面的方法<br><img src="/img/bVF4wD?w=767&h=254" alt="图片描述" title="图片描述"></p>
<p>同时我们再回来看 defineReactive这个重要的方法<br><img src="/img/bVF4wy?w=553&h=597" alt="图片描述" title="图片描述"></p>
<p>当Watcher访问组件的属性时,通过Dep.target,Vue可以知道是Watcher访问的, 这样当Vue自己的Watcher访问属性的时候会被记录成订阅者,而我们访问的时候Vue不会执行多余的代码。这是一个很精妙的设计,将Object.defineProperty 与 订阅发布设计模式结合起来了。<br> 看一下整个的流程图<br><img src="/img/bVGh8b?w=1271&h=676" alt="图片描述" title="图片描述"></p>
<h2>Vue的render函数就是 Watcher 的 expOrFn</h2>
<p>理解了以上Vue是如何将Object.defineProperty 与 订阅发布设计模式结合起来的,然后我们再举一反三:Vue的render函数如果就是 Watcher 的 expOrFn会怎么样?<br>回到Vue的源码里:<br><img src="/img/bVF4wH?w=549&h=296" alt="图片描述" title="图片描述"></p>
<p>这里的 vm._render就是 render函数的一个封装,我们可以看到本质上:Vue的render函数就是 Watcher 的 expOrFn。那初始化的时候我们会先执行一边 render函数,在执行render函数的过程中访问了哪些 组件的属性,Vue都会用上面的提到的方法帮我们把依赖记录下来。所以当这个属性变化的时候,自然而然,就像文章开头的watch一样,我们会重新render一次,(开头的例子是输出“name change”)。</p>
<h2>总结</h2>
<p>讲到这里大家应该都能够明白Vue的响应式数据流是如何实现的。同时我们能够发现Vue提供给我们的许多语法糖都是同样的道理,比如Vue的computer就是将computer函数作为Watcher的expOrFn。<br>希望大家在理解Vue响应式数据流的基础上能够更加自信、灵活和稳健的使用这个优秀的框架。</p>
从零搭建移动H5开发项目实战
https://segmentfault.com/a/1190000007464652
2016-11-12T23:23:17+08:00
2016-11-12T23:23:17+08:00
kukuv2
https://segmentfault.com/u/kukuv2
23
<h2>从零搭建移动H5开发项目实战</h2>
<h3>前端H5的前世今身</h3>
<p>在Pc的时代,前端技术无疑统治了大多数用户的交互界面!而在移动为王的今天,NA开发在早期占领了大多数用户的交互界面,后来逐渐的前端H5开发找到了自己的技术优势,慢慢的后来居上。</p>
<h4>前端H5的优势有:</h4>
<ul>
<li><p>轻松的热更新,(无需等待用户漫长的更新时间)</p></li>
<li><p>code once,run anyway,(极大缩短产品的开发时间)</p></li>
<li><p>丰富的社区、成熟的技术栈和人才储备</p></li>
</ul>
<h4>与此同时也面临了许多难题,比如:</h4>
<ul>
<li><p>性能问题(在低端机型上不够流畅,点击延迟等)</p></li>
<li><p>兼容性问题(不仅要适配各种各样的屏幕,还要面对各类厂商对系统浏览器进行篡改引发的兼容性问题)</p></li>
<li><p>加载时间</p></li>
</ul>
<h4>分庭抗礼</h4>
<p>至此前端H5和NA开发形成了一种互补的关系,各有自己的适用场景和自己的优劣之处:</p>
<ul>
<li><p>H5适合做活动页,运营页,简单的展示页。(支持浏览器的地方就能展示,就能使用)</p></li>
<li><p>H5适合做产品最小原型,开发效率快(一份代码跑两个平台)</p></li>
<li><p>H5适合还不成熟需要频繁迭代的产品</p></li>
</ul>
<h3>移动开发之荡</h3>
<p>移动h5开发和桌面web开发有许多不同的地方,一个传统的桌面web开发工程师,如果没有经过一定的学习和尝试无法开发出适应移动web的应用。<br>那移动开发相较于传统web开发有哪些避无可避的难点呢?接下来我将结合在BANFF项目中的实践来分别介绍这些移动h5开发中的难点以及如何解决这些难点。</p>
<h4>难题一:浏览器默认样式</h4>
<p>不同系统+不同品牌+不同版本的浏览器都会有各种各样自己的默认样式,很多时候如果忽视浏览器的默认样式会导致显示样式上出现兼容性问题,有的时候可能在某些机型上看上去很好,但是换了一个机型却显示又不正常。</p>
<h5>在桌面web时代</h5>
<p>前端要适配的浏览器有限(有IE,火狐,chrome,360等)。这个时候我们可以不考虑这些默认的样式,毕竟不一致的地方较少。这个时候可以采用常规的css normall,将各个浏览器的css显示差异控制在一定范围内,这样既能保留平台的特色UI展示,又能避免出现兼容性问题。</p>
<h5>移动h5时代</h5>
<p>在移动h5上情况就不一样了,手机系统多种多样,浏览器平台数不胜数。如果不严格控制住浏览器的默认样式,显示的兼容性问题就比较严重了。</p>
<p>在BANFF项目中我们对比了 css normal 和 css reset 两种方案,最终采用了css reset 技术,因为在测试过程中我们发现,采用css normal方案去开发移动h5,总会遇到有一些机型的样式无法贴合UE图的效果,所以我们采用css reset将各个平台的css显示差异都抹平,这样就无需考虑浏览器的默认样式了。虽然方法简单粗暴,但是对移动h5开发来说却非常管用。</p>
<p><img src="/img/bVFt23?w=572&h=683" alt="clipboard.png" title="clipboard.png"><br>css reset方案的核心代码 <br><img src="/img/bVFt29?w=857&h=683" alt="clipboard.png" title="clipboard.png"> IE下不同版本的默认样式摘要</p>
<h4>难题二:屏幕适配</h4>
<p>如何适配数不清的屏幕尺寸,曾经是困扰前端开发人员的一大难题。</p>
<p>浏览器占比:<img src="/img/bVFt3i?w=748&h=498" alt="clipboard.png" title="clipboard.png"><br>主流的屏幕类型:<img src="/img/bVFt3j?w=773&h=498" alt="clipboard.png" title="clipboard.png"></p>
<p>看了以上的浏览器占比和主流的屏幕类型就知道屏幕适配对前端开发来说是一个多大的问题了。</p>
<p>在屏幕适配这个问题上,曾今出现了许多优秀的解决方案:</p>
<h5>最开始的基于media技术的响应式布局</h5>
<p>bootstrap样式库采用了这套适配方案,这套方案的核心思想是将屏幕分为三种类型,搭配上栅格系统,我们能够写出同时兼容移动端小屏幕和pc大屏幕的页面。</p>
<h5>移动端适配方案分为几个派别:</h5>
<h6>固定高度,宽度自适应</h6>
<p>这是目前使用较多的方法,垂直方向用定值,水平方向用百分比、定值、flex都行。腾讯、亚马逊、搜狐的首页都是使用的这种方法。<br>这种方法使用了完美视口:</p>
<pre><code class="html"><meta name="viewport" content="width=device-width,initial-scale=1"></code></pre>
<h6>固定宽度,viewport缩放</h6>
<p>这种方案:设计图、页面宽度、viewport width使用一个宽度,浏览器帮我们完成缩放。单位使用px即可。</p>
<p>原理是根据屏幕宽度来动态生成viewport,生成的 viewport 基本是这样:</p>
<pre><code><meta name="viewport" content="width=640,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no"></code></pre>
<h6>rem做宽度,viewport缩放</h6>
<p>这种方案是目前业界比较认可的一种方案,也是吸取了其他方案的长处再进行改良的一种方案<br>原理有以下三点:</p>
<ul>
<li><p>动态的生成 viewport;</p></li>
<li><p>屏幕宽度设置 rem的大小,即给<html>设置font-size;</p></li>
<li><p>根据设备像素比(window.devicePixelRatio)给<html>设置data-dpr</p></li>
</ul>
<p>rem 适配效果 <br><img src="/img/bVFt3r?w=815&h=602" alt="clipboard.png" title="clipboard.png"></p>
<p>通过fekey转换来避免手写rem<img src="/img/bVFt3q?w=726&h=602" alt="clipboard.png" title="clipboard.png"></p>
<p>在BANFF项目中我们比较了以上的几种适配方案</p>
<p>首先响应式布局的解决方案无法实现真正的移动适配,它的适配只能解决pc大屏幕到手机小屏幕的问题,但是手机屏幕任然有很多种,这个时候基于响应式布局的来写页面会发现在Iphone6下看上去和UE效果图一致,但到了iphone5s下按钮之间的间距和UE效果图就差很多,更不用说Iphone4s和其他一众Android机型了</p>
<p>而rem方案能够解决小屏幕的适配问题,因为它的显示单位rem是随着屏幕大小,rem方案比(固定宽度,viewport缩放)来说有优势的地方是可以使用两种不同的单位,想让元素适配的时候就用rem,想让文字不缩放的时候就用px。比如1px的线rem就能轻松搞定,而其他方案不行。比如一些文字我们并不希望它适配,因为部分字体适配后会显示出毛边,这个时候用px能实现不适配的效果。rem的不足之处是我们在书写样式的时候要手动将UE的标注转化成rem。</p>
<p>最终我们采用了wmflex 基于(rem做宽度,viewport缩放) 的 解决方案,很好的实现了适配各类屏幕。同时采用了fekey(px To rem)来解决书写rem不方便的问题,这样我们在写样式的时候只要和按照UE标准的<br>750px来就行了,fekey会自动帮助我们转为rem。经过测试在低端的Android机上或者是dpr等于2的IPhone6s和dpr等于3的IPhone6s plus都能很好的按照交互图来展示。</p>
<p>可以说基于rem适配原理的这一套解决方案,我们已经能够轻松适配各种类型、各种大小的屏幕。</p>
<h4>难题三:点击延迟</h4>
<p>web开发对鼠标有一套完整的事件支持,但是对移动系统上的点击,触控,滑动的事件支持并不完善。就拿最常见的点击来说,h5就有过很长一段时间的不好体验。</p>
<p>点击延迟,对于早期的h5开发可以说是致命的,相较于native的流畅来说,h5的300毫秒的点击延迟几乎是不可接受的。</p>
<p>业界常用的方法是采用将touch事件来进行一系列封装,进而得出一套触控Api来。</p>
<p>fastclick就是经过大量优化的去除点击延迟解决方案。原理是hook了浏览器的touch事件来模拟click事件,让前端开发人员以熟悉的click来书写代码</p>
<p>除了点击事件,滚动、滑动、多点触控,这些浏览器不原生提供的能力都需要我们用代码去模拟出来。</p>
<p><img src="/img/bVFt3p?w=1033&h=240" alt="clipboard.png" title="clipboard.png"></p>
<p>在BANFF项目中采用较常规的解决方案fastclick来去除点击延迟,在以后的项目中如果遇到更复杂的交互需求,会采用更具扩展性的hammerjs来处理各种各样的触碰需求。比如滑动、旋转、多点触碰。</p>
<h4>难题四:mock与调试</h4>
<p>H5页面运行环境多样,如果仅仅是通过报错后弹出alert框这种形式去调试的话,开发效率会降低很多。</p>
<p>首先H5页面肯定是能运行在Pc Chrome上的,借用Chrome的成熟调试体系效率会提高很多;但是我们要考虑到NA内嵌的webview,其中js代码的运行时机要依赖websdk的加载。无法简单的将h5应用拿到chrome上调试。并且除了常用的waimaiApp端,还要考虑微信端,或者是Banff端。所以我们要有一套mock机制,在Pc端上走Mock的代码,在NA端或者微信端上走端对应的代码。</p>
<p>一种较好的代码架构思路是我们提供一个Adapter层,Adapter层对业务代码提供一致的接口,然后Adapter根据不同的使用场景对接不同的端代码<br><img src="/img/bVFt3u?w=777&h=533" alt="clipboard.png" title="clipboard.png"><br><img src="/img/bVFt3z?w=253&h=533" alt="clipboard.png" title="clipboard.png"></p>
<p>有了Adapter层后我们根据什么来判断当前代码运行在什么端呢?比较常见的方法是通过浏览器的ua来进行判端。如果担心代码的体积问题,我们也能通过fekey或其他打包工具,在打包阶段,打包出不同端对应的代码。这样能减少代码的加载时间和体积。 </p>
<p>总结和展望</p>
<blockquote><p>移动h5开发有许多难点,这些难点如果传统web开发人员不去学习和了解就会踩坑。对于一个从零开始的移动端h5项目,我总结了以上这些移动开发难点,希望之后的人能少踩点坑,站在我的肩膀上提高项目开发的效率和质量。之后我们会结合fekey在项目初始化阶段把这些问题解决,产出一份移动开发的项目模板,替新人将这些坑踩平。</p></blockquote>
网页缓存 cache 二三事
https://segmentfault.com/a/1190000003933694
2015-10-31T11:36:51+08:00
2015-10-31T11:36:51+08:00
kukuv2
https://segmentfault.com/u/kukuv2
0
<h2>前言</h2>
<p>一个网页是如何在浏览器上进行缓存的,其中涉及了哪些机制,有哪些技术可以采用,如何才能最大化利用缓存.个人觉得这些知识点都是一个前端工程师必须了解的.曾经在面试的时候被问过一个问题,"有没有不返回304,同时又使用了缓存的情况?".当时因为对缓存机制了解不深,所以答不上来.下面我总结一下这方面的知识.</p>
<h2>通过 HTTP 报头</h2>
<h3>Cache-Control,Expires(响应报头)</h3>
<p>这两个响应报头是浏览器缓存机制的第一道坎.当你发起一个 GET 请求时,服务器返回的响应报头里含有这两个报头,那么你下一次发起请求时(以某种方式, F5刷新除外),在你设置的资源过期时间前浏览器都不会再次请求服务器,而是直接使用已经缓存的版本.并且 status code 是200.这也是我上面说的不是只有返回了304才会使用缓存</p>
<p><img src="/img/bVqjrp" alt="clipboard.png" title="clipboard.png"><br>在 chrome 里我们可以清楚的看到 status code 旁边写的是(from cache).响应报头大致的形式也就是像上面那张图一样.</p>
<h3>Last-Modified,ETag(响应报头);If-Modified-Since,If-None-Match(请求报头)</h3>
<p>这几个报头可以说是浏览器缓存机制里的第二道坎.当你请求某个资源时,如上面的图,响应报头会设置 Last-Modified 和 ETag,Last-Modified 是这个资源的最后修改时间, ETag 是该资源的唯一标识符.这两个值会在你再一次请求该资源时以 If-Modified-Since 和 If-None-Match 的形式附在请求头里发给服务器由服务器判读该资源是否过期.如果没过期,返回304,如果过期了,返回带有响应主体也就是 content并且status code为200的响应.请求报头大致如下.这个过程称为服务器再认证.</p>
<p><img src="/img/bVqjrT" alt="clipboard.png" title="clipboard.png"></p>
<h3>这两种报头的作用区间</h3>
<p><img src="/img/bVqjxa" alt="clipboard.png" title="clipboard.png"><br>借用一张简洁的图来展示它们的作用区间的区别</p>
<h3>GET与 POST</h3>
<p>只有GET请求才会触发浏览器的缓存机制.这也是 GET和 POST 的区别之一. GET 的目的应该是请求资源并且不会对服务器上的数据产生改动,是幂等的.而 POST 的目的就是为了修改数据.所以对于一些GET 方法的Ajax也可以使用响应报头的方法进行缓存, jQuery 里的 ajax 方法里的 cache=false 就只对 GET 方法有效,原理是在 url 后加上一个时间戳类似于 <a href="https://link.segmentfault.com/?enc=1NGSIeDuVNdPSENYYCSUZw%3D%3D.D9vj0CLqpTq%2FmU%2BiKJ9NfTTWY0%2BCX3QGToa0QgEH4Fk4bNts2YXLyESQKakvi4kx" rel="nofollow">http://www.test.com?a=b_201510300000,</a>以防止浏览器缓存请求.</p>
<h2>通过 manifest(html5)</h2>
<p>manifest 是 一种新的缓存方式,通过 manifest 可以让网页离线使用.下面我总结下在使用 manifest 的时候一些要注意的地方,应该工作上没有使用 manifest 的场景,所以不做过多的深入</p>
<ol>
<li><p>manifest文件 的 content-type必须是'text/cache-manifest',不然浏览器会不识别这是缓存文件.</p></li>
<li><p>引入 manifest 的方式是<html lang="en" manifest="hello.manifest">,默认的引入的 html 文件也会被缓存.</p></li>
<li><p>manifest 文件的格式类似</p></li>
</ol>
<pre><code class="javascript">CACHE MANIFEST
bundle.js</code></pre>
<p>4.一旦浏览器缓存成功后,下一次浏览器会先使用缓存的文件,然后再发请求检查 manifest 文件是否有变化.所以在下次打开网页前,用户看到的还是旧的文件.你可以使用 window.applicationCache 来控制当发现 manifest 文件过期后自动刷新,这样来保证用户能看到新的内容.<br> 5.manifest 文件的检查是根据类似于文件 md5的方式来检查的,也就是说如果你在 manifest 文件里加了一个新空行,浏览器也会认为 manifest 文件有更新,然后重新下载缓存文件以备下次使用.</p>
<h2>通过 webStorageApi(html5)</h2>
<p>webStorageApi 我主要讲一下 localStorage, 因为在工作中有使用场景.在开发新后台主页面的时候,左侧是一个很长的多级菜单,右面是 主内容.大概是这样</p>
<p><img src="/img/bVqFvn" alt="图片描述" title="图片描述"><br>当左侧菜单拉长时,点击链接跳到新页面,这时左侧菜单的滑动条也要保持在上个页面的滑动位置,这时可以监听点击菜单事件,通过 localStorage 来保存滑动条高度</p>
<pre><code class="javascript"> if(target.attr('href') !== "javascript:void(0);"){
if(window.localStorage){
localStorage.setItem("siteScroll", scrollTop);
}
}else{
$('.nav-secondary').scrollTop(target.offset().top - menuOffsetTop);
}</code></pre>
<p>下次页面打开的时候再获取并设置滑动条的位置</p>
<pre><code class="javascript"> if(window.localStorage){
var scrollTop = localStorage.getItem("siteScroll");
$('.nav-secondary').scrollTop(scrollTop);
}</code></pre>
<blockquote><p>因本人水平有限,总结过程中难免出错,还望大家能够指出,谢谢!</p></blockquote>
<p>查阅应用了以下书籍和内容</p>
<blockquote><p><a href="https://link.segmentfault.com/?enc=3qvImT7wlQTEaqbN0UnS9A%3D%3D.wjHyM%2FzMW0wOQ6ZZFaQZzpVl%2Bon3IM0c9VY0EkImIJ7a32IQumGAzk0gXq3x7hsXoZ9l1BLRD1WIiOyBR3TjBA%3D%3D" rel="nofollow">http://www.alloyteam.com/2012/03/web-cache-2-browser-cache/</a><br>html5高级程序设计</p></blockquote>
JavaScript函数声明与函数表达式
https://segmentfault.com/a/1190000003713006
2015-09-06T18:20:33+08:00
2015-09-06T18:20:33+08:00
kukuv2
https://segmentfault.com/u/kukuv2
3
<h2>JavaScript函数声明与函数表达式</h2>
<h3>如何定义一个函数</h3>
<p> 在JavaScript里有两种定义函数的方法</p>
<ol>
<li><p>函数声明<br> function 函数名称 (参数:可选){ 函数体 }</p></li>
<li><p>函数表达式<br> function 函数名称(可选)(参数:可选){ 函数体 }</p></li>
</ol>
<h3>常见的函数定义以及所属的定义方法</h3>
<ul>
<li><p>function foo(){} 函数声明</p></li>
<li><p>var bar = function foo(){}; 函数表达式</p></li>
<li><p>new function bar(){}; 函数表达式</p></li>
<li><p>function foo(){ function bar(){} 函数声明}</p></li>
<li><p>(function(){})() 函数表达式</p></li>
<li><p>+function(){}() 函数表达式</p></li>
<li><p>!function(){}() 函数表达式</p></li>
<li><p>;(function(){})() 函数表达式,分号反正前面没加分号,解析错误</p></li>
</ul>
<h3>函数声明与函数表达式的一些细微的不同</h3>
<p>在JavaScript里函数声明会有一个hoist的过程,也就是说在函数执行的之前,函数体就已经被解析了。一个典型的例子</p>
<pre><code class="javascript">if (true) {
function foo() {
return 'first';
}
}
else {
function foo() {
return 'second';
}
}
foo();</code></pre>
<p>正常情况下,得到的结果是 second <br>而</p>
<pre><code class="javascript">var foo;
if (true) {
foo = function() {
return 'first';
};
}
else {
foo = function() {
return 'second';
};
}
foo();</code></pre>
<p>我们能得到想要的结果</p>
<blockquote><p><a href="https://link.segmentfault.com/?enc=Ljz6qIRC%2FWSExvypX9MiVA%3D%3D.cYPH%2Fkz6PCRPFvM0kNdTrBCNCDl1m%2F5QOIi%2B%2Bu41uZsPEYqmLGRpbbdX0nHHEmtpIt%2Fhl%2BGew3qU0KDa5BfGMQ%3D%3D" rel="nofollow">http://www.nowamagic.net/librarys/veda/detail/1630</a></p></blockquote>
浏览器同源策略以及跨域请求时可能遇到的问题
https://segmentfault.com/a/1190000003711795
2015-09-06T15:00:50+08:00
2015-09-06T15:00:50+08:00
kukuv2
https://segmentfault.com/u/kukuv2
3
<h2>跨域请求基础知识</h2>
<h3>浏览器的同源策略</h3>
<p> 浏览器的源指的是 协议://域名:端口 这样的URL组合。我们首先要明确几点</p>
<ul>
<li><p>www.foo.com 和 foo.com 是不同域</p></li>
<li><p>www.foo.com 和 www.foo.com/b/1 是同域的,因为浏览器的源中不包含路径</p></li>
<li><p>https 和 http 的协议不同,是不同域<br>当以上三个元素中的一个与网页的document.domain不相同时,浏览器会认为你的请求是跨越请求。</p></li>
</ul>
<h3>cookie机制</h3>
<p> 经常容易与同源策略搞混的是cookie的发送机制。</p>
<ul>
<li><p>cookie 可以通过设置domain 为 domain=".foo.com" , 这样设置如果用户访问的www.foo.com 或是user.foo.com 那么这个cookie都会被发送</p></li>
<li><p>cookie 可以通过这事path 为 path="/auto/" ,这样设置如果用户访问的是www.foo.com/user时将不会发送这个cookie</p></li>
</ul>
<h3>修改当前域</h3>
<ul><li><p>可以通过设置document.domain 为当前域的一个后缀来修改当前域,例如 可以将域名为 www.foo.com 的document.domain 设置为 foo.com : document.domain="foo.com"来绕过一些跨域问题。</p></li></ul>
<h2>浏览器 -- 浏览器的跨域访问</h2>
<p> 可以通过window.postMessage()来解决浏览器中不同页面不同域名的通信问题</p>
<h2>浏览器 -- 服务器的跨域访问</h2>
<p> 浏览器跨域访问不同域的服务器的解决方案一般有三种</p>
<ul>
<li><p>jsonp,利用JavaScript加载没有同域策略的机制。一般返回的数据格式是:callback(json);</p></li>
<li><p>Iframe间通信,在当前页面下append一个Iframe。例如</p></li>
</ul>
<pre><code class="javascript">$(document.body).append('<IFRAME id="authIframe" style="display:none">');
$('iframe#authIframe').attr('src', CONST_CONTENT_ROOT_URL+'/index.jsp');
$('iframe#authIframe').load(function(){
});</code></pre>
<ul><li><p>服务器返回带有HTTP access control 的头来实现跨域访问。</p></li></ul>
<h2>HTTP access control (CORS)</h2>
<p> 使用Access-Control的跨域请求有两种,简单的跨域请求和带有先导请求(options)的跨域请求</p>
<h3>简单的跨域请求</h3>
<p> 简单的跨域请求和带有先导请求的跨域请求最大的区别是他不会先发送一个先导请求。<br> 浏览器对简单的跨域请求的定义是</p>
<ul>
<li><p>请求方法是get,post,head中的一种</p></li>
<li><p>content-Type必须为application/x-www-form-urlencoded, multipart/form-data, 或text/plain中的一种</p></li>
<li><p>没有自定义请求头</p></li>
</ul>
<h3>带有先导请求(options)的跨域请求</h3>
<p> 带有先导请求(options)的跨域请求,浏览器会自动先发送一个options方法的请求到服务器端,并查看相应头里是否有Access-Control头部。值得注意的是options方法的请求并不会携带cookie,也就是如果如果你的请求必须要登陆验证的话那么你就必须构造一个简单请求。</p>
<p>下面是一些常用的Access-Control头部<br>请求头</p>
<ul>
<li><p>Access-Control-Request-Method : 先导请求中的请求头,告诉服务器真实请求的http方法</p></li>
<li><p>Access-Control-Request-Headers :先导请求中的请求头,告诉服务器真实请求的http请求头<br>相应头</p></li>
<li><p>Access-Control-Allow-Origin :服务器允许跨域请求的origin</p></li>
<li><p>Access-Control-Expose-Headers : 允许JavaScript读取的头部</p></li>
<li><p>Access-Control-Allow-Credentials :是否允许携带cookie</p></li>
<li><p>Access-Control-Allow-Methods :允许的请求方法</p></li>
<li><p>Access-Control-Allow-Headers :允许的请求头部</p></li>
</ul>
<blockquote><p><a href="https://link.segmentfault.com/?enc=8lqekVj7SxUX1lkuEjiF6Q%3D%3D.1yPGzGLsc93nPkKJ4cOu0UXVUQD6BzqnSjm3jPVA97iKPHukKjWHoXu4Tc1II5wAd8HxdujYZ0Rj2Wa7TYhJiZRqq7bal2PPh8ktQT753t4pA2TofyaeAfuS4nKlikNx" rel="nofollow">https://developer.mozilla.org/zh-TW/docs/HTTP/Access_control_CORS#Access-Control-Allow-Origin</a></p></blockquote>
<p>由于水平有限,出错难免,如有出错,还望指正,谢谢!</p>
javascript 的基本类型 和 与操作符结合时的类型转换
https://segmentfault.com/a/1190000003688282
2015-08-31T07:50:29+08:00
2015-08-31T07:50:29+08:00
kukuv2
https://segmentfault.com/u/kukuv2
0
<h2>javascript 的基本类型</h2>
<p>javascript的基本类型和类型转换系统相较于其他语言例如 Java 来说可以说是非常混乱的.这个是许多新手必定会遇到的坑.首先javascript 有五种简单的基本类型(undefined,null,Number,Boolean,String).和一种复杂的数据类型object.</p>
<p>类型检测有两种方式 typeof 和 instanceof . </p>
<p>instanceof用来检测对象的原型链. 但有时候 instanceof 也会不好用比如不同window.frames[0]里的 Array检测,对于已经实现了 toString 方法的类型,我们可以用Object.prototype.toString.call(obj) 来检测,得到结果类似[object Array].</p>
<p>typeof : 因为NaN属于 number 的一种所以 typeof NaN === 'number' ; 在javascript 里 Object,String等都是一种构造函数,所以 typeof Object === 'function',typeof String === 'function'.<br>typeof 所有的检测结果如下:</p>
<table>
<thead><tr>
<th>Type</th>
<th>Result</th>
</tr></thead>
<tbody>
<tr>
<td>Undefined</td>
<td>"undefined"</td>
</tr>
<tr>
<td>Null "object"</td>
<td>(see below)</td>
</tr>
<tr>
<td>Boolean</td>
<td>"boolean"</td>
</tr>
<tr>
<td>Number</td>
<td>"number"</td>
</tr>
<tr>
<td>String</td>
<td>"string"</td>
</tr>
<tr>
<td>Symbol</td>
<td>(new in ECMAScript 2015) "symbol"</td>
</tr>
<tr>
<td>Host object</td>
<td>(provided by the JS environment) Implementation-dependent</td>
</tr>
<tr>
<td>Function object (implements [[Call]] in ECMA-262 terms)</td>
<td>"function"</td>
</tr>
<tr>
<td>Any other object</td>
<td>"object"</td>
</tr>
</tbody>
</table>
<h2>与操作符结合后的 javascript 类型转换</h2>
<pre><code> 弱类型的 javascript 的许多操作符会自动类型转换,很多时候转换后的
结果会让人吃惊.下面我们来总结一下:</code></pre>
<h3>1. 尝试将两个变量转换为数值(调用 Number())的操作符 :一元操作符 ++,--;乘性操作符 *,</h3>
<pre><code> Number的转换规则是
1.1 如果是string:
1.1.1 判断是否能转换为数值含有字母和其他非.符号的直接返回
NaN ; 1.0 , .1 均可以转换为数值 ,.1.1含两个以上的不能转换为数值.
1.1.2 能转换为数值的返回对应数值.
1.2 如果是boolean:
1.2.1 true 转换为0;
1.2.2 false 转换为1;
1.3 如果是 undefined: 转换为 NaN
1.4 如果是 null 转换为 0
1.5 如果是 Object var result = obj.valueof(); return Number(result) 如果得到 NaN,再调用 toString</code></pre>
<h3>2. 尝试将两个变量转换为布尔值: 布尔操作符 !;条件操作符 ? :</h3>
<pre><code> Boolean的转换规则是:
2.1 如果是string: 空字符串''返回 false ,其他返回 true.(注意 new String('') 属于对象)
2.2 如果是number: 0返回 false,其他 true
2.3 undefined 和 null 返回 false
2.4 object : 返回true</code></pre>
<h3>3. 先判断是要转换为哪种基本类型,再做转换. 加性:+;条件>,<;非严格相等 ==</h3>
<h4>3.1 加性 +,-</h4>
<pre><code> 3.1.1如果两个都是数值正常计算 infinity,-infinity 和 +0,-0 略过..
3.1.2如果两个都是字符串拼接.
3.1.3如果只有一个是字符串,将另一个转换为字符串.
3.1.4如果有一个是对象,尝试转换为字符串
3.1.5如果两个都不是字符串,且其中一个是数值,将另一个转换为数值.
3.1.6其他情况都是 NaN</code></pre>
<h4>3.2 条件 >,<</h4>
<pre><code> 3.2.1两个都是数值,数值比较
3.2.2两个都是字符串,字符编码比较 (注意 A<a)
3.2.3其中一个是 NaN ,false
3.2.4其中一个是数值,转换另一个为数值
3.2.5对象先尝试转换为数值,不行字符串再比较
3.2.6布尔转换为数值.</code></pre>
<h4>3.3 不严格相等 == 不同类型是</h4>
<pre><code> 3.3.1布尔值先转换为数值
3.3.2一个是字符串,一个是数值.字符串转换为数值
3.3.3对象调用 valueof(),再比较
3.3.4 == 有 NaN,就为 fasle ;!= 有 NaN ,就为 true
3.3.5 undefined == null ,undefined 和 null 不会转换为其他类型进行比较.
</code></pre>
<h3>4.if 语句 使用 Boolean()</h3>
<p>部分内容来源于 :</p>
<blockquote><p>Javascript 高级程序设计, MDN和网络</p></blockquote>