Javascript Synopsis

  • Tutorial
I am a .NET developer. But lately more and more often I come across JavaScript. Moreover, in 50% of the cases I write something on it, in the remaining 50 - I deal with someone else's code, and even passed through minification, and sometimes obfuscation. In this article I wanted to share those points that seemed to me important for understanding the language and working effectively with it. There will be nothing new or unknown for people who have already dealt with the language, and there will not be something that cannot be found in other sources. For me, the article will be useful as a way to better understand the subject, for readers, I hope, as an occasion to refresh my knowledge.

Brendan Ike mentioned that JavaScript was created in 10 days. I think the idea has hatched longer. Be that as it may, the language has turned out and since then has only gained popularity. Especially after the advent of AJAX.

JavaScript is a language with weak dynamic implicit typing, automatic memory management, and prototype inheritance.

JavaScript consists of three distinct parts:

  • core (ECMAScript),
  • Browser Object Model or BOM
  • Document Object Model (DOM)


The article will mainly deal with the kernel. Of course, in the code examples, the DOM and BOM elements will be used, but I will not focus on them.

Type system


The JavaScript type diagram looks something like this:

  • Number
  • String
  • Boolean
  • Object
    • Function
    • Array
    • Date
    • Regexp
  • Null
  • Undefined


Approximately - because there are still types for errors that are not included in this diagram.

Of these, 5 types are primitives:

  • Number
  • String
  • Boolean
  • Null
  • Undefined


Everything else is objects. The Boolean, String, and Number primitives can be wrapped in corresponding objects. In this case, the objects will be instances of the Boolean, String, and Number constructors, respectively.

console.log('Holly Golightly' == new String('Holly Golightly')); // true
console.log('Holly Golightly' === new String('Holly Golightly')); // false
console.log('Holly Golightly' === String('Holly Golightly')); // true


Primitives have no properties. If, for example, we try to get the value of the length property from the String primitive, the primitive will be converted to an object, the property will get a property value, after which it will go somewhere to the garbage collector.

Primitives cannot add a property.

var person = 'Paul Varjak';
person.profession = 'writer';
console.log(person.profession); // undefined


What happened The primitive person was converted to an object, a property was added to the object, after which it went into oblivion.

The numbers


Numbers in JavaScript are represented by the Number type, in the language there is no division into integers, fixed-point numbers, floating-point numbers. Do not forget that operations on fractional numbers are not always accurate. For instance,

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.2 + 0.3); // 0.5


There are several special meanings in the language for the Number type: + Infinity, -Infinity, and NaN.

console.log(Infinity === Infinity) // true


And now, attention

console.log(NaN === NaN); // false


NaN is not equal at all. In case of checking for NaN, the isNaN function is built into the language. Oh yes, there isFinite function too. NaN is contagious - the result of any arithmetic operations or functions from Math with NaN is also NaN. But <<, >>, >>>, ||, |, ^, & ,! (there are even more than arithmetic ones) NaN can destroy.

console.log(NaN || 2); // 2
console.log(0 || NaN); // NaN
console.log(!NaN); // true


You need to be careful with the parseInt function, in old and new browsers it behaves differently when processing strings starting from scratch. And it is better to always indicate the number system. Older browsers perceive the leading zero as a sign of an octal number.

Lines


Strings in JavaScript are nothing more than sequences of Unicode characters. There is no separate type for single characters; instead, a string of length 1 is used.

Strings in JavaScript are immutable. That is, the line cannot be changed after creation, all operations on the lines create new objects. Strings as arguments to a function are passed by reference, not by value. But even if the same line is processed by different methods, due to the immutability of the lines, the code behaves predictably.

Do not forget that the replace function replaces only the first occurrence of a substring in a string, if the first parameter receives the string, and not the regular expression. Moreover, the regular expression must be global (must have the g modifier).

var str = "This is the house that Jack built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built".replace("Jack", "Captain Jack Sparrow");
console.log(str);
// This is the house that Captain Jack Sparrow built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built
var str = "This is the house that Jack built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built".replace(/Jack/, "Captain Jack Sparrow");
console.log(str);
// This is the house that Captain Jack Sparrow built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built
var str = "This is the house that Jack built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built".replace(/Jack/g, "Captain Jack Sparrow");
console.log(str);
// This is the house that Captain Jack Sparrow built. This is the malt That lay in the house that Captain Jack Sparrow built. This is the rat, That ate the malt That lay in the house that Captain Jack Sparrow built


By the way, a callback function can also be passed. The example below can save you from developing bicycles.

var callback =  (function(i){
  return function(a){
    i++; 
    return a + i;
  };
})(0);
var str = "This is the house that Jack built. This is the malt That lay in the house that Jack built. This is the rat, That ate the malt That lay in the house that Jack built".replace(/Jack/g, callback);
console.log(str);
// This is the house that Jack1 built. This is the malt That lay in the house that Jack2 built. This is the rat, That ate the malt That lay in the house that Jack3 built


It is important to remember that regular expressions store state, and the result of the test and exec methods depends on both arguments and state. Here are a couple of examples (thanks, sekrasoft ):

/a/g.test('aa') // true
/a/g.test('ab') // true
var re = /a/;
re.test('aa') // true
re.test('ab') // true
// но
var re = /a/g;
re.test('aa') // true
re.lastIndex // 1, в 'aa' больше одного вхождения a, конца строки не достигли
re.test('ab') // false
re.lastIndex // 0, т.к. достигли конца строки
re.test('ab') // true
re.lastIndex // 1


Cyrillic strings are best compared with the localeCompare function.

"Ёлка" > "Арбуз" // false
"Елка" > "Арбуз" // true
"Ёлка".localeCompare("Арбуз") // 1
"Елка".localeCompare("Арбуз") // 1


Premature optimization is evil. Here are a couple of examples:

jsperf.com/array-join-vs-connect

// Вариант 1
var arr = [];
for (var i = 0; i < 10000; i++) {
  arr.push(i);
}
var result = arr.join(', ')
// Вариант 2
var result = '';
for (var i = 0; i < 9999; i++) {
  result += i + ', ';
}
result += i;


The second option wins in performance.

jsperf.com/heera-string-literal-vs-object

// Вариант 1
var s = '0123456789';
for (var i = 0; i < s.length; i++) {
  s.charAt(i);
}
// Вариант 2
var s = new String('0123456789');
for (var i = 0; i < s.length; i++) {
  s.charAt(i);
}


In this case, the first option wins by a huge margin. The fact is that browser engines have already integrated optimizations for such templates.

Both double and single quotes can be used to frame strings. JSON is valid with double quotes only. The rest is to adhere to the style adopted on the project.

null and undefined


null and undefined are primitives that do not have objects corresponding to them. Therefore, an attempt to add a property to one of these primitives or to get the property value, unlike strings, numbers, and Boolean values, will result in a TypeError.
Semantically null and undefined are similar, but there are differences. null means no object, undefined means no value as such. null is a keyword, undefined is a global context property. True, in modern browsers, assigning it a different value does not work. Any unassigned variable defaults to undefined.

The objects


Objects in JavaScript are associative arrays.

An empty object can be created in several ways.

var obj = {};
var obj1 = new Object();
var obj2 = Object.create(null);


The first method is called literal and is recommended for use.

You can also create an object through the constructor function.

function Company(name, address){
  this.name = name;
  this.address = address;
}
var company = new Company('Sing-Sing', 'Ossining, Westchester County, New York, United States');


There are two main ways to access the properties of the created object.

obj.name = 'Tiffany'
var name = obj.name;


and

obj['name'] = 'Tiffany';
var name = obj['name'];


The key in JavaScript objects is always a string, so they cannot be used as dictionaries with arbitrary keys. If you try to use a non-string as the key, then the value used will be cast to the string.

var obj = {};
obj[1] = 1;
obj[null] = 2;
obj[{}] = 3;
obj[{a: 1}] = 4;
var val = obj[{}] // 4
Object.getOwnPropertyNames(obj); // ["1", "null", "[object Object]"]


You can see that {} and {a: 1} were cast to the same value - "[object Object]", and the number and null were converted to the corresponding lines.

The language has the ability to create properties with getters and setters. There are several ways to do this.

Literal:

var consts = {
  get pi(){
    return 3.141592;
  }, set pi(){
    throw new Error('Property is read only');
  }
};


Using the Object.defineProperty Function:

var consts = {};
Object.defineProperty(consts, ‘pi’, {
  get : function () {
    return 3.14159265359;
  }, set: function(){
    throw new Error('Property is read only');
  }
});


Using the Object.defineProperties function:

var consts = {};
Object.defineProperties(consts, {'pi': {
  get : function () {
    return 3.14159265359;
  }, set: function(){
    throw new Error('Property is read only');
  }}, 'e': {
  get : function () {
    return 2.71828182846;
  }, set: function(){
    throw new Error('Property is read only');
  }
}});


Functions


In javascript, functions are objects of the built-in Function class. They can be assigned to variables, passed as parameters to functions, returned as a function result, and access their properties.

Functions are named:

function getAnswer(){
  return 42;
}


and anonymous:

var getAnswer = function(){
  return 42;
}


You can pass as many parameters as you like into the function. All of them will be available through the arguments object. In addition, this object will have length properties - the number of arguments and callee - a link to the function itself.

function toArray(){
  return Array.prototype.slice.call(arguments);
}
toArray(1, 2, 3, 6, 'Tiffany'); // [1, 2, 3, 6, "Tiffany"]


A link to the function itself allows you to create recursive anonymous functions.

var fibonacci = function(n){
  if (n <= 1){
    return n;
  } else { 
    return arguments.callee(n - 2) + arguments.callee(n - 1);
  }
}
console.log(fibonacci(22)); // 17711


In the latest edition of the standard, this property has been removed. But you can write like this:

var fibonacci = function f(n){
  if (n <= 1){
    return n;
  } else { 
    return f(n - 2) + f(n - 1);
  }
}
console.log(fibonacci(22)); // 17711


The scope of variables declared via var in JavaScript is limited by the function. The let keyword is on the way, it will set the block scope, but while browsers support it reluctantly.

Often do it. This is called a self-executing function or immediately invoked function expression.

(function(){
  var person = new Person();
  // do something
})();


I think this is a good practice. Thus, it turns out not to clog the global scope with unnecessary variables.

This keyword


The value of this in JavaScript is independent of the object in which the function is created. It is determined during a call.

In a global context:

console.log(this); // Window


As a property of an object:

var obj= {
  data: 'Lula Mae'
};
function myFun() {
  console.log(this);
}
obj.myMethod = myFun;
obj.myMethod(); // Object {data: "Lula Mae", myMethod: function} 


Like a regular function:

var obj = {
  myMethod : function () {
    console.log(this);
  }
};
var myFun = obj.myMethod;
myFun(); // Window


Executing via eval (do not use eval):

function myFun() {
  console.log(this);
}
var obj = {
  myMethod : function () {
    eval("myFun()");
  }
};
obj.myMethod(); // Window


Using call or apply methods:

function myFunc() {
  console.log(this);
}
var obj = {
  someData: "a string"
};
myFunc.call(obj); // Object {someData: "a string"}


In the constructor:

var Person = function(name){
  this.name = name;
  console.log(this);
}
var person = new Person('Lula Mae'); // Person {name: "Lula Mae"}


By the way, constructor functions are usually capitalized.

More recently, the bind method has appeared, which binds a function to a context. More precisely, it does not just bind the function to the context, it creates a new function with the specified context, unlike call and apply.

var myFunc = function() {
  console.log(this);
}.bind(999);
myFunc(); // Number {[[PrimitiveValue]]: 999}


Short circuits


JavaScript is designed so that nested functions have access to variables of external functions. This is the closure.

Let us return to the example of Jack.

var callback =  (function(i){
  return function(a){
    i++; 
    return a + i;
  };
})(0);
var str = 'Jack Jack Jack'.replace(/Jack/g, callback);
console.log(str); // Jack1 Jack2 Jack3


It so happened that a function that takes an argument a has access to the variables of an external function. And each time when calling the internal function, we increase by 1 the counter variable i.

A simpler example:

function add(a) {
  var f = function(b) {
    return a+b;
  };
  return f;
}
console.log(add(5)(7)); // 12


Let's see what happened.

When a function is called, its context is created. It is convenient to consider it simply an object in which each variable of the function corresponds to a property with its name. And when they call nested functions, they get a link to the context of an external function. When accessing a variable, a search is performed in the context of the function, then in the context of the external function, and so on.

Call add (5)

  1. Generated by [[scope]] = {a: 5}
  2. The function f = function (b) {return a + b; }
  3. The f function gets a reference to [[scope]]
  4. A reference to the function f is added to [[scope]]
  5. Returns a reference to the function f


Call add (5) (7)

  1. Generated by [[scope2]] = {b: 7}
  2. A property is searched for in the [[scope2]] object. Not found.
  3. A property is searched for in the [[scope]] object. Found, the value is 5.
  4. Производится поиск свойства b в объекте [[scope2]]. Найдено, значение равно 7.
  5. Складываются 5 и 7.
  6. Возвращается результат сложения — число 12.


Передача параметров


function myFunc(a, b){
  console.log('myFunc begins');
  console.log('myFunc ' + a);
  console.log('myFunc ' + b);
}
function getArgument(arg){
  console.log('getArgument ' + arg);
  return arg;
}
myFunc(getArgument(5), getArgument(7));
// getArgument 5
// getArgument 7
// myFunc begins
// myFunc 5
// myFunc 7 


So what? Firstly, it can be seen that the arguments are calculated before they are passed to the function, this is the so-called strict strategy for processing parameters ... Secondly, they are calculated from left to right. This behavior is defined by the standard and is implementation independent.

Are values ​​passed by reference or by value? Primitives, except strings, are passed by value. Strings are passed by reference, and compared by value. Since strings are immutable, this saves memory and does not have any side effects. Objects are passed by reference. It should be noted that before passing the argument by reference to the function, a copy of the link is created, which exists only inside the called function. That is, in other words, objects are passed by reference value. Let's look at an example:

var a = { data: 'foo' };
var b = { data: 'bar' };
function change(arg){
  arg.data = 'data';
}
function swap(x, y){
  var tmp = x;
  x = y;
  y = tmp;
}
change(a);
swap(a, b);
console.log(a); // Object {data: "data"} 
console.log(b); // Object {data: "bar"}


It turns out that you can change the property of an object - a copy of the link refers to the same object. And when assigning a new value to a link, it only affects the copy that was passed to the function, and not the original link.

Variable bubbling and name resolution


Let's look at such code.

var a = 1; 
function b(){
  console.log(a); 
  if (false){
    var a = 2;
  }
} 
b(); // undefined


Why not 1? The fact is that for variables declared through var, the scope is limited by the function, and also that there is a mechanism for floating up variables. The language interpreter always carries the declaration of all variables to the beginning of the scope. In this case, only the announcement is transferred, and the value assignment is not transferred. The code above is equivalent to the following:

var a = 1; 
function b(){
  var a;
  console.log(a); 
  if (false){
    a = 2;
  }
} 
b();


The algorithm for finding an object by name is:

  1. Search among language predefined variables. If found, use it.
  2. Search among formal function parameters. If found, use it.
  3. Search among declared functions for the current scope. If found, use it.
  4. Search among declared variables for the current scope. If found, use it.
  5. Go to the scope above and start over.


The exception confirms the existence of a general rule where these exceptions are not specified. The arguments variable is just such an exception. Although it is a language-predefined variable, the formal arguments parameter takes precedence when searching for a value.

function a(){
  console.log(arguments);
}
function b(arguments){
  console.log(arguments);
}
a(); // []
b(); // undefined


Inheritance


Unlike languages ​​such as Java, C #, C ++, objects are not inherited in JavaScript. However, class is a reserved word; you cannot name a variable like that.

Each object contains a link to another object called a prototype. The prototype contains a link to your prototype and so on. At some point, an object with a null prototype is located, and the chain ends.

It is believed that the prototype inheritance model is more powerful than class-based inheritance. There is the following argument in favor of such a judgment: inheritance on classes is implemented on top of the prototype rather easily, but on the contrary, no.

It has already been mentioned that objects in JavaScript are simply associative property dictionaries. Now it turns out that there is still a hidden property, we will denote it [[Prototype]], which cannot be used in the code, and which serves as a “reserve” source of properties. Let's see how a property search is in this case.

Suppose we have such a prototype chain

{a: 1, b: 2} ---> {b: 3, c: 4} ---> null

console.log(o.a); // 1


The object has its own property a, so just take its value.

console.log(o.b); // 2


The object has its own property b, so just take its value. The prototype also has such a property, but we do not test it. This is called property overlap.

console.log(o.c); // 4


The object does not have property c. But it is in the prototype, we use the property of the prototype.

console.log(o.d); // undefined


There is no d property in the object. We are looking for a prototype, it is not there either. We continue the search in the prototype of the prototype, and it is null. We stop the search, the property was not found, return undefined.

With methods, everything happens exactly the same. Still, methods are also properties of an object. One caveat - the this keyword in a function will point to an object, not a prototype, even if the function is found in the prototype. In principle, this is logical.

How to assign a prototype to an object? There are several ways.

Firstly, prototypes are assigned automatically when creating objects, arrays, functions.

var o = {a: 1}; // o ---> Object.prototype ---> null
var a = ["horse", "table"]; // a ---> Array.prototype ---> Object.prototype ---> null
function getRandomNumber(){
  return 4;
}
// getRandomNumber ---> Function.prototype ---> Object.prototype ---> null


Secondly, prototypes can be assigned when creating objects through the constructor. In this case, the prototype of the object becomes the prototype of the constructor.

function Animal(){
  this.eat = function(){
    console.log('eat called');
  };
}
function Cat(name){
  this.name = name;
};
Cat.prototype = new Animal();
var cat = new Cat('No name cat');
cat.eat(); // eat called
console.log(cat.name); // No name cat
console.log(cat.constructor); // Animal


The [[Prototype]] property is set to Cat.prototype when new cat () is executed. However, the constructor property of the cat object was assigned the value Animal. You can fix the code so that the constructor remains correct. Add the line Cat.prototype.constructor = Cat;

function Animal(){
  this.eat = function(){
    console.log('eat called');
  };
}
function Cat(name){
  this.name = name;
};
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat = new Cat('No name cat');
cat.eat(); // eat called
console.log(cat.name); // No name cat
console.log(cat.constructor); // Cat


Thirdly, a prototype can be assigned when creating an object using the Object.create method. The prototype is indicated in the first argument of this method.

var a = {a: 1}; // a ---> Object.prototype ---> null
var b = Object.create(a); // b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1


You can’t just assign a prototype. prototype is a property of the constructor, not the object.

var o = { a: 1 };
o.prototype = { b: 2 };
console.log(o.b); // undefined


But you can change the prototypes of built-in types, for example, Object. This is bad practice. The only acceptable case for changing built-in prototypes can only be emulation of features from newer versions of the language.

Strict mode


This mode is enabled by directive

'use strict';


This directive means that the code will be executed in accordance with the ECMAScript 5 standard. That is, some things will work differently. Perhaps more logical and more correct, but not the same as before. The directive can be applied to the entire script or to a single function, including nested functions. Nested refers to functions declared inside a function. If the function is declared elsewhere, and inside the “strict” function it is only executed, then the directive does not act on it. This is clearly seen in the example:

function a(){
  console.log(arguments.callee);
}
(function() {
  "use strict";
  function b(){
    console.log(arguments.callee);
  }
  a(); // function a(){...}
  b(); // TypeError
})();


And the self-invoking function is here so that the example works in the browser console. “Use strict” in the console does not work outside the function.

What will change? Firstly, the arguments.callee property will no longer be available, as already mentioned.

Secondly, this will not be replaced with a global object in the case of null or undefined or will wrap in a constructor instance in the case of a primitive.

(function() {
  "use strict";
  var a = function(){
    console.log(this);
  }.bind(null)	
  a(); // null
})();
(function() {
  var a = function(){
    console.log(this);
  }.bind(null)	
  a(); // Window
})();


Third, it will not be possible to create global variables without explicitly declaring them.

(function() {
  "use strict";
  a = 0; // ReferenceError: a is not defined
})();


Fourth, the with (obj) {} construct will no longer be supported

. Fifth, it will not be possible to create an object with the same keys.

(function() {
  "use strict";
  var o = { p: 1, p: 2 }; // SyntaxError: Duplicate data property in object literal not allowed in strict mode
})();


This is not all, but I will not list everything.

Private and public


There are no private and public keywords in the language, but you can separate private and public data. There are several ways of this day, for example Module Pattern:

blackBox = (function(){
  var items = ['table', 'horse', 'pen', 48];
  return {
    pop: function(){
      return items[~~(Math.random() * items.length)];
    }
  };
})();
console.log(blackBox.pop()); // 48
console.log(blackBox.items); // undefined


Or you can do this:

function BlackBox(){
  var items = ['table', 'horse', 'pen', 48];
  this.pop = function(){
    return items[~~(Math.random() * items.length)];
  };
}
var blackBox = new BlackBox();
console.log(blackBox.pop()); // "pen"
console.log(blackBox.items); // undefined


Debugging


The debugger statement calls the debugger, if one is available. In this case, the execution of the script stops on the line with this instruction.

For example, if you have to figure out where the pop-up message came from, you can run it in the console:

window.alert = function(){
  debugger;
};


Now, instead of the message, the debugger will start, and the script will stop at the place of the alert call.

Sometimes it is useful to log the call stack of a function. You can do it this way:

function a(){
  console.log(new Error().stack);
}
function b(){
  a();
}
function c(){
  b();
}
c();

Also popular now: