許久沒有寫總結博客了,這陣子都是在Git上做學習筆記。馬上就到年底了,也是時候做一些知識總結了。(承前啟后)
JavaScript是一門很有意思的語言,如名字里帶Java,但它和Java並沒有什么關系。JS本身又是弱語言類型的(數據類型的隱式轉換),不像Java、C等對數據類型有嚴格的要求。這個特性的優點就是數據操作和使用很靈活,缺點就是可能由此帶來一些問題和會有很多基於這個特點的技巧需要記,也就使得用起來相對復雜一些。且相對來說JS的發展屬於起步晚一些的,一些規范和功能都在不停得制定和完善。而且最近的新功能和新版本更新越來越快。
JS的發展歷程,20世紀末的從開始到慢慢規范。1997年第一版發布,1998年修改為國際標准格式,1999年正則、作用域鏈、新的控制指令、異常處理、錯誤定義、數據輸出格式化等,JS開始快速廣泛使用。2009年,添加了嚴格模式以對前面模糊的概念進行規范和修正,增加了getters,setters,JSON等。2011年發布ES5標准。這里就從這個版本開始總結。其實關於JS的版本有很多不同的叫法,比如這里說的ES是ECMAScript的縮寫,ES5又叫ECMAScript5.1,甚至還有ISO/IECxxx的國際標准版本號,甚至還可以叫ES2011。叫法太多了,想記反而會把自己搞的頭疼,所以索性大家近年來都只認ES5這一種叫法。
ES5
ES5是2011年發布的,主要新增的特性是(其實2009年的版本不是標准正式版本,所以2009年版本的那幾個特性也可以算作是ES5的特性,比如嚴格模式,JSON等,這里不細說了):
- JSON,主要是parse和stringify兩個方法比較常用;
- Object擴展,Object.create(prototype,[descriptors]),Object.defineProperties(object,descriptors)(defineProperty單個), 和 getPrototypeOf 、getOwnPropertyDescriptor 、getOwnPropertyNames 、seal、freeze、preventExtensions (這3個是一套)、keys等常用屬性和方法;
- Array原型擴展,Array的prototype原型增加了isArray、indexOf、lastIndexOf、every、some、forEach、map、filter、reduce、reduceRight(這7個是遍歷相關)等方法;
- Function原型擴展,新增bind方法,參考call和apply進行對比記憶;(三者都用於將this的指向做改變,第一個參數都是目標對象,bind和call的參數形式一樣,可以多個參數,bind是將函數返回,call和apply是立即執行,apply是兩個參數,第二個參數是后邊的參數的數組)
ES6
ES6較ES5更新變化較大較多,是2015年發布。主要部分如下:
1.塊級作用域聲明,let和const,功能如var,但屬於塊級作用域,比較容易區分;
2.類Class,ES6之前都是使用構造函數結合原型鏈等其他類似方法來實現一個實例對象,現可直接使用class來實現,示例如下;
1 // Old 2 function Person(name, age) { 3 this.name = name 4 this.age = age 5 } 6 Person.prototype.say = function() { 7 return 'Name:' + this.name + ',age:' +this.age 8 } 9 // New 10 class Person { 11 constructor(name, age) { 12 this.name = name 13 this.age = age 14 } 15 say() { 16 return 'Name:' + this.name + ',age:' +this.age 17 } 18 }
3.箭頭函數,即將匿名函數的定義方式進行縮減,=>代替function關鍵字,主要用於匿名函數的地方,沒有自己的this,arguments,super,new.target,如arr.forEach(item=>{});
4.默認參數值,即在定義方法時可以很方便的為參數指定默認值,如 const funcA = (age=0)=>{};
5.模板字符串,主要用於字符串拼接,形勢是使用``結合${},如const name='xxx';alert(`Name:${name}`);
6.解構賦值,通過解構賦值,將屬性或值 從 對象或數組中取出賦值給其他變量,解釋比較晦澀,例如交換兩個變量的值[a,b]=[b,a],就不需要再使用定義第三個中間值的傳統處理方法了,可以算得上是顛覆傳統了;
7.Module模塊化,在ES6之前,模塊化主要是CommonJS和AMD定制的一類規則,ES6增加了標准的模塊化,示例如下;
1 // Old 2 // circle.js 3 const {PI} = Math 4 exports.area = (r) => PI * r ** 2 5 exports.circumference = (r) => PI * r * 2 6 7 // index.js 8 const circle = require('./circle.js') 9 console.log(`圓形面積:${circle.area(2)}`) 10 11 // New 12 // circle.js 13 const {PI} = Math 14 export const area = (r) => PI * r ** 2 15 export const circumference = (r) => PI * r * 2 16 17 // index.js 18 import {area} = './circle.js' 19 console.log(`圓形面積:${area(2)}`)
8.擴展運算符(三個點),只用於可迭代對象,如現有一個將3個數相加的方法,如果要將一個數組的3個數相加,傳統寫法是使用 方法.apply(null, arr),使用擴展運算符則可以直接 方法(...arr) 來執行;
9.對象屬性簡寫,如有const a = '1';const obj = {a:a},可寫作const obj = {a},屬性名與變量名 的字符相同的情況使用;
10.Promise,這就比較好玩了,在之前實現異步的一般方法是使用回調,有了Promise之后,異步和鏈式調用有了新的方向。說到這里就很容易扯到執行順序上,因為JS是順序執行的,而又有Promise、回調甚至await/async來打亂執行順序實現異步,很多時候都很喜歡問。此外Promise解決了回調地域的寫法即多層嵌套,比如最常見的ajax請求需要依賴前邊的請求返回的時候,傳統寫法就是嵌套的ajax,現可寫作Promise.then,then里的入參即是前邊一個請求的返回,這也是為什么Promise經常和then出現在一起,而且then其實return的時候返回的也是一個Promise,形如Promise.resolve()包裝,如:Promise.resolve(1).then(res => {//res是1 return 2}).then(res => {// res是2});// TODO -- 總結awite/async和Promise
11.for...of,可迭代對象(Array,Map,Set,String,TypedArray,arguments)迭代,如const arr = [1,2,3];for(const el of arr){xxx};
12.symble類型,一種新的基本數據類型,如const a = Symble(22),不支持使用new生成,作用是生成唯一值,如Symbol(1) == Symbol(1)是false的;
13.迭代器(Interator)/生成器(Generator),主要功能是實現自定義迭代,真正使用到的地方目前見到並不多,見過有人結合promise實現類似await/async的效果,但是還不如直接使用何必繞來繞去,所以這里不詳細介紹,等以后有適用場景再做詳細對比,簡單介紹,訪問下一項next(),關鍵字yield和*,舉例如下;
// stage1 function *makeRangeIterator(start = 0, end = Infinity, step = 1) { for (let i = start; i < end; i += step) { yield i; } }// 實測*貼着function和貼着方法名沒有區別;yield起着類似return的效果 var a = makeRangeIterator(1,10,2) a.next() // {value: 1, done: false} a.next() // {value: 3, done: false} // stage2 function *createIterator(items){ for (let i = 0; i< items.length; i++){ yield items[i] } } let iterator = createIterator([1,2,3]) console.log(iterator.next()); // { value:1, done: false } // stage3 function *createIterator(){ yield 1 yield 2 yield 3 } let iterator = createIterator() console.log(iterator.next().value) //1
14.Set/WeakSet,Set類型對象允許存儲任意類型的唯一值,因其唯一性,可用來數組去重如[...new Set(arr)],WeakSet類似但只能存放對象引用,且元素如果沒有其他對象或屬性引用就會被釋放回收,且對象無法被枚舉,兩者有add,has,delete,clear等方法;
15.Map/WeakMap,Map類型保存鍵值對,鍵和值都可以是任意值或對象,Map類型方法有set(key, value)和get,has,delete,clear等,WeakMap與Map的區別與14類似,key只能是對象;
16.Proxy/Reflect,代理反射,效果是建立監聽和watcher的感覺,實現攔截js,元編程,語法 -- var proxy = new Proxy(target, handler),handler可以包含set,get,apply,has等等,Reflect與其一一對應如 - Reflect.has(Object, 'assign'),示例實現數據監聽;
1 const observe = (data, callback) => { 2 return new Proxy(data, { 3 get(target, key) { 4 return Reflect.get(target, key) 5 }, 6 set(target, key, value, proxy) { 7 callback(key, value); 8 target[key] = value; 9 return Reflect.set(target, key, value, proxy) 10 } 11 }) 12 } 13 14 const FooBar = { open: false }; 15 const FooBarObserver = observe(FooBar, (property, value) => { 16 property === 'open' && value 17 ? console.log('FooBar is open!!!') 18 : console.log('keep waiting'); 19 }); 20 console.log(FooBarObserver.open) // false 21 FooBarObserver.open = true // FooBar is open!!!
17.Regex對象的擴展,正則新增修飾符i,iu,y,(u是unicode模式),新增屬性 - flag屬性查看修飾符、sticky屬性對應y,字符串方法的實現改為調用RegExp
方法(?)-- TODO 正則需要穩固;
18.Math擴展,0b/0B表示二進制,0o/0O表示8進制,Number.EPSILON(最小精度)等一些常量,Number的擴展方法(.parseInt(),parseFloat(),isFinite(),isNaN(),isInteger(),isSafeInteger()),Math方法擴展,(trunc()-返回整數部分,sign()返回數值類型正負0,cbrt()立方根,clz32()32位無符號整數,fround()32位單精度浮點數形式,imul()返回兩個數相乘,hypot()所有數值平方和的平方根,expm1()e的n次方-1,log1p()等於Math.log(1 + n),log10(),log2(),三角函數全部的雙曲正弦 -- 在原三角函數名后加h如sinh());
19.Array擴展,form(如console.log(Array.from([1, 2, 3], x => x + x)) // [2, 4, 6])(console.log(Array.from('foo')) // ["f", "o", "o"])參數為源和handler,of(如Array.of(3)//[3];Array.of(1,2)//[1,2]),copyWithin - array.copyWithin(target, start, end),后兩個參數可選,默認首到尾,不改變數組長度,只替換值,如
var fruits = ["Banana", "Orange", "Apple", "Mango",'???'];
fruits.copyWithin(2, 0);
// ["Banana", "Orange", "Banana", "Orange", "Apple"]
find(),findIndex()(均是只返回第一個),fill(),keys()(索引),values()(這兩個方法同樣適用於對象),entries() 返回key value的遍歷器對象 -- next的value是存放key和value即這里是index和值的數組,數組空位 -- 將空位自動轉化為undefined或empty;
ES7
ES6的變動真的是太多了,后邊的版本相對來說就沒有那么多了,也就是趨於穩定了。2016年發布,ES7主要如下:
- Array的includes()方法;
- 冪運算符**,等同於Math.pow();
- 模板字符串轉義;
ES8
2017年發布:
1.async/await,解決前邊ES6的promise的then嵌套太多的問題,當然並沒有明顯的優劣,只是寫法和閱讀性的不同async/await是成對出現的,示例:
1 async function myFetch() { 2 let response = await fetch('coffee.jpg') 3 return await response.blob() 4 } 5 6 myFetch().then((blob) => { 7 let objectURL = URL.createObjectURL(blob) 8 let image = document.createElement('img') 9 image.src = objectURL 10 document.body.appendChild(image) 11 })
2.Object.values(),前邊已介紹;
3.Object.entries(),前邊已介紹;
4.padStart()/padEnd(),給字符串進行擴展,參數是目標長度和要用來填充長度不夠的部分的字符串,如號碼的使用星號加密,如下;
1 function encryStr(str, len, aimFlag = '*') { 2 const lastDigits = str.slice(-len) 3 const maskedNumber = lastDigits.padStart(str.length, aimFlag) 4 return maskedNumber 5 }
5.尾逗號,ShareArrayBuffer(谷歌火狐被禁,安全性),Atomics對象(ShareArrayBuffer對象的原子操作);
6.Object.getOwnPropertyDescriptors(),獲取對象屬性的描述符,如淺拷貝:
1 // 淺拷貝一個對象 2 Object.create( 3 Object.getPrototypeOf(obj), 4 Object.getOwnPropertyDescriptors(obj) 5 )
ES9
2018年發布:
1.for await of,for of配合迭代生成器的使用,使得可以遍歷異步,示例如下,asyncGenerator的定義省略;
1 (async function() { 2 for await (num of asyncGenerator()) { 3 console.log(num) 4 } 5 })()
2.模板字符串轉義的部分語法調整;
3.正則表達式反向斷言,斷言的定義:又叫非消耗性匹配或非獲取匹配,是一個對當前匹配位置之前或之后的字符的測試, 它不會實際消耗任何字符,之前的都是正向斷言。(?=pattern)
零寬正向肯定斷言,(?!pattern)
零寬正向否定斷言,(?<=pattern)
零寬反向肯定斷言,(?<!pattern)
零寬反向否定斷言;
4.正則Unicode轉義,s/dotAll 模式,命令捕獲組(后邊單獨再詳細總結);
5.Object Rest Spread,即...rest,在ES9之前,...多用於數組的操作,比如實現復制拆分等,在ES9里給對象也擴展了這種語法,比如,實現對象的復制,而且是類似深復制的效果,示例如下,
1 const input = { 2 a: 1, 3 b: 2, 4 c: 4 5 } 6 const output = { 7 ...input, 8 c: 3 9 } 10 input.a=0 11 console.log(input,output) // {a: 0, b: 2, c:4} {a: 1, b: 2, c: 3}
值得注意的是,如果對象的屬性是一個對象,則對象是淺復制的,可以理解為只深復制一層,結合rest的使用示例如下;
1 const input = { 2 a: 1, 3 b: 2, 4 c: 3 5 } 6 let { a, ...rest } = input 7 console.log(a, rest) // 1 {b: 2, c: 3}
6.Promise.prototype.finally(),不論Promise是成功還是失敗都會執行這里的內容;
ES10
1.Array.prototype.flat() / flatMap(),flat是按參數指定的深度降維,flatMap()與 map() 方法和深度為1的 flat()效果差不多,可以代替reduce() 與 concat(),示例如下;
1 var arr1 = [1, 2, 3, 4] 2 arr1.flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]] 3 arr1.flatMap(x => [x, x * 2]) // [1, 2, 2, 4, 3, 6, 4, 8] 4 arr.reduce((acc, x) => acc.concat([x, x * 2]), []) // [1, 2, 2, 4, 3, 6, 4, 8]
2.String.prototype.trimStart() / trimLeft() / trimEnd() / trimRight(),去掉單邊空格,start/end才是標准名稱,left/right是別名;
3.Object.fromEntries(),Object.fromEntries([['a',1]])//{'a':1} ,是Object.entries()的反函數,參數是一個形如二維數組的map對象;
4.Symbol.prototype.description,Symbol對象是描述符,只讀,其實字符上來說就是其new的時候的參數;
5.String.prototype.matchAll,參照match,很好理解,正則匹配;
6.Function.prototype.toString() 返回注釋與空格;
7.try-catch的catch的參數非必須,可以直接try{}catch{}了;
8.BigInt,內置對象,表示大於2的53次方-1的整數,即Number的最大數之外的整數,定義一個大整數可以用整數字面量后邊加n表示,或者用BigInt(整數字面量)來表示,值得關注的是BigInt不能和別的類型的數值進行運算,否則會拋出異常,在BigInt出來以后,JS的原始類型便增加到了7個,如下:•Boolean•Null•Undefined•Number•String•Symbol (ES6)•BigInt (ES10);
9.globalThis,全局對象的this值;
10.import(),動態引入,可以接then;
11.私有元素與方法,關鍵字是#,表示私有,示例如下;
1 class Counter extends HTMLElement { 2 #xValue = 0 3 4 get #x() { 5 return #xValue 6 } 7 set #x(value) { 8 this.#xValue = value 9 window.requestAnimationFrame(this.#render.bind(this)) 10 } 11 12 #clicked() { 13 this.#x++ 14 } 15 16 constructor() { 17 super(); 18 this.onclick = this.#clicked.bind(this) 19 } 20 21 connectedCallback() { 22 this.#render() 23 } 24 25 #render() { 26 this.textContent = this.#x.toString() 27 } 28 } 29 window.customElements.define('num-counter', Counter)
擴展
此外,還有幾個對我們平常代碼效率提升很有用的特性:
1.可選鏈操作符
比如,我們平常在請求接口的時候,可能接口的數據處理比較簡單,就可能會有多層嵌套的對象是數據,我們要取比較深的數據的時候,就得一層一層判斷以免有外層為null導致拋出異常,如let nestedProp = obj && obj.first && obj.first.second,有了該特性之后,可以直接簡寫為let nestedProp = obj?.first?.second,使用問號代替原來需要重復寫的部分;
2.空位合並操作符
比如,我們平常在做三元運算或使用||來取值時,由於0,false和空會被當做false處理,因而會取到后邊的值,而如果想使這些值作為有效項,則就要改成if判斷,比較冗長,現可以直接使用??來實現即兩個問號,表示除了null和undefined都有效,如let c = a ?? b;
3.static關鍵字
類似於java中的static,表示靜態字段,也可結合#私有來使用;
4.await的擴展
ES8新增的await/async中只允許await在async函數內使用,現可在外部使用,如const strings = await import(`/i18n/${navigator.language}`),還有在http請求或者數據庫連接等方面也很方便;
5.弱引用WeakRef
與WeakMap,WeakSet類似,這兩者實現了部分弱引用,WeakRef則是真正的弱引用,WeakRef 實例具有一個方法 deref,該方法返回被引用的原始對象,如果原始對象已被收集,則返回undefined對象。
以上幾個擴展屬性目前還沒有廣泛支持,如需使用需要謹慎。