引言
内容速递
看了本文您能了解到的知识!
在本篇文章中,将带你了解什么是call和apply,call和apply的用途、如何手写call
和apply
以及call
和apply
的使用场景。
1、什么是call和apply
call()
和apply()
是JavaScript
中的两个内置方法,用于调用函数并指定函数中的this
值。
两者的区别是:
call()
方法的语法和作用与apply()
方法类似,只有一个区别,就是call()
方法接受的是一个参数列表,而apply()
方法接受的是一个包含多个参数的数组。
1.1、call
MDN给的解释:Function.prototype.call()
**call()
**方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数。
1.2、apply
MDN给的解释:Function.prototype.apply()
apply()
方法调用一个具有给定 this
值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。
需要注意的是,使用
call
和apply
方法改变函数的this
指向后,函数会立即执行,并返回执行结果。
2、call和apply的语法介绍
2.1、call语法
语法:
function.call(thisArg, arg1, arg2, ...)
参数:
-
thisArg:可选的。在
function
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。 -
arg1, arg2, …:指定的参数列表。
返回值:
使用调用者提供的 this
值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined
。
2.1、apply语法
语法:
function.apply(thisArg, [argsArray])
参数:
-
thisArg:在
func
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。 -
argsArray: 可选。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给
func
函数。如果该参数的值为null
或undefined
,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。
返回值:
调用有指定 this
值和参数的函数的结果。
3、call与apply的用法
3.1、call的用法
一个函数继承另外一个函数的属性
代码:
function Guizimo(name, age) {
this.name = name
this.age = age
}
function Zimo(name, age) {
Guizimo.call(this, name, age)
this.sex = 'man'
}
console.log(new Zimo('guizimo', '24'))
// Zimo{name: 'guizimo', age: '24', sex: 'man'}
代码解析:
-
在
Guizimo
中定义了name
和age
两个属性。 -
在
Zimo
中使用call
,让Zimo
继承了Guizimo
的属性 -
new
一个Zimo
,拥有Guizimo
中定义的属性。
3.2、apply的用法
合并两个数组
代码:
const array = ['a', 'b'];
const elements = [0, 1, 2];
array.push.apply(array, elements);
console.log(array);
// ["a", "b", 0, 1, 2]
代码解析:
-
定义了两个数组
array
和elements
。 -
在
array
的push
上使用apply
,将elements
数组传入。 -
array
拥有elements
的元素。
4、手写call与apply
4.1、手写call
思路:
-
处理好传入的
this
和参数。 -
创建一个临时属性接收
当前this
。 -
将参数作为属性置入创建的的临时属性中,并执行得到结果。
-
清理掉刚创建的临时属性。
-
返回结果
普通版
/**
* 自定义实现call
* @returns {*|void}
*/
Function.prototype.myCall = function () {
// 判断this的指向
if (typeof this !== 'function') {
throw new Error('type error')
}
// 处理参数,拿到传入的this与参数
const args = Array.from(arguments)
// 如果传入的this为null时,指向全局的window对象
const newThis = args.shift() || window
// 创建一个临时属性接受this,注意唯一性,可能会覆盖原有的属性
newThis.fn = this
// 将参数作为属性置入
const res = newThis.fn(...args)
// 删除属性,防止污染
delete newThis.fn
// 返回
return res
}
进阶
想了一下这个还是可以改进的。在使用newThis.fn = this
的时候,这个fn
需要保持唯一性,不覆盖外部传入的属性,因此可以使用Symbol
,注意使用Symbol
之后就不再使用newThis.fn
,而是newThis[fn]
进阶版
/**
* 自定义实现call,优化版
* @returns {*}
*/
Function.prototype.myCallPlus = function () {
// 判断this的指向
if (typeof this !== 'function') {
throw new Error('type error')
}
// 处理参数,拿到传入的this与参数
const args = Array.from(arguments)
// 如果传入的this为null时,指向全局的window对象
const newThis = args.shift() || window
// 创建一个唯一的key
let fn = Symbol()
// 接收临时this
newThis[fn] = this
// 将参数作为属性置入
const res = newThis[fn](...args)
// 删除属性,防止污染
delete newThis[fn]
// 返回
return res
}
4.2、手写apply
思路:
和call
几乎一样,区别是在参数的处理
- 处理好传入的
this
和参数。 - 创建一个临时属性接收
当前this
。 - 将参数作为属性置入创建的的临时属性中,并执行得到结果。
- 清理掉刚创建的临时属性。
- 返回结果。
普通版
/**
* 自定义实现apply
* @param thisArg
* @param args
* @returns {*|void}
*/
Function.prototype.myApply = function (thisArg, args) {
// 判断this的指向
if (typeof this !== 'function') {
throw new Error('type error')
}
// 处理外部传递过来的this,如果this为null时,指向全局的window对象
const newThis = thisArg || window
// 创建一个Null对象,用作返回的载体
let res = null
// 创建一个临时属性接受this,注意唯一性,可能会覆盖原有的属性
newThis.fn = this
// 将参数作为属性置入
res = newThis.fn(...args)
// 删除属性,防止污染
delete newThis.fn
// 返回
return res
}
进阶版
/**
* 自定义实现apply,进阶版
* @param thisArg
* @param args
* @returns {*}
*/
Function.prototype.myApplyPlus = function (thisArg, args) {
// 判断this的指向
if (typeof this !== 'function') {
throw new Error('type error')
}
// 处理外部传递过来的this,如果this为null时,指向全局的window对象
const newThis = thisArg || window
// 创建一个唯一的key
const fn = Symbol()
// 接收临时this
newThis[fn] = this
// 传入参数,调用新属性
const res = newThis[fn](...args)
// 删除属性,防止污染
delete newThis[fn]
// 返回
return res
}
5、使用场景
5.1 call的使用场景
1、调用父构造函数
在子构造函数中可以使用call
调用父构造函数,这种方式可以实现继承,这种使用子构造方法之后,都会拥有父构造函数的属性。
// 父构造方法
function Father (name, age) {
this.name = name
this.age = age
}
// 子构造方法
function Child(name, age) {
Father.call(this, name, age)
this.sex = 'man'
}
console.log(new Child('guizimo', '24'))
// Child {name: 'guizimo', age: '24', sex: 'man'}
2、调用匿名函数
创建了一个匿名函数,通过call
将obj
作为属性给到匿名函数使用。
const obj = {
name: 'guizimo',
age: '24'
}; // 此处;不可省略
(function (id) {
this.print = function () {
console.log(`id : ${id} ${this.name} : ${this.age}`);
};
this.print();
}).call(obj, 1);
// id : 1 guizimo : 24
这里我遇到了一个小插曲:
有一个报错:Uncaught TypeError: {(intermediate value)(intermediate value)} is not a function
原因是在匿名函数使用之前,需要加上
;
3、设置指定的上下文this
给某个函数指定上下文this
,这是call
比较通常的用法
const obj = {
name: 'guizimo',
description: 'a good boy'
};
function fn() {
let reply = [this.name, 'is', this.description].join(' ');
console.log(reply);
}
fn.call(obj);
// guizimo is a good boy
4、使用call不携带this参数
在使用call
的时候不给到指定的this
参数,这时this
的值为全局对象
var name = 'guizimo' // 注意这里使用 var 来声明
function fn() {
console.log(`${this.name} is a good boy`)
}
fn.call()
// guizimo is a good boy
注意:
在严格模式下,
this
的值将会是undefined
。在调用call
的时候,会给到报错。
5.2、apply的使用场景
1合并数组
使用apply
可以将push
改造为类似concat
的效果,但是不会创建一个新的数组,而是在原数组上直接合并。
const array = ['a', 'b'];
const elements = [0, 1, 2];
array.push.myApply(array, elements);
console.log(array);
// ["a", "b", 0, 1, 2]
2、兼容max和min的限制
apply
有一个好处就是可以避免循环。
const numbers = [5, 6, 2, 3, 7];
let max = Math.max.apply(null, numbers); // 等同与 Math.max(5, 6, 2, 3, 7)
let min = Math.min.apply(null, numbers); // 等同与 Math.min(5, 6, 2, 3, 7)
看起来使用了apply的收益还是不大,当我们使用Math.max和Math.min是会出现一个问题
JavaScript
引擎参数长度上限: 65536)。
为了避免这个问题,可以将数组切块后循环传入目标方法。
function minOfArray(arr) {
let min = Infinity;
let QUANTUM = 32768;
for (let i = 0, len = arr.length; i < len; i += QUANTUM) {
const submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
min = Math.min(submin, min);
}
return min;
}
let min = minOfArray([5, 6, 2, 3, 7]);
博客说明与致谢
文章所涉及的部分资料来自互联网整理,其中包含自己个人的总结和看法,分享的目的在于共建社区和巩固自己。
引用的资料如有侵权,请联系本人删除!
感谢勤劳的自己,个人博客,GitHub,公众号【归子莫】,小程序【子莫说】
如果你感觉对你有帮助的话,不妨给我点赞鼓励一下,好文记得收藏哟!
幸好我在,感谢你来!