湯姆大叔的深入理解JavaScript讀後感一(1——16節)

今天要分享的內容是tom大叔的JavaScript系列的讀書觀後感

第一部分,編寫高質量JavaScript代碼的基本要點

內容不多,但是作爲從頭規範開始,值得遵循,現在看來,才更能明白裏面的道理。
總結一下:

1. 避免全局變量,爲啥?自己去看,以及隱式全局變量的副作用,那怎麼辦?命名空間
2. 推薦單一的單var形式,如  var a = 1,b = 2,myobject = {};形式
3. for循環,預存緩存變量,for-in遍歷對象用hasOwnProperty,去掉原型鏈的屬性
4. 避免使用eval,記住該咒語“eval()是魔鬼”,給setInterval(), setTimeout()和Function()構造函數傳遞字符串,大部分情況下,與使用eval()是類似的,因此要避免                     
5. 編碼規範,縮進,空格,花括號,分號
6. 命名函數,啥時候大寫,啥時候小寫,推薦駝峯命名
7. 關於註解,通常,當你深入研究一個問題,你會很清楚的知道這個代碼是幹嘛用的,但是,當你一週之後再回來看的時候,想必也要耗掉不少腦細胞去搞明白到底怎麼工作的。就是保持註釋的及時更新,因爲過時的註釋比沒有註釋更加的誤導人。

第二部分,揭祕命名函數表達式

總結如下:

1. 什麼是申明函數?
2. 什麼是表達式函數?
3. 什麼是函數語句?
4. 什麼是命名函數?
5. 主要是區別的地方,很細微,然後又很多瀏覽器的怪異行爲,坑比較多
6. 還有一種函數表達式不太常見,就是被括號括住的(function foo(){}),他是表達式的原因是因爲括號 ()是一個分組操作符,它的內部只能包含表達式
7. 後面的部分,如果第一次看,肯定看不懂,是啥意思,建議先忽略掉

第三部分,全面解析Module模式

總結如下:

1. 正常的module模式,缺點就是,每次都必須new,每個實例都是單獨的

“`
var Calculator = function (eq) {
//這裏可以聲明私有成員

      var eqCtl = document.getElementById(eq);

      return {
          // 暴露公開的成員
          add: function (x, y) {
              var val = x + y;
              eqCtl.innerHTML = val;
          }
      };
  };

“`
2. 改進過後的,利用函數自執行,可以直接使用

“`
var blogModule = (function () {
var my = {}, privateName = “博客園”;

function privateAddTopic(data) {
    // 這裏是內部處理代碼
}

my.Name = privateName;
my.AddTopic = function (data) {
    privateAddTopic(data);
};

return my;

} ());
“`
3. 改進鬆耦合

“`
var blogModule = (function (my) {

// 添加一些功能   

return my;

} (blogModule || {}));
“`

   4. 克隆

“`
var blogModule = (function (old) {
var my = {},
key;
for (key in old) {
if (old.hasOwnProperty(key)) {
my[key] = old[key];
}
}

   var oldAddPhotoMethod = old.AddPhoto;
   my.AddPhoto = function () {
       // 克隆以後,進行了重寫,當然也可以繼續調用oldAddPhotoMethod
   };

   return my;

} (blogModule));

“`

      5. 子模塊

“`
blogModule.CommentSubModule = (function () {
var my = {};
// …

return my;

} ());
“`

第四部分,立即調用的函數表達式

1. 自動執行
(function () { /* code */ } ()); // 推薦使用這個
2. (function () { /* code */ })(); // 但是這個也是可以用的,括號
3.在一個表達式後面加上括號(),該表達式會立即執行,但是在一個語句後面加上括號(),是完全不一樣的意思,他的只是分組操作符。
4. 閉包的經典例子

“`
錯誤的:
var elems = document.getElementsByTagName(‘a’);

for (var i = 0; i < elems.length; i++) {

   elems[i].addEventListener('click', function (e) {
       e.preventDefault();
       alert('I am link #' + i);
   }, 'false');

}
正確的:會鎖住對應的index的值,雖然i最後爲10,但是之前的值,已經記錄好了
var elems = document.getElementsByTagName(‘a’);

for (var i = 0; i < elems.length; i++) {

(function (lockedInIndex) {

    elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am link #' + lockedInIndex);
    }, 'false');

})(i);

}

“`
5. 不管什麼是自執行,還是立即調用,不用分那麼清楚,只需知道立即執行一次匿名函數就夠了

第五部分,強大的原型和原型鏈

1. 原型關鍵詞,prototype
2. 說白了,原型就是一個對象
3. 通過hasOwnProperty,區分屬性從哪裏來的
4. 默認還有一個原型的鏈條,一直到Object.prototype爲止
5. 所有的對象都有"[[prototype]]"屬性(通過__proto__訪問),該屬性對應對象的原型
6. 所有的函數對象都有"prototype"屬性,該屬性的值會被賦值給該函數創建的對象的"__proto__"屬性
7. 所有的原型對象都有"constructor"屬性,該屬性對應創建所有指向該原型的實例的構造函數
8. 函數對象和原型對象通過"prototype"和"constructor"屬性進行相互關聯
9. 最簡單易懂的看下邊

原型鏈介紹

第六七八部分,S.O.L.I.D五大原則之單一職責SRP(忽略)

The Single Responsibility Principle(單一職責SRP)
The Open/Closed Principle(開閉原則OCP)
The Liskov Substitution Principle(里氏替換原則LSP)
The Interface Segregation Principle(接口分離原則ISP)
The Dependency Inversion Principle(依賴反轉原則DIP)

第九部分,根本沒有“JSON對象”這回事!

簡單的說
1. // 這是JSON字符串
   var foo = '{ "prop": "val" }';
2. // 這是對象字面量
   var bar = { "prop": "val" };
3. // 將字符串反序列化成json對象
   var my_obj = JSON.parse( foo );
4. json對象,和字面量對象,明顯的區別,前者必須有引號,後者去掉引號可以
       var bar = { prop: "val" };合法的

第十部分,JavaScript核心(晉級高手必讀篇)

1. 執行上下文棧(Execution Context Stack),有三種類型:global, function和eval。
2. 執行上下文(Execution Context),變量對象(variable object),this指針(this value),作用域鏈(scope chain)
函數表達式[function expression](而不是函數聲明[function declarations,區別請參考本系列第2章])是不包含在VO[variable object]裏面的
3. 變量對象(variable object) 是與執行上下文相關的 數據作用域(scope of data) 。
   它是與上下文關聯的特殊對象,用於存儲被定義在上下文中的 變量(variables) 和 函數聲明(function declarations) 。
4. 當函數被調用者激活,這個特殊的活動對象(activation object) 就被創建了
5. 作用域鏈的原理和原型鏈很類似,如果這個變量在自己的作用域中沒有,那麼它會尋找父級的,直到最頂層。
6. 這表示,在我們去搜尋__parent__之前,首先會去__proto__的鏈接中。
7. 閉包是一系列代碼塊(在ECMAScript中是函數),並且靜態保存所有父級的作用域。通過這些保存的作用域來搜尋到函數中的自由變量。
8. 在ECMAScript中,是不可以給this賦值的,因爲,還是那句話,this不是變量。
9. this是執行上下文環境的一個屬性,而不是某個變量對象的屬性

第十一部分,執行上下文(Execution Contexts)

可以把調用上下文作爲第二個參數傳遞給eval。那麼,如果這個上下文存在,就有可能影響“私有”(有人喜歡這樣叫它)變量。

第十二部分,變量對象(Variable Object)

    if (true) {
        var a = 1;
        } else {
        var b = 2;
        }
    alert(a); // 1
    alert(b); // undefined,不是b沒有聲明,而是b的值是undefined

變量相對於簡單屬性來說,變量有一個特性(attribute):{DontDelete},這個特性的含義就是不能用delete操作符直接刪除變量屬性。

a = 10;
alert(window.a); // 10

alert(delete a); // true

alert(window.a); // undefined

var b = 20;
alert(window.b); // 20

alert(delete b); // false

alert(window.b); // still 20

但是這個規則在有個上下文裏不起走樣,那就是eval上下文,變量沒有{DontDelete}特性。

第十三部分,This? Yes,this!

1,一個函數上下文中確定this值的通用規則如下:
2. 在一個函數上下文中,this由調用者提供,由調用函數的方式來決定。如果調用括號()的左邊是引用類型的值,this將設爲引用類型值的base對象(base object),在其他情況下(與引用類型不同的任何其它屬性),這個值爲null。不過,實際不存在this的值爲null的情況,因爲當this的值爲null的時候,其值會被隱式轉換爲全局對象。
3. 我們可以很明確的告訴你,爲什麼用表達式的不同形式激活同一個函數會不同的this值,答案在於引用類型(type Reference)不同的中間值。
4. 標識符是變量名,函數名,函數參數名和全局對象中未識別的屬性名
5. (function () {
     alert(this); // null => global
   })();
6. 函數調用中手動設置this,apply,call
7. 作爲構造器調用的函數中的this,都將this的值設置爲新創建的對象。
8. 引用類型和this爲null,默認爲golbal

第十四部分,作用域鏈(Scope Chain)

1. 函數上下文的作用域鏈在函數調用時創建的,包含活動對象和這個函數內部的[[scope]]屬性。
2. 注意這重要的一點--[[scope]]在函數創建時被存儲--靜態(不變的),永遠永遠,直至函數銷燬 
3. 閉包理解
var x = 10;

function foo() {
  alert(x);
}

(function () {
  var x = 20;
  foo(); // 10, but not 20
})();
[[Scope]]包括在函數內創建的詞法作用域(父變量對象)。當函數進一步激活時,在變量對象的這個詞法鏈(靜態的存儲於創建時)中,來自較高作用域的變量將被搜尋。
此外,這個例子也清晰的表明,一個函數(這個例子中爲從函數“foo”返回的匿名函數)的[[scope]]持續存在,即使是在函數創建的作用域已經完成之後。
4. 通過函構造函數創建的函數的[[scope]]屬性總是唯一的全局對象
5. 源於ECMAScript 的原型特性。如果一個屬性在對象中沒有直接找到,查詢將在原型鏈中繼續。即常說的二維鏈查找。(1)作用域鏈環節;(2)每個作用域鏈--深入到原型鏈環節
6. 全局和eval上下文中的作用域鏈,全局上下文的作用域鏈僅包含全局對象
7. 在代碼執行階段有兩個聲明能修改作用域鏈。這就是with聲明和catch語句。它們添加到作用域鏈的最前端,對象須在這些聲明中出現的標識符中查找。

第十五部分,函數(Functions)

1。 只有這2個位置可以聲明函數,也就是說:不可能在表達式位置或一個代碼塊中定義它。
2. // 函數可以在如下地方聲明:
   // 1) 直接在全局上下文中
   function globalFD() {
     // 2) 或者在一個函數的函數體內
     function innerFD() {}
   }
3. 相當一部分問題出現了,我們爲什麼需要函數表達式?答案很明顯——在表達式中使用它們,”不會污染”變量對象。最簡單的例子是將一個函數作爲參數傳遞給其它函數。
4. 這種模式中,初始化的FE的名稱通常被忽略:
(function () {
   // 初始化作用域 
})();
5. ”爲何在函數創建後的立即調用中必須用圓括號來包圍它?”,答案就是:表達式句子的限制就是這樣的。
6. function () {
     ...
   }();

   // 即便有名稱

   function foo() {
     ...
   }();
如果在全局代碼裏定義(也就是程序級別),解釋器會將它看做是函數聲明,因爲他是以function關鍵字開頭,第一個例子,我們會得到SyntaxError錯誤,是因爲函數聲明沒有名字(我們前面提到了函數聲明必須有名字)。

第二個例子,我們有一個名稱爲foo的一個函數聲明正常創建,但是我們依然得到了一個語法錯誤——沒有任何表達式的分組操作符錯誤。在函數聲明後面他確實是一個分組操作符,而不是一個函數調用所使用的圓括號。

第十六部分,閉包(Closures)

var z = 10;

function foo() {
  alert(z);
}

foo(); // 10 – 使用靜態和動態作用域的時候

(function () {

  var z = 20;
  foo(); // 10 – 使用靜態作用域, 20 – 使用動態作用域

})();

// 將foo作爲參數的時候是一樣的
(function (funArg) {

  var z = 30;
  funArg(); // 10 – 靜態作用域, 30 – 動態作用域

})(foo);
上述描述的就是兩類funarg問題 —— 取決於是否將函數以返回值返回(第一類問題)以及是否將函數當函數參數使用(第二類問題)。

爲了解決上述問題,就引入了 閉包的概念。

這裏說明一下,開發人員經常錯誤將閉包簡化理解成從父上下文中返回內部函數,甚至理解成只有匿名函數才能是閉包。

再說一下,因爲作用域鏈,使得所有的函數都是閉包(與函數類型無關: 匿名函數,FE,NFE,FD都是閉包)。
ECMAScript中,閉包指的是:

從理論角度:所有的函數。因爲它們都在創建的時候就將上層上下文的數據保存起來了。哪怕是簡單的全局變量也是如此,因爲函數中訪問全局變量就相當於是在訪問自由變量,這個時候使用最外層的作用域。
從實踐角度:以下函數纔算是閉包:
即使創建它的上下文已經銷燬,它仍然存在(比如,內部函數從父函數中返回)
在代碼中引用了自由變量

順便提下,函數對象的 apply 和 call方法,在函數式編程中也可以用作應用函數。 apply和call已經在討論“this”的時候介紹過了;這裏,我們將它們看作是應用函數 —— 應用到參數中的函數(在apply中是參數列表,在call中是獨立的參數):

閉包還有另外一個非常重要的應用 —— 延遲調用:

var a = 10;
setTimeout(function () {
  alert(a); // 10, after one second
}, 1000);

還可以創建封裝的作用域來隱藏輔助對象:

var foo = {};

// 初始化
(function (object) {

  var x = 10;

  object.getX = function _getX() {
    return x;
  };

})(foo);

alert(foo.getX()); // 獲得閉包 "x" – 10
發佈了107 篇原創文章 · 獲贊 8 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章