理解bind与call,apply区别及其在实际项目中部分场景的运用

这次总结一下这周遇到的一些坑和总结这周工作的所得。

一、关于bind,call,apply的运用

我本来并不想讲这三者的原理的,但是既然要讲实际的运用就顺带讲一下关键性的一点吧。
首先讲讲三者的共同点
改变this指向
实现继承关系
…….
下面上一段代码

    let obj = {
      dividend: 2
    }
    function isOdd (num) {
      console.log(this.dividend) // 1、undefined 2、2
      let result = num%this.dividend === 0 ? '偶数':'奇数';
      return (num + '是' + result);
    }
    isOdd(8); // 8是奇数
    isOdd.call(obj, 8); // 8是偶数

大家可以很明显的看出函数isOdd原本是指向window的,这里内部的this.dividend的值为undefined,所以第一次直接执行isOdd函数将会产生 8 是奇数的情况,因为num/this.dividend是NaN不为0,那么结果即为奇数;

但是在第二次利用了isOdd.call后,isOdd的指向不再是window了,而是指向了call的第一个参数,此时为obj,而函数执行内部的this.dividend自然就变成了2,最终完成函数的执行,输出结果 8 位偶数。

而apply与call的用法是一致的,只是传参形式上call我们是这样的xxx.call(obj, arg1, arg2,...),而apply的参数必须要以数组的形式传递xxx.apply(obj, ['arg1', 'arg2', ...])

bind是以call的形式传参的,但是bind与call、apply最大的区别就是bind绑定this指向并传参后仍然为一个函数,并没有去调用,而call与apply是直接调用函数。下面我会以一个小例子的形式说明一下我在实际项目中用到bind的一种小情况;

二、日常项目中bind及call的运用例子

在正常的项目开发中,我们时常会使用各类开源的组件与框架,那么无法避免的会存在一些个性化的需求是原本的组件与框架事先没做到的,这时我们就可以去在使用组件提供的方法时利用bind、call、apply传递我们需要的参数并且在对应事件中做数据结构的处理;

  • 场景一:某些开源组件提供的函数内会有依赖注入的情况,我们直接将处理好的数据结构抛入依赖注入的函数中,组件便会帮我们完成dom结构的建立,如elementui的autoComplete组件,内部有一个cb的依赖,我们直接将数组传入cb中便会在input标签focus时出现对应的建议值

    先上有依赖注入的demo来方便大家理解bind在这个场景中的使用

    function add (arr) {
      let count = 0;
      arr.forEach( (val) => count += val );
      return count;
    }
    
    function even (num) {
      let result = num % 2 === 0 ? true:false;
      return result === true ? '偶数':'奇数';
    }
    
    function countIsLimit (arr, countFn ,limitNum) {
      let count = countFn(arr);
      // 这里你当然可以不用arguments,而选择直接在参数上多写一个参数名,这里我这样用只是为了告诉大家这种场景而已,新增参数名当然更直观。
      let isEven = arguments[3](count); 
      let result = count <= limitNum ? '小于或等于限定值': '大于或等于限定值';
      return count + result + limitNum + '并且是' + isEven;
    }
    let testArr = [1, 2, 3, 4];
    
    let bindResult = countIsLimit.bind(null, testArr, add, 10, even)
    bindResult(); // 10小于或等于限定值10并且是偶数

    最后的结果显而易见了,这个demo模仿了上述我所说的场景,首先我没有改变countIsLimit这个函数的this指向,因为在此场景下我们不需要改变,那么我们便设为null让他继续指向window,在countIsLimit的最后一个参数并不是我后面bind的even,因为原本的函数中就没这个参数,这个参数是后来我加入的一个自定义依赖,而原本函数中便存在一个countFn的依赖来进行最后的数据处理,但是现在我有一个需求就是我不止需要知道测试的数组和与限定值的关系,还要知道这个和的奇偶性,这时我们需要在函数内拿到数组的和再进行处理,即再数组内执行add函数后进行数据处理,这时这个需求就和我们场景中一致了,我们需要传入新的参数对函数内部得到的数据进行处理并输出,而这里我传入了一个新的依赖函数even来进行处理,并且将处理结果添加至最后的返回值上,这也是函数式编程的一个概念,所有的需求都有对应函数处理,并且通过依赖注入的形式互相处理最终数据,这种需要依赖注入的场景只有bind能实现,因为我们需要新传入依赖函数后,在函数内部进行处理后才执行bind的函数,如果此时用call与apply的话当然也可以,但是显然没有这么直观了,尤其是在某些我们并不清楚业务逻辑的情况下,我们是不知道业务最终返回的数据形式的,此时我们就应该只传入依赖函数,让熟悉的人去编写业务逻辑处理了,这种情况显然就不能用call与apply了!

  • 场景2:用别人已经封装好的事件与函数,只是需要我们传入新值去判断,实际上这种情况与场景一类似,只是这时我们扮演的是熟悉业务逻辑的人,而不是新增依赖函数的人,这种场景就简单太多了;上面讲bind、call、apply的运用所用的demo就是例子~

三、利用JSON API解决部分场景下的深度克隆问题

在处理某些全局对象的数据时,我们只是在我们的模块去对全局变量的数据结构进行处理后输出,此时我们一般会深度克隆某一全局对象然后再进行处理,以防因为对象与数组为地址引用而改变了全局对象,当然在ES6有新的解决方式,但毕竟很多人还是没用ES6的~

尤其在使用vuex、redux等插件来处理全局数据的情况尤其适用。

关键点就是JSON.stringifyJSON.parse的使用,当我们将一个对象利用stringify转化为json字符串在利用parse转回来时,已经等于遍历字符串 new 了一个新对象了,此时就不存在直接赋值引用的情况了,下面上demo。

let object = {
  name: 'YOLO',
  gender: 'male',
  hobby: ['movie', 'computer game']
}
let newObject = object;
newObject.name = 'jack';
console.log(newObject.name); // 'jack'
console.log(object.name); // 'jack'

显然这里是因为object是一个对象,而对象的赋值是地址引用,而深度克隆毫无疑问就是为了解决这种问题而存在的,但是这里我们使用一个新 方法

let jsonObject = JSON.parse(JSON.stringify(object))
jsonObject.name = 'jack';
console.log(object.name); // 'YOLO'
console.log(jsonObject.name); // 'jack'

原理的话我上面已经讲过了,大家都明白JSON API的使用前提是可以将键值转化为字符串,而使用这种方法如果键值是一个匿名函数的话这种方法就会失效,所以我最开始也声明过这属于奇技淫巧,只适用于某些场景~

猜你喜欢

转载自blog.csdn.net/yolo0927/article/details/68952652