欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  web前端

深入理解《​Javascript高级程序设计》

程序员文章站 2022-03-29 14:10:40
...
Javascript高级程序设计这本书内容很多也很厚,希望其他没有时间的人可以通过看这系列摘录,就可以大体学到书里面的核心内容。

  绿色背景的内容是我认为比较值得注意的原著内容。

  黄色背景的内容是我认为非常重要的原著内容。

  我的理解会用蓝色的字体标示出来。

  这章的内容较多,而且比较重要,分两篇来记录,这个是下篇。

  5.5.4 函数内部属性

  在函数内部,有两个特殊的对象: arguments 和 this。其中, arguments 在第 3 章曾经介绍过,它是一个类数组对象,包含着传入函数中的所有参数。虽然 arguments 的主要用途是保存函数参数,但这个对象还有一个名叫 callee 的属性该属性是一个指针,指向拥有这个 arguments 对象的函数。请看下面这个非常经典的阶乘函数

function factorial(num){if (num <=1) {return 1;

} else {return num * factorial(num-1)

}

}

  定义阶乘函数一般都要用到递归算法;如上面的代码所示,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用 arguments.callee。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
function factorial(num){if (num <=1) {return 1;

} else {return num * arguments.callee(num-1)

}

}
View Code

在这个重写后的 factorial()函数的函数体内,没有再引用函数名 factorial。这样,无论引用

函数时使用的是什么名字,都可以保证正常完成递归调用。例如:

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
var trueFactorial = factorial;

factorial = function(){return 0;

};

alert(trueFactorial(5)); //120alert(factorial(5)); //0
View Code

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

函数内部的另一个特殊对象是 this,其行为与 Java 和 C#中的 this 大致类似。换句话说, this引用的是函数据以执行的环境对象——或者也可以说是 this 值(当在网页的全局作用域中调用函数时,this 对象引用的就是 window)。来看下面的例子。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
window.color = "red";var o = { color: "blue" };function sayColor(){

alert(this.color);

}

sayColor(); //"red"o.sayColor = sayColor;

o.sayColor(); //"blue"
View Code

  上面这个函数 sayColor()是在全局作用域中定义的,它引用了 this 对象。由于在调用函数之前,this 的值并不确定,因此 this 可能会在代码执行过程中引用不同的对象。当在全局作用域中调用sayColor()时, this 引用的是全局对象 window;换句话说,对 this.color 求值会转换成对window.color 求值,于是结果就返回了"red"。而当把这个函数赋给对象 o 并调用 o.sayColor()时,this 引用的是对象 o,因此对 this.color 求值会转换成对 o.color 求值,结果就返回了"blue"。

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

  ECMAScript 5 也规范化了另一个函数对象的属性:caller。除了 Opera 的早期版本不支持,其他浏览器都支持这个 ECMAScript 3 并没有定义的属性。这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null。例如:

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
function outer(){

inner();

}function inner(){

alert(inner.caller);

}

outer();
View Code

以上代码会导致警告框中显示 outer()函数的源代码。因为 outer()调用了 inter(),所以inner.caller 就指向 outer()。为了实现更松散的耦合,也可以通过 arguments.callee.caller来访问相同的信息。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
function outer(){

inner();

}function inner(){

alert(arguments.callee.caller);

}

outer();
View Code

IE、 Firefox、 Chrome 和 Safari 的所有版本以及 Opera 9.6 都支持 caller 属性。

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

  5.5.5 函数属性和方法

  前面曾经提到过, ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性: length 和 prototype。其中, length 属性表示函数希望接收的命名参数的个数,如下面的例子所示。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
function sayName(name){

alert(name);

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

}function sayHi(){

alert("hi");

}

alert(sayName.length); //1alert(sum.length); //2alert(sayHi.length); //0
View Code

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

  每个函数都包含两个非继承而来的方法: apply()和 call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。首先, apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例也可以是arguments 对象。例如:

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
function sum(num1, num2){return num1 + num2;

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

alert(callSum1(10,10)); //20alert(callSum2(10,10)); //20
View Code

  在上面这个例子中, callSum1()在执行 sum()函数时传入了 this 作为 this 值(因为是在全局作用域中调用的,所以传入的就是 window 对象)和 arguments 对象。而 callSum2 同样也调用了sum()函数,但它传入的则是 this 和一个参数数组。这两个函数都会正常执行并返回正确的结果。

  call()方法与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来,如下面的例子所示。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
function sum(num1, num2){return num1 + num2;

}function callSum(num1, num2){return sum.call(this, num1, num2);

}

alert(callSum(10,10)); //20
View Code

  在使用 call()方法的情况下, callSum()必须明确地传入每一个参数。结果与使用 apply()没有什么不同。至于是使用 apply()还是 call(),完全取决于你采取哪种给函数传递参数的方式最方便。如果你打算直接传入 arguments 对象,或者包含函数中先接收到的也是一个数组,那么使用 apply()肯定更方便;否则,选择 call()可能更合适。(在不给函数传递参数的情况下,使用哪个方法都无所谓。)

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

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
window.color = "red";var o = { color: "blue" };function sayColor(){

alert(this.color);

}

sayColor(); //redsayColor.call(this); //redsayColor.call(window); //redsayColor.call(o); //blue
View Code

  这个例子是在前面说明 this 对象的示例基础上修改而成的。这一次, sayColor()也是作为全局函数定义的,而且当在全局作用域中调用它时,它确实会显示"red"——因为对 this.color 的求值会转换成对 window.color 的求值。而 sayColor.call(this)和 sayColor.call(window),则是两种显式地在全局作用域中调用函数的方式,结果当然都会显示"red"。但是,当运行 sayColor.call(o)时,函数的执行环境就不一样了,因为此时函数体内的 this 对象指向了 o,于是结果显示的是"blue"。

  使用 call()(或 apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。在前面例子的第一个版本中,我们是先将 sayColor()函数放到了对象 o 中,然后再通过 o 来调用它的;而在这里重写的例子中,就不需要先前那个多余的步骤了。

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

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

alert(this.color);

}var objectSayColor = sayColor.bind(o);

objectSayColor(); //blue

在这里, sayColor()调用 bind()并传入对象 o,创建了 objectSayColor()函数。 object

SayColor()函数的 this 值等于 o,因此即使是在全局作用域中调用这个函数,也会看到"blue"。这种技巧的优点请参考第 22 章。

支持 bind()方法的浏览器有 IE9+、 Firefox 4+、 Safari 5.1+、 Opera 12+和 Chrome。

每个函数继承的 toLocaleString()和 toString()方法始终都返回函数的代码。返回代码的格式则因浏览器而异——有的返回的代码与源代码中的函数代码一样,而有的则返回函数代码的内部表示,即由解析器删除了注释并对某些代码作了改动后的代码。由于存在这些差异,我们无法根据这两个方法返回的结果来实现任何重要功能;不过,这些信息在调试代码时倒是很有用。另外一个继承的valueOf()方法同样也只返回函数代码。

5.6 基本包装类型

  为了便于操作基本类型值, ECMAScript 还提供了 3 个特殊的引用类型: Boolean、 Number 和String。这些类型与本章介绍的其他引用类型相似,但同时也具有与各自的基本类型相应的特殊行为。实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。来看下面的例子。

var s1 = "some text";

var s2 = s1.substring(2);

  这个例子中的变量 s1 包含一个字符串,字符串当然是基本类型值。而下一行调用了 s1 的substring()方法,并将返回的结果保存在了 s2 中。我们知道,基本类型值不是对象,因而从逻辑上讲它们不应该有方法(尽管如我们所愿,它们确实有方法)。其实,为了让我们实现这种直观的操作,后台已经自动完成了一系列的处理。当第二行代码访问 s1 时,访问过程处于一种读取模式,也就是要从内存中读取这个字符串的值。而在读取模式中访问字符串时,后台都会自动完成下列处理。

(1) 创建 String 类型的一个实例;

(2) 在实例上调用指定的方法;

(3) 销毁这个实例。

可以将以上三个步骤想象成是执行了下列 ECMAScript 代码。

var s1 = new String("some text");

var s2 = s1.substring(2);

s1 = null;

  经过此番处理,基本的字符串值就变得跟对象一样了。而且,上面这三个步骤也分别适用于 Boolean和 Number 类型对应的布尔值和数字值。引用类型与基本包装类型的主要区别就是对象的生存期。使用 new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。

来看下面的例子:

var s1 = "some text";

s1.color = "red";

alert(s1.color); //undefined

在此,第二行代码试图为字符串 s1 添加一个 color 属性。但是,当第三行代码再次访问 s1 时,其 color 属性不见了。问题的原因就是第二行创建的 String 对象在执行第三行代码时已经被销毁了。第三行代码又创建自己的 String 对象,而该对象没有 color 属性。当然,可以显式地调用 Boolean、 Number 和 String 来创建基本包装类型的对象。不过,应该在绝对必要的情况下再这样做,因为这种做法很容易让人分不清自己是在处理基本类型还是引用类型的值。对基本包装类型的实例调用 typeof 会返回"object",而且所有基本包装类型的对象都会被转换为布尔值 true。

Object 构造函数也会像工厂方法一样,根据传入值的类型返回相应基本包装类型的实例。例如:

var obj = new Object("some text");

alert(obj instanceof String); //true

把字符串传给 Object 构造函数,就会创建 String 的实例;而传入数值参数会得到 Number 的实例,传入布尔值参数就会得到 Boolean 的实例。

要注意的是,使用 new 调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。例如:

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
var value = "25";var number = Number(value); //转型函数alert(typeof number); //"number"var obj = new Number(value); //构造函数alert(typeof obj); //"object"
View Code

在这个例子中,变量 number 中保存的是基本类型的值 25,而变量 obj 中保存的是 Number 的实例。要了解有关转型函数的更多信息,请参考第 3 章。

尽管我们不建议显式地创建基本包装类型的对象,但它们操作基本类型值的能力还是相当重要的。而每个基本包装类型都提供了操作相应值的便捷方法。

5.6.1 Boolean类型

Boolean 类型是与布尔值对应的引用类型。要创建 Boolean 对象,可以像下面这样调用 Boolean构造函数并传入 true 或 false 值。

var booleanObject = new Boolean(true);

Boolean 类型的实例重写了 valueOf()方法,返回基本类型值 true 或 false;重写了 toString()

方法,返回字符串"true"和"false"。可是, Boolean 对象在 ECMAScript 中的用处不大,因为它经常会造成人们的误解。其中最常见的问题就是在布尔表达式中使用 Boolean 对象,例如:

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
var falseObject = new Boolean(false);var result = falseObject && true;

alert(result); //truevar falseValue = false;

result = falseValue && true;

alert(result); //false
View Code

前面讨论过,布尔表达式中的所有对象都会被转换为 true,因此 falseObject 对象在布尔表达式中代表的是 true。结果, true && true 当然就等于 true 了。

基本类型与引用类型的布尔值还有两个区别。首先, typeof 操作符对基本类型返回"boolean",而对引用类型返回"object"。其次,由于 Boolean 对象是 Boolean 类型的实例,所以使用 instanceof操作符测试 Boolean 对象会返回 true,而测试基本类型的布尔值则返回 false。例如:

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
alert(typeof falseObject); //objectalert(typeof falseValue); //booleanalert(falseObject instanceof Boolean); //truealert(falseValue instanceof Boolean); //false
View Code

理解基本类型的布尔值与 Boolean 对象之间的区别非常重要——当然,我们的建议是永远不要使用 Boolean 对象。

5.6.2 Number类型

Number 是与数字值对应的引用类型。要创建 Number 对象,可以在调用 Number 构造函数时向其中传递相应的数值。下面是一个例子。

var numberObject = new Number(10);

与 Boolean 类型一样, Number 类型也重写了 valueOf()、 toLocaleString()和 toString()

方法。重写后的 valueOf()方法返回对象表示的基本类型的数值,另外两个方法则返回字符串形式的数值。我们在第 3 章还介绍过,可以为 toString()方法传递一个表示基数的参数,告诉它返回几进制数值的字符串形式,如下面的例子所示。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
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"NumberTypeExample01.htm
View Code

除了继承的方法之外, Number 类型还提供了一些用于将数值格式化为字符串的方法。

其中, toFixed()方法会按照指定的小数位返回数值的字符串表示,例如:

var num = 10;

alert(num.toFixed(2)); //"10.00"

NumberTypeExample01.htm

这里给 toFixed()方法传入了数值 2,意思是显示几位小数。于是,这个方法返回了"10.00",即以 0 填补了必要的小数位。如果数值本身包含的小数位比指定的还多,那么接近指定的最大小数位的值就会舍入,如下面的例子所示。

var num = 10.005;

alert(num.toFixed(2)); //"10.01"

能够自动舍入的特性,使得 toFixed()方法很适合处理货币值。但需要注意的是,不同浏览器给这个方法设定的舍入规则可能会有所不同。在给 toFixed()传入 0 的情况下, IE8 及之前版本不能正确舍入范围在{(-0.94,-0.5],[0.5,0.94)}之间的值。对于这个范围内的值, IE 会返回0,而不是1或1;其他浏览器都能返回正确的值。 IE9 修复了这个问题。

5.6.3 String类型

String 类型是字符串的对象包装类型,可以像下面这样使用 String 构造函数来创建。

var stringObject = new String("hello world");

String 对象的方法也可以在所有基本的字符串值中访问到。其中,继承的 valueOf()、toLocale

String()和 toString()方法,都返回对象所表示的基本字符串值。

String 类型的每个实例都有一个 length 属性,表示字符串中包含多个字符。来看下面的例子。

var stringValue = "hello world";

alert(stringValue.length); //"11"

这个例子输出了字符串"hello world"中的字符数量,即"11"。应该注意的是,即使字符串中包含双字节字符(不是占一个字节的 ASCII 字符),每个字符也仍然算一个字符。

String 类型提供了很多方法,用于辅助完成对 ECMAScript 中字符串的解析和操作。

1. 字符方法

两个用于访问字符串中特定字符的方法是: charAt()和 charCodeAt()

2. 字符串操作方法

下面介绍与操作字符串有关的几个方法。第一个就是 concat(),用于将一或多个字符串拼接起来,返回拼接得到的新字符串。先来看一个例子。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
var stringValue = "hello ";var result = stringValue.concat("world");

alert(result); //"hello world"alert(stringValue); //"hello"
View Code

ECMAScript还提供了三个基于子字符串创建新字符串的方法:slice()、substr()和substring()。这三个方法都会返回被操作字符串的一个子字符串,而且也都接受一或两个参数。第一个参数指定子字符串的开始位置,第二个参数(在指定的情况下)表示子字符串到哪里结束。具体来说, slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的位置。而 substr()的第二个参数指定的则是返回的字符个数。如果没有给这些方法传递第二个参数,则将字符串的长度作为结束位置。与concat()方法一样, slice()、 substr()和 substring()也不会修改字符串本身的值——它们只是返回一个基本类型的字符串值,对原始字符串没有任何影响。请看下面的例子。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
var stringValue = "hello world";

alert(stringValue.slice(3)); //"lo world"alert(stringValue.substring(3)); //"lo world"alert(stringValue.substr(3)); //"lo world"alert(stringValue.slice(3, 7)); //"lo w"alert(stringValue.substring(3,7)); //"lo w"alert(stringValue.substr(3, 7)); //"lo worl"
View Code

3. 字符串位置方法

有两个可以从字符串中查找子字符串的方法: indexOf()和 lastIndexOf()。这两个方法都是从

一个字符串中搜索给定的子字符串,然后返子字符串的位置(如果没有找到该子字符串,则返回-1)。这两个方法的区别在于: indexOf()方法从字符串的开头向后搜索子字符串,而 lastIndexOf()方法是从字符串的末尾向前搜索子字符串。还是来看一个例子吧。

var stringValue = "hello world";

alert(stringValue.indexOf("o")); //4

alert(stringValue.lastIndexOf("o")); //7

4. trim()方法

ECMAScript 5 为所有字符串定义了 trim()方法。这个方法会创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果。例如:

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
var stringValue = " hello world ";var trimmedStringValue = stringValue.trim();

alert(stringValue); //" hello world "alert(trimmedStringValue); //"hello world"
View Code

5. 字符串大小写转换方法

接下来我们要介绍的是一组与大小写转换有关的方法。 ECMAScript 中涉及字符串大小写转换的方法有 4 个: toLowerCase()、 toLocaleLowerCase()、 toUpperCase()和 toLocaleUpperCase()

7. localeCompare()方法

与操作字符串有关的最后一个方法是 localeCompare(),这个方法比较两个字符串,并返回下列值中的一个:

q 如果字符串在字母表中应该排在字符串参数之前,则返回一个负数(大多数情况下是-1,具体的值要视实现而定);

q 如果字符串等于字符串参数,则返回 0;

q 如果字符串在字母表中应该排在字符串参数之后,则返回一个正数(大多数情况下是 1,具体的值同样要视实现而定)。

下面是几个例子。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
var stringValue = "yellow";

alert(stringValue.localeCompare("brick")); //1alert(stringValue.localeCompare("yellow")); //0alert(stringValue.localeCompare("zoo")); //-1
View Code

5.7 单体内置对象

  ECMA-262 对内置对象的定义是:“由 ECMAScript 实现提供的、不依赖于宿主环境的对象,这些对象在 ECMAScript 程序执行之前就已经存在了。”意思就是说,开发人员不必显式地实例化内置对象,因为它们已经实例化了。前面我们已经介绍了大多数内置对象,例如Object、 Array 和 String。

ECMA-262 还定义了两个单体内置对象: Global 和 Math。

5.7.1 Global对象

  Global(全局)对象可以说是 ECMAScript 中最特别的一个对象了,因为不管你从什么角度上看,这个对象都是不存在的。 ECMAScript 中的 Global 对象在某种意义上是作为一个终极的“兜底儿对象”来定义的。换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是 Global 对象的属性。本书前面介绍过的那些函数,诸如 isNaN()、isFinite()、parseInt()以及 parseFloat(),实际上全都是 Global对象的方法。除此之外, Global 对象还包含其他一些方法。

1. URI 编码方法

Global 对象的 encodeURI()和 encodeURIComponent()方法可以对 URI(Uniform Resource

Identifiers,通用资源标识符)进行编码,以便发送给浏览器。有效的 URI 中不能包含某些字符,例如空格。而这两个 URI 编码方法就可以对 URI 进行编码,它们用特殊的 UTF-8 编码替换所有无效的字符,从而让浏览器能够接受和理解。

2. eval()方法

现在,我们介绍最后一个——大概也是整个 ECMAScript 语言中最强大的一个方法:eval()。eval()方法就像是一个完整的 ECMAScript 解析器,它只接受一个参数,即要执行的 ECMAScript (或 JavaScript)字符串。看下面的例子:

eval("alert('hi')");

这行代码的作用等价于下面这行代码:

alert("hi");

当解析器发现代码中调用 eval()方法时,它会将传入的参数当作实际的 ECMAScript 语句来解析,然后把执行结果插入到原位置。通过 eval()执行的代码被认为是包含该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用域链。这意味着通过 eval()执行的代码可以引用在包含环境中定义的变量,举个例子:

var msg = "hello world";

eval("alert(msg)"); //"hello world"

可见,变量 msg 是在 eval()调用的环境之外定义的,但其中调用的 alert()仍然能够显示"hello world"。这是因为上面第二行代码最终被替换成了一行真正的代码。同样地,我们也可以在 eval()调用中定义一个函数,然后再在该调用的外部代码中引用这个函数:

eval("function sayHi() { alert('hi'); }");

sayHi();

显然,函数 sayHi()是在 eval()内部定义的。但由于对 eval()的调用最终会被替换成定义函数

的实际代码,因此可以在下一行调用 sayHi()。对于变量也一样:

eval("var msg = 'hello world'; ");

alert(msg); //"hello world"

在 eval()中创建的任何变量或函数都不会被提升,因为在解析代码的时候,它们被包含在一个字符串中;它们只在 eval()执行的时候创建。

严格模式下,在外部访问不到 eval()中创建的任何变量或函数,因此前面两个例子都会导致错误。同样,在严格模式下,为 eval 赋值也会导致错误:

"use strict";

eval = "hi"; //causes error

能够解释代码字符串的能力非常强大,但也非常危险。因此在使用 eval()时必须极为谨慎,特别是在用它执行用户输入数据的情况下。否则,可能会有恶意用户输入威胁你的站点或应用程序安全的代码(即所谓的代码注入)。

3. Global 对象的属性

Global 对象还包含一些属性,其中一部分属性已经在本书前面介绍过了。例如,特殊的值

undefined、 NaN 以及 Infinity 都是 Global 对象的属性。此外,所有原生引用类型的构造函数,像Object 和 Function,也都是 Global 对象的属性。下表列出了 Global 对象的所有属性。

undefined 特殊值undefined Date 构造函数Date

NaN 特殊值NaN RegExp 构造函数RegExp

Infinity 特殊值Infinity Error 构造函数Error

Object 构造函数Object EvalError 构造函数EvalError

Array 构造函数Array RangeError 构造函数RangeError

Function 构造函数Function ReferenceError 构造函数ReferenceError

Boolean 构造函数Boolean SyntaxError 构造函数SyntaxError

String 构造函数String TypeError 构造函数TypeError

Number 构造函数Number URIError 构造函数URIError

ECMAScript 5 明确禁止给 undefined、 NaN 和 Infinity 赋值,这样做即使在非严格模式下也会导致错误。

4. window 对象

ECMAScript 虽然没有指出如何直接访问 Global 对象,但 Web 浏览器都是将这个全局对象作为window 对象的一部分加以实现的。因此,在全局作用域中声明的所有变量和函数,就都成为了 window对象的属性。来看下面的例子。

深入理解《​Javascript高级程序设计》深入理解《​Javascript高级程序设计》
var color = "red";function sayColor(){

alert(window.color);

}

window.sayColor(); //"red"
View Code

JavaScript中的 window 对象除了扮演 ECMAScript规定的 Global 对象的角色外,

还承担了很多别的任务。第 8 章在讨论浏览器对象模型时将详细介绍 window 对象。

5.7.2 Math对象

ECMAScript 还为保存数学公式和信息提供了一个公共位置,即 Math 对象。与我们在 JavaScript 直接编写的计算功能相比, Math 对象提供的计算功能执行起来要快得多。 Math 对象中还提供了辅助完成这些计算的属性和方法。

1. Math 对象的属性

Math 对象包含的属性大都是数学计算中可能会用到的一些特殊值。下表列出了这些属性。

深入理解《​Javascript高级程序设计》

虽然讨论这些值的含义和用途超出了本书范围,但你确实可以随时使用它们。

2. min()和 max()方法

Math 对象还包含许多方法,用于辅助完成简单和复杂的数学计算。

其中, min()和 max()方法用于确定一组数值中的最小值和最大值。这两个方法都可以接收任意多个数值参数,如下面的例子所示。

var max = Math.max(3, 54, 32, 16);

alert(max); //54

var min = Math.min(3, 54, 32, 16);

alert(min); //3

3. 舍入方法

下面来介绍将小数值舍入为整数的几个方法: Math.ceil()、 Math.floor()和 Math.round()。

这三个方法分别遵循下列舍入规则:

Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;

Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;

Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数(这也是我们在数学课上学到的舍入规则)。

4. random()方法

Math.random()方法返回大于等于 0 小于 1 的一个随机数。对于某些站点来说,这个方法非常实用,因为可以利用它来随机显示一些名人名言和新闻事件。套用下面的公式,就可以利用 Math.random()从某个整数范围内随机选择一个值。

值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)

5. 其他方法

深入理解《​Javascript高级程序设计》

5.8 小结

对象在 JavaScript 中被称为引用类型的值,而且有一些内置的引用类型可以用来创建特定的对象,现简要总结如下:

引用类型与传统面向对象程序设计中的类相似,但实现不同;

Object 是一个基础类型,其他所有类型都从 Object 继承了基本的行为;

Array 类型是一组值的有序列表,同时还提供了操作和转换这些值的功能;

Date 类型提供了有关日期和时间的信息,包括当前日期和时间以及相关的计算功能;

RegExp 类型是 ECMAScript 支持正则表达式的一个接口,提供了最基本的和一些高级的正则表达式功能。

函数实际上是 Function 类型的实例,因此函数也是对象;而这一点正是 JavaScript 最有特色的地方。由于函数是对象,所以函数也拥有方法,可以用来增强其行为。

因为有了基本包装类型,所以 JavaScript 中的基本类型值可以被当作对象来访问。三种基本包装类型分别是: Boolean、 Number 和 String。以下是它们共同的特征:

每个包装类型都映射到同名的基本类型;

在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作;

操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象。

在所有代码执行之前,作用域中就已经存在两个内置对象:Global 和 Math。在大多数 ECMAScript实现中都不能直接访问 Global 对象;不过, Web 浏览器实现了承担该角色的 window 对象。全局变量和函数都是 Global 对象的属性。 Math 对象提供了很多属性和方法,用于辅助完成复杂的数学计算任务。

以上就是 深入理解《​Javascript高级程序设计》的详细内容,更多请关注其它相关文章!