精通JavaScript系列,详解知识体系,等你开吃

简介: 精通JavaScript系列,详解知识体系,等你开吃

摘要

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代码完整截图:

640.png

结与思考

以上举例中仅用了一个参数的函数,总结三种调用方式对比。

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中运行截图

640.png

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方法更加简洁清晰,传统方式使用循环逐个元素增加则有点臃肿。

总结说明

以上所有相关内容,在实战中都可尝试思考,找到发挥各自己功效的应用场景,但一般情况下来讲,除去一些必要地高级应用或特定场景应用外,还是要记住代码的本质,是给人看的,而不是给机器看的,所以代码应该方便理解,如果太过高深或不能很容易的方便人们阅读并理解其中的含义,那么效果也许会大打折扣。

相关文章
|
7月前
|
自然语言处理 JavaScript 前端开发
|
3月前
|
前端开发 JavaScript UED
【JavaScript】面试手撕防抖
防抖: 首先它是常见的性能优化技术,主要用于处理频繁触发的浏览器事件,如窗口大小变化、滚动事件、输入框内容改变等。在用户连续快速地触发同一事件时,防抖机制会确保相关回调函数在一个时间间隔内只会被执行一次。
37 0
|
9月前
|
缓存 前端开发 网络协议
必知必会的JavaScript前端面试题篇(一),不看后悔!
必知必会的JavaScript前端面试题篇(一),不看后悔!
|
4月前
|
前端开发 JavaScript
能让你早点下班的36个JavaScript实用函数!
能让你早点下班的36个JavaScript实用函数!
|
4月前
|
JSON 前端开发 JavaScript
【面试题】 「中高级前端面试」JavaScript手写代码无敌秘籍
【面试题】 「中高级前端面试」JavaScript手写代码无敌秘籍
|
9月前
|
存储 前端开发 JavaScript
必知必会的JavaScript前端面试题篇(二),不看后悔!
必知必会的JavaScript前端面试题篇(二),不看后悔!
|
JavaScript 前端开发 Java
手撕前端面试题【JavaScript】
手撕前端面试题【JavaScript】
116 0
手撕前端面试题【JavaScript】
|
JavaScript 前端开发
手撕前端面试题【javascript】
手撕前端面试题【javascript】
135 0
手撕前端面试题【javascript】
|
JavaScript 前端开发
手撕JavaScript面试题
手撕JavaScript面试题
111 0
手撕JavaScript面试题
|
自然语言处理 JavaScript 算法
学了这么久,还真就不太懂JavaScript,不信?
内部槽对应于与对象关联的内部状态,并由各种 ECMAScript 规范算法使用。内部槽不是对象属性,也不是继承的。根据特定的内部槽规范,这种状态可能由任何 ECMAScript 语言类型的值或特定的 ECMAScript 规范类型值组成。除非另有明确指定,否则内部槽是作为创建对象过程的一部分分配的,不能动态添加到对象中。
118 0
学了这么久,还真就不太懂JavaScript,不信?