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

執行上下文

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

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

作用域

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

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

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

只有函數和全局作用域

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

作用域鏈

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

PS:自由變量因爲在當前作用域沒有定義,所以只能去父級作用域查找.
(注意:父級作用域是在函數“定義”的時候就已經確定了的,自由變量這種一層層往父級查找的鏈式結構也就是“作用域鏈”)

閉包

閉包實際上是對js作用域鏈的一種應用形式;主要利用了作用域鏈從父級函數獲取變量的特性,從外部調用父級函數局部變量並且互不污染,或者子函數循環利用父級函數的變量達到某種計算用途。

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

場景一:函數作爲返回值

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

場景二:函數作爲參數傳遞

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;

應用舉例一:setTimeout中的參數傳遞。
由於直接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);
//閉包特性一:調用函數內部的變量,利用作用域鏈原理,能獲取函數fn1的父級函數的局部變量a進行計算。
setTimeout(fn1,200); //輸出的a=11,b=1;
//閉包特性二:讓變量a的值始終保持在內存中,不會在fn1調用後被自動清除,再次執行fn1的時候還能繼續上一次的計算。
setTimeout(fn1,500); //輸出的a=12,b=1;
//特性二的注意事項:fn2創建的時候與fn1是相互獨立的,對應的父級函數Fn的變量a也互不影響,好比父親在每個孩子出生時都準備了一個新的存錢罐,每個孩子都用自己的。
setTimeout(fn2,800); //輸出的a=11,b=1;

應用舉例二:創建10個<a>標籤,點擊的時候彈出來對應的序號。

<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標籤
                alert(i);
            });

        })(i);

    }
</script>
</body>

this

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

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、作爲構造函數執行:(例如:new Foo(),this指向這個新對象)
2、作爲對象屬性執行:(this指向對象)
3、作爲普通函數執行:(this指向window)
4、call()、apply()、bind():(this指向傳入的第一個對象參數,bind只有一個參數)
參考:https://www.w3cschool.cn/jsnote/jsnote-this.html

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

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章