> 웹 프론트엔드 > JS 튜토리얼 > 이 기사에서는 호출, 적용 및 바인드 메소드 구현에 대한 심층적인 이해를 제공합니다.

이 기사에서는 호출, 적용 및 바인드 메소드 구현에 대한 심층적인 이해를 제공합니다.

青灯夜游
풀어 주다: 2021-07-12 18:04:30
앞으로
2089명이 탐색했습니다.

이 글에서는 코드 예제를 사용하여 호출, 적용 및 바인딩 구현 방법에 대한 심층 분석을 제공합니다. 이러한 메소드의 구체적인 사용법은 MDN 또는 사이트의 기사에 이미 명확하게 설명되어 있으므로 생략하겠습니다. 여기서 자세히 설명하지 마세요.

이 기사에서는 호출, 적용 및 바인드 메소드 구현에 대한 심층적인 이해를 제공합니다.

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을 통해 함수를 호출할 때 에 전달할 수 있습니다. >call 의 thisArg는 함수에서 이를 지정합니다. 그리고 이는 우리의 주요 목표인 thisArg를 통해 함수가 호출되는 한 달성될 수 있습니다. call 调用函数的时候,可以通过传给 call 的 thisArg 指定函数中的 this。而只要使得函数是通过 thisArg 调用的,就能实现这一点,这就是我们的主要目标。

实现要点

  • 最终是通过函数去调用 myCall 的,所以 myCallcall 一样挂载在函数原型上。同时,也正因为是通过函数去调用 myCall 的,所以在 myCall 内部我们可以通过 this 拿到 myCall的调用者,也就是实际执行的那个函数。

  • 按理说,myCall 是挂载在函数原型上,当我们通过一个非函数去调用 myCall 的时候,肯定会抛出错误,那么为什么还要在 myCall 中检查调用者的类型,并自定义一个错误呢?这是因为,当一个调用者 obj = {} 是一个对象,但是继承自 Function 的时候(obj.__proto__ = Function.prototype),它作为一个非函数实际上也是可以调用 myCall 方法的,这时候如果不进行类型检查以确保它是个函数,那么后面直接将它当作函数调用的时候,就会抛出错误了

  • 传给 call 的 thisArg 如果是 null 或者 undefined,那么 thisArg 实际上会指向全局对象;如果 thisArg 是一个基本类型,那么可以使用 Object() 做一个装箱操作,将其转化为一个对象 —— 主要是为了确保后续可以以方法调用的方式去执行函数。那么可不可以写成 thisArg = thisArg ? Object(thisArg) : globalThis 呢?其实是不可以的,如果 thisArg 是布尔值 false,那么会导致 thisArg 最终等于 globalThis,但实际上它应该等于 Boolean {false}

  • 前面说过,可以在 myCall 里通过 this 拿到实际执行的那个函数,所以 thisArg.fn = this 相当于将这个函数作为 thisArg 的一个方法,后面我们就可以通过 thisArg 对象去调用这个函数了。

  • thisArg.fn = this 相当于是给 thisArg 增加了一个 fn 属性,所以返回执行结果之前要 delete 这个属性。此外,为了避免覆盖 thisArg 上可能存在的同名属性 fn,这里也可以使用 const fn = Symbol(&#39;fn&#39;) 构造一个唯一属性,然后 thisArg[fn] = this

  • ES3 版本和 ES6 版本主要的区别在于参数的传递以及函数的执行上:

    • ES6 因为引入了剩余参数,所以不管实际执行函数的时候传入了多少个参数,都可以通过 args 数组拿到这些参数,同时因为引入了展开运算符,所以可以展开 args 参数数组,把参数一个个传递给函数执行

    • 但在 ES3 中没有剩余参数这个东西,所以在定义 myCall 的时候只接收一个 thisArg 参数,然后在函数体中通过 arguments 类数组拿到所有参数。我们需要的是 arguments 中除第一个元素(thisArg)之外的所有元素,怎么做呢?如果是 ES6,直接[...arguments].slice(1)

    • 구현 포인트

      • 결국 myCall은 함수를 통해 호출되므로 myCall</code > <code>call과 같은 함수 프로토타입에 탑재됩니다. 동시에 myCall이 함수를 통해 호출되기 때문에 myCall 내부에서 이를 통해 myCall의 호출자를 얻을 수 있습니다. 실제로 실행되는 함수입니다.

      myCall이 함수 프로토타입에 탑재되어 있는 것은 당연합니다. 함수가 아닌 것을 통해 myCall을 호출하면 오류가 발생합니다. ? 또한 myCall에서 호출자의 유형을 확인하고 사용자 정의 오류를 정의하시겠습니까? 이는 호출자 obj = {}가 객체이지만 Function(obj.__proto__ = Function.prototype</code >)에서 상속받는 경우, 함수가 아닌 경우 실제로 <code>myCall 메서드를 호출할 수 있습니다. 이때 함수인지 확인하기 위한 유형 검사가 수행되지 않으면 나중에 함수로 직접 호출될 때 오류가 발생합니다

      🎜call에 전달된 thisArg가 null이거나 정의되지 않은 경우 thisArg는 실제로 전역 객체를 가리킵니다. thisArg가 기본 유형이면 Object는 Used()는 박싱 작업을 수행하고 이를 개체로 변환합니다. 이는 주로 나중에 메서드 호출을 통해 함수가 실행될 수 있도록 보장하기 위한 것입니다. 그러면 thisArg = thisArg ? Object(thisArg) : globalThis 로 쓸 수 있나요? 실제로는 불가능합니다. thisArg가 부울 값 false인 경우 thisArg는 결국 globalThis와 같아지지만 실제로는 Boolean {false}와 같아야 합니다. 🎜🎜🎜🎜앞서 언급한 것처럼 myCall에서 this를 통해 실제로 실행되는 함수를 얻을 수 있으므로 thisArg.fn = this는 이 함수를 thisArg A 메소드로 사용하는 것과 같습니다. , 나중에 thisArg 개체를 통해 이 함수를 호출할 수 있습니다. 🎜🎜🎜🎜thisArg.fn = this는 thisArg에 fn 속성을 추가하는 것과 동일하므로 실행 결과를 반환하기 전에 이 속성을 삭제해야 합니다. 또한 thisArg에 존재할 수 있는 동일한 이름으로 fn 속성을 덮어쓰는 것을 방지하려면 const fn = Symbol('fn')을 사용하여 고유한 속성을 구성한 다음 < 코드>thisArg[fn] = this< /code>. 🎜🎜🎜🎜ES3 버전과 ES6 버전의 주요 차이점은 매개변수 전달과 함수 실행입니다. 🎜
        🎜🎜ES6에서는 나머지 매개변수를 소개합니다. 따라서 실제 함수 실행 시 전달되는 매개변수 수에 관계없이 args 배열을 통해 이러한 매개변수를 얻을 수 있습니다. 동시에 확장 연산자의 도입으로 인해 args 매개변수 배열을 확장하여 얻을 수 있습니다. 매개변수는 하나씩 함수에 전달되어 실행🎜🎜🎜🎜하지만 ES3에는 매개변수가 남지 않아서 myCall을 정의할 때 thisArg 매개변수 하나만 받고, 함수 본문의 인수 클래스 배열을 통해 모든 매개변수를 가져옵니다. 우리에게 필요한 것은 첫 번째 요소(thisArg)를 제외한 인수의 모든 요소입니다. ES6이라면 [...arguments].slice(1)만으로도 충분하지만 이는 ES3이므로 인덱스 1부터 시작하는 인수만 순회한 다음 이를 인수 배열. 여기에 푸시된 매개변수는 문자열 형식이라는 점에 유의해야 합니다. 이는 주로 나중에 eval을 통해 함수를 실행할 때 매개변수를 함수에 하나씩 전달하기 위한 것입니다. 🎜🎜🎜🎜함수를 실행하려면 왜 eval을 전달해야 하나요? 함수가 실제로 수신해야 하는 매개변수 수를 모르고 확장 연산자를 사용할 수 없기 때문에 실행 가능한 문자열 표현식만 구성하고 함수의 모든 매개변수를 명시적으로 전달할 수 있습니다. 🎜🎜🎜🎜🎜🎜🎜apply의 손글씨 구현은 호출과 매우 유사하므로 구현도 매우 유사합니다. 주목해야 할 차이점은 호출이 thisArg 매개변수를 승인한 후에는 여러 매개변수를 수신할 수도 있고(즉, 매개변수 목록을 승인함) 적용이 thisArg 매개변수를 수신한 후에는 일반적으로 두 번째 매개변수가 배열 또는 배열이라는 점입니다. -유사 객체: 🎜
        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

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

        위 내용은 이 기사에서는 호출, 적용 및 바인드 메소드 구현에 대한 심층적인 이해를 제공합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    관련 라벨:
    원천:segmentfault.com
    본 웹사이트의 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
    인기 튜토리얼
    더>
    최신 다운로드
    더>
    웹 효과
    웹사이트 소스 코드
    웹사이트 자료
    프론트엔드 템플릿