您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關ECMAScript的新特性有哪些的內容。小編覺得挺實用的,因此分享給大家做個參考。一起跟隨小編過來看看吧。
ES6成為JavaScript的下一代標準后,標準委員會(TC39)在每年都會發布一個ES的新版本,每個版本里都引入了很多實用的新特性,在日常的項目開發中,如果我們掌握這些實用的新特性,將大大的提升開發效率,下面讓我們全面的掌握這些ES的新特性吧~
之前使用var來定義變量,為我們提供了新的方式
let用來聲明變量,const用來聲明常量。
const TAG = "我是常量";let a; a = 2;console.log(TAG, "a=" + a); //我是常量 a=2復制代碼
let和const為JavaScript新增了塊級作用域,通常情況下,{}包裹的代碼擁有的作用域就是塊級作用域,聲明的變量或常量只在塊級作用域內有效,外部不能訪問。
if (true) { //外層塊級作用域 let a = 1; const A = 1; if (true) { //內層塊級作用域 let a = 2; } console.log(a,A); //(1)輸出:1 , 1}console.log(a); //(2)Uncaught ReferenceError: a is not defined復制代碼
上面有兩個塊級作用域,都聲明了變量a,但外層塊級作用域與內層塊級作用域無關,所以(1)處輸出的是外層的變量值1,(2)處訪問了不在一個塊級作用域定義的變量,所以會報錯。
另外一個理解塊級作用域的示例。
//for循環體內的定時器//在ES6之前,是沒有塊級作用域的,變量用var聲明,直接掛載在全局作用域上for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i); //3、3、3 }, 100); }//使用var聲明,for同步操作優先于setTimeout異步操作,在開始執行setTimeout的時候,//for循環已經執行完,i為3,則后續每次setTimeout輸出的i都是3//使用let聲明的話,則會在循環體內部形成閉包,每次for循環都會給閉包提供每次循環i的值,//并且不被釋放,最終setTimeout里會分別輸出0、1、2for (let i = 0; i < 3; i++) { setTimeout(function () { console.log(i); //0 1 2 }, 100); }復制代碼
不能在變量和常量聲明之前使用。
let和const命令會使區塊形成封閉作用域,若在聲明之前使用,就會報錯,這個在語法上,稱為“暫時性死區”(簡稱TDZ)。
if (true) { tmp = "abc"; // ReferenceError let tmp; }復制代碼
let a = 1;let a = 2;//報錯 SyntaxError: Identifier 'a' has already been declared const B=1;const B=2;//報錯 SyntaxError: Identifier 'B' has already been declared 復制代碼
let聲明的變量,全局對象(window,global,self)不能訪問到
let a = 10;console.log(window.a); //undefined復制代碼
ES6對字符串進行了一些擴展,如下:
ES6新增了模板字符串(字符串
)的方式定義字符串,用來解決之前字符串很長要換行、字符串中有變量或者表達式遇到的問題,下面是具體的使用場景
//一、打印長字符串且換行,直接在模板字符串中保留格式即可let welcome=` 你好 歡迎來到ES6 ——謝謝 `console.log(welcome);/* 輸出結果為: 你好 歡迎來到ES6 ——謝謝 *///二、字符串中有變量或者表達式,直接在模板字符串中使用${變量/表達式}即可let type = "ES6";let name1 = "mango";let name2 = "和goman";let welcome = `歡迎${name1 + name2}來到${type}世界`; console.log(welcome); //learn1.js?c1a0:7 歡迎mango和goman來到ES6世界復制代碼
判斷字符串是否包含一個指定字符串,返回boolean類型。
const str = "ECMAScript"console.log(str.includes("EC")); //true 找不到返回false 復制代碼
startsWith()用來判斷字符串是否以指定字符串作為開頭,返回boolean類型。
endsWith()用來判斷字符串是否以指定字符串作為結尾,返回boolean類型。
const str = "ECMAScript"console.log(str.startsWith("ECM")); //true console.log(str.endsWith("Script")); //true 復制代碼
將原有字符串重復n遍,得到一個新的字符串
const str = "ECMAScript";console.log(str.repeat(3)); //ECMAScriptECMAScriptECMAScript復制代碼
ES6開始逐步減少全局性方法,使得語言逐步模塊化,所以把一些處理數值的方法轉移到了Number對象上,功能行為保持不變。
//將目標轉換為整數//ES5parseInt("5.6") //5//ES6Number.parseInt("5.6") //5//將目標轉換為浮點數//ES5parseFloat("12.45str") //12.45//ES6Number.parseFloat("12.45str") //12.45復制代碼
另外,為了便于開發,Number還增加了一些方法和屬性
一、判斷一個數值是否是整數Number.isInteger(25) // trueNumber.isInteger(25.1) // false二、獲取JavaScript最大安全值和最小安全值Number.MAX_SAFE_INTEGER=9007199254740991Number.MIN_SAFE_INTEGER=-9007199254740991三、判斷一個數值是否是在安全范圍Number.isSafeInteger(9007199254740992) //false復制代碼
新引入原始數據類型,用來表示獨一無二的值。
let sym = Symbol();let sym2 = Symbol();console.log(sym == sym2); //false 生成的值是獨一無二的,所以不相等console.log(typeof sym); //symbol typeof查看值的類型為symbollet symWithDesc = Symbol("name"); //Symbol()括號內可以添加描述console.log(symWithDesc.toString()); //輸出:Symbol(name) 打印描述需要轉換成字符串復制代碼
一、消除魔術字符串
假如我們需要做一個點擊菜單,做不同處理的功能,我們通常會這樣實現。
const clickMenu = function (menu) { switch (menu) { case "home": break; case "me": break; } }; clickMenu("home")復制代碼
"home"這種可能會多次出現,與代碼形成強耦合的字符串就是魔術字符串,在項目中我們應該盡量消除魔術字符串,下面使用Symbol消除魔術字符串
const MENU_TYPE = { home: Symbol(), me: Symbol(), };const clickMenu = function () { switch (menu) { case MENU_TYPE.home: break; case MENU_TYPE.me: break; } }; clickMenu(MENU_TYPE.home);復制代碼
二、作為對象獨一無二的屬性值
假如我們想生成一個公司人名對象,并以每個人名為key值,這時候如果有人名重名便會有問題,而Symbol能解決這個問題
const scores = { [Symbol("張三")]: { age: 22, }, [Symbol("李四")]: { age: 21, }, [Symbol("張三")]: { age: 20, }, };復制代碼
注意,通過Symbol定義的屬性,只能通過下面兩種方式進行遍歷,否則無法獲取屬性。
for (let key of Object.getOwnPropertySymbols(scores)) { console.log(key, key); }for (let key of Reflect.ownKeys(scores)) { console.log(key, scores[key]); }復制代碼
為了更方便地實現數據操作,ES6新增了Set和Map兩種數據結構。
Set是類似于數組,但成員的值都是唯一的數據結構。
新建一個存儲月份的Set數據結構,可以定義一個空的Set實例,也可以是帶有數組形式的默認數據。
let monthSets = new Set();let monthSets2 = new Set(["一月","二月","三月"]);復制代碼
//添加數據monthSets.add("一月"); monthSets.add("二月").add("三月");console.log(monthSets); //Set(3) {"一月", "二月", "三月"}//遍歷集合Set//forEach():使用回調函數遍歷每個成員monthSets.forEach((item) => console.log(item)); //一月 二月 三月//for...of:直接遍歷每個成員for (const item of monthSets) { console.log(item); //一月 二月 三月}//刪除數據monthSets.delete("二月");console.log(monthSets); // Set(2) {"一月", "三月"}monthSets.clear(); //console.log(monthSets); // Set(0) {}復制代碼
Set數據結構在實際項目中還有很多應用場景。
let monthSets = new Set(["一月", "二月", "三月"]);//一、快速判斷數據元素是否存在monthSets.has("一月"); //true//二、統計數據元素個數monthSets.size; //3console.log(monthSets.size); //3//三、數組去重let arr = [1, 2, 3, 2, 3, 4, 5];let set = new Set(arr);console.log(set); // {1, 2, 3, 4, 5}//四、合并去重let arr = [1, 2, 3];let arr2 = [2, 3, 4];let set = new Set([...arr, ...arr2]);console.log(set); // {1, 2, 3, 4}//五、取數組交集let arr1 = [1, 2, 3];let arr2 = [2, 3, 4];let set1 = new Set(arr1);let set2 = new Set(arr2);let resultSet = new Set(arr1.filter((item) => set2.has(item)));console.log(Array.from(resultSet)); // [2, 3]//六、取數組差級let arr1 = [1, 2, 3];let arr2 = [2, 3, 4];let set1 = new Set(arr1);let set2 = new Set(arr2);let arr3 = arr1.filter((item) => !set2.has(item));let arr4 = arr2.filter((item) => !set1.has(item));console.log([...arr3, ...arr4]); //[1, 4]復制代碼
WeakSet與Set類似,也是不重復的值的集合,但WeakSet的成員只能是對象。WeakSet引用的對象都是弱引用,如果其他對象不再引用該對象,那么垃圾回收機制就會自動回收這些對象所占用的內存,不考慮該對象還存在于WeakSet之中。
React源碼中有很多地方使用到了WeakSet,例如在react-reconciler/src/ReactFiberHotReloading.new.js中。
export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) { if (__DEV__) { if (resolveFamily === null) { // Hot reloading is disabled. return; } if (typeof WeakSet !== 'function') { return; } if (failedBoundaries === null) { failedBoundaries = new WeakSet(); } failedBoundaries.add(fiber); } }復制代碼
Map是一種鍵值對集合,與對象類似,但Object只支持“字符串:值”,而Map支持“各種類型的值:值”,map給我們提供了更合適的“鍵值對”數據結構。
//定義let map = new Map();//添加數據let address = { address: "江蘇" }; map.set("name", "ES6"); map.set(27, "年齡信息"); map.set(address, "地址信息");console.log(map); //{"name" => "ES6", 27 => "年齡信息", {…} => "地址信息"}//獲取數據let name = map.get("name");let age = map.get(27);let addressObj = map.get(address);console.log(name, age, addressObj);//獲取成員數量console.log(map.size); //3//判斷是否指定key成員console.log(map.has("name")); //true復制代碼
map通常可以用forEach和for...of的方式進行遍歷。
//定義let map = new Map(); map.set("id", 1); map.set("name", "mango"); map.set("address", { province: "江蘇", city: "南京", }); map.forEach((key, value) => console.log(key, value));for (const [key, value] of map) { console.log(key, value); }//輸出 id 1 name mango address {province: "江蘇", city: "南京"}復制代碼
WeakMap與Map類似,也是用來生成鍵值對的集合。但WeakMap只接受對象作為鍵名,并且鍵名所指向的對象,屬于弱引用對象。
ES6對數組進行了很多的擴展,具體如下
擴展運算符是三個點(...),將一個數組轉為用逗號分隔的參數序列,通常用在函數參數中。
假如我們需要一個求和函數,并且支持傳入任意數量的值。
function sum(...params) { let sum = arr.reduce(function (prev, cur) { return prev + cur; }); return sum; }let arr = [1, 2, 3, 4, 5];console.log(sum(arr)); //輸出 15復制代碼
Array.from()方法從一個類似數組或可迭代對象創建一個新的淺拷貝的數組實例,通常有以下四種實用場景。
//一、克隆一個數組let num = [1, 2, 3];let newNum = Array.from(num);console.log(newNum, num === newNum); //[1, 2, 3] false//二、使用指定值,初始化一個數組//給定長度為10,默認值是數組2和對象{key:1}let length = 4;let defaultValue = 2;let defaultObj = { key: 1 };let arrValue = Array.from({ length }, (item,index) => defaultValue);let arrObj = Array.from({ length }, (item,index) => defaultObj);console.log(arrValue); // [2, 2, 2, 2]console.log(JSON.stringify(arrObj)); //[{"key":1},{"key":1},{"key":1},{"key":1}]//三、生成值范圍數組function range(end) { return Array.from({ length: end }, (item, index) => index); }let arr = range(4);console.log(arr); // [0, 1, 2, 3]//四、數組去重,結合set使用let arr = [1, 1, 2, 3, 3];let set = new Set(arr);console.log(Array.from(set));復制代碼
如何創建一個數組,有下面幾種常用方式
//一、數組字面量const arr1 = [];//二、構造函數const arr2 = Array(3); //[null,null,null]const arr3 = Array("3"); //["3"]//這時想要用構造函數創建一個數字為7的數組,發現上面方式是無法滿足的,而ES6提供了Array.of()能滿足我們的需求const arr3 = Array.of(7); //[7]復制代碼
find()方法返回數組中滿足提供的測試函數的第一個元素的值,若沒有找到對應元素返回undefined
findIndex()方法返回數組中滿足提供的測試函數的第一個元素的索引,若沒有找到對應元素則返回-1。
假如我們想要在一個成績數組中,找到達到及格分數的最低分。
const score = [34, 23, 66, 12, 90, 88, 77, 40];const passMin = score.find((value) => value > 60);console.log(passMin); //66const pass = score.findIndex((value) => value > 60);console.log(pass); //2復制代碼
ES6新增 for...of 數組遍歷方式
const score = [34, 23, 66, 12,];for (let value of score) { console.log(value); // 34, 23, 66, 12}復制代碼
ES6對函數進行了很多的擴展,具體如下
ES6允許為函數的參數設置默認值,即可以直接寫在參數定義的后面。
//參數b設置了默認值為2,在方法調用的時候并沒有傳值,所以b直接使用默認值function sum(a, b = 2) { return a + b; }console.log(sum(1)); //3復制代碼
ES6引入reset參數,形式為...變量名,可以用來獲取傳遞給函數的多余參數。
function sum(a, ...values) { console.log(a, values); //1 [2, 3, 4, 5]} sum(1, 2, 3, 4, 5);復制代碼
name屬性返回函數名,length屬性返回沒有指定默認值的參數個數。
function sum(a, b, c, d = 1) { console.log(a, values); //1 [2, 3, 4, 5]}console.log(sum.name); //sumconsole.log(sum.length); //3復制代碼
ES6允許使用箭頭(=>)的方式定義函數,有下面幾種箭頭函數實現形式。
想要實現一個加法函數,ES5的形式如下
function sum(a, b) { return a + b; }復制代碼
而如果使用箭頭函數實現的話,則如下
sumArrow = (a, b) => { return a + b; };復制代碼
上面是箭頭函數的基本變現形式,不同的場景還有不同的實現形式。
//對于上面的sumArrow函//一、如果只有一個參數,可以省略括號sumArrow = a => { return a; }; 二、如果返回值是表達式,可以省略return和{} sumArrow = a => a; 三、如果返回值是字面量對象,一定要用小括號包起來 sumArrow = () => ({ a: 1, b: 2 });復制代碼
箭頭函數與普通函數除了實現方式不同外,還有個不同的點就是對this的處理方式。
//普通函數let math = { name: "mathName", sum: function (a, b) { console.log(this.name); //math return a + b; }, }; math.sum();//箭頭函數globalThis.name = "globalName";let math = { name: "mathName", sum: (a, b) => { console.log(this.name); //globalName return a + b; }, }; math.sum();復制代碼
從上面示例可以看到,箭頭函數和普通函數最終打印的this.name不一致。對于普通函數,this指向的是調用sum方法的math對象,所以this.name打印的是“mathName”。而對于箭頭函數,this指向的是定義sum方法的全局對象,所以this.name打印的是“globalName”。
在后續的開發過程中,我們將會經常使用到箭頭函數,在使用的過程中,我們需要有以下幾點注意
解構賦值是一種表達式,可以將屬性和值從對象和數組中取出,賦值給其他變量。
假如我們拿到一個對象,需要獲取指定的屬性值。則解構賦值讓我們無需通過調用屬性的方式賦值,而是通過指定一個與對象結構相同模板的方式,獲取想要的屬性值。
const people = { name: "ES6", age: 27, sex: "male", };//如果通過調用屬性賦值,則需要這么做let name = people.name;let age = people.age;let sex = people.sex;console.log(name, age, sex); //ES6 27 male//而使用解構賦值的方式,代碼會更加的清晰簡單const { name, age } = People;console.log(name, age); //ES6 27 male復制代碼
除了上面這種基本用法,還有其他使用方式
const people = { name: "ES6", age: 27, sex: "male", };// 一、屬性順序不需保持一致,名稱相同即可const { age, name, sex } = people;console.log(name, age, sex); //ES6 27 male//二、取值時,重新定義變量名const { age: newAge, name: newName, sex: newSex } = people;console.log(name, age, sex); //Uncaught ReferenceError: age is not definedconsole.log(newName, newAge, newSex); //ES6 27 male//三、賦值過程中設置默認值const { nickName = "昵稱", age } = people;console.log(nickName, age); //昵稱 27//四、reset運算符。只獲取想要的屬性,其他屬性都放在新的變量里。const { name, ...peopleParams } = people;console.log(name, peopleParams); //ES6 {age: 27, sex: "male"}//五、嵌套對象取值const people = { name: "ES6", address: { province: "江蘇", }, };const { address: { province }} = people;console.log(province); //江蘇復制代碼
假如我們拿到一個數組,需要獲取指定的元素值。
const [a, b, c] = [1, 2, 3];console.log(a, b, c); //1 2 3復制代碼
除了上面這種基本用法,還有其他使用方式
//一、待解構的除了是數組,還可以是任意可遍歷的對象const [a, b, c] = new Set([1, 2, 3]);console.log(a, b, c); //1 2 3//二、被賦值的變量還可以是對象的屬性,不局限于單純的變量const num = {}; [num.a, num.b, num.c] = [1, 2, 3];console.log(num); //{a: 1, b: 2, c: 3}//三、解構賦值在循環體中的應用const num = { a: 10, b: 20, c: 30, };for (const [key, value] of Object.entries(num)) { console.log(key, value); //a 10 b 20 c 30}//四、跳過賦值元素const [a, , c] = [1, 2, 3]; //存在空位的數組叫稀疏數組console.log(a, c); //1 3//五、rest 參數const [a,...other] = [1, 2, 3];console.log(a, other); //1 [2, 3]//六、賦值過程中設置默認值const [a, , , d = 10] = [1, 2, 3];console.log(d); //10復制代碼
字符串解構賦值可以當成數組解構賦值
const [a, b, c, d] = "ECMAScript2015";console.log(a, b, c, d); //E C M A復制代碼
ES6對對象進行了很多的擴展,具體如下
從ES6開始,如果對象的屬性名和屬性值相同,則有簡寫的方式。
let province = "江蘇";const address = { province, //等同于 province: province city: "南京", };復制代碼
從ES6開始,可以使用變量或表達式定義對象的屬性。
let key = "province";const address = { [key]: "省份", city: "南京", };console.log(address); //{province: "省份", city: "南京"}復制代碼
判斷兩個值是否是同一個值。在Object.is()之前,有“==”和“===”兩種方式判斷值是否相等,但這兩個方式都有一定缺陷,如下
//== 在判斷相等前會對不是同一類型的變量進行強制轉換,最終導致“”與false相等console.log("" == false); //true//=== 會將-0與+0視為相等,而將Number.NaN與NaN視為不相等console.log(-0 === +0); //trueconsole.log(Number.NaN === NaN); //false復制代碼
所以,需要一種運算,在所有場景下,只要兩個值是一樣的,那么就應該相等,在實際項目開發過程中,推薦使用Object.is()來判斷值相等。
console.log(Object.is(-0, +0)); //falseconsole.log(Object.is(Number.NaN, NaN)); //truelet a = { value: 1 };let b = { value: 1 };console.log(Object.is(a, b)); //false 對象都是同一個引用才相等復制代碼
用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象,它將返回目標對象。
語法:
Object.assign(target, ...sources) 參數說明: target:目標對象 sources:源對象 返回值:合并后的目標對象
const target = { a: 1,};const source = { b: "B", c: "C" };const assignObj = Object.assign(target, source);console.log(assignObj); //{a: 1, b: "B", c: "C"} //其他應用//一、如果目標對象與源對象屬性具有相同值,則源對象屬性值會覆蓋目標對象屬性值const target = { a: 1,b: 2};const source = { b: "B", c: "C" };const assignObj = Object.assign(target, source);console.log(assignObj); //{a: 1, b: "B", c: "C"} //目標對象的b屬性值被覆蓋//二、源對象可以有多個值const target = { a: 1 };const source1 = { b: "B", c: "C" };const source2 = { d: "D", e: "E" };const assignObj = Object.assign(target, source1, source2);console.log(assignObj); //{a: 1, b: "B", c: "C", d: "D", e: "E"}復制代碼
假如我們想要循環遍歷一個對象的鍵與值,則可以使用下面幾種方式進行遍歷
const score = { name: "mango", age: "25", score: 80, };//for...infor (let key in score) { console.log(key, score[key]); // 分別輸出:name mango 、 age 25 、score 80}//Object.keys()用來獲取所有key組成的數組Object.keys(scoreObj).forEach(key => { console.log(key, scoreObj[key]) //分別輸出:name mango 、 age 25 、score 80})//Object.getOwnPropertyNames()用來獲取所有key組成的數組Object.getOwnPropertyNames(scoreObj).forEach(key => { console.log(key, scoreObj[key]) //分別輸出:name mango 、 age 25 、score 80})//Reflect.ownKeys()用來獲取所有key組成的數組Reflect.ownKeys(scoreObj).forEach(key => { console.log(key, scoreObj[key]) //分別輸出:name mango 、 age 25 、score 80})復制代碼
JavaScript是一種基于對象的語言,我們遇到的所有東西幾乎都是對象,但ES6之前是沒有class的,而在ES6版本中正式引入了class,讓JavaScript成為了一種真正的面向對象語言,我們可以像下面這樣在JavaScript中進行面向對象編程。
//通過class關鍵字定義類class People{ //類的構造函數 constructor(name, age) { this.name = name; this.age = age; } //實例方法 getName() { return this.name; } //靜態方法 static say() { console.log("Hello ES6"); } }//繼承class Student extends People { constructor(name, age) { super(name, age); } }//對象創建與調用let student = new Student("mango", "27"); student.getName(); Student.say();復制代碼
通過上面的代碼,我們具體說明下JavaScript中進行面向對象編程。
通過class關鍵字聲明類,支持構造函數construct做對象初始化。
class People{ constructor() { //初始化 } }復制代碼
Class對象中有兩種對象屬性,分別是實例屬性和靜態屬性。實例屬性必須定義在類的方法里,而靜態屬性必須定義在類的外面。
class People{ constructor() { //定義實例屬性 this.name = ""; this.age = 0; } } People.desc="類描述"; //定義的靜態屬性//訪問People people=new People();console.log(people.name);console.log(People.name);復制代碼
類中定義的屬性,默認都是可讀可寫的,但是如果這時候我們想指定屬性不可被修改該如何實現呢?那么便要用到set和get了,set和get可以定義一個屬性,但是如果只有get而沒有set,則屬性不可以進行修改。
class People { get sex() { return "男"; } }let people = new People();console.log(people.sex); people.sex="女" //Uncaught TypeError: Cannot set property sex of #<People> which has only a getter復制代碼
Class對象中有三種方法,分別是構造方法、實例方法還有靜態方法。
class People { //構造方法 constructor(name, age) { this.nameA = name; this.age = age; } //實例方法 getName() { return this.nameA; } //靜態方法 static say() { console.log("Hello " + People.desc); } } People.desc = "類描述";let people = new People("mango", "27");let name = people.getName();console.log(name); //mangoPeople.say(); //Hello 類描述復制代碼
繼承是面向對象語言很重要的一大特征,ES6新加入了extends和super關鍵字來實現繼承。
class People { constructor(name) { this.name = name; } getName() { return this.name; } }//繼承class Student extends People { constructor(name, age) { super(name, age); } }//Student類繼承了People類,student對象中super調用了父類的構造函數,并傳遞了name參數,因為繼承的特性,student也擁有了父類的getName()方法let student = new Student("ES6");console.log(student.getName());復制代碼
通過以上對class的學習,我們得知道其實class并不是新引入的數據類型,其實class只是一種語法糖,它的實質完全可以看作構造函數的另一種寫法。
class People { constructor(name) { this.name = name; } getName() { return this.name; } }console.log(typeof People); //functionconsole.log(People.prototype); //{constructor: ?, getName: ?}復制代碼
異步編程其實就是處理異步任務,在進行異步編程之前,我們需要了解JavaScript是單線程的,在同一時間只能做一件事。
JavaScript之所以設計成單線程,是與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動以及操作DOM。這決定了它只能是單線程,否則會帶來很多復雜的同步問題。例如,如果JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器就不曉得以哪個線程為準。所以,為了避免復雜,從一誕生,JavaScript就是單線程的。
單線程就意味著,所有任務都需要排隊,前一個任務結束,后一個任務才會執行。那么如果前一個任務很長的話,那么后面一個任務不是就一直需要等待了嗎?于是乎,JS將所有任務分成了兩類,同步和異步。
同步:只有前一個任務執行完畢,才能執行后一個任務
異步:當同步任務執行到某個需要耗時執行的運行環境API時,就會觸發異步任務,此時運行環境(瀏覽器或Node)就會單獨開線程去處理這些異步任務。
下面是JavaScript運行原理圖,同步任務在JS主線程完成,異步任務則新開一個線程
疑問:不是說JavaScript是單線程的嗎,那為什么又新開了一條線程處理任務呢,這不是多線程方式嗎?
有這個疑問不奇怪,我們需要清楚JavaScript單線程其實說的是JavaScript引擎是單線程的,開發者只能通過單線程的方式進行JavaScript開發,而新開了一條線程處理任務是底層執行環境決定的,JavaScript執行環境是多線程。
在實際項目中,異步編程使用場景極其之多,請求個接口數據、創建個定時器、緩存個數據都離不開異步編程的身影,為了更好的處理異步任務,ES6給我們提供兩種新的方式,分別是Promise和Generator。
Promise 是一個代理對象,代表了一個異步任務的最終成功或者失敗狀態。Promise允許你為異步任務的成功或失敗分別設置對應的處理方法,以類似同步的方式便捷的進行異步編程。
一個Promise有三種狀態:
const promise = new Promise(function (resolve, reject) { let result=執行異步任務; if(result){ //如果異步任務成功完成 resolve() }else{ //如果異步任務執行失敗 reject(); } });復制代碼
創建Promise對象需要傳遞一個executor參數,executor是帶有resolve和reject兩個參數的函數,這兩個參數是JavaScript引擎提供的兩個函數,Promise構造函數執行會立即調用executor函數。
實例好promise對象后,我們可以使用下面的語法來對異步任務完成后的狀態進行處理。
promise.then(onFulfilled,onRejected)
promise.then( (result) => { console.log("異步任務處理成功,執行相應方法"); }, (error) => { console.log("異步任務處理失敗,執行相應方法"); } );復制代碼
但在具體的項目開發中,我們通常都是使用已經存在的Promise對象,下面我們就通過使用常用的promise對象fetch獲取接口數據。
我們需要調用接口,獲取一個用戶列表數據
fetch("http://jsonplaceholder.typicode.com/users") .then(function (response) { return response.json(); }) .then(function (res) { console.log(res); // [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] });復制代碼
response是一個包含響應結果的Response對象,它只是一個HTTP響應,而不是真正的JSON。為了獲取JSON的內容,需要使用json()方法獲取一個Promise對象,然后再使用then獲取JSON數據。
Promise提供了catch()方法,用來捕獲異步操作過程中遇到的錯誤異常,使用場景如下
const CatchPromise = new Promise(function (resolve, reject) { reject(new Error("error msg")); }); CatchPromise.then().catch((e) => { console.error(e); //Error: error msg});復制代碼
在Promise對象中,除了可以使用reject(new Error())的方式觸發異常,還可以使用throw new Error()的方式觸發異常,但不建議使用throw new Error()的方式,因為這種方式不會改變Promise的狀態。
Promise.all()用于處理多個異步任務,例如處理多張圖片上傳。Promise.all()接受一個promise對象數組作為參數,執行完畢返回一個Promise對象。
Promise.all()的狀態變化:
傳入的promise對象數組全部變為fulfill狀態則返回成功,調用resolve()
傳入的promise對象數組有一個變為reject狀態則返回失敗,調用reject()
const promise1 = new Promise(function (resolve, reject) { setTimeout(function () { resolve("promise1"); }, 2000); });const promise2 = new Promise(function (resolve, reject) { setTimeout(function () { resolve("promise2"); }, 1000); });const promise3 = new Promise(function (resolve, reject) { setTimeout(function () { resolve("promise3"); }, 3000); });const promiseAll = Promise.all([promise1, promise2, promise3]); promiseAll.then(function (results) { console.log(results); // ["promise1", "promise2", "promise3"]});復制代碼
Promise.race()也是用于處理多個異步任務,與Promise.all()一樣,Promise.race()接受一個promise對象數組作為參數,執行完畢返回一個Promise對象。
Promise.race()的狀態變化:
傳入的promise對象數組有一個變為resolve狀態則返回成功,調用resolve()
傳入的promise對象數組有一個變為reject狀態則返回失敗,調用reject()
const promise1 = new Promise(function (resolve, reject) { setTimeout(function () { reject("promise1"); }, 2000); });const promise2 = new Promise(function (resolve, reject) { setTimeout(function () { resolve("promise2"); }, 1000); });const promise3 = new Promise(function (resolve, reject) { setTimeout(function () { resolve("promise3"); }, 3000); });const promiseAll = Promise.race([promise1, promise2, promise3]); promiseAll.then(function (results) { console.log(results); // promise2});復制代碼
Generator函數是用來處理異步任務的函數,函數內部包裹的就是異步任務的處理。Generator不同于普通函數,當執行到異步任務,可以暫停,直到異步任務執行完畢再繼續往下執行,類似同步的方法進行異步編程。
Generator函數在使用上具體有以下特點
那么具體如何使用generator函數實現異步編程呢,在學習Promise的時候,我們實現了一個獲取一個用戶列表數據的案例,下面我們看看如何使用generator函數實現吧。
function* loadUsers() { const API = "http://jsonplaceholder.typicode.com/users"; console.log("等待數據請求"); yield fetch(API); //暫停,開始執行異步任務 console.log("數據請求完成"); console.log("繼續其他邏輯操作"); }const generator = loadUsers();const promise = generator.next().value;console.log(promise); promise .then(function (response) { return response.json(); }) .then(function (result) { console.log(result); //[{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] generator.next(); //打印:數據請求完成 繼續其他邏輯操作。異步任務執行完畢后調用next(),繼續執行generator函數中后續代碼 });復制代碼
Proxy翻譯過來叫代理,Proxy可以通過自定義行為來改變對象的基本操作,例如屬性賦值、查找、枚舉、函數調用等。
const p=new Proxy(target,handler); 參數說明: target:需要使用Proxy包裝的目標對象(可以是任何類型的對象,包括原生數組,函數,甚至是另外一個代理) handler:代理目標對象基本操作的對象 返回值: p:target被代理后不可以直接訪問,而只能訪問
let people = { name: "mango", //設置屬性不可讀 age: 27, //設置屬性不可被修改};let peopleHandler = { //設置屬性值讀取的捕捉器 get: function (target, prop, receiver) { if (Object.is(prop, "name")) { return prop + "屬性不可讀"; } }, //設置屬性值操作的捕捉器 set: function (target, prop, value) { if (Object.is(prop, "age")) { throw new Error(prop + "屬性不可寫"); } }, };let peopleProxy = new Proxy(people, peopleHandler); peopleProxy.age = 10; // Uncaught Error: age屬性不可寫console.log(peopleProxy.name); //輸出:name屬性不可讀復制代碼
let people = { address: null, };let peopleHandler = { //設置屬性值讀取的捕捉器 get: function (target, prop, receiver) { return target[prop] ?? "默認值"; //空值合并操作符 }, };let peopleProxy = new Proxy(people, peopleHandler);console.log(peopleProxy.address); //輸出:默認值復制代碼
//handler.apply()用于攔截函數的調用function sum(a, b) { console.log(a + b); return a + b; }////對于sum方法,關注的是處理數據相加的邏輯//通過代理則可以處理在調用方法時候,對參數的校驗,數據打點等const sumProxy = new Proxy(sum, { apply: function (target, thisArg, argumentsList) { console.log("調用了方法", "打點"); }, }); sumProxy(1, 2);復制代碼
**
ES6在語言標準上,通過Module實現了模塊功能,現階段幾乎取代之前用來實現JavaScript模塊化的CommonJS和AMD規范,成為了瀏覽器環境和node環境通用的模塊化解決方案。
Module實現的模塊化屬于“編譯時加載”,即在編譯時就完成了模塊之間的加載,通過這種“編譯時加載”的方式,使得在不運行代碼的情況下就可以通過詞法分析、語法分析等對程序代碼進行掃描,以驗證代碼的規范性、安全性和可維護性,讓靜態分析成為了可能。
Module實現的模塊化功能主要有兩個命令構成:
一個模塊就是一個獨立的文件,該文件內的所有變量,外部無法獲取,如果想要外部獲取模塊內的某些變量,就必須使用export關鍵字導出變量,在需要引入該導出變量的的模塊中必須使用import關鍵字引入變量。
下面舉例說明export命令導出對外接口的幾種方式,在ExportDemo.js文件中,
//方法一export let a = 1;//方法二let b = 2;export { b };復制代碼
//方法一export function test(){ console.log("name"); }//方法二let test2=function test(){ console.log("name"); }export {test2 as newName}復制代碼
注意在方法二中,使用了as關鍵字,as關鍵字可以在導出時重命名對外的接口名。
//方法一export class People { say() { console.log("Hello Module"); } }//方法二export { People };復制代碼
使用export導出了模塊中的對外接口后,其他JS文件就可以通過import關鍵字加載這個模塊,使用如下。
//大括號中的變量名必須與被導出對外接口名一致import { a, b, test as newTest, People } from "./ExportDemo";//導入a和b的變量console.log(a, b);//導入test方法,同樣可以使用as關鍵字在導入的時候重命名newTest();//導入People類let people = new People(); people.say();復制代碼
其他使用方式**
在日常開發過程中,Module模塊化還有下面幾種常見的使用方式。
//import { a, b, test, People } from "./ExportDemo";//上面的導入方式可以改寫成下面方式import * as ExportModule from "./ExportModule";//使用的使用,加上前綴即可ExportModule.test()復制代碼
//導出const People = { say: function () { console.log("Hello Module"); }, };export default People;//導入import People from "./ExportModule"; People.say(); //Hello Module復制代碼
//假如有a、b、c三個文件模塊,//c文件模塊如下 c.jslet people={name:"mango",age:27};let address="南京";export { people, address };//有下面幾種使用場景//一、在b中導入c中的people和address,并導出給a使用export {people,address} from 'c'//二、在b中整體導入c,并導出給a使用export * from 'c'//三、在b中導入people,并作為b的導出名稱【具名接口改為默認接口】export {people as default} from 'c'//當c的導出方式為export default的時候,并可以使用【默認接口改為具名接口】export {default as NewPeople} from 'c'復制代碼
感謝各位的閱讀!關于ECMAScript的新特性有哪些就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。