标签 javascript 下的文章

理解 JavaScript Promise

Promise 与事件监听类似,除了以下几点:

  1. 一个 Promise 只能成功或者失败一次。它不能进行从成功到失败的状态转换,反之亦然。
  2. 如果在一个 Promise 成功或者失败后添加了相应的回调函数,那么该回调还是会被立即执行,尽管相关事件早已发生。

Promise 术语

一个 Promise 处于以下四种状态之一。

  • fulfilled:操作成功。
  • rejected:操作失败。
  • pending:操作进行中,还没有得到操作结果。
  • settled:Promise 已经被 fulfilled 或 rejected,且不是 pending。

JavaScript 中的 Promise

Promise 的构造器接受一个函数作为回调函数,同时给回调函数提供了两个参数:resolve 和 reject。

var promise = new Promise(function(resolve, reject) {
    // 类似异步操作的一些逻辑,然后...
    
    if(/*正常直行*/) {
        resolve('Stuff worked!);
    } else {
        reject(Error('It broke'));
    }
});

下面看下如何使用 Promise:

promise.then(function(result) {
    console.log(result); // 执行成功
}, function(err) {
    console.log(err); // Error: '执行失败'
});

与其它库的兼容性

jQuery 中的 Deferred 对象与 Promise/A+ 并不兼容。jQuery 中也有 Promise 类型,但它只是 Deferred 的一个子集。我们可以将它们转换成标准的 Promise,而且最好当它们一出现的时候就进行转换:

var jsPromise = Promise.reslove($.ajax('/whatever.json'));

// jQuery 的 Deferred 的 then 返回了更多的参数
var jqDeferred = $.ajax('/whatever.json');
jqDeferred.then(function(response, statusText, xhrObj) {
    // ...
}, function(xhrObj, textStatus, err) {
    // ...
});

// 转化为 Promise 后只会保留第一个参数,其它参数均会被忽略
jsPromise.then(function(response) {
    // ...
}, function(xhrObj) {
    // ...
});

将 Promise 用于 XMLHttpRequest

// 用 Promise 封装 XMLHttpRequest 实现的简单 Get 方法
function get(url) {
    return new Promise(function(reslove, reject) {
        var req = new XMLHttpRequest();
        req.open('GET', url);
        
        req.onload = function() {
            if (req.status == 200) {
                resolve(req.response);
            } else {
                reject(Error(req.statusText));
            }
        };
        
        req.onerror = function() {
            reject(Error('Network Error'));
        };
        
        req.send();
    });
}

下面是使用方法:

get('story.json').then(function(response) {
    
}, function(error) {
    
});

链式调用

var promise = new Promise(function(resolve, reject) {
    resolve(1);
});

promise.then(function(val) {
    console.log(val);
    return val + 2;
}).then(function(val) {
    console.log(val);
});

// 可以使用这种方式修改之前的代码
get('story.json').then(function(response) {
    return JSON.parse(response);
}).then(function(response) {
    console.log(response);
});
// 再简化
get('story.json').then(JSON.parse).then(function(response) {
    console.log(response); // json 格式的数据
});
// 更好的方式
function getJSON(url) {
    return get(url).then(JSON.parse);
}

异步操作队列

当在“then”方法的回调函数里有返回值的时候,这里有些重要的事情需要我们关注。如果你返回的是一个值,那么下一个“then”会立即调用,并把这个值传递给它的回调函数。如果你返回的是一个“类 Promise”对象,那么下一个“then”方法会等待执行,直到这个 Promise 被解决(成功或失败)后才执行。

getJSON('story.json').then(function(story) {
    return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
    console.log(chapter1);
});

现在同简单的回调模式相比,Promise 开始显示出自己的优势了。可以像下面这样写个获取章节内容的函数:

var storyPromise;

function getChapter(i) {
    // 闭包
    storyPromise = storyPromise || getJSON('story.json');
    return storyPromise.then(function(story) {
        return getJSON(story.chapterUrls[i]);
    });
}

getChapter(0).then(function(chapter) {
    console.log(chapter);
    return getChapter(1);
}).then(function(chapter) {
    console.log(chapter);
});

错误处理

// 注意 then(func1, func2) 和 then(func1).catch(func2) 的区别
// catch 类似于 try/catch ,能够捕获到上一步中的任何异常,其后的
// then 还可以继续执行
asyncThing1().then(function(){
    return asyncThing2();
}).then(function(){
    return asyncThing3();
}).catch(function(err){
    return asyncRecovery1();
}).then(function(){
    return asyncThing4();
}, function(err){
    return asyncRecovery2();
}).catch(function(err){
    console.log("Don't worry about it.");
}).then(function(){
    console.log('All done!');
});

并行和串行

这里是并行和串行的异步操作同时存在的例子:

var sequence = Promise.resolve();

story.chapterUrls.forEach(function(chapterUrl){
    // 在 sequence 后添加如下操作
    sequence = sequence.then(function() {
        return getJSON(chapterUrl);
    }).then(function(chapter){
        addHtmlToPage(chapter.html);
    });
});

Promise.resolve() 会根据传入的参数返回一个 Promise。与之对应的 Promise.reject(val) 方法,会返回一个根据你传入的参数值(或 underfined)为 reject 结果的 Promise。

getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);
    
    return story.chapterUrls.reduce(function(sequence, chapterUrl) {
        // 当前一章节的 Promise 完成后
        return sequence.then(function(){
            // 接着获取下一章节
            return getJSON(chapterUrl);
        }).then(function(chapter){
            addHtmlToPage(chapter.html);
        });
    }, Promise.reslove());
}).then(function(){
    addTextToPage("All done.");
}).catch(function(err){
    addTextToPage("Argh, broken: " + err.message);
}).then(function(){
    document.querySelector('.spinner').style.display = 'none';
});

Promise.all 接受一个 Promise 数组作为参数,创建一个所有的 Promise 都 resolve 后才 resolve 的 Promise 的对象,resolve 结果是一个数组,包含了前面所有 Promise 的 resolve 结果,并且顺序和传入时的顺序是完全一致的。

getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);
    
    return Promise.all(
        story.chapterUrls.map(getJSON)
    );
}).then(function(chapters){
    chapters.forEach(function(chapter) {
        addHtmlToPage(chapter.html);
    });
}).catch(function(err){
    addTextToPage("Argh, broken: " + err.message);
}).then(function(){
    document.querySelector('.spinner').style.display = 'none';
});

不想等到所有章节都获取到后才显示到页面上:

getJSON('story.json').then(function(story) {
    addHtmlToPage(story.heading);
    
    return story.chapterUrls.map(getJSON) // getJSON 后的 Promise 
        .reduce(function(sequence, chapterPromise){
            return sequence.then(function(){
                return chapterPromise;
            }).then(function(chapter){
                addHtmlToPage(chapter.html);
            });
        }, Promise.resolve());
}).then(function(){
    addTextToPage("All done.");
}).catch(function(err){
    addTextToPage("Argh, broken: " + err.message);
}).then(function(){
    document.querySelector('.spinner').style.display = 'none';
});

Promise 厉害的地方还远不止于此!

JavaScript 高程设计——面向对象的程序设计(一)

理解对象

ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”

属性类型

ECMA-262 第5版在定义只有内部才用的特性(attribute)时,描述了属性(property)的各种特征。ECMA-262 定义这些特性是为了实现 JavaScript 引擎用的,因此在 JavaScript 中不能直接访问它们。

ECMAScript 中有两种属性:数据属性访问器属性

1.数据属性

数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性。

  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true

  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true

  • [[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true

  • [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined

使用 ECMAScript 5 的 Object.defineProperty() 方法修改属性默认的特性。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descrip-tor)对象的属性必须是:configurableenumerablewritablevalue

var person = {};
Object.defineProperty(person, "name", {    
    writable: false,
    value: "Nicholas"
});

//"Nicholas"
alert(person.name);
person.name = "Greg";
//"Nicholas"
alert(person.name);

2. 访问器属性

访问器属性不包含数据值;它们包含一对儿 gettersetter 函数。在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。访问器属性有如下4个特性。

  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为 true

  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为 true

  • [[Get]]:在读取属性时调用的函数。默认值为 undefined

  • [[Set]]:在写入属性时调用的函数。默认值为 undefined

创建对象

工厂模式

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");

person1.sayName();   //"Nicholas"
person2.sayName();   //"Greg"

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

构造函数模式

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");

person1.sayName();   //"Nicholas"
person2.sayName();   //"Greg"

alert(person1 instanceof Object);  //true
alert(person1 instanceof Person);  //true
alert(person2 instanceof Object);  //true
alert(person2 instanceof Person);  //true

alert(person1.constructor == Person);  //true
alert(person2.constructor == Person);  //true

alert(person1.sayName == person2.sayName);  //false 
// 以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建 Function 新实例的机制仍然是相同的

任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过 new 操作符来调用,那它跟普通函数也不会有什么两样。

原型模式

使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

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();
person2.sayName();   //"Nicholas"

alert(person1.sayName == person2.sayName);  //true

// // 内部都有一个指向Person.prototype的指针,因此都返回了true
alert(Person.prototype.isPrototypeOf(person1));  //true
alert(Person.prototype.isPrototypeOf(person2));  //true

//only works if Object.getPrototypeOf() is available
if (Object.getPrototypeOf){
    alert(Object.getPrototypeOf(person1) == Person.prototype);  //true
    alert(Object.getPrototypeOf(person1).name);  //"Nicholas"
}

prototype 属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。

通过使用 hasOwnProperty() 方法,什么时候访问的是实例属性,什么时候访问的是原型属性就一清二楚了。

要取得对象上所有可枚举的实例属性,可以使用 EC-MAScript 5 的 Object.keys() 方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

如果你想要得到所有实例属性,无论它是否可枚举,都可以使用 Object.getOwnPropertyNames() 方法。

var keys = Object.keys(Person.prototype);
//"name,age,job,sayName"
alert(keys);

var keys1 = Object.getOwnPropertyNames(Person.prototype); 
//"constructor,name,age,job,sayName" 
alert(keys1);

更简单的原型语法

function Person(){
}

Person.prototype = {
    constructor : Person, 
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

//重设构造函数,只适用于ECMAScript 5兼容的浏览器 
Object.defineProperty(Person.prototype, "constructor", {     
    enumerable: false,     
    value: Person 
});  

var friend = new Person();

alert(friend instanceof Object);  //true
alert(friend instanceof Person);  //true
alert(friend.constructor == Person);  //false
alert(friend.constructor == Object);  //true

原型对象的问题

原型模式的最大问题是由其共享的本性所导致的。对于包含引用类型值的属性来说,会导致创建的对象共享同一个属性,其中一个修改其他的实例也会被修改。

JavaScript 高程设计——引用类型(二)

RegExp 类型

ECMAScript 通过 RegExp 类型来支持正则表达式。使用下面类似 Perl 的语法,就可以创建一个正则表达式。

var expression = / pattern / flags ;

正则表达式的匹配模式支持下列3个标志(flags):g ——全局,i ——不区分大小写,m ——匹配多行。

// 匹配字符串中所有"at"的实例
var pattern1 = /at/g;
// 匹配第一个"bat"或"cat",不区分大小写
var pattern2 = /[bc]at/i;
// 匹配所有以"at"结尾的3个字符的组合,不区分大小写
var pattern3 = /.at/gi;
// 与pattern1相同,只不过是使用构造函数创建的
var pattern4 = new RegExp("[bc]at", "i");

由于 RegExp 构造函数的模式参数是字符串,所以在某些情况下要对字符进行双重转义。所有元字符都必须双重转义,那些已经转义过的字符也是如此。

使用正则表达式字面量和使用 RegExp 构造函数创建的正则表达式不一样。在 ECMAScript 3 中,正则表达式字面量始终会共享同一个 RegExp 实例,而使用构造函数创建的每一个新 RegExp 实例都是一个新实例。

ECMAScript 5 明确规定,使用正则表达式字面量必须像直接调用 RegExp 构造函数一样,每次都创建新的 RegExp 实例。

RegExp 实例属性

RegExp 的每个实例都具有下列属性,通过这些属性可以取得有关模式的各种信息。

  • global:布尔值,表示是否设置了g标志。
  • ignoreCase:布尔值,表示是否设置了i标志。
  • lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0算起。
  • multiline:布尔值,表示是否设置了m标志。
  • source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。

RegExp 实例方法

RegExp 对象的主要方法是 exec(),该方法是专门为捕获组而设计的。exec() 接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回 null。返回的数组虽然是 Array 的实例,但包含两个额外的属性:indexinput 。其中,index 表示匹配项在字符串中的位置,而 input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串。

正则表达式的第二个方法是 test(),它接受一个字符串参数。在模式与该参数匹配的情况下返回 true;否则,返回 false

尽管 ECMAScript 中的正则表达式功能还是比较完备的,但仍然缺少某些语言(特别是 Perl)所支持的高级正则表达式特性。

Function 类型

每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

在函数内部,有两个特殊的对象:argumentsthis。其中,arguments 是一个类数组对象,包含着传入函数中的所有参数。虽然 arguments 的主要用途是保存函数参数,但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。

arguments.callee

this 引用的是函数据以执行的环境对象——或者也可以说是 this 值。

ECMAScript 5 中 caller 属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null

当函数在严格模式下运行时,访问 arguments.callee 会导致错误。ECMAScript 5 还定义了 arguments.caller 属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是 undefined。严格模式还有一个限制:不能为函数的 caller 属性赋值,否则会导致错误。

ECMAScript 中的函数包含两个属性:length和proto-type。其中,length属性表示函数希望接收的命名参数的个数。对于 ECMAScript 中的引用类型而言,prototype 是保存它们所有实例方法的真正所在。

在 ECMAScript 5 中,prototype 属性是不可枚举的,因此使用 for-in 无法发现。

每个函数都包含两个非继承而来的方法:apply()call() 。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。

function callSum1(num1, num2){    
    // 传入arguments对象    
    return sum.apply(this, arguments);
}

function callSum2(num1, num2){    
    // 传入数组    
    return sum.apply(this, [num1, num2]);
}

call() 方法与 apply() 方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call() 方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。

事实上,传递参数并非 apply()call() 真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。

ECMAScript 5 还定义了一个方法:bind()。这个方法会创建一个函数的实例,其 this 值会被绑定到传给 bind() 函数的值。

基本包装类型

ECMAScript 提供了3个特殊的引用类型:Boolean、Number 和 String。

单体内置对象

ECMA-262 还定义了两个单体内置对象:Global 和 Math。Global 对象的 encodeURI()encodeURIComponent() 方法可以对 URI(Uniform Resource Identifiers,通用资源标识符)进行编码,以便发送给浏览器。

Math.random() 方法返回介于0和1之间一个随机数,不包括0和1。

JavaScript 高程设计——引用类型(一)

引用类型的值(对象)是引用类型的一个实例。在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起。

对象是某个特定引用类型的实例。新对象是使用 new 操作符后跟一个构造函数来创建的。构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。

var person = new Object();

Object 类型

创建 Object 实例的方式有两种。第一种是使用 new 操作符后跟 Object 构造函数,如下所示:

var person = new Object();
person.name = "Nicholas";
person.age = 29;

另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。

var person = {    
    name : "Nicholas",    
    age : 29
};

Array 类型

创建数组的基本方式有两种。第一种是使用 Array 构造函数,如下面的代码所示。

var colors = new Array();
// 创建 length 值20的数组
var colors = new Array(20); 
// 码创建了一个包含3个字符串值的数组
var colors = new Array("red", "blue", "green");

// 创建一个包含3项的数组
var colors = Array(3);
// 创建一个包含1项,即字符串"Greg"的数组
var names = Array("Greg");

// 创建一个包含3个字符串的数组
var colors = ["red", "blue", "green"];

数组最多可以包含4 294 967 295个项,如果想添加的项数超过这个上限值,就会发生异常。

检测数组

对于一个网页,或者一个全局作用域而言,使用 instanceof 操作符就能得到满意的结果:

if (value instanceof Array){    
    //对数组执行某些操作
}

对于有还有多个框架的页面,需要使用 ECMAScript 5 新增的 Array.isArray() 方法:

if (Array.isArray(value)){    
    //对数组执行某些操作
}

如果数组中的某一项的值是 null 或者 undefined,那么该值在 join()toLocaleString()toString()valueOf()方法返回的结果中以空字符串表示。

栈方法

push() 方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。而 pop() 方法则从数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项。

队列方法

shift() 能够移除数组中的第一个项并返回该项,同时将数组长度减1。unshift()shift() 的用途相反:它能在数组前端添加任意个项并返回新数组的长度。

重排序方法

数组中已经存在两个可以直接用来重排序的方法:reverse()sort()

比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回0,如果第一个参数应该位于第二个之后则返回一个正数。

function compare(value1, value2) {    
    if (value1 < value2) {        
        return 1;     
    } else if (value1 > value2) {        
        return -1;     
    } else {        
        return 0;    
    }
}

var values = [0, 1, 5, 10, 15];
values.sort(compare);

// 15,10,5,1,0
alert(values);

reverse()sort() 方法的返回值是经过排序之后的数组。

操作方法

  • concat: concat() 方法可以基于当前数组中的所有项创建一个新数组。具体来说,这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat() 方法传递参数的情况下,它只是复制当前数组并返回副本。如果传递给 concat() 方法的是一或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中。如果传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。

  • slice: slice() 能够基于当前数组中的一或多个项创建一个新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。

  • splice: splice() 的主要用途是向数组的中部插入项,但使用这种方法的方式则有如下3种。

var colors = ["red", "green", "blue"];
// 删除第一项
var removed = colors.splice(0,1);
// green,blue
alert(colors);
// red,返回的数组中只包含一项
alert(removed);

// 从位置1开始插入两项
removed = colors.splice(1, 0, "yellow", "orange");
// green,yellow,orange,blue
alert(colors);
// 返回的是一个空数组
alert(removed);

// 插入两项,删除一项
removed = colors.splice(1, 1, "red", "purple");
// green,red,purple,orange,blue
alert(colors);
// yellow,返回的数组中只包含一项
alert(removed);

位置方法

ECMAScript 5 为数组实例添加了两个位置方法:indexOf()lastIndexOf() 。这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf() 方法从数组的开头(位置0)开始向后查找,lastIndexOf() 方法则从数组的末尾开始向前查找。

迭代方法

ECMAScript 5 为数组定义了5个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响 this 的值。传入这些方法中的函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。

  • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true
  • filter():对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
  • forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  • some():对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true

缩小方法

ECMAScript 5 还新增了两个缩小数组的方法:reduce()reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。

这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为缩小基础的初始值。传给 reduce()reduceRight() 的函数接收4个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。

var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){    
    return prev + cur;
});

// 15
alert(sum);

Date 类型

在调用 Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数。ECMAScript 提供了两个方法:Date.parse()Date.UTC()

var now = new Date();

var someDate = new Date(Date.parse("May 25, 2004"));
var someDate = new Date("May 25, 2004");

// GMT时间2000年1月1日午夜零时
var y2k = new Date(Date.UTC(2000, 0));

// GMT时间2005年5月5日下午5:55:55
var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

var y2k = new Date(2000, 0);

ECMAScript 5 添加了 Data.now() 方法,返回表示调用这个方法时的日期和时间的毫秒数。

// 取得开始时间
// 在不支持now()的浏览器中使用 var start = +new Date(); 
var start = Date.now();

// 调用函数 doSomething();

// 取得停止时间
var stop = Date.now(),    
    result = stop – start;

日期格式化方法(略)

日期/时间组件方法(略)

JavaScript 高程设计——变量、作用域和内存问题

基本类型和引用类型的值

5种基本数据类型:Undefined、Null、Boolean、Number和String是按值访问的,因为可以操作保存在变量中的实际的值。

引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。

基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。参数只能按值传递。

虽然在检测基本数据类型时 typeof是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。为此,ECMAScript 提供了 instanceof 操作符,其语法如下所示:

result = variable instanceof constructor

使用 typeof 操作符检测函数时,该操作符会返回 "function"。在 IE 和 Firefox 中,对正则表达式应用 typeof 会返回 "object",在 Safari 和 Chrome 中返回 "function"

执行环境及作用域

每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。

如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。

try-catch 语句的 catch 块和 with 语句可以延长作用域链。

垃圾收集

垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以备将来收回其占用的内存。用于标识无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两个策略——标记清除和引用计数。

JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

到2008年为止,IE、Firefox、Opera、Chrome 和 Safari 的 JavaScript 实现使用的都是标记清除式的垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。

另一种不太常见的垃圾收集策略叫做引用计数(referencecounting)。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。

因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个做法叫做解除引用(dereferencing)。