前言
毕业到入职腾讯已经差很少一年的时光了,接触了不少项目,也积累了不少实践经验,在处理问题的方式方法上有很大的提高。随着时间的增长,越发发现基础知识的重要性,不少开发过程当中遇到的问题都是由最基础的知识点遗忘形成,基础不牢,地动山摇。因此,就再次回归基础知识,从新学习JavaScript相关内容,加深对JavaScript语言本质的理解。日知其所亡,身为有追求的程序员,理应不断学习,不断拓展本身的知识边界。本系列文章是在此阶段产生的积累,以记录下以往没有关注的核心知识点,供后续查阅之用。
2017
04/10
事实上,程序中如今运行的部分和未来运行的部分之间的关系就是异步编程的核心。 到底何时控制台 I/O 会延迟,甚至是否可以被观察到,这都是游移不定的。若是在调 试的过程当中遇到对象在 console.log(..) 语句以后被修改,可你却看到了意料以外的结果, 要意识到这多是这种 I/O 的异步化形成的。若是遇到这种少见的状况,最好的选择是在 JavaScript 调试器中使用断点, 而不要依赖控制台输出。次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,好比经过 JSON.stringify(..)。
04/11
多线程编程是很是复杂的。由于若是不经过特殊的步骤来防止这种中断和交错运行的话,可能会获得出乎意料的、不肯定的行为,一般这很让人头疼。
JavaScript 从不跨线程共享数据。
var res = []; // response(..)从Ajax调用中取得结果数组
function response(data) {
// 一次处理1000个
var chunk = data.splice(0, 1000);
// 添加到已有的res组
res = res.concat(
// 建立一个新的数组把chunk中全部值加倍
chunk.map(function(val) {
return val * 2;
}));
// 还有剩下的须要处理吗?
if (data.length > 0) {
// 异步调度下一次批处理
setTimeout(function() {
response(data);
}, 0);
}
}
// ajax(..)是某个库中提供的某个Ajax函数
ajax("http://some.url.1", response);
ajax("http://some.url.2", response);
咱们把数据集合放在最多包含 1000 条项目的块中。这样,咱们就确保了“进程”运行时 间会很短,即便这意味着须要更多的后续“进程”,由于事件循环队列的交替运行会提升 站点 /App 的响应(性能)。
05/10
让咱们来简单总结一下使链式流程控制可行的 Promise 固有特性。
- 调用 Promise 的 then(..) 会自动建立一个新的 Promise 从调用返回。
- 在完成或拒绝处理函数内部,若是返回一个值或抛出一个异常,新返回的(可连接的) Promise 就相应地决议。
- 若是完成或拒绝处理函数返回一个 Promise,它将会被展开,这样一来,无论它的决议 值是什么,都会成为当前 then(..) 返回的连接 Promise 的决议值。
05/11
Promise 局限性 :
顺序错误处理 : Promise 的设计局限性(具体来讲,就 是它们连接的方式)形成了一个让人很容易中招的陷阱,即 Promise 链中的错误很容易被 无心中默默忽略掉。 关于 Promise 错误,还有其余须要考虑的地方。因为一个 Promise 链仅仅是链接到一块儿的成员 Promise,没有把整个链标识为一个个体的实体,这意味着没有外部方法能够用于观 察可能发生的错误。 遗憾的是,不少时候并无为 Promise 链序列的中间步骤保留的引用。所以,没有这样的引用,你就没法关联错误处理函数来可靠地检查错误。
单一值 :根据定义,Promise 只能有一个完成值或一个拒绝理由。在简单的例子中,这不是什么问题,可是在更复杂的场景中,你可能就会发现这是一种局限了。 通常的建议是构造一个值封装(好比一个对象或数组)来保持这样的多个信息。这个解决 方案能够起做用,但要在 Promise 链中的每一步都进行封装和解封,就十分丑陋和笨重了。
单决议:Promise 最本质的一个特征是:Promise 只能被决议一次(完成或拒绝)。在许多异步状况中,你只会获取一个值一次,因此这能够工做良好。 可是,还有不少异步的状况适合另外一种模式——一种相似于事件和 / 或数据流的模式。
惯性:Promise 提供了一种不一样的范式,所以,编码方式的改变程度从某处的个别差别到某种情 况下的大相径庭都有可能。你须要刻意的改变,由于 Promise 不会从目前的编码方式中自 然而然地衍生出来。
// polyfill安全的guard检查
if (!Promise.wrap) {
Promise.wrap = function(fn) {
return function() {
var args = [].slice.call(arguments);
return new Promise(function(resolve, reject) {
fn.apply(null, args.concat(function(err, v) {
if (err) {
reject(err);
} else {
resolve(v);
}
}));
});
};
};
}
没法取消的 Promise:一旦建立了一个 Promise 并为其注册了完成和 / 或拒绝处理函数,若是出现某种状况使得 这个任务悬而未决的话,你也没有办法从外部中止它的进程。
Promise 性能:更多的工做,更多的保护。这些意味着 Promise 与不可信任的裸回调相比会更慢一些。这是显而易见的,也很容易理解。
05/12
Promise 很是好,请使用。它们解决了咱们因只用回调的代码而备受困扰的控制反转问题。 它们并无摈弃回调,只是把回调的安排转交给了一个位于咱们和其余工具之间的可信任的中介机制。 Promise 链也开始提供(尽管并不完美)以顺序的方式表达异步流的一个更好的方法,这有助于咱们的大脑更好地计划和维护异步 JavaScript 代码。
05/14
并无向第一个 next() 调用发送值,这是有意为之。只有暂停的 yield 才能接受这样一个经过 next(..) 传递的值,而在生成器的起始处咱们调用 第一个 next() 时,尚未暂停的 yield 来接受这样一个值。规范和全部兼容浏览器都会默默丢弃传递给第一个 next() 的任何东西。传值过去仍然不 是一个好思路,由于你建立了沉默的无效代码,这会让人迷惑。所以,启动 生成器时必定要用不带参数的 next()。
若是你的生成器中没有 return 的话——在生成器中和在普通函数中同样,return 固然不是必需的——总有一个假定的 / 隐式的 return;(也就是 return undefined;)。
05/15
若是你只是想要迭代一个对象的全部属性的话(不须要保证特定的顺序),能够经过 Object.keys(..) 返回一个 array,相似于 for (var k of Object. keys(obj)) { .. 这样使用。这样在一个对象的键值上使用 for..of 循环与 for..in 循环相似,除了 Object.keys(..) 并不包含来自于 [[Prototype]] 链上的属性,而 for..in 则包含。
一般在实际的 JavaScript 程序中使用 while..true 循环是很是糟糕的主意,至 少若是其中没有 break 或 return 的话是这样,由于它有可能会同步地无限循 环,并阻塞和锁住浏览器 UI。可是,若是在生成器中有 yield 的话,使用这 样的循环就彻底没有问题。由于生成器会在每次迭代中暂停,经过 yield 返 回到主程序或事件循环队列中。简单地说就是:“生成器把 while..true 带回 了 JavaScript 编程的世界!”
05/16
生成器 yield 暂停的特性意味着咱们不只可以从异步函数调用获得看似同步的返回值,还 能够同步捕获来自这些异步函数调用的错误!
ES6 中最完美的世界就是生成器(看似同步的异步代码)和 Promise(可信任可组合)的结合。
得到 Promise 和生成器最大效用的最天然的方法就 是 yield 出来一个 Promise,而后经过这个 Promise 来控制生成器的迭代器。
05/17
生成器是 ES6 的一个新的函数类型,它并不像普通函数那样老是运行到结束。取而代之的是,生成器能够在运行当中(彻底保持其状态)暂停,而且未来再从暂停的地方恢复运行。 这种交替的暂停和恢复是合做性的而不是抢占式的,这意味着生成器具备独一无二的能力来暂停自身,这是经过关键字 yield 实现的。不过,只有控制生成器的迭代器具备恢复生 成器的能力(经过 next(..))。
yield/next(..) 这一对不仅是一种控制机制,实际上也是一种双向消息传递机制。yield .. 表达式本质上是暂停下来等待某个值,接下来的 next(..) 调用会向被暂停的 yield 表达式传回 一个值(或者是隐式的 undefined)。 在异步控制流程方面,生成器的关键优势是:生成器内部的代码是以天然的同步 / 顺序方式表达任务的一系列步骤。其技巧在于,咱们把可能的异步隐藏在了关键字 yield 的后面, 把异步移动到控制生成器的迭代器的代码部分。 换句话说,生成器为异步代码保持了顺序、同步、阻塞的代码模式,这使得大脑能够更自 然地追踪代码,解决了基于回调的异步的两个关键缺陷之一。
05/18
在 Worker 内部是没法访问主程序的任何资源的。这意味着你不能访问它的任何全局变量, 也不能访问页面的 DOM 或者其余资源。记住,这是一个彻底独立的线程。
Web Worker 一般应用于哪些方面呢?
- 处理密集型数学计算
- 大数据集排序
- 数据处理(压缩、音频分析、图像处理等)
- 高流量网络通讯
Transferable 对象(http:// updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast)。这时发生的是对象所 有权的转移,数据自己并无移动。一旦你把对象传递到一个 Worker 中,在原来的位置 上,它就变为空的或者是不可访问的,这样就消除了多线程编程做用域共享带来的混乱。 固然,全部权传递是能够双向进行的。
异步编码模式使咱们可以编写更高效的代码,通 常可以带来很是大的改进。可是,异步特性只能让你走这么远,由于它本质上仍是绑定在 一个单事件循环线程上。
SIMD 打算把 CPU 级的并行数学运算映射到 JavaScript API,以得到高性能的数据并行运 算,好比在大数据集上的数字处理。
05/19
Benchmark.js :任何有意义且可靠的性能测试都应该基于统计学上合理的实践。
对于微小运算的绝大多数测试结果,好比 ++x 对比 x++ 的迷思,像出于性能考虑应该用 X 代替 Y 这样的结论都是不成立的。 这能够归结为一点,测试不真实的代码只能得出不真实的结论。若是有实际可能的话,你 应该测试实际的而非可有可无的代码,测试条件与你指望的真实状况越接近越好。只有这 样得出的结果才有可能接近事实。 像 ++x 对比 x++ 这样的微观性能测试结果为虚假的可能性至关高,可能咱们最好就假定它们是假的。
jsPerf.com (http://jsperf.com):它使用 Benchmark.js 库来运行统计上精确可靠的测试,并把测试结果放在一个公开 可得的 URL 上,你能够把这个 URL 转发给别人。
在考虑对代码进行性能测试时,你应该习惯的第一件事情就是你所写的代码并不老是引擎真正运行的代码。不要试图和 JavaScript 引擎比谁聪明。对性能优化来讲,你极可能会输。
“没有比临时 hack 更持久的了”。颇有可能你如今编写的用来绕过一些性能 bug 的代码可能 比浏览器的性能问题自己存在得更长久。
咱们应该关注优化的大局,而不是担忧这些微观性能的细微差异。
高德纳——计算访谈 6(1974 年 12 月) :程序员们浪费了大量的时间用于思考,或担忧他们程序中非关键部分的速度,这 些针对效率的努力在调试和维护方面带来了强烈的负面效果。咱们应该在,好比 说 97% 的时间里,忘掉小处的效率:过早优化是万恶之源。但咱们不该该错过 关键的 3% 中的机会。
05/21
尾调用优化 :ES6 包含了一个性能领域的特殊要求-尾调用优化(Tail Call Optimization,TCO)。
简单地说,尾调用就是一个出如今另外一个函数“结尾”处的函数调用。这个调用结束后就 没有其他事情要作了(除了可能要返回结果值)。
调用一个新的函数须要额外的一块预留内存来管理调用栈,称为栈帧。然而,若是支持 TCO 的引擎可以意识到一个函数调用位于尾部,这意味着外部函数基本上已经完成了,那么在调用 函数时,它就不须要建立一个新的栈帧,而是能够重用已有的栈帧。这样不只速度更快,也更节省内存。
递归是 JavaScript 中一个纷繁复杂的主题。由于若是没有 TCO 的话,引擎须要实现一 个随意(还彼此不一样!)的限制来界定递归栈的深度,达到了就得中止,以防止内存耗 尽。有了 TCO,尾调用的递归函数本质上就能够任意运行,由于不再须要使用额外的内存! ES6 之因此要求引擎实现 TCO 而不是将其留给引擎自由决定,一个缘由是缺少 TCO 会导 致一些 JavaScript 算法由于惧怕调用栈限制而下降了经过递归实现的几率。