this關鍵字是函數當中最重要的一個知識點。它在JavaScript中的表現也會有一些細微的不一樣,在嚴格和非嚴格模式之下也會有一些差異。html
絕大多數狀況下,this的指向由函數的調用方式決定。它不能被賦值,而且每次函數調用,它也有可能會不一樣。ES5
引入了bind
方法來設置函數的this
值,而不須要考慮函數的調用方式,ES6
的箭頭函數不提供自身的this
綁定,它的this
由當前上下文決定。前端
const obj = { name:"hello,world!", getName(){ return this.name; } } console.log(obj.getName());//"hello,world!"
this
它的值是當前上下文(global,function,eval)中的一個屬性,在非嚴格模式下,它老是指向一個對象,而在嚴格模式下,它能夠被設置成任意值。node
全局上下文即全局對象,例如在瀏覽器環境當中,this始終指的是window對象,不管是否是嚴格模式。來看以下一個示例:web
//在瀏覽器環境中,window對象就是全局對象 console.log(this === window);//true //不用標識符定義一個變量,也會自動將該變量添加到window對象中,做爲window對象的一個屬性 a = 250; console.log(this.a);//250 this.message = "hello,world!"; console.log(message); console.log(window.message); //都是打印的"hello,world!"
筆記:能夠始終使用
globalThis
來獲取一個全局對象,不管你的代碼是否在當前上下文運行。
var obj = { func:function(){ console.log(this); console.log(globalThis); } } obj.func();//先打印obj對象,再打印window對象,瀏覽器環境中
在函數內部,this取決於它被調用的方式。例如如下的非嚴格模式下,沒有手動去經過設置調用方式,而且是在全局環境下調用的,因此this指向全局對象。typescript
function fn(){ return this; } //在瀏覽器環境中 console.log(fn() === window);//true //在node.js環境中 console.log(fn() === globalThis);//true
然而,在嚴格模式下,若是沒有爲this設置值,那麼this會保持爲undefined。如:數組
function fn(){ 'use strict'; return this; } console.log(fn() === undefined) //true
tips:上例中,由於fn是直接被調用的,也就是並非做爲對象的屬性來調用(window.fn),因此this應是undefined,有些瀏覽器在最初支持嚴格模式的時候並無正確的實現這個功能,因此錯誤的返回了window對象。
若是想要改變this值,須要使用call
或apply
方法。如例:瀏覽器
var obj = { value:"this is custom object!"}; var value = "this is global object!"; var getThis = function(){ return this.value; } console.log(getThis());//"this is global object!" console.log(getThis.apply(obj))//"this is custom object!" console.log(getThis.call(obj))//"this is custom object!"
儘管ES6
的類和函數有些類似,this的表現也會相似,但也有一些區別和注意事項。app
在類當中,this就是一個常規的類對象,類裏面定義的非靜態的方法都會被添加到this對象的原型當中。例:函數
class Test { constructor(){ const p = Object.getPrototypeOf(this); console.log(Object.getOwnPropertyNames(p)); } getName(){} getValue(){} static getNameAndValue(){} } new Test();//["constructor","getName","getValue"]
tips:靜態方法不是this的屬性,它們只是類自身的屬性。
好比,咱們要調用以上的getNameAndValue
方法,咱們能夠像以下這樣調用:this
Test.getNameAndValue(); //或者 const test = new Test(); test.constructor.getNameAndValue();
在派生類當中,不會像基類那樣,有初始的綁定。什麼是派生類?也就是繼承基類的類。例如:
class Base { constructor(){ this.key = "base"; } } class Test extends Base {} //這裏的test就是一個派生類
在派生類的構造函數當中,若是不使用super綁定this,則在使用this的過程當中會報錯Must call super constructor in derived class before accessing 'this' or returning from derived constructor
。大體意思就是要有一個super綁定。如:
class Base { constructor(){ this.key = "base"; } } class Test extends Base { constructor(){ console.log(this); } } //ReferenceError
可是若是咱們稍微改一下,以下:
class Base { constructor(){ this.key = "base"; } } class Test extends Base { constructor(){ super();//這時候會生成一個this綁定 console.log(this); } } //Test,繼承了基類的屬性和方法,至關於執行this = new Base()
派生類不能在沒有super方法的構造函數中返回一個除對象之外的值,或者說是有super方法的前面直接返回一個對象之外的值也是不行的,除非根本就沒有構造函數。如:
class Base { constructor(){ this.key = "base"; } } class Test extends Base { constructor(){ return 1; super(); } } //TypeError
可是下面的示例不會出錯:
class Base { constructor(){ this.key = "base"; } } class Test extends Base { constructor(){ return {}; super(); } }
下面示例會報錯:
class Base { constructor(){ this.key = "base"; } } class Test extends Base { constructor(){ return 1; } } //TypeError
下面示例不會報錯:
class Base { constructor(){ this.key = "base"; } } class Test extends Base { constructor(){ return {}; } }
在非嚴格模式下,若是調用call或apply方法,傳入的第一個參數,也就是被用做this的值不是一個對象,則會嘗試被轉換爲對象。基本類型值,如null何undefined會被轉換成全局對象,而像其餘的基本類型值則會使用對應的構造函數來轉換成對象。例如number類型數字1就會調用new Number(1),string類型'test'就會調用new String('test')。
例如:
function sum(c,d){ return this.a + this.b + c + d; } var a = 3,b = 4; var count = { a:1, b:2 } //call方法後面的參數直接被用做函數的參數 console.log(sum.call(count,3,4));//10 console.log(sum.call(count,'3',4))//'334' console.log(sum.call(null,3,4));//14 console.log(sum.call(undefined,'3',4));//'734' console.log(sum.call(1,3,4));//new Number(1)上沒有a和b屬性,因此是this.a + this.b就是NaN,即兩個undefined相加 console.log(sum.call('',1,'2'))//'NaN2' //apply方法參數只能傳數組參數 //TypeError // console.log(sum.apply(count,3,4)); // console.log(sum.apply(count,'3',4)) // console.log(sum.apply(null,3,4)); // console.log(sum.apply(undefined,'3',4)); // console.log(sum.apply(1,3,4)); // console.log(sum.apply('',1,'2')) //必須這樣傳 console.log(sum.apply(count,[3,4]));//10 console.log(sum.apply(count,['3',4]))//'334' console.log(sum.apply(null,[3,4]));//14 console.log(sum.apply(undefined,['3',4]));//'734' console.log(sum.apply(1,[3,4]));//new Number(1)上沒有a和b屬性,因此是this.a + this.b就是NaN,即兩個undefined相加 console.log(sum.apply('',[1,'2']))//'NaN2'
再來看一個示例以下:
function test(){ console.log(Object.prototype.toString.call(this)) } console.log(test.call(7));//[object Number] console.log(test.call(undefined));//[object global],在瀏覽器環境下指向爲[Object window] console.log(test.apply('123'));//[object String]
根據以上示例,咱們就能夠知道了利用Object.prototype.toString方法來判斷一個對象的類型。如能夠封裝一個函數以下:
function isObject(value){ return Object.prototype.toString.call(value) === '[object Object]'; } //等價於 function isObject(value){ return Object.prototype.toString.apply(value) === '[object Object]'; } //等價於 function isObject(value){ return {}.toString.call(value) === '[object Object]'; } //等價於 function isObject(value){ return {}.toString.apply(value) === '[object Object]'; }
ES5
引入了bind
方法,該方法爲Function
的原型對象上的一個屬性,在一個函數fn
中調用fn.bind(object)
將會建立一個和該函數相同做用域以及相同函數體的函數,可是它的this
值將被綁定到bind
方法的第一個參數,不管這個新建立的函數以什麼方式調用。如:
function fn(){ var value = "test"; return this.value; } var obj = { value:"objName" } var newFn = fn.bind(obj); console.log(fn.bind(obj)());//objName console.log(newFn());//objName var bindObj = { value:"bind", f:fn, g:newFn, h:fn.bind(bindObj) } var newBind = { a:fn.bind(bindObj) } console.log(bindObj.f());//bind console.log(bindObj.g());//objName console.log(bindObj.h());//undefined console.log(newBind.a());//bind
在箭頭函數中,this與封閉環境當中的上下文的this綁定一致,在全局環境中,那它的this就是全局對象。如:
var obj = { a:() => { return this; }, b:function(){ var x = () => { return this;}; return x(); } } console.log(obj.a());//global console.log(obj.b());//obj
注意:不管使用call,apply仍是bind其中的哪種方法,都不能改變箭頭函數的this指向,由於都將被忽略,可是仍然能夠傳遞參數,理論上第一個參數設置爲null或者undefined爲最佳實踐。
如:
//在瀏覽器環境下globalObject是window對象 let globalObject = this; let getThis = () => this; console.log(getThis() === globalObject);//true let obj = { getThis:getThis } console.log(obj.getThis() === globalObject);//true console.log(obj.getThis.call(obj) === globalObject);//true console.log(obj.getThis.apply(obj) === globalObject);//true // 使用bind並未改變this指向 console.log(obj.getThis.bind(obj)() === globalObject);//true
也就是說,不管如何,箭頭函數的this都指向它的封閉環境中的this。以下:
var obj = { a:() => { return this; }, b:function(){ var x = () => { return this;}; return x(); } } console.log(obj.a());//global在瀏覽器環境下是window對象 console.log(obj.b());//obj
當調用某個對象中的函數中的方法時,在訪問該函數中的this對象,將會指向這個對象。例如:
var value = "this is a global value!"; var obj = { value:"this is a custom object value!", getValue:function(){ return this.value; } } console.log(obj.getValue());//"this is a custom object value!"
這樣的行爲方式徹底不會受函數定義的方式和位置影響,例如:
var value = "this is a global value!"; var obj = { value:"this is a custom object value!", getValue:getValue } function getValue(){ return this.value; } console.log(obj.getValue());//"this is a custom object value!"
此外,它只受最接近的引用對象的影響。如:
var value = "this is a global value!"; var obj = { value:"this is a custom object value!", getValue:getValue } obj.b = { value:"this is b object value!", getValue:getValue } function getValue(){ return this.value; } console.log(obj.b.getValue());//"this is b object value!"
在對象的原型鏈中,this一樣也指向的是調用這個方法的對象,實際上也就至關於該方法在這個對象上同樣。如:
var obj = { sum:function(){ return this.a + this.b; } } var newObj = Object.create(obj); newObj.a = 1; newObj.b = 2; console.log(newObj.sum());//3 console.log(obj.sum());//NaN
上例中,newObj對象繼承了obj的sum方法,而且咱們未newObj添加了a和b屬性,若是咱們調用newObj的sum方法,this實際上指向的就是newObj這個對象,因此咱們能夠獲得結果爲3,可是咱們調用obj.sum方法的時候,this指向的是obj,obj對象並無a和b屬性,因此也就是兩個undefined相加,就會是NaN。obj就做爲了newObj的原型對象,這也是原型鏈當中的一個很是重要的特色。
注意:Object.create()方法表示建立一個新對象,會以第一個參數做爲新對象的原型對象,第一個參數只能爲null或者新對象,不能爲其它基本類型的值,如undefined,1,''等。
在一個對象的setter
或者getter
中一樣的this指向設置或者獲取這個屬性的對象。如:
function average(){ return (this.a + this.b + this.c) / 3; } var obj = { a:1, b:2, c:3 get sum:function(){ return this.a + this.b + this.c; } } Object.defineProperty(obj,'average',{ get:average, enumerable:true, configurable:true }); console.log(obj.average,obj.sum);//2,6
當一個函數被當作構造函數調用時(使用new關鍵字),this指向的就是實例化的那個對象。
注意:儘管構造函數返回的默認值就是this指向的那個對象,可是也能夠手動設置成返回其它的對象,若是手動設置的值不是一個對象,則返回this對象。
如:
function C(){ this.a = 1; } var c1 = new C(); console.log(c1.a);//1 function C2(){ var obj = { a:2 } this.a = 3; return obj; } var c2 = new C2(); console.log(c2.a);//2
在上例中實例化的c2的構造函數C2中,因爲手動的設置了返回的對象obj
,因此致使this.a = 3
這條語句被忽略,從而獲得結果爲2,就好像"殭屍"代碼。固然也不能算是"殭屍"代碼,由於實際上它是被執行了的,只不過對外部沒有形成影響,因此能夠被忽略。
當函數是一個DOM事件處理函數,它的this就指向觸發事件的元素(有一些瀏覽器在使用非addEventListener動態添加函數時不遵照這個約定)。如:
function changeStyle(e){ console.log(this === e.currentTarget);//true console.log(this === e.target);//true //將背景色更改成紅色 this.style.setProperty('background',"#f00"); } // 獲取文檔中全部的DOM元素 var elements = document.getElementsByTagName('*'); for(let i = 0,len = elements.length;i < len;i++){ //爲每一個獲取到的元素添加事件 elements[i].addEventListener('click',changeStyle,false); }
當在內聯事件中調用函數時,this指向的就是這個元素。但只有最外層的代碼才指向這個元素,若是是內部嵌套函數中沒有指定this,則指向全局對象。如:
<button type="button" onclick="document.writeln(this.tagName.toLowerCase())">clicked me</button> <!-- 點擊按鈕會在頁面中出現button -->
<button type="button" onclick="document.writeln((function(){return this})())">clicked me</button> <!-- 在瀏覽器環境下頁面會寫入[object Window] -->
類中的this取決於如何調用,但實際上在開發當中,咱們手動的去綁定this爲該類實例是一個頗有用的方式,咱們能夠在構造函數中去更改this綁定。如:
class Animal { constructor(){ //利用bind方法讓this指向實例化的類對象 this.getAnimalName = this.getAnimalName.bind(this); } getAnimalName(){ console.log("The animal name is ",this.animalName); } getAnimalNameAgain(){ console.log("The animal name is ",this.animalName); } get animalName(){ return "dog"; } } class Bird { get animalName(){ return "bird"; } } let animal = new Animal(); console.log(animal.getAnimalName());//The animal name is dog; let bird = new Bird(); bird.getAnimalName = animal.getAnimalName; console.log(bird.getAnimalName());//The animal name is dog; bird.getAnimalNameAgain = animal.getAnimalNameAgain; console.log(bird.getAnimalNameAgain());//The animal name is bird;
在這個示例中,咱們始終將getAnimalName
方法的this綁定到實例化的Animal
類對象上,因此儘管在Bird
類中定義了一個animalName
屬性,咱們在調用getAnimalName
方法的時候,始終獲得的就是Animal
中的animalName
屬性。因此第二個打印仍然是dog
。
注意:在類的內部老是使用的嚴格模式,因此調用一個this值爲undefined的方法會拋出錯誤。
打個廣告,我在思否上線的課程玩轉typescript,適用於有必定基礎的前端,還望你們多多支持,謝謝。