3
\$\begingroup\$

I have 2 questions about functions inside object literals. I have included the workarounds I've been using, but they seem hackish. Is there a better way to do this?

ns = {
  a: function (x, y) { return x+y; },

  // Problem 1

  // This does not work: Error: ns.b is not a function
  b: this.a,

  // Is this the correct workaround for this?
  c: function (x, y) { return this.a(x, y); },

  // Problem 2

  // I understand why this won't work, because a is not defined in d's scope
  d: function (x, y) {
    function add(n, o) {
      return this.a(n, o);
    }
    return add(3, 4);
  },

  // Is this the correct workaround for this?  It works in Firefox and Chrome.
  e: function (x, y) {
    function add(n, o) {
      return ns.a(n, o);
    }
    return add(3, 4);
  }
};

ns.a(3, 4); //===> 7

ns.b(3, 4); //===> Error: ns.a is not a function
ns.c(3, 4); //===> 7

ns.d(3, 4); //===> Error: no method named a
ns.e(3, 4); //===> 7

EDIT: Thank you for all your replies. As missingno pointed out I am using ns to define a module, though I avoided the module pattern because I don't have any private variables. The following seems to be an elegant solution to my problems.

var ns = {};
ns.a = function (x, y) { return x+y; };
ns.b = ns.a;
ns.c = function (x, y) {
  function add(n, o) {
    return ns.a(3, 4);
  }
  return add(x, y);
}

ns.a(3, 4); //===> 7
ns.b(3, 4); //===> 7
ns.c(3, 4); //===> 7

Since this has been moved to Code Review, I'd welcome any critiques of my implementation of Scheme's SRFI-1.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ Why was this moved to code review? It's a technical question about javascript scope and context with an extremely contrived example. \$\endgroup\$
    – Alex Wayne
    Dec 12, 2011 at 1:29
  • \$\begingroup\$ Indeed; this is an SO question through-and-through. It's not "can you improve my program"; it's "what programming technique am I supposed to use?" \$\endgroup\$ Dec 12, 2011 at 7:52

4 Answers 4

4
\$\begingroup\$

Problem 1

Just assign them one at a time, once the object already exists:

ns = {};
ns.a = function (x, y) { return x+y; };
ns.b = ns.a;

To answer specifically whether there's any other workaround if you're dead set on declaring these inside the object literal syntax: no.

Problem 2

Looks good enough to me, though I'd try to avoid using the object's name so deep into its definition.

Consider using prototyping instead.

\$\endgroup\$
3
\$\begingroup\$

Problem 1 When creating an object literal, this scoped by whatever function is constructing the object. This allows you to do:

this.foo = 'bar';
var obj = { foo: this.foo };
console.log(obj.foo); // 'bar'

In fact the object doesn't exist until the end of the object literal, so there is no object to reference.

The correct work around for you issue would be like so. Because you cannot use the same value for multiple keys in an object literal.

ns = { a: function() {} };
ns.c = ns.a; // the same function now is on .a and .c

Problem 2

This has nothing to do with scope, but instead context (the value of this). In function invocations, the dot syntax is what provides the this. For example:

obj.someFn() // inside someFn: this === obj

It's the syntax that provides this. So when there is no dot accessor, this defaults to the global object (window in a browser).

The usual work around is to simply save a reference to the current object which is shared with any functions in the same scope.

d: function(x, y) {
  var self = this;
  var add = function(x, y) {
    self.a(x, y);
  };
  add(3, 4);
}

Or use call or apply to explicitly set the function context, which overrides the default binding of this with whatever object you want.

d: function(x, y) {
  var add = function(x, y) {
    this.a(x, y);
  };
  add.call(this, 3, 4);
}
\$\endgroup\$
1
  • \$\begingroup\$ Really cool answer Alex. That's clear enough for me. Thanks \$\endgroup\$
    – thangchung
    Feb 21, 2014 at 3:54
2
\$\begingroup\$

1) The normal workaround for duplicating functions is just using a variable

var f = function(){};
var ns = {
   a: f,
   b: f
},

2) and the normal workaround for nested this is using an intermediate variable

var that = this;
var f = function(){
    that.a();
}

But... your ns variable name makes it look like you are using your object literal to define a module. If that is the case, consider instead using a more general module pattern so you are less constrained in the things you can do.

//this is just an example. There are many variations on this theme...
var ns = (function(){

    var M = {};

    var a_private_f = function(){ /*...*/ };

    M.a = function(){
        a_private_f();
    }

    M.b = function(){
        M.a();
    }

    return M;

}());

Avoiding the use of this to reference other functions in your namespace also allows your functions to be passed around as callbacks, in situations where the this will not point to the namespace object.

\$\endgroup\$
0
\$\begingroup\$
// I understand why this won't work, because a is not defined in d's scope

Incorrect. As you wrote it, d's scope is irrelevant in the add function because you've called it without a scope object, i.e. add(3, 4). If you want the scope in add to be the same as the scope in d, you should use add.call(this, 3, 4).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.