【笔记】js-call()的实现分析

154 阅读3分钟
    // 1. call的使用
        function person1() {
            console.log(this.name)
        }
        var egg1 = {
            name: 'call1的实现'
        }
        person1.call(egg1)  // -- call的实现
     // 2. call相当于
        var egg2 = {
            name: 'call2的实现',
            person2: function() {
                console.log(this.name)  // this 指向调用它的对象
            }
        }
        egg2.person2() // -- call的实现
 // 3. 给函数添加原型对象
        function person3(a,b,c) {
            console.log(this.name)
            console.log(a,b,c)
        }
        var egg3= {
            name: 'call3的实现'
        }

            // 3.1 this 隐式绑定
            // 隐式绑定
            // (1) 如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上
            // (2) 如果函数调用前存在多个对象,this指向距离调用自己最近的对象
            // 扩展函数,使函数拥有一个名为 newCall 的原型对象
            Function.prototype.newCall1 = function( obj ) {
                console.log( this )
            }
            // 这个时候 newCall 里的this因为隐式绑定指向的是person 函数
            person3.newCall1( egg3 )  // --ƒ person() { console.log(this.name) }

            // 3.2 硬绑定this
            Function.prototype.newCall2 = function( obj ) {
                obj.p = this  // 给 obj 形参添加一个方法(this指向的函数)
                obj.p();      // 调用这个函数 这个时候 this 指向的就是形参 obj
                delete obj.p  // 因为添加方法等于改写了函数的属性,所以要把这个方法删除掉
            }
            person3.newCall2( egg3 )  // --call的实现

            // 3.3 处理第一个以外的参数
            Function.prototype.newCall3 = function( obj ) {
                obj.p = this  
                var newArguments = []
                // 这个时候参数位置不对称,所以需要将除了第一个参数以外的参数另外保存起来
                // 让参数回归到正常的序号
                for ( var i=1; i < arguments.length; i++ ) {
                    newArguments.push( arguments[i] )
                }
                console.log(newArguments)
                // obj.p(); // -- undefined undefined undefined  内部执行的是不带参数的函数
                obj.p(newArguments);  // -- ['参数1', '参数2', '参数3'] undefined undefined  这是因为数组本身就是一个参数   
                delete obj.p 
            }
            person3.newCall3( egg3, '参数1', '参数2', '参数3', )  // --call的实现 

            // 3.4 参数传递的问题
            Function.prototype.newCall4 = function( obj ) {
                obj.p = this  
                var newArguments1 = []
                var newArguments2 = []
                for ( var i=1; i < arguments.length; i++ ) {
                    newArguments1.push( arguments[i] )  // -- newArguments1=['参数1', '参数2', '参数3']
                    newArguments2.push( "arguments["+ i +"]" ) // --newArguments1=['arguments[1]', 'arguments[2]', 'arguments[3]']
                }
                // eval 函数会执行括号里的语句
                // 字符串加法会隐式调用toString()方法,
                // 所以"obj.p("+newArguments1+')' = "obj.p(参数1, 参数2, 参数3)' 
                // eval("obj.p("+newArguments1+')')   --参数1 is not defined
                // 所以"obj.p("+newArguments2+')' = "obj.p(arguments[1], arguments[2], arguments[3])'
                eval("obj.p(" + newArguments2 + ')')  // --参数1 参数2 参数3
                delete obj.p 
            }
            person3.newCall4( egg3, '参数1', '参数2', '参数3', )  // call的实现 --参数1 参数2 参数3

            // 3.5当第一个参数为null的时候
            Function.prototype.newCall5 = function( obj ) {
                var obj = obj || window  // 没有这个运算的话 代码会报错
                obj.p = this  
                var newArguments2 = []
                for ( var i=1; i < arguments.length; i++ ) {
                    newArguments2.push( "arguments["+ i +"]" )
                }
                eval("obj.p(" + newArguments2 + ')') 
                delete obj.p 
            }
            person3.newCall5( null, '参数1', '参数2', '参数3', ) // --参数1 参数2 参数3

     //  4. 函数返回值
        function person4(a,b,c) {
             return {
                name:this.name,
                a: a, b: b, c: c
            }
        }
        var egg4 = {
            name: 'call的实现',
            p:function(){
                console.log('zijide p')
            }
        }
        
        Function.prototype.newCall6 = function( obj ) {
            var obj = obj || window
            obj.p = this  
             var newArguments2 = []
            for ( var i=1; i < arguments.length; i++ ) {
                newArguments2.push( "arguments["+ i +"]" )
            }
            eval("obj.p(" + newArguments2 + ')') 
            delete obj.p 
        }
        var bibi = person4.newCall6( egg4, '参数1', '参数2', '参数3', ) 
        console.log(bibi) // --undefined

        // 最终版
        //  只需要将eval() 执行的结果保存到一个变量中,并返回这个变量就可以了
        Function.prototype.newCall7 = function( obj ) {
            var obj = obj || window
            obj.p = this  
            var newArguments2 = []
            for ( var i=1; i < arguments.length; i++ ) {
                //  newArguments2.push( "arguments["+ i +"]" )
                 newArguments2.push( arguments[i] )
            }
            // var result =  eval("obj.p(" + newArguments2 + ')') 
            var result = obj.p(...newArguments2)
            delete obj.p 
            return result
        }
        var bibi2 = person4.newCall7( egg4, '参数1', '参数2', '参数3', ) 
        console.log(bibi2) // --{name: 'call的实现', a: '参数1', b: '参数2', c: '参数3'}

注意: 这样的写法会到一个问题,如果 egg 里有一个名为 p 的属性,这样执行会将 egg 里的属性 p 删掉

再附上一个朋友的写法,对比之下,我傻的很-_-!

f5343ec814606bf1d47f81a1bbacd91.png