三分钟了解内存管理

本文由 六不准 翻译。
原文出处:A crash course in memory management

此篇文章是以下系列文章中的第一篇:

如果你想了解 Javascript 引入 ArrayBuffer 和 SharedArrayBuffer 的原因,那么你最好知道一些内存管理的知识。

你可以把内存想象成一些盒子。它们可以是办公室里面的快递箱,也可以是小宝贝们装玩具的盒子。

如果你想把玩具留给其他小朋友玩,那么你可以先把这个玩具放进这个盒子里。

image

每个盒子有一个编号,那就是内存地址。有了这个编号,其他人就可以根据它来找到这个盒子。

这些盒子都一样大小,可以存储一定容量的信息。盒子的大小是根据计算机来确定的,平时我们称它为字长。通常字长为 32-bits or 64-bits。在这篇文章,为了更直观的展示,我用 8-bits(位)来表示。

image

如果我们想把数字 2 存到这些盒子里,非常简单,把数字转换成二进制即可。

image

那么问题来了,如果我们想把一个字母存进区,比如字母H,该怎么办呢?

答案是,我们可以用数字的方式来代表字母,所以我们需要用 UTF-8 等编码格式对字母进行编码。然后,再转换成相应的数字,原理就像下面的编码转盘一样。这样我们就可以存储一个字母啦。
image

当我们想把已编码的 H 从盒子里面取出来的时候,得需要一个解码器将其转换回字母 H 。

自动化的内存管理

一般情况下,当你写 JavaScript 代码的时候,你并不需要考虑到内存,更不需要直接操作内存,那谁在帮你完成呢?答案是:JS引擎。它会在你和内存之间搭起一座桥梁,帮助你管理内存。

image

举个例子,当我们用 React 来创建一个变量的时候。

image

JS 引擎会将变量进行编译,将其转化成二进制方式。

image

并且 JS 引擎会在内存中找到一个区域用来存储编译好的变量。通常这个过程叫内存分配。

image

内存分配结束以后,JS 引擎还会持续监听这个变量访问情况。如果这个变量不再被访问,那么内存会将这个变量占有的空间清空,清空以后的空间可以用来存放新的变量。

image

JS 引擎处理将各种类型的变量(字符串,对象和其他类型的值)分配进内存,当这些变量不再被访问并且将其从内存中清空的过程,我们称之为垃圾回收。

像 JavaScript 不直接操作内存的这类语言,通常我们称它们为内存自动管理语言。

这种自动化的内存管理的机制对开发者来说更为简单方便。但是这种方式也有不足的地方,那就是会加大内存开销,有时候而这些开销会对代码性能产生不可预知的影响。

手动内存管理

手动调度和自动管理内存的语言有较大区别。举个例子,让我们来看下,如果用 C 语言编写的话,React会如何工作?(现在可以WebAssembly来实现哟)

C 语言没有 JavaScript 在内存中的抽象层。相反,你直接操作内存,比如读写操作。

image

当你编译 C 或者 WebAssembly 语言的时候,我们使用的工具会加入一些辅助代码。比如,用来编码或者解码。这些代码我们通常称之为运行环境。运行环境对于 C 或者 WebAssembly 的作用类似于 JS 引擎对 Javascript 作用.

image

手动调度内存的语言,有一个特性,那就是运行环境不包含垃圾回收机制。

但是不要太担心,那并不是意味着开发者只能靠自己,什么事情都得亲历亲为。事实上,你仍然可以从运行环境中得到一些帮助。比如,C 语言中,开发环境会一直监控内存的使用状态,将空闲的空间提供给新的变量。

image

在代码过程中,你可以使用 函数 malloc (memory allocate 简称),要求运行环境从内存空闲列表中找到适合存储你数据的地址。内存空闲列表会把刚占用的区域地址删除掉。当这些数据使用完毕,你又可以调用 deallocate 方法,将占用的内存释放。这个时候内存空闲列表又会加上刚刚占用的区域。

写代码过程中,什么时候调用分配以及回收内存这些函数,因为都是开发者自己决定的,所以我们称之为手动内存管理。

作为开发者,搞清楚什么时候去清理内存不同区域是一件很困难的事情。如果你在错误的阶段去清理,很有可能会导致 bug 甚至一些安全漏洞。反过来,要是不清理,又会导致内存泄漏。

这就是为什么很多现代语言都用自动化内存管理来避免一些人为的错误。但是这种方式会产生额外开销影响到性能。这个点会在下一篇文章来详细介绍。

关于作者 Lin Clark

Lin 是 Mozilla 的一名工程师。她的工作领域有:JavaScript,WebAssembly,Rust,和 Servo,当然还会画一些可爱形象的关于代码的漫画。

code-cartoons.com

@linclark

作者的更多文章…

响应式设计的未来——Flexbox

在CSS中,浮动几乎代替了表格用来服务于布局,做为替身,比表格好用得多。但他们仍然有限。现在有一个建议的解决方案称为CSS Flexible Box布局模块,一般称为Flexbox。

Flexbox是CSS3推荐方法,用来帮助设计师更好的控制页面的元素,在没有比较好的解决方案之前,Flexbox要比其他现代布局方案要更强。

我们仍然没有大规模使用Flexbox, 是因为它至今还是受到限制的。如果Flexbox得到浏览器友好支持的时候,加上媒体查询的弹性布局会使Web设计师在Web布局上带来革命性的变化。

CSS的痛楚

在写这篇教程时,支持Flexbox的浏览器还是有限的。Chrome浏览器需要添加其自身的前缀“-webkit-”才能识别。其他的Webkit内核浏览器,特别是移动端的Safari和Android浏览支持都是仅仅有限的。而微软IE只有IE10。不过这个Flexbox规范本身是在不断变化,在过去的几年里看到有关于他的许多变化。但随着规范的定案和浏览器后期的开发,Flexbox终将成为一个很重要的属性——用于设计上的布局。下面主要介绍它在响应式设计中应用。

容器(Containers)

一个Flexbox布局需要创建一个包含元素的容器,比如一个“

”,用来包裹一些项目。最基本的,Flexbox会让其伸缩项目放在一个线上(主线、侧线),不需要修改HTML结构就能实现其同一基线上的任意摆放。如下面的示例:著作权归作者所有。

1
2
3
4
5
<div>
<p>1</p>
<p>2</p>
<p>3</p>
</div>

flexbox-flex-direction

在上面的示例中,三个伸缩项目可以通过“flex-direction”属性使用其在伸缩容器中放置在第一和最后,也可以是水平或垂直摆放。在一个web页面中,这意味着,在不改变HTML结构情况之下整个布局可能会重安排。这将是一个名副其实的响应式设计。

当使用“display:block”时元素变成块形式垂直摆放(在不浮动情况之下),“display:inline”会让元素包裹文本保持在同一行,而“display:flex”可以让设计师改变方向、顺序、对齐方式和间距。从列表布局角度出处,Flexbox布局适用于任何系列的产品。Flexbox可用的用途包括:

  • 快速调整列、页眉、页脚和侧边栏位置
  • 自动计算导航间距
  • 意味着更少的HTML使用CSS实现更多的布局
  • 鉴于随机物品在同一个系列,使用.important在顶部随意移动显示。

最重要的是要注意Flexbox对设计的视觉风格的影响。重新排列元素在屏幕上显示的次序并不会影响他们的HTML结构与Javascript的框架或屏幕阅读器的阅读。

对齐(ALIGNMENT)

除了方向,伸缩项目有可能会推到伸缩容器的开始位置(全部靠左)、结束位置(全部靠右)、居中以及有一个的间距。可以在伸缩容器上添加“justify-content”属性来控制伸缩项目之间对齐方式:开始、结束等。

flexbox-justify

上图在伸缩项目排列中隐含的线称为“主轴(main axis)”。这里的示例让导航的链接沿着主轴线(显示的黑线),根据需要自动改变空间。

每个主轴都有一个方向(flex-direction)和一个流(flex-fow):水平方向的有向左或向右(row或row-reverse);垂直方向的有向下和向上(column和column-reverse)。上面例子中的第一个设置了flex-row为row,意思是“flex-start”让伸缩项目向左边推。当flex-row设置为“row-reverse”时,像最后一个示例所示,“flex-start”推到伸缩容器最右边。

包裹(WRAPPING)

当一系列的伸缩项目到达伸缩容器的终点位置时,可以让伸缩项目在一行显示或者多行显示,你就传统的内联元素一样。

flexbox-flex-wrap

上面的属性应用到伸缩容器上。其他的适用于每个伸缩项目。

顺序(ORDER)

最后一个主要属性(有关于响应式设计方面)用来控制元素出现的精确顺序。著作权归作者所有。

flexbox-flex-order

默认情况之下,伸缩容器中的每一个伸缩项目都有一个“order:0”属性。给其中一个伸缩项目设置“order:1”,会让这个伸缩项目沿主轴向右(侧轴向下)推动。如果设置一个伸缩项目的order值小于0,例如:“order:-1”,会让这个伸缩项目沿主轴向左(侧轴向上)推动。

Flexbox的侧轴具有和主轴一样的属性功能。到目前为止,影响响应式设计的主要属性是“flex-direction”、“flexbox-justify”和order。

Flexbox和Media Queries

响应式设计需要在不同屏幕尺寸具有不同的布局。不幸的是,一些布局要求HTML具有一个特定的方式,比如说图片在标题前,或者图片在标题后。Flexbox可以通过CSS来控制更多的移位,这样网页设计师就可以通过媒体查询在不同的屏幕大小中应用不同的配置。

这里有一个挑战性的案例:一个新闻网站想要展示其最新的文章片段。每个片段都有一个缩略图和不同数量的文本。需要有三种布局风格:桌面宽屏;笔记本的中等大小屏和智能手机的320px宽。可访问性,它必须保持导航在或者接近网站的标题,但出版本商想尽可能的突出大标题显示。

flexbox-flex-direction

HTML结构

为了结构的清晰,这个示例使用了简单的HTML结构:

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
<html>
<body>
<div class="wrapper">
<header>
<img src="images/trendy-logo.png" />
</header>
<nav>
<a href="#">home</a>
<a href="#">about</a>
<a href="#">other</a>
<a href="#">contact</a>
</nav>
<section>
<article>
<figure><img src="images/box.png" /></figure>
<div>
<h2>Sample headline goes here</h2>
<p>Sample text sample text sample text sample text sample text sample text sample text </p>
</div>
</article>
<article></article>
</section>
</div>
</body>
</html>

有一些重要的事项:

  • 一个<div>容器包裹了整个页面。
  • 标题和导航链接在文章列表区域<section>前面
  • 每一篇文章都是一个独立的章节放在<section>里面。

Media Queries条件

我们设置了三个响应式断点:窄屏、中屏和宽屏。

1
2
3
4
5
6
7
8
9
@media only screen and (max-width: 480px) {
/* 窄屏 */
}
@media only screen and (min-width: 481px) and (max-width: 960px) {
/* 中屏 */
}
@media only screen and (min-width: 961px) {
/* 宽屏 */
}

窄屏:交替图标和重排结构

1、首先指定每个<article>元素作为伸缩容器。在这些例子中,我们使用-webkit-前缀来支持Chrome。

1
2
3
4
5
article {
display: -webkit-flex;
-webkit-align-items: start;
}

2、接下来为所有的文章列表设置方向:

1
2
3
4
5
6
article:nth-child(odd) {
-webkit-flex-direction: row;
}
article:nth-child(even) {
-webkit-flex-direction: row-reverse;
}

3、把内容优先于导航显示,我们需要把“.wrapper”设置为伸缩容器,并设置伸缩流的方向为“column”:

1
2
3
4
5
.wrapper {
display: -webkit-flex;
-webkit-flex-direction: column;
}

4、最后,把<nav>元素也设置成伸缩容器,使用“justify-content”属性设置导航链接之间的间距工:

1
2
3
4
5
6
nav {
-webkit-order: 999;
display: -webkit-flex;
-webkit-justify-content: space-around;
}

设置了“order”为999是为了过度的布局,而没有设置二到八这样的数值,其实任何正数都可以工作。但设置999是为了应对未来order的变化,999是一个安全的数值,但有一些网站将会超过导航998节。

中屏:一个纵列

浏览器在480px到800px之间宽,可能有足够的高度连续展示文章列表供用户阅读。这个布局,我们要做到的就是这一点:

1、像窄屏的布局一样,将导航栏变成弹性布局,并给导航链接留有一定的空间:

1
2
3
4
5
6
nav {
display: -webkit-flex;
-webkit-flex-direction: row;
-webkit-justify-content: space-between;
}

2、Flexbox布局不需要花哨,这个例子演示的了如何让Flexbox制作一个等宽列,可用来代替以前那种依靠浮动和清除浮动的布局

1
2
3
4
5
6
article {
display: -webkit-flex;
-webkit-justify-content: start;
-webkit-flex-direction: row;
}

宽屏:使用额外的空间

宽屏的布局主要利用“.wrapper”伸缩容器的空间,把文章列表像tiling一样的铺置。

1、把“.wrapper”变成伸缩容器,将把导航放在左边:

1
2
3
4
5
.wrapper {
display: -webkit-flex;
-webkit-flex-direction: row;
}

如果想把导航放在右边,可以使用“row-reverse”来替代“row”。

2、标题和导航叠加是个很棘手的事情,因为我们把”.wrapper”变成了伸缩容器,同时也使标题、导航和section的文章列表变成了水平展示。意思就是标题、logo和左边的导航叠加在一列中。

解决这样的问题,我们要有一点手法:使用一个负的margin-left和padding-top,将导航拉到logo的下面。

1
2
3
4
5
6
7
8
9
10
11
12
nav {
width: 25%;
margin-left: -50px;
}
nav a {
display: block;
padding: 10px 0;
}
nav a:first-child {
padding-top: 120px;
}

3、文章列表要像Tile一样展示,需要给section设置成伸缩容器,然后缩小让其放置在同一行

1
2
3
4
5
6
7
8
9
10
section {
display: -webkit-flex;
-webkit-flex-wrap: wrap;
-webkit-align-items: flex-start;
}
article {
width: 180px;
margin: 10px;
}

结果:三种不同的布局,利用空间来实现响应式布局是最好的。

下一步

虽然Flexbox布局还不能得到广泛的支持,但后结随着规范的落定和各大浏览器厂商的支持力度提高,Flexbox将会成为CSS布局的好工具。

英文原文地址

中文译文地址

理解浏览器关键渲染路径正确姿势

当浏览器接收到服务器返回的HTML响应,并且把像素展示在屏幕上之前,需要经过很多步骤。浏览器渲染的这一系列步骤,我们称之为“关键渲染路径”

如果你对CRP有所了解,那么它对如何提高网站的性能有很大的帮助。CRP有如下6个阶段

  • 构建DOM树
  • 构建CSSOM 树
  • 运行JavaScript
  • 创建渲染树
  • 生成布局
  • 绘制

CRP-Sequence-Copy

1. 构建DOM树

DOM(文档对象模型)树是一个表示HTML页面的对象。它从根元素<html>开始,然后根据每个元素/文本创建节点。嵌套在元素内的元素表示为子节点,每个节点包含该元素的完整属性。比如,<a>元素在其节点上将会存在href属性。

比如接下来的这个HTML页面结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<title>Understanding the Critical Rendering Path</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Understanding the Critical Rendering Path</h1>
</header>
<main>
<h2>Introduction</h2>
<p>Lorem ipsum dolor sit amet</p>
</main>
<footer>
<small>Copyright 2017</small>
</footer>
</body>
</html>

以上页面结构将会被构建成如下DOM树

DOM

HTML的一个好处是它可以部分执行。 内容开始出现在页面上时候,不一定要求页面加载完毕。 但是,对于其他资源,例如(CSS和JavaScript)会阻碍页面的呈现。

2. 构建CSSOM树

CSSOM(CSS对象模型)树是一个表示页面相关样式的对象。它和DOM呈现方式类似,对于每个节点的关联样式,无论明确声明还是隐性继承,都会包含在内。

上面HTML结构中的style.css文件,包含以下样式

1
2
3
4
5
6
7
8
9
10
body { font-size: 18px; }
header { color: plum; }
h1 { font-size: 28px; }
main { color: firebrick; }
h2 { font-size: 20px; }
footer { display: none; }

以上样式将会被构建成如下CSSOM树

CSSOM

CSS因为其“渲染阻塞型资源”特性。这意味着以下的渲染树在没有解析完成之前是不能构建的。不像HTML,CSS因为其继承级联关系,所以是不能被部分执行的。后面定义的样式会覆盖和改变之前定义的样式。如果在页面没有完全渲染之前,我们只用之前定义好的样式,那么页面将展示不正确。所以CSS必须要被完全解析才能进入下一个阶段。

只有被当前设备应用的CSS文件才被视为阻止渲染。 <link rel =“stylesheet”>标签可以接受媒体属性,用于表明在什么设备下加载哪些CSS文件。 例如,如果我们有一个样式表,其媒体属性为orientation:landscape,如果我们正在纵向模式下查看该页面,则该CSS文件不会被视为渲染阻塞。

CSS也可以是“脚本阻塞”。 这是因为JavaScript文件必须等到CSSOM已经构建,然后才能运行。

3. 运行JavaScript

JavaScript 也被视为“渲染阻塞型资源”。那意味着JavaScript会阻塞HTML的解析。

当解析器遇到<script>标签,不管是页面内还是外部引用,HTML渲染就会停止,并且读取JavaScript并执行。这就是为什么,如果我们的JavaScript文件需要引用HTML的元素,它必须放在该文档解析以后。

为了避免JavaScript被解析器阻塞,可以通过设置异步加载async属性来加载它。

4. 创建渲染树

渲染树是DOM和CSSOM的集合。它是页面最终渲染结果的树状结构。这意味着它只会捕获页面可见的内容,比如,如果一个元素CSS样式设置成display: none,那么这个元素是不会被捕获的。

根据上面提到的DOM和CSSOM,他们的渲染树将是这样的

Render-Tree

5. 生成布局

布局是根据视图窗口大小确定的,CSS样式渲染的环境也是依赖它,例如百分比或视图单位。 视图窗口大小由文档头中提供的标签确定,或者如果未提供标签,则使用默认的视口宽度980像素。

例如,最常见的视图窗口值是对应于设备宽度 -

1
2
<meta name="viewport" content="width=device-width,initial-scale=1">

如果用户访问设备上宽度为1000像素,则大小将基于该单位。 一半的视图窗口将是500px,10vw将是100px,等等。

6. 绘制

最后,在绘制步骤中,页面的可见内容可以被转换为要在屏幕上显示的像素。

绘制步骤花费的时间取决于DOM的大小,以及应用的样式。 一些样式需要比其他样式花更多的时间来执行。 例如,复杂的渐变背景图像将需要比简单的实心背景颜色更多的时间。

步骤汇总

要查看页面的关键渲染路径,我们可以在Chrome DevTools中查看它。它位于“Timeline”标签下(在Canary版本下,此版本很快会成为Chrome的稳定版,已重命名为Performance)。

再一起看下上面完整的例子(带<script>标签)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>
<head>
<title>Understanding the Critical Rendering Path</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Understanding the Critical Rendering Path</h1>
</header>
<main>
<h2>Introduction</h2>
<p>Lorem ipsum dolor sit amet</p>
</main>
<footer>
<small>Copyright 2017</small>
</footer>
<script src="main.js"></script>
</body>
</html>

在页面加载时如果我们查看Event Log,我们将看到

Timeline

  • 发送请求 - 发送获取index.html请求
  • 解析HTML和发送资源请求 - 开始解析HTML和构建DOM。发送获取style.css和main.js的请求
  • 解析样式 - 根据style.css文件创建CSSOM
  • 执行脚本 - 执行main.js
  • 生成布局 - 根据meta里面的视图窗口生成布局
  • 绘制 - 以像素的形式绘制页面

基于以上这些信息,我们就可以决定如何优化关键渲染路径。 我将在后面的文章中介绍一些优化技巧。

原文地址

六不准的微信小程序之如何创建一个Tabs

最近用微信小程序做了一个网络图片文件夹,里面用了一些官方提供的接口和组件,自己也实现了一些官方组件之外的小功能。

比如本文将介绍移动端布局中比较常用的Tabs布局是如何实现的。

需求

  • 图片文件夹界面Tabs作为布局的导航。
  • 每个Tab选中,其内容区块将展示该Tab的内容。
  • Tab选中有明显的高亮展示。

实现方式

  • 总体:navigator组件 结合 获取url参数 来实现该功能。
  • wxml布局:
1
2
3
4
5
<view class="tabs">
<navigator redirect class="tab {{currentType == 'latest' ? 'tab-selected' : ''}}" url="gif?type=latest" hover-class="tab-hover">最新</navigator>
<navigator redirect class="tab {{currentType == 'gif' ? 'tab-selected' : ''}}" url="gif?type=gif" hover-class="tab-hover">动图</navigator>
<navigator redirect class="tab {{currentType == 'static' ? 'tab-selected' : ''}}" url="gif?type=static" hover-class="tab-hover">趣图</navigator>
</view>
  • wxss样式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.tabs {
display: flex;
position: relative;
background-color: #fff;
}
.tabs .tab {
flex: 1;
text-align: center;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
color: #000;
height: 84rpx;
line-height: 84rpx;
box-sizing: border-box;
}
.tabs .tab-hover,
.tabs .tab-selected {
color: #1aad19;
border-bottom: 2px solid #1aad19;
}
  • javascript:
1
2
3
4
5
6
//获取url中type参数,若没有该参数,默认显示最新tab
onLoad: function(option){
this.setData({
currentType: option.type ? option.type : 'latest',
})
}

页面效果

  • wechat-tabs

微信小程序和你有多大关系

转一篇介绍微信小程序的文章,用于科普在现阶段微信小程序可以做什么场景,适合做什么场景。

自年初1月11日张小龙在北京微信公开课上发言起,历经大半年后,万众瞩目的“微信应用号”终于现身。相比“应用号”,“小程序”这个名字更加可爱。随后的事情,你应该知道,朋友圈刷屏讨论小程序,全民兴奋。

作为和微信紧密沟通的第三方平台,侯斯特对于小程序是怎么看?

先看结论:

小程序好的一面

  • 微信可能给予入口,带来新的流量机遇(目前看应该是在发现里)

  • 在微信中调用更多的硬件接口,可以做出更多有趣的功能

  • 可以和公众号互相呼应,提供更加完整的服务体验

  • 体验更佳,使用更流畅(真的很流畅)

小程序坏的一面

  • 一个升级版的网页开发包,和JS-SDK没有本质区别

  • 与添加小程序的粉丝之间关系弱,不具备公众号里粉丝关注的强关系,你可能没办法随时给使用你小程序的粉丝发消息

  • 限制发送给好友/群,不能分享到朋友圈,不能添加任何外链(这个是噩梦)

  • 微信官方的审核机制,这会让很多好的营销点子和小程序模板失去作用(重名、已审核的都会被拒绝,这个早起占坑很重要,审核机制说明戳:https://mp.weixin.qq.com/debug/wxadoc/product/reject.html?t=20161107)

  • 流量获取还是需要自己解决,竞争过于激烈。公众号没做好的话,小程序也做不好

  • 对于技术的要求提高,品牌没有做好准备

以下内容涉及小程序的知识普及,可以选择性阅读。

正确理解微信小程序
自从张小龙 2016 年初提出做「应用号」,外界对应用号的猜测和期待从来没有停止过。大多数人和媒体认为,小程序将会为营销带来新机会。

但是真的是这样么?我们来看看10点真相。

  • 1、小程序是微信接下来的重点产品
    甚至是最高优先级的产品之一,因为这是微信要成为真正的 OS 的路径。这意味着,开发者可以完全放心把精力和资源放到小程序上。

  • 2、无关注,无阅读压力
    和服务号、订阅号不一样,小程序是没有关注功能的。这意味着,对用户来说,心理成本更小,用户通过搜索进入小程序,马上就可以使用,不像服务号还需要先关注。
    但对开发者来说,这显然不是好事。这意味着:
    你无法群发消息,因为你根本没有关注者
    你可能需要自行建立用户系统,但转化率是个问题
    所以,小程序在一定程度上,提高了产品运营能力的要求。

  • 3、不是H5,也不是混合模式
    我们经常在朋友圈看到的非常炫酷还带背景音乐的 H5 页面,将不会在小程序里出现。
    微信小程序开发使用改自 Javascript, CSS, XML 的语言,同时提供了各种自有的组件和 API,这让小程序变得独立:
    它不兼容 HTML,网页代码在小程序里无法使用
    开发之前,开发者需要熟悉小程序开发语言,按照微信的命名方法,说不定会被称为 WeLang。
    不兼容 HTML,不仅意味着你不能在页面里使用 HTML 标记,也意味着你不能嵌入 HTML 网页:要么不嵌入,要么用 WeLang 重写。

  • 4、不能外链
    不兼容 HTML 还意味着,你无法在小程序里放置外链。HTML 里的 标记是被禁止的。
    这很大程度上限制了营销,服务号里,我们好歹还能在文章里插入链接,而目前版本的小程序,是不能插入外链的,哪怕是放置二维码,直接在页面上长按,也没有「识别二维码」选项(噩梦啊~~~)。

  • 5、无法分享到朋友圈,限制发送给好友/群
    当前版本的小程序是不支持分享到朋友圈的,在有限的条件下可以将小程序的某些页面分享给通信录的个人或群,但无法分享到朋友圈,这意味着你不会在朋友圈看到小程序刷屏,刷屏的,还是原来那些东西。
    从经验上来看,微信会尽一切努力维护朋友圈秩序。以后小程序能不能分享到朋友圈我不知道,但至少一开始不打开这个口对微信来说是好事,一旦打开,就很难收回来了。

  • 6、微信自创了开发语言
    前面已经提到过,微信小程序不是用 HTML 开发的,也不兼容 HTML 标记,它是一套自有的语言(暂且叫 WeLang),使用 WeLang 开发出来的页面,其体验是与原生 app 接近的,因为除了数据,定义页面的样式、数据结构、逻辑等文件已经提前下载,不像网页那样需要实时加载,而且,因为页面可以调用小程序提供的组件,这些组件早已内置在微信客户端,它们的体验其实就是「原生」的。这样的体验,是非常流畅,非常原生的。

  • 7、前端开发成本极低
    前端开发其中一个最大的成本是兼容性适配,不管是做网页的前端需要适配各种浏览器,还是做 Android 客户端开发,需要在各种尺寸、性能不同的设备中反复调试。
    对于创业公司来说,这些成本的支出是不划算的,因为创业公司需要快速将产品推出市场,兼容性问题往往为快速迭代带来障碍。
    开发微信小程序,对于前端工程师来说,成本是相对较低的,因为微信已经解决了兼容性问题,前端工程师只需要学习 WeLang,然后按照规范去开发,兼容性问题,交给微信。
    一次开发,多平台通用。

  • 8、支持离线使用与 Websocket 的想象力
    微信小程序支持离线使用,也支持后台运行,这为小工具带来想象力。
    比如,像万年历、Todolist、番茄闹钟这样的工具,会大量出现。我更期待的是,微信将来提供一种会话与小程序之间直接通信的能力。
    小程序很多 API 与服务号类似,但其中的 Websocket API 是新增的。很多拿到内测的朋友都跟我说,这个新的 API 可以带来巨大的想象力,比如,你可以在小程序里打造一个「你画我猜」的游戏。

  • 9、NO游戏,NO直播,NO社交
    目前版本小程序文档里明确写明,游戏类、直播类、小程序导航,小程序链接互推,小程序排行榜等都不能提交。我们自己提交了一个类似聊天室的小程序,也被微信告知不能过审核。

  • 10、有审核机制
    前面提到了「提交」这个词。和订阅号、服务号不一样,你发文章不需要通过微信审核,你改按钮功能也不需要,但小程序的每个版本更新,都必须通过微信审核 — 就像 App Store 那样。
    对用户来说,这是好事,意味着大部分通过审核的服务都是质量过关的,坏消息是,对于只把目光放在营销层面的人,这里又是另一个限制。

ES6 的 12 个核心功能一览

本文由 伯乐在线 - 古鲁伊 翻译。
英文出处:Adrian Mejia

过去几年 JavaScript 发生了很大的变化。下面的 12 个新功能现在就可以用起来了。

JavaScript 历史

新补充的语言叫 ECMAScript 6,也叫 ES6 或 ES2015+。

JavaScript 自 1995 年面世以来,一直在缓慢地改进着。每隔几年都会有新的补充。1997 年成立的 ECMAScript 引领着 JavaScript 的发展。已发布的版本有 ES3、 ES5、 ES6 等。

JavaScript 历史

JavaScript 进化史

ES3 与 ES5 之间隔了 10 年,而ES5 与 ES6 之间隔了 6 年。改进的新模式是每年都渐进式地做一些小改动,而不是像 ES6 一样一次性地进行大量的更改。

浏览器支持

所有的现代浏览器和环境都已经支持 ES6 了。

JavaScript 进化史

来源:https://kangax.github.io/compat-table/es6/

Chrome、MS Edge、Firefox、Safari、Node和其它很多环境都已经嵌入程序以支持 JavaScript ES6 的大部分功能。所以从本教程学到的所有功能都可以直接使用。

来开始学习 ECMAScript 6 吧!

ES6 核心功能

可以在浏览器控制台上尝试下面的代码。

控制台代码

所以不要照单全收,试试每个 ES5 和 ES6 示例。我们开始吧。

块级作用域变量

在 ES6 中,我们使用 let/const,而非 var 来声明变量。
var 有什么缺点呢?
var 的问题是变量会泄露到其它代码块,比如 for 循环或是 if 块。
test(false) 应该返回 outer,但并不是,得到的值是 undefined。
为什么?
因为即使 if 块没有执行,第 4 行的表达式 var x 也被提升了。

var 提升:

  • var 是在函数作用域中的,即使在声明前,它在整个函数内都是可用的。
  • 声明被提升了。所以变量在声明前就可以使用。
  • 初始化 没有 被提升。如果使用 var 的话,一定 要在顶部声明变量。
  • 应用提升规则后,就好理解代码是如何执行的了:
1
2
3
4
5
6
7
8
9
10
11
ES5
var x = 'outer';
function test(inner) {
var x; // HOISTED DECLARATION
if (inner) {
x = 'inner'; // INITIALIZATION NOT HOISTED
return x;
}
return x;
}

ECMAScript 2015帮助解决了这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
ES6
let x = 'outer';
function test(inner) {
if (inner) {
let x = 'inner';
return x;
}
return x; // gets result from line 1 as expected
}
test(false); // outer
test(true); // inner

用 let 替代 var 以便按预期的那样去执行代码。如果没有调用 if 块,变量 x 就不会提升到块外。

Let 提升 和“temporal dead zone”

  • 在 ES6 中,let 会将变量提升到块顶部(而 非 像 ES5 是函数顶部)。
  • 但是在变量声明前引用变量会造成 ReferenceError。
  • let 是块作用域的,不可以在声明前使用。
  • “Temporal dead zone”是块开始直到变量被声明的这段区域。

IIFE

在介绍 IIFE 之前,我们来看个例子:

1
2
3
4
5
6
ES5
{
var private = 1;
}
console.log(private); // 1

如你所见,private 会发生泄漏。需要使用 IIFE(立即执行函数表达式)将其包起来:

1
2
3
4
5
6
ES5
(function(){
var private2 = 1;
})();
console.log(private2); // Uncaught ReferenceError

如果看过 jQuery/lodash 或是其它开源项目的代码,你会注意到它们都利用了 IIFE,以避免污染全局环境,而只在全局下定义 _、$或是 jQuery。

ES6 更工整,不再需要使用 IIFE,只要用块和 let 就可以了:

1
2
3
4
5
6
ES6
{
let private3 = 1;
}
console.log(private3); // Uncaught ReferenceError

Const

如果不希望变量的值再改变,可以使用 const。

const

总之:用 let 和 const 代替 var。

使用 const 进行引用;避免使用 var。

如果必须重新指定引用,可以用 let 代替 const。

文本模板

遇到文本模板时,不必再用嵌套连接了。比如:

1
2
3
4
5
ES5
var first = 'Adrian';
var last = 'Mejia';
console.log('Your name is ' + first + ' ' + last + '.');

现在可以用 反引号(`) 和字符串插值 ${}:

1
2
3
4
5
ES6
const first = 'Adrian';
const last = 'Mejia';
console.log(`Your name is ${first} ${last}.`);

多行字符串

不必像这样再连接 + n 字符串了:

1
2
3
4
5
6
7
8
9
10
11
ES5
var template = '<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >n' +
' <div class="view">n' +
' <input class="toggle" type="checkbox" [checked]="todo.isDone">n' +
' <label></label>n' +
' <button class="destroy"></button>n' +
' </div>n' +
' <input class="edit" value="">n' +
'</li>';
console.log(template);

ES6 中同样可以用反引号解决:

1
2
3
4
5
6
7
8
9
10
11
ES6
const template = `<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >
<div class="view">
<input class="toggle" type="checkbox" [checked]="todo.isDone">
<label></label>
<button class="destroy"></button>
</div>
<input class="edit" value="">
</li>`;
console.log(template);

两段代码会得到完全相同的结果。

解构赋值

ES6 解构非常简明并且好用。看看下面的例子:

获取数组元素

1
2
3
4
5
6
ES5
var array = [1, 2, 3, 4];
var first = array[0];
var third = array[2];
console.log(first, third); // 1 3

等同于:

1
2
3
4
5
ES6
const array = [1, 2, 3, 4];
const [first, ,third] = array;
console.log(first, third); // 1 3

调换值

1
2
3
4
5
6
7
8
ES5
var a = 1;
var b = 2;
var tmp = a;
a = b;
b = tmp;
console.log(a, b); // 2 1

等同于

1
2
3
4
5
6
ES6
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

返回多个值的解构

1
2
3
4
5
6
7
8
9
10
ES5
function margin() {
var left=1, right=2, top=3, bottom=4;
return { left: left, right: right, top: top, bottom: bottom };
}
var data = margin();
var left = data.left;
var bottom = data.bottom;
console.log(left, bottom); // 1 4

在第3行,也可以像这样用数组返回(并保存序列):

1
return [left, right, top, bottom];

但之后调用时需要考虑返回数据的顺序。

1
2
var left = data[0];
var bottom = data[3];

ES6 中调用时只会选择需要的数据(第 6 行):

1
2
3
4
5
6
7
8
ES6
function margin() {
const left=1, right=2, top=3, bottom=4;
return { left, right, top, bottom };
}
const { left, bottom } = margin();
console.log(left, bottom); // 1 4

注意:第3行用到了一些其它的 ES6 功能。可以将 { left: left } 简化为 { left }。看看和 ES5 的版本相比,现在多简洁啊~很酷不是吗?

参数匹配解构

1
2
3
4
5
6
7
8
9
ES5
var user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName(user) {
var firstName = user.firstName;
var lastName = user.lastName;
return firstName + ' ' + lastName;
}
console.log(getFullName(user)); // Adrian Mejia

等同于(但更简洁):

1
2
3
4
5
6
7
ES6
const user = {firstName: 'Adrian', lastName: 'Mejia'};
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
console.log(getFullName(user)); // Adrian Mejia

深度匹配

1
2
3
4
5
6
7
8
9
ES5
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
var tmp = settings();
var displayColor = tmp.display.color;
var keyboardLayout = tmp.keyboard.layout;
console.log(displayColor, keyboardLayout); // red querty

等同于(但更简洁):

1
2
3
4
5
6
7
ES6
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}
const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();
console.log(displayColor, keyboardLayout); // red querty

也叫对象解构。

如你所见,解构很有用,并有助于形成好的编码风格。

最佳实践:

  • 使用数组解构获取元素或调换变量,这样就不用创建临时引用了。
  • 对于多返回值的情况,不要用数组解构,用对象解构。

类和对象

ES6 用“类”替代“构造函数”。

在 JavaScript 中,每个对象都有原型对象。所有 JavaScript 对象都从原型上继承方法和属性。

ES5 以面向对象编程(OOP)的方式创建对象,是利用构造函数实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
ES5
var Animal = (function () {
function MyConstructor(name) {
this.name = name;
}
MyConstructor.prototype.speak = function speak() {
console.log(this.name + ' makes a noise.');
};
return MyConstructor;
})();
var animal = new Animal('animal');
animal.speak(); // animal makes a noise.

ES6 提供了语法糖,可以用 class、constructor 等新的关键字、更少的样板代码实现相同的效果。同样可以看到相比于constructor.prototype.speak = function (),用 speak() 定义方法更加清晰:

1
2
3
4
5
6
7
8
9
10
11
12
ES6
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
const animal = new Animal('animal');
animal.speak(); // animal makes a noise.

可以看到两种方式(ES5/6)的结果和使用方式相同。

最佳实践:

  • 最好使用 class 语法,避免直接操作 prototype。原因是这样代码更加简明易懂。
  • 避免出现空的构造器。如果没有指明,类会有默认的构造器的。

继承

基于前面的 Animal 类,现在想要拓展 Animal,定义一个 Lion 类。

ES5 原型继承的方式有些复杂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ES5
var Lion = (function () {
function MyConstructor(name){
Animal.call(this, name);
}
// prototypal inheritance
MyConstructor.prototype = Object.create(Animal.prototype);
MyConstructor.prototype.constructor = Animal;
MyConstructor.prototype.speak = function speak() {
Animal.prototype.speak.call(this);
console.log(this.name + ' roars ');
};
return MyConstructor;
})();
var lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.

在此我没有详细解读所有的代码,但是需要注意:

  • 第 3 行,明确地用参数调用 Animal 构造器。
  • 7-8 行,将 Lion 原型赋值为 Animal的原型。
  • 11 行,从父类 Animal 调用了 speak 方法。

ES6 提供了新的关键字 extends 和 super。

1
2
3
4
5
6
7
8
9
10
11
ES6
class Lion extends Animal {
speak() {
super.speak();
console.log(this.name + ' roars ');
}
}
const lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.

效果相同,但是相比于 ES5,ES6 代码更易读,完胜。

最佳实践:

  • 使用内置的 extends 实现继承。

原生 Promise

用 promise 替代回调地狱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ES5
function printAfterTimeout(string, timeout, done){
setTimeout(function(){
done(string);
}, timeout);
}
printAfterTimeout('Hello ', 2e3, function(result){
console.log(result);
// nested callback
printAfterTimeout(result + 'Reader', 2e3, function(result){
console.log(result);
});
});

这个函数接收一个回调,在 done 后执行。我们想要先后执行两次,所以在回调中又一次调用了 printAfterTimeout。

如果需要 3 或 4 次回调,代码很快就一团糟了。那么用 promise 如何实现呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ES6
function printAfterTimeout(string, timeout){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(string);
}, timeout);
});
}
printAfterTimeout('Hello ', 2e3).then((result) => {
console.log(result);
return printAfterTimeout(result + 'Reader', 2e3);
}).then((result) => {
console.log(result);
});

promise 中可以用 then 在某个函数完成后执行新的代码,而不必再嵌套函数。

箭头函数

ES6 没有移除函数表达式,但是新增了箭头函数。

ES5 中,this 的指向有问题:

1
2
3
4
5
6
7
8
9
ES5
var _this = this; // need to hold a reference
$('.btn').click(function(event){
_this.sendData(); // reference outer this
});
$('.input').on('change',function(event){
this.sendData(); // reference outer this
}.bind(this)); // bind to outer this

在函数内,需要用临时变量指向 this 或者使用 bind 绑定。ES6 中可以使用箭头函数。

For…of

最开始用 for ,然后使用 forEach,而现在可以用 for…of:

1
2
3
4
5
6
7
ES6
// this will reference the outer one
$('.btn').click((event) => this.sendData());
// implicit returns
const ids = [291, 288, 984];
const messages = ids.map(value => `ID is ${value}`);

ES6 的 for…of 也可以用来迭代。

默认参数

之前需要检测变量是否定义了,而现在可以指定 default parameters 的值。或许你之前像下面这样写过?

1
2
3
4
5
6
7
8
9
10
11
12
ES5
function point(x, y, isFlag){
x = x || 0;
y = y || -1;
isFlag = isFlag || true;
console.log(x,y, isFlag);
}
point(0, 0) // 0 -1 true
point(0, 0, false) // 0 -1 true
point(1) // 1 -1 true
point() // 0 -1 true

这可能是检测变量有值或指定默认值的惯用模式,但也存在一些问题:

  • 第 8 行,我们传的值是 0, 0 但是得到的是 0, -1
  • 第 9 行,传进去 false 但是得到的是 true。

如果默认参数是布尔值或将值设为 0,是没有用的。想知道为什么?我会在下面的 ES6 示例后说明。

有了 ES6,现在可以用更少的代码实现更好的效果了。

1
2
3
4
5
6
7
8
9
ES6
function point(x = 0, y = -1, isFlag = true){
console.log(x,y, isFlag);
}
point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 true

注意第 5 行和第 6 行我们拿到了想要的值。ES5 的示例不好用,是因为先要检测 undefined 的值,而 false、 null、 undefined 和 0 都是假的值。我们可以加些代码:

1
2
3
4
5
6
7
8
9
10
11
12
ES5
function point(x, y, isFlag){
x = x || 0;
y = typeof(y) === 'undefined' ? -1 : y;
isFlag = typeof(isFlag) === 'undefined' ? true : isFlag;
console.log(x,y, isFlag);
}
point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 true

现在当检测 undefined 值时就符合我们的要求了。

剩余参数

之前使用 arguments,而现在可以用展开操作符。

ES5 中处理不定参数很麻烦:

1
2
3
4
5
6
7
8
ES5
function printf(format) {
var params = [].slice.call(arguments, 1);
console.log('params: ', params);
console.log('format: ', format);
}
printf('%s %d %.2f', 'adrian', 321, Math.PI);

现在可以用展开操作符 … 达到相同的目的。

1
2
3
4
5
6
7
ES6
function printf(format, ...params) {
console.log('params: ', params);
console.log('format: ', format);
}
printf('%s %d %.2f', 'adrian', 321, Math.PI);

展开操作符

之前用 apply(),现在可以方便地使用展开操作符 … 了:

提示:apply() 可以将数组转化为一系列参数。例如 Math.max() 接收一系列参数,但如果想应用于数组的话可以用 apply 帮助实现。

mathApply

如上所述,apply 可以将数组当作参数序列进行传递:

1
2
3
ES5
Math.max.apply(Math, [2,100,1,6,43]) // 100

ES6 可以用展开操作符:

1
2
3
ES6
Math.max(...[2,100,1,6,43]) // 100

之前用 concat 合并数组,现在也可以用展开操作符:

1
2
3
4
5
6
ES6
const array1 = [2,100,1,6,43];
const array2 = ['a', 'b', 'c', 'd'];
const array3 = [false, true, null, undefined];
console.log([...array1, ...array2, ...array3]);

总结

JavaScript 已经发生了许多改变。本文包含了大部分的核心功能,这些是每个 JavaScript 程序员都应该知道的。本文还涉及了一些最佳实践,可以使你的代码更加简明易懂。

如果你觉得还有一些其它的必会功能,请在下方留言,方便我更新本文。

极路由无法绑定迅雷远程解决方法

相关背景

家里有一台极路由,装了迅雷远程下载插件,通过迅雷,把喜欢看的电影,美剧下载到挂载在路由器上。这样局域网的设备(台式机,笔记本,平板,手机,电视)都可以通过各自播放器观看视频,非常方便。

然而,最近不知道是极路由还是迅雷抽风,一直不能下载成功。网上搜索了下,也没有完整的解决方案,并且有同样问题的同学也不少,于是整理了下如下一个步骤,用于解决最近极路由无法绑定迅雷远程的问题。

问题描述

  • 打开迅雷远程http://yuancheng.xunlei.com,登录以后添加设备
  • 按照提示,打开极路由链接 http://192.168.199.1:9000/getsysinfo,发现没有激活码,而是出现以下信息

    1
    [0,1,1,1,"",1,"3.680.2.253_20","",1,"25642347",0]

    各数字具体含义

解决办法

  • 致电极路由客服电话:4006024680
  • 向客服描述问题
  • 提供你本地的mac地址:
  • 开启极路由允许远程调试:
    • 路径:系统设置 -> 路由器诊断 -> 允许远程调试
  • 客服会给你一个激活码
  • 再次打开迅雷远程http://yuancheng.xunlei.com,输入激活码
  • 激活成功,开启的远程下载之旅吧!

写在之后

  • 问了极路由客服,为什么之前可以绑定,现在不行了。得到的答案是:迅雷策略有调整,相关激活码也是路由器厂商向迅雷申请的。

资源链接

六不准的微信小程序开发指南一

本章主要讲微信小程序开发前的准备工作

微信小程序官方链接

详细的小程序产品以及功能介绍

下一章主要内容:官方的实例如何在本地运行起来

六不准的微信小程序开发指南二

上一章,我们主要介绍了一些微信小程序的链接信息,比如官方文档以及开发者工具等等。本章主要讲如何把微信小程序的官方API在本地运行起来,这样大家在做自己的小程序的时候就可以看到有哪些组件和接口可以调用。这些也是开发一个微信小程序的基础。

微信小程序API源码下载

搭建步骤

  • 解压zip包到你的工程目录,例如wechatTest
  • 打开微信web开发者工具,创建新项目。
    • 输入AppID,若有
    • 输入项目名称,例如wechatTest
    • 项目目录,选择你的工程目录,例如wechatTest文件夹
    • 注意:请不要勾选 在当前目录中创建 quick start 项目 选项
      创建项目
  • 其实,到这一步,就已经可以看到微信小程序的组件和接口了
    组件接口界面

下一章主要内容:模仿一个流行的应用做个实例

weex开发踩过的坑

weex is not defined

  • 官方示例:”weex-vue-render”: “^0.11.2”,
  • 可运行版本:”weex-vue-render”: “^0.2.0”

下一章主要内容:模仿一个流行的应用做个实例