1. 程式人生 > 實用技巧 >一文搞定this問題

一文搞定this問題

this 可能屬於 JavaScript 開發中老生常談的問題了,但 this 相關問題依舊困擾著許多開發同學,這裡將之前總結的一篇筆記與大家分享。

this 預設繫結

預設繫結時,this 指向全域性即,window。值得注意的是,在嚴格模式環境中,預設繫結的this指向undefined。

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

function fn1() {
    "use strict";
    console.log(this); //undefined
    console.log(this.name);
};

var name = 'Jack';

fn(); 
// window
// Jack

fn1()
// undefined
// Uncaught TypeError: Cannot read property 'name' of undefined

this 隱式繫結

  1. 如果函式作為一個物件的屬性呼叫,那麼它的 this 指向這個物件。
// 例子1
function fn() {
    console.log(this.name);
};
let obj = {
    name: 'LiLei',
    func: fn
};
obj.func() // LiLei

// 例子2
// 如果函式呼叫前存在多個物件,this 指向距離呼叫自己最近的物件
function fn() {
    console.log(this.name);
};
let obj = {
    name: 'Jack',
    func: fn,
};
let obj1 = {
    name: 'Tom',
    o: obj
};
obj1.o.func() // Jack
  1. 隱式丟失
// 例子1. 最常見的就是作為引數傳遞以及變數賦值時,會存在隱式繫結丟失的問題。
var name = 'LiLei';
let obj = {
    name: 'Tom',
    fn: function () {
        console.log(this.name);
    }
};

function fn1(param) {
    param();
};
fn1(obj.fn);// LiLei

// 例子2. 變數賦值
var name = 'LiLei';
let obj = {
    name: 'Tom',
    fn: function () {
        console.log(this.name);
    }
};
let fn1 = obj.fn;
fn1(); // LiLei

this 顯式繫結

通過call、apply以及bind方法改變this的行為我們叫做顯式繫結繫結,相比隱式繫結 this 的指向更加明顯。

let obj1 = {
    name: 'A'
};
let obj2 = {
    name: 'B'
};
let obj3 = {
    name: 'C'
}
var name = 'D';

function fn() {
    console.log(this.name);
};
fn(); // D
fn.call(obj1); // A
fn.apply(obj2); // B
fn.bind(obj3)(); // C

如果以上方法第一個引數傳遞的是 null 或者 undefined,this 將指向 window。

bind、call、apply 都可以改變 this 指向,但他們的區別是什麼:

  • call、apply在改變this指向的同時還會執行函式,而bind在改變this後是返回一個全新的boundFcuntion繫結函式,這也是為什麼上方例子中bind後還加了一對括號 ()的原因。
  • bind屬於硬繫結,返回的 boundFunction 的 this 指向無法再次通過bind、apply或 call 修改;call與apply的繫結只適用當前呼叫,呼叫完就沒了,下次要用還得再次綁。
  • call與apply功能完全相同,唯一不同的是call方法傳遞函式呼叫形參是以雜湊形式,而apply方法的形參是一個數組。在傳參的情況下,call的效能要高於apply,因為apply在執行時還要多一步解析陣列。

new 方法

new 一個函式主要發生了一下幾步:

  1. 以構造器的 prototype 屬性為原型,建立新物件;
  2. 將this(可以理解為上句建立的新物件)和呼叫引數傳給構造器,執行;
  3. 如果構造器沒有手動返回物件,則返回第一步建立的物件

this繫結優先順序

如果一個函式呼叫存在多種繫結方法,this最終指向誰呢?可以記住以下優先順序:

  1. 顯式繫結 > 隱式繫結 > 預設繫結
  2. new繫結 > 隱式繫結 > 預設繫結
// 顯式 > 隱式
let obj = {
    name:'A',
    fn:function () {
        console.log(this.name);
    }
};
obj1 = {
    name:'B'
};
obj.fn.call(obj1);// B

// new>隱式
obj = {
    name: 'C',
    fn: function () {
        this.name = 'D';
    }
};
let echo = new obj.fn();
echo.name;// D

箭頭函式的 this

ES6的箭頭函式中其實並沒有 this,它裡面的 this 指向取決於外層作用域中的 this,外層作用域或函式的this指向誰,箭頭函式中的this便指向誰。

function fn() {
    return () => {
        console.log(this.name);
    };
};
let obj1 = {
    name: 'A'
};
let obj2 = {
    name: 'B'
};
fn.call(obj1)(); // fn this指向obj1,箭頭函式this也指向obj1
fn.call(obj2)(); // fn this 指向obj2,箭頭函式this也指向obj2