JavaScript 機(jī)制 /

堆棧結(jié)構(gòu)

執(zhí)行上下文(運(yùn)行環(huán)境)

運(yùn)行環(huán)境規(guī)定函數(shù)中代碼執(zhí)行的順序,JavaScript 引擎會以棧的方式來處理他們,在打開瀏覽器,存在JavaScript 的情況下,全局上下文首先入棧,在全局上下文環(huán)境下執(zhí)行其中可執(zhí)行的代碼,在遇到可執(zhí)行的函數(shù)時(shí)該函數(shù)會創(chuàng)建一個(gè)單獨(dú)的執(zhí)行上下文環(huán)境,(函數(shù)只有在被調(diào)用時(shí)才會執(zhí)行新的上下文環(huán)境)該函數(shù)執(zhí)行上下文入棧,運(yùn)行該函數(shù)內(nèi)的代碼,在該函數(shù)內(nèi)代碼執(zhí)行完畢后該函數(shù)上下文從棧中彈出,如果在函數(shù)中存在return能直接終止可執(zhí)行代碼的執(zhí)行,因此直接將當(dāng)前上下文彈出棧,而全局上下文會在瀏覽器關(guān)閉后出棧

預(yù)解析

對象部分
所有變量的聲明,在函數(shù)內(nèi)部第一行代碼開始執(zhí)行的時(shí)候就已經(jīng)完成
執(zhí)行上下文可以分為兩個(gè)周期,創(chuàng)建階段和代碼執(zhí)行階段
在創(chuàng)建階段執(zhí)行上下文會分別創(chuàng)建變量對象、建立作用域鏈,以及確定this的指向
在代碼執(zhí)行階段會完成變量賦值、函數(shù)引用,以及執(zhí)行其他代碼,在執(zhí)行完畢后彈出棧,然后看是否會被其它函數(shù)引用,否則會等待被回收

變量對象的創(chuàng)建

  1. 創(chuàng)建arguments對象,檢查當(dāng)前上下文中的參數(shù),建立該對象下的屬性與屬性值
  2. 檢查當(dāng)前上下文的函數(shù)聲明(使用function關(guān)鍵字聲明的函數(shù)),在變量對象中以函數(shù)名建立一個(gè)屬性,屬性值為指向該函數(shù)所在內(nèi)存地址的引用,如果函數(shù)名的屬性已經(jīng)存在,那么該屬性將會被新的引用覆蓋
  3. 檢查當(dāng)前上下文中的變量聲明(var),每找到一個(gè)變量聲明,就在變量對象中以變量名建立一個(gè)屬性,屬性值為undefined,如果該變量名的屬性已經(jīng)存在,為了防止同名的函數(shù)被修改為undefined,會直接跳過,原屬性值不會被修改

這里需要注意幾個(gè)點(diǎn),首先是變量對象的創(chuàng)建是在每一個(gè)函數(shù)的執(zhí)行上下文環(huán)境中,也就是說全局的上下文執(zhí)行環(huán)境中的變量和單獨(dú)函數(shù)中的變量是在兩個(gè)不同的執(zhí)行上下文環(huán)境,不會相互影響,在局部函數(shù)中聲明變量只會存在該函數(shù)的執(zhí)行上下文環(huán)境中

第二是使用function聲明的優(yōu)先級會大于使用var聲明的變量的優(yōu)先級,預(yù)解析時(shí)會優(yōu)先讀取function聲明,并且該值會覆蓋之前使用var/function聲明的同名變量/函數(shù),然后再讀取var聲明的匿名函數(shù)/變量,但是如果存在同名的function聲明,則會直接跳過,不會修改function的聲明

console.log(a());//this's b
function a() {
    return "this's a"
}
function a() {
    return "this's b"
}

還有一點(diǎn)需要注意的是上面的規(guī)則只是變量對象的創(chuàng)建過程的規(guī)則,在執(zhí)行過程中是不會受上面規(guī)則的影響的,只會依照聲明的先后順序來決定之后該變量的值

console.log(foo);//? foo() {
                     //console.log("this's foo");
                 //}
function foo() {
    console.log("this's foo");
}
var foo="foo";
console.log(foo);//foo
變量對象的創(chuàng)建過程.png

而全局變量對象,它的變量對象就是window對象,this的指向也是window對象

垃圾回收機(jī)制

JavaScript的垃圾回收機(jī)制中注意一點(diǎn),局部變量會在上下文彈出棧時(shí)被垃圾回收機(jī)制回收,而全局變量則不確定什么時(shí)候會被回收,所以我們要謹(jǐn)慎使用全局變量,在確定全局變量不再被引用時(shí)手動將其賦值為null,讓其進(jìn)入垃圾回收,這樣可以節(jié)省內(nèi)存

作用域鏈

作用域鏈,由當(dāng)前環(huán)境與上層環(huán)境的一系列變量對象組成,它保證了當(dāng)前執(zhí)行環(huán)境對符合訪問權(quán)限的變量和函數(shù)的有序訪問,JavaScript的作用域是由變量創(chuàng)建的位置決定的,而不是變量使用的位置

作用域鏈保證了函數(shù)可以訪問的數(shù)據(jù)有哪些,注意這個(gè)作用域鏈由函數(shù)定義的位置決定,而不是由函數(shù)使用時(shí)的位置決定,閉包也是一樣,閉包在被創(chuàng)建后在即使在其它位置使用但其作用域鏈還是原先創(chuàng)建閉包時(shí)所形成的作用域鏈

 var fn = null;
    function foo() {
        var a = 2;
        function innnerFoo() {
            console.log(c); // 在這里,試圖訪問函數(shù)bar中的c變量,會拋出錯(cuò)誤
            console.log(a);
        }
        fn = innnerFoo; // 將 innnerFoo的引用,賦值給全局變量中的fn
    }

    function bar() {
        var c = 100;
        fn(); // 此處的保留的innerFoo的引用
    }

    foo();//先執(zhí)行foo,為fn賦值,也就是將fn由變量對象轉(zhuǎn)化為活動對象
    bar();
 var name = "stephenchan";
    function callMePlz() {
        console.log(name);
    }

    function myFunc() {
        var name = "endlesscode";
        callMePlz();
    }
    myFunc();//stephenchan

在記住上面的前提下,我們再深入談一下作用域鏈,在JavaScript中,萬物皆對象,雖然在某些時(shí)候這個(gè)說話并不準(zhǔn)確,但說函數(shù)是一個(gè)對象這是沒有問題的,函數(shù)對象和其他對象一樣,擁有可以通過代碼訪問的屬性和一系列JavaScript引擎自身的內(nèi)部屬性,其中一個(gè)內(nèi)部屬性是[scope],這個(gè)屬性是JavaScript提供的,該屬性包含了函數(shù)被創(chuàng)建的作用中對象的集合,這個(gè)集合被稱為函數(shù)的作用域鏈,它決定了哪些數(shù)據(jù)能夠被函數(shù)訪問

當(dāng)一個(gè)函數(shù)被創(chuàng)建后,它的作用域鏈會被創(chuàng)建該函數(shù)的作用域中可訪問的數(shù)據(jù)對象填充,我們舉一個(gè)栗子

function add(num1,num2){
  var sum=num1+num2;
  return sum;
}

在函數(shù)add創(chuàng)建時(shí),因?yàn)樗且粋€(gè)全局對象中的函數(shù),按照我們上面的說法,它的作用域鏈會被全局對象中可訪問的對象填充,這些可訪問的對象包括window等JavaScript本身定義的屬性/方法,也包括我們自己在代碼中定義的全局對象/方法(下圖只是一部分全局變量)

作用域鏈-全局對象填充add.jpg

當(dāng)我們在函數(shù)add執(zhí)行時(shí)

var total=add(5,10)

然后在執(zhí)行函數(shù)時(shí),在遇到每一變量時(shí)都會給變量一個(gè)標(biāo)識符,用于識別該變量,但為了便于理解,這里我們就跳過這個(gè)概念,會讀取add函數(shù)的所有局部變量,命名參數(shù),參數(shù)集合以及this,將它們共同組成一個(gè)新的對象,叫"活動對象(activation object)",該對象將被推入到作用域鏈的前端,當(dāng)上下文環(huán)境被銷毀,活動對象也就會隨之銷毀

執(zhí)行函數(shù)add時(shí)作用域鏈如下

執(zhí)行函數(shù)add的作用域鏈.jpg

在函數(shù)執(zhí)行過程中,每次遇到一個(gè)變量,都會從作用域鏈頭部也就是活動對象開始搜索,如果在活動對象內(nèi)存在同名變量,那么就使用該變量,如果沒有找到那么會繼續(xù)向作用域鏈下一個(gè)對象中查找,如果都沒有,則認(rèn)為該標(biāo)識符未定義

所以,這也解釋了JavaScript中函數(shù)會有塊的作用域,外界訪問不到,而子作用域中可以訪問父作用域中定義的變量,但是父作用域不能訪問子作用域中的變量,因?yàn)樽宰饔糜蛟谧饔糜蜴溕洗嬖诟缸饔糜虻膶ο螅诓檎易兞繒r(shí)如果在自作用域活動對象內(nèi)查找不到會向下去找父作用域?qū)ο髢?nèi)是否存在同名的變量,但在父作用域內(nèi)的作用域鏈上是不存在子函數(shù)的作用域鏈的,而全局對象的作用域鏈上時(shí)不存在我們自己定義的函數(shù)的對象的,所以我們在函數(shù)內(nèi)定義的變量在外界是無法讀取的

查找順序.png

參考文檔:理解 JavaScript 作用域和作用域鏈

事件循環(huán)機(jī)制(任務(wù)隊(duì)列)

JavaScript是單線程的,但是可以同時(shí)有多個(gè)任務(wù)隊(duì)列

任務(wù)隊(duì)列又分為macro-task(宏任務(wù))與micro-task(微任務(wù)),在最新標(biāo)準(zhǔn)中,它們被分別稱為task與jobs。

macro-task大概包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task大概包括: process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)

setTimeout/Promise等我們稱之為任務(wù)源。而進(jìn)入任務(wù)隊(duì)列的是他們指定的具體執(zhí)行任務(wù)。

來自不同任務(wù)源的任務(wù)會進(jìn)入到不同的任務(wù)隊(duì)列。同源任務(wù)則會進(jìn)入到同一個(gè)任務(wù)隊(duì)列,其中setTimeout與setInterval是同源的

事件循環(huán)的順序,決定了JavaScript代碼的執(zhí)行順序。它從script(整體代碼)開始第一次循環(huán)。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。直到調(diào)用棧清空(只剩全局),然后執(zhí)行所有的micro-task。當(dāng)所有可執(zhí)行的micro-task執(zhí)行完畢之后。循環(huán)再次從macro-task開始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,然后再執(zhí)行所有的micro-task,這樣一直循環(huán)下去。

其中每一個(gè)任務(wù)的執(zhí)行,無論是macro-task還是micro-task,都是借助函數(shù)調(diào)用棧來完成。

setTimeout作為任務(wù)分發(fā)器,將任務(wù)分發(fā)到對應(yīng)的宏任務(wù)隊(duì)列中。

Promise的then方法會將任務(wù)分發(fā)到對應(yīng)的微任務(wù)隊(duì)列中,但是它構(gòu)造函數(shù)中的方法會直接執(zhí)行

事件循環(huán)機(jī)制-任務(wù)隊(duì)列.png

面向?qū)ο?補(bǔ)充)

in操作符的應(yīng)用場景之一:in可以用來判斷一個(gè)對象是否擁有某一個(gè)屬性/方法,無論該屬性/方法存在與實(shí)例對象還是原型對象,我們可以用這種特性來判斷當(dāng)前頁面是否是在移動端打開

isMobile='ontouchsart in document'

關(guān)于new關(guān)鍵字到底在創(chuàng)建構(gòu)造函數(shù)時(shí)做了什么,之前自己也有過總結(jié),但是現(xiàn)在看到了大概的代碼實(shí)現(xiàn)

var Person=function (name,age) {
    this.name=name;
    this.age=age;
    this.getName=function () {
    return this.name;
    }
}

function New(fun){//接收構(gòu)造函數(shù)為參數(shù)

    var res={};//聲明一個(gè)對象,該對象為最終的返回實(shí)例

    if(fun.prototype !== null){
        res.__proto__=fun.prototype;
        //將聲明對象的原型指向構(gòu)造函數(shù)的原型
    }

    var ret=fun.apply(res,Array.prototype.slice.call(arguments,1));
    //將構(gòu)造函數(shù)內(nèi)部的this指向?qū)嵗龑ο髍es

    if((typeof ret==="object" || typeof ret==="function")&&ret!==null){
        return ret;
    }
    //如果我們在構(gòu)造函數(shù)中指定了返回對象,那么new的執(zhí)行結(jié)果就是返回值

    return res;
    //如果沒有明確返回對象,則默認(rèn)返回res,也就是實(shí)例對象
}

var p=New(Person,'tom',20);
this

在面試題中經(jīng)??吹疥P(guān)于this的問題,今天恰好看到一篇關(guān)于this的文章,總結(jié)一下,用于日后裝逼

在js中有三種函數(shù)的調(diào)用模式

  1. function(p1,p2){ console.llog(this)};
  2. iffn.str(p1,p2);
  3. fn.call(context,p1,p2);
    之前我們常用的是第一種和第二種模式,但實(shí)際上只有我們要樹立一個(gè)概念,只有第三種模式才是正常的調(diào)用模式,第一種和第二種的模式都可以轉(zhuǎn)化為第三種模式

第一種可以轉(zhuǎn)化為function.call(undefined,p1,p2);
第二種模式可以轉(zhuǎn)化為 fn.str.call(fn,p1,p2);
第一種情況下this指向的是undefined,但是在js中規(guī)定,如果指向undefined,那么會將指針指向window

其實(shí)上面對this的解釋比較好了,下面的點(diǎn)可以說時(shí)一點(diǎn)補(bǔ)充吧

this的指向時(shí)在函數(shù)被調(diào)用時(shí)所確定的

在一個(gè)函數(shù)上下文中,this由調(diào)用者提供,由調(diào)用函數(shù)的方式來決定。如果調(diào)用者函數(shù),被某一個(gè)對象所擁有,那么該函數(shù)在調(diào)用時(shí),內(nèi)部的this指向該對象。如果函數(shù)獨(dú)立調(diào)用,那么該函數(shù)內(nèi)部的this,則指向undefined。但是在非嚴(yán)格模式中,當(dāng)this指向undefined時(shí),它會被自動指向全局對象。

var a = 20;
function fn() {
    function foo() {
        console.log(this.a);
    }
    foo();
}
fn();

還有一點(diǎn)時(shí)切記注意當(dāng)前是否是嚴(yán)格模式,嚴(yán)格模式下直接調(diào)用沒有被對象擁有的函數(shù)的this是會直接報(bào)錯(cuò)的,我們可以在嚴(yán)格模式和非嚴(yán)格模式下觀察以下代碼的輸出結(jié)果

'use strict';
var a = 20;
function foo () {
    var a = 1;
    var obj = {
        a: 10, 
        c: this.a + 20,
        fn: function () {
            return this.a;
        }
    }
    return obj.c;

}
console.log(foo());   
console.log(window.foo());  

參考文章:[前端基礎(chǔ)進(jìn)階系列]

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

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

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