JS黑魔法之this,setTimeout/setInterval,arguments

來源:互聯網
上載者:User

標籤:style   blog   http   color   io   使用   ar   java   for   

最近發現了JavaScript Garden這個JS黑魔法收集處,不過裡面有一些東西並沒有說得很透徹,於是邊看邊查文檔or做實驗,寫了一些筆記,順手放在部落格。等看完了You don‘t know JS講this和prototype的部分,說不定又會再寫一點。

函數名字是可選的

通常用匿名函數的地方,匿名函數也是可以帶名字的(ES3開始)。便於debug時提供點額外資訊/遞迴。

foo(function bar(){ ... });

但這時候bar只能在bar裡訪問,不能在外面訪問(not defined)。同樣地:

var foo = function bar() {    bar(); // Works}bar(); // ReferenceError

這跟

function bar() { ... }

的區別在於後者被賦給了window(或其他global object),相當於

bar = function() { ... }

前者的引用轉給了foo(第一段的引用則在其他地方都無法訪問)。賦給了global object當然都可以訪問。由於JS的name resolution,函數名可以在函數自己內訪問。

追記:IE8-會leak掉這個bar到外面去=__=!!

this的五種綁定
  1. 在全域下直接用this,指的是global object,瀏覽器中

    console.log(this === window); // true
  2. 在以function foo()形式聲明的函數裡指的也是global object(注意甚至函式宣告內嵌在方法裡都是這樣,後面會講到)

    function foo() {    console.log(this === window); // true};foo();
  3. 在以形如a.foo()調用的時候,指的是調用的對象,點前面的東西(注意一定要出現括弧才是以方法形式調用,否則調用時不是方法,依然是普通函數,看後文)

    var a = {};a.foo = function() {    console.log(this === a); // true};a.foo();
  4. 在建構函式裡指的是新new出來的對象。注意這裡不能直接用this == b檢查,因為建構函式調用完之前和之後這個新構造的對象本身是有區別的,不過如果延遲一下再判斷,等構造完之後就可以看出this指向的是被返回的那個新對象了。(用that儲存而不是直接用this是因為setTimeout調用函數時用的是global object,看後文)

    function foo() {    var that = this;    setTimeout(function(){console.log(that === b);}, 1000); // true}var b = new foo();
  5. applycallbind是指哪打哪,這裡不贅述

內嵌函數的this綁定
var foo = {};foo.method = function() {    function test() {        console.log(this === window);  // true    }    test();}foo.method();

如果在方法裡聲明一個函數,這函數裡的this又變成了global object,裡面和外面的this不一樣。事實上這麼看就明白怎麼回事了:

var foo = {};foo.method = function() {    test = function() {  // test belongs to the global object        console.log(this === window);  // true    }    test();}foo.method();

最主要的是要理解,直接用function test() {}這樣的聲明,無論在哪裡都會綁到global object去的。要放在本地,必須用function expression,用var test = function test() {}

一般的workaround有:

  1. 常見的用that

    var foo = {};foo.method = function() {    var that = this;    var test = function test() {  // store the outer this        console.log(that == foo);  // true    }    test();}foo.method();

    來讓裡面的函數也能用到外面的this。(注意that不是特殊名字,可以隨便用)通常和閉包一起用,來將this傳來傳去。注意var test = function test()這一句是關鍵,如果還是用function test(),其實換湯不換藥,因為test依然綁到了global object去,只不過加了個閉包而已,而且這樣test就泄露到了全域去,你就可以直接在全域調用這個看上去似乎只屬於method的test函數……2333

  2. 將內嵌函數綁在this上

    var foo = {};foo.method = function() {    this._test = function() {        console.log(this == foo);  // true    }    this._test();}foo.method();

    不過這樣一來foo就額外帶上+暴露了一個外面不需要的函數

  3. 用bind

    var foo = {};foo.method = function() {    var test = (function test() {        console.log(this == foo);  // true    }).bind(this);    test()}foo.method();
this的延遲綁定
var bar = {}bar.baz = function() {    console.log(this === bar);  // false    console.log(this === window);  // true}var foo = bar.baz;foo();

foo裡this又指回了foo所屬對象——global object。所以this是在函數執行時而不是聲明時綁定的,這也是prototypal inheritance的基礎

function Foo() {}Foo.prototype.method = function() {    console.log(this === b);  // b would be available when executed after b is declared!};function Bar() {}Bar.prototype = Foo.prototype;var b = new Bar();b.method();

在b.method()裡this指的就是b了(Bar的執行個體),不然指的應該是一個Foo的執行個體……呵呵那就是implemation inheritance了。注意由於函數執行的時候已經有b,所以在foo裡引用b的時候不會報錯。JS的函數裡的引用都延遲到執行時去找,聲明時是不檢查的。

setTimeout裡的this
function Foo() {    this.value = 42;    this.method = function() {        // this refers to the global object        console.log(this.value); // undefined        console.log(this === window); // true    };    setTimeout(this.method, 500);}new Foo();

setTimeout會脫離當前上下文,用global object調用第一個參數。事實上會以為setTimeout(this.method, 500);,無非是腦補成了會調用this.method(),但事實上傳進去的不過是一個沒有bind過的函數引用,可以理解為:

method = this.method;  // method belongs to the global objectsetTimeout(method, 500);

簡單來說,只要記得只有實際在代碼裡看到形如this.method()(注意括弧)的調用,才能認為函數執行時的this指向點前面的部分。沒有看到括弧,就不能這樣想當然。

如果想要讓this是直覺上的那個對象,可以用that+閉包來保證傳進去的函數裡的this是你想要的值

function Foo() {    this.value = 42;    var that = this;    this.method = function() {        // this refers to the new instance        console.log(that.value); // 42        console.log(that === b); // true    };    setTimeout(this.method, 500);}var b = new Foo();

或者用bind:

function Foo() {    this.value = 42;    this.method = (function method() {        console.log(this.value); // 42        console.log(this === b); // true    }).bind(this);    setTimeout(this.method, 500);}var b = new Foo();
setTimeout v.s. setInterval

setInterval只管調用函數,不管函數執行,所以如果被調用的函數阻塞了,而且阻塞的時間大於調用間隔,那麼當這個函數執行完之後,可能會有一大波被調用還沒開始執行的函數擠上來,像這樣:

function foo(){    // something that blocks for 1 second}setInterval(foo, 100);

解決方案是

function foo(){    // something that blocks for 1 second    setTimeout(foo, 1000);}foo();

這樣會等到函數執行完之後,再等待間隔,再進行下一次調用。注意用setTimeout+傳函數的方式遞迴的時候是不會stackoverflow的,因為setTimeout+傳函數只是做標記要調用而不是真的要調用。傳進去的函數在執行完之後會立刻返回(setTimeout不會阻塞,所以不需要等待他返回),不會在棧上等著,自然也就不會stackoverflow了。

如何清除所有的timeout

setTimeout屬於DOM的一部分(而且是DOM 0),所以在ECMAScript標準裡沒有說明,但是在各大瀏覽器中,setTimeout的ID事實上是越後的越大,所以可以立刻setTimeout一下,得到當前的最大ID,然後逐個清除

// clear "all" timeoutsvar biggestTimeoutId = window.setTimeout(function(){}, 1),i;for(i = 1; i <= biggestTimeoutId; i++) {    clearTimeout(i);}

但是因為標準裡沒有說,所以這個方法在未來不一定靠譜。HTML5對setTimeout做了規範,但是目前對這個返回的ID的規範是“a user-agent-defined integer that is greater than zero that will identify the timeout to be set by this call in the list of active timers.”也就是說只要是唯一的正整數就可以了,至於怎麼變就是user-agent-defined,依然不靠譜啊噗

arguments不是數組
  • 所以不能用pushpopslice
  • 可以用for-in
  • 轉換為數組

    Array.prototype.slice.call(arguments);

    但是這種做法 1.速度慢 2.解譯器無法最佳化 所以沒必要的時候不要用

  • ES5 strict mode下arguments無法用[]來訪問or修改
arguments.callee

最佳化殺手,盡量不要用

arguments.callee通常用來reference函數本身,但是除非在用applycall否則完全可以用函數名代替。arguments.callee.caller(同Function.caller)通常用於reference調用這個函數的函數,但是這種用法顯然破壞封裝(函數的行為居然要依賴被調用的上下文)。使用了arguments.callee或者Function.caller之後解譯器很難確定函數的行為,導致無法進行inline最佳化。

在ES5 strict mode下使用arguments.callee會報錯。

JS黑魔法之this,setTimeout/setInterval,arguments

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.