Awe JavaScript [1] 基本概念

weixin_34370347發表於2017-06-23

前言

本文是 Awesome JavaScript 系列文章的第一篇,本系列文章主要為 JavaScript 的一些常見知識點,是我在 JavaScript 學習過程中的一些筆記。


JavaScript 簡介

JavaScript 誕生於 1995 年,和博主同年哈哈。當時,它的主要目的是處理以前由伺服器端語言(如 Perl)負責的一些輸入驗證操作。JavaScript 從一個簡單的輸入驗證器發展為一門強大的程式語言,完全出乎人們的預料。應該說,它既是一門非常簡單的語言,又是一門非常複雜的語言。

1997 年,以 JavaScript 1.1 為藍本的建議被提交給了歐洲計算機制造商協會(ECMA,European Computer Manufacturers Association)。不久後,該協會定義了一種名為 ECMAScript 的新指令碼語言的標準,即 ECMA-262。

1998 年,ISO/IEC(International Organization for Standardization and International Electrotechnical Commission,國標標準化組織和國際電工委員會)也採用了 ECMAScript 作為標準(即 ISO/IEC-16262)。自此以後,瀏覽器開發商就開始致力於將 ECMAScript 作為各自 JavaScript 實現的基礎,也在不同程度上取得了成功。

到現在呢,我們可以說 JavaScript 是一門專為與網頁互動而設計的指令碼語言,他其實由以下三部分組成:

  • ECMAScript,由 ECMA-262 定義,提供核心語言功能。
  • 文件物件模型(DOM,Document Object Model),針對 XML 但經過擴充套件用於 HTML 的應用程式程式設計介面(API,Application Programming Interface)。提供訪問和操作網頁內容的方法和介面。
  • 瀏覽器物件模型(BOM,Browser Object Model),提供與瀏覽器互動的方法和介面。


在 HTML 中使用 JavaScript

JavaScript 放置位置

傳統的做法是將所有的 <script> 元素都放到頁面的 <head> 元素中。這樣做的目的是將所有的外部檔案(包括 CSS 檔案和 JavaScript 檔案)的引用都放在相同的地方。但是這樣就意味著必須等到全部 JavaScript 程式碼都被下載、解析和執行完成後,才能開始呈現頁面的內容(瀏覽器在遇到 <body> 標籤時才開始呈現內容)。這種情況下的使用者體驗就非常的不好。

其實我們可以將所有的 <script> 元素都放到頁面的 <body> 元素中頁面內容的後面,即 </body> 前。這樣在解析包含 JavaScript 程式碼之前,頁面的內容將完全呈現在瀏覽器中。而使用者也會因為瀏覽器視窗顯示空白頁面的時間縮短而感到開啟頁面的速度加快了。

noscript 元素

早期瀏覽器都面臨一個特殊的問題,即當瀏覽器不支援 JavaScript 時如何讓頁面平穩退化。對這個問題的最終解決方案就是創造一個 <noscript> 元素,用以在不支援 JavaScript 的瀏覽器中顯示替代的內容。這個元素可以包含能夠出現在文件 <body> 中的任何 HTML 元素(<script> 除外)。包含在 <noscript> 元素中的內容只有在瀏覽器不支援指令碼或瀏覽器支援指令碼但是指令碼被禁用的情況下才會顯示出來。

<html>
  <head>
    <title>Example HTML Page</title>
    <script type="text/javascript" defer="defer" src="example1.js"></script>
    <script type="text/javascript" defer="defer" src="example1.js"></script>
  <head>
  <body>
    <noscript>
      <p>本頁面需要在瀏覽器支援(啟用)JavaScript。</p>
    </noscript>
  </body>
</html>複製程式碼


基本概念

識別符號

所謂識別符號就是指變數、函式、屬性的名字,或者函式的引數。識別符號可以是按照下列格式規則組合起來的一或多個字元:

  • 第一個字元必須是一個字母、下劃線 _ 或一個美元符號 $
  • 其他字元可以是字母、下劃線、美元符號或數字;
  • 識別符號中的字母可以包含擴充套件的 ASCIIUnicode 字母字元,但不推薦這樣做。

註釋

註釋推薦像下面這樣寫:

// 單行註釋

/*
* 這是一個多行
* (塊級)註釋
*/複製程式碼

嚴格模式

ECMAScript 5 引入了嚴格模式的概念,嚴格模式是為 JavaScript 定義了一種不同的解析與操作模型。嚴格模式啟用方法如下:

function doSomething() {
  "use strict";
  //函式體
}複製程式碼

"use strict"; 這行程式碼其實是一個編譯指示(pragma),用於高速支援的 JavaScript 引擎切換到嚴格模式。

變數

用 var 操作符定義的變數將成為定義該變數的作用域中的區域性變數。也就是說,如果在函式中使用 var 定義一個變數,那麼這個變數在函式退出後就會被銷燬。

function test() {
  var message = "hi";  //區域性變數
}
test();
alert(message);       //錯誤!複製程式碼
function test() {
  message = "hi";  //全域性變數
}
test();
alert(message);       //"hi"複製程式碼

不推薦濫用全域性變數,因為在區域性作用域中定義的全域性變數很難維護。而且給未經宣告的變數賦值在嚴格模式下會導致丟擲 ReferenceError 的錯誤。

可以使用一條語句定義多個變數,只要把每個變數(初始化與否均可)用逗號分隔開即可:

var message = "hi",
    found = false,
    age = 29;複製程式碼

因為 ECMAScript 是鬆散型別的,因而使用不同型別初始化變數的操作可以放在一條語句中完成。

注意,在嚴格模式下不能定義名為 evalarguments 的變數,否則會導致語法錯誤。

資料型別

ECMAScript 中有五種簡單資料型別(基本資料型別):UndefinedNullBooleanNumberString。還有一種複雜資料型別 -- Object,在本質上,Object 是一組無序的明值對組成的。乍一看這幾種資料型別不足以表示所有資料,但是 ECMAScript 資料型別具有動態性,所以沒有必要再定義其他型別的資料了。

typeof 操作符

返回值 含義
"undefined" 這個值未定義
"boolean" 這個值是布林值
"string" 這個值是字串
"number" 這個值是數值
"object" 這個值是物件或 null
"function" 這個值是函式
var message = "some string";
alert(typeof message);    //"string"
alert(typeof (message));  //"string"
alert(typeof 95);         //"number"複製程式碼

上面幾個例子說明,typeof 操作符的運算元可以是變數(message),也可以是數值字面量。注 -- typeof 是一個操作符。

在 JavaScript 中,null 是一個 object,即 typeof null; 返回 object。這是設計的缺陷,在最初,使用標記位來區分物件型別和原始型別,物件型用 0 標識,原始型用 1 標識。導致了全零的 null 被識別為 objectnull 被認為是一個空的物件引用,也就是一個空的物件指標。這也正是使用 typeof 操作符檢測 null 值時會返回 object 的原因。

在技術上講,函式在 ECMAScript 中是物件,不是一種資料型別。然而函式確實也有一些特殊的屬性,因此通過 typeof 操作符來區分函式和其他物件是有必要的。

Undefined 型別

在 JavaScript 中,包含 undefined 值的變數與尚未定義的變數還是不一樣的。

var message;      //
alert(message);  //
alert(age);複製程式碼
var message;
alert(typeof message);  //"undefined"
alert(typeof age);      //"undefined"複製程式碼

即便未初始化的變數會自動被賦予 undefined 值,但顯示的初始化變數依然是明智的選擇。如果做到這一點,那麼當 typeof 操作符返回 undefined 值時,我們就知道被檢測的變數是沒有被宣告還是尚未初始化。

對於未宣告的變數,只能執行一項操作即用 typeof 操作符檢測其資料型別(未宣告的變數呼叫 delet 不會報錯,但沒意義,而且在嚴格模式下也會報錯)。

未初始化和未宣告的變數的區別就是,在用 typeof 操作符檢測其資料型別時都顯示 undefined,但是在除此之外呼叫未宣告的變數時就會報錯。

因為在 JavaScript 中未定義和未宣告的變數用 typeof 操作符檢測其資料型別時都顯示 undefined,所以 DOM 相關函式都是返回 null,從 API 設計角度來講是合理的。

無論什麼情況下,都沒有必要將一個變數的值顯示的設定為 undefined

Null 型別

如果定義的變數準備在將來儲存物件,那麼最好將該變數初始化為 null 而不是其他值。這樣只要檢查 null 值就可以知道相應的變數是否已經儲存了一個物件的引用,如下所示:

if(car != null) {
  //對 car 物件執行某些操作
}複製程式碼

所以只要意在儲存物件的變數還沒有真正儲存物件,就應該明確地讓該變數儲存 null 值。

實際上,undefined 值是派生自 null 值的,因此 ECMA-262 規定對他們的相等性測試要返回 true

alert(null == undefined);      //true複製程式碼

Number 型別

  • 因為儲存浮點數值需要的記憶體空間是儲存整數值的兩倍,所以 ECMAScript 會不失時機的將浮點數值轉換為整數值。

  • ECMAScript 能夠表示的數的範圍為 Number.MIN_VALUE ~ Number.MAX_VALUE,在大多數瀏覽器中為 5e-324 ~ 1.7976931348623157e+308。當程式執行時,數值超過正負範圍時會被分別轉化為 Infinity-Infinity。想確定一個數是否超出 JavaScript 數值範圍,可以用 isInfinite() 函式。

var result = Number.MIN_VALUE + Number.MIN_VALUE;
alert(isFinite(result));     //false複製程式碼
  • NaN 即非數值(Not a Number)是一個特殊值。用於表示一個本來要返回數值的運算元未返回數值的情況(這樣就不會丟擲錯誤了)。其有兩個特點,首先任何涉及 NaN 操作都會返回 NaN,這一點在多步計算中可能會導致問題。其次, NaN 與任何值都不相等,包括其本身。ECMAScript 也定義了 isNaN(); 函式。這個函式接收一個引數,這個引數可以是任何型別的,而函式會幫我們確定這個引數是否 不是數值。函式檢查過程是 `isNaN(); => valueOf(); => toString();

  • 有三個可以把非數值轉化為數值的函式:Number()parseInt()parseFloat()。在使用 parseInt() 轉換資料型別時,為了避免錯誤解析,建議無論何時都要明確指定基數。多數情況下我們要解析的都是是進位制數,因此始終將 10 作為第二個引數是十分必要的。

var num1 = Number("Hello world!");  //NaN
var num2 = Number("");              //0
var num3 = Number("000011");        //11
var num4 = Number(true);            //1

alert(num1);
alert(num2);
alert(num3);
alert(num4);複製程式碼
var num1 = praseInt("10", 2);       //2   (按二進位制解析)
var num1 = praseInt("10", 8);       //8   (按八進位制解析)
var num1 = praseInt("10", 10);      //10  (按十進位制解析)
var num1 = praseInt("10", 16);      //16  (按十六進位制解析)複製程式碼

parseFloat() 只解析十進位制值,所以其沒有第二個引數。

var num1 = parseFloat("1234blue");    //1234 - integer
var num2 = parseFloat("0xA");         //0
var num3 = parseFloat("22.5");        //22.5
var num4 = parseFloat("22.34.5");     //22.34
var num5 = parseFloat("0908.5");      //908.5
var num6 = parseFloat("3.125e7");     //31250000

alert(num1);
alert(num2);
alert(num3);
alert(num4);
alert(num5);
alert(num6);複製程式碼

String 型別

字串由雙引號或單引號表示都可以,在 ECMAScript 中的這兩種語言形式沒有什麼區別。

任何字串的長度都可以通過訪問其 length 屬性取得,如果字串中包含雙位元組字元,那麼 length 屬性可能不會精確的返回字串中的字元數目。

var text = "This is the letter sigma: \u030a.";
alert(text.length);   //輸出 28複製程式碼

ECMAScript 中的字串是不可變的,如果要改變某個變數儲存的字串,首先要銷燬原來的字串,然後再用另一個包含新值的字串填充該變數,這個過程是在後臺完成的,這也就是某些舊版本瀏覽器在拼接字串的時候速度很慢的原因了。

要把一個值轉換為字串有兩種方法,第一種是 toString() 方法。數值、布林值、物件和字串值都有相應的 toString() 方法,但是 nullundefined 值沒有。一般呼叫 toString() 方法時不用傳遞引數,但是他也可以傳遞引數。

var age = 11;
var ageAsString = age.toString();    //the string "11"
var found = true;
var foundAsString = found.toString(); //the string "true"

alert(ageAsString);
alert(typeof ageAsString);
alert(foundAsString);
alert(typeof foundAsString);複製程式碼
var num = 10;
alert(num.toString());       //"10"
alert(num.toString(2));      //"1010"
alert(num.toString(8));      //"12"
alert(num.toString(10));     //"10"
alert(num.toString(16));     //"a"複製程式碼

在不知道要轉換的值是不是 nullundefined 的情況下可以使用第二種方法:轉型函式 String()。使用這種方法時,如果值有 toString() 方法則會呼叫該方法,沒有的話就按本方法規則執行。

var value1 = 10;
var value2 = true;
var value3 = null;
var value4;

alert(String(value1));     //"10"
alert(String(value2));     //"true"
alert(String(value3));     //"null"
alert(String(value4));     //"undefined"複製程式碼

Object 型別

ECMAScript 中的物件其實就是一組資料和功能的集合。

var o = new Object();複製程式碼


操作符

在 ECMAScript 中,當對數值應用位操作符時,後臺發生如下的轉換過程:64 位的數值被轉換為 32 位數值,然後執行位操作,最後再將 32 位的結果轉換回 64 位數值。但是這個轉換過程會導致特殊的 NaN 和 Infinity 值應用位操作時,這兩個值會被當成 0 來處理。對非數值可以先使用 Number() 函式將該值轉換為一個數值,然後再應用位操作。

var num1 = 25;             //binary 00000000000000000000000000011001
var num2 = ~num1;          //binary 11111111111111111111111111100110
alert(num2);               //-26複製程式碼

按位非操作的本質就是運算元的負值減一。

左移操作:左移操作符為 <<,左移不會影響運算元的符號位。

var oldValue = 2;             //equal to binary 10
var newValue = oldValue << 5; //equal to binary 1000000 which is decimal 64
alert(newValue);              //64複製程式碼

右移操作分為有符號 >> 和無符號 >>> 兩種。對於正數來說,這兩種方法的結果一樣。但對於負數來說,無符號右移是以 0 填充空位,而不是像有符號右移那樣以符號位的值來填充空位。

var oldValue = -64;              //equal to binary 11111111111111111111111111000000
var newValue = oldValue >>> 5;   //equal to decimal 134217726
alert(newValue);                 //134217726複製程式碼

我們可以利用邏輯或的行為特性來避免為變數賦 nullundefined 值。例如:

var myObject = preferredObject || backupObject;複製程式碼

上面這段程式碼,如果 preferredObject 的值不是 null,那麼它的值將被賦給 myObject;如果是 null,則將 backupObject 的值賦給 myObject。ECMAScript 程式的賦值語句常用這種模式。

加性操作符有以下特性:+0+0 結果為 +0-0-0 結果為 -0+0-0 結果為 +0。如果兩個運算元都是字串,則將第二個運算元與第一個運算元拼接起來。如果只有一個運算元是字串,則將另一個運算元轉換為字串,然後再將兩個字串拼接起來。

var result1 = 5 + 5;     //two numbers
alert(result1);           //10
var result2 = 5 + "5";   //a number and a string
alert(result2);           //"55"複製程式碼
var num1 = 5;
var num2 = 10;
var message = "The sum of 5 and 10 is " + num1 + num2;
alert(message);    //"The sum of 5 and 10 is 510"複製程式碼
var num1 = 5;
var num2 = 10;
var message = "The sum of 5 and 10 is " + (num1 + num2);
alert(message);    //"The sum of 5 and 10 is 15"複製程式碼

減性操作符有以下特性:+0+0 結果為 +0-0+0 結果為 -0-0+0 結果為 +0

var result1 = 5 - true;    //4 because true is converted to 1
var result2 = NaN - 1;     //NaN
var result3 = 5 - 3;       //2
var result4 = 5 - "";      //5 because "" is converted to 0
var result5 = 5 - "2";     //3 because "2" is converted to 2
var result6 = 5 - null;    //5 because null is converted to 0複製程式碼

相等操作符有相等 == 和不相等 !=、全等 === 和不全等 !== 兩種。前者先轉換再比較,後者僅比較不轉換。除此之外無區別。轉換指轉換成數值。

var result1 = ("55" == 55);    //true ?equal because of conversion
var result2 = ("55" === 55);   //false ?not equal because different data types

var result3 = ("55" != 55);    //false ?equal because of conversion
var result4 = ("55" !== 55);   //true ?not equal because different data types

alert(null == undefined);    //true
alert(null === undefined);   //false

alert("NaN" == NaN);        //false
alert("NaN" === NaN);       //false
alert(NaN == NaN);          //false
alert(NaN === NaN);         //false
alert(NaN != NaN);          //true
alert(NaN !== NaN);         //true

alert(false == 0);          //true
alert(false === 0);         //false
alert(true == 1);           //true
alert(true === 1);          //false

alert(null == 0);           //false
alert(undefined == 0);      //false

alert(5 == "5");            //true
alert(5 === "5");           //false複製程式碼

注意:null == undefined 會返回 true,而 null === undefined 會返回 false,因為他們是不同型別的值。由於相等和不相等操作符存在型別轉換問題,而為了保持程式碼中資料型別的完整性,我們推薦使用全等和不全等操作符。

條件操作符:

var max = (num1 > num2) ? num1 : num2;複製程式碼


語句

  • if 語句do-while 語句while 語句for 語句for-in 語句lable 語句break 和 continue 語句with 語句switch 語句

  • 因為 ECMAScript 中不存在塊級作用域,因此在迴圈內部定義的變數也可以在外部訪問到。例如:

var count = 10;
for (var i=0; i < count; i++){
    alert(i);
}
alert(i);    //10複製程式碼
  • for-in語句是一種精準的迭代語句,可以用來列舉物件屬性。用法是:
for (property in wxpression) statement複製程式碼

下面是一個示例:這個例子迴圈顯示 BOM 中 window 物件的所有屬性。

for (var propName in window) {
  document.wright(propName);
}複製程式碼

如果要迭代的物件的變數值為 nullundefinedfor-in 語句會丟擲錯誤。ECMAScript 5 已經更正了這一行為,對這種情況不再丟擲錯誤,而只是不執行迴圈體。為了保證最大限度相容性,建議在使用 for-in 迴圈之前先檢查確認該物件的值不是 nullundefined

  • lable 語句可以在程式碼中新增標籤,以便將來使用。語法為:
lable: statement;複製程式碼

程式碼示例:這個例子中定義的 start 標籤可以在將來由 breakcontinue 語句引用。加標籤的語句一般都要與 for 語句等迴圈語句配合使用。

start: for (var - = 0; i < count; i++) {
}複製程式碼

下面這段程式碼使得 break 語句不僅會退出內部的 for 語句,而且也會退出外部的 for 語句。

var num = 0;

outermost:
for (var i=0; i < 10; i++) {
  for (var j=0; j < 10; j++) {
    if (i == 5 && j == 5) {
      break outermost;
    }
    num++;
  }
}

alert(num);    //55複製程式碼
var num = 0;

outermost:
for (var i=0; i < 10; i++) {
  for (var j=0; j < 10; j++) {
    if (i == 5 && j == 5) {
        continue outermost;
    }
    num++;
  }
}

alert(num);    //95複製程式碼
  • with 語句的作用是將程式碼的作用域設定到一個特定的物件中,語法如下:
with (expression) statement;複製程式碼

定義 with 語句的目的是簡化多次編寫同一個物件的工作,如下面的例子所示:

var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;複製程式碼

with 語句寫的話就可以簡化成下面這樣:

with (location) {
  var qs = search.substring(1);
  var hostName = hostname;
  var url = href;
}複製程式碼

注意:嚴格模式下不允許使用 with 語句,否則將視為語法錯誤。同時,大量使用這種語句會導致效能下降,同時也會給除錯程式碼造成困難,因此在開發大型應用程式時不建議使用 with 語句。

  • switch 語句,雖然 ECMAScript 的 switch 語句是借鑑其他語言的,但是也有其特色。可以在 ECMAScript 的 switch 語句中使用任何資料型別。其次,每一個 case 的值不一定是常量,可以是變數,也可以是表示式。
switch ("hello world") {
  case "hello" + " world": 
    alert("Greeting was found.");
    break;
  case "goodbye": 
    alert("Closing was found.");
    break;
  default: 
    alert("Unexpected message was found.");
}複製程式碼
var num = 25;
switch (true) {
  case num < 0: 
    alert("Less than 0.");
    break;
  case num >= 0 && num <= 10: 
    alert("Between 0 and 10.");
    break;
  case num > 10 && num <= 20: 
    alert("Between 10 and 20.");
    break;
  default: 
    alert("More than 20.");
}複製程式碼


函式

對於函式的返回值,推薦的做法是要麼讓函式始終都返回一個值,要麼永遠都不要返回值,否則,如果函式有時候返回值,有時候不返回值,會給除錯程式碼帶來不便。

ECMAScript 不介意你傳遞的引數個數和引數的資料型別,因為 ECMAScript 中的引數只在內部是一個陣列來表示的。實際上在函式體內可以通過 arguments 物件來訪問這個引數陣列,從而獲得傳遞給函式的每一個引數。arguments 物件只是與陣列類似但並不是 Array 的例項。

沒有傳遞值的命名引數將自動被賦予 undefined 值,這就和定義變數但為初始化類似。

在 ECMAScript 中,定義了兩個名字相同的函式,則該名字只屬於後定義的函式。

相關文章