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
來獲取一個全局對象,不管你的代碼是否在當前上下文運行。typescript
var obj = {
func:function(){
console.log(this);
console.log(globalThis);
}
}
obj.func();//先打印obj對象,再打印window對象,瀏覽器環境中
複製代碼
在函數內部,this取決於它被調用的方式。例如如下的非嚴格模式下,沒有手動去經過設置調用方式,而且是在全局環境下調用的,因此this指向全局對象。segmentfault
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
方法。如例:markdown
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
方法,咱們能夠像以下這樣調用:
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的方法會拋出錯誤。
打個廣告,我在思否上線的課程玩轉typescript1,玩轉typescript2適用於有必定基礎的前端,還望你們多多支持,謝謝。