【進階2-1期】深刻淺出圖解做用域鏈和閉包

更新:謝謝你們的支持,最近折騰了一個博客官網出來,方便你們系統閱讀,後續會有更多內容和更多優化,猛戳這裏查看前端

------ 如下是正文 ------webpack

本期的主題是做用域閉包,本計劃一共28期,每期重點攻克一個面試重難點,若是你還不瞭解本進階計劃,文末點擊查看所有文章。git

若是以爲本系列不錯,歡迎點贊、評論、轉發,您的支持就是我堅持的最大動力。github


紅寶書(p178)上對於閉包的定義:閉包是指有權訪問另一個函數做用域中的變量的函數 關鍵在於下面兩點:web

  • 是一個函數
  • 能訪問另一個函數做用域中的變量

對於閉包有下面三個特性:面試

  • 一、閉包能夠訪問當前函數之外的變量
function getOuter(){
  var date = '815';
  function getDate(str){
    console.log(str + date);  //訪問外部的date
  }
  return getDate('今天是:'); //"今天是:815"
}
getOuter();
複製代碼
  • 二、即便外部函數已經返回,閉包仍能訪問外部函數定義的變量
function getOuter(){
  var date = '815';
  function getDate(str){
    console.log(str + date);  //訪問外部的date
  }
  return getDate;     //外部函數返回
}
var today = getOuter();
today('今天是:');   //"今天是:815"
today('明天不是:');   //"明天不是:815"
複製代碼
  • 三、閉包能夠更新外部變量的值
function updateCount(){
  var count = 0;
  function getCount(val){
    count = val;
    console.log(count);
  }
  return getCount;     //外部函數返回
}
var count = updateCount();
count(815); //815
count(816); //816
複製代碼

做用域鏈

Javascript中有一個執行上下文(execution context)的概念,它定義了變量或函數有權訪問的其它數據,決定了他們各自的行爲。每一個執行環境都有一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。算法

詳情查看 【進階1-2期】JavaScript深刻之執行上下文棧和變量對象跨域

做用域鏈:當訪問一個變量時,解釋器會首先在當前做用域查找標示符,若是沒有找到,就去父做用域找,直到找到該變量的標示符或者不在父做用域中,這就是做用域鏈。瀏覽器

做用域鏈和原型繼承查找時的區別:若是去查找一個普通對象的屬性,可是在當前對象和其原型中都找不到時,會返回undefined;但查找的屬性在做用域鏈中不存在的話就會拋出ReferenceError安全

做用域鏈的頂端是全局對象,在全局環境中定義的變量就會綁定到全局對象中。

全局環境

無嵌套的函數
// my_script.js
"use strict";

var foo = 1;
var bar = 2;

function myFunc() {
  
  var a = 1;
  var b = 2;
  var foo = 3;
  console.log("inside myFunc");
  
}

console.log("outside");
myFunc();
複製代碼

定義時:當myFunc被定義的時候,myFunc的標識符(identifier)就被加到了全局對象中,這個標識符所引用的是一個函數對象(myFunc function object)。

內部屬性[[scope]]指向當前的做用域對象,也就是函數的標識符被建立的時候,咱們所可以直接訪問的那個做用域對象(即全局對象)。

myFunc所引用的函數對象,其自己不只僅含有函數的代碼,而且還含有指向其被建立的時候的做用域對象。

調用時:當myFunc函數被調用的時候,一個新的做用域對象被建立了。新的做用域對象中包含myFunc函數所定義的本地變量,以及其參數(arguments)。這個新的做用域對象的父做用域對象就是在運行myFunc時能直接訪問的那個做用域對象(即全局對象)。

有嵌套的函數

當函數返回沒有被引用的時候,就會被垃圾回收器回收。可是對於閉包,即便外部函數返回了,函數對象仍會引用它被建立時的做用域對象。

"use strict";
function createCounter(initial) {
  var counter = initial;
  
  function increment(value) {
    counter += value;
  }
  
  function get() {
    return counter;
  }
  
  return {
    increment: increment,
    get: get
  };
}

var myCounter = createCounter(100);
console.log(myCounter.get());   // 返回 100

myCounter.increment(5);
console.log(myCounter.get());   // 返回 105
複製代碼

當調用 createCounter(100) 時,內嵌函數increment和get都有指向createCounter(100) scope的引用。假設createCounter(100)沒有任何返回值,那麼createCounter(100) scope再也不被引用,因而就能夠被垃圾回收。

可是createCounter(100)其實是有返回值的,而且返回值被存儲在了myCounter中,因此對象之間的引用關係以下圖:

即便createCounter(100)已經返回,可是其做用域仍在,而且只能被內聯函數訪問。能夠經過調用myCounter.increment() 或 myCounter.get()來直接訪問createCounter(100)的做用域。

當myCounter.increment() 或 myCounter.get()被調用時,新的做用域對象會被建立,而且該做用域對象的父做用域對象會是當前能夠直接訪問的做用域對象。

調用get()時,當執行到return counter時,在get()所在的做用域並無找到對應的標示符,就會沿着做用域鏈往上找,直到找到變量counter,而後返回該變量。

單獨調用increment(5)時,參數value保存在當前的做用域對象。當函數要訪問counter時,沒有找到,因而沿着做用域鏈向上查找,在createCounter(100)的做用域找到了對應的標示符,increment()就會修改counter的值。除此以外,沒有其餘方式來修改這個變量。閉包的強大也在於此,可以存貯私有數據。

建立兩個函數:myCounter1myCounter2

//my_script.js
"use strict";
function createCounter(initial) {
  /* ... see the code from previous example ... */
}

//-- create counter objects
var myCounter1 = createCounter(100);
var myCounter2 = createCounter(200);
複製代碼

關係圖以下

myCounter1.increment和myCounter2.increment的函數對象擁有着同樣的代碼以及同樣的屬性值(name,length等等),可是它們的[[scope]]指向的是不同的做用域對象。

參考

從做用域鏈談閉包

進階系列目錄

  • 【進階1期】 調用堆棧
  • 【進階2期】 做用域閉包
  • 【進階3期】 this全面解析
  • 【進階4期】 深淺拷貝原理
  • 【進階5期】 原型Prototype
  • 【進階6期】 高階函數
  • 【進階7期】 事件機制
  • 【進階8期】 Event Loop原理
  • 【進階9期】 Promise原理
  • 【進階10期】Async/Await原理
  • 【進階11期】防抖/節流原理
  • 【進階12期】模塊化詳解
  • 【進階13期】ES6重難點
  • 【進階14期】計算機網絡概述
  • 【進階15期】瀏覽器渲染原理
  • 【進階16期】webpack配置
  • 【進階17期】webpack原理
  • 【進階18期】前端監控
  • 【進階19期】跨域和安全
  • 【進階20期】性能優化
  • 【進階21期】VirtualDom原理
  • 【進階22期】Diff算法
  • 【進階23期】MVVM雙向綁定
  • 【進階24期】Vuex原理
  • 【進階25期】Redux原理
  • 【進階26期】路由原理
  • 【進階27期】VueRouter源碼解析
  • 【進階28期】ReactRouter源碼解析

交流

進階系列文章彙總:github.com/yygmind/blo…,內有優質前端資料,歡迎領取,以爲不錯點個star。

我是木易楊,網易高級前端工程師,跟着我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高級前端的世界,在進階的路上,共勉!

相關文章
相關標籤/搜索