Home  >  Article  >  Web Front-end  >  Detailed introduction to JavaScript modularization and SeaJs source code analysis

Detailed introduction to JavaScript modularization and SeaJs source code analysis

黄舟
黄舟Original
2017-03-10 15:31:581277browse

The structure of the web page is becoming more and more complex. It can be regarded as a simple APP. If you still put all the code into one file as before, there will be some problems:

  • Global variables affect each other

  • JavaScript files become larger, affecting loading speed

  • The structure is confusing and difficult to maintain

Compared with the backend (such as Java), you can see the obvious gap. In 2009, Ryan Dahl created the node.js project to use JavaScript for server programming, which marked the official birth of "JS modular programming".

Basic Principle

A module is a collection of functions. Then you can split a large file into some small files, define different functions in each file, and then introduce them in HTML:

var module1 = new Object({
    _count : 0,
    m1 : function (){
        //...
    },
    m2 : function (){
        //...
    }
});

The disadvantage of this is that all members in the module are exposed! We know that the local variables of the function cannot be accessed from the outside, so we can use the immediate execution function to optimize:

var module1 = (function(){
    var _count = 0;
    var m1 = function(){
        //...
    };
    var m2 = function(){
        //...
    };
    return {
        m1 : m1, m2 : m2
    };
})();

Everyone may define modules in various ways. If they can all follow certain specifications, the benefits will be It will be very large: can refer to each other!

Module specification

The math.js module is defined in node.js as follows:

function add(a, b){
    return a + b;
}
exports.add = add;

When used in other modules, just use the global require function to load:

var math = require('math');
math.add(2,3);

There is no problem in synchronizing require on the server, but the browser cannot do this in a network environment, so there is an asynchronous AMD specification:

require(['math'], function (math) {// require([module], callback);
    math.add(2, 3);
});

The module is defined as follows (the module can Depends on other modules):

define(function (){ // define([module], callback);
    var add = function (x,y){
        return x+y;
    };
    return { add: add };
});

You can load many other resources with RequireJS (see here), which is very good and powerful! I use SeaJS more often at work, and the specification used is called CMD. It is recommended (should refer to asynchronous mode):

as lazy as possible!

The difference between the processing method of dependent modules and AMD is:

AMD is executed in advance (dependency front-end), and CMD is delayed execution (dependency is nearby).

The way to define a module in CMD is as follows:

define(function(require, exports, module) {
    var a = require('./a');
    a.doSomething();
    var b = require('./b');
    b.doSomething();
});

For usage, please refer to the document directly, so I won’t go into details here!

SeaJS source code analysis

When I first came into contact with modularization, I felt that this was too simple. Isn’t it just:

Set onload and src when creating the script tag!

In fact this is the case, but not entirely! Let’s start looking at the SeaJS code (sea-debug.js). A module may go through the following states during the loading process:

var STATUS = Module.STATUS = {
    // 1 - The `module.uri` is being fetched
    FETCHING: 1,
    // 2 - The meta data has been saved to cachedMods
    SAVED: 2,
    // 3 - The `module.dependencies` are being loaded
    LOADING: 3,
    // 4 - The module are ready to execute
    LOADED: 4,
    // 5 - The module is being executed
    EXECUTING: 5,
    // 6 - The `module.exports` is available
    EXECUTED: 6,
    // 7 - 404
    ERROR: 7
}

The Modul object is used in memory to maintain module information:

function Module(uri, deps) {
    this.uri = uri
    this.dependencies = deps || [] // 依赖模块ID列表
    this.deps = {} // 依赖模块Module对象列表
    this.status = 0 // 状态
    this._entry = [] // 在模块加载完成之后需要调用callback的模块
}

Start the module on the page The system needs to use the seajs.use method:

seajs.use(‘./main’, function(main) {// 依赖及回调方法
    main.init();
});

The overall logic of the loading process can be seen in Module.prototype.load:

Module.prototype.load = function() {
    var mod = this
    if (mod.status >= STATUS.LOADING) {
        return
    }
    mod.status = STATUS.LOADING
    var uris = mod.resolve() // 解析依赖模块的URL地址
    emit("load", uris)
    for (var i = 0, len = uris.length; i < len; i++) {
        mod.deps[mod.dependencies[i]] = Module.get(uris[i])// 从缓存取或创建
    }
    mod.pass(); // 将entry传递给依赖的但还没加载的模块
    if (mod._entry.length) {// 本模块加载完成
        mod.onload()
        return
    }
    var requestCache = {};
    var m;
    // 加载依赖的模块
    for (i = 0; i < len; i++) {
        m = cachedMods[uris[i]]
        if (m.status < STATUS.FETCHING) {
            m.fetch(requestCache)
        } else if (m.status === STATUS.SAVED) {
            m.load()
        }
    }
    for (var requestUri in requestCache) {
        if (requestCache.hasOwnProperty(requestUri)) {
            requestCache[requestUri]()
        }
    }
}

Generally speaking, the logic is very smooth, so I won’t go into details. The only thing that is more convoluted is the _entry array. I couldn't find a relatively easy-to-understand article on the Internet, so I roughly understood it while looking at the code and guessing. In fact, I just need to remember its goal:

When all dependent modules are loaded. Then execute the callback function!

In other words:

The array_entry stores a list of which module dependencies may be loaded after the current module is loaded (reverse relationship of dependencies) !

For example, module A depends on modules B, C, and D. Then the status after pass is as follows:

At this time A The remain in is 3, which means that it still has three dependent modules that have not been loaded! And if module B depends on modules E and F, then A will also be passed out when it is loaded:

There are several details:

  1. Modules that have been loaded will not be propagated;

  2. Modules that have been propagated once will not be propagated again;

  3. If the dependent module is being loaded, it will be propagated recursively;

After maintaining the dependency relationship, you can load the module through Module.prototype.fetch. There are two ways# Implementation method of ##sendRequest:

  1. importScripts

  2. script

Then according to As a result, the

load or error method is executed. After all the dependent modules are loaded, the onload method will be executed:

Module.prototype.onload = function() {
    var mod = this
    mod.status = STATUS.LOADED
    for (var i = 0, len = (mod._entry || []).length; i < len; i++) {
        var entry = mod._entry[i]
        if (--entry.remain === 0) {
            entry.callback()
        }
    }
    delete mod._entry
}

where

--entry.remain is equivalent to telling the module corresponding to the entry: your One of the dependencies list is already completed! And entry.remain === 0 means that all the modules it depends on have been loaded! Then the callback function will be executed at this time:

for (var i = 0, len = uris.length; i < len; i++) {
    exports[i] = cachedMods[uris[i]].exec();
}
if (callback) {
    callback.apply(global, exports)// 执行回调函数
}

After the script is downloaded, the

define method will be executed immediately to maintain the module information:

is not explicitly specified When dependencies, parseDependencies will be used to use the require() fragment in the regular matching method (it is a good habit to specify a dependency list).

Then execute the

factory method to generate module data:

var exports = isFunction(factory) ?
    factory.call(mod.exports = {}, require, mod.exports, mod) :
    factory

然后执行你在seajs.use中定义的callback方法:

if (callback) {
    callback.apply(global, exports)
}

当你写的模块代码中require时,每次都会执行factory方法:

function require(id) {
    var m = mod.deps[id] || Module.get(require.resolve(id))
    if (m.status == STATUS.ERROR) {
        throw new Error(&#39;module was broken: &#39; + m.uri)
    }
    return m.exec()
}

到这里核心的逻辑基本上讲完了,补一张状态的转换图:

以后在用的时候就可以解释一些诡异的问题了!

总结

模块化非常好用,因此在ECMAScript 6中也开始支持,但是浏览器支持还是比较堪忧的~~

The above is the detailed content of Detailed introduction to JavaScript modularization and SeaJs source code analysis. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn