This is a blog post of our Code Reading Wednesdays from Codacy (http://www.codacy.com): we make code reviews easier and automatic.
Recently I have been working mostly in Javascript static code analysis and error detection. In this blog post I will to dive into Javascript scoping details, try to explain a few common errors that we, as developers, make and show how you can prevent those issues.
Javascript Scopes
Much has been written about Javascript scopes. It is a difficult concept to master and even experienced programmers sometimes make incorrect assumptions about how scopes work in Javascript. This is not a comprehensive explanation, but I will try to cover a few important details.
Function-scope vs Block-scope
Unlike other languages, Javascript has function level scope. This means that the declared variables/functions are accessible from anywhere within the function they were declared in (or from the global scope, if that is the case). This contrasts with block-scope which restricts the variable scope to the block they were declared in. Consider the following code:
var a = 3;
function foo() { var a = 2 console.log(a); }
if(true) { var a = 1; console.log(a) }
foo(); console.log(a) If you execute this, it will print 1 2 1. The if statement does not create a new scope, therefore, the variable declaration inside it belongs to the global scope and overrides the previously defined value of a. On the other hand, we can see that the function foo manipulates its own definition of a.
Hoisting In Javascript, functions and variables are always declared at the beginning of their scope. If you didn't declare them at the beginning of the scope, the Javascript interpreter will move them there. This feature, usually referred to as hoisting, may cause unexpected results for those who are unaware of it.
Let's look at some examples:
console.log(a); var a = 1;
Usually when you try to use an undeclared variable you get a ReferenceError, but in this case the output is undefined because a was hoisted to the beginning of the scope. Here is what was actually executed by the interpreter:
var a;
console.log(a); //undefined a = 1; Notice how the variable declaration is moved to the top, but the initialisation remains in the same line. Hoisting is also applied to function declarations, here is another example:
var a = 1; function a() { return 2; }
console.log(a) //1 console.log(a()) //TypeError: a is not a function Here, the declarations are moved to the top and the assignment a = 1 overrides the function body. Note that function expressions, that usually take the form of var a = function () { }, follow the same rules as variable declarations, which means that the variable name will be hoisted but not the function body. A more detailed explanation of the differences between function declarations and function expressions can be found here.
As you can see, declaring variables and functions with the same name can cause a lot of problems. Ideally, all variables should be declared at the top of the scope to avoid confusion.
Implicit declarations Implicitly declared variables do not throw an error in Javascript. Instead, they are added as a property to the global scope.
var a = 1; function createGlobal(){ b = 2; } createGlobal();
console.log(a); //1 console.log(b); //2 After invoking the function createGlobal, the variable b was added to the global scope and became accessible from anywhere. Using global variables is already a bad practice; silently declaring a global variable with an implicit statement only makes it worse.
One way to prevent this from happening is to enable Javascript strict mode because it forbids variable declaration without the var identifier, among other features.
Common Javascript errors
Besides scoping problems, there are many other language gotchas that you should be careful with, specially if you are new to Javascript. Here are a few common ones: Use '===' instead of '==' The difference between these operators is simple:
- === compares two values
- == compares two values after making the necessary type conversions
A comprehensive explanation of how these type conversions are performed can be found in the ECMAScript 5.1 documentation, but I think that you will get the idea with a few examples:
1 == "1" //true because "1" is converted to 1 1 === "1" //false
1 == true //true because true is converted to 1 1 === true //false
undefined == null //true - specific case detailed in the documentation undefined === null //false
"hello" == "hello" //true "hello" === "hello" //true The == comparison is dangerous because its behaviour depends on the type of data being compared. Most of the time, you want to make an explicit comparison without any surprises so you should use ===.
Always specify radix parameter in parseInt The second argument of the parseInt function is used to specify a radix for the number conversion. If no radix is specified, the function can return unexpected results. For example, if the string begins with a 0, the string may be interpreted as an octal number:
parseInt("032") //returns 26 parseInt("032", 10) //returns 32
Recent browsers already cast the string to a decimal number in this case, but, in order to prevent compatibility problems, the radix argument should always be used. string.replace() only replaces the first occurrence To replace all occurrences within a string you must specify the /g flag to the string.replace method, otherwise it will only replace the first occurrence of the pattern:
var zed = "Zed's dead, baby. Zed's dead."
zed.replace(/dead/, "alive") //Zed's alive, baby. Zed's dead. zed.replace(/dead/g, "alive") //Zed's alive, baby. Zed's alive.
Missing parameters on function call All function parameters in Javascript are optional. This means that you can always invoke a function with less parameters than it expects:
function concat(a, b) { return a + b }
concat("hello"); //"helloundefined"
While sometimes intended, this is a frequent source of bugs because the function call does not throw any errors at all. The body is executed and the unspecified arguments have the value undefined. In order to prevent this, you could set a default value for each parameter or throw an error if any of them is undefined.
Conclusion
Writing bug free and maintainable code is a challenge that we face everyday. Despite being the assembly of the web, Javascript language leaves some room for ambiguity. A good code style reviewed by your team as whole is a great starting point to make sure some of these issues don’t get into production.
Next time I will try to analyze open source projects and detect some of these issues in real world products.
Share your opinion on /r/javascript
(shameless plug) Codacy already has analysis patterns that detect most of the problems mentioned in this article. For example, using implicit global variables or redeclaring variables with the same name in the same scope will automatically raise a red flag. If you have other complaints or issues that you want to automatically detect, feel free to contact me to talk about your code.
Edit: We just published an ebook: “The Ultimate Guide to Code Review” based on a survey of 680+ developers. Enjoy!
About Codacy
Codacy is used by thousands of developers to analyze billions of lines of code every day!
Getting started is easy – and free! Just use your GitHub, Bitbucket or Google account to sign up.