精华内容
下载资源
问答
  • nodejs笔记-异步编程

    2018-04-23 07:25:49
    1.函数式编程 1.1高阶函数 函数参数只接受基本数据类型或者对象引用,返回值也是基本数据类型和对象引用。 //常规参数传递和返回 function foo(x) { return x; } 复制代码高阶函数则是可以把函数作为参数和返回值的...

    1.函数式编程

    1.1高阶函数

    函数参数只接受基本数据类型或者对象引用,返回值也是基本数据类型和对象引用。

    //常规参数传递和返回
    function foo(x) {
        return x;
    }
    复制代码

    高阶函数则是可以把函数作为参数和返回值的函数。

    function foo(x) {
        return function() {
            return x
        }
    }
    复制代码
    function foo(x, bar) {
        return bar(x);
    }
    复制代码

    上面这个函数相同的foo函数但是传入的bar参数不同则可以返回不同的结果,列如数组的sort()方法,它是一个高阶函数,接受一个参数方法作为排序运算。

    var arr = [40, 80, 60, 5, 30, 77];
    arr.sort(function(a, b) {
        return a - b;
    });
    //运行结果[5, 30, 40, 60, 77, 80];
    复制代码

    通过修改sort方法的参数可以决定不同的排序方法,这就是高阶函数的灵活性。
    结合Node的基本事件模块可以看到,事件处理方式正是基于高阶函数的特性来完成的。在自定义事件中,通过为相同的事件注册不同的回调函数,可以很灵活的处理业务逻辑。

    var emit = new events.EventEmitter();
    emit.on('event', function() {
      //do something  
    });
    复制代码

    1.2偏函数

    偏函数指的是创建一个调用另外一部分(参数或者变量已经预置的函数)的函数的用法例如:

    var toString = Object.prototype.toString;
    var isString = function(obj) {
        return toString.call(obj) == '[object String]';
    }
    var isFunction = function(obj) {
        return toString.call(obj) == '[object Function]';
    }
    复制代码

    这段代码用于判断类型,通常会进行上述定义,代码存在相似性如果要判断更多会定义更多的isXXX()方法,这样就会出现冗余代码。为了解决重复问题,引入一个新函数用于批量创建这样的类似函数。

    var isType = function (type) {
        return function(obj) {
            return toString.call(obj) == '[object '+ type +']';
        }
    }
    var isString = isType('String');
    var isFunction = isType('Function');
    复制代码

    这种通过指定部分参数来产生一个新的定制函数的形式就是偏函数。

    2.异步编程优势与难点

    2.1异步优势

    Node的最大特性是基于事件驱动的非阻塞I/O模型,这使得CPU和I/O不互相依赖,让资源更好的利用,对于网络应用而言使得各个单点之间可以更有效的组织起来,这使得Node在云计算中广受青睐。 由于事件循环模型要应对海量请求,所有请求作用在单线程上需要防止任何一个计算过多的消耗CPU时间片。建议计算对CPU的耗用不超过10ms,或将大量的计算分解成小量计算,通过setImmediate()进行调度。

    2.2难点

    1.异常处理

    通常处理异常时使用 try/catch/final语句进行异常捕获:

    try {
        JSON.parse(str);
    }catch(e) {
        console.log(e)
    }
    复制代码

    但这对于异步编程不一定适用。I/O实现异步有两个阶段:提交请求和处理结果。这两个阶段中间有事件循环调度,彼此互不关联,一步方法通常在第一个阶段请求提交后立即返回,但是错误异常并不一定发生在这个阶段,try/catch就不一定会发生作用了。

    var async = function(callback) {
        process.nextTick(callback);
    }
    
    try {
        async(callback);
    }catch(e) {
        
    }
    复制代码

    调用async方法后callback会被存起来知道下一个事件循环才被执行,try/catch操作只能捕获当前时间循环内的异常。对callback中的异常不起作用。
    Node在处理异常上形成了一个约定,将异常作为回调的第一个参数传回,如果是空值,表明没有异常抛出:

    async(function(err, res)) {
        
    });
    复制代码

    在自行编写的异步方法上也需要去遵循这样的原则: 1.必须执行调用者传入的回调函数; 2.正确的传回异常供调用者判断;

    var async = function(callback) {
        process.nextTick(function() {
            var res = 'something';
            if(error) {
                return callback(error);
            }else {
                return callback(null, res);
            }
        })
    }
    复制代码

    在异步编程中,另一个容易犯的错误是对用户传递的callback进行异常捕获,

    try {
        req.body = JSON.parse(buf, options.reviver);
        callback();
    }catch(e) {
        err.body = buf;
        err.status = 400;
        callback(e);
    }
    复制代码

    如果JSON.parse出现错误代码将进入catch部分这样回调函数callback将被执行两次,正确的做法应该是

    try {
        req.body = JSON.parse(buf, options.reviver);
    }catch(e) {
        err.body = buf;
        err.status = 400;
        return callback(e);
    }
    callback();
    复制代码
    2.函数嵌套过深

    Node中事物中会出现多个异步调用的场景,列如遍历目录:

    fs.readdir('path', function(err, files) {
        files.forEach(function(fileName, index) {
            fs.readFile(fileName, 'utf8', function(err, file) {
                //TODO
            })
        })
    });
    复制代码

    上述操作由于两次操作存在依赖关系,函数嵌套行为情有可原,但是在某些场景列如渲染网页:通常需要数据、模版、资源文件,这三个操作互不依赖但是最终结果又是三者不可缺一,如果采用默认异步调用会是这样:

    fs.readFile('path', 'utf8', function(err, templete) {
        db.query(sql, function(err, data) {
            l10n.get(function(err, res) {
                //TODO
            })   
        })
    })
    复制代码

    这样导致了代码嵌套过深,不易读且不好维护。

    3.阻塞代码

    javascript没有sleep()这样让线程沉睡的功能,只有setInterval()和setTimeout()这两个函数,但是这两个函数不能阻塞后面的代码执行。

    4.多线程编程

    说到javascript时候,通常谈的是单线程上执行。随着业务复杂,对于多核CPU的利用要求也越来越高。浏览器中提出了Web Workers。可以更好的利用多核CPU为大量计算服务。前端Web Workers也是利用消息机制合理的使用多核CPU的理想模型。

    Web Workers的工作示意图
    Node借鉴了这个模式,child_process是其基础API,cluster模块是更深层次的应用。

    5.异步转同步

    Node提供了绝大部分的异步API和少量的同步API,Node中试图同步编程,但并不能得到原生支持,需要借助库或者编译手段实现。

    3.异步编程解决方案

    目前,异步编程主要解决方案有三种:

    1. 事件发布/订阅模式
    2. Promise/Deferred模式
    3. 流程控制库

    3.1事件发布/订阅模式

    Node自身提供的events模块是发布/订阅的一个简单实现,Node中部分模块都继承自它。它具有addListener/on()、 once()、 removeListener()、 removeAllListener()、 emit() 等基础方法。

    //订阅
    emitter.on('event', function(msg) {
        console.log(msg)
    });
    emitter.emit('event', 'this is msg');
    复制代码

    Node对事件发布/订阅的机制做了一些额外处理:

    1. 如果对一个事件添加超过10个侦听器,将会得到一条警告,设计者认为太多的侦听器可能会导致内存泄漏,调用emitter.setMaxListener(0);可以去掉这个限制。
    2. 为了异常处理,EventEmitter对象对error事件进行了特殊对待。如果运行期间的错误出发了error事件,EventEmitter会检测是否对error事件添加过侦听器,如果加了,这个错误将交给侦听器处理,否则将作为异常抛出。如果外部没有捕获异常,将会引起线程退出。
    1.继承events模块

    实现一个继承EventEmitter的类:

    var events = require('events');
    function Stream() {
        events.EventEmitter.call(this);
    }
    util.inherits(stream, events.EventEmitter);
    复制代码

    Node在util模块封装了继承方法。

    2.利用事件列队结局雪崩问题

    在事件订阅/发布模式中,通常有一个once()方法,通过它添加的侦听器只能执行一次,执行后将被移除。
    在计算机中,缓存由于放在内存中,访问书的快,用于加速数据访问,让绝大多数请求不必重复去做一些低效的数据读取。所谓的雪崩问题,就是高访问量、大并发量的情况下缓存失效的情况,此时大量的请求同时涌入数据库中,数据库无法承受大量查询请求进而影响网站整体响应速度。

    //查询数据库
    var select = function(callback) {
        db.select(sql, function(err, res) {
            callback(res);
        })
    }
    复制代码

    如果站点刚启动缓存中还没有数据,如果访问量巨大,同一句sql会被执行多次反复查询数据库,将会影响性能。改进方案:加一个状态锁

    var status = 'ready';
    var select = function(callback) {
        if(status === 'ready') {
            status = 'pending';
            db.select(sql, function(err, res) {
                status = 'ready';
                callback(res);
            })
        }
    }
    复制代码

    但是在这种情况下连续多次调用只有第一次调用是生效的,后续调用是没有数据服务的,这个时候可以引入事件列队:

    var proxy = new events.EventEmitter();
    var status = 'ready';
    var select = function(callback) {
        proxy.once('selected', callback);
        if(status == 'ready') {
            status = 'pending';
            db.select(sql, function(err, res) {
                proxy.emit('selected')
                status = 'ready';
            })
        }
    }
    复制代码

    利用once()方法,将所有请求的回调压入事件列队,利用其执行一次就好将监视器移除的特点,保证一次回调只会被执行一次。

    3.多异步之间的协作方案

    以上面提到的渲染网页(模版读取、数据读取、本地资源读取)为例:

    var count = 0;
    var res = {};
    var done = function(key, val) {
        res[key] = val;
        count ++;
        if(count === 3) {
            render(res);
        }
    };
    fs.readFile(template_path, 'utf8', function(err, tp) {
        done('tp', tp);
    }); 
    db.query(sql, (err, data) {
        done('data', data);
    });
    l10n.get(function(err, res) {
        done('res', res);
    });
    复制代码

    通常用于检测次数的变量叫做'哨兵变量'。利用偏函数来处理哨兵变量和第三方函数的关系:

    var after = function(times, callback) {
        var count = 0,res = {};
        return function(key, val) {
            res[key] = val;
            count ++;
            if(count == times) {
                callback(res);
            }
        }
    }
    
    var emitter = new events.EventEmitter();
    var done = after(times, render);
    
    emitter.on('done', done);
    emitter.on('done', other);
    
    fs.readFile(template_path, 'utf8', function(err, tp) {
        emitter.emit('done', 'tp', tp);
    }); 
    db.query(sql, (err, data) {
        emitter.emit('done', 'data', data);
    });
    l10n.get(function(err, res) {
        emitter.emit('done', 'res', res);
    });
    复制代码
    4.EventProxy

    扑灵写的EventProxy模块,是对事件订阅/发布模式的扩充

    var proxy = new EventProxy();
    proxy.all('tp', 'data', 'res', function(tp, data, res) {
        //TODO
    });
    fs.readFile(template_path, 'utf8', function(err, tp) {
        proxy.emit('tp', tp);
    }); 
    db.query(sql, (err, data) {
        proxy.emit('data', data);
    });
    l10n.get(function(err, res) {
        proxy.emit('res', res);
    });
    复制代码

    EventProxy提供了all()方法来订阅多个事件,所有事件触发后侦听器才会被触发。另一个tail()方法在满足条件时只需一次后,如果组合事件中的某个事件再次被触发,侦听器会用最新的数据继续只需。
    after()方法:实现事件在执行多少次后执行侦听器的单一事件组合订阅方式:

    //执行10次data事件后触发侦听器
    var proxy = new EventProxy();
    proxy.after('data', 10, function(datas) {
        //TODO
    })
    复制代码

    EventProxy原理
    EventProxy来源自Backbone的事件模块,它在每个非all的事件触发时都会触发一次all事件

    trigger: functuon(eventName) {
        var list, calls, ev, callback, args;
        var both = 2;
        if(!(calls = this._callbacks)) return;
        while (both--) {
            ev = both?eventName:'all';
            if(list = calls[ev]) {
                for(var i = 0, l = list.length; i < 1; i ++) {
                    if(!(callback = list[i])) {
                        list.splice(i, i);
                        i --;
                        l --;
                    }else {
                        args = both? Array.prototype.slice.call(arguments, 1):argument;
                        callback[0].apply(callback[1] || this, args);
                    }
                }
            }
        }
        return this;
    }
    复制代码

    EventProxy则是将all当做一个事件流的拦截层,在其中注入一些业务来处理单一事件无法解决的异步处理问题。

    5.EventProxy异常处理

    EventProxy提供了fail()和done()两个实例方法来优化异常处理。

    var proxy = new EventProxy();
    proxy.all('tp', 'data', function(tp, data, res) {
        //TODO
    });
    proxy.fail(function(err) {
        //错误处理
    })
    fs.readFile(template_path, 'utf8', proxy.done('tp')); 
    db.query(sql, proxy.done('data'));
    
    proxy.done('tp')等价于
    function(err, data) {
        if(err) {
            return proxy.emit('error', err)
        }
        proxy.emit('tp', data)
    }
    复制代码

    3.2.Promise/Deferred模式

    使用事件的方式时,执行的流程被预先设定。Promise/Deferred模式先执行异步调用,延迟传递处理方式。

    //普通jquery Ajax调用
    $.get('api',{
        success: onSuccess,
        error: onError,
        complete: onComplete
    })
    复制代码

    需要提前预设对应的回调函数

    //Promise/Deferred模式 jquery Ajax调用
    $.get('api')
        .success(onSuccess)
        .error(onError)
        .complete(onComplete);
    复制代码

    Promise/Deferred模式即使不传入回调函数也能执行,传统方法一个事件只能传入一个回调函数,而Deferred对象可以对事件加入任意业务处理逻辑。

    $.get('api')
        .success(onSuccess1)
        .success(onSuccess2);
    复制代码

    CommonJS抽象出了 Promises/A,Promises/B,Promises/D这样的典型异步Promise/Deferred模型。

    1.Promises/A

    Promises/A提议对单个异步操作做出这样的定义:

    1. Promise操作只会处在3种状态的一种:未完成状态、完成状态、失败状态。
    2. Promise的状态只会出现从未完成向完成或者失败状态转换,不能逆向。完成和失败不能互相转换。
    3. Promise状态一旦转换将不能被更改。

    Promise状态转换示意图

    Promises/A API定义比较简单。一个Promise对象只要具备then()方法即可。对应then()的要求:

    1. 接受完成、错误的回调方法。操作完成或错误时,将会调用对应方法。
    2. 可选的支持progress事件回调作为第三个方法。
    3. then()方法只接受function对象,其他的会被忽略。
    4. then()方法继续返回Promise对象,实现链式调用。

    then()方法定义:

    then(fulfilledHandler, errorHandler, progressHandler)  
    复制代码

    Promises/A演示:

    var Promise = function() {
        EventEmitter.call(this);
    };
    util.inherit(Promise, EventEmitter);
    
    Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {
        if(typeof fulfilledHandler === 'function') {
            this.once('success', fulfilledHandler)
        }
        if(typeof errorHandler === 'function') {
            this.once('error', errorHandler)
        }
        if(typeof progressHandler === 'function') {
            this.on('progress', progressHandler)
        }
        return this;
    }
    复制代码

    then()方法将回调函数存放起来,为了完成整改流程,还需要触发执行这些函数的地方,实现这些功能的对象被称为Deferred,即延迟对象:

    var Deferred = function() {
        this.state = 'unfulfilled';
        this.promise = new Promise();
    }
    Deferred.prototype.resolve = function(obj) {
        this.state = 'fulfilled';
        this.promise.emit('success', obj);
    }
    Deferred.prototype.reject = function(obj) {
        this.state = 'faild';
        this.promise.emit('error', obj);
    }
    Deferred.prototype.progress = function(obj) {
        this.promise.emit('progress', obj);
    }
    复制代码

    状态与方法对应关系
    利用Promises/A模式,对一个典型的响应对象进行封装:

    res.setEncoding('utf8');
    res.on('data', function(chunk) {
        //成功
        console.log(chunk)
    });
    res.on('end', function() {
        //失败
    })
    res.on('error', function(err) {
        //progress
    }) 
    //通过改造
    var promisify = function(res) {
        var deferred = new Deferred();
        var res = '';
        res.on('data', function(chunk) {
            res += chunk;
            deferred.progress(chunk);
        })
        res.on('end', function() {
            promise.resovle(res);
        })
        res.on('error',function(err) {
            promise.reject(err)
        })
        return deferred.promise;
    }
    //简便写法
    promisify(res).then(function() {
        //成功
    },function(err) {
        //失败
    }, function(chunk) {
        //progress
    })
    复制代码

    从上面代码可以看出,Deferred主要用于内部,用于维护异步模型的状态;Promise则作用于外部,通过then()方法暴露给外部添加自定义逻辑。

    Promise和Deferred整体关系示意图
    于事件发布/订阅相比Promise/Deferred模式API接口更加简洁,它将不变的部分封装在Deferred中,将可变的部分交个Promise。

    Q模块...

    2.Promise中的多异步协作

    简单原型实现:

    Deferred.prototype.all = function(promises) {
        var count = promises.length;
        var that = this;
        var res = [];
        promises.forEach(function(promise, i) {
            promise.then(function(data) {
                count --;
                res[i] = data;
                if(count === 0) {
                    that.resolve(res);
                }
            }, function(err) {
                that.reject(err);
            })
            return this.promise;
        })
    }
    复制代码

    通过all()方法抽象多个异步操作。只有所有异步操作成功,这个异步操作才算成功,其中有一个失败,整个异步操作就失败。
    (实际使用推荐使用when,Q模块,是对完整的Promise提议的实现)

    3.Promise的进阶

    现有一组纯异步的API,为完成一件串联事代码如下:

    obj.api1(function(val1) {
        obj.api2(val1, function(val2) {
            obj.api3(val2, function(val3) {
                obj.api4(val3, function(val4) {
                    callback(val4);
                })
            })
        })
    })
    复制代码

    使用普通函数将上面代码展开:

    var handler1 = function(val1) {
        obj.api2(val1, handler2);
    }
    var handler2 = function(val2) {
        obj.api3(val2, handler3);
    }
    var handler3 = function(val3) {
        obj.api4(val3, handler4);
    }
    var handler41 = function(val4) {
        callback(val4)
    }
    obj.api1(handler1);
    复制代码

    使用事件机制

    var emitter = new EventEmitter();
    
    emitter.on('step1', function() {
        obj.api1(function(val1) {
            emitter.emit('step2', val1);
        })
    })
    emitter.on('step2', function(val1) {
        obj.api2(val1, function(val2) {
            emitter.emit('step3', val2);
        })
    })
    emitter.on('step3', function(val2) {
        obj.api3(val2, function(val3) {
            emitter.emit('step42', val3);
        })
    })
    emitter.on('step4', function(val3) {
        obj.api4(val3, function(val4) {
            callback(val4);
        })
    })
    emitter.emit('step1');
    复制代码

    使用事件后代码量明显增加,需要一种更好的方式。
    支持序列执行的Promise
    理想的方法---链式调用:

    promise()
        .then(obj.api1)
        .then(obj.api2)
        .then(obj.api3)
        .then(obj.api4)
        .then(function(val4) {
            
        },function(err) {
            
        }).done();
    复制代码

    通过改造代码以实现链式调用:

    var Deferred = function() {
        this.promise = new Promise();
    }
    //完成状态
    Deferred.prototype.resolve = function(obj) {
        var promise = this.promise;
        var handler;
        while((handler = promise.queue.shift())) {
            if(handler && handler.fulfilled) {
                var ret = handler.fulfilled(obj);
                if(ret && ret.isPromise) {
                    ret.queue = promise.queue;
                    this.promise = ret;
                    return;
                }
            }
        }
    }
    //失败状态
    Deferred.prototype.reject = function(err) {
        var promise = this.promise;
        var handler;
        while((handler = promise.queue.shift())) {
            if(handler && handler.error) {
                var ret = handler.error(err);
                if(ret && ret.isPromise) {
                    ret.queue = promise.queue;
                    this.promise = ret;
                    return;
                }
            }
        }
    }
    //生成回调函数
    Deferred.prototype.callback = function() {
        var that = this;
        return function(err, file) {
            if(err) {
                return that.reject(err);
            }else {
                that.resolve(file);
            }
        }
    }
    
    var Promise = function() {
        this.queue = [];
        this.isPromise = true;
    }
    
    Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {
        var handler = {};
        if(typeof fulfilledHandler === 'function') {
            handler.fulfilled = fulfilledHandler;
        }
        if(typeof errorHandler === 'function') {
            handler.errorHandler = errorHandler;
        }
        this.queue.push(handler);
        return this;
    }
    复制代码

    以两次文件读取为例,假设读取第二个文件是依赖第一个文件中的内容:

    var readFile1 = function(file, encoding) {
        var deferred = new Deferred();
        fs.readFile(file, encoding, deferred.callback());
        return deferred.promise;
    }
    var readFile2 = function(file, encoding) {
        var deferred = new Deferred();
        fs.readFile(file, encoding, deferred.callback());
        return deferred.promise;
    }
    readFile1('file1.txt', 'utf8').then(function(file1) {
        return readFile2(file1.trim(), 'utf8');
    }).then(function(file2) {
        //file2
    })
    复制代码

    要让Promise支持链式执行,主要通过两个步骤。

    1. 将所有的回调都存到队列中。
    2. Promise完成时,逐个执行回调,一旦检测到返回了新的Promise对象,就停止执行,然后将当前Deferred对象的promise引用改为新的Promise对象,并将列队中余下的回调转给它。
      将API Promise化 为了更好体验的API,需要较多的准备工作。批量将方法Promise化:
    //smooth(fs.readFile)
    var smooth = function(method) {
        return function() {
            var deferred = new Deferred();
            var ags = Array.prototype.slice.call(argument, 1);
            args.push(deferred.callback());
            method.apply(null, args);
            return deferred.promise;
        }
    }
    复制代码

    上面的两次读取文件可以简化为:

    var readFile = smooth(fs.readFile);
    readFile('file1.txt', 'utf8').then(function(file1) {
        return readFile(file1.trim(), 'utf8');
    }).then(function(file2) {
        
    })
    复制代码

    3.3.ES6 Generator

    书中没有提到这种模式,下面补充一下。
    Generator函数是ES6提供的一个异步解决方案。最大的特点是可以暂停执行。 Generator函数和普通的函数区别有两个, 1:function和函数名之间有一个*号, 2:函数体内部使用了yield表达式
    调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(Iterator Object)需要调用遍历器的next()方法才能使函数继续执行,直到遇到yield方法再次暂停执行。

    function* gen() {
        var a = 10;
        console.log(a);
        yield a ++;
        console.log(a);
    }
    复制代码

    上面是一个Generator函数,运行:

    var g = gen();
    复制代码

    并么有打印出a的值,执行gen()后只是得到了一个遍历器对象。

    g.next();
    //10
    复制代码

    执行遍历器的next()方法后输出了10。

    g.next();
    //11
    复制代码

    再次执行next(),yield后的语句被执行输出10。
    遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值

    使用ES6的Promise和Generator解决恶魔金字塔
    function *readFileStep(path1) {
        let path2 = yield new Promise((resovle, reject) => {
            fs.readFile(path1, 'utf8', (err, data) => {
                resovle(data);
            });
        });
        let path3 = yield new Promise((resovle, reject) => {
            fs.readFile(path2, 'utf8', (err, data) => {
                resovle(data);
            });
        });
        return new Promise((resovle, reject) => {
            fs.readFile(path3, 'utf8', (err, data) => {
                resovle(data);
            });
        });
    }
    function run(it) {
        function go(result) {
            if (result.done) {
                return result.value;
            }
            return result.value.then(function(value) {
                return go(it.next(value));
            });
        }
        return go(it.next());
    };
    
    run(readFileStep('./file1.txt')).then((data) => {
        console.log(data)
    });
    复制代码
    1. 定义一个Generator函数readFileStep,内含三个异步的读取文件yield函数,并且每个函数返回一个Promise。
    2. 实现一个执行器,获取每次异步执行返回的Promise对象的结果,如果结果返回后且遍历器不是完成状态就继续执行next()方法,直到完成返回最终的Promsie对象。
    3. 将Generator函数放入执行器中执行,得到最终的Promsie对象进行操作。

    3.4.ES7 Async/Await

    async函数是对Generator函数的改进,在ES7中出现。Generator函数需要依靠执行器才能执行,async函数自带执行器执行方法与常规函数一样。
    与Generator函数一样在异步操作的时候async函数返回一个Promise对象,使用then()方法进行后续处理。
    使用async实现Generator函数中的样例:

    async function readFileStep(path1) {
        let path2 = await new Promise((resovle, reject) => {
            fs.readFile(path1, 'utf8', (err, data) => {
                resovle(data);
            });
        });
        let path3 = await new Promise((resovle, reject) => {
            fs.readFile(path2, 'utf8', (err, data) => {
                resovle(data);
            });
        });
        return new Promise((resovle, reject) => {
            fs.readFile(path3, 'utf8', (err, data) => {
                resovle(data);
            });
        });
    }
    
    readFileStep('./file1.txt').then((data) => {
        console.log(data)
    });
    复制代码

    只需要像普通函数一样执行readFileStep即可得到最终结果的Promise对象。

    展开全文
  • javascript运行机制

    2018-01-27 21:05:41
    :朴老师对本文做了 评注 ,详细得指出了文中存在的错误说法,建议阅读。) 一、为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么...

    一年前,我写了一篇《什么是 Event Loop?》,谈了我对Event Loop的理解。

    上个月,我偶然看到了Philip Roberts的演讲《Help, I'm stuck in an event-loop》。这才尴尬地发现,自己的理解是错的。我决定重写这个题目,详细、完整、正确地描述JavaScript引擎的内部运行机制。下面就是我的重写。

    进入正文之前,插播一条消息。我的新书《ECMAScript 6入门》出版了(版权页内页1内页2),铜版纸全彩印刷,非常精美,还附有索引(当然价格也比同类书籍略贵一点点)。预览和购买点击这里

    cover

    2014年10月13日更新:本文已经做了较大修改,反映了我现在的认识。关于setTimeout的更多解释和示例,请参阅我正在写的《JavaScript标准参考教程》。)

    2014年10月11日更新:朴灵老师对本文做了评注,详细得指出了文中存在的错误说法,建议阅读。)

    一、为什么JavaScript是单线程?

    JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

    JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

    所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

    二、任务队列

    单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

    如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

    JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

    于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

    具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

    (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

    (2)主线程之外,还存在一个"任务队列"(taskqueue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

    (3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

    (4)主线程不断重复上面的第三步。

    下图就是主线程和任务队列的示意图。

    任务队列

    只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

    三、事件和回调函数

    "任务队列"是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列",就是读取里面有哪些事件。

    "任务队列"中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列",等待主线程读取。

    所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。

    "任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但是,由于存在后文提到的"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

    四、Event Loop

    主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

    为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲《Help, I'm stuck in an event-loop》)。

    Event Loop

    上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

    执行栈中的代码(同步任务),总是在读取"任务队列"(异步任务)之前执行。请看下面这个例子。

    
        var req = new XMLHttpRequest();
        req.open('GET', url);    
        req.onload = function (){};    
        req.onerror = function (){};    
        req.send();
    

    上面代码中的req.send方法是Ajax操作向服务器发送数据,它是一个异步任务,意味着只有当前脚本的所有代码执行完,系统才会去读取"任务队列"。所以,它与下面的写法等价。

    
        var req = new XMLHttpRequest();
        req.open('GET', url);
        req.send();
        req.onload = function (){};    
        req.onerror = function (){};   
    

    也就是说,指定回调函数的部分(onload和onerror),在send()方法的前面或后面无关紧要,因为它们属于执行栈的一部分,系统总是执行完它们,才会去读取"任务队列"。

    五、定时器

    除了放置异步任务的事件,"任务队列"还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做"定时器"(timer)功能,也就是定时执行的代码。

    定时器功能主要由setTimeout()和setInterval()这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeout()。

    setTimeout()接受两个参数,第一个是回调函数,第二个是推迟执行的毫秒数。

    
    console.log(1);
    setTimeout(function(){console.log(2);},1000);
    console.log(3);
    

    上面代码的执行结果是1,3,2,因为setTimeout()将第二行推迟到1000毫秒之后执行。

    如果将setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。

    
    setTimeout(function(){console.log(1);}, 0);
    console.log(2);
    

    上面代码的执行结果总是2,1,因为只有在执行完第二行以后,系统才会去执行"任务队列"中的回调函数。

    总之,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。

    HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。

    需要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。

    六、Node.js的Event Loop

    Node.js也是单线程的Event Loop,但是它的运行机制不同于浏览器环境。

    请看下面的示意图(作者@BusyRich)。

    Node.js

    根据上图,Node.js的运行机制如下。

    (1)V8引擎解析JavaScript脚本。

    (2)解析后的代码,调用Node API。

    (3)libuv库负责NodeAPI的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。

    (4)V8引擎再将结果返回给用户。

    除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与"任务队列"有关的方法:process.nextTicksetImmediate。它们可以帮助我们加深对"任务队列"的理解。

    process.nextTick方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。请看下面的例子(via StackOverflow)。

    
    process.nextTick(function A() {
      console.log(1);
      process.nextTick(function B(){console.log(2);});
    });
    
    setTimeout(function timeout() {
      console.log('TIMEOUT FIRED');
    }, 0)
    // 1
    // 2
    // TIMEOUT FIRED
    

    上面代码中,由于process.nextTick方法指定的回调函数,总是在当前"执行栈"的尾部触发,所以不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前"执行栈"执行。

    现在,再看setImmediate。

    
    setImmediate(function A() {
      console.log(1);
      setImmediate(function B(){console.log(2);});
    });
    
    setTimeout(function timeout() {
      console.log('TIMEOUT FIRED');
    }, 0);
    

    上面代码中,setImmediate与setTimeout(fn,0)各自添加了一个回调函数A和timeout,都是在下一次Event Loop触发。那么,哪个回调函数先执行呢?答案是不确定。运行结果可能是1--TIMEOUT FIRED--2,也可能是TIMEOUT FIRED--1--2。

    令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。

    
    setImmediate(function (){
      setImmediate(function A() {
        console.log(1);
        setImmediate(function B(){console.log(2);});
      });
    
      setTimeout(function timeout() {
        console.log('TIMEOUT FIRED');
      }, 0);
    });
    // 1
    // TIMEOUT FIRED
    // 2
    

    上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1--TIMEOUT FIRED--2,这时函数A一定在timeout前面触发。至于2排在TIMEOUT FIRED的后面(即函数B在timeout后面触发),是因为setImmediate总是将事件注册到下一轮Event Loop,所以函数A和timeout是在同一轮Loop执行,而函数B在下一轮Loop执行。

    我们由此得到了process.nextTick和setImmediate的一个重要区别:多个process.nextTick语句总是在当前"执行栈"一次执行完,多个setImmediate可能则需要多次loop才能执行完。事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了,主线程根本不会去读取"事件队列"!

    
    process.nextTick(function foo() {
      process.nextTick(foo);
    });
    

    事实上,现在要是你写出递归的process.nextTick,Node.js会抛出一个警告,要求你改成setImmediate。

    另外,由于process.nextTick指定的回调函数是在本次"事件循环"触发,而setImmediate指定的是在下次"事件循环"触发,所以很显然,前者总是比后者发生得早,而且执行效率也高(因为不用检查"任务队列")。

    (完)

    一灯学堂

    优达学城

    留言(128条)

    写的不错

    补充:Cluster是node js 多进程的API,是提高js资源使用效率的一种方法。
    API描述:A single instance of Node runs in a single thread. To take advantage of multi-core systems the user will sometimes want to launch a cluster of Node processes to handle the load.

    简言之就是JS只有一个主线程,主线程执行完执行栈的任务后去检查异步的任务队列,如果异步事件触发,则将其加到主线程的执行栈。

    在https://medium.com/@shijuvar/web-development-trends-for-2015-and-beyond-c2d3c1ef5718这篇文章中的关于nodejs的可维护性、可用性和高性能的论证不知道阮一峰大哥怎么看?

    今天发现#atom-shell#做同步ipc调用时,执行栈是可能被挂起的,相当于主线程sleep了,等ipc返回后才会接着执行后续代码。如果挂起的时候同一段代码被event又一次触发,逻辑就可能出错。

    也就是说执行栈可能不能在一个loop内被执行完,因为会被挂起,是否挂起取决于运行环境和执行的操作种类。被挂起后前面注册的callback还是有可能先于执行栈剩余的代码触发。

    补充一下Timer相关的问题:
    1. 其实并不是“有些浏览器规定了setTimeout 的最小时间间隔”,这个最小时间间隔是W3C在HTML标准中规定,规定要求低于4ms的时间间隔算为4ms。
    2. process.nextTick在最新版nodejs中不被推荐使用,推荐使用setImmediate ,原因在于nextTick是在当前帧介绍后立即执行,会阻断IO并且有最大数量限制;而setImmediate不会阻断IO,更像是setTimeout(func, 0)

    @rednax:

    谢谢指出,我还不知道HTML5把4毫秒写入了标准,已经改过来了。

    我查了Node.js 10.0的发布说明( http://blog.nodejs.org/2013/03/11/node-v0-10-0-stable/ ),对于process和setImmediate的差异,还在消化当中。

    (3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
    不同任务会分给不同线程?不是直接调用相关操作系统接口?

    我想转载这篇博客,可以吗?

    我觉得单线程的异步没什么意义。你看一下这个文章:

    http://cnn237111.blog.51cto.com/2359144/1556987

    requestAnimFrame应该是requestAnimationFrame吧。

    单线程效率高是假,简化开发是真。
    跟GUI相关的框架的都采用这种模型,QT、GTK、MFC、Android UI框架、这里的JS都是这样的。

    setImmediate的例子有问题吧,触发A之后并不是直接输出1,而是执行另一个setImmediate,这个setImmediate会把B添加到任务队列尾部,在执行B之前会触发setTimeout的回调,所以例子的输出顺序应该是:

    // TIMEOUT FIRED
    // 1
    // 2

    @numbbbbb:

    谢谢指出,你说得对,例子有错,已经改过来了。

    引用万能小新的发言:

    requestAnimFrame应该是requestAnimationFrame吧。

    谢谢指出,改过来了。

    setImmediate(function A() {
    console.log(1);
    setImmediate(function B(){console.log(2);});
    });

    setTimeout(function timeout() {
    console.log('TIMEOUT FIRED');
    }, 0)
    // 1
    // TIMEOUT FIRED
    // 2
    ===========
    同样的这段代码, 为什么后边的说 Immediate 和 Timeout 的先后顺序不确定呢? 求解释...

    https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5eacbead

    朴灵对您文章的评注。语言虽然略显犀利,但是个人觉得评注的应该是对的。

    @Leo:

    谢谢指出,我修改文章时,脑子糊涂了,已经改过来了。

    门都还没入就来写科普

    打人不打脸,绝交。。

    引用mamamiya的发言:

    https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5eacbead

    朴灵对您文章的评注。语言虽然略显犀利,但是个人觉得评注的应该是对的。

    看了一下不觉得评注对到哪里去,只有吹毛求疵之感。比如同步异步介绍,本来就无大错;比如node图里面的OS operation,推敲一下就可以猜到那是指同步操作(自然不走event loop了);至于watcher啥的,显然只是实现上的特色,即使用同一个queue实现也未尝不可。

    我去,被朴灵狠劈,现在更乱了

    请问你一个问题,你的主业是金融,为什么还花这么多时间在编程上?即使你明知道不管你如何努力地来提高自己的编程水平,也无法改变业余的地位,也得不到什么实际的回报。

    引用rednax的发言:


    看了一下不觉得评注对到哪里去,只有吹毛求疵之感。
    比如同步异步介绍,本来就无大错;比如node图里面的OS operation,推敲一下就可以猜到那是指同步操作(自然不走event loop了);至于watcher啥的,显然只是实现上的特色,即使用同一个queue实现也未尝不可。

    多看几眼还真是

    "setImmediate的另一个重要特点:一次"事件循环"只能触发一个由setImmediate指定的回调函数。"
    这个说法其实也不准确,准确的说是因为setImmediate是在事件循环结束后触发,

    setImmediate(function A() {
    console.log(1);
    setImmediate(function B(){console.log(2);});
    });

    setTimeout(function timeout() {
    console.log('TIMEOUT FIRED');
    }, 0);
    当第一个loop结束后触发第一次setImmediate,此时setImmediate调用之后开始将回调函数加入队列等待下一个loop结束触发, 而不是一次"事件循环"只能触发一个由setImmediate指定的回调函数。多个只要在loop前已经执行掉同样会在一次"事件循环"中一起触发。
    setImmediate(function A() {
    console.log(1);
    });
    setImmediate(function B(){console.log(2);});
    实际上,上面调用2次setImmediate会形成一个immediateQueue链表,loop结束后会执行改链表里所有的函数~

    引用Leo的发言:

    setImmediate(function A() {
    console.log(1);
    setImmediate(function B(){console.log(2);});
    });

    setTimeout(function timeout() {
    console.log('TIMEOUT FIRED');
    }, 0)
    // 1
    // TIMEOUT FIRED
    // 2
    ===========
    同样的这段代码, 为什么后边的说 setImmediate 和 Timeout 的先后顺序不确定呢? 求解释...

    @Leo , node中的底层loop有3种运行模式,DEFAULT, UV_RUN_ONCE,UV_RUN_NOWAIT具体差异详见libuv文档 。 刚启动node解析执行脚本时以UV_RUN_ONCE的模式执行也就是说第一次loop是UV_RUN_ONCE模式,在每一个loop中先检测timers,然后进入io_poll此时会阻塞等待事件发生,事件发生后执行setImmediate中的回调,当以UV_RUN_ONCE模式执行的时候会再次检测timers 。照这个原理来说,setImmediate应该比setTimeout先执行。

    setTimeout(fn,0) 底层实际执行的是 setTimeout(fn,1), 此时会向loop队列中注册一个超时事件,假设注册时当前时间戳是1000,它的过期时间就是1001。按上面的说法1ms 第一次loop执行完后先执行setImmediate回调,再执行timer回调。但是有一点别忘了,您的计算机此时还有其他任务在处理,cpu不是全部用来跑你的node进程的,也就是说虽然cpu很快,但执行指令总得花点时间吧,从setTimeout(fn,0)注册一个1ms的超时事件到第一次执行timers可能就已经花了1ms的时间,此时系统时间已经到1001了,那么对应的timer回调当然就会先于setImmediate执行,所以说Immediate 和 Timeout 的先后顺序不确定。

    引用淘杰的发言:

    当第一个loop结束后触发第一次setImmediate,此时setImmediate调用之后开始将回调函数加入队列等待下一个loop结束触发, 而不是一次"事件循环"只能触发一个由setImmediate指定的回调函数。多个只要在loop前已经执行掉同样会在一次"事件循环"中一起触发。

    谢谢指出,确实如此,已经改正了。

    究竟谁对谁错呢?现在有些混乱了

    引用crazy的发言:

    究竟谁对谁错呢?现在有些混乱了

    普通应用没必要这么吹毛球疵,只要了解一下 process.nextTick 和 setImmediate 的区别就好了,官方的 API 说明已经解释得很好,带实际应用示例。阮先生没有把官方介绍的 process.nextTick 实际用例放上来直接硬剥是有点无趣了。

    我们由此得到了process.nextTick和setImmediate的一个重要区别……事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了……

    不知道哪里来的「事实上」,官方示例已经明确地说「This is not a simple alias to setTimeout(fn, 0)」了,当然也更不可能原本作为 setImmediate 的替代,或者反过来。process.nextTick 是有特殊用途的,并还是单单脱离原有的 event loop。

    引用Jak Wings的发言:

    不知道哪里来的「事实上」,官方示例已经明确地说「This is not a simple alias to setTimeout(fn, 0)」了,当然也更不可能原本作为 setImmediate 的替代,或者反过来。process.nextTick 是有特殊用途的,并还是单单脱离原有的 event loop。

    囧,抱歉,才看了官方发布说明,看来 process.nextTick 是被滥用后才加上 setImmediate 的。不过我还是觉得 API 说明页面的说明比发布那时的好,setImmediate 是和 setTimeout 使用同一个 event loop 的,应该凸出了和 process.nextTick 的不同了,应该也是后来提防大家滥用才这么设定的。

    令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。

    我刚刚用 v0.10.32 和 v0.11.14测试过了,这个也没有保证。我能得到 1-T...-2 和 1-2-T... 两种结果,甚至是 T...-1-2。信不信由你。

    引用Jak Wings的发言:


    我刚刚用 v0.10.32 和 v0.11.14测试过了,这个也没有保证。我能得到 1-T...-2 和 1-2-T... 两种结果,甚至是 T...-1-2。信不信由你。

    我测的结果,一直都是1-T-2。难道这个也不确定…… 

    @淘杰:

    谢谢解释~

    引用淘杰的发言:

    setTimeout(fn,0) 底层实际执行的是 setTimeout(fn,1), 此时会向loop队列中注册一个超时事件,假设注册时当前时间戳是1000,它的过期时间就是1001。按上面的说法1ms 第一次loop执行完后先执行setImmediate回调,再执行timer回调。但是有一点别忘了,您的计算机此时还有其他任务在处理,cpu不是全部用来跑你的node进程的,也就是说虽然cpu很快,但执行指令总得花点时间吧,从setTimeout(fn,0)注册一个1ms的超时事件到第一次执行timers可能就已经花了1ms的时间,此时系统时间已经到1001了,那么对应的timer回调当然就会先于setImmediate执行,所以说Immediate 和 Timeout 的先后顺序不确定。

    这么解释的确合理,定时器的原理都是如此,告诉cpu有个任务在某个时间应该执行。但也只有cpu有空的时候,才能尽快执行才可。

    读完正文觉得很多东西是纯理论的理解,有些可能来源于猜测。

    这篇文章更该是一个学习笔记,文中提到的很多event其实和Javascript语言完全无关,纯粹是浏览器的处理和行为。

    对于任何一个不基于浏览器且支持多线程的程序来讲(比如随便一个C实现的游戏),会发现同样的事件处理比如鼠标操作、键盘快捷键等等,是需要去自己单独实现的,那么也许实现中使用了Event loop,但这一部分从未集成到语言中,对吧?

    理解这个事情更好的办法是真的去实现一个类似的程序。

    看来我需要买书好好学学了。

    引用阮一峰的发言:

    我测的结果,一直都是1-T-2。难道这个也不确定…… 

    对了,V8 引擎能代表所有实现了 ES6 的浏览器的具体实现么?

    引用Jak Wings的发言:

    V8 引擎能代表所有实现了 ES6 的浏览器的具体实现么?

    问题是Node采用了V8

    的确有些说法不太严谨~~~

    引用牛牛的发言:

    请问你一个问题,你的主业是金融,为什么还花这么多时间在编程上?即使你明知道不管你如何努力地来提高自己的编程水平,也无法改变业余的地位,也得不到什么实际的回报。

    请问你一个问题,你的主业是专利局小职员,为什么还花这么多时间在物理上?即使你明知道不管你如何努力地来提高自己的数学水平,也无法改变业余的地位,也得不到什么实际的回报。

    引用爱因斯坦的发言:

    请问你一个问题,你的主业是专利局小职员,为什么还花这么多时间在物理上?即使你明知道不管你如何努力地来提高自己的数学水平,也无法改变业余的地位,也得不到什么实际的回报。

    因为热爱 所以热爱

    阮大侠你就别再祸害程序员了,建议先补一下计算机组成原理,编译原理,算法导论,C语言基础再来出书,不然你出了也没人敢看吧。另外起码要实践一点儿项目。

    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.onload = function (){};
    req.onerror = function (){};
    req.send();

    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.send();
    req.onload = function (){};
    req.onerror = function (){};

    你说这俩等价,等价个毛线啊,req.send();之后如果浏览器有缓存这个函数会立即返回,也就是说不管你是onload还是onerror都不会执行。要先绑定回调再去执行send(),这样才没bug。

    不过瘾

    Js原来还有这么深奥的功能

    引用牛牛的发言:

    请问你一个问题,你的主业是金融,为什么还花这么多时间在编程上?即使你明知道不管你如何努力地来提高自己的编程水平,也无法改变业余的地位,也得不到什么实际的回报。

    阮老师已经是程序员了……

    不正确的文章为什么不删掉

    想不到阮老师去了阿里巴巴做前端阿

    很长一段时间,google日历无法访问,但网上又找不到好用的在线日历软件,偶然间看到您开发的这个简易版google日历,发自内心的高兴,但所有的浏览器都用了,就是无法联接,联的过程中,显示一个进度条,等到100%之后,马上出现一个空白页,提示无法连接。郁闷中!! 如果才能正常联接,期待您的指点。我的邮箱为:qudetong@www5a.com,手机:13501282988.如果可能能话,我愿意为您长期进行测试各项功能

    欢迎兔哥加入大阿里!

    强烈建议博主开个微信公众号!

    引用reducm的发言:

    想不到阮老师去了阿里巴巴做前端阿

    是为了和朴灵PK才来的么?

    好久没更新博客了,甚是想念。

    听说阮先生,要去蚂蚁金服了...
    阮先生好久不更新了,要记得常常更新哦。

    @腊月乘凉:

    确实是这样的

    有没有办法查看Function Object的internal property?

    有一个问题,事件执行的粒度是多大?是一个函数还是一个语句块?
    在大量的异步方法同时操作一个变量的时候,会不会出现类似线程的问题呢?

    var start = new Date();
    console.log(start);

    var print = setTimeout(function() {
    var end = new Date();
    console.log('从开始到执行这个setTimeout函数花掉的时间为', end - start, "ms");
    }, 1000);

    var test = [];
    for(var i = 0; i test.push(i);
    }
    var middle = new Date();
    console.log('执行数组操作所花掉的时间为:',middle - start, 'ms');

    我想请教一下这里的setTimeout会在什么时候执行?我再node里面和浏览器里面分别跑了几次。得到的结果是:
    node:
    执行数组操作所花掉的时间为: 379 ms
    从开始到执行这个setTimeout函数花掉的时间为: 1030ms
    按照您的说法,setTimeout应该是尽可能在能执行的时候执行,这里运算操作只是379ms,没有超过初始设定的1000ms,按道理1030ms不应该是1001ms么?
    浏览器:
    执行数组操作所花掉的时间为: 8016 ms
    从开始到执行这个setTimeout函数花掉的时间为 8017 ms
    这个结果中,运算时间很长,远远超过了1000ms,所以setTimeout不是在1001ms执行的,而是等到元算结束之后立即执行,那么结果奇怪了,这个setTimeout时间设定成1000ms是从什么时候开始的?我的理解是一开始就计时,但是1000ms无法执行,等到数组操作完成之后才能执行,这样计时操作似乎没有存在的意义了。

    原来水平也不过如此。有点水平的人都看得懂朴灵的批注比较准确。

    从这篇文章看得出,阮老师不是科班出身的
    不过非科班的也有非科班的好,希望能够继续有文章看

    阮老师,那如果在ajax的回调函数中,存在setTimeOut函数,这个时候回调函数中还是按照Event loop的机制吗?就是说回调函数相当于主线程,把setTimeOut函数放在任务对象,先执行回调函数的代码再执行执行setTimeout函数?

    阮老师,你的站点统计工具:specificclick好像不工作耶,请须知。

    对您的网站有一些小提议,供您参考。
    1、给页面右下角或者右侧加一个按钮,可以直接跳到最顶层,这样对读者来说很方便,因为一直拖动滚动条很麻烦。同时,这个按钮相对于浏览器窗口是固定的。
    2、给标题下面加上您文章的写作日期。作为读者我感觉这个时间挺重要的。

    嗯,还是比较实用的内容呢,我会长期关注的。

    请不要说你在淘宝工作好吗?淘宝不都是大牛吗?

    淘宝的人都喜欢出书吗?竟然都敢出书?

    引用腊月乘凉的发言:

    阮大侠你就别再祸害程序员了,建议先补一下计算机组成原理,编译原理,算法导论,C语言基础再来出书,不然你出了也没人敢看吧。另外起码要实践一点儿项目。

    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.onload = function (){};
    req.onerror = function (){};
    req.send();

    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.send();
    req.onload = function (){};
    req.onerror = function (){};

    你说这俩等价,等价个毛线啊,req.send();之后如果浏览器有缓存这个函数会立即返回,也就是说不管你是onload还是onerror都不会执行。要先绑定回调再去执行send(),这样才没bug。

    擦,能不能不要绑定宿主环境。

    感觉文章的表述有些乱,比如“不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。”,这句让人理解的意思是“任务队列”主动通知“主线程”。而后面所表达的却是“主线程”在执行栈清空后才会到任务队列中取任务。前后主被动关系矛盾啊。

    还有,“(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。”。这时异步任务还在队列里没执行呢,怎么就会有了运行结果呢?

    烦请阮老师在方便的时候不吝赐教。同时,感谢阮老师每次耐心细致的讲解和分享!

    定时器执行流程还是没讲清楚。。。。

    能说说这个网站是怎么建 的么

    不管扑灵和你都写的云里雾里的。。。不透彻。。

    老师你好,请教个问题,如果是如图示Execution Context Stack所展示的那样,岂不是后来的Context先执行?
    那为何
    console.log('a');
    console.log('b');
    可以得到我们所要的执行顺序呢?

    上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1--TIMEOUT FIRED--2,这时函数A一定在timeout前面触发。

    结果不总是1--TIMEOUT FIRED--2 还可能是TIMEOUT--1--2。

    node 版本v0.12.2 windows 64位系统


    @腊月乘凉:

    的确,这个地方阮老师的说法是不对的。两者并不等价。

    问题图片丢失。

    执行栈(execution context stack)清空的时机是什么?

    引用简陋的以数据的发言:

    感觉文章的表述有些乱,比如“不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。”,这句让人理解的意思是“任务队列”主动通知“主线程”。而后面所表达的却是“主线程”在执行栈清空后才会到任务队列中取任务。前后主被动关系矛盾啊。

    还有,“(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。”。这时异步任务还在队列里没执行呢,怎么就会有了运行结果呢?

    烦请阮老师在方便的时候不吝赐教。同时,感谢阮老师每次耐心细致的讲解和分享!

    同惑啊,“异步任务有了运行结果,就在'任务队列'之中放置一个事件”,这句不大理解呢,文中不是说同步任务清空之后才执行任务队列的么?前面这个异步的结果是哪里的?求解~~

    window.onload = function(){

    document.onmousedown = function(){
    var oInput = document.createElement("input");
    oInput.value = "输入内容";
    document.body.appendChild(oInput);
    oInput.focus();
    oInput.select();
    }
    }

    为什么把onmousedowm换成onclick在ie与chrome下显示效果不同

    大神我有个问题,如何判断主线程结束,可以进入队列。什么时候能第二次进入主线程。

    技术学术自然有需要严谨的地方,老师自然不可能触类旁引,将各个面都涉及的东西讲透,所以有些语言带过,也有确实考虑不周的地方。同样是做技术的,我觉有人跟进技术潮流,出来讲就很好了,至少人家在治学,敢于发表,听取意见。

    文中提到 “如果将setTimeout()的第二个参数设为0,就表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。”,
    提到“setImmediate方法则是在当前"任务队列"的尾部添加事件”,
    又提到“Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面”。

    这三块东西逻辑矛盾了,肯定有谁出错了,求指点。

    所有windows平台的软件都基于消息队列循环方式。浏览器只是普通的windows本地程序,和winform,mfc等都是同样的机制

    读了3遍,还是有一些不太理解的地方。
    比如:同步任务执行完毕之后,才会去执行异步任务。
    那会不会出现这样一种情况,a,b,c,d,e是同步任务。1,2,3是异步任务。
    当a,b,c,d,e 执行完毕之后,主线程立马就去将异步任务1,2,3全部读入执行栈,还是按顺序只读取第一个?
    那如果,a,b,c,d,e执行完毕之后,会不会在某种情况下,c又要重新执行一次?那此时的c算同步任务还是异步任务?
    还有,到底何种任务叫同步任务,何种任务又要异步任务?
    我初浅的理解是:一般浏览器的默认执行行为,比如 dom加载,css渲染之类的都叫同步任务。
    click,onload之类的都叫异步任务。
    理由是:前者在浏览器整个生命周期中,都算一种比较固定的步骤,每一次都要执行。且绝大多数任务都需要等待这些任务执行完毕之后才能执行。也就是说,前者是后者的载体。
    异步任务:说实话,我不太理解。比较耗时的任务?不属于那种生命周期必须执行的任务(比如click)?

    所以,当一个浏览器打开源代码给我看,我不太能指出哪些是同步任务,哪一些又是异步任务。

    但我得到的收获是:javascript是单线程的,开浏览器的时候,会有一些默认每次都要执行的代码属于主任务,它们放在执行堆栈上,当它们执行完毕之后,主线程会去异步队列去找那一些准备好了的任务接着执行。前者执行完毕,循环的主要是后者那些异步队列里的任务了。

    看得我肚子饿了

    这文章误导人啊

    引用简陋的以数据的发言:

    感觉文章的表述有些乱,比如“不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。”,这句让人理解的意思是“任务队列”主动通知“主线程”。而后面所表达的却是“主线程”在执行栈清空后才会到任务队列中取任务。前后主被动关系矛盾啊。

    还有,“(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。”。这时异步任务还在队列里没执行呢,怎么就会有了运行结果呢?

    烦请阮老师在方便的时候不吝赐教。同时,感谢阮老师每次耐心细致的讲解和分享!

    第一个问题阮老师表述可能有问题,但是意思还是执行完js主线程的代码才会去看浏览器任务队列中的事件,再执行js代码中该事件对应的代码。
    第二个问题我的理解是,任务队列里放的是ajax这类的任务,是交给浏览器发起HTTP请求去执行的,当有了返回结果就会在任务队列中增加一个事件表示该ajax请求已经返回了结果,任务队列里的任务和js主线程是同时执行的。 不影响js是单线程的这个结论,只能说浏览器还会提供接口来供js实现异步操作。

    引用青苔的发言:

    老师你好,请教个问题,如果是如图示Execution Context Stack所展示的那样,岂不是后来的Context先执行?
    那为何
    console.log('a');
    console.log('b');
    可以得到我们所要的执行顺序呢?

    Execution Context Stack 存放的是调用关系,你的例子的顺序是console.log('a'); 入栈直接运行后出栈,然后console.log('b');入栈,执行后出栈;
    如果例子是这样:

    function log() {
    console.log(arguments);
    }

    log('a');

    那么出入栈顺序是,log('a'); 入栈,console.log('a');入栈,执行console.log('a');出栈,log('a');出栈;
    如果觉得我说的不是很清楚,建议看看这个视频:http://vimeo.com/96425312

    引用LeeSir的发言:

    第一个问题阮老师表述可能有问题,但是意思还是执行完js主线程的代码才会去看浏览器任务队列中的事件,再执行js代码中该事件对应的代码。
    第二个问题我的理解是,任务队列里放的是ajax这类的任务,是交给浏览器发起HTTP请求去执行的,当有了返回结果就会在任务队列中增加一个事件表示该ajax请求已经返回了结果,任务队列里的任务和js主线程是同时执行的。 不影响js是单线程的这个结论,只能说浏览器还会提供接口来供js实现异步操作。

    还是你解释的比较清楚。

    setImmediate(function (){
    setImmediate(function A() {
    console.log(1);
    setImmediate(function B(){console.log(2);});
    });

    setTimeout(function timeout() {
    console.log('TIMEOUT FIRED');
    }, 0);
    });

    阮老师, 上面的例子, 在测试中也有下面的情况:
    TIMEOUT FIRED
    1
    2

    我的node版本v5.11.0
    关于 setTimeout感觉它会在插队"任务队列"(时间点到了,就优先插队到任务队列队首)

    发现我们培训老师讲的课应该是借鉴了阮老师的文章。。。有的原话都一样。。

    “挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。”
    这句话我有一点不解,这个异步任务是要等所有同步任务都执行完毕了之后进入主线程呢?还是只要有IO设备返回结果就立马进入主线程,可以插队到已在主线程的同步任务前面?

    自定义的回调函数会放到消息队列中吗?

    关于setImmediate和setTimeout的执行顺序,确实是不定的:

    setImmediate(function (){
    setTimeout(function timeout() {
    console.log('同一loop中的setTimeout方法执行,输出:TIMEOUT FIRED');
    }, 0);
    console.log('和 setTimeout、setImmediate 同一层次的代码执行');
    setImmediate(function A() {
    ** console.log('同一loop中的setImmediate 方法执行, 输出:1' );
    setImmediate(function B(){console.log('下一loop中的setImmediate 方法执行, 输出:2');});
    });
    });

    执行结果:
    和 setTimeout、setImmediate 同一层次的代码执行
    同一loop中的setTimeout方法执行,输出:TIMEOUT FIRED
    同一loop中的setImmediate 方法执行, 输出:1
    下一loop中的setImmediate 方法执行, 输出:2

    如果注释掉标记为**的那行代码:
    setImmediate(function (){
    setTimeout(function timeout() {
    console.log('同一loop中的setTimeout方法执行,输出:TIMEOUT FIRED');
    }, 0);
    // console.log('和 setTimeout、setImmediate 同一层次的代码执行');
    setImmediate(function A() {
    console.log('同一loop中的setImmediate 方法执行, 输出:1' );
    setImmediate(function B(){console.log('下一loop中的setImmediate 方法执行, 输出:2');});
    });
    });
    执行结果:
    同一loop中的setImmediate 方法执行, 输出:1
    同一loop中的setTimeout方法执行,输出:TIMEOUT FIRED
    下一loop中的setImmediate 方法执行, 输出:2

    上面的**标记错了,应该是上边那句console:

    setImmediate(function (){
    setTimeout(function timeout() {
    console.log('同一loop中的setTimeout方法执行,输出:TIMEOUT FIRED');
    }, 0);
    ** console.log('和 setTimeout、setImmediate 同一层次的代码执行');
    setImmediate(function A() {
    console.log('同一loop中的setImmediate 方法执行, 输出:1' );
    setImmediate(function B(){console.log('下一loop中的setImmediate 方法执行, 输出:2');});
    });
    });

    文章写得很好,书也写得好!
    http://es6.ruanyifeng.com/#docs/async

    setImmediate(function (){
    setImmediate(function A() {
    console.log('1');
    setImmediate(function B(){console.log('2');});
    });

    setTimeout(function timeout() {
    console.log('3');
    }, 0);
    });

    上面这个代码的输出顺序实际上也是随机的。可能是 1,3,2;也可能是3,1,2.

    在 I/O 操作的回调里,他们的顺序才不是随机的:

    fs.readFile('../../README.md', () => {
    setTimeout(() => {
    console.log('1')
    }, 0);

    setImmediate(() => {
    console.log('2')
    });
    });

    总是先输出2, 再输出1. 文档提到的:
    The main advantage to using setImmediate() over setTimeout() is setImmediate() will always be executed before any timers if scheduled within an I/O cycle, independently of how many timers are present.
    —— https://github.com/nodejs/node/blob/master/doc/topics/event-loop-timers-and-nexttick.md

    呀 朴灵老师批注找不到了

    如果执行栈(主线程)在执行完正要loop任务队列前,主线程的代码又被触发执行了一次会怎么样?比如两次点击,是会先执行任务队列还是主线程的代码

    阮老师是一位不错的践行者,我前端典范。

    引用fans的发言:

    大神我有个问题,如何判断主线程结束,可以进入队列。什么时候能第二次进入主线程。

    `event queue`中新增加event(比如点击) 和 `任务队列`中任务完成触发回调都会再次进入主线程。

    根据MDN上官方的描述:
    > The event loop got its name because of how it's usually implemented, which usually resembles:

    while (queue.waitForMessage()) {
    queue.processNextMessage();
    }

    queue.waitForMessage waits synchronously for a message to arrive if there is none currently.

    这篇文章中大量的关于主线程`主动获取任务事件结束回调`的描述是错误的。回调会让`queue.waitForMessage()`返回`true`,激活新的event loop,从而及时处理回调。

    setImmediate(function (){
    setImmediate(function A() {
    console.log(1);
    setImmediate(function B(){console.log(2);});
    });

    setTimeout(function timeout() {
    console.log('TIMEOUT FIRED');
    }, 0);
    });
    // 1
    // TIMEOUT FIRED
    // 2

    上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1--TIMEOUT FIRED--2

    阮老师,我测试的这个运行结果。也是不确定的,还有可能是TIMEOUT FIRED--1--2

    一直有一个疑问未能解开,Event Loop这个机制是不是由一个单独的(浏览器/node)线程控制,比如setTimeout(()=>{}, 1000)方法,延迟1000ms,是谁来监控这1000ms后加入消息queue的,是浏览器的某个(事件处理)线程么?

    引用yyf的发言:

    一直有一个疑问未能解开,Event Loop这个机制是不是由一个单独的(浏览器/node)线程控制,比如setTimeout(()=>{}, 1000)方法,延迟1000ms,是谁来监控这1000ms后加入消息queue的,是浏览器的某个(事件处理)线程么?

    对的 之前我也很疑惑 如果是单线程只能做一件事 执行到setTimeout 或ajax之后 把他们放入"事件池" 再去执行后续的代码 这个时候就会出现问题 说好的单线程只能做一件事的引擎怎么一边监控事件池的状态一边执行后续的代码呢? 后来偶然看到一位大牛的博客翻译了一篇墙外的文章才突然顿悟(文章地址找不到了)。因为浏览器是多线程啊 当浏览器监控到"事件池"状态更新时会通知改变js引擎 这时候js引擎会在空闲的时候停下来去执行"事件池"里面的回调 所谓空闲的时候是指的是当前引擎执行的语句块上下文执行完毕时的正要执行下一个语句块时的状态 比如你在 setTimeout(()=>{console.log(1000 end)}, 1000) 后面写个for的死循环 因为单线程 你永远也无法得到输出 或是在setTimeout后面写一个函数让其循环1300后它就会在1300后去回调输出而不是1000后

    作为NodeJS的初学者,这是我看到过的最好的NodeJS事件循环机制的解析教程,终于弄懂了,谢谢老师!

    好像挺简单的, 为什么搞得好像很深奥的样子呢

    文章中的这段话:但是,由于存在后文提到的"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

    引起了我很大的疑惑,请楼主参考一下
    http://ejohn.org/blog/how-javascript-timers-work/

    真心搞不懂了。。回调那个对吗?

    > 令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。

    实际上, 应该是只发生在异步 I/O 回调中. 这句话下面给的例子, 我在本地测试输出是有两种可能结果: TIMEOUT FIRED - 1 - 2 和 1 - TIMEOUT FIRED - 2.

    如果是下面的例子

    ```js
    const fs = require('fs')
    fs.readdir(__dirname, () => {
    setImmediate(() => console.log(1))
    setTimeout(() => console.log(2), 0)
    })
    setImmediate(() => console.log('A'))
    setTimeout(() => console.log('A'), 0) // setTimeout 和 setImmediate 结果可对换, 因此都输出 A 就好
    ```

    上面例子结果只有两种可能, 一种是当异步回调在当前事件循环被执行时结果为 A 1 A 2. 一种是异步回调在下一个事件循环被执行, 结果是 A A 1 2. 1 永远先于 2 输出. 2 永远在最后输出. 屡试不爽.

    所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
    这句话说错了吧?
    function a(callback){
    callback();
    while(true){

    }
    }
    a(function(){
    alert("触发了,没有进入异步队列")
    });
    能不能解释一下您说的回调进入异步队列,为什么alert还是会执行?

    @邢坤:

    。。。叫callback就是回调函数了?

    所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。
    个人感觉这里的说法不靠谱,个人理解的回调函数就是在一个函数中调用另外一个函数,只是说异步里使用回调函数的场景很多,但并不能这样("所谓"回调函数"(callback),就是那些会被主线程挂起来的代码")来解释回调函数吧?

    任务队列里面放的应该是回调函数,而非事件吧?那上面写的是callback queue,而且逻辑上这样也更通顺吧?

    似乎还是有问题啊,例子2执行和推理矛盾

    HTML中的自定义事件和Node中的自定义事件是否是异步的?

    引用Hiccup的发言:

    不正确的文章为什么不删掉

    确实啊,不删掉就算了,也不在之前的文章标个大标头提示一下。看了之前那篇,结果再看这篇,开头就是说之前那篇理解有误。能不让人说什么呢?感觉这位博主写的很多不严谨,好多东西都只是引出处再略微表示一下自己的理解,都没有去验证的。不知道为啥名气这么高哦???以后不敢看此人的博客了,生命有限...

    有类似动态动画描述下 Node 的两个方法?

    引用wuliao的发言:

    确实啊,不删掉就算了,也不在之前的文章标个大标头提示一下。看了之前那篇,结果再看这篇,开头就是说之前那篇理解有误。能不让人说什么呢?感觉这位博主写的很多不严谨,好多东西都只是引出处再略微表示一下自己的理解,都没有去验证的。不知道为啥名气这么高哦???以后不敢看此人的博客了,生命有限...

    对于我们这些小白来说,不看阮老师的文章会走更多错误的路,看了阮老师的文章,少走很多弯路。
    而且,阮老师的文章 通俗易懂,有错误也正常啊。
    你自己理解,估计连犯错误的机会都没有吧。能看懂吗?就你这智商?

    牛人多了,能让我们这些小白受益的又有几个?
    天下之大,免费的有几样?除了阳光?空气?还有什么?
    如果你觉得自己非常聪明,那可以不看对吧?

    引用wuliao的发言:

    确实啊,不删掉就算了,也不在之前的文章标个大标头提示一下。看了之前那篇,结果再看这篇,开头就是说之前那篇理解有误。能不让人说什么呢?感觉这位博主写的很多不严谨,好多东西都只是引出处再略微表示一下自己的理解,都没有去验证的。不知道为啥名气这么高哦???以后不敢看此人的博客了,生命有限...

    其实一直以来,阮老师就表明他写的文章只是自己的认识罢了,没有强迫任何一个人认为他如何如何,也没有因为博客的事情和别人撕逼。他只是安安静静自己学习而已。名气大与不大并不是他自己的原因,更不存在故意恶意引导。 不喜欢可以不看,要增强自我辨识能力才是最根本的

    引用twang的发言:

    我想转载这篇博客,可以吗?

    再次找相关资料,来到这里,看到三年多之前自己的评论,有点感慨;
    最伤的是,再次感觉对我有帮助。。。

    这里面的挂起的任务队列的先进先出是不是有问题,还是我的理解有问题,如果这个异步队列是按先进后出,为什么不是谁先有结果而先输出谁,入可以按着先进 出为什么还是按着栈堆的方式 这不是会很有问题吗 很多没完成的任务被输出了一次

    还有为啥用栈的方式 而不是堆的方式 栈的方式是先进后出 如果用堆呢 挂起的时候

    个人感觉说的不全面,并不是只简单地分为主任务和task queue吧,起码还有microtask与macrotask,这几者之间也是有执行顺序的,并且同时影响页面刷新的时机

    我要发表看法

    «-必填

    «-必填,不公开

    «-我信任你,不会填写广告链接

    展开全文
  • 中文拼音排序(web前端实现)

    万次阅读 2013-12-25 11:10:32
    var array = ['武汉', '北京', '上海', '天津']; array.sort( function compareFunction(param1, param2) { return param1.localeCompare(param2, 'zh-Hans-CN', {sensitivity: 'accent'}); } ); array // [...

    var array = ['武汉', '北京', '上海', '天津'];
    array.sort(
        function compareFunction(param1, param2) {
            return param1.localeCompare(param2'zh-Hans-CN', {sensitivity: 'accent'});
        }
    );
    array // ["北京", "上海", "天津", "武汉"]

    localeCompare为最新函数。

    以下为多年前没有提供localeCompare之前的解决方案, 可忽略。

    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


    中文按照拼音排序的原理就是将几千个汉字及其拼音建立映射关系,然后将几千个汉字排序。

    这个办法虽然看似很笨,但因为数据量并不大,实际应用中很靠谱。

    转自: http://xkspace.iteye.com/blog/177473


    test.html

    <html><head>
        <title></title>
        <script type="text/javascript" src="./qswhGB2312.js"></script>
        <script type="text/javascript" src="./SyjSort.js"></script>
        <script language=javascript>

            function init(){
    var sorter=new syj.Sort('mainTable');
                sorter.bindCell(0,'NUMBER');
                sorter.bindCell(1,'STRING_EN',false);
                sorter.bindCell(2,'STRING_CH');
                sorter.bindCell(3,'DATE');
            }
        </script>
    </head>
    <body οnlοad="init();">
    <table border='1' id='mainTable'>
        <tr>
            <th>数字</th>
            <th>英文</th>
            <th>中文</th>
            <th>2000-01-01</th>
        </tr>
        
        <tr>
            <td>2</td>
            <td>bdwed</td>
            <td>王鹏</td>
            <td>2008-08-01</td>
        </tr>
    <tr>
            <td>3</td>
            <td>apsfsdf</td>
            <td>孙钰佳</td>
            <td>1997-07-01</td>
        </tr>
        <tr>
            <td>4</td>
            <td>cdsec</td>
            <td>李广</td>
            <td>2006-03-01</td>
        </tr>
    <tr>
            <td>1</td>
            <td>derq</td>
            <td>重庆</td>
            <td>2006-06-01</td>
        </tr>
    <tr>
            <td>5</td>
            <td>nerq</td>
            <td>邹</td>
            <td>2006-06-01</td>
        </tr>
    <tr>
            <td>8</td>
            <td>e</td>
            <td>严</td>
            <td>2006-06-01</td>
        </tr>
    <tr>
            <td>7</td>
            <td>ferq</td>
            <td>李</td>
            <td>2006-06-01</td>
        </tr>
        <tr>
            <td>6</td>
            <td>kerq</td>
            <td>成龙</td>
            <td>2006-06-01</td>
        </tr>
    </table>
    </body>
    </html>



    SyjSort.js

    if (syj == null) var syj = {};
    /*
      表格排序 author 孙钰佳 2008/02/24 sunyujia@yahoo.cn
      汉字按拼音排序需要qswhGB2312.js拼音库的支持,此拼音库支持的汉字范围为GB2312精简出自qiushuiwuhen
      动态构建的table同样可以排序,只需指定TD的sortObject属性指向待取值的对象(例如input控件)的引用 例cell.sortObject=动态构建出的input对象
      在页面加载时执行如下脚本即可
        var sorter=new syj.Sort('mainTable');
        sorter.bindCell(0,'NUMBER');
        sorter.bindCell(1,'STRING_EN',false);如果列表已经有序指定顺序false正序
        sorter.bindCell(2,'STRING_CH');
        sorter.bindCell(3,'DATE');
    */
    var table
    //tbName待排序的表名start起始tr下标默认1,end结束tr下标默认0
    syj.Sort =function(tbName,start,end){
        this.tbName=tbName;//表名
        this.table=document.getElementById(tbName);//表对象
        this.start=start==null?1:start;//起始tr如果值为1则第一行不参与排序
        this.end=end==null?0:end;//结束tr如果值为2则最后两行不参与排序
        this.heads=this.table.rows[0].cells;
    this.spanSrc=document.createElement("<span>");
        this.spanSrc.innerHTML="▼";//占位用


    }
    //指定待排序的列cellIdx列号下标,desc如果列表已经有序true为倒序false为正序,null为无序,type类型支持类型有DATE,NUMBER,STRING_EN,STRING_CH
    syj.Sort.prototype.bindCell=function(cellIdx,type,desc){
        var c=this.heads[cellIdx],o=this;
        c.style.cursor='hand';
        c.οnclick=function(){
            o.sortCell(this,type); 
            if(o.onclickExt!=null)o.onclickExt(this);//执行扩展的onclickExt事件
        }
        var f=this.spanSrc.cloneNode(true);
        f.style.visibility='hidden';
        c.updateSymbol=function(){ if(c.desc) f.innerHTML="▼";else f.innerHTML="▲";}
        c.desc=desc;
        if(c.desc!=null)c.updateSymbol();
        c.οnmοuseοver=function(){if(c.desc!=null)f.style.visibility='visible';}
        c.οnmοuseοut=function(){f.style.visibility='hidden';}
        c.appendChild(f);
        c.style.textIndent=f.offsetWidth;
    }
    //核心排序方法,采用冒泡排序,使用dom交换数据,不影响内存中cell对象的状态
    syj.Sort.prototype.sortCell=function(cell,type){
        var ips = this.table.all.tags("INPUT"),cks = [];//排序字段 解决交换tr时checkbox的IE bug
        for(var i=0; i < ips.length; i++){if(ips[i].type == "checkbox") cks.push(ips[i], ips[i].checked);}
        if(cell.desc==null) cell.desc=false;else cell.desc=!cell.desc;
        var funcName='CMP_'+type;//根据类型匹配算法的函数名称
        if(this[funcName]==null){alert("类型错误,合法的类型为DATE,NUMBER,STRING_EN,STRING_CH");return ;}
        for (var i = this.start,cIdx=cell.cellIndex,rows=this.table.rows,size = rows.length - this.end; i < size; i++) {
            for (var k = this.start; k < size - 1 - i + this.start; k++) {
                var row1=rows[k],row2=rows[k + 1];
                var x=this.getValue(row1.cells[cIdx]),y=this.getValue(row2.cells[cIdx]);
                if (this[funcName](x,y,cell)>0) 
                    row1.swapNode(row2);
            }
        }
        cell.updateSymbol();
        while(cks.length > 0)cks.shift().checked = cks.shift();
    }
    //数字比较算法
    syj.Sort.prototype.CMP_NUMBER=function(x,y,cell){
        var r="/[^d|.|-]/g";
        x=x.replace(r,"");
        y=y.replace(r,"");
        return this.compare(x*1,y*1,cell);
    }
    //时间比较算法
    syj.Sort.prototype.CMP_DATE=function(x,y,cell){
        var d='1900-01-01';
        var x=this.strToDate(x==''?d:x);
        var y=this.strToDate(y==''?d:y);
        var z=x-y;
        return cell.desc?z*(-1):z;
    }
    //英文字符串算法
    syj.Sort.prototype.CMP_STRING_EN=function(x,y,cell){
        x=this.getFirstChar(x);
        y=this.getFirstChar(y);
        return this.compare(x,y,cell);
    }
    //中文字符串算法
    syj.Sort.prototype.CMP_STRING_CH=function(x,y,cell){
    x=x==""?"":getGB2312Spell(this.getFirstChar(x));
        y=y==""?"":getGB2312Spell(this.getFirstChar(y));
    return this.compare(x,y,cell);
    }
    //简单值比较算法
    syj.Sort.prototype.compare=function(x,y,cell){
        if(x>y) return cell.desc?-1:1;
        else if(x<y) return cell.desc?1:-1;
        else return 0;
    }
    //取字符串的第一个字符
    syj.Sort.prototype.getFirstChar=function(s){
        if(s=="")return "";
        return (s+"").substr(0,1);
    }
    //字符串转成日期类型 格式 MM/dd/YYYY MM-dd-YYYY YYYY/MM/dd YYYY-MM-dd   
    syj.Sort.prototype.strToDate=function(ds){    
        var d = new Date(Date.parse(ds));   
        if (isNaN(d)){    
            var arys= ds.split('-');   
            d = new Date(arys[0],arys[1]-1,arys[2]);   
        }
        return d;   
    }
    //取TD中的值
    syj.Sort.prototype.getValue=function(cell){
        var v;
        if(cell.sortObject!=null) v= cell.sortObject.value!=null?cell.sortObject.value:cell.sortObject.innerText;
        else v= cell.innerText;
        return v.replace(/(^s*)|(s*$)/g,"");
    }&nbsp;&nbsp;


    qswhGB2312.js


    var strGB="啊阿埃挨哎唉哀皑癌蔼矮艾碍爱隘鞍氨安俺按暗岸胺案肮昂盎凹敖熬翱袄傲奥懊澳芭捌扒叭吧笆八疤巴拔跋靶把耙坝霸罢爸白柏百摆佰败拜稗斑班搬扳般颁板版扮拌伴瓣半办绊邦帮梆榜膀绑棒磅蚌镑傍谤苞胞包褒剥薄雹保堡饱宝抱报暴豹鲍爆杯碑悲卑北辈背贝钡倍狈备惫焙被奔苯本笨崩绷甭泵蹦迸逼鼻比鄙笔彼碧蓖蔽毕毙毖币庇痹闭敝弊必辟壁臂避陛鞭边编贬扁便变卞辨辩辫遍标彪膘表鳖憋别瘪彬斌濒滨宾摈兵冰柄丙秉饼炳病并玻菠播拨钵波博勃搏铂箔伯帛舶脖膊渤泊驳捕卜哺补埠不布步簿部怖擦猜裁材才财睬踩采彩菜蔡餐参蚕残惭惨灿苍舱仓沧藏操糙槽曹草厕策侧册测层蹭插叉茬茶查碴搽察岔差诧拆柴豺搀掺蝉馋谗缠铲产阐颤昌猖场尝常长偿肠厂敞畅唱倡超抄钞朝嘲潮巢吵炒车扯撤掣彻澈郴臣辰尘晨忱沉陈趁衬撑称城橙成呈乘程惩澄诚承逞骋秤吃痴持匙池迟弛驰耻齿侈尺赤翅斥炽充冲虫崇宠抽酬畴踌稠愁筹仇绸瞅丑臭初出橱厨躇锄雏滁除楚础储矗搐触处揣川穿椽传船喘串疮窗幢床闯创吹炊捶锤垂春椿醇唇淳纯蠢戳绰疵茨磁雌辞慈瓷词此刺赐次聪葱囱匆从丛凑粗醋簇促蹿篡窜摧崔催脆瘁粹淬翠村存寸磋撮搓措挫错搭达答瘩打大呆歹傣戴带殆代贷袋待逮怠耽担丹单郸掸胆旦氮但惮淡诞弹蛋当挡党荡档刀捣蹈倒岛祷导到稻悼道盗德得的蹬灯登等瞪凳邓堤低滴迪敌笛狄涤翟嫡抵底地蒂第帝弟递缔颠掂滇碘点典靛垫电佃甸店惦奠淀殿碉叼雕凋刁掉吊钓调跌爹碟蝶迭谍叠丁盯叮钉顶鼎锭定订丢东冬董懂动栋侗恫冻洞兜抖斗陡豆逗痘都督毒犊独读堵睹赌杜镀肚度渡妒端短锻段断缎堆兑队对墩吨蹲敦顿囤钝盾遁掇哆多夺垛躲朵跺舵剁惰堕蛾峨鹅俄额讹娥恶厄扼遏鄂饿恩而儿耳尔饵洱二贰发罚筏伐乏阀法珐藩帆番翻樊矾钒繁凡烦反返范贩犯饭泛坊芳方肪房防妨仿访纺放菲非啡飞肥匪诽吠肺废沸费芬酚吩氛分纷坟焚汾粉奋份忿愤粪丰封枫蜂峰锋风疯烽逢冯缝讽奉凤佛否夫敷肤孵扶拂辐幅氟符伏俘服浮涪福袱弗甫抚辅俯釜斧脯腑府腐赴副覆赋复傅付阜父腹负富讣附妇缚咐噶嘎该改概钙盖溉干甘杆柑竿肝赶感秆敢赣冈刚钢缸肛纲岗港杠篙皋高膏羔糕搞镐稿告哥歌搁戈鸽胳疙割革葛格蛤阁隔铬个各给根跟耕更庚羹埂耿梗工攻功恭龚供躬公宫弓巩汞拱贡共钩勾沟苟狗垢构购够辜菇咕箍估沽孤姑鼓古蛊骨谷股故顾固雇刮瓜剐寡挂褂乖拐怪棺关官冠观管馆罐惯灌贯光广逛瑰规圭硅归龟闺轨鬼诡癸桂柜跪贵刽辊滚棍锅郭国果裹过哈骸孩海氦亥害骇酣憨邯韩含涵寒函喊罕翰撼捍旱憾悍焊汗汉夯杭航壕嚎豪毫郝好耗号浩呵喝荷菏核禾和何合盒貉阂河涸赫褐鹤贺嘿黑痕很狠恨哼亨横衡恒轰哄烘虹鸿洪宏弘红喉侯猴吼厚候后呼乎忽瑚壶葫胡蝴狐糊湖弧虎唬护互沪户花哗华猾滑画划化话槐徊怀淮坏欢环桓还缓换患唤痪豢焕涣宦幻荒慌黄磺蝗簧皇凰惶煌晃幌恍谎灰挥辉徽恢蛔回毁悔慧卉惠晦贿秽会烩汇讳诲绘荤昏婚魂浑混豁活伙火获或惑霍货祸击圾基机畸稽积箕肌饥迹激讥鸡姬绩缉吉极棘辑籍集及急疾汲即嫉级挤几脊己蓟技冀季伎祭剂悸济寄寂计记既忌际妓继纪嘉枷夹佳家加荚颊贾甲钾假稼价架驾嫁歼监坚尖笺间煎兼肩艰奸缄茧检柬碱硷拣捡简俭剪减荐槛鉴践贱见键箭件健舰剑饯渐溅涧建僵姜将浆江疆蒋桨奖讲匠酱降蕉椒礁焦胶交郊浇骄娇嚼搅铰矫侥脚狡角饺缴绞剿教酵轿较叫窖揭接皆秸街阶截劫节桔杰捷睫竭洁结解姐戒藉芥界借介疥诫届巾筋斤金今津襟紧锦仅谨进靳晋禁近烬浸尽劲荆兢茎睛晶鲸京惊精粳经井警景颈静境敬镜径痉靖竟竞净炯窘揪究纠玖韭久灸九酒厩救旧臼舅咎就疚鞠拘狙疽居驹菊局咀矩举沮聚拒据巨具距踞锯俱句惧炬剧捐鹃娟倦眷卷绢撅攫抉掘倔爵觉决诀绝均菌钧军君峻俊竣浚郡骏喀咖卡咯开揩楷凯慨刊堪勘坎砍看康慷糠扛抗亢炕考拷烤靠坷苛柯棵磕颗科壳咳可渴克刻客课肯啃垦恳坑吭空恐孔控抠口扣寇枯哭窟苦酷库裤夸垮挎跨胯块筷侩快宽款匡筐狂框矿眶旷况亏盔岿窥葵奎魁傀馈愧溃坤昆捆困括扩廓阔垃拉喇蜡腊辣啦莱来赖蓝婪栏拦篮阑兰澜谰揽览懒缆烂滥琅榔狼廊郎朗浪捞劳牢老佬姥酪烙涝勒乐雷镭蕾磊累儡垒擂肋类泪棱楞冷厘梨犁黎篱狸离漓理李里鲤礼莉荔吏栗丽厉励砾历利傈例俐痢立粒沥隶力璃哩俩联莲连镰廉怜涟帘敛脸链恋炼练粮凉梁粱良两辆量晾亮谅撩聊僚疗燎寥辽潦了撂镣廖料列裂烈劣猎琳林磷霖临邻鳞淋凛赁吝拎玲菱零龄铃伶羚凌灵陵岭领另令溜琉榴硫馏留刘瘤流柳六龙聋咙笼窿隆垄拢陇楼娄搂篓漏陋芦卢颅庐炉掳卤虏鲁麓碌露路赂鹿潞禄录陆戮驴吕铝侣旅履屡缕虑氯律率滤绿峦挛孪滦卵乱掠略抡轮伦仑沦纶论萝螺罗逻锣箩骡裸落洛骆络妈麻玛码蚂马骂嘛吗埋买麦卖迈脉瞒馒蛮满蔓曼慢漫谩芒茫盲氓忙莽猫茅锚毛矛铆卯茂冒帽貌贸么玫枚梅酶霉煤没眉媒镁每美昧寐妹媚门闷们萌蒙檬盟锰猛梦孟眯醚靡糜迷谜弥米秘觅泌蜜密幂棉眠绵冕免勉娩缅面苗描瞄藐秒渺庙妙蔑灭民抿皿敏悯闽明螟鸣铭名命谬摸摹蘑模膜磨摩魔抹末莫墨默沫漠寞陌谋牟某拇牡亩姆母墓暮幕募慕木目睦牧穆拿哪呐钠那娜纳氖乃奶耐奈南男难囊挠脑恼闹淖呢馁内嫩能妮霓倪泥尼拟你匿腻逆溺蔫拈年碾撵捻念娘酿鸟尿捏聂孽啮镊镍涅您柠狞凝宁拧泞牛扭钮纽脓浓农弄奴努怒女暖虐疟挪懦糯诺哦欧鸥殴藕呕偶沤啪趴爬帕怕琶拍排牌徘湃派攀潘盘磐盼畔判叛乓庞旁耪胖抛咆刨炮袍跑泡呸胚培裴赔陪配佩沛喷盆砰抨烹澎彭蓬棚硼篷膨朋鹏捧碰坯砒霹批披劈琵毗啤脾疲皮匹痞僻屁譬篇偏片骗飘漂瓢票撇瞥拼频贫品聘乒坪苹萍平凭瓶评屏坡泼颇婆破魄迫粕剖扑铺仆莆葡菩蒲埔朴圃普浦谱曝瀑期欺栖戚妻七凄漆柒沏其棋奇歧畦崎脐齐旗祈祁骑起岂乞企启契砌器气迄弃汽泣讫掐恰洽牵扦钎铅千迁签仟谦乾黔钱钳前潜遣浅谴堑嵌欠歉枪呛腔羌墙蔷强抢橇锹敲悄桥瞧乔侨巧鞘撬翘峭俏窍切茄且怯窃钦侵亲秦琴勤芹擒禽寝沁青轻氢倾卿清擎晴氰情顷请庆琼穷秋丘邱球求囚酋泅趋区蛆曲躯屈驱渠取娶龋趣去圈颧权醛泉全痊拳犬券劝缺炔瘸却鹊榷确雀裙群然燃冉染瓤壤攘嚷让饶扰绕惹热壬仁人忍韧任认刃妊纫扔仍日戎茸蓉荣融熔溶容绒冗揉柔肉茹蠕儒孺如辱乳汝入褥软阮蕊瑞锐闰润若弱撒洒萨腮鳃塞赛三叁伞散桑嗓丧搔骚扫嫂瑟色涩森僧莎砂杀刹沙纱傻啥煞筛晒珊苫杉山删煽衫闪陕擅赡膳善汕扇缮墒伤商赏晌上尚裳梢捎稍烧芍勺韶少哨邵绍奢赊蛇舌舍赦摄射慑涉社设砷申呻伸身深娠绅神沈审婶甚肾慎渗声生甥牲升绳省盛剩胜圣师失狮施湿诗尸虱十石拾时什食蚀实识史矢使屎驶始式示士世柿事拭誓逝势是嗜噬适仕侍释饰氏市恃室视试收手首守寿授售受瘦兽蔬枢梳殊抒输叔舒淑疏书赎孰熟薯暑曙署蜀黍鼠属术述树束戍竖墅庶数漱恕刷耍摔衰甩帅栓拴霜双爽谁水睡税吮瞬顺舜说硕朔烁斯撕嘶思私司丝死肆寺嗣四伺似饲巳松耸怂颂送宋讼诵搜艘擞嗽苏酥俗素速粟僳塑溯宿诉肃酸蒜算虽隋随绥髓碎岁穗遂隧祟孙损笋蓑梭唆缩琐索锁所塌他它她塔獭挞蹋踏胎苔抬台泰酞太态汰坍摊贪瘫滩坛檀痰潭谭谈坦毯袒碳探叹炭汤塘搪堂棠膛唐糖倘躺淌趟烫掏涛滔绦萄桃逃淘陶讨套特藤腾疼誊梯剔踢锑提题蹄啼体替嚏惕涕剃屉天添填田甜恬舔腆挑条迢眺跳贴铁帖厅听烃汀廷停亭庭挺艇通桐酮瞳同铜彤童桶捅筒统痛偷投头透凸秃突图徒途涂屠土吐兔湍团推颓腿蜕褪退吞屯臀拖托脱鸵陀驮驼椭妥拓唾挖哇蛙洼娃瓦袜歪外豌弯湾玩顽丸烷完碗挽晚皖惋宛婉万腕汪王亡枉网往旺望忘妄威巍微危韦违桅围唯惟为潍维苇萎委伟伪尾纬未蔚味畏胃喂魏位渭谓尉慰卫瘟温蚊文闻纹吻稳紊问嗡翁瓮挝蜗涡窝我斡卧握沃巫呜钨乌污诬屋无芜梧吾吴毋武五捂午舞伍侮坞戊雾晤物勿务悟误昔熙析西硒矽晰嘻吸锡牺稀息希悉膝夕惜熄烯溪汐犀檄袭席习媳喜铣洗系隙戏细瞎虾匣霞辖暇峡侠狭下厦夏吓掀锨先仙鲜纤咸贤衔舷闲涎弦嫌显险现献县腺馅羡宪陷限线相厢镶香箱襄湘乡翔祥详想响享项巷橡像向象萧硝霄削哮嚣销消宵淆晓小孝校肖啸笑效楔些歇蝎鞋协挟携邪斜胁谐写械卸蟹懈泄泻谢屑薪芯锌欣辛新忻心信衅星腥猩惺兴刑型形邢行醒幸杏性姓兄凶胸匈汹雄熊休修羞朽嗅锈秀袖绣墟戌需虚嘘须徐许蓄酗叙旭序畜恤絮婿绪续轩喧宣悬旋玄选癣眩绚靴薛学穴雪血勋熏循旬询寻驯巡殉汛训讯逊迅压押鸦鸭呀丫芽牙蚜崖衙涯雅哑亚讶焉咽阉烟淹盐严研蜒岩延言颜阎炎沿奄掩眼衍演艳堰燕厌砚雁唁彦焰宴谚验殃央鸯秧杨扬佯疡羊洋阳氧仰痒养样漾邀腰妖瑶摇尧遥窑谣姚咬舀药要耀椰噎耶爷野冶也页掖业叶曳腋夜液一壹医揖铱依伊衣颐夷遗移仪胰疑沂宜姨彝椅蚁倚已乙矣以艺抑易邑屹亿役臆逸肄疫亦裔意毅忆义益溢诣议谊译异翼翌绎茵荫因殷音阴姻吟银淫寅饮尹引隐印英樱婴鹰应缨莹萤营荧蝇迎赢盈影颖硬映哟拥佣臃痈庸雍踊蛹咏泳涌永恿勇用幽优悠忧尤由邮铀犹油游酉有友右佑釉诱又幼迂淤于盂榆虞愚舆余俞逾鱼愉渝渔隅予娱雨与屿禹宇语羽玉域芋郁吁遇喻峪御愈欲狱育誉浴寓裕预豫驭鸳渊冤元垣袁原援辕园员圆猿源缘远苑愿怨院曰约越跃钥岳粤月悦阅耘云郧匀陨允运蕴酝晕韵孕匝砸杂栽哉灾宰载再在咱攒暂赞赃脏葬遭糟凿藻枣早澡蚤躁噪造皂灶燥责择则泽贼怎增憎曾赠扎喳渣札轧铡闸眨栅榨咋乍炸诈摘斋宅窄债寨瞻毡詹粘沾盏斩辗崭展蘸栈占战站湛绽樟章彰漳张掌涨杖丈帐账仗胀瘴障招昭找沼赵照罩兆肇召遮折哲蛰辙者锗蔗这浙珍斟真甄砧臻贞针侦枕疹诊震振镇阵蒸挣睁征狰争怔整拯正政帧症郑证芝枝支吱蜘知肢脂汁之织职直植殖执值侄址指止趾只旨纸志挚掷至致置帜峙制智秩稚质炙痔滞治窒中盅忠钟衷终种肿重仲众舟周州洲诌粥轴肘帚咒皱宙昼骤珠株蛛朱猪诸诛逐竹烛煮拄瞩嘱主著柱助蛀贮铸筑住注祝驻抓爪拽专砖转撰赚篆桩庄装妆撞壮状椎锥追赘坠缀谆准捉拙卓桌琢茁酌啄着灼浊兹咨资姿滋淄孜紫仔籽滓子自渍字鬃棕踪宗综总纵邹走奏揍租足卒族祖诅阻组钻纂嘴醉最罪尊遵昨左佐柞做作坐座亍丌兀丐廿卅丕亘丞鬲孬噩丨禺丿匕乇夭爻卮氐囟胤馗毓睾鼗丶亟鼐乜乩亓芈孛啬嘏仄厍厝厣厥厮靥赝匚叵匦匮匾赜卦卣刂刈刎刭刳刿剀剌剞剡剜蒯剽劂劁劐劓冂罔亻仃仉仂仨仡仫仞伛仳伢佤仵伥伧伉伫佞佧攸佚佝佟佗伲伽佶佴侑侉侃侏佾佻侪佼侬侔俦俨俪俅俚俣俜俑俟俸倩偌俳倬倏倮倭俾倜倌倥倨偾偃偕偈偎偬偻傥傧傩傺僖儆僭僬僦僮儇儋仝氽佘佥俎龠汆籴兮巽黉馘冁夔勹匍訇匐凫夙兕亠兖亳衮袤亵脔裒禀嬴蠃羸冫冱冽冼凇冖冢冥讠讦讧讪讴讵讷诂诃诋诏诎诒诓诔诖诘诙诜诟诠诤诨诩诮诰诳诶诹诼诿谀谂谄谇谌谏谑谒谔谕谖谙谛谘谝谟谠谡谥谧谪谫谮谯谲谳谵谶卩卺阝阢阡阱阪阽阼陂陉陔陟陧陬陲陴隈隍隗隰邗邛邝邙邬邡邴邳邶邺邸邰郏郅邾郐郄郇郓郦郢郜郗郛郫郯郾鄄鄢鄞鄣鄱鄯鄹酃酆刍奂劢劬劭劾哿勐勖勰叟燮矍廴凵凼鬯厶弁畚巯坌垩垡塾墼壅壑圩圬圪圳圹圮圯坜圻坂坩垅坫垆坼坻坨坭坶坳垭垤垌垲埏垧垴垓垠埕埘埚埙埒垸埴埯埸埤埝堋堍埽埭堀堞堙塄堠塥塬墁墉墚墀馨鼙懿艹艽艿芏芊芨芄芎芑芗芙芫芸芾芰苈苊苣芘芷芮苋苌苁芩芴芡芪芟苄苎芤苡茉苷苤茏茇苜苴苒苘茌苻苓茑茚茆茔茕苠苕茜荑荛荜茈莒茼茴茱莛荞茯荏荇荃荟荀茗荠茭茺茳荦荥荨茛荩荬荪荭荮莰荸莳莴莠莪莓莜莅荼莶莩荽莸荻莘莞莨莺莼菁萁菥菘堇萘萋菝菽菖萜萸萑萆菔菟萏萃菸菹菪菅菀萦菰菡葜葑葚葙葳蒇蒈葺蒉葸萼葆葩葶蒌蒎萱葭蓁蓍蓐蓦蒽蓓蓊蒿蒺蓠蒡蒹蒴蒗蓥蓣蔌甍蔸蓰蔹蔟蔺蕖蔻蓿蓼蕙蕈蕨蕤蕞蕺瞢蕃蕲蕻薤薨薇薏蕹薮薜薅薹薷薰藓藁藜藿蘧蘅蘩蘖蘼廾弈夼奁耷奕奚奘匏尢尥尬尴扌扪抟抻拊拚拗拮挢拶挹捋捃掭揶捱捺掎掴捭掬掊捩掮掼揲揸揠揿揄揞揎摒揆掾摅摁搋搛搠搌搦搡摞撄摭撖摺撷撸撙撺擀擐擗擤擢攉攥攮弋忒甙弑卟叱叽叩叨叻吒吖吆呋呒呓呔呖呃吡呗呙吣吲咂咔呷呱呤咚咛咄呶呦咝哐咭哂咴哒咧咦哓哔呲咣哕咻咿哌哙哚哜咩咪咤哝哏哞唛哧唠哽唔哳唢唣唏唑唧唪啧喏喵啉啭啁啕唿啐唼唷啖啵啶啷唳唰啜喋嗒喃喱喹喈喁喟啾嗖喑啻嗟喽喾喔喙嗪嗷嗉嘟嗑嗫嗬嗔嗦嗝嗄嗯嗥嗲嗳嗌嗍嗨嗵嗤辔嘞嘈嘌嘁嘤嘣嗾嘀嘧嘭噘嘹噗嘬噍噢噙噜噌噔嚆噤噱噫噻噼嚅嚓嚯囔囗囝囡囵囫囹囿圄圊圉圜帏帙帔帑帱帻帼帷幄幔幛幞幡岌屺岍岐岖岈岘岙岑岚岜岵岢岽岬岫岱岣峁岷峄峒峤峋峥崂崃崧崦崮崤崞崆崛嵘崾崴崽嵬嵛嵯嵝嵫嵋嵊嵩嵴嶂嶙嶝豳嶷巅彳彷徂徇徉後徕徙徜徨徭徵徼衢彡犭犰犴犷犸狃狁狎狍狒狨狯狩狲狴狷猁狳猃狺狻猗猓猡猊猞猝猕猢猹猥猬猸猱獐獍獗獠獬獯獾舛夥飧夤夂饣饧饨饩饪饫饬饴饷饽馀馄馇馊馍馐馑馓馔馕庀庑庋庖庥庠庹庵庾庳赓廒廑廛廨廪膺忄忉忖忏怃忮怄忡忤忾怅怆忪忭忸怙怵怦怛怏怍怩怫怊怿怡恸恹恻恺恂恪恽悖悚悭悝悃悒悌悛惬悻悱惝惘惆惚悴愠愦愕愣惴愀愎愫慊慵憬憔憧憷懔懵忝隳闩闫闱闳闵闶闼闾阃阄阆阈阊阋阌阍阏阒阕阖阗阙阚丬爿戕氵汔汜汊沣沅沐沔沌汨汩汴汶沆沩泐泔沭泷泸泱泗沲泠泖泺泫泮沱泓泯泾洹洧洌浃浈洇洄洙洎洫浍洮洵洚浏浒浔洳涑浯涞涠浞涓涔浜浠浼浣渚淇淅淞渎涿淠渑淦淝淙渖涫渌涮渫湮湎湫溲湟溆湓湔渲渥湄滟溱溘滠漭滢溥溧溽溻溷滗溴滏溏滂溟潢潆潇漤漕滹漯漶潋潴漪漉漩澉澍澌潸潲潼潺濑濉澧澹澶濂濡濮濞濠濯瀚瀣瀛瀹瀵灏灞宀宄宕宓宥宸甯骞搴寤寮褰寰蹇謇辶迓迕迥迮迤迩迦迳迨逅逄逋逦逑逍逖逡逵逶逭逯遄遑遒遐遨遘遢遛暹遴遽邂邈邃邋彐彗彖彘尻咫屐屙孱屣屦羼弪弩弭艴弼鬻屮妁妃妍妩妪妣妗姊妫妞妤姒妲妯姗妾娅娆姝娈姣姘姹娌娉娲娴娑娣娓婀婧婊婕娼婢婵胬媪媛婷婺媾嫫媲嫒嫔媸嫠嫣嫱嫖嫦嫘嫜嬉嬗嬖嬲嬷孀尕尜孚孥孳孑孓孢驵驷驸驺驿驽骀骁骅骈骊骐骒骓骖骘骛骜骝骟骠骢骣骥骧纟纡纣纥纨纩纭纰纾绀绁绂绉绋绌绐绔绗绛绠绡绨绫绮绯绱绲缍绶绺绻绾缁缂缃缇缈缋缌缏缑缒缗缙缜缛缟缡缢缣缤缥缦缧缪缫缬缭缯缰缱缲缳缵幺畿巛甾邕玎玑玮玢玟珏珂珑玷玳珀珉珈珥珙顼琊珩珧珞玺珲琏琪瑛琦琥琨琰琮琬琛琚瑁瑜瑗瑕瑙瑷瑭瑾璜璎璀璁璇璋璞璨璩璐璧瓒璺韪韫韬杌杓杞杈杩枥枇杪杳枘枧杵枨枞枭枋杷杼柰栉柘栊柩枰栌柙枵柚枳柝栀柃枸柢栎柁柽栲栳桠桡桎桢桄桤梃栝桕桦桁桧桀栾桊桉栩梵梏桴桷梓桫棂楮棼椟椠棹椤棰椋椁楗棣椐楱椹楠楂楝榄楫榀榘楸椴槌榇榈槎榉楦楣楹榛榧榻榫榭槔榱槁槊槟榕槠榍槿樯槭樗樘橥槲橄樾檠橐橛樵檎橹樽樨橘橼檑檐檩檗檫猷獒殁殂殇殄殒殓殍殚殛殡殪轫轭轱轲轳轵轶轸轷轹轺轼轾辁辂辄辇辋辍辎辏辘辚軎戋戗戛戟戢戡戥戤戬臧瓯瓴瓿甏甑甓攴旮旯旰昊昙杲昃昕昀炅曷昝昴昱昶昵耆晟晔晁晏晖晡晗晷暄暌暧暝暾曛曜曦曩贲贳贶贻贽赀赅赆赈赉赇赍赕赙觇觊觋觌觎觏觐觑牮犟牝牦牯牾牿犄犋犍犏犒挈挲掰搿擘耄毪毳毽毵毹氅氇氆氍氕氘氙氚氡氩氤氪氲攵敕敫牍牒牖爰虢刖肟肜肓肼朊肽肱肫肭肴肷胧胨胩胪胛胂胄胙胍胗朐胝胫胱胴胭脍脎胲胼朕脒豚脶脞脬脘脲腈腌腓腴腙腚腱腠腩腼腽腭腧塍媵膈膂膑滕膣膪臌朦臊膻臁膦欤欷欹歃歆歙飑飒飓飕飙飚殳彀毂觳斐齑斓於旆旄旃旌旎旒旖炀炜炖炝炻烀炷炫炱烨烊焐焓焖焯焱煳煜煨煅煲煊煸煺熘熳熵熨熠燠燔燧燹爝爨灬焘煦熹戾戽扃扈扉礻祀祆祉祛祜祓祚祢祗祠祯祧祺禅禊禚禧禳忑忐怼恝恚恧恁恙恣悫愆愍慝憩憝懋懑戆肀聿沓泶淼矶矸砀砉砗砘砑斫砭砜砝砹砺砻砟砼砥砬砣砩硎硭硖硗砦硐硇硌硪碛碓碚碇碜碡碣碲碹碥磔磙磉磬磲礅磴礓礤礞礴龛黹黻黼盱眄眍盹眇眈眚眢眙眭眦眵眸睐睑睇睃睚睨睢睥睿瞍睽瞀瞌瞑瞟瞠瞰瞵瞽町畀畎畋畈畛畲畹疃罘罡罟詈罨罴罱罹羁罾盍盥蠲钅钆钇钋钊钌钍钏钐钔钗钕钚钛钜钣钤钫钪钭钬钯钰钲钴钶钷钸钹钺钼钽钿铄铈铉铊铋铌铍铎铐铑铒铕铖铗铙铘铛铞铟铠铢铤铥铧铨铪铩铫铮铯铳铴铵铷铹铼铽铿锃锂锆锇锉锊锍锎锏锒锓锔锕锖锘锛锝锞锟锢锪锫锩锬锱锲锴锶锷锸锼锾锿镂锵镄镅镆镉镌镎镏镒镓镔镖镗镘镙镛镞镟镝镡镢镤镥镦镧镨镩镪镫镬镯镱镲镳锺矧矬雉秕秭秣秫稆嵇稃稂稞稔稹稷穑黏馥穰皈皎皓皙皤瓞瓠甬鸠鸢鸨鸩鸪鸫鸬鸲鸱鸶鸸鸷鸹鸺鸾鹁鹂鹄鹆鹇鹈鹉鹋鹌鹎鹑鹕鹗鹚鹛鹜鹞鹣鹦鹧鹨鹩鹪鹫鹬鹱鹭鹳疒疔疖疠疝疬疣疳疴疸痄疱疰痃痂痖痍痣痨痦痤痫痧瘃痱痼痿瘐瘀瘅瘌瘗瘊瘥瘘瘕瘙瘛瘼瘢瘠癀瘭瘰瘿瘵癃瘾瘳癍癞癔癜癖癫癯翊竦穸穹窀窆窈窕窦窠窬窨窭窳衤衩衲衽衿袂袢裆袷袼裉裢裎裣裥裱褚裼裨裾裰褡褙褓褛褊褴褫褶襁襦襻疋胥皲皴矜耒耔耖耜耠耢耥耦耧耩耨耱耋耵聃聆聍聒聩聱覃顸颀颃颉颌颍颏颔颚颛颞颟颡颢颥颦虍虔虬虮虿虺虼虻蚨蚍蚋蚬蚝蚧蚣蚪蚓蚩蚶蛄蚵蛎蚰蚺蚱蚯蛉蛏蚴蛩蛱蛲蛭蛳蛐蜓蛞蛴蛟蛘蛑蜃蜇蛸蜈蜊蜍蜉蜣蜻蜞蜥蜮蜚蜾蝈蜴蜱蜩蜷蜿螂蜢蝽蝾蝻蝠蝰蝌蝮螋蝓蝣蝼蝤蝙蝥螓螯螨蟒蟆螈螅螭螗螃螫蟥螬螵螳蟋蟓螽蟑蟀蟊蟛蟪蟠蟮蠖蠓蟾蠊蠛蠡蠹蠼缶罂罄罅舐竺竽笈笃笄笕笊笫笏筇笸笪笙笮笱笠笥笤笳笾笞筘筚筅筵筌筝筠筮筻筢筲筱箐箦箧箸箬箝箨箅箪箜箢箫箴篑篁篌篝篚篥篦篪簌篾篼簏簖簋簟簪簦簸籁籀臾舁舂舄臬衄舡舢舣舭舯舨舫舸舻舳舴舾艄艉艋艏艚艟艨衾袅袈裘裟襞羝羟羧羯羰羲籼敉粑粝粜粞粢粲粼粽糁糇糌糍糈糅糗糨艮暨羿翎翕翥翡翦翩翮翳糸絷綦綮繇纛麸麴赳趄趔趑趱赧赭豇豉酊酐酎酏酤酢酡酰酩酯酽酾酲酴酹醌醅醐醍醑醢醣醪醭醮醯醵醴醺豕鹾趸跫踅蹙蹩趵趿趼趺跄跖跗跚跞跎跏跛跆跬跷跸跣跹跻跤踉跽踔踝踟踬踮踣踯踺蹀踹踵踽踱蹉蹁蹂蹑蹒蹊蹰蹶蹼蹯蹴躅躏躔躐躜躞豸貂貊貅貘貔斛觖觞觚觜觥觫觯訾謦靓雩雳雯霆霁霈霏霎霪霭霰霾龀龃龅龆龇龈龉龊龌黾鼋鼍隹隼隽雎雒瞿雠銎銮鋈錾鍪鏊鎏鐾鑫鱿鲂鲅鲆鲇鲈稣鲋鲎鲐鲑鲒鲔鲕鲚鲛鲞鲟鲠鲡鲢鲣鲥鲦鲧鲨鲩鲫鲭鲮鲰鲱鲲鲳鲴鲵鲶鲷鲺鲻鲼鲽鳄鳅鳆鳇鳊鳋鳌鳍鳎鳏鳐鳓鳔鳕鳗鳘鳙鳜鳝鳟鳢靼鞅鞑鞒鞔鞯鞫鞣鞲鞴骱骰骷鹘骶骺骼髁髀髅髂髋髌髑魅魃魇魉魈魍魑飨餍餮饕饔髟髡髦髯髫髻髭髹鬈鬏鬓鬟鬣麽麾縻麂麇麈麋麒鏖麝麟黛黜黝黠黟黢黩黧黥黪黯鼢鼬鼯鼹鼷鼽鼾齄";


    var qswhSpell=["a",0,"ai",2,"an",15,"ang",24,"ao",27,"ba",36,"bai",54,"ban",62,"bang",77,"bao",89,"bei",106,"ben",121,"beng",125,"bi",131,"bian",155,"biao",167,"bie",171,"bin",175,"bing",181,"bo",190,"bu",211,"ca",220,"cai",221,"can",232,"cang",239,"cao",244,"ce",249,"ceng",254,"cha",256,"chai",267,"chan",270,"chang",280,"chao",293,"che",302,"chen",308,"cheng",318,"chi",333,"chong",349,"chou",354,"chu",366,"chuai",382,"chuan",383,"chuang",390,"chui",396,"chun",401,"chuo",408,"ci",410,"cong",422,"cou",428,"cu",429,"cuan",433,"cui",436,"cun",444,"cuo",447,"da",453,"dai",459,"dan",471,"dang",486,"dao",491,"de",503,"deng",506,"di",513,"dian",532,"diao",548,"die",557,"ding",564,"diu",573,"dong",574,"dou",584,"du",591,"duan",606,"dui",612,"dun",616,"duo",625,"e",637,"en",650,"er",651,"fa",659,"fan",667,"fang",684,"fei",695,"fen",707,"feng",722,"fo",737,"fou",738,"fu",739,"ga",784,"gai",786,"gan",792,"gang",803,"gao",812,"ge",822,"gei",839,"gen",840,"geng",842,"gong",849,"gou",864,"gu",873,"gua",891,"guai",897,"guan",900,"guang",911,"gui",914,"gun",930,"guo",933,"ha",939,"hai",940,"han",947,"hang",966,"hao",969,"he",978,"hei",996,"hen",998,"heng",1002,"hong",1007,"hou",1016,"hu",1023,"hua",1041,"huai",1050,"huan",1055,"huang",1069,"hui",1083,"hun",1104,"huo",1110,"ji",1120,"jia",1173,"jian",1190,"jiang",1230,"jiao",1243,"jie",1271,"jin",1298,"jing",1318,"jiong",1343,"jiu",1345,"ju",1362,"juan",1387,"jue",1394,"jun",1404,"ka",1415,"kai",1419,"kan",1424,"kang",1430,"kao",1437,"ke",1441,"ken",1456,"keng",1460,"kong",1462,"kou",1466,"ku",1470,"kua",1477,"kuai",1482,"kuan",1486,"kuang",1488,"kui",1496,"kun",1507,"kuo",1511,"la",1515,"lai",1522,"lan",1525,"lang",1540,"lao",1547,"le",1556,"lei",1558,"leng",1569,"li",1572,"lia",1606,"lian",1607,"liang",1621,"liao",1632,"lie",1645,"lin",1650,"ling",1662,"liu",1676,"long",1687,"lou",1696,"lu",1702,"lv",1722,"luan",1736,"lue",1742,"lun",1744,"luo",1751,"ma",1763,"mai",1772,"man",1778,"mang",1787,"mao",1793,"me",1805,"mei",1806,"men",1822,"meng",1825,"mi",1833,"mian",1847,"miao",1856,"mie",1864,"min",1866,"ming",1872,"miu",1878,"mo",1879,"mou",1896,"mu",1899,"na",1914,"nai",1921,"nan",1926,"nang",1929,"nao",1930,"ne",1935,"nei",1936,"nen",1938,"neng",1939,"ni",1940,"nian",1951,"niang",1958,"niao",1960,"nie",1962,"nin",1969,"ning",1970,"niu",1976,"nong",1980,"nu",1984,"nv",1987,"nuan",1988,"nue",1989,"nuo",1991,"o",1995,"ou",1996,"pa",2003,"pai",2009,"pan",2015,"pang",2023,"pao",2028,"pei",2035,"pen",2044,"peng",2046,"pi",2060,"pian",2077,"piao",2081,"pie",2085,"pin",2087,"ping",2092,"po",2101,"pu",2110,"qi",2125,"qia",2161,"qian",2164,"qiang",2186,"qiao",2194,"qie",2209,"qin",2214,"qing",2225,"qiong",2238,"qiu",2240,"qu",2248,"quan",2261,"que",2272,"qun",2280,"ran",2282,"rang",2286,"rao",2291,"re",2294,"ren",2296,"reng",2306,"ri",2308,"rong",2309,"rou",2319,"ru",2322,"ruan",2332,"rui",2334,"run",2337,"ruo",2339,"sa",2341,"sai",2344,"san",2348,"sang",2352,"sao",2355,"se",2359,"sen",2362,"seng",2363,"sha",2364,"shai",2373,"shan",2375,"shang",2391,"shao",2399,"she",2410,"shen",2422,"sheng",2438,"shi",2449,"shou",2496,"shu",2506,"shua",2539,"shuai",2541,"shuan",2545,"shuang",2547,"shui",2550,"shun",2554,"shuo",2558,"si",2562,"song",2578,"sou",2586,"su",2589,"suan",2602,"sui",2605,"sun",2616,"suo",2619,"ta",2627,"tai",2636,"tan",2645,"tang",2663,"tao",2676,"te",2687,"teng",2688,"ti",2692,"tian",2707,"tiao",2715,"tie",2720,"ting",2723,"tong",2733,"tou",2746,"tu",2750,"tuan",2761,"tui",2763,"tun",2769,"tuo",2772,"wa",2783,"wai",2790,"wan",2792,"wang",2809,"wei",2819,"wen",2852,"weng",2862,"wo",2865,"wu",2874,"xi",2903,"xia",2938,"xian",2951,"xiang",2977,"xiao",2997,"xie",3015,"xin",3036,"xing",3046,"xiong",3061,"xiu",3068,"xu",3077,"xuan",3096,"xue",3106,"xun",3112,"ya",3126,"yan",3142,"yang",3175,"yao",3192,"ye",3207,"yi",3222,"yin",3275,"ying",3291,"yo",3309,"yong",3310,"you",3325,"yu",3346,"yuan",3390,"yue",3410,"yun",3420,"za",3432,"zai",3435,"zan",3442,"zang",3446,"zao",3449,"ze",3463,"zei",3467,"zen",3468,"zeng",3469,"zha",3473,"zhai",3487,"zhan",3493,"zhang",3510,"zhao",3525,"zhe",3535,"zhen",3545,"zheng",3561,"zhi",3576,"zhong",3619,"zhou",3630,"zhu",3644,"zhua",3670,"zhuai",3672,"zhuan",3673,"zhuang",3679,"zhui",3686,"zhun",3692,"zhuo",3694,"zi",3705,"zong",3720,"zou",3727,"zu",3731,"zuan",3739,"zui",3741,"zun",3745,"zuo",3747];


    function UrlEncode(str){
        //var i,c,p,q,ret="",strSpecial="!"#$%&'()*+,/:;<=>?@[]^`{|}~%";
        for(i=0;i<str.length;i++){
            if(str.charCodeAt(i)>=0x4e00){
                var p=strGB.indexOf(str.charAt(i));
                if(p>=0){
                    q=p%94;
                    p=(p-q)/94;
                    ret+=("%"+(0xB0+p).toString(16)+"%"+(0xA1+q).toString(16)).toUpperCase();
                }
            }
            else{
                c=str.charAt(i);
                if(c==" ")
                    ret+="+";
                else if(strSpecial.indexOf(c)!=-1)
                    ret+="%"+str.charCodeAt(i).toString(16);
                else
                    ret+=c;
            }
        }
        return ret;
    }
    //取得汉字的拼音
    function getGB2312Spell(str,sp){
        var i,c,t,p,ret="";
        if(sp==null)sp="";
        for(i=0;i<str.length;i++){
            if(str.charCodeAt(i)>=0x4e00){
                p=strGB.indexOf(str.charAt(i));
                if(p>-1&&p<3755){
                    for(t=qswhSpell.length-1;t>0;t=t-2)if(qswhSpell[t]<=p)break;
                    if(t>0)ret+=qswhSpell[t-1]+sp;
                }
            }
        }
        return ret.substr(0,ret.length-sp.length);
    }








    展开全文
  • js 实现繁体和简体之间的转换

    千次阅读 2013-09-11 14:34:54
    div class="language">  a name="gb2big5" id="gb2big5">繁體中文 a>  script type="text/javascript" language ="Javascript" src="js/gb2big5.Js">script>  
                    < div  class ="language">
                         < a  name ="gb2big5"  id ="gb2big5"> 繁體中文  </ a >
                         < script  type ="text/javascript"  language  ="Javascript"  src ="js/gb2big5.Js"></ script >
                     </ div >

    页面上点击 “繁體中文”即可
    gb2big5.Js内容如下:


    var Default_isFT = 1 //默认是否繁体,0-简体,1-繁体
    var StranIt_Delay = 50 //翻译延时毫秒(设这个的目的是让网页先流畅的显现出来)


    //-------代码开始,以下别改-------
    //转换文本
    function StranText(txt,toFT,chgTxt)
    {
    if(txt==""||txt==null)return ""
    toFT=toFT==null?BodyIsFt:toFT
    if(chgTxt)txt=txt.replace((toFT?"简":"繁"),(toFT?"繁":"简"))
    if(toFT){return Traditionalized(txt)}
    else {return Simplized(txt)}
    }
    //转换对象,使用递归,逐层剥到文本
    function StranBody(fobj)
    {
    if(typeof(fobj)=="object"){var obj=fobj.childNodes}
    else 
    {
    var tmptxt=gb2big5_Obj.innerHTML.toString()
    if(tmptxt.indexOf("简")<0)
    {
    BodyIsFt=1
    gb2big5_Obj.innerHTML=StranText(tmptxt,0,1)
    gb2big5.title=StranText(gb2big5.title,0,1)
    }
    else
    {
    BodyIsFt=0
    gb2big5_Obj.innerHTML=StranText(tmptxt,1,1)
    gb2big5.title=StranText(gb2big5.title,1,1)
    }
    setCookie(JF_cn,BodyIsFt,7)
    var obj=document.body.childNodes
    }
    for(var i=0;i<obj.length;i++)
    {
    var OO=obj.item(i)
    if("||BR|HR|TEXTAREA|".indexOf("|"+OO.tagName+"|")>0||OO==gb2big5_Obj)continue;
    if(OO.title!=""&&OO.title!=null)OO.title=StranText(OO.title);
    if(OO.alt!=""&&OO.alt!=null)OO.alt=StranText(OO.alt);
    if(OO.tagName=="INPUT"&&OO.value!=""&&OO.type!="text"&&OO.type!="hidden")OO.value=StranText(OO.value);
    if(OO.nodeType==3){OO.data=StranText(OO.data)}
    else StranBody(OO)
    }
    }
    function JTPYStr()
    {
    return '皑蔼碍爱翱袄奥坝罢摆败颁办绊帮绑镑谤剥饱宝报鲍辈贝钡狈备惫绷笔毕毙闭边编贬变辩辫鳖瘪濒滨宾摈饼拨钵铂驳卜补参蚕残惭惨灿苍舱仓沧厕侧册测层诧搀掺蝉馋谗缠铲产阐颤场尝长偿肠厂畅钞车彻尘陈衬撑称惩诚骋痴迟驰耻齿炽冲虫宠畴踌筹绸丑橱厨锄雏础储触处传疮闯创锤纯绰辞词赐聪葱囱从丛凑窜错达带贷担单郸掸胆惮诞弹当挡党荡档捣岛祷导盗灯邓敌涤递缔点垫电淀钓调迭谍叠钉顶锭订东动栋冻斗犊独读赌镀锻断缎兑队对吨顿钝夺鹅额讹恶饿儿尔饵贰发罚阀珐矾钒烦范贩饭访纺飞废费纷坟奋愤粪丰枫锋风疯冯缝讽凤肤辐抚辅赋复负讣妇缚该钙盖干赶秆赣冈刚钢纲岗皋镐搁鸽阁铬个给龚宫巩贡钩沟构购够蛊顾剐关观馆惯贯广规硅归龟闺轨诡柜贵刽辊滚锅国过骇韩汉阂鹤贺横轰鸿红后壶护沪户哗华画划话怀坏欢环还缓换唤痪焕涣黄谎挥辉毁贿秽会烩汇讳诲绘荤浑伙获货祸击机积饥讥鸡绩缉极辑级挤几蓟剂济计记际继纪夹荚颊贾钾价驾歼监坚笺间艰缄茧检碱硷拣捡简俭减荐槛鉴践贱见键舰剑饯渐溅涧浆蒋桨奖讲酱胶浇骄娇搅铰矫侥脚饺缴绞轿较秸阶节茎惊经颈静镜径痉竞净纠厩旧驹举据锯惧剧鹃绢杰洁结诫届紧锦仅谨进晋烬尽劲荆觉决诀绝钧军骏开凯颗壳课垦恳抠库裤夸块侩宽矿旷况亏岿窥馈溃扩阔蜡腊莱来赖蓝栏拦篮阑兰澜谰揽览懒缆烂滥捞劳涝乐镭垒类泪篱离里鲤礼丽厉励砾历沥隶俩联莲连镰怜涟帘敛脸链恋炼练粮凉两辆谅疗辽镣猎临邻鳞凛赁龄铃凌灵岭领馏刘龙聋咙笼垄拢陇楼娄搂篓芦卢颅庐炉掳卤虏鲁赂禄录陆驴吕铝侣屡缕虑滤绿峦挛孪滦乱抡轮伦仑沦纶论萝罗逻锣箩骡骆络妈玛码蚂马骂吗买麦卖迈脉瞒馒蛮满谩猫锚铆贸么霉没镁门闷们锰梦谜弥觅绵缅庙灭悯闽鸣铭谬谋亩钠纳难挠脑恼闹馁腻撵捻酿鸟聂啮镊镍柠狞宁拧泞钮纽脓浓农疟诺欧鸥殴呕沤盘庞国爱赔喷鹏骗飘频贫苹凭评泼颇扑铺朴谱脐齐骑岂启气弃讫牵扦钎铅迁签谦钱钳潜浅谴堑枪呛墙蔷强抢锹桥乔侨翘窍窃钦亲轻氢倾顷请庆琼穷趋区躯驱龋颧权劝却鹊让饶扰绕热韧认纫荣绒软锐闰润洒萨鳃赛伞丧骚扫涩杀纱筛晒闪陕赡缮伤赏烧绍赊摄慑设绅审婶肾渗声绳胜圣师狮湿诗尸时蚀实识驶势释饰视试寿兽枢输书赎属术树竖数帅双谁税顺说硕烁丝饲耸怂颂讼诵擞苏诉肃虽绥岁孙损笋缩琐锁獭挞抬摊贪瘫滩坛谭谈叹汤烫涛绦腾誊锑题体屉条贴铁厅听烃铜统头图涂团颓蜕脱鸵驮驼椭洼袜弯湾顽万网韦违围为潍维苇伟伪纬谓卫温闻纹稳问瓮挝蜗涡窝呜钨乌诬无芜吴坞雾务误锡牺袭习铣戏细虾辖峡侠狭厦锨鲜纤咸贤衔闲显险现献县馅羡宪线厢镶乡详响项萧销晓啸蝎协挟携胁谐写泻谢锌衅兴汹锈绣虚嘘须许绪续轩悬选癣绚学勋询寻驯训讯逊压鸦鸭哑亚讶阉烟盐严颜阎艳厌砚彦谚验鸯杨扬疡阳痒养样瑶摇尧遥窑谣药爷页业叶医铱颐遗仪彝蚁艺亿忆义诣议谊译异绎荫阴银饮樱婴鹰应缨莹萤营荧蝇颖哟拥佣痈踊咏涌优忧邮铀犹游诱舆鱼渔娱与屿语吁御狱誉预驭鸳渊辕园员圆缘远愿约跃钥岳粤悦阅云郧匀陨运蕴酝晕韵杂灾载攒暂赞赃脏凿枣灶责择则泽贼赠扎札轧铡闸诈斋债毡盏斩辗崭栈战绽张涨帐账胀赵蛰辙锗这贞针侦诊镇阵挣睁狰帧郑证织职执纸挚掷帜质钟终种肿众诌轴皱昼骤猪诸诛烛瞩嘱贮铸筑驻专砖转赚桩庄装妆壮状锥赘坠缀谆浊兹资渍踪综总纵邹诅组钻致钟么为只凶准启板里雳余链泄';
    }
    function FTPYStr()
    {
        return '皚藹礙愛翺襖奧壩罷擺敗頒辦絆幫綁鎊謗剝飽寶報鮑輩貝鋇狽備憊繃筆畢斃閉邊編貶變辯辮鼈癟瀕濱賓擯餅撥缽鉑駁蔔補參蠶殘慚慘燦蒼艙倉滄廁側冊測層詫攙摻蟬饞讒纏鏟産闡顫場嘗長償腸廠暢鈔車徹塵陳襯撐稱懲誠騁癡遲馳恥齒熾沖蟲寵疇躊籌綢醜櫥廚鋤雛礎儲觸處傳瘡闖創錘純綽辭詞賜聰蔥囪從叢湊竄錯達帶貸擔單鄲撣膽憚誕彈當擋黨蕩檔搗島禱導盜燈鄧敵滌遞締點墊電澱釣調叠諜疊釘頂錠訂東動棟凍鬥犢獨讀賭鍍鍛斷緞兌隊對噸頓鈍奪鵝額訛惡餓兒爾餌貳發罰閥琺礬釩煩範販飯訪紡飛廢費紛墳奮憤糞豐楓鋒風瘋馮縫諷鳳膚輻撫輔賦複負訃婦縛該鈣蓋幹趕稈贛岡剛鋼綱崗臯鎬擱鴿閣鉻個給龔宮鞏貢鈎溝構購夠蠱顧剮關觀館慣貫廣規矽歸龜閨軌詭櫃貴劊輥滾鍋國過駭韓漢閡鶴賀橫轟鴻紅後壺護滬戶嘩華畫劃話懷壞歡環還緩換喚瘓煥渙黃謊揮輝毀賄穢會燴匯諱誨繪葷渾夥獲貨禍擊機積饑譏雞績緝極輯級擠幾薊劑濟計記際繼紀夾莢頰賈鉀價駕殲監堅箋間艱緘繭檢堿鹼揀撿簡儉減薦檻鑒踐賤見鍵艦劍餞漸濺澗漿蔣槳獎講醬膠澆驕嬌攪鉸矯僥腳餃繳絞轎較稭階節莖驚經頸靜鏡徑痙競淨糾廄舊駒舉據鋸懼劇鵑絹傑潔結誡屆緊錦僅謹進晉燼盡勁荊覺決訣絕鈞軍駿開凱顆殼課墾懇摳庫褲誇塊儈寬礦曠況虧巋窺饋潰擴闊蠟臘萊來賴藍欄攔籃闌蘭瀾讕攬覽懶纜爛濫撈勞澇樂鐳壘類淚籬離裏鯉禮麗厲勵礫曆瀝隸倆聯蓮連鐮憐漣簾斂臉鏈戀煉練糧涼兩輛諒療遼鐐獵臨鄰鱗凜賃齡鈴淩靈嶺領餾劉龍聾嚨籠壟攏隴樓婁摟簍蘆盧顱廬爐擄鹵虜魯賂祿錄陸驢呂鋁侶屢縷慮濾綠巒攣孿灤亂掄輪倫侖淪綸論蘿羅邏鑼籮騾駱絡媽瑪碼螞馬罵嗎買麥賣邁脈瞞饅蠻滿謾貓錨鉚貿麽黴沒鎂門悶們錳夢謎彌覓綿緬廟滅憫閩鳴銘謬謀畝鈉納難撓腦惱鬧餒膩攆撚釀鳥聶齧鑷鎳檸獰甯擰濘鈕紐膿濃農瘧諾歐鷗毆嘔漚盤龐國愛賠噴鵬騙飄頻貧蘋憑評潑頗撲鋪樸譜臍齊騎豈啓氣棄訖牽扡釺鉛遷簽謙錢鉗潛淺譴塹槍嗆牆薔強搶鍬橋喬僑翹竅竊欽親輕氫傾頃請慶瓊窮趨區軀驅齲顴權勸卻鵲讓饒擾繞熱韌認紉榮絨軟銳閏潤灑薩鰓賽傘喪騷掃澀殺紗篩曬閃陝贍繕傷賞燒紹賒攝懾設紳審嬸腎滲聲繩勝聖師獅濕詩屍時蝕實識駛勢釋飾視試壽獸樞輸書贖屬術樹豎數帥雙誰稅順說碩爍絲飼聳慫頌訟誦擻蘇訴肅雖綏歲孫損筍縮瑣鎖獺撻擡攤貪癱灘壇譚談歎湯燙濤縧騰謄銻題體屜條貼鐵廳聽烴銅統頭圖塗團頹蛻脫鴕馱駝橢窪襪彎灣頑萬網韋違圍爲濰維葦偉僞緯謂衛溫聞紋穩問甕撾蝸渦窩嗚鎢烏誣無蕪吳塢霧務誤錫犧襲習銑戲細蝦轄峽俠狹廈鍁鮮纖鹹賢銜閑顯險現獻縣餡羨憲線廂鑲鄉詳響項蕭銷曉嘯蠍協挾攜脅諧寫瀉謝鋅釁興洶鏽繡虛噓須許緒續軒懸選癬絢學勳詢尋馴訓訊遜壓鴉鴨啞亞訝閹煙鹽嚴顔閻豔厭硯彥諺驗鴦楊揚瘍陽癢養樣瑤搖堯遙窯謠藥爺頁業葉醫銥頤遺儀彜蟻藝億憶義詣議誼譯異繹蔭陰銀飲櫻嬰鷹應纓瑩螢營熒蠅穎喲擁傭癰踴詠湧優憂郵鈾猶遊誘輿魚漁娛與嶼語籲禦獄譽預馭鴛淵轅園員圓緣遠願約躍鑰嶽粵悅閱雲鄖勻隕運蘊醞暈韻雜災載攢暫贊贓髒鑿棗竈責擇則澤賊贈紮劄軋鍘閘詐齋債氈盞斬輾嶄棧戰綻張漲帳賬脹趙蟄轍鍺這貞針偵診鎮陣掙睜猙幀鄭證織職執紙摯擲幟質鍾終種腫衆謅軸皺晝驟豬諸誅燭矚囑貯鑄築駐專磚轉賺樁莊裝妝壯狀錐贅墜綴諄濁茲資漬蹤綜總縱鄒詛組鑽緻鐘麼為隻兇準啟闆裡靂餘鍊洩';
    }
    function Traditionalized(cc){
    var str='',ss=JTPYStr(),tt=FTPYStr();
    for(var i=0;i<cc.length;i++)
    {
    if(cc.charCodeAt(i)>10000&&ss.indexOf(cc.charAt(i))!=-1)str+=tt.charAt(ss.indexOf(cc.charAt(i)));
      else str+=cc.charAt(i);
    }
    return str;
    }
    function Simplized(cc){
    var str='',ss=JTPYStr(),tt=FTPYStr();
    for(var i=0;i<cc.length;i++)
    {
    if(cc.charCodeAt(i)>10000&&tt.indexOf(cc.charAt(i))!=-1)str+=ss.charAt(tt.indexOf(cc.charAt(i)));
      else str+=cc.charAt(i);
    }
    return str;
    }


    function setCookie(name, value) //cookies设置
    {
    var argv = setCookie.arguments;
    var argc = setCookie.arguments.length;
    var expires = (argc > 2) ? argv[2] : null;
    if(expires!=null)
    {
    var LargeExpDate = new Date ();
    LargeExpDate.setTime(LargeExpDate.getTime() + (expires*1000*3600*24));
    }
    document.cookie = name + "=" + escape (value)+((expires == null) ? "" : ("; expires=" +LargeExpDate.toGMTString()));
    }


    function getCookie(Name) //cookies读取
    {
    var search = Name + "="
    if(document.cookie.length > 0) 
    {
    offset = document.cookie.indexOf(search)
    if(offset != -1) 
    {
    offset += search.length
    end = document.cookie.indexOf(";", offset)
    if(end == -1) end = document.cookie.length
    return unescape(document.cookie.substring(offset, end))
    }
    else return ""
     }
    }


    var gb2big5_Obj=document.getElementById("gb2big5")
    if (gb2big5_Obj)
    {
    var JF_cn="ft"+self.location.hostname.toString().replace(/\./g,"")
    var BodyIsFt=getCookie(JF_cn)
    if(BodyIsFt!="1")BodyIsFt=Default_isFT
    with(gb2big5_Obj)
    {
    if(typeof(document.all)!="object") //非IE浏览器
    {
    href="javascript:StranBody()"
    }
    else
    {
    href="#";
    οnclick= new Function("StranBody();return false")
    }
    title=StranText("点击以繁体中文方式浏览",1,1)
    innerHTML=StranText(innerHTML,1,1)
    }
    if(BodyIsFt=="1"){setTimeout("StranBody()",StranIt_Delay)}
    }
    展开全文
  • 前言:之前在百度应用里面看到了这个过类似的工具,很想知道怎么实现的。 琢磨下,自己写了一个,效果如下。 <!-------HTML CODE----------> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML ...
  • :朴老师对本文做了 评注 ,详细得指出了文中存在的错误说法,建议阅读。) 一、为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么...

空空如也

空空如也

1
收藏数 7
精华内容 2
关键字:

nodejs朴灵