A javascript design pattern for options with default values?

断了今生、忘了曾经 提交于 2019-11-30 10:25:24

问题


// opt_options is optional
function foo(a, b, opt_options) {
  // opt_c, opt_d, and opt_e are read from 'opt_options', only c and d have defaults
  var opt_c = 'default_for_c';
  var opt_d = 'default_for_d';
  var opt_e; // e has no default

  if (opt_options) {
    opt_c = opt_options.c || opt_c;
    opt_d = opt_options.d || opt_d;
    opt_e = opt_options.e;
  }
}

The above seems awfully verbose. What's a better way to handle argument options with default parameters?


回答1:


This uses jQuery.extend but could be interchanged with an object merger from your library of choice or Object.assign in ES6.

function Module(options){
    var defaults = {
        color: 'red'
    };
    var actual = $.extend({}, defaults, options || {});
    console.info( actual.color );
}

var a = new Module();
// Red
var b = new Module( { color: 'blue' } );
// Blue

Edit: Now also in underscore or lodash!

function Module(options){
    var actual = _.defaults(options || {}, {
         color: 'red'
    });
    console.info( actual.color );
}

var a = new Module();
// Red
var b = new Module( { color: 'blue' } );
// Blue

In Javascript ES6 you can use Object.assign:

function Module(options = {}){
    let defaults = {
        color: 'red'
    };
    let actual = Object.assign({}, defaults, options);
    console.info( actual.color );
}



回答2:


There are a couple new ways in ES6/ES2015. Using Object.assign:

options = Object.assign({}, defaults, options);

Using destructuring assignment:

const { a = 1, b = 2 } = options;

You can also use destructuring function parameters:

const ƒ = ({a = 1, b = 2, c = 3} = {}) => {
   console.log({ a, b, c });
};

No dependencies!




回答3:


To get default options without additional dependencies, I use the following pattern:

var my_function = function (arg1, arg2, options) {
    options = options || {};
    options.opt_a = options.hasOwnProperty('opt_a') ? options.opt_a : 'default_opt_a';
    options.opt_b = options.hasOwnProperty('opt_b') ? options.opt_b : 'default_opt_b';
    options.opt_c = options.hasOwnProperty('opt_c') ? options.opt_c : 'default_opt_b';


    // perform operation using options.opt_a, options.opt_b, etc.
};

Although a bit verbose, I find it easy to read, add/remove options and add defaults. When there are LOTS of options, a slightly more compact version is:

var my_function = function (arg1, arg2, options) {
    var default_options = {
        opt_a: 'default_opt_a',
        opt_b: 'default_opt_b',
        opt_c: 'default_opt_c'};

    options = options || {};
    for (var opt in default_options)
        if (default_options.hasOwnProperty(opt) && !options.hasOwnProperty(opt))
            options[opt] = default_options[opt];

    // perform operation using options.opt_a, options.opt_b, etc.
};



回答4:


And the more compact jQuery version:

function func(opts) {
    opts = $.extend({
        a: 1,
        b: 2
    }, opts);

    console.log(opts);
}

func();            // Object {a: 1, b: 2} 
func({b: 'new'});  // Object {a: 1, b: "new"} 



回答5:


If you need to do this in many consecutive functions, a way to standardize the process and speed it up is:

function setOpts (standard, user) {
  if (typeof user === 'object' {
    for (var key in user) {
      standard[key] = user[key];
    }
  }
}

Then you can just define your functions simply like this:

var example = function (options) {
  var opts = {
    a: 1,
    b: 2,
    c:3
  };
  setOpts(opts, options);
}

This way you only define one options object inside the function, which contains the default values.

If you want to put an extra check to avoid prototype inheritance, the first function can be:

function setOpts (standard, user) {
  if (typeof user === 'object') {
    Object.keys(user).forEach(function (key) {
      standard[key] = user[key];
    });
  }
}

The latter case is not supported for: IE < 9, Chrome < 5, Firefox < 4, Safari < 5

(you can check the compatibility table here)


Finally ECMAScript 6 will bring us the best way to do this: default parameters.

It will take a few months though before this is widely supported across browsers.




回答6:


Although Object.assign is pretty straight way to merge options with defaults it has some disadvantages:

  1. if you want to set conditional options with ternary operator - it will overwrite defaults even for undefined values:

    const options = {
      logging: isProduction ? 'none' : undefined
    };
    const defaults = {
      logging: 'verbose'
    }
    Object.assign({}, defaults, options); // {logging: undefined} !
    
  2. if you provide incorrect option name - you will not be warned:

    const options = {
      loging: 'none' // typo
    };
    const defaults = {
      logging: 'verbose'
    }
    Object.assign({}, defaults, options); // {logging: 'verbose', loging: 'none'} !
    

To cover these cases I've created tiny flat-options package.
It does not overwrite defaults for undefined values:

const options = {
  logging: isProduction ? 'none' : undefined
};
const defaults = {
  logging: 'verbose'
}
flatOptions(options, defaults); // {logging: 'verbose'}

and warns for incorrect option names:

const options = {
  loging: 'none' // typo
};
const defaults = {
  logging: 'verbose'
}
flatOptions(options, defaults); // throws "Unknown option: loging."

Hope this helps!




回答7:


If you have access to ES6 with a stage 4 proposal (such as with Babel) you can accomplish this with spread and destructuring assignment.

const defaultPrintOptions = {
  fontName: "times",
  fontStyle: "normal",
  fontSize: 10,
  align: "left"
};

// Setting the null default isn't necessary but
// makes it clear that the parameter is optional.
// Could use {} but would create a new object
// each time the function is called.
function print(text, options = null) {
  let {
    fontName,
    fontStyle,
    fontSize,
    align
  } = {
    ...defaultPrintOptions,
    ...options
  };

  console.log(text, fontName, fontStyle, fontSize, align);
}

print("All defaults:");
print("Override some:", {
  fontStyle: "italic",
  align: "center"
});
print("Override all:", {
  fontName: "courier",
  fontStyle: "italic",
  fontSize: 24,
  align: "right"
});

This also works (but may create more objects):

function myFunction({ 
  text = "", 
  line = 0, 
  truncate = 100 
} = {}) {
  console.log(text, line, truncate);
}

(latter example from David Walsh - @wprl's answer also mentions this)




回答8:


      var mergeOptions = function mergeOptions(userOptions) {

        // Default options
        var options = {
            height: "100px",
            width: "100px" ,
            color: "blue"
        }

        if (userOptions) {
            Object.keys(userOptions).forEach(function (key) {
                options[key] = userOptions[key]
            })
        }

        return options;
    }



回答9:


I think you're looking for something like this (sorry for the late reply):

function foo(a, b, options) { 
    this.defaults = {
        x: 48, 
        y: 72,
        z: 35
    };
    for (var i in this.defaults) {
        if (options[i] != "undefined") { this.defaults[i] = options[i]; }
    }
    // more code...
}

edit: apologies, grabbed this from some old code... You should make sure to use the hasOwnProperty() method to make sure you don't iterate over everything on function.prototype

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/hasOwnProperty




回答10:


Now that I think about it, I kind of like this:

function foo(a, b, opt_options) {
  // Force opt_options to be an object
  opt_options = opt_options || {};

  // opt_c, opt_d, and opt_e are read from 'opt_options', only c and d have defaults
  var opt_c = 'default_for_c' || opt_options.c;
  var opt_d = 'default_for_d' || opt_options.d;
  var opt_e = opt_options.e; // e has no default
}


来源:https://stackoverflow.com/questions/9602449/a-javascript-design-pattern-for-options-with-default-values

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!