关于CommonJS中Module实现的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
real_newbie
V2EX    Javascript

关于CommonJS中Module实现的问题

  •  
  •   real_newbie 2011-04-28 12:22:06 +08:00 9403 次点击
    这是一个创建于 5354 天前的主题,其中的信息可能已经有所发展或是发生改变。
    原生的Javascript中可以通过Closure来实现模块化. 比如像这里所做的这样: http://yuiblog.com/blog/2007/06/12/module-pattern/

    在CommonJS的实现中, 是这样做的: http://wiki.commonjs.org/wiki/Modules/1.1#Sample_Code

    CommonJS中的这种module的方式是怎么实现的呢?
    31 条回复    1970-01-01 08:00:00 +08:00
    real_newbie
        1
    real_newbie  
    OP
       2011-04-28 13:12:57 +08:00
    是不是CommonJS中的所谓Module和Yuiblog里所说的Module其实不是同一个概念?

    前者只是单纯的把不同的function放到了不同的文件里, 加入了require的机制; 后者可以进行一些类似数据封装的工作?
    kuno
        2
    kuno  
       2011-04-28 19:38:12 +08:00
    commonJS的module只是社区的一个proposal, 目前只有node.js部分实现了它。
    不过,目前看起来下一个版本的ecmascript的module系统会不太一样,
    兰州可以参考这个视频里的内容,
    kuno
        3
    kuno  
       2011-04-28 19:42:55 +08:00
    刚看了commonJS的wiki,原来还有很多的其他系统也实现了。
    更正一下, :(
    real_newbie
        4
    real_newbie  
    OP
       2011-04-28 19:46:49 +08:00
    @kuno,

    感谢回复, CommonJS的实现并不是只有node.js, 像SproutCore, CouchDB之类的也有实现. (CommonJS的Spec有实现的列表)

    你给出的那个视频好像是介绍下一代Javascript中如何实现module的吧?(抱歉, 没仔细看视频, 只看了标题和开头). 我目前在做的东西是基于CouchDB的, 所以我想知道是关于CommonJS的, 而不是下一代Javascript的实现.

    不管怎么样, 还是非常感谢你~
    aligo
        5
    aligo  
       2011-04-28 19:47:06 +08:00
    数据封装是什么意思?
    一般控制访问的话,return返回的是一个匿名函数或者对象,作为闭包,可以访问scope的东西,但是调用那个module的时候就用不了了

    yui的module就是基于这个基本原理包装起来的,这样就不会因为随便操作一般对象的prototype产生问题
    real_newbie
        6
    real_newbie  
    OP
       2011-04-28 19:52:13 +08:00
    @aligo,

    对于yui的module我是理解的, 就和你说的一样.
    aligo
        7
    aligo  
       2011-04-28 19:56:22 +08:00
    CommonJS的那个require应该就是
    function(jsfile){
    var exports = {};
    然后这里执行js文件里的代码
    return exports;
    }
    real_newbie
        8
    real_newbie  
    OP
       2011-04-28 20:00:34 +08:00
    @aligo,

    所以其实和yui的module是两个不同的概念, 是这样子吗?
    aligo
        9
    aligo  
       2011-04-28 20:08:05 +08:00
    @real_newbie 一样是闭包,不过如果那玩意要在客户端使用require,必须是像jsonp一样,完整的代码是:
    do_export(function(){
    var exports = {};
    然后这里执行js文件里的代码
    return exports;
    });

    不过这样的动态载入无故增加http请求数量,实在多余,一般是把所有js文件打包成一个,压缩之后要求客户端缓存
    real_newbie
        10
    real_newbie  
    OP
       2011-04-28 20:17:02 +08:00
    @aligo,

    我觉得不一样, 就拿主帖子中CommonJS Spec里的例子来讲. 我觉得如果是和yuiblog里一样的话, math.js应该改成下面这样样子, 才是一样的:

    exports.add = fuction() { return function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
    sum += args[i++];
    }
    return sum;
    };
    };

    然后再require的时候要这个样子:

    var add = require('math').add();
    aligo
        11
    aligo  
       2011-04-28 20:28:32 +08:00
    恩,你这个意思我明白,是要在exports.add有似有变量吧,不过你这个也不对,应该是:
    exports.add = fuction() {
    var args = arguments;
    return (function(arguments) {
    var sum = 0, i = 0, l = args.length;
    while (i < l) {
    sum += args[i++];
    }
    return sum;
    })(arguments);
    };

    但是我上面说的是,你要给前端使用的话,只有在上面这段代码的东西的基础上,再包裹
    do_export(function(){
    var exports = {};
    上面的代码
    return exports;
    });

    同时预先定义好do_export,用以接受require发出的http jsonp请求,但是这里有一个问题,浏览器的js貌似要blocking i/o等待require发起的异步请求返回是一件相当麻烦的事情了

    不知道你看懂了我说的没有。。。
    aligo
        12
    aligo  
       2011-04-28 20:29:11 +08:00
    订正:
    exports.add = fuction() {
    var args = arguments;
    return (function(args) {
    var sum = 0, i = 0, l = args.length;
    while (i < l) {
    sum += args[i++];
    }
    return sum;
    })(args);
    };
    real_newbie
        13
    real_newbie  
    OP
       2011-04-28 20:32:21 +08:00
    @aligo,

    抱歉, 我一开始就没讲明白, 我并不是要在前端使用的. 是用在Server端的.

    exports.add = fuction() {
    var args = arguments;
    return (function(arguments) {
    var sum = 0, i = 0, l = args.length;
    while (i < l) {
    sum += args[i++];
    }
    return sum;
    })(arguments);
    };

    这个不和我的require('math').add(); 只是你这一版本的应该还是require('math).add, 不用加"()"进行调用, 对吧?
    aligo
        14
    aligo  
       2011-04-28 20:42:03 +08:00
    @real_newbie 恩,我只是和你地址里的那个代码实现一样的东西var add = require('math').add;而不是var add = require('math').add();

    不过扯远了,我要说的是module这东西,用来实现通过scope进行访问控制是可以的,就像yui那样

    然后如果前端使用它是为了分开文件就没有必要了,而这里的require实现的包括exports,对于每个文件是相互独立的,实现原理应该就是#7的那样,对于不同环境当然有不同实现,不过你这样去理解就够了
    real_newbie
        15
    real_newbie  
    OP
       2011-04-28 20:46:38 +08:00
    @aligo,

    呵呵, 事实上你#11里讲的关于前端的那一段我的确没有看的太明白. 不过因为我是用在Server端的, 所以就不关心啦;)
    aligo
        16
    aligo  
       2011-04-28 20:52:50 +08:00
    @real_newbie 恩,其实就是前端要实现require动态载入js的话,就是动态插入script标签,必然事先得提供一个do_export函数用来接受闭包
    aligo
        17
    aligo  
       2011-04-28 20:56:04 +08:00
    @real_newbie 主要是你提到了yui,我以为你要在前端实现这个require,于是劝你放弃
    real_newbie
        18
    real_newbie  
    OP
       2011-04-28 21:04:19 +08:00
    @aligo,

    恩, 提到yui, 主要是因为我理解yui的这种方式, 所以想进行对比下.

    另外off topic, 前端的话, 好像有这个玩意: https://github.com/jrburke/requirejs

    不过似乎压缩以后还是显的有点过大了.
    aligo
        19
    aligo  
       2011-04-28 21:13:25 +08:00
    @real_newbie 恩,这个东西well-scoped module就是用define()包裹新加入的js,类似我刚才说的do_export()

    不过一般用用直接插入script标签就够了,不过意义不大,因为这增加了http请求次数,一般需求都是把js合并后各种压缩成一个
    kuno
        20
    kuno  
       2011-04-29 10:14:46 +08:00
    根据我使用node.js的经验,commonJS的实现并没有使用closure.
    在node里面,module是一个特别的全局对象,有一个exports的属性。所有能够被调用的对象又都是exports的成员。当一个这个module通过require('MODULE_NAME')被调用之后,exports包含的成员就被载入了当前客户端global scope, 成为一个全局对象了。


    function require(jsfile) {
    var module = eval(do_some_io(jsfile));

    return moduel.exports;
    }
    aligo
        21
    aligo  
       2011-04-29 11:53:03 +08:00
    @kuno 我试了一下
    bar.js:
    var foo = 'bar'

    exports.bar = function () {
    return foo
    }

    foo.js:
    require.paths.unshift('.')

    var bar = require('bar')

    console.log(typeof bar.foo == 'undefined')
    console.log(typeof bar.bar == 'function')

    console.log(bar.foo)
    console.log(bar.bar())


    node foo.js结果是:
    true
    true
    undefined
    bar

    这里新require进来的js并不是一个global scope,如果拿来和ruby比较的话
    module Bar
    BAR = 'bar'
    def ...
    end
    然后在别的地方是可以Bar::BAR的
    real_newbie
        22
    real_newbie  
    OP
       2011-04-29 11:56:18 +08:00
    @kuno,

    恩, 我也是这么理解的. 只是单纯的把原来放在一个文件里的东西分散到了不同的文件, 便于管理而已.
    aligo
        23
    aligo  
       2011-04-29 11:57:51 +08:00
    @kuno 并且如果再加入一个rab.js如下
    exports.rab = function () {
    return exports.bar()
    }

    然后在原来的的foo.js
    var rab = require('rab')
    console.log(rab.rab())

    这样是会出错的
    aligo
        24
    aligo  
       2011-04-29 12:02:53 +08:00
    @kuno 最后,再创建一个oof.js:
    exports.oof = function () {
    return oof_text
    }
    exports.test_oof = function () {
    return typeof oof_text == 'undefined'
    }

    foo.js改为:
    var oof_text = 'oof!'
    require.paths.unshift('.')
    var oof = require('oof')

    console.log(oof.test_oof())
    console.log(oof.oof())

    你会看到true然后出错
    real_newbie
        25
    real_newbie  
    OP
       2011-04-29 12:04:09 +08:00
    我不明白这个所谓global scope是什么意思.

    如果要复用bar()似乎应该是这样子做:

    function bar() {}

    exports.bar = function() {
    return bar();
    }

    exports.rab = function() {
    return bar();
    }
    aligo
        26
    aligo  
       2011-04-29 12:06:17 +08:00
    总之我要说的是这里require进来的东西就是module,每个js都是自己的scope,只有通过exports.的东西才会被require返回,但是这个exports在次require之间也是互相独立的
    aligo
        27
    aligo  
       2011-04-29 12:13:13 +08:00
    然后再来玩个新的bar.js:
    var foo = 'b'
    var_foo = 'ar'

    Bar = function () {
    return foo + var_foo
    }


    然后这是新foo.js

    require.paths.unshift('.')

    require('bar')

    console.log(typeof bar == 'undefined')
    console.log(typeof var_bar == 'undefined')
    console.log(typeof Bar == 'function')

    console.log(Bar)
    console.log(Bar())
    console.log(bar)
    console.log(var_bar)

    结果是
    true
    true
    true
    [Function]
    bar
    到最后2个就出错了
    n2n3
        28
    n2n3  
       2011-04-29 12:25:47 +08:00
    real_newbie
        29
    real_newbie  
    OP
       2011-04-29 12:33:30 +08:00
    我总算是搞明白了.

    @kuno 的require实现其实没有错. 但是return module.exports时候, 那些在module里却没有被exports的东西还是在的. 所以return的还是一个Closure.

    @aligo的理解是正确的.
    real_newbie
        30
    real_newbie  
    OP
       2011-04-29 12:42:30 +08:00
    @n2n3, 果然还是源代码最高啊. module.js里的"Module._load"很清楚了.
    aligo
        31
    aligo  
       2011-04-29 15:06:03 +08:00
    恩,所以继续离题一下,如果要在前端里实现这样的效果,就需要像你上面提到的那个requirejs那样,在每个js中用一个define()包裹,像#9那样
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1484 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 16:44 PVG 00:44 LAX 08:44 JFK 11:44
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86