《Javascript高级程序设计》
1. script
<script>
的6个属性:async, charset, defer, language, src, type
与解析嵌入式Javascript代码一样,在解析外部Javascript文件时(包括下载文件),页面的处理会暂时停止。无论如何包含代码,只要不存在defer和async属性,浏览器都会按照<script>
元素在页面中出现的先后顺序对它依次进行解析。第一个script元素包含的代码解析完后,第二个才会被解析。
延迟脚本
脚本会被延迟到整个页面都解析完毕后再运行。defer属性只适用于外部文件。1
<script type=”text/javascript”defer=”defer”src=”example.js”></script>
异步脚本
async只适用于外部文件。指定async的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容。1
<script type=”text/javascript” async src=”example.js”></script>
2.基本概念
JS的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。换句话说,每个变量仅仅是一个用于保存值得占位符。var message
未经初始化的变量,会保存一个特殊的值——undefined。用var操作符定义的变量会成为定义改变量的作用域中的局部变量。如果在函数中使用var定义一个变量,那么这个变量在函数退出后会被销毁。1
2
3
4
5function test(){
var message = "Hi";
}
test();
alert(message); // 错误
typeof
检查变量的数据类型,返回Undefined, boolean, string, number, object, function
instanceof
如果变量是给定引用类型的实例(根据原型链识别),那么instanceof会返回true1
2alert(person instanceof Object) // 变量person是Object吗?
alert(colors instanceof Array) // 变量colors是Array吗?
boolean
对任何数据类型值调用Boolean()函数,总是会返回一个boolean值。
数据类型 | 转换为true的值 | 转换为false的值 |
---|---|---|
Boolean | true | false |
String | 任何非空字符串 | “”(空字符串) |
Number | 任何非零数字值(包括无穷大) | 0和NaN |
Object | 任何对象 | null |
Undefined | n/a | undefined |
NaN
确定这个参数是否“不是数值”1
2
3
4
5isNaN(NaN) // true
isNaN(10) // false
isNaN("10") // false
isNaN("blue") // true
isNaN(true) // false(可以转换为数值1)
数值转换
parseInt: 如果第一个字符是数字字符,直到遇到一个非数字字符。无法解析返回NaN
parseFloat: 从第一个字符开始解析,直到遇见一个无效的浮点数字字符为止。
退出循环break
会立即退出循环,强制继续执行循环后面的语句。continue
会立即退出循环,但退出循环后会从循环的顶部继续执行。1
2
3
4
5
6
7
8
9
10var num = 0;
for (var i = 1; i < 10; i++){
if (i % 5 == 0){
break;
// continue;
}
num++;
}
alert(num); // break: 4
alert(num); // continue: 8
break和continue都可以与label语句联合使用,从而返回代码中特定的位置。1
2
3
4
5
6
7
8
9
10
11
12
13var num = 0;
outermost;
for (var i = 0; i < 10; i++){
for (var j = 0; j < 10; j++){
if (i == 5 && j == 5){
break outermost;
// continue outermost;
}
num++;
}
}
alert(num); // break: 55,break不仅会退出j循环,还会退出i循环
alert(num); // continue: 95, continue退出内部循环,执行外部循环,i=5,j=6~10不会被执行
3.变量、作用域和内存问题
Undefined, Null, Boolean, Number, String这5种基本数据类型是按值访问,可以操作在变量中实际的值。引用类型的值是按引用访问的。
3.1 复制变量值
- 复制一个基本类型的值,会在变量对象上创建一个新值,然后把该值复制到位新变量分配的位置上。
1
2
3var num1 = 5;
var num2 = num1;
// num1中的5和num2中的5是完全独立的。两个变量不会相互影响。
- 复制一个引用类型的值,同样会将存储在变量对象中的值复制一份放到新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,这个指针指向存储在堆中的一个对象。
1
2
3
4
5var obj1 = new Object();
var obj2 = obj1;
obj1.name = ‘mike’;
alert(obj2.name) // ‘mike’
// 复制后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。
4.2 执行环境与作用域
执行环境定义了变量或函数有权访问的数据。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
全局执行环境
在Web浏览器中,全局执行环境被认为是window
对象。函数的执行环境
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的变量和函数。1
2
3
4
5
6
7
8
9
10
11
12
13var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问color、anotherColor和tempColor
}
// 这里可以访问color和anotherColor,但不能访问tempColor
swapColors();
}
// 这里只能访问color changeColor();
没有块级作用域
在其他类C的语言中,由{}
封闭的代码块都有自己的作用域。但在JS中,if
语句中的变量生命会将变量添加到当前的执行环境中。1
2
3
4if (true){
var color = "blue";
}
alert(color); // "blue"
由for语句创建的变量i即使在for循环执行结束后,也依旧会存在于循环外部的执行环境汇总。1
2
3
4for(var i = 0; i < 10; i++){
doSomething(i);
}
alert(i); // 10
6. 创建对象
1 | var book = { |
6.1 构造函数
new
一个新实例会经历以下4个步骤: 创建一个新对象;将构造函数的作用域赋给新对象(this指向这个新对象); 执行构造函数中的代码(为这个新对象添加属性); 返回新对象。1
2
3
4
5
6
7
8
9
10function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
var person1 = new Person('Greg', 27, 'Doctor');
var person2 = new Person('Nicholas', 29, 'Software Enginner');
person1和person2分别保存着Person的不同实例。这两个对象都有一个constructor构造函数属性,该属性指向Person。
使用call()
或apply()
在某个特殊对象的作用域中调用Person()
函数。这里是在对象o的作用域中调用,因此调用后o就拥有了所有属性和sayName()方法。1
2
3var o = new Object();
Person.call(o, 'Kristen', 25, 'Nurse');
o.sayName(); // "Kristen"
6.2 原型模式
每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象。原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。1
2
3
4
5
6
7
8
9function Person(){}
Person.prototype.name = 'Nicholas';
Person.prototype.sayName = function(){
alert(this.name);
}
var person = new Person();
person.sayName(); // "Nicholas"
hasOwnProperty()
检测一个属性是存在于实例中,还是存在于原型中。in操作符
1
2console.log(person1.hasOwnProperty("name")); // false
console.log("name" in person1); // true更简洁的写法
1
2
3
4
5
6
7
8function Person(){}
Person.prototype = {
constructor : Person,
name: 'Nicholas',
sayName: function(){
alert(this.name);
}
};原型对象的问题
由于name存在于Person.prototype而非person1中,当person1.name被修改时,这个修改也会通过person2.name反映出来。因此我们很少单独使用原型模式。
6.3 组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。1
2
3
4
5
6
7
8
9
10
11
12function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['Amy', 'Mike'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
alert(this.name);
}
}
6.4 寄生模式
当我们想创建一个具有额外方法的特殊数组,由于不能直接修改Array构造函数,因此可以用这个模式。1
2
3
4
5
6
7
8
9
10
11
12
13
14function SpecialArray(){
var values = new Array();
// 添加值
values.push.apply(values, arguments);
// 添加方法
values.toPiedString = function(){
return this.join("|");
}
return values;
}
var colors = new SpecialArray("red", "blue", "green");
console.log(colors.toPipedString()); // "red|blue|green"
7. 继承
7.1 原型链
让原型对象等于另一个类的实例。创建SuperType的实例,并将实例赋值给SubType.prototype。本质是重写原型对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// SuperType实例中的所有属性和方法,现在存在于SubType.prototype中。
function SuperType(){
this.value = true;
}
SuperType.prototype.getSuperValue = function(){
return this.value;
}
function SubType(){
this.subValue = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subValue;
}
var instance = new SubType();
console.log(instance.getSuperValue); // true
略
8. 函数表达式
定义函数的方式有两种1
2
3
4
5// 函数声明
function functionName(args){}
// 函数表达式
var functionName = function(args){}
8.1 闭包
闭包是指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式,就是在一个函数内部创建另一个函数。
var value1=
这两行代码是内部函数(一个匿名函数)的代码,这两行代码访问了外部函数中的变量propertyName。即使这个内部函数被返回了,它仍然可以访问变量propertyName,之所以还能访问这个变量,是因为内部函数的作用域链中包含createComparisonFunction()
的作用域。1
2
3
4
5
6
7
8
9
10
11
12function createComparisonFunction(propertyName){
return function(object1, object2){
var value1 = object1(propertyName);
var value2 = object2(propertyName);
if (value < value2){
return -1;
} else {
return 1;
}
}
}
9. use strict
使用严格模式,可以减少意外创建全局变量,导致内存泄露的情况。一些在正常模式下可以运行的语句,在严格模式下将不能运行。
正常模式 | 严格模式 |
---|---|
如果一个变量没有声明就赋值,默认是全局变量 | 严格模式禁止这种用法,全局变量必须显式声明 |
允许动态绑定,即某些属性和方法属于哪一个对象,不是在编译时确定的,而是在运行时确定的 | 严格模式在某些情况下,只允许静态绑定,属性和方法归属哪个对象,在编译阶段就确定。1. 禁止使用with语句2. 增加eval作用域 |
禁止this关键字指向全局对象 function f(){ "use strict"; this.a = 1; } f(); // 报错,this未定义 |
|
禁止删除变量,只有configurable为true的对象属性才能被删除 | |
显示报错 | |
重名错误:对象、函数参数不能有重名的属性 | |
禁止八进制表示法 var n = 0100 // error |
|
不允许对arguments赋值,arguments不再追踪参数的变化, 禁止使用arguments.callee | |
新版JS会引入块级作用域。为了与新版本接轨,严格模式只允许在全局作用域,或函数作用域的顶层声明函数。不允许在非函数的代码块内声明函数。 | |
保留字,implements, interface, let, package, private, protected, public, static, yield, class, enum, export, extends, import, super, const 使用这些词作为变量名将报错 |