JavaScript诞生于1995年,最早的作用是在本地客户端处理数据验证。

作者认为要想完全的理解和掌握JavaScript,关键在于弄清它的本质,历史和局限性。

1997年制定标准ECMA-262的ECMAScript标准

1.2 JavaScript的实现

一个完整的JavaScript由下面三个不同的部分组成:

核心(ECMAScript) 由ECMA-262定义,提供核心语言功能

文档对象模型(DOM) 提供访问和操作网页内容的方法和接口

浏览器对象模型(BOM) 提供和浏览器交互的方法和接口

2.在HTML中使用JavaScript

<script>标签

<script>从上到下依次解析,解析完一个才会解析下一个

所以为了降低浏览器延迟,通常把<script>标签放到<body>标签的最后边

<script>还有延迟脚本(defer) 和 异步脚本(async)的属性

3.1语法

1 区分大小写

2标识符

第一字符必须是一个字母 下划线(_) 美元符($)

其他字符可以是字母 下划线 美元符或者数字

(推荐使用驼峰大小写格式,也就是第一个字母小写,剩下的每个有意义的单词首字母大写)

3注释

单行注释

//这是一个单行注释

多行注释

/*   这是一个

多行注释

*/

4严格模式

在严格模式下,ECMAScript 3中的一些不确定的行为将得到处理,而且对某些不安全的操作也会抛出错误

5语句

ECMAScript 中的语句以一个分号结尾,如果省略分号,则由解析器确定语句的结尾(建议不要省略)

3.2 关键字和保留字

ECMA-26描述了一组具有特定用途的关键字。按照规则,关键字是语言保留的,不能用作标识符。保留字也不行。

3.3 变量

ECMAScript的变量是松散类型的,松散类型可以用来保存任何类型的数据。每个变量仅仅是用于保存值的占位符而已。定义变量要使用var操作符,var是一个关键字,后跟变量名(即一个标识符);

省略var操作符可以定义全局变量,但是不推荐,不利于维护,而且会造成不必要的混乱。

可以一条语句定义多个变量

var a='hi',b=false,c;

3.4 数据类型

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

还有一种复杂数据类型:Object。Object本质上是由一组无序的名值对组成的。

ECMAScript不支持任何创建自定义类型的机制,所有值最终都将是上述6种数据类型之一。

3.4.1 typof操作符

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

typeof 可能返回的字符串:

  • "undefined"——这个值未定义
  • "boolean"——这个值是布尔值
  • "string"——这个值是字符串
  • "number"——这个值是数值
  • "object"——这个值是对象或null
  • "function"——这个值是函数

注意,typeof是一个操作符而不是函数,typeof后的圆括号尽管可以使用,但不是必须的。

typeof操作符还可以用来区分函数和其他对象(函数在ECMAScript中是对象,而不是一种数据类型)

3.4.2 Undefined类型

在使用var声明变量但未对其初始化时,这个变量就是Undefined。

一般不需要显式地把一个变量设置为undefined值得情况,字面值undefined的主要目的是用于比较。

引入这个值是为了正式区分空对象指针与未初始化的变量。

对未初始化和未声明的变量执行typeof操作符都返回undefined值(即便未初始化的变量会自动被赋予undefined值,但显式地初始化变量依然是明智的选择,如果全都初始化的话,undefined便是未声明)

3.4.3 Null类型

Null类型是第二个只有一个值的数据类型,这个特殊的值就是null。

从逻辑角度来看,null值表示一个空对象指针,这也正是使用typeof操作符检测null值会返回"object"的原因。

var car = null;
alert(typeof car);    //"object"

如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为null而不是其他值,这样一来,只要直接检测null值就可以知道相应的变量是否已经保存了一个对象的引用了。

if(car != null){//对car对象执行某些操作
}

实际上,undefined值是派生自null值的,因此ECMA-262规定对他们的相等性测试要返回true:

alert(null == undefined);    //true

注意,虽然如此但是无论在什么情况下都没有必要把一个变量的值显式地设置为undefined。

只要意在保存对象的变量还没有真正的保存对象,就应该让该变量保存null值。

这样不仅可以体现null作为空对象指针的惯例,也有助于进一步null和undefined。

3.4.4 Boolean类型

Boolean类型是ECMAScript中使用的最多的一种类型,该类型只有两个字面值:true和false。

注意:这两个值和数字值不是一回事,因此true不一定等于1,fales不一定等于0;

true 和 false区分大小写,例如True和False不是Boolean值,只是标识符。

ECMAScript中所有类型的值都有与这两个Boolean的值等价的值

任何数据类型的值调用Boolean()函数,总会返回一个Boolean值。

各种数据类型及其对应的转换规则

数据类型 转换为true的值 转换为false的值
Boolean true false
String 任何非空字符串 空字符串
Number 任何非零数字值(包括无穷大) 0和NaN
Object 任何对象 bull
Undefined N/A(not applicable 不适用) undefined

例:

var message = "Hello world!";
if(message){alert("Value is true"); //会弹出
}

3.4.5 Number类型

Number类型使用IEEE754格式来表示整数和浮点数值。

1.浮点数值

由于保存浮点数值需要的内存空间是保存整数值的两倍,因此ECMAScript会不失时机地将浮点数值转换为整数值。如果小数点后面没有任何数值或者浮点数值本身表示的就是一个整数(如1.0)那么该数值也会被转换为整数。

var floatNum1 = 1.;    //小数点后边没有数字--解析为1
var floatNum2 = 10.0;    //整数--解析为10

对于那些极大或极小的数值,可以用e表示法(即科学计数法)表示的浮点数值表示。

例:

3.125e7等于3.125乘以10的七次方

3e-7 等于0.0000003

注意:浮点数值最高精度是17位小数,但计算精确度远不如整数。

例:0.1+0.2的结果不是0.3,而是0.30000000000000004。因此永远不要测试某个特定的浮点数值。(关于浮点数值计算会产生舍入误差:这是使用基于IEEE754数值的浮点计算的通病)

2.数值范围

由于内存的限制,并不能保存所有数值。

最小数值Number.MIN_VALUE

最大数值Number.MAX_VAlue

超出这个范围数值会被自动转换成特殊的Infinity值(正无穷),

如果这个值是负值则会被转换为-Infinity(负无穷)。

3.NaN

NaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来就要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。

例如,其他编程语言中。任何数除以0都会导致错误,从而停止代码执行。但是在ECMAScript中,任何数除以0会返回NaN,因此不会影响其他代码的执行。

NaN有两个非同寻常的特点:

任何涉及NaN的操作(例如NaN/10)都会返回NaN

NaN与任何值都不相等,包括NaN本身。

例:

alert(NaN == NaN);    //false

针对NaN这两个特点,ECMAScript定义了isNaN()函数。

这个函数会帮我们确定这个参数是否“不是数值”。

例:

alert(isNaN(NaN));    //true
alert(isNaN(10));    //false (10是一个数值)
alert(isNaN("10"));    //false (可以被转换成数值10)
alert(isNaN("blue"));    //true (不可以被转换成数值)
alert(isNaN(true));    //false (可以被转换成数值1)

4.数值转换

有三个函数可以把非数值转换为数值:Number(),parseInt(),和parseFloat()。

函数Number()可以用于任何数据类型,另外两个函数专门用于把字符串转换成数值。

Number()函数的转换规则如下:

如果是Boolean值,true和false将分别被转换为1和0

如果是数字值,只是简单的传入和返回

如果是null值,返回0

如果是undefined,返回NaN

如果是字符串:

  • 如果字符串只包含数字(包含前面带加号或头号的情况),则将其转换为十进制数值。例:"1"-->1,"123"-->123,"011"-->11(前导的零被忽略了)
  • 如果字符串中包含有效的浮点格式。例:”"1.1"-->1.1(前导的零也会被忽略)
  • 如果字符串中包含有效的十六进制格式。例:"0xf",将其转换为相同大小的十进制整数值。
  • 如果字符串是空的(不包含任何字符),则将其转换为0。
  • 如果字符串中包含除上述格式之外的字符,则将其转换为NaN
  • 如果是对象,则调用对象的valueOf()方法,如果转换的结果是NaN,则调用toString()方法

例:

var num1 = Number("Hello world");    //NaN
var num2 = Number("");    //0
var num3 = Number("000011");    //11
var num4 = Number(true);    //1

由于Number()函数在转换字符串时比较复杂而且不够合理,因此在处理整数的时候更常使用的是parseInt()函数

parseInt()函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字符串前面的空调格,直到找到一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN,也就会说用parseInt()转换空字符传会返回NaN(Number()对空字符串返回0)。

如果第一个字符是数字,则继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。例如:"1234blue"会转换为1234,“22.5”会被转换为22,因为小数点并不是有效的字符。

如果字符串中的第一个字符是数字字符,parseInt()也能识别出各种整数格式(十进制、八进制、十六进制’)。例:字符串"0x"开头切后边跟数字,会被当做一个十六进制整数。

例:

var num1 = parseInt("1234blue");    //1234
var num2 = parseInt("");    //NaN
var num3 = parseInt("0xA");    10(十六进制数)
var num4 = parseInt("22.5");    //22
var num5 = parseInt("070");    //56(八进制数)
var num6 = parseInt("70");    //70(十进制数)

注意:parseInt()解析八进制字面量的字符串时,ECMAScript3 和5 存在分歧

例如:var num = parseInt("070");

在ECMAScript 3 JavaScript引擎中,“070”被当做八进制字面量,因此转换后的值是十进制的56,而在ECMAScript 5 JavaScript引擎中,parseInt()已经不具有解析八进制的能力,因此前导的零无效。转换后的值是十进制的70

为了消除在使用parseInt()函数时可能导致的上述困惑,可以为这个函数提供第二个参数:转换时使用的基数(即多少进制)。

例:

var num1 = parseInt("10",2);    //2 (按二进制解析)
var num2 = parseInt("10",8);    //8 (按八进制解析)
var num3 = parseInt("10",10);    //10 (按十进制解析)
var num4 = parseInt("10",16);    //16 (按十六进制解析)

多数情况下,我们要解释的都是十进制数值,因此始终将10作为第二个参数都是非常有必要的。

var num1 = parseInt("10",10);    //10

parseFloat()也是从第一个字符(位置0)开始解析每个字符。一直解析到字符串末尾,或者解析到遇到一个无效的浮点数字字符为止。(也就是说,字符串中第一个小数点有效,第二个无效);例子:"22.34.50"将会被转换为22.34。

注意:1.parseFloat()只解析十进制数,因此他没有第二个参数制定基数的用法。

2.如果字符串包含的是一个可解析为整数的数(没有小数点,或者小数点后边都是0),parseFloat()会返回整数

例:

var num1 = parseFloat("1234blue");    //1234(整数)
var num2 = parseFloat("0xA");    //0(不支持十六进制)
var num3 = parseFloat("22.5");    //22.5
var num4 = parseFloat("22.34.6");    //22.34
var num5 = parseFloat("0908.5");    //908.5
var num6 = parseFloat("3.125e7");    //31250000

3.4.6 String类型

String类型用于表示由零或多个16位Unicode字符组成的字符序列,即字符串。字符串可以由双引号("")或单引号('')表示。

例:

var color = "pink";
var color2 = 'blue';

1.字符字面量

String数据类型包含一些特殊的字符字面量,也叫转义序列,用于表示非打印字符,或者有其他用途的字符。

字面量 含义
\n 换行
\t 制表
\b 空格
\r 回车
\f 进纸
\\ 斜杠

例:

var text = "This is the letter sigma: \u03a3.";
alert(text.length);    //28

任何字符串的长度都可以通过访问其length属性获得。

2.字符串的特点

ECMAScript中的字符串是不可改变的,也就是说,字符串一旦创建,他的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,例如:

var lang = "Java";
lang = lang + "Script";

第二行代码把lang的值重新定义为“JavaScript”,实现过程如下:首先创建一个能容纳10个字符的新字符串,然后在这个字符串中填充“Java”和“Script”,最后一步是销毁原来的字符串“Java”和“Script”,因为这两个字符串已经没用了。(这也是旧版浏览器例如ie6,Firefox1.0)中拼接字符串时速度很慢的原因所在。

3.转换为字符串

要把一个值转换为一个字符串有两种方式:toString()方法String()转型函数。

几乎每个值都有toString()方法,这个方法唯一要做的就是返回相应值的字符串表现。

例:

var age = 11;
var ageAsString = age.toString();    //字符串"11"
var found = true;
var foundAsString = found.toString();    //字符串"true"

数值、布尔值,对象和字符串值都有toString()方法。但null和undefined值没有这个方法。

调用数值的toString()方法时,可以传递一个参数:输出数值的基数。通过传递基数,toString可以输出二进制、八进制‘十六进制,乃至其他任何有效进制格式表示的字符串值。

例:

var num = 10;
alert(num.toString());    //"10"
alert(num.toString(2));    //"1010"
alert(num.toString(8));    //"12"
alert(num.toString(10));    //"10"
alert(num.toString(16));    //"a"

注意,默认的(没有参数的)输出值与指定基数10时的输出值相同。

在不知道要转换的值是不是null和undefined的情况下,还可以使用转型函数String(),这个函数能够将任何类型的值转换为字符串。

String()函数遵循下列转换规则:

如果有toString()方法,则调用该方法(没有参数)并返回相应的结果;

如果值是null,则返回“null”

如果值是undefined,则返回“undefined”

例:

var value1 = 10;
var value2 = true;
var value3 = null'
var value4;
alert(String(value1));    //"10"
alert(String(value2));    //"true"
alert(String(value3));    //"null"
alert(String(value4));    //"undefined"

要把某个数值转换为字符串,可以使用加号操作符,把它与一个字符串(" ")加在一起。

3.47 Object类型

ECMAScript中的对象其实就是一组数据和功能的集合。对象可以通过执行new操作符后跟要创建的对象类型名称来创建。而创建Object类型的实例并为其添加属性和(或)方法,就可以创建自定义对象,例:

var o = new Object();

注意:仅仅创建Object的实例并没有什么用处,但关键是要理解一个重要的思想:即在ECMAScript中,Object类型是所有它的实例的基础。换句话说,Object类型所有具有任何属性和方法也同样存在于更具体的对象中。

由于在ECMAScript中Object是所有对象的基础,因此所有的对象都具有这些接本的属性和方法。

注意:从技术角度讲,ECMA-262中对象的行为不一定适用与JavaScript中的其他对象。浏览器环境中的对象,比如BOM和DOM中的对象都属于宿主对象,因为他们是由宿主实现提供和定义的。ECMA-262不负责定义宿主对象,因此宿主对象也可能不会继承Object。

3.5 操作符

ECMA-262描述了一组用于操作数据值的操作符,包括算数操作符、位操作符、关系操作符和相等操作符。

3.5.1 一元操作符

只能操作一个值的操作符叫做一元操作符。一元操作符是ESCMAScot中最简单的操作符。

1.递增和递减操作符

递增和递减操作符直接借鉴自C,而且各自有两个版本:前置型和后置型。例:

var num1 = 2;
var num2 = 20;
var num3 = --num1 + num2;    //等于21
var num4 = num1 + num2;    等于21var num5 = num1-- + num2;    //等于22
var num5 = num1 + num2;    等于21

这里的差别根源在于,这里在计算num3时使用了num1的原始值(2)完成了加法计算,而在num4则使用了递减后的值(1)

这四个操作符对任何值都适用

2.一元加和减操作符

例:

var s1 = "01";
var s2 = "1.1";
var s3 = "z";
var b = "false";
var f = 1.1;
var o = {valueOf:function(){return -1;}
}s1 = -s1;    //数值变成了-1
s2 = -s2;    //数值变成了-1
s3 = -s3;    //数值变成了NaN
b = -b;    //数值变成了0
f = -f;    //数值变成了-1.1
o = -o;    //数值变成了1

3.5.2 位操作符

位操作符用于最基本的层次上,即按内存中表示数值的位来操作数值。

3.5.3 布尔操作符

布尔操作符一共有3个:非(NOT),与(AND)和 或(OR)。

1.逻辑非

逻辑非操作符由一个叹号(!)表示,可以应用于ECMAScript中的任何值。无论这个值是什么数据类型,这个操作符都会返回一个布尔值。逻辑非操作符首先会将它的操作数转换成一个布尔值,然后再对其求反。

逻辑非 规则:

对象 false
空字符串 true
非空字符串 false
数值0 true
任意非0数值(包括Infinity) false
null true
undefined true
NaN true

逻辑非操作符也可以用于将一个值转换为与其对应的布尔值。

同时使用两个逻辑非操作符,实际上就会模拟Boolean()转型函数。

例:

alert(!!"blue");    //true
alert(!!"0");    //false
alert(!!NaN);    //false
alert(!!"");    //false
alert(!!12345);    //true

2.逻辑与

逻辑与操作符由两个和号(&&)表示,有两个操作数,如下例子所示:

var result = true && fales;

逻辑与的真值表如下:

第一个操作数 第二个操作数 结果
true true true
true false false
false true false
false false false

逻辑与操作可以应用于任何类型的操作数,而不仅仅是布尔值。

在一个数不是布尔值的情况下,遵循下列规则:

  • 如果第一个操作数是对象,则返回第二个操作数;
  • 如果第二个操作数是对象,则只有在第一个操作数的求值结果为true的情况下才会返回该对象;
  • 如果两个操作数都是对象,则返回第二个操作数;
  • 如果有一个操作数是null,则返回null;
  • 如果有一个操作数是NaN则返回NaN;
  • 如果有一个操作数是undefined,则返回undefined。

注意:逻辑与操作属于短路操作,即如果第一个操作数能够决定结果,那么就不会再对第二个操作数求值。所以,如果第一个操作数是false,则第二个操作数是什么值,结果都不再可能是true了。

3. 逻辑或

逻辑或操作符由两个竖线符号(||)表示,有两个操作数,如下所示:

var result = true || false;

逻辑或的真值表如下:

第一个操作数 第二个操作数 结果
true true true
true false true
false true true
false false false

如果一个数不是布尔值,遵循规则:

  • 如果第一个操作数是对象,则返回第一个操作数;
  • 如果第一个操作数求值结果是false,则返回第二个操作数;
  • 如果两个操作数都是对象,则返回第一个操作数;
  • 如果两个操作数都是null,则返回null;
  • 如果两个操作数都是NaN,则返回NaN;
  • 如果有一个操作数是undefined,则返回undefined。

与逻辑与操作符相似,逻辑或操作符也是短路操作符。也就是说,如果第一个操作数的求值结果为true,就不会对第二个操作值求值了。

3.5.4 乘性操作符

ECMAScript定义了3个乘性操作符:乘法、除法和求模。

注意:在操作数为非数值的情况下,后台会先使用Number()转型函数将其转换为数值。也就是说,空字符串将被当做0,布尔值将被当做1。

1.乘法

乘法操作符由一个星号(*)表示,用于计算两个数值的乘积。

  • 如果操作值都是数值,执行常规乘法计算,如果乘积超过了ECMAScript数值的表示范围,则返回Infinity或-Infinity;
  • 如果有一个操作数是NaN,则结果是NaN;
  • 如果是Infinity与0相乘,则结果是NaN;
  • 如果是Infinity与非零数值相乘,则结果还是Infinity,-Infinity同理
  • 如果Infinity与Infinity相乘,则结果是Infinity;
  • 如果有一个操作值不是数值,则在后台调用Number()将其转换为数值,然后在应用上面的规则
        var a = "11";var b = "11"var c = a * b;console.log(c);    //121

2. 除法

除法操作符由一个斜线(/)表示,执行第二个操作数除第一个操作数的计算。

规则同乘法;

3. 求模

求模(余数)操作数由一个百分号(%)表示;

规则同乘法;

3.5.5 加性操作符

1.加法

加法操作符(+)的用法如下所示:

var result = 1 + 2;
  • 如果有一个操作数是NaN,则结果是NaN;
  • 如果是Infinity加Infinity没,则结果是Infinity;
  • 如果是-Infinity加-Infinity,则结果是-Infinity;
  • 如果是Infinity加-Infinity,则结果是NaN;
  • 如果是+0加+0,则结果是+0;
  • 如果是-0加-0,则结果是-0;
  • 如果是+0加-0,则结果是+0;
  • 不过有一个操作符是字符串,那么就要应用如下规则:
  • 如果两个操作数都是字符串,则将第二个操作数与第一个操作数拼接起来
  • 如果只有一个操作符是字符串,则将另一个操作数转换为字符串,然后再将两个字符串拼接起来。

如果有一个操作数是对象、数值或布尔值,则调用它们的toString()方法取得相应的字符串值,然后再应用前面关于字符串的规则。对于undefined和null,则分别调用String()函数并取得字符串“undefined”和“null”。

例:

        var result1 = 5 + 5;console.log(result1);    //10var result2 = 5 + "5";    console.log(result2);    //55

忽视加法操作中的数据类型是ECMAScript编程中最常见的一个错误。

例:

        var num1 = 5;var num2 = 10;var message = "num1+num2="+num1+num2;console.log(message);    //num1+num2=510

原因:每个加法操作是独立执行的。第一个加法操作将一个字符串和一个数值(5)拼接了起来,结果是一个字符串。而第二个加法操作又用这个字符串去加另一个数值(10),当然也会得到一个字符串。

如果想先对数值执行算数计算,然后再讲结果与字符串拼接起来,应该使用圆括号:

        var num1 = 5;var num2 = 10;var message = "num1+num2="+(num1+num2);console.log(message);    //num1+num2=15

2. 减法

减法操作符(-)是另一个常用的操作符。

var result = 2 - 1;

规则与加法类似;

var a1 = 5 - true;    //4,因为true被转换成了1
var a2 = NaN -1;    //NaN
var a3 = 5 - 3;    //2
var a4 = 5 - "";    //5,因为""被转换成了0
var a5 = 5 -"2";    //3,因为"2"被转换成了2
var a6 = 5 - null;    //5,因为null被转换成了0

3.5.6 关系操作符

小于(<)、大于(>)、小于等于(<=)和大于等于(>=)。这几个操作符都返回一个布尔值。

例:

var result1 = 5 > 3;    //true
var result2 = 5 < 3;    //false

当关系操作符的操作数使用了非数值时,也要进行数据转换或完成某些奇怪的操作。以下是相应的规则:

  • 如果两个操作数都是数值,则执行数值比较。
  • 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值。
  • 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。
  • 如果一个操作数是对象,则调用这个对象的valueOf()方法,如果对象没有valueOf()方法,则调用toString()方法,并用得到的结果根据前面的规则执行比较
  • 如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。

由于大写字母的字符编码全部小于小写字母的字符编码,因此我们就会看到下列奇怪现象:

var result = "Brick" < "alphabet";    //true

原因是字母B的字符编码为66,而字母a的字符编码是97。如果要真正按字母表顺序比较字符串,就必须吧两个操作数转换为相同大小写形式(全部大写或全部小写),然后再执行比较

var result = "Brick".toLowerCase() < "alphabet".toLowerCase();  //false

另一种奇怪的现象发生在比较两个数字字符串的情况下,例:

var rusult = "23" < "3";    //true

因为两个操作数都是字符串,而字符串比较的是字符编码(2的字符编码是50,而3的字符编码是51)。如果把一个操作数改为数值,比较的结果就正常了:

var result = "23" < 3;    //false

在比较数值和字符串时,字符串都会被转换成数值,然后再以数值方式与另一个数值比较。

可是,如果这个字符串不能被转换成一个合理的数值呢?比如:

var result = "a" < 3;    //false,因为“a”被转换成了NaN

由于字母“a"不能转换成合理的数值,因此就被转换成了NaN。根据规则,任何操作数与NaN进行关系比较,结果都是false。于是。就出现了下面这个有意思的现象:

var result1 = NaN < 3;    //false
var result2 = NaN >=3'    //false

3.5.7 相等操作符

两组操作符:相等和不相等——先转换再比较,全等和不全等——仅比较而不转换。

1.相等和不相等

ECMAScript中的相等操作符由两个等于号(==)表示,如果两个操作数相等,则返回true。而不相等操作符由叹号后跟等于号(!=)表示,如果两个操作数不相等,则返回true。这两个操作符都会先转换操作数(通常称为强制转换型),然后再比较他们的相等性。

在转换不同的数据类型时,相等和不相等操作符遵循下列基本规则:

  • 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——false转换为0,而true转换为1;
  • 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
  • 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较;

这两个操作符在进行比较时要遵循下列规则:

  • null和undefined是相等的。
  • 要比较相等性之前,不能将null和undefined转换成其他任何值。
  • 如果有一个操作值是NaN,则相等操作符返回false,而不相等操作符返回true。重要提示:即使两个操作数都是NaN,相等操作符也返回false;因为按照规则,NaN不等于NaN。
  • 如果两个操作数都是对象,则比较他们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true;否则,返回false。

下表列出了一些特殊情况比较结果:

表达式 表达式
null==undefined true true==1 true
"NaN==NaN" false true==2 false
5 == NaN false undefined == 0 false
NaN == NaN false null == 0 false
NaN!=NaN true "5"==5 true
false == 0 true    

2.全等和全不等

除了在比较之前不转换操作数之外,全等和不全等操作符与相等和不相等操作符没有什么区别。全等操作符由3个等于号(===)表示,他只在两个操作数未经转换就相等的情况下返回true,如下所示:

var result = ("55"==55);    //true,因为转换后相等
var result = ("55" === 55); //false,因为不同的数据类型不相等

前者字符串“55”先被转换成了数值55,然后再与另一个数值55进行比较。第二个比较使用了全等操作符不转换数值的方式比较同样的字符串和值。再不转换的情况下,字符串不等于数值,结果是false。

不全等操作符由一个叹号后跟两个等于号(!==)表示,他在两个操作数未经转换就不相等的情况下返回true。例如:

var result1 = ("55"!=55);    //false,因为转换后相等
var result2 = ("55" !==55);    //true,因为不同的数据类型不相等

注意:null == undefined会返回true,但null == undefined会返回false,因为他们是不同类型的值。

由于相等和不相等操作符存在类型转换问题,而为了保持代码中数据类型的完整性,我们推荐使用全等和不全等操作符。

3.5.8 条件操作符

条件操作符应该算是ECMAScript中最灵活的一种操作符了

例:

variable = boolean_expression ? true_value : false_value;var max = (num1 > num2) ? num1 : num2;

3.5.9 赋值操作符

简单的赋值操作符由等于号(=)表示,其作用就是把右侧的值赋值给左侧的变量,例:

var num = 10;

如果在等于号(=)前面再添加乘性操作符,加性操作符或位操作符,就可以完成复合赋值操作。

  • 乘/赋值(*=);
  • 除/赋值(/=);
  • 模/赋值(%=);
  • 加/赋值(+=);
  • 减/赋值(-=);

3.5.10 逗号操作符

使用逗号操作符可以在一条语句中执行多个操作,如下例子:

var num1 = 1,num2 = 2, num3 = 3;

逗号操作符除了用于声明多个变量,还可以用于赋值,在用于赋值时,逗号操作符总会返回表达式中的最后一项:

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

由于0是表达式中的最后一项,因此num的值就是0.

3.6 语句

ECMA-262规定了一组语句(也称为流控制语句)。

3.6.1 if语句

大多数编程语言中最常见的一个语句就是if语句。

if(condition) statement1 else statement2
if(i>25){alert("Greater than 25");
}else{aler("Less than or equal to 25");
}

推荐后者,多行书写

3.6.2 do-while语句

do-while语句是一种后测试循环语句,即只有在循环体中的代码执行之后,才会测试出口条件。换句话说,在对条件表达式求值之前,循环体内的代码至少会被执行一次。

do{statement
}while(expression);

示例:

var i = 0;
do{i+=2;
}while(i<10);
alert(i);

像do-while这种后测试循环语句最常用于循环体中的代码至少要被执行一次的情形。

3.6.3 while语句

while语句属于前测试循环语句,也就是说,在循环体内的代码被执行之前,就会对出口条件求值。所以,循环体内的代码有可能永远不会被执行。

while语句 例:

while(expression)statement
var i = 0;
while(i<10){i += 2;
}

在这个例子中,变量i开始时的值为0,每次循环都会递增2。而只要i的值小于10,循环就会继续下去。

3.6.4 for语句

for语句也是一种前测试循环语句,但它具有在执行循环之前初始化变量和定义循环后要执行的代码的能力。以下是for语句的语法:

for(initialization;expression;post-loop-expression) statement

示例:

var count = 10;
for (var i = 0;i < count; i++){alert(i);
}

这个for循环语句与下面的while语句功能相同:

var count = 10;
var i = 0;
while(i < count){alert(i);i++;
}

使用while循环做不到的,使用for循环同样也做不到。也就是说,for循环只是把与循环有关的代码集中了一个位置。

在for循环的初始化表达是中,也可以不使用var关键字。该变量的初始化可以在外部执行,例:

var count = 10;
var i;
for(i = 0;i < count;i++){alert(i);
}

由于ECMAScript中不存在块级作用域,因此在循环内部定义的变量也可以在外部访问到。例:

var count = 10;
var i;
for(i = 0;i < count;i++){alert(i);
}
alert(i);    //10

即使i是在循环内部定义的一个变量,但在循环外部仍然可以访问到它。

for语句中的初始化表达式、控制表达式和循环后表达式都是可选的。将这两个表达式全部省略,就会创建一个无限循环,例:

for(;;){                //无限循环doSomething();
}

而只给出控制表达式实际上就把for循环转换成了while循环,例:

var count = 10;
var i = 0;
for (;i<count;){alert(i);i++;
}

3.6.5 for-in语句

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

for-in语句的语法:

for(property in expression) statement

实例:

for(var propName in window){document.write(propName);}
var person = {fname:"John", lname:"Doe", age:25}; var text = "";
var x;
for (x in person) {text += person[x] + " ";
}//输出结果为:John Doe 25

注意:ECMAScript对象的属性没有顺序。因此,通过for-in循环输出的属性名的顺序是不可预测的。具体来说,所有的属性都会被返回一次,但返回的先后顺序次序可能会因浏览器而异。

但是,如果要迭代的对象的变量值为null或undefined,for-in语句会抛出错误。ECMAScript 5 更正了这一次行为;对这种情况不再抛出错误,而是不执行循环体。为了保证最大限度的兼容性,建议在使用for-in循环之前,先检测确认该对象的值不是null或undefined。

3.6.6 label语句

使用label语句可以在代码中添加标签,以便将来使用。lable语句的语法:

start:for(var i = 0; i < count; i++){alert(i);
}

这个例子中定义的start标签可以将来由break或continue语句引用。加标签的语句一般都要与for语句等循环语句欧赔和使用。

3.6.7 break和continue语句

break和continue语句用于在循环中精确地控制代码的执行。其中,break语句会立即退出循环,强制继续执行循环后面的语句。而continue语句虽然也是立即退出循环,但退出循环后会从循环的顶部继续执行。例:

var num = 0;
for(var i = 1; i < 10;i++){if(i%5 == 0){break;}num++;
}
alert(num);    //4
var num = 0;
for(var i = 1; i < 10;i++){if(i%5 == 0){continue;}num++;
}
alert(num);    //8

例子的结果显示8,也就是循环总共执行了8次。当变量i等于5时,循环会在num再次递增之前退出,但接下来执行的是下一次循环,即i的值等于6的循环。于是,循环又继续执行,直到i等于10时自然结束。而num的最终值之所以是8,是因为continue语句导致它少递增了一次。

3.6.8 with语句

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

with(expression) statement;

定义with语句的目的主要是为了简化多次编写同一个对象的工作,例:

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

上面几行代码都包含location对象。如果使用with语句,可以把上面的代码改写成:

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

在with语句的代码块内部,每个变量首先被认为是一个局部变量,而如果在局部环境中找不到该变量的定义,就会查询location对象中是否有相同的属性。如果发现了相同属性,则以location对象属性的值作为变量的值。

严格模式下不允许使用with语句,否则将视为语法错误。

注意:由于大量使用with语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用with语句。

3.6.9 switch语句

switch语句与if语句关系最为密切。例:

switch (expression){case value: statementbreak;case value: statementbreak;case value: statementbreak;case value: statementbreak;case value: statementbreak;default: statement
}

switvh语句中的每一种情形(case)的含义是:“如果表达式等于这个值(value),则执行后面的语句(statement)”。而break关键字会导致代码执行流跳出switch语句。如果省略break关键字,就会导致执行完当前case后,继续执行下一个case。最后的default关键字则用于在表达不匹配前面任何一种情形的时候,执行机动代码(因此,也相当于一个else语句)。

从根本上讲,switch语句就是为了让开发人员免于编写像下面这样的代码:

if(i==25){alert("25");
}else if(i==35){alert("35")
}else if(i==45){alert("45");
}else{alert("Other");
}

等价的switch语句:

switch(i){        //i等于多少哪个case的值,就执行哪个casecase 25:alert("25");break;case 35:alert("35");break;case 45:alert("45");break;default:alert("Other");
}

通过为每个case后面都添加一个break语句,就可以避免同时执行多个case代码的情况。假如确实需要混合几种情形,不要忘了在代码中添加注释,说明你有意省略了break关键字。例:

switch(i){case 25://合并两种情形case 35:alert("25 or "35");break;case 45:alert("45");break;default:alert("Other");
}

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

switch("hello world"){case "hello" + " world":alert("Greeting was found");break;case "goodbye":alert("Closing was found");break;default:alert("Unexpected message was found.");
}

在这个例子中,switch语句使用的就是字符串。其中,第一种情况实际上是一个对字符串拼接操作求值的表达式。由于这个字符串拼接表达式的结果与switch的参数相同,因此结果就会显示“Greeting was found”。而且,使用表达式作为case值还可以实现下列操作:

var num = 25;
switch(true){case num < 0;alert("Less than 0");break;case num >= 0 && num <=10;alert("Between 0 and 10");break;case num > 10 && num <=20;alert("Between 10 and 20");break;default:alert("More than 20");
}

这个例子首先在switch语句外面声明了变量num。而之所以给switch语句传达表达式true,是因为每个case值都可以返回一个布尔值。每个case按照顺序被求值,直到找到匹配的值或者遇到default语句为止。

注意:switch语句在比较值时使用的是全等操作符,因此不会发生类型转换(例如,字符串“10”不等于数值10)。

3.7 函数

函数对任何语言来说都是一个核心概念。通过函数可以封装任意多条语句,而且可以在任何地方,任何时候调用执行。ECMAScript中的函数使用function关键字来声明,后跟一组参考数以及函数体。函数的基本语法如下所示:

function functionName(arg0,arg1,...,argN){statements
}

以下是一个函数示例:

function sayHi(name,message){alert("Hello"+name+","+message);
}

调用sayHi()函数

sayHi("Nicholas","how are you today?");

这个函数的输出结果是“HelloNicholas,how are you today?”。

ECMAScript中的函数在定义时不必指定返回值。实际上,任何函数在任何时候都可以通过return语句后跟要返回的值来实现返回值。例:

function sum(num1,num2){return num1 + num2;
}

这个sum()函数的作用是把两个值加起来返回一个结果。调用这个函数的示例代码如下:

var result = sum(5,10);

这个函数会在执行完return语句之后停止并立即退出。因此,位于return语句之后的任何代码都永远不会执行。例:

function sum(num1,num2){return num1 + num2;alert("Hello world");    //永远不会执行
}

一个函数中也可以包含多个return语句,如下所示:

function diff(num1,num2){if(num1 < num2){return num2 - num1;}else{return num1 - num2;}
}

return 语句也可以不带有任何返回值。在这种情况下,函数在停止后将返回undefined值。这种用法一般用在需要提前停止函数执行又不需要返回值的情况下。例:

function sayHi(name,message){return;alert("Hello"+name+","+message);    //永远不会调用
}

推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值。否则,如果函数有时候返回值,有时候不返回值,会给调试代码带来不便。

3.7.1 理解参数

ECMAScript函数的参数与大多数其他语言中函数的参数有所不同。ECMAScript函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。也就是说,定义函数只接收两个参数,在调用这个函数时也可以传递一个、三个,甚至不传递参数。

因为ECMAScript中的参数在内部是用一个数组来表示的。实际上,在函数体内可以通过arguments对象来访问这个参数数组,从而获取传递给函数的每一个参数。

注意:arguments对象只是与数组类似(它并不是Array的实例),因为可以使用方括号访问它的每一个元素(第一个元素是arguments[0],第二个元素是arguments[1]。。。),使用length属性来确定传递进来多少个参数。例:

function sayHi(name,age){alert("Hello" + argumnets[0]+","+arguments)[1]);console.log(arguments.length);    //2   参数的长度
}sayHi("xiaozhi","18");

arguments.length的运用:

function doAdd(num1,num2){if(arguments.length == 1){alert(num1 + 10);}else if(){alert(arguments[0]+num2);}
}

arguments.lengt的值是由传入的参数个数决定的,不是由定义函数时的参数个数决定的。

注意:没有传递值命名参数将自动被赋予undefined值。这就跟定义了变量但又没有初始化一样。

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

3.7.2 没有重载

ECMAScript函数不能像传统意义上那样实现重载。ECMAScript函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。

如果在ECMAScript中定义了两个名字相同的函数,则该名字只属于后定义的函数。例:

 function add(num){return num+100;}function add(num){return num+200;}var result = add(100);console.log(result);    //300

通过检查传入函数中参数的类型和数量并作出不同的反应,可以模拟方法的重载。

第四章 变量、作用域和内存问题

按照ECMA-262的定义,JavaScript的变量与其他语言的变量有很大区别。JavaScript变量松散类型的本质,决定了它只是在特定时间用于保存特定值的一个名字而已。由于不存在定义某个变量必须要保存何种数据类型的规则,变量的值及其他数据类型可以在脚本的生命周期内改变。

4.1 基本类型和引用类型的值

ECMAScript变量可能包含两种不同数据类型的值:基本数据类型值和引用类型值。基本数据类型指的是简单的数据段,而引用数据类型指那些可能由多个值构成的对象。

在将一个值赋值给变量时,解析器必须确定这个值是基本类型值还是引用类型值(5种基本数据类型:Undefined、Null、Boolean、Number和String)。这五种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。

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

注意:在很多语言中,字符串以对象的形式来表示,因此被认为是引用类型的。ECMAScript放弃了这一传统。

4.1.1 动态的属性

定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值。但是,当这个值保存到变量中以后,对不同类型值可以执行的操作则大相径庭。对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。例:

var person = new Object();
person.name = "Nicholas";
alert(person.name);    //"Nicholas"

如果对象(person)不被销毁或者这个属性(name)不被删除,则这个属性将一直存在。

但是,我们不能给基本数据类型的值添加属性,尽管这样不会导致任何错误。比如:

var name = "Nicholas";
name.age = 27;
alert(name.age);    //undefined 

在这个例子中,我们为字符串name定义了一个名为age的属性,并为该属性赋值27。但访问不到。这说明只能给引用类型值动态的添加属性,以便将来使用。

4.1.2 复制变量值

如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到新变量分配的位置上。例:

var num1 = 5;
var num2 = num1;

在此,num1中保存的值是5.当使用num1的值来初始化num2时,num2中也保存了值5.但num2中的5与num1中的5是完全独立的,该值只是num1中5的一个副本。此后,这两个变量可以参与任何操作而不会互相影响。

当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量,因此,改变其中一个变量,就会影响另一个变量,例:

var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nicholas";
alert(obj2.name);    //"Nicholas"

obj1和obj2都指向同一个对象。当为obj1添加name属性后,可以通过obj2来访问这个属性。

4.1.3 传递参数

ECMAScript中所有函数的参数都是按值传递的。也就是把函数外部的值复制给函数内部的参数。

基本类型值的传递如同基本类型的复制一样,而引用类型值得传递,则如同引用类型变量的复制一样。

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用ECMAScript的概念来说,就是arguments对象中的一个元素)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反应在函数的外部。例:

        function addTen(num){num+=10;return num;}var count = 20;var result = addTen(count);console.log(count);     //20,没有变化console.log(result);    //30

函数addTen有一个参数num,而参数实际上是函数的局部变量。在调用这个函数时,变量count作为参数被传递给函数,这个变量的值是20.于是,数值20被复制给参数num以便在addTen()中使用。在函数内部,参数num的值被加上了10,但这一变化不会影响函数外部的count变量。参数num与变量count互不相识,它们仅仅是具有相同的值。假如num是按引用传递的话,那么变量count的值也将变成30,从而反映函数内部的修改。

如果是对象,例:

        function setName(obj){obj.name = "Nicholas";obj = newObject();obj.name = "Greg";}var person = new Object();setName(person);console.log(person.name);    //"Nicholas"

如果person是按引用传递的,那么person就会自动被修改为指向其name属性值为“Greg”的新对象。但是,当接下来再访问person。name时,显示的值仍然是“Nicholas”。这说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

可以把ECMAScript函数的参数想象成局部变量。

4.1.4 检测类型

typof操作符是确定一个变量是字符串、数值、布尔值,还是Undefined的最佳工具。如果变量的值是一个对象或null,typeof操作符会返回“object”:

var n = null;
var o = new Object();
alert(typeof n);    //object
alert(typeof o);    //object

引用类型,想知道它是什么类型的对象。

result = variable instanceof constructor

如果变量是给定引用类型的实例,那么instanceof操作符就会返回true。例:

alert(person instanceof Object);    //变量person是Object吗?
alert(person instanceof Array);    //变量person是Array吗?
alert(person instanceof RegExp);    //变量person是RegExp吗?

根据规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof操作符始终会返回true。如果使用instanceof操作符检测基本类型的值,则该操作符始终返回false。

4.2 执行环境及作用域

执行环境是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义页随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器时才会被销毁)。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain),作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用链中的下一个变量对象来自包含(外部)环境,而下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)。

在局部作用域中定义的变量可以在局部环境中与全局变量互相使用。例:

        var color = "blue";function changeColor(){var anotherColor = "red";function swapColors(){var tempColor = anotherColor;   //tempColor = "red"anotherColor = color;   //anotherColor = "blue"color = tempColor;  //color = "red"// 这里可以访问color、anotherColor和tempColor}// 这里可以访问color和anotherColor,但不能访问tempColorswapColors();}// 这里只能访问colorchangeColor();

其中,内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都可以向上搜索作用域,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。

注意:函数参数也被当做变量来对待,因此其访问规则与执行环境中的其他变量相同。

4.2.1 延长作用域链

  • try-catch语句的catch块;
  • with语句。

这两个语句都会在作用域链的前端添加一个变量对象。对with语句来说,会将指定的对象添加到作用域链中。对catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。例:

functon buildUrl(){var qs = "?debug=true";with(location){var url = href + qs;}return url;
}

4.2.2 没有块级作用域

JavaScript没有块级作用域经常会导致理解上的困惑。例:

if(true){var color = "blue";
}alert(color);    //"blue"

这里是一个if语句中定义了变量color。如果是在C、C++或Java中,color会在if语句执行完毕后被销毁。但在JavaScript中,if语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中。在使用for语句时尤其要牢记这一差异,例如:

for(var i = 0; i<10;i++){doSomething(i);
}alert(i);    //10

对于有块级作用域的语言来说,for语句初始化变量的表达式所定义的变量,只会存在于循环的环境中。而对于JavaScript来说,由for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。

1.声明变量

使用var声明的变量会自动被添加到最近的环境中。在函数内部,最接近的环境就是函数的局部环境;在with语句中,最接近的环境是函数环境。如果初始化变量时没有使用var声明,该变量会自动被添加到全局变量。

注意:在编写JavaScript代码的过程中,不声明而直接初始化变量是一个常见的错误做法,因为这样可能会导致意外。建议在初始化变量之前,一定要先声明,这样就可以避免类似的问题。在严格模式下,初始化未经声明的变量会导致错误。

2.查询标识符

当某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。局部环境—>全局环境,如果全局环境也没有找到这个标识符,则意味着未声明。例:

var color = "blue";
function getColor(){return color;
}alert(getColor());    //"blue"

4.3 垃圾收集

JavaScript具有自动垃圾收集机制,也就是说执行环境会负责管理代码执行过程中使用的内存。

垃圾收集机制的原理:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。

JavaScript中最常用的垃圾收集方法是标记清除,另一种不太常见的垃圾收集策略叫做引用计数。

确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用——这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象。局部变量会在它们离开执行环境时自动被解除引用。

第五章 引用类型

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

注意:尽管ECMAScript从技术上将是一门面向对象的语言,但是它不具备传统的面向对象语言所支持的类和接口等基本结构。引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法、

为避免混淆,本书不使用类这个概念。

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

var person = new Object();

这行代码创建了Object引用类型的一个新实例,然后把该实例保存在了变量person中。使用的构造函数是Object,他只是为新对象定义了默认的属性和方法。

5.1 Object类型

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

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

2,另一种方式是使用对象字面量表示法。例:

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

在使用对象字面量语法时,属性名也可以使用字符串,如下面这个例子所示。

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

这个例子会创建一个对象,包含三个函数:name、age和5.但这两的数值属性名会自动转换为字符串。

3.使用对象字面量语法时,如果留空其花括号,则可以定义只包含默认属性和方法的对象,如下所示:

var person = {};    //与new Object相同
person.name = "Nicholas";
person.age = 29;

对象字面量语法最常用,因为简便(代码少)。

5.2 Array类型

除Object外,最常用类型。ECMAScript中的数组和其他语言的相同是都有序列表。不同是每一项都可以保存任何类型的数据。而且数组的大小可以动态的调整,即可以随着数据的添加自动增长以容纳新增数据。例:

var colors = new Array();

如果预先直到数组保存的项目数量,也可以给构造函数传递该数量,而该数量会自动变成length属性的值。例:创建length值为20的数组:

var colors = new Array(20);

也可以向Array构造函数传递数组中应该包含的项,例子:包含三个字符串的数组

var colors = new Array("red","blue","green");

构造函数传递一个值也可以创建数组。传递的如果是数值,就是对应数值个数的数组,是其他类型就是只有一项的数组

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

另外,在使用Array构造函数时也可以省略new操作符。如下所示:

var colors = Array(3);
var names = Array("Greg");

创建数组的第二种基本方法:使用数组字面量表示法。例:

var colors = ["red","blue","green"];
var names = [];    //创建一个空数组
var values = [1,2,];    //不要这样,这样会创建一个包含2或者3项的数组
var options = [,,,,,];    //不要这样,这样会创建一个包含5或者6项的数组

原因是IE8及之前版本中的ECMAScript实现在数组字面量方面存在bug。在像这种省略值得情况下,每一项都将获得undefined值。所以不建议使用这种语法。

注意:与对象一样,在使用数组字面量表示法时,也不会调用Array构造函数。

在读取和设置数组的值时,方括号中的索引表示要访问的值。例:

var colors = ["red","blue","green"];
alert(colors[0]);
alert(colors[1]);
alert(colors[2]);colors[2]="black";    //修改第三项
colors[3]="brown";    //新增第四项

数组的长度 .length,数组的length属性很有特点——他不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。例:

var colors = ["red","blue","green"];
colors.length = 2;
alert(colors[2]);    //undefined

如上,length属性小了1个,数组会移除最后一项。

如果将length属性设置为大于数组项数的值,则新增的每一项都会取得undefined,例:

var colors = ["red","blue","green"];
colors.length = 4;
alert(colors[3]);    //undefined

利用length属性也可以方便地在数组末尾添加新项,例:

var colors = ["red","blue","green"];
colors[colors.length]="black";    //在位置3 添加一种颜色
colors[colors.length]="brown";    //在位置4 再添加一种颜色
var colors = ["red","blue","green"];
colors[99]="black";    //在位置99 添加一种颜色
alert(colors.length);  //100

这个例子,我们向colors数组的位置99插入了一个值,结果数组新长度(length)就是100(99+1)。而位置3到位置98实际上都是不存在的,所以访问他们都将返回undefined。

注意:数组最多可以包含4 294 967 295个项。

5.2.1 检测数组

instanceof操作符 。 例:

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

instanceof操作符的问题在于,它假定单一的全局执行环境。如果从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。

为了解决这个问题,ECMAScript 5新增了Attay.isArray()方法。这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。方法如下:

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

支持Array.isArray()方法需要浏览器IE9+

5.2.2 转换方法

调用数组的toString()和valueOf()方法会返回相同的值

var colors = ["red","blue","green"];
alert(colors.toString);    //red,blue,green
alert(colors.valueOf());    //red,blue,green
alert(colors);    //red,blue,green

由于alert()要接受字符串参数,所以它会在后台调用toString()方法。

join()方法只接受一个参数,即用作分隔符的字符串

var colors = ["red","blue","green"];
alert(colors.join(","));    //red,green,nlue
alert(colors.join("||"));    //red||green||nlue

5.2.3 栈方法

栈中项的插入(叫做推入)和移除(叫做弹出),只发生在一个位置——栈的顶部。ECMAScript为数组专门提供了push()和pop()方法,以便实现类似栈的行为。

push()方法可以接受任意数量的参数,把他们逐个添加到数组末尾,并返回修改后数组的长度。

pop()方法则是从数组末尾移除最后一项,减少数组的length值,然后返回移除的项。例:

var colors = new Array();
var count = colors.push("red","green");    //推入两项
alert(count);    //2
count = colors.push("black");    推入另一项
aler(count);    //3
var item = colors.pop();    //取得最后一项
alert(item);    //"black"
alert(colors.length);    //2

可以将栈方法与其他数组方法连用,例:

        var colors = ["red","blue"];colors.push("brown");   //添加另一项colors[3]="black";  //添加一项console.log(colors.length);   //4var item = colors.pop();console.log(item);  //"black"

5.2.4 队列方法

栈数据结构的访问规则是LIFO(后进先出),而队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出)。队列在列表的末端添加项,从列表的前端移除项。实现这一操作的数组方法就是shift(),它能够移除数组中的第一项并返回该项,同时将数组长度减1。结合使用shift()和push()方法,可以像使用队列一样使用数组。例:

var colors = new Array();
var count = colors.push("red","green");
alert(count);    //2
count = colors.push("black");
alert(count");    //3
var item = colors.shift();
alert(item);
alert(colors.length);    //2

ECMAScript还为数组提供了一个unshift()方法。unshift()与shift()的用途相反:它能在数组前端添加任意个项并返回新数组的长度。

5.2.5 重排序方法

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

reverse()方法会对反转数组项的顺序。例:

var values = [1,2,3,4,5];
values.reverse();
alert(values);    //5,4,3,2,1

sort()方法按升序排列数组项——即最小的值位于最前边,最大的值排在最后面。例:

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

因为为了实现排序,sort()方法会调用每个数组项的toString()转型方法,然后比较得到的字符串。

        function compare(value1,value2){if(value1 < value2){return -1} else if(value1 > value2){return 1;} else{return 0;}}var values = [0,1,10,15,5];values.sort(compare);alert(values);    //0,1,5,10,15

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

对于数值类型或者其valueOf()方法会返回数值类型的对象类型,可以使用一个更简单的比较函数。这个函数只要用第二个值减第一个值即可。

如果想要按照升级排序,则compare()函数中的return语句应该返回value2-value1。

function compare(value1,value2){return value2 - value1;var values = [0,1,10,15,5];values.sort(compare);alert(values);    //15,10,5,1,0
}

由于比较函数通过返回一个小于零、等于零或大于零的值来影响排序结果,因此减去操作就可以适当地处理所有这些情况。

5.2.6 操作方法

concat()方法可以基于当前数组中的所有项创建一个新数组。例:

var colors = ["red","green","blue"];
var colors2 = colors.concat("yellow",["black","brown"]);
alert(colors);    //red,green,blue
alert(colors2);    //red,green,blue,yellow,black,brown

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

注意:slice()方法不会影响原始数组。例:

var colors = ["red","green","blue","yellow","pink"];var colors2 = colors.slice(1);var colors3 = colors.slice(1,4);console.log(colors2);   //"green","blue","yellow","pink"console.log(colors3);   //"green","blue","yellow"

注意:如果slice()方法的参数中有一个负数,则用数组长度加上该数来确定相应的位置。例:在一个包含5项的数组上调用slice(-2,-1)与调用slice(3,4)得到的结果相同。如果结束位置小于起始位置,则返回空数组。

splice()方法:

删除:可以删除任意数量的项,只需指定2个参数:要删除的第一项的位置和要删除的项数。例如,splice(0,2)会删除数组中的前两项。

插入:可以向指定位置插入任意数量的项,只需要提供3个参数:起始位置、0(要删除的项数)和要插入的项,如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,splice(2,0,"red","green")会从当前数组的位置2开始插入字符串“red”和“green”。

替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例:splice(2,1,"red","green")会删除当前数组位置2的项,然后再从位置2开始插入字符“red”和“green”。

splice()方法始终都会返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组)。下面的代码展示了上述3种使用splice()方法的方式。

        var colors = ["red","green","blue"];var removed = [colors.splice(0,1)]; //删除第一项alert(colors);    //green,bluealert(removed);     //red,返回的数组中只包含一项removed = colors.splice(1,0,"yellow","orange");    //从位置1开始插入两项alert(colors);  //green,yellow,orange,bluealert(removed);     //返回的是一个空数组removed = colors.splice(1,1,"red","purple");    //插入两项,删除一项alert(colors);  //green,red,purple,orange,bluealert(removed); //yellow,返回的数组中只包含一项

5.2.7 位置方法

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

这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1.在比较第一个参数与数组中的每一项时,会使用全等操作符;也就是说,要求查找的项必须严格相等(就像使用===一样);例:

        var numbers = [1,2,3,4,5,4,3,2,1];alert(numbers.indexOf(4));  //3alert(numbers.lastIndexOf(4));  //5alert(numbers.indexOf(4,4));    //5alert(numbers.lastIndexOf(4,4));    //3var person = {name:"Nicholas"};var people = [{name:"Nicholas"}];var morePeople = [person];alert(people.indexOf(person));  //-1alert(morePeople.indexOf(person));  //0

5.2.8 迭代方法

ECMAScript 5 为数组定义了5个迭代方法。每个方法都能接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响this的值。传入这些方法中的函数会接受三个参数:数组项的值|、该项在数组中的位置和数组对象本身。根据使用的方法不同,这个函数执行后的返回值可能会也可能不会影响访问的返回值,以下是这5个迭代方法的作用。

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

以上方法都不会修改数组中的包含的值。

最相似的是every()和some(),它们都用于查询数组中的项是否满足某个条件。对every()来说,传入的函数必须对每一个项都返回true,这个方法才返回true;否则,它就会返回false。而some()方法则是只要传入的函数对数组中的某一项返回true,就会返回true。(看方法的名字也知道,every每一个true为true,some某一项true为true)例:

        var numbers = [1,2,3,4,5,4,3,2,1];var everyResult = numbers.every(function(item,index,array){console.log(item);  //1console.log(index); //0console.log(array); //[1,2,3,4,5,4,3,2,1]return(item > 2);   })alert(everyResult); //falsevar someResult = numbers.some(function(item,index,array){return(item >2);})alert(someResult);  //true

filter()函数,它利用指定的函数确定是否在返回的数组中包含的某一项。例如,要返回一个所有数值都大于2的数组,可以使用以下代码。

var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item,index,array){return (item > 2);
});alert(filterResult);    //[3,4,5,4,3]

map()也返回一个数组,而这个数组的每一项都是在原始数组中的对应项上运行传入函数的结果。例:

        var numbers = [1,2,3,4,5,4,3,2,1];var mapResult = numbers.map(function(item,index,array){return item * 2;})alert(mapResult);   //2,4,6,8,10,8,6,4,2

map()方法适合创建包含的项与另一个数组一 一对应的数组。

forEach()方法:它只是对数组中的每一项运转传入的函数。这个方法没有返回值,本质上与使用for循环迭代数组一样。例;

var numbers = [1,2,3,4,5,4,3,2,1];
numbers.forEach(function(item,index,arrary){//执行某些操作
})

5.2.9 缩小方法

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

reduce()方法从数组的第一项开始,逐个遍历到最后,而reduceRight()则从数组的最后一项开始,向前遍历到第一项。

使用reduce()方法可以执行求数组中所有值之和的操作,例:

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

第一次执行回调函数,prev是1,cur是2.第二次,prev是3(1加2的结果),cur是3(数组的第三项)。这个过程会持续把数组中的每一项都访问一遍,最后返回结果。

reduceRight()的作用类似,只不过方向反过来而已。例:

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

在这个例子中,第一次执行回调函数,pre是5,cur是4。当然,最终结果相同,因为执行的都是简单相加操作。

使用reduce()还是reduceRight(),主要取决于要从哪头开始遍历数组。除此之外,他们完全相同。

5.3 Date类型

ECMAScript中的Date类型是在早期Java中的java.util.Date类基础上构建的。为此Data类型使用UTC(国际协调时间)1970年1月1日零时开始经过的毫秒数来保存日期。在使用这种数据存储格式的条件下,Data类型保存的日期能够精确到1970年1月1日之前或之后的285616年。

要创建一个日期对象,使用new操作符和Date构造函数即可,例:

var data = new Data();

在调用Date构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果根据特定的日期和时间穿件日期对象,必须传入表示该日期的毫秒数(即UTC时间1970年1月1日零时起至该日期止经过的毫秒数)。为了简化这一计算过程,ECMAScript提供了两个方法:Date.pase()和Date.UTC()。

Date.parse()方法接受一个表示日期的字符串参数。

  • “月/日/年”,如6/13/2004;
  • "英文月名 日,年",如january 12,2004;
  • “英文星期几 英文月名 日 年 时:分:秒 时区”,如 Tue May 25 2004 00:00:00 GMT-0700。
  • ISO 8601扩展格式 YYYY-MM-DDTHH:mm:ss:sssZ(例如 2004-05-25T00:00:00)。只有兼容ECMAScript5的实现支持这种格式。

例如:要为2004年5月25日创建一个日期对象,可以使用下面代码:

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

ECMAScript5 添加了Date.now()方法,返回表示调用这个方法时的日期和时间的毫秒数。这个方法简化了使用Date对象分析代码的工作。例:

        // 取得开始时间var start = Date.now();console.log(start);     //1623986411655//调用函数doSomething();//获取停止时间var stop = Date.now(),result = stop - start;

5.3.2  日期格式化方法

Date类型还有一些专门用于将日期格式化为字符串的方法,方法如下。

  • toDateString——以特定于实现的格式显示星期几、月、日和年;
  • toTimeString——以特定于实现的格式显示时、分、秒和时区;
  • toLocaleDateString——以特定于地区的格式显示星期几、月、日和年;
  • toLocaleTimeString——以特定于实现的格式显示时、分、秒;
  • toUTCString——以特定于实现的格式完整的UTC日期;

5.4  RegExp类型

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

var expression = /pattem/ flags;

................................................

5.5  Function类型

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

function sum (num1,num2){return num1 + num2;
}

与下面使用函数表达式定义函数的方式几乎相差无几。

var sum = function(num1,num2){return num1 + num2;
}

以上代码定义了变量sum并将其初始化为一个函数。function关键字后面没有函数名。这是因为在使用函数表达式定义函数时,没必要使用函数名——通过变量sum即可引用函数。

最后一种定义函数的方式是使用Function构造函数。Function构造函数可以接受任意数量的参数,但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数。例:

var sum = new Function("num1","num2","return num1 + num2");    不推荐

从技术角度讲,这是一个函数表达式。但是,不推荐。因为这种语法会导致解析两次代码,从而影响性能。

由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没什么不同。换句话说,一个函数会有多个名字,例:

function sum(num1,num2){return num1 + num2;
}alert(sum(10,10));    //20var anotherSum = sum;
alert(anotherSum(10,10));    //20
sum = null;
alert(anotherSum(10,10));    //20

5.5.1  没有重载(深入理解)

将函数名想象为指针,有助于理解为什么ECMAScript中没有函数重载的概念。例:

function addSomeNumber(num){retuen num + 100;
}function addSomeNumber(num){retuen num + 200;
}var result = addSomeNumber(100);    //300

这个例子声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。以上代码实际上与下面的代码没有什么区别。

var addSomeNumber = function(num){return num + 100;};addSomeNumber = function(num){return num + 200;
};var result = addSomeNumber(100);    //300

在创建第二个函数时,实际上覆盖了引用第一个函数的变量addSomeNumber。

5.5.2 函数声明与函数表达式

解析器会率先读取函数声明,并使用在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。例:

alert(sum(10,10));
function sum(num1,num2){return num1 + num2;
}

以上代码完全可以正常运行。因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中。如果像下面例子所示,把上面的函数声明改为等价的函数表达式,就会在执行期间导致错误。

alert(sum(10,10));
var sum = function(num1,num2){return num1 + num2;
}

以上代码之所以会在运行期间产生错误,原因在于函数位于一个初始化语句中,而不是一个函数声明。

除了什么时候可以通过变量访问函数这一点区别之外,函数声明与函数表达式的语法其实是等价的。

也可以同时使用函数声明和函数表达式,例如var sum = function sum(){}。不过,这种语法在Safari中会导致错误。

5.5.3 作为值得函数

因为ECMAScript中的函数本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。例:

function callSomeFunction(someFunction,someArguent){return someFunction(someArgument);
}

这个函数接受两个参数。第一个参数应该是一个函数,第二个参数应该是要传递给该函数一个值。然后,就可以像下面的例子一样传递函数了。

function add10(num){return num + 10;
}var result1 = callSomeFunction(add10,10);
alert(result1);    //20function getGreeting(name){return "Hello,"+name;
}var result2 = callSomeFunction(getGreeting,"Nicholas");
alert(result2);    //"Hello,Nicholas"

5.5.4 函数内部属性

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

function factorial(num){if(num<=1){return 1;}else{return num * factorial(num-1);}}

这个函数的执行与函数名factorial紧紧耦合在了一起,为了消除这种紧密耦合的现象,可以像下面这样使用arguments.callee。

function factorial(num){if(num<=1){return 1;}else{return num * arguments.callee(num-1);}}

在这个重写后的factorial()函数的函数体内,没有再引用函数名factorial。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。例:

function factorial(num){if(num<=1){return 1;}else{return num * arguments.callee(num-1);}}var trueFactorial = factorial;factorial = function(){return 0;};alert(trueFactorial(5));    //120alert(factorial(5));    //0

变量trueFactorial获得了factorial的值,实际上是在另一个位置上保存了一个函数的指针。然后,我们又将一个简单地返回0的函数赋值给factorial变量。如果像原来的factorial()那样不使用arguments.callee,调用trueFactorial()就会返回0。可是,在解除了函数体内的代码与函数名的耦合状态之后,trueFactorial()仍然能够正常地计算阶乘;

函数内部的另一个特殊对象是this,换句话说,this引用的是函数据以执行的环境对象——或者也可以说是this值(当在网页的全局作用域中调用函数时,this对象引用的就是window)。例:

        window.color = "red";var o = {color:"blue"};function sayColor(){alert(this.color);}sayColor();     //"red"o.sayColor = sayColor;o.sayColor();      //"blue"

注意:函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的执行环境中执行,全局sayColor()函数与o.sayColor()指向的仍然是同一个函数。

ECMAScript 5 也规范了另一个函数对象的属性:caller。这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,他的值为null。例:

function outer(){inner();
}function inner(){alert(inner.caller);
}outer();

以上代码会导致警告框中显示outer()函数的源代码。function outer(){
    inner();
}

因为outer()调用了inter(),所以inner.caller就指向outer()。为了实现更松散的耦合,也可以通过arguments.callee.caller来访问相同的信息。

         function outer(){inner();}function inner(){alert(arguments.callee.caller);}outer();

当函数在严格模式下运行时,访问arguments.callee会导致错误。ECMAScript 5还定义了arguments.caller属性,严格模式下访问也会导致错误,非严格模式下这个属性始终是undefined。定义这个属性是为了分清arguments.caller和函数caller属性。以上变化都是为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。

严格模式还有一个限制:不能为函数的caller属性赋值,否则会导致错误。

5.5.5 函数属性和方法

ECMAScript中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length和prototype。其中,length属性表示函数希望接受的命名参数的个数,例:

在ECMAScript核心所定义的全部属性中,最耐人寻味的就要数prototype属性了。对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。换句话说,诸如toString()和valueOf()等方法实际上都保存在prototype名下,只不过是通过各自对象的实例访问罢了。在创建自定义引用类型以及实现继承时,prototype属性的作用是极为重要的。在ECMAScript5中,prototype属性是不可枚举的,因此使用for-in无法发现。

JavaScript高级程序设计 总结(一)详细版相关推荐

  1. JavaScript高级程序设计(第3版)非扫描版

    前端学习js的红皮书 文档:JavaScript高级程序设计(第3版)非扫?.. 文档:JavaScript高级程序设计(第3版)非扫?.. 链接:http://note.youdao.com/not ...

  2. 《JavaScript高级程序设计(第2版)》

    <JavaScript高级程序设计(第2版)>比较之前的第1版,几乎全部更新.重写了上一版的全部内容.此书是世界顶级WEB专家Nicholas C.Zaks尼古拉斯.泽卡斯所著,融入了作者 ...

  3. 《JavaScript高级程序设计(第3版)》教程大纲

    词条 <JavaScript高级程序设计>是2006年人民邮电出版社出版的图书,作者是(美)(Nicholas C.Zakas)扎卡斯.本书适合有一定编程经验的开发人员阅读,也可作为高校相 ...

  4. 手牵手系列之记录JavaScript高级程序设计(第3版)

    背景 <JavaScript高级程序设计>可谓是前端工程师的圣典,全书比较基础讲述js的知识点.最初想要读这本书并做笔记的目的是巩固基础知识点,弥补不足,希望自己能坚持下来,争取每天都能有 ...

  5. javascript 高级程序设计(第三版)读后归纳

    第4章 变量.作用域和内存问题 复制变量值 基本类型值复制(如:Number.String .Boolean.Null和Undefined),分配新的位置 引用类型值(如:对象类型Object typ ...

  6. JavaScript高级程序设计(第3版)pdf

    下载地址:网盘下载 内容简介  · · · · · · 本书是JavaScript 超级畅销书的最新版.ECMAScript 5 和HTML5 在标准之争中双双胜出,使大量专有实现和客户端扩展正式进入 ...

  7. 前端的葵花宝典 - 红宝书《JavaScript高级程序设计(第4版)》学习笔记

    目录 前言 1.第1章 什么是JavaScript 1.1 ECMAScript 1.2 DOM 1.3 BOM 小结 第2章 HTML 中的JavaScript 2.1 < script &g ...

  8. 《JavaScript高级程序设计(第四版)》红宝书学习笔记(2)(第四章:变量、作用域与内存)

    个人对第四版红宝书的学习笔记.不适合小白阅读.这是part2.持续更新,其他章节笔记看我主页. (记 * 的表示是ES6新增的知识点,记 ` 表示包含新知识点) 第四章:变量.作用域与内存 4.1 原 ...

  9. 《JavaScript高级程序设计(第四版)》红宝书学习笔记(1)

    个人对第四版红宝书的学习笔记.不适合小白阅读.这是part1,包含原书第二章(HTML中的Javascript)和第三章(语言基础).持续更新,其他章节笔记看我主页. (记 * 的表示是ES6新增的知 ...

  10. JavaScript高级程序设计(第4版)(红宝书)的学习笔记

    第1章   什么是JavaScript 1.在此之前,要验证某个必填字段是否已填写,或者某个输入的值是否有效,需要与服务器的一次往返通信    从简单的输入验证脚本到强大的编程语言,JavaScrip ...

最新文章

  1. opengl库区分:glut、freeglut、glfw、glew、gl3w、glad
  2. node建立博客系统遇到的问题,1,乱码。2,multer的使用错误。3使用session问题...
  3. Android初级开发笔记-- activity启动模式的学习(1)
  4. VB中使用PNG格式图片的一种新方法
  5. 【NLP】NLP实战篇之tensorflow2.0快速入门
  6. 4.Python算法之试探算法思想(回溯法)
  7. javascript Window 对象模型
  8. [蓝桥杯][算法提高VIP]断案-枚举
  9. java全局变量和局部变量_Java 10 –局部变量类型推断
  10. JMS(Java消息服务)与消息队列ActiveMQ基本使用(一)
  11. 毕业设计论文选题系统系统用例图_基于Web的毕业论文管理系统的设计与实现
  12. mysql 注释写法有哪些_mysql的注释有几种写法?
  13. 不写一行代码,搭建Jenkins+Jmeter+Ant接口自动化框架
  14. Objective-C基础学习笔记(八)-内存管理-autorelease使用-property创建对象的内存管理-循环引用的内管管理...
  15. ABAP中的枚举对象
  16. C语言求解根号2的近似值
  17. 2017 ACM-ICPC 青岛站 总结
  18. uniapp H5 扫码 扫一扫 功能
  19. android APK 查看程序MD5
  20. nginx反向代理指定dns

热门文章

  1. java随机生成昵称
  2. c语言中strncpy的用法,C语言中函数strcpy ,strncpy ,strlcpy的用法【转】
  3. 关于某学习通网页鼠标不能移出视频窗口的问题
  4. visio软件安装包各个版本收集整理
  5. DevExpress WPF控件3D打印应用场景 - 实现3D打印系统可视化窗口
  6. 虚拟串口服务器怎manager,VSPManager虚拟串口管理软件
  7. 【网管知识】狼牙抓鸡器中毒后的解决办法
  8. 访问无偏移的谷歌地图——工具篇
  9. 常用问题排查工具和分析神器,值得收藏
  10. 全新卡盟系统PHP版 集成易支付_2020易支付系统/聚合支付系统源码/免签约聚合支付系统/集成易支付相互对接...