JavaScript:函數、標准對象
初識函數
函數就和Java中的方法是一樣的,說白了,就是一系列語句的集合,我們可以提取出來實現復用!
在JavaScript中,定義函數的方式如下:
function abs(x) { if (x >= 0) { return x; } else { return -x; } }
上述abs()函數的定義如下:
function
指出這是一個函數定義;abs
是函數的名稱;(x)
括號內列出函數的參數,多個參數以,分隔;{ ... }
之間的代碼是函數體,可以包含若干語句,甚至可以沒有任何語句。
請注意,函數體內部的語句在執行時,一旦執行到return時,函數就執行完畢,並將結果返回。因此,函數內部通過條件判斷和循環可以實現非常復雜的邏輯。
如果沒有return語句,函數執行完畢后也會返回結果,只是結果為undefined。
由於JavaScript的函數也是一個對象,上述定義的abs()函數實際上是一個函數對象,而函數名abs可以視為指向該函數的變量。
因此,第二種定義函數的方式如下:
var abs = function (x) { if (x >= 0) { return x; } else { return -x; } };
在這種方式下,function (x) { ... }是一個匿名函數,它沒有函數名。但是,這個匿名函數賦值給了變量abs,所以,通過變量abs就可以調用該函數。
上述兩種定義完全等價,注意第二種方式按照完整語法需要在函數體末尾加一個;,表示賦值語句結束。
調用函數時,按順序傳入參數即可:
abs(10); // 返回10 abs(-9); // 返回9
由於JavaScript允許傳入任意個參數而不影響調用,因此傳入的參數比定義的參數多也沒有問題,雖然函數內部並不需要這些參數:
abs(10, 'blablabla'); // 返回10 abs(-9, 'haha', 'hehe', null); // 返回9
傳入的參數比定義的少也沒有問題:
abs(); // 返回NaN
要避免收到undefined,可以對參數進行檢查:
function abs(x) { //類型比較,和拋出異常~ if (typeof x !== 'number') { throw 'Not a number'; } if (x >= 0) { return x; } else { return -x; } }
arguments 它只在函數內部起作用,並且永遠指向當前函數的調用者傳入的所有參數。利用arguments,你可以獲得調用者傳入的所有參數。也就是說,即使函數不定義任何參數,還是可以拿到參數的值:
function abs() { if (arguments.length === 0) { return 0; } var x = arguments[0]; return x >= 0 ? x : -x; } abs(); // 0 abs(10); // 10 abs(-9); // 9
rest參數
ES6標准引入了rest參數 :
function foo(a, b, ...rest) { console.log('a = ' + a); console.log('b = ' + b); console.log(rest); } foo(1, 2, 3, 4, 5); // 結果: // a = 1 // b = 2 // Array [ 3, 4, 5 ] foo(1); // 結果: // a = 1 // b = undefined // Array []
rest參數只能寫在最后,前面用...標識,從運行結果可知,傳入的參數先綁定a、b,多余的參數以數組形式交給變量rest,所以,不再需要arguments我們就獲取了全部參數。
如果傳入的參數連正常定義的參數都沒填滿,也不要緊,rest參數會接收一個空數組(注意不是undefined)。
變量作用域與解構賦值
在JavaScript中,用var申明的變量實際上是有作用域的。
如果一個變量在函數體內部申明,則該變量的作用域為整個函數體,在函數體外不可引用該變量:
'use strict'; function foo() { var x = 1; x = x + 1; } x = x + 2; // ReferenceError! 無法在函數體外引用變量x
如果兩個不同的函數各自申明了同一個變量,那么該變量只在各自的函數體內起作用。換句話說,不同函數內部的同名變量互相獨立,互不影響:
'use strict'; function foo() { var x = 1; x = x + 1; } function bar() { var x = 'A'; x = x + 'B'; }
由於JavaScript的函數可以嵌套,此時,內部函數可以訪問外部函數定義的變量,反過來則不行:
'use strict'; function foo() { var x = 1; function bar() { var y = x + 1; // bar可以訪問foo的變量x! } var z = y + 1; // ReferenceError! foo不可以訪問bar的變量y! }
如果內部函數和外部函數的變量名重名怎么辦?來測試一下:
function foo() { var x = 1; function bar() { var x = 'A'; console.log('x in bar() = ' + x); // 'A' } console.log('x in foo() = ' + x); // 1 bar(); } foo();
這說明JavaScript的函數在查找變量時從自身函數定義開始,從“內”向“外”查找。如果內部函數定義了與外部函數重名的變量,則內部函數的變量將“屏蔽”外部函數的變量。
變量提升
JavaScript的函數定義有個特點,它會先掃描整個函數體的語句,把所有申明的變量“提升”到函數頂部:
'use strict'; function foo() { var x = 'Hello, ' + y; console.log(x); var y = 'Bob'; } foo();
雖然是strict模式,但語句var x = 'Hello, ' + y;並不報錯,原因是變量y在稍后申明了。但是console.log顯示Hello, undefined,說明變量y的值為undefined。這正是因為JavaScript引擎自動提升了變量y的聲明,但不會提升變量y的賦值。
對於上述foo()函數,JavaScript引擎看到的代碼相當於:
function foo() { var y; // 提升變量y的申明,此時y為undefined var x = 'Hello, ' + y; console.log(x); y = 'Bob'; }
由於JavaScript的這一怪異的“特性”,我們在函數內部定義變量時,請嚴格遵守“在函數內部首先申明所有變量”這一規則。最常見的做法是用一個var申明函數內部用到的所有變量:
function foo() { var x = 1, // x初始化為1 y = x + 1, // y初始化為2 z, i; // z和i為undefined // 其他語句: for (i=0; i<100; i++) { ... } }
全局作用域
不在任何函數內定義的變量就具有全局作用域。實際上,JavaScript默認有一個全局對象window,全局作用域的變量實際上被綁定到window的一個屬性:
'use strict'; var course = 'Learn JavaScript'; alert(course); // 'Learn JavaScript' alert(window.course); // 'Learn JavaScript'
因此,直接訪問全局變量course和訪問window.course是完全一樣的。
你可能猜到了,由於函數定義有兩種方式,以變量方式var foo = function () {}定義的函數實際上也是一個全局變量,因此,頂層函數的定義也被視為一個全局變量,並綁定到window對象:
'use strict'; function foo() { alert('foo'); } foo(); // 直接調用foo() window.foo(); // 通過window.foo()調用
進一步大膽地猜測,我們每次直接調用的alert()函數其實也是window的一個變量:
window.alert('調用window.alert()'); // 把alert保存到另一個變量: var old_alert = window.alert; // 給alert賦一個新函數: window.alert = function () {} alert('無法用alert()顯示了!'); // 恢復alert: window.alert = old_alert; alert('又可以用alert()了!');
局部作用域
由於JavaScript的變量作用域實際上是函數內部,我們在for循環等語句塊中是無法定義具有局部作用域的變量的:
'use strict'; function foo() { for (var i=0; i<100; i++) { // } i += 100; // 仍然可以引用變量i }
為了解決塊級作用域,ES6引入了新的關鍵字let,用let替代var可以申明一個塊級作用域的變量:
'use strict'; function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } // SyntaxError: i += 1; }
常量
由於var和let申明的是變量,如果要申明一個常量,在ES6之前是不行的,我們通常用全部大寫的變量來表示“這是一個常量,不要修改它的值”:
var PI = 3.14;
ES6標准引入了新的關鍵字const來定義常量,const與let都具有塊級作用域:
'use strict'; const PI = 3.14; PI = 3; // 某些瀏覽器不報錯,但是無效果! PI; // 3.14
方法
在一個對象中綁定函數,稱為這個對象的方法。
在JavaScript中,對象的定義是這樣的:
var xiaoming = { name: '小明', birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; } }; xiaoming.age; // function xiaoming.age() xiaoming.age(); // 今年調用是25,明年調用就變成26了
綁定到對象上的函數稱為方法,和普通函數也沒啥區別,但是它在內部使用了一個this關鍵字,這個東東是什么?
在一個方法內部,this是一個特殊變量,它始終指向當前對象,也就是xiaoming這個變量。所以,this.birth可以拿到xiaoming的birth屬性。
標准對象
在JavaScript的世界里,一切都是對象。
但是某些對象還是和其他對象不太一樣。為了區分對象的類型,我們用typeof操作符獲取對象的類型,它總是返回一個字符串:
typeof 123; // 'number' typeof NaN; // 'number' typeof 'str'; // 'string' typeof true; // 'boolean' typeof undefined; // 'undefined' typeof Math.abs; // 'function' typeof null; // 'object' typeof []; // 'object' typeof {}; // 'object'
Date
在JavaScript中,Date對象用來表示日期和時間。要獲取系統當前時間,用:
var now = new Date(); now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST) now.getFullYear(); // 2015, 年份 now.getMonth(); // 5, 月份,注意月份范圍是0~11,5表示六月 now.getDate(); // 24, 表示24號 now.getDay(); // 3, 表示星期三 now.getHours(); // 19, 24小時制 now.getMinutes(); // 49, 分鍾 now.getSeconds(); // 22, 秒 now.getMilliseconds(); // 875, 毫秒數 now.getTime(); // 1435146562875, 以number形式表示的時間戳
注意,當前時間是瀏覽器從本機操作系統獲取的時間,所以不一定准確,因為用戶可以把當前時間設定為任何值。
如果要創建一個指定日期和時間的Date對象,可以用:
var d = new Date(2015, 5, 19, 20, 15, 30, 123); d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST)
你可能觀察到了一個非常非常坑爹的地方,就是JavaScript的月份范圍用整數表示是0~11,0表示一月,1表示二月……,所以要表示6月,我們傳入的是5!這絕對是JavaScript的設計者當時腦抽了一下,但是現在要修復已經不可能了。
Date對象表示的時間總是按瀏覽器所在時區顯示的,不過我們既可以顯示本地時間,也可以顯示調整后的UTC時間:
var d = new Date(); d.toLocaleString(); d.toUTCString();
JSON
JSON是JavaScript Object Notation的縮寫,它是一種數據交換格式。
在JSON出現之前,大家一直用XML來傳遞數據。因為XML是一種純文本格式,所以它適合在網絡上交換數據。XML本身不算復雜,但是,加上DTD、XSD、XPath、XSLT等一大堆復雜的規范以后,任何正常的軟件開發人員碰到XML都會感覺頭大了,最后大家發現,即使你努力鑽研幾個月,也未必搞得清楚XML的規范。
終於,在2002年的一天,道格拉斯·克羅克福特(Douglas Crockford)同學為了拯救深陷水深火熱同時又被某幾個巨型軟件企業長期愚弄的軟件工程師,發明了JSON這種超輕量級的數據交換格式。
並且,JSON還定死了字符集必須是UTF-8,表示多語言就沒有問題了。為了統一解析,JSON的字符串規定必須用雙引號"",Object的鍵也必須用雙引號""。
由於JSON非常簡單,很快就風靡Web世界,並且成為ECMA標准。幾乎所有編程語言都有解析JSON的庫,而在JavaScript中,我們可以直接使用JSON,因為JavaScript內置了JSON的解析。
把任何JavaScript對象變成JSON,就是把這個對象序列化成一個JSON格式的字符串,這樣才能夠通過網絡傳遞給其他計算機。
如果我們收到一個JSON格式的字符串,只需要把它反序列化成一個JavaScript對象,就可以在JavaScript中直接使用這個對象了。
在 JavaScript 語言中,一切都是對象。因此,任何JavaScript 支持的類型都可以通過 JSON 來表示,例如字符串、數字、對象、數組等。看看他的要求和語法格式:
- 對象表示為鍵值對,數據由逗號分隔
- 花括號保存對象
- 方括號保存數組
JSON 鍵值對是用來保存 JavaScript 對象的一種方式,和 JavaScript 對象的寫法也大同小異,鍵/值對組合中的鍵名寫在前面並用雙引號 "" 包裹,使用冒號 : 分隔,然后緊接着值:
{"name": "QinJiang"} {"age": "3"} {"sex": "男"}
很多人搞不清楚 JSON 和 JavaScript 對象的關系,甚至連誰是誰都不清楚。其實,可以這么理解:
-
JSON 是 JavaScript 對象的字符串表示法,它使用文本表示一個 JS 對象的信息,本質是一個字符串。
var obj = {a: 'Hello', b: 'World'}; //這是一個對象,注意鍵名也是可以使用引號包裹的 var json = '{"a": "Hello", "b": "World"}'; //這是一個 JSON 字符串,本質是一個字符串
JSON 和 JavaScript 對象互轉
-
要實現從JSON字符串轉換為JavaScript 對象,使用 JSON.parse() 方法:
var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //結果是 {a: 'Hello', b: 'World'}
-
要實現從JavaScript 對象轉換為JSON字符串,使用 JSON.stringify() 方法:
var json = JSON.stringify({a: 'Hello', b: 'World'}); //結果是 '{"a": "Hello", "b": "World"}'
上代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JSON_秦疆</title> </head> <body> <script type="text/javascript"> //編寫一個js的對象 var user = { name:"秦疆", age:3, sex:"男" }; //將js對象轉換成json字符串 var str = JSON.stringify(user); console.log(str); //將json字符串轉換為js對象 var user2 = JSON.parse(str); console.log(user2.age,user2.name,user2.sex); </script> </body> </html>