0%

因为工作的原因,很少有机会接触移动 web 开发,一直都还挺遗憾的,偶尔写几个页面也都是 “按照套路” 出牌,最近终于有空了解一些概念性的东西,记录一下
文中的图片为了说明问题均为盗图,具体出处会在文末注明

本文大概将以下几个概念以做对比

设备的 pixels 和 CSS 的 pixels

**设备的pixels** 设备像素是我们直觉上觉得「靠谱」的像素。这些像素为你所使用的各种设备都提供了正规的分辨率 大多数情况下能从`screen.width/height` 取出具体值 当然了 设备的pixels对web开发人员几乎毫无用处 这里只需要知道它的概念即可 **CSS的pixels** 这些就是那些控制你的样式表如何被渲染的像素

现代浏览器上的缩放是基于伸展 pixels
所以 html 元素上的宽度不会因为你缩放了 200% 而变成了两倍宽,它在形式上还是一倍宽 只不过占用了两倍的设备 pixels
如下图 1-1 有 4 个 1pixels,缩放为 100% 的 html 元素 此时 css pixels 和设备的 pixels 完全重合

此时我们如果缩小浏览器 css 的 pixels 开始收缩,导致 1 单位的设备 pixels 上重叠了多个 css 的 pixels 如下图 1-2

如果放大浏览器 css 的 pixels 就会放大 导致 1 单位的 css pixels 上重叠了多个设备 pixels 如图 1-3

总而言之 你只需要关注 CSS 的 pixels 这些 pixels 将指定你的样式被如何渲染
就像上面所说的 设备的 pixels 对开发人员无用 但是对用户有用,因为用户回手动缩放页面,这些开发人员不用关注 浏览器会自动保证你的 css pixels 会被伸展还是被收缩

所谓的 100% 缩放

100% 缩放的情况下 1 单位的的 CSS pixels 严格等于 1 单位的设备 pixels

屏幕尺寸和浏览器尺寸

**屏幕尺寸(Screen size)** 含义:用户的屏幕的完整大小 度量:设备的pixels 不会因为缩放而改变 是显示器的特征 对我们来说没用 获取方式 如下图1-4 ![](/images/viewports/4.jpg)

浏览器尺寸(Window size)
含义:包含滚动条尺寸的浏览器完整尺寸
度量:CSS pixels
浏览器内部尺寸,它定义了当前用户有多大区域。可供你的 css 布局占用
如下图 1-5

页面的滚动移位

含义:页面的移位 度量:CSS pixels 定义了页面(document)的相对于窗口远点的位移,可以利用这个特性获取用户滚动了多少的滚动条距离 如下图1-6 ![](/images/viewports/6.jpg)

viewport 以及度量 viewport

**viewport** 啊啊啊 终于提到viewport了 鸡冻 划重点 `viewport`是控制``元素的容器 是``的爹

你发现了么?
百分比布局时 你定义的一个侧边栏宽度为 10% 当你改变大小时 它的宽度会自动扩张和收缩 原理是啥
当然了 它的宽度是依赖父元素 假如它父元素就是 <body> 那么 <body> 多宽?
向上类推 <body> 的宽度取决于它的父元素 <html>
呃.. 废话好多 <html> 宽度取决于它的父元素
<html> 恰好等于浏览器的宽度 所以你的 10% 会占用浏览器宽度的 10% 我们都是这么用的 今天深扒发现
<html> 宽度受 viewport 限制 ,等于 viewport 宽度的 100%
也就是说
viewport 严格等于浏览器窗口
需要注意的是:viewport 不是一个 html 的概念 所以不能通过 CSS 修改它

真实页面宽度概念
如果你放大页面几倍 如何标识页面宽度(此时已经有横向滚动条了,也就是说页面的内容溢出了 <html> 元素)
使用 document width
如图 1-7

如图 1-8

度量 viewport
含义:viewport 尺寸
度量:CSS pixels
如下图 1-9

document.documentElement 代表 HTML 文档根元素<html>
来 先看张图

这张图是在为 <html> 元素赋值 25% 但是 document.documentElement.clientWidth 值仍然不变
说明 document. documentElement. clientWidth/Height 只会给出 viewport 的尺寸,而不管元素尺寸如何改变
那么问题来了
我是不是也可以用 window.innerWidth 来定义 viewport
呃.
他与 document.documentElement.clientWidth 有一点细微的差别
前者不包含滚动条

html 元素以及度量 html

**html** ta爹(`viewport`)如果`document.documentElement.clientWidth`表示那么``这样获取 `document.documentElement.offsetWidth` ![](/images/viewports/11.jpg) 如果给``元素赋值了宽度 那么`offsetWidth`就会真实的反映出来

关于事件坐标

**pageX/Y, clientX/Y, screenX/Y**
  • pageX/Y:从原点到事件触发点的 CSS 的 pixels
  • clientX/Y:从 viewport 原点(浏览器窗口)到事件触发点的 CSS 的 pixels
  • screenX/Y:从用户显示器窗口原点到事件触发点的设备 的 pixels。
    上图


媒体查询 width/height 与 device-width/height

* `device-width/height`使用`screen.width/height`来做为的判定值。该值以设备的pixels来度量 * `width/height`使用`documentElement.clientWidth/Height`即viewport的值。该值以CSS的pixels来度量 ![](/images/viewports/16.jpg) 桌面浏览器上使用width

mobile 浏览器

移动设备的屏幕宽度比桌面浏览器小(好多废话.) 试想一下 如果我们只是copy桌面的样式到移动设备 该有多丑 如下图 移动设备浏览器在初始默认打开以最小缩放模式打开网站。(即在手机屏幕上展示完整宽度的页面)

假设当前设备的宽度是 400px 还是之前说过的 10% 侧边栏,如果移动设备上做同样的处理,会显示 40px 的宽 太窄了,布局会变得非常可怕
那么 如何处理?

两种 viewport

因为viewport太窄,最显然的解决方式就是将它变宽 由此 引出了 虚拟视口 (`viewportvisualviewport`) 与 布局视口(`viewportlayoutviewport`) 关于它们 有一个很好的解释
1
2
3
4
5
6
  想象一下viewportlayoutviewport是一张大的不能改变大小和角度的图片 现在你有个更小的框来观看这张大图片
这个框被不透明的材料包围 因而你只能看到大图片的一部分 你通过这个框子看到的大图片的那部分就叫做虚拟视口(viewportvisualviewport)

你拿着这个框拿着站的离大图原点(用户的缩小页面功能)以一次性看到这个大图片
你站的离的近一点(用户的放大页面功能)以看到一部分
你能改变这个框框的远近 但是这张大图片的大小和形状都不会改变

visualviewport 是当前显示在屏幕上的部分页面。用户会滚动页面来改变可见部分,或者缩放浏览器来改变 visualviewport 的尺寸。
如下图

但是 CSS 布局 通常是按照 layoutviewport 定义的,这要比 visualviewport 宽很多

元素的宽度继承于layoutviewport

缩放 Zooming

两种viewports都以CSS的 pixels来度量。当你通过缩放改变visualviewport时,layoutviewport保存不变。

屏幕尺寸

**理解layout viewport** 许多移动设备浏览器在初始默认打开以最小缩放模式打开网站(也就是在手机屏幕上展示完整宽度的页面)


此时浏览器已经选择好他们的 layoutviewport 的尺寸 它完整覆盖了最小缩放模式下的移动浏览器的屏幕,这个时候 layoutviewport 的宽度高度和最小缩放模式下能在页面上显示的内容的宽度和高度一致。


那么移动端如何计算 layoutviewport 的尺寸?
document. documentElement. clientWidth/Height


理解 visual viewport
那么移动端如何计算 visualviewport 的尺寸?
window.innerWidth/Height 随着用户的缩放浏览器 值会改变 更多 更少的 CSS pixels 放进了屏幕

屏幕尺寸 screen
和 pc 浏览器一样 screen.width/height 标示了设置屏幕的尺寸 以设备的 pixels 显然 这跟开发人员没有什么关系

页面的滚动移位

你同样需要知道当前的虚拟视口相对于布局视口的距离 这叫做`滚动位移` ,它像在pc端获取一样 使用**window.pageX/YOffset**

html 元素以及度量 html

html元素的整体尺寸,和pc端一致 使用`document.documentElement.offsetWidth/Height`,元素以CSS pixels度量

关于事件坐标

同pc浏览器 只需要关注 pageX/Y

媒体查询 width/height 与 device-width/height

也如同pc浏览器 `width/height `使用css的pixels度量layoutviewport 即`document. documentElement. clientWidth/Height ` `device-width/height `使用设备的pixels 即 `screen.width/height. ` 所有浏览器都遵循这个原理

viewport 的 meta 标签

1
<meta name="viewport" content="width=320">
最初这是apple的一个html扩展标签,被很多浏览器复用 设置 `虚拟视口`的宽度

假设你现在创建一个页面 并不为它设置宽度 那么它会伸展开来占据 100% 的 viewlayout 的宽度 绝大多数浏览器缩小这个页面在一屏的宽度上显示这个 layoutviewport


当用户放大页面 绝大多数会保存元素的宽度(保持元素的定位不变)而导致文字超出屏幕

当你设置

1
<meta name="viewport" content="width=320">

你网站的 layoutviewport 变成了 320px。页面的初始状态就很正确了

此时此刻 ,反复思索着昨晚的梦,人生充满无奈,珍惜眼前人

具体的故事背景记不清了 大概是朋友的奶奶蹊跷去世了,我去她家安慰她并试图查事故原因,结果等我回去找他们的时候发现自己已经变成了隐形人 其实就是已经死掉了,显然我自己也不知道自己发生了什么事情,事实就是我还在人间,就是我能看世人,世人再也看不到我了

第一反应是: 我要回家
我不知道他们是怎么处理世间的我,我只知道我的灵魂回家了
我也记不清爸妈是否知不知道我已经不在了,我用力的跟他们讲话 只可惜他们大大小小全都对我视而不见,我来不及思考 依然用力呼唤他们,追着拉着,全都没有用, 我好无力…

想到多年前看过一部日本的温情电影,讲的也是主人公意外身故 化身隐形人回家,发现自己没办法让家人注意到也是很痛苦 不过电影就是电影 有一个巫婆是阴阳眼 成了 家人跟 “隐形人” 沟通的桥梁,必要的时候还会帮助家人度过难关 影片最后是巫婆帮助隐形人附身于自己身上,与妻子做最后的告别,最后安稳投胎
印象中这才是鬼魂的正确打开方式 国产剧中的鬼魂仿佛都很厉害的样子,有仇报仇,有冤报冤,一言不合就施法 也是~

我当时想起来银行账户还有钱 就想给他们转过去,但是负责管我的警察,暂且叫他 灵魂摆渡人 (404 号便利店), 对耶 我为什么没去 404 号便利店找夏冬青帮我实现愿望,大概我遇见的是赵吏 笑 cry 他说 我可以往人间账户汇款,就是有损耗 1W 只能到账 1k 这尼玛是跨行手续费吗😢

不记得最后是咋解决的, 反正世界就再也没有我了,就算再挣扎
我开始回忆一生,那些活过的平淡日子,能记起来的只有桩桩件件的遗憾

我还没有孝顺父母

上次电话里没有喊一嗓子 爸妈我爱你

我应该再回一次家的

想在老家给爹妈买房子的钱还没存够

家庭旅行的计划家门口还没出

想起来这些碎碎念

突然我就开始怀念了

我想,如果我在世的时候能有这些危机感 现在也不会有这么多遗憾了

私人一点的是

恋爱记有这么多点滴 另一半还没添加
这世上 除了爸妈亲人 连个记挂我的人都没有

好悲哀

早上被闹钟叫醒 发现自己还活着

感谢上帝 有些事只有失去过才知道 我拥有的还很多

致每一个空虚的你

标题好像是句废话,” 不规范” 何谈 效率 呢?

  1. 何为效率
  2. 从产品看效率
  3. 从 UI 看效率
  4. 从开发看效率
  5. 从测试看效率

何为效率?

百度百科上大概是这么定义的:是指在给定投入和技术等条件下,最有效地使用资源以满足设定的愿望和需要的评价方式

对我来说最直观的判断方式就是 项目是否有延期 线上是否有 bug

  • 效率有多重要?

    大部分的电子厂工资都是按件计费的,多劳多得,单位时间产出与收入能正比,所以工人们都非常努力 这是自发性行为 所以也是最任怨的。

    对我而言 效率能带来好心情 好状态以及好的生活态 废话 大家都一样 –

从产品看效率
一款产品的成功与否 有多方便的决定因素 产品最终都是面向用户的 一个不能为用户创造价值的产品应该是没有使用价值的,产品本就是提升效率的一种方式

那么 接下来 我要开始吐槽了

如果产品需求不明确或者需求不完整 项目要不要开始做?
一般来说 不要 绝对不要 除非是两个完全不相干的模块 昂 基本也不太可能
但是尼 为了所谓的工期 产品大大一般会这么说 做呗 先做着 我这么单(wu)纯(nai)的 每次都被套路住🤡 然后后来计算工时的时候 : 哦 你不是从那个什么什么时候就开始了吗?👐🏽
这还不是最主要的,毕竟 时间就像海绵里的水 挤一挤总会有的😂

最主要的当然是改需求啦 这大概是所有产品最擅长的

这两者本质上是没有矛盾的,只不过产品更多的以目标为导向,需求只是手段,而对开发来说 技术是手段 需求是目标 只不过产品更容易主观,比如需求,比如说功能,比如说交互,这些都受到个人经验,眼界,学识很大影响。朝三暮四,朝令夕改,是常有的事情。

说真的
产品的功能、质量、发布时间和需要投入的资源这四者不能都是封闭条件,否则可能无解

而且现在的产品很是聪明啊 需求给了,开始做了,做到最后发现这种情况下不是很合理,简单啊 改呗 反正做到这里我逻辑也捋清楚了,也知道该咋办了产品默想到

呃.. 这两天刚遇到个类似的情况 哦 不好意思,是经常遇到,提测一周也测了一周了,代码上体验版(预上线)了

1
2
3
4
5
6
7
8
9
10
11
产品:咦?这块不是那样儿的吗?怎么是这样的?

开发:? 前两天不是专门讨论过这个问题吗,这个就是这样儿的!

产品:哦?是吗? 但是这样儿看起来好诡异啊!就应该是那样儿的 改一下吧

开发:...当初接口设计的就是这样儿的,怎么不早说?这都要上线啦!😩

产品:"之前流程没走通 不太清楚这块儿的逻辑,现在改一下吧" 然后就走了

开发:...

还有就是排期

产品经常这么一句话:因为我们的上线时间是 XX 号 测试需要 XX 天 所以 开发排下期吧 ?

…..

排完期之后..

1
2
3
4
5
6
7
8
9
10
11
12
13
产品:昂昂,你这儿不行啊 时间太长了 能不能缩短点

开发:我按工时算的 每一项 都列出来了 不会差太多

产品:这不行啊 因为我们要XX提审 测试需要XX天 所以你必须 XX天提测

开发:这等于 XX 天的工期 你给我压到 XX/2 天

产品:嘿嘿😉

开发:...

开发OS:"赶紧开发 为了给测试腾时间😂😭🙈,尼萌给开发时间充足了 问题自然就会少很多好吗 测试好像是最重要的噻"

百度过类似的问题 最好的办法是让产品也学会基础的编程,了解每一个实现都是需要耗费资源的,这样才能基于对有限资源的理解,做出更谨慎的规划。
然而,这基本不可能,所以..

实际上 那些非常紧急的需求,这么一搞成了拖延的第一个关键点

tuxie

从 UI 看效率

呃。对于用户来说 一个产品能不能吸引到你,最浅显也最重要的是眼缘,说俗气点就是颜值
而且 UI 链接 产品与开发 自然也是马虎不得的 深刻理解原型的精髓 然后利用智慧的小脑瓜设计成用户想要的样子,上面说了 他们最重要的工作是 深刻理解原型的精髓,因为 对于,设计师总是有不一样的定义,就比如 情人眼里出西施

非常不幸 我们 UI 也是必须要吐槽的对象

直接上日常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
开发:设计小哥,对比原型 是不是缺了一张图

设计:?

开发:搜索中状态

设计:沉默

产品:哦 那个不就是在未搜索的状态基础上把导航去掉吗?还用出图吗?

一分钟后~~

设计:突然扔出来一张图 @开发:好了

开发:瞅了一眼 @设计:嗯 ?不是这样儿的吧

设计:?

开发:把产品的话 @设计:“哦 那个不就是在未搜索的状态基础上把导航去掉吗?还用出图吗?”

设计:沉默

两分钟后~~

设计:扔出来一张图 呐 上传了

开发:嗯 现在才是对的

设计:嗯


类似的事情简直日常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
上午~

产品:今天周五 需要把UI提的问题都改完

开发:ok

设计:ok

下午~

开发:什么时候有空,当面改

设计:嗯

下午四点~

开发:什么时候有空

设计:现在过去

开发:嗯

改了大概有两个之后 设计被叫走

又过了一个小时~

开发:快下班了 啥时候改尼

设计:今天比较忙 现在可以开始了

开发: 嗯?我看了一下 你拿到的不是最新版吧 好些问题现在不能复现了

设计:哦

一个小时之后

开发:改完了 我下班了啊 今天有点事儿

设计:嗯 好的 下班吧

开发 走了

两个小时后 忙完 开发都到家了~~

产品:@开发:在哪里呢 啥时候回来

开发:?我到家了啊

产品:你咋走了?设计小哥还在等你呢

开发:?我走的时候跟他说了啊 他知道啊

产品:我不是说了今天要改完吗 设计review了 还有一点问题

开发:... 那我明天去加班吧

第二天,到公司之后

开发:在群里@设计@产品 :这个只有截图看不出来哪有问题噻

设计:沉默

产品:沉默

开发:...

从开发看效率

开发效率无疑存在于两方面

强健的框架支撑与准确的拆分估期

一般情况下是不会延期的 也算是这几个环节当中最可控的

从测试看效率

你理解一个开发只用了一天的功能测试测了四天的心情吗

你理解一个开发用了一周 测试用了 9 个工作日的心痛吗

🙊

结语:💊

场景一:首页增加下拉刷新功能无效?

经过各种尝试 问题定位到了是因为 在 scroll-view 中使用了 onPullDownRefresh,最后通过改首页的代码结构达到了预期结果

结论:
在滚动 scroll-view 时会阻止页面回弹,所以在 scroll-view 中滚动,是无法触发 onPullDownRefresh

如果非要在 scroll-view 使用下拉刷新,官方给出的解决方案是监听页面的滚动事件 bindscroll 厄.. 也是一种方法 只不过我的更粗鲁一些

其他需要注意的:

  • 不要在 scroll-view 中使用 textareamapcanvasvideo 组件
  • 目前只验证过 textarea 暂且放到场景二吧
  • scroll-into-view 的优先级高于 scroll-top

场景二:无法在 scroll-view 中使用 textarea?

这是个只有在真机上才会出现的 bug

刚开始以为是不能在某些特定的组件内使用textarea 几番尝试 发现是因为 textarea组件与其他组件的层级关系 后来发现 文档在最下方用最小号的字体给标注出来了已经!吐血

结论:
textarea组件是由客户端创建的原生组件,他的层级是最高的,在实际项目中 要保证本页面中 无
弹层之类一切可能会覆盖到页面的鬼。

其他需要注意的:

  • 不要在 scroll-view 中使用 textarea 组件。
  • css 动画对 textarea 组件无效
  • textarea 的 blur 事件会晚于页面上的 tap 事件,如果需要在 button 的点击事件获取 textarea,可以使用 form 的 bindsubmit。
  • 官方遗留 bug: textarea 在列表渲染时,新增加的 textarea 在自动聚焦时的位置计算错误。

    场景三:textarea 的 placeholder 不固定的问题

当把textarea放到一个 position:fixed 中的元素中时,会发现这个textarea也会跟着固定位置,但是textarea的 placeholder 内容不会固定,当滚动页面时,placeholder 的内容会跟着滚动

解决方式:textarea组件增加属性 fixed

结论:踩坑大法好

场景四:微信小程序的兼容问题

wx.request() 返回的状态码 res.statusCode 的值 在 IOS 下是 init 型的数据 但是在 Android 6.0.1 上却是 String 型数据
要特别注意判断状态码的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
X
wx.request({
url: 'http://api.example.com',
success: function (res) {
if (res.statusCode === 200) {
// success
} else {
// server failure
}
}
})
像这样使用 === 就错了 不要判断类型
使用 ==即可

小程序的 WXML 没有 HTML 的宽容度​那么高,单标签必需是 /> 结尾的 不然会报错。

场景五:小程序版本的兼容问题

无论用哪个组件 必须时刻注意当前兼容的版本 做低版本兼容

1
2
3
4
5
6
7
8
 wx.openBluetoothAdapter()
} else {
// 如果希望用户在最新版本的客户端上体验您的小程序,可以这样子提示
wx.showModal({
title: '提示',
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
})
}

Fundebug 能够实时监测小程序,捕获小程序 Bug,然后反馈给开发者
Fundebug

场景六:小程序版本的兼容问题

绑定事件的时候,当两个方法重名时,后面的方法会覆盖前面的方法,并且 IDE 不报错

场景七:image

小程序的 image 与 HTML5 的 img 最大的区别在于:
小程序的 image 是按照 background-image 来实现的。
默认 image 的高宽是 320*240。必须通过样式定义去覆盖这个默认高宽,auto 在这里不生效。
开发者说这样设置的原因是:如果设置 auto ,页面布局会因为图片加载的过程有一个闪的现象(例如高度从 0 到 height ),所以要求一定要设置一个宽度和高度。

图片包括三种缩放模式 scaleToFill、aspectFit、aspectFill 和 9 种裁剪模式,三种缩放模式的实现原理对应如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
scaleToFill{

background-size:100% 100%;//不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素

}

aspectFit{

background-size:contain;//保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。

}

aspectFill{

background-size:cover;//保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。

}

场景八:navigator

navigator 支持相对路径和绝对路径的跳转,默认是打开新页面,当前页面打开需要加 redirect;
navigator 仅支持 5 级页面的跳转;
navigator 不可跳转到小程序外的链接地址;

1
<navigator class="navigator" redirect  url="../login/index" >登录页</navigator>

在小程序开发工具里,默认打开新页面,工具左上角有返回按钮。加上 redirect,当前页打开,不出现返回按钮。

场景八:大胆使用 flex 布局

在做传统 H5 的时候,为了兼容各种低端设备的机型,通常不太敢轻易尝试 flex,但在小程序里就可以大胆的使用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.media {

display: flex;

justify-content:center;
align-items:center;
}

.media .content {

flex: 1;

}

场景八:大胆使用 flex 布局

在做传统 H5 的时候,为了兼容各种低端设备的机型,通常不太敢轻易尝试 flex,但在小程序里就可以大胆的使用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.media {

display: flex;

justify-content:center;
align-items:center;
}

.media .content {

flex: 1;

}

场景八:页面最后一个 view 组件 设置 margin-bottom 属性 IOS 机型 失效的问题

解决方式:当前 view 当前组件下加一个同级 view

1
2
3
4
5
6
7
8
9
10
11
12
wxml
<view>这是个按钮</view>
<view class="marginB"></view>

css

.marginB{
height: 0rpx;
font-size: 0rpx;
}


场景九:实现长按保存图片 短按图片隐藏

现有问题 用户体验不佳 长按需要抬起才能弹出 sheetAction 菜单

理想状态下是长按开始计时 350 秒之后自动弹操作菜单

利用 bindtouchstartbindtouchend 事件结合 bindtap 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

问题代码一

WXML

<view bindtouchstart="mytouchstart" bindtouchend="mytouchend" bindtap="editAddress" >

JS

editAddress: function (event) {
let that = this;
//触摸时间距离页面打开的毫秒数
var touchTime = that.data.touch_end - that.data.touch_start;
console.log(touchTime);
//如果按下时间大于350为长按
if (touchTime > 350) {

//TODO..

} else {

//TODO..

}
},
//按下事件开始
mytouchstart: function (e) {
let that = this;
that.setData({
touch_start: e.timeStamp
})
console.log(e.timeStamp + '- touch-start')
},
//按下事件结束
mytouchend: function (e) {
let that = this;
that.setData({
touch_end: e.timeStamp
})
console.log(e.timeStamp + '- touch-end')
}


改进

小程序的事件触发顺序

单击 touchstart → touchend → tap
双击 touchstart → touchend → tap → touchstart → touchend → tap
长按 touchstart → longtap → touchend → tap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
问题代码二

wxml

<view bindlongtap="longPress" bindtap="tapFun" style="width: 100%;height:100%" mode="scaleToFill" src="{{img_url}}"></view>

js

longPress(e){
var that = this;
console.log('你点击了长按时间',e)
// 可以通过 wx.getSetting 先查询一下用户是否授权了 "scope.record" 这个 scope
wx.getSetting({
success(res) {
console.log('222222222222222222')
if (!res['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
// 调起操作菜单
console.log('33333333333333333333')
console.log('wx.showActionSheet()',wx.showActionSheet)
wx.showActionSheet({
itemList: ['保存到本地'],
success: function(res) {
console.log('操作菜单弹出成功','第'+res.tapIndex+'个操作菜单弹出成功')
// 下载文件资源到本地。客户端直接发起一个 HTTP GET 请求,返回文件的本地临时路径。
if(res.tapIndex ==0){
console.log('wx.downloadFile',wx.downloadFile)
wx.downloadFile({
url: that.data.img_url, //仅为示例,并非真实的资源
success: function(res) {
wx.playVoice({
filePath: res.tempFilePath
});
console.log('wx.playVoice()',wx.playVoice)
console.log('将图片先保存到本地--res.tempFilePath',res.tempFilePath);
// 用户已经同意小程序使用功能,后续调用 wx.startRecord 接口不会弹窗询问
console.log('wx.saveImageToPhotosAlbum()',wx.saveImageToPhotosAlbum)
wx.saveImageToPhotosAlbum({
filePath:res.tempFilePath,
success(res) {
wx.showToast({
title: '保存成功',
icon: 'success'
});
setTimeout(function(){
wx.hideToast();
},3000)
},
fail(res){
wx.showToast({
title: '保存失败',
icon: 'success'
});
setTimeout(function(){
wx.hideToast();
},3000)
}
})

}
})
}
},
fail: function(res) {
console.log('操作菜单弹出失败',res.tapIndex)
}
})


}
})
}
}
})
},


tapFun: function(event) {
that.setData({
qrShow:false
})
}


很显然 这也是有问题的

因为事件机制。总会触发 tap事件 表现形式比 问题一还难以接受

好了,结合前两种 解决办法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
WXml

<image wx:if="{{img_url !=''}}" bindlongtap="editAddress1" bindtouchstart="mytouchstart" bindtouchend="mytouchend" style="width: 100%;height:100%" mode="scaleToFill" src="{{img_url}}"></image>


js

//按下事件开始
mytouchstart:function(e){
let that = this;
that.setData({
touch_start: e.timeStamp
})
console.log(e.timeStamp + '- touch-start')
},

//按下事件结束
mytouchend:function(e){
let that = this;
that.setData({
touch_end: e.timeStamp
})
console.log(e.timeStamp + '- touch-end')
that.editAddress(e);
},

// 判断是长按操作还是短按操作
editAddress: function(event) {
var that = this;
if(that.data.touch_end - that.data.touch_start < 350){
that.setData({
qrShow:false
})
}


},

editAddress1(e){} // 同问题二 longPress()

场景十:第二次扫码进入小程序的缓存问题

  • 不要改变 props

错误例子:

1
2
3
var component = <Component />;
component.props.foo = x; // bad
component.props.bar = y; // also bad

这样写是错误的,因为我们手动直接添加的属性 React 后续没办法检查到属性类型错误,也就是说,当我们手动添加的属性发生类型错误时,在控制台是看不到错误信息的

在 React 的设定中,初始化完 props 后,props 是不可变的。改变 props 会引起无法想象的后果

正确写法:

1
2
3
4
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;

当需要拓展我们的属性的时候,定义个一个属性对象,并通过 {…props} 的方式引入,React 会帮我们拷贝到组件的 props 属性中。
重要的是 — 这个过程是由 React 操控的,不是手动添赋值的属性

需要覆盖的时候可以这么写

1
2
3
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;

  • React 默认会进行 HTML 的转义如下

输入:

1
2
3
4
5
6
var content='<strong>content</strong>';

React.render(
<div>{content}</div>,
document.body
);

输出:

1
<stonrg>content</strong>

避免转义:

1
2
3
4
5
6
var content='<strong>content</strong>';    

React.render(
<div dangerouslySetInnerHTML={{__html: content}}></div>,
document.body
);
  • 如果在编写时使用了 react 自定义属性 react 是不会渲染的

错误做法:

1
2
3
4
React.render(
<div dd='xxx'>content</div>,
document.body
);

正确做法:

1
2
3
4
5
React.render(
<div data-dd='xxx' aria-dd='xxx'>content</div>,
document.body
);

require.js

出现背景:所有的 javascript 代码都在一个文件中,代码越来越多时必须分成多个文件,依次加载,问题:加载 js 的时候浏览器停止渲染,加载文件越多,网页的响应时间就越长,由于 js 之间有依赖关系,因此必须严格保证加载顺序,当依赖关系变的复杂时,代码的编写和维护都会变的异常困难

  1. 加载 require.js
1
2
3
4
5
6
7
8
9
10
11
<script src="js/require.js" defer async='true'></script>
async 表明这个文件需要异步加载
在require.js的基础上加载自己的 main.js
<script src="require.js" data-main="js/main"></script>
ata-main:指定程序的主模块,这个人间会第一个被require.js加载,由于require.js默认的文件后缀名是js,所以可以把main.js 简写成main

//main.js

require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
    // some code here
  })``
  1. 模块的加载
    当加载不同路径下的模块可以使用 require.config () 可以对模块的加载进行自定义, require.config () 就写在主模块 (main.js) 的头部,参数就是一个对象,这个对象的 path 属性指定各个模块的加载路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  require.config({
    paths: {
      "jquery": "lib/jquery.min",
      "underscore": "lib/underscore.min",
      "backbone": "lib/backbone.min"
    }
  });
  
  另一种形式
  
   require.config({
    baseUrl: "js/lib",
    paths: {
      "jquery": "jquery.min",
      "underscore": "underscore.min",
      "backbone": "backbone.min"
    }
  });
  
  再或者
   require.config({
    paths: {
      "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
    }
  });
  1. AMD 模块的写法
    require.js 加载的模块采用 AMD 规范
    具体来说,就是模块必须采用特定的 define () 函数来定义,如果一个模块不依赖其他模块,那么可以直接定义在 define 函数中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//math.js 定义了一个math模块
define(function(){
var add = function(){
return x+y;
};

return {
add:add
}
})

加载方法:

// main.js
  require(['math'], function (math){
    alert(math.add(1,1));
  });
  
  如果这个模块还依赖其他模块,那么那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。
   define(['myLib'], function(myLib){
    function foo(){
      myLib.doSomething();
    }
    return {
      foo : foo
    };
  });
  当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。

  1. AMD 模块的写法
    加载非规范的模块
    理论上 require.js 加载的模块,必须是按照 AMD 规范、用 define () 函数定义的模块
    加载非规范模块,必须先用 require.config () 方法定义它们的一些特征
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
eg:加载非AMD规范模块 underscore,backbone
  require.config({
    shim: {

      'underscore':{
        exports: '_'
      },
      'backbone': {
        deps: ['underscore', 'jquery'],
        exports: 'Backbone'
      }
    }
  });
  
  shim属性:专门用来配置不兼容的模块。具体来说,每个模块要定义
  (1)exports值(输出的变量名),表明这个模块外部调用时的名称;
  (2)deps数组,表明该模块的依赖性。
  
  eg:
   jQuery的插件可以这样定义:
    shim: {
    'jquery.scroll': {
      deps: ['jquery'],
      exports: 'jQuery.fn.scroll'
    }
  }
  1. require 插件
    domready 插件,可以让回调函数在页面 DOM 结构加载完成后再运行。

     

    1
    2
    3
    4
    require(['domready!'], function (doc){
        // called once the DOM is ready
      });
      

text 和 image 插件,则是允许 require.js 加载文本和图片文件。

1
2
3
4
5
6
7
8
9
10
define([
    'text!review.txt',
    'image!cat.jpg'
    ],

    function(review,cat){
      console.log(review);
      document.body.appendChild(cat);
    }
  );

类似的插件还有 json 和 mdown,用于加载 json 文件和 markdown 文件

  1. 原始写法
1
2
function m1(){}
function m2(){}

缺点:污染了全局变量,容易与其它模块发生命名冲突,而且模块之间看不出直接关系

  1. 对象写法
1
2
3
4
5
var moudle = new Object({
_count = 0;
m1:function(){},
m2:function(){}
})

缺点:会暴露所有模块成员,内部状态可以被外部改写

  1. 立即执行函数的写法 (达到不暴露私有成员的目的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var moudle1 = (function(){
var count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};

return {
m1:m1,
m2:m2
}
})();

moudle1 就是 javascript 模块的基本写法

  1. 放大模式
    背景:如果一个模块很大必须分为几个部分,或者一个模块需要继承另外一个模块时
1
2
3
4
5
6
var moudle1 = (function(mod){
mod.m3 = function(){
//...
}
return mod;
})(moudle1);

上面的代码为 moudle1 添加了一个新方法 m3,然后返回新的 moudle1 模块

  1. 宽放大模式
    背景:在浏览器环境中,模块的各个部分都是从网上获取的,有时候无法知道哪个部分会先加载,如果单纯采用放大模式,第一个执行的 部分有可能加载一个不存在的空对象
1
2
3
4
5
6
var moudle1 = (function(mod){
mod.m3 = function(){
//...
}
return mod;
})(window.moudle1 || {});
  1. 输入全局变量
    背景:保持模块独立性,内部最好不要与程序的其他部分直接交互
1
2
3
var moudle1 = (function($,YAHOO){
//...
})(jQuery, YAHOO);

保持独立的同时,模块的依赖关系变的更明显

  1. 模块的规范
    CommonJS 和 AMD
    CommonJS:nodejs 的模块系统,是参照 CommonJS 规范实现的,在 CommonJS 中,有一个全局方法 require (),用于加载模块
1
2
3
4
eg:
var math = require('math');
调用math模块提供的方法:
math.add(2,3); // 5
  1. 浏览器环境
    局限使 CommonJS 规范不适用于浏览器环境
1
2
var math = require('math');
math.add(2,3); // 5

在浏览器中运行,第二行在第一行之后运行,也就是说必须得等到 math 模块加载完成,如果加载时间很长,整个应用都会停在那里等,对于服务器端来说,所有模块都放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间,但是对于浏览器,这就是致命的问题,取决于网速,
所以,浏览器端的模块不能采用同步加载,要采用异步加载

  1. AMD
    ‘异步模块定义’,采用异步方式加载模块,所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,回调函数才执行
    AMD 也采用 require () 语句加载模块,不同于 CommonJS,它要求两个参数  
1
2
3
4
require([module], callback)
require(['math'], function (math) {
    math.add(2, 3);
  });

工厂模式
解决了重复实例化的问题

1
2
3
4
5
6
7
8
9
10
11
12
function createObject(name,age){
var obj = new Object();
obj.name= name;
obj.age = age;
obj.run = function(){
return this.name + this.age + '运行中'
}
return obj;
}

var box1 = createObject(‘lee’,100)
var box2 = createObject(‘jack’,200)

工厂模式的缺点:
无法区分实例是哪个对象的实例

构造函数模式

1
2
3
4
5
6
7
8
9
function Box(name,age){
this.name= name;
this.age = age;
this.run = function(){
return this.name + this.age + '运行中'
}
}
var box3 = new Box(‘lee’,100)
var box4 = new Box(‘jack’,200)

如何识别了对象?
构造函数没有 new Object,但是它后台回自动 var obj = new Object ();
this 指的就是 obj
没有返回值

console.log(box4 instanceof Box)

对象冒充
把 o 冒充成 box 对象

1
2
var  o = new Object();
Box.call(o,'Lee',100)

原型

prototype 原型属性是一个对象

1
function Box(){}

这里如果有属性或者方法 叫做实例属性和实例方法

1
2
Box.prototypr.name 原型属性
Box.prototypr.run=function(){} 原型方法

_proto_: 实际上是一个指向原型对象的一个指针,它的作用就是指向构造函数的原型属性 constructor

1
2
3
var box1 = new Box()
box1.constructor 指向构造函数
box1._proto_指向原型对象

判断一个对象实例是不是指向了对象的原型对象,实例化之后是自动指向的

1
Box.prototype.isPrototypeOf(box1)

什么叫闭包? 有什么用

闭包是指有权访问另一个作用域中的变量和函数,常见的形式是在某个作用域中定义的函数

闭包的作用域链包括三部分:

  1. 函数本身作用域
  2. 闭包定义的作用域
  3. 全局作用域

闭包的常见用途?

  1. 读取函数内部的变量
  2. 将变量始终保持在内存中
  3. 模拟面向对象的代码风格

匿名执行函数

不加 var 关键字 默认回呗添加到全局对象的属性中去,类似的临时变量的属性加入全局对象有很多坏处
比如:
别的函数可能误用这些变量 造成全局变量过于庞大,影响访问速度(因为变量的取值是需要从圆形链上遍历的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var datamodel = {    
table : [],
tree : {}
};

(function(dm){
for(var i = 0; i < dm.table.rows; i++){
var row = dm.table.rows[i];
for(var j = 0; j < row.cells; i++){
drawCell(i, j);
}
}

//build dm.tree
})(datamodel);

创建了一个匿名的函数并立即执行它,由于外部无法引用它内部的变量,因此在执行之后很快就会被释放 不会污染全局对象

缓存

设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,
那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。

实现封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var person = function(){    
//变量作用域为函数内部,外部无法访问
var name = "default";

return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();

print(person.name);//直接访问,结果为undefined
print(person.getName());
person.setName("abruzzi");
print(person.getName());

数组的扩展
Array.form () 将两类对象转换为数组
1. 类似数组的对象
2. 可遍历的对象

1
2
3
4
5
6
let arrayLike = {
'0':'a',
'1':'b',
'2':'c',
'3':'d'
}

ES5 写法

1
var arr1 = [].slice.call(arrayLike)

ES6 写法

1
let arr2 = Array.form(arrayLike)

任何有 length 属性的对象,都可以通过 Array.from 方法转为数组,而此时扩展运算符就无法转换。

值得提醒的是,扩展运算符(…)也可以将某些数据结构转为数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// arguments对象
function foo() {
var args = [...arguments];
}

// NodeList对象
[...document.querySelectorAll('div')]

Array.from还可以接受第二个参数,作用类似于数组的map方法

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

Array.of()
Array.of方法用于将一组值,转换为数组。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10

fill 方法使用给定值,填充一个数组。

1
['a', 'b', 'c'].fill(7)

ES6 提供三个新的方法 ——entries()**,keys() values ()**—— 用于遍历数组

1
[1, 2, 3].includes(2);     // true

for of 循环

ES6 引入 rest 参数(形式为 “… 变量名”),用于获取函数的多余参数

扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(…[1, 2, 3])
// 1 2 3

函数的 name 属性,返回该函数的函数名。

1
2
function foo() {}
foo.name // "foo"

(1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。

对象的扩展
var bax= {bac} = var baz = {bac:bac}

1
2
3
4
5
6
var o = {
method:function(){}
} ==
var o = {
method(){}
}

let propKey = ‘foo’;

let obj = {
[propKey]: true,
[‘a’ + ‘bc’]: 123
};

const person = {
sayName() {
console.log(‘hello!’);
},
};

person.sayName.name // “sayName”

Object.is()用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

1
2
3
4
5
6
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

不同之处只有两个:一是+0不等于-0,二是NaN等于自身

Object.is(+0, -0) // false

Object.is(NaN, NaN) // true

Object.assign () 实行的是浅拷贝不是深拷贝

如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);

obj1.a.b = 2; 如果值更改 则拷贝之后的值也是变化的
obj2.a.b // 2

如果该参数不是对象,则会先转成对象,然后返回。

typeof Object.assign(2) // "object"

Object.assign可以用来处理数组,但是会把数组视为对象。

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

Object.assign(someClass.prototype,{
someMethod(){
...
},
antherMethod(){
...
}
})

===
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};

克隆对象
只能可通他自身的值而不能可通它继承的值

1
2
3
function clone(origin){
return Object.assign({},origin)
}
1
2
3
4
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
1
2
3
4
5
6
7
8
9
Object.getOwnPropertyDescriptor 获取该属性的描述对象
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }

Object.keys 方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

1
2
3
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

屏幕快照 2017-02-25 下午12.58.09

1
2
3
var obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
1
2
3
4
var obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

ES6 中遍历对象的属性

for…in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
Object.keys 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)。
Object.getOwnPropertyNames 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)
Object.getOwnPropertySymbols 返回一个数组,包含对象自身的所有 Symbol 属性。

__proto__属性(前后各两个下划线),用来读取或设置当前对象的 prototype 对象。目前,所有浏览器(包括 IE11)都部署了这个属性。

1
2
3
4
5
// es6的写法
var obj = {
method: function() { ... }
};
obj.__proto__ = someOtherObj;
1
2
3
4
5
6
7
8
9
10
11
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);

proto.y = 20;
proto.z = 40;

obj.x // 10
obj.y // 20
obj.z // 40
上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。

ES6 引入的原始类型 “
Symbol
凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
Symbol 函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象
由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
Symbol 值不能与其他类型的值进行运算,会报错。
但是,Symbol 值可以显式转为字符串。
另外,Symbol 值也可以转为布尔值,但是不能转为数值。

let s = Symbol();

typeof s

数组去重

新增的数据结构 Set Map

1
2
3
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

1
2
var set = new Set([1, 2, 3, 4, 4]);
[...set]

// 去除数组的重复成员
在 Set 内部,两个 NaN 是相等。

1
2
3
[...new Set(array)]
var set = new Set([1,1,2,2,3,4,5,5,6])
[...set]
1
2
3
4
let map = new Map([
['F', 'no'],
['T', 'yes'],
]);
1
2
3
for (let key of map.keys()) {
console.log(key);
}

Proxy(代理器) 元编程(对编程语言进行编程)

在目标对象之前设置一层拦截 外界对它的访问必须先通过这一层拦截

1
2
3
4
5
6
7
8
9
10
11
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},

set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});

重写了 get 和 set 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
var proxy = new Proxy(target, handler);


var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

Promise (承诺)

所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var promise = new Promise(function(resolve,reject){

if('异步操作成功'){
resolve(value)
}else{
reject(error)
}
})


promise.then(function(value){
成功
},function(error){
失败
})

遍历器对象本质上,就是一个指针对象。

回调函数
事件监听
发布 / 订阅
Promise 对象

Generator 函数
yield 表示执行到此处执行权将交给其它协程也就是说 yield 命令部两个阶段的分界线

Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象

1
2
3
4
5
6
7
8
function* gen(x){
try{
var y = yield x +2;
}catch(e){
console.log(e)
}
return y
}
1
2
3
4
5
6
7
var fetch = require('node-fetch');

function* gen(){
var url = 'http://api.github.com/users/github';
var result = yield fetch (url);
console.log('ds')
}

Thunk 函数是自动执行 generator 函数的一种方法

传名调用

1
2
3
 f(x + 5)
// 传名调用时,等同于
f(x + 5)

传值调用

1
2
3
 f(x + 5)
// 传值调用时,等同于
f(6)

Thunk 是传名调用的实现,将参数放到一个临时的函数中,再将这个临时函数传入函数体

这个临时函数叫做 Thunk 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var x = 1;
function f(m){
return m*2;
}

f(x+5)

等同于

var thunk = function(){
return x+5
}

function (thunk){
return thunk()*2
}

async :Generator 函数的语法糖

1
2
3
4
5
6
var asyncReadFile = async function () {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

async 🌧️generator 比较

  1. async 有内置执行器 不需要调用 next 方法
    拥有更好的语义(比起星号和 yield 语义更清楚了)
  2. async 函数返回的是 promise 对象比 Generator 函数返回值是 Iterator 对象方便多了 可以用 then 方法指定下一步操作

async 和 await

  • async 表示函数中有异步操作
  • await 表示紧跟在后面的表达式需要等待结果
  • async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖。
  • async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
}

async function main() {
try {
var val1 = await firstStep();
var val2 = await secondStep(val1);
var val3 = await thirdStep(val1, val2);

console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}

async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}


class

基本形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point{

constructor(x,y){

this.x = x;
this.y = y

}


toString(){
return '(' + this.x + ', ' + this.y + ')';
}
}

Class 不存在变量提升(hoist),这一点与 ES5 完全不同。

1
2
3
4
5
const MyClass = class Me {
getClassName() {
return Me.name;
}
};

类名是 MyClass 而不是 me Me 只在 Class 的内部代码可用,指代当前类。

const MyClass = class { /* … */ };

模块加载方案

  • CommonJs 应用于服务器

  • AMD 应用于浏览器

    ES6 提供模块功能 尽量的静态化)

1
import {stat,exut,readFile} from 'fs'

Es6 模块是编译时加载
ES6 的模块自动采用严格模式

1
2
function  f(){}
export {f}
1
2
import {lastName as surname} from './profile'
import命令具有提升效果
1
2
3
4
5
6
导出
export default

引入
import * as circle from './circle'; 模块的整体加载

使用 import 命令的时候用户不需要知道所加载的变量名或者函数名 用这个语法可以为模块指定默认输出

1
2
3
export default function (){
console.log('foo')
}

引入的时候 import 可以为该匿名函数指定任意的名字,这个时候 import 的后面不使用大括号

1
2
3
4
5
6
7
8
9
10
11
12
import customName from  ..

export default function ee (){
console.log('foo')
}
==

function foo (){
console.log('ee')
}

export default foo;

foo 的函数名 foo 在模块外部时无效的 视同匿名函数加载

使用 export 时,对应的 import 语句需要使用大括号。

使用 export default 对应的 import 语句不需要大括号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 // 第一组
export default function crc32() { // 输出
// ...
}

import crc32 from 'crc32'; // 输入

import { default as xxx } from 'modules';

// 第二组
export function crc32() { // 输出
// ...
};

import {crc32} from 'crc32'; // 输入

本质上,export default 就是输出一个叫做 default 的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。

import { default as xxx } from ‘modules’

1
export {add as default};

// 等同于

1
// export default add;

ES6 模块

1
2
<script type="module" src="foo.js"></script>

都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了