JavaScriptのコールバック関数とその使い方を詳しく解説

青灯夜游
リリース: 2020-11-03 17:47:33
転載
2658 人が閲覧しました

JavaScriptのコールバック関数とその使い方を詳しく解説

JavaScript では、関数はファーストクラスのオブジェクトです。つまり、関数はファーストクラスの管理を備えたオブジェクトのように使用できます。関数は実際にはオブジェクトであるため、変数に「格納」したり、関数パラメーターとして渡したり、関数内で作成したり、関数から返すことができます。

関数はファーストクラスのオブジェクトであるため、JavaScript でコールバック関数を使用できます。次の記事では、コールバック関数についてすべて学びます。コールバック関数は、おそらく JavaScript で最もよく使用される関数プログラミング手法であり、文字通り JavaScript または jQuery コードの小さな部分のように見えますが、多くの開発者にとって依然として謎です。この記事を読むと、コールバック関数の使用方法が理解できます。

コールバック関数は、関数型プログラミングと呼ばれるプログラミング パラダイムから派生した概念です。簡単に言えば、関数型プログラミングとは、関数を変数として使用することです。関数型プログラミングは、昔も今も広く使用されていませんが、特別な訓練を受けたマスターレベルのプログラマーだけが使用できる秘密のテクニックとみなされていました。

幸いなことに、関数プログラミングのテクニックは完全に説明されており、私のような一般の人でも簡単に使用できるようになりました。関数型プログラミングの主なトリックの 1 つはコールバック関数です。次のコンテンツでは、コールバック関数の実装が実際には通常の関数にパラメータを渡すのと同じくらい簡単であることがわかります。このヒントは非常に簡単なので、なぜこれが高度な JavaScript テクニックに関する章に含まれることが多いのか不思議に思います。

コールバック関数または高階関数とは

#コールバック関数は高階関数とも呼ばれ、次のような関数です。がパラメータとして別の関数の関数 (ここでは他の関数

otherFunction と呼びます) に渡されると、コールバック関数が otherFunction で呼び出されます。コールバック関数は本質的にプログラミング パターン (一般的な問題に対して作成されたソリューション) であるため、コールバック関数の使用はコールバック パターンとも呼ばれます。

これは、jQuery でコールバック関数を使用する簡単で一般的な例です:

//注意到click方法中是一个函数而不是一个变量
//它就是回调函数
$("#btn_1").click(function() {
    alert("Btn 1 Clicked");
});
ログイン後にコピー

前の例でわかるように、関数をパラメータとして

click## に渡しました。 # 方法。 click メソッドは、渡した関数を呼び出します (または実行します)。これは JavaScript におけるコールバック関数の典型的な使用法であり、jQuery で広く使用されています。 これは、JavaScript の典型的なコールバック関数の別の例です:

var friends = ["Mike", "Stacy", "Andy", "Rick"];

friends.forEach(function (eachName, index){
    console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 4. Rick
});
ログイン後にコピー

もう一度、匿名関数 (名前のない関数) をパラメータとして

に渡していることに注目してください。 forEach

メソッド。 これまで、匿名関数を引数として別の関数またはメソッドに渡してきました。より実践的な例を見て独自のコールバック関数を作成する前に、コールバック関数がどのように機能するかを理解しましょう。

コールバック関数はどのように機能するのでしょうか? 関数は JavaScript のファーストクラス オブジェクトであるため、関数をオブジェクトのように扱います。そのため、関数を変数のように渡したり、関数内で関数を返したり、他の関数で関数を返したりすることができます。 。コールバック関数をパラメータとして別の関数に渡すときは、関数定義のみを渡します。パラメータに対して関数を実行していません。通常の実行関数のように、一対の実行括弧

()

を使用して関数を渡しません。 コールバック関数はすぐには実行されないことに注意することが重要です。これは、(その名前が示すように) 含まれている関数内の特定の時点で「コールバック」されます。したがって、最初の jQuery の例は次のようになります:

//匿名函数不会再参数中被执行
//这是一个回调函数
$("#btn_1").click(function(){
    alert("Btn 1 Clicked");
});
ログイン後にコピー

この匿名関数は、後で関数本体内で呼び出されます。名前がある場合でも、それを含む関数内の

arguments

オブジェクトを通じて取得されます。

コールバック関数はクロージャですコールバック関数を変数として別の関数に渡すことができる場合、コールバック関数にはクロージャが含まれます。コールバック関数がそれを含む関数内で定義されているかのように、関数内の特定の時点で実行されます。これは、コールバック関数が本質的にクロージャであることを意味します。

ご存知のとおり、クロージャはそれを含む関数のスコープに入ることができるため、コールバック関数はグローバル スコープの変数だけでなく、クロージャを含む関数内の変数も取得できます。

コールバック関数実装の基本原則コールバック関数は複雑ではありませんが、コールバック関数の作成と使用を開始する前に、次のことを行う必要があります。コールバック関数を実装するためのいくつかの基本原則を理解してください。

名前付き関数または匿名関数をコールバックとして使用する前の jQuery の例と forEach の例では、パラメータ位置 Anonymous で定義されたパラメータを使用しました。コールバック関数として機能します。これは、コールバック関数の使用における一般的な魔法のトリックです。もう 1 つの一般的なパターンは、名前付き関数を定義し、関数名を変数として関数に渡すことです。たとえば、次の例:

//全局变量
var allUserData = [];

//普通的logStuff函数,将内容打印到控制台
function logStuff (userData){
    if ( typeof userData === "string"){
        console.log(userData);
    } else if ( typeof userData === "object"){
        for(var item in userData){
            console.log(item + ": " + userData[item]);
        }
    }
}

//一个接收两个参数的函数,后面一个是回调函数
function getInput (options, callback){
    allUserData.push(options);
    callback(options);
}

//当我们调用getInput函数时,我们将logStuff作为一个参数传递给它
//因此logStuff将会在getInput函数内被回调(或者执行)
getInput({name:"Rich",speciality:"Javascript"}, logStuff);
//name:Rich
//speciality:Javascript
ログイン後にコピー

传递参数给回调函数

既然回调函数在执行时仅仅是一个普通函数,我们就能给它传递参数。我们能够传递任何包含它的函数的属性(或者全局属性)作为回调函数的参数。在前面的例子中,我们将options作为一个参数传递给了回调函数。现在我们传递一个全局变量和一个本地变量:

//全局变量
var generalLastName = "Cliton";

function getInput (options, callback){
    allUserData.push (options);
    //将全局变量generalLastName传递给回调函数
    callback(generalLastName,options);
}
ログイン後にコピー

在执行之前确保回调函数是一个函数

在调用之前检查作为参数被传递的回调函数确实是一个函数,这样的做法是明智的。同时,这也是一个实现条件回调函数的最佳时间。

我们来重构上面例子中的getInput函数来确保检查是恰当的。

function getInput(options, callback){
    allUserData.push(options);
    
    //确保callback是一个函数
    if(typeof callback === "function"){
        //调用它,既然我们已经确定了它是可调用的
        callback(options);
    }
}
ログイン後にコピー

如果没有适当的检查,如果getInput的参数中没有一个回调函数或者传递的回调函数事实上并不是一个函数,我们的代码将会导致运行错误。

使用this对象的方法作为回调函数时的问题

当回调函数是一个this对象的方法时,我们必须改变执行回调函数的方法来保证this对象的上下文。否则如果回调函数被传递给一个全局函数,this对象要么指向全局window对象(在浏览器中)。要么指向包含方法的对象。

我们在下面的代码中说明:

//定义一个拥有一些属性和一个方法的对象 //我们接着将会把方法作为回调函数传递给另一个函数

var clientData = {
    id: 094545,
    fullName "Not Set",
    //setUsrName是一个在clientData对象中的方法
    setUserName: fucntion (firstName, lastName){
        //这指向了对象中的fullName属性
        this.fullName = firstName + " " + lastName;
    }
} 

function getUserInput(firstName, lastName, callback){
    //在这做些什么来确认firstName/lastName
    //现在存储names
    callback(firstName, lastName);
}
ログイン後にコピー

在下面你的代码例子中,当clientData.setUsername被执行时,this.fullName并没有设置clientData对象中的fullName属性。相反,它将设置window对象中的fullName属性,因为getUserInput是一个全局函数。这是因为全局函数中的this对象指向window对象。

getUserInput("Barack","Obama",clientData.setUserName);
console.log(clientData,fullName);  //Not Set
//fullName属性将在window对象中被初始化     
console.log(window.fullName);  //Barack Obama
ログイン後にコピー

使用Call和Apply函数来保存this

我们可以使用Call或者Apply函数来修复上面你的问题。到目前为止,我们知道了每个JavaScript中的函数都有两个方法:CallApply。这些方法被用来设置函数内部的this对象以及给此函数传递变量。

call接收的第一个参数为被用来在函数内部当做this的对象,传递给函数的参数被挨个传递(当然使用逗号分开)。Apply函数的第一个参数也是在函数内部作为this的对象,然而最后一个参数确是传递给函数的值的数组。

听起来很复杂,那么我们来看看使用ApplyCall有多么的简单。为了修复前面例子的问题,我将在下面你的例子中使用Apply函数:

//注意到我们增加了新的参数作为回调对象,叫做“callbackObj”
function getUserInput(firstName, lastName, callback. callbackObj){
    //在这里做些什么来确认名字
    callback.apply(callbackObj, [firstName, lastName]);
}
ログイン後にコピー

使用Apply函数正确设置了this对象,我们现在正确的执行了callback并在clientData对象中正确设置了fullName属性:

//我们将clientData.setUserName方法和clientData对象作为参数,clientData对象会被Apply方法使用来设置this对象     
getUserName("Barack", "Obama", clientData.setUserName, clientData);

//clientData中的fullName属性被正确的设置
console.log(clientUser.fullName); //Barack Obama
ログイン後にコピー

我们也可以使用Call函数,但是在这个例子中我们使用Apply函数。

允许多重回调函数

我们可以将不止一个的回调函数作为参数传递给一个函数,就像我们能够传递不止一个变量一样。这里有一个关于jQuery中AJAX的例子:

function successCallback(){
    //在发送之前做点什么
}
  
function successCallback(){
//在信息被成功接收之后做点什么
}

function completeCallback(){
//在完成之后做点什么
}

function errorCallback(){
    //当错误发生时做点什么
}

$.ajax({
    url:"http://fiddle.jshell.net/favicon.png",
    success:successCallback,
    complete:completeCallback,
    error:errorCallback
});
ログイン後にコピー

“回调地狱”问题以及解决方案

在执行异步代码时,无论以什么顺序简单的执行代码,经常情况会变成许多层级的回调函数堆积以致代码变成下面的情形。这些杂乱无章的代码叫做回调地狱因为回调太多而使看懂代码变得非常困难。我从node-mongodb-native,一个适用于Node.js的MongoDB驱动中拿来了一个例子。这段位于下方的代码将会充分说明回调地狱:

var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), 
                 {'pk':CustomPKFactory});
p_client.open(function(err, p_client) {
    p_client.dropDatabase(function(err, done) {
        p_client.createCollection('test_custom_key', function(err, collection) {
            collection.insert({'a':1}, function(err, docs) {
                collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, 
                function(err, cursor) {
                    cursor.toArray(function(err, items) {
                        test.assertEquals(1, items.length);
                        // Let's close the db
                        p_client.close();
                    });
                });
            });
        });
    });
});
ログイン後にコピー

你应该不想在你的代码中遇到这样的问题,当你当你遇到了

  • 你将会时不时的遇到这种情况

  • 这里有关于这个问题的两种解决方案。

  • 给你的函数命名并传递它们的名字作为回调函数,而不是主函数的参数中定义匿名函数。

  • 模块化L将你的代码分隔到模块中,这样你就可以到处一块代码来完成特定的工作。然后你可以在你的巨型应用中导入模块。

创建你自己的回调函数

既然你已经完全理解了关于JavaScript中回调函数的一切(我认为你已经理解了,如果没有那么快速的重读以便),你看到了使用回调函数是如此的简单而强大,你应该查看你的代码看看有没有能使用回调函数的地方。回调函数将在以下几个方面帮助你:

  • 避免重复代码(DRY-不要重复你自己)

  • 在你拥有更多多功能函数的地方实现更好的抽象(依然能保持所有功能)

  • 让代码具有更好的可维护性

  • 使代码更容易阅读

  • 编写更多特定功能的函数

创建你的回调函数非常简单。在下面的例子中,我将创建一个函数完成以下工作:读取用户信息,用数据创建一首通用的诗,并且欢迎用户。这本来是个非常复杂的函数因为它包含很多if/else语句并且,它将在调用那些用户数据需要的功能方面有诸多限制和不兼容性。

相反,我用回调函数实现了添加功能,这样一来获取用户信息的主函数便可以通过简单的将用户全名和性别作为参数传递给回调函数并执行来完成任何任务。

简单来讲,getUserInput函数是多功能的:它能执行具有无种功能的回调函数。

//首先,创建通用诗的生成函数;它将作为下面的getUserInput函数的回调函数

function genericPoemMaker(name, gender) {
    console.log(name + " is finer than fine wine.");
    console.log("Altruistic and noble for the modern time.");
    console.log("Always admirably adorned with the latest style.");
    console.log("A " + gender + " of unfortunate tragedies who still manages a perpetual smile");
}

//callback,参数的最后一项,将会是我们在上面定义的genericPoemMaker函数
function getUserInput(firstName, lastName, gender, callback) {
    var fullName = firstName + " " + lastName;
    // Make sure the callback is a function
    if (typeof callback === "function") {
    // Execute the callback function and pass the parameters to it
    callback(fullName, gender);
    }
}
ログイン後にコピー

调用getUserInput函数并将genericPoemMaker函数作为回调函数:

getUserInput("Michael", "Fassbender", "Man", genericPoemMaker);
// 输出
/* Michael Fassbender is finer than fine wine.
Altruistic and noble for the modern time.
Always admirably adorned with the latest style.
A Man of unfortunate tragedies who still manages a perpetual smile.
*/
ログイン後にコピー

因为getUserInput函数仅仅只负责提取数据,我们可以把任意回调函数传递给它。例如,我们可以传递一个greetUser函数:

unction greetUser(customerName, sex)  {
    var salutation  = sex && sex === "Man" ? "Mr." : "Ms.";
    console.log("Hello, " + salutation + " " + customerName);
}

// 将greetUser作为一个回调函数
getUserInput("Bill", "Gates", "Man", greetUser);

// 这里是输出
Hello, Mr. Bill Gates
ログイン後にコピー

我们调用了完全相同的getUserInput函数,但是这次完成了一个完全不同的任务。

正如你所见,回调函数很神奇。即使前面的例子相对简单,想象一下能节省多少工作量,你的代码将会变得更加的抽象,这一切只需要你开始使用毁掉函数。大胆的去使用吧。

在JavaScript编程中回调函数经常以几种方式被使用,尤其是在现代Web应用开发以及库和框架中:

  • 异步调用(例如读取文件,进行HTTP请求,等等)

  • 时间监听器/处理器

  • setTimeoutsetInterval方法

  • 一般情况:精简代码

结束语

JavaScript回调函数非常美妙且功能强大,它们为你的Web应用和代码提供了诸多好处。你应该在有需求时使用它;或者为了代码的抽象性,可维护性以及可读性而使用回调函数来重构你的代码。

推荐教程:《JavaScript视频教程

以上がJavaScriptのコールバック関数とその使い方を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:javascriptissexy.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート