{"id":18280105,"url":"https://github.com/lowesyang/you-dont-know-js-notebook","last_synced_at":"2025-04-05T06:30:19.132Z","repository":{"id":94818163,"uuid":"83565855","full_name":"lowesyang/You-Dont-Know-JS-Notebook","owner":"lowesyang","description":"《你不知道的JS》阅读笔记","archived":false,"fork":false,"pushed_at":"2017-03-01T14:57:53.000Z","size":10,"stargazers_count":22,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-20T22:52:54.355Z","etag":null,"topics":["ecmascript6","javascript"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lowesyang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-01T14:50:32.000Z","updated_at":"2023-11-03T17:12:46.000Z","dependencies_parsed_at":"2023-04-24T06:01:48.426Z","dependency_job_id":null,"html_url":"https://github.com/lowesyang/You-Dont-Know-JS-Notebook","commit_stats":null,"previous_names":["lowesyang/you-dont-know-js-notebook"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lowesyang%2FYou-Dont-Know-JS-Notebook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lowesyang%2FYou-Dont-Know-JS-Notebook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lowesyang%2FYou-Dont-Know-JS-Notebook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lowesyang%2FYou-Dont-Know-JS-Notebook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lowesyang","download_url":"https://codeload.github.com/lowesyang/You-Dont-Know-JS-Notebook/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247299766,"owners_count":20916183,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ecmascript6","javascript"],"created_at":"2024-11-05T12:33:06.752Z","updated_at":"2025-04-05T06:30:18.883Z","avatar_url":"https://github.com/lowesyang.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# You-Dont-Know-JS-Notebook\n\n# 上卷\n### 作用域闭包\n- 闭包：当函数可以记住并访问所在的**词法作用域(不仅仅是显式的变量)**时，就产生了闭包，即使函数是在当前词法作用域之外执行的。\n- let声明可以用来劫持块作用域，如:\n```javascript\n    for(var i=0;i\u003c10;i++){\n       let j=i;    //块作用于变量\n        setTimeout(function(){\n            console.log(j);\n       }) \n   }\n```\n但若将let声明在for循环头部时，将会有一个特殊行为——每次迭代都会声明一次，并初始化为上一次迭代结束时的值。如：\n```javascript\n    for(let i=0;i\u003c10;i++){      //每次循环都会声明一个新的i,并且赋值为旧的i+1。\n        setTimeout(function(){\n            console.log(j);\n       }) \n   }\n```\n\n### 作用域\n- 词法作用域：一套关于引擎如何寻找变量以及会在何处找到变量的规则\n- 动态作用域：作用域在运行时被动态确定的形式\n- 块作用域的两个小例子\n  - with\n  - catch\n  - let创建块作用域变量\n\n### this\n- arguments.callee 可以指向匿名函数自身，书上说**已被废弃**，但在最新的浏览器中仍然有效。\n- this在任何情况下都不指向函数的词法作用域。作用域“对象”无法通过代码访问\n- 当一个函数被调用时，会创建一个活动记录（有时候也称为执行上下文）。这个记录会包含函数在哪里被调用（调用栈）、调用方法、传入的参数等信息。**this是记录的其中一个属性，会在函数执行过程中得到。**\n- 如何寻找调用位置\n  - 调用位置在当前正在执行函数的前一个调用中\n  ```javascript\n  function baz(){\n      // 当前调用栈是:baz\n      // 因此当前调用位置是全局作用域\n      \n      console.log(\"baz\");\n      bar();// \u003c-- bar的调用位置\n  }\n  function bar(){\n      // 当前调用栈是baz-\u003ebar\n      // 因此，当前调用位置在baz中\n      console.log(\"bar\");\n      foo();  // \u003c-- foo的调用位置\n  }\n  function foo(){\n      // 当前调用栈是baz -\u003e bar -\u003e foo\n      // 因此当前调用位置在bar中\n      console.log(\"foo\");\n  }\n  ```\n- 严格模式下，全局对象将无法使用默认绑定。\n  ```javascript\n  function foo() {\n      \"use strict\";\n      console.log( this.a );\n  }\n  var a = 2;\n  foo(); // TypeError: this is undefined\n  ```\n  仅当foo()定义中使用了严格模式，才会使得this无法绑定至全局作用域。\n- 作用域绑定\n  - 默认绑定\n  - 隐式绑定：当函数引用有上下文对象时，**隐式绑定**规则会把函数调用中的this绑定到这个上下文对象。\n  - 显示绑定：\n      - 硬绑定：使用bind()，call()和apply()显示地强制绑定作用域\n  - new绑定\n  - 判断this（优先级从上到下）\n     1. 函数是否在new 中调用（new 绑定）？如果是的话this 绑定的是新创建的对象。\nvar bar = new foo()\n     2. 函数是否通过call、apply（显式绑定）或者硬绑定调用？如果是的话，this 绑定的是\n指定的对象。\nvar bar = foo.call(obj2)\n     3. 函数是否在某个上下文对象中调用（隐式绑定）？如果是的话，this 绑定的是那个上\n下文对象。\nvar bar = obj1.foo()\n     4. 如果都不是的话，使用默认绑定。如果在严格模式下，就绑定到undefined，否则绑定到\n全局对象。\nvar bar = foo()\n  - 绑定的例外：\n    - call(null),apply(null) 实际应用默认绑定规则\n  ```javascript\n  function foo() {\n      console.log( this.a );\n  }\n  var a = 2;\n  var o = { a: 3, foo: foo };\n  var p = { a: 4 };\n  o.foo(); // 3\n  (p.foo = o.foo)(); // 2\n  ```\n  赋值语句p.foo=o.foo返回值是目标函数的引用，因此调用位置是foo()而不是p.foo()或o.foo()\n  - 软绑定：给默认绑定指定一个全局对象和undefined以外的值，实现和硬绑定相同的效果，同时保留隐式绑定或显式绑定修改this的能力。\n  [关于书上代码的解释](https://segmentfault.com/q/1010000006223479)\n  - 箭头函数使用词法作用域，根据外层作用域来决定this，不使用上述四种标准规则。\n  \n### 对象\n- 六个主要类型\n  - 简单基本类型：string、boolean、number、null、undefined本身并不是对象\n      - 语言本身的一个Bug: typeof null=='object'，但其实null是基本类型\n  - 复杂基本类型：object\n- 内置对象\n  - String,Number,Boolean,Object,Function,Array,Date,RegExp,Error\n- ES5 Object相关方法\n  - Object.assign方法中，成员属性采用=操作符赋值，并不是深拷贝。\n  - Object.preventExtensions(obj) 禁止扩展\n  - Object.seal(..) 创建一个\"密封\"的对象，实际上会在现有对象上调用Object.preventExtensions(..)，并把所有现有属性标记为configurable:false\n      - 禁止扩展\n      - 禁止配置\n  - Object.freeze(..) 创建一个冻结对象，实际上会调用Object.seal(..)并把所有\"数据访问\"属性标记为writable:false\n      - 禁止扩展\n      - 禁止配置\n      - 禁止修改属性值\n- [[Put]]大致会检查下面内容\n  1. 属性是否是访问描述符？如果是并且存在setter就调用setter\n  2. 属性的数据描述符中writable是否为false?如果是，在非严格模式下静默失败，在严格模式下抛出TypeError异常\n  3. 如果都不是，将该值设置为属性的值\n- getter和setter的另一种写法\n```javascript\nvar myObj={\n    get a(){\n      return this.val;\n    },\n    set a(newVal){\n      this.val=newVal;\n    }\n}\n```\n- 检查属性是否存在\n  - \"xx\" in Obj:会检查属性是否在对象及其原型链中\n  - hasOwnProperty(..)只会检查属性是否在Obj对象中，不会检查原型链\n- 枚举\n  - “可枚举”相当于“可以出现在对象属性的遍历中”\n  - Object.propertyIsEnumerable(..)会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足enumerable:true\n  - Object.keys(..)返回一个数组，包含所有可枚举属性。\n  - Object.getOwnPropertyNames(..)返回一个数组，包含所有属性，无论它们是否可枚举。\n- 遍历\n  - ES6新增 for..of循环语法来遍历value而非key\n  - 数组对象有迭代器Symbol.iterator，而普通对象没有。可以自行添加一个：\n  ```javascript\n  var obj={\n      a:2,\n      b:3\n  }\n  Object.defineProperty(obj,Symbol.iterator,{\n      enumerable:false,\n      writable:false,\n      configurable:false,\n      value:function(){\n          var o=this;\n          var idx=0;\n          var ks=Object.keys(o);\n          return{\n              next:function(){\n                  return {\n                      value:o[ks[idx++]],\n                      done:idx\u003eks.length\n                  }\n              }\n          }\n      }\n  })\n  ```\n  \n### 类 继承\n- 类实际上是一种设计模式。\n- 寄生继承\n```javascript\n// “寄生类” Car\nfunction Car() {\n     // 首先，car 是一个Vehicle\n     var car = new Vehicle();\n     // 接着我们对car 进行定制\n     car.wheels = 4;\n     // 保存到Vehicle::drive() 的特殊引用\n     var vehDrive = car.drive;\n     // 重写Vehicle::drive()\n     car.drive = function() {\n     vehDrive.call( this );\n     console.log(\n          \"Rolling on all \" + this.wheels + \" wheels!\"\n     );\n     return car;\n}\n```\n- 隐式混入\n```javascript\nvar Something = {\n    cool: function() {\n        this.greeting = \"Hello World\";\n        this.count = this.count ? this.count + 1 : 1;\n    }\n};\nSomething.cool();\nSomething.greeting; // \"Hello World\"\nSomething.count; // 1\nvar Another = {\n    cool: function() {\n    // 隐式把Something 混入Another\n        Something.cool.call( this );\n    }\n};\nAnother.cool();\nAnother.greeting; // \"Hello World\"\nAnother.count; // 1 （count 不是共享状态）\n```\n\n### 原型\n- [[Prototype]]原型链\n  - 假设有```obj.foo=\"foo\"```的语句。如果foo属性不直接存在于obj中而是存在于原型链上层时，上述语句会出现三种情况：\n    1. 如果在[[Prototype]] 链上层存在名为foo 的普通数据访问属性（参见第3 章）并且没有被标记为只（writable:false），那就会直接在myObject 中添加一个名为foo 的新属性，它是屏蔽属性。\n    2. 如果在[[Prototype]] 链上层存在foo，但是它被标记为只读（writable:false），那么无法修改已有属性或在obj上创建屏蔽属性。如果运行在严格模式下，代码会抛出一个错误。否则，这条赋值语句会被忽略。总之，不会发生屏蔽。\n    3. 如果在[[Prototype]] 链上层存在foo 并且它是一个setter（参见第3 章），**那就一定会调用这个setter**。foo 不会被添加到（或者说屏蔽于）myObject，也不会重新定义foo 这个setter。\n    - 如果希望在上述第二、三种情况下也屏蔽foo，那就需要使用Object.defineProperty来向obj添加foo\n- 原型继承\n  - 方法一：```newObj.prototype=Object.create(oldObj.prototype)```\n  - 方法二：```newObj.prototype=new oldObj()```\n  - ES6新增的修改对象原型的API:```Object.setPrototypeOf(obj,other.prototype)```\n      - polyfill:\n      ```javascript\n      Object.setPrototypeOf=Object.setPrototypeOf||function(obj,proto){\n          obj.__proto__=proto;\n          return obj;\n      }\n      ```\n- 检查\"类\"关系\n  - ```a instanceof Foo``` 返回a的整条原型链中是否有指向Foo.prototype的对象。但仅限对象与函数之间的关系检查。\n  - ```obj1.isPrototypeOf(obj2)``` 返回obj1是否出现在obj2的原型链中。\n  - ```Object.getPrototypeOf(..)``` 获取原型链(\\__proto__)\n- class语法\n  - ES6中，class无法定义类成员属性，只能定义类成员方法\n  - super只能用于constructor()函数中。在子类中，只有在调用super()之后，才能使用this指针。\n  \n### 行为委托\n- 委托行为：在找不到属性或者方法引用时，会把这个请求委托给另一个对象。js中的表现是委托原型链上的对象。\n- 使用函数对象的name标识符：\n```javascript\nvar Foo={\n    bar:function bar(){}\n}\n```\n好处是方便自我引用和追踪调用栈。\n- 自省：检查实例的类型\n  - 主要目的：通过创建方式来判断对象的结构和功能\n- JS的原型链机制本质上就是行为委托机制。\n\n\n# 中卷\n### 类型\n- symbol是ES6新增的第七种类型\n- ```typeof null === \"object\" // true```\n\n### 值\n- 数组\n  - length 由数组最大的下标决定，之前的所有元素若未赋值，则为undefined。例如:\n  ```javascript\n  var a=[]\n  a[2]='1';\n  a.length===3 //true\n  ```\n- 数字\n  - js中没有真正意义上的整数。“整数”即是没有小数的十进制数。\n  - .tofixed(..)用来指定小数部分的显示位数\n  - .topRecision(..)用来指定有效数位的显示位数\n  - 浮点数相加通常不精确。解决方案是设置一个误差范围值，称为\"机器精度\"。ES6中，该值定义为Number.EPSILON中\n  - 能够呈现的最大浮点数：Number.MAX_VALUE;最小浮点数:Number.MIN_VALUE\n  - 非自反值：NaN\n\n### 原生函数\n- js会自动为基本类型值包装一个封装对象\n  - 不需要对诸如.length这样的场景做提前的性能优化，原因是浏览器已经对此做了优化，直接使用封装对象来\"提前优化\"反而会降低执行效率。\n- 优先使用基本类型而非封装对象\n- Function.prototype是一个空函数;RegExp.prototype是一个\"空\"的正则表达式（非空对象）;Array.prototype是一个空数组\n- 函数调用的详细过程：\n  - 当调用一个函数时，一个新的执行上下文就会被创建。\n  1. 创建阶段：执行上下文会分别创建变量对象，建立作用域链，以及确定this的指向。\n      - 创建变量对象的过程：\n      1. 建立arguments对象。\n      2. 检查当前上下文的函数声明，也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性，属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在，那么该属性将会被新的引用所覆盖。\n      3. 检查当前上下文中的变量声明，每找到一个变量声明，就在变量对象中以变量名建立一个属性，属性值为undefined。如果该变量名的属性已经存在，为了防止同名的函数被修改为undefined，则会直接跳过，原属性值不会被修改。\n  2. 代码执行阶段：创建完成之后，开始执行代码。这个时候，会完成变量赋值，函数引用，以及执行其他代码。\n  ![](http://upload-images.jianshu.io/upload_images/599584-391af3aad043c028.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n  3.执行完毕后出栈，等待被回收\n\n### 强制类型转换\n- 值类型转换\n  - 发生在运行时(runtime)\n  - 隐式强制类型转换 ```var a=42; var b=a+\"\" ```\n  - 显式强制类型转换 ```var c=String(a);```\n- 抽象值操作\n  - toString\n      - 对于普通对象，除非自行定义，否则toString()返回内部属性[[Class]]的值\n  - JSON\n      - JSON.stringify()可以字符串化所有安全的JSON值\n      - 不安全的JSON值：undefined,function,symbol(ES6+)和包含循环引用(对象之间互相引用，形成一个无线循环)的对象。\n      - toJSON() 如果对象中定义了toJSON()方法，JSON字符串化时会首先调用该方法，然后用它的值来进行序列化。\n  - ToNumber\n      - Number()\n      - undefined-\u003eNaN,null-\u003e0\n      - 处理失败时返回NaN\n      - \"\",\"\\n\",\" \"等空字符串被ToNumber强制类型转换为0\n  - ToBoolean\n      - 假值(falsy value):undefined,null,false,+0,-0,NaN,\"\"。转换为false\n      - 假值对象(falsy object)\n          - 并非封装了假值的对象\n          - document.all是一个假值对象。经常通过将其转换为Boolean来判断浏览器是否是老版本IE。\n      ```javascript\n      if(document.all){ /* it's IE */}\n      ```\n      - 真值:除\"\"外的其他字符串,[],{},function(){}等\n  - 对象的toPrimitive操作:调用valueOf()，若有并返回基本类型值，则使用该值进行强制类型转；若没有则调用toString()。如果两者都没有则产生TypeError错误。\n  - 显示强制转换\n      - 例:``` var c=\"3.14\"; var d=+c; //3.14```\n      - 日期显示转换为数字\n          - 获取时间戳的快速方法：```var timestamp=+new Date();```\n      - ~运算符\n          - 只适用于32位整数。强制操作数使用32位格式\n          - 首先将值强制类型转换为32位数字，然后执行字位操作\"非\"\n          - 对负数的处理与Math.floor(..)不同\n      ```javascript\n      Math.floor(-49.6);  // -50\n      ~~-49.6;            // -49\n      ```\n          - 用途：将字符串转为整数，取整，将值截为32位整数等\n  - 显式解析数字字符串\n      - parseInt(..)\n          - 先将参数强制类型转换为字符串再进行解析。\n          - 一些奇怪的行为：\n    ```javascript\n    parseInt( 0.000008 ); // 0 (\"0\" 来自于 \"0.000008\")\nparseInt( 0.0000008 ); // 8 (\"8\" 来自于 \"8e-7\")\nparseInt( false, 16 ); // 250 (\"fa\" 来自于 \"false\")\nparseInt( parseInt, 16 ); // 15 (\"f\" 来自于 \"function..\")\nparseInt( \"0x10\" ); // 16\nparseInt( \"103\", 2 ); // 2\n    ```\n- 相等比较\n  - 对于x==\n      - 若type(x)为数字,type(y)为字符串，则返回x==ToNumber(y)的结果\n      - 若type(x)为bool,type(y)为数字，则返回toNumber(x)==y的结果\n      - 若type(x)为字符串或数字,type(y)为对象，则返回x==ToPrimitive(y)的结果\n  - 在==中,null和undefined是一回事。除此之外其他值都不存在这种情况。\n  ```javascript\n  var a = null;\nvar b;\na == b; // true\na == null; // true\nb == null; // true\na == false; // false\nb == false; // false\na == \"\"; // false\nb == \"\"; // false\na == 0; // false\nb == 0; // false\n  ```\n  - 根据规范,\u003c= 被处理为 \u003e，然后将结果翻转。例如a\u003c=b被处理为a\u003eb，然后将该结反转得到原结果值。\n  \n### 混合环境javascript\n- 全局DOM变量\n  - 声明一个全局变量不仅仅是创建了一个全局变量，还会在global对象（浏览器中为window）中创建一个同名属性\n  - 由于浏览器演进的历史遗留问题，在创建带有id属性的DOM元素时也会创建同名的全局变量:\n  ```javascript\n  \u003cdiv id=\"foo\"\u003e\u003c/div\u003e\n  console.log(foo)  //HTML元素\n  ```\n- 部分引擎的一些限制：\n  - 字符串常量中允许的最大字符数（并非只是针对字符串值）；\n  - 可以作为参数传递到函数中的数据大小（也称为栈大小，以字节为单位）；\n  - 函数声明中的参数个数；\n  - 未经优化的调用栈（例如递归）的最大层数，即函数调用链的最大长度；\n  - JavaScript 程序以阻塞方式在浏览器中运行的最长时间（秒）；\n  - 变量名的最大长度。\n\n### 异步\n- 并发\n  - 严格来说，setTimeout(..,0)并不直接把项目插入到事件循环队列。定时器会在“有机会”的时候插入事件。因此两个连续的setTimeout(..,0)调用并不能严格保证其顺序处理。\n- 任务\n  - ES6引入了任务对列，建立在事件循环队列之上。\n      - 挂在事件循环队列的每个tick之后的一个队列\n      - 在每次事件循环中要确保任务对列中的事件都做完（实际上用了嵌套循环）\n\n### Promise\n- Promise调用then(..)的时候，即使这个Promise已经决议，一同给then(..)的回调也总会被异步调用。\n- 一个Promise决议后，这个Promise上所有的通过then(..)注册的回调都会在下一个异步时机点依次被立即调用。\n- 不要依赖于Promise间回调的顺序和调度。\n- 若把同一个回调注册了不止一次(p.then(f);p.then(f))，那它被调用的次数就会和注册次数相同。\n- Promise回调中发生的异常错误，都会导致Promise拒绝(reject)\n- 关于Promise从创建到状态改变的执行顺序:\n```javascript\nlet promise = new Promise(function(resolve, reject) {\n    console.log('Promise');\n    resolve();\n});\npromise.then(function() {\n    console.log('Resolved.');\n});\nconsole.log('Hi!');\n// Promise\n// Hi!\n// Resolved\n```\n- Promise.catch():最好使用catch(..)来捕获error。原因：catch既可以捕捉Promise回调中的错误，也可以捕捉在它之前的then(..)回调中的错误。\n- 将多个Promise实例包装成一个新的Promise实例\n  - Promise.all([]):仅当所有Promise为resolve，状态才变为resolve；如果有其中一个reject，状态就变为reject\n  - Promise.race([]):当其中有一个实例改变了状态，则最终状态发生改变。那个率先改变状态的Promise实例返回的值便传递给then()（或catch()）回调函数。\n- Promise.resolve()（Promise.reject()）将现有对象转换成Promise对象。具体请看《ECMAScript6入门》\n\n### 生成器\n- 任意一个数组对象的Symbol.iterator方法，等于该对象的遍历器生成函数，调用该函数会返回该对象的一个遍历器对象。\n```javascript\nlet myIterable={};\nlet it=myIterable[Symbol.iterator]()\nit.next()\n```\nfor..of 应用的对象必须有Symbol.iterator方法\n- Generator函数就是遍历器生产函数，因此可以把Generator赋值给对象的Symbol.iterator属性\n- 若Generator函数中有赋值yield的语句，则next()传入的参数影响到函数内变量的值。例如:\n```javascript\nfunction* foo(x) {\n    var y = 2 * (yield (x + 1));\n    var z = yield (y / 3);\n    return (x + y + z);\n}\nvar a = foo(5);\na.next() // Object{value:6, done:false}\na.next() // Object{value:NaN, done:false}\na.next() // Object{value:NaN, done:true}\nvar b = foo(5);\nb.next() // { value:6, done:false }\nb.next(12) // { value:8, done:false }\nb.next(13) // { value:42, done:true }\n```\n\n### 程序性能\n- web worker\n  - worker之间以及它们与主程序之间不会共享任何作用域或资源\n  - 实例化worker:\n  ```javascript\n  var w1=new Worker(\"http://xxx/index.js\")  //应该指向一个javascript位置\n  ```\n  - 共享worker```new SharedWorker(url)```\n      - 用于创建一个可以共享的中心Worker，节省资源\n      - 调用程序必须使用worker的port对象用于通信：\n    ```javascript\n    w1.port.addEventListener(...)\n    w1.port.postMessage(..)\n    ```\n- SIMD(单指令多数据)是一种数据并行方式，与Web Worker的任务并行相对。\n  - 重点：并行处理数据的多个位\n- asm.js 指js中可以高度优化的一个子集\n\n### 性能测试与调优\n- **Benchmark.js** 已经实现了许多有效的性能测试逻辑\n- 写好测试\n  - 不要试图窄化到真实代码的微小片段，以及脱离上下文而只测量这一小部分的性能，因为\n包含更大（仍然有意义的）上下文时功能测试和性能测试才会更好。这些测试可能也会运\n行得慢一点，这意味着环境中发现的任何差异都更有意义。\n- 尾调用优化(TCO)\n  - 尾调用：出现在另一个函数“结尾”处的函数调用，并且调用结束后没有其余事情要做了\n  ```javascript\n  function foo(x){\n      return x;\n  }\n  function bar(y){\n      return foo(y+1);  // 尾调用\n  }\n  function baz(){\n      return 1+bar(40); // 非尾调用\n  }\n  ```\n  - 优化：支持TCO的引擎能够意识到foo(y+1)位于尾部，意味着bar(..)基本上已经完成了，那么在调用foo(..)时，就不需要创建一个新的栈帧，而是可以重用已有的bar(..)的栈帧。这样不仅速度快，也更节省内存。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flowesyang%2Fyou-dont-know-js-notebook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flowesyang%2Fyou-dont-know-js-notebook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flowesyang%2Fyou-dont-know-js-notebook/lists"}