[深刻01] 執行上下文

導航

[深刻01] 執行上下文
[深刻02] 原型鏈
[深刻03] 繼承
[深刻04] 事件循環
[深刻05] 柯里化 偏函數 函數記憶
[深刻06] 隱式轉換 和 運算符
[深刻07] 瀏覽器緩存機制(http緩存機制)
[深刻08] 前端安全
[深刻09] 深淺拷貝
[深刻10] Debounce Throttle
[深刻11] 前端路由
[深刻12] 前端模塊化
[深刻13] 觀察者模式 發佈訂閱模式 雙向數據綁定
[深刻14] canvas
[深刻15] webSocket
[深刻16] webpack
[深刻17] http 和 https
[深刻18] CSS-interview
[react] Hooksjavascript

[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CI前端

[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程vue

執行上下文的概念

執行上下文是:javaScript代碼解析和執行時所在的環境,javascript中運行的全部代碼都在執行上下文中執行java

須要理解的一些名詞

  • VO: 變量對象 variable object
  • AO: 活動對象 active object
  • EC: 執行上下文 execution context
  • ECS: 執行上下文棧 execution context stack
  • Scope Chain: 做用域鏈
  • Lexical Environment: 詞法環境
  • Variable Environment: 變量環境
  • execution:執行
  • lexical:詞彙的

執行上下文的類型

執行上下文分爲三種類型:node

全局執行上下文:

  • js代碼運行起來,首先進入該環境。不在任何函數中的js代碼都位於全局執行上下文中
  • 一個程序中只能存在一個全局上下文,位於執行上下文棧的最底部
  • 全局執行上下文會作兩件事:(1)建立一個全局對象 (2)將this指向這個全局對象
  • 在瀏覽器環境全局對象是window,在node環境全局對象是global

函數執行上下文:

  • 每次調用一個函數,都會生成一個新的函數執行上下文
  • 每一個函數都有本身的函數執行上下文,但只有被調用時纔會被建立
  • 函數執行上下文的生命週期分爲兩個階段,建立階段和執行階段

Eval執行上下文:

  • eval函數執行時產生的執行上下文,不建議使用

執行上下文棧(函數調用棧)

  • 棧是一個後進先出的結構,用於儲存 在js代碼執行期間產生的全部執行上下文
  • 具體過程:
    • 在javascript剛開始運行時,首先產生全局執行上下文,壓入棧,位於棧底
    • 每當發生一個函數被調用,則產生函數執行上下文,壓入棧,位於棧頂
    • 當這個函數執行完後,會從執行上下文棧中彈出
    • 引擎會繼續去執行位於棧頂的函數

執行上下文的生命週期

建立階段:(預編譯階段)react

  • 執行上下文會建立: 變量對象VO(arguments,形參,變量,函數),做用域鏈,this

執行階段:webpack

  • 變量賦值,函數引用,以及執行其餘代碼
  • 當(執行上下文)執行完畢後,就會出棧,等待被js垃圾回收機制回收

變量對象

變量對象VO 活動對象AO git

變量對象的分類

  • 不一樣執行上下文環境的變量對象有不一樣的表現
  • 全局執行上下文中的 變量對象
  • 函數執行上下文中的 變量對象

全局執行上下文中的變量對象

  • 全局執行上下文中的變量對象就是:全局對象
  • 什麼是全局對象
    • 進入任何執行上下文以前就創建的對象,只有一份,在程序任何地方都能訪問,生命週期終止於程序退出時
  • 全局對象初始化:
    • 初始化一系列的原始屬性:Math、String、Date、parseInt、window等
    • 瀏覽器中,window對象引用全局對象自身,全局環境中this也能引用自身

函數執行上下文中的變量對象

  • 在函數執行上下文中,變量對象VO 用活動對象AO來表示
  • AO是在進入函數執行上下文時(預編譯階段)被建立的,經過argument對象進行初始化
  • 複習下執行上下文的生命週期:(1)建立階段即預編譯階段 (2)執行階段。

(1) 函數執行上下文的,進入函數執行上下文階段(預編譯階段)代碼還未真正執行,此時AO被建立,此時包含的屬性有:

  • arguments對象
  • 全部形參
    • 形參名稱和形參的值,組成了AO對象的屬性。
    • 傳遞實參,則該形參的值被賦值爲實參
    • 沒有傳遞實參,值是undefined
  • 全部函數聲明
  • 全部變量聲明
  • this
函數執行上下文
- 建立階段(進入執行上下文階段,或者說預編譯階段),即進入函數執行上下文時,AO被建立,代碼並未真正執行
- 此時AO中的屬性有:arguments, 形參,函數聲明,變量聲明,this
- 代碼示例:


function a(name, age) {
    var b = 1
    function c() {}
    var d = function()()
    (function e () {}); // 注意e函數的寫法
    var f = function g(){}
}
a('woow') // 注意這裏沒有傳實參age


------------
預編譯階段的AO以下:
AO(a function execution context) = {
    arguments: {
        ......
    },
    name: 'woow',
    age: undefined,
    b: undefined,
    c: reference to function c(){},
    d: undefined
    f: undefined
}
// 注意:
// e函數表達式不在AO中
// g函數不在AO中


------------
執行階段的AO以下:
AO(a function execution context) = {
    arguments: {
        ......
    },
    name: 'woow',
    age: undefined,
    b: 1,
    c: reference to function c(){},
    d: reference to FunctionExpression "d",
    f: reference to FunctionExpression "f"
}
複製代碼

(2)活動對象AO和變量對象VO的區別:

  • 變量對象:是規範或引擎實現層面的,在js的環境中沒法訪問,在進入函數執行上下文階段(預編譯階段),VO才被激活,成爲AO
  • 活動對象:只有成爲了活動對象,變量對象的屬性和方法才能被訪問

變量提高

優先級: 函數形參 > 函數聲明 > 變量聲明

函數名已經存在,則新的覆蓋舊的

變量名已經存在,直接跳過變量聲明

- 變量提高,其實是在函數執行上下文中:進入函數執行上下文時,生成的活動對象AO的屬性的賦值過程
變量提高
- 優先級:形參 > 函數聲明 > 變量聲明 
- 函數名已經存在,則新的覆蓋舊的
- 變量名已經存在,則跳過變量聲明(注意:只是跳過變量的聲明,賦值是正常的賦值)

複製代碼
(例1)
function a(name, age) {
    console.log(name) // -------------------------- 'wang'
    console.log(age) // --------------------------- undefined
    var name = 'woow_wu7'
    console.log(name) // -------------------------- 'woow_wu7'
}
a('wang')

實際執行的代碼以下:
function a(name, age) {
    var name = 'wang'
    var age = undefined // ------------------------ 優先級:形參 > 變量聲明,因此形參聲明並被賦值
    // var name = undefined // -------------------- 變量名已經存在了,跳過變量聲明,即這行代碼不執行
    console.log(name) // -------------------------- 'wang'
    console.log(age) // --------------------------- undefined, 未傳入實參
    name = 'woow_wu7' // -------------------------- 執行階段被從新賦值
    console.log(name) // -------------------------- 'woow_wu7'
}
a('wang')
複製代碼
(例2)
function a(name, age) {
    console.log(name) // -------------------------- function name(){....}
    console.log(age) // --------------------------- undefined
    var name = 'woow_wu7'
    function name() {
        console.log('name()')
    }
    console.log(name) // --------------------------- 'woow_wu7'
}
a('wang')

實際執行的代碼以下:
function a(name, age) {
    var name = 'wang' // -------------------------- 優先級:形參 > 函數聲明 > 變量聲明
    function name() {...} // ---------------------- 優先級:函數聲明 > 變量聲明;函數名name已經存在,則新的覆蓋舊的
    // var name = undefined // -------------------- 變量名已經存在,則跳過變量聲明,至關於該行不執行
    console.log(name) // -------------------------- function name(){....}
    console.log(age) // --------------------------- undefined
    name = 'woow_wu7' // -------------------------- 執行時從新被賦值
    function name() {
        console.log('name()')
    }
    console.log(name) // --------------------------- 'woow_wu7'
}
a('wang')
複製代碼

個人語雀:www.yuque.com/woowwu/msyq… juejin.im/post/5dc7db…
juejin.im/post/5bdfd3…
juejin.im/post/5ab072…
juejin.im/post/58ec3c…
www.jianshu.com/p/330b1505e…web

複習canvas

手寫new

  • 構造函數
    • 構造函數的首字母一般大寫
    • this指向的是實例對象
    • 調用時經過new命令
    • 構造函數也能夠接收參數
  • new命令
    • 執行構造函數,返回實例對象
    • new命令自己就能夠執行構造函數,因此函數後面的那對括號能夠不要。好比:new fn 或者 new fn() 均可以
  • 構造函數調用忘記使用new命令?
    • 構造函數忘記使用new命令調用的話,構造函數就成了普通函數,函數中的this就要在調用時才能肯定指向
    • 如何避免構造函數忘記使用new命令調用:
    • (1)在構造函數內使用嚴格模式'use strict',這樣忘記使用new就是報錯
    • (2)就是判斷是否使用了new,如何判斷?
    • !(this instanceof Fubar) => 若是this不是Fubar的實例,就用new命令調用return new Fubar()
  • new命令的原理
    • 建立一個空對象,做爲將要返回的實例對象
    • 將這個空對象的原型指向構造函數的prototype屬性
    • 將這個空對象賦值給函數內部的this關鍵字
    • 執行構造函數內部的代碼
    • 若是構造函數的返回值是一個對象,就返回這個對象。不然返回這個空對象即this對象
  • 構造函數中的return
    • 構造函數中,若是return後面跟着一個對象,new命令就會返回這個對象
    • 構造函數中,若是return後面跟的不是一個對象,或者沒有返回值,就是返回this對象
    • 普通函數若是用new命令調用,則會返回一個空對象
手寫new
- new命令執行構造函數,返回實例對象
- 構造函數中有return,若是後面跟一個對象new命令會返回這個對象,若是後面不是對象,就會返回this對象
- new命令生成的實例對象能夠繼承構造函數的prototype屬性 => 即實例能夠訪問構造函數的prototype上的屬性和方法
- new命令生成的實例能夠訪問構造函數中的屬性和函數


過程:
1. 新建一個空對象
2. 將空對象的隱式原型指向構造函數的顯示原型 => 空對象就能訪問構造函數的prototype上的屬性和方法
3. 將構造函數中的this指向這個空對象 => this上綁定的屬性和方法實際上就綁在了空對象上
4. 執行構造函數 => 若是有return,並返回一個對象,就返回這個對象,若是不是對象,就返回這個空對象
5. 若是構造函數的返回值是一個對象,就返回這個對象。不然返回這個空對象即this對象


代碼:
function Constructor(name) {
  this.name = name
  this.age = 20
  return this
}
Constructor.prototype.address = 'chongqing'

function _new() {
  const obj = {}
  // 還能夠經過 const obj = new Object()來生成

  const paramsConstructor = Array.prototype.shift.call(arguments)
  // 獲取傳入new函數的構造函數
  // 等於 [].prototype.shift.call(arguments)
  // 等於 [].prototype.shifig.apply(arguments)

  obj.__proto__ = paramsConstructor.prototype
  // 將空對象的隱式原型指向構造函數的顯示原型
  // 這樣paramsConstructor.prototype就成了obj的原型對象,obj能夠訪問原型對象上的屬性和方法
  // 注意:Object.create() 該方法能夠以參數對象爲原型,返回一個實例對象
  // 因此:空對象的生成,加空對象的隱式原型的綁定能夠一行代碼搞定:
  //!!!!!!!能夠:const obj = Object.create(paramsConstructor.prototype)
  //!!!! 還能夠:const obj = Object.setPrototypeOf({}, paramsConstructor.prototype)

  const res = paramsConstructor.apply(obj, arguments)
  // 將構造函數中的this綁定在空對象上,並執行構造函數
  // 注意:這裏arguments是shift後剩下的值,由於apply能夠本身轉化類數組的對象,因此無需額外操做

  return Object.prototype.toString.call(res).includes('Object') ? res : obj
  // 若是構造函數返回的是一個對象,就返回這個對象,不然返回這個空對象
  // 等於 typeof res === 'object' ? res : obj
}
const res = _new(Constructor, 'woow_wu7')
console.log(res, 'res11')
複製代碼
相關文章
相關標籤/搜索