前言
大家好,我是热爱前端的luckyCover,今天分享几道手写题,希望大家都能轻松拿下。
在手写前,你需要掌握this指向问题,了解call、apply、bind属性的含义。如果你还不了解,可以看我的上一篇文章:我不允许你还不了解this、call、apply、bind- luckyCover掘金
手写call
手写call函数前,我们来捋一下书写步骤:
- 函数名:尽可能贴合原属性名,最好带有自定义的含义(如myCall、customCall等)
- 参数:第一个参数是
上下文对象
,第二个参数开始可能有不确定个参数个数(即参数可能不止一个)
- 函数体:
上下文绑定及函数调用
javascript
Function.prototype.myCall = function(context, ...args) {
// 生成唯一的键,方便后续函数执行完进行删除操作
const key = Symbol();
// 给当前上下文对象绑定this
context[key] = this;
// 调用函数并传递参数,一般的函数会有返回值,我们还是用res接收一下
const res = context[key](...args);
// 调用结束即刻删除当前引用,防止上下文对象中存在该函数
delete context[key];
// 返回返回值
return res;
}
写完测试下吧~
javascript
function myFn(a, b, c, d, e) {
console.log(`${this.myName}`, a, b, c, d, e);
}
const obj = {
myName: 'yy',
}
myFn.myCall(obj, 1, 2, 3, 4, 5); // 参数逐个传递 输出结果:yy, 1, 2, 3, 4, 5
console.log(obj); // { myName: 'yy' }
手写apply
apply
和call
的区别就在第二个参数上,步骤:
- 函数名:
myApply
- 参数:第一个参数是
上下文对象
,第二个参数是一个类数组
- 函数体:
上下文绑定及函数调用
javascript
Function.prototype.myApply = function(context, args) {
// 生成唯一的键,和上边call相同作用
const key = Symbol();
// 给当前上下文对象绑定this
context[key] = this;
// 参数args是一个数组,我们需要将其展开
const res = context[key](...args);
// 调用结束即刻删除当前引用,防止上下文对象中存在该函数
delete context[key];
// 返回返回值
return res;
}
测试如下:
javascript
function myFn(a, b, c, d, e) {
console.log(`${this.myName}`, a, b, c, d, e);
}
const obj = {
myName: 'yy',
}
myFn.myApply(obj, [1, 2, 3, 4, 5]); // 以数组形式传递 输出结果:yy, 1, 2, 3, 4, 5
console.log(obj); // { myName: 'yy' }
手写bind
bind
和call
的唯一区别在于bind不会立即执行函数
,而是返回一个绑定好上下文的新函数
,我们看下步骤:
- 函数名:
myBind
- 参数:第一个参数是
上下文对象
,第二个参数往后逐个传递(和call传参相同)
- 函数体:
上下文绑定及函数调用,返回新的函数
javascript
Function.prototype.myBind = function(context, ...args) {
// 保存this(即函数本身)
let fn = this;
return function(...args1) {
let allArgs = [...args, ...args1];
// 判断是否为new的构造函数
if(new.target) {
return new fn(...allArgs);
} else {
fn.call(context, ...allArgs); // 需展开数组中每个元素
// fn.apply(context, allArgs); // 调用apply情况下,直接传递allArgs数组,无需展开
}
}
}
测试myBind
函数:
javascript
function myFn(a, b, c, d, e) {
console.log(`${this.myName}`, a, b, c, d, e);
}
const obj = {
myName: 'yy',
}
const newFn = myFn.myBind(obj, 1, 2, 3, 4, 5)
newFn(); // 输出:yy, 1, 2, 3, 4, 5
console.log(obj); // { myName: 'yy' }
下面我为大家提供不一样的写法,但是实现效果还是一样滴,大家根据自己情况选择即可。
不同的写法
call
javascript
Function.prototype.myCall = function(context) {
// 类型校验
if(typeof this !== 'function') {
throw new Error('不是函数哦')
}
// 保存函数于上下文对象中
context.fn = this
// 整理参数
arguments[1] ? context.fn(...[].slice.call(arguments, 1)) : context.fn()
// 删除上下文对象中的fn函数
delete context.fn
}
简单解释下这一条表达式含义:
text
arguments[1] ? context.fn(...[].slice.call(arguments, 1)) : context.fn()
arguments
:传递给当前函数的参数,可能不止一个arguments[1]
:函数的第二个参数,我们知道第一个参数是context(上下文对象)...[].slice.call(arguments, 1)
:其实arguments
就是call函数的上下文对象
,就相当是arguments.slice(1)
,我们知道arguments
指的是函数接收的所有参数(它是个数组
),slice(1)
中的1
表示截取数组元素的起始索引(索引默认从0开始)
,所以是从第二个参数开始截取一直到数组末尾。而且slice方法会返回一个新数组
,那么这个新数组的元素可能就放到了代码里最前边的[]
中,并且我们将该数组展开(...[]
)并作为参数传递给context.fn()
。- 如果没有第一个参数,即
arguments[1]
为空,那么直接调用函数,context.fn()
,没有传递参数,默认的上下文对象就为window
。
相信理解了call函数及其参数的作用
,上边代码多看几遍都能看得懂的,不理解就先写出代码,再反复看,实操还是最重要滴😊。
apply
javascript
Function.prototype.myApply = function(context) {
// 类型校验
if(typeof this !== 'function') {
throw new Error('不是函数哦')
}
// 保存函数于上下文对象中
context.fn = this
// 整理参数
arguments[1] ? context.fn(...arguments[1]) : context.fn()
// 删除上下文对象中的fn函数
delete context.fn
}
text
arguments[1] ? context.fn(...arguments[1]) : context.fn()
我们知道apply
的第二个参数是个数组
,那么也不需要往后截取所有参数了,直接将数组展开
作为参数传递即可。
bind
javascript
Function.prototype.myApply = function(context) {
// 类型校验
if(typeof this !== 'function') {
throw new Error('不是函数哦')
}
// 整理参数
let arg = arguments[1] ? [].slice.call(arguments, 1) : []
return (...newArgs) => {
this.apply(context, arg.concat(newArgs))
// this.call(context, ...arg.concat(newArgs))
}
// 删除上下文对象中的fn函数
delete context.fn
}
text
let arg = arguments[1] ? [].slice.call(arguments, 1) : []
[].slice.call(arguments, 1)
,这里和上边call
的写法是一样的,只是前边没有加三个点...
,此时返回的新数组是未展开的
,因为后边要返回一个新的函数
,调用新函数
时可能会传递新的参数
,那么就要在新的函数里边将参数进行合并
。至于新函数里边使用call
还是apply
来调用都可以,使用call
来调用时记得合并完数组要将其展开
。
结语
以上就是本篇的所有内容,喜欢的请点点赞支持下luckyCover哈哈,大家有更好的方法也欢迎评论区互动呀~