手写简易call,apply,bind
面试中可能会让你手写写这几个函数,但是他们的内部实现其实是C++代码,所以我们这里只是用js实现简易版的,不会过多的考虑边界情况,当然,应对面试,肯定是够用了。
一、call函数实现
对于这个我就一步一步的给大家分析(后面的apply和bind就不在一步步地分析了)
1.在Function的原型上挂载我们自己的call函数
Function.prototype.xtCall = function() {
console.log(111);
}
function foo() {
console.log('222');
}
foo.xtCall()
当我们执行foo.xtCall()时,只会打印111 说明我们的foo是未被调用的,下一步目标自然是执行foo.xtCall()时,让他调用foo
Function.prototype.xtCall = function() {
console.log(111);
const fn = this
fn()
}
function foo() {
console.log('222');
}
foo.xtCall()
我们的xtCall函数,在被人调用的时候,明显是隐式调用,如foo.xtCall(),所以里面的this,指向的是调用者,如foo,显示你就会发现我们的111和222都会打印了。
接下来,我们就应想办法改变this的指向了(因为call是有改变this指向的功能的哟)
Function.prototype.xtCall = function(thisArg) {
// 获取需要执行的函数
const fn = this
// 调用需要被执行的函数
thisArg.fn = fn
thisArg.fn()
delete thisArg.fn
}
function foo() {
console.log('222', this);
}
foo.xtCall({name:'xt'})
看thisArg.fn = fn thisArg.fn()如果我们不这样写 我们是直接写fn()那么foo的this,打印的是window,因为fn()是默认绑定,但是我们要做的操作是把我们传入的第一个值作为this,那么我们就可以想到用隐式绑定,覆盖默认绑定,也是就thisArg.fn()。那么delete thisArg.fn是做什么的呢,这是因为我们这里是js不是c++,在执行thisArg.fn = fn,他们给我们传入的对象(thisArg)添加上fn所以我们需要在thisArg.fn()执行后删除它,让他不要多出属性。
其实到这里 我们的对象类型,就已经完成了,我们再来考虑,传入的是number,string这种类型的情况。
这里我们可以先看看系统提供的call方法,是怎么实现的,其实他是把number,string这种类型转成了对象类型。其实我们相信也知道,因为number,string他们是不可以添加属性的,只有转出object类型。
所以我们先实现吧
Function.prototype.xtCall = function(thisArg) {
// 获取需要执行的函数
const fn = this
// 当thisArg不是object类型时,强转为object
thisArg = Object(thisArg) // 如果thisArg本身就是Object他也不会嵌套的,放心用
// 调用需要被执行的函数
thisArg.fn = fn
thisArg.fn()
delete thisArg.fn
}
function foo() {
console.log('222', this);
}
foo.xtCall(111)
经过上面的处理,已经意味着thisArg是number,string,boolean,object时我们都处理好了,但是我们知道系统的call,当你传入的是null,nudefined时他的this是window,所以我们继续处理吧
// call的实现
Function.prototype.xtCall = function(thisArg) {
// 获取需要执行的函数
const fn = this
// 当thisArg不是object类型时,强转为object 如果你null或者undefined时指向window
thisArg = (thisArg !== null || thisArg !== undefined) ? Object(thisArg) : window
// 调用需要被执行的函数
thisArg.fn = fn
thisArg.fn()
delete thisArg.fn
}
function foo() {
console.log('222', this);
}
foo.xtCall(null)
好了 我们现在对this就处理好了,现在我们就剩最后一步,处理额外参数问题
// call的实现
// call的实现
Function.prototype.xtCall = function(thisArg, ...args) {
// console.log(args); args是个数组[10,20]
// 获取需要执行的函数
const fn = this
// 当thisArg不是object类型时,强转为object 如果你null或者undefined时指向window
thisArg = thisArg ? Object(thisArg) : window
// 调用需要被执行的函数
thisArg.fn = fn
// ...args 展开 args -》 [10,20] -> 10,20
const res = thisArg.fn(...args)
delete thisArg.fn
// 返回执行结果
return res
}
function foo(n1, n2) {
// console.log('222', this, n1, n2);
return n1 + n2
}
// foo.xtCall({name: 'xt'}, 10, 20)
console.log(foo.xtCall({name: 'xt'}, 10, 20));
这里我们主要就是通过es6中的剩余参数及...展开运算符来处理的参数问题,并且我们也拿到了函数执行返回结果,到此我们的zi定义call就全部实现了。
二、apply函数实现
其实他与call实现都是差不多的,唯一不一样的就是,apply的参数使用数组接收的
Function.prototype.xtApply = function (thisArg, argsArr) {
const fn = this;
thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
thisArg.fn = fn
let res
if (!argsArr) {
res = thisArg.fn()
} else {
res = thisArg.fn(...argsArr)
}
delete thisArg.fn
return res
}
function foo(n1, n2) {
// console.log('222', this, n1, n2);
return n1 + n2
}
// foo.xtApply({name: 'xt'}, [10,20])
console.log(foo.xtApply({name: 'xt'}, [10,20]));
三、bind函数实现
bind返回的是个函数,且他的参数是一个个的传的,且参数可以在bind的时候传 也可以在调其返回函数时传 如:
function foo(n1, n2, n3) {
console.log(this, n1, n2, n3);
}
// var bar = foo.bind(this, 1, 2, 3)
// bar()
// var bar = foo.bind(this)
// bar(1, 2, 3)
const bar = foo.bind(this, 1)
bar(2, 3)
这三种写法都是一样的,所以这些都是我们要考虑的情况了
Function.prototype.xtBind = function (thisArg, ...args) {
const fn = this;
thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
thisArg.fn = fn
return function (...newArgs) {
const finalArgs = [...args, ...newArgs]
const res = thisArg.fn(...finalArgs)
delete thisArg.fn
return res
}
}