this
?的值取決于它出現的上下文:函數、類或全局。
在函數內部,this
?的值取決于函數如何被調用,this
?是語言在函數體被執行時為你創建的綁定
對于典型的函數,this
?的值是函數被訪問的對象。換句話說,如果函數調用的形式是?obj.f()
,那么?this
?就指向?obj,例如
function getThis() {return this;
}const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };obj1.getThis = getThis;
obj2.getThis = getThis;console.log(obj1.getThis()); // { name: 'obj1', getThis: [Function: getThis] }
console.log(obj2.getThis()); // { name: 'obj2', getThis: [Function: getThis] }
注意,雖然函數是相同的,但是根據其調用的方式,
this
?的值是不同的。這與函數參數的工作方式類似。
this到底指向誰
調用函數會創建新的屬于函數自身的執行上下文,執行上下文的調用創建階段會決定this的指向,結論:this的指向,是在調用函數時根據執行上下文所動態確定的,或者說this關鍵字指向函數(方法)運行時的所有者
1.規則:
- 在函數體中,簡單調用該函數時(非顯式/隱式綁定下),嚴格模式下this綁定到undefined,否則綁定到全局對象window/global
- 一般構造函數new調用,綁定到新創建的對象上
- 一般由call/bind/apply方法顯式調用,綁定到指定參數的對象上
- 一般由上下文對象調用,綁定在該對象上
- 箭頭函數中,根據外層上下文綁定的this決定this的指向
2.實例分析練習
(1)全局環境下的this
function f1(){console.log(this);
}function f2() {'use strict'console.log(this);
}f1() //Window對象
f2() //undefined
const foo = {bar: 10,fn: function () {console.log(this); //Windowconsole.log(this.bar); //undefined}
}var fn1=foo.fn
fn1()
上題中this指向的是window,雖然fn函數在foo對象中作為方法被引用,但是在賦值給fn1后,fn1的執行仍然是在window的全局環境中
const foo = {bar: 10,fn: function () {console.log(this); //{bar:10,fn:f}console.log(this.bar); //10}
}foo.fn()
上題中this指向的是最后調用他的對象,在foo.fn()語句中this指向foo對象
注意:在執行函數時,如果函數中的this是被上一級的對象所調用,那么this指向的就是上一級的對象;否則指向全局環境
?(2)上下文對象調用中的this
const student={name:'hist',fn:function(){return this}
}console.log(student.fn()===student); //true
const person = {name: 'hist',brother: {name: 'comp',fn: function () {return this.name}}
}console.log(person.brother.fn()); //comp
上題在這種嵌套關系中,this指向最后調用它的對象
const o1 = {text: 'o1',fn: function () {return this.text}
}const o2 = {text: 'o2',fn: function () {return o1.fn()}
}const o3 = {text: 'o3',fn: function () {var fn= o1.fnreturn fn()}
}console.log(o1.fn()); //o1
console.log(o2.fn()); //o1
console.log(o3.fn()); //undefined
第二個相當于最后調用時還是用o1調用的fn()
第三個將o1.fn()賦值給fn后,直接調用fn(),沒有用對象點調用,所以這里this指向window
如果想讓第二個輸出o2怎么做?
const o2 = {text: 'o2',fn: o1.fn() }
此時我們將賦值操作提前,相當于先把o1.fn()變為this.text,此時我們用o2調用fn()指向的就是o2對象
??(3)bind/call/apply改變this指向
補充:bind/call/apply使用方法
const target={}fn.call(target, 'arg1', 'arg2') fn.apply(target,['arg1','arg2']) fn.bind(target, 'arg1', 'arg2')()
示例一
function greet() {console.log(`Hello, ${this.name}`);
}const person = { name: 'Alice' };// 使用 call 明確指定 this
greet.call(person); // 輸出:Hello, Alice
示例二
function greet(city, country) {console.log(`Hello, ${this.name} from ${city}, ${country}`);
}const person = { name: 'Bob' };// 使用 apply,參數是數組
greet.apply(person, ['New York', 'USA']); // 輸出:Hello, Bob from New York, USA
示例三
function greet() {console.log(`Hello, ${this.name}`);
}const person = { name: 'Charlie' };// 使用 bind 創建一個新的函數
const boundGreet = greet.bind(person)();// 輸出:Hello, Charlie
??(4)構造函數和this
function Foo(){this.bar='hist'
}const instance=new Foo()
console.log(instance.bar);//hist
new操作符調用構造函數做了什么?
- 創建一個新的對象
- 將構造函數的this指向這個新對象
- 為這個對象添加屬性、方法等
- 最終返回新對象
下面是構造函數中出現顯示return的情況,分為兩種場景:
function Foo() {this.user = 'hist'const o = {}return o
}const instance = new Foo()
console.log(instance.user);//undefined
?將會輸出 undefined,此時 instance 是返回的空對象 o。
function Foo() {this.user = 'hist';return 1;
}const instance = new Foo();
console.log(instance.user);//hist
?將會輸出 hist,也就是說此時 instance 是返回的目標對象實例 this。
結論:如果在構造函數中顯式返回一個值,且返回的是一個對象,那么 this 就指向這個返回的對象;如果返回的不是一個對象,那么 this 仍然指向實例。
???(5)箭頭函數中的this指向
箭頭函數和普通函數有一個重要的區別:箭頭函數不會有自己的 this,它會繼承外部上下文的 this。
const foo = {fn: function () {setTimeout(function () {console.log(this);});},
};
foo.fn();//window
this 出現在 setTimeout() 中的匿名函數里,因此 this 指向 window 對象
const foo = {fn: function () {setTimeout(() => {console.log(this);});},
};foo.fn();//{fn: ?}
- 在?
foo.fn()
?調用時,this
?在?fn
?方法內指向?foo
?對象。 setTimeout
?中的箭頭函數繼承了外部的?this
,也就是?foo
?對象。
(6)this優先級
通過 call、apply、bind、new 對 this 綁定的情況稱為顯式綁定
根據調用關系確定的 this 指向稱為隱式綁定
function foo(a) {console.log(this.a);
}const obj1 = {a: 1,foo: foo,
};const obj2 = {a: 2,foo: foo,
};obj1.foo.call(obj2); //2
obj2.foo.call(obj1); //1
call、apply 的顯式綁定一般來說優先級更高
function foo(a) {this.a = a;
}const obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a);
上述代碼通過 bind,將 bar 函數中的 this 綁定為 obj1 對象。執行 bar(2) 后,obj1.a 值為 2。即經過 bar(2) 執行后,obj1 對象為:{a: 2}。
當再使用 bar 作為構造函數時:
var baz = new bar(3)
console.log(baz.a)//3
將會輸出 3。我們看 bar 函數本身是通過 bind 方法構造的函數,其內部已經將 this 綁定為 obj1,它再作為構造函數,通過 new 調用時,返回的實例已經與 obj1 解綁。 也就是說:new 綁定修改了 bind 綁定中的 this,因此 new 綁定的優先級比顯式 bind 綁定更高。
function foo() {return (a) => {console.log(this.a);};
}const obj1 = {a: 1,
};const obj2 = {a: 2,
};const bar = foo.call(obj1);
bar.call(obj2);//1
由于 foo() 的 this 綁定到 obj1,bar(引用箭頭函數)的 this 也會綁定到 obj1,箭頭函數的綁定無法被修改。
var a = 123;const foo = () => (a) => {console.log(this.a);};const obj1 = {a: 2,};const obj2 = {a: 3,};const bar = foo.call(obj1);console.log(bar.call(obj2));//123
箭頭函數的綁定無法被修改,foo的執行上下文是window
如果將第一處的var a = 123;改為const?a = 123;
答案將會輸出為 undefined,原因是因為使用 const 聲明的變量不會掛載到 window 全局對象當中