JavaScript, 摆脱 'this',使用本地作用域

3
在一个典型的JS类中,所有对成员函数的调用都必须在前面加上this关键字。我看了一种技术,可以让我创建一个相互依赖的静态函数库,并依赖于闭包/作用域,使事情变得更容易一些。
例如:
var Singleton={
  //main entry point
  // call with fn name, args...
  call:function(){
    var args=[];
    if (arguments.length==0) {
      return;
    }
    // get the fn name
    var fn=arguments[0];
    var x;
    // make args array
    for (x=1;x<arguments.length;x++) {
      args[args.length]=arguments[x];
    }
    // I want to get rid of this part
    // See below for what I wish
    // Here I have access to fns below due to hoisting in js
    // so I put them in a map...
    var fns={
      test:test
      // etc, more like this I do not want to type/maintain
    }
    // ... all so I can do this
    // get my function.
    var fun=fns[fn];
    // instead of that, I would like to "override whitespace" and
    // say something like:
    // var fun=['fn_name'];
    // so I can index into local scope and get a fn
    //
    // log error if not found
    if (typeof fun=='undefined') {
      loge('Singleton: function not found:'+fn);
      return;
    }
    // ok, run the function
    return fun.apply(window,args);
    // the test fn accesses test2() without dot notation
    function test(a){
      // Note: here in test fn it can access test2() 
      // without using this.test2() syntax
      // as you would in normal objects
      var s=test2();
      alert(s+' test:'+a);
    };
    function test2(){
      return 'test2';
    };
  }
}

我希望更熟悉JavaScript进展的人能就如何模拟“隐含但不必要的this”提供建议,因为this默认为window,这总让我感到奇怪,如果this可以指向一个附加了本地作用域的匿名对象,那不是很好吗。
我想说['localObject']以获取作用域中的内容。
编辑:
看到一些回答后,我将把它重新阐述为一个挑战:
我正在寻找一种语法技巧,一种方式来像@Varuna所说的那样,“1.访问静态方法而不使用此变量,即它们将保持彼此全局。2.不想维护本地数组以实现静态方法,并希望在本地范围内实现。”
换句话说,我需要声明的函数自行注册,但我不想多次声明函数名。我想@Varuna可能有一种使用eval访问本地作用域的解决方案。
以下方法不起作用:
var o={};
o['fn']=function fn(){};
o['fn2']=function fn2(){};

因为你需要两次声明函数名称,但是闭包得以保留。

而这个:

var a=[
  function fn(){}
  ,function fn2(){}
];
Register(a);

不起作用,因为据我所知,你会失去闭包,即fn2无法看到fn。这也使得以下声明式样式成为“this噩梦”:

window.MINE={
  fn:function fn(){
    //this?
    // want to say fn2(), not this.fn2(), nor MINE.fn2()
  }
  ,fn2:function fn2(){
    //this?
  }
  ,deeper:{
    //more
  }
};

但是如果你创建了一个奇怪的属性,在分配时完成注册,像这样的东西可能会起作用:
var registar=new Registar();
registar.reg=function fn(){};
registar.reg=function fn2(){};
//then access
var fn=registar.getFn(n);
// or
var fn=registar._[n];

以上内容依赖于JS属性和可以访问fn.name,但据我所知并非在所有情况下都可用。

2
var localObject=this; - mplungjan
你可以在IIFE中创建所有的访问器,然后在本地作用域中引用它们,尽管你需要显式地将接口附加到公共对象。 - Jimmy Breck-McKye
嗯,你根本没有调用任何成员函数,那么你对this有什么问题? - Bergi
我已经进行了编辑并详细说明了。@JimmyBreck-McKye - Mark Robbins
但是你仍然应该维护一个映射表,以区分私有和公共的静态函数。很抱歉,没有其他解决方案(假设您不想使用黑色的“eval”魔法)。 - Bergi
显示剩余2条评论
5个回答

1

这里是一个“直接回答”,因为我真的不喜欢在这里看到的内容。我不明白为什么你需要这种构造,因为它已经作为语言核心的一部分存在。

1. 动态查找 你正在以一种“前所未有”的方式进行动态查找, 哈希表已经为你完成了这个功能,并且哈希查找非常快速。 如果你正在使用eval()来执行随机字符串以进行简单的名称查找, 那么你真的需要暂时离开键盘...(请勿介意)

2. 闭包 你提到“使用闭包”,但实际上你并没有使用。 你的call函数每次调用时都会重新声明测试函数, 并在自己的变量作用域表中查找(“新鲜版本”)函数, 而不是在父作用域链(也称为闭包)之外的词法位置查找它们。

3. nfe vs. nfd命名函数表达式命名函数声明 ...你不能将一个函数赋值给局部变量并保留闭包。 这是一个特性,你可能不知道它的工作原理(我也被绊倒了)。 查看this article以获得澄清。

4. 异常 Singleton: 找不到函数名... x4! 只需调用函数, 解释器无论如何都会为您抛出异常,如果它找不到/执行函数

5. eval (又名^^) Singleton.call.ctx.fun = eval(Singleton.call.ctx.fn); eval在这里接受任何字符串(#!),并愉快地执行像:'for(;;);''while(1);'这样永远运行的代码。 您可能不想有任何代码运行,除非它是您的东西。

6. 参数处理 使用单个 (Object) options 参数被视为最佳实践,以“微调”任何重要的捆绑功能,而不是尝试通过类型检查提供的参数列表来解决问题。

以下是我和 @Jimmy Breck-McKye 建议您执行的简单步骤:

var Singleton.call = (function () {


  var funcmap = {
    'f_1': function () {}, 
    // etc.
    'f_N': function () {},
  };


  return function (options) {

    // options members:
    //   context, (Object) context, (defaults to global if none is given)
    //   func,    (String) function_name, 
    //   args,    (Array)  arguments to pass into a function


    // this line does everything your 100+ lines long snippet was trying to:
    // look's up parent scope for a function, tries to run it 
    // passing provided data, throws if it gets stuck.
    return funcmap[options.func].apply(options.context, options.args);
  };

})();
//

两点- 1)这主要是一种过渡性的机制,一种重构机制。使用案例是我在全局范围内编写了一组相互依赖的函数,并希望松散地打包它们,而不必将this指针注入到它们的函数体中。2)在您的代码中,您在映射中拥有的函数彼此看不见,因此我必须在它们的函数体中使用funcmap代替this指针。换句话说,据我所知,这是将一堆函数从全局范围中拖出并使它们工作而无需将它们连接起来的唯一方法。 - Mark Robbins
如果你在全局范围内拥有它们,那么它们自动可用于任何作用域(除非由相同的本地变量名称“掩盖”)。你可以通过索引全局对象来动态地引用它们。 - public override
@nicolav 正确。但我不再希望它们在全局范围内存在。这旨在允许相关函数进行聚类,并允许我在开发过程中发现哪些函数需要公开访问。 - Mark Robbins

1
如果我理解正确,您想创建具有以下特点的对象:
  • 拥有静态成员
  • ...可以在不使用this符号的情况下访问
最简单的解决方案(假设我正确理解了您的问题)是使用闭包来存储您的静态字段,通过名称直接访问它们,然后显式地将它们添加为对象成员。
请考虑:
var myConstructor = (function(){

    var foo = 'someStaticField';
    var bar = function(){
        alert('A static method returns ' + foo);
    };

    return function(){
        return {
            foo : foo,
            bar : bar
        };
    };

})();
var myInstance = new myConstructor();

1

你可以在不同的闭包中声明可以相互访问的函数,并将它们通过绑定你的call方法到一个包含这些函数的对象中,导出给主程序。类似于之前的帖子(稍作修改):

var Singleton = {

  call: (function() {

    // here 'call' is bound to object containig your test functions
    // this: {test, test2}

    if (0 == arguments.length) return;

    // log error if not found
    if ('function' != typeof this[arguments[0]]) {
      console.warn('Singleton: function not found:' + arguments[0]);
      return;
    }

    // '...index into local scope and get function 
    // ie. get the function by it's name
    return this[arguments[0]].
      apply(window, Array.prototype.slice.call(arguments, 1));




    // --- or:


    // you can explicitly introduce function names to current scope, 
    // by `eval`-ing them here (not very much preferred way in JavaScript world):

    for (var fname in this) 
      if (this.hasOwnProperty(fname)) 
        eval('var ' + fname + ' = ' + this[fname]);


    // and you can reference them directly by using their names

    var fn = eval(arguments[0]);        
    return fn.apply(window, Array.prototype.slice.call(arguments, 1));



  }).bind(

    (function() {


      var _exports = {};


      function test (a) {
        var s = test2();
        alert(s + ' test: ' + a);
      }

      function test2 () {
        return 'test2';
      }



      _exports['test']  = test;
      _exports['test2'] = test2;


      return _exports;


    })()

  )};

  Singleton.call('test', 'foo and stuff');
  //

前一篇文章:

您在谈论Function#bind功能,它使函数的上下文得以“定制化”。将您的call方法绑定到所需的“本地上下文”上,如下所示:

var Singleton = {

  //main entry point
  // call with fn name, args...
  call: (function() {

    // here `this` (context) is object bound to `call` method
    // not `global` object, which is default for 'unbound' functions

    var locals = this; // {fns, shift, loge, isfunc}

    var fn;
    var fun;
    var x;

    if (arguments.length == 0)
      return;

    // get the fn name
    fn = locals.shift(arguments);

    // '...index into local scope and get a fn'
    fun = locals.fns[fn];

    // log error if not found
    if (!locals.isfunc(fun)) {
      locals.loge('Singleton: function not found:' + fn);
      return;
    }
    // ok, run the function
    return fun.apply(window, arguments);

  // lock `call`'s context to provided object
  // and use `this` to reference it inside `call`
  }).bind({

    fns: (function(_) {

      // and you can '...create a library of inter-dependent STATIC functions' 
      // in this closure and invoke them in `call` method above

      _.test = function (a) {
        var s = _.test2();
        alert(s + ' test: ' + a);
      };

      _.test2 = function() {
        return 'test2';
      };

      return _;
    })({}),

    // and create couple of helper methods as well...

    isfunc: (function(_getclass) {

      _getclass.func = _getclass(_getclass);

      return ('function' !== typeof(/foo/)) ?
        function(node) {
          return 'function' == typeof node;
      } :
        function(node) {
          return _getclass.func === _getclass(node);
      };
    })(Function.prototype.call.bind(Object.prototype.toString)),

    loge: console.warn,

    shift: Function.prototype.call.bind(Array.prototype.shift)

  }),

};

Singleton.call('test', 'foo and stuff');


// eof

看起来很有趣,我需要进一步研究才能弄清楚它的实际作用。我不喜欢的是fn.name没有被保留,而且你必须在函数内部使用_.fn——这正是我一开始想要避免的。除了获取函数名称之外,我不需要this。请参见我的编辑以获得澄清。 - Mark Robbins

1
根据我的理解,您想要: 1. 访问静态方法而不使用此变量,即它们将保持全局相互关联。 2. 不想为静态方法维护本地数组,并希望在本地范围内实现。
您可以使用 eval 检查方法是否存在。请参见这里
唯一的缺点是这将使用 eval 方法。 代码如下:
var Singleton = {
        //main entry point
        // call with fn name, args...
        call: function () {
            var args = [];
            if (arguments.length == 0) {
                return;
            }
            // get the fn name
            var fn = arguments[0];
            var x;
            // make args array
            for (x = 1; x < arguments.length; x++) {
                args[args.length] = arguments[x];
            }

            //check whether function exist in local scope and not in global scope
            if (typeof eval(fn) !== 'undefined' && typeof window[fn] === 'undefined') {
                // ok, run the function
                return eval(fn).apply(window, args);
            }
            else{
                // log error if not found                
                loge('Singleton: function not found:' + fn);
                return;
            }

            // the test fn accesses test2() without dot notation
            function test(a) {
                // Note: here in test fn it can access test2() 
                // without using this.test2() syntax
                // as you would in normal objects
                var s = test2();
                alert(s + ' test:' + a);
            };
            function test2() {
                return 'test2';
            };
        }
    }

我喜欢这个,似乎在紧急情况下eval总是可以成为任何问题的答案,但在这种情况下,我没有想到它,因为我试图假装eval不存在,因为它有缺点,但在这个应用中,它是一个相当干净的解决方案。 eval(undefined)==42 ;) - Mark Robbins

0

在这里回答自己的问题。

问题的核心是,您不能将函数分配给本地变量并使其保留闭包。

考虑到当编写具有全局和窗口范围的函数时,使用this不必要来调用具有相同作用域的另一个函数。但成员函数则不是这种情况。

另一种说法是,没有空间可以让您的光标停留,并且随着您声明函数,它会自动附加到当前的this

function fn(){}// if we are in global scope, then window.fn becomes defined
// but if we are inside, say, a constructor, simple declaration will not attach
// it to this, but fn is available in scope.

任何函数声明的赋值都会破坏预期的闭包:

var IdentifierAvailableToClosure=function Unavailable(){}

但在声明之后进行赋值是有效的:

function NowAvailable(){}
var SynonymAvailableToo=NowAvailable;

这就是我不想重复两次使用名称来使机制工作的意思。

这个事实让我放弃了其他方法,而选择依赖推荐的 eval。这是一个初稿:

// This object is an encapsulation mechanism for a group of
// inter-dependent, static-ish, functions that can call each other
// without a this pointer prefix.
// Calls take the form of:
// Singleton.call(functionName:String [,arg1]...)
// or
// Singleton.call(contextObject:Object, functionName:String [,arg1]...)
// If a context is not provided, window is used.
//
// This type of mechanism is useful when you have defined a group
// of functions in the window/global scope and they are not ready
// to be formalized into a set of classes, or you have no intention
// of doing that
//
// To illustrate the issue, consider that a function
// which is defined in window/global scope
// does not have to use the this pointer to call a function of
// identical scope -- yet in a class member function, the this pointer
// MUST be used
// Therefore, trying to package such functions requires injecting
// the this pointer into function bodies where calls to associater
// functions are made
//
// Usage is primarily for development where one has control over
// global namespace pollution and the mechanism is useful in
// refactoring prior to formalization of methods into classes
var Singleton={
  // Main call point
  call:function(){
    // Bail with error if no args
    if (arguments.length==0) {
      throw('Singleton: need at least 1 arg');
    }
    // As all functions in the local scope library below
    // have access to the local scope via closure, we want to reduce
    // pollution here, so lets attach locals to this call
    // function instead of declaring locals
    //
    // Prepare to call anon fn
    Singleton.call.args=arguments;
    // Make ctx have args, context object, and function name
    Singleton.call.ctx=(function (){// return args,ctx,name
      // out
      var args=[];
      //locals
      var x, fn;
      // collapse identifier
      var a=Singleton.call.args;
      // closure object avail to functions, default to window
      that=window;
      // first real function argument
      var arg_start=1;
      // first arg must be function name or object
      if (typeof a[0]=='string') {// use window ctx
        fn=a[0];
      // if first arg is object, second is name
      }else if (typeof a[0]=='object') {
        // assign given context
        that=a[0];
        // check second arg for string, function name
        if (typeof a[1]!='string') {
          var err='Singleton: second argument needs to be a fn name'
                 +' when first arg is a context object';
          throw(err)
          return;
        }
        // ok, have a name
        fn=a[1];
        // args follow
        arg_start=2;
      }else{
        // improper arg types
        var err='Singleton: first argument needs to be a string or object';
        throw(err)
      }
      // build args array for function
      for (x=arg_start;x<a.length;x++) {
        args[args.length]=a[x];
      }
      // return context
      return {
        args: args
        ,that:that
        ,fn:fn
      };
    })();
    // using function library present in local scope, try to find specified function
    try{
      Singleton.call.ctx.fun=eval(Singleton.call.ctx.fn);
    }catch (e){
      console.error('Singleton: function name not found:' + Singleton.call.ctx.fn);
      throw('Singleton: function name not found:' + Singleton.call.ctx.fn);
    }
    // it must be a function
    if (typeof Singleton.call.ctx.fun !== 'function') {
      console.error('Singleton: function name not found:' + Singleton.call.ctx.fn);
      throw('Singleton: function name not found:' + Singleton.call.ctx.fn);
    }
    // library functions use that instead of this
    // that is visible to them due to closure
    var that=Singleton.call.ctx.that;
    // Do the call!
    return Singleton.call.ctx.fun.apply(that, Singleton.call.ctx.args);
    //
    // cool library of functions below,
    // functions see each other through closure and not through this.fn
    function test(s){
      alert(test2()+' test:'+s);
    }
    function info_props(){
      console.info(this_props());
    }
    function test2(){
      return 'test2';
    }
    function this_props(){
      var s='';
      for (var i in that) {
        s+=' '+i;
      }
      return s;
    };
  }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接