终极异步方案

JavaScript异步编程一直是一件很麻烦的事,从最早的回调函数,到 Promise 对象,
再到 Generator 函数,每次都有所改进。但总感觉缺点什么。

直到 async/await 出现,很多人认为它是异步操作的终极解决方案。

async 应该会在 ECMAScript 7 引入。在 NodeJs v7.4.0 正式加入 NodeJs 大家庭。

async 是 Generator 函数的语法糖

1
2
3
4
5
6
7
8
9
10
var fs = require('fs');

var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};

Generator 写法:

1
2
3
4
5
6
7
8
var co = require('co');
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
co(gen);

写成 async 函数,就像下面这样:

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 ,把 yield 换成 await。而且自带执行器
(Generator 函数的执行必须依靠执行器,所以才有了 co 函数库)。

async 函数用法

  • 同 Generator 函数一样,async 函数返回一个 Promise 对象。
    所以调用 async 函数的函数也要处理 Promise 对象。

  • 用 try…catch 来处理 Promise 对象 rejected

  • await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

  • 将 forEach 方法的参数改成 async 函数不能够实现继发执行。应该采用 for 循环。

  • 希望多个请求并发执行,可以使用 Promise.all 方法。

评论和共享

CommonJS

首先,最先出现的是 CommonJSCommonJS API 定义很多普通应用程序(主要指非浏览器的应用)使用的API。它的终极目标是提供一个类似 PythonRubyJava 标准库。这样的话,开发者可以使用 CommonJS API 编写应用程序,然后这些应用可以运行在不同的 JavaScript 解释器和不同的主机环境中。

2009年,美国程序员 Ryan Dahl 创造了 node.js 项目,将 javascript 语言用于服务器端编程。这标志 Javascript 模块化编程”正式诞生。

NodeJSCommonJS 规范的实现, webpack 也是以 CommonJS 的形式来书写。NPM 作为 Node 的包管理器,帮助Node 解决依赖包的安装问题,也要遵循 CommonJS

Browserify

browserify 是最常用的 CommonJS 转换工具。

然后看个例子

1
2
3
4
5
6
7
8
// foo.js
module.exports = function(x) {
console.log(x);
};

// main.js
var foo = require("./foo");
foo("Hi");

1
$ browserify main.js > compiled.js

即可打包,Browserify 具体做了什么继续向下看即可:

1
$ npm install browser-unpack -g

然后我们将上面生成的 compiled.js 解包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ browser-unpack < compiled.js

[
{
"id":1,
"source":"module.exports = function(x) {\n console.log(x);\n};",
"deps":{}
},
{
"id":2,
"source":"var foo = require(\"./foo\");\nfoo(\"Hi\");",
"deps":{"./foo":1},
"entry":true
}
]

Browserify 在这里将所有模块放到一个数组里,id 是模块的编号,source 是源码,deps 为依赖,entry 为指定入口。

Browserify 先找到 entry: true 的地方,然后执行 source 中的代码,遇到 require 就去deps 中寻找依赖,并执行所依赖的代码,将结果赋值给 require 前的变量。

AMD

AMD ( Asynchronous Module Definition ),是为了解决 CommonJS 不能异步加载的问题。AMD 采用异步的方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等加载完成后,这个回调函数才会执行。

AMD 也采用 require() 语句加载模块,但是不同于 CommonJS,它要求两个参数:

1
require([module], callback);

第一个参数 [module] ,是一个数组,里面的成员就是要加载的模块;第二个参数 callback ,则是加载成功之后的回调函数。如果将前面的代码改写成 AMD 形式,就是下面这样:

1
2
3
require(['math'], function (math) {
  math.add(2, 3);
});

math.add()math 模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD 比较适合浏览器环境。目前,主要有两个 Javascript 库实现了 AMD 规范: require.jscurl.js

require.js

先去官方网站下载最新版本。

1
然后将它放到项目中即可

如果加载这个文件,也可能造成网页失去响应。解决办法有两个,一个是把它放在网页底部加载,另一个是写成下面这样:

1
<script src="js/require.js" defer async="true" ></script>

async 属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持 defer,所以把 defer 也写上。

加载 require.js 以后,下一步就要加载我们自己的代码了。假定我们自己的代码文件是 main.js ,也放在 js 目录下面。那么,只需要写成下面这样就行了:

1
<script src="js/require.js" data-main="js/main"></script>

data-main 属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的 main.js ,这个文件会第一个被 require.js加载。由于 require.js 默认的文件后缀名是js,所以可以把 main.js 简写成 main

主模块的写法:

1
2
3
4
// main.js
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
  // some code here
});

默认情况下,require.js 假定这三个模块与 main.js 在同一个目录,文件名分别为 moduleA.jsmoduleB.jsmoduleA.js,然后自动加载。

使用 require.config() 方法,我们可以对模块的加载行为进行自定义。require.config() 就写在主模块(main.js)的头部。参数就是一个对象,这个对象的 paths 属性指定各个模块的加载路径。

1
2
3
4
5
6
7
require.config({
  paths: {
    "jquery": "jquery.min",
    "underscore": "underscore.min",
    "backbone": "backbone.min"
  }
});

模块的写法

模块必须采用特定的 define() 函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在 define() 函数之中。

1
2
3
4
5
6
7
8
9
// math.js
define(function (){
 var add = function (x,y){
    return x+y;
  };
  return {
    add: add
  };
});

ES6 module

ES6发布的 module 并没有直接采用 CommonJS ,甚至连 require 都没有采用,也就是说 require 仍然只是 node 的一个私有的全局方法,module.exports 也只是 node 私有的一个全局变量属性,跟标准半毛钱关系都没有。

export 详细用法
import 详细用法

区别

require 的使用非常简单,它相当于 module.exports 的传送门,module.exports 后面的内容是什么,require 的结果就是什么,对象、数字、字符串、函数……再把 require 的结果赋值给某个变量,相当于把 requiremodule.exports 进行平行空间的位置重叠,而且 require 理论上可以运用在代码的任何地方,甚至不需要赋值给某个变量之后再使用。在使用时,完全可以忽略模块化这个概念来使用 require,仅仅把它当做一个 node 内置的全局函数,它的参数甚至可以是表达式:

但是 import 则不同,它是编译时的(require是运行时的),它必须放在文件开头,而且使用格式也是确定的,不容置疑。它不会将整个模块运行后赋值给某个变量,而是只选择 import 的接口进行编译,这样在性能上比 require 好很多。

从理解上,require 是赋值过程,import 是解构过程,当然,require 也可以将结果解构赋值给一组变量,但是 import 在遇到 default 时,和 require 则完全不同: var $ = require('jquery');import $ from 'jquery' 是完全不同的两种概念。

评论和共享

注意: 正则表达式中不要插入没用的空格。

直接量字符

字符 匹配
字母和数字字符 自身
\o NUL字符 (\u0000)
\t 制表符 (\u0009)
\n 换行符 (\u000A)
\v 垂直制表符 (\u000B)
\f 换页符 (\u000C)
\r 回车符 (\u000D)

字符类

字符 匹配
[…] 方括号内的任意字符
[^…] 不在方括号内的任意字符
. 除换行符和其他Unicode行终止符之外的任意字符
\w 任何ASCII字符组成的单词,等价于[a-zA-Z0-9]
\W 任何不是ASCII字符组成的单词,等价于[^a-zA-Z0-9]
\s 任何Unicode空白符
\S 任何非Unicode空白符的字符,和\w和\S不同
\d 任何ASCII数字,等价于[0-9]
\D 除了ASCII数字之外的任何字符,等价于[^0-9]
[\b] 退格直接量

重复

字符 含义
{n,m} 匹配前一项至少n次,但是不能超过m次
{n,} 匹配前一项n次或者更多次
{n} 匹配前一项n次
? 匹配前一项0次或1次, 等价于 { 0, 1 }
+ 匹配前一项1次或多次,等价于 { 1, }
* 匹配前一项0次或多次,等价于 { 0, }

非贪婪的重复: 例如“??”,“+?”,“*?”

正则表达式的选择,分组和引用字符

字符 含义
| 选择,匹配的是该符号左边的子表达式或右边的子表达式
(…) 组合,将几个项组合为一个单元,这个单元可通过 “ * ”,“ + ”,“ ? ” 和 “ | ” 等符号修饰,而且可以记住和这个组合相匹配的字符串供引用使用
(?:…) 只组合,把项目组合到一个单元,但不记忆与该组相匹配的字符
\n 和第 n 个富足第一次匹配的字符相匹配,组是圆括号中的子表达式,组索引是从左到右的左括号数

锚字符

字符 含义
\^ 匹配字符串的开头,在多行检索中,匹配一行开头
\& 匹配字符串的结尾,在多行检索中,匹配一行结尾
\b 匹配字符串的边界,位于字符\w和\W之间的位置
\B 匹配非单词边界的位置
(?=p) 零宽正向先行断言,要求接下来的字符都与p匹配,但不能包括匹配p的那些字符
(?!p) 零宽负向先行断言,要求接下来的字符都不与p匹配

修饰符

字符 含义
i 执行不区分大小写的匹配
g 执行一个全局匹配,找到所有的匹配,而不是在找到一个就停止
m 多行匹配模式

用于匹配的String方法

search() 返回匹配字符串的起始坐标,没有则返回-1

replace() 两个参数,替换字符

match() 匹配字符串如果没有修饰符g,则不进行全局检索,返回一个数组,在其中可以用括号匹配详细内容

6
1
2
3
4
5
let result = 'visit my Blog :https://www.baidu.com:80'.match(/(https?):\/\/([\w.]+):(\d*)/);
console.log(result);
console.log('协议:',result[1]);
console.log('网址:',result[2]);
console.log('端口:',result[3]);

结果

1
2
3
4
5
6
7
8
9
[ 'https://www.baidu.com:80',
'https',
'www.baidu.com',
'80',
index: 15,
input: 'visit my Blog :https://www.baidu.com:80' ]
协议: https
网址: www.baidu.com
端口: 80

split() 截取字符串

评论和共享

JavaScript 简介

JavaScript简介

JavaScript简史


Netscape公司的布兰登·艾奇(Brendan Eich),1995年开发LiveScript为了搭上被媒体超热的Java的热潮,改名为JavaScript。 微软在Internet Explorer 3 中加入JScript
1997年,以 JavaScript 1.1 为蓝本的由欧洲计算机制造商协会(ECMA,European Computer Manufacturers Association)完成ECMA-262 定义名为ECMAScript 1998年,ISO/IEC(国标标准化组织和国际电工委员会)也采用了ECMAScript 作为标准(即ISO/IEC-16262)

JavaScript实现


JavaScript包括 ECMAScript ,DOM ,BOM 核心(ECMAScript)
ECMA-262 第1版:本质上与Netscape的JavaScript 1.1 相同——只不过删除了所有针对浏览器的代码并作了一些较小的改动。 ECMA-262 第2版:这一版中内容的更新是为了与ISO/IEC-16262 保持严格一致,没有作任何新增、修改或删节处理。
ECMA-262 第3版:才是对该标准第一次真正的修改。修改的内容涉及字符串处理、错误定义和数值输出。这一版还新增了对正则表达式、新控制语句、try-catch 异常处理的支持,并围绕标准的国际化做出了一些小的修改。 ECMA-262 第4版:不仅包含了强类型变量、新语句和新数据结构、真正的类和经典继承,还定义了与数据交互的新方式。
与此同时,TC39 下属的一个小组也提出了一个名为ECMAScript 3.1 的替代性建议,该建议只对这
门语言进行了较少的改进。
ECMA-262 第5版:ECMAScript 3.1 成为ECMA-262 第5 版,并于2009 年12 月3 日正式发布。第五版澄清第三版中已知的歧义并添加了原生JSON解析,
继承的方法和高级属性定义,严格模式。
文档对象模型(DOM)(提供访问和操作网页内容的方法和接口)
文档对象模型(DOM,Document Object Model)是针对XML 但经过扩展用于HTML 的应用程序编
程接口(API,Application Programming Interface)。
其他DOM 标准
SVG(Scalable Vector Graphic,可伸缩矢量图)1.0; MathML(Mathematical Markup Language,数学标记语言)1.0;
SMIL(Synchronized Multimedia Integration Language,同步多媒体集成语言)。 浏览器对象模型(BOM)(提供与浏览器交互的方法和接口)
出新浏览器窗口的功能; 移动、缩放和关闭浏览器窗口的功能;
提供浏览器详细信息的navigator 对象; 提供浏览器所加载页面的详细信息的location 对象;
提供用户显示器分辨率详细信息的screen 对象; 对cookies 的支持;
* 像XMLHttpRequest 和IE 的ActiveXObject 这样的自定义对象。

在HTML 中使用JavaScript

script元素


6个属性 async:可选。表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或
等待加载其他脚本。只对外部脚本文件有效。
charset:可选。表示通过src 属性指定的代码的字符集。由于大多数浏览器会忽略它的值,
因此这个属性很少有人用。
defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有
效。
language:已废弃。原来用于表示编写代码使用的脚本语言(如JavaScript、JavaScript1.2
或VBScript)。大多数浏览器会忽略这个属性,因此也没有必要再用了。
src:可选。表示包含要执行代码的外部文件。
type:可选。可以看成是language 的替代属性(默认值为text/javascript) 注意转义
1
2
3
4
5
6
<script type="text/javascript">
function sayScript(){
alert("<\/script>");
//此处要加转义符
}
</script>


注意不可省略
1
2
3
4
<script type="text/javascript" src="example.js"><script/>
<!--注意:此处不能写成
<script type="text/javascript" src="example.js"/>
-->
带有src 属性的\元素不应该在其\和\标签之间再
包含额外的JavaScript 代码。如果包含了嵌入的代码,则只会下载并执行外部
脚本文件,嵌入的代码会被忽略。

无论如何包含代码,只要不存在defer 和async 属性,浏览器都会按照\元素在页面中
出现的先后顺序对它们依次进行解析。(解析完第一个才会解析第二个)
由于浏览器会先解析完不使用defer 属性的\元素中的代码,然后再解析后面的内容,
所以一般应该把\元素放在页面最后,即主要内容后面,\标签前面。

嵌入代码与外部文件

  • 外部文件的优点:
    • 可维护性
    • 可缓存(如果有两个页面都使用同一个文件,那么这个文件只需下载一次。)
    • 适应未来(不存在上面提到的转义问题)

noscript元素

  • 浏览器不支持脚本,或者浏览器支持脚本,但脚本被禁用时会显示标签中的文本。

评论和共享

规范

在最新的 DOM 规范中,事件的捕获与冒泡是通过 addEventListener(...arg) 的第三个参数的 capture来指定的。默认为 false,也就是默认冒泡。

1
2
3
4
5
addEventListener(type, listener, {
capture: false,
passive: false,
once: false
})

addEventListener 第三个参数

触发规则

event

在多个嵌套情况下,捕获的触发是从外到内的,冒泡则相反。先进行捕获,然后是冒泡。

这里要注意一点,在最后的一层中,先触发冒泡或捕获决定于代码中出现的先后,先出现的先执行。

不是所有的事件都能冒泡,例如:blurfocusloadunload

DOM事件流

同时支持两种事件模型:捕获型事件和冒泡型事件,但是,捕获型事件先发生。两种事件流会触及DOM中的所有对象,从document对象开始,也在document对象结束。

DOM事件模型最独特的性质是,文本节点也触发事件

绑定事件方式

在一个支持W3C DOM的浏览器中,像这样一般的绑定事件方式,是采用的事件冒泡方式。

1
2
3
ele.onclick = ()=>{
//doSomething
}

IE浏览器

IE只支持事件冒泡,不支持事件捕获,它也不支持 addEventListener 函数,不会用第三个参数来表示是冒泡还是捕获,它提供了另一个函数 attachEvent

1
2
3
ele.attachEvent("onclick", ()=>{
//doSomething
});

阻止事件传播:

  • 在W3c中,使用 stopPropagation() 方法
  • 在IE下设置 cancelBubble = true
    在捕获的过程中 stopPropagation(); 后,后面的冒泡过程也不会发生了

阻止事件的默认行为

  • 在W3c中,使用 preventDefault(); 方法;
  • 在IE下设置 window.event.returnValue = false;

评论和共享

作者的图片

Archie Shi

Nothing to say


Front-End Development Engineer