关闭 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在运行中的整个执行过程。

    上一篇返回首页 下一篇

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

    别人在看

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

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

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

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    IT头条

    无线路由大厂 TP-Link突然大裁员:补偿N+3

    02:39

    Meta 千万美金招募AI高级人才

    00:22

    更容易爆炸?罗马仕充电宝被北京多所高校禁用,公司紧急回应

    17:19

    天衍”量子计算云平台,“超算+量算” 告别“算力孤岛时代”

    18:18

    华为Pura80系列新机预热,余承东力赞其复杂光线下的视频拍摄实力

    01:28

    技术热点

    MySQL基本调度策略浅析

    MySQL使用INSERT插入多条记录

    SQL Server高可用的常见问题

    3D立体图片展示幻灯片JS特效

    windows 7上网看视频出现绿屏的原因及解决方法

    windows 7 64位系统的HOSTS文件在哪里?想用它加快域名解析

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

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