国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Js 中的閉包和this

AlphaWatch / 629人閱讀

摘要:自由變量指的是不在函數內部聲明的變量。作用域鏈就是在所有內部環境中查找變量的鏈式表。閉包的形式閉包的過程寫的不是很嚴謹。

要弄懂這個問題首先要搞清楚一個概念, 執行上下文

執行上下文

執行上下文是什么?

可以簡單理解執行上下文是js代碼執行的環境,當js執行一段可執行代碼時,會創建對應的執行上下文。他的組成如下:

executionContextObj = {
    this: 對的就是你關注的那個this,
    VO:變量對象,
    scopeChain: 作用域鏈,跟閉包相關
}

由于JS是單線程的,一次只能發生一件事情,其他事情會放在指定上下文棧中排隊。

js解釋器在初始化執行代碼時,會創建一個全局執行上下文到棧中,接著隨著每次函數的調用都會創建并壓入一個新的執行上下文棧。函數執行后,該執行上下文被彈出。

五個關鍵點:

單線程

同步執行

一個全局上下文

無限制函數上下文

每次函數調用創建新的上下文,包括調用自己

執行上下文建立的步奏

## 創建階段

初始化作用域鏈
創建變量對象
創建arguments
掃描函數聲明
掃描變量聲明
求this

執行階段

初始化變量和函數的引用
執行代碼
this
在函數執行時,this 總是指向調用該函數的對象。要判斷 this 的指向,其實就是判斷 this 所在的函數屬于誰。

指向調用對象:

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2
// 指向全局對象

function foo() {
    console.log( this.a );
}
var a = 2;
foo(); // 2
// 注意

//接上
var bar = foo
a = 3
bar() // 3不是2

// 通過這個例子可以更加了解this是函數調用時才確定的.

//再繞一點

function foo() {
    console.log( this.a );
}
function doFoo(fn) {
    this.a = 4
    fn();
}
var obj = {
    a: 2,
    foo: foo
};
var a =3
doFoo( obj.foo ); // 4
// 而

function foo() {
    this.a = 1
    console.log( this.a );
}
function doFoo(fn) {
    this.a = 4
    fn();
}
var obj = {
    a: 2,
    foo: foo
};
var a =3
doFoo( obj.foo ); // 1

這是為什么呢?是因為優先讀取foo中設置的a,類似作用域的原理嗎?

通過打印foo和doFoo的this,可以知道,他們的this都是指向window的,他們的操作會修改window中的a的值。并不是優先讀取foo中設置的a

因此如果把代碼改成

function foo() {
    setTimeout(() => this.a = 1,0)
    console.log( this.a );
}
function doFoo(fn) {
    this.a = 4
    fn();
}
var obj = {
    a: 2,
    foo: foo
};
var a =3
doFoo( obj.foo ); // 4
setTimeout(obj.foo,0) // 1

上面的代碼結果可以證實我們的猜測。

用new構造就指向新對象:
a = 4
function A() {
    this.a = 3
    this.callA = function() {
        console.log(this.a)
    }
}
A() // 返回undefined, A().callA會報錯。callA被保存在window上
var a = new A()
a.callA() // 3,callA在new A返回的對象里
apply/call/bind

大家應該都很熟悉,令this指向傳遞的第一個參數,如果第一個參數為null,undefined或是不傳,則指向全局變量

a = 3
function foo() {
    console.log( this.a );
}
var obj = {
    a: 2
};
foo.call( obj ); // 2
foo.call( null ); // 3
foo.call( undefined ); // 3
foo.call(  ); // 3
var obj2 = {
    a: 5,
    foo
}
obj2.foo.call() // 3,不是5!
//bind返回一個新的函數
function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}
var obj =
    a: 2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
箭頭函數

箭頭函數比較特殊,沒有自己的this,它使用封閉執行上下文(函數或是global)的 this 值。

var x=11;
var obj={
 x:22,
 say:()=>{
   console.log(this.x); //this指向window
 }
}
obj.say();// 11
obj.say.call({x:13}) // 11
x = 14
obj.say() // 14
//對比一下
var obj2={
 x:22,
 say() {
   console.log(this.x); //this指向obj2
 }
}
obj2.say();// 22
obj2.say.call({x:13}) // 13
事件監聽函數

指向被綁定的dom元素

document.body.addEventListener("click",function(){
    console.log(this)
}
)
// 點擊網頁
// ...

HTML
HTML標簽的屬性中是可能寫JS的,這種情況下this指代該HTML元素。

變量對象

變量對象是與執行上下文相關的數據作用域,存儲了上下文中定義的變量和函數聲明。

變量對象式一個抽象的概念,在不同的上下文中,表示不同的對象

全局執行上下文的變量對象
全局執行上下文中,變量對象就是全局對象。
在頂層js代碼中,this指向全局對象,全局變量會作為該對象的屬性來被查詢。在瀏覽器中,window就是全局對象。

var a = 1
console.log(window.a) // 1
console.log(this.a) // 1
函數執行上下文的變量對象

函數上下文中,變量對象VO就是活動對象AO。

初始化時,帶有arguments屬性。
函數代碼分成兩個階段執行

進入執行上下文時
此時變量對象包括

形參

函數聲明,會替換已有變量對象

變量聲明,不會替換形參和函數

函數執行

根據代碼修改變量對象的值

舉個例子

function test (a,c) {
  console.log(a, b, c, d) // 5 undefined [Function: c] undefined
  var b = 3;
  a = 4
  function c () {
  }
  var d = function () {
  }
  console.log(a, b, c, d) // 4 3 [Function: c] [Function: d]
  var c = 5
  console.log(a, b, c, d) // 4 3 5 [Function: d]
}
test(5,6)

來分析一下過程

1.創建執行上下文時

VO = {
arguments: {0:5},
a: 5,
b: undefined,
c: [Function], //函數C覆蓋了參數c,但是變量聲明c無法覆蓋函數c的聲明
d: undefined, // 函數表達式沒有提升,在執行到對應語句之前為undefined
}

執行代碼時
通過最后的console可以發現,函數聲明可以被覆蓋

作用域鏈
先了解一下作用域

作用域

變量與函數的可訪問范圍,控制著變量及函數的可見性與生命周期。分為全局作用域和局部作用域。

全局作用域:

在代碼中任何地方都能訪問到的對象擁有全局作用域,有以下幾種:

在最外層定義的變量;

全局對象的屬性

任何地方隱式定義的變量(未定義直接賦值的變量),在任何地方隱式定義的變量都會定義在全局作用域中,即不通過 var 聲明直接賦值的變量。

局部作用域:

JavaScript的作用域是通過函數來定義的,在一個函數中定義的變量只對這個函數內部可見,稱為函數(局部)作用域

作用域鏈

作用域鏈是一個對象列表,用以檢索上下文代碼中出現的標識符。
標識符可以理解為變量名稱,參數,函數聲明。

函數在定義的時候會把父級的變量對象AO/VO的集合保存在內部屬性[[scope]]中,該集合稱為作用域鏈。
自由變量指的是不在函數內部聲明的變量。
當函數需要訪問自由變量時,會順著作用域鏈來查找數據。子對象會一級一級的向上查找父對象的變量,父對象的變量對子對象是可見的,反之不成立。
作用域鏈就是在所有內部環境中查找變量的鏈式表。

可以直接的說,JS采用了詞法作用域(靜態作用域),JS的函數運行在他們被定義的作用域中,而不是他們被執行的作用域。可以舉一個例子說明:

var s = 3
function a () {
  console.log(s)
}
function b () {
  var s = 6
  a()
}
b() // 3,不是6

如果js采用動態作用域,打印出來的應該是6而不是3,這個例子說明了js是靜態作用域。

函數作用域鏈的偽代碼:

function foo() {
   function bar() {
       ...
   }
}
foo.[[scope]] = [
 globalContext.VO
];
bar.[[scope]] = [
   fooContext.AO,
   globalContext.VO
];

函數在運行激活的時候,會先復制[[scope]]屬性創建作用域鏈,然后創建變量對象VO,然后將其加入到作用域鏈。

executionContextObj: {
    VO:{},
    scopeChain: [VO, [[scope]]]
}
閉包

閉包是什么

閉包按照mdn的定義是可以訪問自由變量的函數

自由變量前面提到過,指的是不在函數內部聲明的變量。

閉包的形式
function a() {
    var num = 1
    function b() {
        console.log(num++)
    }
    return b
}
var c1 = a()
c1() // "1"
c1() // "2"
var c2 = a()
c2() // "1"
c2() // "2"
閉包的過程

寫的不是很嚴謹。可能省略了一些過程:

運行函數a
創建函數a的VO,包括變量num和函數b
定義函數b的時候,會保存a的變量對象VO和全局變量對象到[[scope]]中
返回函數b,保存到c1
運行c1
創建c1的作用域鏈,該作用域鏈保存了a的變量對象VO
創建c1的VO
運行c1,這是發現需要訪問變量num,在當前VO中不存在,于是通過作用域鏈進行訪問,找到了保存在a的VO中的num,對它進行操作,num的值被設置成2
再次運行c1,重復第二步的操作,num的值設置成3
一些問題
通過上面的運行結果,我們可以觀察到,c2所訪問num變量跟c1訪問的num變量不是同一個變量。我們可以修改一下代碼,來確認自己的猜想

function a() {
    var x = {y : 4}
    function b() {
        return x
    }
    return b
}
var c1 = a()
var c2 = a()
c1 === c2()  // false

因此我們可以確定,閉包所訪問的變量,是每次運行父函數都重新創建,互相獨立的。
注意,同一個函數中創建的自由變量是可以在不同的閉包共享的

function a() {
    var x = 0
    function b() {
        console.log(x++)
    }
    function c() {
        console.log(x++)
    }
    
    return {
        b,
        c
    }
}
var r =  a()
r.b() // 0
r.c() // 1

補充一個查看作用域鏈和閉包的技巧

打開chrome控制臺

console.dir(r.b)
f b() {
    [[Scopes]]: [
        {x:0}, 
        {type: "global", name: "", object: Window}
    ]    
}

最后
最后,我們再來總結一下執行上下文的過程,加深下印象

var scope = "global scope";
function checkscope(a){
    var scope2 = "local scope";
}
checkscope(5);

創建全局上下文執行棧

創建全局變量globalContext.VO.

創建checkscope函數

將全局變量VO保存為作用域鏈,設置到函數的內部屬性[[scope]]

checkscope.[[scope]] = [
    globalContext.VO
];

執行checkscope函數
創建函數執行上下文,將checkscope函數執行上下文壓入執行上下文棧

ECStack = [

checkscopeContext,
globalContext

];

函數執行上下文創建階段

第一步是復制[[scope]],創建作用域鏈

checkscopeContext = {
    Scope: checkscope.[[scope]],
}

第二步是創建活動對象AO

checkscopeContext = {
    AO: {
        arguments: {
            0: 5
            length: 1
        },
        a: 5
        scope2: undefined
    },
    Scope: checkscope.[[scope]],
}

第三步是將活動對象AO放入作用域鏈頂端

checkscopeContext = {
    AO: {
        arguments: {
            0: 5
            length: 1
        },
        a: 5
        scope2: undefined
    },
    Scope:  [AO, checkscope.[[scope]]],
}

第四步,求出this,上下文創建階段結束

這里的this等于window

進入函數執行階段
隨著函數執行,修改AO的值

  AO: {
      arguments: {
    0: 5
          length: 1
      },
a: 5
      scope2: "local scope"
  },

函數執行完畢
函數上下文從執行上下文棧彈出.

ECStack = [
    globalContext
];

文章寫的比較長,涉及的范圍也比較廣,可能有不少的錯誤,希望大家可以指正。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95903.html

相關文章

  • JS核心知識點梳理——上下文、作用域、閉包this(中)

    摘要:引言滿滿的干貨,面試必系列,參考大量資料,并集合自己的理解以及相關的面試題,對核心知識點中的作用域閉包上下文進行了梳理。本篇重點介紹閉包和。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 滿滿的干貨,面試必bei系列,參考大量資料,并集...

    rottengeek 評論0 收藏0
  • JS腳丫系列】重溫閉包

    摘要:內部的稱為內部函數或閉包函數。過度使用閉包會導致性能下降。,閉包函數分為定義時,和運行時。循環會先運行完畢,此時,閉包函數并沒有運行。閉包只能取得外部函數中的最后一個值。事件綁定種的匿名函數也是閉包函數。而對象中的閉包函數,指向。 閉包概念解釋: 閉包(也叫詞法閉包或者函數閉包)。 在一個函數parent內聲明另一個函數child,形成了嵌套。函數child使用了函數parent的參數...

    MartinDai 評論0 收藏0
  • js 閉包的使用技巧

    摘要:閉包的學術定義先來參考下各大權威對閉包的學術定義百科閉包,又稱詞法閉包或函數閉包,是引用了自由變量的函數。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。 前言 上一章講解了閉包的底層實現細節,我想大家對閉包的概念應該也有了個大概印象,但是真要用簡短的幾句話來說清楚,這還真不是件容易的事。這里我們就來總結提煉下閉包的概念,以應付那些非專人士的心血來潮。 閉包的學術...

    dendoink 評論0 收藏0
  • js閉包的理解

    摘要:一般來講,函數執行完畢后,局部活動對象就會被銷毀,內存中僅保存全局作用域,但是閉包的情況有所不同理解閉包的前提先理解另外兩個內容作用域鏈垃圾回收作用域鏈當代碼在執行過程中,會創建變量對象的一個作用域鏈。 閉包是javascript語言的一個難點,也是它的特色,很多高級應用都要依靠閉包來實現。個人的理解是:函數中嵌套函數。 閉包的定義及其優缺點 閉包是指有權訪問另一個函數作用域中的變量的...

    EasonTyler 評論0 收藏0
  • JS基礎-閉包

    摘要:如果不用的話,你實際上聲明了一個全局變量閉包有權訪問另一個函數作用域的變量,常見的創建方式就是在一個函數內部創建另一個函數,通過另一個函數訪問這個函數的局部變量。 變量作用域 變量的作用域無非就是兩種:全局變量和局部變量。 函數內部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量! 閉包 有權訪問另一個函數作用域的變量,常見的創建方式就是在一個函數內部創建...

    Scliang 評論0 收藏0
  • JS進擊之路:閉包

    摘要:常見問題說到閉包相關的問題,最典型的就是變量和指向這兩類問題。如果有錯誤或不嚴謹的地方,歡迎批評指正,如果喜歡,歡迎點贊。 引言 閉包這個詞對很多前端開發人員來說既熟悉又陌生,熟悉是因為很多人都用過閉包,但是用的時候不知道閉包,陌生是因為并不理解閉包,接下來這篇文章將會從多方面介紹閉包 定義 閉包是怎么定義的呢?當函數可以記住并訪問所在的詞法作用域時,就產生了閉包,即使函數在當前詞法作...

    tolerious 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<