JavaScript中 固定 this指向 (call、apply、bind 函数)

embedded/2024/10/22 0:06:33/

前置知识

  • this 关键字
  • js原型及原型链

背景

由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即this的指向是可变的。
this的动态切换,固然为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。
有时,需要把this固定下来,避免出现意想不到的情况。如果对this不太了解,可以先看一下我另一篇关于 this 的文章

概念

JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向。

这三个方法都放在了Function对象的原型对象上,因此所有Function实例对象上都能调用这三个方法,也就是说只有函数才能调用这三个方法。

Function.prototype.call()

函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

语法:func.call(thisValue, arg1, arg2, ...)
第一个参数是this所需要指向的那个对象;后面的参数是函数调用时所需的参数。

call 方法的第一个参数,应该是一个对象。如果参数为空、null、undefined,则默认传入全局对象

var n = 123;
var obj = { n: 456; };function a() {console.log(this.a);
}a.call(); // 123
a.call(null); // 123
a.call(undefined);  // 123
a.call(window); // 123
a.call(obj);  // 123

如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。

function f() {return this;
};f.call(5); // Number {[[PrimitiveValue]]: 5}

call方法的其中一个使用场景是调用对象的原生方法。

看下面示例:

javascript">var obj = {};
obj.hasOwnProperty('toString') // false// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {return true;
};
obj.hasOwnProperty('toString') // trueObject.prototype.hasOwnProperty.call(obj, 'toString') // false

上面代码中,hasOwnPropertyobj对象继承的方法,如果这个方法一旦被覆盖,就不会得到正确结果。
call方法可以解决这个问题,它将hasOwnProperty方法的原始定义放到obj对象上执行,这样无论obj上有没有同名方法,都不会影响结果。

可能最后一行有点绕,这里再啰嗦解释一下:
因为obj对象可能覆盖Object原型上的方法
因此,这里我们可以直接执行原型的方法。然后将函数内部的指向改为obj对象,并传入'toString'参数,这时候其实就等同于上面代码第二行
因为拿第二行来说,我们知道通过obj调用hasOwnProperty,这时候在hasOwnProperty函数内部,this肯定就是obj
因此最后一行的执行结果和第二行是一样的。区别在于,最后一行是直接调用的原始定义方法,不会受后续重写方法的影响

Function.prototype.apply()

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数。
语法: func.apply(thisValue, [arg1, arg2, ...])

Function.prototype.bind()

概念: bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

语法: func.bind(thisValue, arg1, arg2, /* …, */ argN)

bind()除了第一个绑定this参数之外,还可以接受更多的参数,将这些参数绑定原函数的参数。

例如func方法有3个参数,那么我们在绑定的时候可以预先传入几个参数,然后我们在后面实际调用新函数的时候,只需要传剩余参数即可。如下所示。

function func(x, y, z) {console.log(this.name, x, y, z);
}var obj = {name: '张三',
};var newFunc = func.bind(obj, 100);
newFunc(200, 300) // 张三 100 200 300

再来看下面这个例子

我们将d.getTime()方法赋给变量print,然后调用print()就报错了。
这是因为getTime()方法内部的this,绑定Date对象的实例,赋给变量print以后,内部的this已经不指向Date对象的实例了。

var d = new Date();
d.getTime() // 1481869925657var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

bind()方法可以解决这个问题。

var print = d.getTime.bind(d);
print() // 1481869925657

上面代码中,bind()方法将getTime()方法内部的this绑定到d对象,这时就可以安全地将这个方法赋值给其他变量了。

再来看一个使人困惑的例子

利用bind()方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的slice()方法为例。

var newSlice = Function.prototype.call.bind(Array.prototype.slice);
newSlice([1, 2, 3], 0, 1) // [1]

这段代码可能理解起来有点困难,又是call又是bind的,我们需要拆分出来理解。

首先,这行代码是直接调用了函数原型上的call函数。

然后我们再通过call函数(Function.prototype.call可以看成call) 调用一个bind方法来生成一个新的函数。

这里先将Array.prototype.slice看做是一个对象
然后通过调用bind方法将Array.prototype.slice变成Function.prototype.call方法所在的对象。

如果对上面这句话感到有点困惑的话,看这里~

首先我们来回顾一下bind方法的语法和概念
概念:bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。
语法:func.bind(thisValue)

通过对概念的理解,我们可以对语法做一个推算:
func.bind(thisValue) 就相当于 thisValue.func
其实这就是绑定this的原理: 将func变为了thisValue对象上的一个方法了。这时候func里的this指向,肯定就是指向thisValue对象了。
这只再回头看一下上面那句话,是否会清晰一下

上面看懂之后,调用时就变成了Array.prototype.slice.call

示例就可以写成下面这样

javascript">Function.prototype.call.bind(Array.prototype.slice)
// 等同于
Array.prototype.slice.call()
javascript">var slice = Function.prototype.call.bind(Array.prototype.slice);
// 这时候 slice 就相当于 Array.prototype.slice.call()
// 因此
slice([1, 2, 3], 0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
// 等同于
[1, 2, 3].slice(0, 1) // [1]

call/apply 和 bind的区别

call/apply是立即执行,bind是返回一个函数,后期需要的时候执行。
bind可以先传入部分参数,在执行的时候传入剩余参数;
call/apply因为是立即执行,所以要在调用的时候传入所有参数

参考链接

阮一峰 js教程
MDN官网


http://www.ppmy.cn/embedded/129405.html

相关文章

新书速览|Android智能座舱开发:从源码到实践

《Android智能座舱开发:从源码到实践》 本书内容 《Android智能座舱开发:从源码到实践》是一本专注于Android智能座舱系统开发与优化的实战指南。《Android智能座舱开发:从源码到实践》共9章,第1章从搭建源码编译环境开始,详细指导读者如何下载和编译An…

归一化——5种方法详细分类说明

归一化(Normalization)是一种数据预处理技术,旨在将不同量纲、不同取值范围的数据转换到相同的尺度上,以便进行更加公平、有效的比较或分析。 通过归一化,数据被调整到一定的标准范围内,常见的范围有 [0, …

Java爬虫API:获取商品详情数据的利器

为什么选择Java爬虫API 强大的库支持:Java拥有丰富的网络编程库,如Apache HttpClient、OkHttp等,这些库提供了强大的HTTP请求功能,使得发送请求和处理响应变得简单。高效的数据处理:Java的数据处理能力,结…

比亚迪车机安装第三方应用教程

比亚迪车机安装第三方应用教程 比亚迪车机U盘安装APP, 无论是dlink3.0还是4.0都是安卓系统,因此理论上安卓应用是都可以安装的,主要就是横屏和竖屏的区别。在比亚迪上安装软件我主要推荐两种方法。 第一种,直接从电脑端下载安装布…

C++(stack和queue)

1. stack的介绍、使用和实现 1.1 stack的介绍 stl里的stack其实和数据结构内的stack和前面数据结构的栈不能说百分百一样,但也有百分之90是一样的,他们的特性都是LIFO(last in first out)先进后出的原则,前面有类似的…

论新能源智能化电动车个性化(高定)产品对制造生产的影响

一、新能源智能化电动车高定体现模式 1.个性体现在品牌之间 在不同主机产产品上体现,例如国产新能源新势力在智能座舱、内饰配置(冰箱、彩电、大沙发)方面对于合资品牌的碾压,提供更多细分,功能拉满的车型。 2.个性化…

C#从零开始学习(继承)(6)

本章所有的代码都放在 https://github.com/hikinazimi/head-first-Csharp 使用冒号继承一个基类,子类扩展一个基类时,他会继承它的成员:也就是基类中的所有字段,属性和方法,他们会自动增加到子类 子类覆盖方法改变它继承的成员 基类中的方法增加virtual关键字子类同名方法增加…

自动化运维:高效IT管理的未来

自动化运维:高效IT管理的未来 在信息技术日新月异的时代,你是否曾思考过,为什么许多企业面临的运维挑战日益严峻?传统的运维方式,像是使用手动工具修理老旧机器,既耗时又容易出错。随着企业对高效、安全的…