JavaScript:萬惡的 this 拿命來(三)

閉包 this

執行上下文決定了變量做用域javascript

閉包,它實際上是一種決策,是一種模式,讓咱們能夠靈活的改變變量做用域html

按慣例,上栗子java

var global = 'global';

function outer(){
    var out = 'outer';

    function middle(){
        var mid = 'middle';

        function inner(){
            var in = 'inner';
            console.log('globa : '+global, ',outer : '+out,
                      ',middle : '+mid, ',inner : '+in);
            //globa : global outer : outer middle : middle inner : inner
        }
        inner();
        console.log(in) //undefined
    }
    middle();
}
outer();

console.log(inner);  //undefined 
console.log(middle); //undefined
console.log(outer);  //undefined
console.log(global); //global

做用域

  • 抽象:不一樣的"函數調用"會產生不一樣的"執行上下文",不一樣的"執行上下文"劃分出了不一樣的"變量做用域"。瀏覽器

  • 具體:我們應該見過婚禮上的蛋糕,圓形的,一圈一圈的同心圓,中間最高,最外圍最低。此處的"最高"和"最低"能夠理解爲訪問權限,及裏面能訪問外面,而外面訪問不了裏面。閉包

變量做用域

變量在inner函數中的做用域 = inner函數內部做用域 + 全部外層的做用域函數

變量在middle函數中的做用域 = middle函數內部做用域 + 全部外層的做用域 - inner函數內部學習

變量在outer函數中的做用域 = outer函數內部做用域 + 全部外層的做用域 - middle函數內部做用域this

備註:以上前提是基於用var聲明變量,省略var聲明變量會致使變量提高!經過這個栗子能夠初看出做用域的端倪編碼

優勢VS缺點

優勢:prototype

  • 合理的造成"管轄區",即"管轄區"內它能被訪問到,"管轄區"外沒這人
  • 不污染外層做用域

缺點

  • 由於受到了"管轄",致使有時須要訪問它時卻訪問不到

閉包

引自阮一峯老師的博客 -- 學習Javascript閉包(Closure)

因爲在Javascript語言中,只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成"定義在一個函數內部的函數"。

因此,在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。

只要我們弄明白閉包,其中的this天然跑不掉。

上栗子

function constfuncs() {
    var funcs = [];
    for (var i = 0; i < 10; i++) {
        funcs[i] = function () {
            return i;
        }
    }
    return funcs;
}
var funcs = constfuncs();
alert(funcs[1]());

這是最近的一個問題,對於funcs[1]()是幾你們能夠去試試

好吧,若是去試了可能會發現,不管你funcs[1]()中輸入的時1仍是9,它的都是10

這個就有意思了,爲何不論怎麼輸入,結果都是10呢?若是你發出了這個疑問,那麼你的潛意識裏確定是弄錯了件事:你認爲

funcs[i] = function () {
     return i;
}

funcs[i]中的i會決定這個匿名函數中返回的i,其實否則。

for循環的過程當中,會不停的建立函數

funcs[0] = function () {
     return i;
}                            //對象字面量被建立
...
funcs[9] = function () {
     return i;
}                            //對象字面量被建立

被建立的函數並無被馬上執行,而是進入了等待隊列,等待你的主動調用

於此同時,i在等於9後又執行了i++操做,如今i等於10

好的,如今我們調用了funcs[1](),那麼下一步函數會返回i,也就是10,因此不管你調用funcs[1]()仍是funcs[9](),它都會返回10

如今改用閉包來解決這個問題了!

其實有一個值得玩味事情是:爲何遇到這樣的問題,咱們會用閉包解決?
換一種說法是:爲何閉包能解決這個應用場景的問題?

讓咱們在回顧一下那句話

在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。

由於咱們正好須要一座橋樑,將外部的i和內部的i關聯起來。

上栗子

function constfuncs() {
    var funcs = [];
    for (var i = 0; i < 10; i++) {
        funcs[i] = (function (i) {   // 標記1
            return function () {      /
                return i;             //   標記2(上下三行)
            };                        /
        })(i)                        //  標記3
    }
    return funcs;
}
var funcs = constfuncs();
console.log(funcs[1]());


- 標記2:咱們在本來返回i的地方,返回了一個匿名函數,裏面再返回了i
- 標記3:咱們傳入了i,架起了鏈接外部的橋樑
- 標記1:咱們將標記3傳入的i做爲參數傳入函數,架起了鏈接內部的橋樑

至此,每當一個for循環執行一次,i也會傳入函數內部被保存/記憶下來。

再來一發

function constfuncs() {
    var funcs = [];
    for (var i = 0; i < 10; i++) {
        funcs[i] = (function () {
            return i;
        }(i));
    }
    return funcs;
}
var funcs = constfuncs();
console.log(funcs[1]);

在這個栗子中,因爲咱們改變了寫法,致使最後的調用方法改變,但依舊是應用閉包的特性。

若是這個栗子懂了,那閉包應該懂了一大半了,若是仍是有點暈,不要緊,我們繼續往下看。

this

如今我們說說閉包this之間的事

上栗子(瀏覽器/REPL中)

var name = 'outer'

function Base(){}

Base.prototype.name = 'base';

Base.prototype.log = function () {

    var info = 'name is ';

    console.log(this.name);            // name is  base

    function inner(){
        console.log(info,this.name);   // name is  outer
    };
    inner();
};

var base = new Base();
base.log();

咱們指望的是經過this訪問原型對象中的name,但是最後卻訪問到全局對象中的name屬性。

因此光有閉包還不夠,咱們須要藉助點別的技巧,改寫log函數

var name = 'outer'

function Base(){}

Base.prototype.name = 'base';

Base.prototype.log = function () {

    var info = 'name is ';

    var self = this;         // 保存this

    function inner(){
        console.log(info,self.name);

    };
    inner();
};

var base = new Base();
base.log();

註解:使用self或that變量來保存this是約定俗成

緣由:
- 因爲inner函數定義在了log函數內部,造成了閉包,致使內部this"氾濫"指向了全局對象,如今作的就是在this尚未"氾濫"的時候,保存它。

更常見的,是這樣的改寫log函數

var name = 'outer'

function Base(){}

Base.prototype.name = 'base';

Base.prototype.log = function () {

    var info = 'name is ';

    var self = this;
    (function inner(){
        console.log(info,self.name);

    })(self);
};

var base = new Base();
base.log();

用一個"當即執行的函數表達式"代替函數建立和調用。

再來一枚經典栗子

var scope = "global";
var object = {
    scope:"local",
    getScope:function(){
        return function(){
            return this.scope;
        }
    }
}

相信你們對函數中的函數應該有必定的警戒性了,this.scope的值是誰你們應該也心中有值了,你們能夠本身動手改一改,實踐纔是王道!

當即執行的函數表達式

最多見的版本大概是長這個樣子:

var name = 'outer';

(function () {

    var name = 'inner';
    console.log(name);          // inner
    console.log(this.name);     // outer
})();

相信你們看過上文後,應該都明白了爲何this.name會輸出outer,下面來講說什麼是當即執行的函數表達式

我們分兩步說:
 - 當即執行
 - 函數表達式

常見的建立函數有這兩種

function Thing(){
    console.log('thing');
}                            //直接函數聲明
Thing();                     //函數調用


var thing = function () {
    console.log('thing');
};                           //函數字面量
thing();                     //函數調用

不妨試試這樣

thing
()

你會發現函數神奇的執行了,也就是說函數名後面跟上一對小括號(),能夠馬上調用函數。

那單獨的那一行thing是什麼呢?它是函數的名字,是一個指針,可是在這裏被解析成了表達式,單獨佔了一行。

也就說咱們一般執行函數都是這麼搞的,那麼萬一這函數沒有名字呢?咱們能夠這樣

(function(){
    console.log('no name');
})();

(function(){
    console.log('no name')
}());

-function(){
    console.log('no name');
}();

+function(){
    console.log('no name');
}();

~function(){
    console.log('no name');
}();

!function(){
    console.log('no name');
}();

除了最上面兩個較常見外,其餘的都挺怪異!可是他們均可以當即執行!

注意函數的前面都有一個符號,'+' , '-' , '~' , '!' , '()'這些符號告訴解析器強制把這些函數聲明解析成函數表達式,最後的一對小括號()又讓這函數表達式當即執行。

注意

若是要使用就請使用前兩個,用小括號()的方式是最正規也是慣例,其餘的方式容易致使本身或者他人誤解,並且不符合編碼規範,強烈不推薦使用,本身在練習的時候能夠玩一玩,體會體會。

應用場景

1.你能夠用當即執行的函數表達式暴露公開的成員方法

var cal = (function () {

    return {

        add: function (a,b) {
            return a + b;
        },
        sub: function (a,b) {
            return a - b;
        }
    }
})();

cal.add(5,2)   // 7
cal.sub(4,1)   // 3

或者

var cal = (function () {

    var way = {};

    way.add = function (a,b) {
        return a + b;
    };
    way.sub = function (a,b) {
        return a - b;
    };

    return way;
})();

cal.add(3,6)   // 9
cal.sub(8,5)   // 3

2.續寫子模塊

cal.controller = (function () {

    var way = {};

    var result;
    way.set = function (args) {
        result = args;
    }

    way.get = function () {
        return result;
    }

    return way;
})();

cal.controller.set(123);   
cal.controller.get();   //  123

總結

  • 細說變量做用域,比較其優缺點
  • 舉例說明閉包的概念,做用
  • 舉例吐槽了閉包this之間的劇情,緣由及解決方案
  • 細說了當即執行的函數表達式的概念及原理
  • 列舉了其應用場景
相關文章
相關標籤/搜索