我是一个尝试学习javascript的新手。在雄辩的javascript的第10章中,讨论了AMD的实现,并展示了define()的机制。首先定义了一个函数getModule,它返回一个描述模块状态的对象;在getModule中使用了未明确显示的backgroundReadFile函数。然后写出define函数。
我可以请求帮助理解这段代码。例如,如果我有一个需要两个模块的函数,即define([mod1,mod2],function(mod1,mod2)...),那么在getModule和define函数中发生了一步一步的过程是什么?谢谢。
以下是可在其在线图书https://eloquentjavascript.net/index.html中看到的代码:
//the getModule function
var defineCache = Object.create(null);
var currentMod = null;
function getModule(name) {
if (name in defineCache)
return defineCache[name];
var module = {exports: null,
loaded: false,
onLoad: []};
defineCache[name] = module;
backgroundReadFile(name, function(code) {
currentMod = module;
new Function("", code)();
});
return module;
}
// the define function
function define(depNames, moduleFunction) {
var myMod = currentMod;
var deps = depNames.map(getModule);
deps.forEach(function(mod) {
if (!mod.loaded)
mod.onLoad.push(whenDepsLoaded);
});
function whenDepsLoaded() {
if (!deps.every(function(m) { return m.loaded; }))
return;
var args = deps.map(function(m) { return m.exports; });
var exports = moduleFunction.apply(null, args);
if (myMod) {
myMod.exports = exports;
myMod.loaded = true;
myMod.onLoad.forEach(function(f) { f(); });
}
}
whenDepsLoaded();
}
答案 0 :(得分:1)
我在代码中添加了一堆注释。 我希望这有用!
// module cache used in getModule
var defineCache = Object.create(null);
// Object.create(null) gives an empty object like {} but even *emptier*
// in that it doesn't inherit properties from a prototype
// which makes it better for using as a key-value map,
// although there's also a built-in Map type that could be used.
// This variable is actually shared between the two functions.
// (see explanation further on)
var currentMod = null;
// the getModule function
function getModule(name) {
// If a module has already loaded *or has started* being loaded,
// it should be in the cache and we can just return it.
if (name in defineCache)
return defineCache[name];
var module = {exports: null,
loaded: false,
onLoad: []};
// Put the module in the cache.
defineCache[name] = module;
// Asynchronously load file contents with a function such as
// fs.readFile() in Node.js, or fetch() in the browser
// (which both have different APIs; readFile would need
// an err parameter in the callback, and fetch uses Promises)
readFileAsync(name, function(code) {
// We're going to execute the code, and the code should include a define() call,
// but `define` will need access to the module object
// corresponding to the code that's calling define().
// So we set `currentMod` to the module object,
// and share this state between the two functions.
currentMod = module;
// Then evaluate the the loaded code.
new Function("", code)();
// Creating a Function and executing it is like using eval()
// but with less stuff considered 'in scope'.
// The new function only has access to global scope,
// which includes the `define` function,
// but not other variables it doesn't need,
// and doesn't need to be able to mess with!
// eval() is more like dynamically including code in-line; it's messy.
// I'm not sure why "" is passed there. According to the docs for Function,
// any arguments before the last would be names of parameters of the function,
// which it's not using any of.
// I suspect it would also work with just new Function(code)().
// I think maybe there should be a `currentMod = undefined;` here
// in case there are multiple top-level define() calls so it doesn't
// overwrite the exports of a (sub-level) module from the first top-level define()
// when doing the second top-level define()
});
// Return the module object even before the file is loaded.
return module;
}
// the define function
function define(depNames, moduleFunction) {
// If this was called from a module loaded from a file,
// currentMod should be set by getModule just previously.
// Otherwise, if this was called outside of a module (i.e. a main script),
// currentMod should be undefined.
var myMod = currentMod;
// Call getModule for each dependency.
// The naming seems a little inconsistent - shouldn't it be `var modules`?
// I guess either works, but anyway deps is an array of the module objects.
// This will also kick off reading the files for each of the modules,
// but execution will continue on below before they're loaded.
var deps = depNames.map(getModule);
// We want to know when all the dependencies are loaded,
// so we need to know when any dependency loads.
// Register callbacks to each dependency's onLoad event
// (by adding it to an array of callbacks).
deps.forEach(function(mod) {
if (!mod.loaded)
mod.onLoad.push(checkForWhenDepsLoaded);
});
// Check immediately in case there are no deps or they've already loaded.
checkForWhenDepsLoaded();
// (I moved this up here so both places that handle
// calling checkForWhenDepsLoaded are next to each other.)
// (They could also both be at the end.)
// This was called whenDepsLoaded but that's not a great name
// because it's not called *when* the deps are loaded,
// it's called to check if they're loaded and then do some stuff if so.
function checkForWhenDepsLoaded() {
// Continue only if all the dependencies are marked as loaded.
if (!deps.every(function(m) { return m.loaded; }))
return;
// Now from here, all deps should be loaded.
// We should only get here once!
// That's important when doing callbacks;
// you need to know whether there's a possibility of calling back multiple times.
// In some cases you do want to call back multiple times,
// like when iterating over a collection (forEach and map etc.)
// but then you still need to be careful not to call back multiple times *per item*.
// You can try to look at code paths to make sure it shouldn't happen,
// but you can also test the assumption!
// You could make a flag variable like "calledBack"
// with `var calledBack = false;` (at the top of `define` if doing it here),
// set it to true when calling the callback, and before that
// log a message in case it does actually get there a second time,
// and if so return so the callback isn't called again.
// Time to call back!
// the caller of define() won't really care about the module *objects*,
// just the exports of said modules,
// so collect only the `exports` of each module in an array.
var args = deps.map(function(m) { return m.exports; });
// Then call back with them as arguments.
// apply() lets you call a function with an array of values as arguments
// essentially calling moduleFunction(args[0], args[1], ...)
var exports = moduleFunction.apply(null, args);
if (myMod) {
myMod.exports = exports;
// Mark as loaded and execute all onLoad handlers.
myMod.loaded = true;
myMod.onLoad.forEach(function(f) { f(); });
}
}
}