你不晓得的 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 库来与逆行同级准确牢靠的测试。