One, warm up – first look at the actual code

A. s file

// Define Wall and internal methods; (function(window, FUNC, undefined){
    var name = 'wall';

    Wall.say = function(name){
        console.log('I\'m '+ name +' !');
    };

    Wall.message = {
        getName : function(){
            return name;
        },
        setName : function(firstName, secondName){
            name = firstName+The '-'+secondName;
        }
    };
})(window.window.Wall || (window.Wall = {}));Copy the code

The index. The JSP file

<script type='text/javascript'>
    <%
        // JavaCode straight outjs
        out.print("Sniffer.run({'base':window, 'name':'Wall.say', 'subscribe':true}, 'wall'); \n"); % >// lab.js is a file loading tool // after loading the dependency of A.js, $lab.script ("a.js").wait(function(){// Trigger subscribed method sniffer.trigger ({'base':window, 'name':' wall.say '}); });</script>Copy the code

This way, no matter how big the A.js file is, wall.say (‘ Wall ‘) can wait until the file is actually loaded.

2. Tool Introduction

// execute wall.message. setName('wang', 'Wall ');
Sniffer.run({
    'base':Wall,
    'name':'message.setName'.'subscribe':true
}, 'wang'.'wall');Copy the code

Look at this execution code and you might be confused – what the hell! 😆

The sniffer.js function is to test the execution method, if not execute, will not throw the error.

For example, wall.message.setName (‘wang’, ‘Wall ‘); If the file in which the method resides has not yet been loaded, no error is reported. The logic is to cache it, wait for the method to load, and then call it.

The method called again is as follows:

// Trigger subscribed methods
Sniffer.trigger({
    'base':Wall,
    'name':'message.setName'
});Copy the code

Online demo: https://wall-wxk.github.io/blogDemo/2017/02/13/sniffer.html (need to see in the console, suggest to use PC)

Speaking of the birth of this tool, because of the needs of the company’s business, a tool written by myself. Because the company’s background language is Java, like to use JSP out.print() method, directly output some JS methods to the client execution. There is a paradox here. Sometimes, before the JS file has been downloaded, the background output statements have already started calling methods, which can be embarrassing.

So, this tool does two things:

1. Check whether the executed JS method exists. If so, execute it immediately. 2. Cache js methods that do not exist for the time being, and then take them out of the cache queue to trigger execution.

The core basis of sniffing — operatorsin

The method iterates through methods in the namespace using the in operator, and if it gets a value, it is executable. Otherwise, it cannot be executed.





The operator in

From this example, you can see how sniffer.js works.

Fourth, abstract the sniffing method

/** * @function {private} funcName -- function name ***.***.*** * @param {object} base -- function name **/
function checkMethod(funcName, base){
    var methodList = funcName.split('. '), // Method name list
        readyFunc = base, // Test the qualified function part
        result = {
            'success':true.'func':function(){}},// Test result returned
        methodName, // Single method name
        i;

    for(i = 0; i < methodList.length; i++){
        methodName = methodList[i];
        if(methodName in readyFunc){
            readyFunc = readyFunc[methodName];
        }else{
            result.success = false;
            return result;
        }
    }

    result.func = readyFunc;
    return result; 
}Copy the code

Like a Wall. The message. The elegantly-named setName (‘ wang ‘, ‘Wall’); To check whether the method can be executed, perform the following steps: 1. Check whether Wall exists in Window. 2. If the Wall exists, check whether message is in the Wall. If setName exists in message, check whether setName exists in message. Finally, the existence of both judgments represents executable. If any of the middle checks fail, the method cannot be executed.

Implement cache

Caching is implemented using closures. By the nature of a queue, stored in a list

; (function(FUN, undefined){
    'use strict'

    var list = []; // The method to call to store the subscription

    // Execute method
    FUN.run = function(){
        // A lot of code...

        // Cache subscribed functionslist.push(...) ; }; }) (window.Sniffer || (window.Sniffer = {}));Copy the code

Determine the contents of a single item in the queue

Since operator in works, it needs a few basis points to test. So the first term we have to have is base

2. Check the character type of method name like wall.message.setName (‘wang’, ‘Wall ‘); Message.setname is also required if the base point {‘base’:Wall} has been specified. {‘base’:Wall, ‘name’:’message.setName’}

3. Cache method args like wall.message.setName (‘wang’, ‘Wall ‘); , has two parameters (‘wang’, ‘wall’), so it needs to be stored. That {‘ base ‘: Wall,’ name ‘:’ message. Elegantly-named setName ‘, ‘the args: [‘ wang’, ‘Wall’]}.

The reason arguments are cached in arrays is because the method arguments change, so subsequent code needs to apply to trigger. Similarly, the parameters need to be cached in an array

So, the individual item contents of the cache queue are as follows:

{
    'base':Wall,
    'name':'message.setName'.'args': ['wang'.'wall']}Copy the code

Implement the run method

; (function(FUN, undefined){
    'use strict'

    var list = []; // The method to call to store the subscription

    * @version {create} 2015-11-30 * @description * Purpose: Only designed for lazy loading * Example: Wall.mytext.init(45, false); * Call: sniffer.run ({'base':window, 'name':' wall.mytext.init '}, 45, false); Or sniffer.run ({'base':Wall, 'name':'mytext.init'}, 45, false); * Example: sniffer.run. apply(window, [{'name':' wall.mytext.init '}, 45, false]); * * /
    FUN.run = function(){
        if(arguments.length < 1 || typeof arguments[0] != 'object') {throw new Error('Sniffer.run argument error ');
            return;
        }

        var name = arguments[0].name, // The function name contains 0 bits of type Object for easy extension
            subscribe = arguments[0].subscribe || false.// Subscribe When the function is executable, the function is called, true: subscribe; False: I don't subscribe to
            prompt = arguments[0].prompt || false.// Whether to display a prompt (when the function fails to execute)
            promptMsg = arguments[0].promptMsg || 'Functionality is still loading, please wait'.// The function failed to execute the prompt
            base = arguments[0].base || window.// Base object, the starting point for function lookup

            args = Array.prototype.slice.call(arguments), // Parameter list
            funcArgs = args.slice(1), // The argument list of the function
            callbackFunc = {}, // temporarily store functions that need to be called back
            result; // Test result

        result = checkMethod(name, base);
        if(result.success){
            subscribe = false;
            try{
                return result.func.apply(result.func, funcArgs); // apply adjusts the pointer pointer to the function
            }catch(e){
                (typeof console! ='undefined') && console.log && console.log('error: name ='+ e.name +'; message='+ e.message); }}else{
            if(prompt){
                // Output prompt to page, code omitted}}// Cache subscribed functions
        if(subscribe){ callbackFunc.name = name; callbackFunc.base = base; callbackFunc.args = funcArgs; list.push(callbackFunc); }};// Sniffing methods
    function checkMethod(funcName, base){
        / / code...
    }
})(window.Sniffer || (window.Sniffer = {}));Copy the code

The run method checks whether the method is executable. If it is, the run method is executed. If not, cache is required based on the parameters passed in.

Arguments.run (); arguments.run ();

The first parameter [0] is used to pass in configuration items. Arguments [0].name and the cache flag arguments[0].subscribe.

From the second parameter to the NTH parameter, the method caller passes in the required parameters.

Using generic methods, convert arguments to a real array. (args = Array. Prototype. Slice. The call (the arguments)) and then, the parameters of the cutting out the method to use. (funcArgs = args.slice(1))

Once the arguments for the run method are processed, you can call the checkMethod method for sniffing.

According to the results of sniffing, there are two cases:

If the sniffing result is executable, call apply and return result.func. Apply (result.func, funcArgs);

The important thing here is that the scope must be result.func, which is Wall. Message.setname for the example. This way, if this is used in the method, the reference does not change.

Return is used because some methods have a return value after execution, so we need to add return to pass the return value.

If the result of the sniffing is not executable, the queue list is cached according to the input configuration value SUBSCRIBE. To cache, concatenate the queue individual items and push them into the list.

Implement trigger method

; (function(FUN, undefined){
    'use strict'

    var list = []; // The method to call to store the subscription

    // Execute method
    FUN.run = function(){
        / / code...
    };

    /** * @function /** @param {object} option Trigger is only designed for lazy loading * In addition, the trigger method is called only if the js on which the subscription method is based has been loaded and parsed * the corresponding item in the list **/ is cleared regardless of whether the trigger succeeds
    FUN.trigger = function(option){
        if(typeofoption ! = ='object') {throw new Error('Sniffer.trigger parameter error ');
            return;
        }

        var funcName = option.name || ' './ / the function name
            base = option.base || window.// Base object, the starting point for function lookup
            newList = [], // To update the list
            result, // Test result
            func, // Store a pointer to the execution method
            i, / / traverse the list
            param; // list[I]

        console.log(funcName in base);

        if(funcName.length < 1) {return;
        }

        // Iterate over the list, execute the corresponding function, and remove it from the cache pool list
        for(i = 0; i < list.length; i++){
            param = list[i];
            if(param.name == funcName){
                result = checkMethod(funcName, base);
                if( result.success ){
                    try{
                        result.func.apply(result.func, param.args);
                    }catch(e){
                        (typeof console! ='undefined') && console.log && console.log('error: name ='+ e.name +'; message='+ e.message); }}}else{
                newList.push(param);
            }
        }

        list = newList;
    };

    // Sniffing methods
    function checkMethod(funcName, base){
        / / code...
    }
})(window.Sniffer || (window.Sniffer = {}));Copy the code

The trigger method is not difficult to understand if the previous run method is understood.

1. The trigger method is first told which method needs to be removed from the queue list to execute. 2. Before executing a method, you need to sniff again to see if the method already exists. It can only be executed if it exists. Otherwise, the method is considered dead and can be removed from the cache.


Nine, practicality and reliability

The utility aspect is indisputable, no matter what the code stack, sniffer.js is worth having!

In terms of reliability, sniffer.js has been used on high-traffic corporate products without any compatibility or performance issues reported so far. This aspect also can guarantee!

Finally, attach a source address: https://github.com/wall-wxk/sniffer/blob/master/sniffer.js