Skip to content

Latest commit

 

History

History
543 lines (408 loc) · 38.6 KB

jsStandard.md

File metadata and controls

543 lines (408 loc) · 38.6 KB

JS基础相关

JS是一种弱类型动态语言

强、弱类型,静态、动态类型

强、弱类型

  • 强类型strongly typed: 如果一种语言的所有程序都是well behaved——即不可能出现forbidden behaviors,则该语言为strongly typed。
  • 弱类型weakly typed: 否则为weakly typed。比如C语言的缓冲区溢出,属于trapped errors,即属于forbidden behaviors..故C是弱类型
  • 弱类型语言,类型检查更不严格,如偏向于容忍隐式类型转换。譬如说C语言的int可以变成double。 这样的结果是:容易产生forbidden behaviours,所以是弱类型的

动态、静态类型

  1. 静态类型 statically: 如果在编译时拒绝ill behaved程序,则是statically typed;
  2. 动态类型dynamiclly: 如果在运行时拒绝ill behaviors, 则是dynamiclly typed。
  3. 误区大家觉得C语言要写int a, int b之类的,Python不用写(可以直接写a, b),所以C是静态,Python是动态。这么理解是不够准确的。譬如Ocaml是静态类型的,但是也可以不用明确地写出来。。 Ocaml是静态隐式类型

静态类型可以分为两种:

  • 如果类型是语言语法的一部分,在是explicitly typed显式类型;
  • 如果类型通过编译时推导,是implicity typed隐式类型, 比如ML和Haskell

下面是些例子

无类型: 汇编 弱类型、静态类型 : C/C++ 弱类型、动态类型检查: Perl/PHP 强类型、静态类型检查 :Java/C# 强类型、动态类型检查 :Python, Scheme 静态显式类型 :Java/C 静态隐式类型 :Ocaml, Haskell

语言类型

相关概念

Program Errors

  • trapped errors。导致程序终止执行,如除0,Java中数组越界访问
  • untrapped errors。 出错后继续执行,但可能出现任意行为。如C里的缓冲区溢出、Jump到错误地址 Forbidden Behaviours
  • 语言设计时,可以定义一组forbidden behaviors. 它必须包括所有untrapped errors, 但可能包含trapped errors. Well behaved、ill behaved

系统类型的区别

  • 类型系统的一些概念,众说纷纭,使用上也比较乱。有些东西,甚至不好严格定义。
  • 关于强弱类型的定义实际上是type checking的概念。 static type checking才是指的能在compile time完全的检测出forbidden behaviors。还有,这里的forbidden behavior还是只与type相关的错误,比如null pointer dereference就不属于这类trapped behavior。然而,基本上所有industry languages都会有dynamic check才能保证type check的完整性,比如array bounds check,就必须要dynamic time check。正是由于不能完全static type checking,才有strong/weak typed这两个概念,他们就是指有多少forbidden behaviors能在compile time检查到。所以,确实如你所说没有一个明确的界限界定强弱,也因此在学术上事实是没有strong/weak的个概念。

通常我们所说的动态语言、静态语言指 动态类型语言(Dynamically Typed Language)和 静态类型语言。 还有一个 Dynamic Programming Language (动态编程语言),静态编程语言。

动态类型语言:在运行期间检查数据的类型的语言。用这类语言编程,不会给变量指定类型,而是在附值时得到数据类型。如:Python和ruby就是典型动 态类型语言。很多脚本语言vbscrīpt,javascrīpt也是这类语言。 Dynamic Programming Language (动态编程语言)指在程序运行过程中可以改变数据类型的结构,对象的函数,变量可以被修改删除。

动态语言,准确地说,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如众所周知的 ECMAScript(JavaScript)便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态 语言。 静态类型语言的类型判断是在运行前判断(如编译阶段),比如C#就是一个静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继 承、接口,而动态类型语言却不需要,所以一般动态语言都会采用dynamic typing,常出现于脚本语言中。 这里我需要明确说明一点,那就是,是不是动态类型语言与这门语言是不是类型安全的完全不相干的,不要将它们联系在一起!

强类型语言是一旦变量的类型被确定,就不能转化的语言。  弱类型语言则反之,一个变量的类型是由其应用上下文确定的。 静态语言的优势:

  1. 由于类型的强制声明,使得IDE有很强的代码感知能力,故,在实现复杂的业务逻辑、开发大型商业系统、以及那些生命周期很长的应用中,依托IDE对系统的开发很有保障;
  2. 由于静态语言相对比较封闭,使得第三方开发包对代码的侵害性可以降到最低; 动态语言的优势:
  3. 思维不受束缚,可以任意发挥,把更多的精力放在产品本身上;
  4. 集中思考业务逻辑实现,思考过程即实现过程;

“编译”和“解释”

“编译”和“解释”的确都有“翻译”的意思,它们的区别则在于翻译的时机安排不大一样。打个比方:假如你打算阅读一本外文书,而你不知道这门外语,那么你可以找一名翻译,给他足够的时间让他从头到尾把整本书翻译好,然后把书的母语版交给你阅读;或者,你也立刻让这名翻译辅助你阅读,让他一句一句给你翻译,如果你想往回看某个章节,他也得重新给你翻译。  前者就相当于我们刚才所说的编译型:一次把所有的代码转换成机器语言,然后写成可执行文件;而后者就相当于我们要说的解释型:在程序运行的前一刻,还只有源程序而没有可执行程序;而程序每执行到源程序的某一条指令,则会有一个称之为解释程序的外壳程序将源代码转换成二进制代码以供执行,总言之,就是不断地解释、执行、解释、执行…… 编译型与解释型,两者各有利弊。前者由于程序执行速度快,同等条件下对系统要求较低,因此像开发操作系统、大型应用程序、数据库系统等时都采用它,像C/C++、Pascal/Object   Pascal(Delphi)、VB等基本都可视为编译语言,而一些网页脚本、服务器脚本及辅助开发接口这样的对速度要求不高、对不同系统平台间的兼容性有一定要求的程序则通常使用解释性语言,如Java、JavaScript、VBScript、Perl、Python等等。  Java号称的这么强大,Java号称是“一次编译,到处执行”,而.net则是“一次编码,到处编译”。呵呵,当然这些都是题外话了。



AST

**抽象语法树(Abstract Syntax Tree)**也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。

程序代码本身可以被映射成为一棵语法树,而通过操纵语法树,我们能够精准的获得程序代码中的某个节点

javascript语句

  • JavaScript代码由语句构成,表明了执行过程的流程、限定和约定,形式上可以是单行语句,也可以是由大括号括起来的复合语句。语句由分号来分隔。语句是“使某事发生”的指令,不存在返回值一说;
  • 当语句位于以下地点之一时,可以省略分号(不会出现语法错误,但可能造成执行阶段的错误):

一行的最后 整个代码文件的最后 在语法分隔符之前(如复合语句的大括号“}”) 复合语句的大括号“}”之后

  • 其它情况下遗漏分号,会在语法分析过程中报错,全部代码完全不执行。

语句中比较特殊的一类是表达式语句,表示“只有表达式,没有其它语法元素的语句”,

  • 例如: 1+2+3;
  • 就是一个表达式语句。


javascript表达式(expression)

  • 表达式:是由运算元和运算符(可选)构成,并产生运算结果的语法结构。

基本表达式(Primary Expression)

this、null、arguments等内置的关键字

变量。即一个已声明的标识符

字面量。仅包括数字字面量、布尔值字面量、字符串字面量、正则字面量

分组表达式,即用来表示立刻进行计算的

这类表达式是原子表达式,是无法再分解的表达式。

总结:分类如下

  • 单值表达式:不使用运算符的表达式
  • 简单表达式:不能再分解的表达式
  • 复杂表达式:需要其它表达式参与的表达式
  • 复合表达式:由运算符将多个单值表达式结合而成的表达式所有表达式均有返回值


数组的push方法

检查某个键名是否存在的运算符in,适用于对象,也适用于数组。

for...in循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。数组的forEach方法,也可以用来遍历数组,详见《标准库》一章的Array对象部分。

delete命令删除一个数组成员,会形成空位,并且不会影响length属性。

JS对象

JavaScript的所有数据都可以被视为对象。由若干个“键值对”(key-value)构成。

对象的生成方法,通常有三种方法。除了像上面那样直接使用大括号生成({}),还可以用new命令生成一个Object对象的实例,或者使用Object.create方法生成。

不符合标识名的条件,所以必须加上引号 对象的每一个“键名”又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。

对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加。

eval('{foo: 123}') # 123
eval('({foo: 123})') # {foo: 123}

上面代码中,如果没有圆括号,eval将其理解为一个代码块;加上圆括号以后,就理解成一个对象。 在浏览器环境,所有全局变量都是window对象的属性。window.a的含义就是读取window对象的a属性,如果该属性不存在,就返回undefined,并不会报错。 这是with语句的一个很大的弊病,就是绑定对象不明确。 建议不要使用with语句,可以考虑用一个临时变量代替with。

JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。

var Vehicle = function () {
  this.price = 1000;};

上面代码中,Vehicle就是构造函数,它提供模板,用来生成实例对象。为了与普通函数区别,构造函数名字的第一个字母通常大写。 构造函数的特点有两个。

函数体内部使用了this关键字,代表了所要生成的对象实例。

生成对象的时候,必需用new命令,调用Vehicle函数。

使用new命令时,它后面的函数调用就不是正常的调用,而是依次执行下面的步骤。

  1. 创建一个空对象,作为将要返回的对象实例
  2. 将这个空对象的原型,指向构造函数的prototype属性
  3. 将这个空对象赋值给函数内部的this关键字
  4. 开始执行构造函数内部的代码

构造函数内部,this指的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。

JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是这个对象(环境)。这本来并不会让用户糊涂,但是 JavaScript 支持运行环境动态切换,也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象,这才是最让初学者感到困惑的地方。如果一个函数在全局环境中运行,那么this就是指顶层对象(浏览器中为window对象)。this是所有函数运行时的一个隐藏参数,指向函数的运行环境。

队列与堆栈

JS中的数组提供了四个操作,以便让我们实现队列与堆栈!

  • shift:从集合中把第一个元素删除,并返回这个元素的值。请注意,该方法不创建新数组,而是直接修改原有的 arrayObject。
  • unshift: 在集合开头添加一个或更多元素,并返回新的长度
  • push:在集合中添加元素,并返回新的长度
  • pop:从集合中把最后一个元素删除,并返回这个元素的值。

JSON对象

  • JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式
  • 相比 XML 格式,JSON 格式有两个显著的优点:书写简单,一目了然;符合 JavaScript 原生语法,可以由解释引擎直接处理,不用另外添加解析代码。被写入ECMAScript 5,成为标准的一部分。 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
  • 简单类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinity和undefined)。字符串必须使用双引号表示,不能使用单引号。
  • 对象的键名必须放在双引号里面。
  • 数组或对象最后一个成员的后面,不能加逗号。
  • JSON.stringify方法将各种类型的值,转成 JSON 字符串。如果原始对象中,有一个成员的值是undefined、函数或 XML 对象,这个成员会被过滤。
  • 如果数组的成员是undefined、函数或 XML 对象,则这些值被转成null。
  • JSON.stringify方法会忽略对象的不可遍历属性。正则对象会被转成空对象。
  • JSON.stringify还可以接受第三个参数,用于增加返回的JSON字符串的可读性。如果是数字,表示每个属性前面添加的空格(最多不超过10个);如果是字符串(不超过10个字符),则该字符串会添加在每行前面。
  • 如果对象有自定义的toJSON方法,那么JSON.stringify会使用这个方法的返回值作为参数,而忽略原对象的其他属性。
  • JSON.stringify发现参数对象有toJSON方法,就直接使用这个方法的返回值作为参数,而忽略原对象的其他参数。
  • Date对象就有一个自己的toJSON方法。
  • toJSON方法的一个应用是,将正则对象自动转为字符串。因为JSON.stringify默认不能转换正则对象,但是设置了toJSON方法以后,就可以转换正则对象了。
  • JSON.parse方法用于将JSON字符串转化成对象。为了处理解析错误,可以将JSON.parse方法放在try...catch代码块中。


JS语法

单线程模型

JavaScript只在一个线程上运行,不代表JavaScript引擎只有一个线程。事实上,JavaScript引擎有多个线程,单个脚本只能在一个线程上运行,其他线程都是在后台配合。

JavaScript从诞生起就是单线程,原因是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了

如果有一个任务特别耗时,后面的任务都会停在那里等待,造成浏览器失去响应,又称“假死”。为了避免“假死”,当某个操作在一定时间后仍无法结束,浏览器就会跳出提示框,询问用户是否要强行停止脚本运行。

JavaScript语言的设计者意识到,这时CPU完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。这种机制就是JavaScript内部采用的Event Loop机制。

消息队列

JavaScript运行时,除了一个运行线程,引擎还提供一个消息队列(message queue),里面是各种需要当前程序处理的消息。新的消息进入队列的时候,会自动排在队列的尾端。

每条消息与一个回调函数相联系,也就是说,程序只要收到这条消息,就会执行对应的函数。另一方面,进入消息队列的消息,必须有对应的回调函数。否则这个消息就会遗失,不会进入消息队列。举例来说,鼠标点击就会产生一条消息,报告click事件发生了。如果没有回调函数,这个消息就遗失了。如果有回调函数,这个消息进入消息队列。等到程序收到这个消息,就会执行click事件的回调函数。

另一种情况是setTimeout会在指定时间向消息队列添加一条消息。如果消息队列之中,此时没有其他消息,这条消息会立即得到处理;否则,这条消息会不得不等到其他消息处理完,才会得到处理。因此,setTimeout指定的执行时间,只是一个最早可能发生的时间,并不能保证一定会在那个时间发生。

Event Loop

Wikipedia的定义是:“Event Loop是一个程序结构,用于等待和发送消息和事件(a programming construct that waits for and dispatches events or messages in a program)”。可以就把Event Loop理解成动态更新的消息队列本身。

排队。因为一个进程一次只能执行一个任务,只好等前面的任务执行完了,再执行后面的任务。

新建进程。使用fork命令,为每个任务新建一个进程。

新建线程。因为进程太耗费资源,所以如今的程序往往允许一个进程包含多个线程,由线程去完成任务。

部署得好,JavaScript程序是不会出现堵塞的,这就是为什么node.js平台可以用很少的资源,应付大流量访问的原因。

setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。

var timerId = setTimeout(func|code, delay)

setTimeout函数接受两个参数,第一个参数func|code是将要推迟执行的函数名或者一段代码,第二个参数delay是推迟执行的毫秒数。 推迟执行的代码必须以字符串的形式,放入setTimeout,因为引擎内部使用eval函数,将字符串转为代码。如果推迟执行的是函数,则可以直接将函数名,放入setTimeout。

setInterval函数的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。

这样写有一个很大的缺点,就是如果用户连续击键,就会连续触发keydown事件,造成大量的Ajax通信。这是不必要的,而且很可能会发生性能问题。正确的做法应该是,设置一个门槛值,表示两次Ajax通信的最小间隔时间。如果在设定的时间内,发生新的keydown事件,则不触发Ajax通信,并且重新开始计时。如果过了指定时间,没有发生新的keydown事件,将进行Ajax通信将数据发送出去。

这种做法叫做debounce(防抖动)方法,用来返回一个新函数。只有当两次触发之间的时间间隔大于事先设定的值,这个新函数才会运行实际的任务。假定两次Ajax通信的间隔不小于2500毫秒,上面的代码可以改写成下面这样。

setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮 Event Loop 时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮 Event Loop 时重新判断。 这意味着,setTimeout和setInterval指定的代码,必须等到本轮 Event Loop 的所有任务都执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行。

document.getElementById('my-ok').onkeypress = function() {
  var self = this;
  setTimeout(function() {
    self.value = self.value.toUpperCase();
  }, 0);}

正常任务与微任务

JavaScript的任务是同步执行的,即执行完前一个任务,然后执行后一个任务。只有遇到异步任务的情况下,执行顺序才会改变。 正常任务(task)与微任务(microtask)。它们的区别在于,“正常任务”在下一轮Event Loop执行,“微任务”在本轮Event Loop的所有任务结束后执行。

setTimeout(fn, 0)在Promise.resolve之后执行。setTimeout语句指定的是“正常任务”,即不会在当前的Event Loop执行。而Promise会将它的回调函数,在状态改变后的那一轮Event Loop指定为微任务。

进入“严格模式”的标志,是一行字符串use strict。

针对单个函数

use strict放在函数体的第一行,则整个函数以“严格模式”运行

正常模式下,函数内部的this可能会指向全局对象,严格模式禁止这种用法,避免无意间创造全局变量。

在if代码块和for代码块中声明了函数,在严格模式下都会报错。

并行与串行的结合

所谓并行与串行的结合,就是设置一个门槛,每次最多只能并行执行n个异步任务。这样就避免了过分占用系统资源。

用同步调用的方法来处理异步调用,promise。

Promise对象的优点在于,让回调函数变成了规范的链式写法,程序流程可以看得很清楚。它的一整套接口,可以实现许多强大的功能,比如为多个异步操作部署一个回调函数、为多个回调函数中抛出的错误统一指定处理方法等等。如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,你不用担心是否错过了某个事件或信号。这种方法的缺点就是,编写和理解都相对比较难。

console对象

  • console对象是 JavaScript 的原生对象,它有点像 Unix系统的标准输出stdout和标准错误stderr,可以输出各种信息到控制台,并且还提供了很多有用的辅助方法。

console的常见用途有两个。

  • console.log方法用于在控制台输出信息。它可以接受多个参数,将它们的结果连接起来输出。
  • console.log方法会自动在每次输出的结尾,添加换行符。如果第一个参数是格式字符串(使用了格式占位符),console.log方法将依次用后面的参数替换占位符,然后再进行输出。
  • %s 字符串
  • %d 整数
  • %i 整数
  • %f 浮点数
  • %o 对象的链接
  • %c CSS格式字符串
  • console.info()和console.debug()都是console.log方法的别名,用法完全一样。只不过console.info方法会在输出信息的前面,加上一个蓝色图标。
  • console.warn(),console.error()
  • warn方法输出信息时,在最前面加一个黄色三角,表示警告;error方法输出信息时,在最前面加一个红色的叉,表示出错,同时会显示错误发生的堆栈。其他方面都一样。
  • 复合型数据转为表格显示的条件是,必须拥有主键。对于数组来说,主键就是数字键。对于对象来说,主键就是它的最外层键。

其他console对象上的的方法

  • console.count(),console.dir(),console.dirxml(), console.assert(),console.time(),console.timeEnd(),console.group(),console.groupend(),console.groupCollapsed(),console.trace(),console.clear()


命令行 API

  • 控制台中,除了使用console对象,还可以使用一些控制台自带的命令行方法。

debugger语句

  • 在Chrome浏览器中,当代码运行到debugger语句时,就会暂停运行,自动打开控制台界面。
  • JavaScript提供了一个内部数据结构,用来描述一个对象的属性的行为,控制它的行为。这被称为“属性描述对象”(attributes object)。每个属性都有自己对应的属性描述对象,保存该属性的一些元信息。 提供6个元属性。
  1. value存放该属性的属性值,默认为undefined
  2. writable存放一个布尔值,表示属性值(value)是否可改变,默认为true。
  3. enumerable存放一个布尔值,表示该属性是否可枚举,默认为true。
  4. configurable存放一个布尔值,表示“可配置性”,默认为true。 get存放一个函数,表示该属性的取值函数(getter),默认为undefined。
  5. set存放一个函数,表示该属性的存值函数(setter),默认为undefined。
  6. Object.getOwnPropertyDescriptor方法可以读出对象自身属性的属性描述对象。
  • Object.defineProperty方法允许通过定义属性描述对象,来定义或修改一个属性,然后返回修改后的对象。
  • 如果属性已经存在,Object.defineProperty方法相当于更新该属性的属性描述对象。


正则表达式

  • 正则表达式(regular expression)是一种表达文本模式(即字符串结构)的方法,有点像字符串的模板,常常用作按照“给定模式”匹配文本的工具。JavaScript 的正则表达式体系是参照 Perl 5 建立的。
  • 新建正则表达式有两种方法。一种是使用字面量,以斜杠表示开始和结束。
  • 字面量和构造函数——在运行时有一个细微的区别。采用字面量的写法,正则对象在代码载入时(即编译时)生成;采用构造函数的方法,正则对象在代码运行时生成。
  • 弱类型、强类型、动态类型、静态类型语言的字面量和构造函数——在运行时有一个细微的区别。采用字面量的写法,正则对象在代码载入时(即编译时)生成;采用构造函数的方法,正则对象在代码运行时生成。
  • ignoreCase:返回一个布尔值,表示是否设置了i修饰符,该属性只读。
  • global:返回一个布尔值,表示是否设置了g修饰符,该属性只读。
  • multiline:返回一个布尔值,表示是否设置了m修饰符,该属性只读。
  • lastIndex:返回下一次开始搜索的位置。该属性可读写,但是只在设置了g修饰符时有意义。
  • source:返回正则表达式的字符串形式(不包括反斜杠),该属性只读。 正则对象的test方法返回一个布尔值,表示当前模式是否能匹配参数字符串。
  • 正则对象使用了g修饰符,表示要记录搜索位置。接着,三次使用test方法,每一次开始搜索的位置都是上一次匹配的后一个位置。
  • 带有g修饰符时,可以通过正则对象的lastIndex属性指定开始搜索的位置。
  • 正则对象的exec方法,可以返回匹配结果。如果发现匹配,就返回一个数组,成员是每一个匹配成功的子字符串,否则返回null。
  • 正则表示式包含圆括号(即含有“组匹配”),则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。
  • 模式结尾添加了一个问号/a+?/,这时就改为非贪婪模式,一旦条件满足,就不再往下匹配。

字符串对象的正则方法

match():返回一个数组,成员是所有匹配的子字符串。 search():按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置。 replace():按照给定的正则表达式进行替换,返回替换后的字符串。 replace方法的第二个参数可以使用美元符号$,用来指代所替换的内容。 split():按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员。

匹配规则

  • 大部分字符在正则表达式中,就是字面的含义,比如/a/匹配a,/b/匹配b。如果在正则表达式之中,某个字符只表示它字面的含义(就像前面的a和b),那么它们就叫做“字面量字符”(literal characters)。
  • 还有一部分字符有特殊含义,不代表字面的意思。它们叫做“元字符”(metacharacters)
  1. 点字符(.) 点字符(.)匹配除回车(\r)、换行(\n) 、行分隔符(\u2028)和段分隔符(\u2029)以外的所有字符。
  2. 位置字符 位置字符用来提示字符所处的位置,主要有两个字符。 ^ 表示字符串的开始位置 $ 表示字符串的结束位置
  3. 选择符(|) 竖线符号(|)在正则表达式中表示“或关系”(OR),即cat|dog表示匹配cat或dog。
  4. 转义符 正则表达式中那些有特殊含义的字符,如果要匹配它们本身,就需要在它们前面要加上反斜杠。需要特别注意的是,如果使用RegExp方法生成正则对象,转义需要使用两个斜杠,因为字符串内部会先转义一次。

字符类

  • 字符类(class)表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内,比如[xyz] 表示x、y、z之中任选一个匹配。
  1. 脱字符(^) 如果方括号内的第一个字符是[^],则表示除了字符类之中的字符,其他字符都可以匹配。比如,[^xyz]表示除了x、y、z之外都可以匹配。如果方括号内没有其他字符,即只有[^],就表示匹配一切字符,其中包括换行符,而点号(.)是不包括换行符的。
  2. 连字符(-) 于连续序列的字符,连字符(-)用来提供简写形式,表示字符的连续范围。

重复类

  • 模式的精确匹配次数,使用大括号({})表示。{n}表示恰好重复n次,{n,}表示至少重复n次,{n,m}表示重复不少于n次,不多于m次。? 问号表示某个模式出现0次或1次,等同于{0, 1}。* 星号表示某个模式出现0次或多次,等同于{0,}。+ 加号表示某个模式出现1次或多次,等同于{1,}。
  • 默认情况下都是最大可能匹配,即匹配直到下一个字符不满足匹配规则为止。这被称为贪婪模式。

修饰符

  • 修饰符(modifier)表示模式的附加规则,放在正则模式的最尾部。修饰符可以单个使用,也可以多个一起使用。
  • g修饰符表示全局匹配(global)
  • 默认情况下,正则对象区分字母的大小写,加上i修饰符以后表示忽略大小写(ignorecase)。
  • m修饰符表示多行模式(multiline),会修改^和$的行为。默认情况下(即不加m修饰符时),^和$匹配字符串的开始处和结尾处,加上m修饰符以后,^和$还会匹配行首和行尾,即^和$会识别换行符(\n)。

Promise

为了更好的看清楚Promise的执行顺序,下面再次用一个简单的例子和运行结果来展示这个问题

"use strict";
var Promise = require("bluebird");
var first = function(){
    console.log("first");
};
var second = function(){
    console.log("second");
}
var third = function(){
    console.log("third");
}

Promise.resolve().then(first).then(second).then(third);

大白话讲解Promise

Promise是一个构造函数,自己身上有all、reject、resolve这几个眼熟的方法,原型上有then、catch等同样很眼熟的方法

Promise的构造函数接收一个参数,是函数,并且传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。

按照标准来讲,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected。

我只是new了一个对象,并没有调用它,我们传进去的函数就已经执行了,这是需要注意的一个细节。所以我们用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数

在我们包装好的函数最后,会return出Promise对象,也就是说,执行这个函数我们得到了一个Promise对象。还记得Promise对象上有then、catch方法吧?这就是强大之处了

function runAsync(){
    var p = new Promise(function(resolve, reject){
        # 做一些异步操作
        setTimeout(function(){
            console.log('执行完成');
            resolve('随便什么数据');
        }, 2000);
    });
    return p;            
}
runAsync().then(function(data){
    console.log(data);
    # 后面可以用传过来的数据做些其他操作
    # ......
});

在runAsync()的返回上直接调用then方法,then接收一个参数,是函数,并且会拿到我们在runAsync中调用resolve时传的的参数。 Promise的正确场景是这样的:

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

all的用法

Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。

all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样 done、finally、success、fail等,这些是啥?这些并不在Promise标准中,而是我们自己实现的语法糖。

本文中所有异步操作均以setTimeout为例子,之所以不使用ajax是为了避免引起混淆,因为谈起ajax,很多人的第一反应就是jquery的ajax,而jquery又有自己的Promise实现。如果你理解了原理,就知道使用setTimeout和使用ajax是一样的意思。说起jquery,我不得不吐槽一句,jquery的Promise实现太过垃圾,各种语法糖把人都搞蒙了,我认为Promise之所以没有全面普及和jquery有很大的关系。后面我们会细讲jquery。

Promise/A+规范

Promise promise 是一个拥有 then 方法的对象或函数,其行为符合本规范;

Promise 的状态

一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、完成态(Fulfilled)和完成态(Rejected)。

等待态(Pending)

  • 处于等待态时,promise 需满足以下条件:可以迁移至完成态或拒绝态

完成态(Fulfilled)

  • 处于完成态时,promise 需满足以下条件:
    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的终值

拒绝态(Rejected)

  • 处于拒绝态时,promise 需满足以下条件:
    • 不能迁移至其他任何状态
    • 必须拥有一个不可变的据因

promise 的 then 方法接受两个参数: promise.then(onFulfilled, onRejected)

onFulfilled 和 onRejected 都是可选参数。

  • 如果 onFulfilled 不是函数,其必须被忽略
  • 如果 onRejected 不是函数,其必须被忽略

注:如果我们只想传onRejected而不想传onFulfilled,可以这么写:pormise.then(null, onRejected) 多次调用

then 方法可以被同一个 promise 调用多次

  • 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
  • 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调

注:这里解释了我们可以链式调用,promise.then().then().then()

返回 then 方法必须返回一个 promise 对象 注3 promise2 = promise1.then(onFulfilled, onRejected);   注:这就是我们能够进行链式调用的原因,因为then方法返回的还是一个promise对象。

jquery中的Promise

ES6中的Promise以及Promise/A+规范,在Promise的知识体系中,jquery当然是必不可少的一环,所以本篇就来讲讲jquery中的Promise,也就是我们所知道的Deferred对象

function runAsync(){
    var def = $.Deferred();
    # 做一些异步操作
    setTimeout(function(){
        console.log('执行完成');
        def.resolve('随便什么数据');
    }, 2000);
    return def;
}
runAsync().then(function(data){
    console.log(data)
});

调用runAsync的时候将返回def对象,然后我们就可以.then来执行回调函数。

var d = runAsync();
d.then(function(data){
    console.log(data)
});
d.resolve('在外部结束');

比如你定义的一个异步操作并指定好回调函数,有可能被别人给提前结束掉,你的回调函数也就不能执行了。

其实他就是一个返回受限Deferred对象的方法,与Promise规范没有任何关系,仅仅是名字叫做promise罢了。虽然名字奇葩,但是推荐使用。

then的链式调用

既然Deferred也是Promise规范的实现者,那么其他特性也必须是支持的。

ajax与Deferred的关系

jquery的ajax返回一个受限的Deferred对象,还记得受限的Deferred对象吧,也就是没有resolve方法和reject方法,不能从外部改变状态。想想也是,你发一个ajax请求,别人从其他地方给你取消掉了,也是受不了的。

success、error与complete