【问题标题】:How to have default params and override those as required in this function?如何拥有默认参数并根据此功能的要求覆盖这些参数?
【发布时间】:2018-04-03 06:46:51
【问题描述】:

我正在编写其他一些开发人员的代码,我注意到一些 JavaScript 文件中使用了以下模式。

var my_team = function() {
    var handleTeam = function() {
        //do some ajax 
        // and update selected DOM element
    }
    return {
        //main function to initiate the module
        init: function() {
            handleTeam();
        }
    };
}();

jQuery(document).ready(function() {
    my_team.init();
});

我处于 JS 开发和学习最佳实践的初级水平。我认为上面的方法称为闭包?对吗?

我想要实现的是:

<select name="players" id="player">
    <option>Mark</option>
    <option>Tom</option>
</select>
<select name="coaches" id="coach">
    <option>Mark</option>
    <option>Tom</option>
</select>

我希望能够将 HTML id-attributes playercoach 传递给 init() 以采取一些操作来操纵 DOM

我知道的一种方法是我可以将init-function 更改为接受两个parameters 并更新handleTeam 以接受两个等等。

init: function(param1, param2) {
    handleTeam(param1, param2);
}

这似乎不是最好的方法,因为除非我更改上面的代码以接受上面列表中的更多parameters,否则以后我将无法传递额外的parameters

我的主要目标是让这个功能在我可以选择默认值或根据需要传递任何parameters 的其他页面上重复使用。

如何使其具有默认的parameters 并根据任何页面的要求覆盖这些?

【问题讨论】:

  • 当你初始化一个团队时,它不会影响团队的填充。也许您应该扩展它以便可以将成员添加到团队中?我不确定你是否遗漏了一些东西,但必须有更多与对象相关的属性。
  • 这是新设置,所以一旦我知道如何传递 param1 和 param2,我将编写额外的代码。目前我有单独的函数,比如 function team(param1, param2) { } 我直接调用它们,比如 team("player_id", "coach_id"); ...
  • @nonuce init: handleTeam 有什么问题?
  • 不确定您的意思?
  • 你可以传递一个对象(例如{param1: 'someValue', param2: 'otherValue'})。

标签: javascript jquery closures parameter-passing iife


【解决方案1】:

我认为上面的方法叫做闭包?对吗?

是的,来自 OP sn-p 的模式是 "Closure",也是 "Immediately Invoked Function Expression (aka "IIFE")


当您询问最佳实践时,我做了一些细微的更改来回答这个问题。因此,我实现了什么并不重要,但更重要的是我是如何做到的(参见内联 cmets)。

如果我猜对了,你想实现这样的目标(为了说明目的,还在函数体中添加了一些东西):

var myTeam = (function( _sDefault, _oDefault ) { // my_team vs. myTeam? Naming convention for JS is CamelCase!

    // underscore prepended or appended to variable names is common use to show that a variable has private access
    var _handleTeam = function( sDefault, oDefault ) {
        console.log( sDefault );
        console.log( oDefault );
        // "cannot call"/"don't has access" to init() nor updatePlayer() 
    }
    return { // deploy public methods
        init: function( sDefault, oDefault ) {
            if ( !sDefault ) sDefault = _sDefault; // devs write: sDefault = _sDefault || sDefault;
            if ( !oDefault ) oDefault = _oDefault;
            _handleTeam( sDefault, oDefault );
        },
        updatePlayer: function() {
            console.log('updatePlayer');
        }
    };

})( 'default', {default: true} ); // pass values on IIFE

myTeam.init(); // initiate with default values
myTeam.init( 'custom', {default: false, custom: true} ); // initiate with custom values
myTeam.init(); // initiate again with default values
myTeam.updatePlayer();

如果符合您的需要,采用上述设计模式是完全可以的。但我可以在这里看到至少 2 个警告。

  1. 私有方法无法访问由返回值部署的公共方法。
  2. 有点难以阅读,因此更难维护。

所以这里有一种模式,我更喜欢上面的模式 |还有闭包和IIFE

var myTeam = (function( _sDefault, _oDefault ) {

    // make sure that _oDefault can not be modified from outer scope
    _oDefault = $.extend({}, _oDefault); // *

    // declare variables with private access
    var _oThis = this, // most devs write "that" instead of "_oThis" like I do, you can see "self" also quite often
        _oBackup = {sDefault: _sDefault, oDefault: $.extend({}, _oDefault)}; // *
    var _handleTeam = function( sDefault, oDefault ) {
        // public methods are now also availabe to private ones
        _oThis.log( sDefault );
        _oThis.log( oDefault );
        return _oThis.updatePlayer();
    }

    // declare properties with public access
    this.setDefaults = function( sDefault, oDefault ) {
        if ( typeof sDefault === 'string' )
            _sDefault = sDefault;
        if ( typeof sDefault === 'boolean' )
            _sDefault = _oBackup.sDefault;
        if ( typeof oDefault === 'object' )
            _oDefault = $.extend({}, oDefault); // *
        if ( typeof oDefault === 'boolean' )
            _oDefault = $.extend({}, _oBackup.oDefault); // *
        return this; // make public methods chainable
    }

    this.updatePlayer = function() {
        return this.log('updatePlayer'); // make public methods chainable
    }

    this.log = function( sLog ) {
        console.log(sLog);
        return this; // make public methods chainable
    }

    this.init = function( sDefault, oDefault ) {
        _handleTeam(
            sDefault || _sDefault,
            oDefault || _oDefault
        );
        return this; // make public methods chainable
    }

    return this; // deploy everything that has public access

})( 'default', {default: true} ); // set default parameters on IIFE

// our public methods are chainable now
myTeam.init().log('initiated with default values')
      .init( 'custom', {default: false, custom: true} ).log('initiated with custom values')
      .setDefaults( false, false ).log('reseted to default values')
      .init().log('initiated reseted default values')
      .setDefaults( 'new default', {default: true, newDefault: true} ).log('set new default values')
      .init().log('initiated with new default values');

// *: if you don't know why I'm using  jQuery.extend for objects, feel free to leave a comment and I can explain...
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"&gt;&lt;/script&gt;

还有一个问题?

init: function(param1, param2) { handleTeam(param1, param2); }

这似乎不是最好的方法,因为我以后将无法传递其他参数,除非我更改上面的代码以接受上面列表中的更多参数。

您可以传递任意数量的参数/参数,而无需事先声明它们(改用arguments):

init: function() {
   console.log(arguments);
   handleTeam(arguments[0], arguments[1], arguments[2]);
   // or you can do it like this as well:
   handleTeam.apply(this, arguments); // 
}
myTeam.init( 'yep', 'don't worry', 'works' )


当我一遍又一遍地阅读您的问题时,我想下面的模型应该是您的方向(或者至少应该能够说明事情如何协同工作)。工作伪代码 | 关闭但没有 IIFE

(function( $ ) { // sure this an IIFE again but thats not essentially to the question at this point

  var Team = function() {
    // private
    var _oThis = this,
        _oTeam = {},
        _privateHelper = function() {
          // this function can not be triggered directly from outer scope
          console.log('_privateHelper was called');
          return _oThis; // use _oThis instead of this here!!!
        },
        _get = function( sId, sIdSub ) {
          return _oTeam[sId] && _oTeam[sId][sIdSub] ? _oTeam[sId][sIdSub] : false;
        },
        _set = function( sId, sIdSub, val ) {
          _oTeam[sId][sIdSub] = val;
          return _privateHelper(); 
        };

    // public
    this.register = function() {
      for( var i = 0, iLen = arguments.length, sId; i < iLen; ++i ) {
        sId = arguments[i];
        _oTeam[ sId ] = {
          $: $('#' + sId), // #1 cache jQuery collection
          aPerson: [], // #2 cache names of each person
          sSelectedPerson: false // #3 cache name of selected person
        };
        _oTeam[ sId ].$.find('option').each(function( iEach ){
          _oTeam[ sId ].aPerson[ iEach ] = $(this).val(); // #2
        });
        this.updateSelectedPerson( sId ); // #3
      }
      return this; // for chaining | BTW: this === _oThis
    }

    this.updateSelectedPerson = function( sId ) {
      if ( _oTeam[ sId ] ) {
        _set(sId, 'sSelectedPerson', _oTeam[ sId ].$.val());
      }
      return this;
    }

    this.getSelectedPerson = function( sId ) {
      return _get(sId, 'sSelectedPerson');
    }

    this.getPersons = function( sId ) {
      return _get(sId, 'aPerson');
    }

    this.update = function( sId ) {
      if ( _oTeam[ sId ] ) {
        console.log(
          'old selected: ' + this.getSelectedPerson( sId ),
          'new selected: ' + this.updateSelectedPerson( sId ).getSelectedPerson( sId )
        );
      }
      return this;
    }

    arguments.length && this.register.apply( this, arguments );
    return this; // deploy public properties
  };

  $(function(){ // document ready

    var oTeam = new Team( 'coach', 'player' ); // would be the same as ...
    // var oTeam = new Team().register( 'coach', 'player' );
    console.log(oTeam.getPersons('coach'));
    console.log(oTeam.getPersons('player'));
    $('select').on('change.team', function(){
      oTeam.update( this.id )
    })

  });

})( jQuery ) // pass jQuery on IIFE for making save use of "$"
<h1 style="font-size:1em;display:inline">select coach and player: </h1>

<select name="players" id="player">
    <option>player Mark</option>
    <option>player Tom</option>
</select>
<select name="coaches" id="coach">
    <option>coach Mark</option>
    <option selected>coach Tom</option>
</select>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


注意有type ofobject的参数

var oO = {prop: 'save???'},
    oA = [true],
    s  = 'save!',
    i  = 0,
    b  = true,
    fn = function( oO, oA, s, i, b ) {
      // every argument will get a new value
      // lets have a look if this effects the original variable that was passed
      oO.prop = 'nope!';
      oA[0]   = 'oh oh!';
      s       = 'yep save';
      i       = 999;
      b       = false;
    };

fn(oO, oA, s, i, b);

                        // initial   -> inner scope -> outer scope
console.log( oO.prop ); // 'save???' -> 'nope!'     -> 'nope!'
console.log( oA[0]   ); // true      -> 'oh oh!'    -> 'oh oh'
console.log( s       ); // 'save!'   -> 'yep save'  -> 'save!'
console.log( i       ); // 0         -> 999         -> 0
console.log( b       ); // true      -> false       -> true


这是迄今为止我找到的关于 WHY 的最佳解释(简短、准确、易于理解,致谢:@newacct):

“对象”不是 JavaScript 中的值,不能“通过”。
您处理的所有值都是引用(指向对象的指针)。
传递或分配一个引用会给出另一个指向同一个对象的引用。当然,您可以通过其他引用修改同一个对象。

https://stackoverflow.com/a/16893072/3931192


因此,如果您现在仔细查看上面的模型 - 这正是我将 jQuery utility method extend 用于与外部范围有某种关联的对象的原因(当作为参数传递时就是这种情况 - 对吧?)。

记住它——从现在开始永远不要忘记它!如果您是新手,这可以为您节省数小时的头痛!

那么如何规避这种行为:

var oO = {prop: 'make it save now'},
    oA = [true],
    fn = function( oO, oA ) {
      var o = jQuery.extend({}, oO, oA);
      console.log('log#1', o);
      o[0] = 'how save is this?';
      o.prop = 'so save now :)';
      console.log('log#2', o);
    };

fn( oO, oA );

console.log('log#3', oO, oA);
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"&gt;&lt;/script&gt;

注意: 下划线或 lodash 等其他库也提供了实现这一点的函数。

【讨论】:

  • 这是一个很好的答案,我喜欢你的代码 cmets 和解释。感谢您花时间分享您的知识和想法。我一定能够在此基础上再接再厉。干杯!
  • 感谢@NoNice 激励你 :)
  • @NoNice BTW:我在对象上使用jQuery.extend的原因很清楚吗?
  • 是的,我查看了 Jquery 的创建插件指南,我确信我理解了这个概念。 learn.jquery.com/plugins/basic-plugin-creation
  • @NoNice 资源确实非常非常好,但没有理解我的意思。我正在谈论将对象作为参数传递。 JS 中有一个非常具体的行为。请查看我编辑的答案的底部。编辑开始于:“注意具有type ofobject 的参数”。我记得当我开始编写插件时,这实际上花了我几个小时。希望我能让你免于头痛。如果它值得你阅读,现在让我...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多