JavaScript中一个经典的正则例子

学习知识的最好方法就是看例子.(个人观点) 因为书上总是会把概念和公式讲的晦涩难懂,才显得作者高大上.所以我总是喜欢 通过例子学习知识.每次写正则的时候总是喜欢查看例子学习如何使用,因为正则实在太耗脑细胞了,记不住,也没必要记住.学会 如何使用并在项目中熟练运用就好了,经常使用的话也就自然记住了.

JavaScript 语言精粹上面有个非常棒的学习例子,每次写正则的时候都会想起它.所以还是记一下分享给大家也方面我查询啦~

var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
var url = 'http://www.ora.com:80/goodparts?q#fragment';
var result = parse_url.exec(url);
var names = ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash'];
var blanks = '     ';
var i;
for (i = 0; i < names.length; i += 1) {
  console.log(names[i] + ':' + blanks.substring(names[i].length), result[i]);
}
1
2
3
4
5
6
7
8
9

让我们分解 parse_url 的各个部分,看看它是如何工作的:

^
1

^字符表示此字符串的开始.它是一个锚,指引 exec 不要跳过那些不像 URL(non-URL-like)的前缀,只匹配那些从开头就像 URL 一样的字符串:

(?:([A-Za-z]+):)?
1

这个因子匹配一个协议名,但仅当它后面跟随一个:(冒号)的时候才匹配.(?:...)表示一个非捕获型分组(noncapturing group). 后缀 ? 表示这个分组是可选的. 问号表示重复 0 次或一次.(...)表示一个捕获型分组(capturing group).一个捕获型分组会复制它 所匹配的文本,并把其放到 result 数组里.每个捕获型分组都会被指定一个编号.第一个捕获型分组的编号是 1,所以该分组所匹配的文本副本 会出现在 result[1]中.[...]表示一个字符类,A-Za-z 这个字符类包含 26 个大写字母和 26 个小写字母.连字符( - )表示范围从 A 到 Z. 后缀 + 表示这个字符类会被匹配一次或者多次.这个组后面跟着字符:,它会按字面进行匹配:

(\/{0,3})
1

下一个因子是捕获型分组 2./表示该匹配/(斜扛).它是\(反斜杠)来进行转义,这样它就不会被错误地解释为这个正则表达式的结束符.后缀{0,3}表示 / 会被匹配 0 次,或者 1~3 次:

([0-9.\-A-Za-z]+)
1

下一个因子是捕获型分组 3.它会匹配一个主机名,由一个或多个数字、字母,以及 . 或 - 字符组成. - 会被转义为 \- 以防止与表示范围的连字符想混淆:

(?::(\d+))?
1

下一个可选的因子匹配端口号,它是由一个前置 : 加上一个或多个数字而组成的序列. \d 表示一个数字字符.一个或多个数字组成的数字串会被捕获型 分组 4 捕获:

(?:\/([^?#]*))?
1

接下来是另一个可选的分组.该分组以一个 / 开始. 之后的字符类[^?#]以一个 ^ 开始, 它表示这个类包含除了?和#之外的所有字符.*表示这个字符类 会被匹配 0 次或多次.

注意这里的处理是不严谨的.这个类匹配除?和#之外的所有字符,其中包括了行结束符、控制字符,以及其他大量不应在此被匹配的字符.大多数情况下,它会按照 我们的预期去做,但某些恶意文本可能会有渗透进来的风险.不严谨的正则表达式是一个常见的安全漏洞发源地.写不严谨的正则表达式比写严谨的正则表达式要容易得多.

(?:\?([^#]*))?
1

接下来,还有一个以一个 ? 开始的可选分组.它包含捕获型分组 6,这个分组包含 0 个或多个非#字符.

(?:#(.*))?
1

最后一个可选分组是以 # 开始的 .会匹配除行结束符以外的所有字符.

$
1

$表示这个字符串的结束.它保证在这个 URL 的尾部没有其他更多内容了.

JavaScript函数中的闭包

JavaScript 中的闭包通常被视作 JavaScript 的高级特性.但是我看了好几次文档也没有明白过来.也没有实际应用过.最近重新学习 JavaScript 打算好好学习下.

# 什么是闭包

闭包用通俗的说法来理解就是子函数可以使用父函数中的局部变量,这种行为叫做闭包.这个变量不是在这个代码块内或者全局上下文中定义的,而是 在定义代码块的环境中定义的(局部变量).一个经典的例子就是:

function a() {
  var i = 0;
  function b() {
    alert(++i);
  }
  return b;
}
var c = a();
c();
1
2
3
4
5
6
7
8
9

函数 b 嵌套在函数 a 内部,函数 a 返回函数 b.这样在执行了 var c = a() 之后,变量 c 变指向了函数 a.再指向 c()就会弹窗显示 i 的值.这段代码就创建了一个 闭包.因为函数 a 外的变量 c 引用了函数 a 内的函数 b.就是说,当函数 a 的内部函数 b 被函数 a 外的一个变量引用的时候,就创建了一个闭包.

# 闭包的用途

  • 可以读取函数内部的变量
  • 这些变量的值始终保持在内存中
  • 模拟私有变量

上面的例子中,i 的值只有 b 函数可以读取.当你不断执行 c()的时候,发现 i 值不断增大,说明 i 没有被内存回收.

# 使用闭包构造模块

模块是一个提供接口却隐藏状态与实现的函数或对象.通过使用函数产生模块,我们几乎可以完全摈弃全局变量的使用. 举例来说,假定我们想要给 String 增加一个 deentityify 方法.它的任务是寻找字符串中的 HTML 字符实体并把它们替换为对应的字符. 这就需要在一个对象中保存字符实体的名字和它们对应的字符.但我们该在哪里保存这个对象呢?我们可以把它放到一个全局变量中, 但全局变量是魔鬼.我们可以把它定义在该函数的内部,但是那会带来运行时的损耗,因为每次执行该函数的时候该字面量都会被求值一次. 理想的方式就是使用闭包实现.

Function.prototype.method = function(name, func) {
  this.prototype[name] = func;
  return this;
};
String.method(
  'deentityify',
  (function() {
    //字符实体表,它映射字符实体打名字到对应的字符.
    var entity = {
      quot: '"',
      lt: '<',
      gt: '>'
    };
    return function() {
      //查找以'&'开头和';'结束打子字符串.如果找到就替换.
      return this.replace(/&([^&;]+);/g, function(a, b) {
        var r = entity[b];
        return typeof r === 'string' ? r : a;
      });
    };
  })()
);
cosole.log('&lt;&quot;&gt;'.deentityify());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

通过给 Function.prototype 增加一个 method 方法,我们下次给对象增加方法的时候就不必键入 prototype 这几个字符,省掉了麻烦. 请注意最后一行,我们用()运算法立刻调用我们刚刚构造出来的函数.这个调用所创建并返回的函数才是 deentityify 方法.
关于这个的用法请参见 wiki:IIFE (opens new window)

# 闭包的缺点

闭包会使局部变量始终存在,内存消耗很大.在 IE 浏览器中它会很容易导致泄露内存. 还有就是不要在循环中引用闭包.

# 参考资料

JavaScript函数调用的四种方式

JavaScript 函数调用除了声明时定义的形式参数,每个函数还接收两个附加的参数:this 和 arguments.参数 this 在面向的对象 编程非常重要,它的值取决于调用的模式.典型的两种函数定义方式有:

var add = function(a, b) {
  return a + b;
};
function add(a, b) {
  return a + b;
}
1
2
3
4
5
6

这两种函数声明的区别是什么,是我上次百度面试的一个问题.当时没回答出来,很是尴尬.现在问了前端菊苣之后得出的答案是: 一个是声明了一个变量 add,并给它赋初始值(一个匿名函数)另一个是声明了一个名为 add 的具名函数区别有:

  1. add.name 不同,trace 的时候显示也不同(第一个 add.name 是"",第二个是"add")
  2. 由于声明会被提升,但赋值语句不会,所以在语句之前访问 add 的时候结果不同(这点老 IE 还有点区别)

在 JavaScript 中四种调用模式分别是:方法调用模式,函数调用模式,构造器调用模式和 apply 调用模式.这些模式在如何初始化关键 参数 this 上存在差异.

# 方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法.当一个方法被调用时,this 被绑定到该对象.如果调用表达式包含一个提取属性 的动作(即包含一个 . 点表达式或[subscript]下标表达式),那么它就是被当做一个方法来调用.

var myObject = {
  value: 0,
  increment: function(inc) {
    this.value += typeof inc === 'number' ? inc : 1;
    console.log(inc);
  }
};
myObject.increment(); // 1
myObject.increment(2); // 3
1
2
3
4
5
6
7
8
9

方法可以使用 this 访问自己所属的对象,所以它能从对象中取值或对对象进行修改.this 到对象的绑定发生在调用的时候.这个"超级"延迟绑定(very late binding) 使得函数可以对 this 高度复用.通过 this 可取得他们所属对象的上下文的方法成为公共方法(public method).

# 函数调用模式

当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的:

var sum = add(3, 4);
1

以此模式调用函数时,this 被绑定到全局对象(浏览器中就是 window 对象).这是语言设计上的一个错误.倘若语言设计正确,那么当内部函数被调用时,this 应该仍然绑定到 外部函数的 this 变量.这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的 this 被绑定了错误的值,所以不能共享该方法对对象的访问权.幸运的是, 有一个很容易的解决方案:如果该方法定义一个变量并给它赋值为 this,那么内部函数就可以通过那个变量访问到 this.按照约定,我们把那个变量命名为 that:

myObject.double = function() {
  //给myObject增加一个 double 方法
  var that = this;
  var helper = function() {
    that.value = add(that.value, that.value);
    console.log(this);
  };
  helper(); //以函数的形式调用helper
};
myObject.double(); //以方法的形式调用double
1
2
3
4
5
6
7
8
9
10

# 构造器调用模式

如果在一个函数前面带上 new 来调用,那么背地里将会创建一个连接到该函数的 prototype 成员的新对象,同时 this 会被绑定到那个新对象上.

//创建一个名为Quo的构造器函数,它构造一个带有status属性的对象.
var Quo = function(string) {
  this.status = string;
};
//给 Quo的所以实例提供一个名为 get_status 的公共方法.
Quo.prototype.get_status = function() {
  return this.status;
};
//构造一个 Quo 实例
var myQuo = new Quo('confused');
1
2
3
4
5
6
7
8
9
10

一个函数,如果创建的目的就是希望结合 new 前缀来调用,那它就被称为构造器函数.这种构造器函数不推荐使用,详细内容请看《JavaScript 语言精粹》后续内容.

# Apply 调用模式

因为 JavaScript 是一门函数式的面向对象编程语言,所以函数可以拥有方法.

apply 方法让我们构建一个参数数组传递给调用函数.它允许我们选择 this 的值.apply 方法接收两个参数,第一个是要绑定给 this 的值,第二个就是一个参数数组.

var array = [3, 4];
var sum = add.apply(null, array);

var statusObject = {
  status: 'A-OK'
};
//statusObject并没有继承自 Quo.prototype,但我们可以在 statusObject 上调用
//get_status 方法,尽管 statusObject 并没有一个名为 get_status 的方法.
var status = Quo.prototype.get_status.apply(statusObject);
1
2
3
4
5
6
7
8
9

以上内容摘录于《JavaScript 语言精粹》.