The author | Yang Tong edit | fan deer

He joined Tencent in June 2016 and is currently engaged in the r&d of internal platform tools in SNG social network Quality Department. Familiar with PHP, JS, CSS, like to play guitar.

Understand JavaScript floating point numbers

As defined by the IEEE754 standard, all numbers in JavaScript are double precision floating point numbers, or 64-bit encoded numbers. Most JavaScript arithmetic operators can evaluate integers, floating-point numbers, or a combination of the two. But bitwise operators are special, and JavaScript does not directly evaluate operands as floating-point numbers. These steps are required to complete the operation:

1. Convert operands 8 and 1 to 32-bit integers.

2, each bit by bit or operation;

3. Convert the result to a 64-bit floating point number. Such as:

8 | 1; / / / / 9 000000000000000000000000000001000 | 000000000000000000000000000001001 = 000000000000000000000000000001001Copy the code

Floating-point calculations are imprecise, and floating-point operations can only be rounded to the nearest representable real number. When performing a series of operations, the results become less and less accurate as rounding errors accumulate. Such as:

0.1 + 0.2; //0.30000000000000004 0.1 + 0.2 + 0.3; / / 0.6000000000000001Copy the code

The associative law in addition sometimes doesn’t hold for floating point numbers in JavaScript:

(0.1 + 0.2) + 0.3; //0.6000000000000001 0.1 + (0.2 + 0.3); / / 0.6Copy the code

Beware of floating-point numbers. A simple strategy for solving their inaccuracy is to convert floating-point numbers to integers, which are computed precisely without worrying about rounding errors.

Beware of implicit casts

In JavaScript, the + operator overloads both numeric addition and string concatenation, depending on the type of argument, as summarized below:

(1) If both operands are numeric, normal addition is performed

(2) If one operand is a string, the other operand is converted to a string, and then string concatenation is performed

(3) If one of the operands is an object, a value, or a Boolean, return the result of toString if the toString method exists and returns the primitive type. If the toString method does not exist or returns a non-primitive type, call the valueOf method. If the valueOf method exists and returns primitive data, return the result of valueOf. Otherwise, an error is thrown. If undefined, null, or NaN, the String() function will be called to get the String values’ undefined ‘, ‘null’, and ‘NaN’, and then operate according to case (2)

The arithmetic operators -, *, /, and % all attempt to convert their arguments to numbers before being evaluated, briefly summarized as follows:

(1) If both operands are numeric, the general operation is performed

(2) If one of the numbers is NaN, the result is NaN

(3) If there is an operand string, Boolean, null, or undefined, the Number() method is called to convert it to a value before the operation

(4) If one of the operands is an object, return the result of valueOf if valueOf exists and returns primitive data. Returns the result of toString if toString exists and returns primitive data. Otherwise, an error is thrown. Then follow the above rules.

Therefore, the valueOf() and toString() methods should be overridden at the same time and return the same numeric string or numeric representation so that you don’t get unexpected results when forcing implicit conversions. Logical operators | |, & & can accept any value as a parameter, parameters of implicit coercion will be into a Boolean value. There are six false values in JavaScript: false, 0, “”, NaN, null, and undefined, and all the other values are true. Use typeof instead of if to check whether a function is undefined:

Function isUndefined(a){if (typeof a === 'undefined'){// or a === undefined console.log('a is not defined')}}Copy the code

Avoid using mixed types= =The operator

"1.0e0" == {valueOf: function(){return true}};   //true
Copy the code

When comparing two parameters, the equality operator == will perform implicit conversion according to the rules to determine whether the two values are equal. The equivalence operator === is the safest. J briefly summarize the implicit conversion rule of == :

Use global objects as little as possible and always declare local variables

Defining global variables contaminates the shared common namespace, can lead to unexpected naming conflicts, is not conducive to modularity, and leads to unnecessary coupling between individual components in a program. Global variables are bound to the global Window object in the browser. Adding or modifying global variables automatically updates the global object, and updating the global object automatically updates the global namespace.

window.foo;  //undefined
var foo = 'global foo';
window.foo;  //"global foo"
window.foo = 'changed'
foo;  //changed
Copy the code

JavaScript simply treats variables that are not declared with VAR as global variables, and if you forget to declare local variables, the changes are implicitly converted to global variables. Local variables should always be declared using var.

function swap(array, i, j){ var temp = a[i]; A [I] = a[j]; a[I] = a[j]; a[j] = temp; }Copy the code

5. Understand variable promotion

JavaScript does not support block-level scoping, where variable definitions are not scoped by the nearest enclosing statement or code block, but by the functions that contain them. Let’s look at an example.

function test(params) { for(var i = 0; i < 10; i++){ var params = i; } return params; } test(20); / / 9Copy the code

A local variable params is declared in the for loop. Since JavaScript does not support block-level scope, params redeclares the function parameter params, resulting in a value that is not passed in.

To understand JavaScript variable declarations, you need to think of a declared variable as consisting of two parts: declaration and assignment. JavaScript implicitly elevates the declaration section to the top of the enclosing function, leaving the assignment where it is. That is, the scope of a variable is the entire function, which is assigned at the location where the = statement occurs. The first method is implicitly promoted by JavaScript, equivalent to the second method. It is recommended to manually upgrade the declaration of local variables to avoid confusion.

function f() { function f() { /*do something*/ var x; / /... / /... {{/ /... / /... var x = /*... */ x = /*... * / / /... / /... }}}}Copy the code

One exception to JavaScript’s lack of block-level scope is exception handling, where try-catch statements bind the caught exception to a variable whose scope is only the catch block. The following example shows that changing the value of x in the catch block does not affect the originally declared value of x, indicating that the scope of the variable is only the catch block.

function test(){
    var x = 'var', result = [];
    result.push(x);
    try{
        throw 'exception';
    } catch(x){
        x = 'catch';
    }
    result.push(x);
    return result;
}
test();  //["var", "var"]
Copy the code

6. Master higher-order functions

Higher-order functions are those that take functions as arguments or return values and are a more abstract kind of function. Functions as arguments (actually callback functions) are used a lot in JavaScript:

,2,1,1,4,9 [3]. Sort (function () {if (x < y) {return - 1; } if(x > y){ return 1; } return 0; }); / / var name =,1,2,3,4,9 [1] [' tongyang ', 'Bob', 'Alice']. name.map(function(name){ return name.toUpperCase(); }); //['TONGYANG', 'BOB', 'ALICE']Copy the code

Learning to use higher-order functions often simplifies code and eliminates boilerplate code. If there is duplicate or similar code, we can consider using higher-order functions.

var aIndex = "a".charCodeAt(0);   //97
var alphabet = "";
for(var i = 0; i < 26; ++i){
    alphabet += String.fromCharCode(aIndex + i)
}
alphabet;  //"abcdefghijklmnopqrstuvwxyz"

var digits = "";
for(var i = 0; i < 10; ++i){
    digits += i;
}
digits;  //0123456789

var random = "";
for(var i = 0; i < 8; ++i){
    random += String.fromCharCode(Math.floor(Math.random() * 26) + aIndex);
}
random;  //atzuvtcz
Copy the code

All three pieces of code have the same basic logic, concatenating strings according to specific rules. We rewrite this code using higher-order functions

function buildString(number, callback){
    var result = "";
    for(var i = 0; i < number; ++i){
        result += callback(i);
    }
    return result;
}

var aIndex = "a".charCodeAt(0);   //97
var alphabet = buildString(26, function(i){
    return String.fromCharCode(aIndex + i);
});
var digits = buildString(10, function(i){
    return i;
});
var random = buildString(8, function(){
    return String.fromCharCode(Math.floor(Math.random() * 26) + aIndex);
});
Copy the code

In contrast, higher-order functions are more simple, logic is more clear, master higher-order functions will improve the quality of the code, which requires more reading excellent source code, more practice in the project to master.

Reuse common array methods on array-like objects

The standard methods in Array.prototype are designed to be reusable to other objects, even if they don’t inherit from Array.

A common array-like object in JavaScript is NodeList in the DOM. Similar to the document. The getElementsByTagName such operations will query node in a Web page, and return the NodeList as the search results. We can use generic array methods on NodeLIst objects, such as forEach, map, and filter.

scriptNodeList = document.getElementsByTagName('script');
[].forEach.call(scriptNodeList, function(node){
    console.log(node.src);
});
Copy the code

Array-like objects have two basic characteristics:

(1) Has an integer length attribute

(2) The length attribute is greater than the maximum index of the object. An index is an integer whose string represents a key in the object

An array-like object can be created with an object literal:

var arrayLike = {0: "a", 1: "b", 2: "c", length: 3};
var result = [].map.call(arrayLike, function(s){
    return s.toUpperCase();
});
result;  //["A", "B", "C"]
Copy the code

Strings can also use the generic array method

var result = [].map.call("abc", function(s){
    return s.toUpperCase();
}); //["A", "B", "C"]
Copy the code

Only one Array method is not generic, the Array concat method. This method checks the parameter’s [[Class]] property. If the argument is a real array, the contents of that array are concatenated as the result; Otherwise, the parameters are concatenated as a single element.

function namesColumn() {
    return ["Names"].concat(arguments);
}
namesColumn('tongyang', 'Bob', 'Frank');  //["Names", Arguments[3]]
Copy the code

We can use the Slice method to do this

function namesColumn() {
    return ['Names'].concat([].slice.call(arguments));
}
namesColumn('tongyang', 'Bob', 'Frank');  /*["Names", "tongyang", "Bob", "Frank]*/
Copy the code

Reusing generic array methods on array-like objects can greatly reduce redundant code and improve code quality