你所不知道的 JS - 模块加载器

You don't know JS - module loader

我正在阅读 github 上的“你不懂 JS”(第 1 版),我正在研究第 5 章中的模块加载器示例。但是有两件事我不知道不明白。我在这里发现了另一个关于同一个模块加载器的问题 (),但它没有回答我的问题。

先上原码:

var MyModules = (function Manager() {
    var modules = {};

    function define(name, deps, impl) {
        for (var i=0; i<deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply( impl, deps );
    }

    function get(name) {
        return modules[name];
    }

    return {
        define: define,
        get: get
    };
})();



MyModules.define( "bar", [], function(){
    function hello(who) {
        return "Let me introduce: " + who;
    }

    return {
        hello: hello
    };
} );        // module["bar"] = hello(who) & deps = []

MyModules.define( "foo", ["bar"], function(bar){
    var hungry = "hippo";

    function awesome() {
        console.log( bar.hello( hungry ).toUpperCase() );
    }

    return {
        awesome: awesome
    };
} );        // module["foo"] = awesome() & deps = [hello(who)]



var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );



console.log(
    bar.hello( "hippo" )
);      // Let me introduce: hippo

foo.awesome();  // LET ME INTRODUCE: HIPPO

现在问题:

modules[name] = impl.apply( impl, deps );
MyModules.define( "foo", ["bar"], function(bar){

如果我删除 impl 一个 bar 它就像原始代码一样工作。

"use strict";
...
modules[name] = impl.apply( deps );
...
MyModules.define( "foo", ["bar"], function(){
...

这两件事是不必要的还是巧合,代码有效?

Why is impl applied to itself (plus the deps object)? I used w3schools/apply() for looking at how apply() works. There is in the 1st example just an object applied to a function, and not the function itself plus an object.

在 MDN 上查看 Function#apply()。通常 MDN 有更多的信息,因此值得参考。

当您调用 apply() 时,第一个参数是所执行函数的 this 值。在很多情况下,该值是什么并不重要 - 如果您不关心它,您也可以传递 null

const thisValue = {foo: 3};

function usingThis() {
  return this.foo + 2;
}

console.log(usingThis.apply(thisValue)); // 3 + 2 = 5

function notUsingThis() { 
  return 8;
}

console.log(notUsingThis.apply(thisValue)); // 8
console.log(notUsingThis.apply(null));      // 8

但是,这个模块加载器传递了正在加载的模块。这也是一个明智的决定,因为在内部使用了 if this,它至少是明智的——不是全局对象,也不是 null 这可能会导致错误.如果不使用 this,则值是什么都没有关系。

还可能值得注意的是,由于 this 的值是函数本身,您在模块内部使用 this.foo = "hello",它会附加到函数对象。由于一个模块只会 运行 一次,这很好 - 您可以读写属性而不必担心它们会被践踏。

Why should i give bar as a function argument in the definition of foo, since it is in the global scope (also in strict mode)?

原因是加载的模块和它加载的模块可能不在同一个范围内。事实上,如果模块 可以 安全地相互引用,那么您根本不需要“加载”它们,因为它们已经准备好所有依赖项。

然而,随着时间的推移,这会导致很难追踪依赖关系和脆弱的代码库。从长远来看,不知道每个模块使用什么是一个糟糕的设计。

如果你看看其他模块系统,它们都会声明依赖关系 显式:CommonJS、异步模块定义 (AMD) 以及 ES6 模块都做同样的事情事物。显示的模块加载器做了非常相似的事情。

为了便于说明,该示例已被简化。是的,在那种情况下只有那种情况,您可以跳过依赖项的显式声明。然而,在真实的代码库中,模块很可能在不同的地方定义,而不能访问相同的范围以防止事情变得混乱。那就是模块加载器的当前设计非常有意义的时候。