解析call、apply、bind三者区别及实现原理
call、apply、bind的区别
bind
bind与call或apply最大的区别就是bind不会被立即调用,而是返回一个函数,函数内部的this指向与bind执行时的第一个参数,而传入bind的第二个及以后的参数作为原函数的参数来调用原函数。
用一个例子来理解一下吧
let obj = {
name: 'wujia',
fn: function (a, b, c) {
console.log(this.name, a, b, c)
}
}
window.name = '吴佳'
let nFn = obj.fn.bind(window, '第一个参数')
nFn('第二个参数', '第三个参数')
// 最后输出:吴佳,第一个参数,第二个参数,第三个参数
根据以上例子,不难看出,我们把obj.fn函数内部this改变成window了,所以this.name的输出实际就是获取window上面的name属性。但这里要注意的是参数方面,我这么写是为了让大家更容易看清楚,我们在bind的时候只传入了一个参数,然后在执行这个bind之后的新函数(这里后面就称之为绑定函数)又传入了两个参数,其实这中间有一个过程就是参数合并,合并后的顺序就是相当于把bind执行的第二参数及之后参数与新绑定函数参数做了一个合并,新绑定函数参数会基于bind方法函数第二参数及之后参数结束位置开始进行合并。当然,如果知道柯里化的同学,就会发现好像有点柯里化的感觉,对吧。
还需要注意的一个地方,就是通过new关键字去实例这个绑定函数时,也就是通过new的方式创建一个对象,bind()函数在this层面上是没有效的,但是在参数层面上是有效的。
同样,用一个例子理解一下吧
let obj = {
name: 'wujia',
fn: function (a, b, c) {
this.age = 20
console.log(this.name, a, b, c)
}
}
window.name = '吴佳'
let nFn = obj.fn.bind(window, '第一个参数')
new nFn('第二个参数','第三个参数')
// 最后输出结果:Undefined,第一个参数,第二个参数,第三个参数
根据上面例子的输出可以看到,我们通过bind为fn函数重新指定了this,this指向了window却并没有生效,但是参数生效了,都打印出来了。fn函数内部打印的this.name为Undefined的原因是因为this通过new关键字去实例化绑定函数的时候,因为bind方法内部做了特殊处理,这个处理可以看作成过滤了当前bind的本次this指向操作,让this指向就指向与现在自己。所以我们通过new去实例化对象的时候,实际上就是去new obj.fn() 而fn内部this指向的就是当前实例化对象,所以再从实例化对象上面去找name属性是肯定找不到的,但是一定会有一个age属性在里面。
call & apply
call、apply其实都是为了改变某个函数运行时的上下文而存在的,简单点说就是为了改变某个运行时函数内部this指向。
call、apply的调用会直接返回函数的执行结果。
使用call或者apply方法,它们第一个参数,都是设置函数内部this需要指向的目标。而区别就在于后续参数传递的不同,apply第二参数需要是一个参数数组,call的第二参数及其之后的参数需要是数组里面的元素。
其实可以看做成,apply第二参数需要一个聚合的参数数组列表,而call的第二参数及其之后的参数都需要展开数组挨个传递。
用个例子理解一下
let obj = {
name: 'wujia',
fn: function (a, b, c) {
this.age = 20
console.log(a, b, c)
return this.name
}
}
window.name = '吴佳'
const name1 = obj.fn.call(window, '第一个参数', '第二个参数', '第三个参数')
const name2 = obj.fn.apply(window, ['第一个参数', '第二个参数', '第三个参数'])
// 两个方法的打印输出:第一个参数, 第二个参数, 第三个参数
// name1 & name2 值都为吴佳
需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向window。如果指定为数字或字符串或者布尔值的this值,则会指向该值的包装对象。
请看以下例子
function fn () {
console.log(this)
}
// call方法的输出与apply一致
fn.apply(undefined) // window
fn.apply(null) // window
fn.apply('') // String {""}
fn.apply(1) // Number {1}
fn.apply(true) // Boolean {true}
call、apply、bind的实现
call
Function.prototype.call = function (context) {
// 基础类型转包装对象
if (context === undefined || context === null) {
context = window
} else if (typeof context === 'string') {
context = new String(context)
} else if (typeof context === 'number') {
context = new Number(context)
} else if (typeof context === 'boolean') {
context = new Boolean(context)
}
// 保存原函数至指定对象的fn属性上
context.fn = this
// 获取除第一个参数之后的所有参数
const args = Array.from(arguments).slice(1)
// 通过指定对象的fn属性执行原函数并出入参数
const fnValue = context.fn(...args)
delete context.fn // 从context中删除fn原函数
return fnValue
}
apply
Function.prototype.apply = function (context, arr) {
// 基础类型转包装对象
if (context === undefined || context === null) {
context = window
} else if (typeof context === 'string') {
context = new String(context)
} else if (typeof context === 'number') {
context = new Number(context)
} else if (typeof context === 'boolean') {
context = new Boolean(context)
}
// 非对象,非undefined,非null的值才会抛错
if (typeof arr !== 'object' && typeof arr !== 'undefined' && typeof arr !== 'null') throw new TypeError('CreateListFromArrayLike called on non-object')
arr = Array.isArray(arr) && arr || [] // 非数组就赋值空数组
// 保存原函数至指定对象的fn属性上
context.fn = this
// 通过指定对象的fn属性执行原函数并出入参数
const fnValue = context.fn(...arr)
delete context.fn // 从context中删除fn原函数
return fnValue
}
bind
Function.prototype.bind = function (context) {
// 保存原函数
const ofn = this
// 获取除第一个参数之后的所有参数
const args = Array.from(arguments).slice(1)
function O() {}
function fn() {
// 第一个参数的判断是为了忽略使用new实例化函数时让this指向它自己,否则就指向这个context指定对象
// 第二个参数的处理做了参数合并, 就是 bind & fn 两个函数的参数合并
ofn.apply(this instanceof O ? this : context, args.concat(Array.from(arguments)))
}
O.prototype = this.prototype
fn.prototype = new O()
return fn
}
如果new这个bind之后return的fn函数,this就会指向一个空对象,这个空对象的原型就会指向构造器的prototype。那么此时this instanceof O 就为true,所以返回的this就是当前被实例化的对象;这样就会忽略掉bind方法的this指向,实现上述new一个bind后的函数特性。
以上就是这次总结的全部内容,如果当中总结的有问题;欢迎各位指教,一起讨论~
微 博:前端吴佳
QQ群:856363266
长按识别二维码
关注「前端技术专栏」加星标
每天给您推送最新原创技术文章
好看,帮点击在看❤️
本文分享自微信公众号 - 前端技术专栏(close_3245974231)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
花十分钟的时间武装你的代码库
当我们的代码库有很多人维护时,经常会出现代码风格不一致或者代码质量不过关,提交信息杂乱的情况,当然啦,即使是一个人的代码库,有的时候,自己写代码时不太注意细节,也会出现风格不一致的情况。 本文正是为了解决这个问题而生,阅读本篇文章并不需要很长时间,如果你的代码库还没有进行这些配置,正是你大展身手的好时机,武装一下你的代码库。 1. 规范 commit 信息 首先,看下 angular 的代码库的 commit 记录,如图: 我们可以利用 commitizen 和 husky 来规范代码库的 commit。 安装以下依赖: npminstall@commitlint/cli@commitlint/config-conventionalhusky-D 如果你还没有安装过 commitizen,那么先全局安装: npminstallcommitizen-g 在 package.json 中增加 husky 字段。 {"husky":{"hooks":{"commit-msg":"commitlint-EHUSKY_GIT_PARAMS"}},} husky 是 git hook 工具,使用 ...
- 下一篇
维度爆炸?Python实现数据压缩竟如此简单!
前言 在之前的文章中,我们已经详细介绍了主成分分析的原理,并用Python基于主成分分析的客户信贷评级进行实战。 在那篇文章中我们指出的主成分分析常见的三个应用场景中,其中有一个是「数据描述」,以描述产品情况为例,比如著名的波士顿矩阵,子公司业务发展状况,区域投资潜力等,需要将多变量压缩到少数几个主成分进行描述,压缩到两个主成分是最理想的,这样便可在一张图内表现出来。 但这类分析一般做主成分分析是不充分的,能够做到因子分析更好。但因子分析的知识点非常庞杂,所以本文将跳过原理,直接通过案例再次「实战PCA分析」,用于主成分分析到因子分析的一个过渡,目标有两个: 能够通过主成分分析结果来估计生成的主成分所表示的含义 借以引出因子分析的优势和学习的必要性是本文的目标。 需求说明 上司希望从事数据分析岗位的你仅用两个短句就概括出以下数据集所反映出的经济现象用几个长句都不一定能够很好的描述数据集的价值,更何况高度凝练的两个短句,短短九个指标就已经十分让人头疼了,如果表格再宽一些呢,比如有二三十个变量? Python实战 本节我们将使用Python对上面的数据进行分析 数据探索 importpan...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装