What's this?
由于運(yùn)行期綁定的特性,JavaScript 中的 this 含義非常多纪挎,它可以是全局對(duì)象沽甥、當(dāng)前對(duì)象或者任意對(duì)象屯换,這完全取決于函數(shù)的調(diào)用方式
隨著函數(shù)使用場(chǎng)合的不同杠娱,this的值會(huì)發(fā)生變化挽牢。但是有一個(gè)總的原則谱煤,那就是this指的是摊求,調(diào)用函數(shù)的那個(gè)對(duì)象
作為函數(shù)調(diào)用
- 在函數(shù)被直接調(diào)用時(shí)this綁定到全局對(duì)象。在瀏覽器中刘离,window 就是該全局對(duì)象
var a = 1
console.log(this) //全局對(duì)象window
function fn(){
console.log(a) //1
console.log(this) //window
console.log(b) //b is nor defined
}
fn()
內(nèi)部函數(shù)
- 函數(shù)嵌套產(chǎn)生的內(nèi)部函數(shù)的this不是其父函數(shù)室叉,仍然是全局變量
function fn(){
function fn1(){
console.log(this) //window
}
fn1()
}
fn()
setTimeout、setInterval
- 這兩個(gè)方法執(zhí)行的函數(shù)this也是全局對(duì)象
document.addEventListener('click', function(e){
console.log(this); //document
setTimeout(function(){
console.log(this); //window
}, 200);
}, false);
- 如果想在setTimeout硫惕、setInterval執(zhí)行的函數(shù)中返回的this是document茧痕,方法一只需要有一個(gè)中間變量,保存this即可恼除。
document.addEventListener('click',function(e){
console.log(this) //document
var _this = this
setTimeout(function(){
console.log(_this) //document
})
})
- 如果想在setTimeout踪旷、setInterval執(zhí)行的函數(shù)中返回的this是document,方法二豁辉,使用bind令野,使其第一個(gè)參數(shù)為this
document.addEventListener('click',function(e){
setTimeout(function(){
console.log(this) //document
}.bind(this))
})
作為構(gòu)造函數(shù)調(diào)用
- 所謂構(gòu)造函數(shù),就是通過(guò)這個(gè)函數(shù)生成一個(gè)新對(duì)象(object)徽级。這時(shí)气破,this就指這個(gè)新對(duì)象
new 運(yùn)算符接受一個(gè)函數(shù) F 及其參數(shù):new F(arguments...)。這一過(guò)程分為三步:
- 創(chuàng)建類的實(shí)例餐抢。這步是把一個(gè)空的對(duì)象的 proto 屬性設(shè)置為 F.prototype 现使。
- 初始化實(shí)例。函數(shù) F 被傳入?yún)?shù)并調(diào)用旷痕,關(guān)鍵字 this 被設(shè)定為該實(shí)例碳锈。
- 返回實(shí)例。
具體實(shí)例:
function People(name){
this.name = name
}
People.prototype.sayName = function(){
console.log(this.name)
}
var p1 = new People('lili')
- 作為對(duì)象方法調(diào)用
在 JavaScript 中欺抗,函數(shù)也是對(duì)象殴胧,因此函數(shù)可以作為一個(gè)對(duì)象的屬性,此時(shí)該函數(shù)被稱為該對(duì)象的方法,在使用這種調(diào)用方式時(shí)团滥,this 被自然綁定到該對(duì)象
var obj = {
name: 'kasck',
fn: function(){
console.log(this) //obj
}
}
obj.fn()
- 對(duì)象方法的嵌套
var obj2 = {
name: 'lll',
obj3: {
fn: function(){
console.log(this) // obj3
}
}
}
obj2.obj3.fn()
- 小陷阱
var obj = {
name: 'kasck',
fn: function(){
console.log(this)
}
}
obj.fn()
var fn2 = obj.fn
fn2() //window.fn2() this === window
Function.prototype.bind
bind:返回一個(gè)函數(shù)竿屹,并且使函數(shù)內(nèi)部的this為傳入的第一個(gè)參數(shù)
var obj = {
name: 'kasck',
fn: function(){
console.log(this)
}
}
obj.fn()
obj3 = {a:3}
var fn3 = obj.fn.bind(obj3) //fn3函數(shù)內(nèi)部的this為bind傳入的第一個(gè)參數(shù)
fn3() // this = obj3
使用call和apply設(shè)置this
- call和apply調(diào)用一個(gè)函數(shù)傳入函數(shù)執(zhí)行上下文參數(shù)。
fn.call(context,parma1,parma2...);
fn.apply(context,paramArray);
語(yǔ)法很簡(jiǎn)單灸姊,第一個(gè)參數(shù)都是希望設(shè)置的this對(duì)象拱燃,不同之處在于call方法接收參數(shù)列表,而apply接收參數(shù)數(shù)組力惯。
fn2.call(obj1);
fn2.apply(obj2);
- call的實(shí)例
var value = 100;
var obj4 = {
value: 200
};
function fn4(a,b){
console.log(this.value + a + b)
}
fn4(3,4) //107
fn4.call(obj4,3,4) //207
- apply的實(shí)例
//與call對(duì)比碗誉,僅僅是調(diào)用參數(shù)的方式不同
fn4.apply(obj4,[3,4]) 207
更多用法
function joinStr(){
console.log(Array.prototype.join.call(arguments)) //a,b,c
console.log(Array.prototype.join.call(arguments,'-')) //a-b-c
var join = Array.prototype.join.bind(arguments)
console.log(join('-')) //a-b-c
}
joinStr('a','b','c')
得到一個(gè)數(shù)組的最大值和最小值
var arr = [1,3,5,9]
console.log(Math.max.apply(null,arr)) //9
console.log(Math.max.call(null,arr)) //NAN
console.log(Math.max.call(null,1,3,5,9)) //9
call與apply的不同之處:
- apply:
最多只能有兩個(gè)參數(shù):新this對(duì)象和一個(gè)數(shù)組argArray。如果給該方法傳遞多個(gè)參數(shù)父晶,則把參數(shù)都寫進(jìn)這個(gè)數(shù)組里面哮缺,當(dāng)然,即使只有一個(gè)參數(shù)甲喝,也要寫進(jìn)數(shù)組里面尝苇。如果argArry不是一個(gè)有效的數(shù)組或者不是arguments對(duì)象,那么將導(dǎo)致一個(gè)TypeError埠胖。如果沒(méi)有提供argArry和thisObj任何一個(gè)參數(shù)糠溜,那么Global對(duì)象將被用作thisObj,并且無(wú)法被傳遞任何參數(shù)直撤。 - call:
則是直接的參數(shù)列表非竿,主要用在JS對(duì)象各方法互相調(diào)用的時(shí)候,使當(dāng)前this實(shí)例指針保持一致或在特殊情況下需要改變this指針谋竖。如果沒(méi)有提供thisObj參數(shù)红柱,那么Global對(duì)象將被用作thisObj。
caller
在函數(shù)A調(diào)用函數(shù)B時(shí)蓖乘,被調(diào)用函數(shù)B會(huì)自動(dòng)生成一個(gè)caller屬性锤悄,指向調(diào)用它的函數(shù)對(duì)象,如果函數(shù)當(dāng)前未被調(diào)用驱敲,或并非被其他函數(shù)調(diào)用铁蹈,則caller為null
function fn(){
function fn1(){
console.log(fn1.caller) //fn
}
fn1()
}
fn()
arguments
- 在函數(shù)調(diào)用時(shí),會(huì)自動(dòng)在該函數(shù)內(nèi)部生成一個(gè)名為 arguments的隱藏對(duì)象
- 該對(duì)象類似于數(shù)組众眨,可以使用[]運(yùn)算符獲取函數(shù)調(diào)用時(shí)傳遞的實(shí)參
- 只有函數(shù)被調(diào)用時(shí)握牧,arguments對(duì)象才會(huì)創(chuàng)建,未調(diào)用時(shí)其值為null
function fn(name,age){
console.log(arguments)
var name = "frank"
console.log(arguments)
arguments[1] = 14
console.log(arguments)
}
fn('lili',23)
函數(shù)的執(zhí)行環(huán)境
一個(gè)函數(shù)被執(zhí)行時(shí)娩梨,會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境(ExecutionContext)沿腰,函數(shù)的所有的行為均發(fā)生在此執(zhí)行環(huán)境中,構(gòu)建該執(zhí)行環(huán)境時(shí)狈定,JavaScript 首先會(huì)創(chuàng)建 arguments變量颂龙,其中包含調(diào)用函數(shù)時(shí)傳入的參數(shù)
接下來(lái)創(chuàng)建作用域鏈习蓬,然后初始化變量。首先初始化函數(shù)的形參表措嵌,值為 arguments變量中對(duì)應(yīng)的值躲叼,如果 arguments變量中沒(méi)有對(duì)應(yīng)值,則該形參初始化為 undefined企巢。
如果該函數(shù)中含有內(nèi)部函數(shù)枫慷,則初始化這些內(nèi)部函數(shù)。如果沒(méi)有浪规,繼續(xù)初始化該函數(shù)內(nèi)定義的局部變量或听,需要注意的是此時(shí)這些變量初始化為 undefined,其賦值操作在執(zhí)行環(huán)境(ExecutionContext)創(chuàng)建成功后笋婿,函數(shù)執(zhí)行時(shí)才會(huì)執(zhí)行誉裆,這點(diǎn)對(duì)于我們理解JavaScript中的變量作用域非常重要,最后為this變量賦值缸濒,會(huì)根據(jù)函數(shù)調(diào)用方式的不同足丢,賦給this全局對(duì)象,當(dāng)前對(duì)象等
至此函數(shù)的執(zhí)行環(huán)境(ExecutionContext)創(chuàng)建成功绍填,函數(shù)開(kāi)始逐行執(zhí)行霎桅,所需變量均從之前構(gòu)建好的執(zhí)行環(huán)境(ExecutionContext)中讀取
三種變量
- 實(shí)例變量:(this)類的實(shí)例才能訪問(wèn)到的變量
- 靜態(tài)變量:(屬性)直接類型對(duì)象能訪問(wèn)到的變量
- 私有變量:(局部變量)當(dāng)前作用域內(nèi)有效的變量
function fn(){
var a = 1 //局部變量
this.b = 3 //實(shí)例變量
}
fn.c = 5 //靜態(tài)變量
原型鏈
- 代碼示例:
function Person(name,age){
this.name = name;
this.age = age
}
Person.prototype.sayName = function(){
console.log(this.name)
}
var p1 = new Person('lili',1);
var p2 = new Person('llll',3);
p1.sayName()
p2.sayName()
-
原型圖
捕獲.PNG
p1.__proto__.constructor === Person
Person.prototype.constructor === Person
p1.constructor === Person
- 我們通過(guò)函數(shù)定義了類
Person
栖疑,類(函數(shù))自動(dòng)獲取屬性prototype
- 每個(gè)類的實(shí)例都會(huì)有一個(gè)內(nèi)部屬性
__proto__
讨永,指向類的prototype
·
如果想看某一個(gè)對(duì)象xx是由誰(shuí)創(chuàng)建的,只需要看xx.proto.constructor === y,就由y創(chuàng)建遇革。
原型鏈相關(guān)問(wèn)題
有如下代碼卿闹,解釋Person、 prototype萝快、proto锻霎、p、constructor之間的關(guān)聯(lián)揪漩。
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){
console.log('My name is :' + this.name);
}
var p = new Person("若愚")
p.sayName();
- 通過(guò)函數(shù)定義了類Person旋恼,類(函數(shù))自動(dòng)獲取屬性prototype;
- 每個(gè)類的實(shí)例都會(huì)有一個(gè)內(nèi)部屬性
__proto__
奄容,指向類的prototype冰更; - P是構(gòu)造函數(shù)Person的一個(gè)實(shí)例,p的
__proto__
指向了Person的prototype屬性昂勒; - prototype是構(gòu)造函數(shù)內(nèi)部的原型函數(shù)蜀细,所以擁有prototype和
__proto__
屬性,其中contructor屬性指向構(gòu)造函數(shù)Person戈盈,__proto__
指向該對(duì)象的原型奠衔。
上例中谆刨,對(duì)對(duì)象 p可以這樣調(diào)用 p.toString()。toString是哪里來(lái)的? 畫出原型圖?并解釋什么是原型鏈归斤。
p.toString()
方法是繼承構(gòu)造函數(shù)Object的原型對(duì)象里定義的toString()
方法痊夭,首先p會(huì)找到自己的toString()
方法,如果沒(méi)有找到脏里,會(huì)沿著__proto__
屬性繼續(xù)到構(gòu)造函數(shù)Person的prototype
里找toString()
方法生兆,如果還是未找到,再繼續(xù)往Person.prototype
的__proto__
找膝宁。
原型鏈:由于原型對(duì)象本身也是對(duì)象鸦难,而每個(gè)JavaScript對(duì)象都有一個(gè)原型對(duì)象,每個(gè)對(duì)象都有一個(gè)隱藏的proto屬性员淫,原型對(duì)象也有自己的原型合蔽,而它自己的原型對(duì)象又可以有自己的原型,這樣就組成了一條鏈介返,這個(gè)就是原型鏈拴事。在訪問(wèn)對(duì)象的屬性時(shí),如果在對(duì)性本身中沒(méi)有找到圣蝎,則會(huì)去原型鏈中查找刃宵,如果找到,則返回值徘公,如果整個(gè)鏈都沒(méi)有找到牲证,則返回undefined。
instanceOf有什么作用关面??jī)?nèi)部邏輯是如何實(shí)現(xiàn)的坦袍?
instanceOf:判斷一個(gè)對(duì)象是否為另一個(gè)對(duì)象的實(shí)例
//
function isInstanceOf(obj,fn){
var oldProto = obj.__proto__;
do{
if(oldProto === fn.prototype){ //prototype是小寫的!
return true;
}else{
oldProto = oldProto.__proto__;
}
}while(oldProto){
return false;
}
}