你不知道的javascript(上卷)讀後感(一)

三劍客

編譯,顧名思義,就是源代碼執行前會經歷的過程,分三個步驟,javascript

  • 分詞/詞法分析,將咱們寫的代碼字符串分解成多個詞法單元
  • 解析/語法分析,將詞法單元集合生成抽象語法樹(AST)
  • 代碼生成,抽象語法樹(AST)轉換成可執行代碼的過程

Tip1:js在語法分析和代碼生成階段有對運行性能進行優化,對冗餘元素進行優化java

Tip2:js的編譯過程不是發生在構建以前,而是代碼執行以前閉包

理解做用域,首先知道三劍客,分別是app

  • 引擎:負責整個代碼編譯及執行的過程
  • 編譯器: 負責詞法分析、語法分析、代碼生成
  • 做用域:負責維護與收集全部聲明的標識符,保證當前執行代碼對這些標識符的訪問權限

舉例子,加深印象,對於var a = 2,三劍客如何協同工做,
編譯器進行分詞、語法分析,而後要代碼生成時,遇到 var a,問一下當前做用域集合「你有沒有這個名稱的變量呀?」,做用域若是說有,那麼忽略聲明,繼續編譯,若是說沒有,那麼就要要求做用域收集一下,而且給它個名字a,而後編譯器就生成了代碼,引擎準備來執行了,先問當前做用域集合,「你這裏有a這個變量嗎?」,有引擎就拿來用,沒有就繼續找該變量,要麼找到,就給它附個值2,沒有那就給你報個錯!函數

理解編譯器的相關術語

  • LSH查詢,通俗解釋就是找到所聲明變量,而且對其賦值的行爲
  • RSH查詢,通俗解釋就是查找聲明的變量

做用域嵌套

當一個塊或是函數嵌套在另一個塊或函數時,就會產生做用域嵌套,因而在當前做用域找不到某個變量時,引擎會往外層嵌套做用域繼續查找,直達到最外層做用域(全局做用域)爲止,也就是所謂的做用域鏈啦!性能

詞法做用域

相信有不少人都是搞不懂詞法做用域是什麼?

所謂的詞法做用域,就是定義在詞法階段(詞法分析)的做用域,由你寫代碼時將變量和塊做用域寫在哪裏來決定的。優化

遮蔽效應

做用域查找會在找到第一個匹配的標識符時中止,不會繼續往上層做用域查找,這就會產生遮蔽效應調試

欺騙詞法做用域

  • eval函數,修改詞法做用域
  • with關鍵字,建立詞法做用域

致使性能降低的緣由,前面咱們提過,在解析/語法分析、生成代碼階段,咱們會對代碼進行優化,剔除冗餘元素,可是當使用eval/with時,全部優化變得沒有意義,由於存在不可預見性,不知道修改和建立的詞法做用域是什麼?因此會致使性能降低。code

函數聲明、函數表達式、匿名函數表達式

函數表達式:function爲第一個詞,那麼就是一個函數聲明,不然就是一個函數表達式
匿名函數表達式:沒有函數名,匿名函數在棧追蹤這種不會顯示有意義的函數名,使得調試困難
IIFE: 當即執行函數表達式((function() { ... }())(function(){ ... })()事件

塊級做用域

let爲其聲明的變量隱式地去劫持了所在的塊級做用域,不會在塊級做用域中進行提高【變量提高】
Demo: with關鍵字爲塊級做用域、{...}爲塊級做用域,用完即銷燬
const常量,不可修改!
任何聲明在某個做用域(函數做用域和塊級做用域)的變量,都是屬於這個做用域。
每一個做用域都會進行提高操做。
函數聲明會被提高,函數表達式不會提高,變量聲明提高的過程當中,函數會優先!

閉包

閉包,有權訪問另一個函數的變量標識符的函數,比較常見的一個閉包問題,就是for循環。

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

會發現每一次輸出的都是6,爲啥勒?全部的回調函數回在循環結束後纔會執行(事件循環)。因此每次都是輸出一個6來。

解決辦法:

一、IIFE,每一個迭代儲存i的值

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

二、IIFE,將i入參修改爲j

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

三、建立閉包的塊做用域

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

四、最優

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

另一個閉包經常使用的場景,就是模塊暴露了,在這裏提供一個現代模塊機制的實現方式,你們能夠細細品嚐,

var MyMoudles = (function Manger() {
    var modules = {};
    function define(name, deps, impl) {
        for (var i = 0; i < deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply(impl, deps);
    }
    function get(name) {
        return modules[name];
    }
})()

調用Demo:

MyMoudles.define('bar', [], function() {
    function hello(who) {
        return "Let me introduce: " + who;
    }
    return {
        hello: hello
    }
})
MyMoudles.define('foo', ['bar'], function(bar) {
    var hungry = 'hippo';
    function awesome() {
        console.log(bar.hello(hungry).toUpperCase());
    }
    return {
        awesome: awesome
    }
})
var bar = MyMoudles.get('bar');
var foo = MyMoudles.get('foo');

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

將來模塊機制

ES6提供了全新的模塊機制,基於函數的模塊(如上述現代模塊機制)並非一個能被靜態識別的模式(編譯器沒法識別),它們的API語義只有等到代碼運行時纔會考慮進來,而ES6模塊就是一個能被靜態識別的模式,就是說API在編譯階段就會檢查API成員是否存在。

原文地址

相關文章
相關標籤/搜索