《你不知道的JavaScript》整理(二)——this


最近在讀一本進階的JavaScript的書《你不知道的JavaScript(上卷)》,這次研究了一下“this”。

當一個函數被調用時,會創建一個活動記錄(執行上下文)。

這個記錄會包含函數在哪里被調用(調用棧)、函數的調用方法、傳入的參數等信息。

this就是記錄的其中一個屬性,會在函數執行的過程中用到。

this既不指向函數自身也不指向函數的作用域

this實際上是在函數被調用時發生的綁定,它指向什么完全取決於函數在哪里被調用

 

一、調用位置

調用位置就在當前正在執行的函數的前一個調用中,源碼查看

function baz() {
  // 當前調用棧是:baz
  // 因此,當前調用位置是全局作用域
  console.log("baz");
  bar(); // <-- bar 的調用位置
}

function bar() {
  // 當前調用棧是 baz -> bar
  // 因此,當前調用位置在 baz 中
  console.log("bar");
  foo(); // <-- foo 的調用位置
}

function foo() {
  // 當前調用棧是 baz -> bar -> foo
  // 因此,當前調用位置在 bar 中
  console.log("foo");
}
baz(); // <-- baz 的調用位置

 

二、綁定規則

你必須找到調用位置,然后判斷需要應用下面四條規則中的哪一條。

1)默認綁定

最常用的函數調用類型:獨立函數調用。可以把這條規則看作是無法應用其他規則時的默認規則

function foo() {
  console.log(this.a);
}
var a = 2;
foo(); // 2

 

2)隱式綁定

隱式綁定的規則是調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含

function foo() {
  console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2

但有時候會出現隱式丟失

function foo() {
  console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo; // 函數  
var a = "oops, global"; //
bar(); // "oops, global"

雖然bar是obj.foo的一個引用,但是實際上,它引用的是foo函數本身

因此此時的bar()其實是一個不帶任何修飾的函數調用,應用了默認綁定

 

3)顯式綁定

使用函數的call(..)和apply(..)方法。

function foo() {
  console.log(this.a);
}
var obj = {
    a: 2
};
foo.call(obj); // 2

在很多庫中經常能看到bind方法,這是一種硬綁定,一種顯式的強制綁定,下面是一種bind實現。

function foo(something) {
  console.log(this.a, something);
  return this.a + something;
}
// 簡單的輔助綁定函數 
function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}
var obj = {
  a: 2
};
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b); // 5

 

4)new綁定

使用new來調用函數,或者說發生構造函數調用時,會自動執行下面的操作:

1. 創建(或者說構造)一個全新的對象。

2. 這個新對象會被執行[[原型]]連接。

3. 這個新對象會綁定到函數調用的this

4. 如果函數沒有返回其他對象,那么new表達式中的函數調用會自動返回這個新對象。

function foo(a) {
  this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2

 

三、優先級

默認綁定的優先級是四條規則中最低的。

1)顯式綁定優先級比隱式綁定要更高

function foo() {
  console.log(this.a);
}
var obj1 = {
    a: 2,
    foo: foo
};
var obj2 = {
    a: 3,
    foo: foo
};

obj1.foo(); // 2 
obj2.foo(); // 3

obj1.foo.call(obj2); // 3 
obj2.foo.call(obj1); // 2

 

2)new綁定比隱式綁定優先級高

function foo(something) {
  this.a = something;
}
var obj1 = {
    foo: foo
};

obj1.foo(2);
console.log(obj1.a); // 2

var bar = new obj1.foo(4);
console.log(obj1.a); // 2 
console.log(bar.a); // 4

 

3)new綁定會修改顯示綁定中this

function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}
function foo(something) { 
  this.a = something;
}
 
var obj1 = {};
var bar = foo.bind(obj1); //bar被硬綁定到obj1上
bar(2);
console.log( obj1.a ); // 2
 
var baz = new bar(3); //
console.log( obj1.a ); // 沒有把obj1.a 修改為3,還是為2 
console.log( baz.a ); // 3

 

4)判斷this

1. 函數是否在new中調用(new綁定)?如果是的話this綁定的是新創建的對象。

var bar = new foo()

2. 函數是否通過call、apply(顯式綁定)或者硬綁定調用?如果是的話,this綁定的是指定的對象。

var bar = foo.call(obj2)

3. 函數是否在某個上下文對象中調用(隱式綁定)?如果是的話,this綁定的是那個上下文對象。

var bar = obj1.foo()

4. 如果都不是的話,使用默認綁定。如果在嚴格模式下,就綁定到undefined,否則綁定到全局對象。

var bar = foo()

 

四、綁定例外

1)被忽略的this

如果你把null或者undefined作為this的綁定對象傳入call、apply或者bind。

這些值在調用時會被忽略,實際應用的是默認綁定規則。

function foo() {
  console.log(this.a);
}
var a = 2;
foo.call(null); // 2

 

2)間接引用

你有可能(有意或者無意地)創建一個函數的“間接引用”。

在這種情況下,調用這個函數會應用默認綁定規則。

function foo() {
  console.log(this.a);
}
var a = 2; 
var o = { a: 3, foo: foo }; 
var p = { a: 4 }; 

o.foo(); // 3
(p.foo = o.foo)(); // 2

賦值表達式p.foo = o.foo的返回值是目標函數的引用,因此調用位置是foo()而不是p.foo()或者o.foo()。

 

3)軟綁定

如果可以給默認綁定指定一個全局對象和undefined以外的值。

那就可以實現和硬綁定相同的效果,同時保留隱式綁定或者顯式綁定修改this的能力。

可以通過一種被稱為軟綁定的方法來實現我們想要的效果。

Function.prototype.softBind = function(obj) {
  var fn = this;
  // 捕獲所有 curried 參數
  var curried = [].slice.call(arguments, 1);
  var bound = function() {
    return fn.apply((!this || this === (window || global)) ? obj : this, curried.concat.apply(curried, arguments));
  };
  bound.prototype = Object.create(fn.prototype);
  return bound;
};
function foo() {
  console.log("name: " + this.name);
}
var obj = { name: "obj" }, 
    obj2 = { name: "obj2" }, 
    obj3 = { name: "obj3" };
 
var fooOBJ = foo.softBind( obj ); 

fooOBJ(); // name: obj <---- 應用了軟綁定

obj2.foo = foo.softBind(obj); 
obj2.foo(); // name: obj2 

fooOBJ.call( obj3 ); // name: obj3

setTimeout( obj2.foo, 10 );// name: obj   <---- 應用了軟綁定

軟綁定版本的foo()可以手動將this綁定到obj2或者obj3上。

但如果應用默認綁定,則會將this綁定到obj。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM