摘要:閉包執行上下文決定了變量作用域而閉包,它其實是一種決策,是一種模式,讓我們可以靈活的改變變量作用域。所以,在本質上,閉包就是將函數內部和函數外部連接起來的一座橋梁。只要咱們弄明白閉包,其中的自然跑不掉。
閉包 this
執行上下文決定了變量作用域
而閉包,它其實是一種決策,是一種模式,讓我們可以靈活的改變變量作用域。
按慣例,上栗子
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函數內部作用域
備注:以上前提是基于用var聲明變量,省略var聲明變量會導致變量提升!通過這個栗子可以初看出作用域的端倪
優點VS缺點優點:
合理的形成"管轄區",即"管轄區"內它能被訪問到,"管轄區"外沒這人
不污染外層作用域
缺點
因為受到了"管轄",導致有時需要訪問它時卻訪問不到
閉包引自阮一峰老師的博客 -- 學習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之間的劇情,原因及解決方案
細說了立即執行的函數表達式的概念及原理
列舉了其應用場景
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85645.html
摘要:在腳本中,默認指向一個空對象,并不是指向,也不是指向。舉個栗子,在函數執行后,覆蓋原先的值我們在外部定義了一個名為的全局變量,它會被默認添加到全局的屬性上。總結在不同的執行環境中的默認指代通過省略聲明變量導致變量提升現象的發生及預防 侃侃JavaScript中的this this為何如此多變? this總是跟它的執行上下文有關,而在JavaScript總會有開辟新的執行上...
摘要:構造函數對于被實例化的,我們稱之為構造函數,及使用關鍵字調用的,對于它們來說,會被改變,指向實例。上栗子全局賦上屬性通過關鍵字創建實例,改變函數內部指向注解通過這個栗子,我們可以看出,通過創建構造函數的實例,使得的指向改變,指向了實例本身。 用栗子說this Bug年年有,今年特別多 對于JavaScript這么靈活的語言來說,少了this怎么活! function ...
摘要:為了實現這個正義偷笑又合理的訴求,你得先學會今天要介紹的設計模式,因為你們公司的這個流程可能就是用今天這個模式設計的。狀態模式對開閉原則的支持并不太好,新增狀態時,不僅得增加狀態類,還得修改原來已經有的狀態,讓之前的狀態切換到新增的狀態。一、定義你是否經常請(偷)假(懶)?是不是對公司萬惡的請假申請流程深惡痛絕。有沒有想過偷偷改造這個萬惡的系統,從 申請->項目經理審批->部門審批->老板審...
摘要:寫在前面在一款應用的整個生命周期,我們都會談及該應用的數據安全問題。用戶的合法性與數據的可見性是數據安全中非常重要的一部分。 寫在前面 在一款應用的整個生命周期,我們都會談及該應用的數據安全問題。用戶的合法性與數據的可見性是數據安全中非常重要的一部分。但是,一方面,不同的應用對于數據的合法性和可見性要求的維度與粒度都有所區別;另一方面,以當前微服務、多服務的架構方式,如何共享Sessi...
摘要:而在中是迭代器生成器,被創造性的拿來做異步流程控制了。當執行的時候,并不執行函數體,而是返回一個迭代器。行代碼再看看文章開頭的行代碼首先生成一個迭代器,然后執行一遍,得到的是一個對象,里面再執行。 廣告招人:阿里巴巴招前端,在這里你可以享受大公司的福利和技術體系,也有小團隊的挑戰和成長空間。聯系: qingguang.meiqg at alibaba-inc.com 首先請原諒我的標題...
閱讀 2291·2023-04-26 00:01
閱讀 796·2021-10-27 14:13
閱讀 1810·2021-09-02 15:11
閱讀 3381·2019-08-29 12:52
閱讀 528·2019-08-26 12:00
閱讀 2569·2019-08-26 10:57
閱讀 3405·2019-08-26 10:32
閱讀 2848·2019-08-23 18:29