每日一名
I know I have this great potential.
释义:我知道我有巨大的潜力。
前言
我们知道它们最主要的功能是改变this的指向,只是用法写会有些区别。在开发中除了在一些基础库或是在公共库中会用到,其他场景很少会用到,但在面试中会被经常问到,三者的区别用法。
我们来重新捋一波...
基本介绍
call
Function.call(obj, [param1[,param2[,…]]])
- 调用者Function
- 第一个参数obj,this就指向obj
- 后面可以传入多个参数以,分隔
使用场景
- 实现对象继承
var obj = {
name: '小明',
}
function play() {
console.log(`${this.name}会打🏀`)
}
play.call(obj)
- 实现函数继承
function play() {
console.log(`我不会打🏀`)
console.log('他会!')
}
function say() {
console.log('你会打🏀不?')
play.call(null)
}
say();
- DOM实现借用数组方法
Array.prototype.slice.call(document.querySelectorAll('div'))
apply
Function.call(obj[,arrayArg])
- 调用者Function
- 第一个参数obj,this就指向obj
- 后面的参数是一个数组或伪数组,这也是 call 和 apply 之间,很重要的一个区别
类数组无法使用 forEach、splice、push 等数组原型链上的方法,毕竟它不是真正的数组。
bind
Function.bind(obj[, arg1[, arg2[, ...]]])
- 调用者Function
- this指向obj
- 后面可以传入多个参数以,分隔
与call、apply区别是:
- bind不会立即执行,而call、apply会立即执行
- bind只会对this作一次绑定,传的参数会当默认参数,后续无需再bind,默认参数也无需再传,而call,apply每次都要调用和传参
apply的妙用
- 查找数组中的最大或最小值
Math.max.apply(null, [4,32,1,5])
Math.min.apply(null, [4,32,1,5])
- 实现多个数组的合并
let arr = [3]
Array.prototype.push.apply(arr, [4,2,5]);
console.log(arr); // 3,4,2,5
手写call,apply,bind
- call
Function.prototype.mycall = function(ctx) {
// 这里不能用箭头函数,因为箭头没有arguments
ctx = ctx || window
// 只是给ctxt新增一个独一无二的属性,避免覆盖ctx原属性
let fn = Symbol()
ctx[fn] = this;
let arg = [...arguments].slice(1) // 取到参数
const result = ctx[fn](...arg); // 执行
delete ctx[fn] // 删除此属性
return result // 返回结果
}
- apply
就相当简单了,只要把参数改成数组形式就好了。
Function.prototype.mycall = function(ctx) {
// 这里不能用箭头函数,因为箭头没有arguments
ctx = ctx || window
// 只是给ctxt新增一个独一无二的属性,避免覆盖ctx原属性
let fn = Symbol()
ctx[fn] = this;
let arg = [...arguments].slice(1) // 取到参数
const result = ctx[fn](arg); // 执行
delete ctx[fn] // 删除此属性
return result // 返回结果
}
- bind
Function.prototype.myBind = function(ctx) {
ctx = ctx || window
const args = Array.from(arguments).slice(1)
return function () {
return fn.apply(ctx, [...args, ...arguments])
}
}
面试:为什么call会比apply快
- call被调用后经历了啥?
1、如果 IsCallable(Function)为false,即 Function 不可以被调用,则抛出一个 TypeError 异常。
2、如果 argArray 为 null 或未定义,则返回调用 Function 的 [[Call]] 内部方法的结果,提供thisArg 和一个空数组作为参数。
3、如果 Type(argArray)不是 Object,则抛出 TypeError 异常。
4、获取 argArray 的长度。调用 argArray 的 [[Get]] 内部方法,找到属性 length。 赋值给 len。
5、定义 n 为 ToUint32(len)。
6、初始化 argList 为一个空列表。
7、初始化 index 为 0。
8、循环迭代取出 argArray。重复循环 while(index < n)
a、将下标转换成String类型。初始化 indexName 为 ToString(index).
b、定义 nextArg 为 使用 indexName 作为参数调用argArray的[[Get]]内部方法的结果。
c、将 nextArg 添加到 argList 中,作为最后一个元素。
d、设置 index = index+1
9、返回调用 Function 的 [[Call]] 内部方法的结果,提供 thisArg 作为该值,argList 作为参数列表。
- apply调用后,经历了啥?
1、如果 IsCallable(Function)为 false,即 Function 不可以被调用,则抛出一个 TypeError 异常。
2、定义 argList 为一个空列表。
3、如果使用超过一个参数调用此方法,则以从arg1开始的从左到右的顺序将每个参数附加为 argList 的最后一个元素
4、返回调用func的[[Call]]内部方法的结果,提供 thisArg 作为该值,argList 作为参数列表。
我们可以看到,明显 apply 比 call 的步骤多很多。
由于 apply 中定义的参数格式(数组),使得被调用之后需要做更多的事,需要将给定的参数格式改变(步骤8)。 同时也有一些对参数的检查(步骤2),在 call 中却是不必要的。
另外一个很重要的点:在 apply 中不管有多少个参数,都会执行循环,也就是步骤 6-8,在 call 中也就是对应步骤3 ,是有需要才会被执行。
综上,call 方法比 apply 快的原因是 call 方法的参数格式正是内部方法所需要的格式。
具体可参考:call和apply的性能对比,这里比较全面。