ES6学习笔记
ES6學習筆記
在學習ES6的過程中做的一些記錄,用于未來的快速回憶。let&const
作用域的概念
- ES6新增塊級作用域的概念(一個大括號括起來的部分就是一個塊作用域)
- let與const用于在塊級作用域中聲明變量,該變量僅在當前塊級作用域有效
- ES6強制啟用嚴格模式,變量未聲明不允許使用
如何使用let與const
- let關鍵詞用于聲明變量
- const關鍵詞用于聲明常量,聲明時必須賦值
- let&const關鍵詞不能在同一個塊級作用域內重復聲明一個變量
- const定義的基本類型常量不能被修改,但是引用類型常量內部的值能被修改,只要不改變常量的引用即可
解構賦值
ES6允許按照一定模式,從數組和對象中提取值,對變量進行賦值。
數組解構賦值
// 完全解構 //本質上這種寫法屬于‘模式匹配‘,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值 let [a,b,c] = [1,2,3]; // a=1,b=2,c=3 let [a,[b]] = [1, [2]]; // a=1,b=[2] let [,,a] = [1,2,3]; // a=3 let [d,...rest] = [1,2,3,4,5,6]; // d=1,rest=[2,3,4,5,6] // 解構不成功 // 如果解構不成功,變量的值就等于undefined let [x,y] = ['xy'] //x='xy',y=undefined // 不完全解構 let [i,j] = [1,2,3]; // i=1,j=2 //如果等號的右邊不是數組,或者說不是可遍歷的結構,那么將會報錯 let [a] = 1; // 這里會報錯:Uncaught TypeError: 1 is not iterable默認值
解構賦值允許有默認值。
let [x,y='b'] = ['a']; console.log(y); //blet [x,y = 'b'] = ['a',undefined]; console.log(y); //b // 數組成員為undefined時,默認值仍會生效 // 因為在ES6內部使用嚴格相等運算符‘===‘,判斷一個位置是否有值, // 所以當一個數組成員嚴格等于undefined,默認值才會生效。let [x,y = 'b'] = ['a',null]; console.log(y); //null // 如果一個數組成員是null,默認值就不會生效,因為null不嚴格等于undefined對象解構賦值
對象的解構與數組有一個重要的不同,數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。
// 變量名與屬性名一致的情況下 let {foo,bar} = {foo : "aaa",bar : "bbb"} console.log(foo); //aaa console.log(bar); //bbb // 實際上 對象的解構賦值是以這樣的形式簡寫的 let {foo : foo ,bar : bar} = {foo : "aaa",bar : "bbb"} // 變量名與屬性名不一致的情況下,必須這樣寫 let {a : name, b : age} = {a : 'zhangsan', b : 33}; console.log(name); //zhangsan console.log(age); //33對象的解構賦值的內部機制,是先找到同名屬性,然后再賦值給對應的變量,真正被賦值的是后者,而不是前者,第一個foo/bar 是匹配的模式,對應的foo/bar屬性值才是變量,真正被賦值的是屬性值(也就是第二個foo/bar)。
字符串解構賦值
const [a,b,c,d,e] = 'hello'; console.log(a); //h console.log(b); //e console.log(c); //l console.log(d); //l console.log(e); //olet { length : len} = 'yahooa'; console.log(len); //類似數組的對象都有一個length屬性,還可以對這個屬性解構賦值布爾值/數值解構賦值
解構賦值時,如果等號右邊是數值和布爾值,則會先轉為對象,但是等號右邊為undefined 和 null時無法轉為對象,所以對他們進行解構賦值時,都會報錯。
let {prop : x } = undefined; console.log(x); //報錯:Uncaught TypeError: Cannot destructure property `prop` of 'undefined' or 'null'函數參數解構賦值
function move({x = 0,y = 0} = { }){return [x,y];} console.log(move({x : 3,y : 4})); //[3,4] console.log(move({x : 3})); //[3,0] console.log(move({})); //[0,0] console.log(move()); //[0,0] // move()的參數是一個對象,通過對這個對象進行解構,得到變量x、y的值,如果解構失敗,x和y 等于默認值 function move2({x,y} = {x : 1, y : 2 }){return [x,y]; } console.log(move2({x : 6,y : 8})); //[6,8] console.log(move2({})); //[undefined,undefined] console.log(move2()); //[1,2] // move2() 是為函數move的參數指定默認值,而不是為變量x和y指定默認值, // 所以與前一種寫法的結果不太一樣,undefined 就會觸發函數的默認值解構作用
-
改變變量的值
let x = 1; let y = 2; [x,y] = [y,x]; // x=2, y=1 -
從方法返回多個值
function example(){return {foo : 'a',bar : 'b'} } let {foo,bar} = example(); // foo='a',bar='b' function example222(){return [1,2,3,4,5]; } let [a,,...b] = example222(); // a=1, b=[3,4,5] -
函數參數的定義
//參數是一組有次序的值 function example([x,y,z]){return x + y + z; } example([1,2,3]) console.log(example([1,2,3])); //6 //參數是一組無次序的值 function f({x,y,z}){return x + y + z; } f({x : 'a', z : 'b', y : 'c' }); console.log(f({x : 'a', z : 'b', y : 'c' })); //acb - 提取JSON數據
- 函數參數的默認值
- 輸入模塊的指定用法
正則擴展
構造函數的變化
// ES5 的方式 let reg = new RegExp('xyz', 'i'); let reg2 = new RegExp(/xyz/i); reg.test('xyz123'); // true reg.test('xyz123'); // true // ES6 方式的變化,可以有第二個參數用作修飾符 // 這種方式,第二個參數的修飾符會覆蓋第一個正則表達式的修飾符 let reg3 = new RegExp(/xyz/ig, 'i') reg3.flags // iu修飾符
// u -> unicode,用于處理unicode編碼的正則匹配 // unicode中4個字節 = 一個字母 /^\uD83D/.test('\uD83D\uDC2A'); // true \uD83D\uDC2A 被當做兩個字母來處理 /^\uD83D/u.test('\uD83D\uDC2A'); // false,\uD83D\uDC2A 被當作一個字母來處理 // 正則中,大括號里面若是unicode編碼,需添加u修飾符才能被識別 /\u{61}/.test('a'); // false /\u{61}/u.test('a'); // true // 用'.'操作符識別大于兩個字節的字符需要加上u修飾符 // '.'操作符不能處理換行符/回車符/行分隔符/段分隔符 /^.$/.test('吉'); // false /^.$/u.test('吉'); // true /吉{2}/.test('吉吉'); // false /吉{2}/u.test('吉吉'); // true // 凡是超過兩個字節的匹配都需要添加 u 修飾符y修飾符
let s = 'bbb_bb_b'; let a1 = /b+/g; let a2 = /b+/y; a1.exec(s); // g修飾符全局匹配,不強調從下一個字符開始匹配,只要接下來的字符能匹配成功就ok a2.exec(s); // y修飾符全局匹配,必須緊跟著下一個字符開始匹配 // sticky屬性用于判斷是否開啟了y修飾符 a1.sticky; // false a2.sticky; // trues修飾符
未實現,僅作為提案
// '.'操作符不能處理換行符/回車符/行分隔符/段分隔符 // 但添加 s 修飾符就可以字符串擴展
Unicode表示法
console.log(`\u0061`); // a console.log(`\u20887`); // []7,會將20087拆成2008 / 7 console.log(`\u{20887}`); // ?, 大括號括起來后,將作為一個字符處理// 每兩個字節為一個單位長度 // ES5 不能很好的處理長度超過兩個字節的字符 let s = '?'; s.length; // 2 'a'.length; // 1 s.codePointAt(0); // 可以很好的處理長度超過兩個字節的字符的碼值,將字符轉為碼值 String.fromCodePoint("0x20087"); // 將碼值轉為字符遍歷接口
let str "\u{20bb7}abc"; // ES5的遍歷方式,不能很好的處理長度超過兩個字節的字符 for(let i=0; i<str.length; i++) {console.log(str[i]); } // 新的遍歷接口,可以自動處理任何字符 for (let code of str) {console.log(code); }模板字符串
let name = 'list'; let info = 'hello world'; let meg = `This is ${name}, ${info}`; console.log(meg);數值擴展
新增方法
Number.isFinite(); // 判斷是否為有窮數 Number.isNaN(); // 判斷是否不是數字 Number.isInteger(); // 判斷是否為整數 , 25/25.0均為true,25.1為false,參數必須為數值 Number.MAX_SAFE_INTEGER // 數的上限,ES5 Number.MAX_SAFE_INTEGER // 數的下限,ES5 Number.isSafeInteger(); // 判斷一個數是否在有效范圍內 Math.trunc(); // 取整數部分 Math.sign(); // 正數返回1,負數返回-1,0返回0 Math.cbrt(); // 求立方根 // 還補充了三角函數方法、對數方法方法調整
將ES5中某些全局方法移到對象下面,如parseInt()由全局移動到Number對象。
數組擴展(新增特性)
Array.from
// 從類數組中創建數組 // 如頁面中有一堆 p 標簽 let p = document.querySelectorAll('p'); let pArr = Array.from(p); // 此時,將集合p轉為了數組pArr// 將數組中的元素按照function的規則處理后并返回一個數組 Array.from([1,3,5], function(item){return item*2}); // [1,6,10]Array.of
let arr = Array.of(1,2,3,4,5); // arr=[1,2,3,4,5] let emptyArr = Array.of(); // emptyArr=[]\copyWithin
let arr = [1,2,3,4,5].copyWithin(0, 3, 5); // arr = [4, 5, 3, 4, 5] // 將起始位置3到終止位置5的元素覆蓋掉從0開始的元素(不包括下標為5的元素) let arr2 = [1,2,3,4,5].copyWithin(0, 1, 5); // arr2 = [2, 3, 4, 5, 5];find/findIndex
let val = [1,2,3,4,5,6].find(function(item){return item>3}); // val = 4 // find找到一個符合條件的元素就結束,并返回該元素的值 let index = [1,2,3,4,5,6].findIndex(function(item){return item>3}); // index = 3 // findIndex找到一個符合條件的元素就結束,并返回該元素的下標fill
let arr = [1,'a',undefined]; arr.fill(6); // arr=[6,6,6] // 將起始位置1到終止位置3的元素替換成5(不包括下標為3的位置) arr.fill(5,1,3); // arr=[6,5,5]entries/keys/values
for (let index of [1,2,3].keys()){console.log(index); // 0,1,2 } for (let value of [1,2,3].values()){console.log(value); // 1,2,3 } for (let [index,value] of [1,2,3].entries()){console.log(index, value); // 0,1;1,2; 2,3 }includes
let a = [1,2,NaN].includes(1); // a = true let b = [1,2,NaN].includes(NaN); // b = true函數擴展
參數默認值
// 有默認值的參數右邊的參數都需要有默認值 function test(x, y='hello world') {console.log("默認值:", x, y); } test('hhh'); // 觸發默認值 test('hhh', 'aaa'); // 不觸發默認值 // 默認參數的作用域 let x = "test"; function test(x, y=x) {console.log(x, y); } function test2(c, y=x) {console.log(x, y); } test("hhh"); // hhh hhh test2("hhh"); // hhh test // 采用這種方式傳遞默認值時,要注意等號右邊變量的作用域 // 等號右邊的變量的值與最近定義的同名變量的值相同rest參數
// rest參數后不能有其他參數 function test3(...arg) {for(let v of arg) {console.log('rest:', v)} }擴展運算符
// 擴展運算符會將解構數據 console.log(...[1,2,3]); // 1;2;3 console.log("a", ...[1,2,3]); // a;1;2;3箭頭函數
// 單個參數時可以不使用括號,多個參數時需要使用括號將其括起來 // 函數體有多個語句時,使用大括號括起來 let arrow = v => v*2; arrow(3); // 6let arrow2 = () => 5; arrow2(); // 5尾調用
// 函數內部的最后一句是另一個函數 // 尾調用可以提升JS的性能 // 下面例子中,fx()就實現了尾調用 function tail(x) {console.log(x); } function fx(x) {return tail(x); } fx(123); // 123對象擴展(新增特性)
簡潔表示法
// 當對象中的屬性名與變量名相同時,可以只寫一個 // 對象里面有方法,在ES6中可以省略function關鍵字 let o = 1; let k = 2; let obj = {o,k,hello () {console.log('hhh');} }屬性表達式
// ES5中,對象中的key是固定的 // ES6中,key是可以用表達式或者變量的 let a = 'b'; let es5_obj = { // {a:c}a: 'c' } let es6_obj = { // {b:c}[a]: 'c' }擴展運算符(ES7提案)
// 不建議使用,支持不好 let {a,b,...c} = {a:'1', b:'2', c:'3', d:'5'}; // a = 1 // b = 2 // c = {c:'3', d: '5'}Object新增方法
Object.is('abc', 'abc'); // 與===的功能相同 Object.assign({a:'1'}, {b:'2'}); // 將第二個對象的內容追加到第一個對象里,淺拷貝 // 只拷貝自身的數據,不拷貝繼承的屬性以及不可枚舉的屬性// 遍歷對象 let obj = {a:'1', b:'2'}; for(let [key, value] of Object.entries(test)) {console.log([key, value]); }Symbol
Symbol概念
這種數據類型提供一個獨一無二的值。
Symbol的作用
-
聲明
// 方法1 let a1 = Symbol(); let a2 = Symbol(); console.log(a1 === a2); // false // 方法2 // 在生成a3前,會檢查‘a3’在全局是否存在 // 若存在,則為取值 // 若不存在,則為生成一個新的Symbol let a3 = Symbol.for('a3') -
作用
// 可用于處理對象里面的重名,防止沖突 let a1 = Symbol.for('abc');
}
// 常規循環只能處理非Symbol值
for(let [key, value] of Object.entries(obj)) {
}
// 這種方式只能處理Symbol值
Object.getOwnPropertySymbol(obj).foreach(function(item) {
})
// 這種方式能夠處理以上兩種情況
Reflect.ownKeys(obj).foreach(function(item) {
})`
Map & Set 數據結構
Set
- Set中的元素是不能重復的
- Set可用于數組去重
- Set里面不會自動數據類型轉換
WeakSet
- WeakSet的元素只能是對象
- WeakSet存儲的是弱引用,且不會檢測是否被垃圾回收機制回收
Map
- 任意數據類型都可以作為Map的數據類型
WeakMap
- WeakMap的key只能是對象
- WeakMap沒有clear方法、沒有size屬性、不能遍歷
- WeakMap存儲的是弱引用,且不會檢測是否被垃圾回收機制回收
Map和Array的對比
let map = new Map(); let array = []; // add map.set('t', 1); // 't'=>1 array.push({t:1}); // {t:1} // query let map_exist = map.has('t'); // true let array_exist = array.find(item=>item.t); // {t:1} // modify map.set('t', 2); // 't' => 2 array.forEach(item => item.t?item.t=2:''); // {t:2} // delete map.delete('t'); let index = array.findIndex(item => item.t); array.splice(index, 1);Set和Array的對比
let set = new Set(); let arr = []; // add set.add({t:1}); arr.push({t:1}); // query let set_exist = set.has({t:1}); // false,因為是不同的引用 let arr_exist = array.find(item=>item.t); // {t:1} // modify set.forEach(item => item.t?item.t=2:''); // 若直接add,則不能修改成功,因為引用不同 arr.forEach(item => item.t?item.t=2:''); // 這里遍歷是確保{t:1}存在 // delete set.forEach(item => item.t?delete(item):''); let index = array.findIndex(item => item.t); array.splice(index, 1);Map、Set、Object的對比
let item = {t:1}; let map = new Map(); let set = new Set(); let obj = {}; // add map.set('t', 1); set.add(item); obj['t'] = 1; // query let map_exist = map.has('t'); let set_exist = set.has(item); let obj_exist = 't' in obj; // modify map.set('t', 2); item.t = 2; // 這里,set存儲的是引用,因此對對象的修改也會修改set里面對應的值 obj['t'] = 2; // delete map.delete('t'); set.selete(item); delete obj['t'];Proxy與Reflect
Proxy的使用
// Proxy為代理,在用戶和對象之間設置代理,防止用戶直接操作對象 // 同時,可以在代理上做一些操作來修改查詢到的內容 // 或者限制用戶對某些屬性的修改行為 let obj = {time: '2018-11-21',name: 'net',_r: 123 }; let monitor = new Proxy(obj, {// 攔截對象屬性的讀取// 這里,當查詢的value中有2018字符串時將其替換成2019get (target, key) {return target[key].replace('2018', '2019'); // 將2018替換成2019},// 攔截對象屬性的修改// 這里只允許對name的修改set (target, key, value) {if(key === 'name') {return target[key] = value;} else {return target[key];}},// 攔截key in obj操作,限制暴露哪些屬性// 此處只允許判斷是否存在name這個key,對于其它key一律返回falsehas (target, key) {if (key === 'name') {return target[key];} else {return false;}},// 攔截delete// 這里只允許刪除以下劃線開頭的屬性deleteProperty (target, key) {if (key.indexOf('_') > -1) {delete target[key];return true;} else {return target[key];}},// 攔截Object.keys,Object.getOwnPropertySymbols,Object.getOwnPropertyNames// 這里將time屬性屬性過濾掉ownKeys (target) {return Object.keys(target).filter(item => item!='time')} }); monitor.time; // 2019-11-21 monitor.time = '2015-11-21'; // 2018-11-21 monitor.name = 'Chung'; // Chung 'name' in monitor; // true 'time' in monitor; // falseReflect的使用
// 使用Reflect有以下好處 // 1.更加有用的返回值 // 2.函數操作,如判斷一個屬性是否在該obj里面, Reflect.has(obj, key) // 3.更加可靠的函數式執行方式 // 4.可變參數形式的構造函數 // 5.控制訪問器或者讀取器的this // Reflect用于代替直接操作對象 // 建議使用Reflect來操作對象 let obj = {time: '2018-11-21',name: 'net',_r: 123 }; Reflect.get(obj, 'time'); // 2018-11-21 Reflect.set(obj, 'name', 'Chung'); // name: 'Chung' Reflect.has(obj, 'name'); // true使用場景
// 使用Proxy和Reflect實現解耦的數據校驗 function validator (target, validator) {return new Proxy(target, {_validator: validator,set (target, key, value, proxy) {if (target.hasOwnProperty(key)) {let val = this._validator[key];if(!!val(value)) {return Reflect.set(target, key, value, proxy);} else {throw Error(`Can not set ${key} to ${value}!`);}} else {throw Error(`${key} not exist!`)}}}) }const personValidator = {name (val) {return typeof val === 'string';},age (val) {return typeof val === 'number' && val > 18;} }class Person {constructor (name, age) {this.name = name;this.age = age;return validator(this, personValidator)} }const person = new Person('hh', 18); console.info(person); // {name: 'hh', age: '18'} person.name = 20; // 不能設置為20,因為 personValidator 限制name的值只能是string類和對象
基本定義和生成實例
// 定義類 class Person {constructor (name='Jarry') {this.name = name;} } // 生成實例 let person = new Person('Chung'); console.log(person); // Person {name: ‘Chung’}繼承
// 定義類 class Person {constructor (name='Jarry') {this.name = name;} } // 使用extends繼承 class Student extends Person {constructor (name='Sunny') {super(name); // 將子類的參數傳遞給父類// 若子類有自己的屬性,則需要將自有屬性放在super()下面} } // 生成實例 let stu = new Student(); console.log(stu); // Student {name: 'Sunny'}Getter 和 Setter
// 定義類 class Person {constructor (name='Jarry') {this.name = name;}// longName是屬性而不是方法get longName () {return 'Good ' + this.name;}set longName (value) {this.name = value;} } // 生成實例 let person = new Person(); person.longName; // 'Good Jarry' person.longName = 'Chung'; // 'Good Chung'靜態屬性和靜態方法
// 定義類 class Person {constructor (name='Jarry') {this.name = name;}// 使用static關鍵字定義靜態方法static tell () {console.log('hello');} } // 定義完類后再定義靜態屬性 Person.age = '18';// 調用靜態方法,使用類名調用而非實例調用 Person.tell(); // 'hello' Person.age; // 18Promise
- Promise是異步編程的一種解決方案
Promise基本使用
// 以回調函數處理異步 let ajax = function(callback) {console.log('執行1');setTimeout(function() {callback && callback.call();}, 1000) } ajax(function() {console.log('Timeout One'); })// 以Promise處理異步 let ajax = function() {console.log("執行2");return new Promise(function(resolve, reject) {setTimeout(function() {resolve();}, 1000)}) } ajax().then(function() {console.log('Timeout Two'); }) // 上面的結果為: // 執行1 // 執行2 // Timeout One // Timeout TwoPromise使用場景
// 所有圖片加載完再添加到頁面 function loadImg(src) {return new Promise((resolve, reject) => {let img = document.createElement('img');img.src = src;img.onload = function () {resolve(img)}img.onerror = function () {reject(err)}}) }function showImgs(imgs) {imgs.forEach(function(img) {document.body.append(img)}) } // all 將多個promise當作一個,全部加載完后才執行resolve Promise.all([loadImg('http://i4.buimg.com/567571/df1ef0720bea6832.png'),loadImg('http://i4.buimg.com/567571/df1ef0720bea6833.png') ]).then(showImgs) // race, 數組中某一個執行完就馬上執行resolve Promise.race([loadImg('http://i4.buimg.com/567571/df1ef0720bea6832.png'),loadImg('http://i4.buimg.com/567571/df1ef0720bea6833.png') ]).then(showImgs)Iterator
- for...of本質上是使用Iterator接口
Generator
Generator基本定義與使用
let tell = function* () {yield 'a';yield 'b';return 'c'; } let k = tell(); k.next(); // {value:'a', done:false} k.next(); // {value:'b', done:false} k.next(); // {value:'c', done:true} k.next(); // {value:'undefined', done:true}let obj = {}; obj[Symbol.iterator] = function* () {yield 1;yield 2;yield 3; } for(let value of obj) {console.log(value); // 1,2,3 }實例
// 應用1:狀態機 // 假設某一事物只有三種狀態,該事物僅在這三種狀態中轉換 // 以下生成器使得state僅在A、B、C間轉換 let state = function* () {while(1) {yield 'A';yield 'B';yield 'C';} }// 應用2:限制抽獎次數 let draw = function(count) {// 此處為具體的抽獎邏輯console.log(`Remain ${count}`); }let residue = function* (count) {while (count>0) {count --;yield draw(count);} }// 應用3:長輪詢 let ajax = function* () {yield new Promise(function(resolve, reject) {setTimeout(() => {resolve({code:0});}, 200);}) }let pull = function() {let generator = ajax();let step = generator.next();step.value.then(function(val) {if(val.code !== 0) {setTimeout(() => {console.log('wait');pull();}, 1000);} else {console.log(val);}}) }pull();Decorator
- 修飾器是一個函數,用來修改類的類型
- 第三方庫core-decorators提供了現成的修飾器
-
解決了兩個問題
- 不同類間共享方法
- 編譯期對類和方法的行為進行改變
-
參數
- target - 目標類
- name - 屬性
- descriptor - 屬性描述符
總結
- 上一篇: thinkphp删除某一学生_基于Thi
- 下一篇: k8s部署jar包_使用Kubernet