yangxiaoxie 发布的文章

JavaScript 高程设计——引用类型(二)

RegExp 类型

ECMAScript 通过 RegExp 类型来支持正则表达式。使用下面类似 Perl 的语法,就可以创建一个正则表达式。

var expression = / pattern / flags ;

正则表达式的匹配模式支持下列3个标志(flags):g ——全局,i ——不区分大小写,m ——匹配多行。

// 匹配字符串中所有"at"的实例
var pattern1 = /at/g;
// 匹配第一个"bat"或"cat",不区分大小写
var pattern2 = /[bc]at/i;
// 匹配所有以"at"结尾的3个字符的组合,不区分大小写
var pattern3 = /.at/gi;
// 与pattern1相同,只不过是使用构造函数创建的
var pattern4 = new RegExp("[bc]at", "i");

由于 RegExp 构造函数的模式参数是字符串,所以在某些情况下要对字符进行双重转义。所有元字符都必须双重转义,那些已经转义过的字符也是如此。

使用正则表达式字面量和使用 RegExp 构造函数创建的正则表达式不一样。在 ECMAScript 3 中,正则表达式字面量始终会共享同一个 RegExp 实例,而使用构造函数创建的每一个新 RegExp 实例都是一个新实例。

ECMAScript 5 明确规定,使用正则表达式字面量必须像直接调用 RegExp 构造函数一样,每次都创建新的 RegExp 实例。

RegExp 实例属性

RegExp 的每个实例都具有下列属性,通过这些属性可以取得有关模式的各种信息。

  • global:布尔值,表示是否设置了g标志。
  • ignoreCase:布尔值,表示是否设置了i标志。
  • lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0算起。
  • multiline:布尔值,表示是否设置了m标志。
  • source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。

RegExp 实例方法

RegExp 对象的主要方法是 exec(),该方法是专门为捕获组而设计的。exec() 接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回 null。返回的数组虽然是 Array 的实例,但包含两个额外的属性:indexinput 。其中,index 表示匹配项在字符串中的位置,而 input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串。

正则表达式的第二个方法是 test(),它接受一个字符串参数。在模式与该参数匹配的情况下返回 true;否则,返回 false

尽管 ECMAScript 中的正则表达式功能还是比较完备的,但仍然缺少某些语言(特别是 Perl)所支持的高级正则表达式特性。

Function 类型

每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

在函数内部,有两个特殊的对象:argumentsthis。其中,arguments 是一个类数组对象,包含着传入函数中的所有参数。虽然 arguments 的主要用途是保存函数参数,但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。

arguments.callee

this 引用的是函数据以执行的环境对象——或者也可以说是 this 值。

ECMAScript 5 中 caller 属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null

当函数在严格模式下运行时,访问 arguments.callee 会导致错误。ECMAScript 5 还定义了 arguments.caller 属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是 undefined。严格模式还有一个限制:不能为函数的 caller 属性赋值,否则会导致错误。

ECMAScript 中的函数包含两个属性:length和proto-type。其中,length属性表示函数希望接收的命名参数的个数。对于 ECMAScript 中的引用类型而言,prototype 是保存它们所有实例方法的真正所在。

在 ECMAScript 5 中,prototype 属性是不可枚举的,因此使用 for-in 无法发现。

每个函数都包含两个非继承而来的方法:apply()call() 。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。

function callSum1(num1, num2){    
    // 传入arguments对象    
    return sum.apply(this, arguments);
}

function callSum2(num1, num2){    
    // 传入数组    
    return sum.apply(this, [num1, num2]);
}

call() 方法与 apply() 方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call() 方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。

事实上,传递参数并非 apply()call() 真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。

ECMAScript 5 还定义了一个方法:bind()。这个方法会创建一个函数的实例,其 this 值会被绑定到传给 bind() 函数的值。

基本包装类型

ECMAScript 提供了3个特殊的引用类型:Boolean、Number 和 String。

单体内置对象

ECMA-262 还定义了两个单体内置对象:Global 和 Math。Global 对象的 encodeURI()encodeURIComponent() 方法可以对 URI(Uniform Resource Identifiers,通用资源标识符)进行编码,以便发送给浏览器。

Math.random() 方法返回介于0和1之间一个随机数,不包括0和1。

JavaScript 高程设计——引用类型(一)

引用类型的值(对象)是引用类型的一个实例。在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起。

对象是某个特定引用类型的实例。新对象是使用 new 操作符后跟一个构造函数来创建的。构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。

var person = new Object();

Object 类型

创建 Object 实例的方式有两种。第一种是使用 new 操作符后跟 Object 构造函数,如下所示:

var person = new Object();
person.name = "Nicholas";
person.age = 29;

另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。

var person = {    
    name : "Nicholas",    
    age : 29
};

Array 类型

创建数组的基本方式有两种。第一种是使用 Array 构造函数,如下面的代码所示。

var colors = new Array();
// 创建 length 值20的数组
var colors = new Array(20); 
// 码创建了一个包含3个字符串值的数组
var colors = new Array("red", "blue", "green");

// 创建一个包含3项的数组
var colors = Array(3);
// 创建一个包含1项,即字符串"Greg"的数组
var names = Array("Greg");

// 创建一个包含3个字符串的数组
var colors = ["red", "blue", "green"];

数组最多可以包含4 294 967 295个项,如果想添加的项数超过这个上限值,就会发生异常。

检测数组

对于一个网页,或者一个全局作用域而言,使用 instanceof 操作符就能得到满意的结果:

if (value instanceof Array){    
    //对数组执行某些操作
}

对于有还有多个框架的页面,需要使用 ECMAScript 5 新增的 Array.isArray() 方法:

if (Array.isArray(value)){    
    //对数组执行某些操作
}

如果数组中的某一项的值是 null 或者 undefined,那么该值在 join()toLocaleString()toString()valueOf()方法返回的结果中以空字符串表示。

栈方法

push() 方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。而 pop() 方法则从数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项。

队列方法

shift() 能够移除数组中的第一个项并返回该项,同时将数组长度减1。unshift()shift() 的用途相反:它能在数组前端添加任意个项并返回新数组的长度。

重排序方法

数组中已经存在两个可以直接用来重排序的方法:reverse()sort()

比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回0,如果第一个参数应该位于第二个之后则返回一个正数。

function compare(value1, value2) {    
    if (value1 < value2) {        
        return 1;     
    } else if (value1 > value2) {        
        return -1;     
    } else {        
        return 0;    
    }
}

var values = [0, 1, 5, 10, 15];
values.sort(compare);

// 15,10,5,1,0
alert(values);

reverse()sort() 方法的返回值是经过排序之后的数组。

操作方法

  • concat: concat() 方法可以基于当前数组中的所有项创建一个新数组。具体来说,这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat() 方法传递参数的情况下,它只是复制当前数组并返回副本。如果传递给 concat() 方法的是一或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中。如果传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。

  • slice: slice() 能够基于当前数组中的一或多个项创建一个新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。

  • splice: splice() 的主要用途是向数组的中部插入项,但使用这种方法的方式则有如下3种。

var colors = ["red", "green", "blue"];
// 删除第一项
var removed = colors.splice(0,1);
// green,blue
alert(colors);
// red,返回的数组中只包含一项
alert(removed);

// 从位置1开始插入两项
removed = colors.splice(1, 0, "yellow", "orange");
// green,yellow,orange,blue
alert(colors);
// 返回的是一个空数组
alert(removed);

// 插入两项,删除一项
removed = colors.splice(1, 1, "red", "purple");
// green,red,purple,orange,blue
alert(colors);
// yellow,返回的数组中只包含一项
alert(removed);

位置方法

ECMAScript 5 为数组实例添加了两个位置方法:indexOf()lastIndexOf() 。这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf() 方法从数组的开头(位置0)开始向后查找,lastIndexOf() 方法则从数组的末尾开始向前查找。

迭代方法

ECMAScript 5 为数组定义了5个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响 this 的值。传入这些方法中的函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。

  • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true
  • filter():对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
  • forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  • some():对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true

缩小方法

ECMAScript 5 还新增了两个缩小数组的方法:reduce()reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。

这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为缩小基础的初始值。传给 reduce()reduceRight() 的函数接收4个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。

var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){    
    return prev + cur;
});

// 15
alert(sum);

Date 类型

在调用 Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数。ECMAScript 提供了两个方法:Date.parse()Date.UTC()

var now = new Date();

var someDate = new Date(Date.parse("May 25, 2004"));
var someDate = new Date("May 25, 2004");

// GMT时间2000年1月1日午夜零时
var y2k = new Date(Date.UTC(2000, 0));

// GMT时间2005年5月5日下午5:55:55
var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

var y2k = new Date(2000, 0);

ECMAScript 5 添加了 Data.now() 方法,返回表示调用这个方法时的日期和时间的毫秒数。

// 取得开始时间
// 在不支持now()的浏览器中使用 var start = +new Date(); 
var start = Date.now();

// 调用函数 doSomething();

// 取得停止时间
var stop = Date.now(),    
    result = stop – start;

日期格式化方法(略)

日期/时间组件方法(略)

JavaScript 高程设计——变量、作用域和内存问题

基本类型和引用类型的值

5种基本数据类型:Undefined、Null、Boolean、Number和String是按值访问的,因为可以操作保存在变量中的实际的值。

引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。

基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。参数只能按值传递。

虽然在检测基本数据类型时 typeof是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。为此,ECMAScript 提供了 instanceof 操作符,其语法如下所示:

result = variable instanceof constructor

使用 typeof 操作符检测函数时,该操作符会返回 "function"。在 IE 和 Firefox 中,对正则表达式应用 typeof 会返回 "object",在 Safari 和 Chrome 中返回 "function"

执行环境及作用域

每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。

如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。

try-catch 语句的 catch 块和 with 语句可以延长作用域链。

垃圾收集

垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以备将来收回其占用的内存。用于标识无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两个策略——标记清除和引用计数。

JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

到2008年为止,IE、Firefox、Opera、Chrome 和 Safari 的 JavaScript 实现使用的都是标记清除式的垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。

另一种不太常见的垃圾收集策略叫做引用计数(referencecounting)。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。

因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个做法叫做解除引用(dereferencing)。

JavaScript 高程设计——基本概念

语法

严格模式

"use strict";

关键字和保留字

关键字

break   case    catch   continue    default delete  
do  else    finally for function    if  in  instanceof  
new return  switch  this    throw   try typeof  
var void    while   with    debugger*

保留字

abstract    boolean byte    char    class   const   
debugger    double  enum    export  extends final   
float   goto    implements  import  int interface   
long    native  package private protected   public  
short   static  super   synchronized    throws  
transient   volatile    let yeid

数据类型

ECMAScript 中有5种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和String。

还有一种复杂数据类型——Object。

鉴于 ECMAScript 是松散类型的,因此需要有一种手段来检测给定变量的数据类型—— typeof 就是负责提供这方面信息的操作符。

null == undefined
// true

永远不要测试某个特定的浮点数值。

ECMAScript 能够表示的最小值保存在 Number.MIN_VALUE 中——在大多数浏览器中,这个值是5e-324;能表示的最大数值保存在 Number.MAX_VALUE 中。超出此范围使用 Infinity-Infinity 值。使用 isFinite() 函数判断是否在此范围内。

NaN 本身有两个非同寻常的特点。首先,任何涉及 NaN 的操作都会返回 NaN,这个特点在多步计算中有可能导致问题。其次,NaN 与任何值都不相等,包括 NaN 本身。使用 isNaN() 函数。

parseInt()/parseFloat()parseFloat(),十六进制格式的字符串则始终会被转换为0。

ECMAScript 中的字符串是不可变的,也就是说,字符串一旦被创建,它们的值就不能改变。

Object 的每个实例都具有下列属性和方法:ConstructorhasOwnProperty(propertyName)isPrototypeOf(object)propertyIsEnumerable(propertyName)toLocaleString()toString()valueOf()

ECMA-262 不负责定义宿主对象,因此宿主对象可能会也可能不会继承 Object

操作符

逗号操作符还可以用于赋值。在用于赋值时,逗号操作符总会返回表达式中的最后一项。

// num的值为0
var num = (5, 1, 4, 8, 0);

语句

for-in 语句是一种精准的迭代语句,可以用来枚举对象的属性。以下是for-in 语句的语法:

for (property in expression) statement

for (var propName in window) {     
    document.write(propName);
}

with 语句的作用是将代码的作用域设置到一个特定的对象中。with 语句的语法如下:

with (expression) statement;

with(location){    
    var qs = search.substring(1);    
    var hostName = hostname;    
    var url = href;
}

可以在 switch 语句中使用任何数据类型(在很多其他语言中只能使用数值),无论是字符串,还是对象都没有问题。其次,每个 case 的值不一定是常量,可以是变量,甚至是表达式。

switch 语句在比较值时使用的是全等操作符,因此不会发生类型转换。

函数

关于 arguments 的行为,还有一点比较有意思。那就是它的值永远与对应命名参数的值保持同步。不过,这并不是说读取这两个值会访问相同的内存空间;它们的内存空间是独立的,但它们的值会同步。但这种影响是单向的:修改arguments[0]的值会自动同步到命名参数中;而修改命名参数不会改变 arguments 中对应的值。另外还要记住,如果只传入了一个参数,那么为 arguments[1] 设置的值不会反应到命名参数中。这是因为 arguments 对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。

ECMAScript 中的所有参数传递的都是值,不可能通过引用传递参数。

JavaScript 高程设计——在HTML中使用 JavaScript

<script> 元素

HTML 4.01 为 <script> 定义了以下6个属性:

  • async:可选。表示应该立即下载脚本,但不应妨碍页面中的其他操作。只对外部脚本文件有效;
  • charset:可选。表示通过 src 属性指定的代码的字符集。很少有人用这个属性;
  • defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行;
  • language:已废弃;
  • src:可选。表示包含要执行代码的外部文件;
  • type:可选。表示编写代码使用的脚本语言的内容类型;目前 type 属性的值依旧还是 text/javascript。不过这个属性并不是必须的,因为没有指定时默认值也是 text/javascript

在使用 <script> 元素嵌入 JavaScript 代码时,不要在代码中的任何地方出现 "</script>"字符串。按照接卸嵌入式代码的规则,当浏览器遇到字符串 "</script>" 时,就会认为是结束的 </script>标签。而通过转义字符 “\” 可解决这个问题:"<\/script>"。例如:

<script>
    function sayScript(){
        alert("<\/script>");
    }
</script>

在引入外部文件时,只能像下面这样写:

<script type="application/javascript" src="example.js"></script>

而不能使用如下形式:

<script type="application/javascript" src="example.js"/>

引入了外部文件的 <script> 元素不能再嵌入代码,嵌入的代码将会被忽略,只外部文件起作用。

无论如何包含代码,只要不存在 deferasync 属性,浏览器都会按照 <script> 元素在页面中出现的先后顺序对它们依次进行解析。

标签的位置

现代 Web 应用程序一般都把全部的 JavaScript 引用放在 元素中页面内容的后面。如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title></title>
</head>
<body>


<script src="example.js"></script>
</body>
</html>

延迟脚本

使用 defer 属性,相当于告诉浏览器立即下载,但延迟执行。IE4/Firefox3.5/Safari5/Chrome 是最早支持此属性的浏览器,其他浏览器会忽略此属性,因此把延迟脚本放在页面底部仍然是最佳选择。使用例子:

<script defer src="example.js"></script>

异步脚本

HTML 5 为 <script> 元素定义了 async 属性。这个属性与 defer 属性类似,都用来改变处理脚本的行为;但与 defer 属性不同的是,标记为 async 脚本并不保证按照指定它们的先后顺序执行。指定 async 属性的目的不是让页面等待两个脚本下载和执行,从而异步加载页面其他内容。因此,建议异步脚本不要在加载期间修改 DOM。异步脚本一定会在页面的 load 事件前执行。

<script async src="example.js"></script>

文档模式

IE 5.5 引入了文档模式的概念,而这个概念是通过使用文档类型(doctype)切换实现的。最初的两种文档模式是:混杂模式(quirks mode)和标准模式(standards mode)。这两种模式主要影响 CSS 内容的呈现,但在某些情况下也会影响 JavaScript 的解释执行。在此之后,IE 又提出一种所谓的准标准模式(almost standards mode)。

如果文档开始处没有发现文档类型声明,则所有的浏览器都会默认开启混杂模式;对于标准模式可以通过使用下面任何一种文档类型来开启:

<!-- HTML 4.0.1 严格型 html:4s -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- HTML 5 !-->
<!DOCTYPE html>

而对于准标准模式,则可以使用过渡型(transitional)或框架集型(frameset)文档类型来触发:

<!-- HTML 4.0.1 过渡型 html:4t -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
    "http://www.w3.org/TR/html4/loose.dtd">
<!-- HTML 4.0.1 框架集型 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" 
    "http://www.w3.org/TR/html4/frameset.dtd">

<noscript> 元素

<body> 中使用此元素。