提问人:Aeternus 提问时间:3/16/2023 最后编辑:Aeternus 更新时间:3/21/2023 访问量:66
修改数组奇怪行为的 Javascript 闭包返回对象
Javascript closure returning object that modifies array weird behavior
问:
我试图从《Node.js 设计模式,第 3 版》一书中了解此代码的行为发生了什么
// using esm modules
// index.mjs
import { readFile } from 'fs';
const cache = new Map()
function inconsistentRead(filename, cb) {
console.log('INSIDE INCONSISTENT READ, filename is: ', filename)
console.log('Cache: ', cache)
if (cache.has(filename)) {
console.log('filename in cache, getting from cache: ', cache)
// invoked synchronously
cb(cache.get(filename))
} else {
// async function
console.log('running readFile from fs')
readFile(filename, 'utf8', (err, data) => {
console.log('inside callback passed into readFile, setting cache')
cache.set(filename, data);
console.log('about to call callback with data = ', data)
cb(data);
})
}
}
function createFileReader(filename) {
const listeners = []
console.log('listeners (empty at first): ', listeners)
inconsistentRead(filename, (value) => {
console.log('inconsistent read callback invoked, listeners: ', listeners)
listeners.forEach((listener) => listener(value));
})
return {
onDataReady: (listener) => {
console.log("about to push listener to listeners", listeners)
listeners.push(listener)
console.log('after pushing to listeners: ', listeners)
}
}
}
const reader1 = createFileReader('data.txt')
console.log('before reader1.ondataready')
reader1.onDataReady((data) => {
console.log(`First call data: ${data}`);
})
以下是上述结果的输出:
listeners (empty at first): []
INSIDE INCONSISTENT READ, filename is: data.txt
Cache: Map(0) {}
running readFile from fs
before reader1.ondataready
about to push listener to listeners []
after pushing to listeners: [ [Function (anonymous)] ]
inside callback passed into readFile, setting cache
about to call callback with data = some basic data
inconsistent read callback invoked, listeners: [ [Function (anonymous)] ]
First call data: some basic data
我感到困惑的是 readFile 的行为。
读取文件不会调用回调,直到我们填充回调数组。我本来以为它什么时候准备好调用,但相反,直到我们推入侦听器数组后它才调用它。listeners
console.log('inconsistent read callback invoked, listeners: ', listeners)
为什么 readFile 中的回调在填充监听器后被调用?是否有可能由于时间原因,它可以在填充之前调用回调?listeners.forEach(...)
答:
const c = closure2()
此行调用 closure2 并返回带有 onDataReady 的对象。通过以下方式调用 onDataReady 后:
c.onDataReady('test1')
它调用 onDataReady 函数,该函数推送数组中的字符串。如果你观察文档,你会看到 array.push 在更新后返回数组元素的计数,因此你得到的输出类似于 1,2,3...
它不会调用
arr.forEach(a => { console.log('ARR ELEMENT: ', a) })
在第一次调用 closure2() 之后的任何时候,由于数组最初是空的,因此不会打印任何内容。
如果要在每次推送/添加数据后打印完整的数组,可以执行如下操作:
function closure2() {
const arr = [];
return {
print: () => arr.forEach(a => {
console.log('ARR ELEMENT: ', a)
}),
onDataReady: (arrElement) => arr.push(arrElement)
}
}
const c = closure2()
c.onDataReady('test1')
c.print(); // PRINTS ["test1"]
c.onDataReady('test2')
c.print() // PRINTS ["test1", "test2"]
c.onDataReady('test3')
c.print() // PRINTS ["test1", "test2", "test3"]
c.onDataReady('test4')
c.print() // PRINTS ["test1", "test2", "test3", "test4"]
根据 OP 的编辑编辑的答案
是的,在您共享的代码中填充侦听器后,将调用回调。这就是 Javascript 的工作原理。这里没有时间问题。即使您尝试读取空文件,回调也会在稍后执行。Javascript 将首先执行所有同步代码,直到调用堆栈为空,然后查看任务队列(微任务队列和宏任务队列)中存在的回调函数。由于读取文件(即 I/O 操作)是一项宏任务,因此它将在调用堆栈为空或执行所有其他顺序同步代码后执行其回调。以下是一些有助于理解此行为的好资源。
评论
arr.forEach
在返回之前只运行一次。它不会回到过去并再次打印。