摘要:閉包執(zhí)行上下文決定了變量作用域而閉包,它其實是一種決策,是一種模式,讓我們可以靈活的改變變量作用域。所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。只要咱們弄明白閉包,其中的自然跑不掉。
閉包 this
執(zhí)行上下文決定了變量作用域
而閉包,它其實是一種決策,是一種模式,讓我們可以靈活的改變變量作用域。
按慣例,上栗子
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作用域
抽象:不同的"函數(shù)調(diào)用"會產(chǎn)生不同的"執(zhí)行上下文",不同的"執(zhí)行上下文"劃分出了不同的"變量作用域"。
具體:咱們應(yīng)該見過婚禮上的蛋糕,圓形的,一圈一圈的同心圓,中間最高,最外圍最低。此處的"最高"和"最低"可以理解為訪問權(quán)限,及里面能訪問外面,而外面訪問不了里面。
變量作用域變量在inner函數(shù)中的作用域 = inner函數(shù)內(nèi)部作用域 + 所有外層的作用域
變量在middle函數(shù)中的作用域 = middle函數(shù)內(nèi)部作用域 + 所有外層的作用域 - inner函數(shù)內(nèi)部
變量在outer函數(shù)中的作用域 = outer函數(shù)內(nèi)部作用域 + 所有外層的作用域 - middle函數(shù)內(nèi)部作用域
備注:以上前提是基于用var聲明變量,省略var聲明變量會導(dǎo)致變量提升!通過這個栗子可以初看出作用域的端倪
優(yōu)點VS缺點優(yōu)點:
合理的形成"管轄區(qū)",即"管轄區(qū)"內(nèi)它能被訪問到,"管轄區(qū)"外沒這人
不污染外層作用域
缺點
因為受到了"管轄",導(dǎo)致有時需要訪問它時卻訪問不到
閉包引自阮一峰老師的博客 -- 學習Javascript閉包(Closure)
由于在Javascript語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡單理解成"定義在一個函數(shù)內(nèi)部的函數(shù)"。
所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。
只要咱們弄明白閉包,其中的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]()是幾大家可以去試試
好吧,如果去試了可能會發(fā)現(xiàn),無論你funcs[1]()中輸入的時1還是9,它的都是10。
這個就有意思了,為什么不論怎么輸入,結(jié)果都是10呢?如果你發(fā)出了這個疑問,那么你的潛意識里肯定是弄錯了件事:你認為
funcs[i] = function () { return i; }
funcs[i]中的i會決定這個匿名函數(shù)中返回的i,其實不然。
在for循環(huán)的過程中,會不停的創(chuàng)建函數(shù):
funcs[0] = function () { return i; } //對象字面量被創(chuàng)建 ... funcs[9] = function () { return i; } //對象字面量被創(chuàng)建
被創(chuàng)建的函數(shù)并沒有被立刻執(zhí)行,而是進入了等待隊列,等待你的主動調(diào)用。
于此同時,i在等于9后又執(zhí)行了i++操作,現(xiàn)在i等于10。
好的,現(xiàn)在咱們調(diào)用了funcs[1](),那么下一步函數(shù)會返回i,也就是10,所以無論你調(diào)用funcs[1]()還是funcs[9](),它都會返回10。
現(xiàn)在改用閉包來解決這個問題了!
其實有一個值得玩味事情是:為什么遇到這樣的問題,我們會用閉包解決?
換一種說法是:為什么閉包能解決這個應(yīng)用場景的問題?
讓我們在回顧一下那句話
在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。
因為我們正好需要一座橋梁,將外部的i和內(nèi)部的i關(guān)聯(lián)起來。
上栗子
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的地方,返回了一個匿名函數(shù),里面再返回了i - 標記3:我們傳入了i,架起了連接外部的橋梁 - 標記1:我們將標記3傳入的i作為參數(shù)傳入函數(shù),架起了連接內(nèi)部的橋梁
至此,每當一個for循環(huán)執(zhí)行一次,i也會傳入函數(shù)內(nèi)部被保存/記憶下來。
再來一發(fā)
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]); 在這個栗子中,由于我們改變了寫法,導(dǎo)致最后的調(diào)用方法改變,但依舊是應(yīng)用閉包的特性。
如果這個栗子懂了,那閉包應(yīng)該懂了一大半了,如果還是有點暈,沒關(guān)系,咱們繼續(xù)往下看。
this現(xiàn)在咱們說說閉包和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函數(shù)
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函數(shù)定義在了log函數(shù)內(nèi)部,形成了閉包,導(dǎo)致內(nèi)部this"泛濫"指向了全局對象,現(xiàn)在做的就是在this還沒有"泛濫"的時候,保存它。
更常見的,是這樣的改寫log函數(shù)
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(); 用一個"立即執(zhí)行的函數(shù)表達式"代替函數(shù)創(chuàng)建和調(diào)用。
再來一枚經(jīng)典栗子
var scope = "global"; var object = { scope:"local", getScope:function(){ return function(){ return this.scope; } } }
相信大家對函數(shù)中的函數(shù)應(yīng)該有一定的警惕性了,this.scope的值是誰大家應(yīng)該也心中有值了,大家可以自己動手改一改,實踐才是王道!
立即執(zhí)行的函數(shù)表達式最常見的版本大概是長這個樣子:
var name = "outer"; (function () { var name = "inner"; console.log(name); // inner console.log(this.name); // outer })();
相信大家看過上文后,應(yīng)該都明白了為什么this.name會輸出outer,下面來說說什么是立即執(zhí)行的函數(shù)表達式。
咱們分兩步說: - 立即執(zhí)行 - 函數(shù)表達式
常見的創(chuàng)建函數(shù)有這兩種
function Thing(){ console.log("thing"); } //直接函數(shù)聲明 Thing(); //函數(shù)調(diào)用 var thing = function () { console.log("thing"); }; //函數(shù)字面量 thing(); //函數(shù)調(diào)用
不妨試試這樣
thing ()
你會發(fā)現(xiàn)函數(shù)神奇的執(zhí)行了,也就是說函數(shù)名后面跟上一對小括號(),可以立刻調(diào)用函數(shù)。
那多帶帶的那一行thing是什么呢?它是函數(shù)的名字,是一個指針,但是在這里被解析成了表達式,多帶帶占了一行。
也就說我們通常執(zhí)行函數(shù)都是這么搞的,那么萬一這函數(shù)沒有名字呢?我們可以這樣
(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"); }();
除了最上面兩個較常見外,其他的都挺怪異!但是他們都可以立即執(zhí)行!
注意注意函數(shù)的前面都有一個符號,"+" , "-" , "~" , "!" , "()"這些符號告訴解析器強制把這些函數(shù)聲明解析成函數(shù)表達式,最后的一對小括號()又讓這函數(shù)表達式立即執(zhí)行。
如果要使用就請使用前兩個,用小括號()的方式是最正規(guī)也是慣例,其他的方式容易導(dǎo)致自己或者他人誤解,而且不符合編碼規(guī)范,強烈不推薦使用,自己在練習的時候可以玩一玩,體會體會。應(yīng)用場景
1.你可以用立即執(zhí)行的函數(shù)表達式暴露公開的成員或方法
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.續(xù)寫子模塊
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總結(jié)
細說變量作用域,比較其優(yōu)缺點
舉例說明閉包的概念,作用
舉例吐槽了閉包和this之間的劇情,原因及解決方案
細說了立即執(zhí)行的函數(shù)表達式的概念及原理
列舉了其應(yīng)用場景
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/85645.html
摘要:在腳本中,默認指向一個空對象,并不是指向,也不是指向。舉個栗子,在函數(shù)執(zhí)行后,覆蓋原先的值我們在外部定義了一個名為的全局變量,它會被默認添加到全局的屬性上。總結(jié)在不同的執(zhí)行環(huán)境中的默認指代通過省略聲明變量導(dǎo)致變量提升現(xiàn)象的發(fā)生及預(yù)防 侃侃JavaScript中的this this為何如此多變? this總是跟它的執(zhí)行上下文有關(guān),而在JavaScript總會有開辟新的執(zhí)行上...
摘要:構(gòu)造函數(shù)對于被實例化的,我們稱之為構(gòu)造函數(shù),及使用關(guān)鍵字調(diào)用的,對于它們來說,會被改變,指向?qū)嵗I侠踝尤仲x上屬性通過關(guān)鍵字創(chuàng)建實例,改變函數(shù)內(nèi)部指向注解通過這個栗子,我們可以看出,通過創(chuàng)建構(gòu)造函數(shù)的實例,使得的指向改變,指向了實例本身。 用栗子說this Bug年年有,今年特別多 對于JavaScript這么靈活的語言來說,少了this怎么活! function ...
摘要:為了實現(xiàn)這個正義偷笑又合理的訴求,你得先學會今天要介紹的設(shè)計模式,因為你們公司的這個流程可能就是用今天這個模式設(shè)計的。狀態(tài)模式對開閉原則的支持并不太好,新增狀態(tài)時,不僅得增加狀態(tài)類,還得修改原來已經(jīng)有的狀態(tài),讓之前的狀態(tài)切換到新增的狀態(tài)。一、定義你是否經(jīng)常請(偷)假(懶)?是不是對公司萬惡的請假申請流程深惡痛絕。有沒有想過偷偷改造這個萬惡的系統(tǒng),從 申請->項目經(jīng)理審批->部門審批->老板審...
摘要:寫在前面在一款應(yīng)用的整個生命周期,我們都會談及該應(yīng)用的數(shù)據(jù)安全問題。用戶的合法性與數(shù)據(jù)的可見性是數(shù)據(jù)安全中非常重要的一部分。 寫在前面 在一款應(yīng)用的整個生命周期,我們都會談及該應(yīng)用的數(shù)據(jù)安全問題。用戶的合法性與數(shù)據(jù)的可見性是數(shù)據(jù)安全中非常重要的一部分。但是,一方面,不同的應(yīng)用對于數(shù)據(jù)的合法性和可見性要求的維度與粒度都有所區(qū)別;另一方面,以當前微服務(wù)、多服務(wù)的架構(gòu)方式,如何共享Sessi...
摘要:而在中是迭代器生成器,被創(chuàng)造性的拿來做異步流程控制了。當執(zhí)行的時候,并不執(zhí)行函數(shù)體,而是返回一個迭代器。行代碼再看看文章開頭的行代碼首先生成一個迭代器,然后執(zhí)行一遍,得到的是一個對象,里面再執(zhí)行。 廣告招人:阿里巴巴招前端,在這里你可以享受大公司的福利和技術(shù)體系,也有小團隊的挑戰(zhàn)和成長空間。聯(lián)系: qingguang.meiqg at alibaba-inc.com 首先請原諒我的標題...
閱讀 2328·2023-04-26 00:01
閱讀 810·2021-10-27 14:13
閱讀 1848·2021-09-02 15:11
閱讀 3393·2019-08-29 12:52
閱讀 544·2019-08-26 12:00
閱讀 2575·2019-08-26 10:57
閱讀 3417·2019-08-26 10:32
閱讀 2860·2019-08-23 18:29