摘要
JS中的函数调用,通常是使用函数名加上参数即可实现函数的调用,但在有些情况下,也会用到call、apply、bind实现函数的调用。其中call和apply的调用方法类似,只是call参数与普通函数调用类似,而apply则是使用数组能数进行调用;bind则有点类似将call方法的部分动作,下面通过实例对三个方法进行说明,如有疑问错漏欢迎交流。
正文
JS广泛应用于现代计算机多种环境下,它不仅在Web前端中应用广泛、同时也在后端服务和桌面应用中应用广泛,如后端Nodejs为主的应用服务,可实现各种应用服务,包括业务服务、数据存取等,而桌面应用如现在开发环境编辑器应用最为广泛的visual studio code(简称vscode),就是使用JS开发的前端工具。
函数作为JS的主要构成部分,举例如下,求2 个数的和:
let add = (a, b) => { return a + b; } let ret = add(3, 4) console.log(ret)
假设公司有2个人,张三和李四,年底要给加薪,我们已经做好了加薪的函数及两个人的对象实例:
let zhangsan = { name: "张三", salary: 18000 } let lisi = { name: "李四", salary: 12000 } //加薪函数 function addSalary(percent) { return this.salary * (1 + percent) } //加薪前数据 console.log("加薪前数据:", zhangsan, lisi)
call
如何给两个人加薪:
通过call(obj, args)方法来调用加薪函数,其中:
Obj为call绑定的对象,args则是调用函数的参数,如下代码所示
//假设每年默认加薪10% zhangsan.salary = addSalary.call(zhangsan, 0.10); lisi.salary = addSalary.call(lisi, 0.10); //加薪后数据 console.log("加薪后数据:", zhangsan, lisi)
apply
使用apply调用加薪函数,apply(obj, [args]),其中:
Obj为call绑定的对象,[args]则是调用函数的参数,数组格式,如下代码所示
//假设每年默认加薪10% zhangsan.salary = addSalary.apply(zhangsan, [0.10]); lisi.salary = addSalary.apply(lisi, [0.10]); //加薪后数据 console.log("加薪后数据:", zhangsan, lisi)
bind
bind则是先绑定到某个对象上,然后再进行调用
//假设每年默认加薪10% bind let zhangsanAddsalary = addSalary.bind(zhangsan); let lisiAddsalary = addSalary.bind(lisi); zhangsan.salary = zhangsanAddsalary(0.10); lisi.salary = lisiAddsalary(0.10); //加薪后数据 console.log("加薪后数据:", zhangsan, lisi)
vscode中demo代码完整截图:
小结与思考
以上举例中仅用了一个参数的函数,总结三种调用方式对比。
call : Function.call(obj, args); apply: Function.apply(obj, [args]); bind : newFun = Function.bind(obj);//先绑定 newFun(args);// 再调用
通过分析其应用,对比似乎apply没什么用,还要加个数组括号,为什么要多此一举呢?下面我们再举多一个真实应用的场景,供进一步对比参考,求输入的数字中最大值,可以调用Math.max函数,但当无法确认有多少个输入数字时,这时apply的“动态”参数调用就起作用了,如下所示代码中:
const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question(`请输入2个以上地数字(以空格区分):`, (answer) => { let nums = answer.split(' ') let maxNum = Math.max.apply(null, nums); console.log(`${nums}中的最大值是${maxNum}`); rl.close(); });
vscode中运行截图
call深度应用
如果前面介绍已经理解了,那么实际开发中也已经基本够用了,但有没有更具体的一些应用呢,实际上也可以使用通过call的调用父类构造实现“继承”功能,子类自动具有父类的相关属性,这个叫“深度”应用有点牵强,但做为抛砖引玉来讲吧,举例如下:
// Fruit 水果 function Fruit(name, price) { this.name = name; this.price = price; } // 苹果 function Apple(name, price, 产地) { // 继承自水果,则默认有name 和 price属性了 Fruit.call(this, name, price); this.产地 = 产地 } // 实例对象 let 陕西红富士 = new Apple("陕西红富士", 3.6, "陕西") let 烟台红富士 = new Apple("烟台红富士", 4.8, "烟台") console.log(陕西红富士) console.log(烟台红富士) //输出结果: Apple { name: '陕西红富士', price: 3.6, '产地': '陕西' } Apple { name: '烟台红富士', price: 4.8, '产地': '烟台' }
apply深度应用
Function.apply(obj, [args])
继承使用apply实现
完整代码如下
// Fruit 水果 function Fruit(name, price) { this.name = name; this.price = price; } // 苹果,此处使用了“汉字”命名变量,这个是没有问题的,也是为了演示方便,一般情况我们也不建议使用汉字变量 function Apple(name, price, 产地) { // 继承自水果,则默认有name 和 price属性了 // Fruit.apply(this, [name, price]); // 以下方法有风险,要求顺序要与“父类”构造一致; arguments取的是当前函数所有参数,本函数中的值相当于[name, price, 产地] Fruit.apply(this, arguments); this.产地 = 产地 } // 实例对象 let 陕西红富士 = new Apple("陕西红富士", 3.6, "陕西") let 烟台红富士 = new Apple("烟台红富士", 4.8, "烟台") console.log(陕西红富士) console.log(烟台红富士) //输出结果: Apple { name: '陕西红富士', price: 3.6, '产地': '陕西' } Apple { name: '烟台红富士', price: 4.8, '产地': '烟台' }
[args]的变种替代
可使用一个拥有length,及小于length对应的索引对象来替代,如前面所述的加薪算法调用,可以使用以下参数进行替代:
//假设每年默认加薪10% apply zhangsan.salary = addSalary.apply(zhangsan, { length: 1, 0: 0.10 }); lisi.salary = addSalary.apply(lisi, { length: 1, 0: 0.10 });
如何将一个数组添加到另一个数组
直接上代码
// 传统方式 let arr1 = [1, 2, 3] let arr2 = ['一', '二', '三'] arr2.forEach((val, idx, arr) => { arr1.push(val); }) console.log(arr1) // apply 方法实现 let arr3 = [1, 2, 3] let arr4 = ['一', '二', '三'] arr3.push.apply(arr3, arr2); //这个就是利用了apply数组参数的特性 console.log(arr3) //输出结果相同 [ 1, 2, 3, '一', '二', '三' ] [ 1, 2, 3, '一', '二', '三' ]
以上两个输出结果都是一样的,但apply方法更加简洁清晰,传统方式使用循环逐个元素增加则有点臃肿。
总结说明
以上所有相关内容,在实战中都可尝试思考,找到发挥各自己功效的应用场景,但一般情况下来讲,除去一些必要地高级应用或特定场景应用外,还是要记住代码的本质,是给人看的,而不是给机器看的,所以代码应该方便理解,如果太过高深或不能很容易的方便人们阅读并理解其中的含义,那么效果也许会大打折扣。