JavaScript 函數調用和this指針

函數調用和this指針

1. 全局環境的this指針

瀏覽器全局環境下this指向window對象javascript

console.log(this);
//Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

nodejs環境下this指向global對象html

2. 函數中的this指針

2.1 全局環境下函數調用

非嚴格模式this指向window,嚴格模式除箭頭函數外指向undefinejava

//a.js 非嚴格模式
function fun1(){console.log(this)}
fun1();//window

(function(){console.log(this)})() //window

var fun2 = ()=>{console.log(this)}
fun2();//window

//b.js 嚴格模式
'use strict';
function fun1(){console.log(this)}
fun1();//undefined

(function(){console.log(this)})() //undefined

var fun2 = ()=>{console.log(this)}
fun2();//window

2.2 做爲對象方法調用

function做爲對象屬性時,this指針指向對象的實例node

var obj1 = {
    name: 'a',
    func1:function(){console.log(this.name);}
}
obj1.func1();//a
//等同於
function func2(){console.log(this.name);}
var obj2 = {
    name: 'b',
    funRef:func2
}
obj2.funRef();

2.3 構造函數調用

構造函數調用時,this指針指向函數的實例(function也是對象的一種)瀏覽器

function func3(c){
    this.c = c;
    console.log(this.c);//c
    console.log(this);//func3 {c: "c"}
}
var finst3 = new func3('c');

2.4 同步回調函數

同步回調函數中的this指針服從上面1-3點的規則閉包

function callback()
{
    console.log(this.d);//undefined
    console.log(this);//window
}
function func4(cb){
    cb.d = 'd';    
    cb();
}
func4(callback);

2.5 異步回調函數

關於JavaScript是單線程執行就很少提了,這裏詳細分析一下對於異步操做的回調函數中this的指向問題。
提到異步操做就不得不提Event Loop,其詳細介紹能夠參見阮一峯大神的這篇app

簡單來講就是:異步

  • 全部異步函數的執行被放在「任務隊列」中由另一個單獨的線程執行。當異步操做有結果時,會通知主線程調用該異步操做的回調函數


注意:這裏就有問題了,當主線程調用異步操做的回調函數時,是以什麼環境和做用域來執行的呢?
async

答案是: 以全局環境和執行回調函數時的做用域鏈來執行回調函數。
函數

參考如下代碼:

setTimeout(function(){
    console.log(this);//1秒後輸出window
    }, 1000);
//再看
var asyncFun1 = {
    propA:"a",
    synFun:function() {        
        console.log(this.propA);
        console.log(this);        
    },
    asynFun: function(){setTimeout(this.synFun, 1000)}
}
asyncFun1.synFun();//輸出依次是
//a
//{propA: "a", synFun: ƒ, asynFun: ƒ}
asyncFun1.asynFun();
//undefined
//window
//再再看
var asyncFun2={
  propB:"b",
  asynFun:function(){
      var c = 'string c';
      setTimeout(function(){
          console.log(c);
      },1000);
    }
}
asyncFun2.asynFun();//輸出'string c'

注意爲何會輸出'string c': 雖然回調函數運行是在全局環境(this指向window),可是在其做用域鏈中是可以訪問到變量c的。

常見的異步操做包括:

  • setTimeout/setInterval
  • XMLHTTPRequest
  • jQuery AJAX(這裏的this被處理過並不指向window)
  • Fetch
  • Promise

關於如何在異步操做的回調函數中使用正確的this指針,往下看。

3. 在異步函數回調中正確使用this指針

經過上面的分析咱們知道在異步回調函數中this會指向window,那麼咱們須要怎麼作才能讓this指向咱們指定的對象呢?

3.1 箭頭函數與做用域鏈

3.1.1 箭頭函數定義: (x,y)=>{return x+y;}

  • 當參數只有一個時,小括號()能夠省略: x=>{return x+1;}
  • 當函數體只有一行語句而且該語句結果做爲返回值時,{}花括號能夠省略: x=>x+1;
var fun1 = (x)=>{return x + 1;}
var fun2 = x=>{return x+1;}
var fun3 = x=>x+1;
//返回值都是2
fun1(1);//2
fun2(1);//2
fun3(1);//2

3.1.2 箭頭函數特性: 箭頭函數體內沒有本身的this指針

那麼箭頭函數體內的this指針將符合做用域鏈原則,指向做用域鏈上最近的this

function fun4() {
  setTimeout(() => {
    console.log(this.id);
  }, 1000);
}
var id = 1;
fun4.call({ id: 2 });//輸出爲2
fun4();//輸出爲1

分析以下:

  • 若是異步操做的回調函數是普通函數,則其this指向window對象。
  • 但箭頭函數體內沒有this指針,則按做用域鏈原則,回調函數中的this指針應該爲fun4的this指針。
  • fun4做爲普通函數,在全局環境調用時this指向window對象,因此 fun4();輸出爲1
  • function.call做用在調用函數時,將新的對象替換原有對象。則意味着fun4.call時fun4體內的this指針將指向{ id: 2 },因此輸出爲2

3.2 閉包和做用域鏈

接下來看看閉包中的this指針問題

//a.js
function debounce(fn, delay) {  
  var timer = null;
  return function() {    
    var context = this;
    var args = arguments;    
    //定義context和args,經過做用域鏈達到保存this指針和arguments的做用
    clearTimeout(timer);
    timer = setTimeout(function() {
       //用apply保證回調函數的this指針不會被異步函數重置爲window 
       fn.apply(context, args);
    }, delay);
  }
}
//等同於
function debounce(fn, delay) {  
  var timer = null;
  return function() {    
    clearTimeout(timer);
    timer = setTimeout(()=>
      {fn.apply(this, arguments) }, delay);
  }
}

document.a1 = "2";
var obj1 = {
    a1:"1",    
    fun1:function(){
        console.log(this);
        console.log(this.a1);
    }
}

document.addEventListener('scroll', debounce(obj1.fun1, 2000));
//2秒後輸出
//document
//2
//b.js
function debounce(fn, delay) {  
  var timer = null;
  return function() {    
    clearTimeout(timer);
    timer = setTimeout(()=> fn(), delay);
  }
}

window.a1 = "3";
document.a1 = "2";
var obj1 = {
    a1:"1",    
    fun1:function(){
        console.log(this);
        console.log(this.a1);
    }
}

document.addEventListener('scroll', debounce(obj1.fun1, 2000));
//2秒後輸出
//window
//3

3.3 Call,Apply,Bind

關於這三個函數的對比和做用網上介紹不少,其主要做用爲:
使用that替換函數內部this的指針指向爲that。

function.call(that, arg1, arg2);
function.apply(that, [arg1, arg2]);
function.bind(that);
//以下
var obj1 = {
  a1:"1",
  fun1:function(){console.log(this.a1)}
}
var obj2 = {
  a1:"2"
}
obj1.fun1();//1
obj1.fun1.call(obj2);//2
var funBind = obj1.fun1.bind(obj2);
funBind();//2
相關文章
相關標籤/搜索