【JS 口袋書】第 1 和 2 章:JS簡介及基礎

前端小智發表於2019-10-07

作者: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 開發人員知道閉包、thisnew、原型系統和更新的特性。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 中我們還有兩個選擇: letconst。舊的瀏覽器可能不支援這些新的關鍵字,除非使用“轉置器”,否則可能會遇到錯誤。在新的瀏覽器中,建議都 letconst 。主要有兩個好處:

  • letconst 都有自己的塊作用域
  • const 不能重新分配,也不能重新宣告

塊作用域是指用 letconst 宣告的變數與在封閉或外部中宣告的相同變數名不重疊。例如:

let name = "前端小智";

{
  let name = "王大冶";
  console.log(name); // "王大冶"
}

console.log(name); // "前端小智"
複製程式碼

這裡的 name 似乎是重複的,但實際上是兩個不同的變數在自己的作用域裡。const 具有相同的行為:

const name = "前端小智";

{
  const name = "王大冶";
  console.log(name); // "王大冶"
}

console.log(name); // "前端小智"
複製程式碼

var 的行為就與 letconst 不一樣了。

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"。 你可以想象一個給定的字串就像一個附加了一堆方法和屬性的物件。當訪問陣列的長度時,你只需呼叫相應的 gettersetter 函式用於設定操作:

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 有字串、數字、函式、布林值和一些稱為 NullUndefined 的特殊型別。JS 中有兩種函式:箭頭函式和常規函式。它們都有各自的用法,根據場景使用它們。

思考

  • arguments 和 引數 之間有什麼區別?

  • JS 中有多少個基本型別

  • 什麼是常規的箭頭函式

  • 函式作為物件方法執行時可以訪問哪些特殊關鍵字?

  • 在 JS 中宣告變數有多少種方法

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具Fundebug

原文:

github.com/valentinoga…

github.com/valentinoga…

交流(歡迎加入群,群工作日都會發紅包,互動討論技術)

乾貨系列文章彙總如下,覺得不錯點個Star,歡迎 加群 互相學習。

github.com/qq449245884…

因為篇幅的限制,今天的分享只到這裡。如果大家想了解更多的內容的話,可以去掃一掃每篇文章最下面的二維碼,然後關注我們們的微信公眾號,瞭解更多的資訊和有價值的內容。

clipboard.png

每次整理文章,一般都到2點才睡覺,一週4次左右,挺苦的,還望支援,給點鼓勵

【JS 口袋書】第 1 和 2 章:JS簡介及基礎

相關文章