JavaScript DOM编程艺术(第2版) 笔记

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


基本概念

程序设计语言 分为 解释型编译型 两大类。Java 或 C++ 等语言需要一个编译器(compiler)。编译器是一种程序,能够把用 Java 等高级语言编写出来的源代码翻译为直接在计算机上执行的文件。

JavaScript 是一种解释型程序设计语言。

解释型程序设计语言 不需要编译器——它们仅 需要解释器。对于 JavaScript 语言,在互联网环境下,Web浏览器负责完成有关的解释和执行工作。浏览器中的 JavaScript 解释器将直接读入源代码并执行。浏览器中如果没有解释器,JavaScript 代码就无法执行。



1、JavaScript语法


1.1 JavaScript 代码的执行:

  • 方式一:将JavaScript 代码放到文档<head>标签中的<script>标签之间;

  • 方式二:把JavaScript代码保存为一个扩展名为 .js 的独立文件,在文档的<head>部分放一个<script>标签,并把它的src属性指向该文件;

但最好的做法是把<script>标签放到HTML文档的最后,</body>标签之前:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8"/>
		<title>Example</title>
	</head>
	<body>
		Mark-up goes here...
		<script src="file.js"></script>
	</body>
</html>

—— 这样能使浏览器 更快加载 页面。

上例中的<script>标签没有包含传统的type="text/javascript"属性。是因为脚本默认是 Java Script,所以并没有必要去指定这个属性。


1.2 JavaScript 语句

只需简单地把各条语句放在不同的行上就可以分隔,如下所示:

first statement
second statement

但是,在每条语句的末尾都加上一个分号,才是一种良好的编程习惯:

first statement;
second statement;

这样做让代码更容易阅读。让每条语句独占一行的做法能更容易跟踪JavaScript脚本的执行顺序。

1.3 变量

JavaScript 允许 直接对变量赋值而 无需事先声明

很多程序设计语言中是不允许的。有很多语言要求在使用任何变量之前必须先对它进行声明(declare)。

JavaScript 脚本中,如果对某个变量赋值之前未声明,赋值操作将自动声明该变量。

虽然 JavaScript 没有强制要求程序员必须提前声明变量,但提前声明变量是一种良好的编程习惯。如下所示:

var mood;
var age;

不必单独声明每个变量,可以用一条语句一次声明多个变量:

var mood, age;

甚至可以 声明变量 和对该 变量赋值 一次完成

var mood = "happy";
var age = 33;

或者写成:

var mood = "happy", age = 33;

在 JavaScript 里,变量 和 其他 语法元素 的名字都是 区分 字母 大小写


1.4 数据类型

必须 明确类型声明 的语言称为 强类型(strongly typed)语言。JavaScript 不需要进行类型声明,因此它是一种 弱类型(weakly typed)语言。

弱类型意味着程序员可以在任何阶段改变变量的数据类型。

1.4.1 字符串

下面这两条语句含义完全相同:

var mood = 'happy';
var mood = "happy";

但是,作为一个好的编程习惯,不管选择用双引号还是单引号,整个脚本中应保持一致(否则,代码会变得难以阅读和理解)。

1.4.2 数值

JavaScript 也支持浮点数

var temperature = -20.33333333

1.4.3 数组


数组用关键字Array声明。声明数组的同时还可指定数组的长度(length):

var beatles = Array(4);

数组下标是从0开始计数;

如果无法预知数组元素个数,可以在声明时不给出元素个数:

var beatles = Array();

这是一种相对简单的方式,在声明数组的同时对它进行填充

var beatles = Array( "John", "Paul", "George", "Ringo" );

甚至,用不着明确表明是在创建数组。只需用一对方括号把各个元素的初始值括起即可:

var beatles = [ "John", "Paul", "George", "Ringo" ];

甚至也可以把多种数据类型混在一起存入一个数组:

var lennon = [ "John", 1940, false ];

1.4.4 对象

与使用Array类似,创建对象使用Object关键字。它不使用方括号和下标来获取元素,而是像任何 JavaScript 对象一样,使用点号来获取属性;

var lennon = { name:"John", year:1940, living:false };

用对象来代替传统数组,就用元素的名字而不是下标数字来访问它们。这提高了脚本的可读性。

1.5 操作

赋值使用等号(=)。加法操作符加号(+),减法操作符减号(-),除法操作符斜杠(/),乘法操作符星号(*)。

  • +=,可以一次完成 “ 加法和赋值 ”(或“拼接和赋值”)操作:
    var year = 2010;
    var message = "The year is ";
    message += year;
    alert(message);   // The year is 2010
    
    运行结果如下所示:
    在这里插入图片描述

1.6 条件语句

1.6.1 if 条件语句

条件必须放在if后面的圆括号中。条件的 求值结果 永远是一个 布尔值,即只能是truefalse

if ('条件') {
	statements;
}

并且,只有在给定条件的求值结果是true的情况下才会执行,如下所示:

if (1 > 2) {
	alert("全世界都疯了!");
}

上例中,1>2 这个条件的值是false,因此alert消息不会出现。

if语句中的{}(花括号)本身并不是必须的。如果if语句中的{}部分只包含一条语句的话,则可不写{}且写在同一行上,如下所示:

if (1 > 2) alert("全世界都疯了!");

为提高脚本的可读性,建议在if语句中使用花括号,这是个 好习惯

else子句:包含在else子句中的语句会在给定条件为假时执行:

if (1 > 2) {
	alert("全世界都疯了!");
} else {
	alert("一切都很好");
}

1.6.2 比较操作符

  • 等于==) :注意,单个等号(=)用于完成赋值操作。

    var my_mood = "happy";
    var your_mood = "sad";
    if (my_mood == your_mood) {
    	alert("我们都有同样的感觉.");
    }
    

  • 不等于!=):由一个感叹号和一个等号构成。

    if (my_mood != your_mood) {
    	alert("我们的心情不一样.");
    }
    

    相等操作符==不表示严格相等

    例如,比较false与一个空字符串会得到什么结果?

    var a = false;
    var b = "";
    if (a == b) {
    	alert("a 等于 b");
    }
    

    这里,条件语句”a==b“求值结果为true,因为相等操作符==认为 空字符串false的含义相同。要进行严格比较,就要使用另一种等号(===)。

  • 全等===):会执行严格的比较,不仅比较值,而且会比较变量的类型:

    var a = false;
    var b = "";
    if (a === b) {
    	alert("a 等于 b");
    }
    

    这样,条件表达式的求值结果就是false了,因为BooleanString不是一种类型。

1.6.3 逻辑操作符

逻辑操作符的操作对象是布尔值。每个逻辑操作数返回一个布尔值true或者是false

  • 逻辑与&&):只有在它的操作数都为true时,返回true

    if ( num >= 5 && num <= 10 ) {
    	alert("号码在正确的范围内.");
    }
    

  • 逻辑或||):操作数都是true,或其中有一个是true,返回true;当操作数都为false时,返回false

    if (num > 10 || num < 5) {
    	alert("号码在正确的范围内.");
    }
    

  • 逻辑非!):把逻辑操作数所返回的布尔值 取反

    if (!(1 > 2)) {
    	alert("世界一切都好");
    }
    
    if (!(num > 10 || num < 5)) {
    	alert("号码在正确的范围内.");
    }
    

1.6.4 循环语句

  • while 循环

    对循环控制条件的求值发生在每次循环结束之前:

    while ('条件') {
    	statements;
    }
    

    区别while循环只要条件为true{}里的代码将反复执行。

    var count = 1;
    while (count < 11) {
    	alert (count);
    	count++;
    }
    

    在这个while循环的内部,用“++”操作符对变量count的值执行加1操作,而这会重复执行10次。

    注: 如果循环控制条件的首次求值结果是false,后面的程序一次也不会被执行。

  • do…while 循环

    对循环条件求值在每次循环结束之后:

    do {
    	statements;
    } while ('条件');
    

    因此,即使循环条件的首次求值为false花括号里的语句至少会被执行一次

    示例代码:

    var count = 1;
    do {
    	alert (count);
    	count++;
    } while (count < 11);
    

  • for 循环

    for 循环是 while 循环的一种变体,好处是循环控制结构更加清晰。

    for (var count = 1; count < 11; count++ ) {
    	alert (count);
    }
    

    常见用途:对某个数组里的全体元素进行遍历处理。



1.7 函数

为实现同一段代码的复用,可以把它们封装成一个函数(function)。每个函数实际上是一个短小的脚本。

定义函数时,可以声明任意多个参数,中间用逗号分隔。在函数的内部,可以像使用普通变量那样使用任何一个参数。

注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。

如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined

  • 语法】:

    function name(arguments) {
    	statements;
    }
    
  • 示例1】:

    // 定义函数
    function shout() {
    	var beatles = Array("John","Paul","George","Ringo");
    	for (var i = 0 ; i < beatles.length; i++ ) {
    		alert(beatles[i]);
    	}
    }
    
    // 调用函数
    shout();
    

  • 示例2】:

    如下所示,该函数需要传递两个参数。如果把 2 个数值传递给函数,这个函数将对进行乘法运算:

    function multiply(num1,num2) {
    	var total = num1 * num2;
    	alert(total);
    }
    

    在定义了这个函数的脚本里,即可在任意位置调用(调用函数时,按顺序 传入参数即可):

    multiply(10,2);
    

  • 【示例3】:
    函数不仅能够(以参数的形式)接收数据,还能够返回数据。这需要用到return语句:

    function multiply(num1,num2) {
    	var total = num1 * num2;
    	return total;
    }
    

    注: 由于 JavaScript 允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数。

  • arguments 关键字
    JavaScript 还有一个特殊的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array

    function foo(x) {
        console.log('x = ' + x); // 10
        for (var i=0; i<arguments.length; i++) {
            console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
        }
    }
    
    // 调用函数
    foo(10, 20, 30);
    
    // 控制台结果输出如下:
    // x = 10
    // arg 0 = 10
    // arg 1 = 20
    // arg 2 = 30
    

    利用arguments,可以获得调用者传入的所有参数。即使函数不定义任何参数,也还是可以拿到参数的值:

    function abs() {
        if (arguments.length === 0) {
            return 0;
        }
        var x = arguments[0];
        return x >= 0 ? x : -x;
    }
    
    abs(); // 0
    abs(10); // 10
    abs(-9); // 9
    

    实际上,`arguments`最常用于判断传入参数的个数:
    // foo(a[, b], c)
    // 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:
    function foo(a, b, c) {
        if (arguments.length === 2) {
            // 实际拿到的参数是a和b,c为undefined
            c = b; // 把b赋给c
            b = null; // b变为默认值
        }
        // ...
    }
    

    要把中间的参数b变为 “可选” 参数,就只能通过arguments判断,然后重新调整参数并赋值。

  • rest 参数

    ES6 标准引入了rest参数,该参数只能写在最后,前面用...标识。

    function foo(a, b, ...rest) {
        console.log('a = ' + a);
        console.log('b = ' + b);
        console.log(rest);
    }
    
    foo(1, 2, 3, 4, 5);
    // 结果:
    // a = 1
    // b = 2
    // Array [ 3, 4, 5 ]
    

    从运行结果可知,传入的参数先绑定ab,多余的参数以数组形式交给变量rest,所以,不再需要arguments我们就获取了全部参数。

    因为rest参数是 ES6 新标准,所以需要先测试一下浏览器是否支持:用rest参数编写一个sum()函数,接收任意个参数并返回它们的和:

    function sum(...rest) {
       ???
    }
    
    // 测试:
    var i, args = [];
    for (i=1; i<=100; i++) {
        args.push(i);
    }
    if (sum() !== 0) {
        console.log('测试失败: sum() = ' + sum());
    } else if (sum(1) !== 1) {
        console.log('测试失败: sum(1) = ' + sum(1));
    } else if (sum(2, 3) !== 5) {
        console.log('测试失败: sum(2, 3) = ' + sum(2, 3));
    } else if (sum.apply(null, args) !== 5050) {
        console.log('测试失败: sum(1, 2, 3, ..., 100) = ' + sum.apply(null, args));
    } else {
        console.log('测试通过!');
    }
    


1.8 对象

对象 是自包含的 数据集合,包含在对象里的数据可以通过两种形式访问—— 属性(property) 和 方法(method):

  • 属性:隶属于某个特定对象的 变量
  • 方法:只有某个特定对象才能调用的 函数

对象就是由一些属性和方法组合在一起而构成的一个 数据实体

创建对象实例需使用new关键字:

var jeremy = new Person;

1.8.1 内建对象

JavaScript 提供了一系列预先定义好的对象,这些对象称为 内建对象(native object),例如Array对象、Math对象、Date对象。

在编写 JavaScript 脚本时,内建对象可以帮助我们 快速简单 地完成许多任务。

当使用new关键字初始化一个 数组 时,其实是在创建一个Array对象的 新实例

var beatles = new Array();

Math对象的round方法可以把十进制数值舍入为一个与之最接近的整数:

var num = 7.561;
var num = Math.round(num);
alert(num);

Date对象可以用来存储和检索与特定日期和时间有关的信息。在创建新实例时,JavaScript 解释器将自动地使用当前日期时间对它进行初始化:

var current_date = new Date();

1.8.2 宿主对象

对Web应用来说,就是由浏览器这个运行环境预先定义好的其他对象。它不是 JavaScript 语言本身提供的。

我们把由浏览器提供的预定义对象称为 宿主对象(host object)。

宿主对象包括FormImageElementdocument对象等。可以通过这些对象获得网页上表单、图像和各种表单元素的信息。



2、DOM


2.1 获取元素

有 3 种 DOM 方法可获取元素节点,分别是通过 元素ID、通过 标签名字 和通过 类名 来获取。

注意,JavaScript 语言区分字母大小写。


  • getElementById

    它是document对象特有的函数getElementById方法只有一个参数。返回一个对象

    document.getElementById(id)
    

  • getElementsByTagName

    该方法返回对象数组,每个对象分别对应文档里给定标签的一个元素。只有一个参数,它的参数是标签的名字:

    element.getElementsByTagName(tag)
    

    注: 即使整个文档里这个标签只有一个元素,getElementsByTagName返回的也是一个数组,此时数组长度为1

    为改善代码的可读性:可把document.getElementsByTagName("li")赋值给一个变量。如下所示:

    var items = document.getElementsByTagName("li");
    for (var i=0; i < items.length; i++) {
    	alert(typeof items[i]);
    }
    

  • getElementByIdgetElementsByTagName综合运用。

    如果想知道id属性值为purchase的元素包含着多少个列表项,必须通过一个更具体的对象去调用这个方法,如下所示:

    var shopping = document.getElementById("purchases");
    var items = shopping.getElementsByTagName("*");
    

  • getElementsByClassName
    该方法返回值是一个数组

    HTML5 DOM(http://www.whatwg.org/specs/web-apps/current-work/)中新增一个方法:getElementsByClassName。该方法能够通过class属性中的类名来访问元素。

    document.getElementsByClassName("sale")
    

    getElementsByClassName方法只有较新的浏览器才支持,下面这个自定义函数能使之 兼容 新老浏览器:

    function getElementsByClassName(node, classname) {
    	if (node.getElementsByClassName) {
        	// 使用现有方法
        	return node.getElementsByClassName(classname);
    	} else {
        	var results = new Array();
        	var elems = node.getElementsByTagName("*");
        	for (var i=0; i<elems.length; i++) {
          		if (elems[i].className.indexOf(classname) != -1) {
            		results[results.length] = elems[i];
          		}
        	}
        	return results;
    	}
    }
    

    getElementsByClassName函数接受两个参数。第一个node表示DOM树中的搜索起点,第二个classname就是要搜索的类名了。

    如果传入节点上已经存在了适当的getElementsByClassName函数,那么这个新函数就直接返回相应的节点列表。

    如果getElementsByClassName函数不存在,这个新函数就会循环遍历所有标签,查找带有相应类名的元素(这个例子不适用于多个类名。)

    如果使用这个函数来模拟前面取得购物列表的操作,就可以这样写:

    var shopping = document.getElementById("purchases");
    var sales = getElementsByClassName(shopping, "sale");
    


小结

  1. 一份 文档 就是一棵 节点树
  2. 节点分为不同的类型:元素节点属性节点文本节点 等。
  3. getElementById将返回一个 对象,该对象对应着文档里的一个特定的元素节点。
  4. getElementsByTagNamegetElementsByClassName将返回一个 对象数组,它们分别对应着文档里的一组特定的元素节点。
  5. 每个节点 都是一个 对象


2.2 获取和设置属性

得到需要的元素以后,我们就可以设法获取它的各个属性。

  • getAttribute()方法:获取元素的各个属性;
  • setAttribute()方法:更改属性节点的值。

2.2.1 getAttribute() 方法

getAttribute方法只有 1 个参数(要查询的属性名),由于该方法 不属于document 对象,所以不能通过 document 对象调用,只能 通过元素节点对象调用

【语法】:

object.getAttribute(attribute)  // attribute:属性

【语法示例】:

var paras = document.getElementsByTagName('p');
for (var i=0; i < paras.length; i++ ) {
	alert(paras[i].getAttribute('title'));
}

当某个<p>元素没有title属性时,getAttribute("title")方法会返回null值;

如果只在title属性有值时才弹出消息,则需要增加if语句来检查getAttribute的返回值是不是null。同时我们增加几个变量以提高脚本的可读性:

var paras = document.getElementsByTagName('p');
for (var i=0; i< paras.length; i++) {
	var title_text = paras[i].getAttribute('title');
	if (title_text != null) {
		alert(title_text);
	}
}

可以进一步优化如下:

var paras = document.getElementsByTagName('p');
for (var i=0; i< paras.length; i++) {
  var title_text = paras[i].getAttribute('title');
  if (title_text) alert(title_text);
}

2.2.2 setAttribute() 方法

setAttribute() 方法允许对属性节点的值做出修改。与getAttribute一样,setAttribute也只能用于元素节点:

【语法】:

object.setAttribute(attribute,value)

【示例】:

var shopping = document.getElementById('purchases');
shopping.setAttribute('title','货物清单');

第一条语句获取到idpurchase的元素,第二条语句把该元素的title属性的值设置为货物清单 注意语法,这里的 属性 之间用英文逗号,分隔 —— 本文笔者注)。

验证title属性的值:

var shopping = document.getElementById('purchases');
console.log(shopping.getAttribute('title')); // 输出 null

shopping.setAttribute('title','货物清单');
console.log(shopping.getAttribute('title')); // 输出 “货物清单”

控制台第一次输出空白,第二次会输出“货物清单”;

上例中,设置了一个节点的title属性,这个属性原先并不存在。setAttribute实际完成了两项操作:先创建这个属性,然后设置它的值。如果setAttribute用在一个本身就有这个属性的元素节点上,那么该属性的值会被覆盖。

通过setAttribute对文档做出修改后,在通过浏览器的view source(查看源代码)选项去查看文档的源代码时看到的仍是改变前的属性值,也就是说,setAttribute做出的修改不会反映在文档本身的源代码里

这种“表里不一”的现象源自DOM的工作模式:先加载文档的静态内容,再动态刷新,动态刷新不影响文档的静态内容。这正是DOM的真正威力:对页面内容进行刷新却不需要在浏览器里刷新页面。



4. JavaScript 图片库

4.1 事件处理函数

事件处理函数的作用是,在特定事件发生时调用特定的 JavaScript 代码。

  • onmouseover:鼠标悬停某个元素上时触发动作;
  • onmouseout:鼠标离开某个元素时触发动作;
  • onclick:用户点击某个链接时触发动作。

4.2 事件处理函数的工作机制

onclick存在默认行为,某些情况下需要对其进行阻止。如a标签的默认跳转行为。

一旦事件发生,相应的 JavaScript 代码就会得到执行并返回一个值,该值被传递给事件处理函数。

例如,我们给某个链接添加一个onclick事件处理函数,并让处理函数所触发的JavaScript代码返回布尔值truefalse。当链接被点击时,如果返回的值是trueonclick事件处理函数就认为“这个链接被点击了”,反之亦然。

可通过下面这个简单测试去验证这一结论:

<a href="http://blog.driverold.com" onclick="return false;">点我</a>

当点击链接时,因为onclick事件处理函数所触发的JavaScript代码返回给它的值是false,所以这个链接的默认行为没有被触发。

同样道理,如果像下面这样,在onclick事件处理函数所触发的 JavaScript 代码里增加一条return false语句,也可以防止用户被带到目标链接窗口:

<li>
    <a href="images/fireworks.jpg" onclick="showPic(this);
➥ return false;" title="烟花表演"> 烟花 </a>
</li>

4.3 向后兼容

4.3.1 对象检测

检测浏览器对JavaScript的支持程度:

只要把某个方法打包在一个if语句里,就可以根据这条if语句的条件表达式的求值结果是true(方法存在)还是false(方法不存在)来决定应该采取怎样的行动。这种检测称为 对象检测(object detection)

注: 在使用对象检测时,一定要删掉方法名后面的圆括号,如果不删掉,测试的将是方法的结果,无论方法是否存在。

【示例】:

function myFunction() {
	if (document.getElementById) {
		statements using getElementById
	}
}

因此,如果某个浏览器不支持getElementById()方法,它就不会执行使用此方法的语句。

但这样写会有一个问题,如果要对多个DOM方法、属性进行判断,那么就会嵌套无数的花括号,可以采用逻辑非!)操作符和逻辑或(||)操作符简化:

if (!document.getElementById || !document.getElementsByTagName) return false;

【示例】

window.onload = function() {
	if (!document.getElementsByTagName) return false; // 退出函数并返回false
	var lnks = document.getElementsByTagName("a");
	
	for (var i=0; i<lnks.length; i++) {
		if (lnks[i].getAttribute("class") == "popup") {
			lnks[i].onclick = function() {
				popUp(this.getAttribute("href"));
				return false;
			}
		}
	}
}

这样,可以确保那些 “古老的” 浏览器不会因为脚本代码而出问题。也是为了让脚本有良好的向后兼容性。


4.3.2 浏览器嗅探

浏览器嗅探 指通过提取浏览器供应商提供的信息来解决向后兼容问题。

从理论上讲,可以通过JavaScript代码检索关于浏览器品牌和版本的信息,但这是一种风险非常大的技术。

  • 因为历史原因,某些浏览器会把自己报告为另外一种浏览器;
  • 浏览器嗅探脚本会变得越来越复杂
    • 如果想让浏览器嗅探脚本能够跨平台工作,就必须测试所有可能出现的供应商和版本号组合。这是一个无穷尽的任务;
    • 测试的组合情况越多,代码就越复杂和冗长。
  • 为做到浏览器版本的精确匹配,每当市场上出现新版本时,就不得不修改这些脚本。

4.4 性能考虑

不要忽视脚本对 Web 应用整体性能的影响。为保证应用流畅地运行,在为文档编写和应用脚本时,需要注意一些问题:

  • 尽量少访问DOM和尽量减少标记;
  • 合并和放置脚本;
  • 压缩脚本

4.4.1 尽量少访问DOM和尽量减少标记

访问 DOM 的方式对脚本性能会产生非常大的影响。如下所示:

if (document.getElementsByTagName("a").length > 0) {
	var links = document.getElementsByTagName("a");
	for (var i=0; i<links.length; i++) {
		// 对每个链接做处理
	}
}

不管什么时候,只要是查询 DOM 中的某些元素,浏览器都会搜索整个DOM 树,从中查找可能匹配的元素。

上列中,这段代码使用了两次getElementsByTagName方法去执行相同的操作,浪费了一次搜索。

更好的办法是把第一次搜索的结果保存在一个变量中,然后在循环里重用该结果,比如:

var links = document.getElementsByTagName("a");
if (links.length > 0) {
	for (var i=0; i<links.length; i++) {
		// 对每个链接做点处理
	}
}

这样,代码功能没有变,但搜索 DOM 的次数由两次降低到了一次

另外,尽量减少文档中的(HTM结构)标记数量。过多不必要的元素只会增加 DOM 树的规模,进而增加遍历 DOM 树以查找特定元素的时间。


4.4.2 合并和放置脚本

推荐的做法是外部脚本文件,即通过<script>元素引入,如下所示:

<script src="script/function.js"></script>

  • 合并

    避免类似下面这种情况:

    <script src="script/functionA.js"></script>
    <script src="script/functionB.js"></script>
    <script src="script/functionC.js"></script>
    <script src="script/functionD.js"></script>
    

    正确的做法是把functionA.jsfunctionB.jsfunctionC.jsfunctionD.js合并到一个脚本文件中,以减少加载页面时发送的请求数量。

  • 放置脚本

    把脚本放在文档的<head>区域。这是传统做法,但是位于<head>块中的脚本会导致浏览器 无法并行加载 其他文件(如图像或其他脚本);

    根据 HTTP 规范,浏览器每次从同一个域名最多只能同时下载2个文件。

    而在下载脚本期间,浏览器不会下载其他任何文件,即使是来自不同域名的文件也不会下载,所有其他资源都要等脚本加载完毕后才能下载。

    把所有<script>标签都放到文档的末尾,</body>标记之前,就可以让页面变得更快。这样,即使在加载脚本,window对象的load事件依然可以执行对文档进行的各种操作。


4.4.3 压缩脚本

压缩脚本,指的是把脚本文件中不必要的字节(如空格和注释),统统 删除,从而达到 “ 压缩 ” 文件的目的。

多数情况下,你应该有两个版本,一个是工作副本,可以修改代码并添加注释;另一个是精简副本,用于放在站点上。通常,为了与非精简版本区分开,最好在精简副本的文件名中加上min字样:

<script src="scripts/scriptName.min.js"></script>

压缩脚本文件,可以 加快加载速度


这可以借助工具来替你完成这件事。


推荐的代码压缩工具

Douglas Crockford 的JSMin(http://www.crockford.com/javascript/jsmin.html);

雅虎的YUI Compressor(http://developer.yahoo.com/yui/compressor);

谷歌的Closure Compiler(http://closure-compiler.appspot.com/home)。



5. 动态创建标记


5.1 传统方法

5.1.1 document.write()方法

document 对象的write()方法可以方便快捷地把字符串插入到文档里。

缺点: 违背了 “行为应与表现分离” 的原则。即使把 document.write 语句挪到外部函数里,也还是需要在HTML标记的<body>部分使用<script>标签才能调用那个函数。

function insertParagraph(text) {
	var str = "<p>";
	str += text;
	str += "</p>";
	document.write(str);
}

把这个函数保存在外部文件example.js里。为了调用该函数,必须在标记里插入<script>标签:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>Test</title>
	<script src="example.js">
	</script>
</head>
<body>  
	<script>
		insertParagraph("This is inserted.");
	</script>
</body>
</html>

像上面这样的例子,这样的标记既不容易阅读和编辑,还很容易导致验证错误。比如<script>标签后面的“<p>”很容易被误认为是<p>标签。

只要有可能,就应该用外部CSS文件代替<font>标签去设定和管理网页的样式信息,最好用外部 JavaScript 文件去控制网页的行为。应避免在<body>部分乱用<script>标签,避免使用document.write方法。


5.1.2 innerHTML属性

如今的浏览器几乎都支持属性innerHTML,该属性并非 W3C DOM 标准的组成部分,但现已经包含到 HTML5 规范中。

它可用来读、写 给定元素 里的 HTML 内容。

©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:上身试试 返回首页