原文链接:http://code.tutsplus.com/tutorials/24-javascript-best-practices-for-beginners--net-5399

   jquery代码优化见   writing better jquery code

   another good writing :24 JavaScript Best Practices for Beginners

Twice a month, we revisit some of our readers’ favorite posts from throughout the history of Nettuts+. This tutorial was first published in October, 2010.

The brilliant Stoyan Stefanov, in promotion of his book, "JavaScript Patterns," was kind enough to contribute an excerpt of the book for our readers, which details the essentials of writing high quality JavaScript, such as avoiding globals, using single var declarations, pre-caching length in loops, following coding conventions, and more.

This excerpt also includes some habits not necessarily related to the code itself, but more about the overall code creation process, including writing API documentation, conducting peer reviews, and running JSLint. These habits and best practices can help you write better, more understandable, and maintainable code—code to be proud of (and be able to figure out) when revisiting it months and years down the road.

Writing Maintainable Code

Software bugs are costly to fix. And their cost increases over time, especially if the bugs creep into the publicly released product. It’s best if you can fix a bug right away, as soon you find it; this is when the problem your code solves is still fresh in your head. Otherwise you move on to other tasks and forget all about that particular code. Revisiting the code after some time has passed requires:

    • Time to relearn and understand the problem
  • Time to understand the code that is supposed to solve the problem

Another problem, specific to bigger projects or companies, is that the person who eventually fixes the bug is not the same person who created the bug (and also not the same person who found the bug). It’s therefore critical to reduce the time it takes to understand code, either written by yourself some time ago or written by another developer in the team. It’s critical to both the bottom line (business revenue) and the developer’s happiness, because we would all rather develop something new and exciting instead of spending hours and days maintaining old legacy code.

Another fact of life related to software development in general is that usually more time is spent reading code than writing it. In times when you’re focused and deep into a problem, you can sit down and in one afternoon create a considerable amount of code.

The code will probably work then and there, but as the application matures, many other things happen that require your code to be reviewed, revised, and tweaked. For example:

  • Bugs are uncovered.
  • New features are added to the application.
  • The application needs to work in new environments (for example, new browsers appear on the market).
  • The code gets repurposed.
  • The code gets completely rewritten from scratch or ported to another architecture or even another language.

As a result of the changes, the few man-hours spent writing the code initially end up in man-weeks spent reading it. That’s why creating maintainable code is critical to the success of an application.

Maintainable code means code that:

  • Is readable
  • Is consistent
  • Is predictable
  • Looks as if it was written by the same person
  • Is documented

Minimizing Globals

JavaScript uses functions to manage scope. A variable declared inside of a function is local to that function and not available outside the function. On the other hand, global variables are those declared outside of any function or simply used without being declared.

Every JavaScript environment has a global object accessible when you use this outside of any function. Every global variable you create becomes a property of the global object. In browsers, for convenience, there is an additional property of the global object called window that (usually) points to the global object itself. The following code snippet shows how to create and access a global variable in a browser environment:

1
2
3
4
5
myglobal = "hello"; // antipattern
console.log(myglobal); // "hello"
console.log(window.myglobal); // "hello"
console.log(window["myglobal"]); // "hello"
console.log(this.myglobal); // "hello"

The Problem with Globals

The problem with global variables is that they are shared among all the code in your JavaScript application or web page. They live in the same global namespace and there is always a chance of naming collisions—when two separate parts of an application define global variables with the same name but with different purposes.

It’s also common for web pages to include code not written by the developers of the page, for example:

  • A third-party JavaScript library
  • Scripts from an advertising partner
  • Code from a third-party user tracking and analytics script
  • Different kinds of widgets, badges, and buttons

Let’s say that one of the third-party scripts defines a global variable, called, for example, result. Then later in one of your functions you define another global variable called result. The outcome of that is the last result variable overwrites the previous ones, and the third-party script may just stop working.

Therefore it’s important to be a good neighbor to the other scripts that may be in the same page and use as few global variables as possible. Later in the book you learn about strategies to minimize the number of globals, such as the namespacing pattern or the self-executing immediate functions, but the most important pattern for having fewer globals is to always use var to declare variables.

It is surprisingly easy to create globals involuntarily because of two JavaScript features. First, you can use variables without even declaring them. And second, JavaScript has the notion of implied globals, meaning that any variable you don’t declare becomes a property of the global object (and is accessible just like a properly declared global variable). Consider the following example:

1
2
3
4
5
function sum(x, y) {
   // antipattern: implied global
   result = x + y;
   return result;
}

In this code, result is used without being declared. The code works fine, but after calling the function you end up with one more variable result in the global namespace that can be a source of problems.

The rule of thumb is to always declare variables with var, as demonstrated in the improved version of the sum() function:

1
2
3
4
function sum(x, y) {
   var result = x + y;
   return result;
}

Another antipattern that creates implied globals is to chain assignments as part of a var declaration. In the following snippet, a is local but b becomes global, which is probably not what you meant to do:

1
2
3
4
5
// antipattern, do not use
function foo() {
   var a = b = 0;
   // ...
}

If you’re wondering why that happens, it’s because of the right-to-left evaluation. First, the expression b = 0 is evaluated and in this case b is not declared. The return value of this expression is 0, and it’s assigned to the new local variable declared with var a. In other words, it’s as if you’ve typed:

1
var a = (b = 0);

If you’ve already declared the variables, chaining assignments is fine and doesn’t create unexpected globals. Example:

1
2
3
4
function foo() {
   var a, b;
   // ... a = b = 0; // both local
}

Yet another reason to avoid globals is portability. If you want your code to run in different environments (hosts), it’s dangerous to use globals because you can accidentally overwrite a host object that doesn’t exist in your original environment (so you thought the name was safe to use) but which does in some of the others.

Side Effects When Forgetting var

There’s one slight difference between implied globals and explicitly defined ones—the difference is in the ability to undefine these variables using the delete operator:

  • Globals created with var (those created in the program outside of any function) cannot be deleted.
  • Implied globals created without var (regardless if created inside functions) can be deleted.

This shows that implied globals are technically not real variables, but they are properties of the global object. Properties can be deleted with the delete operator whereas variables cannot:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
// define three globals
var global_var = 1;
global_novar = 2; // antipattern
(function () {
   global_fromfunc = 3; // antipattern
}());
 
// attempt to delete
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true
 
// test the deletion
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"

In ES5 strict mode, assignments to undeclared variables (such as the two antipatterns in the preceding snippet) will throw an error.

Access to the Global Object

In the browsers, the global object is accessible from any part of the code via the windowproperty (unless you’ve done something special and unexpected such as declaring a local variable named window). But in other environments this convenience property may be called something else (or even not available to the programmer). If you need to access the global object without hard-coding the identifier window, you can do the following from any level of nested function scope:

1
2
3
var global = (function () {
   return this;
}());

This way you can always get the global object, because inside functions that were invoked as functions (that is, not as constrictors with new) this should always point to the global object. This is actually no longer the case in ECMAScript 5 in strict mode, so you have to adopt a different pattern when your code is in strict mode. For example, if you’re developing a library, you can wrap your library code in an immediate function and then from the global scope, pass a reference to this as a parameter to your immediate function.

Single var Pattern

Using a single var statement at the top of your functions is a useful pattern to adopt. It has the following benefits:

  • Provides a single place to look for all the local variables needed by the function
  • Prevents logical errors when a variable is used before it’s defined
  • Helps you remember to declare variables and therefore minimize globals
  • Is less code (to type and to transfer over the wire)

The single var pattern looks like this:

1
2
3
4
5
6
7
8
9
function func() {
   var a = 1,
       b = 2,
       sum = a + b,
       myobject = {},
       i,
       j;
   // function body...
}

You use one var statement and declare multiple variables delimited by commas. It’s a good practice to also initialize the variable with an initial value at the time you declare it. This can prevent logical errors (all uninitialized and declared variables are initialized with the value undefined) and also improve the code readability. When you look at the code later, you can get an idea about the intended use of a variable based on its initial value—for example, was it supposed to be an object or an integer?

You can also do some actual work at the time of the declaration, like the case with sum = a + b in the preceding code. Another example is when working with DOM (Document Object Model) references. You can assign DOM references to local variables together with the single declaration, as the following code demonstrates:

1
2
3
4
5
function updateElement() {
   var el = document.getElementById("result"),
       style = el.style;
   // do something with el and style...
}

Hoisting: A Problem with Scattered vars

JavaScript enables you to have multiple var statements anywhere in a function, and they all act as if the variables were declared at the top of the function. This behavior is known as hoisting. This can lead to logical errors when you use a variable and then you declare it further in the function. For JavaScript, as long as a variable is in the same scope (same function), it’s considered declared, even when it’s used before the var declaration. Take a look at this example:

1
2
3
4
5
6
7
8
// antipattern
myname = "global"; // global variable
function func() {
    alert(myname); // "undefined"
    var myname = "local";
    alert(myname); // "local"
}
func();

In this example, you might expect that the first alert() will prompt “global” and the second will prompt “local.” It’s a reasonable expectation because, at the time of the first alert, myname was not declared and therefore the function should probably “see” the global myname. But that’s not how it works. The first alert will say “undefined” becausemyname is considered declared as a local variable to the function. (Although the declaration comes after.) All the variable declarations get hoisted to the top of the function. Therefore to avoid this type of confusion, it’s best to declare upfront all variables you intend to use.

The preceding code snippet will behave as if it were implemented like so:

1
2
3
4
5
6
7
8
myname = "global"; // global variable
function func() {
   var myname; // same as -> var myname = undefined;
   alert(myname); // "undefined"
   myname = "local";
   alert(myname); // "local"
}
func();

For completeness, let’s mention that actually at the implementation level things are a little more complex. There are two stages of the code handling, where variables, function declarations, and formal parameters are created at the first stage, which is the stage of parsing and entering the context. In the second stage, the stage of runtime code execution, function expressions and unqualified identifiers (undeclared variables) are created. But for practical purposes, we can adopt the concept of hoisting, which is actually not defined by ECMAScript standard but is commonly used to describe the behavior.

for Loops

In for loops you iterate over arrays or array-like objects such as arguments andHTMLCollection objects. The usual for loop pattern looks like the following:

1
2
3
4
// sub-optimal loop
for (var i = 0; i < myarray.length; i++) {
   // do something with myarray[i]
}

A problem with this pattern is that the length of the array is accessed on every loop iteration. This can slow down your code, especially when myarray is not an array but anHTMLCollection object.

HTMLCollections are objects returned by DOM methods such as:

  • document.getElementsByName()
  • document.getElementsByClassName()
  • document.getElementsByTagName()

There are also a number of other HTMLCollections, which were introduced before the DOM standard and are still in use today. There include (among others):

  • document.images: All IMG elements on the page
  • document.links : All A elements
  • document.forms : All forms
  • document.forms[0].elements : All fields in the first form on the page

The trouble with collections is that they are live queries against the underlying document (the HTML page). This means that every time you access any collection’s length, you’re querying the live DOM, and DOM operations are expensive in general.

That’s why a better pattern for for loops is to cache the length of the array (or collection) you’re iterating over, as shown in the following example:

1
2
3
for (var i = 0, max = myarray.length; i < max; i++) {
   // do something with myarray[i]
}

This way you retrieve the value of length only once and use it during the whole loop.

Caching the length when iterating over HTMLCollections is faster across all browsers— anywhere between two times faster (Safari 3) and 190 times (IE7).

Note that when you explicitly intend to modify the collection in the loop (for example, by adding more DOM elements), you’d probably like the length to be updated and not constant.

Following the single var pattern, you can also take the var out of the loop and make the loop like:

1
2
3
4
5
6
7
8
9
function looper() {
   var i = 0,
        max,
        myarray = [];
   // ...
   for (i = 0, max = myarray.length; i < max; i++) {
      // do something with myarray[i]
   }
}

This pattern has the benefit of consistency because you stick to the single var pattern. A drawback is that it makes it a little harder to copy and paste whole loops while refactoring code. For example, if you’re copying the loop from one function to another, you have to make sure you also carry over i and max into the new function (and probably delete them from the original function if they are no longer needed there).

One last tweak to the loop would be to substitute i++ with either one of these expressions:

1
2
i=i+ 1
i += 1

JSLint prompts you to do it; the reason being that ++ and -- promote “excessive trickiness.” If you disagree with this, you can set the JSLint option plusplus to false. (It’s true by default.)

Two variations of the for pattern introduce some micro-optimizations because they:

  • Use one less variable (no max)
  • Count down to 0, which is usually faster because it’s more efficient to compare to 0 than to the length of the array or to anything other than 0

The first modified pattern is:

1
2
3
4
var i, myarray = [];
for (i = myarray.length; i--;) {
   // do something with myarray[i]
}

And the second uses a while loop:

1
2
3
4
5
var myarray = [],
    i = myarray.length;
while (i--) {
   // do something with myarray[i]
}

These are micro-optimizations and will only be noticed in performance-critical operations. Additionally, JSLint will complain about the use of i--.

for-in Loops

for-in loops should be used to iterate over nonarray objects. Looping with for-in is also called enumeration.

Technically, you can also use for-in to loop over arrays (because in JavaScript arrays are objects), but it’s not recommended. It may lead to logical errors if the array object has already been augmented with custom functionality. Additionally, the order (the sequence) of listing the properties is not guaranteed in a for-in. So it’s preferable to use normal for loops with arrays and for-in loops for objects.

It’s important to use the method hasOwnProperty() when iterating over object properties to filter out properties that come down the prototype chain.

Consider the following example:

01
02
03
04
05
06
07
08
09
10
11
12
// the object
var man = {
   hands: 2,
   legs: 2,
   heads: 1
};
 
// somewhere else in the code
// a method was added to all objects
if (typeof Object.prototype.clone === "undefined") {
   Object.prototype.clone = function () {};
}

In this example we have a simple object called man defined with an object literal. Somewhere before or after man was defined, the Object prototype was augmented with a useful method called clone(). The prototype chain is live, which means all objects automatically get access to the new method. To avoid having the clone() method show up when enumerating man, you need to call hasOwnProperty() to filter out the prototype properties. Failing to do the filtering can result in the function clone() showing up, which is undesired behavior in mostly all scenarios:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1.
// for-in loop
for (var i in man) {
   if (man.hasOwnProperty(i)) { // filter
      console.log(i, ":", man[i]);
   }
}
/* result in the console
hands : 2
legs : 2
heads : 1
*/
// 2.
// antipattern:
// for-in loop without checking hasOwnProperty()
for (var i in man) {
   console.log(i, ":", man[i]);
}
/*
result in the console
hands : 2
legs : 2
heads : 1
clone: function()
*/

Another pattern for using hasOwnProperty() is to call that method off of the Object.prototype, like so:

1
2
3
4
5
for (var i in man) {
   if (Object.prototype.hasOwnProperty.call(man, i)) { // filter
      console.log(i, ":", man[i]);
   }
}

The benefit is that you can avoid naming collisions is case the man object has redefined hasOwnProperty. Also to avoid the long property lookups all the way toObject, you can use a local variable to “cache” it:

1
2
3
4
5
6
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
    if (hasOwn.call(man, i)) { // filter
        console.log(i, ":", man[i]);
    }
}

Strictly speaking, not using hasOwnProperty() is not an error. Depending on the task and the confidence you have in the code, you may skip it and slightly speed up the loops. But when you’re not sure about the contents of the object (and its prototype chain), you’re safer just adding the hasOwnProperty() check.

A formatting variation (which doesn’t pass JSLint) skips a curly brace and puts the if on the same line. The benefit is that the loop statement reads more like a complete thought (“for each element that has an own property X, do something with X”). Also there’s less indentation before you get to the main purpose of the loop:

1
2
3
4
5
// Warning: doesn't pass JSLint
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) if (hasOwn.call(man, i)) { // filter
    console.log(i, ":", man[i]);
}

(Not) Augmenting Built-in Prototypes

Augmenting the prototype property of constructor functions is a powerful way to add functionality, but it can be too powerful sometimes.

It’s tempting to augment prototypes of built-in constructors such as Object()Array(), or Function(), but it can seriously hurt maintainability, because it will make your code less predictable. Other developers using your code will probably expect the built-in JavaScript methods to work consistently and will not expect your additions.

Additionally, properties you add to the prototype may show up in loops that don’t usehasOwnProperty(), so they can create confusion.

Therefore it’s best if you don’t augment built-in prototypes. You can make an exception of the rule only when all these conditions are met:

  • It’s expected that future ECMAScript versions or JavaScript implementations will implement this functionality as a built-in method consistently. For example, you can add methods described in ECMAScript 5 while waiting for the browsers to catch up. In this case you’re just defining the useful methods ahead of time.
  • You check if your custom property or method doesn’t exist already—maybe already implemented somewhere else in the code or already part of the JavaScript engine of one of the browsers you support.
  • You clearly document and communicate the change with the team.

If these three conditions are met, you can proceed with the custom addition to the prototype, following this pattern:

1
2
3
4
5
if (typeof Object.protoype.myMethod !== "function") {
   Object.protoype.myMethod = function () {
      // implementation...
   };
}

switch Pattern

You can improve the readability and robustness of your switch statements by following this pattern:

01
02
03
04
05
06
07
08
09
10
11
12
var inspect_me = 0,
    result = '';
switch (inspect_me) {
case 0:
   result = "zero";
   break;
case 1:
   result = "one";
   break;
default:
   result = "unknown";
}

The style conventions followed in this simple example are:

  • Aligning each case with switch (an exception to the curly braces indentation rule).
  • Indenting the code within each case.
  • Ending each case with a clear break;.
  • Avoiding fall-throughs (when you omit the break intentionally). If you’re absolutely convinced that a fall-through is the best approach, make sure you document such cases, because they might look like errors to the readers of your code.
  • Ending the switch with a default: to make sure there’s always a sane result even if none of the cases matched.

Avoiding Implied Typecasting

JavaScript implicitly typecasts variables when you compare them. That’s why comparisons such as false == 0 or "" == 0 return true.

To avoid confusion caused by the implied typecasting, always use the === and !==operators that check both the values and the type of the expressions you compare:

1
2
3
4
5
6
7
8
9
var zero = 0;
if (zero === false) {
   // not executing because zero is 0, not false
}
 
// antipattern
if (zero == false) {
   // this block is executed...
}

There’s another school of thought that subscribes to the opinion that it’s redundant to use === when == is sufficient. For example, when you use typeof you know it returns a string, so there’s no reason to use strict equality. However, JSLint requires strict equality; it does make the code look consistent and reduces the mental effort when reading code. (“Is this == intentional or an omission?”)

Avoiding eval()

If you spot the use of eval() in your code, remember the mantra “eval() is evil.” This function takes an arbitrary string and executes it as JavaScript code. When the code in question is known beforehand (not determined at runtime), there’s no reason to useeval(). If the code is dynamically generated at runtime, there’s often a better way to achieve the goal without eval(). For example, just using square bracket notation to access dynamic properties is better and simpler:

1
2
3
4
5
6
7
// antipattern
var property = "name";
alert(eval("obj." + property));
 
// preferred
var property = "name";
alert(obj[property]);

Using eval() also has security implications, because you might be executing code (for example coming from the network) that has been tampered with. This is a common antipattern when dealing with a JSON response from an Ajax request. In those cases it’s better to use the browsers’ built-in methods to parse the JSON response to make sure it’s safe and valid. For browsers that don’t support JSON.parse() natively, you can use a library from JSON.org.

It’s also important to remember that passing strings to setInterval()setTimeout(), and the Function() constructor is, for the most part, similar to using eval() and therefore should be avoided. Behind the scenes, JavaScript still has to evaluate and execute the string you pass as programming code:

1
2
3
4
5
6
7
8
9
// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);
 
// preferred
setTimeout(myFunc, 1000);
setTimeout(function () {
   myFunc(1, 2, 3);
}, 1000);

Using the new Function() constructor is similar to eval() and should be approached with care. It could be a powerful construct but is often misused. If you absolutely must use eval(), you can consider using new Function() instead. There is a small potential benefit because the code evaluated in new Function() will be running in a local function scope, so any variables defined with var in the code being evaluated will not become globals automatically. Another way to prevent automatic globals is to wrap theeval() call into an immediate function.

Consider the following example. Here only un remains as a global variable polluting the namespace:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
console.log(typeof un); // "undefined"
console.log(typeof deux); // "undefined"
console.log(typeof trois); // "undefined"
 
var jsstring = "var un = 1; console.log(un);";
eval(jsstring); // logs "1"
 
jsstring = "var deux = 2; console.log(deux);";
new Function(jsstring)(); // logs "2"
 
jsstring = "var trois = 3; console.log(trois);";
(function () {
   eval(jsstring);
}()); // logs "3"
 
console.log(typeof un); // number
console.log(typeof deux); // undefined
console.log(typeof trois); // undefined

Another difference between eval() and the Function constructor is that eval() can interfere with the scope chain whereas Function is much more sandboxed. No matter where you execute Function, it sees only the global scope. So it can do less local variable pollution. In the following example, eval() can access and modify a variable in its outer scope, whereas Function cannot (also note that using Function or new Function is identical):

01
02
03
04
05
06
07
08
09
10
(function () {
   var local = 1;
   eval("local = 3; console.log(local)"); // logs 3
   console.log(local); // logs 3
}());
 
(function () {
   var local = 1;
   Function("console.log(typeof local);")(); // logs undefined
}());

Number Conversions with parseInt()

Using parseInt() you can get a numeric value from a string. The function accepts a second radix parameter, which is often omitted but shouldn’t be. The problems occur when the string to parse starts with 0: for example, a part of a date entered into a form field. Strings that start with 0 are treated as octal numbers ( base 8 ) in ECMAScript 3; however, this has changed in ES5. To avoid inconsistency and unexpected results, always specify the radix parameter:

1
2
3
4
var month = "06",
    year = "09";
month = parseInt(month, 10);
year = parseInt(year, 10);

In this example, if you omit the radix parameter like parseInt(year), the returned value will be 0, because “09” assumes octal number (as if you did parseInt( year, 8 )) and 09 is not a valid digit in base 8.

Alternative ways to convert a string to a number include:

1
2
+"08" // result is 8
Number("08") // 8

These are often faster than parseInt(), because parseInt(), as the name suggests, parses and doesn’t simply convert. But if you’re expecting input such as “08 hello”,parseInt() will return a number, whereas the others will fail with NaN.

Coding Conventions

It’s important to establish and follow coding conventions—they make your code consistent, predictable, and much easier to read and understand. A new developer joining the team can read through the conventions and be productive much sooner, understanding the code written by any other team member.

Many flamewars have been fought in meetings and on mailing lists over specific aspects of certain coding conventions (for example, the code indentation—tabs or spaces?). So if you’re the one suggesting the adoption of conventions in your organization, be prepared to face resistance and hear different but equally strong opinions. Remember that it’s much more important to establish and consistently follow a convention, any convention, than what the exact details of that convention will be.

Indentation

Code without indentation is impossible to read. The only thing worse is code with inconsistent indentation, because it looks like it’s following a convention, but it may have confusing surprises along the way. It’s important to standardize the use of indentation.

Some developers prefer indentation with tabs, because anyone can tweak their editor to display the tabs with the individually preferred number of spaces. Some prefer spaces—usually four. It doesn’t matter as long as everyone in the team follows the same convention. This book, for example, uses four-space indentation, which is also the default in JSLint.

And what should you indent? The rule is simple—anything within curly braces. This means the bodies of functions, loops (do, while, for, for-in), ifsswitches, andobject properties in the object literal notation. The following code shows some examples of using indentation:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
function outer(a, b) {
    var c = 1,
        d = 2,
        inner;
    if (a > b) {
        inner = function () {
            return {
                r: c - d
            };
        };
    } else {
        inner = function () {
            return {
                r: c + d
            };
        };
    }
    return inner;
}

Curly Braces

Curly braces should always be used, even in cases when they are optional. Technically, if you have only one statement in an if or a for, curly braces are not required, but you should always use them anyway. It makes the code more consistent and easier to update.

Imagine you have a for loop with one statement only. You could omit the braces and there will be no syntax error:

1
2
3
// bad practice
for (var i = 0; i < 10; i += 1)
   alert(i);

But what if, later on, you add another line in the body of the loop?

1
2
3
4
// bad practice
for (var i = 0; i < 10; i += 1)
   alert(i);
   alert(i + " is " + (i % 2 ? "odd" : "even"));

The second alert is outside the loop although the indentation may trick you. The best thing to do in the long run is to always use the braces, even for one-line blocks:

1
2
3
4
// better
for (var i = 0; i < 10; i += 1) {
   alert(i);
}

Similarly for if conditions:

01
02
03
04
05
06
07
08
09
10
11
12
// bad
if (true)
   alert(1);
else
   alert(2);
 
// better
if (true) {
   alert(1);
} else {
   alert(2);
}

Opening Brace Location

Developers also tend to have preferences about where the opening curly brace should be—on the same line or on the following line?

1
2
3
if (true) {
   alert("It's TRUE!");
}

OR:

1
2
3
4
if (true)
{
   alert("It's TRUE!");
}

In this specific example, it’s a matter of preference, but there are cases in which the program might behave differently depending on where the brace is. This is because of the semicolon insertion mechanism—JavaScript is not picky when you choose not to end your lines properly with a semicolon and adds it for you. This behavior can cause troubles when a function returns an object literal and the opening brace is on the next line:

1
2
3
4
5
6
7
8
// warning: unexpected return value
function func() {
   return
  // unreachable code follows
   {
      name : "Batman"
   }
}

If you expect this function to return an object with a name property, you’ll be surprised. Because of the implied semicolons, the function returns undefined. The preceding code is equivalent to this one:

1
2
3
4
5
6
7
8
// warning: unexpected return value
function func() {
   return undefined;
  // unreachable code follows
   {
      name : "Batman"
   }
}

In conclusion, always use curly braces and always put the opening one on the same line as the previous statement:

1
2
3
4
5
function func() {
   return {
      name : "Batman"
   };
}

A note on semicolons: Just like with the curly braces, you should always use semicolons, even when they are implied by the JavaScript parsers. This not only promotes discipline and a more rigorous approach to the code but also helps resolve ambiguities, as the previous example showed.

White Space

The use of white space can also contribute to improved readability and consistency of the code. In written English sentences you use intervals after commas and periods. In JavaScript you follow the same logic and add intervals after list-like expressions (equivalent to commas) and end-of-statements (equivalent to completing a “thought”).

Good places to use a white space include:

  • After the semicolons that separate the parts of a for loop: for example, for (var i
    = 0; i < 10; i += 1) {...}
  • Initializing multiple variables (i and max) in a for loop: for (var i = 0, max = 10; i < max; i += 1) {...}
  • After the commas that delimit array items: var a = [1, 2, 3];
  • After commas in object properties and after colons that divide property names and
    their values: var o = {a: 1, b: 2};
  • Delimiting function arguments: myFunc(a, b, c)
  • Before the curly braces in function declarations: function myFunc() {}
  • After function in anonymous function expressions: var myFunc = function () {};

Another good use for white space is to separate all operators and their operands with spaces, which basically means use a space before and after +, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=, and so on:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
// generous and consistent spacing
// makes the code easier to read
// allowing it to "breathe"
var d = 0,
    a = b + 1;
if (a && b && c) {
    d = a % c;
    a += d;
}
 
// antipattern
// missing or inconsistent spaces
// make the code confusing
var d = 0,
    a = b + 1;
if (a && b && c) {
    d = a % c;
    a += d;
}

And a final note about white space—curly braces spacing. It’s good to use a space:

  • Before opening curly braces ({) in functions, if-else cases, loops, and object literals
  • Between the closing curly brace (}) and else or while

A case against liberal use of white space might be that it could increase the file size, but
minification takes care of this issue.

An often-overlooked aspect of code readability is the use of vertical white space. You can use blank lines to separate units of code, just as paragraphs are used in literature to separate ideas.

Naming Conventions

Another way to make your code more predictable and maintainable is to adopt naming conventions. That means choosing names for your variables and functions in a consistent manner.

Below are some naming convention suggestions that you can adopt as-is or tweak to your liking. Again, having a convention and following it consistently is much more important than what that convention actually is.

Capitalizing Constructors

JavaScript doesn’t have classes but has constructor functions invoked with new:

1
var adam = new Person();

Because constructors are still just functions, it helps if you can tell, just by looking at a function name, whether it was supposed to behave as a constructor or as a normal function.

Naming constructors with a capital first letter provides that hint. Using lowercase for functions and methods indicates that they are not supposed to be called with new:

1
2
function MyConstructor() {...}
function myFunction() {...}

Separating Words

When you have multiple words in a variable or a function name, it’s a good idea to follow a convention as to how the words will be separated. A common convention is to use the so-called camel case. Following the camel case convention, you type the words in lowercase, only capitalizing the first letter in each word.

For your constructors, you can use upper camel case, as in MyConstructor(), and for function and method names, you can use lower camel case, as in myFunction(),calculateArea() and getFirstName().

And what about variables that are not functions? Developers commonly use lower camel case for variable names, but another good idea is to use all lowercase words delimited by an underscore: for example, first_namefavorite_bands, andold_company_name. This notation helps you visually distinguish between functions and all other identifiers—primitives and objects.

ECMAScript uses camel case for both methods and properties, although the multiword property names are rare (lastIndex and ignoreCase properties of regular expression objects).

Other Naming Patterns

Sometimes developers use a naming convention to make up or substitute language features.

For example, there is no way to define constants in JavaScript (although there are some built-in such as Number.MAX_VALUE), so developers have adopted the convention of using all-caps for naming variables that shouldn’t change values during the life of the program, like:

1
2
3
// precious constants, please don't touch
var PI = 3.14,
    MAX_WIDTH = 800;

There’s another convention that competes for the use of all caps: using capital letters for names of global variables. Naming globals with all caps can reinforce the practice of minimizing their number and can make them easily distinguishable.

Another case of using a convention to mimic functionality is the private members convention. Although you can implement true privacy in JavaScript, sometimes developers find it easier to just use an underscore prefix to denote a private method or property. Consider the following example:

01
02
03
04
05
06
07
08
09
10
11
12
var person = {
    getName: function () {
        return this._getFirst() + ' ' + this._getLast();
    },
 
    _getFirst: function () {
        // ...
    },
    _getLast: function () {
        // ...
    }
};

In this example getName() is meant to be a public method, part of the stable API, whereas _getFirst() and _getLast() are meant to be private. They are still normal public methods, but using the underscore prefix warns the users of the person object that these methods are not guaranteed to work in the next release and shouldn’t be used directly. Note that JSLint will complain about the underscore prefixes, unless you set the option nomen: false.

Following are some varieties to the _private convention:

  • Using a trailing underscore to mean private, as in name_ and getElements_()
  • Using one underscore prefix for _protected properties and two for __privateproperties
  • In Firefox some internal properties not technically part of the language are available, and they are named with a two underscores prefix and a two underscore suffix, such as __proto__ and __parent__

Writing Comments

You have to comment your code, even if it’s unlikely that someone other than you will ever touch it. Often when you’re deep into a problem you think it’s obvious what the code does, but when you come back to the code after a week, you have a hard time remembering how it worked exactly.

You shouldn’t go overboard commenting the obvious: every single variable or every single line. But you usually need to document all functions, their arguments and return values, and also any interesting or unusual algorithm or technique. Think of the comments as hints to the future readers of the code; the readers need to understand what your code does without reading much more than just the comments and the function and property names. When you have, for example, five or six lines of code performing a specific task, the reader can skip the code details if you provide a one-line description describing the purpose of the code and why it’s there. There’s no hard and fast rule or ratio of comments-to-code; some pieces of code (think regular expressions) may actually require more comments than code.

The most important habit, yet hardest to follow, is to keep the comments up to date, because outdated comments can mislead and be much worse than no comments at all.

编写高质量js代码的更多相关文章

  1. 编写高质量JS代码的68个有效方法(八)

    [20141227]编写高质量JS代码的68个有效方法(八) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  2. 编写高质量JS代码的68个有效方法(七)

    [20141220]编写高质量JS代码的68个有效方法(七) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  3. 编写高质量JS代码的68个有效方法(六)

    [20141213]编写高质量JS代码的68个有效方法(六) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  4. 编写高质量JS代码的68个有效方法(四)

    [20141129]编写高质量JS代码的68个有效方法(四) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  5. 编写高质量JS代码的68个有效方法(三)

    [20141030]编写高质量JS代码的68个有效方法(三) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  6. 编写高质量JS代码的68个有效方法(二)

    [20141011]编写高质量JS代码的68个有效方法(二) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  7. JavaScript手札:《编写高质量JS代码的68个有效方法》(一)(1~5)

    编写高质量JS代码的68个有效方法(一) *:first-child { margin-top: 0 !important; } body>*:last-child { margin-botto ...

  8. 编写高质量JS代码中

    前段时间看了几道关于前端javascript的面试题目,方觉函数调用模式等基础的重要性.于是,下定决心,好好补补基础,即便不能深入语言的内部设计模式,也要对基本面向对象概念有比较深入的理解. 继续上一 ...

  9. 编写高质量JS代码的68个有效方法(十三)

    No.61.不要阻塞I/O事件队列 Tips: 异步API使用回调函数来延缓处理代价高昂的操作以避免阻塞主应用程序 JavaScript并发的接收事件,但会使用一个事件队列按序地处理事件处理程序 在应 ...

  10. 编写高质量JS代码的68个有效方法(十)

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

随机推荐

  1. 使用EPEL和REMI第三方yum源

    http://dl.fedoraproject.org/pub/epel/ epel-release-latest-.noarch.rpm redhat5 epel-release-latest-.n ...

  2. (转)void指针(void *的用法)

    指针有两个属性:指向变量/对象的地址和长度 但是指针只存储地址,长度则取决于指针的类型 编译器根据指针的类型从指针指向的地址向后寻址 指针类型不同则寻址范围也不同,比如: int*从指定地址向后寻找4 ...

  3. Creating a simple static file server with Rewrite--reference

    Today, I’d like to take a quick moment to demonstrate how to make a simple file server using Rewrite ...

  4. Android(java)学习笔记226:服务(service)之为什么使用服务

    1.服务 service 长期在后台运行的进程,一般没有应用程序界面   2.进程线程和应用程序之间的关系 应用程序开启,系统启动一个Linux进程,所有的组件都是运行在同一个进程的同一个线程(mai ...

  5. 解决JFreeChart中文乱码问题

    以下是解决X轴,Y轴中文字符乱码的代码: JFreeChart chart = ChartFactory.createBarChart3D("管网损耗",// 图表标题 " ...

  6. HDU 5037 Frog(贪心)

    题意比较难懂,一只青蛙过河,它最多一次跳L米,现在河中有石头,距离不等,上帝可以往里加石头,青蛙非常聪明,它一定会选择跳的次数最少的路径.问怎么添加石头能让青蛙最多的次数.输出青蛙跳的最多的次数. 考 ...

  7. Foudation框架之一些常用结构体和常用类

    表示范围作用的结构体:NSRange:       有三种方式创建新的NSRange: 1.NSRange range:            range.location = 17;         ...

  8. iOS8中添加的extensions总结(一)——今日扩展

    通知栏中的今日扩展 分享扩展 Action扩展 图片编辑扩展 文件管理扩展 第三方键盘扩展 注:此教程来源于http://www.raywenderlich.com的<iOS8 by Tutor ...

  9. MySQL where

    MySQL where 子句 我们知道从MySQL表中使用SQL SELECT 语句来读取数据. 如需有条件地从表中选取数据,可将 WHERE 子句添加到 SELECT 语句中. 语法 以下是SQL ...

  10. [C++] namespace相关语法

    本段测试代码包括如下内容: (1) 如何访问namespace中声明的名称:(2) namespace导致的相关冲突:(3) namespace可嵌套:(4) 可以在namespace中使用using ...