首頁  >  文章  >  web前端  >  JS中this、apply、call、bind的相關介紹

JS中this、apply、call、bind的相關介紹

巴扎黑
巴扎黑原創
2017-09-20 09:28:541262瀏覽

JS中的this、apply、call、bind是一道經典面試題,最好還是了解一下 this 的指向和 call、apply、bind 三者的區別。以下就跟著腳本之家小編一起學習this、apply、call、bind的知識吧

這又是一個面試經典問題~/(ㄒoㄒ)/~~也是ES5中眾多坑中的一個,在ES6 中可能會極大避免this 產生的錯誤,但是為了一些老代碼的維護,最好還是了解一下this 的指向和call、apply、bind 三者的區別。

this 的指向

在ES5 中,其實this 的指向,總是堅持一個原理:this 永遠指向最後調用它的那個對象,來,跟著我朗讀三遍:this 永遠指向最後調用它的那個對象,this 永遠指向最後調用它的那個對象,this 永遠指向最後調用它的那個對象。記住這句話,this 你已經了解一半了。

下面我們來看一個最簡單的例子:

範例1:


#
 var name = "windowsName";
 function a() {
  var name = "Cherry";
  console.log(this.name);   // windowsName
  console.log("inner:" + this); // inner: Window
 }
 a();
 console.log("outer:" + this)   // outer: Window

這個相信大家都知道為什麼log 的是windowsName,因為根據剛剛的那句話“this 永遠指向最後調用它的那個對象”,我們看最後調用a 的地方a();,前面沒有調用的對像那麼就是全局對象window,這就相當於是window. a();注意,這裡我們沒有使用嚴格模式,如果使用嚴格模式的話,全域物件就是undefined,那麼就會報錯Uncaught TypeError: Cannot read property 'name' of undefined。

再看下這個例子:

範例2:


var name = "windowsName";
 var a = {
  name: "Cherry",
  fn : function () {
   console.log(this.name);  // Cherry
  }
 }
 a.fn();

在這個例子中,函數fn 是物件a 呼叫的,所以列印的值就是a 中的name 的值。是不是有點清晰了呢~

我們做一個小小的改動:

範例3:


##

var name = "windowsName";
 var a = {
  name: "Cherry",
  fn : function () {
   console.log(this.name);  // Cherry
  }
 }
 window.a.fn();

這裡印Cherry的原因也是因為剛剛那句話“this 永遠指向最後調用它的那個對象”,最後調用它的對象仍然是對象a。

我們再來看這個範例:

範例 4:


 var name = "windowsName";
 var a = {
  // name: "Cherry",
  fn : function () {
   console.log(this.name);  // undefined
  }
 }
 window.a.fn();

這裡為什麼會印 undefined 呢?這是因為正如剛剛所描述的那樣,調用fn 的是a 對象,也就是說fn 的內部的this 是對象a,而對象a 中並沒有對name 進行定義,所以log 的this.name 的值是undefined 。

這個例子還是說明了:this 永遠指向最後調用它的那個對象,因為最後調用fn 的對像是a,所以就算a 中沒有name 這個屬性,也不會繼續向上一個對象尋找this. name,而是直接輸出undefined。

再來看一個比較坑的例子:

例5:


#

 var name = "windowsName";
 var a = {
  name : null,
  // name: "Cherry",
  fn : function () {
   console.log(this.name);  // windowsName
  }
 }
 var f = a.fn;
 f();

這裡你可能會有疑問,為什麼不是Cherry,這是因為雖然將a 物件的fn 方法賦值給變數f 了,但是沒有調用,再接著跟我念這句話:“this 永遠指向最後調用它的那個物件”,由於剛剛的f 並沒有調用,所以fn() 最後仍然是被window 呼叫的。所以 this 指向的就是 window。

由以上五個例子我們可以看出,this 的指向並不是在創建的時候就可以確定的,在 es5 中,永遠是this 永遠指向最後調用它的那個對象。

再來看一個例子:

範例6:


 var name = "windowsName";
 function fn() {
  var name = 'Cherry';
  innerFunction();
  function innerFunction() {
   console.log(this.name);  // windowsName
  }
 }
 fn()

讀到現在了應該能夠理解這是為什麼了(o゚▽゚)o。

怎麼改變this 的指向

改變this 的指向我總結有以下幾種方法:

使用ES6 的箭頭函數

在函數內部使用_this = this

使用apply、call、bind

new 實例化一個物件

範例7:


var name = "windowsName";
 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)  
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   },100);
  }
 };
 a.func2()  // this.func1 is not a function

在不使用箭頭函數的情況下,是會報錯的,因為最後呼叫setTimeout 的物件是window,但是在window 中並沒有func1 函數。

我們在改變 this 指向這一節將把這個例子當作 demo 來改造。

箭頭函數

眾所周知,ES6 的箭頭函數是可以避免 ES5 中使用 this 的坑的。箭頭函數的 this 總是指向函數定義時的 this,而非執行時。 ,箭頭函數需要記著這句話:「箭頭函數中沒有this 綁定,必須透過尋找作用域鏈來決定其值,如果箭頭函數被非箭頭函數包含,則this 綁定的是最近一層非箭頭函數的this,否則,this 為undefined」。

範例8 :


 var name = "windowsName";
 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)  
  },
  func2: function () {
   setTimeout( () => {
    this.func1()
   },100);
  }
 };
 a.func2()  // Cherry

在函數內部使用_this = this

如果不使用ES6,那麼這種方式應該是最簡單的不會出錯的方式了,我們是先將呼叫這個函數的物件保存在變數_this 中,然後在函數中都使用這​​個_this,這樣_this 就不會改變了。

範例 9:


#

 var name = "windowsName";
 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)  
  },
  func2: function () {
   var _this = this;
   setTimeout( function() {
    _this.func1()
   },100);
  }
 };
 a.func2()  // Cherry

这个例子中,在 func2 中,首先设置 var _this = this;,这里的 this 是调用 func2 的对象 a,为了防止在 func2 中的 setTimeout 被 window 调用而导致的在 setTimeout 中的 this 为 window。我们将 this(指向变量 a) 赋值给一个变量 _this,这样,在 func2 中我们使用 _this 就是指向对象 a 了。

使用 apply、call、bind

使用 apply、call、bind 函数也是可以改变 this 的指向的,原理稍后再讲,我们先来看一下是怎么实现的:

使用 apply

例 10:


 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   }.apply(a),100);
  }
 };
 a.func2()   // Cherry

使用 call

例 11:


 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   }.call(a),100);
  }
 };
 a.func2()   // Cherry

使用 bind

例 12:


var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   }.bind(a)(),100);
  }
 };
 a.func2()   // Cherry

apply、call、bind 区别

刚刚我们已经介绍了 apply、call、bind 都是可以改变 this 的指向的,但是这三个函数稍有不同。

在 MDN 中定义 apply 如下;

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

语法:


fun.apply(thisArg, [argsArray])

thisArg:在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。

apply 和 call 的区别

其实 apply 和 call 基本类似,他们的区别只是传入的参数不同。

call 的语法为:


fun.call(thisArg[, arg1[, arg2[, ...]]])

所以 apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

例 13:


var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.apply(a,[1,2])  // 3

例 14:


var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.call(a,1,2)  // 3

bind 和 apply、call 区别

我们先来将刚刚的例子使用 bind 试一下


 var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.bind(a,1,2)

我们会发现并没有输出,这是为什么呢,我们来看一下 MDN 上的文档说明:

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

所以我们可以看出,bind 是创建一个新的函数,我们必须要手动去调用:


var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.bind(a,1,2)()   // 3

以上是JS中this、apply、call、bind的相關介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn