关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » UI前端 »browserify运行原理分析

    browserify运行原理分析

    2014-10-22 00:00:00 出处:Ye Mo
    分享

    目前对于前端工程师而言,假如只针对浏览器编写代码,那么很简单,只需要在页面的script脚本中引入所用js就可以了。

    但是某些情况下,我们可能需要在服务端也跑一套类似的逻辑代码,考虑如下这些情景(以node作为后端为例):

    1.spa的应用,需要同时支持服务端直出页面以及客户端pjax拉取数据渲染,客户端和服务器公用一套渲染模板并执行大部分类似的逻辑。

    2.一个通过websocket对战的游戏,客户端和服务端可能需要进行类似的逻辑计算,两套代码分别用于对用户客户端的展示以及服务端实际数值的计算。

    这些情况下,很可能希望我们客户端代码的逻辑能够同时无缝运行在服务端。

    解决方法1:UMD

    一种解决方法是使用UMD的方式,前端使用requirejs,同时兼容nodejs的情况,例如:

    (function (window, factory) {
        if (typeod exports === 'object') {
    
            module.exports = factory();
        } else if (typeof define === 'function' && define.amd) {
    
            define(factory);
        } else {
    
            window.eventUtil = factory();
        }
    })(this, function () {
        //module ...
    });

    解决方案2:使用browerify,使代码能同时运行于服务端和浏览器端。

    什么是browserify?

    Browserify 可以让你使用类似于 node 的 require() 的方式来组织浏览器端的 Javascript 代码,通过预编译让前端 Javascript 可以直接使用 Node NPM 安装的一些库。

    例如我们可以这样写js,同时运行在服务端和浏览器中:

    mo2.js:

    exports.write2 = function(){
        //write2
    }

    mo.js:

    var t = require("./mo2.js");
    exports.write = function(){
        t.write2();
    }

    test.js:

    var mo = require("./mo.js");
    mo.write();

    代码可以完全已node的形式编写。

    原理分析:

    总体过程其实可以分为以下几个步骤:

    阶段1:预编译阶段

    1.从入口模块开始,分析代码中require函数的调用

    2.生成AST

    3.根据AST找到每个模块require的模块名

    4.得到每个模块的依赖关系,生成一个依赖字典

    5.包装每个模块(传入依赖字典以及自己实现的export和require函数),生成用于执行的js

    阶段2:执行阶段

    从入口模块开始执行,递归执行所require的模块,得到依赖对象。

    具体步骤分析:

    1.从入口模块开始,分析代码中require函数的调用

    由于浏览器端并没有原生的require函数,所以所有require函数都是需要我们自己实现的。因此第一步我们需要知道一个模块的代码中,哪些地方用了require函数,依赖了什么模块。

    browerify实现的原理是为代码文件生成AST,然后根据AST找到require函数依赖的模块。

    2.生成AST

    文件代码:

    var t = require("b");
    t.write();

    生成的js描述的AST为:

    {
        "type": "Program",
        "body": [
            {
                "type": "VariableDeclaration",
                "declarations": [
                    {
                        "type": "VariableDeclarator",
                        "id": {
                            "type": "Identifier",
                            "name": "t"
                        },
                        "init": {
                            "type": "CallExpression",
                            "callee": {
                                "type": "Identifier",
                                "name": "require"
                            },
                            "arguments": [
                                {
                                    "type": "Literal",
                                    "value": "b",
                                    "raw": ""b""
                                }
                            ]
                        }
                    }
                ],
                "kind": "var"
            },
            {
                "type": "ExpressionStatement",
                "expression": {
                    "type": "CallExpression",
                    "callee": {
                        "type": "MemberExpression",
                        "computed": false,
                        "object": {
                            "type": "Identifier",
                            "name": "t"
                        },
                        "property": {
                            "type": "Identifier",
                            "name": "write"
                        }
                    },
                    "arguments": []
                }
            }
        ]
    }

    可以看到我们代码中调用的require函数,对应AST中的对象为上面红字部分。

    3.根据AST找到每个模块require的模块名

    生成了AST之后,我们下一部就需要根据AST找到require依赖的模块名了。再次看看上面生成的AST对象,要找到require的模块名,实质上就是要:

    找到type为callExpression,callee的name为require所对应的第一个argument的value。

    关于生成js描述的AST以及解析AST对象,可以参考:

    https://github.com/ariya/esprima 代码生成AST

    https://github.com/substack/node-detective 从AST中提取reqiure

    https://github.com/Constellation/escodegen AST生成代码

    4.得到每个模块的依赖关系,生成一个依赖字典

    从上面的步骤,我们已经可以获取到每个模块的依赖关系,因此可以生成一个以id为键的模块依赖字典,browerify生成的字典示例如下(根据之前的范例代码生成):

    {
        1:[
        function(require,module,exports){
            var t = require("./mo2.js");
            exports.write = function(){
                document.write("test1");
                t.write2();
            }
        },
        {"./mo2.js":2}
        ],
        2:[
        function(require,module,exports){
            exports.write2 = function(){
                document.write("=2=");
            }
        },
        {}
        ],
        3:[
        function(require,module,exports){
            var mo = require("./mo.js");
            mo.write();
        },
        {"./mo.js":1}
        ]}

    字典记录了拥有那些模块,以及模块各自依赖的模块。

    5.包装每个模块(传入依赖字典以及自己实现的export和require函数),生成用于执行的js

    拥有了上面的依赖字典之后,我们相当于知道了代码中的依赖关系。为了让代码能执行,最后一步就是实现浏览器中并不支持的export和require。因此我们需要对原有的模块代码进行包装,就像上面的代码那样,外层会传入自己实现的export和require函数。

    然而,应该怎样实现export和require呢?

    export很简单,我们只要创建一个对象作为该模块的export就可以。

    对于require,其实我们已经拥有了依赖字典,所以要做的也很简单了,只需要根据传入的模块名,根据依赖字典找到所依赖的模块函数,然后执行,一直重复下去(递归执行这个过程)。

    在browerify生成的js中,会添加以下require的实现代码,并传递给每个模块函数:

    (function e(t,n,r){
        function s(o,u){
            if(!n[o]){
                if(!t[o]){
                    var a=typeof require=="function"&&require;
                    if(!u&&a)
                        return a(o,!0);
                    if(i)
                        return i(o,!0);
    
                    var f=new Error("Cannot find module '"+o+"'");
                    throw f.code="MODULE_NOT_FOUND",f
                }
                var l=n[o]={exports:{}};
                t[o][0].call(l.exports,function(e){
                    var n=t[o][1][e];
                    return s(n n:e)
                },l,l.exports,e,t,n,r)
            }
            return n[o].exports
        }
        var i=typeof require=="function"&&require;
        for(var o=0;o<r.length;o++)
            s(r[o]);
        return s
    })

    我们主要关注的红字部分,其中t是传入的依赖字典(之前提到的那块代码),n是一个空对象,用于保存所有新创建的模块(export对象),对比之前的依赖字典来看就比较清晰了:

    首先我们创建module对象(包含一个空对象export),并分别把module和export传入模块函数作为浏览器自己实现的module和export,然后,我们自己实现一个require函数,该函数获取模块名,并递归寻找依赖的模块执行,最后获取到所有被依赖到的模块对象,这个也是browerify生成的js在运行中的整个执行过程。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    商业智能成CIO优先关注点 技术落地方显成效(1)

    用linux安装MySQL时产生问题破解

    JAVA中关于Map的九大问题

    windows 7旗舰版无法使用远程登录如何开启telnet服务

    Android View 事件分发机制详解

    MySQL用户变量的用法

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。