使用與輸出
使用 JavaScript
- 內部使用:置於
<script></script>
中- 在
HTML
中,JavaScript 指令碼需置於<script></script>
標籤之間 - 這部分內容可以放在
<head>
中,也可以放在<body>
中 <head>
與<body>
中的 JavaScript 有什麼區別呢- 簡單來說,區別就是執行順序不同:
<head>
中的 JavaScript 會 HTML 標籤解析之前就執行,而<body>
中的是在載入後才執行的 - 舉個例子,指令碼中的某個函式涉及到 DOM 操作,那麼前提當然是這個 DOM 節點已經生成了,若放在
<head>
中,那麼就會在 HTML 標籤解析之前執行,也就沒有對應的 DOM 物件可以操作了
- 簡單來說,區別就是執行順序不同:
- 應該放在哪裡呢
- 需呼叫才執行的指令碼或事件觸發執行的指令碼放在 HTML 的
<head>
部分中,這樣可以保證在任何呼叫之前被載入 - 些當頁面被載入時才會執行的指令碼應該放在 HTML 的
<body>
部分,放在<body>
部分的指令碼通常被用來生成頁面的內容 - 一般是把函式放入
<head>
部分中,或者放在頁面底部,這樣可以把它們安置到同一位置,不會干擾頁面的內容
- 需呼叫才執行的指令碼或事件觸發執行的指令碼放在 HTML 的
- 參考連結(defer和async超出此卷的範圍,之後另做講解)
- 在
- 外部使用:置於外部 js 檔案中,通過
<script src="xxx.js"></script>
引入- 通常這個引入 src 的地方也就是你原本打算在
<script>
中寫入內容的地方
- 通常這個引入 src 的地方也就是你原本打算在
輸出 JavaScript
- JavaScript 沒有提供 任何列印或者顯示的函式,我們可以選擇以下幾種輔助方案輸出:
window.alert()
彈出警告框document.write()
寫入 HTML 文件中innerHTML
寫入 HTML 元素中console.log()
寫入瀏覽器的控制檯中
-
下面我們詳解這幾個方法
window.alert()
- 基本用法請閱讀:MDN — window.alert
-
document.write()
- 基本用法請閱讀:
document.write(exp1, exp2, exp3, ...)
可以接受一個或者多個引數,該方法會按照順序寫入由document.open()
開啟的一個文件流中(多個引數貌似不會換行顯示)- 這裡的引數可以是 變數、函式體、自執行函式、數字、字串、表示式 等等,引數也同樣支援 HTML 標籤,只是需要用字串形式
- 例如:
<script type="text/javascript"> var a = 100; var b = function(){ console.log('200') }; //變數 document.write(a,'<br/>'); //函式體 document.write(b,'<br/>'); //自執行函式 document.write(function(){ return 300; }(),'<br/>'); //數字 document.write(400,'<br/>'); //字串 document.write('500','<br/>'); //表示式 document.write((300*2),'<br/>'); //HTML 標籤 document.write('<h1>700</h1>'); </script>
瀏覽器顯示如下:
- 注意,因為 document.write() 寫入文件流,所以 在已載入或者已關閉的文件上呼叫 document.write() 會自動呼叫 document.open(),這將覆蓋該文件,比如:
<p>ABCDE</p> <button onclick="displayDate()">點選這裡</button> <script type="text/javascript"> function displayDate(){ document.write('覆蓋頁面') } </script>
此時,不管你的頁面上有什麼內容,點選按鈕,就會立刻覆蓋整個頁面
這種情況一般伴隨著兩個條件:1、在函式內部呼叫 document.write();2、通過按鈕響應呼叫函式
再次重複一遍!向一個已經載入,並且沒有呼叫過 document.open() 的文件寫入資料時,會自動完成呼叫 document.open() 的操作,一旦完成了資料寫入,系統要求呼叫document.close()
以告訴瀏覽器頁面已經載入完畢,寫入的資料會被解析到文件結構模型裡,生成對應的 DOM 結點 - 而直接嵌入 HTML 中,不會呼叫 document.open() 這也就是為什麼我們上上個例子中連續多個 document.write() 沒有被覆蓋
- 試一下:手動使用
document.close()
:<h2>AB</h2> <h2>CD</h2> <h2>EF</h2> <script type="text/javascript"> document.write('document.close()之前','<br />'); document.close(); document.write('document.close()之後') </script>
<h2>AB</h2> <h2>CD</h2> <h2>EF</h2> <script type="text/javascript"> window.onload = function(){ document.write('document.close()之前','<br />'); document.close(); document.write('document.close()之後') } </script>
這是因為,
window.onload
表達的是
An event handler for the load event of a window.
換言之,在文件載入完畢的時候,document.write() 在頁面載入後呼叫,但在 W3C 規範中沒有定義時,會發生自動的 document.open() 呼叫,所以頁面才會被清除
innerHTML
- innerHTML 有著雙向功能:①獲取 HTML 內容;②寫入 HTML 內容。案例如下:
<div id="x"> <p>此為用 innerHTML 獲取內容1<p> <p>此為用 innerHTML 獲取內容2<p> </div> <button type="button" onclick="Inner()">點選</button> <script type="text/javascript"> function Inner(){ alert(document.getElementById('x').innerHTML); document.getElementById('x').innerHTML = '此為用 innerHTML 寫入內容' } </script>
- innerHTML 有著雙向功能:①獲取 HTML 內容;②寫入 HTML 內容。案例如下:
console.log()
- 與
alert()
不同的是,console.log()
並不會阻斷執行緒,用法比較簡單,在此不細說了,下面列舉一些控制檯常用方法 - console.log() :輸出普通訊息
- console.info() :輸出提示資訊
- console.error() :輸出錯誤資訊
- console.warn() :輸出警示資訊
- console.debug() :輸出除錯資訊
- 與
語句與表示式
語句
語句(statement) 是為了 完成某種任務 而進行的 操作 。
- 你可以把一個語句理解成一個動作,一個行為,比如 if語句、迴圈語句,如下的賦值語句:
var a = 1;
- 上述語句先用
var
命令宣告瞭變數a
,再將1+3
的運算結果賦值給變數a
- 上述語句先用
- 一個程式 是由 一系列語句 組成的
- JS 語句以分號結尾,JS是 弱型別 的語言,雖然分號有時可以省略,也請在省略時 注意是否會造成語法錯誤 ,但是一個分號就肯定表示著一個語句的結束,多個語句可以寫在一行內。如:
var a = 1; var b = 2;
- 分號前面可以沒有任何內容,JavaScript 引擎將其視為空語句。
;;; //表示三條空語句
- 語句不存在返回值的說法
- 一般JavaScript的語句分為以下幾種:
- 宣告語句:變數宣告和函式宣告
- 賦值語句:在語句中比較重要的一類
- 控制語句:能夠 改變語句的執行順序,包括條件語句和迴圈語句,還有比較特殊的標籤語句
- 表示式語句:是JS中最簡單的語句,此類語句只由表示式組成,沒有其他語法元素
- 賦值、delete、函式呼叫 這三類既是表示式,又是語句,所以叫做表示式語句
- 注:JS中某些需要語句的地方,若可以使用一個表示式來代替,那麼此語句即是表示式語句。但反過來不可以,不能在一個需要表示式的地方放一個語句。比如,一個if語句不能作為一個函式的引數。
表示式
表示式 是由 運算子 和 運算元(可選) 構成的,併產生 運算結果 的語法結構 。
- 以下在 ES5 中,被稱為 基本表示式(Primary Expression):
- this、null、arguments 等 內建關鍵字
- 變數,即一個已宣告的識別符號
- 字面量,僅包括數字字面量、布林值字面量、字串字面量、正則字面量
- 分組表示式,即用來表示立刻進行計算的
- 上述表示式均為 原子表示式,即無法再分解的表示式
- 除了基本表示式外,還有 複雜表示式,這類表示式 需要其他表示式的參與,如下:
- 物件、陣列初始化表示式:
- 其實他們也算字面量的一種,但不把它們算作基本表示式,是因為物件字面量、陣列字面量所包含的成員也都是表示式
- 陣列初始化表示式語法如下:
[expression,expression,expression] //可以有 0個或多個子表示式
初始化的結果是一個新建立的陣列,陣列的元素是逗號分隔的表示式的值。舉例:
[] //一個空陣列;[]內留空即表示該陣列沒有任何元素 [1+2,3+4] //擁有兩個元素的陣列,第一個是3,第二個是7
- 物件初始化表示式語法如下:
{ expression1: expression2, expression1: expression2, expression1: expression2, } //在ES5及其之前,expression1只能是字串字面量,但ES6開始支援以下寫法: { } //expression1可以是任何返回值為字串或Symbol型別的表示式
初始化的結果是一個新建立的物件,舉例:
{ foo: bar(3,5) } //不過同時,它也是一個完全合法的語句,這個語句的組成部分有: // 1.一個程式碼塊:一個由大括號包圍的語句序列; // 2.一個標籤:你可以在任何語句前面放置一個標籤.這裡的foo就是一個標籤. // 3.一條語句:表示式語句bar(3, 5).
- 函式定義表示式(注意,需與函式宣告語句區分開)
- 關於函式宣告和函式表示式 可檢視此兩篇博文:
js函式宣告和函式表示式的區別 以及 js函式宣告和函式表示式
- 關於函式宣告和函式表示式 可檢視此兩篇博文:
- 屬性訪問表示式:
- 屬性訪問表示式的兩種語法如下:
expression.identifier //expression可以是任意的表示式,identifier是屬性名(必須合法) //注:跟在物件後面的句點'.'不是運算子,而是屬性訪問表示式的語法結構的一部分
以及
expression1[expression2] //其中,expression1與expression2都可以是任意表示式 //而expression2的值會被轉化為字串(除非它是一個Symbol型別)
- 屬性訪問表示式的兩種語法如下:
- 呼叫表示式(分為 函式呼叫 和 方法呼叫):
- 函式呼叫的語法如下:
expression0([expression1[,expression2[,expression3]]]) //expression0是一個返回值為函式物件的表示式 //小括號內的是引數列表,其引數為0或多個,用逗號分隔 //小括號並非操作符,而是呼叫語法的一部分
- 方法呼叫的語法如下:
expression0([[expression1[,expression2[,expression3]]]) //其中,expression0 是一個返回值為函式物件的表示式 //小括號提供一個逗號分隔的引數列表
- 函式呼叫的語法如下:
- 物件建立表示式:
- 該表示式的語法如下:
new expression0([expression1[,expression2[,expression3]]]) //同呼叫表示式一樣,//expression0是一個返回值為函式物件的表示式 //小括號並非操作符而是語法的一部分
- 該表示式的語法如下:
- 物件、陣列初始化表示式:
- 若表示式中未使用運算子,則稱為
單值表示式
,否則為複合表示式
- JavaScript表示式必有返回值,單值表示式返回其值本身,複合表示式返回運算的結果值
- 總結出的表示式分類如下:
- 單值表示式(不使用運算子)
- 簡單表示式(不能再分解)
- 複雜表示式(需要其他表示式的參與)
- 符合表示式(由運算子將多個單值表示式結合而成的表示式)
- 單值表示式(不使用運算子)
變數
相關概念
- 變數是對 “值” 的具名引用
- 變數的型別是沒有限制的,所以你可以隨時修改變數的型別,這也表明了 JavaScript是一種動態型別語言
識別符號
- 識別符號(identifier) 指的是用來識別各種值的合法名稱,最常見的識別符號就是變數名以及函式名,識別符號對大小寫敏感
- 命名規則如下:
- 以
字母 或者 $ 或者 _
開頭,後面可包含數字
- 對大小寫敏感
- 中文是合法識別符號,如:
var 臨時變數 = 1;
- 保留字不能作識別符號
- 以
- 保留字如下:
arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield
變數宣告
- 變數的宣告與賦值是兩個過程,如果只是宣告變數而沒有賦值,則該變數的值是
undefined
,undefined是一個特殊的值,表示未定義 - 重複地宣告變數是無效的
- 初識變數提升
- 由於 JavaScript 引擎的工作方式是:先解析程式碼,獲取所有被宣告的變數再執行,所以,所有變數的宣告都會被提到 作用域的頂端
- 看幾個例子:
function test(){ console.log(a); var a = 1; } test(); //undefined //上面的程式碼等效於: function test () { var a; console.log(a); a = 123; } test(); //undefined
a = 1; var a; console.log(a); //1
看一道經典的面試題:
console.log(v1); //undefined var v1 = 100; function foo() { console.log(v1); //undefined var v1 = 200; console.log(v1); //200 } foo(); console.log(v1); //100
- 實際上,js 引擎並不會把
var a = 1;
當做一個過程,而是看做var a;
和a = 1;
,分別對應著宣告與賦值,分別對應著編譯階段和執行階段,而宣告會被提到作用域的頂端 - 如果理解了上面三個例子,那麼已經成功認識了變數提升這個概念,徹底理解會在之後的作用域中詳解
- 參考閱讀:
- 幾種變數的宣告:var、let、const
- 先認識一個塊級作用域:在 ES5 中,作用域有全域性作用域和函式作用域,而在 ES6 中,增加了一個 塊級作用域,塊級作用域由
{ }
包括(所以 if 語句等等也會形成塊級作用域),舉個栗子://通過var定義的變數可以跨塊級作用域訪問到 { var a = 1; console.log(a); //1 } console.log(a); //1 //通過var定義的變數不可以跨函式作用域訪問到 (function A(){ var b = 2; console.log(b); //2 })(); console.log(b); //報錯:b is not defined //if語句和for語句形成的是塊級作用域而非函式作用域,因為var定義的變數可以在外部訪問 if(true){ var c = 3; } console.log(c); //3 for(var i = 0; i < 4; i++){ var d = 5; } console.log(i); //4 console.log(d); //5
- 承接之前的塊級作用域,說說 var、let、const 的區別:
- var 定義的變數,沒有塊級作用域的概念,所以可以跨塊級作用域訪問, 但不能跨函式作用域訪問
- let 定義的變數,只能在塊級作用域裡訪問,不能跨塊訪問,也不能跨函式訪問。
- const 用來定義常量,使用時必須初始化(即必須賦值),只能在塊作用域裡訪問,而且不能修改
- 舉例如下:
//塊級作用域 { //const 不允許改變 var a = 1; let b = 2; const c = 3; c = 4; //報錯:Assignment to constant variable //const 宣告變數時必須初始化 var A; let B; const C; //報錯:Missing initializer in const declaration //列印,觀察 console.log(a); //1 console.log(b); //2 console.log(c); //3 console.log(A); //undefined console.log(B); //undefined } console.log(a); //1 console.log(b); //報錯:b is not defined console.log(c): //報錯:b is not defined //函式作用域 (function Fun(){ var i = 5; let j = 6; const k = 7; })(); //console.log(i); //報錯:i is not defined //console.log(j); //報錯:j is not defined //console.log(k); //報錯:k is not defined
- 讀到這裡,已經初步認識了三者,接下來詳細說明一下各自的用法
- let 宣告變數
- 之前提到過,ES5 沒有塊級作用域,這導致很多場景只能通過閉包來解決不合理的衝突,ES6 出來之後,我們可以通過 let 即可實現
- 注意,let 宣告的變數只在 let 命令所在的程式碼塊內有效,如上個例子中的
console.log(b);
與console.log(j);
均會報錯 “未定義”,所以,for 迴圈就很適合 let,另外,說到 for 迴圈順便提提它的特點:設定迴圈變數的那部分是一個 父作用域,而迴圈體內部是一個單獨的 子作用域for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
- let 不存在變數提升,之前在 var 的變數提升總讓人感覺邏輯怪怪的,現在在 let 中不存在變數提升了
- 暫時性死區:只要塊級作用域記憶體在 let 命令,它所宣告的變數就 “繫結”(binding)這個區域,不再受外部的影響,例如:
var a = 1; if(true){ a = 'abc'; let a; }
上面程式碼中,var 先將 a 宣告為全域性變數,但是塊級作用域內 let 又宣告瞭一個區域性變數 a,導致後者繫結這個塊級作用域,所以在 let 宣告變數前,對 a 賦值會報錯
因為 ES6 已經明確規定,如果區塊中存在 let 和 const 命令,這個區塊對這些命令宣告的變數,從一開始就形成了 封閉作用域,不可在宣告之前就使用這些變數
也就是在用 let 和 const 宣告變數之前,不可以使用該變數,這種語法稱為 暫時性死區(temporal dead zone,簡稱 TDZ) - var 可以重複宣告變數的,只不過無效而已,但是 let 是不允許重複宣告同一變數的,例如:
var a = 1; var a; console.log(a); let b = 1; let b; console.log(b); //報錯:Identifier 'b' has already been declared function fun(){ var a = 1; let a; //報錯:Identifier 'a' has already been declared } //下面的不報錯 function fun(x){ { let x; } } fun(); //不報錯
- const 宣告變數
- const 宣告一個 只讀的常量,一旦宣告,常量的值就不能改變,這也同時意味著,一旦用 const 宣告變數,就必須立刻賦值,不能留到之後再賦值,例如:
//不能改變值 const a = 1; a = 2; //報錯:Assignment to constant variable. //必須立刻賦值 const b; b = 3; //報錯:Missing initializer in const declaration
- const 的作用域與 let 相同:只在宣告所在的塊級作用域內有效,同樣的,const 宣告的常量也不會有提升現象,而且也同樣存在暫時性死區,只能在宣告的位置後面使用
- 參考閱讀:
- 擴充:const 真的不能修改麼?
- 先看個例子:
const person = { name: 'A', age: 15 } person.name = 'B'; console.log(person.name) //B
- 因為物件是引用型別的,person 儲存的僅僅是 物件的指標,這也就意味著,const 似乎只在乎指標有沒有發生改變,而修改物件的型別是不會改變物件指標的,所以是被允許的
- 也就是說,const 定義的引用型別只要指標不發生改變,其他的不論如何改變都是允許的,用下面的程式碼測試,果然報錯:
const person = { name: 'A', age: 15 } person = { name: 'A', age: 16 } console.log(person.name); //報錯:Assignment to constant variable.
- 先看個例子:
- const 宣告一個 只讀的常量,一旦宣告,常量的值就不能改變,這也同時意味著,一旦用 const 宣告變數,就必須立刻賦值,不能留到之後再賦值,例如:
- 先認識一個塊級作用域:在 ES5 中,作用域有全域性作用域和函式作用域,而在 ES6 中,增加了一個 塊級作用域,塊級作用域由