日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

你不知道的 JavaScript 笔记——作用域和闭包

發布時間:2025/3/21 javascript 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你不知道的 JavaScript 笔记——作用域和闭包 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第一章:作用域是什么

程序中變量存儲在哪里,需要是怎么找到它,這就需要設計一套存儲以及能方便的找到它的規則,這個規則就是作用域

編譯原理

JavaScript 是一門編譯語言,它與傳統編譯語言不同,但編譯步驟又非常相似;它不是提前編譯的,編譯結果也不能在分布式系統中進行移植。

傳統編譯步驟

分詞 / 詞法分析
將由字符組成的字符串分解成有意義的代碼,例如:var a = 2;通常會被分解為var、a、=、;這些詞法單元,判斷a是獨立的詞法單元還是其他詞法單元一部分時,如果調用的是有狀態的解析規則那這個過程就是詞法分析。

解析 / 語法分析
將詞法單元流(數組)轉換成一個由元素逐級嵌套所組成的代表程序語法結構的樹,叫做“抽象語法樹”(Abstract Syntax Tree,AST)

代碼生成
將 AST 轉換為可執行代碼的過程被稱為代碼生成。簡單說就是:將var a = 2;的 AST 轉化為一組機器指令,用來創建一個a的變量(包括分配內存),并將一個值存儲在a中。

理解作用域

var a = 2;在 JavaScript 引擎看來是兩個完全不同的聲明。

  • 遇到var a首先編譯器會向作用域詢問是否有a變量存在這一作用域,如果有就忽略,沒有就創建a。
  • 接下來處理a = 2這個賦值操作,先向當前作用域詢問是否有這個變量,如果有就使用這個變量,并賦值;如果沒有,繼續找。
  • LHS查詢 RHS查詢
    LHS 賦值操作的目標是誰;RHS 誰是賦值操作的源頭(我要查找×××,并把它給我);簡單說如果查找的目的是對變量進行賦值,會使用 LHS 查詢,如果查找的目的是為了獲取變量值,會使用 RHS 查詢。
    var a = foo(a){...} 這里的聲明,a = ...并不會做 LHS 查詢。

    function foo(a){var b = a;return a + b; } var c = foo(2) LHS c = ... a = 2(隱示變量分配) b = ...RHS foo(...) = a a ... ... b

    第二章:詞法作用域

    詞法作用域是在寫代碼時,將變量和塊級作用域寫在哪里來決定的

    詞法階段

    function foo(a){var b = a * 2;function bar(c){console.log(a,b,c); //2,4,12}bar( b * 3); } foo(2);
  • 全局作用域下包含foo
  • foo作用域下包含a、b、bar
  • bar作用域下包含c
    對b、c的 RHS 查詢是在上一級作用域中完成的
  • 欺騙詞法

    eval()接受一個字符串作為參數,把它當做代碼來執行,在哪個作用域下調用它,它的作用域就在哪邊

    function foo(str,a){eval(str);console.log(a,b); //2,3 } var b = 2; foo('var b = 3;',2)

    with可以簡化單調重復的賦值操作

    var obj ={a = 1,b = 2,c = 3 }; //單調重復的賦值 obj.a = 4; obj.b = 5; obj.c = 6; //用with簡化 with(obj){a = 7;b = 8;c = 9; } function foo(obj){with(obj){ //相當于 obj.a = 2,區別 with 創建了全局變量 aa = 2;} } var o1 = {a:3; } var o2 ={b:3; } foo(o1) console.log(o1.a) //2 foo(o2) console.log(o2.a) //undefined console.log(a) //2

    eval()、with實際工作中不推薦使用,會影響性能。

    第三章:函數作用域和塊作用域

    函數中的作用域

    函數內部可以訪問函數外部的變量,函數外部不可以訪問函數內部變量

    函數作用域

    函數的名稱也會污染全局作用域,可以用匿名函數+立即執行函數來實現

    立即執行函數

    有一個bug,上一行表達式必須要分號結尾,省略會報錯

    var a = 2; (function(global){var a = 3;console.log(a); //3console.log(global.a) //2 })(window) var a = 2; (function(){var a = 3;console.log(a); //3 }())

    作用域閉包

    1. 在自己定義的作用域以外的地方執行
    2. 使用回調函數也是閉包

    function foo(){var a = 2function bar(){console.log(a)}return bar } var baz = foo() baz() //2 這里就用了閉包

    bar是在foo里面定義的,所以它是在foo作用域下,但調用它的地方是在foo作用域外,就這構成了閉包。
    在來看一個

    function foo(){var a = 2function bar(){console.log(a)}baz(bar) } function baz(fn){fn() //2,在這里執行了bar,這里構成了閉包 } foo()

    還有

    var fn function foo(){var a =2function bar(){console.log(a)}fn = baz } function baz(){fn() } foo() baz() //2,構成了閉包,這里執行了 bar,構成了閉包。

    在循環中使用閉包
    閉包是函數和聲明該函數的詞法環境的組合。

    回到我們上面說的:在自己定義的作用域以外的地方執行,這里聲明的i是全局變量,使用全局變量不構成閉包。

    for(var i = 1; i <= 3; i++){setTimeout(function(){console.log(i) //打印出 3 個 4,這里沒有閉包},1000) }

    如果要寫成閉包的樣子,必須要在外面用函數包裹一層,并調用它,才能形成閉包

    function xxx(){for(var i = 1; i <= 3; i++){setTimeout(function(){console.log(i) //打印出 3 個 4,這是一個閉包沒有立即執行},1000)} } xxx()

    優化1:這里setTimeout里面的i和立即執行函數的形參構成閉包,阻隔了與for循環的i形成閉包。

    function xxx(){for(var i = 1; i <= 3; i++){(function(i){ //這個形參 i 和外面的 i 不是同一個變量setTimeout(function(){console.log(i) //1,2,3,用立即執行函數,},1000)})(i)} } xxx()

    優化2:用let聲明

    function xxx(){for(let i = 1; i <= 3; i++){setTimeout(function(){console.log(i) //1,2,3,用 let 聲明變量,在循環的過程中不止一次的聲明},1000)} } xxx()

    模塊中閉包
    模塊中閉包需要具備兩個條件:

  • 必須有外部的封閉函數,該函數至少被調用一次
  • 封閉函數至少返回一個內部函數(可以用 return 或 window)
  • 例:

    var foo = function(){function a(){console.log(1)}function b(){console.log(2)}return {a:a,b:b} } var c = foo() c.a() //1

    總結

    以上是生活随笔為你收集整理的你不知道的 JavaScript 笔记——作用域和闭包的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。