做用域, 閉包和this關鍵字

做用域

執行上下文分爲全局上下文(在瀏覽器中全局上下文爲window對象),和函數上下文。全局上下文存儲的即是全局變量和方法。而在函數裏面定義的變量和方法則儲存在函數上下文裏面。 當代碼執行時,遇到變量,首先會在當前做用域裏面找,沒有的話則會到父級做用域,直到找到爲止或者到達全局做用域javascript

var color = 「blue」;
function changeColor(){
    var anotherColor = 「red」;
    function swapColors(){
        var tempColor = anotherColor; 
        anotherColor = color;
        color = tempColor;
//color, anotherColor, and tempColor are all accessible here }
//color and anotherColor are accessible here, but not tempColor
    swapColors(); 
    }
//only color is accessible here changeColor();
複製代碼

上面的代碼有三個執行上下文:全局上下文,changeColor()的函數上下文,swapColors()的函數上下文。全局上下文的color變量在swapColors()函數裏面被訪問到,正是由於做用域鏈的關係,做用域鏈是向上的而這不是向下的,因此全局上下文沒有辦法訪問到tempColor這個變量java

javascript在es6以前是沒有塊級做用域的es6

if (true) {
    var color = 'blue'
}
console.log(color)
複製代碼

在if裏面建立的color是能夠在全局上下文訪問到的,es6以後可使用let關鍵字來限制變量的範圍。面試

自由變量將從做用域鏈中去尋找,可是 依據的是函數定義時的做用域鏈,而不是函數執行時,以上這個例子就是閉包。閉包主要有兩個應用場景:算法

  • 函數做爲返回值,上面的例子就是
  • 函數做爲參數傳遞
function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
function F2(f1) {
    var a = 200
    console.log(f1())
}
var f1 = F1()
F2(f1)
複製代碼

以上結果輸出的100,由於函數定義時a的值已是100了。瀏覽器

若是在函數內部分配一個之前沒有被定義的變量的值,它會自動成爲全局範圍的一部分。bash

function myFunction() {
  myVariable = 'JavaScript';
}
myFunction();
console.log(myVariable); //JavaScript
複製代碼

this

this是什麼呢,this其實就是函數調用時的執行上下文 func(p1, p2)執行這個代碼,等價於func.call(undefined, p1, p2), 裏面的context爲undefined,當context 就 null 或者 undefined,那麼 window 對象就是默認的 context(嚴格模式下默認 context 是 undefined),那麼這時候的this就是等價於winodow。 若是是執行obj.f1(p1),至關於執行obj.f1.call(obj, p1),這時候的this就是obj的執行上下文閉包

默認綁定:

規則:在非嚴格模式下,默認綁定的this指向全局對象,嚴格模式下this指向undefinedapp

function foo() {
  console.log(this.a); // this指向全局對象
}
var a = 2;
foo(); // 2
function foo2() {
  "use strict"; // 嚴格模式this綁定到undefined
  console.log(this.a); 
}
foo2(); // TypeError:a undefined
複製代碼

默認綁定規則如上述栗子,書中還提到了一個微妙的細節:dom

function foo() {
  console.log(this.a); // foo函數不是嚴格模式 默認綁定全局對象
}
var a = 2;
function foo2(){
  "use strict";
  foo(); // 嚴格模式下調用其餘函數,不影響默認綁定
}
foo2(); // 2
複製代碼

因此:對於默認綁定來講,決定this綁定對象的是函數體是否處於嚴格模式,嚴格指向undefined,非嚴格指向全局對象。

一般不會在代碼中混用嚴格模式和非嚴格模式,因此這種狀況很罕見,知道一下就能夠了,避免某些變態的面試題挖坑。

隱式綁定:

規則:函數在調用位置,是否有上下文對象,若是有,那麼this就會隱式綁定到這個對象上。

function foo() {
      console.log(this.a);
    }
    var a = "Oops, global";
    let obj2 = {
      a: 2,
      foo: foo
    };
    let obj1 = {
      a: 22,
      obj2: obj2
    };
    obj2.foo(); // 2 this指向調用函數的對象
    obj1.obj2.foo(); // 2 this指向最後一層調用函數的對象
    
    // 隱式綁定丟失
    let bar = obj2.foo; // bar只是一個函數別名 是obj2.foo的一個引用
    bar(); // "Oops, global" - 指向全局
複製代碼

隱式綁定丟失:

隱式綁定丟失的問題:實際上就是函數調用時,並無上下文對象,只是對函數的引用,因此會致使隱式綁定丟失。

一樣的問題,還發生在傳入回調函數中,這種狀況更加常見,而且隱蔽,相似:

test(obj2.foo); // 傳入函數的引用,調用時也是沒有上下文對象。
複製代碼

顯式綁定:

就像咱們上面看到的,若是單純使用隱式綁定確定沒有辦法獲得指望的綁定,幸虧咱們還能夠在某個對象上強制調用函數,從而將this綁定在這個對象上

規則:咱們能夠經過applycallbind將函數中的this綁定到指定對象上。

function foo() {
    console.log(this.a);
}
let obj = {
    a: 2
};
foo.call(obj); // 2
複製代碼

傳入的不是對象:

若是你傳入了一個原始值(字符串,布爾類型,數字類型),來當作this的綁定對象,這個原始值轉換成它的對象形式。

若是你把null或者undefined做爲this的綁定對象傳入call/apply/bind,這些值會在調用時被忽略,實際應用的是默認綁定規則。

new綁定:

書中提到:在js中,實際上並不存在所謂的'構造函數',只有對於函數的'構造調用'。

new的時候會作哪些事情:

  1. 建立一個全新的對象
  2. 這個新對象會被執行 [[Prototype]] 鏈接。
  3. 這個新對象會綁定到函數調用的this
  4. 若是函數沒有返回其餘對象,那麼new表達式中的函數調用會自動返回這個新對象。

規則:使用構造調用的時候,this會自動綁定在new期間建立的對象上。

function foo(a) {
  this.a = a; // this綁定到bar上
}
let bar = new foo(2);
console.log(bar.a); // 2
複製代碼

this四種綁定規則的優先級

若是在某個調用位置應用了多條規則,如何肯定哪條規則生效?

obj.foo.call(obj2); // this指向obj2 顯式綁定比隱式綁定優先級高。
    new obj.foo(); // thsi指向new新建立的對象 new綁定比隱式綁定優先級高。
複製代碼

顯式綁定和new綁定沒法直接比較(會報錯),默認綁定是不該用其餘規則以後的兜底綁定因此優先級最低,最後的結果是:

顯式綁定 > 隱式綁定 > 默認綁定

new綁定 > 隱式綁定 > 默認綁定

箭頭函數的this指向不會使用上述的四條規則:

function foo() {
  return () => {
    console.log(this.a);
  };
}
let obj1 = {
  a: 2
};
let obj2 = {
  a: 22
};
let bar = foo.call(obj1); // foo this指向obj1
bar.call(obj2); // 輸出2 這裏執行箭頭函數 並試圖綁定this指向到obj2
複製代碼

從上述栗子能夠得出,箭頭函數的this規則:

  1. 箭頭函數中的this繼承於它外面第一個不是箭頭函數的函數的this指向
  2. 箭頭函數的 this 一旦綁定了上下文,就不會被任何代碼改變

閉包

一般,函數的做用域及其全部變量都會在函數執行結束後被銷燬。可是,在建立了一個閉包之後,這個函數的做用域就會一直保存到閉包不存在爲止。 閉包有三個特性:

1.函數嵌套函數

2.函數內部能夠引用外部的參數和變量

3.參數和變量不會被垃圾回收機制回收

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

// 釋放對閉包的引用
add5 = null;
add10 = null;
複製代碼

閉包的做用,就是保存本身私有的變量,經過提供的接口(方法)給外部使用,但外部不能直接訪問該變量。

當咱們須要在模塊中定義一些變量,並但願這些變量一直保存在內存中但又不會「污染」全局的變量時,就能夠用閉包來定義這個模塊。

閉包的缺點:閉包的缺點就是常駐內存,會增大內存使用量,使用不當很容易形成內存泄露。

函數套函數就是閉包嗎?:不是!,當一個內部函數被其外部函數以外的變量引用時,纔會造成了一個閉包。

對象模式 函數內部定義個一個對象,對象中綁定多個函數(方法),返回對象,利用對象的方法訪問函數內的數據

function createPerson() {
    var __name__ = "";
    return {
        getName: function () {
            return __name__;
        },
        setName: function( value ) {
            // 若是不姓張就報錯
            if ( value.charAt(0) === '張' ) {
                __name__ = value;
            } else {
                throw new Error( '姓氏不對,不能取名' );
            }
        }
    }
}
var p = createPerson();
p.set_Name( '張三丰' );
console.log( p.get_Name() );
p.set_Name( '張王富貴' );
console.log( p.get_Name() );
複製代碼

函數模式 函數內部定義一個新函數,返回新函數,用新函數得到函數內的數據

function foo() {
    var num = Math.random();
    function func() {
        return mun;
    }
    return func;
}
var f = foo();
// f 能夠直接訪問這個 num
var res1 = f();
var res2 = f();
複製代碼

沙箱模式 沙箱模式就是一個自調用函數,代碼寫到函數中同樣會執行,可是不會與外界有任何的影響,好比jQuery

(function () {
   var jQuery = function () { // 全部的算法 }
   // .... // .... jQuery.each = function () {}
   window.jQuery = window.$ = jQuery;
})();
$.each( ... )
複製代碼

回收機制

在javascript中,若是一個對象再也不被引用,那麼這個對象就會被垃圾回收機制回收; 若是兩個對象互相引用,而再也不被第3者所引用,那麼這兩個互相引用的對象也會被回收。

變量聲明提高

js 代碼在運行前都會進行 AST 解析,函數申明默認會提到當前做用域最前面,變量申明也會進行提高。但賦值不會獲得提高。

參考 https://juejin.im/post/5b3715def265da59af40a630#heading-2

相關文章
相關標籤/搜索