第十章 变量、作用域及内存
JavaScript的变量与其他语言的变量有很大区别。JavaScript变量是松散型的(不强制类型),决定了它只是在特定时间用于保存特定值的一个名字而已。由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变。
10.1 变量及作用域
(1) 值类型和引用类型
JavaScript的变量包括两种类型,即值类型和引用类型。
值类型:值类型指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。
引用类型:引用类型值则是指那些保存在堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
将一个值赋给变量时,解析器必须确定这个值是值类型,还是引用类型值。值类型有以下几种:Undefined、Null、Boolean、Number和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们是通过按值访问的。(在某些语言中,字符串以对象的形式来表示,因此被认为是引用类型。JavaScript放弃这一传统。)
如果赋值的是引用类型的值,则必须在堆内存中为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址大小是固定的,因此可以将内存地址保存在栈内存中。这样,当查询引用类型的变量时,先从栈中读取内存地址,然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。
(2) 动态属性
定义基本类型值和引用类型值的方式是相似的:创建一个变量并为该变量赋值。但是,当这个值保存到变量中以后,对不同类型值可以执行的操作则大相径庭。
<script>
//定义一个对象,即引用类型
var car = new Object();
car.brand = "Mercedes Benz";
alert(car.brand);
//定义一个字符串变量,即值类型
var cellPhone = "iphone6";
cellPhone.color = "Gold";
alert(cellPhone.color);
</script>
(3) 变量复制
在变量复制方面,值类型和引用类型也有所不同。值类型复制的是值本身,而引用类型复制的是地址。
//值类型复制
var car1 = "Mercedes Benz"; //在栈内存生成一个car1的变量,保存的值为 'Mercedes Benz'
var car2 = car1; //在栈内存再生成一个car2的变量,保存的值也是 'Mercedes Benz'
car2是虽然是car1的一个副本,但从图示可以看出,它是完全独立的。也就是说,两个变量分别操作时互不影响。
//引用类型复制
var car1 = new Object();
car.brand = "Mercedes Benz";
var car2 = car1;
car2.brand = "BMW";
alert(car1.brand);
引用类型中,car2其实就是car1,因为他们指向的是同一个对象。如果这个对象中的name属性被修改了,car2.name和car1.name输出的值都会被相应修改掉了。
(4) 传递参数
JavaScript中所有函数的参数都是按值传递的,言下之意就是说,参数不会按引用传递,虽然变量有基本类型和引用类型之分。
<script>
function sum(num){ //按值传递,传递的参数是基本类型
num += 10; //这里的num是局部变量
return num;
}
var num = 10;
var result = sum(num);
alert(result);
alert(num);
</script>
以上的代码中,传递的参数是一个值类型。而函数里的num是一个局部变量,和外面的num没有任何联系。
<script>
function setName(obj){ //按值传递,传递的参数是引用类型
obj.name = "马云";
var obj = new Object();
obj.name = "刘强东";
}
var obj = new Object();
setName(obj);
alert(obj.name); //马云
</script>
(5) 检测类型
要检测一个变量的类型,我们可以通过typeof运算符来判别。诸如:
<script>
var name = "Johnny";
alert(typeof name);
</script>
虽然typeof运算符在检查值类型的数据的时候非常好用,但检测引用类型的时候,它就不是那么好用了。通常,我们并不想知道它是不是对象,而是想知道它到底是什么类型的对象。因为数组也是object,null也是Object等等。对于引用类型的检测,当需要知道这个引用类型具体是什么对象的时候,应该使用instanceof运算符。
<script>
var obj1 = [1,2,3];
alert(obj1 instanceof Array); //是否是数组
var obj2 = {};
alert(obj2 instanceof Object); //是否是对象
var obj3 = new String('Lee');
alert(obj3 instanceof String); //是否是字符串对象
</script>
注意:当使用instanceof检查基本类型的值时,它会返回false。
(6) 执行环境及作用域
执行环境是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
(1) 全局执行环境(全局作用域)
全局执行环境是最外围的执行环境。在Web浏览器中,全局执行环境被认为是window对象。因此所有的全局变量和函数都是作为window对象的属性和方法创建的。全局的变量和函数,都是window对象的属性和方法。
var color = "blue";
function showColor(){
alert(color);
}
showColor();
var color = "blue";
function showColor(){
alert(window.color);
}
window.showColor();
当执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。如果是全局环境下,需要程序执行完毕,或者网页被关闭才会销毁。
(2) 局部执行环境(局部作用域)
函数里的局部作用域里的变量替换全局变量,但作用域仅限在函数体内这个局部环境。
<script>
var color = "blue"; //全局变量
function setColor(color){
color = color; //局部变量
alert(color);
}
setColor("red");
alert(color);
</script>
(7) 没有块及作用域
块级作用域表示诸如if语句等有花括号封闭的代码块,所以,支持条件判断来定义变量。
<script>
if (true) { //if语句代码块没有局部作用域
var color = 'red';
}
alert(color);
for (var i = 0; i < 10; i ++){ //没有局部作用域
var fullName = 'Johnny';
}
alert(i);
alert(fullName);
</script>
<script>
var color = "red";
function sum(num1, num2){
var num = num1 + num2;
return num;
}
sum(1, 2);
alert(num); //报错 num is not defined
</script>
注意:定义及初始化变量的时候一定要加上var。
10.2 内存管理
JavaScript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。其他语言比如C和C++,必须手工跟踪内存使用情况,适时的释放,否则会造成很多问题。而JavaScript则不需要这样,它会自行管理内存分配及无用内存的回收。
JavaScript最常用的垃圾收集方式是标记清除。垃圾收集器会在运行的时候给存储在内存中的变量加上标记。然后,它会去掉环境中正在使用变量的标记,而没有被去掉标记的变量将被视为准备删除的变量。最后,垃圾收集器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。
垃圾收集器是周期性运行的,这样会导致整个程序的性能问题。比如IE7以前的版本,它的垃圾收集器是根据内存分配量运行的,比如256个变量就开始运行垃圾收集器,这样,就不得不频繁地运行,从而降低的性能。
一般来说,确保占用最少的内存可以让页面获得更好的性能。那么优化内存的最佳方案,就是一旦数据不再有用,那么将其设置为null来释放引用,这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象。
第十一章 内置对象
内置对象的定义是:“由JavaScript实现提供的、不依赖宿主环境的对象,这些对象在JavaScript程序执行之前就已经存在了。”意思就是说,开发人员不必显示地实例化内置对象;因为它们已经实例化了。JavaScript只定义了两个内置对象:Global和Math。
11.1 Global对象
Global(全局)对象是JavaScript中一个特别的对象,因为这个对象是不存在的。在JavaScript中不属于任何其他对象的属性和方法,都属于它的属性和方法。所以,事实上,并不存在全局变量和全局函数;所有在全局作用域定义的变量和函数,都是Global对象的属性和方法。
因为JavaScript没有定义怎么调用Global对象,所以,Global.属性或者Global.方法()都是无效的。(Web浏览器将Global作为window对象的一部分加以实现)
Global对象有一些内置的属性和方法:
(1) URI编码方法
URI编码可以对链接进行编码,以便发送给浏览器。它们采用特殊的UTF-8编码替换所有无效字符,从而让浏览器能够接受和理解。
encodeURI()不会对本身属于URI的特殊字符进行编码,例如冒号、正斜杠、问号和#号;而encodeURIComponent()则会对它发现的任何非标准字符进行编码。
<script>
var parm = "//Johnny 张";
alert(encodeURI(parm)); //Johnny%20%E5%BC%A0
alert(encodeURIComponent(parm)); //%2F%2FJohnny%20%E5%BC%A0
</script>
因为encodeURIComponent()编码比encodeURI()编码来的更加彻底,一般来说encodeURIComponent()使用频率要高一些。
使用了URI编码过后,还可以进行解码,通过decodeURI()和decodeURIComponent()来进行解码。
<script>
var parm = "//Johnny 张";
var encode1 = encodeURI(parm);
var encode2 = encodeURIComponent(parm);
var decode1 = decodeURI(encode1);
var decode2 = decodeURIComponent(encode2);
alert(encode1); //Johnny%20%E5%BC%A0
alert(encode2); //%2F%2FJohnny%20%E5%BC%A0
alert(decode1);
alert(decode1);
</script>
(2) eval()方法
eval()方法主要担当一个字符串解析器的作用,他只接受一个参数,而这个参数就是要执行的JavaScript代码的字符串。
<script>
eval('var num = 100'); //解析了字符串代码
alert(num);
eval('alert(100)'); //同上
eval('function num() {return 123}'); //函数也可以
alert(num());
</script>
eval()方法的功能非常强大,但也非常危险。因此使用的时候必须极为谨慎。特别是在用户输入数据的情况下,非常有可能导致程序的安全性,比如代码注入等等。
(3) Global对象属性
Global对象包含了一些属性:undefined、NaN、Object、Array、Function等等。
(4) window对象
Global没有办法直接访问,而Web浏览器可以使用window对象来实现全局访问。
11.2 Math对象
JavaScript还为保存数学公式和信息提供了一个对象,即Math对象。与我们在JavaScript直接编写计算功能相比,Math对象提供的计算功能执行起来要快得多。
(1) Math对象的属性
Math对象包含的属性大多是数学计算中可能会用到的一些特殊值。
属性 | 说明 |
---|---|
Math.E | 自然对数的底数,即常量e的值 |
Math.LN10 | 10的自然对数 |
Math.LN2 | 2的自然对数 |
Math.LOG2E | 以2为底e的对数 |
Math.LOG10E | 以10为底e的对数 |
Math.PI | PI的值 |
Math.SQRT1_2 | 1/2的平方根 |
Math.SQRT2 | 2的平方根 |
(2) min()和max()方法
Math.min()用于确定一组数值中的最小值。Math.max()用于确定一组数值中的最大值。
(3) 舍入方法
Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数。
Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数。
Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数。
<script>
alert(Math.ceil(25.9)); //26
alert(Math.ceil(25.5)); //26
alert(Math.ceil(25.1)); //26
alert(Math.floor(25.9)); //25
alert(Math.floor(25.5)); //25
alert(Math.floor(25.1)); //25
alert(Math.round(25.9)); //26
alert(Math.round(25.5)); //26
alert(Math.round(25.1)); //25
</script>
(4) random()方法
Math.random()方法返回介于0到1之间一个随机数,不包括0和1。如果想大于这个范围的话,可以套用一下公式:
值 = Math.floor(Math.random() * 总数 + 第一个值)
<script>
alert(Math.floor(Math.random() * 10 + 1)); //随机产生1-10之间的任意数
</script>
(5) 其他方法
方法 | 说明 |
---|---|
Math.abs(num) | 返回num的绝对值 |
Math.exp(num) | 返回Math.E的num次幂 |
Math.log(num) | 返回num的自然对数 |
Math.pow(num,power) | 返回num的power次幂 |
Math.sqrt(num) | 返回num的平方根 |
Math.acos(x) | 返回x的反余弦值 |
Math.asin(x) | 返回x的反正弦值 |
Math.atan(x) | 返回x的反正切值 |
Math.atan2(y,x) | 返回y/x的反正切值 |
Math.cos(x) | 返回x的余弦值 |
Math.sin(x) | 返回x的正弦值 |
Math.tan(x) | 返回x的正切值 |
第十二章 面向对象与原型
12.1 创建对象
12.1.1 new Object()
创建Object对象,然后给这个对象新建属性和方法。
<script>
var car = new Object(); //创建一个Object对象
car.brand = "Mercedes-Benz"; //创建一个brand属性并赋值
car.model = "E260L";
car.color = "White";
car.drive = function(){ //创建一个drive()方法并返回值
return this.brand + " " + this.model + " " + this.color + " is driving.";
};
alert(car.drive());
</script>
存在的问题:
这个创建了一个“Mercedes-Benz”的对象,如果需要在创建一个“BMW”的对象,则类似的代码还要在写一遍,如果需要再创建其他品牌车的对象,那么类似的代码就要写许多份。
解决办法:
使用工厂模式创建对象。
12.1.2 工厂模式
为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复代码的问题。
<script>
function createCarObject(brand, model, color){ //集中实例化的函数
var car = new Object();
car.brand = brand;
car.model = model;
car.color = color;
car.drive = function(){
return this.brand + " " + this.model + " " + this.color + " is driving.";
};
return car;
}
var car1 = createCarObject("Mercedes-Benz", "E260L", "white");
var car2 = createCarObject("Mercedes-Benz", "GLC260", "white");
var car3 = createCarObject("BMW", "X5", "Red");
alert(car1.drive());
alert(car2.drive());
alert(car3.drive());
</script>
存在的问题:
工厂模式解决了实例化代码重复的问题,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们到底是哪个对象的实例。
<script>
function createCarObject(brand, model, color){ //集中实例化的函数
var car = new Object();
car.brand = brand;
car.model = model;
car.color = color;
car.drive = function(){
return this.brand + " " + this.model + " " + this.color + " is driving.";
};
return car;
}
var car1 = createCarObject("Mercedes-Benz", "E260L", "white");
var car2 = createCarObject("Mercedes-Benz", "GLC260", "white");
var car3 = createCarObject("BMW", "X5", "Red");
alert(typeof car1);
alert(car1 instanceof Object);
alert(typeof car2);
alert(car2 instanceof Object);
alert(typeof car3);
alert(car3 instanceof Object);
</script>
解决办法:
采用构造函数可用来创建特定的对象
12.1.3 构造函数
使用构造函数的方法,即解决了实例化代码重复的问题,又解决了对象识别的问题。
<script>
function Car(brand, model, color){ //构造函数模式
this.brand = brand;
this.model = model;
this.color = color;
this.drive = function(){
return this.brand + " " + this.model + " " + this.color + " is driving.";
};
}
var car1 = new Car("Mercedes-Benz", "E260L", "white");
alert(car1.drive());
alert(typeof car1);
alert(car1 instanceof Car);
var car2 = new Car("Mercedes-Benz", "GLC260", "white");
alert(car2.drive());
alert(typeof car2);
alert(car2 instanceof Car);
var car3 = new Car("BMW", "X5", "Red");
alert(car3.drive());
alert(typeof car3);
alert(car3 instanceof Car);
</script>
使用了构造函数的方法,和使用工厂模式的方法的不同之处如下:
(1) 构造函数方法没有显示的创建对象(new Object());
(2) 直接将属性和方法赋值给this对象;
(3) 没有return语句。
构造函数的方法有一些规范:
(1) 函数名和实例化构造名相同且大写;
(2) 通过构造函数创建对象,必须使用new运算符。
构造函数和普通函数的唯一区别,就是他们调用方式不同。只不过,构造函数也是函数,必须用new运算符来调用,否则就是普通函数。
<script>
function Car(brand, model, color){ //集中实例化的函数
this.brand = brand;
this.model = model;
this.color = color;
this.drive = function(){
return this.brand + " " + this.model + " " + this.color + " is driving.";
};
}
function sayHello(fullName){
return "Hello " + fullName;
}
var car1 = new Car("Mercedes-Benz", "E260L", "white");
alert(car1.drive());
alert(sayHello("Johnny"));
</script>
探讨构造函数内部的方法(或函数)的问题。
<script>
function Car(brand, model, color){ //集中实例化的函数
this.brand = brand;
this.model = model;
this.color = color;
this.drive = function(){
return this.brand + " " + this.model + " " + this.color + " is driving.";
};
}
var car1 = new Car("Mercedes-Benz", "E260L", "white");
var car2 = new Car("Mercedes-Benz", "GLC260", "white");
alert(car1.brand === car2.brand); //true, 类型内容均相等
alert(car1.model === car2.model); //false, 类型相等内容不等
alert(car1.color === car2.color); //true, 类型内容均相等
alert(car1.drive === car2.drive); //false, 引用地址不相等
</script>
12.2 原型
我们创建的每个函数都有一个原型属性(prototype),这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
12.2.1 使用构造函数创建原型对象
<script>
//使用声明构造函数的方式,在原型中添加属性和方法
function SaloonCar(){}
SaloonCar.prototype.carType = "家用型轿车";
SaloonCar.prototype.drive = function(){
return "Saloon car is driving.";
};
//在构造函数中添加属性和方法
function Suv(brand, model){
this.brand = brand;
this.model = model;
this.drive = function(){
return this.brand + " " + this.model + " is driving.";
};
}
var bora = new SaloonCar();
var mazda = new SaloonCar();
alert(bora.drive === mazda.drive); //true
var bmwX5 = new Suv("BMW", "X5");
var benzGlc = new Suv("Benz", "GLC260");
alert(bmwX5.drive === benzGlc.drive); //false
</script>
在原型模式声明中,多了一个proto属性,这个属性都是创建对象时自动生成的。proto属性是实例指向原型对象的指针,它的作用就是指向构造函数的原型属性constructor。通过这个属性,就可以访问到原型里的属性和方法了。
说明:IE浏览器在脚本访问proto会不能识别,火狐和谷歌浏览器及其他某些浏览器均能识别。虽然可以输出,但无法获取内部信息。
原型模式的执行流程:
Step1:先查找构造函数实例里的属性或方法,如果有,立刻返回;
Step2:如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
通过原型添加的值类型的属性,其值不能被重写:
<script>
//使用声明构造函数的方式,在原型中添加属性和方法
function SaloonCar(){}
SaloonCar.prototype.carType = "家用型轿车";
SaloonCar.prototype.drive = function(){
return "Saloon car is driving.";
};
var bora = new SaloonCar();
bora.carType = "商务用车";
alert(bora.carType);
var mazda = new SaloonCar();
alert(mazda.carType);
</script>
内置方法和操作符:
isPrototypeOf()
判断一个对象是否指向了该构造函数的原型对象。
<script>
//使用声明构造函数的方式,在原型中添加属性和方法
function SaloonCar(){}
SaloonCar.prototype.carType = "家用型轿车";
SaloonCar.prototype.drive = function(){
return "Saloon car is driving.";
};
var bora = new SaloonCar();
alert(SaloonCar.prototype.isPrototypeOf(bora));
</script>
hasOwnProperty()
判断一个属性是构造函数中的属性而非原型中的属性。
<script>
//使用声明构造函数的方式,在原型中添加属性和方法
function SaloonCar(){}
SaloonCar.prototype.carType = "家用型轿车";
SaloonCar.prototype.drive = function(){
return "Saloon car is driving.";
};
//在构造函数中添加属性和方法
function Suv(brand, model){
this.brand = brand;
this.model = model;
this.drive = function(){
return this.brand + " " + this.model + " is driving.";
};
}
var bora = new SaloonCar();
alert(bora.hasOwnProperty("carType")); //false
var benzGlc = new Suv("Benz", "GLC260");
alert(benzGlc.hasOwnProperty("brand")); //true
</script>
in
判断指定属性是否在对象中,无论是构造函数中定义的还是原型定义的属性。
<script>
//使用声明构造函数的方式,在原型中添加属性和方法
function SaloonCar(){}
SaloonCar.prototype.carType = "家用型轿车";
SaloonCar.prototype.drive = function(){
return "Saloon car is driving.";
};
//在构造函数中添加属性和方法
function Suv(brand, model){
this.brand = brand;
this.model = model;
this.drive = function(){
return this.brand + " " + this.model + " is driving.";
};
}
var bora = new SaloonCar();
alert("carType" in bora);//true
var benzGlc = new Suv("Benz", "GLC260");
alert("brand" in benzGlc);//true
</script>
12.2.2 使用字面量创建原型对象
为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式。使用构造函数创建原型对象和使用字面量创建原型对象在使用上基本相同,但还是有一些区别,字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反。
<script>
//使用字面量方式声明原型属性和方法
function SaloonCar(){}
SaloonCar.prototype = {
carType: "家用型轿车",
drive: function(){
return "Saloon car is driving.";
}
};
var bora = new SaloonCar();
alert(bora instanceof SaloonCar);
alert(bora instanceof Object);
alert(bora.constructor === SaloonCar); //字面量方式,返回false,否则,true
alert(bora.constructor === Object); //字面量方式,返回true,否则,false
</script>
如果想让字面量方式的constructor指向实例对象,那么可以这么做:
SaloonCar.prototype = {
constructor: SaloonCar,
carType: "家用型轿车",
drive: function(){
return "Saloon car is driving.";
}
};
说明:使用字面量方式创建的原型对象为什么constructor会指向Object?那是因为SaloonCar.prototype={};这种写法其实就是创建了一个新对象。而每创建一个对象,就会同时创建它prototype属性,这个对象也会自动获取constructor属性。所以,新对象的constructor重写了SaloonCar原来的constructor,因此会指向新对象,那个新对象没有指定构造函数,那么就默认为Object。
原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型,所以不要重复声明原型。
<script>
//使用字面量方式声明原型属性和方法
function SaloonCar(){}
SaloonCar.prototype = {
constructor: SaloonCar,
carType: "家用型轿车",
drive: function(){
return "Saloon car is driving.";
}
};
SaloonCar.prototype = {
color: "White"
};
var bora = new SaloonCar();
alert(bora.drive());//报错
</script>
原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含值类型的属性也还可以。但如果属性包含引用类型,就存在一定的问题:
<script>
function Student(){};
Student.prototype = {
constructor: Student,
family: ["父亲", "母亲"],
showFamily: function(){
return this.family;
}
};
var s1 = new Student();
s1.family.push("哥哥");
alert(s1.showFamily()); //父亲,母亲,哥哥
var s2 = new Student();
alert(s2.showFamily()); //父亲,母亲,哥哥
</script>
为了解决构造传参和共享问题,可以使用构造函数+原型模式:
<script>
function Student(fullName, age) { //不共享的使用构造函数
this.fullName = fullName;
this.age = age;
this.family = ['父亲', '母亲'];
};
Student.prototype = { //共享的使用原型模式
constructor : Student,
showFamily : function () {
return this.fullName + " " + this.age + "岁,家庭成员:" + this.family;
}
};
var s1 = new Student("佟大为", 32);
s1.family.push("哥哥");
alert(s1.showFamily()); //父亲,母亲, 哥哥
var s2 = new Student("马伊琍", 20);
alert(s2.showFamily()); //父亲,母亲
var s3 = new Student("赵丽颖", 19);
s3.family.push("姐姐");
alert(s3.showFamily()); //父亲,母亲, 姐姐
</script>
说明:这种构造函数+原型的模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。
原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在声明一个对象时,构造函数+原型部分让人感觉又很怪异,最好就是把构造函数和原型封装到一起。为了解决这个问题,我们可以使用动态原型模式(实际项目中使用的模式)。
<script>
function Student(fullName, age) { //不共享的使用构造函数
this.fullName = fullName;
this.age = age;
this.family = ['父亲', '母亲'];
if(typeof this.showFamily != "function"){
Student.prototype.showFamily = function(){ //共享的使用原型模式
return this.fullName + " " + this.age + "岁,家庭成员:" + this.family;
};
}
};
var s1 = new Student("佟大为", 32);
s1.family.push("哥哥");
alert(s1.showFamily()); //父亲,母亲, 哥哥
var s2 = new Student("马伊琍", 20);
alert(s2.showFamily()); //父亲,母亲
var s3 = new Student("赵丽颖", 19);
s3.family.push("姐姐");
alert(s3.showFamily()); //父亲,母亲, 姐姐
</script>
说明:
- 当第一次调用构造函数时,showFamily()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样及得到了封装,又实现了原型方法共享,并且属性都保持独立。
- 不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系。
12.2.3 继承
继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而JavaScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。
<script>
function Product(){
this.color = "White";
}
function Cellphone(){
this.name = "iphone"
}
Cellphone.prototype = new Product();
var cellphone = new Cellphone();
alert(cellphone.name);
alert(cellphone.color);
</script>
在JavaScript里,被继承的函数称为超类型(其他语言成为父类),继承的函数称为子类型(其他语言成为派生类)。继承也有之前问题,比如字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
为了解决引用共享和超类型无法传参的问题,我们可以采用对象冒充(伪造对象、经典继承)的方法来解决这两种问题。
<script>
function Product(name){
this.manufacturers = ["Apple", "Sumsung"];
this.name = name;
}
function Cellphone(name, brand, model){
Product.call(this, name)
this.brand = brand;
this.model = model;
}
var product1 = new Cellphone("cellphone", "letv", "1Pro");
product1.manufacturers.push("letv");
alert(product1.name + ": " + product1.brand + product1.model + "\n" + product1.manufacturers);
var product2 = new Cellphone("cellphone", "meizu", "魅蓝3");
product2.manufacturers.push("meizu");
alert(product2.name + ": " + product2.brand + product2.model + "\n" + product2.manufacturers);
</script>
借用构造函数虽然解决了刚才两种问题,但没有原型,复用则无从谈起。所以,我们需要原型链+借用构造函数的模式,这种模式成为组合继承。
<script>
function Product(name){
this.manufacturers = ["Apple", "Sumsung"];
this.name = name;
if(typeof this.show != "function"){
Product.prototype.show = function(){//方法放到原型中,使其每次实例化时方法地址保持一致
alert("The product is:" + this.name);
}
}
}
function Cellphone(name, brand, model){
Product.call(this, name)
this.brand = brand;
this.model = model;
}
function Pad(name, brand, model){
Product.call(this, name);
this.brand = brand;
this.model = model;
}
Cellphone.prototype = new Product();
Pad.prototype = new Product();
var product1 = new Cellphone("cellphone", "iphone", "6puls");
product1.show();
var product2 = new Pad("pad", "ipad", "air");
product2.show();
alert(product1.show === product2.show);//true
</script>
组合式继承是JavaScript最常用的继承模式。
第十三章 匿名函数和闭包
13.1 匿名函数
匿名函数就是没有名字的函数。
(1)普通函数(有名字的函数)
function showCarInfo(car){
var carInfo = "Car brand:" + car.brand + "\n" +
"Car model:" + car.model + "\n" +
"Car color:" + car.color;
alert(carInfo);
}
(2)匿名函数(没有名字的函数)
function(){
alert("hello.");
}
如何执行匿名函数:
方法1:通过表达式自我执行
(function(){
alert("hello.");
})();
方法2:把匿名函数赋值给变量
var show = function(){
alert("hello.");
};
show();
方法3:函数里的匿名函数
function show(){
return function(){
return "hello.";
}
}
alert(show()());
实际项目中使用场景:
场景一、回掉函数
场景二、闭包
13.2 闭包
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。
使用闭包有一个优点,也是它的缺点:就是可以把局部变量驻留在内存中,可以避免使用全局变量。(全局变量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难,所以推荐使用私有的,封装的局部变量)。
function show(){
var num = 0;
return function(){
num++;
return num;
}
}
var s = show();
alert(s()); //1
alert(s()); //2
alert(s()); //3
说明:由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。
第十四章 BOM操作
BOM(Browser Object Model)也叫浏览器对象模型,它提供了很多对象,用于访问浏览器的功能。BOM缺少规范,每个浏览器提供商又按照自己想法去扩展它,那么浏览器共有对象就成了事实的标准。所以,BOM本身是没有标准的或者还没有哪个组织去标准它。
14.1 window对象
BOM的核心对象是window,它表示浏览器的一个实例。window对象处于JavaScript结构的最顶层,对于每个打开的窗口,系统都会自动为其定义 window 对象。
(1) window对象的属性
属性 | 含义 |
---|---|
closed | 当窗口关闭时为真 |
defaultStatus | 窗口底部状态栏显示的默认状态消息 |
document | 窗口中当前显示的文档对象 |
frames | 窗口中的框架对象数组 |
history | 保存有窗口最近加载的URL |
length | 窗口中的框架数 |
location | 当前窗口的URL |
name | 窗口名 |
offscreenBuffering | 用于绘制新窗口内容并在完成后复制已存在的内容,控制屏幕更新 |
opener | 打开当前窗口的窗口 |
parent | 指向包含另一个窗口的窗口(由框架使用) |
screen | 显示屏幕相关信息,如高度、宽度(以像素为单位) |
self | 指示当前窗口。 |
status | 描述由用户交互导致的状态栏的临时消息 |
top | 包含特定窗口的最顶层窗口(由框架使用) |
window | 指示当前窗口,与self等效 |
(2) window对象的方法
方法 | 功能 |
---|---|
alert(text) | 创建一个警告对话框,显示一条信息 |
blur() | 将焦点从窗口移除 |
clearInterval(interval) | 清除之前设置的定时器间隔 |
clearTimeOut(timer) | 清除之前设置的超时 |
close() | 关闭窗口 |
confirm() | 创建一个需要用户确认的对话框 |
focus() | 将焦点移至窗口 |
open(url,name,[options]) | 打开一个新窗口并返回新window对象 |
prompt(text,defaultInput) | 创建一个对话框要求用户输入信息 |
scroll(x,y) | 在窗口中滚动到一个像素点的位置 |
setInterval(expression,milliseconds) | 经过指定时间间隔计算一个表达式 |
setInterval(function,millisenconds,[arguments]) | 经过指定时间间隔后调用一个函数 |
setTimeout(expression,milliseconds) | 在定时器超过后计算一个表达式 |
setTimeout(expression,milliseconds,[arguments]) | 在定时器超过时后计算一个函数 |
print() | 调出打印对话框 |
find() | 调出查找对话框 |
window下的属性和方法,可以使用window.属性、window.方法()或者直接属性、方法()的方式调用。例如:window.alert()和alert()是一样的。
14.2 location对象
location是BOM对象之一,它提供了与当前窗口中加载的文档有关的信息,还提供了一些导航功能。事实上,location对象是window对象的属性,也是document对象的属性;所以window.location和document.location等效。
(1) location对象的属性
属性 | 描述的URL内容 |
---|---|
hash | 如果该部分存在,表示锚点部分 |
host | 主机名:端口号 |
hostname | 主机名 |
href | 整个URL |
pathname | 路径名 |
port | 端口号 |
protocol | 协议部分 |
search | 查询字符串 |
(2) location对象的方法
方法 | 功能 |
---|---|
assign() | 跳转到指定页面,与href等效 |
reload() | 重载当前URL |
repalce() | 用新的URL替换当前页面 |
14.3 history对象
history对象是window对象的属性,它保存着用户上网的记录,从窗口被打开的那一刻算起。
(1) history对象的属性
属性 | 描述的URL内容 |
---|---|
length | history对象中的记录数 |
(2) history对象的方法
方法 | 功能 |
---|---|
back() | 前往浏览器历史条目前一个URL,类似后退 |
forward() | 前往浏览器历史条目下一个URL,类似前进 |
go(num) | 浏览器在history对象中向前或向后 |
第十五章 DOM操作
DOM(Document Object Model)即文档对象模型,针对HTML和XML文档的API(应用程序接口)。DOM描绘了一个层次化的节点树,运行开发人员添加、移除和修改页面的某一部分。
15.1 DOM介绍
(1) 节点
加载HTML页面时,Web浏览器生成一个树型结构,用来表示页面内部结构。DOM将这种树型结构理解为由节点组成。
从上图的树型结构,我们理解几个概念,html标签没有父辈,没有兄弟,所以html标签为根标签。head标签是html子标签,meta和title标签之间是兄弟关系。如果把每个标签当作一个节点的话,那么这些节点组合成了一棵节点树(后面我们经常把标签称作为元素,是一个意思)。
(2) 节点种类
节点种类分为三种,元素节点、文本节点、属性节点。
<div title="属性节点">测试div</div>
DOM操作请参照后续jQuery课程。
第十六章 JavaScript事件
16.1 事件介绍
JavaScript事件是由访问Web页面的用户引起的一系列操作,例如:用户点击。当用户执行某些操作的时候,再去执行一系列代码。事件一般是用于浏览器和用户操作进行交互。最早是IE和Netscape Navigator中出现,作为分担服务器端运算负载的一种手段。直到几乎所有的浏览器都支持事件处理。而DOM2级规范开始尝试以一种复合逻辑的方式标准化DOM事件。IE9、Firefox、Opera、Safari和Chrome全都已经实现了“DOM2级事件”模块的核心部分。IE8之前浏览器仍然使用其专有事件模型。
JavaScript有三种事件模型:内联模型、脚本模型和DOM2模型。
16.2 内联模型
这种模型是传统且单一的处理事件的方法。在内联模型中,事件处理函数是HTML标签的一个属性,用于处理指定事件。虽然内联在早期使用较多,但它是和HTML混写的,并没有与HTML分离,现在已很少使用。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JavaScript事件</title>
</head>
<script>
function hello(n){
alert("hello " + n);
}
</script>
<body>
<button onclick="alert('hello.')">内联事件(嵌入内容)</button>
<button onclick="hello('Johnny')">内联事件(调用函数)</button>
</body>
</html>
16.3 脚本模型
由于内联模型违反了HTML与JavaScript代码层次分离的原则。为了解决这个问题,我们可以在JavaScript中处理事件。这种处理方式就是脚本模型。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JavaScript事件</title>
</head>
<script>
window.onload = function(){
var btn = document.getElementById("btnOnclick");
btn.onclick = function(){
alert("hello.");
};
};
</script>
<body>
<button id="btnOnclick">脚本模型</button>
</body>
</html>
16.4 事件处理函数
JavaScript可以处理的事件类型为:鼠标事件、键盘事件、HTML事件。
JavaScript事件处理函数及其使用列表
事件处理函数 | 影响的元素 | 何时发生 |
---|---|---|
onabort | 图像 | 当图像加载被中断时 |
onblur | 窗口、框架、所有表单对象 | 当焦点从对象上移开时 |
onchange | 输入框,选择框和文本区域 | 当改变一个元素的值且失去焦点时 |
onclick | 链接、按钮、表单对象、图像映射区域 | 当用户单击对象时 |
ondblclick | 链接、按钮、表单对象 | 当用户双击对象时 |
ondragdrop | 窗口 | 当用户将一个对象拖放到浏览器窗口时 |
onError | 脚本 | 当脚本中发生语法错误时 |
onfocus | 窗口、框架、所有表单对象 | 当单击鼠标或者将鼠标移动聚焦到窗口或框架时 |
onkeydown | 文档、图像、链接、表单 | 当按键被按下时 |
onkeypress | 文档、图像、链接、表单 | 当按键被按下然后松开时 |
onkeyup | 文档、图像、链接、表单 | 当按键被松开时 |
onload | 主题、框架集、图像 | 文档或图像加载后 |
onunload | 主体、框架集 | 文档或框架集卸载后 |
onmouseout | 链接 | 当图标移除链接时 |
onmouseover | 链接 | 当鼠标移到链接时 |
onmove | 窗口 | 当浏览器窗口移动时 |
onreset | 表单复位按钮 | 单击表单的reset按钮 |
onresize | 窗口 | 当选择一个表单对象时 |
onselect | 表单元素 | 当选择一个表单对象时 |
onsubmit | 表单 | 当发送表格到服务器时 |
实际项目中一般使用jQuery来实现脚本模式添加事件,详细请参照jQuery课程。
第十七章 表单处理
为了分担服务器处理表单的压力,JavaScript提供了一些解决方案,从而大大打破了处处依赖服务器的局面。
实际项目中一般使用jQuery来处理表单,详细请参照jQuery课程。
第十八章 错误处理与调试
良好的错误处理机制可以及时的提醒用户,知道发生了什么事,而不会惊慌失措。为此,作为开发人员,我们必须理解在处理JavaScript错误的时候,都有哪些手段和工具可以利用。我们可以使用try-catch语句来捕获可能会发生的错误并进行处理。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>原型</title>
</head>
<script>
window.onload = function(){
var btn = document.getElementById("btnOnclick");
btn.onclick = function(){
try{
var arr = new Array[-5];
}catch(err){
alert(err.message);
}
};
};
</script>
<body>
<input type="text" id="input-number" placeholder="请输入日期" />
<button id="btnOnclick">脚本模型</button>
</body>
</html>
善用try-catch
在明明知道某个地方会产生错误,可以通过修改代码来解决的地方,是不适合用try-catch的。或者是那种不同浏览器兼容性错误导致错误的也不太适合,因为可以通过判断浏览器或者判断这款浏览器是否存在此属性和方法来解决。
常规错误和这种浏览器兼容错误,我们都不建议使用try-catch。因为常规错误可以修改代码即可解决,浏览器兼容错误,可以通过普通if判断即可。并且try-catch比一般语句消耗资源更多,负担更大。所以,在万不得已,无法修改代码,不能通过普通判断的情况下才去使用try-catch,比如后面的Ajax技术。
第十九章 Cookie
19.1 Cookie介绍
随着Web越来越复杂,开发者急切的需要能够本地化存储的脚本功能。这个时候,第一个出现的方案:cookie诞生了。cookie的意图是,在本地的客户端的磁盘上以很小的文件形式保存数据。
cookie也叫HTTP Cookie,最初是客户端与服务器端进行会话使用的。比如,会员登录,下次回访网站时无须登录了;或者是购物车,购买的商品没有及时付款,过两天发现购物车里还有之前的商品列表。
HTTP Cookie要求服务器对任意HTTP请求发送Set-Cookie,因此,Cookie的处理原则上需要在服务器环境下进行。当然,现在大部分浏览器在客户端也能实现Cookie的生成和获取。(目前Chrome不可以在客户端操作,其他浏览器均可)
cookie的组成:
cookie由名/值(键值对)形式的文本组成:name=value。完整格式为:
name=value; [expires=date]; [path=path]; [domain=somewhere.com]; [secure]
中括号是可选,name=value是必选。
expires=date 失效时间,如果没有声明,则为浏览器关闭后即失效。声明了失效时间,那么时间到期后方能失效。
path=path 访问路径,当设置了路径,那么只有设置的那个路径文件才可以访问cookie。
domain=domain 访问域名,用于限制只有设置的域名才可以访问,那么没有设置,会默认限制为创建cookie的域名。
secure 安全设置,指明必须通过安全的通信通道来传输(HTTPS)才能获取cookie。
具体Cookie的操作一般使用jQuery来实现,请参照jQuery课程。
19.2 Cookie的局限性
Cookie虽然在持久保存客户端用户数据提供了方便,分担了服务器存储的负担。但是还有很多局限性的。
(1) 每个特定的域名下最多生成20个cookie(根据不同的浏览器有所区别)
- IE7和之后的版本最多可以50个cookie。IE7最初也只能20个,之后因被升级不定后增加了。
- Firefox最多50个cookie
- Opera最多30个cookie
- Safari和Chrome没有做硬性限制。
为了更好的兼容性,所以按照最低的要求来,也就是最多不得超过20个cookie。当超过指定的 cookie时,浏览器会清理掉早期的cookie。IE和Opera会清理近期最少使用的cookie,Firefox会随机清理cookie。
(2) Cookie的最大大约为4096字节(4k),为了更好的兼容性,一般不能超过4095字节即可。
(3) Cookie存储在客户端的文本文件,所以特别重要和敏感的数据是不建议保存在Cookie的。比如银行卡号,用户密码等。
第二十章 XML
20.1 XML简介
XML是可扩展标记语言,被设计用来传输和存储数据。XML与HTML的区别如下:
- XML用来传输和存储数据,HTML被设计用来显示数据。
- XML被设计为具有自我描述性,通过XML可以发明自己的标签。
- XML是W3C(World Wide Web Consortium),即万维网联盟的推荐标准。
- XML是没有任何行为的,仅仅是纯文本。
20.2 XML用途
XML 应用于 web 开发的许多方面,常用于简化数据的存储和共享。XML主要用途如下:
(1) XML把数据从 HTML 分离
如果你需要在HTML文档中显示动态数据,那么每当数据改变时将花费大量的时间来编辑HTML。通过XML,数据能够存储在独立的XML文件中。这样你就可以专注于使用 HTML进行布局和显示,并确保修改底层数据不再需要对 HTML 进行任何的改变。通过使用几行 JavaScript,你就可以读取一个外部 XML 文件,然后更新 HTML 中的数据内容。
(2) XML简化数据共享
在真实的世界中,计算机系统和数据使用不兼容的格式来存储数据。XML数据以纯文本格式进行存储,因此提供了一种独立于软件和硬件的数据存储方法。这让创建不同应用程序可以共享的数据变得更加容易。
(3) XML简化数据传输
通过 XML,可以在不兼容的系统之间轻松地交换数据。对开发人员来说,其中一项最费时的挑战一直是在因特网上的不兼容系统之间交换数据。由于可以通过各种不兼容的应用程序来读取数据,以XML交换数据降低了这种复杂性。
(4) XML简化平台的变更
升级到新的系统(硬件或软件平台),总是非常费时的。必须转换大量的数据,不兼容的数据经常会丢失。
XML 数据以文本格式存储。这使得 XML 在不损失数据的情况下,更容易扩展或升级到新的操作系统、新应用程序或新的浏览器。
(5) XML使您的数据更有用
由于XML独立于硬件、软件以及应用程序,XML使数据更可用,也更有用。不同的应用程序都能够访问您的数据,不仅仅在HTML页中,也可以从XML数据源中进行访问。通过XML您的数据可供各种阅读设备使用(手持的计算机、语音设备、新闻阅读器等),还可以供盲人或其他残障人士使用。
20.3 XML树形结构
XML文档形成了一种树结构,它从“根部”开始,然后扩展到“枝叶”。
<?xml version="1.0" encoding="utf-8"?>
<student>
<id>10001</id>
<name>Johnny</name>
<sex>female</sex>
<major>Software engineering</major>
</student>
XML 文档必须包含根元素。该元素是所有其他元素的父元素。所有元素均可拥有子元素。
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book category="Cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="Children">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="Web">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
20.4 XML语法
XML 的语法规则很简单,且很有逻辑。
(1) 所有XML元素都须有关闭标签
<title lang="en">Everyday Italian</title>
(2) XML 标签对大小写敏感
<Message>这是错误的。</message>
<message>这是正确的。</message>
(3) XML 必须正确地嵌套
<b><i>This text is bold and italic</b></i> <!--错误-->
<b><i>This text is bold and italic</i></b> <!--正确-->
(4) XML 文档必须有根元素
<?xml version="1.0" encoding="utf-8"?>
<root>
<child>
<subchild>.....</subchild>
</child>
</root>
(5) XML 的属性值必须加引号
<!--错误-->
<note date=08/08/2008>
<to>George</to>
<from>John</from>
</note>
<!--正确-->
<note date="08/08/2008">
<to>George</to>
<from>John</from>
</note>
(6) 实体引用
在 XML 中,一些字符拥有特殊的意义。如果你把字符 "<" 放在 XML 元素中,会发生错误,因为解析器会把它当作新元素的开始。
<message>if salary < 1000 then</message> <!--错误-->
为了避免这个错误,请用实体引用来代替 "<" 字符:
<message>if salary < 1000 then</message>
在 XML 中,有 5 个预定义的实体引用(专业字符):
转义字符 | 代表字符 | 说明 |
---|---|---|
< | < | 小于 |
> | > | 大于 |
& | & | and符 |
' | ' | 单引号 |
" | " | 双引号 |
(7) XML中的注释
在XML中编写注释的语法与 HTML 的语法很相似。
<!-- 注释内容 -->
(8) 在XML中,空格会被保留
HTML会把多个连续的空格字符裁减(合并)为一个,在XML中,文档中的空格不会被删节。
<message>Hello my name is David.</message>
20.5 XML元素
XML 元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。元素可包含其他元素、文本或者两者的混合物。元素也可以拥有属性。
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book category="Cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="Children">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="Web">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
在上例中,<bookstore> 和 <book> 都拥有元素内容,因为它们包含了其他元素。<author> 只有文本内容,因为它仅包含文本。<book> 元素拥有属性 (category=" Children ")。
XML命名规则
- 名称可以含字母、数字以及其他的字符
- 名称不能以数字或者标点符号开始
- 名称不能以字符“xml”(或者 XML、Xml)开始
- 名称不能包含空格
- 可使用任何名称,没有保留的字词。
20.6 XML属性
XML元素可以在开始标签中包含属性,类似HTML。属性 (Attribute) 提供关于元素的额外(附加)信息。
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book category="Cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="Children">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="Web">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
20.7 XML验证
XML Schema是W3C支持的基于XML的验证方式,它名为XML Schema。
<?xml version="1.0" encoding="utf-8"?>
<xs:element name="note">
<xs:complexType>
<xs:sequence>
<xs:element name="to" type="xs:string"/>
<xs:element name="from" type="xs:string"/>
<xs:element name="heading" type="xs:string"/>
<xs:element name="body" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
第二十一章 JSON
21.1 JSON简介
尽管有许多宣传关于 XML 如何拥有跨平台,跨语言的优势,然而,除非应用于Web Services,否则,在普通的 Web 应用中,开发者经常为 XML 的解析伤透了脑筋,无论是服务器端生成或处理 XML,还是客户端用JavaScript解析XML,都常常导致复杂的代码,极低的开发效率。实际上,对于大多数Web应用来说,他们根本不需要复杂的XML来传输数据,XML 的扩展性很少具有优势,许多AJAX应用甚至直接返回HTML片段来构建动态 Web 页面。和返回XML并解析它相比,返回HTML片段大大降低了系统的复杂性,但同时缺少了一定的灵活性。
现在,JSON为Web应用开发者提供了另一种数据交换格式。JSON即JavaScript Object Natation,它是一种轻量级的数据交换格式,非常适合于服务器与JavaScript的交互。
21.2 JSON语法
和XML一样,JSON也是基于纯文本的数据格式。由于JSON天生是为JavaScript准备的,因此,JSON的数据格式非常简单,您可以用JSON传输一个简单的String,Number,Boolean也可以传输一个数组,或者一个复杂的 Object 对象。
JSON语法是JavaScript对象表示法语法的子集。
- 数据在名称/值对中(键值对);
- 数据由逗号分隔;
- 花括号保存对象;
- 方括号保存数组。
(1) JSON名称/值对(键值对)
JSON数据的书写格式是:名称/值的键值对对。键值对对包括字段名称(在双引号中),后面写一个冒号,然后是值。
"name": "Johnny"
(2) JSON值
JSON值可以是:
- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true 或 false)
- 数组(在方括号中)
- 对象(在花括号中)
- null
(3) JSON对象
JSON对象在花括号中书写,对象可以包含多个名称/值对。
{
"id": 10001,
"name": "Johnny",
"sex": "female",
"major": "Software engineering"
}
(4) JSON数组
JSON 数组在方括号中书写,数组可包含多个对象。
{
"employees": [
{
"firstName":"John",
"lastName":"Doe"
},
{
"firstName":"Anna",
"lastName":"Smith"
},
{
"firstName":"Peter",
"lastName":"Jones"
}
]
}
(5) JSON使用JavaScript语法
因为JSON使用JavaScript语法,所以无需额外的软件就能处理 JavaScript 中的 JSON。通过 JavaScript,您可以创建一个对象数组,并像这样进行赋值。
{
"employees": [
{
"firstName":"John",
"lastName":"Doe"
},
{
"firstName":"Anna",
"lastName":"Smith"
},
{
"firstName":"Peter",
"lastName":"Jones"
}
]
}
可以像这样访问JavaScript对象数组中的第一项:
employees[0].lastName;
可以像这样修改数据:
employees[0].lastName = "Jobs";
(6) JSON文件
JSON文件的文件类型是 ".json"
JSON文本的MIME类型是 "application/json"
21.3 JSON使用
JSON最常见的用法之一,是从web服务器上读取JSON数据(作为文件或作为 HttpRequest),将JSON数据转换为JavaScript对象,然后在网页中使用该数据。
(1) 将json字符串解析为json对象
<script>
var res = "{\"id\": 10001,\"name\": \"Johnny\",\"sex\": \"female\",\"major\": \"Software engineering\"}";
var obj = JSON.parse(res);//将json字符串解析为json对象
alert(obj.name);
</script>
(2) 将json对象转为json字符串
<script>
var obj = {
id: 10001,
name: "Johnny",
sex: "female",
major: "Software engineering"
};
var str = JSON.stringify(obj); //将json对象转为json字符串
alert(str);
</script>
第二十二章 Ajax
Ajax,是Asynchronous JavaScript + XML的简写。这种技术能够向服务器请求额外的数据而无刷新页面,会带来更好的用户体验。Ajax技术核心是XMLHttpRequest对象(简称XHR),这是由微软首先引入的一个特性,其他浏览器提供商后来都提供了相同的实现。
在实际项目中,Ajax的实现是由jQuery来实现的,请参照jQuery课程。