《Javascript高级程序设计》阅读记录(一):第二、三章

时间:2021-02-05 10:23:57

《Javascript高级程序设计》阅读记录(一)

  这个系列,我会把阅读《Javascript高级程序设计》之后,感觉讲的比较深入,而且实际使用价值较大的内容记录下来,并且注释上我的一些想法。做这个一方面是提升了我的阅读效果以及方便我以后阅读

  另一个目的是,Javascript高级程序设计这本书内容很多也很厚,希望其他没有时间的人可以通过看这系列摘录,就可以大体学到书里面的核心内容。

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

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

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

  第一章由于只介绍了基本的内容,所以这里我就不进行记录了。

第2 章 在 HTML 中使用 JavaScript

2.1 <script>元素

async:可选。表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。只对外部脚本文件有效。

charset:可选。表示通过 src 属性指定的代码的字符集。由于大多数浏览器会忽略它的值,

因此这个属性很少有人用。

defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。

2.1.2 延迟脚本

  HTML 4.01 为<script>标签定义了 defer 属性。这个属性的用途是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素中设置defer 属性,相当于告诉浏览器立即下载,但延迟执行。

  虽然我们把<script>元素放在了文档的<head>元素中,但其中包含的脚本将延迟到浏览器遇到</html>标签后再执行。 HTML5 规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于 DOMContentLoaded 事件执行。在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在 DOMContentLoaded 事件触发前执行,因此最好只包含一个延迟脚本

  实际使用中,可以通过window.onload等达到同样效果,所以这个属性应用并不是很多。我有一篇文字专门总结了有关javascript脚本执行顺序,有兴趣可以看下。

  Javascript加载执行顺序http://www.cnblogs.com/qixinbo/p/6940441.html

  另外,还可以通过setTimeout(fun,time);进行延时执行,这个是非阻塞性的延时,也就是说编译器会继续执行setTimeout后面的语句,等到time定义的事件到后,再执行fun定义的内容。

  

第3 章 基本概念

3.4.7 Object类型

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

var o = new Object();

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

Object 的每个实例都具有下列属性和方法。

constructor:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(constructor)就是 Object()。

hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如: o.hasOwnProperty("name"))。

isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型(第 5 章将讨论原型) 。

propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用 for-in 语句

(本章后面将会讨论)来枚举。与 hasOwnProperty()方法一样,作为参数的属性名必须以字符

串形式指定。

toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。

toString():返回对象的字符串表示。

valueOf():返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值相同

  由于在 ECMAScript 中 Object 是所有对象的基础,因此所有对象都具有这些基本的属性和方法。第 5 章和第 6 章将详细介绍 Object 与其他对象的关系,从技术角度讲, ECMA-262 中对象的行为不一定适用于 JavaScript 中的其他对象。

  浏览器环境中的对象,比如 BOM 和 DOM 中的对象,都属于宿主对象,因为它们是由宿主实现提供和定义的。 ECMA-262 不负责定义宿主对象,因此宿主对象可能会也可能不会继承 Object。

3.6.5 for-in语句

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

for (property in expression) statement

下面是一个示例:

for (var propName in window) {

document.write(propName);

}

3.7.1 理解参数

ECMAScript 函数的参数与大多数其他语言中函数的参数有所不同。 ECMAScript 函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。也就是说,即便你定义的函数只接收两个参数,在调用这个函数时也未必一定要传递两个参数。可以传递一个、三个甚至不传递参数,而解析器永远不会有什么怨言。之所以会这样,原因是 ECMAScript 中的参数在内部是用一个数组来表示的。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数(如果有参数的话)。如果这个数组中不包含任何元素,无所谓;如果包含多个元素,也没有问题。实际上,在函数体内可以通过 arguments 对象来访问这个参数数组,从而获取传递给函数的每一个参数

其实, arguments 对象只是与数组类似(它并不是 Array 的实例),因为可以使用方括号语法访问它的每一个元素(即第一个元素是 arguments[0],第二个元素是 argumetns[1],以此类推),使用 length 属性来确定传递进来多少个参数。在前面的例子中, sayHi()函数的第一个参数的名字叫name,而该参数的值也可以通过访问 arguments[0]来获取。因此,那个函数也可以像下面这样重写,即不显式地使用命名参数:

function sayHi() {

alert("Hello " + arguments[0] + "," + arguments[1]);

}

这个重写后的函数中不包含命名的参数。虽然没有使用 name 和 message 标识符,但函数的功能依旧。这个事实说明了 ECMAScript 函数的一个重要特点:命名的参数只提供便利,但不是必需的。另外,在命名参数方面,其他语言可能需要事先创建一个函数签名,而将来的调用必须与该签名一致。但在 ECMAScript 中,没有这些条条框框,解析器不会验证命名参数。通过访问 arguments 对象的 length 属性可以获知有多少个参数传递给了函数。下面这个函数会在每次被调用时,输出传入其中的参数个数:

function howManyArgs() {

alert(arguments.length);

}

howManyArgs(
"string", 45); //2

howManyArgs();
//0

howManyArgs(
12); //1

执行以上代码会依次出现 3 个警告框,分别显示 2、 0 和 1。由此可见,开发人员可以利用这一点让函数能够接收任意个参数并分别实现适当的功能。请看下面的例子:

function doAdd() {

if(arguments.length == 1) {

alert(arguments[
0] + 10);

}
else if (arguments.length == 2) {

alert(arguments[
0] + arguments[1]);

}

}

doAdd(
10); //20

doAdd(
30, 20); //50

函数 doAdd()会在只有一个参数的情况下给该参数加上 10;如果是两个参数,则将那个参数简单相加并返回结果。因此, doAdd(10)会返回 20,而 doAdd(30,20)则返回 50。虽然这个特性算不上完美的重载,但也足够弥补 ECMAScript 的这一缺憾了。

另一个与参数相关的重要方面,就是 arguments 对象可以与命名参数一起使用,如下面的例子所示:

function doAdd(num1, num2) {

if(arguments.length == 1) {

alert(num1
+ 10);

}
else if (arguments.length == 2) {

alert(arguments[
0] + num2);

}

}

在重写后的这个 doAdd()函数中,两个命名参数都与 arguments 对象一起使用。由于 num1 的值与 arguments[0]的值相同,因此它们可以互换使用(当然, num2 和 arguments[1]也是如此)。

关于 arguments 的行为,还有一点比较有意思。那就是它的值永远与对应命名参数的值保持同步。

例如

function doAdd(num1, num2) {

arguments[
1] = 10;

alert(arguments[
0] + num2);

}

每次执行这个 doAdd()函数都会重写第二个参数,将第二个参数的值修改为 10。因为 arguments对象中的值会自动反映到对应的命名参数,所以修改 arguments[1],也就修改了 num2,结果它们的值都会变成 10。不过,这并不是说读取这两个值会访问相同的内存空间;它们的内存空间是独立的,但它们的值会同步。另外还要记住,如果只传入了一个参数,那么为 arguments[1]设置的值不会反应到命名参数中。这是因为 arguments 对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。

关于参数还要记住最后一点:没有传递值的命名参数将自动被赋予 undefined 值。这就跟定义了变量但又没有初始化一样。例如,如果只给 doAdd()函数传递了一个参数,则 num2 中就会保存undefined 值。

严格模式对如何使用 arguments 对象做出了一些限制。首先,像前面例子中那样的赋值会变得无效。也就是说,即使把 arguments[1]设置为 10, num2 的值仍然还是 undefined。其次,重写arguments 的值会导致语法错误(代码将不会执行)。

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

3.7.2 没有重载

ECMAScript 函数不能像传统意义上那样实现重载。而在其他语言(如 Java)中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数的类型和数量)不同即可。如前所述, ECMAScirpt函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。

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

每一个函数名就是一个指针变量,存储了指向这个函数的内存地址。