IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    javascript原生实现bind方法实例分析

    谁谁发表于 2016-11-01 12:17:27
    love 0

    bind

    由于javascript中作用域是由其运行时候所处的环境决定的,所以往往函数定义和实际运行的时候所处环境不一样,那么作用域也会发生相应的变化。
    例如下面这个情况:

    var id = 'window';
    //定义一个函数,但是不立即执行
    var test = function(){
        console.log(this.id)
    }
    test() // window
    //把test作为参数传递
    var obj = {
        id:'obj',
        hehe:test
    }
    //此时test函数运行环境发生了改变
    obj.hehe() // 'obj'
    //为了避免这种情况,javascript里面有一个bind方法可以在函数运行之前就绑定其作用域,修改如下
    
    var id = 'window';
    var test = function(){
        console.log(this.id)
    }.bind(window)
    var obj = {
        id:'obj',
        hehe:test
    }
    test() // window
    obj.hehe() // window

    上面介绍了bind方法的一个重要作用就是为一个函数绑定作用域,但是bind方法在低版本浏览器不兼容,这里我们可以手动实现一下。

    先拆分一下关键思路:

    1. 因为bind方法不会立即执行函数,需要返回一个待执行的函数(这里用到闭包,可以返回一个函数)return function(){}

    2. 作用域绑定,这里可以使用apply或者call方法来实现 xx.call(yy)/xx.apply(yy)

    3. 参数传递,由于参数的不确定性,需要用apply传递数组(实例更明了)xx.apply(yy,[...Array...]),如果用call就不太方便了,因为call后面的参数需要一个个列出来

    有了上述的思路,大致的雏形已经明了了,代码应该也很容易实现

    Function.prototype.testBind = function(that){
        var _this = this,
            /*
            *由于参数的不确定性,统一用arguments来处理,这里的arguments只是一个类数组对象,有length属性
            *可以用数组的slice方法转化成标准格式数组,除了作用域对象that以外,
            *后面的所有参数都需要作为数组参数传递
            *Array.prototype.slice.apply(arguments,[1])/Array.prototype.slice.call(arguments,1)
            */
            slice = Array.prototype.slice,
            args = slice.apply(arguments,[1]);
        //返回函数    
        return function(){
            //apply绑定作用域,进行参数传递
            return _this.apply(that,args)
        }    
    }

    测试

    var test = function(a,b){
        console.log('作用域绑定 '+ this.value)
        console.log('testBind参数传递 '+ a.value2)
        console.log('调用参数传递 ' + b)
    }
    var obj = {
        value:'ok'
    }
    var fun_new = test.testBind(obj,{value2:'also ok'})
    
    fun_new ('hello bind')
    // 作用域绑定 ok
    // testBind参数传递 also ok
    // 调用参数传递  undefined

    注意:

    上面已经实现了bind方法的作用域绑定,但是美中不足的是,既然我们返回的是一个函数,调用的时候应该支持传递参数,很显然,上面的 fun_new 调用的时候并不支持传参,只能在 testBind 绑定的时候传递参数,因为我们最终调用的是这个返回函数

    function(){
            return _this.apply(that,args)
        }    
    
    这里面的args在绑定的时候就已经确定了,调用的时候值已经固定,
    我们并没有处理这个function传递的参数。

    我们对其进行改造

    return function(){
            return _this.apply(that,
                args.concat(Array.prototype.slice.apply(arguments,[0]))
            )
        }    

    这里的 Array.prototype.slice.apply(arguments,[0]) 指的是这个返回函数执行的时候传递的一系列参数,所以是从第一个参数开始 [0] ,之前的args = slice.apply(arguments,[1])指的是 testBind方法执行时候传递的参数,所以从第二个开始 [1],两则有本质区别,不能搞混,只有两者合并了之后才是返回函数的完整参数

    所以有如下实现

    Function.prototype.testBind = function(that){
        var _this = this,
            slice = Array.prototype.slice,
            args = slice.apply(arguments,[1]);
        return function(){
            return _this.apply(that,
                        args.concat(Array.prototype.slice.apply(arguments,[0]))
                    )
        }    
    }

    测试

    var test = function(a,b){
        console.log('作用域绑定 '+ this.value)
        console.log('testBind参数传递 '+ a.value2)
        console.log('调用参数传递 ' + b)
    }
    var obj = {
        value:'ok'
    }
    var fun_new = test.testBind(obj,{value2:'also ok'})
    
    fun_new ('hello bind')
    // 作用域绑定 ok
    // testBind参数传递 also ok
    // 调用参数传递  hello bind

    在以上2种传参方式中,bind的优先级高,从 args.concat(Array.prototype.slice.apply(arguments,[0])) 也可以看出来,bind的参数在数组前面。

    未完。。。

    以上是个人见解,不对的地方望指导,谢谢!



沪ICP备19023445号-2号
友情链接