1. let/const feature
Before the release of ES6 standard, JS generally declared variables through the keyword var. at the same time, there was no obvious code block declaration. To form code blocks, they usually adopted closure. For example:
var arr = [] for(var i = 0; i < 5; i++) { arr.push(function() { console.log(i) }) } arr.forEach(function(item) { item() })
As for why the output is all the number 5, it involves the event loop mechanism of JS. When the function in the asynchronous queue is executed, because the keyword var will not generate a code block, the parameter i = 5, and finally all the number 5 is output. Using the previous method, we can modify it as follows:
var arr = [] for(var i = 0; i < 5; i++) { (function(i) { arr.push(function() { console.log(i) }) })(i) } arr.forEach(function(item) { item() }) // Output 0 1 2 3 4
After the introduction of let and const, the two keywords will automatically generate code blocks, and there is no variable promotion. Therefore, you can output the numbers 0 to 4 by replacing the var keyword with let
var arr = [] for(let i = 0; i < 5; i++) { arr.push(function() { console.log(i) }) } arr.forEach(function(item) { item() }) // Output 0 1 2 3 4
The difference between the keyword let and const is that the value type variable declared with const cannot be re assigned, while the reference type variable can
Variable promotion is because the browser is compiling and executing JS When coding, variables and functions will be declared first, var Keyword declared variables will also default to undefined,The variable is assigned when the declaration statement is executed.
It is worth noting that in the process of refactoring the original code, we should pay great attention to the unexpected situation that using let blindly to replace var may occur:
var snack = 'Meow Mix' function getFood(food) { if (food) { var snack = 'Friskies' return snack } return snack } getFood(false) // undefined
Although the statement in if is not executed when using var, the local variable of var snap = undefined has been declared when declaring the variable, and the final output is undefined in the local variable.
let snack = 'Meow Mix' function getFood(food) { if (food) { let snack = 'Friskies' return snack } return snack } getFood(false) // 'Meow Mix'
When using let, you can't get the local snack variable in the code block (in the temporary dead zone) when you don't execute the if statement, and finally output the snack in the global variable.
The current best practice for using block level bindings is to use by default const,Use only when you really need to change the value of a variable let. In this way, the immutability of code can be realized to some extent, so as to prevent some errors.
2. Arrow function
In ES6, arrow function is the most interesting new feature. It is a new syntax that uses arrow = > to define functions. The main differences from traditional functions are:
- No this, super, arguments and new Target binding
- Cannot be called through the new keyword
- No prototype
- The binding of this cannot be changed
- arguments object is not supported
- Duplicate named parameters are not supported
This binding is a common error source in JS programs. Especially in functions, it is easy to control the value of this, which often leads to unexpected behavior.
Before the arrow function appears, when declaring the constructor and modifying the prototype, you often need to do a lot of processing on the value of this:
function Phone() { this.type = type } Phone.prototype.fixTips = function(tips) { // var that = this return tips.map(function(tip) { // return that.type + tip return this.type + tip // }) },this) //Handle this here or redirect this with the commented out method that }
To output the correct fixTips, you must store the point of this in the variable or find a context binding for it. If you use the arrow function, it is easy to implement:
function Phone() { this.type = type } Phone.prototype.fixTips = function(tips) { return tips.map(tip => this.type + tip) }
Like the above example, when we write a function, the arrow function is more concise and can simply return a value. When we need to maintain a this context, we can use the arrow function
3. String
I think ES6 has the most new features in string processing. This paper only summarizes the commonly used methods, but it is recommended that you have time to learn more about them.
.includes()
Previously, when it is necessary to judge whether a string contains some strings, it is basically judged by the return value of indexOf():
var str = 'superman' var subStr = 'super' console.log(str.indexOf(subStr) > -1) // true
Now you can simply use includes() to judge, and a Boolean value will be returned:
const str = 'superman' const subStr = 'super' console.log(str.includes(subStr)) // true
Of course, there are also two special methods. Their usage is the same as that of includes():
- startWith(): returns true if the specified text is detected at the beginning of the string
- endsWith(): returns true if the specified text is detected at the end of the string
.repeat()
Before that, we need to repeat the string. We need to encapsulate a function ourselves:
function repeat(str, count) { var strs = [] while(str.length < count) { strs.push(str) } return strs.join('') }
Now you just need to call repeat():
'superman'.repeat(2) // supermansuperman
Template string
I think the template string is also one of the most awesome features of ES6, because it greatly simplifies our processing of strings and is used very well in the development process.
First, it eliminates the need for escape processing:
var text = 'my name is \'Branson\'.' const newText = `my name is 'Branson'.`
Then it also supports inserts, line breaks, and expressions:
const name = 'Branson' console.log(`my name is ${name}.`) // my name is Branson. const text = (` what's wrong ? `) console.log(text) // what's // wrong // ? const today = new Date() const anotherText = `The time and date is ${today.toLocaleString()}.` console.log(anotherText) // The time and date is 2017-10-23 14:52:00
4. Deconstruction
Deconstruction allows us to use a simpler syntax to separate values from an array or object (even deep) and store them.
There's nothing to say about this one. Just put the code:
// Array deconstruction // ES5 var arr = [1, 2, 3, 4] var a = arr[0] var b = arr[1] var c = arr[2] var d = arr[3] // ES6 let [a, b, c, d] = [1, 2, 3, 4] // Object deconstruction // ES5 var luke = {occupation: 'jedi', father: 'anakin'} var occupation = luke.occupation // 'jedi' var father = luke.father // 'anakin' // ES6 let luke = {occupation: 'jedi', father: 'anakin'} let {occupation, father} = luke console.log(occupation) // 'jedi' console.log(father) // 'anakin'
5. Module
Before ES6, we used libraries such as Browserify to create client modularization in node JS. In ES6, we can directly use all types of modularity (AMD and CommonJS).
Exit definition of CommonJS module:
module.exports = 1 module.exports = { foo: 'bar' } module.exports = ['foo', 'bar'] module.exports = function bar () {}
Exit definition of ES6 module:
/ Expose a single object export let type = 'ios' // Expose multiple objects function deDuplication(arr) { return [...(new Set(arr))] } function fix(item) { return `${item} ok!` } export {deDuplication, fix} // Exposure function export function sumThree(a, b, c) { return a + b + c } // Bind default output let api = { deDuplication, fix } export default api // export { api as default }
// Import entire file import 'test' // Overall loading import * as test from 'test' // Import on demand import { deDuplication, fix } from 'test' // Exit encountered is export { foo as default, foo1, foo2 } import foo, { foo1, foo2 } from 'foos'
6. Parameters
Before parameters, we need to do a lot of processing whether it is default parameters, indefinite parameters or renamed parameters. With ES6, it is much simpler:
Default parameters:
// ES5 function add(x, y) { x = x || 0 y = y || 0 return x + y } // ES6 function add(x=0, y=0) { return x + y } add(3, 6) // 9 add(3) // 3 add() // 0
Indefinite parameters:
// ES5 function logArgs() { for(var i = 0; i < arguments.length; i++) { console.log(arguments[i]) } } // ES6 function logArgs(...args) { for(let arg of args) { console.log(arg) } }
Parameter naming:
// ES5 function Phone(options) { var type = options.type || 'ios' var height = options.height || 667 var width = options.width || 375 } // ES6 function Phone( {type='ios', height=667, width=375}) { console.log(height) }
Expand operation:
Find the maximum value of an array:
// ES5 Math.max.apply(null, [-1, 100, 9001, -32]) // ES6 Math.max(...[-1, 100, 9001, -32])
Of course, this feature can also be used to merge arrays:
const player = ['Bryant', 'Durant'] const team = ['Wade', ...player, 'Paul'] console.log(team) // ['Wade', 'Bryant', 'Durant', 'Paul']
7. class
We are familiar with the word object-oriented. Before that, the realization of object-oriented programming in JS was based on prototype chain. ES6 provides many kinds of syntax sugars. Through these syntax sugars, we can simplify many operations on prototype in Code:
// ES5 // Create a class function Animal(name, age) { this.name = name this.age = age } Animal.prototype.incrementAge = function() { this.age += 1 } // Class inheritance function Human(name, age, hobby, occupation) { Animal.call(this, name, age) this. hobby = hobby this.occupation = occupation } Human.prototype = Object.create(Animal.prototype) Human.prototype.constructor = Human Human.prototype.incrementAge = function() { Animal.prototype.incrementAge.call(this) console.log(this.age) }
Using syntax sugar simplification in ES6
// ES6 // Create a class class Animal { constructor(name, age) { this.name = name this.age = age } incrementAge() { this.age += 1 } } // Class inheritance class Human extends Animal { constructor(name, age, hobby, occupation) { super(name, age) this.hobby = hobby this.occupation = occupation } incrementAge() { super.incrementAge() console.log(this.age) } }
Note: Although there are many similarities between classes and custom types, we still need to keep these differences in mind:
- Function declarations can be promoted, while class declarations are similar to let declarations and cannot be promoted; Declaration statements will remain in the temporary deadband until they are actually executed.
- All code in the class declaration will automatically run in strict mode, and you can't force the code out of strict mode.
- In a custom type, you need to use object The defineproperty () method manually specifies a method that cannot be enumerated; In a class, all methods are non enumerable.
- Each class has a constructor method. Calling those methods that do not contain a constructor through the keyword new will cause the program to throw an error.
- Calling the constructor of a class in a way other than the keyword new will cause the program to throw an error.
- Modifying the class name in a class will cause the program to report an error
8. Symbols
Symbols existed before ES6. Now we can directly use a developed interface.
Symbols are immutable and unique. You can make a key in any hash.
Symbol():
Calling Symbol() or Symbol(description) can create a unique symbol, but it cannot be seen globally. One use case of Symbol() is to patch a class or namespace, but it's certain that you won't update it. For example, you want to give react Add a refreshComponent method to the component class, but you can be sure that you will not update this method later:
const refreshComponent = Symbol() React.Component.prototype[refreshComponent] = () => { // do something }
Symbol.for(key) will also create a unique and unchangeable symbol, but it can be seen globally that two identical calling symbols For (key) will return the same symbol class:
Symbol('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol('foo') // false Symbol.for('foo') === Symbol.for('foo') // true
The common use of Symbols (especially Symbol.for(key)) is for synergy. It can be implemented by looking for Symbol members in the parameters of the object in the known interface in a third-party plug-in, such as
function reader(obj) { const specialRead = Symbol.for('specialRead') if (obj[specialRead]) { const reader = obj[specialRead]() // do something with reader } else { throw new TypeError('object cannot be read') } }
In another library:
const specialRead = Symbol.for('specialRead') class SomeReadableType { [specialRead]() { const reader = createSomeReaderFrom(this) return reader } }
9. Set/Map
Before that, developers used object attributes to simulate set and map sets
// set var set = Object.create(null) set.foo = true if(set.foo) { // do something } // map var map = Object.create(null) map.foo = 'bar' var value = map.foo console.log(value) // 'bar'
Since the operations of set and map in ES6 are similar to those in other languages, this article will not introduce them more, mainly through several examples.
In ES6, a sequence table set is added, which contains some independent non repetitive values. Through the set set, the data can be accessed quickly and various discrete values can be tracked more effectively.
The most used about set should be de duplication
const arr = [1, 1, 2, 11, 32, 1, 2, 3, 11] const deDuplication = function(arr) { return [...(new Set(arr))] } console.log(deDuplication(arr)) // [1, 2, 11, 32, 3]
map is a very necessary data structure. Before ES6, we implemented hash table through objects:
var map = new Object() map[key1] = 'value1' map[key2] = 'value2'
But it doesn't prevent us from accidentally rewriting functions with some special attribute names: (I don't understand, I don't understand)
getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned') // TypeError: Property 'hasOwnProperty' is not a function
In ES6, map allows us to get, set and search the queue value:
let map = new Map() map.set('name', 'david') map.get('name') // david map.has('name') // true
The more surprising part of map is that it is not limited to using strings as keys, but also any other type of data as keys:
let map = new Map([ ['name', 'david'], [true, 'false'], [1, 'one'], [{}, 'object'], [function () {}, 'function'] ]) for(let key of map.keys()) { console.log(typeof key) } // string, boolean, number, object, function
Note: we use map When the get () method is used to test equality, the test will not work if non primitive type values such as functions or objects are used in the map, so we should use primitive type values such as strings, Boolean and Numbers.
We can also use it entries() to traverse the iteration:
for(let [key, value] of map.entries()) { console.log(key, value); }
10. Weak Set/Weak Map
For set and WeakSet, the biggest difference between them is that WeakSet saves objects that are worth weak references. The following example will show their differences:
let set = new WeakSet(), key = {} set.add(key) console.log(set.has(key)) // true // Remove object key Last strong reference to( WeakSet References in are also automatically removed) key = null
After this code is executed, the key reference in the WeakSet cannot be accessed. In addition to this, they have the following differences:
- In the instance of WeakSet, if non object parameters are passed into the add(), has() and delete() methods, the program will report an error.
- The WeakSet set is not iterative, so it cannot be used in a for of loop.
- The WeakSet collection does not expose any iterators (such as the keys() and values() methods), so the contents cannot be detected by the program itself.
- The WeakSet collection does not support the forEach() method.
- The size property is not supported in the WeakSet collection.
In short, if you only need to track object references, you should use the WeakSet collection rather than the ordinary set collection.
Before ES6, in order to store private variables, we had various methods to implement, one of which is to use the naming convention:
class Person { constructor(age) { this._age = age } _incrementAge() { this._age += 1 } }
However, naming conventions are still confusing in code and do not really keep private variables inaccessible. Now we can use WeakMap to store variables:
let _age = new WeakMap() class Person { constructor(age) { _age.set(this, age) } incrementAge() { let age = _age.get(this) + 1 _age.set(this, age) if (age > 50) { console.log('Midlife crisis') } } }
One cool thing about storing variables in WeakMap is its key. It doesn't need the attribute name. You can use reflect Ownkeys() to see this:
const person = new Person(50) person.incrementAge() // 'Midlife crisis' Reflect.ownKeys(person) // []
A more practical practice is to store DOM elements in WeakMap without polluting the elements themselves:
let map = new WeakMap() let el = document.getElementById('someElement'); // Store a weak reference to the element with a key map.set(el, 'reference') // Access the value of the element let value = map.get(el) // 'reference' // Remove the reference el.parentNode.removeChild(el) el = null
As shown above, when an object is destroyed by the garbage collection mechanism, WeakMap will automatically a key value pair about the object.
Note: to further illustrate the practicability of this example, consider how jQuery implements caching an object related to the DOM element object of the reference. Using jQuery, when a specific element is removed from the document, jQuery will automatically free up memory. Overall, jQuery is useful in any DOM library.
11. Promise
Before the emergence of ES6, asynchronous functions were mainly handled through callback functions. Although it looks good, after using too many callback functions, it will be found that too many nested callback functions will cause callback Hell:
func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }) }) }) }) })
When we have , Promise , we can convert these into vertical Code:
func1(value1) .then(func2) .then(func3) .then(func4) .then(func5, value5 => { // Do something with value 5 })
The native , Promise , has two processors: resolve (callback when , Promise , is full) and reject (callback when , Promise , is rejected):
new Promise((resolve, reject) => reject(new Error('Failed to fulfill Promise'))) .catch(reason => console.log(reason))
Promise's benefits: using some column callbacks for error handling will make the code very chaotic. Using promise, I can clearly make the error bubble and deal with it at the right time. Even after promise determines resolved/rejected, its value cannot be changed - it will never change.
This is a practical example of using Promise:
const request = require('request') return new Promise((resolve, reject) => { request.get(url, (error, response, body) => { if (body) { resolve(JSON.parse(body)) } else { resolve({}) } }) })
We can also use promise All() to process multiple asynchronous functions in parallel:
let urls = [ '/api/commits', '/api/issues/opened', '/api/issues/assigned', '/api/issues/completed', '/api/issues/comments', '/api/pullrequests' ] let promises = urls.map((url) => { return new Promise((resolve, reject) => { $.ajax({ url: url }) .done((data) => { resolve(data); }) }) }) Promise.all(promises) .then((results) => { // Do something with results of all our promises })
12. Generators generator
Just as Promise can help us avoid callback hell, Generator can help us make the code style Cleaner - write asynchronous code in a synchronous code style, which is essentially a function that can pause the calculation and then return the value of the expression:
function* sillyGenerator() { yield 1 yield 2 yield 3 yield 4 } var generator = sillyGenerator(); console.log(generator.next()) // { value: 1, done: false } console.log(generator.next()) // { value: 2, done: false } console.log(generator.next()) // { value: 3, done: false } console.log(generator.next()) // { value: 4, done: false }
Next can go back to the value returned by the next yield. Of course, the above code is very unnatural. We can use , Generator , to write asynchronous operations synchronously
function request(url) {
getJSON(url, function(response) {
generator.next(response)
})
}
The generator function here will return the required data:
function* getData() { var entry1 = yield request('http://some_api/item1') var data1 = JSON.parse(entry1) var entry2 = yield request('http://some_api/item2') var data2 = JSON.parse(entry2) }
Through yield, we can ensure that entry1 has the data we need to parse and store in data1.
Although we can use the Generator to write asynchronous operations synchronously, the propagation of confirmation errors is no longer clear. We can add Promise in the Generator:
function request(url) { return new Promise((resolve, reject) => { getJSON(url, resolve) }) }
Then we write a function to call next step by step and use the request method to generate a {Promise:
function iterateGenerator(gen) { var generator = gen() (function iterate(val) { var ret = generator.next() if(!ret.done) { ret.value.then(iterate) } })() }
After adding "Promise" to "Generators", we can use "Promise" more clearly Catch and reject to catch errors. Let's use the new} Generator, which is quite similar to the previous one:
iterateGenerator(function* getData() { var entry1 = yield request('http://some_api/item1') var data1 = JSON.parse(entry1) var entry2 = yield request('http://some_api/item2') var data2 = JSON.parse(entry2) })
13. Async Await
When ES7 really comes, async await can realize the asynchronous processing implemented by Promise and Generators with less processing:
var request = require('request') function getJSON(url) { return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body) }) }) } async function main() { var data = await getJSON() console.log(data) // NOT undefined! } main()
14. Getter/Setter function
ES6 has started to implement the # getter # and # setter # functions:
class Employee { constructor(name) { this._name = name } get name() { if(this._name) { return 'Mr. ' + this._name.toUpperCase() } else { return undefined } } set name(newName) { if (newName == this._name) { console.log('I already have this name.') } else if (newName) { this._name = newName } else { return false } } } var emp = new Employee("James Bond") // uses the get method in the background if (emp.name) { console.log(emp.name) // Mr. JAMES BOND } // uses the setter in the background emp.name = "Bond 007" console.log(emp.name) // Mr. BOND 007
The latest version of the browser also implements getter and setter functions in the object. We can use them to calculate properties, and add listeners and processing before setting and obtaining a property.
var person = { firstName: 'James', lastName: 'Bond', get fullName() { console.log('Getting FullName') return this.firstName + ' ' + this.lastName }, set fullName (name) { console.log('Setting FullName') var words = name.toString().split(' ') this.firstName = words[0] || '' this.lastName = words[1] || '' } } person.fullName // James Bond person.fullName = 'Bond 007' person.fullName // Bond 007
Note: article source: https://www.jianshu.com/p/b7eb2c3e95bc