ES6 of JavaScript (self study)

Posted by khovorka on Sun, 21 Nov 2021 01:59:17 +0100

ES6 introduction

   ES6, fully known as ECMAScript 6.0, is the next version standard of JavaScript, which was released in June 2015.

   ES6 is mainly to solve the inherent shortcomings of ES5. For example, there is no concept of class in JavaScript, but currently the JavaScript of browsers is ES5 version, and most advanced browsers also support ES6, but only realize some features and functions of ES6.

  at present, all major browsers basically support the new features of ES6. Among them, Chrome and Firefox browsers are the most friendly to the new features of ES6, and IE7~11 basically do not support ES6.

1, let and const commands

  the let command is added in ES6 to declare variables. Its usage is similar to var, but the declared variables are only valid in the code block where the let command is located.

<script>

    for (var i = 0; i < 10; i++) {
    }
    console.log(i);//10
    for (let j = 0; j < 10; j++) {
    }
    console.log(j);//ReferenceError
</script>

  the above code is in the code block, and two variables are declared with let and var respectively. Then call these two variables outside the code block. As a result, the variable declared by let reports an error, and the variable declared by var returns the correct value. This shows that the variables declared by the let are valid only in the code block in which it is located.

The   var command will cause "variable promotion", that is, the variable can be used before declaration, and the value should be undefined. This phenomenon is somewhat strange. According to general logic, variables should be used after declaration statements.

  in order to correct this phenomenon, the let command changes the syntax behavior. The variables it declares must be used after declaration, otherwise an error will be reported.

<script>

    // var situation
    console.log(foo); // Output undefined
    var foo = 2;
    // let's go
    console.log(bar); // Error ReferenceError
    let bar = 2;
</script>

    in the above code, the variable foo is declared with the var command, and the variable promotion will occur. That is, when the script starts running, the variable foo already exists, but has no value, so it will output undefined. The variable bar is declared with the let command and will not be promoted. This means that the variable bar does not exist before it is declared. If it is used, an error will be thrown.

    as long as the let command exists in the block level scope, the variables declared by it will "bind" this area and will no longer be affected by the outside.

<script>
    var tmp = 123;
    if (true) {
        tmp = 'abc'; // ReferenceError
        let tmp;
    }
</script>

    in the above code, there is a global variable tmp, but the let in the block level scope declares a local variable tmp, which causes the latter to bind the block level scope. Therefore, an error will be reported when assigning a value to tmp before the let declares the variable.

   ES6 clearly stipulates that if there are let and const commands in the block, the variables declared by the block for these commands form a closed scope from the beginning. If these variables are used before declaration, an error will be reported.

   in short, the variable is not available in the code block until it is declared with the let command. Syntactically, this is called "temporary dead zone" (TDZ).

<script>
    if (true) {
        // TDZ start
        tmp = 'abc'; // ReferenceError
        console.log(tmp); // ReferenceError
        let tmp; // TDZ end
        console.log(tmp); // undefined
        tmp = 123;
        console.log(tmp); // 123
    }
</script>

  in the above code, before the let command declares the variable tmp, it belongs to the "dead zone" of the variable tmp.

   ES6 specifies that temporary deadband and let and const statements do not have variable promotion, mainly to reduce runtime errors and prevent the use of this variable before variable declaration, resulting in unexpected behavior. Such errors are common in ES5. With this provision, it is easy to avoid such errors.

  in short, the essence of temporary deadband is that as soon as you enter the current scope, the variable to be used already exists, but cannot be obtained. You can obtain and use the variable only when the line of code declaring the variable appears.

   let cannot declare the same variable repeatedly within the same scope.

<script>
    // report errors
    function func() {
        let a = 10;
        var a = 1;
    }
    // report errors
    function func() {
        let a = 10;
        let a = 1;
    }
</script>

Therefore, you cannot redeclare parameters inside a function.

const declares a read-only constant. Once declared, the value of the constant cannot be changed.

<script>
    const PI = 3.1415;
    PI // 3.1415
    PI = 3;
    // TypeError: Assignment to constant variable.
</script>

The above code indicates that changing the value of the constant will report an error. The variable declared by const must not change its value, which means that once const declares the variable, it must be initialized immediately and cannot be assigned later.

be careful:

  the scope of   1.const is the same as that of the let command: it is valid only within the block level scope where the declaration is located.

  the constants declared by the   2.const command are not promoted, and there is also a temporary dead zone, which can only be used after the declared position.

   3.const actually guarantees that the value of the variable cannot be changed, but the data stored at the memory address pointed to by the variable cannot be changed. For simple types of data (numeric, string, Boolean), the value is stored at the memory address pointed to by the variable, so it is equivalent to a constant. However, for composite type data (mainly objects and arrays), the memory address pointed to by the variable only stores a pointer to the actual data. Const can only ensure that the pointer is fixed (that is, it always points to another fixed address). As for whether the data structure it points to is variable, it can not be controlled at all. Therefore, you must be very careful when declaring an object as a constant.

  4.ES5 has only two methods to declare variables: var command and function command. In addition to adding let and const commands in ES6, the following chapters will also mention two other methods of declaring variables: import command and class command. Therefore, ES6 has six methods to declare variables.

2, Deconstruction assignment

   ES6 allows you to extract values from arrays and objects and assign values to variables according to a certain pattern, which is called deconstructing. Previously, when assigning values to variables, you could only specify values directly.

<script>
    let a = 1;
    let b = 2;
    let c = 3;
    //ES6 allows you to write as follows.
    [a, b, c] = [1, 2, 3];
</script>

   the above code indicates that the value can be extracted from the array and the variable can be assigned according to the corresponding position. In essence, this writing belongs to "pattern matching". As long as the patterns on both sides of the equal sign are the same, the variables on the left will be given corresponding values. Here are some examples of using nested arrays for deconstruction.

<script>
    let [foo, [[bar], baz]] = [1, [[2], 3]];
    foo // 1
    bar // 2
    baz // 3

    let [, , third] = ["foo", "bar", "baz"];
    third // "baz"
    let [x, , y] = [1, 2, 3];
    x // 1
    y // 3

    let [head, ...tail] = [1, 2, 3, 4];
    head // 1
    tail // [2, 3, 4]

    let [x, y, ...z] = ['a'];
    x // "a"
    y // undefined
    z // []
</script>

  if the deconstruction is unsuccessful, the value of the variable is equal to undefined.

   another case is incomplete deconstruction, that is, the pattern to the left of the equal sign matches only part of the array to the right of the equal sign. In this case, deconstruction can still succeed.

<script>
    
    let [x, y] = [1, 2, 3];
    x // 1
    y // 2
    let [a, [b], d] = [1, [2, 3], 4];
    a // 1
    b // 2
    d // 4
</script>

   if the right side of the equal sign is not an array (or strictly speaking, not a traversable structure), an error will be reported. For example, let [foo] = 1;

   deconstruction assignment allows default values to be specified.

<script>
    let [foo = true] = [];
    foo // true
    let [x, y = 'b'] = ['a']; // x='a', y='b'
    let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
</script>

   note that the strict equality operator (= = =) is used internally in ES6 to judge whether a position has a value. Therefore, the default value will take effect only when an array member is strictly equal to undefined. If an array member is null, the default value will not take effect because null is not strictly equal to undefined.

   when the default value of deconstruction assignment is an expression, the expression is evaluated lazily, that is, it will be evaluated only when it is used.

<script>
    function f() {
        return 5;
    }
    let [x = f()] = [1];
</script>

Deconstruction can be used not only for arrays, but also for objects.

<script>
    let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
    console.info(foo,bar) // aaa bbb
</script>

There is an important difference between the deconstruction of objects and arrays. The elements of the array are arranged in order, and the value of the variable is determined by its position; The attributes of the object have no order, and the variable must have the same name as the attribute in order to get the correct value. If the deconstruction fails, the value of the variable is equal to undefined.

  default values can also be specified for the deconstruction of   objects. The default value takes effect if the property value of the object is strictly equal to undefined.

<script>
    var {x, y = 5} = {x: 1};
    console.info(x) // 1
    console.info(y) // 5
</script>

   note: if you want to use a declared variable for deconstruction assignment, you will report an error let x; {x} = {x: 1}; Because the JavaScript engine will interpret {x} as a code block, resulting in syntax errors. This problem can only be solved by not writing curly braces at the beginning of the line and avoiding JavaScript interpreting them as code blocks.

let x;
({x} = {x: 1});

Strings can also be deconstructed and assigned. This is because at this point, the string is converted into an array like object.

<script>
    let [a, b, c] = 'hello';
    console.info(a) // "h"
    console.info(b) // "e"
    console.info(c) // "l"
</script>

Three, template string

In the traditional JavaScript language, the output template is usually written like this (jQuery method is used below).

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

The above method is quite cumbersome and inconvenient. ES6 introduces template string to solve this problem.

$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

   template string is an enhanced version of string, which is identified by back quotation marks (`). It can be used as an ordinary string, can also be used to define a multi line string, or embed variables in the string.

// Normal string
`In JavaScript '\n' is a line-feed.`

// Multiline string
`In JavaScript this is
 not legal.`

// Embedded variable in string
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

    the template strings in the above code are expressed in backquotes. If you need to use backquotes in the template string, you should escape it with a backslash.

  use template strings to represent multiline strings, and all spaces and indents will be retained in the output. If you don't want to wrap back and forth, you can use the trim method to eliminate it.

   variables are embedded in the template string, and the variable name needs to be written in ${}. Arbitrary JavaScript expressions can be placed inside braces, which can be used for operations, reference object properties, call other functions, etc.

   the value in braces is not a string and will be converted to a string according to general rules. For example, if there is an object in braces, the toString method of the object will be called by default.

4, String addition method

Traditionally, JavaScript has only the indexOf method, which can be used to determine whether a string is contained in another string. ES6 provides three new methods.

   includes(): returns a Boolean value indicating whether the parameter string is found.

   startsWith(): returns a Boolean value indicating whether the parameter string is at the head of the original string.

   endsWith(): returns a Boolean value indicating whether the parameter string is at the end of the original string.

<script>
    let s = 'Hello world!';
    s.startsWith('Hello') // true
    s.endsWith('!') // true
    s.includes('o') // true
</script>

   these three methods support the second parameter, which indicates the location where the search is started. s.startsWith(‘world’, 6) // true

    when the second parameter n is used, the behavior of endsWith is different from the other two methods. It targets the first n characters, while the other two methods target from the nth position to the end of the string.

   the repeat method returns a new string, indicating that the original string is repeated n times. If the parameter is decimal, it will be rounded. If the parameter of repeat is negative or Infinity, an error will be reported. If the parameter of repeat is a string, it will be converted to a number first.

'hello'.repeat(2) // "hellohello"

   ES2017 introduces the function of string completion length. If a string is not long enough, it will be completed at the head or tail. padStart() is used for head completion and padEnd() is used for tail completion.

<script>
    'x'.padStart(5, 'ab') // 'ababx'
    'x'.padStart(4, 'ab') // 'abax'
    'x'.padEnd(5, 'ab') // 'xabab'
    'x'.padEnd(4, 'ab') // 'xaba'
</script>

    in the above code, padStart() and padEnd() accept two parameters. The first parameter is the maximum length of string completion, and the second parameter is the string used to complete. If the length of the original string is equal to or greater than the maximum length, the string completion will not take effect and the original string will be returned. If the second parameter is omitted, the default is to use spaces to complete the length.

   if the sum of the length of the complement string and the original string exceeds the maximum length, the complement string exceeding the number of bits will be truncated.

'abc'.padStart(10, '0123456789')
// '0123456abc'

A common use of padStart() is to specify the number of digits for numeric completion. The following code generates a 10 bit numeric string.

'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"

  ES2019 adds trimStart() and trimEnd() methods to string instances. Their behavior is consistent with trim(), trimStart() eliminates spaces at the beginning of the string, and trimEnd() eliminates spaces at the end. They return a new string and do not modify the original string.

const s = '  abc  ';
s.trim() // "abc"
s.trimStart() // "abc  "
s.trimEnd() // "  abc"

   trimStart() only eliminates the spaces in the head and retains the spaces in the tail. trimEnd() behaves similarly. In addition to the space bar, these two methods are also effective for invisible white space symbols such as tab at the beginning (or end) of the string and line feed. The browser also deploys two additional methods. trimLeft() is the alias of trimStart() and trimlight() is the alias of trimEnd().

5, Default value of function

   before ES6, you can't directly specify default values for function parameters. You can only use alternative methods.

function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

   the above code checks whether the parameter y of the function log is assigned. If not, specify the default value as World. The disadvantage of this writing method is that if the parameter y is assigned, but the corresponding Boolean value is false, the assignment does not work. As in the last line of the above code, the parameter y is equal to the null character, and the result is changed to the default value. To avoid this problem, you usually need to judge whether the parameter y is assigned, and if not, it is equal to the default value.

   ES6 allows you to set default values for function parameters, that is, write them directly after the parameter definition.

function log(x, y = 'World') {
  console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

  in addition to simplicity, the writing method of ES6 has two advantages: first, people who read the code can immediately realize which parameters can be omitted without looking at the function body or documents; Secondly, it is conducive to future code optimization. Even if this parameter is completely removed in the external interface of the future version, the previous code will not be unable to run. Parameter variables are declared by default, so they cannot be declared again with let or const.

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

   the default value of the parameter can be combined with the default value of the deconstruction assignment.

function foo({x, y = 5}) {
  console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

   if no parameters are provided during function foo call, variables x and y will not be generated, resulting in an error. This can be avoided by providing default values for function parameters.

ction foo({x, y = 5} = {}) {
  console.log(x, y);
}
foo() // undefined 5

Exercise: what is the difference between the following two writing methods?

// Writing method I
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}
// Writing method 2
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

   the above two methods set default values for function parameters. The difference is that the default value of function parameters is an empty object, but the default value of object deconstruction assignment is set; The default value of function parameter in writing 2 is an object with specific attributes, but the default value of object deconstruction assignment is not set.

// Function without parameters
m1() // [0, 0]
m2() // [0, 0]
// When both x and y have values
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x has a value and y has no value
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x and y have no value
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

6, rest parameter of function

   ES6 introduces the rest parameter (in the form of... Variable name) to obtain the redundant parameters of the function, so there is no need to use the arguments object. The variable matched with rest parameter is an array, which puts redundant parameters into the array.

function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }
  return sum;
}
add(2, 5, 3) // 10

  the following is an example where the rest parameter replaces the arguments variable.

// How to write the arguments variable
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}
// Writing method of rest parameter
const sortNumbers = (...numbers) => numbers.sort();

   after comparing the two writing methods of the above code, it can be found that the writing method of the rest parameter is more natural and concise.

  the arguments object is not an array, but an array like object. Therefore, in order to use the array method, you must first convert it into an array using Array.prototype.slice.call. The rest parameter does not have this problem. It is a real array, and array specific methods can be used. The following is an example of rewriting the array push method with the rest parameter.

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}
var a = [];
push(a, 1, 2, 3)

Note that the rest parameter cannot be followed by other parameters (that is, it can only be the last parameter), otherwise an error will be reported. function f(a, …b, c) { }

7, Arrow function

   ES6 allows you to define functions using "arrows" (= >).

var f = v => v;
// Equivalent to
var f = function (v) {
  return v;
};

   if the arrow function does not require parameters or requires multiple parameters, use a parenthesis to represent the parameter part.

f = () => 5;
// Equivalent to
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// Equivalent to
var sum = function(num1, num2) {
  return num1 + num2;
};

    if the code block of the arrow function is more than one statement, enclose them in braces and return them with the return statement.

   since braces are interpreted as code blocks, if the arrow function directly returns an object, parentheses must be added outside the object, otherwise an error will be reported.

//report errors
let getTempItem = id => { id: id, name: "Temp" };
// No error reporting
let getTempItem = id => ({ id: id, name: "Temp" });

    if the arrow function has only one line of statement and does not need to return a value, you can use the following writing method instead of writing braces.

let fn = () => void doesNotReturn();

   arrow functions can be used in conjunction with variable deconstruction.

const full = ({ first, last }) => first + ' ' + last;
// Equivalent to
function full(person) {
  return person.first + ' ' + person.last;
}

be careful:

The this object in the arrow function body is the object where it is defined, not the object where it is used.

8, Concise representation of objects

   ES6 allows you to write variables and functions directly in braces as object properties and methods. Such writing is more concise.

const foo = 'bar';
const baz = {foo};
// Equivalent to
const baz = {foo: foo};

  in addition to attribute shorthand, methods can also be shorthand.

const o = {
  method() {
    return "Hello!";
  }
};
// Equivalent to
const o = {
  method: function() {
    return "Hello!";
  }
};

9, Add attribute of object

  JavaScript defines the properties of an object in two ways.

  / / method 1 obj.foo = true;

   / / method 2 obj ['a' + 'bc'] = 123;

   the first method of the above code is to directly use the identifier as the attribute name, and the second method is to use the expression as the attribute name. At this time, the expression should be placed in square brackets.

   in ES5, only method 1 (identifier) can be used to define the properties of an object.

   ES6 allows you to use method 2 (expression) as the attribute name of the object when defining an object literally, that is, put the expression in square brackets.

let propKey = 'foo';
let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

   expressions can also be used to define method names.

let obj = {
  ['h' + 'ello']() {
    return 'hi';
  }
};
obj.hello() // hi

10, Object class

   ES5 compares whether two values are equal. There are only two operators: the equality operator () and the strict equality operator (=). Both of them have disadvantages. The former will automatically convert the data type, the NaN of the latter is not equal to itself, and + 0 is equal to - 0. JavaScript lacks an operation. In all environments, as long as the two values are the same, they should be equal. ES6 proposes the "same value equality" algorithm to solve this problem. Object.is is a new method to deploy this algorithm. It is used to compare whether two values are strictly equal, which is basically consistent with the behavior of the strict comparison operator (= = =).

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

   there are only two differences: one is that + 0 is not equal to - 0, and the other is that NaN is equal to itself.

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Object.assign()

The Object.assign method is used to merge objects and copy all enumerable attributes of the source object to the target object.

var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

The first parameter of the Object.assign method is the target object, and the following parameters are the source object.

Note that if the target object has a property with the same name as the source object, or multiple source objects have a property with the same name, the subsequent property will overwrite the previous property.

Object can also get the key, value and entries of the object.

const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
}
console.log(Object.keys(person));//["name", "age", "language"]
console.log(Object.values(person));//["jack", 21, Array(3)]
console.log(Object.entries(person));//[Array(2), Array(2), Array(2)]

11, map and reduce

  map and reduce methods are added to the array. Map (): receive a function to process all elements in the original array with this function and put them into the new array for return.

   case: there is a string array, and we want to convert it into an int array

let arr = ['1','20','-5','3'];
console.log(arr)

arr = arr.map(s => parseInt(s));
console.log(arr)

Reduce (): receive a function (required) and an initial value (optional)

The first parameter (function) receives two parameters:

  • The first parameter is the result of the last reduce process
  • The second parameter is the next element in the array to be processed

   reduce() will process the elements in the array with reduce from left to right, and take the processing result as the first parameter of the next reduce. For the first time, the first two elements will be used as calculation parameters, or the initial value specified by the user will be used as the starting parameter.

  case:

const arr = [1, 20, -5, 3]
let a1 = arr.reduce((a, b) => a + b)//19
let a2 = arr.reduce((a, b) => a * b)//-300
let a4 = arr.reduce((a, b) => a * b, 0)//0
let a4 = arr.reduce((a, b) => a * b, 1)//-300
let a4 = arr.reduce((a, b) => a * b, -1)//300

12, Promise object

  promise is a solution for asynchronous programming, which is more reasonable and powerful than traditional solutions - callback functions and events. Promise is simply a container that holds the results of an event (usually an asynchronous operation) that will not end in the future. Syntactically speaking, promise is an object from which you can get the message of asynchronous operation. Promise provides a unified API, and various asynchronous operations can be processed in the same way.

  ES6 has native support for Promise. A Promise is an object waiting to be executed asynchronously. When its execution is completed, its state will become resolved or rejected.

Requirements:

  1. Find out the current user information

  2. Find out his course according to the current user's id

  3. Find out the score according to the current course id

$.ajax({
    url: "mock/user.json",
    success(data) {
        console.log("Query user:", data);
        $.ajax({
            url: `mock/user_corse_${data.id}.json`,
            success(data) {
                console.log("Courses found:", data);
                $.ajax({
                    url: `mock/corse_score_${data.id}.json`,
                    success(data) {
                        console.log("Score found:", data);
                    },
                    error(error) {
                        console.log("An exception occurred:" + error);
                    }
                });
            },
            error(error) {
                console.log("An exception occurred:" + error);
            }
        });
    },
    error(error) {
        console.log("An exception occurred:" + error);
    }
});

After using Promise, the code looks like this:

let p = new Promise((resolve, reject) => {
    //1. Asynchronous operation
    $.ajax({
        url: "mock/user.json",
        success: function (data) {
            console.log("Query user succeeded:", data)
            resolve(data);
        },
        error: function (err) {
            reject(err);
        }
    });
});

p.then((obj) => {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: `mock/user_corse_${obj.id}.json`,
            success: function (data) {
                console.log("User course query succeeded:", data)
                resolve(data);
            },
            error: function (err) {
                reject(err)
            }
        });
    })
}).then((data) => {
    console.log("Results of the previous step", data)
    $.ajax({
        url: `mock/corse_score_${data.id}.json`,
        success: function (data) {
            console.log("Course score query succeeded:", data)
        },
        error: function (err) {
        }
    });
})

After code encapsulation is simplified:

function get(url, data) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: url,
            data: data,
            success: function (data) {
                resolve(data);
            },
            error: function (err) {
                reject(err)
            }
        })
    });
}

get("mock/user.json")
    .then((data) => {
        console.log("User query succeeded~~~:", data)
        return get(`mock/user_corse_${data.id}.json`);
    })
    .then((data) => {
        console.log("Course query succeeded~~~:", data)
        return get(`mock/corse_score_${data.id}.json`);
    })
    .then((data) => {
        console.log("Course score query succeeded~~~:", data)
    })
    .catch((err) => {
        console.log("An exception occurred", err)
    });

Topics: Javascript Front-end ECMAScript html5