New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

没有把依赖的模块作为回调函数的参数时,导致依赖的模块的factory函数不执行 #20

Closed
winnieBear opened this Issue Jul 11, 2014 · 8 comments

Comments

Projects
None yet
4 participants
@winnieBear

winnieBear commented Jul 11, 2014

当define一个模块时,没有把依赖的模块作为回调函数的参数时,导致依赖的模块的factory函数不执行,例如:

//mod a file
define('a',[],function(){
    console.log('a factory call');
});

//mod b file
define('b',[],function(){
    console.log('b factory call');
});

//mod all file
define('all',['a','b'],function(a){ //这里参数没有b
    console.log('all factory call')
})
/call
require(['all'], function() {
    console.log('all callback');
  })

//执行结果
a factory call a.js:2
all factory call all.js:2
all callback 

//其中b factory call 没有执行
@erik168

This comment has been minimized.

Contributor

erik168 commented Jul 11, 2014

是的。在1.8版本以上,esl的module定义策略是用时定义。如果你没有使用,factory是不会invoke的。

@winnieBear

This comment has been minimized.

winnieBear commented Jul 11, 2014

这种依赖某个模块,但是不会使用该模块的场景是哪些?

@erik168

This comment has been minimized.

Contributor

erik168 commented Jul 11, 2014

我们暂时没有发现依赖一个模块,但是不用的情况。

将module factory invoke时机推迟到使用时这件事情的出发点,我们有一些讨论,可以参考 #14

另外,使用amd进行开发,在开发时我们有一些practice,主要有几点:

  1. 不要写id
  2. 不要写dependencies数组,应该在factory内require
  3. define(id,dependencies,factory)的形式是通过工具(比如r.js)打包后的结果,而不是手写成这样

详细可以参考这里:https://github.com/ecomfe/spec/blob/master/module.md

@winnieBear winnieBear closed this Jul 11, 2014

@winnieBear

This comment has been minimized.

winnieBear commented Jul 11, 2014

我看了你们的模块和加载器规范之后,我有一个疑惑,当这种模块定义的写法

//define mod
define( function( require ) {
    require("dep1");
    require("dep2");
    //....
});

采用CMD这样定义模块的方法去定义一个模块的时候,再用esl加载这样的模块时,仍旧还是按照_AMD_的规则进行加载吗?

我试了下:
AMD定义方法

//a.js
define('a',[],function(){
    console.log('a factory call');
});

//b.js
define('b',[],function(){
    console.log('b factory call');
});

//all.js
define('all',['a','b'],function(a,b){
       // 执行时所有的依赖都加载完毕
    console.log('all factory call begin')
    //----
    console.log('all factory call over')
})
//use
 require(['all'], function() {
    console.log('all callback');
  })
//result
a factory call a.js:2
b factory call b.js:2
all factory call begin all.js:2
all factory call over all.js:4
all callback 

但是当我用上面CMD的模块定义方法定义模块时

//a.js
define(function(require){
    console.log('a factory call');
});

//b.js
define(function(require){
    console.log('b factory call');
});

//all.js
define(function(require){
       // 执行时所有的依赖还没加载吧?
    console.log('all factory call begin')
     require('a');
     require('b');
    console.log('all factory call over')
})

//result
all factory call begin all.js:2
a factory call a.js:2
b factory call b.js:2
all factory call over all.js:5
all callback 

可以看到这种定义的方法和运行结果好像已经是CMD的样子,那esl是AMD加载器,可是好像是按照CMD的机制在运行。

如果执行factory的时候,并没有提前执行依赖,这好像不是AMD的行为,我用requirejs执行了上述的例子,requirejs无论上述哪种写法都是按照AMD的“依赖提前执行”这个规则来执行的,而esl则是截然不同的结果。难道esl对后一种写法(CMD写法)其实就是按照CMD规则来加载,对前一种写法(AMD写法)就是按照AMD规则来加载?

也许我的理解是有问题的,只是突然有点晕...

@winnieBear winnieBear reopened this Jul 11, 2014

@otakustay

This comment has been minimized.

Member

otakustay commented Jul 11, 2014

requirejs我记得从某个版本开始也是延后执行factory的:requirejs/requirejs#183

另外AMD并没有规定factory的执行时机,无论是加载后执行还是第一次require时执行,都不违反AMD的标准,而CMD是规定了factory 必须 延后执行而已

@errorrik errorrik closed this Jul 15, 2014

@winnieBear

This comment has been minimized.

winnieBear commented Jul 15, 2014

其实我提出的问题不是factory是define的时候执行还是第一次requrie的时候执行,目前requirejs也是第一次require的时候执行的。

我提出的问题是在执行一个模块的factory的时候,是否提前执行该模块所依赖的模块的factory,CMD是你requrie某个依赖的模块时候,才开始执行这个依赖模块的factory;而AMD是在执行该模块的factory之前提前把所依赖的模块都加载了,也就是所依赖的模块的factory要是没执行,都会提前执行了。

通过测试,当模块按照上述第二种方法,即CMD的模块定义的方法定义的时候,用esl加载模块时,当模块加载的时候,该模块的依赖的模块不会在该模块的factory执行之前加载,而是当用require调用的时候才加载;这种方法加载的结果我对比了下seajs,结果是完全一致的;而requirejs无论哪种方法定义都不会变。

@errorrik

This comment has been minimized.

Contributor

errorrik commented Jul 16, 2014

首先,下面两种方式都是AMD的模块定义方式。当然,第二种方式也是CMD的模块定义方式。可以看出,从模块定义的书写上,CMD是AMD的子集。在具体项目中,我们号称使用AMD,通常开发时按第二种方式书写,上线前通过相关工具打包成第一种方式的代码。

define('all',['a','b'],function(a,b){
    console.log('all factory call begin')
    console.log('all factory call over')
})

define(function(require){
    console.log('all factory call begin')
    require('a');
    require('b');
    console.log('all factory call over')
})

然后,你的问题是关于factory call的时机。

在AMD spec里,对指定dependencies的场景,有相应描述

The dependencies must be resolved prior to the execution of the module factory function, and the resolved values should be passed as arguments to the factory function with argument positions corresponding to indexes in the dependencies array.

The dependencies argument is optional. If omitted, it should default to ["require", "exports", "module"]. However, if the factory function's arity (length property) is less than 3, then the loader may choose to only call the factory with the number of arguments corresponding to the function's arity or length.

我是这么理解的,对于factory中的形式参数,loader应该对dependencies里的声明模块,先执行factory初始化好,然后按顺序传递给factory。但是,当factory的形式参数数目少于3时,loader可以根据参数数量的前几个dependencies模块,去call factory。也就是说,dependencies数组里,后面一些模块的初始化时机,是可以自由把握的;在call factory的时候,dependencies数组中位于形式参数length后面index的模块,不一定要初始化完毕。


然后是AMD对define中的同步require的描述

Dependencies can be found in an AMD module when this form of define() is used:

    define(function (require) {
        var a = require('a');
    });

The define factory function can be parsed for require('') calls (for instance by using a language parser or using Function.prototype.toString() and regexps) to find dependencies, load and execute the dependencies, then run the code above. In that manner, the require('a') call can return the module value.

这里只说了loader可以去parse出require的模块,去加载它,然后执行依赖模块,然后run the code above。这样require('a')就能返回相应模块。我理解这里的意思是,在require('a')执行前,需要完成a模块的载入和初始化执行,但并没有说必须在factory执行前就要完成a模块的载入和初始化执行。否则这里的描述就应该是then call the factory,而不是then run the code above

而且,就算不看AMD spec,只看requirejs,它也是有循环依赖的处理机制的。循环依赖出现的情况,是没法保证module factory运行的时候,dependencies全部加载完毕的。


综上,AMD对于factory内部require的依赖模块,以及在dependencies数组中声明但是在factory形式参数列表之外的依赖模块,并没有明确规定这些依赖模块需要在什么时机执行factory。ESL是遵循AMD规范的,所以它是一个AMD的loader。

至于CMD,其明确强制规定了Execution must be lazy,这点我觉得,对于AMD的不明确规定,是一件很好的事情。ESL在这上面采用和CMD一样的模块初始化时序。但是ESL不是一个CMD的loader,因为它没有require.async。

@winnieBear

This comment has been minimized.

winnieBear commented Jul 17, 2014

非常感谢@errorrik细致的讲解,学到了很多,谢谢。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment