this是什么
在函數(shù)運(yùn)行時,基于調(diào)用位置的條件自動生成的內(nèi)部對象苛败,可以理解為動態(tài)綁定對象到this上满葛。
需要強(qiáng)調(diào)的是:
- 只針對函數(shù),在函數(shù)內(nèi)部使用
- this由調(diào)用位置的上下文自動的決定罢屈,而不是函數(shù)聲明的位置(代碼書寫的位置)
- 必須是運(yùn)行時才確定嘀韧,而不是編寫時
- this綁定之后可理解為自動生成的內(nèi)部對象
示例:
var foo = "golbal foo";
var myObj = {foo : 'myObj foo'};
var say = function(){
console.log(this.foo);
}
myObj.say = say;
myObj.say(); //結(jié)果:myObj foo
say(); //結(jié)果:golbal foo ,相當(dāng)于window.say()缠捌,內(nèi)部的this->window對象
this的四個綁定規(guī)則及優(yōu)先級
下面四個為this的綁定優(yōu)先級規(guī)則锄贷,第一個優(yōu)先級最高译蒂。判斷執(zhí)行流程需要做的就是找到函數(shù)的調(diào)用位置并判斷使用哪條規(guī)則。
1. 函數(shù)是否通過new Base()
方式綁定谊却?如果是柔昼,this
綁定新創(chuàng)建的對象
new
的調(diào)用會自動執(zhí)行下面代碼(示例代碼),Base函數(shù)中的this
指向新創(chuàng)建的對象(一般):
var obj = new Base()
// 1. 創(chuàng)建(或者說構(gòu)造)一個全新的對象炎辨;
var _obj = {}
// 2. 我們將這個空對象的__proto__成員指向了Base函數(shù)對象prototype成員對象
_obj.__proto__ = Base.prototype
// 3. 我們將Base函數(shù)對象的this指針替換成_obj捕透,然后再調(diào)用Base函數(shù)
var _return = Base.call(_obj)
// 4 如果函數(shù)沒有返回其他對象,那么new表達(dá)式中的函數(shù)調(diào)用會自動返回這個新對象
if (typeof(_return) === 'object') {
obj = _return
} else {
obj = _obj
}
2. 函數(shù)是否通過call
碴萧、apply
乙嘀、bind
顯式綁定?如果是破喻,this
綁定所指定的對象
強(qiáng)制將foo
方法中的this
綁定到obj
對象上虎谢,即使后面再次更新綁定也不生效。
function foo(){
console.log(this.a);
}
var obj = { a:2 };
var bar = function(){
foo.call(obj);
};
bar(); //2
setTimeout( bar, 1000); //2
bar.call( window ); //2
注意:
bind
綁定會返回新函數(shù)曹质,對新函數(shù)無法更改內(nèi)部的this婴噩,原因同上。但是對原函數(shù)可以隨意切換綁定羽德。
function base () {
console.log(this.hello)
}
var a = {
hello:'aaa'
}
var b = {
hello:'bbb'
}
base.call(a) // aaa
base.call(b) // bbb
var bb = base.bind(b) // 強(qiáng)綁定讳推,返回的bb函數(shù)已無法更改this
bb.call(a) // bbb
bb.call(b) // bbb
base.call(a) // aaa
base.call(b) // bbb
3. 函數(shù)是否在某個上下文對象中隱式調(diào)用?如果是玩般,this
綁定到那個上下文對象
function foo(){
console.log(this.a);
}
var obj1 = {
a : 2,
foo : foo
}
var obj2 = {
a : 1,
obj1 : obj1
}
obj2.obj1.foo(); //結(jié)果:2
foo()
執(zhí)行時的上下文是obj1
,因此函數(shù)內(nèi)的this
->obj1
注意:
隱式綁定會出現(xiàn)綁定丟失的問題礼饱,不過這個很好推理坏为。
var a = "foo";
function foo(){
console.log(this.a);
}
function doFoo(fn){ //var fn = obj.foo
fn();
}
var obj = {
a : 2,
foo : foo
}
doFoo(obj.foo); //"foo" this->window
var bar = obj.foo
bar(); // "foo" 相當(dāng)于:window.bar(), this->window
bar.call(obj); // "2" this->obj
setTimeout(obj.foo, 100); //"foo"
4. 上述全部不是镊绪,則this->window上
匀伏,如果是嚴(yán)格模式,this->undefined
// 嚴(yán)格模式是蝴韭?
var a = function ( ) {
"use strict"
//...
}
事件中的this
1. 作為DOM事件處理
在事件處理函數(shù)中的this指向綁定事件的對象event.currentTarget
document.getElementById('button').addEventListener('click',function (event) {
console.log(this)
console.log(event.target === event.currentTarget)
console.log(event.target === this)
})
currentTarget:綁定事件的元素够颠,恒等于this
target:觸發(fā)事件的元素,可能是綁定事件元素的子元素接收到了事件
2. 作為內(nèi)聯(lián)事件處理
當(dāng)代碼在元素上進(jìn)行調(diào)用處理榄鉴,this指向的是這個DOM元素履磨,this->$(button)
<button onclick="alert(this.tagName.toLowerCase());"> Show this</button>
function函數(shù)中返回,則this指向window庆尘,this->window
<button onclick="alert((function(){return this}()));">Show inner this</button>
IIFE中的this
不管IIFE寫在哪剃诅,里面的this都指向window。相當(dāng)于是在window下執(zhí)行IIFE函數(shù)驶忌。此外矛辕,自執(zhí)行函數(shù)返回值由內(nèi)部函數(shù)返回。
注意點
1. 忽略this
把null
或undefined
作為this
的綁定對象傳入call
、apply
聊品、bind
飞蹂,調(diào)用時會被忽略,實際應(yīng)用的是默認(rèn)綁定規(guī)則this->window
翻屈!
function foo(){
console.log(this.a);
}
var a = 1;
foo.call(null, 2); // 1 this->window
foo.apply(undefined, [3]); //1 this->window
foo.apply(window, [3]); //1 this->window
2. 間接引用
function foo(){
console.log(this.a);
}
var a = 2;
var o = { a : 3,foo : foo};
var p = { a : 4};
o.foo(); //3
(p.foo = o.foo)(); //2 間接引用, 前面返回foo函數(shù)陈哑,相當(dāng)于:(foo)(), this->window
var pfoo = o.foo;
pfoo(); //2 隱式丟失
3. 箭頭函數(shù)
箭頭函數(shù)中的this無法被修改,this指向由外層函數(shù)決定妖胀,常用于事件處理器或定時器等異步場景芥颈。
function foo(){
setTimeout(()=>{
console.log(this.a);
},100);
}
var obj = { a : 2};
foo.call(obj);
等價于:
function foo(){
var self = this;
setTimeout(function(){
console.log(self.a);
},100);
}
var obj = { a : 2};
foo.call(obj);
補(bǔ)充
1. bind()
實現(xiàn)?
function bind(f, o){
if(f.bind){
return f.bind(o);
}else{
return function(){
return f.apply(o, arguments);
}
}
}
2. 什么是嚴(yán)格模式赚抡?
為了向新版JS語法過度的模式爬坑。
放置位置
嚴(yán)格模式編譯指示: "use strict"
- 放置在腳本第一行
- 聲明在函數(shù)體中第一行
var a = function ( ) {
"use strict"
//...
}
與非嚴(yán)格模式的區(qū)別
- 無法創(chuàng)建全局變量,變量必須使用var聲明
"use strict"
var a = 123
console.log(a)
console.log(a === window.a)
- 靜默失敗都會拋出錯誤
- 禁止使用with
var obj= { };obj.a=1;obj.b=2;
with(obj){
alert(a+b)
}
with的作用是將obj對象中的變量在{}中展開可直接訪問涂臣,注意這沒有影響window對象盾计。類似于作用域拼接。
- 嚴(yán)格模式下赁遗,eval內(nèi)部有自己的作用域
- 默認(rèn)的this指向undefined署辉,而不是window,避免window變量被污染
- 禁止在函數(shù)內(nèi)部使用callee岩四、caller哭尝、arguments這些屬性
- 對象屬性名唯一,函數(shù)傳參名唯一
- 禁止八進(jìn)制表示法
- 不允許對arguments賦值
- arguments不在追蹤參數(shù)的變化
- 不能再非函數(shù)的代碼塊中聲明函數(shù)
var a= 6;
if(a>2){
function fn ( ) {
alert('hi');
}
fn( );
} //報錯
- 禁止使用保留關(guān)鍵字
測試題
1. 為什么要使用this剖煌,而不是傳參解決問題
- 復(fù)用
在不同的對象環(huán)境下執(zhí)行了它們材鹦,達(dá)到了復(fù)用的效果,而不用為了在不同的對象環(huán)境下執(zhí)行而必須針對不同的對象環(huán)境寫對應(yīng)的函數(shù)了耕姊。
- 模擬類
2. 基礎(chǔ)call
function identify() {
return this.name.toUpperCase();
}
function sayHello() {
var greeting = "Hello, I'm " + identify.call( this );
console.log( greeting );
}
var person1= {
name: "Kyle"
};
var person2= {
name: "Reader"
};
identify.call( person1); // KYLE
identify.call( person2); // READER
sayHello.call( person1); // Hello, I'm KYLE
sayHello.call( person2); // Hello, I'm READER
函數(shù)在哪里調(diào)用才決定了this到底引用的是啥
3. this不是指向函數(shù)本身
function fn(num) {
console.log( "fn: " + num );
// count用于記錄fn的被調(diào)用次數(shù)
this.count++;
}
fn.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
fn( i );
}
}
// fn: 6
// fn: 7
// fn: 8
// fn: 9
console.log( fn.count ); // 0 -- 耶桶唐?咋不是4捏?
- fn.count是函數(shù)本身的屬性茉兰,因為函數(shù)也是對象
- this.count是fn函數(shù)構(gòu)造器中的變量尤泽, 也是全局變量,this->window
4. 繼承+引用+this
function Parent() {
this.a = 1;
this.b = [1, 2, this.a]; // this.a只在函數(shù)體內(nèi)存在规脸,這里相當(dāng)于設(shè)置“默認(rèn)值”
this.c = { demo: 5 };
this.show = function () {
console.log(this.a , this.b , this.c.demo );
}
}
function Child() {
this.a = 2;
this.change = function () {
// this中變量就近引用坯约,如果沒有就從原型鏈繼續(xù)找
this.b.push(this.a);
this.a = this.b.length;
this.c.demo = this.a++;
}
}
Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
parent.show(); // 1 [1,2,1] 5
child1.show(); // 11 [1,2,1] 5
child2.show(); // 12 [1,2,1] 5
child1.change(); // a->5, b->[1,2,1,11], c.demo->4,this就近+繼承+引用
child2.change(); // a->6, b->[1,2,1,11,12], c.demo->5莫鸭,this就近+繼承+引用
parent.show(); // 1 [1,2,1] 5
child1.show(); // 5 [1,2,1,11,12] 5
child2.show(); // 6 [1,2,1,11,12] 5
需要注意的是:
- 繼承+引用
child2.__proto__ === child1.__proto__ // true