Introduction Code is for Humans 来自灵魂的两个拷问:
有没有一段代码没有效果,但是就是不知道为什么没效果?
Have you ever had a piece of code, that it broke but you just had no idea why it broke?
有没有一段代码起作用了,但是不知道为什么起作用了?
Have you ever written a piece of code that worked and you had no idea why it worked?
如果你不知道你的代码为什么有作用,在他崩溃时你是无法修复的,除了寄希望于运气、猜测和魔法。
If you don’t know why your code works, you have no hope of fixing it when it breaks, other than sheer luck and guessing and magic.
JavaScript New Feature Process js现在是一个变化飞速的语言,每天都有新的功能与框架推出,这其中很大一部分原因是因为js在ES6之前已停滞多年。ES6为了解决这个问题将每年更新版本。
Declarative JavaScript js的发展方向之一就是js正在成为一门更具声明性的语言。
Browser Support Transpilers 嗯,幸好我们有babel。
Course Overview JavaScript Versions(新版本)
ES6/ES2015
ES2016
ES2017
ES2018
ES2019
Features(新功能)
Template Strings(模板字符串)
String padding/trimming(字符串填充/修剪)
Destructuring(解构赋值)
Array find() / includes()(数组的find()和include()方法)
Array flat() / flatMap()(数组的flat()和flatMap()方法)
Iterators, Generators(迭代器,生成器)
RegExp Improvements(正则表达式的改进)
async .. await(异步)
async* .. yield await
Strings Template Strings 改进了字符串拼接的体验。使用``代替要拼接的字符串的引号,使用${}包裹js表达式,可以是变量名,js语句甚至另一个模板字符串。注意,模板字符串的行为更像立即执行函数,既在执行时就立即替换变量语句为真正的字符串。
Tagged Templates 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function formatCurrency (string, ...values ) { var str = '' for (let i = 0 ; i < string.length ; i++) { if (i > 0 ) { if (typeof values[i - 1 ] == 'number' ) { str += `$${values[i - 1 ].toFixed(2 )} ` } else { str += values[i - 1 ] } } str += string[i] } return str } var amount = 12.3 var msg = formatCurrency `The total for your order is ${amount} `
Applying Tagged Templates 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function logger (string, ...values ) { var str = '' for (let i = 0 ; i < string.length ; i++){ if (i > 0 ) { if (values[i - 1 ] && typeof values[i - 1 ] == 'object' ) { if (values[i - 1 ] instanceof Error ) { if (values[i - 1 ].stack ) { str += values[i - 1 ].stack continue } }else { try { str += JSON .stringify (values[i - 1 ]) continue } catch (err) {} } } str += values[i - 1 ] } str += string[i] } console .log (str) return str }
1 2 3 4 5 6 7 8 9 10 11 12 13 var v = 42 var o = { a : 1 , b : [2 , 3 , 4 ] }logger`This is my value: ${v} and another: ${o} ` try { nothing () } catch (err) { logger`Caught: ${err} ` }
Tagged Template Exercise 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function upper (strings,...values ) { var str = '' for (let i = 0 ; i < strings.length ; i++) { if (i > 0 ) { str += String (values[i - 1 ]).toUpperCase () } str += strings[i] } return str } var name = "kyle" , twitter = "getify" , topic = "JS Recent Parts" ; console .log ( upper`Hello ${name} (@${twitter} ), welcome to ${topic} !` === "Hello KYLE (@GETIFY), welcome to JS RECENT PARTS!" );
Padding & Trimming padStart() 1 2 3 4 5 6 7 8 9 10 11 var str = 'hello' str.padStart (5 ) str.padStart (8 ) str.padStart (8 , '*' ) str.padStart (8 , '12345' ) str.padStart (8 , 'ab' )
padEnd() 1 2 3 4 5 6 7 8 9 10 11 var str = 'hello' str.padEnd (5 ) str.padEnd (8 ) str.padEnd (8 , '*' ) str.padEnd (8 , '12345' ) str.padEnd (8 , 'ab' )
trim() trimStart() trimEnd() 1 2 3 4 5 6 7 var str = ' some stuff \t\t' str.trim () str.trimStart () str.trimEnd ()
Array Destructuring(数组解构赋值) Destructuring(解构赋值) 将结构分解成各个部分
decomposing a structure into its individual parts
MDN解构赋值
解构赋值语法是一种 Javascript 表达式,它使得将值从数组,或属性从对象,提取到不同的变量中,成为可能。
结构赋值可以使我们将如下代码:
1 2 3 4 5 6 7 8 9 10 var tmp = getSomeData ()var first = tmp[0 ], second = tmp[1 ] var firstName = first.name , firstEmail = first.email !== undefined ? first.email : 'nobody@none.tld' var secondName = second.name , secondEmail = second.email !== undefined ? second.email : 'nobody@none.tld'
简写为如下代码:
1 2 3 4 5 6 7 8 9 10 var [ { name : firstName, email : firstEmail = 'nobody@none.tld' }, { name : secondName, email : secondEmail = 'nobody@none.tld' } ] = getSomeData ()
Refactoring Code Using Destructuring(使用解构赋值重新组织代码) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function data ( ) { return [1 , , 3 , 4 , 5 ] } var tmp = data ()var first = tmp[0 ], second = tmp[1 ] !== undefined ? tmp[1 ] : 10 , third = tmp[2 ], fourth = tmp.slice (3 ) var [ first, second = 10 , third, ...fourth ] = data ()
Spread Operator & Declaring Destructured Arrays(展开运算符和声明解构数组)
展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。(译者注: 字面量一般指 [1, 2, 3] 或者 {name: “mdn”} 这种简洁的构造方式)
展开运算符只能在赋值最后使用(代表了将剩余所有数据放在数组中)。
Declaration & Assignment(声明和赋值) 结构赋值是一个赋值操作,而不是声明操作,所以可以给已经声明的变量赋值,同时也不能给未声明的变量赋值。(使用中注意未声明的变量前要使用const
,let
,var
关键字声明,已经声明的变量注意在行首或上一行尾加;
,因为根据js的ASI(Automatic Semicolon Insert自动分号插入机制)并不会在括号,方括号,正则开头的斜杠,加号,减号前插入分号 。
1 2 3 4 5 6 7 8 9 10 11 function data ( ) { return [1 , , 3 , 4 , 5 ] } var obj = {};[ obj.first , obj.second = 10 , obj.third , ...obj.fourth ] = data ()
Comma Separation(逗号分离) 1 2 3 4 5 6 7 8 9 10 function data ( ) { return [1 , , 3 , 4 , 5 ] } var [ first, , third, ...obj.fourth ] = data () || []
交换变量的值
1 2 3 4 5 6 7 8 9 var x = 1 , y = 2 { let tmp = x x = y y = temp } ;[x, y] = [y, x]
Parameter Arrays(参数数组) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function data (tmp ) { var [ first, second, third ] = tmp console .log (first, second, third) } function data ([ first, second, third ] = [] ) { console .log (first, second, third) }
Nested Array Destructuring(套嵌数组解构赋值) 1 2 3 4 5 6 7 8 9 10 11 12 function data ( ) { return [1 , [2 , 3 ], 4 ] } var [ first, [ second, third ] = [], fourth ] = data () || []
Object Destructuring(对象解构赋值) Object Destructuring(对象的解构赋值) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function data ( ) { return { a : 1 , c : 3 , d : 4 } } var tmp = data ()var first = tmp.a , second = tmp.b , third = tmp.c var { a : first, b : second = 2 , ...third } = data ()
Object Assignment Destructuring(声明变量时对象的解构赋值) 在使用对象的解构赋值时,如果前面没有const
、let
或var
关键字,直接使用{},那么会被解析为一个块级作用域,为了说明这是一个解构赋值语句,需要将语句用()包裹起来,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function data ( ) { return { a : 1 , c : 3 , d : 4 } } var first, second, third;({ a : first, b : second = 2 , ...third } = data ())
Object Default Assignment(对象的默认值) 1 2 3 4 5 6 7 8 function data ( ) { return {} var { a : first, b : second = 2 , ...third } = data () || {}
1 2 3 4 5 6 7 8 9 10 11 12 13 function data ( ) { return { a : 1 , c : 3 , d : 4 } } var { a, b = 2 } = data () || {}
Nested Object Destructuring(套嵌对象的解构赋值) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function data ( ) { return { a : 1 , c : { d : 4 , e : 5 } } } var { a : first, b : second = 2 , c : { d : fourth, e : fifth } = {} } = data () || {}
使用数组或对象解构赋值时最好设置一个默认值(空数组或空对象)
Parameter Objects(参数对象) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function data (obj = {} ) { var { a, b } = obj console .log (a, b) } function data ({ // 直接在接收对象时将对象的对应属性的值赋值给函数的两个参数 a, b } = {} ) { console .log (a, b) }
Nested Object & Array Destructuring(套嵌对象与数组的解构赋值) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var obj = { a : 1 , b : { x : 2 }, c : 3 } var { a, b, c, b : { x : e } = {} } = obj || {}
Further Destructuring(进一步了解解构赋值) Named Arguments(命名参数) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function lookupRecord (store = 'person-records' , id = -1 ){ console .log (store, id) } function lookupRecord ({ // 把参数设置为对象解构赋值的形式 store = 'person-records' , id = -1 } = {} ) { console .log (store, id) } lookupRecord ({id = 42 })
Destructuring & Restructuring(解构赋值与重构) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 var defaults = { url : 'http://some.base.url/api' , method : 'post' , headers : [ 'Content-Type: text/plain' ] } var settings = { url : 'http://some.other.url/' , data : 43 , callback : function (response ) { } } ajax (_.extend ({}, defaults, settings))ajax (Object .assign ({}, defaults, settings))function ajaxOptions ({ // 传参时直接把参数对象修改为目标结构并设置默认值 url = 'http://some.base.url/api' , method = 'post' , data, callback, headers: [ header0 = 'Content-Type: text/plain' , ...otherHeaders ] = [] } = {} ) { return { url, method, data, callback, headers : [ headers0, ...otherHeaders ] } } var default = ajaxOptions () ajax (default ) var settings = { url : 'http://some.other.url/' , data : 43 , callback : function (response ) { } } ajax (ajaxOptions (settings))
Array Methods(数组方法) find, findIndex. & includes Array.prototype.find() & Array.prototype.findIndex 1 2 3 4 5 6 7 var arr = [{a : 1 }, {a : 2 }]arr.find (v => v && v.a > 1 ) arr.find (v => v && v.a > 10 ) arr.findIndex (v => v && v.a > 10 )
Array.prototype.includes() 1 2 3 4 5 6 7 8 9 10 11 12 13 var arr = [10 , 20 , NaN , 30 , 40 , 50 ]arr.includes (20 ) arr.includes (60 ) arr.includes (20 , 3 ) arr.includes (10 , -2 ) arr.includes (40 , -2 ) arr.includes (NaN )
flat & faltMap
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。 flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。
1 2 3 4 5 var nestedValues = [1 , [2 , 3 ], [[]], [4 , [5 ]], 6 ]nestedValues.flat (0 ) nestedValues.flat () nestedValues.flat (2 )
1 2 3 4 5 6 [1 , 2 , 3 ].map (v => [v * 2 , String (v * 2 )]) [1 , 2 , 3 ].map (v => [v * 2 , String (v * 2 )]).flat () [1 , 2 , 3 ].flatMap (v => [v * 2 , String (v * 2 )])
1 2 3 4 5 6 7 8 [1 , 2 , 3 , 4 , 5 , 6 ].flatMap (function doubleEvens (v ) { if (v % 2 === 0 ) { return [v, v * 2 ] } else { return [] } })
Iterators + Generators(迭代器+生成器) Iterators(迭代器) MDN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var str = 'hello' , world = ['w' , 'o' , 'r' , 'l' , 'd' ] var iterator1 = str[Symbol .iterator ]() iterator2 = world[Symbol .iterator ]() iterator1.next () iterator1.next () iterator1.next () iterator1.next () iterator1.next () iterator1.next () iterator2.next () iterator2.next () iterator2.next () iterator2.next () iterator2.next () iterator2.next ()
Declarative Iterators 1 2 3 4 5 6 7 8 9 var str = 'hello' for ( let it = str[Symbol .iterator ](), v, result; (result = it.next ()) && !result.done && (v = result.value || true ); ){ console .log (v) }
1 2 3 4 5 6 7 8 9 10 var str = 'hello' , it = str[Symbol .iterator ]() for (let v of it) { console .log (v) } for (let v of str) { console .log (v) }
1 2 3 4 var str = 'hello' var letters = [...str] letters
Data Structure Without Iterators js对象没有原生的内置迭代器
1 2 3 4 5 6 7 8 9 10 11 12 var obj = { a : 1 , b : 2 , c : 3 } for (let v of obj) { console .log (v) } [...obj]
使用一个工厂函数制作对象内的迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var obj = { a : 1 , b : 2 , c : 3 , [Symbol .iterator ]: function ( ) { var keys = Object .keys (this ) var index = 0 return { next : () => (index < keys.length ) ? { done : false , value : this [keys[index++]] } : { done : true , value : undefined } } } } [...obj]
Generators
GeneratorFunction 是一个可以作为迭代器工厂的特殊函数。当它被执行时会返回一个新的Generator对象。 如果使用function*语法,则函数将变为GeneratorFunction。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function *main ( ) { yield 1 yield 2 yield 3 return 4 } var it = main ()it.next () it.next () it.next () it.next () [...main ()]
通过上面代码可以发现,在使用扩展运算符进行赋值时,return后的值并没有出现在数组中,所以使用generator时使用yeild赋值,return最好不要进行赋值操作。
使用generator给对象新进迭代代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 var obj = { a : 1 , b : 2 , c : 3 , *[Symbol .iterator ]() { for (let key of Object .keys (this )) { yield this [key] } } } [...obj]
Regular Expressions(正则表达式) Look Ahead & Behind RegExp MDN
1 2 3 4 5 6 7 8 9 10 11 12 13 var msg = 'Hello World' msg.match (/(l.)/g ) msg.match (/(l.)$/g ) msg.match (/(l.)(?=o)/g ) msg.match (/(l.)(?!o)/g ) msg.match (/(?<=e)(l.)/g ) msg.match (/(?<!e)(l.)/g )
Named Capture Groups 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var msg = 'Hello World' msg.match (/.(l.)/ ) msg.match (/([jkl])o Wor\1/ ) msg.match (/(?<cap>l.)/ ).groups msg.replace (/(?<cap>l.)/g , '-$<cap>-' ) msg.replace (/(?<cap>l.)/g , function re (...args ) { var [,,,,{cap}] = args return cap.toUpperCase () })
dotAll Mode
dotAll 属性表明是否在正则表达式中一起使用”s”修饰符(引入/s修饰符,使得.可以匹配任意单个字符)。dotAll 是一个只读的属性,属于单个正则表达式实例。
1 2 3 4 5 6 7 8 9 var msg = ` The quick brown fox jumps over the lazy dog` msg.match (/brown.*over/ ) msg.match (/brown.*over/ s)
Async Await Async Functions promise chains: yunk 使用promise的回调函数写法:
1 2 3 4 5 6 7 8 9 10 11 12 fetchCurrentUser () .then (function onUser (user ) { return Promise .all ([ fetchArchivedOrders (user.id ), fetchCurrentOrders (user.id ) ]) }) .then (function onOrders ( [archivedOrders, currentOrders] ) { })
sync-async(with generators) 使用generator和iterator的写法:
1 2 3 4 5 6 7 8 9 runner (function *main ( ) { var user = yield fetchCurrentUser () var [archivedOrders, currentOrders] = yield Promise .all ([ fetchArchivedOrders (user.id ), fetchCurrentOrders (user.id ) ]) })
async functions
async function 声明用于定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。但是如果你的代码使用了异步函数,它的语法和结构会更像是标准的同步函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 async function mian ( ) { var user = await fetchCurrentUser () var [archivedOrders, currentOrders] = await Promise .all ([ fetchArchivedOrders (user.id ), fetchCurrentOrders (user.id ) ]) } mian ()
Async Iteration 1 2 3 4 5 6 7 async function fetchFiles (files ) { var prs = files.map (getFile) prs.forEach (function each (pr ) { console .log (await pr) }) }
library fasy: better async FP iterations getify/fasy
1 2 3 4 5 6 7 async function fetchFiles (files ) { var prs = await FA .concurrent .map (getFile, files) await FA .serial .forEach (async function each (pr ) { console .log (await pr) }, prs) }
Async Function Problems
await Only Promises(await关键字只能处理Promise对象)
Scheduling (Starvation)(可能会造成微任务队列拥堵,产生无限循环问题)
External Cancelation(一旦开始就几乎不可能从外部取消)
library CAF: cancelable async functions etify/CAF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var token = new CAF .cancelToken ()var main = CAF (function *main (signal, url ) { var resp = yield fetch (url, {signal}) return resp }) main (token.signal , 'http://some.tld/other' ) .then (onRestonse, onCancelOrError) setTimeout (function onElapsed ( ) { token.abort ('Request took too long!' ) }, 5000 )
Async Generators with yield async* .. yield await async all-at-once:
1 2 3 4 5 6 7 8 9 10 11 12 13 async function fetchURLs (urls ) { var results = [] for (let url of urls) { let resp = await fetch (url) if (resp.status == 200 ) { let text = await resp.text () results.push (text.toUpperCase ()) } else { results.push (undefined ) } } return results }
overloaded yield:
1 2 3 4 5 6 7 8 9 10 11 function *fetchURLs (urls ) { for (let url of urls) { let resp = yield fetch (url) if (resp.status == 200 ) { let text = yield resp.text () yield text.toUpperCase () } else { yield undefined } } }
async generators:
1 2 3 4 5 6 7 8 9 10 11 async function *fetchURLs (urls ) { for (let url of urls) { let resp = await fetch (url) if (resp.status == 200 ) { let text = await resp.text () yield text.toUpperCase () } else { yield undefined } } }
Async Generators Iteration 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 async function main (favoriteSites ) { var it = fetchURLs (favoriteSites) while (true ) { let res = await it.next () if (res.done ) break let text = res.value console .log (text) } } async function main (favoriteSites ) { for await (let text of fetchURLs(favoriteSites) ) { console .log (text) } }