目次
手写实现 bind
ホームページ ウェブフロントエンド jsチュートリアル この記事では、call、apply、bind メソッドの実装について詳しく説明します。

この記事では、call、apply、bind メソッドの実装について詳しく説明します。

Jul 12, 2021 pm 06:03 PM
apply bind javascript

この記事では、コード例を使用して、call、apply、bind の実装方法を詳細に分析します。これらのメソッドの具体的な使用方法については、すでに MDN またはサイト上の記事で明確に説明されています。ここでは詳細には触れません。

この記事では、call、apply、bind メソッドの実装について詳しく説明します。

#call の手書き実装

#ES3 バージョン

Function.prototype.myCall = function(thisArg){
    if(typeof this != 'function'){
        throw new Error('The caller must be a function')
    }
     if(thisArg === undefined || thisArg === null){
        thisArg = globalThis
    } else {
        thisArg = Object(thisArg)
    }   
    var args = []
    for(var i = 1;i < arguments.length;i ++){
        args.push(&#39;arguments[&#39; + i + &#39;]&#39;)
    }
    thisArg.fn = this
    var res = eval(&#39;thisArg.fn(&#39; + args + &#39;)&#39;)
    delete thisArg.fn
    return res
}
ログイン後にコピー

ES6 バージョン

Function.prototype.myCall = function(thisArg,...args){
    if(typeof this != &#39;function&#39;){
        throw new Error(&#39;The caller must be a function&#39;)
    }
    if(thisArg === undefined || thisArg === null){
        thisArg = globalThis
    } else {
        thisArg = Object(thisArg)
    }
    thisArg.fn = this
    const res = thisArg.fn(...args)
    delete thisArg.fn
    return res
}
ログイン後にコピー

call

で関数を呼び出す場合、thisArg を call に渡すことで関数内で指定できます。そして、これは、関数が thisArg を通じて呼び出される限り達成できます。これが私たちの主な目標です。

実装のポイント

    最終的には、
  • myCall

    が関数を通じて呼び出されるので、 myCallcall のように関数プロトタイプにマウントされます。同時に、myCall は関数を通じて呼び出されるため、myCall 内でこれを通じて myCall の呼び出し元を取得できます。その関数を実際に実行します。

  • myCall

    が関数プロトタイプにマウントされているのは当然のことです。非関数を通じて myCall を呼び出すと、間違いなくエラーがスローされるのに、なぜ myCall で呼び出し元のタイプを確認してエラーをカスタマイズする必要があるのでしょうか。これは、呼び出し元 obj = {} がオブジェクトであるが、Function (obj.__proto__ = Function.prototype) を継承する場合、非関数として処理されるためです。 , 実際には myCall メソッドを呼び出すことができますが、このとき、関数であることを確認するための型チェックが行われていないと、後で関数として直接呼び出されたときにエラーがスローされます。

  • call

    に渡された thisArg が null または未定義の場合、thisArg は実際にはグローバル オブジェクトを指します。thisArg が基本型の場合は、 # # を使用できます。 #Object() ボックス化操作を実行し、それをオブジェクトに変換します。これは主に、後でメソッド呼び出しによって関数を実行できるようにするためです。したがって、 thisArg = thisArg ? Object(thisArg) : globalThis のように書くことができますか? thisArg がブール値 false の場合、thisArg は最終的に globalThis と等しくなりますが、実際には Boolean {false} と等しくなるはずです。

    前に述べたように、
  • myCall
  • の this を通じて実際に実行された関数を取得できるため、

    thisArg.fn = this は Use と同等です。この関数を thisArg のメソッドとして定義すると、thisArg オブジェクトを通じてこの関数を呼び出すことができます。

  • thisArg.fn = this
  • は thisArg に fn 属性を追加するのと同じなので、実行結果を返す前にこの属性を削除する必要があります。さらに、thisArg に存在する可能性がある同じ名前のプロパティ fn を上書きするのを避けるために、

    const fn = Symbol('fn') を使用して一意のプロパティを構築し、その後 thisArg[fn] = this

    ES3 バージョンと ES6 バージョンの主な違いは、パラメーターの受け渡しと関数の実行にあります。
  • ES6 では、残りの部分が導入されています。実際に関数を実行するときにどれだけ多くのパラメータが渡されても、これらのパラメータは args 配列を通じて取得できますが、同時に展開演算子の導入により args パラメータ配列を展開して実行することもできます。パラメータは実行のために 1 つずつ関数に渡されます
    • しかし、ES3 には残りのパラメータのようなものはないため、
    • myCall
    • を定義するときは、受け取るだけですthisArg パラメータを 1 つ取得し、関数本体の引数クラス配列を通じてすべてのパラメータを取得します。必要なのは、最初の要素 (thisArg) を除く引数内のすべての要素です。 ES6 の場合は

      [...arguments].slice(1) だけですが、これは ES3 なので、インデックス 1 から始まる引数を走査して、それらを args 配列にプッシュすることしかできません。 。また、ここでプッシュされるのは文字列形式のパラメータであることに注意してください。これは主に、後で eval を介して関数を実行するときに、パラメータを関数に 1 つずつ渡すのを容易にするためです。

      関数を実行するために eval を使用する必要があるのはなぜですか?関数が実際に受け取る必要があるパラメーターの数が不明であり、展開演算子を使用できないため、実行可能な文字列式を構築し、関数のすべてのパラメーターを明示的に渡すことしかできません。
    #手書きの実装 apply

apply の使用法は call と非常に似ているため、実装も非常に似ています。 。注意する必要がある違いは、呼び出しが thisArg パラメータを受け取った後、複数のパラメータも受け取ることができる (つまり、パラメータ リストを受け取る) ことと、apply が thisArg パラメータを受け取った後、通常 2 番目のパラメータが配列であることです。 - のようなオブジェクト:
fn.call(thisArg,arg1,arg2,...)
fn.apply(thisArg,[arg1,arg2,...])
ログイン後にコピー

如果第二个参数传的是 null 或者 undefined,那么相当于是整体只传了 thisArg 参数。

ES3 版本

Function.prototype.myApply = function(thisArg,args){
    if(typeof this != &#39;function&#39;){
        throw new Error(&#39;the caller must be a function&#39;)
    } 
    if(thisArg === null || thisArg === undefined){
        thisArg = globalThis
    } else {
        thisArg = Object(thisArg)
    }
    if(args === null || args === undefined){
        args = []
    } else if(!Array.isArray(args)){
        throw new Error(&#39;CreateListFromArrayLike called on non-object&#39;)
    }
    var _args = []
    for(var i = 0;i < args.length;i ++){
        _args.push(&#39;args[&#39; + i + &#39;]&#39;)
    }
    thisArg.fn = this
    var res = _args.length ? eval(&#39;thisArg.fn(&#39; + _args + &#39;)&#39;):thisArg.fn()
    delete thisArg.fn
    return res
}
ログイン後にコピー

ES6 版本

Function.prototype.myApply = function(thisArg,args){
    if(typeof thisArg != &#39;function&#39;){
        throw new Error(&#39;the caller must be a function&#39;)
    } 
    if(thisArg === null || thisArg === undefined){
        thisArg = globalThis
    } else {
        thisArg = Object(thisArg)
    }
    if(args === null || args === undefined){
        args = []
    } 
    // 如果传入的不是数组,仿照 apply 抛出错误
    else if(!Array.isArray(args)){
        throw new Error(&#39;CreateListFromArrayLike called on non-object&#39;)
    }
    thisArg.fn = this
    const res = thisArg.fn(...args)
    delete thisArg.fn
    return res
}
ログイン後にコピー

实现要点

基本上和 call 的实现是差不多的,只是我们需要检查第二个参数的类型。

手写实现 bind

bind 也可以像 callapply 那样给函数绑定一个 this,但是有一些不同的要点需要注意:

  • bind 不是指定完 this 之后直接调用原函数,而是基于原函数返回一个内部完成了 this 绑定的新函数
  • 原函数的参数可以分批次传递,第一批可以在调用 bind 的时候作为第二个参数传入,第二批可以在调用新函数的时候传入,这两批参数最终会合并在一起,一次传递给新函数去执行
  • 新函数如果是通过 new 方式调用的,那么函数内部的 this 会指向实例,而不是当初调用 bind 的时候传入的 thisArg。换句话说,这种情况下的 bind 相当于是无效的

ES3 版本

这个版本更接近 MDN 上的 polyfill 版本。

Function.prototype.myBind = function(thisArg){
    if(typeof this != &#39;function&#39;){
        throw new Error(&#39;the caller must be a function&#39;)
    }
    var fnToBind = this
    var args1 = Array.prototype.slice.call(arguments,1)
    var fnBound = function(){
        // 如果是通过 new 调用
        return fnToBind.apply(this instanceof fnBound ? this:thisArg,args1.concat(args2))     
    }
    // 实例继承
    var Fn = function(){}
    Fn.prototype = this.prototype
    fnBound.prototype = new Fn()
    return fnBound
}
ログイン後にコピー

ES6 版本

Function.prototype.myBind = function(thisArg,...args1){
    if(typeof this != &#39;function&#39;){
        throw new Error(&#39;the caller must be a function&#39;)
    }
    const fnToBind = this
    return function fnBound(...args2){
        // 如果是通过 new 调用的
        if(this instanceof fnBound){
            return new fnToBind(...args1,...args2)
        } else {
            return fnToBind.apply(thisArg,[...args1,...args2])
        }
    }
}
ログイン後にコピー

实现要点

1.bind 实现内部 this 绑定,需要借助于 apply,这里假设我们可以直接使用 apply 方法

2.先看比较简单的 ES6 版本:

1). 参数获取:因为 ES6 可以使用剩余参数,所以很容易就可以获取执行原函数所需要的参数,而且也可以用展开运算符轻松合并数组。

2). 调用方式:前面说过,如果返回的新函数 fnBound 是通过 new 调用的,那么其内部的 this 会是 fnBound 构造函数的实例,而不是当初我们指定的 thisArg,因此 this instanceof fnBound会返回 true,这种情况下,相当于我们指定的 thisArg 是无效的,new 返回的新函数等价于 new 原来的旧函数,即 new fnBound 等价于 new fnToBind,所以我们返回一个 new fnToBind 即可;反之,如果 fnBound 是普通调用,则通过 apply 完成 thisArg 的绑定,再返回最终结果。从这里可以看出,bind 的 this 绑定,本质上是通过 apply 完成的。

3.再来看比较麻烦一点的 ES3 版本:

1). 参数获取:现在我们用不了剩余参数了,所以只能在函数体内部通过 arguments 获取所有参数。对于 myBind,我们实际上需要的是除开第一个传入的 thisArg 参数之外的剩余所有参数构成的数组,所以这里可以通过 Array.prototype.slice.call 借用数组的 slice 方法(arguments 是类数组,无法直接调用 slice),这里的借用有两个目的:一是除去 arguments 中的第一个参数,二是将除去第一个参数之后的 arguments 转化为数组(slice 本身的返回值就是一个数组,这也是类数组转化为数组的一种常用方法)。同样地,返回的新函数 fnBound 后面调用的时候也可能传入参数,再次借用 slice 将 arguments 转化为数组

2). 调用方式:同样,这里也要判断 fnBound 是 new 调用还是普通调用。在 ES6 版本的实现中,如果是 new 调用 fnBound,那么直接返回 new fnToBind(),这实际上是最简单也最容易理解的方式,我们在访问实例属性的时候,天然就是按照 实例 => 实例.__proto__ = fnToBind.prototype 这样的原型链来寻找的,可以确保实例成功访问其构造函数 fnToBInd 的原型上面的属性;但在 ES3 的实现中(或者在网上部分 bind 方法的实现中),我们的做法是返回一个 fnToBind.apply(this),实际上相当于返回一个 undefined 的函数执行结果,根据 new 的原理,我们没有在构造函数中自定义一个返回对象,因此 new 的结果就是返回实例本身,这点是不受影响的。这个返回语句的问题在于,它的作用仅仅只是确保 fnToBind 中的 this 指向 new fnBound 之后返回的实例,而并没有确保这个实例可以访问 fnToBind 的原型上面的属性。实际上,它确实不能访问,因为它的构造函数是 fnBound 而不是 fnToBind,所以我们要想办法在 fnBound 和 fnToBind 之间建立一个原型链关系。这里有几种我们可能会使用的方法:

 // 这里的 this 指的是 fnToBind
 fnBound.prototype = this.prototype
ログイン後にコピー

这样只是拷贝了原型引用,如果修改 fnBound.prototype,则会影响到 fnToBind.prototype,所以不能用这种方法

// this 指的是 fnToBind
fnBound.prototype = Object.create(this.prototype)
ログイン後にコピー

通过 Object.create 可以创建一个 __proto__ 指向 this.prototype 的实例对象,之后再让 fnBound.prototype 指向这个对象,则可以在 fnToBind 和 fnBound 之间建立原型关系。但由于 Object.create 是 ES6 的方法,所以无法在我们的 ES3 代码中使用。

// this 指的是 fnToBind
const Fn = function(){}
Fn.prototype = this.prototype
fnBound.prototype = new Fn()
ログイン後にコピー

这是上面代码采用的方法:通过空构造函数 Fn 在 fnToBind 和 fnBound 之间建立了一个联系。如果要通过实例去访问 fnToBind 的原型上面的属性,可以沿着如下原型链查找:

实例 => 实例.__proto__ = fnBound.prototype = new Fn() => new Fn().__proto__ = Fn.prototype = fnToBind.prototype

更多编程相关知识,请访问:编程教学!!

以上がこの記事では、call、apply、bind メソッドの実装について詳しく説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 Dec 17, 2023 pm 02:54 PM

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 はじめに: 技術の継続的な発展により、音声認識技術は人工知能の分野の重要な部分になりました。 WebSocket と JavaScript をベースとしたオンライン音声認識システムは、低遅延、リアルタイム、クロスプラットフォームという特徴があり、広く使用されるソリューションとなっています。この記事では、WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法を紹介します。

WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー Dec 17, 2023 pm 05:30 PM

WebSocketとJavaScript:リアルタイム監視システムを実現するためのキーテクノロジー はじめに: インターネット技術の急速な発展に伴い、リアルタイム監視システムは様々な分野で広く利用されています。リアルタイム監視を実現するための重要なテクノロジーの 1 つは、WebSocket と JavaScript の組み合わせです。この記事では、リアルタイム監視システムにおける WebSocket と JavaScript のアプリケーションを紹介し、コード例を示し、その実装原理を詳しく説明します。 1.WebSocketテクノロジー

WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 Dec 17, 2023 am 09:39 AM

WebSocket と JavaScript を使用してオンライン予約システムを実装する方法 今日のデジタル時代では、ますます多くの企業やサービスがオンライン予約機能を提供する必要があります。効率的かつリアルタイムのオンライン予約システムを実装することが重要です。この記事では、WebSocket と JavaScript を使用してオンライン予約システムを実装する方法と、具体的なコード例を紹介します。 1. WebSocket とは何ですか? WebSocket は、単一の TCP 接続における全二重方式です。

JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 Dec 17, 2023 pm 12:09 PM

JavaScript と WebSocket を使用してリアルタイム オンライン注文システムを実装する方法の紹介: インターネットの普及とテクノロジーの進歩に伴い、ますます多くのレストランがオンライン注文サービスを提供し始めています。リアルタイムのオンライン注文システムを実装するには、JavaScript と WebSocket テクノロジを使用できます。 WebSocket は、TCP プロトコルをベースとした全二重通信プロトコルで、クライアントとサーバー間のリアルタイム双方向通信を実現します。リアルタイムオンラインオーダーシステムにおいて、ユーザーが料理を選択して注文するとき

簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 Jan 05, 2024 pm 06:08 PM

JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 Dec 17, 2023 pm 05:13 PM

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 はじめに: 今日、天気予報の精度は日常生活と意思決定にとって非常に重要です。テクノロジーの発展に伴い、リアルタイムで気象データを取得することで、より正確で信頼性の高い天気予報を提供できるようになりました。この記事では、JavaScript と WebSocket テクノロジを使用して効率的なリアルタイム天気予報システムを構築する方法を学びます。この記事では、具体的なコード例を通じて実装プロセスを説明します。私たちは

JavaScript で HTTP ステータス コードを簡単に取得する方法 JavaScript で HTTP ステータス コードを簡単に取得する方法 Jan 05, 2024 pm 01:37 PM

JavaScript で HTTP ステータス コードを取得する方法の紹介: フロントエンド開発では、バックエンド インターフェイスとの対話を処理する必要があることが多く、HTTP ステータス コードはその非常に重要な部分です。 HTTP ステータス コードを理解して取得すると、インターフェイスから返されたデータをより適切に処理できるようになります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法と、具体的なコード例を紹介します。 1. HTTP ステータス コードとは何ですか? HTTP ステータス コードとは、ブラウザがサーバーへのリクエストを開始したときに、サービスが

JavaScriptでinsertBeforeを使用する方法 JavaScriptでinsertBeforeを使用する方法 Nov 24, 2023 am 11:56 AM

使用法: JavaScript では、insertBefore() メソッドを使用して、DOM ツリーに新しいノードを挿入します。このメソッドには、挿入される新しいノードと参照ノード (つまり、新しいノードが挿入されるノード) の 2 つのパラメータが必要です。

See all articles