call、apply和bind的原理及使用场景

深入理解 JavaScript 中的 call、apply 和 bind call、apply 和 bind 是 JavaScript 中非常重要的方法,它们通过显式地改变 this 的指向,提供了更灵活的函数调用方式。理解它们的区别和实现原理,可以帮助我们更好地掌握 JavaScript 的函数执行机制,并在实际开发中灵活运用。 阅读详情

在JavaScript 中,call、apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向,从而可以达到`接花移木`的效果

原理:

1、call(thisArgs [,args...])

该方法可以传递一个thisArgs参数和一个参数列表,thisArgs 指定了函数在运行期的调用者,也就是函数中的 this 对象,而参数列表会被传入调用函数中。thisArgs 的取值有以下四种情况:

  • 不传,或者传null,undefined, 函数中的 this 指向 window 对象
  • 传递另一个函数的函数名,函数中的 this 指向这个函数的引用
  • 传递字符串、数值或布尔类型等基础类型,函数中的 this 指向其对应的包装对象,如 String、Number、Boolean
  • 传递一个对象,函数中的 this 指向这个对象
function a(){
    console.log(this); //输出函数a中的this对象
}
function b(){} //定义函数b
 
var obj = {name:'onepixel'}; //定义对象obj
 
a.call(); //window
a.call(null); //window
a.call(undefined);//window
a.call(1); //Number
a.call(''); //String
a.call(true); //Boolean
a.call(b);// function b(){}
a.call(obj); //Object

这是call 的核心功能,它允许你在一个对象上调用该对象没有定义的方法,并且这个方法可以访问该对象中的属性,至于这样做有什么好处

var a = {
 
    name:'onepixel', //定义a的属性
 
    say:function(){ //定义a的方法
        console.log("Hi,I'm function a!");
    }
};
 
function b(name){
    console.log("Post params: "+ name);
    console.log("I'm "+ this.name);
    this.say();
}
 
b.call(a,'test');
>>
Post params: test
I'm onepixel
I'm function a!

当执行b.call 时,字符串`test`作为参数传递给了函数b,由于call的作用,函数b中的this指向了对象a, 因此相当于调用了对象a上的函数b,而实际上a中没有定义b 。

2、apply(thisArgs [,args[]])

apply 和 call 的唯一区别是第二个参数的传递方式不同,apply 的第二个参数必须是一个数组(或者类数组),而 call 允许传递一个参数列表。值得你注意的是,虽然 apply 接收的是一个参数数组,但在传递给调用函数时,却是以参数列表的形式传递,我们看个简单的例子:

function b(x,y,z){
    console.log(x,y,z);
}
 
b.apply(null,[1,2,3]); // 1 2 3

apply 的这个特性很重要,(可以借助别的对象的内置方法来实现本对象的特定功能,如计算数组最大值)

3、bind(thisArgs [,args...])

bind是ES5 新增的一个方法,它的传参和call类似,但又和 call/apply 有着显著的不同,即调用 call 或 apply 都会自动执行对应的函数,而 bind 不会执行对应的函数,只是返回了对函数的引用。粗略一看,bind 似乎比call/apply 要落后一些,那ES5为什么还要引入bind 呢?

其实,ES5引入 bind 的真正目的是为了弥补 call/apply 的不足,由于 call/apply 会对目标函数自动执行,从而导致它无法在事件绑定函数中使用,因为事件绑定函数不需要我们手动执行,它是在事件被触发时由JS 内部自动执行的。而 bind 在实现改变函数 this 的同时又不会自动执行目标函数,因此可以完美的解决上述问题,看一个例子就能明白:

var obj = {name:'onepixel'};
 
/**
 * 给document添加click事件监听,并绑定onClick函数
 * 通过bind方法设置onClick的this为obj,并传递参数p1,p2
 */
document.addEventListener('click',onClick.bind(obj,'p1','p2'),false);
 
//当点击网页时触发并执行
function onClick(a,b){
    console.log(
            this.name, //onepixel
            a, //p1
            b  //p2
    )
}

当点击网页时,onClick 被触发执行,输出onepixel p1 p2, 说明 onClick 中的 this 被 bind 改变成了obj 对象,为了对 bind 进行深入的理解,我们来看一下 bind 的 polyfill 实现:

if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        var args = Array.prototype.slice.call(arguments, 1);
        var fToBind = this;  // this在这里指向的是目标函数
        var fBind = function () {
            return fToBind.apply(
                // 如果外部执行var obj = new fBind(), 则将obj作为最终的this,放弃使用oThis
                this instanceof fBind
                    ? this  // 此时的 this 就是 new 出的 obj
                    : oThis || this, // 如果传递的 oThis 无效,就将 fBind 的调用者作为this
                // 将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递
                args.concat(Array.prototype.slice.call(arguments))
            );
        };
 
        // 将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用
        fBind.prototype = this.prototype;
 
        // 返回fBind的引用,由外部按需调用
        return fBind;
    };
}      
一旦函数通过bind传递了有效的this对象,则该函数在运行期的this将指向这个对象,即使通过call或apply来试图改变this的指向也是徒劳的。
var obj1 = {
    name: 'Tom'
}
var obj2 = {
    name: 'Joy' 
}
setTimeout(function() {
    console.log(this.name);
}.bind(obj1).bind(obj2), 0);
 
// Tom

当对一个函数调用多次bind的时候,最终起作用的是第一个bind,这是因为每个bind 都会返回一个闭包 fBind,fBind 里保存了执行目标函数的 scope 和 arguments。执行多次 bind ,目标函数会被多个闭包包装,然后从外到里去执行,当执行真正目标函数的时候,apply 函数中的 scope 和  arguments 读取当前闭包里的变量。   

// 实现数组的去重功能
Array.prototype.unique = function(fn) {
        var rst = [];
        var tmp = {};
        this.forEach(function(val) {
            // 使用call来改变fn的this指向,这里传window
            var key = 'uniq' + (typeof fn === 'function' ? fn.call(window, val) : val);
            if (!tmp.hasOwnProperty(key)) {
                rst.push(val);
                tmp[key] = null;
            }
        }, this);
 
        return rst;
}
 
// 对象数组去重
var arr = [
    { id: 2 }, { id: 4 }, { id: 3 }, { id: 3 }, { id: 4 }, { id: 6 }
]
arr.unique(function(v) {
     console.log(this) // 使用bind传递了Array,则this一定是Array,而不会是window
     return v.id
}.bind(Array));

应用场景

1. 继承

JavaScript中没有诸如Java、C# 等高级语言中的extend 关键字,因此JS 中没有继承的概念,如果一定要继承的话,call 和 apply 可以实现这个功能:

function Animal(name,weight){
   this.name = name;
   this.weight = weight;
}
 
function Cat(){
    Animal.call(this,'cat','50');
  //Animal.apply(this,['cat','50']);
 
   this.say = function(){
      console.log("I am " + this.name+",my weight is " + this.weight);
   }
}
 
var cat = new Cat();
cat.say();//I am cat,my weight is 50

当通过new 运算符产生了cat 时,Cat中的 this 就指向了cat对象(关于new运算符的讲解,请参考JS构造函数和new运算符,而继承的关键是在于Cat中执行了Animal.call(this,'cat','50') 这句话,在call中将this作为thisArgs参数传递,于是Animal 方法中的 this 就指向了Cat中的 this,而 cat 中的 this 指向的是 cat 对象,所以Animal 中的 this 指向的就是 cat 对象,在 Animal 中定义了name 和 weight 属性,就相当于在 cat 中定义了这些属性,因此 cat 对象便拥有了Animal 中定义的属性,从而达到了继承的目的。

2. 移花接木(巧妙通过this的改变,对当前对象使用别的对象的方法)

在讲下面的内容之前,我们首先来认识一下JavaScript 中的一个非标准专业术语:ArrayLike (类数组/伪数组)

ArrayLike 对象即拥有数组的一部分行为,在DOM 中早已表现出来,而jQuery 的崛起让ArrayLike 在JavaScript 中大放异彩。ArrayLike 对象的精妙在于它和JS 原生的 Array 类似,但是它是自由构建的,它来自开发者对JavaScript 对象的扩展,也就是说:对于它的原型(prototype)我们可以自由定义,而不会污染到JS原生的Array。 

ArrayLike 对象在JS中被广泛使用,比如DOM 中的NodeList, 函数中的arguments 都是类数组对象,这些对象像数组一样存储着每一个元素,但它没有操作数组的方法,而我们可以通过call 将数组的某些方法`移接`到ArrayLike 对象,从而达到操作其元素的目的。比如我们可以这样遍历函数中的arguments:

function test(){
    // 检测arguments是否为Array的实例
    console.log(
            arguments instanceof Array, //false
            Array.isArray(arguments)  //false
    );
    // 判断arguments是否有forEach方法
    console.log(arguments.forEach); //undefined
 
    // 将数组中的forEach应用到arguments上
    Array.prototype.forEach.call(arguments,function(item){
        console.log(item); // 1 2 3 4
    });
 
}
test(1,2,3,4);



// 将 arguments(类数组)转换为真数组
function sum() {
    // 方法1:使用 call
    const args = Array.prototype.slice.call(arguments);
    
    // 方法2:使用 apply
    // const args = Array.prototype.slice.apply(arguments);
    
    return args.reduce((a, b) => a + b, 0);
}

console.log(sum(1, 2, 3, 4));  // 10

// 类数组对象也能借用
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const realArray = Array.prototype.slice.call(arrayLike);
console.log(realArray);  // ['a', 'b', 'c']

除此之外,对于apply 而言,我们上面提到了它独有的一个特性,即apply 接收的是数组,在传递给调用函数的时候是以参数列表传递的。 这个特性让apply 看起来比call 略胜一筹,比如有这样一个场景:给定一个数组[1,3,4,7],然后求数组中的最大元素,而我们知道,数组中并没有获取最大值的方法,一般情况下,你需要通过编写代码来实现。而我们知道,Math 对象中有一个获取最大值的方法,即Math.max(), max方法需要传递一个参数列表,然后返回这些参数中的最大值。而apply 不仅可以将Math 对象的max 方法应用到其他对象上,还可以将一个数组转化为参数列表传递给max,看代码就能一目了然:

var arr = [2,3,1,5,4];
 
Math.max.apply(null,arr); // 5

当然,使用es6的扩展运算符会更加优雅

const max2 = Math.max(...arr);
3. 函数柯里化
function add(a, b, c) {
    return a + b + c;
}

// 使用 bind 实现柯里化
const add5 = add.bind(null, 5);
const add5And3 = add5.bind(null, 3);

console.log(add5And3(2));  // 10 (5+3+2)
console.log(add5(3, 2));   // 10 (5+3+2)
4. 定时器中保持 this

定时器中的回调函数,是单独执行的 ,所以默认this指向的是全局对象window,因此需要使用bind绑定this到当前Timer实例

function Timer() {
    this.seconds = 0;
    
    // 错误:setTimeout 中 this 会指向 window
    // setInterval(function() {
    //     this.seconds++;  // this 指向 window
    // }, 1000);
    
    // 正确:使用 bind 绑定 this
    setInterval(function() {
        this.seconds++;
        console.log(this.seconds);
    }.bind(this), 1000);
}

const timer = new Timer();
// 每秒输出:1, 2, 3...

转载:https://www.cnblogs.com/onepixel/p/5143863.html

js系列十三:手写call,apply,bind及面试题解析 他们有什么不同呢?怎么运用他们呢? call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。 let obj = { name: "一个", } function fn(firstName, lastName) { console.log(this); console.log(`我的全名是${firstName}${this.name}${lastName}`);... 阅读详情

相关推荐

如何使用Ray和PyTorch训练模型

用户编写训练函数 → 定义单机训练逻辑。通过扩缩容配置 → 指定需要多少资源(Worker 数量、CPU/GPU)。训练器整合训练函数和配置 → 自动将训练函数分发到多个 Worker 上并行执行。Worker 运行训练函数 → 在分布式环境中完成模型训练。通过这种模式,Ray Train 实现了对分布式训练的抽象,用户只需关注单机训练逻辑,无需手动管理分布式通信和资源分配。

m0_62464865的博客 1922

call ,apply和bind方法 详解

JavaScript 函数call、apply和bind的区别和用法详解

3万+

从加权最小二乘(WLS)到IAA:手把手推导DOA估计中的迭代自适应核心公式

本文深入解析了从加权最小二乘(WLS)到迭代自适应算法(IAA)在DOA估计中的数学推导与应用。通过动态调整加权矩阵,IAA在低快拍数、相干源等复杂场景中展现出卓越性能,显著提升角度分辨率和检测概率。文章详细介绍了算法核心步骤、工程优化策略及前沿发展,为雷达探测和无线通信领域提供重要参考。

weixin_30847865的博客 291

call()、apply()、bind() 区别、使用场景、实现方式

call()、apply()、bind() 区别、使用场景、实现方式

Lyrelion的博客 2823

图像修复的万能模型:用Instruct-IPT统一解决去雨、去雾、去模糊三大难题

从原理到实战,你已经掌握了Instruct-IPT的核心逻辑和使用方法。它不仅能一站式解决多种图像退化问题,还能通过文本指令灵活控制,是图像恢复领域的“多面手”。现在,不妨找几张有雨雪、雾霾、模糊问题的图像,用Instruct-IPT亲手试试效果。当你看到模糊的图像变得清晰,被雨雾遮挡的细节重新浮现时,你会真切感受到这项技术的魅力。后续还可以尝试调整文本指令的描述方式,看看模型的响应有什么变化——探索的过程,就是你技术进阶的最好见证。

m0_71589036的博客 227

基于SiamFC的红外目标跟踪

初始化阶段:在第一帧中,通过用户标注获得目标框,从目标区域中提取一个固定大小的模板(称为 exemplar)。搜索阶段:在后续帧中,在以当前目标中心为中心的较大区域(称为 instance)内进行搜索,将模板与搜索区域通过卷积操作得到响应图。位置更新:在响应图中找到峰值位置,并转换到原图坐标,用以更新目标中心。部分实现中还会考虑多尺度搜索和目标尺寸更新。import cv2# 定义 SiamFC 简易网络(示例)# 特征提取部分,注意红外图像通常为单通道,使用较少的卷积核数提高实时性。

weixin_43745234的博客 774

实现call()、apply()、bind()函数及各自使用场景

本文主要讲解实现call()、apply()、bind()函数及使用场景举例。在JavaScript中,call(),apply(), 和bind()是函数对象的方法,它们允许你以不同的方式调用函数,并可以设置函数体内this的值。

qq_53911056的博客 1498

HyperLynx(十五)多板仿真

在对话框的“Design file#1”下拉列表中选择“B00,Mainboard”,在其下方的“Reference designators”列表中选择元器件“J1”,然后在“Designfile#2”下拉列表中选择“BO1、RAM1”,在其下方的“Refer ence designators”列表中选择元器件“J1”,单击“Insert”按钮,为这两个元器件建立互连映射。在“Assign Models”对话框中,选择“B00 Motherboard”,将“U16.B12”设置为输出。

小幽余生不加糖 5062

call、apply、bind详细讲解

call、apply、bind详细讲解,从小白的思维出发,更加好理解!

m0_60273757的博客 1661

RK3568平台(USB篇)USB Gadget 驱动

USB 设备驱动,按照设备端关联的 USB 控制器 是工作在 主模式 还是 从模式,分为 USB 设备主机侧驱动 (主模式),或者 USB 设备从机侧驱动 (从模式)。同时,工作在 主模式 的 USB 控制器,称为 USB 主机控制器 (UHC: USB Host Controller),工作在 从模式 的 USB 控制器,称为 USB 设备控制器 (UDC: USB Device Controller)。有的 USB 控制器,只能工作在 主模式 或 从模式 中的某一种;

weixin_49303682的博客 1440

构建AI招聘助手:提高人才筛选与匹配效率

人工成本高:传统招聘过程涉及大量的简历筛选和面试安排,需要耗费大量的人力和时间。匹配效率低:简历筛选过程中,人为因素可能导致信息遗漏或误判,影响匹配效果。招聘周期长:从职位发布到最终录用,传统招聘流程往往耗时较长,影响企业业务发展。机器学习(Machine Learning)是一门让计算机通过数据和经验来学习并改进性能的学科。自然语言处理(Natural Language Processing,NLP)是计算机科学领域与人工智能领域中的一个重要方向,旨在让计算机能够理解和解释人类语言。

AI天才研究院 1306

bind、call、apply 区别

一、bind、call、apply的作用 关于call、apply、bind函数,它们主要用来改变this指向的,在很多框架中常有用到,而且也是面试官喜欢问到的问题:多数会问道三者的区别, 以及手动实现它们。 call的用法 fn.call(thisArg, arg1, arg2, arg3, ...) 调用fn.call时会将fn中的this指向修改为传入的第一个参数thisArg;将后面的参数传入给fn,并立即执行函数fn。 let obj = { name: "xiaoming",

weixin_40856652的博客 2万+

基于A*算法的二维栅格地图路径规划与避障MATLAB仿真

本文将介绍如何使用MATLAB编写基于A*算法的二维栅格地图路径规划与避障的仿真程序。我们可以使用二维数组表示地图,其中每个元素表示一个栅格的状态,例如1表示障碍物,0表示可行走区域。接下来,我们需要定义A*算法的节点类。G值表示从起始节点到当前节点的代价,H值是启发式函数估计的从当前节点到目标节点的代价。算法,我们可以有效地找到起始点到目标点的最短路径,并在仿真中进行可视化展示。算法的基本思路是从起始节点开始,按照一定的启发式函数选择下一个节点,直到找到目标节点或者遍历完所有可行走区域。

ByteWhizX的博客 1274

Call、Apply、Bind详解

摘要:call、apply和bind是JavaScript中改变函数this指向的三个核心方法。它们的主要区别在于:call和apply会立即执行函数,前者接收参数列表,后者接收参数数组;而bind返回新函数,延迟执行并支持参数分次传入。典型应用场景包括:call/apply用于方法借用和继承,bind用于事件绑定和参数柯里化。三者还涉及严格模式、箭头函数和性能优化等注意事项。实现原理上,它们都通过临时属性绑定this,其中bind还需处理构造函数调用的情况。

2203_75479159的博客 2019

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

call、apply、bind的区别与应用场景

之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串.....),而不会去调用Object上原型toString方法,所以采用arr.toString()不能得到其对象类型,只能将arr转换为字符串类型;上面的栗子,很明显函数sayColor是在全局作用域(环境/window)中调用的,而全局作用域中有一个color属性,值为"red",sayColor.call(this)这一行代码就是表示。的不同,apply是接收的参数是一个数组。

2401_83127745的博客 1083

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

call\apply\bind详解

call\apply\bind详解

博哥的二进制世界 775

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

apply、call、bind方法的区别和模拟实现

1 区别 这三个方法都是Function原型对象里面的方法(Function.prototype),都可以指定方法的第一个参数来改变某个函数或方法运行时的上下文(指向当前this的指向)的作用。bind与apply、call方法不同的是,它返回一个新函数,等执行新函数时才会去改变上下文,常用于回调函数,而后两者是直接运行的,只是传参的方式不一样:apply的第2个参数是一个数组或类数组,call是...

老曹的博客 1252

手写call()、apply()、bind()函数

手写call(),apply(),bind(),call()和apply()的区别

ispaomoya博客 1533

原生JavaScript实现call、apply和bind

如果用call进行绑定,其实相当于在egg对象里面增加一个person函数,再用egg.person()调用就好了 简单来说就是在egg这个位置开始进行调用,然后执行person函数,如果发现了this就会绑定到egg,因为egg直接执行了person,egg是真正的调用位置,因此this指向了egg 此时的this指向window,person函数进行调用,然后执行newCall函数,发现了this,因此需要进行绑定,person才是真正调用位置,所以this指向person函数 实现call .

qq_35218523的博客 8274

call、apply、bind的使用、作用、区别、原理、实现

众所周知这仨老哥的作用都是改变作用域,但是很多博客只是简单介绍了作用和区别,关于内部具体什么逻辑都是一知半解,这样不知其理,使用起来很容易出现一些意想不到的bug,浪费很多时间成本,这里记录下来 也方便自己回顾查找。

qq_26288475的博客 546

Web前端之JavaScript实现函数方法的原型方法、apply、call、bind

Web前端之JavaScript实现函数方法的原型方法、apply、call、bind

优快云博客主页地址:https://mengjian.blog.youkuaiyun.com/ 2852

JavaScript中的new,bind,call,apply的原理及简易实现

Function原型链中的 apply,call 和 bind 方法是 JavaScript 中相当重要的概念,与 this 关键字密切相关,相当一部分人对它们的理解还是比较浅显,所谓js基础扎实,绕不开这些基础常用的API,这次让我们来了解它们吧! 实现new运算符 原理 new 关键字会进行如下的操作: 创建一个空的简单JavaScript对象(即{}); 链接该对象(设置该对象的co...

知秋 466

手写JS函数的call、apply、bind实现

  之所以要写这篇,是因为曾经面试被要求在白纸上手写bind实现   结果跟代码一样清晰明确,一阵懵逼,没写出来!   下面,撸起袖子就是干!~   把call、apply、bind一条龙都整一遍!~~ call 定义与使用 Function.prototype.call(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Re...

YiYing's sharing 1016

手写call、apply、bind函数与它们的基本使用

手写call、apply、bind函数与它们的基本使用

weixin_47342392的博客 410

原生实现call、apply、bind

原生实现call、apply、bind call实现 function call (context, ...rest) { // 防止传入null或其他的类型出现错误 if (typeof context === 'object') { context = context || window } else { context = Object.create(null) } con...

yq_giveYouMoney的博客 275

深入理解call、applay和bind

这三个方法都是用来改变某一个函数this关键字指向的 call 当前实例通过原型链的查找机制,找到Function.prototype上的call方法 =》function call() { [native code] } fn.call(): 把找到的call方法执行 当call方法执行的时候,内部处理的事情 => 首先把操作函数中的this绑定为第一个参数 => 把call方法第...

杨兴元的博客 3669
上一篇: 什么是IP地址、域名和端口
下一篇: 实例化(Instancing)与合批(Batching)
nocol.
博客等级 码龄7年 587粉丝 67原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值