作者::Wflynn
什麼是函數的調用位置
調用位置就是函數在代碼中被調用的位置(而不是聲明的位置)
為什麼要了解調用位置:隻有了解函數的調用位置才能進一步的确定 this
的綁定對象
function baz () {
// 目前調用棧是:baz, 是以,目前調用位置是全局作用域
console.log("baz");
bar(); // bar 的調用位置
}
function bar () {
// 目前調用棧是 baz -> bar,是以,目前調用位置在 baz 中
console.log("bar");
fnn(); // fnn 的調用位置
}
function fnn () {
// 目前調用棧是 baz -> bar -> fnn, 是以,目前調用位置在 bar 中
console.log("fnn");
}
baz(); // baz 的調用位置
this 是什麼
this
是包含它的函數作為方法被調用時所屬的對象。
- 包含它的函數。
- 作為方法被調用時。
- 所屬的對象。
随着函數使用場合的不同,this 的值會發生變化。this 指向什麼,完全取決于什麼地方以什麼方式調用,而不是建立時。
this 的四種綁定規則
this
的
4
種綁定規則分别是:預設綁定、隐式綁定、顯式綁定、
new
綁定。優先級從低到高。
new
綁定 > 顯式綁定> 隐式綁定 > 預設綁定
預設綁定
最常用的函數調用類型:獨立函數調用。可以把這條規則看作是無法應用其他規則時的預設規則。
如代碼(非嚴格模式下)和圖檔所示
-
被解析成了this.a
,window.a
中的fnn
是等于this
的。window
- 由于
是直接使用不帶任何修飾的函數引用進行調用的,是以隻能使用預設綁定,無法應用其他規則,是以fnn()
指向全局對象。。this
function fnn() {
console.log(this);
console.log(this === window);
console.log(this.a);
}
var a = 2;
fnn(); // 2
隐式綁定
調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含
如代碼(非嚴格模式下)和圖檔所示
-
被解析成了this.a
,obj.a
中的fnn
是等于this
的。obj
- 由于調用
函數時,有引用上下文對象fnn()
,隐式綁定規則會把函數調用中的obj
綁定到這個上下文對象this
,是以obj
指向this
對象。。obj
function fnn() {
console.log(this);
console.log(this === obj);
console.log(this.a);
}
var obj = {
a: 2,
fnn: fnn
};
obj.fnn(); // 2
對象屬性引用鍊中隻有最頂層或者說最後一層會影響調用位置
如代碼所示,最後輸出的 等于
this.a
,因為最後調用 fnn 的上下文對象是
888
,是以 this 綁定在
obj1
上
obj1
function fnn() {
console.log(this);
console.log(this === obj1); // true
console.log(this.a); // 888
}
var obj1 = {
a: 888,
fnn: fnn
};
var obj = {
a: 2,
obj1: obj1
};
obj.obj1.fnn(); // this.a 輸出 888
隐式丢失
一個最常見的
this
綁定問題就是被隐式綁定的函數會丢失綁定對象,也就是說它會應用預設綁定,進而把
this
綁定到全局對象或者
undefined
上,取決于是否是嚴格模式。
如代碼所示,最後輸出的 等于
this.a
,雖然
888
是
bar
的一個引用,但是實際上,它引用的是
obj.fnn
fnn
函數本身,
是以此時的
其實是一個不帶任何修飾的函數調用,是以應用了預設綁定。
bar()
function fnn() {
console.log(this); // window 對象
console.log(this == window); // true
console.log(this.a); // 888
}
var obj = {
a: 2,
fnn: fnn
};
var bar = obj.fnn;
var a = "888"; // a 是全局對象的屬性
bar(); // 888
回調函數中的例子
function fnn() {
console.log(this); // window 對象
console.log(this == window); // true
console.log(this.a); // 888
}
function doFnn(fn) {
// fn 其實引用的是 fnn
fn(); // <-- 調用位置!
}
var obj = {
a: 2,
fnn: fnn
};
var a = "888"; // a 是全局對象的屬性
doFnn(obj.fnn); // 888
顯式綁定
使用
call
,
apply
或者
bind
方法綁定
call
call()
方法使用一個指定的
this
值和單獨給出的一個或多個參數來調用一個函數
function fnn(arg1,) {
console.log(this);
console.log(this === obj); // true
console.log(this.a); // 888
console.log(arg1, arg2); // 參數一 參數二
}
var obj = {
a: 888,
fnn: fnn
};
var a = 2
fnn.call(obj, '參數一', '參數二'); // 888
apply
apply()
方法調用一個具有給定
this
值的函數,以及以一個數組(或類數組對象)的形式提供的參數。
function fnn(arg1,) {
console.log(this);
console.log(this === obj); // true
console.log(this.a); // 888
console.log(arg1, arg2); // 參數一 參數二
}
var obj = {
a: 888,
fnn: fnn
};
var a = 2
fnn.apply(obj, ['參數一', '參數二']); // 888
bind
bind()
方法建立一個新的函數,在
bind()
被調用時,這個新函數的
this
被指定為
bind()
的第一個參數,而其餘參數将作為新函數的參數,供調用時使用。
function fnn(arg1,) {
console.log(this);
console.log(this === obj); // true
console.log(this.a); // 888
console.log(arg1, arg2); // 參數一 參數二
}
var obj = {
a: 888,
fnn: fnn
};
var a = 2
const fnnBind = fnn.bind(obj, '參數一', '參數二');
fnnBind() // 888
bind
與
apply
和
call
的差別是,
bind
是建立一個函數,但不會直接調用
new 綁定
使用
new
來調用
People(..)
時,我們會構造一個新對象并把它綁定到
People(..)
調用中的
this
上。
代碼示例
// 聲明一個構造函數
function People(name){
console.log(fnn != this); // true
this.name = name;
}
// 使用 new 建立執行個體對象 fnn
var fnn = new People("FX");
console.log(fnn);
console.log(fnn.name) // FX;
如代碼和圖檔所示,我們
new
一個執行個體對象,代碼執行過程如下
var fnn = {} // 建立一個空對象; 或者 var fnn = new Object()
fnn.__proto__ = People.prototype // 将該對象 fnn 的隐式原型指向構造函數顯式原型
People.call(fnn, "FX") // 将構造函數中 this 指向建立的對象 fnn,并傳入參數 "FX"
return fnn // 傳回對象 fnn,person 指向建立的對象 fnn(對象類型指派為按引用傳遞,fnn 與 person 指向同一個對象)
為什麼 的值為
console.log(fnn != this)
true
,關于這一點我還不是特别了解。
按照我得想法,可能是在
對象執行個體的過程中,執行個體對象實際并沒有建立完畢,導緻的不相等,如果有更好的了解,歡迎大家留言。
new
new
綁定遇到
retrun
function fnn () {
this.user = 'fx'
return {}
}
var a = new fnn()
console.log(a.user) // undefined
function fnn () {
this.user = 'fx'
return function () {
}
}
var a = new fnn()
console.log(a.user) // undefined
function fnn () {
this.user = 'fx'
return 1
}
var a = new fnn()
console.log(a.user) // fx
function fnn () {
this.user = 'fx'
return undefined
}
var a = new fnn()
console.log(a.user) // fx
function fn () {
this.user = 'fx'
return null
}
var a = new fn
console.log(a.user) // fx
如果傳回值是一個對象,那麼
this
指向的就是那個傳回的對象,如果傳回值不是一個對象那麼
this
還是指向函數的執行個體。
還有一點就是雖然
null
也是對象,但是在這裡
this
還是指向那個函數的執行個體。
優先級測試
顯示綁定 與 隐式綁定
如下代碼,可以看出 顯示綁定 優先級大于 隐式綁定
function fnn () {
console.log(this.a)
}
var obj1 = {
a: 2,
fnn: fnn
}
var obj2 = {
a: 3,
fnn: fnn
}
obj1.fnn() // 2
obj2.fnn() // 3
obj1.fnn.call(obj2) // 3
obj2.fnn.call(obj1) // 2
new
綁定 與 隐式綁定
如下代碼,可以看出 new
綁定 優先級大于 隐式綁定
function fnn (num) {
this.a = num
}
var obj1 = {
a: 2,
fnn: fnn
}
var bar = new obj1.fnn(4);
console.log(obj1.a); // 2
console.log(bar.a); // 4
new
綁定 與 顯示綁定
如下代碼,可以看出 new
綁定 優先級大于 顯示綁定
function fnn (num) {
this.a = num
}
var obj1 = {
a: 2,
}
var bar = fnn.bind(obj1);
bar(888)
console.log(obj1.a); // 888
var baz = new bar(666);
console.log(obj1.a); // 888
console.log(baz.a); // 666
this 丢失的情況
如果你把
null
或者
undefined
作為
this
的綁定對象傳入
call
、
apply
或者
bind
,這些值在調用時會被忽略,實際應用的是預設綁定規則,如下代碼
function fnn() {
console.log(this.a);
}
var a = 2;
fnn.call(null); // 2
間接引用的問題
如下代碼,指派表達式
p.fnn = o.fnn
的傳回值是目标函數的引用,是以調用位置是
fnn()
而不是
p.fnn()
或者
o.fnn()
,這裡會應用預設綁定
function fnn () {
console.log(this.a)
}
var a = 2
var o = {
a: 3,
fnn: fnn
}
var p = {
a: 4
}
o.fnn(); // 3
(p.fnn = o.fnn)() // 2
箭頭函數的 this
如下代碼所示,
fnn()
内部建立的箭頭函數會捕獲調用時
fnn()
的
this
。由于
fnn()
的
this
綁定到
obj1
,
bar
(引用箭頭函數)的
this
也會綁定到
obj1
,箭頭函數的綁定無法被修改。(
new
也不行!)
function fnn () {
// 傳回一個箭頭函數
return () => {
// this 繼承自 fnn()
console.log(this.a)
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = fnn.call(obj1)
bar.call(obj2) // 2
非箭頭函數,輸出
3
function fnn () {
// 傳回一個箭頭函數
return function () {
// this 繼承自 fnn()
console.log(this.a)
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = fnn.call(obj1)
bar.call(obj2) // 3
測試題
示例一
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); // 12
}
}
}
o.b.fn(); // 12
var o = {
a:10,
b:{
// a:12,
fn:function(){
console.log(this.a); //undefined
}
}
}
o.b.fn(); // undefined
示例二
var o = {
a: 10,
b: {
a: 12,
fn: function () {
console.log(this.a) //undefined
console.log(this) //window
}
}
}
var j = o.b.fn
j()
示例三
var x = 10
var obj = {
x: 20,
f: function () {
console.log(this.x) // 20
function fnn () {
console.log(this.x)
}
fnn() // 10 預設綁定,這裡 this 綁定的是 window
}
}
obj.f()
-
使用預設綁定,fnn(1)
相當于this.a = arg
,window.a = arg
相當于return this
return window
-
相當于var a = fnn(1)
,是以window.a = window
等于a.a
,依次類推window
仍舊是 windowa.a.a.a
-
使用預設綁定,fnn(10)
相當于this.a = 10
,window.a = 10
相當于return this
return window
-
相當于console.log(b.a)
等于 10window.a
function fnn (arg) {
this.a = arg
return this
}
var a = fnn(1)
console.log(a.a) // window
var b = fnn(10)
console.log(b.a) // 10
var x = 10
var obj = {
x: 20,
f: function () {
console.log(this.x)
}
}
var bar = obj.f
var obj2 = {
x: 30,
f: obj.f
}
obj.f() // 20
bar() // 10
obj2.f() // 30