- this機(jī)制:this在運(yùn)行時(shí)進(jìn)行綁定,并不是在編寫是進(jìn)行綁定,它的上下文取決于函數(shù)調(diào)用時(shí)的各種條件。this的綁定和和函數(shù)的聲明位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式
- 當(dāng)一個(gè)函數(shù)被調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)活動(dòng)記錄(執(zhí)行上下文),這個(gè)記錄會(huì)包含函數(shù)在哪里被調(diào)用、函數(shù)的調(diào)用方式、傳入的參數(shù)等信息,this就是這個(gè)記錄的一個(gè)屬性,會(huì)在函數(shù)的執(zhí)行中被找到。
四種綁定規(guī)則
默認(rèn)綁定
我們先來(lái)看一段代碼:
var name = 'global'
function person(){
var name = 'inner'
console.log(this.name)
}
person() // global
這里我們執(zhí)行person()、輸出結(jié)果是在全局上的name的值,那么為什么呢,因?yàn)檫@里的this指向全局對(duì)象,執(zhí)行的是默認(rèn)綁定。
那么我們?cè)趺粗篮螘r(shí)使用默認(rèn)綁定呢,這里函數(shù) person()在調(diào)用的時(shí)候沒有應(yīng)用任何的修飾,直接使用的是 person(),因此就只能使用默認(rèn)綁定,那么使用修飾調(diào)用是什么樣的、之后會(huì)進(jìn)行詳細(xì)說(shuō)明。
但是也不是默認(rèn)綁定的所以情況都是指向全局對(duì)象,當(dāng)你使用嚴(yán)格模式編寫代碼時(shí),
this指向undefined。
'use strict'
var name = 'global'
function person(){
var name = 'inner'
console.log(this.name)
}
person() //Cannot read property 'name' of undefined
隱式綁定
先上代碼:
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj = {
name:'objName',
person: person
}
obj.person() // objName
這里我們遇見第一種帶有修飾的調(diào)用obj.person() 而非默認(rèn)綁定中的直接調(diào)用person()
就像我們最開始說(shuō)的那種,this不是在聲明時(shí)進(jìn)行綁定的,而是在調(diào)用時(shí)進(jìn)行綁定的。當(dāng)函數(shù)的引用有上下文對(duì)象時(shí),隱式綁定會(huì)把函數(shù)調(diào)用中的this綁定到這個(gè)上下文對(duì)象,這個(gè)例子中this綁定到obj對(duì)象上,this.name就相當(dāng)于obj.name,我們最終就會(huì)得到objName。
下面這個(gè)例子中,說(shuō)明只有在引用鏈的最后一層起作用。
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name:'obj1Name',
person: person
}
var obj2 = {
name:'obj2Name',
obj1: obj1
}
obj2.obj1.person() // obj1Name
常見面試題:隱式丟失問題
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name:'obj1Name',
person: person
}
var obj2 = obj1.person
obj2()// global
這里聲明一個(gè)變量 obj2 = obj1.person 之后調(diào)用 obj(2) 雖然obj2是對(duì)obj1.person的引用,但是在下文的調(diào)用時(shí)使用的是沒有任何修飾的調(diào)用,即obj2,這將應(yīng)用我們上面提到的默認(rèn)綁定,即非嚴(yán)格模式下this綁定到全局對(duì)象,
嚴(yán)格模式下綁定到undefined,輸出golbal也符合我們的預(yù)期。
再看下面的代碼:
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
function student(fn) {
// fn=obj1.person
fn()
}
var obj1 = {
name: 'obj1Name',
person: person
}
student(obj1.person) //global
這里執(zhí)行student()是相當(dāng)于 fn=obj1.person fn是對(duì)obj1.person的引用,和上個(gè)例子中同樣,發(fā)生隱式綁定丟失,對(duì)this使用默認(rèn)綁定。
setTimeout:
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name: 'obj1Name',
person: person
}
setTimeout(obj1.person, 1000); // node 環(huán)境下 undefined
我們來(lái)看下 mdn對(duì)this指向錯(cuò)誤的解釋:
由setTimeout()調(diào)用的代碼運(yùn)行在與所在函數(shù)完全分離的執(zhí)行環(huán)境上。這會(huì)導(dǎo)致,這些代碼中包含的 this 關(guān)鍵字在非嚴(yán)格模式會(huì)指向 window (或全局)對(duì)象,嚴(yán)格模式下為 undefined,這和所期望的this的值是不一樣的
mdn上polyfill實(shí)現(xiàn)setTimeout的部分代碼
window.setTimeout = function(vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
var aArgs = Array.prototype.slice.call(arguments, 2);
return __nativeST__(vCallback instanceof Function ? function() {
vCallback.apply(null, aArgs);
} : vCallback, nDelay);
}
我們簡(jiǎn)化一下:去掉args
window.setTimeout = function(vCallback, nDelay ) {
return __nativeST__(vCallback instanceof Function ? vCallback() : vCallback, nDelay);
}
我們可以看出和上面一樣,vCallback = obj1.person 同樣是對(duì)obj1.person的引用。
顯示綁定
call、apply
一般來(lái)說(shuō)基本上所有的函數(shù)都是由Function創(chuàng)建,Function的原型鏈上有兩個(gè)方法call,apply
mdn上關(guān)于call和apply的方法接收的參數(shù):
call: fun.call(thisArg, arg1, arg2, ...)
apply:fun.apply(thisArg, [argsArray])
可以看出第一個(gè)參數(shù)是一樣的都是this,不同點(diǎn)在于call接收的是參數(shù)列表,apply接收一個(gè)數(shù)組。
這種顯示的更改this的指向方法,我們稱之為顯示綁定。
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name: 'obj1Name',
person: person
}
person.call(obj1) //obj1Name
這樣我們雖然解決了this綁定的值,但是并沒有解決之前提到的綁定丟失問題。
bind:
變種:我們封裝一個(gè)函數(shù),當(dāng)每次調(diào)用函數(shù)時(shí)將this綁定到obj1上。
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name: 'obj1Name',
person: person
}
function bind(fn,obj,...args){
return function(){
fn.apply(obj,args)
}
}
var res = bind(person,obj1)
res() //obj1Name
同樣,javascript 也為我們提供了這個(gè)方法:Function.protoype.bind()
mdn 上關(guān)于bind()的polyfill:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
// 獲取調(diào)用時(shí)(fBound)的傳參.bind 返回的函數(shù)入?yún)⑼沁@么傳遞的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 維護(hù)原型關(guān)系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
可以看出都是和我們寫的bind()核心是一樣的內(nèi)部調(diào)用call、apply。
new 綁定
當(dāng)new 一個(gè)函數(shù)的時(shí)候會(huì)執(zhí)行一下的幾步:
- 創(chuàng)建一個(gè)空對(duì)象;
- 將空對(duì)象的原型指向函數(shù)的property
- 調(diào)用apply、call 方法空對(duì)象的this指向函數(shù)
- 如果函數(shù)返回了一個(gè)“對(duì)象”,那么這個(gè)對(duì)象會(huì)取代整個(gè)new出來(lái)的結(jié)果。如果構(gòu)造函數(shù)沒有返回對(duì)象,那么new出來(lái)的結(jié)果為步驟1創(chuàng)建的對(duì)象
之前寫了一篇模擬實(shí)現(xiàn) new的文章
new的模擬實(shí)現(xiàn)
示例:
function Person(name) {
this.name = name
}
var student = new Person('lili')
console.log(student.name) // lili
ES6 箭頭函數(shù)
箭頭函數(shù)本身沒有this,而是根據(jù)外層(函數(shù)或者全局)作用域來(lái)決定this.
var name = 'outer'
function person() {
setTimeout(() => {
console.log(this.name)
}, 100)
}
var obj = {
name: 'lili',
person: person
}
obj.person() // lili