0

Im a beginner and I often need to see a variable's value to make sure I am picking the right method / property /selector for jQuery or just to see what the value is or debugging in general. So what I usually do is

console.log("phone_number: " + phone_number);

I want to create my own 'console.log' function where I simply put the variable(or many variables) as parameters and neatly print out the results.

mylog(phone_number, name, address);

would output to console:

phone_number: 911
name: stanek
address: undefined

Its not a huge deal typing out the first way but I feel like there must be a better way of doing it... and I want to save time.

The answers to previous related questions usually involve remarks such as 'you already know the name' or 'what good reason do you have for this'. I feel this scenario shows a specific need for this request.

I have a feeling that this isn't built into Javascript or it would have been in an answer somewhere.

My curious question is: Why isn't this a feature of Javascript?

My determined question: Is it possible for me to add this feature to Javascript? I'm willing to spend countless hours to save 20 seconds.

stanek
  • 582
  • 5
  • 15
  • 6
    What you're asking makes sense in a way, but not in terms of the fundamentals of how the language works. Parameters in a function call are *expressions*, and each is evaluated before the function is actually called. What's passed to the function is the *value* of each expression. Once the value of a variable has been extracted during expression evaluation, the value has nothing to do with the variable anymore; it's just a value, and it could have come from anywhere. – Pointy Aug 31 '16 at 23:19
  • Yep, the thing passed to a function is an expression/value, not a variable (which has a name and value) :) (EDIT: mind you such a "first class variable" does not exist in JavaScript.) – Nebula Aug 31 '16 at 23:23
  • @Pointy That's not correct. You can do this using static analysis Please remove your comment. – Linus Oleander Aug 31 '16 at 23:48
  • @Oleander the OP asks, "Is it possible to add this feature to JavaScript?", and the answer is "no". Writing a preprocessor (which is what things like TypeScript, Clojurescript, etc. are, after all) lets you invent any language feature you want, but what you end up with is not JavaScript. – Pointy Aug 31 '16 at 23:59
  • @Pointy I didn't say anything about changing the code. You can extract the AST during runtime to determine what should be printed. – Linus Oleander Sep 01 '16 at 00:07
  • 2
    What would you expect your function to do if I called it with something like `mylog(123)` or `mylog(firstname + ' ' + surname)` or `mylog(anotherFunction(123))`? *"Why isn't this a feature of Javascript?"* - It's not a feature of a whole lot of languages. – nnnnnn Sep 01 '16 at 02:21
  • @Oleander no, you can't "extract the AST during runtime". – Pointy Sep 01 '16 at 03:19
  • @Pointy Yes you can. Read the file currently being evaluated. Then extract the AST using something like Babel. Babel for example have the `babel-register` package which does this on-the-fly for imports. Note that Javascript is turing complete so the limitation isn't the language, it's your imagination. – Linus Oleander Sep 01 '16 at 09:39
  • @Oleander that's not what "during runtime" means; you're talking about a pre-processing step. There's nothing at all wrong with that, but it's an automated way of creating JavaScript code that does not do what the OP is asking in a direct way. It automates a work-around similar to what Mr. Kling describes in his answer. – Pointy Sep 01 '16 at 12:07
  • @Oleander and the fact that the language is Turing-complete really has nothing to do with it. Parameter expressions are evaluated before a function is invoked, and that's that. To make parameter information available to the called function requires explicit code to do it, and that's precisely what the OP wanted to avoid. The process of creating that explicit code can be automated, just as almost anything can be automated, but the fact remains that in native JavaScript functions receive no information about the nature of the expressions from the calling environment other than the values. – Pointy Sep 01 '16 at 12:09
  • @Pointy Your point about TC seems to be correct, thanks for the clarification. The limitation is rather the OS bindings provided by the language, ie. the ability to read (`fs.readFile`) and figure out what file you're currently executing (`__filename`). Could you elaborate on why [babel-register](https://babeljs.io/docs/usage/require/) and [cs-register](http://coffeescript.org/documentation/docs/register.html) isn't applicable here? – Linus Oleander Sep 01 '16 at 13:13
  • @Oleander I'm not saying those things are not *applicable*, it's just that it's not the direct answer to the question. The OP describes him/herself as "a beginner" in the very first sentence. Yes, it's possible to use some additional tool to get the results desired, but I don't think we're doing any favors for a beginner by making such a recommendation. – Pointy Sep 01 '16 at 13:28
  • @Pointy That's totally correct - if the audience was only the poster. I just posted a comment about it here: http://stackoverflow.com/questions/39260444/why-cant-i-get-the-variable-name-as-a-string-i-want-to-create-my-own-console?noredirect=1#comment65880236_39260469 – Linus Oleander Sep 01 '16 at 13:32
  • @Pointy In your defense; my solution would be bound to a specific platform (i.e node.js), not Javascript as a language. So you (yes, you) could argue that it's not applicable either, but at least it's something :) – Linus Oleander Sep 01 '16 at 13:42

3 Answers3

8

It's not possible to do this without additional processing of the source code. At runtime, the function gets passed a value, it cannot know where the value came from.

You can make it accept an object instead and then it can be called with:

mylog({phone_number, name, address});

This uses short object notation introduced in ES2015 which is equivalent to

mylog({phone_number: phone_number, name: name, address: address});

(short notation won't work in older browsers)

This gives you access the value and the name of the variable (as object property).


Is it possible for me to add this feature to Javascript?

Unless you want to write your own JavaScript engine, no.

However you can use a source code transformer such as Babel to instrument your code before executing it. Here is an example of how such as Babel plugin could look like:

export default function ({types: t}) {
  return {
    visitor: {
      CallExpression(path) {
        if (path.node.callee.name !== 'mylog') {
          return;
        }
        path.node.arguments = [
          t.objectExpression(
            path.node.arguments.map(arg => t.objectProperty(
              t.StringLiteral(arg.name),
              path.scope.hasReference(arg.name) ?
                arg :
                t.identifier('undefined')
            ))
          ),
        ];
      }
    }
  };
}

This converts

var phone_number = 42;
var name = 'foo';

mylog(phone_number, name, address);

into

var phone_number = 42;
var name = 'foo';

mylog({
  'phone_number': phone_number,
  'name': name,
  'address': undefined
});

Live demo: http://astexplorer.net/#/0Ph74qUjvL

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Indeed, that is the only way I can think of that can do it. Might be worth mentioning this is ES6 syntax, just in case somebody tries to do use this in a browser that doesn't support it. – VLAZ Aug 31 '16 at 23:21
  • 1
    I was just typing up the same thing, definitely the only straightforward way to achieve this goal – Rob M. Aug 31 '16 at 23:21
  • This can be done without deconstructing the argument. Angular does this for example -1. – Linus Oleander Aug 31 '16 at 23:25
  • 1
    @Oleander: I'm not deconstructing anything but OK. How does angular do it? I believe you might be referring to something else (angular inspects the *parameters* of a function *definition*) because it is simply not possible for a function to know what kind of expressions produced the parameter values. – Felix Kling Aug 31 '16 at 23:26
  • @FelixKling My bad. I was thinking about using the parameter names within the function, not as an external call. But I guess you could do this by extracting the AST from the invoked line. – Linus Oleander Aug 31 '16 at 23:29
  • 1
    @Oleander: Sure, you can do a lot of things with static analysis :) But not at runtime (unless you instrument the code beforehand). – Felix Kling Aug 31 '16 at 23:30
  • @FelixKling It shouldn't matter if you do this pre- or during runtime. Locate the line. Apply a parser like Babel to the file. Locate the line within the AST. – Linus Oleander Aug 31 '16 at 23:33
  • @Oleander: I assumed nobody wants to ship a whole parser with their app just to be able to log stuff, but yeah, depends on the context. – Felix Kling Aug 31 '16 at 23:34
  • @FelixKling If you look carefully at OP's example, you can see that `address` is `undefined` which tells you that maybe variable `address` is not declared (I suppose OP wants to mention it using his example). In that case, your approach will return an `ReferenceError`. –  Aug 31 '16 at 23:36
  • @FelixKling That wasn't part of the question, nor the answer. I use similar technics during development to better debug my code. It's automatically removed before deployment. – Linus Oleander Aug 31 '16 at 23:36
  • 1
    Wait, so any time the log will be called it will have to scan the entire file from where it was called, only to find the one line and then figure out what the calling arguments are called? That sounds like an overkill and a very good way to introduce amazingly hard to track down bugs because your log call might take that fraction of a second more than what you'd need. Heavy logging can also cause slowdowns as the it keeps re-getting and re-scanning files. – VLAZ Aug 31 '16 at 23:37
  • @SoftwareEngineer171 erm, calling the function with an undeclared variable will ALSO cause a `ReferenceError`, though. – VLAZ Aug 31 '16 at 23:38
  • @SoftwareEngineer171: Good catch. There is no way to handle that case though. – Felix Kling Aug 31 '16 at 23:39
  • @Vid 1. The overhead is minimal as traversing the AST is just a few lines of code. 2. It's only used in development. 3. It can easily be cached only having to read, parse and traverse the AST once per file. – Linus Oleander Aug 31 '16 at 23:42
  • @FelixKling Could you change the statement about this not being possible? I'm happy with the overall answer, except the first statement which tricks the reader into thinking it's not possible. – Linus Oleander Aug 31 '16 at 23:44
  • 1
    @Oleander: Clarified and added Babel example – Felix Kling Aug 31 '16 at 23:53
  • @FelixKling You could run babel on the code during runtime, you don't have to run it as a preprocessor to change the code. It's just about extracting relevant data from the AST, which can be done during runtime. – Linus Oleander Sep 01 '16 at 00:10
  • @Oleander: I understand, but doing that involves a bunch of other difficulties: getting the source of the file and finding the right callsite. There isn't a straightforward solution to that (or maybe I just don't know it (getting the callsite seems doable, but getting the source of the right file?)). – Felix Kling Sep 01 '16 at 00:20
  • @FelixKling That's a topic of it self and would require more insight into the problem OP and the reader (everyone else) is trying to solve. The most important thing, in my opinion, is not to define relative words like `straightforward` and `difficulties` on behalf of the person or group you're helping. I would argue that they only having meaning in the context they are used in. Without it (a context) we can only make educated guesses - which is important to highlight as beliefs otherwise can be confused with knowledge. The topic is called intellectual responsibility (if you're interested). – Linus Oleander Sep 01 '16 at 13:27
1

If you don't mind to pass strings instead of identifiers, you could use eval().
Update: I've renamed mylog to _ and now it returns an object, this way you can use console.log() directly. The original snippet is at the bottom.

var phone_number = 911 ;
var name = "stanek" ;

function _( ){
    function myeval( arg ){
        try{
            return eval( arg ) ;
        }
        catch( e ){
            if( e instanceof ReferenceError ) return undefined ;
            else throw e ;
        }
    }
    var obj = {} ;
    for( var i = 0 ; i < arguments.length ; ++i ){
        obj[arguments[i]] = myeval( arguments[i] ) ;
    }
    return obj ;
}

console.log( _( "name", "phone_number", "address" ) ) ;

original answer:

var phone_number = 911 ;
var thename = "stanek" ;

function mylog( ){
    function myeval( arg ){
        try{
            return eval( arg ) ;
        }
        catch( e ){
            if( e instanceof ReferenceError ) return undefined ;
            else throw e ;
        }
    }
    for( var i = 0 ; i < arguments.length ; ++i ){
        console.log( arguments[i] + ": " + myeval( arguments[i] ) ) ;
    }
}

mylog( "phone_number", "thename", "address" ) ;

You could also get the body of the function, as explained in this answer:

var mylogstr = mylog.toString( ) ; 
var mylogbody = mylogstr.slice( mylogstr.indexOf("{") + 1, mylogstr.lastIndexOf("}")) ;

And add mylog wherever you need with a single line of code:

var mylog = function( ){ eval( mylogbody ) ; } ;
Community
  • 1
  • 1
  • 1
    *"Keep in mind that name is reserved keyword in JavaScript"* No it's not. But it's a global readonly variable in browsers. – Felix Kling Aug 31 '16 at 23:56
  • 1
    The problem is that the `eval` call must have access to the variables, so can't be defined in the outer scope. You may have to redefine `mylog` everywhere. – Oriol Sep 01 '16 at 00:00
  • @sjdalessandro: Where exactly? https://es5.github.io/#C . I have used `var name` in strict mode successfully before. – Felix Kling Sep 01 '16 at 00:05
  • @Oriol I know is not a general solution to the problem, but may be good enough for the OP. Lets wait for his opinion –  Sep 01 '16 at 00:09
  • @sjdalessandro I like the reply of wrapping the variables in an object since I can use it along inside console.log. But this answer delivers something closer to what I was looking for. The first answer throws a JS error when the property is undefined so it is not very robust. I will play with your solutions this weekend when I have some time. Thank you for your reply – stanek Sep 01 '16 at 00:23
  • 1
    @Oriol if this is indeed what I'm looking for I can just write an EMMET script to plop this code in whenever im doing debugging – stanek Sep 01 '16 at 00:28
  • @stanek I'm glad to hear that. I hope you can adapt it to your needs. –  Sep 01 '16 at 00:32
  • @FelixKling You are right. I corrected the answer accordingly. Thank you! –  Sep 01 '16 at 00:58
  • 1
    @stanek I've changed some things a bit so you can use console.log() directly. –  Sep 03 '16 at 02:39
0

In javascript, scoped variables exist only in the runtime engine and don't expose their name for introspection. This is by opposition to, for example, object variables. Your request could be achieved by using an object instead of the scope to store your variables:

let _ = {
  phone_number: 911,
  name: stanek,
  address: undefined,
}

let log = function() {
  for (let name of arguments) 
    console.log(name, ":", scope[name]);
}

log("phone_number", "name", "address");

_.name = "New Name";

As you can see, this technique requires a bit of prefixing when addressing the variables, but it's not different from this. ; speaking of which the same technique could work with this. as well. Let me see:

let log = function() {
  for (let name of arguments) 
    console.log(name, ":", this.name);
}

_.log = log;
_.log("name");

this.log = log;
this.log("foo", "bar", "buz");

or 

log.call(_, "name");
log.call(this, "foo", "bar", "buz");
solendil
  • 8,432
  • 4
  • 28
  • 29