深入理解call、bind、和apply的使用以及底层代码手写

call、bind、和apply的使用

在 JavaScript 中,callapplybind 是三个非常常见的方法,用来显式地指定函数的 this 绑定,它们都可以用来改变函数的执行上下文(即函数内部的 this 指向)。尽管它们的功能相似,但在使用时有一些区别。
开启use strict this 的值为 undefined 而不是全局对象

1. call 方法

call() 方法调用一个函数,并允许你在调用时指定 this 的值,同时以逗号分隔的参数列表形式传递参数。

语法:
func.call(thisArg, arg1, arg2, ...);
person1.fn.call(person2) 
  • thisArg: 调用该函数时绑定的 this 值。
  • arg1, arg2, ...: 传给函数的参数。
示例:
function greet(greeting, punctuation) {
  console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Alice" };

// 使用 call 调用函数并指定 this
greet.call(person, "Hello", "!");
// 输出: "Hello, Alice!"

在这个例子中,greet 函数的 this 指向了 person 对象,通过 call 方法传递了 this 以及函数的参数。

2. apply 方法

apply()call() 类似,但它接受的是数组形式的参数,而不是一个个分开的参数。

语法:
func.apply(thisArg, [argsArray]);
  • thisArg: 调用该函数时绑定的 this 值。
  • argsArray: 传给函数的参数数组。
示例:
function greet(greeting, punctuation) {
  console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Bob" };

// 使用 apply 调用函数并指定 this
greet.apply(person, ["Hi", "."]);
// 输出: "Hi, Bob."

apply() 通常在你已经有一个数组或类数组对象需要作为参数传入时更为有用。

使用场景:求数组中的最大值

你可以用 apply 将数组传给 Math.max() 函数。

const numbers = [1, 2, 3, 4, 5];

const max = Math.max.apply(null, numbers);
console.log(max); // 输出: 5

这里 apply 将数组中的元素作为参数传递给了 Math.max(),而 null 则是因为 Math.max() 不依赖 this

3. bind 方法

bind()callapply 不同,它不会立即调用函数,而是返回一个新的函数,该函数的 this 永远绑定为你指定的 thisArg 值。你可以在以后调用这个新函数。

语法:
const boundFunc = func.bind(thisArg, arg1, arg2, ...);
  • thisArg: 调用该函数时绑定的 this 值。
  • arg1, arg2, ...: (可选)调用时预置的参数。
示例:
function greet(greeting, punctuation) {
  console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Charlie" };

// 创建一个绑定了 this 的新函数
const boundGreet = greet.bind(person, "Hey");
boundGreet("!"); // 输出: "Hey, Charlie!"

在这个例子中,greet.bind(person, "Hey") 返回了一个新函数 boundGreet,它的 this 永远绑定为 person,并且预置了第一个参数为 "Hey"。调用 boundGreet("!") 时,实际上是调用了 greet("Hey", "!")

4. callapplybind 的区别总结

方法什么时候调用?参数形式返回值
call立即调用函数逐个参数传递调用函数的结果
apply立即调用函数通过数组传递参数调用函数的结果
bind返回一个新函数逐个参数传递(预置参数可选)绑定了 this 的新函数

5. 实际应用场景

1. 借用其他对象的方法:

你可以通过 callapply 借用其他对象的函数。例如,使用数组的 slice 方法来将类数组对象转换为数组。

const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };

// 使用 call 借用 Array.prototype.slice
const realArray = Array.prototype.slice.call(arrayLike);
console.log(realArray); // 输出: ['a', 'b', 'c']
2. 函数柯里化:

bind() 可用于函数柯里化(将部分参数固定,返回一个新函数),例如:

function multiply(x, y) {
  return x * y;
}

const double = multiply.bind(null, 2); // 固定 x 为 2
console.log(double(5)); // 输出: 10

在这个例子中,double 函数实际上是 multiply(2, y)bind() 将第一个参数固定为 2

3. 设置定时器
function fn(name){
	console.log('hello'+name)
}
const delayFn=fn.bind(null,'key')
//this指向widow
setTimeout(delayFn,2000)

call、bind、和apply的手写

1. call方法:

Function.prototype.myCall = function(context, ...args) {
    // 判断调用 myCall 的是否是函数
    if (typeof this !== 'function') {
        throw new TypeError('被调用的对象必须是函数');
    }

    // 如果没有传入上下文对象,默认为全局对象
    context = context || globalThis;

    // 用 Symbol 来创建唯一的 fn,防止名字冲突
    let fn = Symbol('key');
    context[fn] = this;

    // 传入 myCall 的参数 args 并执行函数
    const result = context[fn](...args);

    // 删除临时添加的 fn 方法
    delete context[fn];

    // 返回结果(对有返回值的函数生效)
    return result;
}

// 定义对象和函数
var obj = {
    name: 'allen',
    fn() {
        console.log(this.name);
    },
    add(a, b) {
        return a + b;
    }
}

var obj1 = {
    name: 'bob'
}

// 测试 myCall 和 call
obj.fn.myCall(obj1); // 输出: "bob"
obj.fn.call(obj1);   // 输出: "bob"

console.log(obj.add.myCall(null, 1, 2)); // 输出: 3
console.log(obj.add.call(null, 1, 2));   // 输出: 3

2. apply方法:

原理:apply的实现思路和call类似,就是apply传入参数是以数组的形式传入,所有多了一步判断传入的参数是否为数组以及在调用方法的时候使用扩展运算符…将传入的参数数组argsArr展开。

Function.prototype.myApply = function(context, argsArr) {
    // 判断调用 myCall 的是否是函数
    if (typeof this !== 'function') {
        throw new TypeError('被调用的对象必须是函数');
    }
    //判断传递的参数类型是否为数组
    if(argsArr && !Array.isArray(argsArr)){
        throw new TypeError('第二个参数必须是数组')
    }

    // 如果没有传入上下文对象,默认为全局对象
    context = context || globalThis;

    // 用 Symbol 来创建唯一的 fn,防止名字冲突
    let fn = Symbol('key');
    context[fn] = this;

    // 传入 myApply 的参数 args 并执行函数
    const result=Array.isArray(argsArr)? context[fn](...argsArr):context[fn]()
    

    // 删除临时添加的 fn 方法
    delete context[fn];

    // 返回结果(对有返回值的函数生效)
    return result;
}
   const arr=[2,3,4,5,6]
   console.log(Math.max.myApply(null,arr));

3. bind方法:

Function.prototype.myBind = function(context, ...args) {
    // 判断调用 myCall 的是否是函数
    if (typeof this !== 'function') {
        throw new TypeError('被调用的对象必须是函数');
    }
    // 如果没有传入上下文对象,默认为全局对象
    context = context || globalThis;


    //保存原始函数(调用 myBind 的函数)的引用
    //保证了在新函数中继续调用该原函数。
    const _this=this
    //返回的 fn 函数支持继续传入额外的参数,并且还会判断是否作为构造函数使用。
    return function fn(...innerArgs){
        //判断返回出去的函数是不是作为构造函数
        if(this instanceof fn){
        /// 如果函数作为构造函数调用,this 指向实例对象
            return new _this(...args,...innerArgs)
        }
        //使用apply方法将原函数绑定的指定的上下文对象,//正常调用函数,将原函数的 this 绑定到指定的 context 上
        return _this.apply(context,args.concat(innerArgs))

    }
    

   
}

const test={
    name:'allen',
    hello:function(a,b,c){
        console.log(`hello,${this.name}!`,a+b+c);
        
    }
}
const obj={
    name:'world'
}
var h1=test.hello.myBind(obj,1)
var h2=test.hello.bind(obj,1)
h1(2,3)
h2(2,3)
console.log(new h1(2,3));
console.log(new h2(2,3));

new 操作创建了一个新实例,这个实例没有 name 属性

总结:

  • callapply 都用于立即调用函数,但传递参数的方式不同:call 使用逗号分隔的参数列表,而 apply 使用数组。
  • bind 返回一个新函数,不会立即调用,可以用于创建带有固定 this 值和部分参数的函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值