一、手写call、apply、bind
(一)手写call
1.call语法:fn.call(obj,...args)
2.手写call方法思路:
1)通过fn.调用call方法,call方法是函数原型上的方法(Function.prototype.call),
2)改变this指向,并将后面n个参数传给fn,返回函数的返回值,
3)如果obj为null/undefined,this指向为window(node下=globalThis)
3.实现核心:在obj上添加一个fn方法,执行后删除,返回函数执行结果
4.this的隐式绑定,当函数add引用有上下文对象时,
就会把函数add调用中的this绑定到这个上下文对象obj上了
5.实现:
Function.prototype.mycall = function(obj, ...args) {
// 如果obj为null/undefined,this指向为window(node下=globalThis(全局对象))
if (obj === null || obj === undefined)
obj = window || globalThis
// console.log(this)// add方法
// 1.给对象添加add方法
obj.temp = this
// 2.调用对象的该方法(隐式改变函数this指向),获得返回值
const res = obj.temp(...args)
// 3.删除对象上的该方法
delete obj.temp
// 4. 返回函数的返回值
return res
}
6.测试call方法与mycall方法:
function add(a, b) {
console.log(this);
return a + b + this.c
}
add.prototype.flag = 'hard'
window.c = 10;
const obj = {
c : 20
}
// call
console.log(add(10, 20))// this = window ; // 40 = 10+20+10
console.log(add.call(obj, 10, 20))// this = obj // 50 = 10+20+20
console.log(add.call(obj)) // NaN
console.log(add.mycall(obj, 10, 20))// this = obj // 50 = 10+20+20
console.log(add.mycall(null, 10, 20))// this = window ; // 40 = 10+20+10
console.log(add.mycall(obj))//NaN
7.测试结果:
(二)手写apply
手写apply方法
1.语法:fn.apply(obj,arr)
2. 思路:同call,注意参数的处理
3.实现
Function.prototype.myapply = function(obj,args) {
// console.log(this)//add方法
// console.log(args)//[10,20]--Array
if (obj === null || obj === undefined) {
obj = window || globalThis
}
let res
// 1.给obj对象添加方法
obj.temp = this
// 2.执行该方法,获得函数返回值
// 注意如果args参数存在,传参调用;如果不存在,直接调用
if (!args) {
res = obj.temp()
} else {
res = obj.temp(...args)
}
// 3. 删除对象上的该方法
delete obj.temp
// 4. 返回函数的返回值
return res
}
4.测试apply:
// apply
console.log(add(10, 20))// this = window ; // 40 = 10+20+10
console.log(add.apply(obj, [10, 20]))// this = obj // 50 = 10+20+20
console.log(add.apply(obj))// this = obj // NaN
console.log(add.myapply(obj, [10, 20]))// this = obj // 50 = 10+20+20
console.log(add.myapply(obj))// this = obj // NaN
console.log(add.myapply(null, [10, 20]))// this = window ; // 40 = 10+20+10
5.测试结果:
(三)手写bind(重点)
手写bind方法
1.语法:fn.bind(obj,...args)
2.功能:返回一个新函数,该函数给fn绑定this为obj,并指定后面的n个参数
3.手写注意点:
1) bind绑定时,可以同时传递全部参数;也可以先传递一些后面再传递完整(函数柯里化)
2) bind可以配合new进行使用,其返回的函数是可以new实例对象的,在bind中需要进行处理(继承)
4.测试bind:
1)bind绑定时,可以一次传入所有参数;
2)bind绑定时可以传入部分参数,后面再次调用补充其他参数;
3)bind搭配new使用,可以new一个以add函数作为构造函数的空对象;
小小记录:
不new=> bind返回的函数内部this为window
new=> bind返回的函数内部this为 add{};原型为add函数的原型
测试代码:
//bind
console.log(add(10, 20))// this = window ; // 40 = 10+20+10
// 1. bind绑定时,传入全部参数
console.log(add.bind(obj, 10, 20)())// this = obj // 50 = 10+20+20
// 2. 先传递一些参数,后面再补充传递
let t1 = add.bind(obj, 30)
console.log(t1)// add 函数
console.log(t1(40)) // 90 = 30 + 40 + 20
// 3. bind配合new使用
let o = add.bind(obj, 80)
let newO = new o(90);
// 在add构造函数里,有打印this//结果为一个 add {}
console.log(newO)// add{}
测试结果:
5.自己实现bind:
1)调用call实现bind:(简单版)
// 通过调用call+this指向问题,实现bind
Function.prototype.mybind = function(obj, ...args1) {
// console.log(this) // add
// 实现1:保存此处this,在function(){}中调用
let that = this
return function(...args2) {
// console.log(this) // window
// 新函数执行call函数返回结果
return that.call(obj, ...args1, ...args2)
}
// 实现2:返回一个箭头函数,其内部this指向外层的this,即add方法
// 不合适,箭头函数不能new....这里只是做个思考记录
// console.log(this) // add
// return (...args2) => {
// // console.log(this)
// // function(){}时:this 指向 window
// // ()=>{}时,this为add方法
// // 新函数执行call函数返回结果
// return this.call(obj,...args1,...args2)
// }
}
简单测试mybind函数:(对比bind)
// 测试mybind
// 1. bind绑定时,传入全部参数
console.log(add.mybind(obj, 10, 20)())// this = obj // 50 = 10+20+20
// 2. 先传递一些参数,后面再补充传递
let t = add.mybind(obj, 30)
console.log(t)// bind中return 的 函数
console.log(t(40)) // 90 = 30 + 40 + 20
// 3. bind配合new使用
let o1 = add.mybind(obj, 70)
let newO1 = new o1(80);
// 在add构造函数里,有打印this//结果为 obj
console.log(newO1)// {} 以bind中return 的 函数 为 构造函数的空对象
测试结果:
问题:
add.prototype.flag = 'hard'
console.log(newO1.flag)// undefined
// newO1实例与add的原型对象没有关系,使用new+bind会使this失效
// 对于this的处理有两种情况:不用new,this效果依旧;用new, this失效
// 如果使用了new来调用函数,即发生了构造函数,那么实例(新对象)会绑定到函数调用的this(new的规则)
小小记录:
不new=> bind返回的函数内部this为window
new=> bind返回的函数内部this为 {};原型上的构造函数为bind返回的函数
2)复杂版实现:添加继承,使得可以读取add原型上的属性和方法
代码实现:
// bind的复杂实现
Function.prototype.myHardBind = function(obj, ...args1) {
let that = this // this => add函数
// console.log(args1)// [10,20]
let mHB = function(...args2) {
// console.log(this)// mHB函数
// console.log(args2)// []
// console.log(this instanceof mHB)// true
// return that.call(obj,...args1,...args2)
if (this instanceof mHB) { // 如果this 是 mHB函数的实例,则this指向mHB函数构造的新实例上
return that.apply(this, [...args1, ...args2])
} else { // 否则this需要指向的实例obj上
return that.apply(obj, [...args1, ...args2])
}
}
// 实现继承(寄生式继承)
let fun = function() { }//创建一个空函数
fun.prototype = that.prototype
mHB.prototype = new fun // mHB的原型为add原型的实例
return mHB
}
测试:
// 测试myHardBind
// 1. bind绑定时,传入全部参数
console.log(add.myHardBind(obj, 10, 20)())// this = obj // 50 = 10+20+20
// 2. 先传递一些参数,后面再补充传递
let t = add.myHardBind(obj, 30)
console.log(t)// bind中return 的 函数
console.log(t(40)) // 90 = 30 + 40 + 20
// 3. bind配合new使用
let o1 = add.myHardBind(obj, 70) // 返回了一个mHB函数
let newO1 = new o1(80);// 将mHB函数作为构造函数执行,其this指向的是构造的新实例
// 在add构造函数里,有打印this//结果为 一个 mHB{}
console.log(newO1)// {} 以bind中return 的 函数 为 构造函数的空对象;其原型为add原型的实例
console.log(newO1.flag)// hard
测试结果:
可以访问到add原型上的属性和方法了,但还不是和原生bind完全相同??只是用继承实现了对add原型上属性和方法的访问,没有返回一个真正的add{}实例??
// 底层是怎么实现的呢?
通过eval ???
小小记录:
不new=> bind返回的函数内部this为window
new=> bind返回的函数内部this为 mHB{};原型为add函数的原型的实例
二、遇到的一些知识点
(一)函数柯里化(学不完的知识之下一个搞这个)
(二)new的实现
(三)继承
(四)eval