深刻理解 Javascript 之 this

深刻淺出this的理解


問題的由來


var obj = {
    foo: function(){}
}

var foo = obj.foo;

// 寫法一
obj.foo();

// 寫法二
foo();

雖然obj.foo和foo指向同一個函數,可是執行結果可能不同。javascript

var obj = {
    foo: function() {
        conosle.log(this.bar)
    },
    bar: 2
};

var foo = obj.foo;
var bar = 3;

obj.foo(); // 2
foo(); // 3

這種差別的緣由就是由於內部使用了this關鍵字,this指向的是函數運行的所在環境,對於obj.foo()來講,this執行obj,對於foo()來講,this指向window全局環境html

this的原理


內存的數據結構

JavaScript 語言之因此有this的設計,跟內存裏面的數據結構有關係。java

var obj = {foo: 5}

clipboard.png

也就是或變量obj是一個地址,後面讀取obj.foo引擎先從obj拿到地址,而後再從該地址讀取原始對象,返回它的屬性值。

原始的對象以字典結構保存,每個屬性名都對應一個屬性描述對象。舉例來講,上面例子的foo屬性,其實是如下面的形式保存的。面試

clipboard.png


函數


這樣的結構是很清晰的,問題在於屬性的值多是一個函數。json

var obj = { foo: function () {} };

這時,引擎會將函數單獨保存在內存中,而後再將函數的地址賦值給foo屬性的value屬性。數組

clipboard.png

因爲函數是一個單獨的值,因此它能夠在不一樣的環境(上下文)執行。數據結構

var f = function () {};
var obj = { f: f };

// 單獨執行
f()

// obj 環境執行
obj.f()

環境變量


var f = function () {
  console.log(x);
};

上面代碼中,函數體裏面使用了變量x。該變量由運行環境提供。app

如今問題就來了,因爲函數能夠在不一樣的運行環境執行,因此須要有一種機制,可以在函數體內部得到當前的運行環境(context)。因此,this就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境。函數

var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// 單獨執行
f() // 1

// obj 環境執行
obj.f() // 2

在obj環境執行,this.x指向obj.x。
函數f在全局環境執行,this.x指向全局環境的x。post

回到咱們最初的問題 obj.foo()是經過obj找到foo,因此就是在obj環境執行。一旦var foo = obj.foo,變量foo就直接指向函數自己,因此foo()就變成在全局環境執行。

阮一峯老師的 this原理


繼續咱們的this

this在js中一直是謎同樣的存在着,在面試中也是常常會被問道

this的指向在函數建立的時候是決定不了的,在調用的時候才能決定

  • 全局範圍內
this;    //在全局範圍內使用`this`,它將會指向全局對象

var name="zhoulujun";

function say(){
    console.log(this.name)
}
say(); //zhoulujun

當執行 say函數的時候, JavaScript 會建立一個 Execute context (執行上下文),執行上下文中就包含了 say函數運行期所須要的全部信息。 Execute context 也有本身的 Scope chain, 當函數運行時, JavaScript 引擎會首先從用 say函數的做用域鏈來初始化執行上下文的做用域鏈。

  • 函數調用
foo();    //this指向全局對象
  • 方法調用*
test.foo();    //this指向test對象
  • 調用構造函數*
new foo();    //函數與new一塊使用即構造函數,this指向新建立的對象
  • 顯式的設置this*
function foo(a, b, c) {}
var bar = {};
foo.apply(bar, [1, 2, 3]);    //this被設置成bar
foo.call(bar, 1, 2, 3);       //this被設置成bar

從函數調用理解this

實例1

myObj3={
        site:"zhoulujun.cn",
        andy:{
            site:"www.zhoulujun.cn",
            fn:function(){
                console.log(this)
                console.log(this.site)
            }
        }
    };
var site="111";
var fn=myObj3.andy.fn;
fn();  // 這裏的調用環境是window


// Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
// 111

clipboard.png

實例2

  • 若是一個函數中有this,這個函數中包含多個對象,儘管這個函數是被最外層的對象所調用,this指向的也只是它上一級的對象
myObj3={
    site:"zhoulujun.cn",
    andy:{
        site:"www.zhoulujun.cn",
        fn:function(){
            console.log(this)
            console.log(this.site)
        }
    }
};
var site="111";
myObj3.andy.fn();


VM51:6 {site: "www.zhoulujun.cn", fn: ƒ}
VM51:7 www.zhoulujun.cn

clipboard.png

實例3

document.getElementById( 'div1' ).onclick = function(){
    console.log( this.id );// 輸出: div1
    var func = function(){ 
        console.log ( this.id );// 輸出: undefined
    } 
    func();
}; 
//修正後
document.getElementById( 'div1' ).onclick = function(){
    var func = function(){ 
        console.log ( this.id );// 輸出: div1
    } 
    func.call(this);
};

實例4

var A = function( name ){ 
    this.name = name;
};
var B = function(){ 
    A.apply(this,arguments);
};
B.prototype.getName = function(){ 
    return this.name;
};
var b=new B('sven');
console.log( b.getName() ); // 輸出:  'sven'

實例5

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

apply、call

由於apply、call存在於Function.prototype中,因此每一個方法都有這兩個屬性。

call
函數名.call(對象,arg1....argn)
//功能:
    //1.調用函數
    //2.將函數內部的this指向第一個參數的對象
    //3.將第二個及之後全部的參數,做爲實參傳遞給函數
apply主要用途是直接用數組傳參
函數名.apply(對象, 數組/僞數組);
//功能:
    //1.調用函數
    //2.將函數內部的this指向第一個參數的對象
    //3.將第二個參數中的數組(僞數組)中的元素,拆解開依次的傳遞給函數做爲實參
//案例求數組中最大值
var a=Math.max.apply( null, [ 1, 2, 5, 3, 4 ] );
console.log(a);// 輸出:5

call應用(將僞數組轉爲數組)

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
Array.prototype.join.call(arrayLike, '&'); // name&age&sex
Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] 
// slice能夠作到類數組轉數組
Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
}); 
// ["NAME", "AGE", "SEX"]


console.log(
    Object.prototype.toString.call(num),
    Object.prototype.toString.call(str),
    Object.prototype.toString.call(bool),
    Object.prototype.toString.call(arr),
    Object.prototype.toString.call(json),
    Object.prototype.toString.call(func),
    Object.prototype.toString.call(und),
    Object.prototype.toString.call(nul),
    Object.prototype.toString.call(date),
    Object.prototype.toString.call(reg),
    Object.prototype.toString.call(error)
);
// '[object Number]' '[object String]' '[object Boolean]' '[object Array]' '[object Object]'
// '[object Function]' '[object Undefined]' '[object Null]' '[object Date]' '[object RegExp]' '[object Error]'
相關文章
相關標籤/搜索