首页 > 其他分享 >深入理解call、bind、和apply的使用以及底层代码手写

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

时间:2024-10-01 11:19:35浏览次数:8  
标签:函数 bind call context apply fn

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 值和部分参数的函数。

标签:函数,bind,call,context,apply,fn
From: https://blog.csdn.net/2401_85770776/article/details/142667528

相关文章

  • Android12.0需求开发篇之Native Binder Demo通信篇章二
    1.需求描述        基于篇章一的基础上,增加NativeBinderDemo通信的回调功能,由于之前信息数据传递是个单向链路,即由client端主动发起,发送到Server服务端,缺失服务端调用客户端的逻辑,而在实际场景中,应用组还需要双向通信。基于此,在之前BspServer服务端的基础上增加回......
  • callable类型 是什么?
    在C++中,callable类型(可调用类型)是指可以像函数一样被调用的对象C++中有多种不同的可调用对象类型,它们可以通过函数调用运算符()被调用。常见的callable类型包括:普通函数(普通函数、静态函数、成员函数)函数指针仿函数(函数对象)Lambda表达式std::function类型1.......
  • idea启动卡在启动界面不动弹,java.net.BindException: Address already in use: bind
    早上刚想打开idea发现卡在启动界面无法动弹任务管理器关闭idea和重启机器都无法解决,搜了一下网上的教程把解决方法记录下:打开AppData\Local\JetBrains\IntelliJIdea2021.2\log查看idea.log发现详细错误如下:2024-09-2908:46:57,944[10149]ERROR-llij.ide.plugins.Plugi......
  • Android性能优化:getResources()与Binder交火导致的界面卡顿优化
    背景某轮测试发现,我们的设备运行一个第三方的App时,卡顿感非常明显:界面加载很慢,菊花转半天滑屏极度不跟手,目测观感帧率低于15对比机(竞品)也会稍微一点卡,但是好很多,基本不会有很大感觉的卡顿可以初步判定我们的设备存在性能问题,亟需优化,拉平到竞品水准。最后发现,这个问题实际......
  • /sys/kernel/debug/binder/目录下主要节点含义
    /sys/kernel/debug/binder/目录下主要节点含义state显示binder设备的整体状态信息包括进程数量、线程数量、待处理事务数量等stats展示binder操作的统计信息如事务数量、内存使用情况等transactions列出当前正在处理的binder事务包括发送方、接收方、数据大小......
  • C语言-动态内存管理(malloc、calloc、realloc)
    目录1.内存布局2.动态内存函数2.1malloc2.1.1malloc是什么2.1.2如何用​编辑2.2free2.2.1free是什么2.2.2如何用2.3calloc2.3.1calloc是什么2.4realloc2.4.1realloc是什么2.4.2realloc如何使用2.4.3realloc可以实现与malloc同样的功能3.常见的动态......
  • RocksDB代码分析——LogAndApply
    这里我们主要分析VersionSet::LogAndApply是怎么管理writer队列的。参数里的edit_lists是需要被apply的改动。每个传入的columnfamilydata对应edit_lists里的一个editlist,即autovector<VersionEdit*>。接下来把每个editlist打包成一个ManifestWriter,放进std::deque<Manifest......
  • 易优CMS致命错误,联系技术支持:Call to undefined function eyPreventShell()-eyoucms
    当你遇到 core/helper.php 第146行左右出现致命错误,并且提示 CalltoundefinedfunctioneyPreventShell() 时,通常是因为某个自定义函数未被定义或未被正确引入。以下是一些具体的解决步骤:步骤1:检查函数定义定位 eyPreventShell 函数查找 eyPreventShell 函数的......
  • vue 的v-bind和v-model
    1.vue中存在很多绑定的操作,常见的操作为:2.这次主要学习v-bind,v-model.v-bind,用来绑定标签,v-model用来绑定表单.使用message来绑定了一个图片作为效果图.同时使用url来绑定input的txt输入,并且将url和超链接的href链接达到可以跳转网页的效果点击说走就走:修改链接,......
  • Example of API call
    指导API仪表板市场价格地图我们的倡议合作伙伴的优惠博客对于企业苹果笔记本支持 一键调用API3.0家API一键调用API3.0×OpenWeather气象服务与首席气象学家丹·哈特和他的团队交谈!产品概念如何开始当前和......