附录

原型继承

nullundefined 外,每个原始数据类型都有 prototype,一种对应的对象封装容器,用于提供 属性。对基元调用方法或属性查找时, JavaScript 在后台封装基元,并调用 而不是对包装器对象执行属性查找。

例如,字符串字面量没有自己的方法,但您可以调用 根据相应的 String 对象对其调用 .toUpperCase() 方法 封装容器:

"this is a string literal".toUpperCase();
> THIS IS A STRING LITERAL

这称为原型继承,也就是继承属性和方法

Number.prototype
> Number { 0 }
>  constructor: function Number()
>  toExponential: function toExponential()
>  toFixed: function toFixed()
>  toLocaleString: function toLocaleString()
>  toPrecision: function toPrecision()
>  toString: function toString()
>  valueOf: function valueOf()
>  <prototype>: Object { … }

您可以使用这些构造函数创建基元,而不仅仅是定义 以价值为依据例如,使用 String 构造函数会创建一个 字符串对象,而不是字符串字面量:该对象不仅包含我们的字符串, 值,而是构造函数的所有继承属性和方法。

const myString = new String( "I'm a string." );

myString;
> String { "I'm a string." }

typeof myString;
> "object"

myString.valueOf();
> "I'm a string."

在大多数情况下,生成的对象的行为与我们在 定义它们。例如,虽然使用 new Number 构造函数会生成一个对象,其中包含所有方法和 Number 原型的属性,您可以在 像处理数字字面量一样:

const numberOne = new Number(1);
const numberTwo = new Number(2);

numberOne;
> Number { 1 }

typeof numberOne;
> "object"

numberTwo;
> Number { 2 }

typeof numberTwo;
> "object"

numberOne + numberTwo;
> 3

您很少需要用到这些构造函数, 原型继承意味着它们不会带来任何实际效益。正在创建 使用构造函数的基元也会导致意外结果,因为 result 是一个对象,而不是简单的字面量:

let stringLiteral = "String literal."

typeof stringLiteral;
> "string"

let stringObject = new String( "String object." );

stringObject
> "object"

这可能会使严格比较运算符的使用变得很复杂:

const myStringLiteral = "My string";
const myStringObject = new String( "My string" );

myStringLiteral === "My string";
> true

myStringObject === "My string";
> false

自动插入分号 (ASI)

在解析脚本时,JavaScript 解释器可以使用一项称为 自动插入分号 (ASI) 以尝试更正省略的 分号。如果 JavaScript 解析器遇到禁用的令牌,它会 尝试在该标记前添加一个分号来修复潜在的语法错误,例如 只要满足以下一个或多个条件即可:

  • 该标记通过换行符与前一个标记分隔。
  • 该令牌为 }
  • 上一个令牌为 ),插入的分号将是末尾 do...while 语句的英文分号。

如需了解详情,请参阅 ASI 规则

例如,在以下语句后省略分号不会导致 出现语法错误:

const myVariable = 2
myVariable + 3
> 5

但是,ASI 无法解释同一行上的多个语句。如果您 在同一行中编写多个语句,请务必使用 分号:

const myVariable = 2 myVariable + 3
> Uncaught SyntaxError: unexpected token: identifier

const myVariable = 2; myVariable + 3;
> 5

ASI 是一种尝试纠错的尝试,而不是一种构建的语法灵活性 转换为 JavaScript。务必在适当的位置使用分号,这样 以生成正确的代码。

严格模式

约束 JavaScript 编写方式的标准已经远远超出了 在早期设计语言的阶段需要考虑的任何因素。对 JavaScript 的预期行为必须避免在旧版网站中导致错误。

ES5 解决了一些长期存在的 JavaScript 语义问题, 通过引入“严格模式”打破现有实现方式,一种方式 为整个脚本或 一个单独的函数。要启用严格模式,请使用字符串字面量 "use strict",后跟一个分号,位于脚本的第一行,或者 函数:

"use strict";
function myFunction() {
  "use strict";
}

严格模式可防止某些“不安全”操作或已弃用的功能 以显式方式代替常见的“静默”错误并且禁止使用 可能会与未来语言功能产生冲突的语法。例如,早期 围绕可变范围的设计决策 让开发者更有可能错误地“污染”全局范围 则无论上下文如何,都可以声明变量,只需省略 var 个关键字:

(function() {
  mySloppyGlobal = true;
}());

mySloppyGlobal;
> true

现代 JavaScript 运行时无法纠正这种行为,并且没有以下风险: (无论是错误还是故意破坏)依赖于该网站的任何网站。 相反,现代 JavaScript 允许开发者选择严格 模式,以及仅在 不会破坏旧版实现的新语言功能:

(function() {
    "use strict";
    mySloppyGlobal = true;
}());
> Uncaught ReferenceError: assignment to undeclared variable mySloppyGlobal

您必须将 "use strict" 编写为 字符串字面量模板字面量 (use strict) 无法运行。您还必须先添加 "use strict",然后再添加 可执行代码。否则,解释器会忽略它。

(function() {
    "use strict";
    let myVariable = "String.";
    console.log( myVariable );
    sloppyGlobal = true;
}());
> "String."
> Uncaught ReferenceError: assignment to undeclared variable sloppyGlobal

(function() {
    let myVariable = "String.";
    "use strict";
    console.log( myVariable );
    sloppyGlobal = true;
}());
> "String." // Because there was code prior to "use strict", this variable still pollutes the global scope

按引用、按值

任何变量,包括对象的属性 函数参数,以及 数组setmap,可包含基元 值或引用值

将基元值从一个变量分配给另一个变量时,JavaScript 引擎会创建该值的副本并将其分配给该变量。

当您将对象(类实例、数组和函数)分配给 变量,而不是创建该对象的新副本,而是包含 对对象在内存中的存储位置的引用。因此,将 变量引用的对象不仅会更改被引用的对象, 该变量包含的值。例如,如果您将一个新的 变量,然后使用新的 变量向该对象添加属性时,系统就会添加该属性及其值 与原始对象相关联:

const myObject = {};
const myObjectReference = myObject;

myObjectReference.myProperty = true;

myObject;
> Object { myProperty: true }

这不仅对于更改对象很重要 因为对象之间的严格相等要求两个变量 引用同一个对象来求值为 true。它们不能引用 不同的对象,即使这些对象在结构上是相同的:

const myObject = {};
const myReferencedObject = myObject;
const myNewObject = {};

myObject === myNewObject;
> false

myObject === myReferencedObject;
> true

内存分配

JavaScript 使用自动内存管理,这意味着内存不需要 在开发过程中明确分配或取消分配。虽然 JavaScript 引擎的内存管理的方法远不止于此, 了解内存的分配方式有助于您了解 使用引用值。

其中有两个“领域”在内存中:“堆栈”和“堆”堆栈存储 静态数据(原始值和对对象的引用),因为 存储此数据所需的固定空间可在 脚本执行。堆会存储对象,而对象需要动态分配的空间 因为它们的大小在执行过程中可能会发生变化。内存被进程释放 称为“垃圾回收”此操作会从系统中移除没有引用的对象 内存。

主线程

JavaScript 从根本上说是一种单线程语言,具有“同步” 。此顺序执行上下文称为主线程。

主线程由其他浏览器任务共享,例如解析 HTML、 渲染和重新渲染网页的某些部分,运行 CSS 动画,以及 处理用户互动,从简单的(如突出显示文字)到 复杂结构(例如与表单元素交互)。浏览器供应商已发现 优化主线程执行的任务的方法,但更为复杂 脚本仍可能会过度使用主线程的资源, 网页性能。

有些任务可以在 称为 Web Worker 的后台线程, 但存在一些限制:

  • 工作器线程只能对独立的 JavaScript 文件执行操作。
  • 他们严重减少或无法访问浏览器窗口和界面。
  • 它们在 与主线程通信的方式方面受到限制。

由于存在这些限制,它们非常适合处理需要大量资源、 否则就会占用主线程

调用堆栈

用于管理“执行上下文”的数据结构, 调用堆栈(通常称为“调用堆栈”)列表, “堆栈”)。当某个脚本首次执行时,JavaScript 解释器 用于创建“全局执行上下文”并将其推送到调用堆栈 语句从头到尾逐一执行, 底部。当解释器在执行 全局上下文,它会推送“函数执行上下文”添加到 返回堆栈顶部,暂停全局执行上下文,并执行函数 执行上下文。

每次调用函数时,该调用的函数执行上下文 推送到堆栈顶部,就在当前执行上下文的上方。 调用堆栈按照“先进先出”的原则运作也就是说, 最近的函数调用(堆栈中最高的)执行, 直到问题得到解决。当该函数完成后,解释器会将其删除 以及包含该函数调用的执行上下文 会再次成为堆栈中的最高项并继续执行。

这些执行上下文会捕获其执行所需的所有值。他们 还建立在 函数,并确定和设置 函数上下文中的 this 关键字。

事件循环和回调队列

这种依序执行意味着包含回调函数的异步任务 功能,例如从服务器获取数据、响应用户互动, 或等待使用 setTimeoutsetInterval 设置的计时器,则会阻塞 直到该任务完成为止,或者意外中断 当回调函数的执行上下文时,当前执行上下文 就会被添加到堆栈中为解决这一问题,JavaScript 管理异步任务 使用事件驱动型“并发模型”由“事件循环”组成和 “回调队列”(有时称为“消息队列”)。

在主线程上执行异步任务时,回调 函数的执行上下文会被放在回调队列中,而不是 调用堆栈。事件循环是一种模式,有时称为 reactor 轮询调用堆栈和回调队列的状态。如果以下文件夹中有任务: 回调队列和事件循环确定调用堆栈为空, 系统将回调队列中的任务逐一推送到堆栈, 。