《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 
 2- console.log(person1.hasOwnProperty("name")); // false 
 console.log("name" in person1); // true
- 更简洁的写法 - 1 
 2
 3
 4
 5
 6
 7
 8- function 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 使用这些词作为变量名将报错 | 
