手撕JS实现call,apply,bind
- call和apply实现思路主要是:
- 判断是否是函数调用,若非函数调用抛异常
- 通过新对象(context)来调用函数
- 给context创建一个
fn
设置为需要调用的函数 - 结束调用完之后删除
fn
- 给context创建一个
call:
Function.prototype.myCall = function (context) {// 先判断调用myCall是不是一个函数// 这里的this就是调用myCall的if (typeof this !== 'function') {throw new TypeError("Not a Function")}// 不传参数默认为windowcontext = context || window// 保存thiscontext.fn = this// 保存参数let args = Array.from(arguments).slice(1) //Array.from 把伪数组对象转为数组// 调用函数let result = context.fn(...args)delete context.fnreturn result}function sayHi() {console.log(`Hi, I'm ${this.name}`);}const person = { name: "Alice" };sayHi.myCall(person); // 输出 "Hi, I'm Alice"
就相当于是把myCall的调用者,也就是sayHi那个函数,重新赋值给传给myCall的context参数上下文,也就是person上下文对象,就相当于person对象有了这个方法,然后通过person调用这个方法,this自然就指向了perosn,也就是myCall的参数这样就改变了sayHi函数调用是它内部的this的指向,因为通过myCall函数真正调用sayHi的是person
apply:
Function.prototype.myApply = function (context) {// 判断this是不是函数if (typeof this !== "function") {throw new TypeError("Not a Function")}let result// 默认是windowcontext = context || window// 保存thiscontext.fn = this// 是否传参if (arguments[1]) {result = context.fn(...arguments[1])} else {result = context.fn()}delete context.fnreturn result}
apply和call唯一的不同就是,apply的第二个参数是一个数组或者类数组,故在函数内部需要判断是否传递了第二个参数,如果有,就需要将数组解构,再传给函数
bind:
Function.prototype.myBind = function(context){
// 判断是否是一个函数
if(typeof this !== "function") {
throw new TypeError("Not a Function")
}
// 保存调用bind的函数
const _this = this
// 保存参数
const args = Array.prototype.slice.call(arguments,1)
// 返回一个函数
return function F () {
// 判断是不是new出来的
if(this instanceof F) {
// 如果是new出来的
// 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
return new _this(...args,...arguments)
}else{
// 如果不是new出来的改变this指向,且完成函数柯里化
return _this.apply(context,args.concat(...arguments))
}
}
}