這篇文章主要為大家展示了“JavaScript中this陷阱有哪些”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“JavaScript中this陷阱有哪些”這篇文章吧。
網(wǎng)站設(shè)計、成都網(wǎng)站制作介紹好的網(wǎng)站是理念、設(shè)計和技術(shù)的結(jié)合。創(chuàng)新互聯(lián)建站擁有的網(wǎng)站設(shè)計理念、多方位的設(shè)計風(fēng)格、經(jīng)驗豐富的設(shè)計團(tuán)隊。提供PC端+手機(jī)端網(wǎng)站建設(shè),用營銷思維進(jìn)行網(wǎng)站設(shè)計、采用先進(jìn)技術(shù)開源代碼、注重用戶體驗與SEO基礎(chǔ),將技術(shù)與創(chuàng)意整合到網(wǎng)站之中,以契合客戶的方式做到創(chuàng)意性的視覺化效果。JavaScript中很多時候會用到this,下面詳細(xì)介紹每一種情況。在這里我想首先介紹一下宿主環(huán)境這個概念。一門語言在運(yùn)行的時候,需要一個環(huán)境,叫做宿主環(huán)境。對于JavaScript,宿主環(huán)境最常見的是web瀏覽器,瀏覽器提供了一個JavaScript運(yùn)行的環(huán)境,這個環(huán)境里面,需要提供一些接口,好讓JavaScript引擎能夠和宿主環(huán)境對接。JavaScript引擎才是真正執(zhí)行JavaScript代碼的地方,常見的引擎有V8(目前最快JavaScript引擎、Google生產(chǎn))、JavaScript core。JavaScript引擎主要做了下面幾件事情:
一套與宿主環(huán)境相聯(lián)系的規(guī)則;
JavaScript引擎內(nèi)核(基本語法規(guī)范、邏輯、命令和算法);
一組內(nèi)置對象和API;
其他約定。
但是環(huán)境不是唯一的,也就是JavaScript不僅僅能夠在瀏覽器里面跑,也能在其他提供了宿主環(huán)境的程序里面跑,最常見的就是nodejs。同樣作為一個宿主環(huán)境,nodejs也有自己的JavaScript引擎--V8。根據(jù)官方的定義:
Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications
global this
在瀏覽器里,在全局范圍內(nèi),this等價于window對象。
在瀏覽器里,在全局范圍內(nèi),用var聲明一個變量和給this或者window添加屬性是等價的。
如果你在聲明一個變量的時候沒有使用var或者let(ECMAScript 6),你就是在給全局的this添加或者改變屬性值。
在node環(huán)境里,如果使用REPL(Read-Eval-Print Loop,簡稱REPL:讀取-求值-輸出,是一個簡單的,交互式的編程環(huán)境)來執(zhí)行程序,this并不是最高級的命名空間,最高級的是global.
> this { ArrayBuffer: [Function: ArrayBuffer], Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1 }, Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1 }, ... > global === this true
在node環(huán)境里,如果執(zhí)行一個js腳本,在全局范圍內(nèi),this以一個空對象開始作為最高級的命名空間,這個時候,它和global不是等價的。
test.js腳本內(nèi)容:
console.log(this); console.log(this === global);
REPL運(yùn)行腳本:
$ node test.js {} false
在node環(huán)境里,在全局范圍內(nèi),如果你用REPL執(zhí)行一個腳本文件,用var聲明一個變量并不會和在瀏覽器里面一樣將這個變量添加給this。
test.js: var foo = "bar"; console.log(this.foo); $ node test.js undefined
但是如果你不是用REPL執(zhí)行腳本文件,而是直接執(zhí)行代碼,結(jié)果和在瀏覽器里面是一樣的(神坑)
> var foo = "bar"; > this.foo bar > global.foo bar
在node環(huán)境里,用REPL運(yùn)行腳本文件的時候,如果在聲明變量的時候沒有使用var或者let,這個變量會自動添加到global對象,但是不會自動添加給this對象。如果是直接執(zhí)行代碼,則會同時添加給global和this
test.js foo = "bar"; console.log(this.foo); console.log(global.foo); $ node test.js undefined bar
上面的八種情況可能大家已經(jīng)繞暈了,總結(jié)起來就是:在瀏覽器里面this是老大,它等價于window對象,如果你聲明一些全局變量(不管在任何地方),這些變量都會作為this的屬性。在node里面,有兩種執(zhí)行JavaScript代碼的方式,一種是直接執(zhí)行寫好的JavaScript文件,另外一種是直接在里面執(zhí)行一行行代碼。對于直接運(yùn)行一行行JavaScript代碼的方式,global才是老大,this和它是等價的。在這種情況下,和瀏覽器比較相似,也就是聲明一些全局變量會自動添加給老大global,順帶也會添加給this。但是在node里面直接腳本文件就不一樣了,你聲明的全局變量不會自動添加到this,但是會添加到global對象。所以相同點(diǎn)是,在全局范圍內(nèi),全局變量終究是屬于老大的。
function this
無論是在瀏覽器環(huán)境還是node環(huán)境, 除了在DOM事件處理程序里或者給出了thisArg(接下來會講到)外,如果不是用new調(diào)用,在函數(shù)里面使用this都是指代全局范圍的this。
test.js foo = "bar"; function testThis () { this.foo = "foo"; } console.log(global.foo); testThis(); console.log(global.foo); $ node test.js bar foo
除非你使用嚴(yán)格模式,這時候this就會變成undefined。
如果你在調(diào)用函數(shù)的時候在前面使用了new,this就會變成一個新的值,和global的this脫離干系。
我更喜歡把新的值稱作一個實(shí)例。
函數(shù)里面的this其實(shí)相對比較好理解,如果我們在一個函數(shù)里面使用this,需要注意的就是我們調(diào)用函數(shù)的方式,如果是正常的方式調(diào)用函數(shù),this指代全局的this,如果我們加一個new,這個函數(shù)就變成了一個構(gòu)造函數(shù),我們就創(chuàng)建了一個實(shí)例,this指代這個實(shí)例,這個和其他面向?qū)ο蟮恼Z言很像。另外,寫JavaScript很常做的一件事就是綁定事件處理程序,也就是諸如button.addEventListener(‘click', fn, false)之類的,如果在fn里面需要使用this,this指代事件處理程序?qū)?yīng)的對象,也就是button。
prototype this
你創(chuàng)建的每一個函數(shù)都是函數(shù)對象。它們會自動獲得一個特殊的屬性prototype,你可以給這個屬性賦值。當(dāng)你用new的方式調(diào)用一個函數(shù)的時候,你就能通過this訪問你給prototype賦的值了。
function Thing() { console.log(this.foo); } Thing.prototype.foo = "bar"; var thing = new Thing(); //logs "bar" console.log(thing.foo); //logs "bar"
當(dāng)你使用new為你的函數(shù)創(chuàng)建多個實(shí)例的時候,這些實(shí)例會共享你給prototype設(shè)定的值。對于下面的例子,當(dāng)你調(diào)用this.foo的時候,都會返回相同的值,除非你在某個實(shí)例里面重寫了自己的this.foo
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { console.log(this.foo); } Thing.prototype.setFoo = function (newFoo) { this.foo = newFoo; } var thing1 = new Thing(); var thing2 = new Thing(); thing1.logFoo(); //logs "bar" thing2.logFoo(); //logs "bar" thing1.setFoo("foo"); thing1.logFoo(); //logs "foo"; thing2.logFoo(); //logs "bar"; thing2.foo = "foobar"; thing1.logFoo(); //logs "foo"; thing2.logFoo(); //logs "foobar";
實(shí)例里面的this是一個特殊的對象。你可以把this想成一種獲取prototype的值的一種方式。當(dāng)你在一個實(shí)例里面直接給this添加屬性的時候,會隱藏prototype中與之同名的屬性。如果你想訪問prototype中的這個屬性值而不是你自己設(shè)定的屬性值,你可以通過在實(shí)例里面刪除你自己添加的屬性的方式來實(shí)現(xiàn)。
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { console.log(this.foo); } Thing.prototype.setFoo = function (newFoo) { this.foo = newFoo; } Thing.prototype.deleteFoo = function () { delete this.foo; } var thing = new Thing(); thing.setFoo("foo"); thing.logFoo(); //logs "foo"; thing.deleteFoo(); thing.logFoo(); //logs "bar"; thing.foo = "foobar"; thing.logFoo(); //logs "foobar"; delete thing.foo; thing.logFoo(); //logs "bar";
或者你也能直接通過引用函數(shù)對象的prototype 來獲得你需要的值。
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { console.log(this.foo, Thing.prototype.foo); } var thing = new Thing(); thing.foo = "foo"; thing.logFoo(); //logs "foo bar";
通過一個函數(shù)創(chuàng)建的實(shí)例會共享這個函數(shù)的prototype屬性的值,如果你給這個函數(shù)的prototype賦值一個Array,那么所有的實(shí)例都會共享這個Array,除非你在實(shí)例里面重寫了這個Array,這種情況下,函數(shù)的prototype的Array就會被隱藏掉。
function Thing() { } Thing.prototype.things = []; var thing1 = new Thing(); var thing2 = new Thing(); thing1.things.push("foo"); console.log(thing2.things); //logs ["foo"]
給一個函數(shù)的prototype賦值一個Array通常是一個錯誤的做法。如果你想每一個實(shí)例有他們專屬的Array,你應(yīng)該在函數(shù)里面創(chuàng)建而不是在prototype里面創(chuàng)建。
function Thing() { this.things = []; } var thing1 = new Thing(); var thing2 = new Thing(); thing1.things.push("foo"); console.log(thing1.things); //logs ["foo"] console.log(thing2.things); //logs []
實(shí)際上你可以通過把多個函數(shù)的prototype鏈接起來的從而形成一個原型鏈,因此this就會魔法般地沿著這條原型鏈往上查找直到找你你需要引用的值。
function Thing1() { } Thing1.prototype.foo = "bar"; function Thing2() { } Thing2.prototype = new Thing1(); var thing = new Thing2(); console.log(thing.foo); //logs "bar"
一些人利用原型鏈的特性來在JavaScript模仿經(jīng)典的面向?qū)ο蟮睦^承方式。任何給用于構(gòu)建原型鏈的函數(shù)的this的賦值的語句都會隱藏原型鏈上游的相同的屬性。
function Thing1() { } Thing1.prototype.foo = "bar"; function Thing2() { this.foo = "foo"; } Thing2.prototype = new Thing1(); function Thing3() { } Thing3.prototype = new Thing2(); var thing = new Thing3(); console.log(thing.foo); //logs "foo"
我喜歡把被賦值給prototype的函數(shù)叫做方法。在上面的例子中,我已經(jīng)使用過方法了,如logFoo。這些方法有著相同的prototype,即創(chuàng)建這些實(shí)力的原始函數(shù)。我通常把這些原始函數(shù)叫做構(gòu)造函數(shù)。在prototype里面定義的方法里面使用this會影響到當(dāng)前實(shí)例的原型鏈的上游的this。這意味著你直接給this賦值的時候,隱藏了原型鏈上游的相同的屬性值。這個實(shí)例的任何方法都會使用這個最新的值而不是原型里面定義的這個相同的值。
function Thing1() { } Thing1.prototype.foo = "bar"; Thing1.prototype.logFoo = function () { console.log(this.foo); } function Thing2() { this.foo = "foo"; } Thing2.prototype = new Thing1(); var thing = new Thing2(); thing.logFoo(); //logs "foo";
在JavaScript里面你可以嵌套函數(shù),也就是你可以在函數(shù)里面定義函數(shù)。嵌套函數(shù)可以通過閉包捕獲父函數(shù)的變量,但是這個函數(shù)沒有繼承this
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { var info = "attempting to log this.foo:"; function doIt() { console.log(info, this.foo); } doIt(); } var thing = new Thing(); thing.logFoo(); //logs "attempting to log this.foo: undefined"
在doIt里面的this是global對象或者在嚴(yán)格模式下面是undefined。這是造成很多不熟悉JavaScript的人深陷 this陷阱的根源。在這種情況下事情變得非常糟糕,就像你把一個實(shí)例的方法當(dāng)作一個值,把這個值當(dāng)作函數(shù)參數(shù)傳遞給另外一個函數(shù)但是卻不把這個實(shí)例傳遞給這個函數(shù)一樣。在這種情況下,一個方法里面的環(huán)境變成了全局范圍,或者在嚴(yán)格模式下面的undefined。
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { console.log(this.foo); } function doIt(method) { method(); } var thing = new Thing(); thing.logFoo(); //logs "bar" doIt(thing.logFoo); //logs undefined
一些人喜歡先把this捕獲到一個變量里面,通常這個變量叫做self,來避免上面這種情況的發(fā)生。
博主非常喜歡用這種方式
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { var self = this; var info = "attempting to log this.foo:"; function doIt() { console.log(info, self.foo); } doIt(); } var thing = new Thing(); thing.logFoo(); //logs "attempting to log this.foo: bar"
但是當(dāng)你需要把一個方法作為一個值傳遞給一個函數(shù)的時候并不管用。
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { var self = this; function doIt() { console.log(self.foo); } doIt(); } function doItIndirectly(method) { method(); } var thing = new Thing(); thing.logFoo(); //logs "bar" doItIndirectly(thing.logFoo); //logs undefined
你可以通過bind將實(shí)例和方法一切傳遞給函數(shù)來解決這個問題,bind是一個函數(shù)定義在所有函數(shù)和方法的函數(shù)對象上面
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { console.log(this.foo); } function doIt(method) { method(); } var thing = new Thing(); doIt(thing.logFoo.bind(thing)); //logs bar
你同樣可以使用apply和call來在新的上下文中調(diào)用方法或函數(shù)。
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { function doIt() { console.log(this.foo); } doIt.apply(this); } function doItIndirectly(method) { method(); } var thing = new Thing(); doItIndirectly(thing.logFoo.bind(thing)); //logs bar
你可以用bind來代替任何一個函數(shù)或者方法的this,即便它沒有賦值給實(shí)例的初始prototype。
function Thing() { } Thing.prototype.foo = "bar"; function logFoo(aStr) { console.log(aStr, this.foo); } var thing = new Thing(); logFoo.bind(thing)("using bind"); //logs "using bind bar" logFoo.apply(thing, ["using apply"]); //logs "using apply bar" logFoo.call(thing, "using call"); //logs "using call bar" logFoo("using nothing"); //logs "using nothing undefined"
你應(yīng)該避免在構(gòu)造函數(shù)里面返回任何東西,因為這可能代替本來應(yīng)該返回的實(shí)例。
function Thing() { return {}; } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { console.log(this.foo); } var thing = new Thing(); thing.logFoo(); //Uncaught TypeError: undefined is not a function
奇怪的是,如果你在構(gòu)造函數(shù)里面返回了一個原始值,上面所述的情況并不會發(fā)生并且返回語句被忽略了。最好不要在你將通過new調(diào)用的構(gòu)造函數(shù)里面返回任何類型的數(shù)據(jù),即便你知道自己正在做什么。如果你想創(chuàng)建一個工廠模式,通過一個函數(shù)來創(chuàng)建一個實(shí)例,這個時候不要使用new來調(diào)用函數(shù)。當(dāng)然這個建議是可選的。
你可以通過使用Object.create來避免使用new,這樣同樣能夠創(chuàng)建一個實(shí)例。
function Thing() { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { console.log(this.foo); } var thing = Object.create(Thing.prototype); thing.logFoo(); //logs "bar"
在這種情況下并不會調(diào)用構(gòu)造函數(shù)
function Thing() { this.foo = "foo"; } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { console.log(this.foo); } var thing = Object.create(Thing.prototype); thing.logFoo(); //logs "bar"
因為Object.create不會調(diào)用構(gòu)造函數(shù)的特性在你繼承模式下你想通過原型鏈重寫構(gòu)造函數(shù)的時候非常有用。
function Thing1() { this.foo = "foo"; } Thing1.prototype.foo = "bar"; function Thing2() { this.logFoo(); //logs "bar" Thing1.apply(this); this.logFoo(); //logs "foo" } Thing2.prototype = Object.create(Thing1.prototype); Thing2.prototype.logFoo = function () { console.log(this.foo); } var thing = new Thing2();
object this
在一個對象的一個函數(shù)里,你可以通過this來引用這個對象的其他屬性。這個用new來新建一個實(shí)例是不一樣的。
var obj = { foo: "bar", logFoo: function () { console.log(this.foo); } }; obj.logFoo(); //logs "bar"
注意,沒有使用new,沒有使用Object.create,也沒有使用函數(shù)調(diào)用創(chuàng)建一個對象。你也可以將對象當(dāng)作一個實(shí)例將函數(shù)綁定到上面。
var obj = { foo: "bar" }; function logFoo() { console.log(this.foo); } logFoo.apply(obj); //logs "bar"
當(dāng)你用這種方式使用this的時候,并不會越出當(dāng)前的對象。只有有相同直接父元素的屬性才能通過this共享變量
var obj = { foo: "bar", deeper: { logFoo: function () { console.log(this.foo); } } }; obj.deeper.logFoo(); //logs undefined
你可以直接通過對象引用你需要的屬性
var obj = { foo: "bar", deeper: { logFoo: function () { console.log(obj.foo); } } }; obj.deeper.logFoo(); //logs "bar"
DOM event this
在一個HTML DOM事件處理程序里面,this始終指向這個處理程序被所綁定到的HTML DOM節(jié)點(diǎn)
function Listener() { document.getElementById("foo").addEventListener("click", this.handleClick); } Listener.prototype.handleClick = function (event) { console.log(this); //logs "" } var listener = new Listener(); document.getElementById("foo").click();
除非你自己通過bind切換了上下文
function Listener() { document.getElementById("foo").addEventListener("click", this.handleClick.bind(this)); } Listener.prototype.handleClick = function (event) { console.log(this); //logs Listener {handleClick: function} } var listener = new Listener(); document.getElementById("foo").click();
HTML this
在HTML節(jié)點(diǎn)的屬性里面,你可以放置JavaScript代碼,this指向了這個元素
override this
你不能重寫this,因為它是保留字。
function test () { var this = {}; // Uncaught SyntaxError: Unexpected token this }
eval this
你可以通過eval來訪問this
function Thing () { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { eval("console.log(this.foo)"); //logs "bar" } var thing = new Thing(); thing.logFoo();
這會造成一個安全問題,除非不用eval,沒有其他方式來避免這個問題。
在通過Function來創(chuàng)建一個函數(shù)的時候,同樣能夠訪問this
function Thing () { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = new Function("console.log(this.foo);"); var thing = new Thing(); thing.logFoo(); //logs "bar"
with this
你可以通過with來將this添加到當(dāng)前的執(zhí)行環(huán)境,并且讀寫this的屬性的時候不需要通過this
function Thing () { } Thing.prototype.foo = "bar"; Thing.prototype.logFoo = function () { with (this) { console.log(foo); foo = "foo"; } } var thing = new Thing(); thing.logFoo(); // logs "bar" console.log(thing.foo); // logs "foo"
許多人認(rèn)為這樣使用是不好的因為with本身就飽受爭議。
jQuery this
和HTML DOM元素節(jié)點(diǎn)的事件處理程序一樣,在許多情況下JQuery的this都指向HTML元素節(jié)點(diǎn)。這在事件處理程序和一些方便的方法中都是管用的,比如$.each
thisArg this
如果你用過underscore.js 或者 lo-dash 你可能知道許多類庫的方法可以通過一個叫做thisArg 的函數(shù)參數(shù)來傳遞實(shí)例,這個函數(shù)參數(shù)會作為this的上下文。舉個例子,這適用于_.each。原生的JavaScript在ECMAScript 5的時候也允許函數(shù)傳遞一個thisArg參數(shù)了,比如forEach。事實(shí)上,之前闡述的bind,apply和call的使用已經(jīng)給你創(chuàng)造了傳遞thisArg參數(shù)給函數(shù)的機(jī)會。這個參數(shù)將this綁定為你所傳遞的對象。
function Thing(type) { this.type = type; } Thing.prototype.log = function (thing) { console.log(this.type, thing); } Thing.prototype.logThings = function (arr) { arr.forEach(this.log, this); // logs "fruit apples..." _.each(arr, this.log, this); //logs "fruit apples..." } var thing = new Thing("fruit"); thing.logThings(["apples", "oranges", "strawberries", "bananas"]);
這使得代碼變得更加簡介,因為避免了一大堆bind語句、函數(shù)嵌套和this暫存的使用。
以上是“JavaScript中this陷阱有哪些”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司行業(yè)資訊頻道!
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)建站www.cdcxhl.com,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。