🚀手撕大厂常见手写题——实现call、apply、bind方法
前言
现在已经来到了春招白热化了,陆陆续续地大厂暑期实习开放了,不搞定这些大厂路上的拦路虎怎么行呢~
话不多说,直接进入主题吧
Call
用法
简单来说:call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。
举例
let Name = {
name: '大眼睛图图',
say() {
console.log(this, 'this')
console.log(`我叫${this.name}`)
}
}
Name1 = {
name: '大耳朵图图'
}
Name.say()
// { name: '大眼睛图图', say: [Function: say] } this
// 我叫大眼睛图图
Name.say.call(Name1)
// { name: '大耳朵图图' } this
// 我叫大耳朵图图
实现
call方法,我们最容易想到的有两个功能
- 将调用它的函数立即执行
- 改变其this指向
这两大核心功能该如何实现呢?
Function.prototype.Mycall = function(context){
// context是Name1传入的对象
context.say = this // this执行调用Mycall的say()函数
context.say() // 执行conext.say()方法
}
Name.say.Mycall(Name1)
可以看到,在这里,我们创建了context的say属性(context.say)
等于调用Mycall的函数(Name.say)
,然后执行context中say方法的(context.say)
同时也直接把调用Mycall的函数(Name.say)
执行了。
带入我们的测试用例,可以通过!!!
两行代码就写完了?没错,核心功能就是两行代码,当然还是有不少的细节值得我们注意:
- call没有指定参数,call中的this会指向window
- 在call中增加的say方法可能会被改写
- 在call中增加say方法,会增加原先call函数中的属性,改变call函数的结构
- call函数可以传入多个参数
接下来,让我们一一解决
Function.prototype.MyCall = function (context, ...arg) {
// 如果MyCall没有传入参数,context就是window
// context是Name1传入的对象
context = context || window
// 用Symbol来创建唯一的fn,不被之前的变量污染
let fn = Symbol()
context[fn] = this
// 传入MyCall的多个参数
context[fn](...arg)
// 应及时将增加的fn方法删除
delete context[fn]
}
示例:
可以看到这里还是得到了我们想要的效果
当MyCall不传参数时
代码:
let Name = {
name: '大眼睛图图',
say() {
console.log(this, 'this')
console.log(`我叫${this.name}`)
}
}
Name1 = {
name: '大耳朵图图'
}
Function.prototype.MyCall = function (context, ...arg) {
// 如果MyCall没有传入参数,context就是window
// context是Name1传入的对象
context = context || window
// 用Symbol来创建唯一的fn,不被之前的变量污染
let fn = Symbol()
context[fn] = this
// 传入MyCall的多个参数
context[fn](...arg)
// 应及时将增加的fn方法删除
delete context[fn]
}
// 测试
Name.say.MyCall()
怎么报错了呢?
注意注意,这里是node
环境下,我们要拿到浏览器
环境下运行
这样就对到了~
这个call方法的手写我们就拿下了!
😁😁😁
apply
用法
apply接收两个参数
-
第一个参数为函数上下文this
-
第二个参数为函数参数只不过是通过一个
数组的形式
传入的。
如下
Name.say.apply(Name1,['大眼睛','大耳朵'])
实现
是不是我们又要重新来一遍类似call的写法呢?
NO NO NO
我们可以发现,它其实和call很像,就是参数的接收形式变成了数组而已,仅仅对参数进行调整就行了
let Name = {
name: '大眼睛图图',
say(a) {
console.log(this, 'this')
console.log(`我叫${this.name}${a}`)
}
}
Name1 = {
name: '大耳朵图图'
}
Function.prototype.MyApply = function (context, arg) {
// 如果MyCall没有传入参数,context就是window
// context是Name1传入的对象
context = context || window
// 用Symbol来创建唯一的fn,不被之前的变量污染
let fn = Symbol()
context[fn] = this
// 传入MyCall的多个参数
context[fn](...arg)
// 应及时将增加的fn方法删除
delete context[fn]
}
// 测试
Name.say.MyApply(Name1, ['1'])
(这里我们对say方法增加了一个参数,以便看到后面的效果)
执行结果
OK!
实现了call还不小心实现了apply,直接买一送一了~
Bind
用法
function.bind接收多个参数
第一个参数
是要绑定的this值剩余的参数
就是传入的参数。- 并且bind函数调用时,被调用的函数
不会立即执行
,这一点和call,apply很不一样
示例:
let Name = {
name: '大眼睛图图',
say() {
console.log(this, 'this')
console.log(`我叫${this.name}`)
}
}
Name1 = {
name: '大耳朵图图'
}
// 测试
const fn = Name.say.bind(Name1)
fn()
// { name: '大耳朵图图' } this
// 我叫大耳朵图图
实现
来规矩,先实现核心功能吧
- 改变
this
执行 - 函数
(say)
不会立即执行
上代码!
Function.prototype.myBind = function (context) {
let fn = Symbol('fn')
fn = this // this 还是指向调用它的函数
// 返回Fn函数,等到下次调用Fn函数时才会调用fn函数
return function Fn() {
return fn.apply(context)
}
}
这里myBind函数直接返回Fn()
函数,很好的解决了当执行myBind函数时say方法
不会调用的问题。
并且这里我们直接用上面造好的轮子——apply
函数直接改变fn函数中的this指向问题
后面再把我们可能要传入的参数补上
Function.prototype.myBind = function (context, ...args) {
let fn = Symbol('fn')
fn = this
return function Fn() {
// 这里arguments的作用是拿到Fn中传入的参数
return fn.apply(context, args.concat(arguments))
}
}
测试:
没错,是我们想要的~
new bind
bind这就实现了?
不要又上当了,我们才实现了一半呢~
bind 还有一个特点
一个绑定函数也能使用new操作符创建对象
这种行为就像把原函数当成构造器。
提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
看个例子再感受一下
function bar() {
this.habit = 'shopping';
}
var bindFoo = bar.bind();
var obj = new bindFoo();
console.log(obj.habit); // shopping
这个new bindFoo的行为,就像把原函数bar当成构造器new了出来
那么问题就变成了bindFoo这个构造函数有没有被new呢?
如果被new了那么它的效果和直接new bar构造函数的效果是一致的,直接new bar就好了
所以在这里我们要通过instanceof这个方法来实现~
Function.prototype.myBind = function (context, ...args) {
let fn = Symbol('fn')
fn = this
return function Fn() {
如果是通过 new 调用的,绑定 this 为实例对象
if (this instanceof Fn) {
return new fn(...args, ...arguments)
}
return fn.apply(context, args.concat(arguments))
}
}
测试:
这下,可算终于实现了~🎉🎉🎉
另外,有些初学者可能对arguments这个函数参数的类数组对象不太了解,所以下面笔者也想简单介绍一下
arguments
arguments
是一个对应于传递给函数的参数的类数组对象。
function fun(a, b, c) {
console.log(arguments[0]); // 1
console.log(arguments[1]); // 2
console.log(arguments[2]); // 3
}
arguments
对象不是一个 Array
。
它类似于 Array
,但除了 length
属性和索引元素之外没有任何 Array
属性。
总结
真的还是挺不容易的,不管怎么样,看懂永远只是学习的第一步,后面还得不断巩固,自己手写和不断复习才能真的掌握它们。
希望能够帮助到屏幕前的你~
转载自:https://juejin.cn/post/7208451786964828217