如何从源码中学习javascript

艾伦说啊,学习javascript,必须要学会看源码,通过高手的源码,你可以从中吸取很多书本上难以看到的技巧。

看源码就好像喝鸡汤,所有的营养都在这汤里了。这汤就是源码,高手写的源码,就是最好的鸡汤。

这也是他短短两年时间,快速在前端界崭头露角,成为一名新星的原因。一般人,他不会说的,但是我觉得,好东西就要分享。

这样才能让前端界有更多新星出现,为推进整个前端的健康发展做出贡献。

他的这些源码经验,不知道会不会在他的新书里出现。如果有,算是提前爆料了。

下面就是我这些天,一边实践,一边总结的一些方法。

我以艾伦的一个模块类require.js为例。

require.js的源码:

/****************************************************************
*          支持AMD,CMD模块加载方式
*          @by Aaron
*               github:https://github.com/JsAaron/aaronRequire
*               blog:http://www.cnblogs.com/aaronjs/
 *****************************************************************/
;(function(r) {
    if (typeof module === "object" && typeof require === "function") {
        module.exports.require = r.require;
        module.exports.define = r.define;
    } else {
        require = r.require;
        define  = r.define;
    }
})(function() {
    var objproto = Object.prototype,
        objtoString = objproto.toString,
        arrproto = Array.prototype,
        nativeForEach = arrproto.forEach,
        modules = {},
        pushStack = {};

    function each(obj, callback, context) {
        if (obj == null) return;
        //如果支持本地forEach方法,并且是函数
        if (nativeForEach && obj.forEach === nativeForEach) {
            obj.forEach(callback, context);
        } else if (obj.length === +obj.length) {
            //for循环迭代
            for (var i = 0, l = obj.length; i < l; i++) {
                if (callback.call(context, obj[i], i, obj) === breaker) return;
            }
        }
    };

    function isFunction(it) {
        return objtoString.call(it) === '[object Function]';
    }

    function isArray(it) {
        return objtoString.call(it) === '[object Array]';
    }

    //解析依赖关系
    function parseDeps(module) {
        var deps = module['deps'],
            temp = [];
        each(deps, function(id, index) {
            temp.push(build(modules[id]))
        })

        return temp;
    }

    function build(module) {
        var depsList,existMod,
            factory = module.factory,
            id = module.id;

        if (existMod = pushStack[id]) { //去重复执行
            return existMod;
        }

        //接口点,将数据或方法定义在其上则将其暴露给外部调用。
        module.exports = {};

        //去重
        delete module.factory;

        if (module.deps) {
            //依赖数组列表
            depsList = parseDeps(module);
            module.exports = factory.apply(module, depsList);
        } else {
            // exports 支持直接 return 或 modulejs.exports 方式
            module.exports = factory(require, module.exports, module) || module.exports;
        }

        pushStack[id] = module.exports;

        return module.exports;
    }

    //解析require模块
    function makeRequire(ids, callback) {
        var r = ids.length,
            shim = [];
        while (r--) {
            shim.unshift(build(modules[ids[r]]));
        }
        if (callback) {
            callback.apply(null, shim);
        } else {
            shim = null;
        }
    }

    return {
        //引入模块
        require: function(id, callback) {
            //数组形式
            //require(['domReady', 'App'], function(domReady, app) {});
            if (isArray(id)) {
                if (id.length > 1) {
                    return makeRequire(id, callback);
                }
                id = id[0];
            }

            if (!modules[id]) {
                throw "module " + id + " not found";
            }

            if (callback) {
                var module = build(modules[id]);
                callback(module)
                return module;
            } else {
                if (modules[id].factory) {
                    return build(modules[id]);
                }
                return modules[id].exports;
            }
        },
        //定义模块
        define: function(id, deps, factory) { //模块名,依赖列表,模块本身
            if (modules[id]) {
                throw "module " + id + " 模块已存在!";
            }
            //存在依赖导入
            if (arguments.length === 3) {
                modules[id] = {
                    id      : id,
                    deps    : deps,
                    factory : factory
                };
            } else {
                modules[id] = {
                    id      : id,
                    factory : deps
                };
            }
        }
    }

}());
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>changsha,Aaron</title>

    <!-- 引用自Aaron私有框架Xut -->
    <script type="text/javascript" src="require.js"></script>

</head>
<body>

</body>
<script type="text/javascript">
    //定义模块a
    define('a',function(){
        function a(){
            console.log('a is fine')
            //your code
        }
        return a
    });
    //定义模块b
    define('b',function(){
       function b(){
            console.log('b is fine')
            //your code
        }
        return b
    });

    //定义模块c,依赖模块a 和 b
    define('c',['a','b'],function(a,b){
        function c(){
            a();
            b();
            console.log('c is fine')
            //your code
        }
        return c
    });

    //引入模块c
    // require('c',function(c){
    //     c()
    // })

    // //引入模块a b c
    // require(['a','b','c'],function(c){
    //    // c()
    // })

    // 引入模块a,并执行
    require('a')()
</script>
</html>

我推荐用谷歌浏览器调式,很方便。

学源码的第一条法则,是从函数调用处开始看。

我的demo是从define('a')开始的。这是最简单的一种情况。

打开浏览器看下

谷歌的控制台很棒,我常用有,Console用来输出日志,Resources用来查看数据

elements查看结构,样式。Sourcese用来断点调式。

今天想重点记录一下这个断点调式。

从哪里开始断,这个直接影响到调式的效果。自己摸索着体会吧。

按F11看光标经过的地方,停留在变量名上,会显示出变量存放的值。这比打一堆的console.log或者alert要方便很多

而且也不用看这个函数在哪里定义的,按F11,自动带你去。你只要在脑子里预先猜想一下,你的心中的流程和实际的是否一

样。在一些具体的方法体内,初步调式的时候,你大可跳过。先理清流程,整出大至的思路。然后看实现的细节就比较有体会了。

在明白这些实现之后,在自己重新默写一遍,看能不能实现。最后试着简化,用自己的代码去实现它。

这一路下来,你会收很多收获的。

原文地址:https://www.cnblogs.com/afrog/p/3829187.html