Automatically set arguments as instance properties in ES6

前端 未结 4 961
孤街浪徒
孤街浪徒 2020-11-27 19:27

CoffeeScript automatically sets the arguments as instance properties in the constructor if you prefix the arguments with @.

Is there any trick to accomplish the sam

4条回答
  •  萌比男神i
    2020-11-27 19:53

    Legacy support script

    I've extended Function prototype to give access to parameter auto-adoption to all constructors. I know we should be avoiding adding functionality to global objects but if you know what you're doing it can be ok.

    So here's the adoptArguments function:

    var comments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/g;
    var parser = /^function[^\(]*\(([^)]*)\)/i;
    var splitter = /\s*,\s*/i;
    
    Function.prototype.adoptArguments = function(context, values) {
        /// Injects calling constructor function parameters as constructed object instance members with the same name.
        /// The context object (this) in which the the calling function is running.
        /// Argument values that will be assigned to injected members (usually just provide "arguments" array like object).
    
        "use strict";
    
        // only execute this function if caller is used as a constructor
        if (!(context instanceof this))
        {
            return;
        }
    
        var args;
    
        // parse parameters
        args = this.toString()
            .replace(comments, "") // remove comments
            .match(parser)[1].trim(); // get comma separated string
    
        // empty string => no arguments to inject
        if (!args) return;
    
        // get individual argument names
        args = args.split(splitter);
    
        // adopt prefixed ones as object instance members
        for(var i = 0, len = args.length; i < len; ++i)
        {
            context[args[i]] = values[i];
        }
    };
    

    The resulting call that adopts all constructor call arguments is now as follows:

    function Person(firstName, lastName, address) {
        // doesn't get simpler than this
        Person.adoptArguments(this, arguments);
    }
    
    var p1 = new Person("John", "Doe");
    p1.firstName; // "John"
    p1.lastName; // "Doe"
    p1.address; // undefined
    
    var p2 = new Person("Jane", "Doe", "Nowhere");
    p2.firstName; // "Jane"
    p2.lastName; // "Doe"
    p2.address; // "Nowhere"
    

    Adopting only specific arguments

    My upper solution adopts all function arguments as instantiated object members. But as you're referring to CoffeeScript you're trying to adopt just selected arguments and not all. In Javascript identifiers starting with @ are illegal by specification. But you can prefix them with something else like $ or _ which may be feasible in your case. So now all you have to do is detect this specific naming convention and only add those arguments that pass this check:

    var comments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/g;
    var parser = /^function[^\(]*\(([^)]*)\)/i;
    var splitter = /\s*,\s*/i;
    
    Function.prototype.adoptArguments = function(context, values) {
        /// Injects calling constructor function parameters as constructed object instance members with the same name.
        /// The context object (this) in which the the calling function is running.
        /// Argument values that will be assigned to injected members (usually just provide "arguments" array like object).
    
        "use strict";
    
        // only execute this function if caller is used as a constructor
        if (!(context instanceof this))
        {
            return;
        }
    
        var args;
    
        // parse parameters
        args = this.toString()
            .replace(comments, "") // remove comments
            .match(parser)[1].trim(); // get comma separated string
    
        // empty string => no arguments to inject
        if (!args) return;
    
        // get individual argument names
        args = args.split(splitter);
    
        // adopt prefixed ones as object instance members
        for(var i = 0, len = args.length; i < len; ++i)
        {
            if (args[i].charAt(0) === "$")
            {
                context[args[i].substr(1)] = values[i];
            }
        }
    };
    

    Done. Works in strict mode as well. Now you can define prefixed constructor parameters and access them as your instantiated object members.

    Extended version for AngularJS scenario

    Actually I've written an even more powerful version with following signature that implies its additional powers and is suited for my scenario in my AngularJS application where I create controller/service/etc. constructors and add additional prototype functions to it. As parameters in constructors are injected by AngularJS and I need to access these values in all controller functions I can simply access them, via this.injections.xxx. Using this function makes it much simpler than writing several additional lines as there may be many many injections. Not to even mention changes to injections. I only have to adjust constructor parameters and I immediately get them propagated inside this.injections.

    Anyway. Promised signature (implementation excluded).

    Function.prototype.injectArguments = function injectArguments(context, values, exclude, nestUnder, stripPrefix) {
        /// Injects calling constructor function parameters into constructed object instance as members with same name.
        /// The context object (this) in which the calling constructor is running.
        /// Argument values that will be assigned to injected members (usually just provide "arguments" array like object).
        /// Comma separated list of parameter names to exclude from injection.
        /// Define whether injected parameters should be nested under a specific member (gets replaced if exists).
        /// Set to true to strip "$" and "_" parameter name prefix when injecting members.
        /// Defines injectArguments defaults for optional parameters. These defaults can be overridden.
    {
        ...
    }
    
    Function.prototype.injectArguments.defaults = {
        /// Comma separated list of parameter names that should be excluded from injection (default "scope, $scope").
        exclude: "scope, $scope",
        /// Member name that will be created and all injections will be nested within (default "injections").
        nestUnder: "injections",
        /// Defines whether parameter names prefixed with "$" or "_" should be stripped of this prefix (default true).
        stripPrefix: true
    };
    

    I exclude $scope parameter injection as it should be data only without behaviour compared to services/providers etc. In my controllers I always assign $scope to this.model member even though I wouldn't even have to as $scope is automatically accessible in view.

提交回复
热议问题