一次筆試引起的關於setTimeout的this的思考

以前對於setTimeout的this指向理解一直迷迷糊糊,在項目實踐中也沒有遇到相關問題,在面試時也沒有被過關於這個問題,因此得過且過,直到最近的一次筆試碰到了令本身困惑的問題纔去深刻的瞭解了。也再一次提醒本身對於知識點的理解真的應該細緻到位,不能停留在表面。javascript

在《高程3》中關於setTimeout的this的描述是:超時調用的代碼都是在全局做用域中執行的,所以函數中this的值在非嚴格模式下指向window對象在嚴格模式下是undefined,那爲何是這樣呢,咱們能夠簡單的理解爲setTimeout爲window對象下的一個方法,本文僅討論非嚴格模式下的狀況。依據高程三的結論,若是真正理解了,咱們能夠搞定遇到的百分之90的問題,如:java

setTimeout(console.log(this),0)//window
複製代碼

let obj = {
    print : function () {
        setTimeout(function () {
            console.log('setTimeout:'+this);
        },0);
    }
}; 
obj.print() //setTimeout: window複製代碼

function say() {
            console.log('setTimeout:'+this);
        }let obj = {
    print : function () {
        setTimeout(say,0);
    }
}; 
obj.print() //setTimeout: window複製代碼

不管是直接引用、經過對象方法調用仍是函數引用都很容易理解,有時候,咱們會遇到兩個this的狀況,以下,一個是setTimeout調用環境中的this,一個是延遲執行函數中的this,這個時候須要注意區別,咱們能夠理解爲,setTimeout中的第一個參數就是一個單純的函數的引用而已,它的指向跟咱們通常的函數調用時同樣取決於被調用時所處的環境。es6

let obj = {
      say : function () {
            console.log(this);  //延遲執行函數中的this
        },
    print : function () {
        setTimeout(this.say,0); //setTimeout調用環境中的this,指向調用者即obj
    }
}; 
obj.print() //setTimeout: window複製代碼

咱們換種寫法讓上面代碼中setTimeout調用環境中的this指向window,此時函數執行就不會有什麼效果了:面試

let obj = {
      say : function () {
            console.log(this);  //延遲執行函數中的this
        },
      print : function () {
        setTimeout(this.say,0); //setTimeout調用環境中的this,指向調用者即obj
    }
}; 
let func = obj.print;
func() 複製代碼

下面再看:bash

var a = 1;
function func(){
        let a = 2;
        setTimeout(function(){
            console.log(a);
            console.log(this.a);
    },0) 
}
func(); //輸出2 1複製代碼

var a = 1;
function func(){
       // let a = 2;
        setTimeout(function(){
            console.log(a);
            console.log(this.a);
    },0) 
}
func(); //輸出1 1複製代碼

可見,在沒有使用this時,在setTimeout超時調用中變量是跟正常函數調用時沿着定義時的做用域向上查找的。函數

那麼,當以字符串形式執行又是怎麼樣呢ui

var a = 2
function say(a){
  console.log(a)
}
function test(){
  let a = 1;
  setTimeout("say(a)",0)
}
test() //2

var a = 2
function test(){
  let a = 1;
  function say(a){
    console.log(a)
  }
  setTimeout("say(a)",0)
}
test()  //say is not defined複製代碼

可見,當把say方法移到test內部時報錯say is not defined,緣由是以字符串形式執行時javascript內部實際上調用了eval(),而eval的執行環境是全局做用域window,全局做用域沒有say方法因此報錯。this

將參數直接以賦值形式傳進去則不會報錯:spa

var a = 2;
function say(a){
    console.log(a)
}
function test(){
    let a = 1;
    setTimeout("say('hhhh')",0)
  }
test() //hhhh複製代碼

如今看看結合es6的箭頭函數時this指向是怎麼樣的,你們都知道,因爲箭頭函數不綁定this, 它會捕獲其所在(即定義的位置)上下文的this值, 做爲本身的this值,在setTimeout中狀況亦是如此。code

let obj = {
    name :  "jay",
    print : function () {
        setTimeout(() => {
            console.log(this.name)
    },0);
    }
}; 
obj.print() //jay複製代碼

如何改變setTimeout的this指向

前面的討論其實已經有兩種答案了,即利用中間變量引用外面的this和應用箭頭函數

方法一
let obj = {
    name :  "jay",
    print : function () {
           let that = this;
        setTimeout(function() {
            console.log(that.name)
    },0);
    }
}; 方法二
let obj = {
    name :  "jay",
    print : function () {
        setTimeout(() => {
            console.log(this.name)
    },0);
    }
}; 複製代碼

還有一種方法是應用bind方法:

方法三
var name = "window";
 function say(){
  console.log(this.name);
}
let obj = {
  name : "jay",
  print : function(){
    setTimeout(say.bind(this),0)
  }
}
obj.print(); //jay

複製代碼

setTimeout參數傳遞問題

1.setTimeout(function,milliseconds,param1,param2,...);  param1,param2,...是可選項,用於給function提供額外的參數,可是注意,該特性在IE9及以前的IE不能使用!!

function say(name) {
   console.log(name)
 }s
etTimeout(say,0,'jay')複製代碼

2.字符串形式傳參:

function say(a,b) {
   console.log(a+b)
 } 
//let name = "jay"
setTimeout( "say(3,4)",3000) //三秒後輸出7複製代碼

注意事項

儘可能避免setTimeout第一個參數爲字符串,setTimeout容許講一個字符串做爲第一個參數,js內部將會調用eval()函數用來動態執行一段字符串腳本,eval()具備許多不可預見的危險性,eval的效率是很是低的,執行一段代碼須要先將字符串轉換爲可執行代碼,也就是比日常多了一步,而且可能隱式建立全局變量。

setTimeout遞歸調用時注意記得應用clearTimeout清除,以免無限遞歸形成內存泄漏。

相關文章
相關標籤/搜索