摘要:刪除對(duì)匿名函數(shù)的引用,以便釋放內(nèi)存在匿名函數(shù)從中被返回后,它的作用域鏈被初始化為包含函數(shù)的活動(dòng)對(duì)象和全局變量對(duì)象。閉包與變量我們要注意到,閉包只能取到任意變量的最后值,也就是我們保存的是活動(dòng)對(duì)象,而不是確定值。
工作中會(huì)遇到很多 this對(duì)象 指向不明的問題,你可能不止一次用過 _self = this 的寫法來傳遞this對(duì)象,它每每會(huì)讓我們覺得困惑和抓狂,我們很可能會(huì)好奇其中到底發(fā)生了什么。
一個(gè)問題現(xiàn)在先來看一個(gè)具體的問題:
var name = "The Window"; var obj = { name: "My obj", getName: function() { return this.name; } }; // 猜測(cè)下面的輸出和背后的邏輯(非嚴(yán)格模式下) object.getName(); (object.getName)(); (object.getName = object.getName)();
如果上面的三個(gè)你都能答對(duì)并知道都發(fā)生了什么,那么你對(duì)JS的this了解的比我想象的要多,可以跳過這篇文章了,如果沒答對(duì)或者不明白,那么這篇文章會(huì)告訴你并幫你梳理下相關(guān)的知識(shí)。
它們的答案是:
object.getName(); // "My Obj" (object.getName)(); // "My Obj" (object.getName = object.getName)(); // "The Window"函數(shù)的作用域
在函數(shù)被調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境及相應(yīng)的作用域鏈,然后,使用arguments以及其他命名參數(shù)的值來初始化函數(shù)的活動(dòng)對(duì)象(activation object,簡(jiǎn)稱AO)。在作用域上,函數(shù)會(huì)逐層復(fù)制自身調(diào)用點(diǎn)的函數(shù)屬性,完成作用域鏈的構(gòu)建,直到全局執(zhí)行環(huán)境。
function compare(value1, value2) { return value1 - value2; } var result = compare(5, 10);
在這段代碼中,result通過var進(jìn)行了變量聲明提升,compare通過function函數(shù)聲明提升,在代碼執(zhí)行之前我們的全局變量對(duì)象中就會(huì)有這兩個(gè)屬性。
每個(gè)執(zhí)行環(huán)境都會(huì)有一個(gè)變量對(duì)象,包含存在的所有變量的對(duì)象。全局環(huán)境的變量對(duì)象始終存在,而像compare函數(shù)這樣的局部環(huán)境的變量對(duì)象,則只在函數(shù)執(zhí)行的過程中存在。當(dāng)創(chuàng)建compare()函數(shù)時(shí),會(huì)創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈,這個(gè)作用域鏈保存在內(nèi)部的[[Scope]]屬性中。
在調(diào)用compare函數(shù)時(shí),會(huì)為它創(chuàng)建一個(gè)執(zhí)行環(huán)境,然后復(fù)制函數(shù)的[[scope]]屬性中的對(duì)象構(gòu)建起執(zhí)行環(huán)境的作用域鏈。此后,又有一個(gè)活動(dòng)對(duì)象(變量對(duì)象)被創(chuàng)建并被推入執(zhí)行環(huán)境作用域鏈的前端。此時(shí)作用域鏈包含兩個(gè)變量對(duì)象:本地活動(dòng)對(duì)象和全局變量對(duì)象。顯然,作用域鏈本質(zhì)上是一個(gè)指向變量對(duì)象的指針列表,它只引用但不包含實(shí)際的變量對(duì)象。
當(dāng)訪問函數(shù)的變量時(shí),就會(huì)從作用域鏈中搜索。當(dāng)函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷毀,內(nèi)存中僅保存全局作用域。
閉包但是,閉包的情況有所不同,在一個(gè)函數(shù)內(nèi)部定義的函數(shù)會(huì)將外部函數(shù)的活動(dòng)對(duì)象添加到它的作用域鏈中去。
function create(property) { return function(object1, object2) { console.log(object1[property], object2[property]); }; } var compare = create("name"); var result = compare({name: "Nicholas"}, {name: "Greg"}); // Nicholas Greg // 刪除對(duì)匿名函數(shù)的引用,以便釋放內(nèi)存 compare = null;
在匿名函數(shù)從create()中被返回后,它的作用域鏈被初始化為包含create()函數(shù)的活動(dòng)對(duì)象和全局變量對(duì)象。這樣,該匿名函數(shù)就可以訪問create中定義的所有遍歷,更為重要的是當(dāng)create()函數(shù)執(zhí)行完畢后,其作用域鏈被銷毀,但是活動(dòng)對(duì)象不會(huì)銷毀,因?yàn)橐廊槐荒涿瘮?shù)引用。當(dāng)匿名函數(shù)別compare()被銷毀后,create()的活動(dòng)對(duì)象才會(huì)被銷毀。
閉包與變量我們要注意到,閉包只能取到任意變量的最后值,也就是我們保存的是活動(dòng)對(duì)象,而不是確定值。
function create() { var result = []; for (var i = 0; i < 10; i++) { result[i] = function() { return i; }; } return result; } create()[3](); // 10
我們通過閉包,讓每一個(gè)result的元素都能夠返回i的值,但是閉包包含的是同一個(gè)活動(dòng)對(duì)象i,而不是固定的1-10的值,所以返回的都是10。但我們可以通過值傳遞的方式創(chuàng)建另外一個(gè)匿名函數(shù)來滿足我們的需求。
function create() { var result = []; for (var i = 0; i < 10; i++) { // 通過值傳遞的方式固定i值 result[i] = function(num) { // 這里閉包固定后的i值,即num值,來滿足我們的需求 return function() { return num; }; }(i); } return result; } create()[3](); // 3閉包與this
我們知道this對(duì)象是基于函數(shù)的執(zhí)行環(huán)境綁定的,在全局的時(shí)候,this等于window,而當(dāng)函數(shù)作為某個(gè)對(duì)象的方法調(diào)用時(shí),this等于那個(gè)對(duì)象。不過,匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此this常常指向window。
var name = "The Window"; var obj = { name: "My obj", getName: function() { return function() { return this.name; }; } }; obj.getName()(); // "The Window"
前面說過,函數(shù)在被調(diào)用時(shí)會(huì)自動(dòng)取得兩個(gè)特殊變量: this和arguments,內(nèi)部函數(shù)在搜索這兩個(gè)變量時(shí),只會(huì)搜索到其活動(dòng)對(duì)象,所以永遠(yuǎn)不會(huì)訪問到外部函數(shù)的這兩個(gè)變量。如果我們想滿足需求,可以固定this對(duì)象并更名即可。
var name = "The Window"; var obj = { name: "My obj", getName: function() { // 固定this對(duì)象,形成閉包,防止跟特殊的this重名 var that = this; return function() { return that.name; }; } }; obj.getName()(); // "My obj"this的綁定
上面對(duì)this的說明可以說是非常的淺薄了,現(xiàn)在我們?cè)敿?xì)的整理下this關(guān)鍵字,它是函數(shù)作用域的特殊關(guān)鍵字,進(jìn)入函數(shù)執(zhí)行環(huán)境時(shí)會(huì)被自動(dòng)定義,實(shí)現(xiàn)原理相當(dāng)于自動(dòng)傳遞調(diào)用點(diǎn)的對(duì)象:
var obj = { name: "Nicholas", speak() { return this.name; }, anotherSpeak(context) { console.log(context.name, context === this); } }; obj.name; //"Nicholas" obj.speak(); // "Nicholas" obj.anotherSpeak(obj); // "Nicholas" true
可以看到,我們?cè)赼notherSpeak()中傳遞的context就是obj,也就是函數(shù)調(diào)用時(shí),執(zhí)行環(huán)境的this值。引擎的這種實(shí)現(xiàn)簡(jiǎn)化了我們的工作,自動(dòng)傳遞調(diào)用點(diǎn)的環(huán)境對(duì)象作為this對(duì)象。
我們要注意的是this只跟調(diào)用點(diǎn)有關(guān),而跟聲明點(diǎn)無關(guān)。這里你需要知道調(diào)用棧,也就是使我們到達(dá)當(dāng)前執(zhí)行位置而被調(diào)用的所有方法的棧,即所有嵌套的函數(shù)棧。
function baz() { // 調(diào)用棧是: `baz` // 我們的調(diào)用點(diǎn)是global scope(全局作用域) console.log( "baz" ); bar(); // <-- `bar`的調(diào)用點(diǎn) } function bar() { // 調(diào)用棧是: `baz` -> `bar` // 我們的調(diào)用點(diǎn)位于`baz` console.log( "bar" ); foo(); // <-- `foo`的調(diào)用點(diǎn) } function foo() { // 調(diào)用棧是: `baz` -> `bar` -> `foo` // 我們的調(diào)用點(diǎn)位于`bar` console.log( "foo" ); } baz(); // <-- `baz`的調(diào)用點(diǎn)
我們整理了四種this對(duì)象綁定的規(guī)則:
默認(rèn)綁定function foo() { console.log( this.a, this === window ); } var a = 2; window.a; // 2 foo(); // 2 true
在這種規(guī)則下,函數(shù)調(diào)用為獨(dú)立的毫無修飾的函數(shù)引用調(diào)用的,此時(shí)foo的調(diào)用環(huán)境就是全局環(huán)境window,所以this就指向window,而在全局下聲明的所有對(duì)象都屬于window,導(dǎo)致結(jié)果為2。
但是在嚴(yán)格模式下,this不會(huì)被默認(rèn)綁定到全局對(duì)象。MDN文檔上寫到:
第一,在嚴(yán)格模式下通過this傳遞給一個(gè)函數(shù)的值不會(huì)被強(qiáng)制轉(zhuǎn)換為一個(gè)對(duì)象。對(duì)一個(gè)普通的函數(shù)來說,this總會(huì)是一個(gè)對(duì)象:不管調(diào)用時(shí)this它本來就是一個(gè)對(duì)象;還是用布爾值,字符串或者數(shù)字調(diào)用函數(shù)時(shí)函數(shù)里面被封裝成對(duì)象的this;還是使用undefined或者null調(diào)用函數(shù)式this代表的全局對(duì)象(使用call, apply或者bind方法來指定一個(gè)確定的this)。這種自動(dòng)轉(zhuǎn)化為對(duì)象的過程不僅是一種性能上的損耗,同時(shí)在瀏覽器中暴露出全局對(duì)象也會(huì)成為安全隱患,因?yàn)槿謱?duì)象提供了訪問那些所謂安全的JavaScript環(huán)境必須限制的功能的途徑。所以對(duì)于一個(gè)開啟嚴(yán)格模式的函數(shù),指定的this不再被封裝為對(duì)象,而且如果沒有指定this的話它值是undefined。
function foo() { "use strict"; console.log( this ); } foo(); // undefined
關(guān)于嚴(yán)格模式還需要注意的是,它的作用范圍只有當(dāng)前的函數(shù)或者標(biāo)簽內(nèi)部,而不包括嵌套的函數(shù)體:
function foo() { console.log( this.a ); } var a = 2; (function(){ "use strict"; foo(); // 2 })();隱含綁定
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
在這個(gè)函數(shù)調(diào)用時(shí),其調(diào)用點(diǎn)為環(huán)境對(duì)象obj,所以函數(shù)執(zhí)行時(shí),this指向obj。
需要注意多重嵌套的函數(shù)引用,在調(diào)用時(shí)只考慮最后一層:
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42
如果函數(shù)并不直接執(zhí)行,而是先引用后執(zhí)行,那么我們應(yīng)該明白,該變量獲得的是另一個(gè)指向該函數(shù)對(duì)象的指針,而脫離了引用的環(huán)境,所以自然失去了this的綁定,這被稱為隱含綁定的丟失:
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; // 函數(shù)引用!其實(shí)得到的是另一個(gè)指向該函數(shù)的指針,脫離了obj環(huán)境 var bar = obj.foo; var a = "oops, global"; bar(); // "oops, global"明確綁定
我們除了上面的兩種默認(rèn)綁定方式,還可以對(duì)其進(jìn)行明確的綁定,主要通過函數(shù)內(nèi)置的call/apply/bind方法,通過它們可以指定你想要的this對(duì)象是什么:
function foo() { console.log( this.a ); } var obj = { a: 2 }; foo.call( obj ); // 2
我們給foo的調(diào)用指定了obj作為它的this對(duì)象,所以this.a即obj.a,結(jié)果為2。
call/apply方法需要傳遞一個(gè)對(duì)象,如果你傳遞的為簡(jiǎn)單原始類型值null,undefined,則this會(huì)指向全局對(duì)象。如果傳遞的為基本包裝對(duì)象,則this會(huì)指向他們的自動(dòng)包裝對(duì)象,即new String(), new Boolean(), new Number(),這個(gè)過程稱為封箱(boxing)。
這里我們應(yīng)該清楚call/apply方法都只在最后一層嵌套生效,所以我們稱呼它為明確綁定:
function foo() { console.log( this.a ); } var obj = { a: 2 }; var bar = function() { foo.call( obj ); }; // `bar`將`foo`的`this`硬綁定到`obj`, 所以它不可以被覆蓋 bar.call( window ); // 2
但如果我們想復(fù)用并返回一個(gè)新函數(shù),并固定this值時(shí),可以這樣做:
function foo(something) { console.log( this.a, something ); return this.a + something; } // 簡(jiǎn)單的`bind`幫助函數(shù) function bind(fn, obj) { return function() { return fn.apply( obj, arguments ); }; } var obj = { a: 2 }; var bar = bind( foo, obj ); var b = bar( 3 ); // 2 3 console.log( b ); // 5
這種方式被稱為硬綁定,也是明確綁定的一種,這個(gè)函數(shù)在被創(chuàng)建時(shí)就已經(jīng)明確的聲明了作用域,也就是該對(duì)象被放置在了[[Scope]]屬性里。這種方式有時(shí)很常用,所以被內(nèi)置在ES5后的版本里,其內(nèi)部實(shí)現(xiàn)(Polyfill低版本補(bǔ)丁)為:
if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== "function") { // 可能的與 ECMAScript 5 內(nèi)部的 IsCallable 函數(shù)最接近的東西 throw new TypeError( "Function.prototype.bind - what " + "is trying to be bound is not callable" ); } var aArgs = Array.prototype.slice.call( arguments, 1 ), fToBind = this, fNOP = function(){}, fBound = function(){ return fToBind.apply( ( this instanceof fNOP && oThis ? this : oThis ), aArgs.concat( Array.prototype.slice.call( arguments ) ) ); } ; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }
在ES6里,bind()生成的硬綁定函數(shù)擁有一個(gè)name屬性,源自于目標(biāo)函數(shù),此時(shí)顯示為bound foo。
在一些語言內(nèi)置的函數(shù)里,提供了可選參數(shù)作為函數(shù)執(zhí)行時(shí)的this對(duì)象,這些函數(shù)的內(nèi)部實(shí)現(xiàn)方式和bind()類似,也是通過apply/call來明確綁定了你傳遞的參數(shù)作為this對(duì)象,如:
var obj = { name: "Nicholas" }; [1,2,3].forEach(function(item) { console.log(item, this.name); }, obj); // 1 "Nicholas" // 2 "Nicholas" // 3 "Nicholas"new綁定
new操作符會(huì)調(diào)用對(duì)象的構(gòu)造器函數(shù)來初始化類成為一個(gè)實(shí)例。它的執(zhí)行過程為:
一個(gè)全新的對(duì)象被憑空創(chuàng)建
這個(gè)新構(gòu)造的對(duì)象被接入原型鏈(__proto__指向該構(gòu)造函數(shù)的prototype)
這個(gè)新構(gòu)造的對(duì)象被綁定為函數(shù)調(diào)用的this對(duì)象
除非函數(shù)返回一個(gè)其它對(duì)象,這個(gè)被new調(diào)用的函數(shù)將返回這個(gè)新構(gòu)建的對(duì)象。
實(shí)質(zhì)上加new關(guān)鍵字和()只不過是該函數(shù)的不同調(diào)用方式而已,前者為構(gòu)造器調(diào)用,后者為執(zhí)行調(diào)用,在調(diào)用過程中,this指向不同,返回值不同。
在new綁定的規(guī)則中,this指向新創(chuàng)建的對(duì)象。
箭頭函數(shù)綁定現(xiàn)在我們看一個(gè)十分特別的this綁定,ES6中加入的箭頭函數(shù),前面的四種都是函數(shù)執(zhí)行時(shí)通過調(diào)用點(diǎn)確認(rèn)this對(duì)象,而箭頭函數(shù)是在詞法作用域確定this對(duì)象,即在詞法解析到該箭頭時(shí)為該函數(shù)綁定this對(duì)象為當(dāng)前對(duì)象:
function foo() { setTimeout(() => { // 這里的`this`是詞法上從`foo()`采用 console.log( this.a ); },100); } var obj = { a: 2 }; foo.call( obj ); // 2綁定的優(yōu)先級(jí)
通過一些具體的實(shí)例對(duì)比,我們可以得出不同綁定方式的優(yōu)先級(jí):
new綁定 > 明確綁定 > 隱含綁定 > 默認(rèn)綁定。
箭頭函數(shù)屬于詞法作用域綁定,所以其優(yōu)先級(jí)更高,但是跟上面的不沖突。
最初的問題現(xiàn)在我們?cè)賮砜聪伦畛醯膯栴}:
var name = "The Window"; var obj = { name: "My obj", getName: function() { return this.name; } }; // 猜測(cè)下面的輸出和背后的邏輯(非嚴(yán)格模式下) obj.getName(); // "My obj" (obj.getName)(); // "My obj" (obj.getName = obj.getName)(); // "The Window"
我們可以看出第一個(gè)直接綁定this對(duì)象為obj,第二個(gè)加上括號(hào)好像是引用了一個(gè)函數(shù),但object.getName與(object.getName)定義一致,所以this依然指向obj;第三個(gè)賦值語句會(huì)返回函數(shù)本身,所以作為匿名函數(shù)來執(zhí)行,就會(huì)返回"The Window"。
參考資料簡(jiǎn)書 - this與對(duì)象原型: http://www.jianshu.com/p/11d8...
MDN - bind: https://developer.mozilla.org...
Github - 深入變量對(duì)象:https://github.com/mqyqingfen...
JS高級(jí)程序設(shè)計(jì):第五章(引用類型),第七章(函數(shù)表達(dá)式)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/88967.html
摘要:理解的函數(shù)基礎(chǔ)要搞好深入淺出原型使用原型模型,雖然這經(jīng)常被當(dāng)作缺點(diǎn)提及,但是只要善于運(yùn)用,其實(shí)基于原型的繼承模型比傳統(tǒng)的類繼承還要強(qiáng)大。中文指南基本操作指南二繼續(xù)熟悉的幾對(duì)方法,包括,,。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。 怎樣使用 this 因?yàn)楸救藢儆趥吻岸耍虼宋闹兄豢炊?8 成左右,希望能夠給大家?guī)韼椭?...(據(jù)說是阿里的前端妹子寫的) this 的值到底...
摘要:跨域請(qǐng)求詳解從繁至簡(jiǎn)前端掘金什么是為什么要用是的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題。異步編程入門道典型的面試題前端掘金在界中,開發(fā)人員的需求量一直居高不下。 jsonp 跨域請(qǐng)求詳解——從繁至簡(jiǎn) - 前端 - 掘金什么是jsonp?為什么要用jsonp?JSONP(JSON with Padding)是JSON的一種使用模式,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題...
摘要:不是引用類型,無法輸出簡(jiǎn)而言之,堆內(nèi)存存放引用值,棧內(nèi)存存放固定類型值。變量的查詢?cè)谧兞康牟樵冎校L問局部變量要比全局變量來得快,因此不需要向上搜索作用域鏈。 贊助我以寫出更好的文章,give me a cup of coffee? 2017最新最全前端面試題 基本類型值有:undefined,NUll,Boolean,Number和String,這些類型分別在內(nèi)存中占有固定的大小空...
摘要:但閉包的情況不同嵌套函數(shù)的閉包執(zhí)行后,,然后還在被回收閉包會(huì)使變量始終保存在內(nèi)存中,如果不當(dāng)使用會(huì)增大內(nèi)存消耗。每個(gè)函數(shù),不論多深,都可以認(rèn)為是全局的子作用域,可以理解為閉包。 閉包(closure)是Javascript語言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。 閉包的特性 閉包有三個(gè)特性: 1.函數(shù)嵌套函數(shù) 2.函數(shù)內(nèi)部可以引用外部的參數(shù)和變量 3.參數(shù)和變量不會(huì)...
摘要:個(gè)人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現(xiàn)在已經(jīng)一年的時(shí)間了,由于工作比較忙,更新緩慢,后面還是會(huì)繼更新,現(xiàn)將已經(jīng)寫好的文章整理一個(gè)目錄,方便更多的小伙伴去學(xué)習(xí)。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個(gè)人前端文章整理 從最開始萌生寫文章的想法,到著手...
閱讀 2645·2021-09-13 10:26
閱讀 1907·2021-09-03 10:28
閱讀 1977·2019-08-30 15:44
閱讀 794·2019-08-29 14:07
閱讀 385·2019-08-29 13:12
閱讀 2143·2019-08-26 11:44
閱讀 2336·2019-08-26 11:36
閱讀 2003·2019-08-26 10:19