JavaScript札記

dhonor發表於2018-12-21

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,通過閉包getAgesetAge,變成了返回物件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

陣列的mapforeach方法,允許提供一個函式作為引數。這個函式內部不應該使用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.

相關文章