javascript之apply,call,bind與this綁定

this綁定

在javascript中,this的指向一般是一個使人頭痛的問題,js中的this的指向與真正的有類的概念的語言(java,c++,c#)不一樣,js中的this並非表示指向自身,而是會隨着調用上下文和做用域而改變。javascript

一般this的綁定有四種狀況java

1.隱式綁定node

即做爲對象的函數進行調用,此時this指向上下文的對象c++

function sayName() {
    console.log(this.name);
}

let person = {
    name: "Jack",
    sayName: sayName
}

person.sayName()  // Jack
複製代碼

2顯示綁定c#

即經過apply,call和bind進行綁定數組

// apply, call軟綁定, 綁定後能夠更改this的指向
function sayName() {
    console.log(this.name);
}

let person = {
    name: 'Jack'
}

let child = {
    name: 'Tim',
    say: say
}

sayName.apply(person);  // Jack
sayName.apply(child);  // Tim
sayName.call(person);  // Jack
sayName.call(child);  // Tim
//bind 硬綁定,一旦綁定,便沒法更改this的指向
let say = sayName.bind(person);
say();  // Jack

child.say();  // Jack
let log = say.bind(child);
log();  // Jack, this並無更改指向
let says = sayName.bind(child);
says();  // Jack, this也沒有更改指向
複製代碼

3.new綁定瀏覽器

當用new建立一個對象時,this指向這個對象app

function Person(name) {
    this.name = name;
}
let person = new Person('Jack');
console.log(person.name);  // Jack
複製代碼

4.默認綁定函數

當不符合上面三種狀況時,則使用默認綁定規則。嚴格模式下綁定到undefined,普通模式下綁定到window(nodejs中綁定到global)ui

var name = 'Jack';
function sayName() {
  console.log(this.name);
}
let person = {
  name: 'Tim',
  sayName: function() {
    sayName();
  }
};
person.sayName();
複製代碼

上面的代碼在nodejs和瀏覽器環境下的結果是不一樣的。雖然nodejs中默認綁定會綁定到global對象,可是上面的代碼結果是輸出undefined;在瀏覽器環境下會輸出Jack

若是將var換成let聲明變量方式,瀏覽器環境下回輸出空字符串,而nodejs下回輸出undefined

綁定的優先級:new > 顯示 > 隱式 > 默認

胖箭頭函數的this綁定

胖箭頭函數沒有本身的this,胖箭頭函數的this繼承自外層代碼塊所在做用域的this,若是外層代碼塊不存在this,則繼續向上查找。

let person = {
  fun1: function() {
    console.log(this);
    return () => {
      console.log(this);
    }
  },
  fun2: function() {
    console.log(this);
    return function() {
      console.log(this);
    }
  },
  fun3: () => {
    console.log(this);
  }
}

let fun1 = person.fun1();  // person
fun1();  // person
let fun2 = person.fun2();  // person
fun2();  // window
person.fun3();  // window
/* 第一次函數調用,採用隱式綁定,this指向上下文對象,也就是person 第二次函數調用,調用了胖箭頭函數,胖箭頭函數的this繼承自上層代碼塊中的this,上層 代碼塊中的this指向person,因此胖箭頭函數的this也指向person 第三次函數調用,採用隱式綁定,this指向上下文對象 第四次函數調用,既沒有隱式綁定條件,沒有顯示綁定條件,也沒有new綁定條件,因此採用默認綁定規則,this指向window 第五次函數調用,調用了胖箭頭函數,而上層person中沒有this,因此向上查找,person的上層爲window,因此這次胖箭頭函數的this指向window */
複製代碼

apply,call,bind詳解

apply

apply接受兩個參數,第一個參數指定函數體內this對象的指向,第二個參數是一個數組或類數組,並將此數組或類數組做爲參數傳遞給所調用的函數

let person = {
  0: "Jack",
  1: "Tim",
  length: 2
}
let persons = [
  "Jack",
  "Tim"
]
function log(a, b) {
  console.log(a);
  console.log(b);
}
log.apply(null, person);   // Jack Tim
log.apply(null, persons);  // Jack Tim
複製代碼

call

call能夠接受多個參數,第一個參數與apply相同,指定函數體內的this對象的指向,其他的參數傳給被調用的函數,做爲被調用函數的參數

function log(a, b) {
  console.log(a);
  console.log(b);
}
log.apply(null, "Jim", "Jack");   // Jim Jack
複製代碼

call和apply的用途

apply和call除了能夠更改this指向外,還能夠用來實現相似於繼承的效果

function Person(name) {
  this.name = name;
  this.sayName = function() {
    console.log(this.name);
  } 
}

function Student() {
  Person.apply(this, arguments);
}

let student = new Student("Jack");
student.sayName();
複製代碼

借用其餘對象的方法

// 數組沒有取得最大值的方法,能夠經過調用Math.max()函數來取得數組的最大值
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.apply(null, arr));  // 9
複製代碼

bind

bind可接受多個參數,並返回一個新函數。第一個參數指定新函數體內的this對象的指向,其他的參數會在傳遞實參以前傳給新函數,做爲新函數的參數

bind只用一個用途,改變this的指向。而且一旦經過bind綁定了this的指向,沒法再次經過bind,apply或call更改this的指向

function sayName() {
    console.log(this.name);
}

let person = {
    name: 'Jack'
};

let child = {
    name: 'Tim',
};

let say = sayName.bind(person);
say();  // Jack
let log = say.bind(child);
log();  // Jack, this並無更改指向
say.apply(child);  // Jack this並無更改指向
say.call(child);  // Jack this並無更改指向
複製代碼

實現apply函數

Function.prototype.mApply = function(context, arr) {
    // 若是context爲null,則將context設置爲window(瀏覽器環境)或global(nodejs環境)
    if (!context) {
        context = typeof window === 'undefined' ? global : window;  // 判斷是在什麼環境下運行,根據環境來給context設置默認值
    } 
    context.fn = this;  // 給context添加函數,使添加的函數指向當前被調用函數
    let res;  // 返回值
    if (!arr) {
        // 若參數爲null,能夠直接調用
        res = context.fn(arr);  // 調用添加函數
    } else if (typeof arr === 'object') {
        // 若參數爲數組或類數組,則經過...運算符將其傳入
        res = context.fn(...arr);  // 調用添加的函數
    }
    delete context.fn;  // 刪除添加的函數
    return res;  // 傳出返回值
}
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.mApply(null, arr));  // 9
複製代碼

實現call函數

Function.prototype.mCall = function(context) {
    // 若是context爲null,則將context設置爲window(瀏覽器環境)或global(nodejs環境)
    if (!context) {
        context = typeof window === 'undefined' ? global : window;  // 判斷是在什麼環境下運行,根據環境來給context設置默認值
    } 
    context.fn = this;  // 給context添加函數,使添加的函數指向被調用的函數
    let args = [...arguments].slice(1);  // 取得除指定上下文參數以外的參數
    let res = context.fn(...args);  // 調用添加的函數,並存儲返回值
    delete context.fn;  // 刪除添加的函數
    return res;  // 傳出返回值
}
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.mCall(null, ...arr));  // 9
複製代碼

實現bind

Function.prototype.mBind = function(context) {
    // 若不是函數
    if (typeof this !== 'function') {
        throw new TypeError("is not a funtion")
    }
    let args = [...arguments].slice(1);
    function Fn() {}
    fun.prototype = this.prototype;
    let self = this;  
    let bound = function() {
        let res = [...args, ...arguments];  // 將bind傳遞的參數與調用時傳遞的參數拼接
        context = this instanceof Fn ? this : context || this;
        return self.apply(context, res);  // 返回值
    }
    bound.prototype = new Fn();
    return bound;
}
function sayName() {
  console.log(this.name);
}

let person = {
  name: 'Jack'
};

let child = {
  name: 'Tim',
};

let say = sayName.mBind(person);
say();  // Jack
let says = say.mBind(child);
says();  // Jack
複製代碼
相關文章
相關標籤/搜索