今天就跟大家聊聊有關現代JavaScript ,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
在 JavaScript 中,有 3 個關鍵字可用于聲明一個變量,他們之間有些差異。那些是 var ,let 和 const。
使用 const 關鍵字聲明的變量無法重新分配,而 let 和 var 可以。
我建議總是默認使用 const 來聲明你的變量,如果你需要改變它,或者稍后需要重新分配,那么實用 let 。
| 作用域 | 可否重新分配 | 可變性 | 暫時性死區 | |
|---|---|---|---|---|
| const | Block | No | Yes | Yes |
| let | Block | Yes | Yes | Yes |
| var | Function | Yes | Yes | No |
const person = "Nick"; person = "John" // 將會引起錯誤,person 不能重新分配
let person = "Nick"; person = "John"; console.log(person) // "John", 使用 let 允許重新分配
變量的 作用域 大致意思是“在哪里可以訪問這個變量”。
var 聲明的變量是 函數作用域 ,這意味著當在函數中創建變量時,該函數中的所有內容都可以訪問該變量。
此外,函數中創建的 函數作用域 變量無法在此函數外訪問。
我建議你把它看作一個 X 作用域 變量,意味著這個變量是 X 的屬性。
function myFunction() {
var myVar = "Nick";
console.log(myVar); // "Nick" - 在這個函數中 myVar 可被訪問到
}
console.log(myVar); // 拋出錯誤 ReferenceError, 在函數之外 myVar 則無法訪問繼續關注變量的作用域,這里是一個更微妙的例子:
function myFunction() {
var myVar = "Nick";
if (true) {
var myVar = "John";
console.log(myVar); // "John"
// 實際上,myVar是函數作用域,我們只是將之前 myVar 的值 "Nick" 抹去,重新聲明為 "John"
}
console.log(myVar); // "John" - 看看 if 語句塊中的指令是如何影響這個值的
}
console.log(myVar); // 拋出錯誤 ReferenceError, 在函數之外 myVar 則無法訪問此外,在執行過程中,var聲明的變量被移動到范圍的頂部。這就是我們所說的變量聲明提升(var hoisting)。
這部分的代碼:
console.log(myVar) // undefined -- 不會報錯 var myVar = 2;
在執行時,被解析為:
var myVar; console.log(myVar) // undefined -- 不會報錯 myVar = 2;
愚人碼頭注:變量提升和函數聲明提升可以查看:JavaScript 中的 Hoisting (變量提升和函數聲明提升)
var 和 let 大致相同,但是用 let 聲明的變量時有以下幾個特性:
塊級作用域( block scoped )
在被分配之前, 無法 訪問使用
在同一個作用域之下,不能重新聲明
我們來看看我們前面的例子,采用塊級作用域( block scoping )后的效果:
function myFunction() {
let myVar = "Nick";
if (true) {
let myVar = "John";
console.log(myVar); // "John"
// 實際上 myVar 在塊級作用域中,(在 if 語句塊中)我們只是創建了一個新變量 myVar。
// 此變量在 if 語句塊之外不可訪問,
// 完全獨立于創建的第一個 myVar 變量!
}
console.log(myVar); // "Nick", 可以看到 if 語句塊中的指令不會影響這個值
}
console.log(myVar); // 拋出錯誤 ReferenceError,myVar 無法在函數外部被訪問。現在,我們來看看 let(和 const)變量在被賦值之前不可訪問是什么意思:
console.log(myVar) // 拋出一個引用錯誤 ReferenceError ! let myVar = 2;
與 var 變量不同的是,如果你在 let 或者 const 變量被賦值之前讀寫,那么將會出現錯誤。這種現象通常被稱為暫存死區(Temporal dead zone)或者 TDZ。
注意: 從技術上講,let 和 const 變量聲明時也存在提升,但并不代表它們的賦值也會被提升。但由于它被設計成了賦值之前無法使用,所以我們直觀感覺上它沒有被提升,但其實是存在提升的。如果想了解更多細節,請看這篇文章。
此外,您不能重新聲明一個 let 變量:
let myVar = 2; let myVar = 3; // 拋出語法錯誤 SyntaxError
const 聲明的變量很像 let ,但它不能被重新賦值。
總結一下 const 變量:
塊級作用域
分配之前無法訪問
在同一個作用域內部,不能重新聲明
不能被重新分配
const myVar = "Nick"; myVar = "John" // 拋出一個錯誤, 不允許重新分配
const myVar = "Nick"; const myVar = "John" // 拋出一個錯誤, 不允許重新聲明
但這里有一個小細節:const 變量并不是不可變,具體來說,如果 const 聲明的變量是 object 和 array 類型的值,那它是 可變的 。
對于對象:
const person = {
name: 'Nick'
};
person.name = 'John' // 這是有效的!person 變量并非完全重新分配,只是值被改變
console.log(person.name) // "John"
person = "Sandra" // 拋出一個錯誤, 因為用 const 聲明的變量不能被重新分配對于數組:
const person = [];
person.push('John'); // 這是有效的!person 變量并非完全重新分配,只是值被改變
console.log(person[0]) // "John"
person = ["Nick"] // 拋出一個錯誤, 因為用 const 聲明的變量不能被重新分配How let and const are scoped in JavaScript – WesBos
Temporal Dead Zone (TDZ) Demystified
ES6 JavaScript 更新引入了 箭頭函數 ,這是另一種聲明和使用函數的方法。以下是他們帶來的好處:
更簡潔
this 的值繼承自外圍的作用域
隱式返回
簡潔性和隱式返回
function double(x) { return x * 2; } // 傳統的方法
console.log(double(2)) // 4const double = x => x * 2; // 相同的函數寫成帶有隱式返回的箭頭函數 console.log(double(2)) // 4
this 的引用
在箭頭函數中, this 意味著封閉執行上下文的 this 值?;旧?,使用箭頭函數,在函數中調用函數之前,您不需要執行 “that = this” 這樣的的技巧。
function myFunc() {
this.myVar = 0;
setTimeout(() => {
this.myVar++;
console.log(this.myVar) // 1
}, 0);
}箭頭函數在許多方面比傳統函數更簡潔。讓我們來看看所有可能的情況:
隱式 VS 顯式返回
顯式返回 (explicit return) 是指在函數體中明確的使用 return 這個關鍵字。
function double(x) {
return x * 2; // 這個函數顯示返回 x * 2, 并且使用了 *return* 關鍵字
}在函數傳統的寫法中,返回總是顯式的。但是如果是使用箭頭函數,你可以執行 隱式返回(implicit return),這表示你不需要使用關鍵字 return 來返回一個值。
要做隱式回傳,代碼必須用一行語句寫完。
const double = (x) => {
return x * 2; // 這里是顯式返回
}由于這里只有一個返回值,我們可以做一個隱式的返回。
const double = (x) => x * 2;
這樣做,我們只需要 移除括號 以及 return 關鍵字。這就是為什么它會被稱為 隱式 返回,return 關鍵字不在了,但是這個函數確實會返回 x * 2。
注意: 如果你的函數沒有回傳一個值 (這種作法有 副作用),那么它將不會做顯式或隱式返回。
另外,如果你想隱式地返回一個 對象(object),你必須用括號包裹,否則它將與塊大括號沖突:
const getPerson = () => ({ name: "Nick", age: 24 })
console.log(getPerson()) // { name: "Nick", age: 24 } -- 箭頭函數隱式地返回一個對象只有一個參數
如果你的函數只接受一個參數,你可以省略包裹它的括號。如果我們拿上述的 double 代碼做為舉例:
const double = (x) => x * 2; // 這個箭頭函數只接受一個參數
包裹參數的括號是可以被省略:
const double = x => x * 2; // 這個箭頭函數只接受一個參數
沒有參數
當沒有為箭頭函數提供任何參數時,你就必須加上括號,否則將會拋出語法錯誤。
() => { // 提供括號,一切都能正常運行
const x = 2;
return x;
} => { // 沒有括號,這不能正常運行!
const x = 2;
return x;
}要理解箭頭函數的精妙之處,你就必須知道 this 在 JavaScript 中是如何運作的。
在一個箭頭函數中,this 等同于封閉執行上下文的 this 值。這意味著,一個箭頭函數并不會創造一個新的 this,而是從它的外圍作用域中抓取的。
如果沒有箭頭函數,你想在一個函數內部的函數中通過 this 訪問變量,你就只能使用 that = this 或者是 self = this 這樣的技巧。
舉例來說,你在 myFunc 中使用 setTimeout 函數:
function myFunc() {
this.myVar = 0;
var that = this; // that = this 技巧
setTimeout(
function() { // 在這個函數作用域中,一個新的 *this* 被創建
that.myVar++;
console.log(that.myVar) // 1
console.log(this.myVar) // undefined -- 見上訴函數聲明
},
0
);
}但是如果你使用箭頭函數,this 是從它的外圍作用域中抓取的:
function myFunc() {
this.myVar = 0;
setTimeout(
() => { // this 值來自它的外圍作用域, 在這個示例中,也就是 myFunc 函數
this.myVar++;
console.log(this.myVar) // 1
},
0
);
}Arrow functions introduction – WesBos
JavaScript arrow function – MDN
Arrow function and lexical this
從 ES2015 JavaScript 更新之后開始,你可以通過下列的語法為函數的參數設定默認值:
function myFunc(x = 10) {
return x;
}
console.log(myFunc()) // 10 -- 沒有提供任何值,所以在 myFunc 中 10 做為默認值分配給 x
console.log(myFunc(5)) // 5 -- 有提供一個參數值,所以在 myFunc 中 x 等于 5
console.log(myFunc(undefined)) // 10 -- 提供 undefined 值,所以默認值被分配給 x
console.log(myFunc(null)) // null -- 提供一個值 (null),詳細資料請見下文默認參數應用于兩種且僅兩種情況:
沒有提供參數
提供 undefined 未定義參數
換句話說,如果您傳入 null ,則不會應用默認參數。
注意: 默認值分配也可以與解構參數一起使用(參見下一個概念以查看示例)。
Default parameter value – ES6 Features
Default parameters – MDN
解構 (destructuring) 是通過從存儲在對象或數組中的數據中提取一些值來創建新變量的簡便方法。
舉個簡單的實例,destructuring 可以被用來解構函數中的參數,或者像是 React 項目中 this.props 這樣的用法。
Object
我們考慮一下以下對象的所有屬性:
const person = {
firstName: "Nick",
lastName: "Anderson",
age: 35,
sex: "M"
}不使用解構:
const first = person.firstName; const age = person.age; const city = person.city || "Paris";
使用解構,只需要 1 行代碼:
const { firstName: first, age, city = "Paris" } = person; // 就是這么簡單!
console.log(age) // 35 -- 一個新變量 age 被創建,并且其值等同于 person.age
console.log(first) // "Nick" -- 一個新變量 first 被創建,并且其值等同于 person.firstName A new variable first is created and is equal to person.firstName
console.log(firstName) // Undefined -- person.firstName 雖然存在,但是新變量名為 first
console.log(city) // "Paris" -- 一個新變量 city 被創建,并且因為 person.city 為 undefined(未定義) ,所以 city 將等同于默認值也就是 "Paris"。注意: 在 const { age } = person; 中, const 關鍵字后的括號不是用于聲明對象或代碼塊,而是 解構(structuring) 語法。
函數參數
解構(structuring) 經常被用來解構函數中的對象參數。
不使用解構:
function joinFirstLastName(person) {
const firstName = person.firstName;
const lastName = person.lastName;
return firstName + '-' + lastName;
}
joinFirstLastName(person); // "Nick-Anderson"在解構對象參數 person 這個參數時,我們可以得到一個更簡潔的函數:
function joinFirstLastName({ firstName, lastName }) { // 通過解構 person 參數,我們分別創建了 firstName 和 lastName 這兩個變數
return firstName + '-' + lastName;
}
joinFirstLastName(person); // "Nick-Anderson"箭頭函數中使用解構,使得開發過程更加愉快:
const joinFirstLastName = ({ firstName, lastName }) => firstName + '-' + lastName;
joinFirstLastName(person); // "Nick-Anderson"Array
我們考慮一下以下數組:
const myArray = ["a", "b", "c"];
不使用解構:
const x = myArray[0]; const y = myArray[1];
使用解構:
const [x, y] = myArray; // 就是這么簡單! console.log(x) // "a" console.log(y) // "b"
ES6 Features – Destructuring Assignment
Destructuring Objects – WesBos
ExploringJS – Destructuring
map,filter 和 reduce 都是數組提供的方法,它們源自于 函數式編程 。
總結一下:
Array.prototype.map() 接受一組數組,在其元素上執行某些操作,并返回具有轉換后的元素數組。
Array.prototype.filter() 接受一組數組,依照元素本身決定是否保留,并返回一個僅包含保留元素的數組。
Array.prototype.reduce() 接受一組數組,將這些元素合并成單個值(并返回)。
我建議盡可能地使用它們來遵循函數式編程 (functional programming) 的原則,因為它們是可組合的,簡潔的且優雅的。
通過這三種方法,您可以避免在大多數情況下使用 for 和 forEach 。當你試圖做一個 for 循環時,嘗試用 map,filter 和 reduce 來組合試試。起初你可能很難做到這一點,因為它需要你學習一種新的思維方式,但一旦你得到它,事情會變得更容易。
愚人碼頭注:JavaScript 函數式編程建議看看以下幾篇文章
JavaScript 中的 Currying(柯里化) 和 Partial Application(偏函數應用)
一步一步教你 JavaScript 函數式編程(第一部分)
一步一步教你 JavaScript 函數式編程(第二部分)
一步一步教你 JavaScript 函數式編程(第三部分)
JavaScript 函數式編程術語大全
const numbers = [0, 1, 2, 3, 4, 5, 6]; const doubledNumbers = numbers.map(n => n * 2); // [0, 2, 4, 6, 8, 10, 12] const evenNumbers = numbers.filter(n => n % 2 === 0); // [0, 2, 4, 6] const sum = numbers.reduce((prev, next) => prev + next, 0); // 21
通過組合 map,filter 和 reduce 來計算 10 分以上的學生成績總和 sum :
const students = [
{ name: "Nick", grade: 10 },
{ name: "John", grade: 15 },
{ name: "Julia", grade: 19 },
{ name: "Nathalie", grade: 9 },
];
const aboveTenSum = students
.map(student => student.grade) // 我們將學生數組映射到他們成績的數組中
.filter(grade => grade >= 10) // 我們過濾成績數組以保持10分以上的元素
.reduce((prev, next) => prev + next, 0); // 我們將合計所有10分以上的成績
console.log(aboveTenSum) // 44 -- 10 (Nick) + 15 (John) + 19 (Julia), 低于10的 Nathalie 被忽略讓我們考慮一下下列數組:
const numbers = [0, 1, 2, 3, 4, 5, 6];
const doubledNumbers = numbers.map(function(n) {
return n * 2;
});
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]這里發生了什么?我們在 numbers 這個數組中使用 .map 方法,map 將會迭代數組的每個元素,并傳遞給我們的函數。該函數的目標是生成并返回一個新的值,以便 map 可以替換掉原本的數組。
我們來解釋一下這個函數,使之更清楚一點:
const doubleN = function(n) { return n * 2; };
const doubledNumbers = numbers.map(doubleN);
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]numbers.map(doubleN) 將會產生 [doubleN(0), doubleN(1), doubleN(2), doubleN(3), doubleN(4), doubleN(5), doubleN(6)] 而它們分別等同于 [0, 2, 4, 6, 8, 10, 12]。
注意: 如果你不需要返回一個新的數組, 且只想執行一個帶有副作用的循環,使用 for / forEach 循環會更為符合你的需求。
const evenNumbers = numbers.filter(function(n) {
return n % 2 === 0; // 如果 "n" 符合條件返回 true , 如果 "n" 不符合條件則 false 。
});
console.log(evenNumbers); // [0, 2, 4, 6]我們在 numbers 數組中使用 .filter 方法,過濾器遍歷數組中的每個元素,并將其傳遞給我們的函數。函數的目標是返回一個布爾值,它將確定當前值是否被保留。過濾之后返回的數組將只包含保留值。
reduce 方法的目標是將迭代數組中的所有元素,減少 到只留下單一值。如何聚合這些元素取決于你。
const sum = numbers.reduce(
function(acc, n) {
return acc + n;
},
0 // 累加器迭代變量的初始值
);
console.log(sum) //21就像 .map 和 .filter 方法一樣, .reduce 方法被應用在數組上并接收一個函數做為第一個參數。
下面是一些差異:
.reduce 接受兩個參數
第一個參數是一個函數,將在每個迭代步驟中被調用。
第二個參數是在第一個迭代步驟(讀取下一個用的)的累加器變量的值(此處是 acc)。
函數參數
作為 .reduce 的第一個參數傳遞的函數需要兩個參數。第一個(此處是 acc)是累加器變量,而第二個參數(n)則是當前元素。
累加器變量的值等于 上一次 迭代步驟中函數的返回值。在迭代過程的第一步,acc 等于你做為 .reduce 時第二個參數所傳遞的值(愚人碼頭注:也就是累加器初始值)。
acc = 0 因為我們把 0 做為 reduce 的第二個參數
n = 0 number 數組的第一個元素
函數返回 acc + n –> 0 + 0 –> 0
acc = 0 因為它是上次迭代所返回的值
n = 1 number 數組的第二個元素
函數返回 acc + n –> 0 + 1 –> 1
acc = 1 因為它是上次迭代所返回的值
n = 2 number 數組的第三個元素
函數返回 acc + n –> 1 + 2 –> 3
acc = 3 因為它是上次迭代所返回的值
n = 3 number 數組的第四個元素
函數返回 acc + n –> 3 + 3 –> 6
acc = 15 因為它是上次迭代所返回的值
n = 6 number 數組的最后一個元素
函數返回 acc + n –> 15 + 6 –> 21
因為它是最后一個迭代步驟了, .reduce 將返回 21 。
Understanding map / filter / reduce in JS
ES2015 已經引入了展開操作符 ... ,可以將可迭代多個元素(如數組)展開到適合的位置。
const arr1 = ["a", "b", "c"]; const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"]
function myFunc(x, y, ...params) {
console.log(x);
console.log(y);
console.log(params)
}
myFunc("a", "b", "c", "d", "e", "f")
// "a"
// "b"
// ["c", "d", "e", "f"]const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
const n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }如果我們有以下兩個數組:
const arr1 = ["a", "b", "c"]; const arr2 = [arr1, "d", "e", "f"]; // [["a", "b", "c"], "d", "e", "f"]
arr2 的第一個元素是一個數組 ,因為 arr1 是被注入到 arr2 之中的。但我們真正想要得到的 arr2 是一個純字母的數組。為了做到這點,我們可以將 arr1 展開(spread) 到 arr2。
通過展開操作符:
const arr1 = ["a", "b", "c"]; const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"]
在函數參數中,我們可以使用 rest 操作符將參數注入到我們可以循環的數組中。這里已經有一個 argument 對象綁定到每個函數上,等同于把數組中的所有參數都傳遞給函數。
function myFunc() {
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
myFunc("Nick", "Anderson", 10, 12, 6);
// "Nick"
// "Anderson"
// 10
// 12
// 6但是如果說,我們希望創造的是一個包含各科成績和平均成績的新學生。將前兩個參數提取為兩個單獨的變量,并把剩下的元素生成一個可迭代的數組是不是更加方便呢?
這正是 rest 操作符允許我們做的事情!
function createStudent(firstName, lastName, ...grades) {
// firstName = "Nick"
// lastName = "Anderson"
// [10, 12, 6] -- "..." 將傳遞所有剩余參數,并創建一個包含它們的 "grades" 數組變量
const avgGrade = grades.reduce((acc, curr) => acc + curr, 0) / grades.length; // 根據 grade 計算平均成績
return {
firstName: firstName,
lastName: lastName,
grades: grades,
avgGrade: avgGrade
}
}
const student = createStudent("Nick", "Anderson", 10, 12, 6);
console.log(student);
// {
// firstName: "Nick",
// lastName: "Anderson",
// grades: [10, 12, 6],
// avgGrade: 9.333333333333334
// }注意: 在這個示例中, createStudent 函數其實并不太好,因為我們并沒有去檢查 grades.length 是否存在又或者它等于 0 的情況。但是這個例子現在這樣寫,能夠更好的幫助我們理解剩余參數的運作,所以我沒有處理上述的這種情況。
對于這一點,我建議你閱讀有關 rest 操作符以前的有關迭代和函數參數的相關說明。
const myObj = { x: 1, y: 2, a: 3, b: 4 };
const { x, y, ...z } = myObj; // 這里是對象被解構
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
// z是對象解構后的剩余部分:myObj 對象除去 x 和 y 屬性后剩余部分被解構
const n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
// 這里 z 對象的屬性展開到 n 中TC39 – Object rest/spread
Spread operator introduction – WesBos
JavaScript & the spread operator
6 Great uses of the spread operator
將變量分配給一個對象屬性時,如果變量名稱和屬性名稱相同,你可以執行以下操作:
const x = 10;
const myObj = { x };
console.log(myObj.x) // 10通常(ES2015之前)當你聲明一個新的 對象字面量 并且想要使用變量作為對象屬性值時,你會寫這樣類似的代碼:
const x = 10;
const y = 20;
const myObj = {
x: x, // 將 x 分配給 myObj.x
y: y // 將 y 變量給 myObj.y
};
console.log(myObj.x) // 10
console.log(myObj.y) // 20如您所見,這樣的作法其實相當重復,因為 myObj 的屬性名稱與要分配給這些屬性的變量名相同。
使用ES2015,當變量名與屬性名稱相同時,您可以進行以下簡寫:
const x = 10;
const y = 20;
const myObj = {
x,
y
};
console.log(myObj.x) // 10
console.log(myObj.y) // 20Property shorthand – ES6 Features
Promises 是一個可以從異步函數 (參考) 同步返回的對象。
可以使用 Promises 來避免 回調地獄 (callback hell) ,而且它們在現代 JavaScript 項目中越來越頻繁地遇到。
const fetchingPosts = new Promise((res, rej) => {
$.get("/posts")
.done(posts => res(posts))
.fail(err => rej(err));
});
fetchingPosts
.then(posts => console.log(posts))
.catch(err => console.log(err));當你在執行 Ajax 請求 時,響應不是同步的,因為資源請求需要時間。如果你要的資源由于某些原因 (404) 不可用,甚至可能永遠都不會請求到。
為了處理這類情況,ES2015 為我們提供了 promises。 Promises 可以有三種不同的狀態:
等待中 (Pending)
達成 (Fulfilled)
拒絕 (Rejected)
假設我們希望使用 promises 去進行 Ajax 請求以獲取 X 資源。
我們首先要創建一個 promise。我們將會使用 jQuery 的 get 方法對 X 資源執行 Ajax 請求。
const xFetcherPromise = new Promise( // 使用 "new" 關鍵字創建promise,并把它保存至一個變量
function(resolve, reject) { // Promise 構造函數需要一個帶有著 resolve 和 reject 這兩個參數的函數作為參數
$.get("X") // 執行 Ajax 請求
.done(function(X) { // 一旦請求完成...
resolve(X); // ... 把 X 值做為參數去 resolve promise
})
.fail(function(error) { // 如果請求失敗...
reject(error); // ... 把 error 做為參數去 reject promise
});
}
)如上示例所示,Promise 對象需要一個帶有兩個參數 ( resolve 和 reject ) 的執行函數。這兩個參數會把 pending 狀態的 promise 分別進行 fulfilled 和 rejected 的處理。
Promise 在實例創建后處于待處理狀態,并且它的執行器函數立即執行。一旦在執行函數中調用了 resolve 或 reject 函數,Promise 將調用相關的處理程序。
為了獲得 promise 結果(或錯誤),我們必須通過執行以下操作來附加處理程序:
xFetcherPromise
.then(function(X) {
console.log(X);
})
.catch(function(err) {
console.log(err)
})如果 promise 成功,則執行 resolve ,并執行 .then 參數所傳遞的函數。
如果失敗,則執行 reject ,并執行 .catch 參數所傳遞的函數。
注意: 如果 promise 在相應的處理程序附加時已經 fulfilled 或 rejected,處理程序將被調用,
因此在異步操作完成和其處理程序被附加之間沒有競爭條件。 (參考: MDN)(MDN)。
JavaScript Promises for dummies – Jecelyn Yeen
JavaScript Promise API – David Walsh
Using promises – MDN
What is a promise – Eric Elliott
JavaScript Promises: an Introduction – Jake Archibald
Promise documentation – MDN
模板字符串是一種單行和多行字符串的 表達式插值 (expression interpolation)。
換句話說,它是一種新的字符串語法,你可以更方便地在 JavaScript 表達式中使用 (例如變量)。
const name = "Nick";
`Hello ${name}, the following expression is equal to four : ${2+2}`;
// Hello Nick, the following expression is equal to four: 4String interpolation – ES6 Features
ES6 Template Strings – Addy Osmani
模板標簽是可以作為模板字符串(template literal)的前綴函數。當一個函數被這鐘方式調用時,第一個參數是出現在模板插值變量之間的字符串數組,并且隨后的參數是插值變量的值??梢允褂谜归_運算符 ... 捕獲所有這些參數。 (參考: MDN)。
注意: 名為styled-components的著名庫很大程度上依賴于此功能。
以下是他們工作的玩具示例。
function highlight(strings, ...values) {
// 愚人碼頭注:為了更好的理解函數的參數,我增加了這樣兩行代碼,特殊說明見示例代碼下面的說明;
console.log(strings);//(3) ["I like ", " on ", ".", raw: Array(3)]
console.log(values);//(2) ["jam", "toast"]
const interpolation = strings.reduce((prev, current) => {
return prev + current + (values.length ? "<mark>" + values.shift() + "</mark>" : "");
}, "");
return interpolation;
}
const condiment = "jam";
const meal = "toast";
highlight`I like ${condiment} on ${meal}.`;
// "I like <mark>jam</mark> on <mark>toast</mark>."——-愚人碼頭注開始——-
標簽函數的第一個參數是一個包含了字符串字面值的數組(在本例中分別為”I like “,” on “和”.”)。如果我們這樣調用 highlight`I like ${condiment} on ${meal}` (注意最后面沒”.”),那么第一個參數還是一個 3 個元素的數組:[“I like “, ” on “, “”],特別注意最后一個元素是的空字符串””;
strings 確實是一個數組,但是它還有一個 raw 屬性。其屬性值 strings.raw 也是一個數組,其元素分別表示 strings 中對應的,經過轉義之前在 模板字符串(Template literals) 中由用戶輸入的字符串。我們來看一個例子:
const myTag = (strs, ...exprs) => {
console.log(strs); //(3) ["x", "\y", "", raw: Array(3)]
console.log(strs.raw); //(3) ["x", "\\y", ""
console.log(exprs); //[1, 2]
};
const obj = { a: 1, b: 2 };
const result = myTag`x${obj.a}\\y${obj.b}`;上例中 "\y" 未轉義之前對應的字符串為 "\\y" ,顯然這兩個字符串長度是不同的。
String.raw 是ES2015,內置對象 String 的一個靜態方法,把它作為Tag,可以做到只替換嵌入表達式而不轉義字符。
const raw = String.raw`1\\2\\${1+2}`;
console.log(raw); //1\\2\\3
console.log(raw.length); //7
const x = `1\\2\\${1+2}`;
console.log(x); //1\2\3
console.log(x.length); //5Template literals遵從字符串的轉義規則:
(1)以 \u 開頭,后跟4個16進制數字,例如,\u00B1表示±
(2)以 \u 開頭,使用大括號括起來的16進制數字,例如,\u{2F804} 表示 你
(3)以 \x 開頭,后跟2個16進制數字,例如,\xB1 表示 ±
(4)以 \ 開頭,后跟10進制數字,用來表示八進制字面量(注:strict mode下不支持)
解釋器遇到 \u 和 \x ,如果發現后面的字符不滿足以上條件,就會報語法錯。例如,
> latex`\unicode` > Uncaught SyntaxError: Invalid Unicode escape sequence
不再展開,具體參考:Template literals。
在第一個參數后的每一個參數,都是已經求值后的替換表達式。 看下面這個例子:
var a = 5;
var b = 10;
function tag(strings, ...values) {
console.log(values[0]); // 15
console.log(values[1]); // 50
return values[0]+values[1]; //65
}
tag`Hello ${ a + b } world ${ a * b}`;上例中,剩余的2個參數值分別是 15 和 50;
——-愚人碼頭注結束——-
一個更有趣的例子:
function comma(strings, ...values) {
return strings.reduce((prev, next) => {
let value = values.shift() || [];
value = value.join(", ");
return prev + next + value;
}, "");
}
const snacks = ['apples', 'bananas', 'cherries'];
comma`I like ${snacks} to snack on.`;
// "I like apples, bananas, cherries to snack on."Wes Bos on Tagged Template Literals
Library of common template tags
ES6模塊用于訪問模塊中顯式導出的模塊中的變量或函數。
我強烈建議您查看 MDN 上有關 import/export(請參閱下面的擴展閱讀資源),它們寫的既簡潔又完整。
命名導出用于從一個模塊導出多個值。
注意: 您命名導出的變量是一等公民(first-class citizens)。
// mathConstants.js
export const pi = 3.14;
export const exp = 2.7;
export const alpha = 0.35;
// -------------
// myFile.js
import { pi, exp } from './mathConstants.js'; // 命名導入 -- 類似于解構語法
console.log(pi) // 3.14
console.log(exp) // 2.7
// -------------
// mySecondFile.js
import * as constants from './mathConstants.js'; // 將所有導出的值注入到 constants 變量中
console.log(constants.pi) // 3.14
console.log(constants.exp) // 2.7雖然命名導入看起來像是 解構(destructuring),但它們具有不同的語法,并且不一樣。 他們不支持默認值,也不支持深層次的解構。
此外,您可以使用別名,但語法不同于解構中使用的語法:
import { foo as bar } from 'myFile.js'; // foo 被導入并注入到一個新的 bar 變量中關于默認導出,每個模塊只能有一個默認導出。默認導出可以是函數,類,對象或其他任何東西。這個值被認為是“主要”的導出值,因為它將是最簡單的導入。 參考: MDN。
// coolNumber.js const ultimateNumber = 42; export default ultimateNumber; // ------------ // myFile.js import number from './coolNumber.js'; // 默認導出,將獨立于其名稱,自動注入到 number 這個變量; console.log(number) // 42
函數導出:
// sum.js
export default function sum(x, y) {
return x + y;
}
// -------------
// myFile.js
import sum from './sum.js';
const result = sum(1, 2);
console.log(result) // 3ECMAScript 6 Modules(模塊)系統及語法詳解
ES6 Modules in bulletpoints
Export – MDN
Import – MDN
Understanding ES6 Modules
Destructuring special case – import statements
Misunderstanding ES6 Modules – Kent C. Dodds
Modules in JavaScript
this 操作符的行為與其他語言不同,在大多數情況之下是由函數的調用方式決定。 (參考: MDN)。
this 概念有很多細節,并不是那么容易理解,我強烈建議你深入了解下面的擴展閱讀。因此,我會提供我個人對于 this 的一點理解和想法。我是從 Yehuda Katz 寫的這篇文章 學到了這個提示。
function myFunc() {
...
}
// 在每個述句后面,你都可以在 myFunc 中找到 this 的值
myFunc.call("myString", "hello") // "myString" -- 首先, .call的參數值被注入到 *this*
// 在非嚴格模式下(non-strict-mode)
myFunc("hello") // window -- myFunc() 是 myFunc.call(window, "hello") 的語法糖
// 在嚴格模式下(strict-mode)
myFunc("hello") // undefined -- myFunc() 是 myFunc.call(undefined, "hello") 的語法糖var person = {
myFunc: function() { ... }
}
person.myFunc.call(person, "test") // person 對象 -- 首先, .call的參數值被注入到 *this*
person.myFunc("test") // person 對象 -- person.myFunc() 是 person.myFunc.call(person, "test") 的語法糖
var myBoundFunc = person.myFunc.bind("hello") // 創造了一個函數,并且把 "hello" 注入到 *this*
person.myFunc("test") // person 對象 -- bind 方法對原有方法并無造成影響
myBoundFunc("test") // "hello" -- myBoundFunc 是把帶有 "hello" 的 person.myFunc 綁定到 *this*Understanding JavaScript Function Invocation and “this” – Yehuda Katz
JavaScript this – MDN
JavaScript 是一個基于原型 的語言(然而Java 是基于類別 的語言)。 ES6 引入了 JavaScript 類,它們是用于基于原型的繼承的語法糖,而 不是 一種新的基于類繼承的模型(參考).
如果您熟悉其他語言的類,那么 類 (class) 這個詞的確容易理解出錯。 如果真的有此困擾,請避免在這樣的認知下思考 JavaScript 類的行為,并將其視為完全不同的新概念。
由于本文檔不是從根本上教你 JavaScript 語言,我會相信你知道什么是原型,以及它們的行為。 如果沒有,請參閱示例代碼下面列出的擴展閱讀,以方便你去理解這些概念:
Understanding Prototypes in JS – Yehuda Katz
A plain English guide to JS prototypes – Sebastian Porto
Inheritance and the prototype chain – MDN
ES6 之前,原型語法:
var Person = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.stringSentence = function() {
return "Hello, my name is " + this.name + " and I'm " + this.age;
}使用 ES6 類(class)* 語法:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
stringSentence() {
return "Hello, my name is " + this.name + " and I'm " + this.age;
}
}
const myPerson = new Person("Manu", 23);
console.log(myPerson.age) // 23
console.log(myPerson.stringSentence()) // "Hello, my name is Manu and I'm 23更好的理解原型:
Understanding Prototypes in JS – Yehuda Katz
A plain English guide to JS prototypes – Sebastian Porto
Inheritance and the prototype chain – MDN
更好的理解類:
ES6 Classes in Depth – Nicolas Bevacqua
ES6 Features – Classes
JavaScript Classes – MDN
Extends 和 super 關鍵字extends 關鍵字用于類聲明或類表達式中,以創建一個類,該類是另一個類的子類(參考: MDN)。 子類繼承超類的所有屬性,另外可以添加新屬性或修改繼承的屬性。
super 關鍵字用于調用對象的父對象的函數,包括其構造函數。
super 關鍵字必須在構造函數中使用 this 關鍵字之前使用。
調用 super() 調用父類構造函數。 如果要將類的構造函數中的一些參數傳遞給其父構造函數,則可以使用 super(arguments) 來調用它。
如果父類有一個 X 的方法(甚至靜態),可以使用 super.X() 在子類中調用。
class Polygon {
constructor(height, width) {
this.name = 'Polygon';
this.height = height;
this.width = width;
}
getHelloPhrase() {
return `Hi, I am a ${this.name}`;
}
}
class Square extends Polygon {
constructor(length) {
// 這里,它調用父類的構造函數的 length,
// 提供給 Polygon 的 width 和 height。
super(length, length);
// 注意: 在派生的類中, 在你可以使用 'this' 之前, 必須先調用 super() 。
// 忽略這個, 這將導致引用錯誤。
this.name = 'Square';
this.length = length;
}
getCustomHelloPhrase() {
const polygonPhrase = super.getHelloPhrase(); // 通過 super.X() 語法訪問父級方法
return `${polygonPhrase} with a length of ${this.length}`;
}
get area() {
return this.height * this.width;
}
}
const mySquare = new Square(10);
console.log(mySquare.area) // 100
console.log(mySquare.getHelloPhrase()) // 'Hi, I am a Square' -- Square 繼承自 Polygon 并可以訪問其方法
console.log(mySquare.getCustomHelloPhrase()) // 'Hi, I am a Square with a length of 10'注意 : 如果我們在 Square 類中調用 super() 之前嘗試使用 this,將引發一個引用錯誤:
class Square extends Polygon {
constructor(length) {
this.height; // 引用錯誤, 必須首先調用 super() !
// 這里,它調用父類的構造函數的 length,
// 提供給 Polygon 的 width 和 height。
super(length, length);
// 注意: 在派生的類中, 在你可以使用 'this' 之前, 必須先調用 super() 。
// 忽略這個, 這將導致引用錯誤。
this.name = 'Square';
}
}Extends – MDN
Super operator – MDN
Inheritance – MDN
除 promises 之外,還有一種新的語法可能會遇到,那就是異步的 async / await。
async / await 函數的目的是簡化同步使用 promise 的行為,并對一組 promises 執行一些處理。正如 promises 類似于結構化的回調,async / await 類似于組合生成器(combining generators) 和 promises。異步函數 總是 返回一個 Promise。 (參考: MDN)
注意: 您必須了解什么是 promises 以及它們是如何工作的,然后再嘗試了解 async / await ,因為它們依賴于 promises 。
注意2: [await 必須在async函數中使用](https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial- c7ec10518dd9#f3f0),這意味著你不能程式碼的頂部使用 await,因為它并不在異步函數之內。
async function getGithubUser(username) { // async 關鍵字允許在函數中使用 await ,意味著函數返回一個 promise
const response = await fetch(`https://api.github.com/users/${username}`); // 執行在這里暫停,直到fetch返回的 Promise 被 resolved
return response.json();
}
getGithubUser('mbeaudru')
.then(user => console.log(user)) // 記錄用戶響應 - 不能使用 await 語法,因為此代碼不在 async 函數中
.catch(err => console.log(err)); // 如果在我們的異步函數中拋出一個錯誤,我們將在這里捕獲它Async / Await 是建立在 promises 概念之上的,但它們允許更強制的代碼風格。
async 操作符將一個函數標記為異步,并將始終返回一個 Promise 。你可以在 async 函數中使用 await 操作符來暫停該行代碼的執行,直到表達式中返回的 Promise resolves 或 rejects 。
async function myFunc() {
// 我們可以使用 *await* 操作符 因為這個函數是 異步(async) 的
return "hello world";
}
myFunc().then(msg => console.log(msg)) // "hello world" -- 由于 async 操作符,myFunc 的返回值將變成了一個 promise當異步函數運行到 return 語句時,將會使用返回的值來 fulfilled Promise。 如果在 async 函數中拋出錯誤,則 Promise 狀態將轉為 rejected 。 如果沒有從 async 函數返回任何值,則在執行 async 函數完成時,仍然會返回 Promise 并 resolves 為無值。
await 操作符用于等待 Promise fulfilled ,只能在 async 函數體內使用。 遇到這種情況時,代碼執行將暫停,直到 promise fulfilled。
注意: fetch 是一個允許執行 AJAX 請求,返回一個 Promise 的函數。
首先,我們來看看如何通過 promises 來獲取一個 github 用戶:
function getGithubUser(username) {
return fetch(`https://api.github.com/users/${username}`).then(response => response.json());
}
getGithubUser('mbeaudru')
.then(user => console.log(user))
.catch(err => console.log(err));等價于這里 async / await :
async function getGithubUser(username) { // promise + await 關鍵字使用允許
const response = await fetch(`https://api.github.com/users/${username}`); // 在此處執行停止,直到 promise 獲得 fulfilled
return response.json();
}
getGithubUser('mbeaudru')
.then(user => console.log(user))
.catch(err => console.log(err));當你需要鏈接(chain)相互依賴的 promises 時,async / await 語法特別方便 。
例如,如果您需要獲取一個令牌(token) ,以便能夠在數據庫上獲取博客文章,然后獲取作者信息:
注意: await 表達式需要包含在括號中,這樣可以在同一行上調用其 resolved 值的方法和屬性。
async function fetchPostById(postId) {
const token = (await fetch('token_url')).json().token;
const post = (await fetch(`/posts/${postId}?token=${token}`)).json();
const author = (await fetch(`/users/${post.authorId}`)).json();
post.author = author;
return post;
}
fetchPostById('gzIrzeo64')
.then(post => console.log(post))
.catch(err => console.log(err));除非我們在 await 表達式外面包裹 try / catch 語句塊,否則不會捕獲異常 – 不管它們是在你的異步函數中被拋出還是在 await 期間被暫停 – 他們將 reject async 函數返回的承諾。
在 async 函數中使用 throw 語句與返回 reject 的 Promise 是相同。 (參考: PonyFoo).
Note : Promises 的行為相同的!
下面是的示例顯示了 promises 是如何處理錯誤鏈的:
function getUser() { // 這個 promise 將被 rejected!
return new Promise((res, rej) => rej("User not found !"));
}
function getAvatarByUsername(userId) {
return getUser(userId).then(user => user.avatar);
}
function getUserAvatar(username) {
return getAvatarByUsername(username).then(avatar => ({ username, avatar }));
}
getUserAvatar('mbeaudru')
.then(res => console.log(res))
.catch(err => console.log(err)); // "User not found !"等價于實用 async / await :
async function getUser() { // 這個 promise 將被 rejected!
throw "User not found !";
}
async function getAvatarByUsername(userId) => {
const user = await getUser(userId);
return user.avatar;
}
async function getUserAvatar(username) {
var avatar = await getAvatarByUsername(username);
return { username, avatar };
}
getUserAvatar('mbeaudru')
.then(res => console.log(res))
.catch(err => console.log(err)); // "User not found !"使用 ES2017 中的 Async(異步) 函數 和 Await(等待)
ES2017 新特性:Async Functions (異步函數)
Async/Await – JavaScript.Info
ES7 Async/Await
6 Reasons Why JavaScript's Async/Await Blows Promises Away
JavaScript awaits
Using Async Await in Express with Node 8
Async Function
Await
Using async / await in express with node 8
在 JavaScript 中,truthy 或 falsy 值是在布爾上下文中求值時被轉換為布爾值的值。布爾上下文的一個最常見的例子是求值 if 條件:
每個值將被轉換為true,除非它們等于以下值:
false
0
"" (空字符串)
null
undefined
NaN
下面是 布爾上下文(boolean context) 的例子:
if 條件求值
if (myVar) {}myVar 可以是任何 一等公民(first-class citizen) (變量, 函數, 布爾值) ,但它會被轉換成一個布爾值,因為它會在布爾上下文中進行就值。
邏輯非 ! 操作符后面
如果其單獨的操作數可以轉換為 true ,則此操作符返回 false ; 否則返回 true 。
!0 // true -- 0 是 falsy(假) 值,所以它返回 true !!0 // false -- 0 是 falsy(假) 值, 所以 !0 返回 true , 所以 !(!0) 返回 false !!"" // false -- 空字符串是 falsy(假) 值, 所以 NOT (NOT false) 等于 false
通過 Boolean 對象構造函數
new Boolean(0) // false new Boolean(1) // true
在一個三元表達式求值時
myVar ? "truthy" : "falsy"
myVar 在布爾上下文中進行求值。
Truthy (MDN)
Falsy (MDN)
Truthy and Falsy values in JS – Josh Clanton
static 關鍵字用于聲明靜態方法。靜態方法是屬于 class(類) 對象,而該類的任何實例都不可以訪問的方法。
class Repo{
static getName() {
return "Repo name is modern-js-cheatsheet"
}
}
// 請注意,我們不必創建 Repo 類的實例
console.log(Repo.getName()) //Repo name is modern-js-cheatsheet
let r = new Repo();
console.log(r.getName()) // 拋出一個 TypeError: repo.getName is not a function靜態方法可以通過使用 this 關鍵字在另一個靜態方法中調用,這不適用于非靜態方法。非靜態方法無法使用 this 關鍵字直接訪問靜態方法。
要在在靜態方法調用另一個靜態方法,可以使用 this 關鍵字 ,像這樣;
class Repo{
static getName() {
return "Repo name is modern-js-cheatsheet"
}
static modifyName(){
return this.getName() + '-added-this'
}
}
console.log(Repo.modifyName()) //Repo name is modern-js-cheatsheet-added-this非靜態方法可以通過2種方式調用靜態方法;
###### 使用 class(類) 名
要在非靜態方法訪問靜態方法,我們可以使用類名,并像屬性一樣調用靜態方法就可以了。 例如ClassName.StaticMethodName:
class Repo{
static getName() {
return "Repo name is modern-js-cheatsheet"
}
useName(){
return Repo.getName() + ' and it contains some really important stuff'
}
}
// 我們需要實例化這個 class(類),以使用非靜態方法
let r = new Repo()
console.log(r.useName()) //Repo name is modern-js-cheatsheet and it contains some really important stuff###### 實用構造函數
靜態方法可以在構造函數對象上作為屬性被調用。
class Repo{
static getName() {
return "Repo name is modern-js-cheatsheet"
}
useName(){
//作為構造函數的屬性來調用靜態方法
return this.constructor.getName() + ' and it contains some really important stuff'
}
}
// 我們需要實例化這個 class(類),以使用非靜態方法
let r = new Repo()
console.log(r.useName()) //Repo name is modern-js-cheatsheet and it contains some really important stuffstatic keyword- MDN
Static Methods- Javascript.info
Static Members in ES6- OdeToCode
在上下文之中有著 “可見的 (visible)” 值和表達式,又或者是可以被引用的。如果變量或是表達式并不在 “當前作用域中”,那么它將會是不可用的。
來源: MDN
如果變量在其初始值后發生變化時我們就稱其為可變的。
var myArray = [];
myArray.push("firstEl") // myArray 發生改變如果一個變量不能被改變的話,我們會說這個變量是 不可變的 (immutable) 。
看完上述內容,你們對現代JavaScript 有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。