理解JS函數之call,apply,bind

来源:https://www.cnblogs.com/pglin/archive/2023/02/24/17151156.html
-Advertisement-
Play Games

前言 在 JavaScript 中,apply、bind 和 call 是三個重要的函數,它們都是 Function.prototype 的方法。這些函數可以讓我們動態地改變函數的 this 值,或者傳遞參數來執行函數。本篇博客將詳細介紹 apply、bind 和 call 的使用方法以及它們之間的 ...


前言

在 JavaScript 中,apply、bind 和 call 是三個重要的函數,它們都是 Function.prototype 的方法。這些函數可以讓我們動態地改變函數的 this 值,或者傳遞參數來執行函數。本篇博客將詳細介紹 apply、bind 和 call 的使用方法以及它們之間的區別。

apply

apply() 方法是 Function.prototype 上的一個方法,可以用於改變函數的 this 值。它接受兩個參數:第一個參數是要綁定的 this 值,第二個參數是一個數組,其中包含要傳遞給函數的參數。apply() 方法會立即執行函數。


function addNumbers(a, b, c) {
  return a + b + c;
}

const numbers = [1, 2, 3];

const result = addNumbers.apply(null, numbers);
console.log(result); // 輸出 6

在上面的例子中,我們定義了一個 addNumbers() 函數,它接受三個參數並返回它們的和。然後,我們定義了一個數組 numbers,其中包含要傳遞給 addNumbers() 函數的參數。我們使用 apply() 方法將數組作為參數傳遞給函數,並將 this 值設置為 null。最後,我們列印出 addNumbers() 函數的返回值。

call

call() 方法和 apply() 方法非常相似,但是它的參數不是數組,而是一個一個傳遞的。call() 方法也可以用於改變函數的 this 值。

function greet(name) {
  console.log(`Hello, ${name}!`);
}

greet.call(null, 'Alice'); // 輸出 "Hello, Alice!"

在上面的例子中,我們定義了一個 greet() 函數,它接受一個參數 name,併在控制臺中輸出一條歡迎信息。我們使用 call() 方法將 this 值設置為 null,並將要傳遞給函數的參數一個一個地傳遞。

bind

bind() 方法和 apply() 方法和 call() 方法有些不同。它不會立即執行函數,而是返回一個新的函數,這個函數的 this 值已經被改變了。我們可以在之後的任何時候調用這個函數。並且傳遞的部分參數也可在後面使用。

const person = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, my name is ${this.name}!`);
  }
};

const greetAlice = person.greet.bind(person);
greetAlice(); // 輸出 "Hello, my name is Alice!"

在上面的例子中,我們定義了一個 person 對象,其中包含一個 greet() 方法。然後,我們使用 bind() 方法創建一個新的函數 greetAlice,並將 person 對象作為 this 值綁定。最後,我們調用 greetAlice() 函數,它會輸出 "Hello, my name is Alice!"。

apply()、call() 和 bind() 的區別

apply()、call() 和 bind()三個方法之間的最大區別在於它們傳遞參數的方式以及它們是否是立即執行函數:

  • apply() 和 call() 方法立即執行函數並且傳遞參數的方式不同。
  • apply() 方法傳遞一個數組作為參數,而 call() 方法一個個傳遞參數。
  • bind() 方法不會立即執行函數,而是返回一個新的函數,這個函數的 this 值已經被綁定了。

在使用這些方法時,我們需要理解它們之間的區別,並根據具體情況進行選擇。如果我們想要立即執行一個函數並傳遞一個數組作為參數,那麼應該使用 apply() 方法。如果我們想要立即執行一個函數並逐個傳遞參數,那麼應該使用 call() 方法。如果我們想要創建一個新的函數並綁定 this 值,那麼應該使用 bind() 方法。

此外,在使用 apply()、call() 和 bind() 方法時,我們需要註意傳遞的 this 值和參數是否正確。如果我們不傳遞 this 值或傳遞一個錯誤的 this 值,那麼函數可能無法正確地執行。同樣,如果我們沒有正確地傳遞參數,那麼函數的結果也可能會出現錯誤。

內部機制

瞭解這些方法的內部機制可以幫助我們更好地理解它們的使用方式。在 JavaScript 中,函數是對象,因此每個函數都有一個 prototype 對象。當我們創建一個函數時,它會自動創建一個 prototype 對象,並將該對象的 constructor 屬性設置為函數本身。

apply()、call() 和 bind() 方法都是在 Function.prototype 上定義的(每個函數都是 Function 對象的實例),因此每個函數都可以使用它們。當我們調用 apply() 或 call() 方法時,JavaScript 引擎會將 this 值設置為傳遞給方法的第一個參數,並將要傳遞給函數的參數作為數組或單個參數傳遞。當我們調用 bind() 方法時,它會返回一個新的函數,並將 this 值設置為綁定的值。

下麵是 apply() 方法的一個簡單實現:

Function.prototype.myApply = function(thisArg, args) {
  // 首先判斷調用 myApply() 方法的對象是否為函數
  if (typeof this !== 'function') {
    throw new TypeError('Function.prototype.myApply called on non-function');
  }
  
  // 如果 thisArg 為 null 或 undefined,則將其設置為全局對象
  if (thisArg === null || thisArg === undefined) {
    thisArg = window;
  }

  // 將函數設置為 thisArg 的方法,以便在 thisArg 上調用它
  thisArg.__myApply__ = this;

  // 執行函數並獲取結果
  const result = thisArg.__myApply__(...args);

  // 刪除在 thisArg 上設置的方法
  delete thisArg.__myApply__;

  // 返回函數的執行結果
  return result;
};

call() 方法的實現機制與 apply() 方法類似,只是在調用函數時將參數傳遞給函數的方式有所不同。call() 方法的實現如下:


Function.prototype.myCall = function(thisArg, ...args) {
  // ... 如上 只是參數需要用...rest 展開

};

bind() 方法的實現機制稍微有些不同。bind() 方法會創建一個新的函數,並將原函數的 this 值綁定到指定的對象上。當新函數被調用時,它會將綁定的 this 值傳遞給原函數,並將任何附加的參數一併傳遞。bind() 方法的實現如下:

Function.prototype.myBind = function(thisArg, ...args) {
  // 首先判斷調用 myBind() 方法的對象是否為函數
  if (typeof this !== 'function') {
    throw new TypeError('Function.prototype.myBind called on non-function');
  }

  // 保存原函數的引用
  const originalFunc = this;

  // 返回一個新函數
  return function boundFunc(...newArgs) {
    // 將 this 值設置為 thisArg,並調用原函數
    return originalFunc.apply(thisArg, [...args, ...newArgs]);
    };
};

這裡,我們通過使用剩餘參數和展開語法來處理任意數量的傳遞參數,同時使用 apply() 方法將綁定的 this 值和所有參數傳遞給原函數。

需要註意的是,這裡的 bind() 方法是定義在 Function 的原型上的。這意味著,所有的函數都可以調用這個方法,並將其綁定到需要的對象上。

值得一提的是,bind() 方法在綁定 this 值的同時,還可以通過將附加參數傳遞給新函數來“柯里化”函數。也就是說,通過預先綁定部分參數,我們可以創建一個新函數,這個函數接受剩餘的參數,併在調用原函數時將這些參數一併傳遞。這個技巧在實際開發中經常用到,可以幫助我們簡化代碼,並提高代碼的可讀性。

以上是 apply()、call() 和 bind() 方法的手動實現方式,當然,JavaScript 中的這些方法已經預先實現好了,我們只需要使用即可

apply()、call() 和 bind() 方法的使用場景

1. 改變函數的 this 值

在 JavaScript 中,函數的 this 值預設指向全局對象(在瀏覽器中通常為 window 對象)。如果我們想要在函數內部使用不同的 this 值,那麼可以使用 apply()、call() 或 bind() 方法來改變它。例如:

const obj = { name: 'Alice' };

function greet() {
  console.log(`Hello, ${this.name}!`);
}

greet.apply(obj); // "Hello, Alice!"
greet.call(obj); // "Hello, Alice!"
const newGreet = greet.bind(obj);
newGreet(); // "Hello, Alice!"

在上面的代碼中,我們使用 apply()、call() 和 bind() 方法來將函數的 this 值設置為 obj 對象,併在函數內部使用它。

2. 函數的參數不確定

有時候我們無法確定函數的參數個數,或者我們想要將一個數組作為參數傳遞給函數。在這種情況下,我們可以使用 apply() 方法來將數組作為參數傳遞給函數。例如:

function sum(a, b, c) {
  return a + b + c;
}

const nums = [1, 2, 3];

const result = sum.apply(null, nums);

console.log(result); // 6

在上面的代碼中,我們使用 apply() 方法將 nums 數組作為參數傳遞給 sum() 函數,從而計算出它們的和。

3. 繼承

在 JavaScript 中,我們可以使用 call() 方法來實現繼承。前面講解對象時也用到過。例如:

function Animal(name) {
  this.name = name;
}

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}

const myDog = new Dog('Fido', 'Golden Retriever');

console.log(myDog.name); // "Fido"
console.log(myDog.breed); // "Golden Retriever"

在上面的代碼中,我們定義了 Animal 和 Dog 兩個構造函數,並使用 call() 方法將 Animal 的屬性和方法繼承到 Dog 中。

4. 部分應用函數

使用 bind() 方法可以實現部分應用函數,即將一個函數的部分參數固定下來,返回一個新的函數。例如:

function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2);

console.log(double(3)); // 6
console.log(double(4)); // 8

在上面的代碼中,我們使用 bind() 方法將 multiply() 函數的第一個參數固定為 2,從而創建了一個新的函數 double(),它的作用是將傳入的參數乘以 2。

最後

總之,apply()、call() 和 bind() 方法非常有用,並且在 JavaScript 開發中經常被使用。熟練掌握它們的用法可以幫助我們更好地編寫高質量的代碼。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2月15日,數倉軟體巨頭Teradata宣佈根據其對中國當前和未來商業環境的慎重評估,將逐步結束在中國的直接運營,後續進入中國公司的關閉程式。 一石激起千層浪,這一消息,在國內的To B市場引起了廣泛關註。Teradata這家進入中國市場26年,為國內數倉發展帶來重要啟蒙,奠定數據服務人才基礎,被業 ...
  • 前言 redis安裝在Linux伺服器上,系統為centos7,安裝的版本為redis6.2.10 下載與安裝 下載地址:https://redis.io/download/#redis-downloads 點擊上面的鏈接就能進入redis下載頁面,最新的目前是7.0 這裡使用的是6.2.10的版本 ...
  • 閱識風雲是華為雲信息大咖,擅長將複雜信息多元化呈現,其出品的一張圖(雲圖說)、深入淺出的博文(雲小課)或短視頻(雲視廳)總有一款能讓您快速上手華為雲。更多精彩內容請單擊此處。 摘要:Hue是一組WEB應用,用於和MRS大數據組件進行交互,能夠幫助用戶瀏覽HDFS,進行Hive查詢,啟動MapRedu ...
  • 前言: 關於ro.serialno這個屬性,相信大家都不陌生了,應用層的Build.getSerial(),Build.SERIAL等均是直接或間接的獲取了這個屬性值。接下來從boot到系統應用,小小的分析一下它的整個流程: 由於是APP經常使用,那我們從應用層分析到底層kernel/boot 一, ...
  • Xcode作為日常開發iOS程式的IDE,支持C、C++、Objective-C、Swift、Ruby等語言進行編寫。日常開發入口就是Xcode workspace或者Xcode project。 workspace是一個Xcode文檔,它將項目和其他文件、project分組。一個workspac... ...
  • 好家伙,本篇為《JS高級程式設計》第十章“函數”學習筆記 1.函數的三種定義方式:函數表達式、函數聲明及箭頭函數 函數聲明: function sum(a) { return a + 1; } 函數表達式: let sum= function(a){ return a + 1; } 箭頭函數: le ...
  • 概述 前端三要素 HTML(結構) :超文本標記語言(Hyper Text Markup Language) ,決定網頁的結構和內容 CSS(表現) :層疊樣式表(Cascading Style sheets) ,設定網頁的表現樣式 JavaScript (行為) :是一種弱類型腳本語言,其源代碼不 ...
  • React Native 的基礎是React, 是在 web 端非常流行的開源 UI 框架。要想掌握 React Native,先瞭解 React 框架本身是非常有幫助的。 一、什麼是React Native 1.1 React Native帶來的驚喜 React Native 是一個使用JavaS ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...