2021 年 02 月 08 日

类型

JavaScript 有七种内置类型:

  • 空值(null)
  • 未定义(undefined)
  • 布尔值( boolean)
  • 数字(number)
  • 字符串(string)
  • 对象(object)
  • 符号(symbol,ES6 中新增)

ypeof null === "object"; // true

数组

arr = [1,2,3]
delete arr[1] // [1, empty, 3]

类数组

  • 有时需要将类数组(一组通过数字索引的值)转换为真正的数组,这一般通过数组工具函数(如 indexOf(..)、concat(..)、forEach(..) 等)来实现。
function foo() {
  var arr = Array.prototype.slice.call(arguments);
  arr.push("bam");
  console.log(arr);
}

foo( "bar", "baz" ); // ["bar","baz","bam"]
  • 用 ES6 中的内置工具函数 Array.from(..) 也能实现同样的功能:
function f() {
  return Array.from(arguments);
}

f(1, 2, 3);

生成器(Generator)

var x = 1
function * foo () {
  x++
  yield // 暂停!
  console.log('x:', x)
}
function bar () {
  x++
}
var it = foo()
// 这里启动foo()! it.next();
x // 2 bar();
x // 3 it.next(); // x: 3

迭代消息传递

function* foo(x) {
  var y = x * (yield);
  return y;
}
var it = foo(6);
// 启动foo(..) it.next();
var res = it.next(7);
res.value; // 42
function *foo() {
    var x = yield 2;
    z++;
    var y = yield (x * z);
    console.log( x, y, z );
}
var z = 1;
var it1 = foo();
var it2 = foo();
var val1 = it1.next().value;
var val2 = it2.next().value;
val1 = it1.next( val2 * 10 ).value;
val2 = it2.next( val1 * 5 ).value;
it1.next( val2 / 2 );
it2.next( val1 / 4 );
// 2 <-- yield 2
// 2 <-- yield 2
// 40   <-- x:20,  z:2
// 600  <-- x:200, z:3
// y:300
// 20 300 3
// y:10
// 200 10 3

我们简单梳理一下执行流程

(1) *foo() 的两个实例同时启动,两个 next() 分别从 yield 2 语句得到值 2。 (2)val2 * 10也就是2 * 10,发送到第一个生成器实例it1,因此x得到值20。z从1增

加到 2,然后 20 * 2 通过 yield 发出,将 val1 设置为 40。 (3) val1 * 5 也就是 40 * 5,发送到第二个生成器实例 it2,因此 x 得到值 200。z 再次从 2

递增到 3,然后 200 * 3 通过 yield 发出,将 val2 设置为 600。 (4) val2 / 2 也就是 600 / 2,发送到第一个生成器实例 it1,因此 y 得到值 300,然后打印

出x y z的值分别是20 300 3。 (5) val1 / 4 也就是 40 / 4,发送到第二个生成器实例 it2,因此 y 得到值 10,然后打印出

x y z的值分别为200 10 3。

生产者与迭代器

  • 假定你要产生一系列值,其中每个值都与前面一个有特定的关系。要实现这一点,需要一个有状态的生产者能够记住其生成的最后一个值。
var gimmeSomething = (function () {
  var nextVal
  return function () {
    if (nextVal === undefined) {
      nextVal = 1
    } else {
      nextVal = (3 * nextVal) + 6
    }
    return nextVal
  }
})()

gimmeSomething(); // 1
gimmeSomething(); // 9
gimmeSomething(); // 33
gimmeSomething(); // 105

程序性能

Web Worker

设想一下,把你的程序分为两个部分:一部分运行在主 UI 线程下,另外一部分运行在另 一个完全独立的线程中。

一个就是,你会想要知道在独立的线程运行是否意味着它可以并行运行(在多 CPU/ 核 心的系统上),这样第二个线程的长时间运行就不会阻塞程序主线程。否则,相比于 JavaScript 中已有的异步并发,“虚拟多线程”并不会带来多少好处。

  • 从 JavaScript 主程序(或另一个 Worker)中,可以这样实例化一个 Worker:
var w1 = new Worker( "http://some.url.1/mycoolworker.js" );

这个 URL 应该指向一个 JavaScript 文件的位置(而不是一个 HTML 页面!),这个文件将 被加载到一个 Worker 中。然后浏览器启动一个独立的线程,让这个文件在这个线程中作 为独立的程序运行。

  • 以下是如何侦听事件(其实就是固定的 “message” 事件):
w1.addEventListener("message", function (evt) {
  // evt.data
});
// 也可以发送 "message" 事件给这个 Worker:
w1.postMessage("something cool to say");
// 在这个 Worker 内部,收发消息是完全对称的:

// "mycoolworker.js"
addEventListener("message", function (evt) {
  // evt.data
});
postMessage("a really cool reply");

注意,专用 Worker 和创建它的程序之间是一对一的关系。也就是说,“message” 事件 没有任何歧义需要消除,因为我们确定它只能来自这个一对一的关系:它要么来自这个 Worker,要么来自主页面。

通常由主页面应用程序创建 Worker,但若是需要的话,Worker 也可以实例化它自己的子 Worker,称为 subworker。有时候,把这样的细节委托给一个“主”Worker,由它来创建 其他 Worker 处理部分任务,这样很有用。不幸的是,到写作本书时为止,Chrome 还不支 持 subworker,不过 Firefox 支持。

要在创建 Worker 的程序中终止 Worker,可以调用 Worker 对象(就像前面代码中的 w1) 上的 terminate()。突然终止 Worker 线程不会给它任何机会完成它的工作或者清理任何资 源。这就类似于通过关闭浏览器标签页来关闭页面。

Worker 环境

在 Worker 内部是无法访问主程序的任何资源的。这意味着你不能访问它的任何全局变量,

也不能访问页面的 DOM 或者其他资源。记住,这是一个完全独立的线程。

但是,你可以执行网络操作(Ajax、WebSockets)以及设定定时器。还有,Worker 可 以访问几个重要的全局变量和功能的本地复本,包括 navigator、location、JSON 和 applicationCache。

你还可以通过 importScripts(..) 向 Worker 加载额外的 JavaScript 脚本:

// 在Worker内部
importScripts( "foo.js", "bar.js" );

这些脚本加载是同步的。也就是说,importScripts(..) 调用会阻塞余下 Worker 的执行.

共享 Worker

如果你的站点或 app 允许加载同一个页面的多个 tab(一个常见的功能),那你可能非常希望通过防止重复专用 Worker 来降低系统的资源使用。在这一方面最常见的有限资源就是 socket 网络连接,因为浏览器限制了到同一个主机的同时连接数目。当然,限制来自于同 一客户端的连接数也减轻了你的资源压力。

在这种情况下,创建一个整个站点或 app 的所有页面实例都可以共享的中心 Worker 就非 常有用了。

  • 这称为 SharedWorker,可通过下面的方式创建(只有 Firefox 和 Chrome 支持这一功能):
var w1 = new SharedWorker( "http://some.url.1/mycoolworker.js" );
  • 因为共享 Worker 可以与站点的多个程序实例或多个页面连接,所以这个 Worker 需要通过 某种方式来得知消息来自于哪个程序。这个唯一标识符称为端口(port),可以类比网络 socket 的端口。因此,调用程序必须使用 Worker 的 port 对象用于通信:
w1.port.addEventListener( "message", handleMessages );
// ..
w1.port.postMessage( "something cool" );
// 初始化端口
w1.port.start();
  • 在共享 Worker 内部,必须要处理额外的一个事件:“connect”。这个事件为这个特定的连

    接提供了端口对象。保持多个连接独立的最简单办法就是使用 port 上的闭包(参见本系列 《你不知道的 JavaScript(上卷)》的“作用域和闭包”部分),就像下面的代码一样,把这

    个链接上的事件侦听和传递定义在 “connect” 事件的处理函数内部:

// 在共享Worker内部
addEventListener("connect", function (evt) {
  // 这个连接分配的端口
  var port = evt.ports[0];
  port.addEventListener("message", function (evt) {
    // ..
    port.postMessage("...1123");
    // ..
  });
  // 初始化端口连接
  port.start();
});

SIMD

单指令多数据(SIMD)是一种数据并行(data parallelism)方式,与 Web Worker 的任务 并行(task parallelism)相对,因为这里的重点实际上不再是把程序逻辑分成并行的块,而 是并行处理数据的多个位。

asm.js

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


© 2021, 滇ICP备19003866号本网站版权归本站作者Ruoduan所有原创文章遵循CC BY-SA 4.0授权许可,转载请注明出处