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
。
请注意,
sort
,reverse
和 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
函数。
柯里化目的:
- 柯里化之后,我们没有丢失任何东西:
log
依然可以被正常调用。
- 我们可以轻松地生成偏函数,例如用于生成今天的日志的偏函数。
高级柯里化实现
这里有两个
if
执行分支:- 如果传入的
args
长度与原始函数所定义的(func.length
)相同或者更长,那么只需要使用func.apply
将调用传递给它即可。
- 否则,获取一个偏函数:我们目前还没调用
func
。取而代之的是,返回另一个包装器pass
,它将重新应用curried
,将之前传入的参数与新的参数一起传入。
Proxy & Reflect
一个
Proxy
对象包装另一个对象并拦截诸如读取/写入属性和其他操作,可以选择自行处理它们,或者透明地允许该对象处理它们。Proxy
target
—— 是要包装的对象,可以是任何东西,包括函数。
handler
—— 代理配置:带有“捕捉器”(“traps”,即拦截操作的方法)的对象。比如get
捕捉器用于读取target
的属性,set
捕捉器用于写入target
的属性,等等。
对
proxy
进行操作,如果在 handler
中存在相应的捕捉器,则它将运行,并且 Proxy 有机会对其进行处理,否则将直接对 target 进行处理。proxy
是一个 target
的透明包装器(wrapper)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..in , Object.keys/values/entries |
[[OwnPropertyKeys]] | ownKeys | Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in , Object.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.get
与 target[prop]
的细微的差别
在
(*)
行- 当我们读取
admin.name
时,由于admin
对象自身没有对应的的属性,搜索将转到其原型。
- 原型是
userProxy
。
- 从代理读取
name
属性时,get
捕捉器会被触发,并从原始对象返回target[prop]
属性,
当调用
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 尝试查找并调用三个对象方法:
- 调用
obj[Symbol.toPrimitive](hint)
—— 带有 symbol 键Symbol.toPrimitive
(系统 symbol)的方法,如果这个方法存在的话,
- 否则,如果 hint 是
"string"
—— 尝试调用obj.toString()
或obj.valueOf()
,无论哪个存在。
- 否则,如果 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
,就像这样原始类型的方法
当作对象的原始类型
- 原始类型仍然是原始的。与预期相同,提供单个值
- JavaScript 允许访问字符串,数字,布尔值和 symbol 的方法和属性
- 为了使它们起作用,创建了提供额外功能的特殊“对象包装器”,使用后即被销毁
对象包装器
“对象包装器”对于每种原始类型都是不同的,它们被称为
String
、Number
、Boolean
、Symbol
和 BigInt
。因此,它们提供了不同的方法如:
以下是
str.toUpperCase()
中实际发生的情况:- 字符串
str
是一个原始值。因此,在访问其属性时,会创建一个包含字符串字面值的特殊对象,并且具有可用的方法,例如toUpperCase()
。
- 该方法运行并返回一个新的字符串(由
alert
显示)。
- 特殊对象被销毁,只留下原始值
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"
是一个只有属性 constructor
的对象,属性 constructor
指向函数自身。Object.prototype
比如当
obj.toString()
被调用时,这个方法是从 Object.prototype
中获取的所有的内建原型顶端都是Object.prototype
。这就是为什么有人说“一切都从对象继承而来”。
注意:基本数据类型也存在对象
如果我们试图访问它们的属性,那么临时包装器对象将会通过内建的构造器
String
、Number
和 Boolean
被创建。它们提供给我们操作字符串、数字和布尔值的方法然后消失。这些对象对我们来说是无形地创建出来的。大多数引擎都会对其进行优化,但是规范中描述的就是通过这种方式。这些对象的方法也驻留在它们的 prototype 中,可以通过
String.prototype
、Number.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中就出现了异步的概念。
同步任务
代码从上到下按顺序执行
异步任务
- 宏任务
script(整体代码)、setTimeout、setInterval、UI交互事件、postMessage、Ajax
- 微任务
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
。
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
的结果。generator.throw
向
yield
传递一个 errorgenerator.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.1
,0.2
这样的小数,实际上在二进制形式中是无限循环小数。什么是
0.1
?0.1
就是 1
除以 10
,1/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
来处理数组for..in
循环会遍历 所有属性,不仅仅是这些数字属性。
在浏览器和其它环境中有一种称为“类数组”的对象,它们 看似是数组。也就是说,它们有
length
和索引属性,但是也可能有其它的非数字的属性和方法,这通常是我们不需要的。for..in
循环会把它们都列出来。所以如果我们需要处理类数组对象,这些“额外”的属性就会存在问题。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) 的内部(隐藏)的关联对象词法环境对象由两部分组成:
- 环境记录(Environment Record) —— 一个存储所有局部变量作为其属性(包括一些其他信息,例如
this
的值)的对象。
- 对 外部词法环境 的引用,与外部代码相关联。
一个“变量”只是 环境记录 这个特殊的内部对象的一个属性。“获取或修改变量”意味着“获取或修改词法环境的一个属性”。
从程序执行进入代码块(或函数)的那一刻起,变量就开始进入“未初始化”状态。它一直保持未初始化状态,直至程序执行到相应的
let
语句。“词法环境”是一个规范对象(specification object):它只存在于 语言规范 的“理论”层面,用于描述事物是如何工作的。我们无法在代码中获取该对象并直接对其进行操作。
在一个函数运行时,在调用刚开始时,会自动创建一个新的词法环境以存储这个调用的局部变量和参数。
在这个函数调用期间,我们有两个词法环境:内部一个(用于函数调用)和外部一个(全局):
- 内部词法环境与
say
的当前执行相对应。它具有一个单独的属性:name
,函数的参数。我们调用的是say("John")
,所以name
的值为"John"
。
- 外部词法环境是全局词法环境。它具有
phrase
变量和函数本身。
内部词法环境引用了outer
。当代码要访问一个变量时 —— 首先会搜索内部词法环境,然后搜索外部环境,然后搜索更外部的环境,以此类推,直到全局词法环境。
再如:
所有函数都有名为
[[Environment]]
的隐藏属性,该属性保存了对创建该函数的词法环境的引用因此,
counter.[[Environment]]
有对 {count: 0}
词法环境的引用。这就是函数记住它创建于何处的方式,与函数被在哪儿调用无关。[[Environment]]
引用在函数创建时被设置并永久保存。注意:通过
makeCounter
的不同调用创建的变量(counter),它们具有独立的外部词法环境,每一个都有自己的 count
。稍后,当调用
counter()
时,会为该调用创建一个新的词法环境,并且其外部词法环境引用获取于 counter.[[Environment]]
:闭包 是指一个函数可以记住其外部变量并可以访问这些变量 也就是说:JavaScript 中的函数会自动通过隐藏的[[Environment]]
属性记住创建它们的位置,所以它们都可以访问外部变量。
setTimeout
& setInterval
setTimeout
& setInterval
嵌套的
setTimeout
相较于 setInterval
能够更精确地设置两次执行之间的延时。例如:
对
setInterval
而言,内部的调度程序会每间隔 100 毫秒执行一次 func(i++)
:使用
setInterval
时,func
函数的实际调用间隔要比代码中设定的时间间隔要短!嵌套的
setTimeout
就能确保延时的固定(这里是 100 毫秒)。装饰器
装饰器
装饰器 是一个围绕改变函数行为的包装器。主要工作仍由该函数来完成
- 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
继承的静态属性和方法
Rabbit extends Animal
创建了两个 [[Prototype]]
引用:Rabbit
函数原型继承自Animal
函数。
Rabbit.prototype
原型继承自Animal.prototype
。
所以:“extends” 语法会设置两个原型:
- 在构造函数的
"prototype"
之间设置原型(为了获取实例方法)。
- 在构造函数之间会设置原型(为了获取静态方法)。
内建方法
内建方法
静态 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()
语句中的两个操作:- 首先,点
'.'
取了属性obj.method
的值。
- 接着
()
执行了它。
那么,
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()参考链接:
- 作者:JinSo
- 链接:https://jinso365.top/article/modern-javascript-javascript
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。