Manual talk:Coding conventions/JavaScript

Jump to navigation Jump to search

About this board

Sebastian Berlin (WMSE) (talkcontribs)
Perhelion (talkcontribs)
Fomafix (talkcontribs)

The examples contradict the conventions: spaces vs. tabs

4
Summary by Perhelion

Done

Jack who built the house (talkcontribs)
JackPotte (talkcontribs)

I've always wondered why tabs whereas the wikicode editor doesn't provide it with the tab key.

Jack who built the house (talkcontribs)

CodeEditor (which opens when editing pages with "javascript", "css" and "scribunto" content models) does.

Mattflaschen-WMF (talkcontribs)

I understand we should not manipulate the prototype (and I'm fine with the conventions saying that).

However, @Sbisson (WMF) noted that user scripts sometimes manipulate the prototype anyway. So it seems we should take defensive measures when looping nonetheless.

ESanders (WMF) (talkcontribs)

Any such scripts should be strongly advised to stop doing that, and definitely not made available as gadgets. I don't think we need to consider how we support them though - that would be like asking "how do we protect against as user script that has window.mw = null;"

Reply to "Manipulating Object prototype"

Whitespace for function parameter lists

7
Mattflaschen (talkcontribs)

The convention to put spaces around function parameter lists was not documented. It is documented for PHP, and is de facto for JS. However, I think it's better to add it specifically here.

This post was posted by Mattflaschen, but signed as Superm401.

Jdforrester (WMF) (talkcontribs)
Automatik (talkcontribs)

Hi,

The page says "We use JSHint as our code quality tool."

But when I use JSHint it tells me not to use space between parenthesis and parameters. So this is not consistent.

Thank you to enlighten me.

Krinkle (talkcontribs)

JSHint is a code quality tool, not coding style. It has some legacy options for whitespace enforcement, however those are deprecated, disabled by default, and our configurations don't enable that option (as reflected by the lint job that runs from Jenkins, which uses the same configuration, and as such should also never enforce or warn for options like that).

The whitespace style option in JSHint is not flexible (it was a boolean option for backwards-compatibility with JSLint to enforce Crockford's code conventions).

If JSHint is warning you about those whitespace rules, make sure you find out why that option is enabled and disable it.

Automatik (talkcontribs)

Actually I was using JSLint instead of JSHint which does not consider these spaces as an error.

Thanks for the expanations furthermore.

Danmichaelo (talkcontribs)

Under "Line length", there is an example mw.foo.getThatFrom('this'). Should it be mw.foo.getThatFrom( 'this' )?

Jdforrester (WMF) (talkcontribs)

Indeed; I've fixed this. Thanks for the spot.

Whitespace inside of square brackets

12
Fomafix (talkcontribs)

What is the coding convention for whitespace inside of square brackets?

var array = ['foo', 'bar'];

or

var array = [ 'foo', 'bar' ];
Mattflaschen (talkcontribs)

None should be used adjacent to the brackets (i.e. the first is correct). The only exception I can think of is when there's a line break immediately after the first bracket (generally used for a long list, or at least a long item).

This post was posted by Mattflaschen, but signed as Superm401.

Jdforrester (WMF) (talkcontribs)

I think this is outdated – I believe that the rule is now you always have spaces when creating an array, but never when accessing:

Right
var array = [ 'foo', 'bar' ];
array[0].print();
Wrong
var array = ['foo', 'bar'];
array[ 0 ].print();
Danwe (talkcontribs)

+1 - This seems much more consistent with the space rules for normal brackets ( and ).

Santhosh.thottingal (talkcontribs)

I doubt whether we need to avoid space while accessing it. When in doubt I usually refer https://contribute.jquery.org/style-guide/js/#arrays-and-function-calls since that is the closest convention to MediaWiki(double quotes vs single quote being major difference). According to that, conventions for white spaces in arrays and function calls

Right

array = [ "*" ];
array = [ a, b ]; 
foo( arg ); 
foo( "string", object ); 
foo( options, object[ property ] ); 
foo( node, "property", 2 );

This also makes it consistent with rules for normal brackets-(). "When a function has parameters, there should be a space on the inside side of both parentheses, for both function definitions and function calls."

I think we need to decide on this and update the Manual. I see square brackets used with and without white space in our codebase(example). Thanks.

Amire80 (talkcontribs)

I agree with Santhosh - consistency with all kinds of brackets and with the jQuery conventions is a good thing.

Just to be sure - this also includes constructs such as array[ i ], right? So

  • array[ i ] - correct
  • array[i] - incorrect
Danwe (talkcontribs)

Would be consistent. Also, array[ 0 ].

Jdforrester (WMF) (talkcontribs)
Technical 13 (talkcontribs)

I'll note that what WMF has for a coding convention is exactly the opposite to what JSlint thinks it should be. It's a real pain to have to JSlint my code to meet other stuff and then have to go back and manually add the MWF CC reguarding whitespace. I propose that either an alternative to JSlint be made available for WMF coding conventions or that the coding conventions be made to comply with the JSlint the rest of the world uses. Thanks for reading and I look forward to responses.

Jdforrester (WMF) (talkcontribs)

Use jscs with the "wikimedia" preset and you'll be great.

Technical 13 (talkcontribs)

What's jscs? Is it a plugin for Notepadd++? Where do I DL it or find documentation on it?

Jdforrester (WMF) (talkcontribs)

jscs == JavaScript Code Style checking module: http://jscs.info/

We use it at Wikimedia to automatically spot violations of the coding conventions. Each repo has it's own configuration, but over time we're slowly converging each repo to compliance with the complete set of the coding conventions that we have.

I've never heard of "Notepad++" but I assume it's a text editor you choose to use. I don't know if it has a jscs plugin; the editor I use (Atom) does, which is very useful.

Code documentation: Distinguishing between object being compatible to interface by convention (duck typing) vs. requiring instance working with instanceof

4
Danwe (talkcontribs)

Recently I have been wondering how to document whether a object (e.g. required by a function's parameter) had to be an instance of a certain constructor vs. more basic requirement that it had to match a certain interface definition.

An example for this is jQuery.Promise. jQuery.Promise is no constructor but certainly is some sort of interface definition that you could do duck-type checks against.

Rather than just documenting that a parameter requires {Object}, we usually write {jQuery.Promise}. It is not clear though whether an instance of that constructor (in this case there is not even a constructor) is required or whether the object only had to stand duck-type checks against the interface. Is there any usual way how to add this information to the documentation? I could immagine something like jQuery.Promise^, so the ^ would imply that no real instance is required.

Mattflaschen (talkcontribs)

I would document it as {jQuery.Promise}. In fact, I have done so in a couple cases already. It is a documented type, and there's specific code implementing it. Based on the implementation and the target option of deferred.promise, I would describe jQuery.Promise as a mixin, rather than an interface.

Despite there being no Promise constructor, it's relatively easy to make a promise; for example, the caller can call the jQuery.Deferred constructor, then call deferred.promise. Technically, the second step is optional, as all Deferred objects are also Promise objects. However, calling .promise is necessary if you want to prevent external code from resolving your Promise.

Although duck-typing may work for Promise sometimes, I don't really see the need to advertise that in this case.

This post was posted by Mattflaschen, but signed as Superm401.

Danwe (talkcontribs)

Sorry, but this is not really what I wanted to know. Promise was just an example for something that can only be checked against by duck-typing and in documentation as some sort of "pseudo constructor" or concept, basically an interface definition.

You can't describe jQuery.Promise as a mixin since there is no jQuery.Promise. {jQuery.Promise} is just that conceptual thing we refer to documentation as described above. Besides, mixins are basically interfaces with implementation. Or you could argue behind each mixin there should be an interface conceptually.

So the question remains, how to document that something is rather referring to some concept rather than a real physical thing (a constructor).

Krinkle (talkcontribs)

While jQuery.Promise may (implementation-wise) not be a public constructor, I would most certainly consider it a class. In essence it is a private class in jQuery that various other classes inherit from by calling the private object constructor function for promises. And while that constructor is not exposed, the objects constructed from it are.

jQuery.Deferred objects mixin all public properties of the internally constructed promise. And deferred.promise() returns the promise object as-is. JavaScript doesn't really have classes to begin with anyway, and as such it is up to a constructor to decide whether to construct objects plainly (inheriting only from Object), or with additional prototypes in the chain. In the case of jQuery's promises, they are plain objects.

Anyway, in jsduck documentation nothing implies that it has to pass instanceof in execution context. It should however pass "instance of" conceptually (an object created by the class documented in jsduck under that name). The idea of "^" doesn't make sense in that case because for all intends and purposes the object is and must in fact be an instance of jQuery.Promise.

If the function you're documenting is invoked with a promise created by jQuery.Deferred (or its callers such as $.ready, jqXHR, jQuery.fn.promise etc.) then using jQuery.Promise seems most appropriate within the conventions of how we use jsduck.

If the function takes a compatible promise created by another library, then I agree we could have an abstract class definition for Promise that could be used for cases where you aren't dealing with promises made by jQuery per se. Be careful though with assuming what is part of a "standard" promises. The specification is still in flux. It's easy to assume you need a simple promise and end up relying on jQuery-specific behaviour.

Reply to "Code documentation: Distinguishing between object being compatible to interface by convention (duck typing) vs. requiring instance working with instanceof"
Technical 13 (talkcontribs)

Is there a plugin for Notepad++ for JShint for these coding conventions? The default one doesn't seem to work very well. It uses an entirely different spacing convention and doesn't have all the mw. globals predefined.

Reply to "Notepad++ JShint"

Discouraging from using a single "var" for multiple variable declarations.

1
Danwe (talkcontribs)

I have been thinking and researching about the topic of multiple vs. single var statements recently and have changed my ways entirely. I switched from using var do declare bunches of variables at the same time to using one var per line. Here are some reasons, going so far that I would completely discourage from using a single var for anything more complex than var a, b, c;

  • Multiple var are better readable in diffs. Each line ends with a ";" and is completely self-contained. E.g.:

var whatever = 0,
    whatever2 = 2;

vs.

var whatever = 0; 
var whatever2 = 2;

Two lines changed One line changed
The multiple var version will therefore keep the git blame for the first line in tact, which is a huge advantage. Changing a line of code as a side effect of syntactical sugar or preferences is bad.
  • Huge difference when debugging. If you have a list of five variables using one var and you want to see the value of the second one before creating the third one, then this is not really possible in any debugger known to me since all five variables are created in one step.
  • Whenever tabs are displayed with 8 spaces, the whole one var thing looks incredibly ugly:
var foo = 123,
        bar = 321;
vs.
var foo = 123;
var bar = 321;
  • When assigning initial values does not fit into one line, one is left with the question how to spread the assignment over multiple lines and I have seen various models, none of which makes me feel comfortable when looking at. Some examples (there are many more variations, you get the idea I hope):
// Quick skimming over the code could give you
// the expression "xxx" is a variable as well. 
var foo = foo(
    xxx + "someverylongstring..." ),
    anothervar = false;
// less misleading but still kind of ugly
var foo = foo(
        xxx + "someverylongstring..." ),
    anothervar = false;
var foo = {
    field: 1
},
    bar = {
        field: 2
    };
vs.
var foo = {
    field: 1
};
var bar = {
    field: 2
};
or, adding tabs for
formatting the first
var as soon as the
second gets added, sucks
a lot when diffing again:

var foo = {
■■■■    field: 1
■■■■};,
    bar = {
        field: 2
    };

vs.

var foo = {
    field: 1
};
var bar = {
    field: 2
};

  • Multiple var offer higher aesthetics for comments on top of variables without even thinking about it:
// some comment about foo
var foo = true,
    // some come comment about bar
    bar = false;

or (better, but haven't seen this one so often)

// some comment about foo
var foo = true,
// some come comment about bar
    bar = false;
vs.
// some comment about foo
var foo = true;
// some come comment about bar
var bar = false;

Compared to the first multi var version this gives more space for comments and does not look odd or like the one comment is more important than the other one.
Also, the first single var version would look quite horrible in a diff when removing the first variable declaration.

Most of this has also been covered in a more extensive article by Ben Alman.

I am not saying we should change all var statements now, but add this to the coding guidelines and have an eye for it during code reviews. Currently the convention does even discourage from using multiple var, I highly disagree with this.

Reply to "Discouraging from using a single "var" for multiple variable declarations."
MarkTraceur (talkcontribs)

In the series of "ambiguous code styles that really shouldn't be ambiguous", what's the best form of loop in the situation of looping through an array?

I'm seeing all three of

for ( var i = 0; i < arr.length; i++ ) {
    ....
}

for ( var i in arr ) {
    ....
}

$.each( arr, function ( i, item ) {
    ....
} );

Input appreciated!

Brion VIBBER (talkcontribs)

for (var i in arr) has to be armored with checks for arr.hasOwnProperty(i) to protect against brokenness if frameworks are present that modify the Array prototype. Bleh!

Personally I prefer to use $.each() most of the time:

  • functional style / iterator feels nicer than counting manually
  • you get a real function scope -- for instance you can safely use i in a lambda function inside your loop, which you can't with the first two forms

However it is a little more expensive, so performance-critical paths may prefer the traditional for loop with iterator variable and comparison to length.

Nischayn22 (talkcontribs)

I myself prefer $.each(), mostly because of its functional nature. Besides, I love the jQuery way.

Krinkle (talkcontribs)

There is no one convention for looping in general, because there are different solutions for different situations.

Looping over an array

When it comes to looping over an array (or array-like objects such as jQuery objects and Argument objects) however, then there really is only one good way, and that's a for loop with a number counter. And the reason is that you want to loop over the the indexes, not over all properties unconditionally.

Example of dump for a jQuery object ($('#content');):

{
    selector: '#content',
    context: HTMLDocument.
    length: 1,
    0: HTMLDivElement
}
/// inherits from jQuery.prototype
/// inherits from Object.prototype

You wouldn't want to loop over all properties, only the indices (based on the length). Even a hasOwnProperty filter won't help you here, because they are all "own" properties.

The other case is when there is a sparse array that may contain gaps, in most cases the intent is to loop over all of them, and yield undefined for gaps. Another case is that in JavaScript everything is either a primitive (string, number, boolean, ..) or an object. Arrays are objects. And as such, keys of properties are strings, which can cause issues when accessed as such. A for-in loop will give you the internal key values, which are strings (as they should be).

e.g. the following will never log the "Success!" message:

var key,
    arr = ['foo', 'bar'];

for ( key in arr ) {
    console.log( 'Key:', $.toJSON( key ), 'Value:', $.toJSON( arr[key] ) );
    if ( key === 1 ) {
        console.log( 'Success! Found the second item (should be "bar"):' + arr[key] );
    }
}

Because arr (as being an object) is { "0": 'foo', "1": 'bar' }, and "1" !== 1.

Looping over an object

If you do want to loop over all properties of an object then, of course, there is nothing wrong with a for-in loop.

var obj = {
    'foo': 'Hello Foolifications'
    'bar': 'Rabarber Barbara\'s bar'
};

for ( key in arr ) {
    console.log( 'Key:', $.toJSON( key ), 'Value:', $.toJSON( arr[key] ) );
    if ( key === 1 ) {
        console.log( 'Success! Found the second item (should be "bar"):' + arr[key] );
    }
}

If you explicitly need to exclude inherited properties (e.g. you have an instance of FooBar that inherits from FooBar.prototype, and you want to clone it):

var clone, key,
    hasOwn = Object.prototype.hasOwnProperty,
    foo = new AwesomeFoo( /* .. */ );

/* .. */

clone = Object.create(AwesomeFoo.prototype);

for ( key in foo ) {
    if ( hasOwn.call( foo, key ) {
        clone[key] = foo[key];
    }
}

So, what about $.each ? Well, $.each is basically the same as a for-in loop ($.each does not filter hasOwn), with the difference that it has its own scope. In most cases you probably don't need it, but if you need to do a lot of loops and want to avoid conflicts with the different counter or key variables, then using a scope can be helpful. Especially in nested loops.

Danwe (talkcontribs)

It should be mentioned that $.each is checking whether the given object/array has a length field. If so, then each will only iterate over the "numeric" fields smaller than "length", no matter whether the field's value is undefined or not.

Reply to "For loop style?"
JGonera (WMF) (talkcontribs)

I noticed something in the mobile extension code and discussed it with Jon (although I'm not sure if I convinced him).

In the existing code a DOM element from a jQuery object is often passed instead of simply passing the jQuery object, e.g.:

setupReferences( $( '#content' )[ 0 ] );

Jon said that he wants to be sure that only one element is passed. I think it just makes the code look more confusing and doesn't really solve the problem. I mean we should simply assume that the selector selects only one element. If it selects more than one element it's obviously a mistake and we actually can't say if [0] is the right one or maybe [1] or [54]. If we really want to be sure we can check .length at the beginning of the invoked function.

Anyone disagrees? Could we include that somewhere in the coding conventions?

Krinkle (talkcontribs)

ID selectors are incapable of returning more than one element. So if you're referring specifically to code using [0] on an object from an ID selector, that doesn't deserve mention in code conventions as it is imho just plain stupid. Something you might be confused by once, but if we start including things like that there is no telling where it will end end. Code conventions isn't a training guide to how the browser works and how the DOM works.

For passing around 1 element, you can use .eq( 0 ) instead of [0] to yield a cloned jQuery object that only contains the first node of the jQuery object collection.

Regarding passing HTMLElement objects or jQuery collections between functions, it depends on what makes sense for the situation. I don't think this is the kind of thing where there is 1 right solution or where using different approaches in different modules makes code harder to use.

jQuery plugins obviously operate on a jQuery collection as they are methods on the jQuery prototype itself. Utilities often operate on 1 single element. In the latter case it is imho preferred to document the method as taking an HTMLElement as opposed to a "jQuery object that must have only 1 element". That way the responsibility of extracting the right data is in the hands of the caller. So that you have Foo.bar( $foo[0] ) instead of Foo.bar( $foo ). That way Foo.bar doesn't have to verify it has length, take the first one, and maybe throw an error if it has more than one?

Also note that constructing a jQuery object for a node is very cheap. It is the first executive use case in the constructor. Nothing to worry about there. Things like $( this ) in DOM callbacks are very common and very fast.

So something like this would be fine:

/** @param {HTMLElement} el */
Foo.getState = function (el) {
  return $(el).data('foo-state');
};

However if utilities are more efficient when they can cache data outside a loop, then you may want to change the function to take an array-like object instead of requiring the callee to make a loop where Foo.bar then has to do initialisation for every single call inside the loop.

So, instead of this:

/** @param {HTMLElement} el */
Foo.setStuff = function (el, stuff) {
  var state = Foo.getState(el) || {};
  state.stuff = Foo.normaliseStuff(stuff);
  return $(el).data('foo-state', state);
};

// One
Foo.setStuff($('#foo-container')[0], /* .. */);

// Many
$('.foo-items').each(function () {
    Foo.setStuff(this, /* .. */);
});

The following would likely be better (so that stuff is only normalised once):

/** @param {Array|jQuery} els */
Foo.setStuff = function (els, stuff) {
  var state, i, len;
  stuff = Foo.normaliseStuff(stuff);
  for (i = 0, len = els.length; i < len; i++) {
    state = Foo.getState(el) || {};
    state.stuff = stuff;
    $(els[i]).data('foo-state', state);
  }
};

// One
Foo.setStuff($('#foo-container'), /* .. */);
// Many
Foo.setStuff($('.foo-items'), /* .. */);

This sort of a bad example since jQuery methods support setting on the entire collection at once, so this is probably better done like $('.foo-items').data('foo-state-stuff, /* .. */); but you get the idea. Also, you could extend Foo.setStuff with els = els.nodeType ? [ els ] : els; so that it supports both:

/** @param {Array|jQuery|HTMLElement} els */
Foo.setStuff = function (els, stuff) {
  var state, i, len;
  els = els.nodeType ? [ els ] : els;
  stuff = Foo.normaliseStuff(stuff);
  for (i = 0, len = els.length; i < len; i++) {
    state = Foo.getState(el) || {};
    state.stuff = stuff;
    $(els[i]).data('foo-state', state);
  }
};

// Alternative:

/** @param {Array|jQuery|HTMLElement} els */
Foo.setStuff = function (els, stuff) {
  $(els).data('foo-state-stuff', stuff);
};

// Alternative (2):

/** @param {jQuery} $els */
Foo.setStuff = function ($els, stuff) {
  $els.data('foo-state-stuff', stuff);
};


// One (node)
Foo.setStuff($('#foo-container')[0], /* .. */);
Foo.setStuff(document.getElementById('foo-container'), /* .. */);
// One
Foo.setStuff($('#foo-container'), /* .. */);
// Many
Foo.setStuff($('.foo-items'), /* .. */);
Wikinaut (talkcontribs)

@Krinkle

(I suggest that) perhaps a shortened version of your reply should go to the article page.

JGonera (WMF) (talkcontribs)

Thanks for the explanation. Maybe I sounded stupid bringing up a problem like that. It just bothered me to see $(something)[0] all around the code and I needed some arguments to convince Jon to stop doing that :)

Reply to "jQuery objects vs. DOM objects"