var str = '{"name":"huangxiaojian","age":"23"}';
JSON.parse(str);
//{name: "huangxiaojian", age: "23"}
var a = {a:1,b:2};
JSON.stringify(a);
//"{"a":1,"b":2}"複製程式碼
如果繼承的屬性是可遍歷的,那麼就會被for...in
迴圈遍歷到。但是,一般情況下,都是隻想遍歷物件自身的屬性,所以使用for...in
的時候,應該結合使用hasOwnProperty
方法,在迴圈內部判斷一下,某個屬性是否為物件自身的屬性。
var person = { name: '老張' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name複製程式碼
閉包(closure)是 Javascript 語言的一個難點,也是它的特色,很多高階應用都要依靠閉包實現。
理解閉包,首先必須理解變數作用域。前面提到,JavaScript 有兩種作用域:全域性作用域和函式作用域。函式內部可以直接讀取全域性變數。
var n = 999;
function f1() {
console.log(n);
}
f1() // 999複製程式碼
上面程式碼中,函式f1
可以讀取全域性變數n
。
但是,函式外部無法讀取函式內部宣告的變數。
function f1() {
var n = 999;
}
console.log(n)
// Uncaught ReferenceError: n is not defined(複製程式碼
上面程式碼中,函式f1
內部宣告的變數n
,函式外是無法讀取的。
如果出於種種原因,需要得到函式內的區域性變數。正常情況下,這是辦不到的,只有通過變通方法才能實現。那就是在函式的內部,再定義一個函式。
function f1() {
var n = 999;
function f2() {
console.log(n); // 999
}
}複製程式碼
上面程式碼中,函式f2
就在函式f1
內部,這時f1
內部的所有區域性變數,對f2
都是可見的。但是反過來就不行,f2
內部的區域性變數,對f1
就是不可見的。這就是 JavaScript 語言特有的"鏈式作用域"結構(chain scope),子物件會一級一級地向上尋找所有父物件的變數。所以,父物件的所有變數,對子物件都是可見的,反之則不成立。
既然f2
可以讀取f1
的區域性變數,那麼只要把f2
作為返回值,我們不就可以在f1
外部讀取它的內部變數了嗎!
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999複製程式碼
上面程式碼中,函式f1
的返回值就是函式f2
,由於f2
可以讀取f1
的內部變數,所以就可以在外部獲得f1
的內部變數了。
閉包就是函式f2
,即能夠讀取其他函式內部變數的函式。由於在 JavaScript 語言中,只有函式內部的子函式才能讀取內部變數,因此可以把閉包簡單理解成“定義在一個函式內部的函式”。閉包最大的特點,就是它可以“記住”誕生的環境,比如f2
記住了它誕生的環境f1
,所以從f2
可以得到f1
的內部變數。在本質上,閉包就是將函式內部和函式外部連線起來的一座橋樑。
閉包的最大用處有兩個,一個是可以讀取函式內部的變數,另一個就是讓這些變數始終保持在記憶體中,即閉包可以使得它誕生環境一直存在。請看下面的例子,閉包使得內部變數記住上一次呼叫時的運算結果。
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
複製程式碼
上面程式碼中,start
是函式createIncrementor
的內部變數。通過閉包,start
的狀態被保留了,每一次呼叫都是在上一次呼叫的基礎上進行計算。從中可以看到,閉包inc
使得函式createIncrementor
的內部環境,一直存在。所以,閉包可以看作是函式內部作用域的一個介面。
為什麼會這樣呢?原因就在於inc
始終在記憶體中,而inc
的存在依賴於createIncrementor
,因此也始終在記憶體中,不會在呼叫結束後,被垃圾回收機制回收。
閉包的另一個用處,是封裝物件的私有屬性和私有方法。
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('張三');
p1.setAge(25);
p1.getAge() // 25複製程式碼
上面程式碼中,函式Person
的內部變數_age
,通過閉包getAge
和setAge
,變成了返回物件p1
的私有變數。
注意,外層函式每次執行,都會生成一個新的閉包,而這個閉包又會保留外層函式的內部變數,所以記憶體消耗很大。因此不能濫用閉包,否則會造成網頁的效能問題。
典型的“類似陣列的物件”是函式的arguments
物件,以及大多數 DOM 元素集,還有字串。
// arguments物件
function args() { return arguments }
var arrayLike = args('a', 'b');
arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false
// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false
// 字串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false複製程式碼
上面程式碼包含三個例子,它們都不是陣列(instanceof
運算子返回false
),但是看上去都非常像陣列。
陣列的slice
方法可以將“類似陣列的物件”變成真正的陣列。
var arr = Array.prototype.slice.call(arrayLike);複製程式碼
避免陣列處理方法中的this
陣列的map
和foreach
方法,允許提供一個函式作為引數。這個函式內部不應該使用this
。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
});
}
}
o.f()
// undefined a1
// undefined a2複製程式碼
上面程式碼中,foreach
方法的回撥函式中的this
,其實是指向window
物件,因此取不到o.v
的值。原因跟上一段的多層this
是一樣的,就是內層的this
不指向外部,而指向頂層物件。
解決這個問題的一種方法,就是前面提到的,使用中間變數固定this
。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
var that = this;
this.p.forEach(function (item) {
console.log(that.v+' '+item);
});
}
}
o.f()
// hello a1
// hello a2複製程式碼
另一種方法是將this
當作foreach
方法的第二個引數,固定它的執行環境。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
}, this);
}
}
o.f()
// hello a1
// hello a2複製程式碼
You will soon get something you have always desired.