標(biāo)簽: js
this的指向問(wèn)題一直是面試?yán)锏某?吞居R彩莈s5的眾坑之一鸯两。對(duì)于es6而講,它極大地避免了this帶來(lái)的錯(cuò)誤长豁。但是為了為了維護(hù)一些老代碼钧唐,還是有必要了解this的。
this的指向原則:
- this 永遠(yuǎn)指向最后調(diào)用它的那個(gè)對(duì)象(es5)匠襟。
- 匿名函數(shù)的this總指向Window對(duì)象(全局對(duì)象)(es5)钝侠。
- 箭頭函數(shù)中的 this 只和定義它時(shí)候的作用域的 this 有關(guān),而與在哪里以及如何調(diào)用它無(wú)關(guān)宅此,同時(shí)它的 this 指向是不可改變的机错。
全局環(huán)境中的 this 是什么
瀏覽器:
console.log(this); //Window
node:
console.log(this); //global
函數(shù)里執(zhí)行
全局函數(shù)執(zhí)行
一個(gè)函數(shù)被直接調(diào)用的時(shí)候,屬于全局調(diào)用父腕,這時(shí)候它的 this 指向 全局對(duì)象
例如:
function test(){
console.log(this);
}
test();//Window
作為對(duì)象的方法調(diào)用
例如:
var obj={
name:"JoeWright",
foo:function(){
console.log(this);
}
};
obj.foo();//Object {name: "JoeWright", foo: ?}
如果把對(duì)象的方法賦值給一個(gè)變量弱匪,然后直接調(diào)用這個(gè)變量,這時(shí)候,this 指向全局對(duì)象。
例如:
var obj={
name:"Joe",
foo:function(){
console.log(this);
}
};
var test=obj.foo;
test();//Window
作為一個(gè)構(gòu)造函數(shù)使用
this 指向了這個(gè)構(gòu)造函數(shù)調(diào)用時(shí)候?qū)嵗鰜?lái)的對(duì)象
例如:
function Person(name){
this.name=name;
console.log(this);
}
//this 指向了這個(gè)構(gòu)造函數(shù)調(diào)用時(shí)候?qū)嵗鰜?lái)的對(duì)象
var p1=new Person("JoeWright"); //Person
//構(gòu)造函數(shù)其實(shí)也是一個(gè)函數(shù)璧亮,如果我們把它當(dāng)作一個(gè)普通函數(shù)執(zhí)行萧诫,這個(gè) this 仍然執(zhí)行全局
var p2=Person("Wright");//Window
匿名函數(shù)使用(this的一些坑)
匿名函數(shù)的this總指向Window對(duì)象
例如:
var obj={
name:"JoeWright",
f1:function(){
console.log(this);//Window
},
f2:function(){
console.log(this);//Object
setTimeout(this.f1,1000);//此時(shí)this.f1相當(dāng)于setTimeout 函數(shù)的形參fun,即做了fun=this.f1這個(gè)操作
}
};
obj.f2();
解決方法(改變this的指向):
1.使用箭頭函數(shù)
var obj={
name:"JoeWright",
f1:function(){
console.log(this);
},
f2:function(){
console.log(this);//Object
setTimeout(()=>{
console.log(this);//Object
},1000);
}
};
obj.f2();
2.在函數(shù)內(nèi)部使用that=this(或者_(dá)this=this)
var obj={
name:"JoeWright",
f1:function(){
console.log(this);
},
f2:function(){
console.log(this);//Object
var that=this;//上面的this指向的是obj4,這里我們用that變量存儲(chǔ)下來(lái)
setTimeout(function(){
console.log(that);//Object
},1000);
}
};
obj.f2();
改變this的指向
改變 this
的指向有以下幾種方法:
- 使用 ES6 的箭頭函數(shù)
- 在函數(shù)內(nèi)部使用
that = this
(或_this = this
) - 使用
apply
枝嘶、call
帘饶、bind
-
new
實(shí)例化一個(gè)對(duì)象
1,2兩點(diǎn)上面已經(jīng)講過(guò),下面介紹下apply
群扶、call
及刻、bind
的用法
例如:
function Cat(name){
this.name=name;
}
function Dog(name){
this.name=name;
}
Cat.prototype.eat=function(food){
console.log(this.name+" eat "+food);
}
Dog.prototype.eat=function(food){
console.log(this.name+" eat "+food);
}
var c=new Cat("kitty");
var d=new Dog("Bubby");
c.eat("fish"); //kitty eat fish
d.eat("meat"); //Bubby eat meat
c.eat.apply(d,["fish"]); //Bubby eat fish
c.eat.call(d,"fish"); //Bubby eat fish
d.eat.call(c,"apple"); //kitty eat apple
d.eat.bind(c,"apple")(); //kitty eat apple
從上述的例子可知:
-
apply
接收的是一個(gè)由若干參數(shù)組成的數(shù)組镀裤,而call接收的是若干個(gè)參數(shù)列表。 -
bind
與call
的用法類似缴饭,但是bind 創(chuàng)建了一個(gè)新的函數(shù)暑劝,我們必須 手動(dòng)去調(diào)用它。
new關(guān)鍵字改變this的指向
如果函數(shù)調(diào)用前使用了 new 關(guān)鍵字, 則是調(diào)用了構(gòu)造函數(shù)颗搂。
例如:
function Cat(name){
this.name=name;
}
var c=new Cat("kitty");
console.log(c.name); //kitty
new的過(guò)程:
偽代碼:
var c=new Cat("kitty");
new Cat{
var obj={};
obj.__proto__=Cat.prototype;
var res=Cat.call(obj,"kitty");
return typeof res==="obj"?res:obj;
}
- 先創(chuàng)建一個(gè)空對(duì)象obj
- 將新創(chuàng)建的空對(duì)象的隱式原型指向其構(gòu)造函數(shù)的顯式原型
- 使用
call
改變this
的指向 - 如果沒有返回值或者返回的是一個(gè)非對(duì)象值担猛,則將obj返回為一個(gè)新對(duì)象;如果返回值是一個(gè)對(duì)象的話丢氢,就直接返回該對(duì)象傅联。
apply,call疚察,bind的應(yīng)用場(chǎng)景
嗯蒸走,既然call,apply稍浆,bind可以改變this的指向载碌,那我們?cè)谀睦锟梢杂玫侥?/p>
(1) 處理偽數(shù)組
什么是偽數(shù)組?
先看一個(gè)例子吧:
<div>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
const oLi=document.getElementsByTagName("li");
console.log(oLi.length);//4
console.log(oLi); //HTMLCollection(4) [li, li, li, li]
console.log(oLi.slice(1,3)); //Uncaught TypeError: oLi.slice is not a function
看到?jīng)]衅枫,上面的oLi
就是一個(gè)偽數(shù)組嫁艇。它有數(shù)組的length
屬性,但是不可用數(shù)組的slice
方法弦撩。
偽數(shù)組的定義:
- 存在length屬性
- 可以通過(guò)數(shù)組下標(biāo)的方式對(duì)元素進(jìn)行訪問(wèn)
- 能像遍歷數(shù)組一樣遍歷
- 不能使用數(shù)組的
slice()
步咪、push()
等方法。
常見的偽數(shù)組
- 函數(shù)的argument對(duì)象
- document.getElementsByTagName()益楼、document.getElementsByClassName()猾漫、childNodes/children等方法的返回值(返回的是一個(gè)節(jié)點(diǎn)列表)
解決辦法:偽數(shù)組沒有這些方法那就借用Array的方法。
[].slice.call(oLi,1,3) //[li, li](推薦)
或者:
oLi.slice=[].slice; //為oLi添加一個(gè)slice方法(會(huì)污染數(shù)組對(duì)象)
console.log(oLi.slice(1,3));//[li, li]
如果可以隨意改變?cè)瓕?duì)象感凤,可以 直接將其轉(zhuǎn)成真正的數(shù)組對(duì)象悯周。
Array.prototype.slice.call(oLi);
console.log(oLi.slice(1,3)); //[li, li]
(2)繼承
- 單繼承
function Person(name){
this.name=name;
}
function Man(sex,name){
this.sex=sex;
Person.call(this,name); //繼承Person的name
}
var p=new Man("male","JoeWright");
console.log(p.sex,p.name); //male JoeWright
- 多繼承
function Person(name){
this.name=name;
}
function Man(sex){
this.sex=sex;
}
function Coder(name,sex,learn){
this.learn=learn;
Person.call(this,name);
Man.call(this,sex);
}
var p2=new Coder("JoeWright","male","FE");
console.log(p2.name,p2.sex,p2.learn);//JoeWright male FE
(3)獲取數(shù)組的最值
Math.max()方法,支持傳遞多個(gè)參數(shù)陪竿,比如:
Math.max(1,4,2,3,7,5,6)
但是它不支持直接傳遞一個(gè)數(shù)組作為參數(shù)禽翼,比如:Math.max(new Array(1,4,2,3,7,5,6))
。
這里族跛,只要我們有方法把數(shù)組闰挡,一個(gè)一個(gè)拆分開來(lái),傳遞到Math.max()
方法中礁哄,就實(shí)現(xiàn)了傳遞數(shù)組的方法长酗。而apply
接收一個(gè)數(shù)組,并且是將數(shù)組中的每個(gè)值桐绒,分開來(lái)夺脾,傳遞給Math.max()
方法
例如:
var arr=[1,3,6,10,9];
console.log(Math.max.apply(null,arr)); //10
console.log(Math.min.apply(null,arr)); //1
//等價(jià)于
console.log(Math.max.apply(Math,arr)); //10
console.log(Math.min.apply(Math,arr)); //1
console.log(Math.max.apply("a",arr)); //10
console.log(Math.min.apply(a,arr)); //Uncaught ReferenceError: a is not defined(a未定義報(bào)錯(cuò))
但為什么上面的例子中apply傳的第一個(gè)參數(shù)為null和Math都會(huì)得到相同的結(jié)果呢之拨?在網(wǎng)上看了很多解答后,我得出一個(gè)結(jié)論: 按apply的語(yǔ)法上來(lái)講咧叭,apply的第一個(gè)參數(shù)此時(shí)是Math.max()方法運(yùn)行時(shí)指定的 this 值敦锌。需要注意的是,指定的 this 值并不一定是該函數(shù)執(zhí)行時(shí)真正的 this 值佳簸,如果這個(gè)函數(shù)處于非嚴(yán)格模式下,則指定為 null 或 undefined 時(shí)會(huì)自動(dòng)指向全局對(duì)象(瀏覽器中就是window對(duì)象)颖变,同時(shí)值為原始值(數(shù)字生均,字符串,布爾值)的 this 會(huì)指向該原始值的自動(dòng)包裝對(duì)象腥刹。 換句話說(shuō):當(dāng)我們傳Math就相當(dāng)于Math對(duì)象調(diào)用max方法马胧,我們傳window相當(dāng)于window調(diào)用這個(gè)方法,傳Number就相當(dāng)于Number函數(shù)對(duì)象調(diào)用max方法... 但是傳入的參數(shù)必須是一個(gè)對(duì)象(或者參數(shù)的原始值的自動(dòng)包裝對(duì)象)衔峰,所以我們傳123佩脊,"aaa",undefined都可以,但是傳入一個(gè)未定義的變量是不行的垫卤,當(dāng)然傳入null也是可以的威彰。
(4)合并數(shù)組
例如:
var arr=[1,3,6,10,9];
var arr2=[2,4,8];
[].push.apply(arr,arr2);
console.log(arr); //[1, 3, 6, 10, 9, 2, 4, 8]