JS手写-手写call、apply、bind函数及相关知识点

一、手写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的实现

http://t.csdn.cn/wHe1c

(三)继承

http://t.csdn.cn/RrApw

(四)eval

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值