理解JavaScript中的this

這裏來個倒敘,先說一下this的幾種狀況指向:javascript

  • 形如obj.fn()的this指向obj,形如fn()的this指向window
  • 匿名函數的this指向window
  • 事件回調函數的this指向dom元素
  • 定時器的回調函數指向window
  • 構造函數中的this指向實例
  • 可使用call、apply、bind來改變this的指向

下面開始:php

我本身結合網上的定義,給出了this的定義:java

函數中this指向了執行時,調用它的而且離它最近的對象

不過這個定義只適用於大部分狀況:數組

先看一段代碼:markdown

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

若是你知道在全局做用域中定義函數變量和函數實際是在window變量上添加屬性和方法的話,那麼上面的代碼就很好理解了,上面的代碼至關於:app

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

最後調用這個函數的對象是window對象,因此this就指向了window,再看:dom

var obj = {
    fn: function() {
        console.log(this);
    }
}
obj.fn(); //this指向obj,至關於window.obj.fn();

上面的代碼咱們能夠看到是window的obj對象調用了fn這個函數,因此定義的時候咱們強調是離它最近的調用對象,這裏obj離得近,this就指向了obj。函數

定義時還強調了是執行時,是什麼意思呢?接着看代碼:ui

function fn() {
    console.log(this)
}
var obj = {
    fn: fn } obj.fn(); //this指向obj

定義時,函數是在全局中定義的,可是執行時咱們是利用obj來調用,它就指向了obj,若是還不太肯定,咱們再看:this

var obj = {
    fn: function() {
        console.log(this)
    }
}
var fn = obj.fn;
fn(); //this指向了window

這下相信了吧,下面介紹幾種比較特殊的狀況:

匿名函數中的this

匿名函數具備全局性,因此this指向的是window。

(function(){ console.log(this); //window })()
var obj = {
    fn: function() {
        (function(){
            console.log(this);
        })()
    }
}
obj.fn(); //window

上面第二段代碼中,儘管是obj調用了fn函數,可是在fn函數中的匿名函數仍然具備全局性,因此this仍然指向window。

函數普通調用的this

普通調用是什麼意思呢?就是形如:fn();不是做爲某個對象的方法。上面有段代碼:

var obj = {
    fn: function() {
        console.log(this);
    }
}
obj.fn(); //this指向obj

咱們稍微修改一下:

var obj = {
    fn: function() {
        function innerFn() {
            console.log(this);
        }
        innerFn();
    }
}
obj.fn(); //輸出window

是否是有些懵逼了,儘管innerFn是在obj的fn函數中被調用的,可是它的做用域鏈上活動對象只有innerFn和全局自己(ES6之前,JavaScript做用域只有函數域),我猜想,在利用obj.fn()調用的時候,JavaScript內部是作了this指向處理的,而普通調用就指向了全局。

有人可能會問,若是我要調用外層中的this怎麼辦?一般咱們會使用一個變量來保存this,例如:

var obj = {
    fn: function() {
        var self = this;
        function innerFn() {
            console.log(self);
        }
        innerFn();
    }
}
obj.fn(); //輸出obj

定時器回調函數中的this

setTimeout(function(){
    console.log(this);  //window
}, 1000)
var obj = {
    fn: function() {
        console.log(this);
    }
}
setTimeout(obj.fn, 1000) //輸出window

setTimeout回調函數你能夠看作是下面這樣:

var obj = {
    fn: function() {
        console.log(this);
    }
}
var callback = obj.fn;
//在設定時間後執行回調函數
callback();

上面你可能就很熟悉了,調用fn函數的是全局對象,因此指向了window對象,若是你想改變定時器中函數this的指向,可使用bind函數:

var obj = {
    fn: function() {
        console.log(this);
    }
}
setTimeout(obj.fn.bind(obj), 1000) //輸出obj

事件處理函數

在JavaScript中咱們能夠這樣綁定一個事件:

<div id="div">這是一個div元素</div>
function doClickDiv(e) {
    //to do something
    console.log(this);
}
var oDiv = document.getElementById('div');
//綁定點擊事件
oDiv.addEventListener('click', doClickDiv, false);

當點擊時,輸出時this指向了div這個節點,早期綁定事件的寫法能夠幫助咱們理解:

function doClickDiv(e) {
    //to do something
    console.log(this);
}
var oDiv = document.getElementById('div');
//綁定點擊事件
oDiv.onclick = doClickDiv;

當點擊div時,會觸發oDiv.onclick函數,至關於oDiv.onclick(),這和使用對象調用是同樣的。

構造函數中的this

JavaScript中的函數是能夠做爲構造函數的,使用new便可,那麼this在這種狀況下指向是什麼?

function Person() {
    this.age = 18;
    this.job = 'student';
}
var person = new Person();
console.log(person)//{age: 18, job: 'student'}

咱們能夠知道person是一個實例,因此函數做爲構造函數時,this是指向實例的,在構造函數中實際是這樣的:

function Person() {
    //隱藏着的語句
    //this = {} 這裏只是簡單說明this是一個對象,它還要關聯Person函數的原型
    this.age = 18;
    this.job = 'student';
    //隱藏着的語句
    //return this;
}
var person = new Person();
console.log(person)//{age: 18, job: 'student'}

從上面咱們能夠看出,構造函數隱式return了this,因此person就是this,可是當構造函數有return語句時,this並不必定指向person。

當return返回一個對象時:

function Person() {
    this.age = 18;
    this.job = 'student';
    return {
        tip: 'this is an object'
    }
}
var person = new Person();
console.log(person)//{tip: "this is an object"}

當return回一個非對象值時:

function Person() {
    this.age = 18;
    this.job = 'student';
    return 1;
}
var person = new Person();
console.log(person)//{age: 18, job: 'student'}

call、apply、bind改變this指向

有時咱們須要改變this的指向,就能夠經過這三個方法函數來實現:

call和apply:

var prop = 'window';

function fn() {
    console.log(this.prop);
}

var obj1 = {
    prop: 'obj1',
    fn: function(){
        console.log(this.prop);
    }
}
var obj2 = {
    prop: 'obj2',
    fn: function(){
        console.log(this.prop);
    }
}

fn();                // 'window'
obj1.fn();           // 'obj1'
obj2.fn();           // 'obj2'
fn.call(obj1)        // 'obj1'
fn.apply(obj1)       // 'obj1'
obj1.fn.call(obj2);  // 'obj2'
obj1.fn.apply(obj2); // 'obj2'

從改變this指向來講,call和apply是同樣的,它們兩個函數的區別在於傳參數形式的不一樣:

var obj = {
    a: 1,
    b: 2,
    c: 3
}

function add(a, b, c) {
    console.log(this);
    return a + b + c;
}

add.call(obj, obj.a, obj. b, obj.c);
add.apply(obj, [obj.a, obj.b, obj.c]);

能夠清楚的看出:call接受的參數是一個一個傳進去的,而apply是傳一個參數數組進去的。這裏插播一個問題:爲何要有call、apply同時存在?其實,有時候改變參數的形式是頗有必要,下面分享一個小技巧,當你要求一個數組的最大值時,你會怎麼作?傳統的作法是用for遍歷一遍數組,而後挑出最大值,可是利用apply你就能夠直接利用js的內置函數:

var arr = [3, 4, 2, 78];
var max = Math.max.apply(Math, arr);  //Math.max的用法是Math.max(1, 3, 6, 2) //6
console.log(max); //78

bind:

bind函數的做用是綁定參數,其中第一個參數就是傳入this指向對象,當時undefined或者null時,this指向window,它會返回一個新函數,看代碼;

function add(a,b,c){
    return a + b + c;
}

add(1,2,3) //6

var addOne = add.bind(null, 1);

addOne(2, 3) //6,至關於add(1, 2, 3);

bind和call的區別在於call是參入參數並執行函數,而bind是傳入綁定參數,返回一個新函數。

相關文章
相關標籤/搜索