🛤️现代 JavaScript —— JavaScript
00 分钟
2022-8-30
2022-9-15
type
status
date
slug
summary
tags
category
icon
password
Edited
Sep 15, 2022 01:17 PM
Created
Aug 24, 2022 08:13 AM

备忘单

数组方法

  • 添加/删除元素:
    • push(...items) —— 向尾端添加元素,
    • pop() —— 从尾端提取一个元素,
    • shift() —— 从首端提取一个元素,
    • unshift(...items) —— 向首端添加元素,
    • splice(pos, deleteCount, ...items) —— 从 pos 开始删除 deleteCount 个元素,并插入 items
    • slice(start, end) —— 创建一个新数组,将从索引 start 到索引 end(但不包括 end)的元素复制进去。
    • concat(...items) —— 返回一个新数组:复制当前数组的所有元素,并向其中添加 items。如果 items 中的任意一项是一个数组,那么就取其元素。
  • 搜索元素:
    • indexOf/lastIndexOf(item, pos) —— 从索引 pos 开始搜索 item,搜索到则返回该项的索引,否则返回 1
    • includes(value) —— 如果数组有 value,则返回 true,否则返回 false
    • find/filter(func) —— 通过 func 过滤元素,返回使 func 返回 true 的第一个值/所有值。
    • findIndex 和 find 类似,但返回索引而不是值。
  • 遍历元素:
    • forEach(func) —— 对每个元素都调用 func,不返回任何内容。
  • 转换数组:
    • map(func) —— 根据对每个元素调用 func 的结果创建一个新数组。
    • sort(func) —— 对数组进行原位(in-place)排序,然后返回它。
    • reverse() —— 原位(in-place)反转数组,然后返回它。
    • split/join —— 将字符串转换为数组并返回。
    • reduce/reduceRight(func, initial) —— 通过对每个元素调用 func 计算数组上的单个值,并在调用之间传递中间结果。
  • 其他:
    • Array.isArray(value) 检查 value 是否是一个数组,如果是则返回 true,否则返回 false
请注意,sortreverse 和 splice 方法修改的是数组本身。
这些是最常用的方法,它们覆盖 99% 的用例。
 

知识点

空值合并运算符 ??

空值合并运算符(nullish coalescing operator)的写法为两个问号 ??
当一个值既不是 null也不是 undefined时,我们将其称为“已定义的(defined)
a ?? b 的结果是:
  • 如果 a 是已定义的,则结果为 a
  • 如果 a 不是已定义的,则结果为 b
换句话说,如果第一个参数不是 null/undefined,则 ?? 返回第一个参数。否则,返回第二个参数
ps:?? 运算符的优先级与 || 相同,它们的的优先级都为 4
 

可选链 ?.

可选链 ?.是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误
如果可选链 ?. 前面的值为 undefined 或者 null ,它会停止运算并返回 undefined
例如 value?.prop
  • 如果 value 存在,则结果与 value.prop 相同,
  • 否则(当 value 为 undefined/null 时)则返回 undefined
ps:?. 语法使其前面的值成为可选值,但不会对其后面的起作用

变体:?.()?.[]

可选链 ?. 不是一个运算符,而是一个特殊的语法结构。它还可以与函数和方括号一起使用
 

arr.at(i)

  • 如果 i >= 0,则与 arr[i] 完全相同。
  • 对于 i 为负数的情况,它则从数组的尾部向前数。
可以通过 at(-1) 快速获取数组最后一个
 

Module 的导入和导出

所有 export 类型:
  • 在声明一个 class/function/… 之前:
    • export [default] class/function/variable ...
  • 独立的导出:
    • export {x [as y], ...}.
  • 重新导出:
    • export {x [as y], ...} from "module"
    • export * from "module"(不会重新导出默认的导出)。
    • export {default [as y]} from "module"(重新导出默认的导出)。
import 类型:
  • 导入命名的导出:
    • import {x [as y], ...} from "module"
  • 导入默认的导出:
    • import x from "module"
    • import {default as x} from "module"
  • 导入所有:
    • import * as obj from "module"
  • 导入模块(其代码,并运行),但不要将其任何导出赋值给变量:
    • import "module"

动态导入

import() 表达式
import(module) 表达式加载模块并返回一个 promise,该 promise resolve 为一个包含其所有导出的模块对象。我们可以在代码中的任意位置调用这个表达式。
 

柯里化(Currying)

柯里化(Currying)是一种关于函数的高阶技术。它不仅被用于 JavaScript,还被用于其他编程语言
柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)
柯里化不会调用函数。它只是对函数进行转换。
实现非常简单:只有两个包装器(wrapper)。
  • curry(func) 的结果就是一个包装器 function(a)
  • 当它被像 curriedSum(1) 这样调用时,它的参数会被保存在词法环境中,然后返回一个新的包装器 function(b)
  • 然后这个包装器被以 2 为参数调用,并且,它将该调用传递给原始的 sum 函数。
柯里化目的:
  1. 柯里化之后,我们没有丢失任何东西:log 依然可以被正常调用。
  1. 我们可以轻松地生成偏函数,例如用于生成今天的日志的偏函数。

高级柯里化实现

这里有两个 if 执行分支:
  1. 如果传入的 args 长度与原始函数所定义的(func.length)相同或者更长,那么只需要使用 func.apply 将调用传递给它即可。
  1. 否则,获取一个偏函数:我们目前还没调用 func。取而代之的是,返回另一个包装器 pass,它将重新应用 curried,将之前传入的参数与新的参数一起传入。
 

Proxy & Reflect

Proxy 和 Reflect
一个 Proxy 对象包装另一个对象并拦截诸如读取/写入属性和其他操作,可以选择自行处理它们,或者透明地允许该对象处理它们。 Proxy 被用于了许多库和某些浏览器框架。在本文中,我们将看到许多实际应用。 语法: target -- 是要包装的对象,可以是任何东西,包括函数。 handler -- 代理配置:带有"捕捉器"("traps",即拦截操作的方法)的对象。比如 get 捕捉器用于读取 target 的属性,set 捕捉器用于写入 target 的属性,等等。 对 proxy 进行操作,如果在 handler 中存在相应的捕捉器,则它将运行,并且 Proxy 有机会对其进行处理,否则将直接对 target 进行处理。 首先,让我们创建一个没有任何捕捉器的代理(Proxy): 由于没有捕捉器,所有对 proxy 的操作都直接转发给了 target 。 我们可以看到,没有任何捕捉器, proxy 是一个 target 的透明包装器(wrapper)。 要激活更多功能,让我们添加捕捉器。 我们可以用它们拦截什么? 对于对象的大多数操作,JavaScript 规范中有一个所谓的"内部方法",它描述了最底层的工作方式。例如 [[Get]],用于读取属性的内部方法,[[Set]] ,用于写入属性的内部方法,等等。这些方法仅在规范中使用,我们不能直接通过方法名调用它们。 Proxy 捕捉器会拦截这些方法的调用。它们在 proxy 规范 和下表中被列出。
Proxy 和 Reflect
一个 Proxy 对象包装另一个对象并拦截诸如读取/写入属性和其他操作,可以选择自行处理它们,或者透明地允许该对象处理它们。

Proxy

  • target —— 是要包装的对象,可以是任何东西,包括函数。
  • handler —— 代理配置:带有“捕捉器”(“traps”,即拦截操作的方法)的对象。比如 get 捕捉器用于读取 target 的属性,set 捕捉器用于写入 target 的属性,等等。
对 proxy 进行操作,如果在 handler 中存在相应的捕捉器,则它将运行,并且 Proxy 有机会对其进行处理,否则将直接对 target 进行处理。
proxy 是一个 target 的透明包装器(wrapper)
notion image
JavaScript 规范中有一个所谓的“内部方法”,它描述了最底层的工作方式。例如 [[Get]],用于读取属性的内部方法,[[Set]],用于写入属性的内部方法,等等。这些方法仅在规范中使用,我们不能直接通过方法名调用它们。
对于每个内部方法,此表中都有一个捕捉器:可用于添加到 new Proxy 的 handler 参数中以拦截操作的方法名称:
内部方法
Handler 方法
何时触发
[[Get]]
get
读取属性
[[Set]]
set
写入属性
[[HasProperty]]
has
in 操作符
[[Delete]]
deleteProperty
delete 操作符
[[Call]]
apply
函数调用
[[Construct]]
construct
new 操作符
[[GetPrototypeOf]]
getPrototypeOf
Object.getPrototypeOf
[[SetPrototypeOf]]
setPrototypeOf
Object.setPrototypeOf
[[IsExtensible]]
isExtensible
Object.isExtensible
[[PreventExtensions]]
preventExtensions
Object.preventExtensions
[[DefineOwnProperty]]
defineProperty
Object.defineProperty, Object.defineProperties
[[GetOwnProperty]]
getOwnPropertyDescriptor
Object.getOwnPropertyDescriptor, for..inObject.keys/values/entries
[[OwnPropertyKeys]]
ownKeys
Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..inObject.keys/values/entries

get & set

get(target, property, receiver) :
  • target —— 是目标对象,该对象被作为第一个参数传递给 new Proxy
  • property —— 目标属性名,
  • receiver —— 如果目标属性是一个 getter 访问器属性,则 receiver 就是本次读取属性所在的 this 对象。通常,这就是 proxy 对象本身(或者,如果我们从 proxy 继承,则是从该 proxy 继承的对象)
set(target, property, value, receiver)
  • target —— 是目标对象,该对象被作为第一个参数传递给 new Proxy
  • property —— 目标属性名称,
  • value —— 目标属性的值,
  • receiver —— 与 get 捕捉器类似,仅与 setter 访问器属性相关。
如果写入操作(setting)成功,set 捕捉器应该返回 true,否则返回 false(触发 TypeError
请注意在 (*) 行中 get 捕捉器的重要细节:
对 user.checkPassword() 的调用会将被代理的对象 user 作为 this(点符号之前的对象会成为 this),因此,当它尝试访问 this._password 时,get 捕捉器将激活(在任何属性读取时,它都会被触发)并抛出错误。
因此,我们在 (*) 行中将对象方法的上下文绑定到原始对象 target。然后,它们将来的调用将使用 target 作为 this,不会触发任何捕捉器。

Reflect

Reflect 是一个内建对象,可简化 Proxy 的创建。
前面所讲过的内部方法,例如 [[Get]] 和 [[Set]] 等,都只是规范性的,不能直接调用。
Reflect 对象使调用这些内部方法成为了可能。它的方法是内部方法的最小包装。
操作
Reflect 调用
内部方法
obj[prop]
Reflect.get(obj, prop)
[[Get]]
obj[prop] = value
Reflect.set(obj, prop, value)
[[Set]]
delete obj[prop]
Reflect.deleteProperty(obj, prop)
[[Delete]]
new F(value)
Reflect.construct(F, value)
[[Construct]]
对于每个可被 Proxy 捕获的内部方法,在 Reflect 中都有一个对应的方法,其名称和参数与 Proxy 捕捉器相同。

Reflect.gettarget[prop] 的细微的差别

在 (*) 行
  1. 当我们读取 admin.name 时,由于 admin 对象自身没有对应的的属性,搜索将转到其原型。
  1. 原型是 userProxy
  1. 从代理读取 name 属性时,get 捕捉器会被触发,并从原始对象返回 target[prop] 属性,
    1. 当调用 target[prop] 时,若 prop 是一个 getter,它将在 this=target 上下文中运行其代码。因此,结果是来自原始对象 target 的 this._name,即来自 user
为了解决这种情况,我们需要 get 捕捉器的第三个参数 receiver。它保证将正确的 this 传递给 getter。在我们的例子中是 admin
Proxy != target

可撤销 Proxy

一个 可撤销 的代理是可以被禁用的代理。
 

原始值转换

当对象相加 obj1 + obj2,相减 obj1 - obj2,或者使用 alert(obj) 打印时会发生什么?
在此类运算的情况下,对象会被自动转换为原始值,然后对这些原始值进行运算,并得到运算结果(也是一个原始值)

hint

这里有三种类型(hint):
  • "string"(对于 alert 和其他需要字符串的操作)
  • "number"(对于数学运算)
  • "default"(少数运算符,通常对象以和 "number" 相同的方式实现 "default" 转换)

为了进行转换,JavaScript 尝试查找并调用三个对象方法:
  1. 调用 obj[Symbol.toPrimitive](hint) —— 带有 symbol 键 Symbol.toPrimitive(系统 symbol)的方法,如果这个方法存在的话,
  1. 否则,如果 hint 是 "string" —— 尝试调用 obj.toString() 或 obj.valueOf(),无论哪个存在。
  1. 否则,如果 hint 是 "number" 或 "default" —— 尝试调用 obj.valueOf() 或 obj.toString(),无论哪个存在。

Symbol.toPrimitive
toString/valueOf
  • 对于 "string" hint:调用 toString 方法,如果它不存在,则调用 valueOf 方法(因此,对于字符串转换,优先调用 toString)。
  • 对于其他 hint:调用 valueOf 方法,如果它不存在,则调用 toString 方法(因此,对于数学运算,优先调用 valueOf 方法)。
其中
  • toString 方法返回一个字符串 "[object Object]"
  • valueOf 方法返回对象自身。

通常我们希望有一个“全能”的地方来处理所有原始转换。在这种情况下,我们可以只实现 toString ,就像这样
 

原始类型的方法

当作对象的原始类型
  1. 原始类型仍然是原始的。与预期相同,提供单个值
  1. JavaScript 允许访问字符串,数字,布尔值和 symbol 的方法和属性
  1. 为了使它们起作用,创建了提供额外功能的特殊“对象包装器”,使用后即被销毁

对象包装器

“对象包装器”对于每种原始类型都是不同的,它们被称为 StringNumberBooleanSymbol 和 BigInt。因此,它们提供了不同的方法
如:
以下是 str.toUpperCase() 中实际发生的情况:
  1. 字符串 str 是一个原始值。因此,在访问其属性时,会创建一个包含字符串字面值的特殊对象,并且具有可用的方法,例如 toUpperCase()
  1. 该方法运行并返回一个新的字符串(由 alert 显示)。
  1. 特殊对象被销毁,只留下原始值 str
所以原始类型可以提供方法,但它们依然是轻量级的。
JavaScript 引擎高度优化了这个过程。它甚至可能跳过创建额外的对象。但是它仍然必须遵守规范,并且表现得好像它创建了一样
 

原型链

[[Prototype]]

对象有一个特殊的隐藏属性 [[Prototype]](如规范中所命名的),它要么为 null,要么就是对另一个对象的引用。该对象被称为“原型”
当我们从 object 中读取一个缺失的属性时,JavaScript 会自动从原型中获取该属性。在编程中,这被称为“原型继承”
属性 [[Prototype]] 是内部的而且是隐藏的,但是这儿有很多设置它的方式。
其中之一就是使用特殊的名字 __proto__
__proto__ 是 [[Prototype]] 的因历史原因而留下来的 getter/setter
__proto__ 与内部的 [[Prototype]] 不一样__proto__ 是 [[Prototype]] 的 getter/setter
现代编程语言建议我们应该使用函数 Object.getPrototypeOf/Object.setPrototypeOf 来取代 __proto__ 去 get/set 原型。

“this” 的值:this 根本不受原型的影响
无论在哪里找到方法:在一个对象还是在原型中。在一个方法调用中,this 始终是点符号 . 前面的对象。
for..in 循环也会迭代继承的属性。

F.prototype

如果 F.prototype 是一个对象,那么 new 操作符会使用它为新对象设置 [[Prototype]]
设置 Rabbit.prototype = animal 的字面意思是:“当创建了一个 new Rabbit 时,把它的 [[Prototype]] 赋值为 animal”。
示意图:
"prototype" 是一个水平箭头,表示一个常规属性,[[Prototype]] 是垂直的,表示 rabbit 继承自 animal。
"prototype" 是一个水平箭头,表示一个常规属性,[[Prototype]] 是垂直的,表示 rabbit 继承自 animal
默认的 "prototype" 是一个只有属性 constructor 的对象,属性 constructor 指向函数自身。
notion image

Object.prototype

notion image
比如当 obj.toString() 被调用时,这个方法是从 Object.prototype 中获取的
所有的内建原型顶端都是 Object.prototype。这就是为什么有人说“一切都从对象继承而来”。
notion image
注意:基本数据类型也存在对象
如果我们试图访问它们的属性,那么临时包装器对象将会通过内建的构造器 StringNumber 和 Boolean 被创建。它们提供给我们操作字符串、数字和布尔值的方法然后消失。
这些对象对我们来说是无形地创建出来的。大多数引擎都会对其进行优化,但是规范中描述的就是通过这种方式。这些对象的方法也驻留在它们的 prototype 中,可以通过 String.prototypeNumber.prototype 和 Boolean.prototype 进行获取。
值 null 和 undefined 没有对象包装器

从原型中借用

现代的获取/设置原形的方法有:

  • Object.getPrototypeOf(obj) —— 返回对象 obj 的 [[Prototype]]
  • Object.setPrototypeOf(obj, proto) —— 将对象 obj 的 [[Prototype]] 设置为 proto
  • Object.create(proto, [descriptors]) —— 利用给定的 proto 作为 [[Prototype]] 和可选的属性描述来创建一个空对象。
更强大的对象克隆方式:
此调用可以对 obj 进行真正准确地拷贝,包括所有的属性:可枚举和不可枚举的,数据属性和 setters/getters —— 包括所有内容,并带有正确的 [[Prototype]]

"Very plain" objects

它没有继承 __proto__ 的 getter/setter 方法。现在,它被作为正常的数据属性进行处理,因此这个示例能够正常工作。
我们可以把这样的对象称为 “very plain” 或 “pure dictionary” 对象,因为它们甚至比通常的普通对象(plain object){...} 还要简单。
 

JS 执行机制(Event Loop)

在我们学js 的时候都知道js 是单线程的如果是多线程的话会引发一个问题在同一时间同时操作DOM 一个增加一个删除JS就不知道到底要干嘛了,所以这个语言是单线程的但是随着HTML5到来js也支持了多线程webWorker 但是也是不允许操作DOM
单线程就意味着所有的任务都需要排队,后面的任务需要等前面的任务执行完才能执行,如果前面的任务耗时过长,后面的任务就需要一直等,一些从用户角度上不需要等待的任务就会一直等待,这个从体验角度上来讲是不可接受的,所以JS中就出现了异步的概念。

同步任务

代码从上到下按顺序执行

异步任务

  1. 宏任务
    1. script(整体代码)、setTimeout、setInterval、UI交互事件、postMessage、Ajax
  1. 微任务
    1. Promise.then catch finally、MutaionObserver、process.nextTick(Node.js 环境)

运行机制

所有的同步任务都是在主进程执行的形成一个执行栈,主线程之外,还存在一个"任务队列",异步任务执行队列中先执行宏任务,然后清空当次宏任务中的所有微任务,然后进行下一个tick如此形成循环
 

generator

而 generator 可以按需一个接一个地返回(“yield”)多个值。它们可与 iterable 完美配合使用,从而可以轻松地创建数据流。
要创建一个 generator,我们需要一个特殊的语法结构:function*,即所谓的 “generator function”。
generator 函数与常规函数的行为不同。在此类函数被调用时,它不会运行其代码。而是返回一个被称为 “generator object” 的特殊对象,来管理执行流程。
一个 generator 的主要方法就是 next()。当被调用时(译注:指 next() 方法),它会恢复上图所示的运行,执行直到最近的 yield <value> 语句(value 可以被省略,默认为 undefined)。然后函数执行暂停,并将产出的(yielded)值返回到外部代码。
next() 的结果始终是一个具有两个属性的对象:
  • value: 产出的(yielded)的值。
  • done: 如果 generator 函数已执行完成则为 true,否则为 false
notion image

generator 是可迭代的

可以使用 for..of 循环遍历它所有的值
因为 generator 是可迭代的,我们可以使用 iterator 的所有相关功能,例如:spread 语法 ...
可迭代对象:

generator组合使用

yield* 指令将执行 委托 给另一个 generator。这个术语意味着 yield* gen 在 generator gen 上进行迭代,并将其产出(yield)的值透明地(transparently)转发到外部。就好像这些值就是由外部的 generator yield 的一样。

“yield” 是一条双向路

yield 是一条双向路(two-way street):它不仅可以向外返回结果,而且还可以将外部的值传递到 generator 内。
调用 generator.next(arg),我们就能将参数 arg 传递到 generator 内部。这个 arg 参数会变成 yield 的结果。
notion image
generator.throw
向 yield 传递一个 error
generator.return
generator.return(value) 完成 generator 的执行并返回给定的 value

异步 generator

异步 iterator 与常规 iterator 在语法上的区别:
Iterable
异步 Iterable
提供 iterator 的对象方法
Symbol.iterator
Symbol.asyncIterator
next() 返回的值是
{value:…, done: true/false}
resolve 成 {value:…, done: true/false} 的 Promise
异步 generator 与常规 generator 在语法上的区别:
Generator
异步 generator
声明方式
function*
async function*
next() 返回的值是
{value:…, done: true/false}
resolve 成 {value:…, done: true/false} 的 Promise
 

奇特点

仅仅只是复制一下,了解一下多么奇妙

使用两个点来调用一个方法

💡
使用两个点来调用一个方法
请注意 123456..toString(36) 中的两个点不是打错了。如果我们想直接在一个数字上调用一个方法,比如上面例子中的 toString,那么我们需要在它后面放置两个点 ..
如果我们放置一个点:123456.toString(36),那么就会出现一个 error,因为 JavaScript 语法隐含了第一个点之后的部分为小数部分。如果我们再放一个点,那么 JavaScript 就知道小数部分为空,现在使用该方法。
也可以写成 (123456).toString(36)
 

0.1 和 0.2 的总和是否为 0.3 ?

💡
0.1 和 0.2 的总和是否为 0.3 ?
我们会得到 false
奇了怪了!如果不是 0.3,那能是啥?
我擦!想象一下,你创建了一个电子购物网站,如果访问者将价格为 ¥ 0.10 和 ¥ 0.20 的商品放入了他的购物车。订单总额将是 ¥ 0.30000000000000004。这会让任何人感到惊讶。
但为什么会这样呢?
一个数字以其二进制的形式存储在内存中,一个 1 和 0 的序列。但是在十进制数字系统中看起来很简单的 0.10.2 这样的小数,实际上在二进制形式中是无限循环小数。
什么是 0.10.1 就是 1 除以 101/10,即十分之一。在十进制数字系统中,这样的数字表示起来很容易。将其与三分之一进行比较:1/3。三分之一变成了无限循环小数 0.33333(3)
在十进制数字系统中,可以保证以 10 的整数次幂作为除数能够正常工作,但是以 3 作为除数则不能。也是同样的原因,在二进制数字系统中,可以保证以 2 的整数次幂作为除数时能够正常工作,但 1/10 就变成了一个无限循环的二进制小数。
使用二进制数字系统无法 精确 存储 0.1 或 0.2,就像没有办法将三分之一存储为十进制小数一样。
IEEE-754 数字格式通过将数字舍入到最接近的可能数字来解决此问题。这些舍入规则通常不允许我们看到“极小的精度损失”,但是它确实存在。
我们可以看到:
当我们对两个数字进行求和时,它们的“精度损失”会叠加起来。
这就是为什么 0.1 + 0.2 不等于 0.3
 

不要用 for..in 来处理数组

💡
不要用 for..in 来处理数组
  1. for..in 循环会遍历 所有属性,不仅仅是这些数字属性。
    1. 在浏览器和其它环境中有一种称为“类数组”的对象,它们 看似是数组。也就是说,它们有 length 和索引属性,但是也可能有其它的非数字的属性和方法,这通常是我们不需要的。for..in 循环会把它们都列出来。所以如果我们需要处理类数组对象,这些“额外”的属性就会存在问题。
  1. for..in 循环适用于普通对象,并且做了对应的优化。但是不适用于数组,因此速度要慢 10-100 倍。当然即使是这样也依然非常快。只有在遇到瓶颈时可能会有问题。但是我们仍然应该了解这其中的不同。
 

数组的 length

💡
数组的 length
当我们修改数组的时候,length 属性会自动更新。准确来说,它实际上不是数组里元素的个数,而是最大的数字索引值加一。
length 属性的另一个有意思的点是它是可写的。
如果我们手动增加它,则不会发生任何有趣的事儿。但是如果我们减少它,数组就会被截断。该过程是不可逆的,下面是例子:
所以,清空数组最简单的方法就是:arr.length = 0
 

可迭代对象

💡
可迭代对象
可以应用 for..of 的对象被称为 可迭代的
  • 技术上来说,可迭代对象必须实现 Symbol.iterator 方法。
    • obj[Symbol.iterator]() 的结果被称为 迭代器(iterator)。由它处理进一步的迭代过程。
    • 一个迭代器必须有 next() 方法,它返回一个 {done: Boolean, value: any} 对象,这里 done:true 表明迭代结束,否则 value 就是下一个值。
  • Symbol.iterator 方法会被 for..of 自动调用,但我们也可以直接调用它。
  • 内建的可迭代对象例如字符串和数组,都实现了 Symbol.iterator
Array.from(obj[, mapFn, thisArg]) 将可迭代对象或类数组对象 obj 转化为真正的数组 Array,然后我们就可以对它应用数组的方法。可选参数 mapFn 和 thisArg 允许我们将函数应用到每个元素。
 

词法环境

💡
在 JavaScript 中,每个运行的函数,代码块 {...} 以及整个脚本,都有一个被称为 词法环境(Lexical Environment) 的内部(隐藏)的关联对象
词法环境对象由两部分组成:
  1. 环境记录(Environment Record) —— 一个存储所有局部变量作为其属性(包括一些其他信息,例如 this 的值)的对象。
  1. 对 外部词法环境 的引用,与外部代码相关联。
一个“变量”只是 环境记录 这个特殊的内部对象的一个属性。“获取或修改变量”意味着“获取或修改词法环境的一个属性”。
从程序执行进入代码块(或函数)的那一刻起,变量就开始进入“未初始化”状态。它一直保持未初始化状态,直至程序执行到相应的 let 语句。
notion image
“词法环境”是一个规范对象(specification object):它只存在于 语言规范 的“理论”层面,用于描述事物是如何工作的。我们无法在代码中获取该对象并直接对其进行操作。
在一个函数运行时,在调用刚开始时,会自动创建一个新的词法环境以存储这个调用的局部变量和参数。
notion image
在这个函数调用期间,我们有两个词法环境:内部一个(用于函数调用)和外部一个(全局):
  • 内部词法环境与 say 的当前执行相对应。它具有一个单独的属性:name,函数的参数。我们调用的是 say("John"),所以 name 的值为 "John"
  • 外部词法环境是全局词法环境。它具有 phrase 变量和函数本身。
内部词法环境引用了 outer
当代码要访问一个变量时 —— 首先会搜索内部词法环境,然后搜索外部环境,然后搜索更外部的环境,以此类推,直到全局词法环境。
notion image
再如:
所有函数都有名为 [[Environment]] 的隐藏属性,该属性保存了对创建该函数的词法环境的引用
notion image
因此,counter.[[Environment]] 有对 {count: 0} 词法环境的引用。这就是函数记住它创建于何处的方式,与函数被在哪儿调用无关。[[Environment]] 引用在函数创建时被设置并永久保存。
注意:通过 makeCounter 的不同调用创建的变量(counter),它们具有独立的外部词法环境,每一个都有自己的 count
稍后,当调用 counter() 时,会为该调用创建一个新的词法环境,并且其外部词法环境引用获取于 counter.[[Environment]]
notion image
闭包 是指一个函数可以记住其外部变量并可以访问这些变量 也就是说:JavaScript 中的函数会自动通过隐藏的 [[Environment]] 属性记住创建它们的位置,所以它们都可以访问外部变量。
 

setTimeout & setInterval

💡
setTimeout & setInterval
嵌套的 setTimeout 相较于 setInterval 能够更精确地设置两次执行之间的延时。
例如:
对 setInterval 而言,内部的调度程序会每间隔 100 毫秒执行一次 func(i++)
notion image
使用 setInterval 时,func 函数的实际调用间隔要比代码中设定的时间间隔要短!
嵌套的 setTimeout 就能确保延时的固定(这里是 100 毫秒)。
notion image
 

装饰器

💡
装饰器
装饰器 是一个围绕改变函数行为的包装器。主要工作仍由该函数来完成
  • func.call(context, arg1, arg2…) —— 用给定的上下文和参数调用 func
  • func.apply(context, args) —— 调用 func 将 context 作为 this 和类数组的 args 传递给参数列表。
对于即可迭代又是类数组的对象,例如一个真正的数组,我们使用 call 或 apply 均可,但是 apply 可能会更快,因为大多数 JavaScript 引擎在内部对其进行了优化
装饰器实现:
通用的 呼叫转移(call forwarding) 通常是使用 apply 完成的:
  • func.bind(context, [arg1], [arg2], ...) —— 可以绑定 this,还可以绑定参数
偏函数应用程序:
 
 

深入:Class内部探究和 [[HomeObject]]

💡
深入:Class内部探究和 [[HomeObject]]
tip: 子类的 constructor 必须调用 super()
JavaScript 为函数添加了一个特殊的内部属性:[[HomeObject]]
当一个函数被定义为类或者对象方法时,它的 [[HomeObject]] 属性就成为了该对象。
然后 super 使用它来解析(resolve)父原型及其方法。
函数通常都是“自由”的,并没有绑定到 JavaScript 中的对象。正因如此,它们可以在对象之间复制,并用另外一个 this 调用它。
[[HomeObject]] 的存在违反了这个原则,因为方法记住了它们的对象。[[HomeObject]] 不能被更改,所以这个绑定是永久的。
在 JavaScript 语言中 [[HomeObject]] 仅被用于 super
如果一个方法不使用 super,那么我们仍然可以视它为自由的并且可在对象之间复制。但是用了 super 再这样做可能就会出错。
[[HomeObject]] 是为类和普通对象中的方法定义的。但是对于对象而言,方法必须确切指定为 method(),而不是 "method: function()"
 

extends

💡
extends
继承的静态属性和方法
notion image
Rabbit extends Animal 创建了两个 [[Prototype]] 引用:
  1. Rabbit 函数原型继承自 Animal 函数。
  1. Rabbit.prototype 原型继承自 Animal.prototype
所以:“extends” 语法会设置两个原型:
  1. 在构造函数的 "prototype" 之间设置原型(为了获取实例方法)。
  1. 在构造函数之间会设置原型(为了获取静态方法)。
 
 

内建方法

💡
内建方法
静态 getter Symbol.species
如果存在,则应返回 JavaScript 在内部用来在 map 和 filter 等方法中创建新实体的 constructor
静态方法 Symbol.hasInstance
instanceof 在检查中会将原型链考虑在内。此外,我们还可以在静态方法 Symbol.hasInstance 中设置自定义逻辑。
Symbol.toStringTag
以使用特殊的对象属性 Symbol.toStringTag 自定义对象的 toString 方法的行为。
 

使用 Object.prototype.toString 方法来揭示类型

💡
使用 Object.prototype.toString 方法来揭示类型
该功能可以使 toString 实际上比这更强大。我们可以将其作为 typeof 的增强版或者 instanceof 的替代方法来使用
  • 对于 number 类型,结果是 [object Number]
  • 对于 boolean 类型,结果是 [object Boolean]
  • 对于 null[object Null]
  • 对于 undefined[object Undefined]
  • 对于数组:[object Array]
 

常规脚本和拥有 type="module" 标识的脚本

💡
常规脚本和拥有 type="module" 标识的脚本
与常规脚本相比,拥有 type="module" 标识的脚本有一些特定于浏览器的差异
模块脚本 总是 被延迟的,与 defer 特性对外部脚本和内联脚本(inline script)的影响相同。
  • 下载外部模块脚本 <script type="module" src="..."> 不会阻塞 HTML 的处理,它们会与其他资源并行加载。
  • 模块脚本会等到 HTML 文档完全准备就绪(即使它们很小并且比 HTML 加载速度更快),然后才会运行。
  • 保持脚本的相对顺序:在文档中排在前面的脚本先执行。
它的一个副作用是,模块脚本总是会“看到”已完全加载的 HTML 页面,包括在它们下方的 HTML 元素。
这是因为模块脚本是被延迟的,所以要等到 HTML 文档被处理完成才会执行它。而常规脚本则会立即运行,所以我们会先看到常规脚本的输出。
 

Reference Type

💡
Reference Type
一个动态执行的方法调用可能会丢失 this
obj.method() 语句中的两个操作:
  1. 首先,点 '.' 取了属性 obj.method 的值。
  1. 接着 () 执行了它。
那么,this 的信息是怎么从第一部分传递到第二部分的呢?
如果我们将这些操作放在不同的行,this 必定是会丢失的
为确保 user.hi() 调用正常运行,JavaScript 玩了个小把戏 —— 点 '.' 返回的不是一个函数,而是一个特殊的 Reference Type 的值。
Reference Type 的值是一个三个值的组合 (base, name, strict),其中:
  • base 是对象。
  • name 是属性名。
  • strict 在 use strict 模式下为 true。
对属性 user.hi 访问的结果不是一个函数,而是一个 Reference Type 的值。对于 user.hi,在严格模式下是:
当 () 被在 Reference Type 上调用时,它们会接收到关于对象和对象的方法的完整信息,然后可以设置正确的 this(在此处 =user)。
Reference Type 是一个特殊的“中间人”内部类型,目的是从 . 传递信息给 () 调用。
而任何例如赋值 hi = user.hi 等其他的操作,都会将 Reference Type 作为一个整体丢弃掉,而会取 user.hi(一个函数)的值并继续传递。所以任何后续操作都“丢失”了 this
除了方法调用之外的任何操作(如赋值 = 或 ||),都会把它转换为一个不包含允许设置 this 信息的普通值。
因此,this 的值仅在函数直接被通过点符号 obj.method() 或方括号 obj['method']() 语法(此处它们作用相同)调用时才被正确传递。还有很多种解决这个问题的方式,例如 func.bind()
 
 

参考链接:
  1. 现代 JavaScript 教程
 
上一篇
前端主题切换
下一篇
Secret

评论
Loading...