What's this?
由于運行期綁定的特性,JavaScript 中的 this 含義非常多,它可以是全局對象、當前對象或者任意對象,這完全取決于函數的調用方式
隨著函數使用場合的不同,this的值會發生變化。但是有一個總的原則,那就是this指的是,調用函數的那個對象
作為函數調用
- 在函數被直接調用時this綁定到全局對象。在瀏覽器中,window 就是該全局對象
var a = 1
console.log(this) //全局對象window
function fn(){
console.log(a) //1
console.log(this) //window
console.log(b) //b is nor defined
}
fn()
內部函數
- 函數嵌套產生的內部函數的this不是其父函數,仍然是全局變量
function fn(){
function fn1(){
console.log(this) //window
}
fn1()
}
fn()
setTimeout、setInterval
- 這兩個方法執行的函數this也是全局對象
document.addEventListener('click', function(e){
console.log(this); //document
setTimeout(function(){
console.log(this); //window
}, 200);
}, false);
- 如果想在setTimeout、setInterval執行的函數中返回的this是document,方法一只需要有一個中間變量,保存this即可。
document.addEventListener('click',function(e){
console.log(this) //document
var _this = this
setTimeout(function(){
console.log(_this) //document
})
})
- 如果想在setTimeout、setInterval執行的函數中返回的this是document,方法二,使用bind,使其第一個參數為this
document.addEventListener('click',function(e){
setTimeout(function(){
console.log(this) //document
}.bind(this))
})
作為構造函數調用
- 所謂構造函數,就是通過這個函數生成一個新對象(object)。這時,this就指這個新對象
new 運算符接受一個函數 F 及其參數:new F(arguments...)。這一過程分為三步:
- 創建類的實例。這步是把一個空的對象的 proto 屬性設置為 F.prototype 。
- 初始化實例。函數 F 被傳入參數并調用,關鍵字 this 被設定為該實例。
- 返回實例。
具體實例:
function People(name){
this.name = name
}
People.prototype.sayName = function(){
console.log(this.name)
}
var p1 = new People('lili')
- 作為對象方法調用
在 JavaScript 中,函數也是對象,因此函數可以作為一個對象的屬性,此時該函數被稱為該對象的方法,在使用這種調用方式時,this 被自然綁定到該對象
var obj = {
name: 'kasck',
fn: function(){
console.log(this) //obj
}
}
obj.fn()
- 對象方法的嵌套
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:返回一個函數,并且使函數內部的this為傳入的第一個參數
var obj = {
name: 'kasck',
fn: function(){
console.log(this)
}
}
obj.fn()
obj3 = {a:3}
var fn3 = obj.fn.bind(obj3) //fn3函數內部的this為bind傳入的第一個參數
fn3() // this = obj3
使用call和apply設置this
- call和apply調用一個函數傳入函數執行上下文參數。
fn.call(context,parma1,parma2...);
fn.apply(context,paramArray);
語法很簡單,第一個參數都是希望設置的this對象,不同之處在于call方法接收參數列表,而apply接收參數數組。
fn2.call(obj1);
fn2.apply(obj2);
- call的實例
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的實例
//與call對比,僅僅是調用參數的方式不同
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')
得到一個數組的最大值和最小值
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:
最多只能有兩個參數:新this對象和一個數組argArray。如果給該方法傳遞多個參數,則把參數都寫進這個數組里面,當然,即使只有一個參數,也要寫進數組里面。如果argArry不是一個有效的數組或者不是arguments對象,那么將導致一個TypeError。如果沒有提供argArry和thisObj任何一個參數,那么Global對象將被用作thisObj,并且無法被傳遞任何參數。 - call:
則是直接的參數列表,主要用在JS對象各方法互相調用的時候,使當前this實例指針保持一致或在特殊情況下需要改變this指針。如果沒有提供thisObj參數,那么Global對象將被用作thisObj。
caller
在函數A調用函數B時,被調用函數B會自動生成一個caller屬性,指向調用它的函數對象,如果函數當前未被調用,或并非被其他函數調用,則caller為null
function fn(){
function fn1(){
console.log(fn1.caller) //fn
}
fn1()
}
fn()
arguments
- 在函數調用時,會自動在該函數內部生成一個名為 arguments的隱藏對象
- 該對象類似于數組,可以使用[]運算符獲取函數調用時傳遞的實參
- 只有函數被調用時,arguments對象才會創建,未調用時其值為null
function fn(name,age){
console.log(arguments)
var name = "frank"
console.log(arguments)
arguments[1] = 14
console.log(arguments)
}
fn('lili',23)
函數的執行環境
一個函數被執行時,會創建一個執行環境(ExecutionContext),函數的所有的行為均發生在此執行環境中,構建該執行環境時,JavaScript 首先會創建 arguments變量,其中包含調用函數時傳入的參數
接下來創建作用域鏈,然后初始化變量。首先初始化函數的形參表,值為 arguments變量中對應的值,如果 arguments變量中沒有對應值,則該形參初始化為 undefined。
如果該函數中含有內部函數,則初始化這些內部函數。如果沒有,繼續初始化該函數內定義的局部變量,需要注意的是此時這些變量初始化為 undefined,其賦值操作在執行環境(ExecutionContext)創建成功后,函數執行時才會執行,這點對于我們理解JavaScript中的變量作用域非常重要,最后為this變量賦值,會根據函數調用方式的不同,賦給this全局對象,當前對象等
至此函數的執行環境(ExecutionContext)創建成功,函數開始逐行執行,所需變量均從之前構建好的執行環境(ExecutionContext)中讀取
三種變量
- 實例變量:(this)類的實例才能訪問到的變量
- 靜態變量:(屬性)直接類型對象能訪問到的變量
- 私有變量:(局部變量)當前作用域內有效的變量
function fn(){
var a = 1 //局部變量
this.b = 3 //實例變量
}
fn.c = 5 //靜態變量
原型鏈
- 代碼示例:
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
- 我們通過函數定義了類
Person
,類(函數)自動獲取屬性prototype
- 每個類的實例都會有一個內部屬性
__proto__
,指向類的prototype
·
如果想看某一個對象xx是由誰創建的,只需要看xx.proto.constructor === y,就由y創建。
原型鏈相關問題
有如下代碼,解釋Person、 prototype、proto、p、constructor之間的關聯。
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){
console.log('My name is :' + this.name);
}
var p = new Person("若愚")
p.sayName();
- 通過函數定義了類Person,類(函數)自動獲取屬性prototype;
- 每個類的實例都會有一個內部屬性
__proto__
,指向類的prototype; - P是構造函數Person的一個實例,p的
__proto__
指向了Person的prototype屬性; - prototype是構造函數內部的原型函數,所以擁有prototype和
__proto__
屬性,其中contructor屬性指向構造函數Person,__proto__
指向該對象的原型。
上例中,對對象 p可以這樣調用 p.toString()。toString是哪里來的? 畫出原型圖?并解釋什么是原型鏈。
p.toString()
方法是繼承構造函數Object的原型對象里定義的toString()
方法,首先p會找到自己的toString()
方法,如果沒有找到,會沿著__proto__
屬性繼續到構造函數Person的prototype
里找toString()
方法,如果還是未找到,再繼續往Person.prototype
的__proto__
找。
原型鏈:由于原型對象本身也是對象,而每個JavaScript對象都有一個原型對象,每個對象都有一個隱藏的proto屬性,原型對象也有自己的原型,而它自己的原型對象又可以有自己的原型,這樣就組成了一條鏈,這個就是原型鏈。在訪問對象的屬性時,如果在對性本身中沒有找到,則會去原型鏈中查找,如果找到,則返回值,如果整個鏈都沒有找到,則返回undefined。
instanceOf有什么作用?內部邏輯是如何實現的?
instanceOf:判斷一個對象是否為另一個對象的實例
//
function isInstanceOf(obj,fn){
var oldProto = obj.__proto__;
do{
if(oldProto === fn.prototype){ //prototype是小寫的!
return true;
}else{
oldProto = oldProto.__proto__;
}
}while(oldProto){
return false;
}
}