作者:valentinogagliardi
譯者:前端小智
來源:github
為了保證的可讀性,本文采用意譯而非直譯。
第1章:JS 簡介
什麼是JavaScript
JS 是一種用於 web 的指令碼語言。JS 誕生於 1995
年,由 **Brendan Eich **一手建立,用於向web頁面新增互動性。那時的網際網路還處於起步階段,我們今天看到的大多數花哨的網頁在那時候還只是一個夢。
在專案經理的催促下,Brendan 只有 10
天的時間來建立一種可以在瀏覽器中執行的動態、靈活的語言。他寫出了 JavaScript,一種有點奇怪的程式語言,它參考了 Java、C 和 Scheme。JS 一直名聲不好,因為它從一開始就有很多怪異的地方。但儘管如此,它還是在名人堂佔據了一席之地,並一直挺到了今天。
現在,JS 被用來建立整個應用程式,稱為SPA(單頁應用程式)。隨著使用量的增加,JS 生態系統也經歷了寒武紀大爆發。我們們今天用於開發 JS 的大多數 Web 工具和庫,很多用 JS 寫的。JS 也被用除前端方面的領域。使用 Node.js
我們們可以建立伺服器端和物聯網應用程式,工業裝置,以及更多。但最重要的是,單頁應用程式是 JS 最突出的用法之一。
在單頁面應用中,JS 負責所有的事情,使 UI 流暢,無需任何頁面重新整理。從使用者的角度來看,這是對傳統 web 應用程式的巨大改進。但是,能力越大,責任越大: JS 對於大多數移動裝置來說是一個沉重的負擔,在設計和構建時應該格外小心。、
為什麼要學 JavaScript
今天學習 JS 並不意味著對變數和函式的膚淺理解:還有很多。JS 開發人員知道閉包、this
、new
、原型系統和更新的特性。JS 一年比一年流行,功能也逐漸完善。現在幾乎每個前端開發人員的工作都需要 JS 知識。招聘經理尋找的不是會使用 JQ (說到jQuery,它似乎正在慢慢消亡) 的。
大多數情況下,你也需要解及學習 TypeScript, 強調型別的 JS。前端開發人員應該要理解 JS 的特性,並能夠編寫慣用的、結構良好的 JS 程式碼。JS 正在迅速傳播,即使你不喜歡這種語言,在這一點上忽視它也可能對你的職業生涯不利。
第2章:JS 基礎
JS 目前有 7 種基本型別,如下:
String
Number
Boolean
Null
Undefined
Object
Symbol
(ES6)
除了 Object
是複雜資料型別外,其它的 6 種是 JS 的基本資料型別。每個 JS 型別都有一個對應的表示,可以在我們們的程式碼中使用,比如字串:
var string = "Hello John";
複製程式碼
數字:
var age = 33;
複製程式碼
說到這裡,JS 也有算術運算:
運算子 | 運算名 |
---|---|
+ | 加法 |
++ | 自增 |
* | 乘法 |
** | 指數 |
- | 減 |
-- | 自減 |
/ | 除 |
% | 取除 |
在 JS 中,可以使用 var
關鍵字將值儲存在變數中,這是宣告變數的最相容方法:
var greet = "Hello";
var year = 89;
var not = false;
複製程式碼
這裡說的相容,是因為在 ES6 中我們還有兩個選擇: let
和 const
。舊的瀏覽器可能不支援這些新的關鍵字,除非使用“轉置器”,否則可能會遇到錯誤。在新的瀏覽器中,建議都 let
和 const
。主要有兩個好處:
let
和const
都有自己的塊作用域const
不能重新分配,也不能重新宣告
塊作用域是指用 let
或 const
宣告的變數與在封閉或外部塊
中宣告的相同變數名不重疊。例如:
let name = "前端小智";
{
let name = "王大冶";
console.log(name); // "王大冶"
}
console.log(name); // "前端小智"
複製程式碼
這裡的 name
似乎是重複的,但實際上是兩個不同的變數在自己的作用域裡。const
具有相同的行為:
const name = "前端小智";
{
const name = "王大冶";
console.log(name); // "王大冶"
}
console.log(name); // "前端小智"
複製程式碼
與 var
的行為就與 let
和 const
不一樣了。
var name = "前端小智";
{
var name = "王大冶";
console.log(name); // "王大冶"
}
console.log(name); // "王大冶"
複製程式碼
正如前端所說,const
不能被重新分配,也不能在同一個作用域中重新宣告。如果你嘗試重新宣告一個 const
,會得到 "SyntaxError: Identifier has already been declared"
。如果將某個值重新賦值給同一個 const
,會得到 "TypeError: Assignment to constant variable"
錯誤。
const name = "前端小智";
const name = "王大冶";
// SyntaxError: Identifier 'name' has already been declared
複製程式碼
下面程式碼也會丟擲錯誤:
const name = "前端小智";
name = "王大冶";
// TypeError: Assignment to constant variable.
複製程式碼
但是,請注意,這裡所說的 “cons 不能重新分配,也不能重新宣告”
時,並不意味著const 是不可變的。
這是初學者都會遇到的問題。事實上,任何稍微複雜一點的 JS 資料結構,如陣列或物件,即使在分配給 const
時,它們的值或者屬性值是可變的,不可變是指這些複雜物件的記憶體地址。
const person = {
name: "前端小智",
age: 21
};
person.name = "王大冶";
console.log(person);
// {name: "王大冶", age: 21}
複製程式碼
const 物件中的不可變是指什麼? 下面是陣列:
const list = [1, 1, 3, 2, 5];
list.shift();
console.log(list); // [ 1, 3, 2, 5 ]
複製程式碼
同樣,不是不可變。 有人說 “const 是不可變” 時,請給他看這些例子。 現在回到基礎。 除了獨立變數之外,還可以使用字面量的方式宣告陣列:
var array = ["Hello", 89, false, true];
複製程式碼
從 0
開始的索引可以訪問陣列元素:
var array = ["Hello", 89, false, true];
var first = array[0]; // "Hello"
複製程式碼
幾乎所有 JS 實體都附加了一些函式,稱為方法。舉兩個例子,陣列有很多處理自身的方法
var array = ["Hello", 89, false, true];
array.push(99);
array.shift();
console.log(array); // [ 89, false, true, 99 ];
複製程式碼
對於字串也是一樣的:
var string = "John";
console.log(string.toUpperCase()); // JOHN
複製程式碼
在第 5 章中,你會知道這些方法從何而來,但這裡有一個提示:它們分別在 Array.prototype
和 String.prototype
上定義。除了方法之外,還有一些屬性對於提取關於字串長度的資訊非常有用:
var string = "John";
console.log(string.length); // 4
複製程式碼
或者陣列的長度:
var array = ["Hello", 89, false, true];
array.push(99);
array.shift();
console.log(array.length); // 4
複製程式碼
這些屬性有些特殊,因為它們被稱為 "getters"/"setters"
。 你可以想象一個給定的字串就像一個附加了一堆方法和屬性的物件。當訪問陣列的長度時,你只需呼叫相應的 getter
。setter
函式用於設定操作:
var array = {
value: ["Hello", 89, false, true],
push: function(element) {
//
},
shift: function() {
//
},
get length() {
// gets the length
},
set length(newLen) {
// sets the length
}
};
// Getter call
var len = array.length
// Setter call
array.length = 50;
複製程式碼
現在,我們們已經奠定了基礎,讓我們仔細看看物件,它是最重要的 JS 型別之一。
站在一個物件的肩膀上
Object
是 JS 中最重要的型別,因此幾乎所有其他實體都可以從中派生。 例如,函式和陣列是專用物件。 JS 中的物件是鍵/值對的容器,如以下示例(字面量形式):
var obj = {
name: "John",
age: 33
};
複製程式碼
還有另一種建立物件的方法,但它很少見,效能低,請避免使用這種形式:
var obj = new Object({
name: "John",
age: 33
});
複製程式碼
正如你所看到的,物件是儲存值的一種方便方法,稍後可以通過訪問相應的屬性來檢索這些值:
var obj = {
name: "前端小智",
age: 26
};
console.log(obj.name); // "前端小智"
複製程式碼
我們們還可以新增新屬性、刪除或更改它們
var obj = {
name: "前端小智",
age: 26
};
obj.address = "王大冶";
delete obj.name;
obj.age = 18;
複製程式碼
物件的鍵也可以是字串,在本例中,我們使用方括號符號訪問屬性:
var obj = {
name: "前端小智",
age: 26,
"complex key": "stuff"
};
console.log(obj["complex key"]); // "stuff"
複製程式碼
但是,點表示法更常見,除非鍵是複雜的字串,否則應該選擇傳統的屬性訪問:
var obj = {
name: "前端小智",
age: 26
};
console.log(obj.name); // "前端小智"
複製程式碼
這是我們們所有需要知道的基本知識,但在 第5章,我們將看到 JS 物件是非常強大的,可以做更多。現在來看看 JS 函式。
5 種不同的 JS 函式
幾乎每種程式語言都有函式,JS 也不例外。函式是可重用的程式碼段。考慮以下示例
function hello(message) {
console.log(message);
}
hello("Hello");
複製程式碼
和
function sum(a, b) {
return a + b;
}
var sum = sum(2, 6);
複製程式碼
第一個函式列印一個字串,第二個函式向外部世界返回一個值。正如你所看到的,函式可以接受引數,列在函式“簽名”中:
// a 和 b 是函式簽名中的引數
function sum(a, b) {
return a + b;
}
複製程式碼
我們們可以在呼叫函式時傳遞值:
// a and b are parameters in the function's signature
function sum(a, b) {
return a + b;
}
// 2 和 6 是該函式的引數
var sum = sum(2, 6);
複製程式碼
用 function
關鍵字宣告的 JS 函式是常規函式,與沒有主體的肩頭函式相反常規函式可以呈現多種形式:
- 命名函式
- 匿名函式
- 物件方法
- 物件方法簡寫(ES 6)
- IIFE(立即執行函式)
命名函式是最傳統的函式型別:
function sum(a, b) {
return a + b;
}
複製程式碼
另一方面,匿名函式沒有名稱,可以分配給一個變數供以後使用
var sum = function(a, b) {
return a + b;
};
複製程式碼
或者用作其他函式中的回撥:
var button = document.createElement("button");
button.addEventListener("click", function(event) {
// do stuff
});
複製程式碼
函式也可以存在於物件中,這種稱為該物件的方法:
var widget = {
showModal: function() {
// do stuff
}
};
widget.showModal();
複製程式碼
常規函式在預設情況下也會得到一個 this
關鍵字,它可以根據呼叫函式的方式賦予不同的含義。在第六章中,我們將詳細探討這個主題。現在有一個簡單的規則:在一個物件內部執行的函式有 this
指向包含物件的指標
var widget = {
html: "<div></div>",
showModal: function() {
console.log(this.html);
}
};
widget.showModal(); // "<div></div>"
複製程式碼
在 ES6 中,你也可以使用物件方法簡寫:
var widget = {
showModal() {
// object method shortand
}
};
widget.showModal();
複製程式碼
最後,IIFE
(立即執行的函式):
var IIFE = (function() {
// what happens in an IIFE stays in the IIFE
})();
複製程式碼
語法可能看起來有點奇怪,但是 IIFE 非常強大,在第4章會看到它們。除了常規函式外,還有箭頭函式,在 ES6 中新增。箭頭函式不使用 function
關鍵字,但它們具有相似的形式:
- 命名箭頭函式
- 匿名箭頭函式
- 物件方法
- IIFE 箭頭函式
箭頭函式很方便,但我建議不要過度使用它們。這是一個命名的箭頭函式。如果沒有引數,可以省略 return
語句並使用圓括號:
const arrow = () => console.log("Silly me");
複製程式碼
如果你需要在箭頭函式中計算一些東西或者呼叫其他函式,可以用花括號包含一個主體
const arrow = () => {
const a = callMe();
const b = callYou();
return a + b;
};
複製程式碼
花括號也是定義物件的字面量形式,這並不意味著我們們可以做類似的事情:
const arrow = () => {
a : "hello",
b: "world"
};
複製程式碼
這是無效的語法。要從箭頭函式返回物件,可以使用圓括號:
const arrow = () => ({
a: "hello",
b: "world"
});
console.log(arrow());
// { a: 'hello', b: 'world' }
複製程式碼
或者使用 return
語句:
const arrow = () => {
return {
a: "hello",
b: "world"
};
};
複製程式碼
與常規匿名函式一樣,也有匿名箭頭函式。這裡有一個作為回撥傳遞給另一個函式
const arr = [1, 2, 3];
const res = arr.map(element => element + 1);
console.log(res); // [ 2, 3, 4 ]
複製程式碼
它以 element
為引數,併為每個陣列元素返回 element +1
。 如你所見,如果箭頭函式只有一個引數,則無需在其周圍加上括號:
const fun = singleParameter => singleParameter + 1;
複製程式碼
但如果你需要更多的引數,括號是必需的:
const fun = (a, b) => a + b + 1;
複製程式碼
箭頭函式也可以作為物件方法出現,但是它們的行為與常規函式不同。在前一段介紹了 this
關鍵字,它是對執行函式的物件的引用。當作為物件方法呼叫時,常規函式將 this
指向宿主物件
var widget = {
html: "<div></div>",
showModal: function() {
console.log(this.html);
}
};
widget.showModal(); // "<div></div>"
複製程式碼
而箭頭函式中的 this
則指向完全不同的東西:
var widget = {
html: "<div></div>",
showModal: () => console.log(this.html)
};
widget.showModal(); // undefined
複製程式碼
因此,箭頭函式不太適合作為物件方法,但是有一些有趣的用例,在本小冊中,我們們將瞭解為什麼以及何時有效使用它們。 最後,來看一下 IIFE 箭頭函式:
(() => {
console.log("aa");
})();
複製程式碼
令人困惑的語法不是嗎? 接著我們們將進入下一章。
傳遞引數
**ECMAScript 中所有函式的引數都是按值傳遞的。**也就是說,把函式外部的值複製給函式內部的引數,就和把值從一個變數複製到另一個變數一樣。基本型別值的傳遞如同基本型別變數的複製一樣,而引用型別值的傳遞,則如同引用型別變數的複製一樣。有不少開發者在這一點上可能會感到困惑,因為訪問變數有按值和按引用兩種方式,而引數只能按值傳遞。
在向引數傳遞基本型別時,被傳遞的值會被複制給一個區域性變數(即命名引數,或者用 ECMAScript 的概念來說,就是 arguments
物件中的一個元素)。在向引數傳遞引用型別的值時,會把這個值在記憶體中的地址複製給一個區域性變數,因此這個區域性變數的變化會反映在函式的外部。請看下面的例子:
function addTen(num){
num += 10;
return num
}
var count = 20;
var result = addTen(count);
alert(count); // 20 沒有變化
alert(result); // 30
複製程式碼
這裡的函式 addTen ()
有一個引數 num ,而引數實際上是函式的區域性變數。在呼叫這個函式時,變數 count
作為引數被傳遞給函式,這個變數的值是 20
。於是,數值 20
被複制給引數 num
以便在 addTen()
中使用。 在函式內部,引數 num
的值被加上了 10
,但這一變化不會影響函式外部的 count
變數。引數的值也將變成 30
,從而反映函式內部的修改。當然,使用數值等基本型別值來說明按值傳遞引數比較簡單,但如果使用物件,那問題就不怎麼好理解了。再舉一個例子:
function setName (obj) {
obj.name = '前端小智';
}
var person = new Object();
setName(person);
alert(person.name) // "前端小智"
複製程式碼
以上程式碼建立一個物件,並將其儲存在了變數 person 中。然後,這個變數被傳遞到 setName() 函式中之後就被複制給了 obj。在這個函式內部, obj 和 person引用的是同一個物件。於是,當在函式內部為 obj 新增 name 屬性後,函式外部的 person 也將有所反映;因為person指向的物件在堆記憶體中只有一個,而且是全域性物件。
有很多開發者錯誤的認為:在區域性作用域中修改的物件會在全域性作用域中反映出來,就說明引數是按引用傳遞。為了證明物件是按值傳遞的,我們再看一看下面這個經過修改的例子:
function setName(obj) {
obj.name = '前端小智';
obj = new Object();
obj.name = '王大冶'
}
var person = new Object();
setName(person);
alert(person.name) // '前端小智'
複製程式碼
這個例子與前一個例子的唯一區別,就是在 setName()
函式中新增了兩行程式碼:一行程式碼為 obj
重新定義了一個物件,另一行程式碼為該物件定義了一個帶有不同值的 name
屬性。在把 person
傳遞給 setName()
後,其 name
屬性被設定為 ‘前端小智’
。然後,又將一個新物件賦給變數 obj
,同時將其 name
屬性設定為 '王大冶'
。
如果 person
是按引用傳遞的,那麼 person
就會自動被修改為指向其 name
屬性為 ‘王大冶'
的新物件。但是原始的引用仍然保持不變。實際上,當在函式內部重寫 obj
時,這個變數引用就是一個區域性物件了。而這個區域性物件會在函式執行完畢後立即被銷燬。
總結
JS 具有七個稱為 “型別” 的基本構建塊,其中 6 個也稱為基本資料型別。 Object
本身就是一種型別,也是該語言最重要的實體。 物件是用於一對鍵/值的容器,並且可以包含幾乎所有其他 JS 的型別,包括函式。
與大多數其他程式語言一樣,JS 有字串、數字、函式、布林值和一些稱為 Null
和Undefined
的特殊型別。JS 中有兩種函式:箭頭函式和常規函式。它們都有各自的用法,根據場景使用它們。
思考
-
arguments 和 引數 之間有什麼區別?
-
JS 中有多少個基本型別
-
什麼是常規的箭頭函式
-
函式作為物件方法執行時可以訪問哪些特殊關鍵字?
-
在 JS 中宣告變數有多少種方法
程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具Fundebug。
原文:
交流(歡迎加入群,群工作日都會發紅包,互動討論技術)
乾貨系列文章彙總如下,覺得不錯點個Star,歡迎 加群 互相學習。
因為篇幅的限制,今天的分享只到這裡。如果大家想了解更多的內容的話,可以去掃一掃每篇文章最下面的二維碼,然後關注我們們的微信公眾號,瞭解更多的資訊和有價值的內容。
每次整理文章,一般都到2點才睡覺,一週4次左右,挺苦的,還望支援,給點鼓勵