第八節(jié) JavaScript中this指向問題

  1. this指向問題

1.1 認識詞法作用域

其實我們js中的作用域就是詞法作用域,我們會發(fā)現(xiàn)詞法作用域最重要的特征是發(fā)生函數(shù)定義時確定的,

動態(tài)作用域呢是在函數(shù)運行時確定的形式,而不是函數(shù)定義時確定的形式

function foo(){
    console.log(a);
}
function bar(){
    var a = 3;
    foo();
}
bar();
var a = 2;

如果js具有動態(tài)作用域那么這里應(yīng)該打印3,但是不好意思告訴你們,js只有詞法作用域,但是js的函數(shù)內(nèi)部有一個在某種程度上和動態(tài)作用域很像的,那就是this

主要區(qū)別是

詞法作用域是在函數(shù)定義時確定的

動態(tài)作用域是在函數(shù)運行時確定的,this也是這樣

詞法作用域關(guān)心的是函數(shù)在何處定義,動態(tài)作用域關(guān)注函數(shù)在何處被調(diào)用

既然動態(tài)作用域和this都只關(guān)心在什么位置被調(diào)用,那么我們就不得不提的調(diào)用棧,調(diào)用位置

1.2 理解調(diào)用棧

調(diào)用位置就是函數(shù)被調(diào)用的位置,調(diào)用棧是是為了能夠到達當(dāng)前執(zhí)行位置所調(diào)用的所有函數(shù)

調(diào)用位置就在當(dāng)前調(diào)用棧執(zhí)行的函數(shù)的前一個調(diào)用棧

function  baz(){
    // 當(dāng)前調(diào)用棧 是 baz
    // 因此調(diào)用位置是在全局
    console.log("baz");
    bar(); // bar的調(diào)用位置
}

function bar(){
    // 當(dāng)前調(diào)用棧 是 baz -- bar
    // 因此調(diào)用位置是baz
    console.log("bar");
    foo(); // foo的調(diào)用位置
}

function foo(){
    // 當(dāng)前調(diào)用棧 是 baz -- bar  -- foo
    // 因此調(diào)用位置是bar中
    console.log("foo");
}

baz()  // baz的調(diào)用位置

在調(diào)用棧中分析調(diào)用位置,因為他決定this的綁定

1.3 this 指向問題

this關(guān)鍵字是JS中最復(fù)雜的機制,它是一個很特別的關(guān)鍵字,被自動定義在所有函數(shù)的作用域中.

function foo(){
    console.log(this)
}
foo();

this指向的是一個對象,我們把this指向的對象叫做函數(shù)執(zhí)行的上下文對象

當(dāng)函數(shù)被調(diào)用的時候,this指向會發(fā)生改變,指向調(diào)用函數(shù)的對象

在函數(shù)預(yù)編譯階段,會生成AO對象,程序還會默認把this作為AO對象的一個屬性名,默認屬性值是window

2.this綁定的規(guī)則

2.1 默認綁定

就是直接使用不帶任何修飾的函數(shù)引用進行的調(diào)用,就是默認綁定,無法應(yīng)用其他規(guī)則

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

// 改變
function fn(){
    console.log(this.a);
}
var a = 2;
fn();

2.2 隱式綁定

考慮函數(shù)調(diào)用位置是否有上下文對象,或者說是否被某個函數(shù)擁有或包含

示例:

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

對象引用鏈只有上一層或者說最后一層在調(diào)用的位置中起作用

function foo(){
    console.log(this.a);
}
var obj2 = {
    a: 42,
    foo: foo
}
var obj1 = {
    a: 22,
    obj2: obj2
}
obj1.obj2.foo();   // 42

隱式綁定的函數(shù)會丟失綁定對象

function foo(){
    console.log(this.a);
}
var obj = {
    a : 20,
    foo: foo
}
var a = 33;
var bar = obj.foo;
bar(); //33
// 此時賦值的知識函數(shù)的引用,bar則是一個不帶任何修飾的函數(shù)調(diào)用,因此應(yīng)用了默認綁定

測試

var name = "wuwei";
function showName(){
    console.log(this.name);
}
var person1 = {
    name: "old",
    sayName:showName
}
var person2 = {
    name: "xiaoming",
    sayName: function(){
        var fun = person1.sayName;
        fun();
    }
}
person1.sayName();    // old       隱式綁定   //person1.sayName()===shawName()  
person2.sayName();    // wuwei

2.3 顯示綁定

我們發(fā)現(xiàn)隱式綁定時,必須在一個對象內(nèi)部包含一個指向函數(shù)的屬性.并通過這個屬性間接的應(yīng)用函數(shù).從而把this隱式的綁定到這個對象上.

如果我們不想在函數(shù)內(nèi)部包含函數(shù)的引用呢.我們就只能用接下來要學(xué)習(xí)的方法,顯示的綁定了

2.3.1 call和applay方法

call && apply方法,

作用.都是在函數(shù)執(zhí)行時,修改this的指向

call 和 apply方法由兩層意思

  1. 函數(shù)執(zhí)行
  2. 在函數(shù)執(zhí)行的時候修改函數(shù)內(nèi)部的this指向

這兩個方法存在于函數(shù)的原型上,至于是原型 ,咱們以后會學(xué),你只要知道函數(shù)可以調(diào)用這兩個方法就行了

使用

function foo(){
    console.log(this.a)
}
var obj = {
    a: 20
}
foo.call(obj);

這里就是通過call這個方法在foo函數(shù)調(diào)用的時候?qū)⒑瘮?shù)內(nèi)部的this綁定到了obj上

如果我傳入一個數(shù)字,字符串 布爾值怎么辦

function foo(){
    console.log(this.a)
}
var obj = {
    a: 20
}
foo.call(true);
// 為什么是undefined 而不報錯呢

這是包裝類,這里第一參數(shù)必須是對象,如果不是對象,會將其轉(zhuǎn)為包裝對象,轉(zhuǎn)不了就是window

call && apply 方法的區(qū)別
使用方法

fn.call(obj,attr1,attr2,attr3,..);
fn.applay(obj,[attr1,attr2,attr3,..])

這兩個方法傳參的方式不同,apply只能傳數(shù)組

示例:

function add(c,d){
    return this.a + this.b + c + d;
}
var s = {a:1,b:2};
console.log(add.call(s,3,4));
console.log(add.apply(s,[5,6]));

上面程序的輸出結(jié)果是什么?

2.3.2硬綁定

function foo(){
    console.log(this.a);
}
var obj = {
    a: 35
}
var bar = function(){
    foo.call(obj);
}

bar();
bar.call(window);

因為硬綁定非常常用的方法,所有ES5也提供了內(nèi)置的方便.bind

function foo(){
    console.log(this);
    console.log(this.a) ;
}
var obj = {
    a: 23
}
var bar = foo.bind(obj)
bar();

此時就算你使用call,apply 也改變不了函數(shù)的bar的this綁定

function foo(){
    console.log(this);
    console.log(this.a) ;
}
var obj = {
    a: 23
}
var a = 33;
var bar = foo.bind(obj)
bar.call(window);

2.4 new 操作符

new操作符在構(gòu)造函數(shù)那一節(jié)的的時候已經(jīng)講過,
現(xiàn)在我們就知道關(guān)于this指向的綁定情況有四種.那么接下來看看優(yōu)先級的問題

  1. 優(yōu)先級

你需要做的就是找到函數(shù)的調(diào)用位置并判斷應(yīng)用了那條規(guī)則,如果某個調(diào)用位置可以應(yīng)用多條規(guī)則,我們就需要通過優(yōu)先級來區(qū)分

  1. 默認的優(yōu)先級最低

  2. 顯示優(yōu)先級高于隱式
    function foo(){
    console.log(this.a)
    }
    var obj1 = {
    a: 10,
    foo: foo
    }
    var obj2 = {
    a: 20,
    foo: foo
    }
    obj1.foo(); // 10
    obj2.foo(); // 20

    obj1.foo.call(obj2);  //  20
    obj2.foo.call(obj1);  //  10
    
  3. new綁定比隱式綁定優(yōu)先級高
    function foo(aa){
    this.a = aa
    }
    var obj = {
    foo:foo
    }
    obj.foo(2);
    console.log(obj.a); // 2

    var bar = new obj.foo(4);
    console.log(obj.a);    // 2
    console.log(bar.a);    // 4
    
  4. new操作符比顯示綁定中的硬綁定優(yōu)先級高
    function foo(aa){
    this.a = aa;
    }
    var obj = {}
    var bar = foo.bind(obj);

    bar(20);
    console.log(obj.a);
    
    var baz = new bar(30);
    console.log(obj.a);
    console.log(baz.a);
    

    至于為什么用new操作符還要用bind綁定是為了提前傳一些參數(shù)進去

判斷原則

  1. 函數(shù)是否使用new操作符調(diào)用,如果有,那么this將綁定到新創(chuàng)建的對象

  2. 函數(shù)是否使用call,apply或者硬綁定,如果有,this指向顯示綁定的對象

  3. 函數(shù)是否在某個上下文對象中調(diào)用,如果有this指向那個上下文對象(隱式綁定)

  4. 如果以上都不是,那就是默認綁定,

  5. 特殊情況

被忽略的this

function foo(){
    console.log(this.a)
}
var a = 2;
foo.call(null);   // 2

如果你傳入null或undefined將會忽略傳入的值,實際使用默認值

那么什么時候會需要用到傳入null的情況呢

使用apply展開參數(shù)

function foo(a,b){
    console.log("a: "+ a + " ,b: " + b);
}
foo.apply(null,[2,3]);

因為這里我們不需要關(guān)心this指向,那么我們用null占位最好,

題:

var a = 10;
function aa(){
    console.log(a);
    a = 0;
    console.log(this.a);
    var a;
    console.log(a);
}
aa();
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容