《JavaScript忍者秘籍》正则表达式

简介

正则表达式是一个拆分字符串并查询相关信息的过程。通常,正则表达式被称为一个模式,是一个用简单的方式描述或者匹配一系列操作符合某个句法规则的字符串。表达式本身包含了允许定义这些模式的术语和操作。大部分时候,相比于硬编码的蛮力验证,正则表达式的实现都更为优雅和易于理解。

基本操作

在 JavaScript 中,有两种方式可以用于创建正则表达式:

  1. 通过正则表达式字面量。

    1
    
    var pattern = /test/;
    
  2. 通过构造 RegExp 对象的实例。

    1
    
    var pattern = new RegExp("test");
    

在开发过程中,如果正则是已知的,则优先选择字面量语法,而第二种则更多的用于动态构建字符串来创建正则表达式。与此同时,除了表达式本身,还有 3 个标志可以用来更详细的描述匹配的操作要求。

  1. i :使得匹配过程不区分大小写。
  2. g :使得所有的实例获得匹配的机会,而不是只匹配第一次出现的结果。
  3. m :使得匹配作用于所有的行。

具体的表达式组成和使用,包括操作符的意义,可以查阅 这个文档

一个具体的例子

编译正则表达式

正则表达式是一个多阶段处理的过程,主要的 2 个阶段是编译执行。编译是发生在正则表达式第一次被创建的时候。而执行则是发生在我们使用编译过的正则表达式进行匹配的时候。通过对稍后要用的正则表达式进行预定义,可以获得明显的速度提升。

一个具体的例子

捕获匹配的片段

正则表达式的实用性表现在捕获已经匹配的结果上,这样方便我们可以进行处理。通过以下 这个例子 ,我们可以看到如何进行简单的非全局的捕获操作。下面的 这个例子 将展示全局捕获非全局捕获的差别。

捕获的引用

  1. 自身匹配:例如使用 \1 来引用表达式中的第一个捕获。
  2. 通过调用 replace() 来替换字符串的时候。如下所示:
1
assert("fontFamily".replace(/([A-Z])/g, "-$1").toLowerCase() == "font-family", "Convert the camelCase into dashed notation.");

小括号有双重责任:不仅要进行分组操作,还可以指定捕获。一般情况下,你可以忽略而不至于产生什么问题,但是如果在正则表达式中有大量的分组,就会引起很多不必要的捕捉。 要让一组括号不进行结果的捕获,我们需要在开始括号后加一个 ?: 标记。

《JavaScript忍者秘籍》原型与面向对象

简介

有些开发人员会认为原型和对象的关系很亲密,但是事实上,这全都是和函数有关。原型虽然是定义对象的一种很方便的方式,但它的本质依然是函数特性。

实例化和原型

通过使用 new 操作符和不使用 new 操作符,来看一下 prototype 属性是如何为新实例提供属性的。

实际的例子

以上例子说明了,函数作为构造器进行调用时,函数的原型是新对象的一个概览。同时,在使用 new 操作符将函数作为构造器调用时,其上下文是新对象实例。这让我们可以通过在构造器函数内的 this 变量来初始化参数。

实际的例子

以上例子还说明了初始化操作的优先级顺序,如下:

  1. 通过原型给对象实例添加的属性。
  2. 在构造器函数内给对象实例添加的属性。

协调引用

现在,让我们来看一下 JavaScript 是如何对引用进行协调的,以及在该过程中 prototype 属性是如何发挥作用的。

实际的例子

这个例子说明了,在对象创建时,不仅仅是简单复制属性那么简单。 事实是,原型上的属性并没有复制到其他地方,而是附加到新创建的对象上了,并可以和对象自身的属性引用一起协调运行。简单描述如下: 引用对象一个属性,如果其本身有,就返回,如果没有,就去看对象的原型,检查原型上是否有……,直到最后为 undefined

对象不仅与其构造器有关,还与构造器所创建对象的原型相关。在 JavaScript 中的每个对象都有一个名为 constructor 的隐式属性,该属性引用的是创建该对象的构造器。原型是实时附加在对象上的。

进一步的例子

这个例子进一步说明了,JavaScript 在查询属性引用的时候,首先是查询对象自身,如果不存在,才在原型上进行查找。

通过构造器判断对象类型

JavaScript 不仅可以利用原型协调属性引用,还可以通过 constructor 来知道是哪个构造器创建了对象实例。constructor 属性作为创建该对象的原始函数的引用,被添加在所有的实例上。利用该属性,可以验证实例的起源。 当然,instanceof 操作符也可以确定一个实例是否是由特定的函数构造器所创建。

《JavaScript忍者秘籍》挥舞函数

简介

闭包是一个函数在创建时允许该自身函数访问并操作该自身函数之外的变量时所创建的作用域。简而言之,闭包可以让函数访问所有的变量和函数,只要这些变量和函数存在于该函数声明时的作用域内就j行。

要记住:声明的函数在后续什么时候都可以被调用,即便是声明时的作用域消失之后。

一个简单的示例

注意:这些闭包结构不是可以轻易看到的,这样的话就会导致在存储和引用信息方面会直接影响性能 每个通过闭包进行信息访问的函数都有一个“锁链”,如果我们愿意,可以在它上面附加任何信息,但是你要时刻注意这方面的开销

使用闭包

私有变量

闭包的一种常见用法是封装一些信息作为“私有变量”,即限制作用域,以达到传统的私有变量的特性,通过闭包可以实现一个类似的功能。

实际的例子

回调和计时器

在这种情况下,函数都是在后期、未指定的时间进行异步调用,在这种情况下,经常需要访问外部数据。

回调的例子

计时器的例子

上述例子,还说明了一个重要的概念。函数在闭包里执行的时候,不仅可以在闭包创建的时刻点上看到这些变量的值,我们还可以对其进行更新。简而言之,闭包不是在创建的哪一个时刻点的状态的快照,而是一个真实的状态封装,只要闭包存在,就可以对其中的变量和函数进行修改。

绑定函数上下文

使用 callapply 方法来操作函数上下文,虽然有用,但是在面向对象的代码中,有潜在的危害性。

简单的例子

上述例子来源于一个流行 JavaScript 库的某个函数的简化,原始版本如下:

1
2
3
4
5
6
7
8
9
Function.prototype.bind = function() {
    var fn = this;
    var args = Array.prototype.slice.call(arguments);
    var object = args.shift();

    return function() {
        return fn.apply(object, args.concat(Array.prototype.slice.call(arguments)));
    };
};

完整的例子

要意识到,这里的 bind 函数,并不意味着它是 apply()call() 的一个替代方法。记住,该方法的潜在目的是通过匿名函数和闭包控制后续执行的上下文。这个重要的区别使得 apply()call() 对事件处理程序和定时器的回调进行延迟执行特别有帮助。内部原理就是下面要说的柯里化。

《JavaScript忍者秘籍》挥舞函数

匿名函数

通常来说, 匿名函数 的使用情况是创建一个供以后使用的函数。有些情况下,函数并不需要名称引用。

如果不是真的需要函数名称,我们为什么要如此费心去创建一个独立的、带有名称的全局函数。JavaScript 的强大威力取决于是否作为函数式语言来使用,函数式编程专注于:少、通常无副作用和将函数作为程序代码的基础构件块。为了不让不必要的函数名称污染全局命名空间,我们将大量创建小型函数进行传递,而不是构建包含大量命令语句的大型函数。

递归

递归要满足两个条件:

  1. 引用自身。
  2. 有终止条件。

实际例子

内联命名函数

尽管可以给内联函数命名,但是这些名称只能在自身函数内部才可见。内联函数的名称和变量名称类似,它们的作用域仅限于声明他们的函数。这也是为什么要将全局函数作为 window 的方法进行创建的原因,不使用 window 的属性,我们没有办法引用这些函数。

实际例子

callee属性 (ES5中已经不推荐使用)

使用 arguments 参数的 calle 属性, 是另一种递归的方式的实现方式。callee 属性引用的是当前所执行的函数,该属性可以作为一个可靠的方法引用函数自身,所有这些不同的函数引用计数,在我们面临大规模复杂程序时提供了很大的帮助。从而避免了采用硬编码或脆弱的变量/属性名依赖。

注意:这一属性已经不再推荐使用,并且在"use strict"模式下会抛错

实际例子

将函数视为对象

先给出一个简单的示例:

1
2
3
var obj = {};
var fn = function(){};
assert(obj && fn, "Both the object and function exist.");

注意:适当放置分号,有助于压缩代码技术的发挥。

因为函数是第一型对象,所以也可以像如下这样:

《JavaScript忍者秘籍》函数基础

函数基础

在 JavaScript 中函数是第一型对象,也就是说,函数可以共处,可以将其视为其他任意类型的 JavaScript 对象, 就好像普通的数据类型一样,函数可以被变量引用,或声明成对象字面量, 甚至作为参数进行传递。 如下所示:

  1. 它们可以通过字面量进行创建。
  2. 它们可以赋值给变量, 数组或其他对象的属性。
  3. 可以作为参数传递。
  4. 可以作为函数的返回值进行返回。
  5. 可以拥有动态创建并赋值的属性,还有方法。

由于我们的大部分代码运行,都是函数调用的结果,所以我们将会发现通用和强大的构造器将给我们提供很大的灵活性和力量。

除了可以像其他对象类型使用以上的功能以外,函数还有一个特殊的功能:调用,并且这些调用通常都是以异步方式进行的,比如浏览器中的事件处理。

浏览器编程和 GUI 桌面应用程序的唯一不同就是, 代码不负责事件轮询和派发, 而是浏览器帮我们处理,我们的职责是为浏览器中发生的各种事件建立处理程序。事件处理程序有一个更通用的名称:回调函数,但是事件处理程序只是回调的一个例子,我们可以在自己的代码中使用回调。需要特别注意的是: 浏览器中的事件轮询是单线程的,每个事件都是按照在队列中所放置的顺序来处理的。所有其他事件必须等到这个事件处理结束以后才能继续。浏览器把事件放到队列上的机制是在事件轮询模型之外,确定事件何时发生并把它们放到事件队列上的过程所处的线程,并不参与事件本身的处理。

JavaScript 语言最重要的功能之一是可以在代码的任何地方创建函数,只要能用表达式,就能创建函数。在一个函数不用被多个地方引用的时候,这一特性可以消除使用不必要的名称所带来的全局命名空间污染。在浏览器中,如果一个命名函数声明在顶层, window 对象上的同名属性则会引用到该函数。 简单验证函数声明

当我们创建一个函数时,我们不仅要关注该函数可用的作用域,还要关注函数自身创建的作用域,以及函数内部的声明是如何影响这些作用域的。在 JavaScript 中作用域是由 function 进行声明的,而不是代码块。声明的作用域是创建于代码块,但不是终结于代码块的。 函数作用域验证

  1. 变量声明的作用域开始于声明的地方,结束于所在函数的结尾,与代码嵌套无关。
  2. 命名函数的作用域是指声明该函数的整个函数范围,与代码嵌套无关。
  3. 对于作用域声明,全局上下文就像一个包含页面所有代码的超大型函数。

函数调用

事实证明,函数调用的方式对其函数内部的代码是如何执行的,有着巨大的影响,尤其是 this 参数。以下是函数调用的4种方式:

  1. 作为一个函数调用
  2. 作为一个方法调用,在对象上调用
  3. 作为构造器调用,创建一个新对象
  4. 通过 apply()call() 方法进行调用

函数参数

从实参到形参

如果传入的参数个数和声明的形参数量不一致,不会抛错。处理策略如下:

《JavaScript忍者秘籍》基础知识

优秀的测试用例具有三个重要的特征

1.可重用性:测试结果应该是高度可再生的,测试不依赖于外部因素。 1.简单性:测试应该只专注于测试一件事。 1.独立性:测试用例应该独立执行。

构建测试的两种主要方法

1.解构型测试用例:在消弱代码隔离问题时进行创建,以消除任何不恰当的问题。 1.构建型测试用例:从一个大家熟知的良好精简场景开始,构建用例,直到能够重现 bug 为止。

例子:构建型测试用例

1.创建一个精简的测试用例时,可以从几个已经包含最小功能的 HTML 文件开始,这些不同的起始文件可以包含不同的功能。例如,一个用于 DOM 操作,一个用于 Ajax 测试,一个用于动画等等。下面是一个测试 DOM 操作的测试用例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<script src="dist/jquery.js"></script>
<script>
    $(document).ready(function () {
        $("#test").append("test");
    });
</script>

<style>
    #test { width: 100px; height: 100px; background: red; }
</style>

<div id="test"></div>
</html>

1.使用干净的代码副本生成一个测试用例,我们使用 shell 脚本。

《Git In Practice》 Notes

Remote Operation

  1. The command git remote prune <git-repo-path> deletes any remote references to branches that have been deleted from the remote repository by other users. See more about this .

  2. Please be very careful when you run command git push --forced, it’s very dangerous because it will rewrite all you related histories in the remote side.

Markdown语法的一些难记住的点

  1. 唯一的限制是那些块级HTML元素,如 <div><table><pre><p> 等,必须使用空行与相邻内容分开,并且块元素的开始和结束标签之前不要留有空格或 TAB。

  2. 不要在块级 HTML 元素内使用 Markdown 格式化命令,但对于行内标签,比如 <span><del> 等,你可以随便用,如果需要,甚至可以使用 <a> 标签来代替 Markdown 自身的链接或图片语法。

  3. 不同于这些块级 HTML 元素,在 HTML 行内元素内的 Markdown 语法标记会被正确处理。

  4. 在 Mardown 代码行内标记和块级标记之中,<& 始终会被自动编码。这使得在 Markdown 文件中书写 HTML 代码更容易。

  5. 正常的段落不要以空白或 TAB 字符开始。

《正则表达式必知必会》笔记

下面是一些平时经常会遗忘的正则表达式的知识点:

  1. . 只能匹配除换行符以外的任何单个字符。

匹配换行符

  1. 在相关书籍中,专业名词普通字符的意思就是纯文本,而元字符代表的含义是特殊字符。

  2. 当你使用[A-z]去匹配所有的字符时,要记得这两个字符的 ASCII 码中还有 [] 等。

匹配时使用ASCII码

  1. 字符 - 只是一个普通的字符。

匹配符号-

  1. [ ] 中的 ^ 用来反义之后所有在当前括号里的数字。

匹配非数字字符

  1. \b :回退符,\f :换页符,\n :换行符,\r :回车符,\t :制表符,\v :垂直制表符。

匹配特殊符号

  1. \r\n 用于 Windows 系统中的文本行的结束标签,\n 用于 Unix 类系统。

HTML5 数据存储

简介

在HTML5之前,本地存储的唯一方案是使用 cookie1,缺点如下:

  • 需要处理过期数据
  • 同一个域中的每一次请求都会携带所有的 cookie
  • 安全性

HTML5 新增了更好、更便捷的本地存储功能2。它们一般具有如下优点:

  • 这些数据可以无限期地保存在用户计算机上
  • 不会主动跟随 HTTP 请求发送到服务端
  • 更大的存储空间
  • 方便使用 JavaScript 操作

对比如下:

存储类型 JS对象 作用和特点
本地存储 localStorage 用于长期保存网站的数据
会话存储 sessionStorage 用于临时保存针对一个窗口(标签页)的数据。在用户关闭窗口或标签页前,这些数据存在,之后会被删除,不受页面导航影响
数据库 indexDB3 创建离线应用,优化性能和改进本地存储

无论本地存储还是会话存储,都是与网站所在的域联系在一起的。如果是不同的网站页面,就不能同时访问一个数据源,类似地,如果使用不同的用户名登录,那么存取的也将是不同的本地数据。

存储/读取数据 API:

使用键值对的方式来操作 storage 对象

1
2
3
4
5
localStorage.setItem(key, value);
localStorage["key"] = value;

value = localStorage.getItem(key);
value = localStorage["key"]

删除/清空数据API:

1
2
3
localStorage.removeItem("key");

sessionStorage.clear();

遍历所有的数据项:

1
2
3
4
5
6
7
for(var i = 0; i < localStorage.length; i++) {
    var key = localStorage.key(i);

    var item = localStorage.getItem(key);

    console.log(item);
}

注意:在通过 localStorage 和 sessionStorage 保存数据的时候,该数据会自动被转换为文本字符串。