js作用域&閉包

js作用域&閉包

作用域

作用域是負責收集并維護由所有聲明的變量組成的一系列查詢,并且實施一套嚴格的規(guī)則,確定當前執(zhí)行的代碼對這些變量的訪問權(quán)限 -- 《你不知道的javascript》

作用域最大的用處就是隔離變量,不同作用域下同名變量不會有沖突

1 作用域鏈

作用域有上下級的關(guān)系,會在當前作用域中尋找變量,如果找不到會沿著創(chuàng)建時作用域鏈一直往上找,直到找到全局作用域。

var a=1;
function f1(){
   var b=2;   
   function f2(){ 
     var c=3;
     console.log(a,b,c);
}  
f2();
}
f1();//1 ,2 ,3

2 作用域形成時機

作用域是在一個函數(shù)創(chuàng)建時就已經(jīng)形成的,而不是調(diào)用時.

function a () {
    let b = 2;
    return function() {
        console.log(b)
    }
}
let b = 222
a(); // 2 而不是222

以上的示例調(diào)用a函數(shù)看似是在全局環(huán)境,但是其中的b卻沒有使用全局的b。而且去尋找定義時的b,沒有找到則沿著創(chuàng)建時的作用域鏈往上找。結(jié)果是2

3 作用域類別

作用域包含全局作用域、函數(shù)作用域、和es6中新增的塊級作用域。
在es6沒有出來之前。我們避免變量污染全局的方法是使用函數(shù)作用域。

最常見的是使用自執(zhí)行函數(shù)來包裹模塊,這樣函數(shù)中的變量只能在局部作用域中生效
(function() {
  // do something
})()

而在es6中新增的let和const可以將變量綁定到所在的任意作用域中通常是{...},為其聲明的變量隱式的劫持了所在的塊級作用域。 -- 《你不知道的javascript》

for (var i = 0; i < 10; i++) {
    setTimeout(() => {
        console.log(i) // 10 10 ... 10
    }, 500)
}
// es6之前 需要使用自執(zhí)行函數(shù)來創(chuàng)建函數(shù)作用域來隔絕變量
for (var i = 0; i < 10; i++) {
    (function(i){
        setTimeout(() => {
        console.log(i) // 10 10 ... 10
    }, 500)
    })(i)   
}
// 而es6中直接使用let就可以實現(xiàn)隔絕變量的作用
for (let i = 0; i < 10; i++) {
    setTimeout(() => {
        console.log(i) // 0 1 2 ... 9
    }, 500)
}

4 提升

代碼被執(zhí)行前會有聲明提升的過程。只有聲明本身會被提升,任何作用域都會進行提升操作。

{
    console.log(a);
    var a = 2;  
}
實際上被解析成如下:
{
        var a;
    console.log(a);
    a = 2;  
}

let和const聲明不會被提升,但是不代表這個作用域中不會進行提升操作。

{
    console.log(a);  //  ReferenceError
    let a = 2;  
}

函數(shù)的提升是優(yōu)先于變量的

foo();  // 1 打印出來的是1 而不是2
var foo;
function foo () {console.log(1)} 
foo = function() {console.log(2)}

this

this的優(yōu)先級

  1. 函數(shù)是否在new中調(diào)用,如果是的話this綁定的是新創(chuàng)建的對象
var bar = new foo();
  1. 函數(shù)通過call、apply(顯式綁定),或者bind(硬綁定)。this綁定的是指定的對象
var bar = foo.call(obj)
  1. 函數(shù)是否在某個上下文對象中調(diào)用(隱式綁定)如果是則綁定的是那個上下文對象
var bar = obj.foo()
  1. 如果都不是的話使用默認綁定,嚴格模式下undefined,否則都是全局對象
var bar = foo()

apply、call都是執(zhí)行本身的函數(shù),而bind是返回一個新函數(shù)。而且可以用來做函數(shù)柯里化

function foo(a,b){
    console.log("a"+a+",b:""+b);
}
var bar = foo.bind(null, 2);
bar(3) // a:2 b:3

1 構(gòu)造函數(shù)中this

function Kimi() {
    this.name = 'kimi'
    console.log(this)  // 'kimi'
}
new Kimi 

如何直接執(zhí)行Kimi() this指向window

2 函數(shù)作為對象的一個屬性

let kimi = {
    name : 'kimi',
    say: function() {
        console.log(this.name) // 'kimi'
    }
}
kimi.say()  // 這樣調(diào)用打印的是‘kimi’

如果不作為屬性調(diào)用
let  a = kimi.say;
a(); // 這樣調(diào)用打印的是window

3 使用call和apply

let kimi = {
    name : 'kimi'
}
let say = function() {
    console.log(this.name)  // 'kimi'
}
say.call(kimi); 

4 全局

全局中的this一直指向window

let name = 'kimi';
let say = function() {
    console.log(this);  // window
    console.log(this.name);  // 'kimi'
}
say();

有以下情況:

let kimi = {
    name : 'kimi',
    say: function() {
        function a () {
                    console.log(this.name)  // 'undefined'
                }
                a();
    }
}
kimi.say();

函數(shù)a雖然是在kimi.say內(nèi)部定義的,但是它仍然是一個普通的函數(shù),this仍然指向window。如果需要重新指向kimi則需要使用箭頭函數(shù)或者用let that = this;保存引用

5 this的用途

this可以動態(tài)的綁定執(zhí)行的對象,起到復(fù)用代碼的作用

jQuery.extend = jQuery.fn.extend = function() {
    target = this;
}

jQuery.extend和jQuery.fn.extend都指向了同一個函數(shù),但是當執(zhí)行時,函數(shù)中的this是不一樣的。
執(zhí)行jQuery.extend(…)時,this指向jQuery;執(zhí)行jQuery.fn.extend(…)時,this指向jQuery.fn。

閉包

在一個函數(shù)內(nèi)部定義的另一個函數(shù),當內(nèi)部函數(shù)在包裹他的函數(shù)之外被執(zhí)行時,就會形成閉包。同時內(nèi)部函數(shù)仍然可以訪問到包裹函數(shù)中的局部變量與函數(shù)。
閉包的兩個常見用途

1. 函數(shù)作為返回值

封裝變量 避免全局中被修改 ,并且記錄狀態(tài)。狀態(tài)不會銷毀丟失
function isFirst() {
    var _list = [];
    return function(id) {
        if(_list.includes(id)) {
            return false
        } 
        _list.push(id)
        return true;
    }
}
var first = isFirst()
first(10) // true
first(10) // false
first(20) // true

2. 函數(shù)作為參數(shù)

function wait(message) {
    setTimeout(function timer() {
        console.log(message);
    }, 1000) 
}
wait('hello');

timer 函數(shù)傳遞給setTimeout(),timer就具有了涵蓋wait()作用域的閉包,因此保有對變量message的引用,等到1000回調(diào)執(zhí)行后,wait的內(nèi)部作用域不會消失

在定時器、事件監(jiān)聽器、Ajax請求、跨窗口通信、webworker、或者其他的異步或者同步的任務(wù)中,只要使用了回調(diào)函數(shù),實際上就在使用閉包。

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

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

  • 一、作用域 作用域:變量生效(可以被訪問)的范圍,用來控制變量的可見性和生命周期。 全局作用域:不單獨屬于某一個函...
    清心挽風閱讀 334評論 0 2
  • 變量的作用域無非就是兩種:全局變量和局部變量。 Javascript語言的特殊之處,就在于函數(shù)內(nèi)部可以直接讀取全局...
    LJQ21閱讀 245評論 0 1
  • 1.全局變量 定義在函數(shù)外部的變量都是全局變量。 聲明提前 2.局部變量 定義在函數(shù)內(nèi)部的變量都是局部變量。 3....
    壬萬er閱讀 285評論 0 0
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點點福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運大...
    HetfieldJoe閱讀 5,711評論 16 88
  • 今天又看到一本干貨滿滿的動物書,短小精悍,與js啟示錄帶來的感覺一樣 (1)重新聲明一個已有的變量,則并不會將該變...
    西蘭花偉大炮閱讀 218評論 0 0

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