call
// 首先明确原生call的作用
//fn.call(a, ...args) //作用就是a.fn(...arg) 让a来调用fn,this指向a
// 返回值就是执行目标函数后的返回值,所以要在内部执行该函数
Function.prototype.myCall = function (context = window, ...args) {
// 让context的默认值为window, ...args表示剩余所有参数 args是所有参数的数组
// 函数内部this指向外部调用该函数的对象,此处场景下指向fn.call的fn
// 给context添加一个属性
context.fn = this
console.log('args',args);
// 运行目标函数,得到返回值,args是数组,还是要用扩展运算符分出来
let result = context.fn(...args)
// 删除fn属性,否则context属性会越来越多
delete context.fn
return result
}
// 上面的方法形参里有arg ,其实可以用arguments属性调用
Function.prototype.myCall2 = function (context = window) {
// arguments是函数的内置属性,表示传递进来的所有参数
// 就算没有在括号里传入参数,也可以通过该属性得到其他途径传入的(跟this一样,由js内部传递进去)
// 比如父组件给子组件的, arguments是一个伪数组,下面的操作是变成真数组并且index从1开始才是参数
// index为0的是传进来的context
let args = [...arguments].slice(1)
context.fn = this
// 使用args的时候要把数组拆开
let result = context.fn(...args)
delete context.fn
return result
}
// 还有种情况是node.js环境下,全局变量不是window,而是global
// 有可能context上面已经有了fn属性,如果,就会覆盖原本的属性
Function.prototype.myCall2 = function (context) {
if(context === undefined || context === null) {
// globalThis是es11的新关键字,指当前环境下的全局变量
context = globalThis
}
// arguments是函数的内置属性,表示传递进来的所有参数
// 就算没有在括号里传入参数,也可以通过该属性得到其他途径传入的(跟this一样,由js内部传递进去)
// 比如父组件给子组件的, arguments是一个伪数组,下面的操作是变成真数组并且index从1开始才是参数
// index为0的是传进来的context
let args = [...arguments].slice(1)
// 用Symbol加上一个唯一的属性
let key = Symbol('key')
context[key] = this
// 使用args的时候要把数组拆开
let result = context[key](...args)
delete context[key]
return result
}
apply
// apply的参数是数组,所以...args应该是一个完整的数组,到时候直接传入就行
Function.prototype.myApply = function(context=window ,...args){
// args是数组包了一层数组
context.fn = this
// 把参数从args里取出来,是一个数组形式,再扩展运算符打开
let result = context.fn(...args[0])
// let result = context.fn(...arguments[1])
delete context.fn
return result
}
// apply的参数是数组,所以参数直接写args就行,写...args有点麻烦
Function.prototype.myApply2 = function(context=window ,args){
if(context ===undefined||context===null){
context = globalThis
}
let key = Symbol('key')
context[key] = this
// 把参数从args里取出来,是一个数组形式,再扩展运算符打开
let result = context[key](...args)
delete context[key]
return result
}
bind
bind与上面两个不同,是返回的一个函数,属于函数柯里化,所以要判断当返回的函数是构造函数的时候,要可以进行new操作,参考文章1 文章2
比如
f3 = f1.bind(f2),
f4 = new f3(),
这里的f4既不是f1也不是f2,是一个全新的实例对象
new操作后得到的新对象,要是一个不同于f1和f2
//fn.bind(a, ...args) //返回的是一个函数,需要自己去调用,a.fn(...arg)() 让a来调用fn,this指向a
Function.prototype.myBind1 = function (context, ...outerArgs) {
let self = this
// 返回一个函数
return function (...innerArgs) {
return self.call(context, ...outerArgs, ...innerArgs)
}
}
// 手写bind不能用箭头函数
Function.prototype.myBind2 = function (context, ...outerArgs) {
if (typeof this !== 'function') {
throw new TypeError('error')
}
let self = this
// 返回一个函数
return function (...innerArgs) {
return self.call(context, ...outerArgs, ...innerArgs)
}
}
Function.prototype.myBind3 = function (context, ...outerArgs) {
// this->func context->obj outerArgs->[10,20]
let self = this
// 返回一个函数
return function F(...innerArgs) { //返回了一个函数,...innerArgs为实际调用时传入的参数
// 考虑F如果是一个构造函数,那么返回出去,就有可能被new F()
// 如果F是构造函数,那么说明调用bind的this即self也是构造函数
/*
为什么要用new
参考 https://blog.csdn.net/weixin_39570777/article/details/111388167?ops_request_misc=&request_id=&biz_id=102&utm_term=%E6%89%8B%E5%86%99bind%E4%B8%ADnew&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-6-.first_rank_v2_pc_rank_v29&spm=1018.2226.3001.4187
当使用bind的函数是一个构造函数时,使用完毕后应该返回去的同样也是一个构造函数
如此,返回去的函数应该是可以进行new 的
new里面发生的步骤,先回顾一下,
首先创建一个空对象,把构造函数的原型对象绑定到空对象的__proto__上
这个空对象就是被返回的实例对象,
然后执行构造函数并把this绑定这个实例,即用实例调用构造函数,得到返回结果,可能有也可能没用
最后如果结果是一个对象,就返回这个对象,如果不是对象就返回这个实例
那么在此情况下,必须要考虑返回去的函数是可以成功进行new操作的
当外部在 new F()的时候,调用该返回的F函数,要得到一个实例对象,或者一个F运行后的返回对象
new内部要把this交给内部新生成的实例对象
内部会有这段代码 F.call(f) , 此时this指向f,f instanceof F为true
所以this instanceof F 为true
那么就要返回
*/
if (this instanceof F) {
// 这下面的new 内部会返回一个新对象,执行调用bind的原构造函数的结果
return new self(...outerArgs, ...innerArgs)
// 或者,此处的this为new 内部的F 的实例对象,是一个全新的对象,既不是调用bind的对象,也不是调用bind后指定的对象,
// 换句话说f3 = f1.bind(f2), f4 = new f3(),这里的f4既不是f1也不是f2,是一个全新的实例对象
return self.call(this, ...outerArgs, ...innerArgs)
}
// 把func执行,并且改变this即可
return self.apply(context, [...outerArgs, ...innerArgs]) //返回改变了this的函数,参数合并
}
}