你所不知道的JavaScript 二

IDeU-TYH發表於2020-11-20

函式作用域:

函式作用域可以解決同名識別符號之間的衝突,兩個識別符號可以具有同樣的名字,但是用途不一樣

// 函式作用域程式碼理解:
function dosomething(a){
    function doSomethingElse(a){
        return a - 1
    }
    var b;
    b = a + doSomethingElse(a * 2);
    console.log(b * 3)
}

dosomething(2) // 15
function foo(){
    function bar(a){
        i = 3,
        console.log(i+a) //這段程式碼修改了for所屬作用域的i
    }

    for(var i=0;i<10;i++){
        bar(i*2) //無限迴圈
    }
}
foo()

bar()內部的賦值表示式i意外覆蓋了宣告在for迴圈中的i',導致出現無限迴圈,
解決方法:可以在內部宣告一個不同的變數,也可以使用let在
內部建立一個獨立於其他作用域的塊級作用域

變數衝突規避解決:
一.將所有的屬性儲存於一個物件當中,這個物件叫做庫的名稱空間,所有需要暴露給外界的功能都會成為這個物件(名稱空間)的屬性,而不是將自己的識別符號暴露在詞法作用域中

二.模組管理:在眾多的模組管理工具中挑選一個使用,使用這些工具,任何庫都無法將識別符號加入到全域性作用域中,只是通過依賴管理器的機制將庫的識別符號顯示匯入在另一個特定的作用域中

函式宣告和函式表示式的區別:
function關鍵字出現在宣告中的位置,如果function是宣告中的第一詞,那就是函式宣告,反之,則是函式表示式

匿名函式和立即執行表示式:
匿名函式的缺點:
1.在棧追蹤沒有有意義的函式名,使得除錯困難
2.沒有函式名的描述優勢,降低了程式碼的可讀性/可理解性

怎麼解決:使用行內函式表示式

setTimeout(function timeoutHandle(){
    console.log('我有名字了')
})  //function(){}

立即執行函式IIFE
(immediately Invoked Function Expression)

//兩種寫法
(function foo(){
    console.log("哈哈")
}())

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

console.log(a) // 2
//把window物件的引用傳遞過去,但將引數命名為global,因此在程式碼風
格上對全域性物件的引用變得比引用一個沒有“全域性”字樣更加清晰

UMD模式(Universal Module Definition)

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

// 倒置程式碼的執行順序,將需要執行的函式放在第二位,在IIFE執
行完了之後當作引數傳遞過去

塊級作用域
:獨立於其他作用域的作用域,且互不干擾
對於閉包的記憶體機制,它可以清晰的告訴引擎有沒有必要繼續儲存閉包,呼叫後垃圾回收,這就是塊級作用域的魅力,反觀函式作用域

let 和const
let和const可以將變數繫結到所在的任意作用域中(通常是{}中),形成塊級作用域,let用來定義變數,const用來定義常量

//使用let變數,在內部建立了塊級作用域,使得在for迴圈裡面呼叫函式bar
不再將i為函式內部固定值3
function foo(){
    function bar(a){
        let i = 3;
        console.log(i+a) 
    }

    for(let i=0;i<10;i++){
        bar(i*2)
    }
}
foo()

補充:with,try/catch所建立出的作用域也是塊級作用域

迴圈


for(var i=0;i<10;i++){
    console.log("haha")
}
console.log(i)  // 十次hahh,和最後迴圈的i = 10,for迴圈內部的
變數i洩露到全域性變數,可以使用let內部建立塊級作用域,不再洩露變數

提升:
重複的var宣告會被忽略掉,後面的宣告將會覆蓋前面宣告

foo()

if(a){
  function foo(){console.log("a");}
}
else{
  function foo(){ console.log("b")}
} 
//變數:ReferenceError
// 函式:TypeError 

應該避免在塊內部宣告函式,也許將來JavaScript的版本會發生改變

閉包
當函式可以記住並訪問所在的詞法作用域時,就形成了閉包,即使函式是在當前詞法作用域外執行

function foo(){
    var a = 2;
    function bar(){
        console.log(a)
    }
    bar()
}

foo()

//bar()具有涵蓋foo()作用域的閉包(實際上它能訪問所有的作用域,
比如全域性作用域),也可以理解為bar()被巢狀在foo()中,bar()被封閉
在foo()的作用域中

重點來了:

function foo(){
    var a = 2;
    function bar(){
        console.log(a)
    }
    return bar;
}

var baz = foo();
baz()

我們知道,函式在執行完成之後,內部的垃圾回收機制會摧毀內部作用域,用來釋放不再使用的記憶體空間,而閉包可以阻止這件事情的發生,函式內部的作用域依然存在,閉包使得函式繼續訪問定義時的詞法作用域

bar()依然有著這個引用,而這個引用就是閉包

// 請繼續理解程式碼:
function foo(){
    var a = 2;
    function baz(){
        console.log(a)
    }
    bar(baz)
}

function bar(fn){
    fn()
}

var baa = foo()
foo()
baa()
// 呼叫外部bar()將內部baz()形成了閉包

引擎內建的一些閉包:

function wait(message){
    setTimeout(function timer(){
        console.log(message)
    },1000)
}

wait("hahhah")

setTimeout()是一個內部函式,當我們這個函式執行完成之後,它的內部作用域也不會消失,因為它也是閉包,它是引擎內部的閉包

var a = "hha";
(function IIFE(){
    console.log(a)
})();

//雖然這段程式碼可以正常工作,但是嚴格意義來講它並不是閉包,因為函式並
不是在本身的詞法作用域下執行的,它是在定義時的詞法作用域執行的

迴圈與閉包:

for(var i=1 ; i<=5;i++){
    setTimeout(function timer(){
        console.log(i);
    },1000)
} 
// 5個6,
why,變數往內部外洩問題,let解決

延遲函式的回撥會在迴圈結束時才執行,迴圈迭代後,最後i=6,i*1000毫秒,線性播放

for(var i=1 ; i<=5;i++){
    (function(){
        var j = i;
        setTimeout(function timer(){
            console.log(j);
        },j*1000)
    })()
}

優化:
for(var i=1 ; i<=5;i++){
    (function(j){
        setTimeout(function timer(){
            console.log(j);
        },j*1000)
    })(i)
}

相關文章