第 3 章 基本概念

typeof 的返回值有undefined, boolean, string, number, object, function

对未初始化的变量执行 typeof 操作符会返回 undefined 值,而对未声明
的变量执行 typeof 操作符同样也会返回 undefined 值

Number.MIN_VALUE 中——在大多数浏览器中,这个值是 5e-324;能够表示的最大数值保存在
Number.MAX_VALUE 中——在大多数浏览器中,这个值是 1.7976931348623157e+308

0 除以 0 才会返回 NaN,正数除以 0 返回 Infinity,负数除以 0 返回-Infinity

转型函数 Number() 可以用于任何数据类型,而另两个函数则专门用于把字符串转换成数值

Number() 函数的转换规则如下:

  • 如果是 Boolean 值, true 和 false 将分别被转换为 1 和 0。
  • 如果是数字值,只是简单的传入和返回。
  • 如果是 null 值,返回 0。
  • 如果是 undefined ,返回 NaN 。
  • 如果是字符串,遵循下列规则:
    • 如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即 “1”
      会变成 1, “123” 会变成 123,而 “011” 会变成 11(注意:前导的零被忽略了);
    • 如果字符串中包含有效的浮点格式,如 “1.1” ,则将其转换为对应的浮点数值(同样,也会忽
      略前导零);
    • 如果字符串中包含有效的十六进制格式,例如 “0xf” ,则将其转换为相同大小的十进制整
      数值;
    • 如果字符串是空的(不包含任何字符),则将其转换为 0;
    • 如果字符串中包含除上述格式之外的字符,则将其转换为 NaN 。
  • 如果是对象,则调用对象的 valueOf() 方法,然后依照前面的规则转换返回的值。如果转换
    的结果是 NaN ,则调用对象的 toString() 方法,然后再次依照前面的规则转换返回的字符
    串值。

String() 函数遵循下列转换规则:

  • 如果值有 toString() 方法,则调用该方法(没有参数)并返回相应的结果;
  • 如果值是 null ,则返回 “null” ;
  • 如果值是 undefined ,则返回 “undefined”

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

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

第四章 变量、作用域和内存问题

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)
。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的 变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对。作用域链中象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

第五章 引用类型

引用类型包括 Object,Array,Date,RegExp,Function,基本包装类型(Boolean,Number,String),单体内置对象(Global,Math)

第六章 面向对象的程序设计

对象内部属性

属性用途默认值
[[Configurable]]表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性true
[[Enumerable]]表示能否通过 for-in 循环返回属性true
[[Writable]]表示能否修改属性的值true
[[value]]包含这个属性的数据值undefined

一旦把属性定义为不可配置的,
就不能再把它变回可配置了。此时,再调用 Object.defineProperty() 方法修改除 writable 之外
的特性,都会导致错误

对象的方法

方法用途参数
Object.defineProperty()修改属性的默认特性属性所在的对象、属性的名字和一个描述符对象
Object.defineProperties()一次定义多个属性要添加和修改其属性的对象、对象的属性与第一个对象中要添加或修改的属性一一对应
Object.getOwnPropertyDescriptor()取得给定属性的描述符属性所在的对象和要读取其描述符的属性名称
Object.getPrototypeOf返回[[Prototype]]的值某个对象
obj.hasOwnProperty只在给定属性存在于对象实例中时,才会返回 true对象的某个属性
Object.keys返回一个包含所有可枚举属性的字符串数组某个对象
Object.getOwnPropertyNames返回所有实例属性组成的数组,无论是否可枚举某个对象
Object.create原型式继承的规范化方法某个对象、[对象属性的配置对象]

创建对象的方式 :工厂模式、构造函数模式、原形模式、组合使用构造函数模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式

要创建 Person 的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下4个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象。
模式缺点
工厂模式没有解决对象识别的问题
构造函数模式每个方法都会在每个实例上重新创建一遍
原型模式会共享引用类型值的属性
组合使用构造函数模式
动态原型模式
寄生构造函数模式
稳妥构造函数模式

工厂模式

function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };
    return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

构造函数模式

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    };
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

原型模式

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName();
//"Nicholas"
var person2 = new Person();

组合使用构造函数模式

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }   
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends);
//"Shelby,Count,Van"
alert(person2.friends);
//"Shelby,Count"
alert(person1.friends === person2.friends);
//false
alert(person1.sayName === person2.sayName);
//true

动态原型模式

function Person(name, age, job){
    //属性
    this.name = name;
    this.age = age;
    this.job = job;
    //方法
    if (typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            alert(this.name);
        };
    }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

重写原型对象会切断现有原型与任何之前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型

继承方式:原型链、借用构造函数模式、组合继承、原型式继承、寄生式继承、寄生组合式继承

继承方式优缺点
原型链引用类型值的原型属性会被所有实例共享 ,在创建子类型的实例时,不能向超类型的构造函数中传递参数
借用构造函数函数无法复用
组合继承√,但超类构造函数会被调用两次
原型式继承
寄生式继承
寄生组合式继承√,解决了组合继承两次调用的问题

原型链继承

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());
//true

借用构造函数继承

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了 SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);
//"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors);
//"red,blue,green"

组合继承

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    //继承属性
    SuperType.call(this, name);
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);
//"red,blue,green,black"
instance1.sayName();
//"Nicholas";
instance1.sayAge();
//29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors);
//"red,blue,green"
instance2.sayName();
//"Greg";
instance2.sayAge();
//27

原型式继承

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

寄生式继承

function createAnother(original){
    var clone = object(original);
    clone.sayHi = function(){
        alert("hi");
    };
    return clone;
}

寄生组合式继承

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
};

第七章 函数表达式

闭包

概念: 闭包是指有权访问另一个函数作用域中的变量的函数

作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象

闭包形成原因: 函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象

使用立即执行函数这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了

第八章 BOM

setTimeout 和 setInterval

JavaScript 是一个单线程序的解释器,因此一定时间内只能执行一段代码。为了控制要执行的代码,就
有一个 JavaScript 任务队列。这些任务会按照将它们添加到队列的顺序执行。 setTimeout() 的第二个
参数告诉 JavaScript 再过多长时间把当前任务添加到队列中
。如果队列是空的,那么添加的代码会立即
执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行。

超时调用的代码都是在全局作用域中执行的,因此函数中 this 的值在非严格模
式下指向 window 对象,在严格模式下是 undefined

般认为,使用超时调用来模拟间歇调用的是一种最佳模式。在开发环境下,很少使用真正的间歇调用,原因是后一个间歇调用可能会在前一个间歇调用结束之前启动。而像前面示例中那样使用超时调用,则完全可以避免这一点。所以,最好不要使用间歇调用

location

可以使用document.location 和 window.loaction访问

它提供了与当前窗口中加载的文档有关的信息,还提供了一些导航功能

属性名例子说明
hash‘#content’返回URL中的hash(#号后跟零或多个字符),如果URL中不包含散列,则返回空字符串
host“www.wrox.com:80”返回服务器名称和端口号(如果有)
hostname“www.wrox.com”返回不带端口号的服务器名称
hrefhttp:/www.wrox.com”返回当前加载页面的完整URL。而location对象的toString()方法也返回这个值
pathname“/WileyCDA/“返回URL中的目录和(或)文件名
port“8080”返回URL中指定的端口号。如果URL中不包含端口号,则这个属性返回空字符串
protocol“http:”返回页面使用的协议。通常是http:或https:
search“?q=javascript”返回URL的查询字符串。这个字符串以问号开头

第22章 高级技巧

作用域安全的构造函数

function Person(name, age, job){
    if (this instanceof Person){
        this.name = name;
        this.age = age;
        this.job = job;
    } else {
        return new Person(name, age, job);
    }
}
var person1 = Person("Nicholas", 29, "Software Engineer");
alert(window.name);
//""
alert(person1.name);
//"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name);
//"Shelby"

使用作用域安全的构造函数会使得call和apply 方法失效,解决办法是使用原型

function Polygon(sides){
    if (this instanceof Polygon) {
        this.sides = sides;
        this.getArea = function(){
            return 0;
        };
    } else {
        return new Polygon(sides);
    }
}
function Rectangle(width, height){
    Polygon.call(this, 2);
    this.width = width;
    this.height = height;
    this.getArea = function(){
        return this.width * this.height;
    };
}
Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
alert(rect.sides);
//2

由于指定了Rectangele的prototype为Ploygon的实例,因此一个 Rectangle 实例也同时是一个 Polygon 实例。这样Polygon的 instance 检测就会通过

惰性载入函数

var createXHR = (function(){
    if (typeof XMLHttpRequest != "undefined"){
        return function(){
            return new XMLHttpRequest();
        };
    } else if (typeof ActiveXObject != "undefined"){
        return function(){
            if (typeof arguments.callee.activeXString != "string"){
                var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i, len;
                for (i=0,len=versions.length; i < len; i++){
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                        break;
                    } catch (ex){
                        //skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        return function(){
            throw new Error("No XHR object available.");
        };
    }
})();

惰性载入表示函数执行的分支仅会发生一次, 惰性载入表示函数执行的分支仅会发生一次