📜 ⬆️ ⬇️

A brief synopsis of javascript

I am a .NET developer. But lately, I’ve increasingly come across JavaScript. Moreover, in 50 cases I write something on it, in the remaining 50 I deal with someone else's code, and even went through minification, and sometimes obfuscation. In this article I wanted to share those points that seemed important to me to understand the language and work 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 excuse to refresh knowledge.

Brendan Ike mentioned that JavaScript was created in 10 days. I think the idea was nurtured longer. Anyway, the language has turned out and since then it is only gaining popularity. Especially after the appearance of AJAX.

JavaScript is a language with weak dynamic implicit typing, automatic memory management and prototype inheritance.
')
JavaScript consists of three separate parts:



The article mainly focuses on the core. Of course, the code examples will use the elements of DOM and BOM, but I will not focus on them.

Type system


A javascript type diagram looks like this:



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

Of these, 5 types are primitives:



Everything else is objects. 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 we, for example, try to get the value of the length property from a String primitive, the primitive will be converted to an object, the object will receive 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 person primitive was converted to an object, the property was added to the object, after which it went into non-existence.

Numbers


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

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


The language has several special values ​​for the type Number: + Infinity, -Infinity, and NaN.

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


And now, attention

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


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

 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 zero. And it is better to always specify the number system. Older browsers perceive the initial zero as a sign of the octal number.

Strings


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 instead.

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 of the function are passed by reference, not by value. But even if the same string is processed by different methods, due to the immutability of strings, 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 argument is a string, and not a 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, the callback function can also be passed. The example below can keep bikes from developing.

 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 the state, and the result of the test and exec methods depends on both the arguments and the 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; 


On performance wins the second option.

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, with a huge margin wins the first option. The fact is that optimizations of such templates are already built into the browser engines.

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

null and undefined


null and undefined are primitives that have no corresponding objects. Therefore, an attempt to add a property to one of these primitives or get the value of a property, as opposed to 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 property of a global context. True, in modern browsers to assign it another value will not work. Any unassigned variable defaults to undefined.

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 a key, the value used will be converted to a 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 converted 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 class Function. They can be assigned to variables, passed as parameters in a function, returned as the result of a function, 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 want to the function. All of them will be accessible through the arguments object. In addition, in this object will be the property length - 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 to a function. The let keyword is on the way; it will set a block scope, but so far browsers support it reluctantly.

Often do so. 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 clutter the global scope with unnecessary variables.

Keyword this


The value of this in JavaScript does not depend on the object in which the function is created. It is determined during the call.

In the 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} 


As usual function:

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


Execution through 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 called with a capital letter.

And recently, the bind method has appeared, which binds a function to a context. More precisely, not just binds 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} 


Closures


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

Let's go back to the Jack example.

 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 turned out that a function that accepts argument a has access to the variables of the external function. And each time we call the internal function, we increment the counter variable i by 1.

An easier 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 just an object in which each variable of the function corresponds to a property with its name. When invoking nested functions, they receive a reference to the context of the external function. When the variable is accessed, it is searched in the context of the function, then in the context of the external function, and so on.

Call add (5)

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


Call add (5) (7)

  1. It is created [[[scope2]] = {b: 7}
  2. Searches for property a in the [[scope2]] object. Not found.
  3. Searches for property a in the [[scope]] object. Found, the value is 5.
  4. Searches for property b in the [[scope2]] object. Found, the value is 7.
  5. Stack 5 and 7.
  6. The result of the addition is the number 12.


Parameter passing


 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? First, it is clear that the arguments are calculated before they are passed to the function, this is the so-called strict parameter processing strategy ... Secondly, they are calculated from left to right. This behavior is defined by the standard and does not depend on the implementation.

Are values ​​passed by reference or value? Primitives, except strings, are passed by value. Strings are passed by reference, and are compared by value. Since strings are immutable, it saves memory and does not have any side effects. Objects are passed by reference. Here 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 function being called. 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 the property of the object can be changed - a copy of the link refers to the same object. And when assigning a new value to a link, it acts only on the copy that was passed to the function, and not on the original link.

Variable pop-up and name resolution


Let's look at this 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 via var, the scope is limited to a function, and also that there is a mechanism for the emergence of variables. The language interpreter always takes 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 predefined language variables. If found, use it.
  2. Search among the formal parameters of the function. If found, use it.
  3. Search among the declared functions of the current scope. If found, use it.
  4. Search among the declared variables of the current scope. If found, use it.
  5. Go to the area of ​​visibility above and start over.


An exception confirms the existence of a general rule where these exceptions are not specified. The variable arguments is just such an exception. Although it is a variable predefined by the language, 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 ++, not classes, but objects are inherited in JavaScript. However, class is a reserved word, so it is impossible to name a variable.

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

It is believed that the prototype model of inheritance 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 - not.

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 by [[Prototype]], which cannot be used in the code, and which serves as a “spare” source of properties. Let's see how a property is searched for in this case.

Let we have such a chain of prototypes

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

 console.log(oa); // 1 


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

 console.log(ob); // 2 


The object has its own property b, so just take its value. In the prototype, this property also exists, but we do not check it. This is called overlapping properties.

 console.log(oc); // 4 


The object has no property c. But it is in the prototype, we use the property of the prototype.

 console.log(od); // undefined 


There is no d property in the object. We are looking for in the prototype, there is none there either. We continue the search in the prototype prototype, and it is null. Stop searching, property not found, return undefined.

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

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

First, 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 constructor becomes the prototype of the object.

 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 assigned the value of Cat.prototype when new Cat () is executed. However, the constructor property of the cat object is 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 


Third, a prototype can be assigned when creating an object using the Object.create method. The prototype is specified 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(ba); // 1 


Just so you can not assign a prototype. prototype is a property of the constructor, not the object.

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


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

Strict mode


This mode is enabled by the 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 in the same way as before. The directive can be applied to the entire script or to a single function, including nested functions. Nested means functions declared inside a function. If the function is declared in a different place, and is only executed inside the “strict” function, then the directive does not act on it. The example clearly shows this:

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


And the self-calling function is there for the example to work in the browser console. “Use strict” in the console does not work outside the function.

What will change? First, the arguments.callee property will no longer be available, this has already been mentioned.

Secondly, this will not be replaced by a global object in the case of null or undefined or turned into 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 it is possible to separate private and public data. There are several ways to do this, for example the 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 instruction invokes the debugger, if available. At the same time, the execution of the script stops at 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 starts, and the script execution stops at the place where the alert is called.

- . :

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

Source: https://habr.com/ru/post/243403/


All Articles