摘要:構造函數(shù)調用構造函數(shù)調用將一個全新的對象作為變量的值,并隱式返回這個新對象作為調用結果。調用方式引起的改變函數(shù)的調用方式最常見的是方法調用構造函數(shù)調用,或者使用調用,也可以是立即執(zhí)行函數(shù)。
全局作用域JavaScript的this機制很復雜,雖然從一開始從事前端工作就和它打交道,但一直未能弄清楚,道明白。在工作中遇到this相關問題,就知道var self = this,一旦去面試遇到各種this相關面試題目時腦子就一片空白,拿不定結果。本文綜合了一些書籍和網(wǎng)上文章對this的分析和講解,提供一些實例來分析各種場景下this是如何指向的。
在瀏覽器宿主環(huán)境中,this指向window對象,并且在全局作用域下,使用var聲明變量其實就相當于操作全局this。
this === window; // true var foo = "bar"; this.foo === window.foo; // true
在嚴格模式下,this會綁定到undefined。
var a = 2; function foo() { "use strict"; console.log(this.a); } foo(); // TypeError: this is not undefined
如果在變量的聲明過程沒有使用let或者var,會隱式創(chuàng)建一個全局變量,但這個變量和普通全局變量的區(qū)別在于它是作為window的一個屬性創(chuàng)建的。二者在使用delete操作符上有明顯的區(qū)別:變量不可以刪除,而對象的屬性是可以刪除的
var a = 2; b = 3; a; // 2 b; // 3 delete a; delete b; a; // 2 b; // Uncaught ReferenceError: b is not defined局部作用域
這里的作用域主要是指在對象、函數(shù)中的this指向。
函數(shù)調用作為函數(shù)調用時,函數(shù)中的this默認指向window。
var a = 1; function foo() { console.log(this.a); } foo(); // 1
如果在立即執(zhí)行函數(shù)中使用了this,它同樣指向window。
var a = 1; (function() { var a = 2; console.log(this.a); })(); // 1方法調用
作為方法調用時,函數(shù)中的this總是指向方法所在的對象。
var obj = { a: 1, foo: function() { console.log(this.a); } } obj.foo();構造函數(shù)調用
構造函數(shù)調用將一個全新的對象作為this變量的值,并隱式返回這個新對象作為調用結果。也就是說指向新生成的實例。
function Foo(name) { this.name = name; this.getName = function() { console.log(this.name); } } var a = new Foo("a"); a.getName(); // "a"使用call和apply方法
可以通過call()和apply()方法顯示改變函數(shù)的this指向。
var a = 1; var obj = { a: 2 } function foo() { console.log(this.a); } foo(); // 1 foo.call(obj); // 2 foo.apply(obj); // 2使用bind方法
bind()方法創(chuàng)建一個新的函數(shù), 當被調用時,將其this關鍵字設置為提供的值,在調用新函數(shù)時,在任何提供之前提供一個給定的參數(shù)序列,然后返回由指定的this值和初始化參數(shù)改造的原函數(shù)拷貝。
var a = 1; var obj = { a: 2 } function foo() { console.log(this.a); } var bar = foo.bind(obj); bar();箭頭函數(shù)中調用
ES6引入了箭函數(shù)的概念,在箭頭函數(shù)中由于沒有this綁定,所以它的默認指向是由外圍最近一層非箭頭函數(shù)決定的。
var a = 1; function Foo(a) { this.a = a; this.getA = function() { var x = () => { this.a = 3; // 改變了外圍函數(shù)Foo屬性a的值 console.log(this.a); // 3 } x(); console.log(this.a); // 3 } } var foo = new Foo(1); foo.getA();問題的產(chǎn)生
上面列舉了在正常情況下this的指向結果。但是在實際開發(fā)過程中,對于不同場景,不同的聲明方式、調用方式、賦值和傳值方式都會影響到this的具體指向。
調用方式引起的改變函數(shù)的調用方式最常見的是方法調用、構造函數(shù)調用,或者使用apply/bind/call調用,也可以是立即執(zhí)行函數(shù)。
var a = 10; var obj = { a: 20, fn: function() { var a = 30; console.log(this.a); } } obj.fn(); // 20 obj.fn.call(); // 10 (obj.fn)(); // 20 (obj.fn, obj.fn)(); // 10 (obj.fn = obj.fn)(); // 10 new obj.fn(); // undefined
對于apply和call第一個參數(shù)如果不傳或者傳遞undefined和null則默認綁定到全局對象,所以obj.fn.call()的調用實際上把this指向了window對象。
對于(obj.fn)(),咋一看,是立即執(zhí)行函數(shù),那么它的this肯定指向了window對象,其實不然,這里obj.fn只是一個obj對象方法的引用,并沒有改變this的指向。
對于(obj.fn, obj.fn)(),這種操作比較少見,工作中也不會去這樣寫。這里首先我們需要了解逗號操作符會對每個操作數(shù)求值,并返回最后一個操作數(shù)的值,其次是這里使用了逗號操作符,里面必然是一個表達式,這種情況下里面的函數(shù)this指向其實已經(jīng)改變了,指向了全局。對于(obj.fn = obj.fn)()中this同樣指向全局。因此可以大膽猜測:如果(x)();中x是一個表達式,并且返回一個函數(shù)(引用),那么函數(shù)x中的this指向全局window。這里還更多的方式來達到同樣目的,比如:(true && obj.fn)() 或者 (false || obj.fn)()。總的來說,我們通過這種方式創(chuàng)建了一個函數(shù)的“間接引用”,從而導致函數(shù)綁定規(guī)則的改變。
對于new obj.fn()的結果其實也沒有什么好說的,函數(shù)使用new操作符調用后返回一個新的實例對象,由于該對象并沒有一個叫a的屬性,所以返回undefined。
函數(shù)作為參數(shù)(變量)傳遞時很多時候,函數(shù)的定義在一個地方,而對象定義方法時只是引用了該函數(shù)。同樣在調用對象方法時,先把它賦值給一個變量(別名),然后使用函數(shù)別名進行調用。使用時有可能導致this綁定的改變。
示例一var a = 10; function foo() { console.log(this.a); } var obj = { a: 20, foo: foo } var bar = obj.foo; // 函數(shù)別名 bar(); // 10
雖然bar是obj.foo的一個引用,但是實際上,它引用的是foo函數(shù)本身,因此應用了函數(shù)的默認綁定規(guī)則。
示例二var a = 10; function foo() { console.log(this.a); } function doFoo(cb) { cb(); // cb 實際上引用的還是foo } var obj = { a: 20, foo: foo } doFoo(obj.foo); // 10 setTimeout(obj.foo, 100); // 10
這里我們將obj.foo以參數(shù)的形式傳遞給函數(shù)doFoo和內置函數(shù)setTimeout。參數(shù)傳遞實際上就是一種賦值,和示例一的結果是一樣的。因此,調用回調函數(shù)的函數(shù)會丟失this的指向。
改變構造函數(shù)的默認返回對象構造函數(shù)使用new操作符調用后會返回一個新的實例對象,但是在定義構造函數(shù)時,可以在函數(shù)中返回任何值來覆蓋默認該返回的實例,這樣一來很可能導致實例this的指向改變。
var a = 10; function f() { this.a = 20; function c() { console.log(this.a); } return c(); } new f(); // 10
這里我們將構造函數(shù)foo的默認返回值改成返回一個函數(shù)c執(zhí)行后的結果。當調用new f()后,內部函數(shù)c中的this實際上指向的是全局。但是如果我們將return c()改成return new c()的話,那么new foo()執(zhí)行的結果是返回一個構造函數(shù)c的實例,由于實例對象中并沒有屬性a,因此結果為undefined。
方法的接收者引起的問題在方法的調用中由調用表達式自身來確定this變量的綁定。綁定的this變量的對象被稱為調用接收者。
var buffer = { entries: [], add: function(s) { this.entries.push(s); }, concat: function() { return this.entries.join(""); } } var source = ["123", "-", "456"]; source.forEach(buffer.add); // Uncaught TypeError: Cannot read property "push" of undefined
由于方法buffer.add()的接收者不是buffer本身,而是forEach方法。事實上,forEach方法的實現(xiàn)使用全局對象作為默認的接收者。由于全局沒有entries屬性,因此會拋出一個錯誤。
要解決上面的問題,一個是使用forEach方法提供的可選參數(shù)作為函數(shù)的接收者。
source.forEach(buffer.add, buffer);
其次是使用bind方法來指定接收者
source.forEach(buffer.add.bind(buffer));對象的實例屬性和原型屬性
這里想要說明的是,在一個對象的實例中,this即可以訪問實例對象的值,也可以獲取原型上的值。
function Foo() {} Foo.prototype.name = "bar"; Foo.prototype.logName = function() { console.log(this.name); } Foo.prototype.setName = function(name) { this.name = name; } Foo.prototype.deleteName = function() { delete this.name; } var foo = new Foo(); foo.setName("foo"); foo.logName(); // "foo" foo.deleteName(); foo.logName(); // "bar" delete foo.name; foo.logName(); // "bar"
當執(zhí)行foo.setName("foo")后,給實例對象foo增加了一個屬性name,同時覆蓋了原型中的同名屬性。當執(zhí)行foo.deleteName()時,實際上是將新增值刪除了,還原了初始狀態(tài)。執(zhí)行delete foo.name時,試圖刪除的還是新增的屬性,但是現(xiàn)在已經(jīng)不存在這個值了。如果需要刪除原始值,可以通過delete foo.__proto__.name來實現(xiàn)。
總結本文只是介紹了一部分有關this的問題,更多知識點可以參考《詳解this》以及MDNthis
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89376.html
摘要:通過使用提供的異常處理語句,可以用結構化的方式來捕捉發(fā)生的錯誤,讓異常處理帶啊與核心業(yè)務代碼實現(xiàn)分離。錯誤與異常處理在應用中的重要性是毋庸置疑的。包括內置對象函數(shù)在內的所有函數(shù)都可以用來調用,這種函數(shù)調用被稱為構造函數(shù)調用。 錯誤與異常 概念 錯誤與異常是什么錯誤,指程序中的費正常運行狀態(tài),在其他編程語言中稱為‘異常’或‘錯誤’。解釋器會為每一個錯誤創(chuàng)建并拋出一個Error對象,其中包...
摘要:錯誤與異常錯誤,指程序中的非正常運行狀態(tài),在其他編程語言中稱為異常或,錯誤。定義一個全局變量,并賦值對象的方法綁定在中,構造函數(shù)只是一些使用操作符時被調用的函數(shù)。包括內置對象函數(shù)在內的所有函數(shù)都可以用來調用,這種函數(shù)調用被稱為構造函數(shù)調用。 錯誤與異常 錯誤,指程序中的非正常運行狀態(tài),在其他編程語言中稱為‘異常’或,‘錯誤’。解釋器為每個錯誤情形創(chuàng)建并拋出一個Error對象,其中包含錯...
摘要:上一章我們學習了遍歷和擴展字符語法。本章我們主要學習中的箭頭函數(shù)箭頭函數(shù)更準確來說叫箭頭函數(shù)表達式。箭頭函數(shù)余普通函數(shù)功能相同,但語法差別比較大。 帶你入門 JavaScript ES6 (三) 本文同步帶你入門 JavaScript ES6 (三),轉載請注明出處。 上一章我們學習了 for of 遍歷和擴展字符語法。本章我們主要學習 ES6 中的箭頭函數(shù) 箭頭函數(shù) 更準確來說叫 箭...
摘要:一錯誤與異常概述錯誤,指程序中的非正常運行狀態(tài),在其它語言中稱為異常或錯誤將每個錯誤中創(chuàng)建個對象,描述包含的錯誤信息通過使用提供異常的處理語句,可以用結構化方式捕捉發(fā)生錯誤,異常處理代碼與核心代碼實現(xiàn)分離語句語句是指中處理異常一種標準方式, JS(JavaScript)一.錯誤與異常1.概述錯誤,指程序中的非正常運行狀態(tài),在其它語言中稱為異常或錯誤將每個錯誤中創(chuàng)建個Error對象,描述...
摘要:接下來我們看下三類異步編程的實現(xiàn)。事件監(jiān)聽事件發(fā)布訂閱事件監(jiān)聽是一種非常常見的異步編程模式,它是一種典型的邏輯分離方式,對代碼解耦很有用處。 一、 一道面試題 前段時間面試,考察比較多的是js異步編程方面的相關知識點,如今,正好輪到自己分享技術,所以想把js異步編程學習下,做個總結。下面這個demo 概括了大多數(shù)面試過程中遇到的問題: for(var i = 0; i < 3; i++...
閱讀 2954·2021-11-17 09:33
閱讀 3118·2021-11-16 11:52
閱讀 482·2021-09-26 09:55
閱讀 2975·2019-08-30 15:52
閱讀 1313·2019-08-30 15:44
閱讀 1257·2019-08-30 13:59
閱讀 796·2019-08-30 13:08
閱讀 1157·2019-08-30 10:50