读《你不知道的javascript》(中)部分东西记录-第二部分

发布时间:2024-06-25 18:01

你不知道的javascript

第二部分

一、回调

1.回调风格的缺陷

利用回调函数存在的缺陷:
1.大脑对于事情的计划时线性的、阻塞的、单线程大的语义。而回调表达异步流程的方式是非线性的、非顺序的,这使得正确推导这样的代码难度较大,就不用书写代码时可能造成的回调地狱了。对于难理解的代码时坏代码,会导致坏bug
2.最重要的一点,回调函数会受到控制反转的影响,因为回调函数暗中把控制权力交给了第三方(通常是不受你控制的第三方工具)来调用你代码的continuation。这种控制转移导致了一系列麻烦的信任问题,比如回调被调用的次数是否超出预期,调用的时机是否符合预期,及第三方吞掉可能出现的异常或者错误。

2.常用的回调风格

分离回调:

 function success(data){
    alert(data);
}
function failure(err){
    alert(err);
}

ajax(\'http://some.url.com/\',success,failure);

错误优先error-first(node风格):

ajax(\'http://some.url.com/\',function(error,data){
    if(err) {
        console.log(err);
    } else {
        console.log(result);
    }
});

二、Promise

1.具有then方法的鸭子类型

识别Promise(或者行为类似于Promise的东西)就被认定为某种称为thenable的东西,将其定义为任何具有then(..)方法的函数或者对象和函数。我们认为,任何这样的值就是Promise一致的thenable。
根据一个值的形态(具有那些属性)对这个值得类型做出一些假定,这种类型检测(type check)一般用术语鸭子类型(duck typing)来表示——\"如果它看起来项鸭子,叫起来像鸭子,那么它就是一只鸭子\",于是,对thenalbe值的鸭子类型检测就大致类似于:

let p = function () { };
p.then = function (cb) {
    cb();
}

function duck_typing_thenable(thenable) {
    return !!thenable && (
        typeof thenable === \"object\" ||
        typeof thenable === \"function\"
    )&&
    typeof thenable.then === \"function\";
}

console.log(duck_typing_thenable(p));

使用Promise.resolve将thenalbe值规范化成一个Promise值:

var p = {
    then: function (cb, errcb) {
        cb(42);
        errcb(\'evil laugh\');
    }
}
p.then(function (data) {
    console.log(data);
},
    function (err) {
        // 如果时正真的promise,那么不会运行到这里
        console.log(\'thenalbe:\' + err);
    });

Promise.resolve(p).then(
    function (data) {
        console.log(data);
    },
    function (err) {
        // 这里不会运行
        console.log(\'promise:\' + err);
    }
)

2.Promise.race()

可用于解决一各可能永远不会决议的Promise。使用一中称为竞态的高级抽象机制:

const timeoutPromise = function (delay) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(\'Promise Timeout\');
        }, delay);
    })
}


const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(\'hello\');
    }, 2000);
});

const pt = Promise.race([p, timeoutPromise(3000)]);
pt.then(console.log)

3.Promise默认的处理函数

当你调用的promise的then(..),只传入一个完成处理函数,一个默认的拒绝处理函数就会顶替上来:

let p = new Promise(function (resolve, reject) {
    foo.baz();
    reject(\'rejected data\');
})

p.then(function fulfilled() {
    // 这里不会执行
},
    // function rejected(error) {
    //     throw  error;
    // }
)

如你所见,默认拒绝处理函数只是把错误重新抛出,这使得错误客户以继续沿着Promise链传播下去。
当你没有给then(..)传递一个适当有效的函数作为处理函数参数,韩式会有作为替代的一个默认处理函数:

let p = new Promise(function (resolve, reject) {
    resolve(\'resolve data\');
})

p.then(
    // function (data) {
    //     return data;
    // },
    null,
    function rejected() {
        // 永远不会执行到这里
    }
)

4.Promise构造函数

使用Promise构造函数创建一个promise对象时,必须提供一个回调函数。这个回调是同步的或立即调用的。这个函数接收到两个函数类型的参数,用以支持promise的决议,通常我们发这两个函数称为resolve(..)和reject(..):

let p = new Promise(function(resolve,reject){
    // resolve(..) 用于决议/完成这个promise
    // reject(..) 用于拒绝这个promise
});

5.promisory

将异步回调风格的函数,包装成promise风格,这个动作被称为\"提升\"或者\"promise工厂化\",在某种意义上,产生promise的函数可一个看作一个promise工厂,书中将其命名为promisory (\'Promise\' + \'factory\')。
自定义包装函数:

function ajax(url, cb) {
    // 模拟异步请求
    setTimeout(function () {
        // cb(new Error(\'ajax Error\'), \'hello\');
        Math.random() > 0.5 ? cb(null, \'no Error Data\') : cb(new Error(\'ajax Error\'), \'Error data\');
        // cb(null, \'no Error Data\');
    }, 1000);
}

// 未包装之前使用
ajax(\'/api/user\', function (err, data) {
    if (err) {
        console.log(err);
    } else {
        console.log(data);
    }
})

// 对回调会promise风格的函数进行包装
if(typeof Promise !== \'undefined\' && !Promise.wrap){
    Promise.wrap = function(fn){
        return function(){
            var args = [].slice.call(arguments);
            return new Promise((resolve,reject)=>{
                args.push(function(err,data){
                    err?reject(err):resolve(data);
                });
                fn.apply(null,args);
            })
        }
    }
}

// 包装之后使用
promise_ajax = Promise.wrap(ajax);
promise_ajax(\'/api/user\').then(function (data) {
    console.log(data);
}, function (err) {
    console.log(err);
})

三、Generator

1.generator的基本使用

生成器是一类特殊的寒素,可以一次或多次的启动或停止,并不一定非得完成,且it = foo()运算并没有执行生成器foo*(),而只是构造了一个迭代器(iterator),这个迭代器会控制它的执行。

function* foo(x) {
    let y = x * (yield 6); // (yield 6) 表达式的值,取决于下次调用 next() 时传入的参数
    return y;
}

let it = foo(8);
console.log(it.next()); // { value: 6, done: false }
console.log(it.next(12));

2.迭代器

a.对象迭代器

在使用for..of遍历对象时,会像对象请求一个[Symbol.iterator]属性,获取一个迭代器对象(拥有一个next属性(值为函数,返回{value:xx,done:false}))的iterable对象

let something = (function () {
    let nextValue;
    return {
        [Symbol.iterator]: function () { return this },
        next: function () {
            nextValue = nextValue === undefined ? nextValue = 1 : (3 * nextValue + 1);
            return { value: nextValue, done: false };
        }
    }
})()
// 这里没有使用for..of循环
for (let count = 0;
    count < 10;) {
    let next = something.next();
    console.log(next.value)
    count++;
}
b.生成器迭代器

严格来说,生成器本身并不是iterable,当你执行了一个生成器也获得了一个迭代器:

function* foo() {
    let nextValue;

    while (true) {
        nextValue = nextValue === undefined ? 1 : nextValue * 3 + 1;
        yield nextValue;
    }
}

for(let n of gen){
    if(n > 10000) break;
    console.log(n);
}

for(let n of gen){
    // 这里并会迭代,gen迭代器在上次for..of的\"异常结束后\",这个迭代器就被终止了
    if(n > 1000) break;
    console.log(n);
}

停止生成器:for..of有一个隐藏的的特性\"异常结束\"(也就是\"提前终止\"),通常由break、return或者未捕获的异常引起,回向生成器的迭代器发送一个信号使其终止。严格来说,在正常循环结束以后,也会一个终止信号。
使用return(..)向迭代器手工发送终止信号,并在迭代器内部使用try..finally语句检测接收这个信号:

function* foo() {
    try {
        let nextValue;

        while (true) {
            nextValue = nextValue === undefined ? 1 : nextValue * 3 + 1;
            yield nextValue;
        }
    } finally {
        // 当迭代器被终止后,代表try中的代码已经被执行完成了,然后就会执行这里
        console.log(\'lceaning up\');
    }
}
let gen = foo();
for (let n of gen) {
    if (n > 1000) {
        console.dir(
            // 向迭代器发送终止信号,gen.return(newvalue) 返回{done:true,value:newvalue},也就达到了终止的目的,终止后,不会再运行下一个迭代循环
            gen.return(\'hello world\').value
        );
        // 这里不需要break
    }
    console.log(n) 
}

3.异步迭代生成器

生成器yield暂停的特性意味着我们不仅能从异步函数调用得到看似同步的返回值,还可以同步捕获来自这些异步函数调用的错误!将异步需求引用到生成器中:

function ajax(url, cb) {
    setTimeout(function () {
        cb(new Error(\'ajax Error\'), \'hello\');
    }, 3000);
}
function foo(x, y) {
    ajax(\'/bar\' + x + y, function (error, data) {
        if (!!error) {
            // 向*main抛出一个错误
            it.throw(error);

        } else {
            // 用收到的data恢复*main(),使其继续执行
            it.next(data);
        }
    })
}
function* main() {
    try {
        var text = yield foo(\'/user\',\'/member\');
        console.log(text);
    }catch(e){
        console.log(e);
    }
}
let it = main();
it.next();

4.支持Promise的Generator Runner

需要使用到的工具函数:

function ajax(url, cb) {
    // 模拟异步请求
    setTimeout(function () {
        // cb(new Error(\'ajax Error\'), \'hello\');
        Math.random() > 0.5 ? cb(null, \'no Error Data\') : cb(new Error(\'ajax Error\'), \'Error data\');
        // cb(null, \'no Error Data\');
    }, 1000);
}

function request(url) {
    // 将回调风格的函数转换成promise
    return new Promise(function (resolve, reject) {
        ajax(url, function (err, data) {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

function foo(x, y) {
    return request(\'/api/foo?x=\' + x + \'&y=\' + y);
}
function* main() {
    try {
        let text1 = yield foo(\'/user\', \'/member\');
        let text2 = yield foo(\'/user\', \'/order\');
        let text3 = yield foo(\'/user\', \'/address\');
        return text1 + text2 + text3;
    } catch (e) {
        console.log(\'error\',e);
    }
}

小小练习(生成器 + Promise):

let it = main();
let nextResult = it.next(undefined);

function handlenextResult(nextResult, cb) {
    if (nextResult.done) {
        cb(nextResult.value)
    } else {
        nextResult.value.then(function (data) {
            handlenextResult(it.next(data), cb);
        }, function (err) {
            it.throw(err);
        });
    }
}

handlenextResult(nextResult, console.log);

封装使用支持Promise的Generator Runner:

function run(gen) {
// 获取传给生成器的参数
let args = [].slice.call(arguments, 1);

// 在当前上下文中初始化一个generator对象
let it = gen.apply(this, args);

// 返回一个promise对象用于生成器完成
return Promise.resolve().then(function handleNext(value) {
// 对下一个yield的值运行 初始 value == undefined
let next = it.next(value);

return (function handleResult(next) {
    if (next.done) {
        // 生成器运行完毕了吗?完毕了就返回生成的return值
        return next.value;
    } else {                     // 将yield的值转换成promise
        return Promise.resolve(next.value).then(
            // (data) => { // 这里的data就是promise决议成功后的返回值
            //   return  handleNext(data)
            // }
            // 简写成为
            handleNext
            // 当生成器为结束时,继续执行
            ,
            function handleErr(err) {
                // 如果返回的promise被拒绝了,就会调用这个函数
                // 就把错误传回給生成器进行出错处理
                // 这里将错误{done:true,value:undefined}包装成promise,
                return Promise.resolve(it.throw(err)).then(
                    // 获取
                    // (data)=>{
                    //     return handleResult(data);
                    // }
                    // 简写成为 handleResult的参数为{done:true,value:undefined},会结束本次的Generator Runner运行
                    handleResult
                );
            })
        }
    })(next);
})
}

run(main).then(function (data) {
console.log(data);
}, function (err) {
console.log(err);
});
console.log(1);

5.生成器中的Promise并发

让异步流程基于promise,特别是基于它们以时间无关的方式管理状态的能力:

function request(url) {
    return new Promise(function promiseHandle(resolve, reject) {
        setTimeout(function timeHandler() {
            resolve(\'success\');
        }, 1000)
    })
}

function* foo() {
    // 让两个请求并行
    let p1 = request(\'/api/user\');
    let p2 = request(\'/api/user\');

    // 等待两个promise都决议
    let r1 = yield p1;
    let r2 = yield p2;


    var r3 = yield request(\'/api/user\' + r1 + r2);
    console.log(r3);
}

// 使用前面的工具run(..)
run(foo);

6.生成器委托

生成器委托主要的目的是组织代码,以达到与普通函数调用的对称,在一个生成器函数中调用另一个生成器。保持生成器分离有助于程序的可读性、可维护性、和调试性。
代码练习:

function* foo() {
    console.log(\'*foo() starting\');
    try {
        yield \'B\';
    } catch (e) {
        console.log(\'error caught inside *foo()\', e);
    }
    yield \'C\';

    throw \'D\';
}
function* bar() {
    yield \'A\';
    try {
        yield* foo();
    } catch (err) {
        console.log(\'error caught inside *bar()\', err);
    }
    yield \'E\';

    yield* baz(); // 这里baz*()抛出的异常没有被捕获,所以bar*()、baz*()都会终止

    // 注:不会到达这里
    yield \'G\'
}
function* baz() {
    throw \'F\'; 
}

let it = bar();
console.log(\'outside\', it.next().value); // A
console.log(\'outside\', it.next(1).value); // B
console.log(\'outside\', it.throw(2).value); //  caught 2, C
console.log(\'outside\', it.next(3).value); // caught 3, E
try {
    console.log(\'outside\', it.next(4).value); 
} catch (err) {
    console.log(\'error catght outside:\', err); // caught F
}

四、程序性能

1.Web Worker

Web Worker是Web平台对HTML5新增的特性,这是浏览器(即宿主环境)的功能,实际上和JavaScript语言本身几乎没有什么关系。也就是说,JavaScript当前并没有任何支持多线程的执行的功能。
虽然js是单线程运作的,但是像浏览器这样的环境,很容易提供多个JavaScript引擎实列,各自运行在自己的线程上,这样你可以在不同的线程上运行不同的程序。程序中么一个这样独立的多线程部分被称为一个(Web Worker)。这种类型的并行化被称为任务并行,因为其重点在于把程序划分为多个块来并发运行。Worker之间以及它们的主线程之间,不会共享任何作用域或资源。就没有了多线程编程的噩梦,而是通过几个基本的事件消息机制相互联系。

// 主线程中
let w1 = new Worker(\'./web_worker_01.js\');
w1.postMessage(\'Hello\');

// 作用于webwork运行
addEventListener(\'message\', function(e) {
    console.log(\'Message received from main script: \' + e.data);
});

2.尾调用优化(Tail Call Optimization)

尾调用,就是出现在另一个函数\"结尾\"处的函数调用。这个调用结束后,就没有其余的事情要做了(处了可能要返回结果值)

function foo(x) {
    return x + 1;
}

function bar(y) {
    return foo(y + 1); // 尾调用
}
function baz(y) {
    return foo(y) + 1; // 非尾调用 
}

调用一个新的函数是需要额外的一块预留内存来管理调用栈,称为栈帧。但当支持TCO的引擎能够意识到foo(y + 1)调用位于bar函数的尾部,这意味着bar(...)基本上已经完成了,那么在调用foo(..)时,他就不需要创建一个新的栈帧,而是可以重用已有的bar(..)的栈帧,这样不仅更快,也更节省内存。在处理递归时,尤其有用。

// 这样并不能使用TCO调优
// function factorial(n){
//     if(n==1){
//         return 1;
//     }
//     return n*factorial(n-1);
// }

// 这样可以使用TCO调优
function factorial(n) {
    function fact(n, res) {
        if (n < 2) return res;
        return  fact(n - 1, n * res);
    }
    return fact(n, 1);
}
console.log(\'f\',factorial(5));

3.其它

a.asm.js

asm.js这个标签指JavaScript语言中可以高度优化的一个子集,通过小心避免某些难以优化的机制和模式(垃圾收集、类型强制转换、等等),asm.js风格的代码可一个JavaScript引擎识别并进行特别激进的底层优化。

b.性能测试Beachmark.js

使用Benchmark.js的性能测试工具来测试js代码的运行性能比较。名为jsPerf网站使用了Beachmark.js库来与逆行同级精确可靠的测试。

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号