溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

作用域和閉包(以及this的用法)

發(fā)布時間:2020-07-07 07:53:07 來源:網(wǎng)絡(luò) 閱讀:523 作者:藝晨光 欄目:web開發(fā)

執(zhí)行上下文

??在介紹作用域特性之前,我們先來回顧一下js的執(zhí)行上下文(詳細介紹:https://www.jianshu.com/p/8f19e45fd1f1)
??一段<script>或者一個函數(shù)之內(nèi),都會去生成一個執(zhí)行環(huán)境(execution context,EC)或稱之為執(zhí)行上下文。當(dāng)一段JS代碼執(zhí)行的時候,JS解釋器會通過兩個階段去產(chǎn)生一個EC。
1.創(chuàng)建階段
o創(chuàng)建變量對象VO
o設(shè)置[[Scope]]屬性的值: (指向作用域鏈)
o設(shè)置this的值: (指向一個EC,默認undefined)
2.初始化變量對象(即設(shè)置變量的值、函數(shù)的引用),然后解釋/執(zhí)行代碼。
  注意事項:
??全局:針對一段<script>,它會生成一個全局的執(zhí)行上下文,在執(zhí)行之前會先去把“變量定義”和“函數(shù)聲明”拿出來裝在對象VO。
??函數(shù):針對一個函數(shù),它會生成一個函數(shù)執(zhí)行上下文,在函數(shù)執(zhí)行之前會先把“變量定義”、“函數(shù)聲明”、“arguments”拿出來封裝在對象VO里邊。

   先執(zhí)行變量定義后,變量值默認為undefined;
  函數(shù)聲明后不會立即執(zhí)行,如果輸出一下函數(shù),能看到函數(shù)體所有的代碼;而需要調(diào)用函數(shù)后(例如:fn();),函數(shù)體中的代碼才會執(zhí)行.
  arguments 是JavaScript里的一個內(nèi)置對象,所有的函數(shù)都有屬于自己的一個arguments對象,它包括了函所要調(diào)用的參數(shù)。
  函數(shù)申明和函數(shù)表達式的區(qū)別:
  function fn(name){} 這是一個函數(shù)聲明,而var a = function(){}這是函數(shù)表達式;
  如果是函數(shù)表達式的寫法,在執(zhí)行之前先拿出來處理的就是var a;了,這時候a只是一個普遍變量(值為undefined)不是函數(shù),這點要注意。

作用域

作用域指的是變量的適用范圍。(詳細介紹:https://www.w3cschool.cn/javascript_prototype/y2cjfozt.html)

js無塊級作用域(不過es6中l(wèi)et定義的變量只在代碼塊中有效,也就有了塊級作用域)

 if (true){
    var name = 'zhao';
}
console.log(name); //zhao,這里依然可以獲取到上面代碼塊里邊的變量。

只有函數(shù)和全局作用域

var a = 100
function fn(){
    var a =200;
    console.log('fn',a); // fn 200
}
console.log('global',a); //  global 100,獲取不到函數(shù)內(nèi)部的變量。

作用域鏈

var a = 100;
function fn1(){
    var b =200;
    function fn2(){
        var c =300;
        // 當(dāng)前作用域沒有定義的變量,即“自由變量”,a和b都是
        console.log(a); // 100
        console.log(b); // 200
        console.log(c); // 300
    }
    fn2();
}
fn1();

PS:自由變量因為在當(dāng)前作用域沒有定義,所以只能去父級作用域查找.
(注意:父級作用域是在函數(shù)“定義”的時候就已經(jīng)確定了的,自由變量這種一層層往父級查找的鏈式結(jié)構(gòu)也就是“作用域鏈”)

閉包

閉包實際上是對js作用域鏈的一種應(yīng)用形式;主要利用了作用域鏈從父級函數(shù)獲取變量的特性,從外部調(diào)用父級函數(shù)局部變量并且互不污染,或者子函數(shù)循環(huán)利用父級函數(shù)的變量達到某種計算用途。

閉包特性一:調(diào)用函數(shù)內(nèi)部的變量,利用作用域鏈原理,能獲取函數(shù)fn1的父級函數(shù)的局部變量進行計算。
閉包特性二:讓這些變量的值始終保持在內(nèi)存中,不會再fn1調(diào)用后被自動清除,再次執(zhí)行fn1的時候還能繼續(xù)上一次的計算。
注意:fn2創(chuàng)建的時候與fn1是相互獨立的,其中的變量a也互不影響,好比父親給每個孩子都準備了一個新的存錢罐。

場景一:函數(shù)作為返回值

function F1(){
    var a =100;
    // 返回一個函數(shù)(函數(shù)作為返回值),為了閱讀方便也可以先定義一個函數(shù),然后retrun函數(shù)名。
    return function(){
        console.log(a); //自由變量,去父作用域?qū)ふ?    }
    // 另外放回函數(shù)的形式也不只是return,同樣在這里以事件的形式綁定在Dom上也是一樣,再或者調(diào)用其他方法傳遞一個函數(shù)出去。
}
var f1 = F1();
var a=200;
f1(); // 100

場景二:函數(shù)作為參數(shù)傳遞

var b=111;
function f1(){
    var a =100;
    console.log(a,b);
}
function f2(fn){
    var a =200;
    var b=222;
    fn();
}
f2(f1); // 100,111  
//并且如果a在F1()沒有定義的話,就會報錯而不是獲取f2中的a,因為它的定義時的父級作用域及之上(即全局作用域)都沒有定義a;

應(yīng)用舉例一:setTimeout中的參數(shù)傳遞。
由于直接setTimeout(function(){},200)這么寫的話,沒辦法傳參,所以可以用閉包的形式來做。

var Fn=function(num){
    var a=10;
    return function(){
        var b=0;
        a+=num;
        b+=num;
        console.log(a,b);
    }
}
var fn1=Fn(1);
var fn2=Fn(1);
//閉包特性一:調(diào)用函數(shù)內(nèi)部的變量,利用作用域鏈原理,能獲取函數(shù)fn1的父級函數(shù)的局部變量a進行計算。
setTimeout(fn1,200); //輸出的a=11,b=1;
//閉包特性二:讓變量a的值始終保持在內(nèi)存中,不會在fn1調(diào)用后被自動清除,再次執(zhí)行fn1的時候還能繼續(xù)上一次的計算。
setTimeout(fn1,500); //輸出的a=12,b=1;
//特性二的注意事項:fn2創(chuàng)建的時候與fn1是相互獨立的,對應(yīng)的父級函數(shù)Fn的變量a也互不影響,好比父親在每個孩子出生時都準備了一個新的存錢罐,每個孩子都用自己的。
setTimeout(fn2,800); //輸出的a=11,b=1;

應(yīng)用舉例二:創(chuàng)建10個<a>標(biāo)簽,點擊的時候彈出來對應(yīng)的序號。

<body>
<script type="text/javascript">

    for(var i=0;i<10;i++){

        (function(i){

            var a=document.createElement('a');
            a.innerHTML=i+'<br>';
            document.body.appendChild(a);
            a.addEventListener('click',function(e){
                e.preventDefault();  //取消默認事件,指a標(biāo)簽
                alert(i);
            });

        })(i);

    }
</script>
</body>

this

核心:this要在執(zhí)行時才能確認值,定義時無法確認。

var a = {
    name: 'A',
    fn: function (){
        console.log(this.name);
    }
}
a.fn(); // this === a (即使是b.a.fn(),this也是a)
a.fn.call({name: 'B'});  // this === {name: 'B'}
var fn1 = a.fn;
fn1();  // this === window

this幾種不同的運用場景
1、作為構(gòu)造函數(shù)執(zhí)行:(例如:new Foo(),this指向這個新對象)
2、作為對象屬性執(zhí)行:(this指向?qū)ο螅?br/>3、作為普通函數(shù)執(zhí)行:(this指向window)
4、call()、apply()、bind():(this指向傳入的第一個對象參數(shù),bind只有一個參數(shù))
參考:https://www.w3cschool.cn/jsnote/jsnote-this.html

call,apply、bind都屬于Function.prototype的一個方法,他們的作用改變函數(shù)的調(diào)用對象,它是JavaScript引擎內(nèi)在實現(xiàn)的,因為屬于Function.prototype,所以每個Function對象實例(就是每個方法)都有call,apply,bind屬性。既然作為方法的屬性,那它們的使用就當(dāng)然是針對方法的了,這幾個方法是容易混淆的。
call,apply的用法差不多,只是參數(shù)稍微不同;(apply()接收兩個參數(shù),一個是函數(shù)運行的作用域(this),另一個是參數(shù)數(shù)組。call()方法第一個參數(shù)與apply()方法相同,但傳遞給函數(shù)的參數(shù)必須列舉出來。)

// 以最簡單window對象為例
function sum(num1, num2) { 
return num1 + num2; 
} 
console.log(sum.call(window, 10, 10)); //20 
console.log(sum.apply(window,[10,10])); //20   這兩都相當(dāng)于window.sum(10,10); 
// 即語法:foo.call(this, arg1,arg2,arg3) == foo.apply(this, arguments) 

而bind的用法有一點差別。(只是傳一個參數(shù)對象,然后返回一個函數(shù)給接受的變量,再另外調(diào)用執(zhí)行。)

window.color = "red"; 
var o = { color: "blue" }; 
function sayColor(){ 
alert(this.color); 
} 
var OSayColor = sayColor.bind(o); 
OSayColor(); //blue 

詳情:https://www.w3cschool.cn/xqw2e7/9m2x12y0.html

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI