1. 程式人生 > >JavaScript —— this指向問題

JavaScript —— this指向問題

20180812 修改一
20180816 修改二
轉載:https://zhuanlan.zhihu.com/p/42145138?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

綜:

普通函式:

  • this總是代表它的直接呼叫者(js的this是執行上下文),例如 obj.func ,那麼func中的this就是obj;
  • 在預設情況(非嚴格模式下,未使用 ‘use strict’),沒找到直接呼叫者,則this指的是 window;
  • 在嚴格模式下,沒有直接呼叫者的函式中的this是undefined;
  • 使用call,apply,bind(ES5新增)繫結的,this指的是繫結的物件;

箭頭函式:

  • 箭頭函式沒有自己的this,它的this是繼承而來;
  • 預設指向在定義它時所處的物件(宿主物件),而不是執行時的物件。 定義它的時候,可能環境是window;
  • 箭頭函式可以方便地讓我們在 setTimeout ,setInterval中方便的使用this;

    this要點

    1. this永遠指向一個物件
    2. this指向取決於函式呼叫的位置

    注:
    JS中,一切皆物件,執行環境也是物件,函式都是在某個物件下執行,而this就是函式執行時所在的物件(環境)


    但JS支援執行環境動態切換 => this指向也是動態的,很難事先確定到底指向哪個物件。

基本原理

function fun(){
    console.log(this.s);
}

var obj = {
    s:'1',
    f:fun
}

var s = '2';
obj.f();  //1
fun();  //2

obj.f()的呼叫中,因為執行環境在obj物件內,因此函式中的this指向物件obj;而在全域性作用域下呼叫 fun() ,函式中的 this 就會指向全域性作用域物件window。

JS中,陣列、函式、物件都是引用型別,在引數傳遞時引用傳遞


obj物件兩個屬性,屬性值型別不同,記憶體中表現形式也不同:屬性 f 儲存 fun 所在記憶體地址。
函式呼叫時

因為函式在js中既可以當做值傳遞和返回,也可以當做物件和建構函式,所有函式在執行時需確定其當前的執行環境,this從而誕生。=> this會根據執行環境的改變而改變,函式中的this也只能在執行時才能最終確定執行環境

執行環境動態切換規則理解

var A = {
    name: '張三',
    f: function () {
        console.log('姓名:' + this.name);
    }
};
​
var B = {
    name: '李四'
};
​
B.f = A.f;  //A物件將匿名函式的地址賦值給B物件
B.f()   // 姓名:李四
A.f()   // 姓名:張三

匿名函式

function foo() {
    console.log(this.a);
}
var obj2 = {
    a: 2,
    fn: foo
};
var obj1 = {
    a: 1,
    o1: obj2
};
obj1.o1.fn(); // 2

obj1物件的o1屬性值:obj2物件的地址;
obj2物件的fn屬性的值:函式foo的地址;
函式foo的呼叫環境在obj2中,因此this指向物件obj2。

——————————————————————-

事件繫結

行內繫結

  • 在html節點內,以節點屬性的方式繫結,屬性名(onclick)是事件名稱前面加’on’,屬性的值則是一段可執行的 JS 程式碼段;
  • 事件觸發時,屬性值作為JS程式碼被執行,當前執行環境下沒有clickFun函式,因此瀏覽器就需要跳出當前執行環境,在整個環境中尋找並執行這個函式,所以函式內部的this就指向了全域性物件window
<input type="button" value="按鈕" onclick="clickFun()">
<script>
    function clickFun(){
        this // 此函式的執行環境在全域性window物件下,因此this指向window;
    }
</script>
  • 如果不是一個函式呼叫,直接在當前節點物件環境下使用this,那麼顯然this就會指向當前節點物件
<input type="button" value="按鈕" onclick="this">
<!-- 執行環境在節點物件中,因此this指向本節點物件 -->

動態繫結與事件監聽

<input type="button" value="按鈕" id="btn">
<script>
    var btn = document.getElementById('btn');
    btn.onclick = function(){
        this ;  // this指向本節點物件
    }
</script>
  • 動態繫結是為節點物件的屬性(事件名稱前面加’on’)重新賦值為一個匿名函式,因此函式在執行時就是在節點物件的環境下,this自然就指向了本節點物件;
  • 事件監聽中this指向的原理與動態繫結基本一致,所以不再闡述;

——————————————————————-

建構函式

function Pro(){
    this.x = '1';
    this.y = function(){};
}
var p = new Pro();

建構函式執行
new 一個建構函式並執行函式內部程式碼的過程是這個五個步驟。當 JS 引擎指向到第3步的時候,會強制將this指向新創建出來的這個物件

——————————————————————-

定時器setInterval

  • setInterval() 是window物件下內建方法,接受兩個引數,第一個引數允許是一個函式或者是一段可執行的 JS 程式碼,第二個引數則是執行前面函式或者程式碼的時間間隔
var obj = {
    fun:function(){
        this ;
    }
}
​
setInterval(obj.fun,1000);      // this指向window物件
setInterval('obj.fun()',1000);  // this指向obj物件
  • setInterval(obj.fun,1000) 的第一個引數是obj物件的fun,因為 JS 中函式可以被當做值來做引用傳遞,實際就是將這個函式的地址當做引數傳遞給了 setInterval方法。換句話說, setInterval 的第一引數接受了一個函式,那麼此時1000毫秒後,函式的執行就已經是在window物件下了,也就是函式的呼叫者已經變成了window物件,所以this指向全域性window物件
  • setInterval(‘obj.fun()’,1000) 中的第一個引數,實際則是傳入的一段可執行的 JS 程式碼;1000毫秒後當 JS 引擎來執行這段程式碼時,則是通過 obj 物件來找到 fun 函式並呼叫執行,那麼函式的執行環境依然在 物件 obj 內,所以函式內部的this指向 obj 物件。

——————————————————————-

函式物件的call()、apply()方法

  • 函式作為物件提供了call(),apply() 方法,他們也可用來呼叫函式,接受一個物件作為引數,用來強制指定本次呼叫時函式中this的指向
call(obj,arg1,arg2…argN);
引數說明:
obj:函式內this要指向的物件
arg1,arg2…argN :引數列表,引數與引數之間使用一個逗號隔開
var lisi = {names:'lisi'};
var zs = {names:'zhangsan'};
function f(age){
    console.log(this.names);
    console.log(age);

}
f(23);//undefined//將f函式中的this指向固定到物件zs上;
f.call(zs,32);//zhangsan
apply(obj,[arg1,arg2…,argN]);
引數說明:
obj:函式內this要指向的物件
arg1,arg2…argN :引數列表,要求格式為陣列
var lisi = {name:'lisi'}; 
var zs = {name:'zhangsan'}; 
function f(age,sex){
    console.log(this.name+age+sex); 
}
//將f函式中的this指向固定到物件zs上;
f.apply(zs,[23,'nan']);