不知道的 JavaScript 之作用域、詞法作用域、閉包

1. 編譯

  1. JS 任何代碼在執(zhí)行前都要編譯,編譯通常發(fā)生在執(zhí)行前幾微秒,盡管一般將 js 歸為“解釋執(zhí)行”的語言,但實際上它是一門編譯語言。
  2. 變量的賦值操作:
  • 編譯器在當前作用域中聲明一個變量(如果之前沒有聲明過);
  • 運行時引擎會在作用域中查找這個變量,如果能找到就對它賦值。

2. 作用域

作用域是根據(jù)名稱查找變量的一套規(guī)則,如果查找的目的是對變量進行賦值,會用 LHS 查詢,如果目的是取變量的值,會使用 RHS 查詢。


欺騙作用域

如果詞法作用域完全由寫代碼時函數(shù)所聲明的位置來定義,那么怎樣才能在運行時來“修改”(欺騙)詞法作用域呢?
欺騙詞法會導致性能下降,??

  1. eval → eval 如果接受了含有一個或多個聲明的代碼,會修改其所在的詞法作用域。
  2. with → with 聲明根據(jù)傳遞給它的對象憑空創(chuàng)造了一個全新的詞法作用域。
    with 可以將一個沒有或有多個屬性的對象處理為一個完全隔離的詞法作用域,因此這個對象的屬性也會被處理為定義在這個作用域中的詞法標識符。

JS 引擎在編譯階段會進行數(shù)項的性能優(yōu)化,其中有些優(yōu)化依賴于能夠根據(jù)代碼的詞法進行靜態(tài)分析,并預先確定所有變量和函數(shù)的定義位置,才能在執(zhí)行過程中快速找到標識符。??副作用:JS 引擎無法在編譯時對作用域查找進行優(yōu)化,引擎只能謹慎地認為這樣的優(yōu)化是無效的,不優(yōu)化會導致代碼變慢。

3. 塊作用域

with:從對象中創(chuàng)建出的作用域僅在 with 聲明中而非外部作用域中有效。
try/catch:catch 分局會創(chuàng)建一個塊作用域,其中聲明的變量僅在 catch 內(nèi)部有效。

4. 函數(shù)優(yōu)先提升

5. 閉包

當函數(shù)可以記住并訪問所在的詞法作用域時,就產(chǎn)生了閉包,即使函數(shù)是在當前詞法作用域之外執(zhí)行的。
只要使用了回調(diào)函數(shù),實際上就是在使用閉包。

6. 現(xiàn)代的模塊機制:將模塊定義封裝為友好的 API

const MyModules = (function Manager() {
    const modules = {};

    function define(name, deps, impl) {
        for (let i = 0; i < deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply(impl, deps);
    }

    function get(name) {
        return modules[name];
    }

    return {
        define,
        get
    };
})();

MyModules.define('bar', [], function() {
    function hello(who) {
        return "Let me introduce: " + who;
    }

    return { hello };
});

MyModules.define('foo', ['bar'], function(bar) {
    const hungry = 'hippo';

    function awesome() {
        console.log(bar.hello(hungry).toUpperCase());
    }

    return { awesome };
});

let bar = MyModules.get('bar');
let foo = MyModules.get('foo');

console.log(bar.hello('hippo'));  // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO

這里的 MyModules 是自執(zhí)行函數(shù)(IIFE),調(diào)用 define 方法時,將模塊的實現(xiàn)匿名函數(shù) impl 方法賦值給 modules[name] → modules[name] = impl.apply(impl, deps)。

  for (let i = 0; i < deps.length; i++) {
            deps[i] = modules[deps[i]];
  }

當有依賴時,比如 'foo’ 模塊依賴 'bar' 模塊的 'foo' 模塊的實現(xiàn),deps[i] = modules[deps[i]]

/* 這里匿名函數(shù)里的 bar  已經(jīng)是 bar 模塊的實現(xiàn)(impl)了*/
MyModules.define('foo', ['bar'], function(bar) {
    const hungry = 'hippo';

    function awesome() {
        console.log(bar.hello(hungry).toUpperCase());
    }

    return { awesome };
});

得益于閉包的機制,modules 對象可以被外部訪問:

 function get(name) {
        return modules[name];
 }

因為有外部的引用 let bar = MyModules.get('bar'),所以當 foo 要依賴 bar 時, modules['bar'] 存在,沒有被垃圾回收。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容