目录
一、myCall
function.call()函数,传入多个参数,其中第一个对象为新的this指向,其余是传入函数中的实参。最终效果是:更改this指向并且立即执行函数,返回新参数下的结果。
1.实现思路
校验调用者是否为函数
通过 typeof this !== "function" 检查当前调用者是否为函数,如果不是则抛出类型错误。这一步确保只有函数能调用 myCall 方法。
处理上下文对象
使用 thisArg || window 确定绑定的上下文对象。如果未传入 thisArg,默认绑定到全局对象(如浏览器中的 window)。将目标对象暂存为 context。
临时绑定原函数到上下文
通过 Symbol('fn') 创建一个唯一键,避免属性名冲突。将原函数(即 this)作为方法挂载到 context 对象上,此时 context[fnKey] 即为需要调用的函数。
执行函数并保存结果
以 context[fnKey](...arg) 方式调用函数,此时函数内的 this 会指向 context。传入剩余参数 ...arg,并保存函数执行结果到 result。
清理临时属性并返回结果
调用完成后,通过 delete context[fnKey] 删除临时添加的属性,避免污染上下文对象。最终返回函数执行结果 result,保持与原 call 方法一致的行为。
2.具体实现
javascript
Function.prototype.myCall = function myCall(thisArg,...arg){
//校验调用者是否为函数
if(typeof this !== "function"){
throw new TypeError("must be a function");
}
//处理上下文对象
//用context保存this要绑定的对象=>得让这个对象调用方法,才能让this最终指向thisArg对象
const context = thisArg || window;
//换绑this,先借助一个函数名
const fnKey = Symbol('fn');
//将原函数(this)作为属性挂载到 context 对象上。对象[属性]才是修改值与方法
context[fnKey] = this;
//调用函数,最终实现this的绑定,并且保存函数执行返回的结果
const result = context[fnKey](...arg);
//删除临时属性
delete context[fnKey];
//保持原函数的效果 返回调用函数的结果
return result;
}
3.测试
javascript
//测试 原本greet函数this指向window 现在myCall指定this指向obj,并且将hi传入到greeting中
function greet(greeting){
console.log(`${greeting},${this.name}`);
}
const obj = {
name:"张三"
}
greet.myCall(obj,'Hi');
二、myApply
function.apply()函数,与call函数唯一区别在于,接收的参数,除了第一个为新的this指向的对象,其余参数用数组形式进行传递,即:apply只接收两个参数。
最终效果是:更改this指向并且立即执行函数,返回新参数下的结果。
1.实现思路
核心目标 :模拟原生 Function.prototype.apply 方法,实现自定义的 myApply 方法,用于显式绑定函数执行时的 this 值,并以数组或类数组形式传递参数。
类型检查与上下文处理
验证调用者是否为函数,非函数调用时抛出类型错误。若未提供 thisArg,默认绑定到全局对象(如浏览器中的 window)。
参数校验逻辑
允许参数为空或传入数组/类数组。若参数存在但非数组或类数组(如普通对象),抛出类型错误。
函数绑定与执行
通过唯一键(如 Symbol)将当前函数临时挂载到目标上下文对象上,避免属性名冲突。使用扩展运算符传递参数执行函数,保存结果后删除临时属性。
边界情况处理
空参数时自动转为空数组。
多传的参数会被忽略(如测试用例中的 '!'),因函数只读取声明位置的参数。
2.具体实现
javascript
Function.prototype.myApply = function(thisArg,argsArray){
//判断调用对象是否是函数
if(typeof this !== "function"){
throw new TypeError("must be function");
}
//判断上下文是否存在
const context = thisArg || window;
//处理传递的参数
const args = argsArray || [];
//检查该传递参数:可以不传、可以为数组、可以为类数组
if((args !== undefined && !Array.isArray(args))
&& !(args instanceof Object)){
throw new TypeError("要么不传,要么传入一个数组或者类数组");
}
//将函数作为上下文对象的一个属性
const fnKey = Symbol('fn');
//将函数挂载
context[fnKey] = this;
//执行函数并且保存结果
const result = context[fnKey](...args);
//删除属性并返回结果
delete context[fnKey];
return result;
}
3.测试
javascript
//测试
function greet(greeting,fuhao){
console.log(`${greeting},${this.name}${fuhao}`);
}
const obj = {name:'张三'};
greet.myApply(obj,['Hi','~','!']);//只会识别对应位置的内容,多出来的不会识别
三、myBind
function.bind()函数
bind:返回新函数,可以延迟执行,支持柯里化 && 不会调用函数,返回原函数的拷贝但是this已经改变
柯里化:可以将传入多个的参数按照一个参数一个参数地进行处理
1.实现思路
核心功能分析 Function.prototype.myBind 需要实现原生 bind 的三个核心特性:绑定 this 值、支持参数柯里化、正确处理 new 调用。以下是具体实现思路:
类型检查与基础绑定 验证调用者必须是函数类型,否则抛出错误。保存原函数引用并返回一个新函数(boundFunction),在闭包中保留 thisArg 和预绑定的参数 boundArgs。
处理 new 调用优先级 通过判断 this 是否是 boundFunction 的实例来区分调用方式:若通过 new 调用,则保留 new 创建的上下文;否则使用绑定的 thisArg。
参数合并与柯里化 将预绑定的参数 boundArgs 与新传入的参数 callArgs 合并,通过 apply 执行原函数并传递合并后的参数数组。
2.具体实现
javascript
//bind:返回新函数,可以延迟执行,支持柯里化 && 不会调用函数,返回原函数的拷贝但是this已经改变
//柯里化:可以将传入多个的参数按照一个参数一个参数地进行处理
Function.prototype.myBind = function myBind(thisArg,...boundArgs){
//检验调用者是否为函数
if(typeof this !== 'function'){
throw new TypeError("must be function");
}
//实现柯里化:保存原函数的调用->return新函数
const originFunc = this;
return function boundFunction(...callArgs){
//处理new操作符:new的优先级比bind高,若是先new,则this指向new;否则指向thisArgs
// 关键:判断 this 是否是 boundFunction 的实例
// 如果通过 new 调用,this 是 boundFunction 的实例
// 如果普通调用,this 指向调用者或全局对象
const context = (this instanceof boundFunction)?this:thisArg;
//再合并参数 其中boundArgs是原参数,callArgs是新增的参数
const allArgs = [...boundArgs,...callArgs];
//执行原函数=>通过apply更改this和接收数组
return originFunc.apply(context,allArgs);
}
}
3.测试
javascript
// 测试 myBind
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
return `${greeting}, ${this.name}${punctuation}`;
}
const obj = { name: "张三" };
// 1. 基本用法:绑定 this
const boundGreet = greet.myBind(obj);
boundGreet('Hi', '!'); // 输出: "Hi, 张三!"
// 2. 柯里化:预先传入部分参数
const boundGreetWithHi = greet.myBind(obj, 'Hello');
boundGreetWithHi('?'); // 输出: "Hello, 张三?"
boundGreetWithHi('!!'); // 输出: "Hello, 张三!!"
// 3. 完全柯里化
const boundGreetFull = greet.myBind(obj, 'Hey', '~');
boundGreetFull(); // 输出: "Hey, 张三~"