JavaScript apply、call、bind 函数详解
apply和call
apply和call非常类似,都是用于改变函数中this的指向,只是传入的参数不同,等于间接调用一个函数,也等于将这个函数绑定到一个指定的对象上:
let name = 'window'
function getName(param1, param2) {
console.log(this.name)
console.log(param1, param2)
}
let obj = {
name: 'easylee',
}
getName.call(obj, 123, 23)
getName.apply(obj, [123, 23])
如上面的例子,如果直接调用 getName 那么返回的是 window ,但是通过 call 方法,将函数绑定到了 obj 上,成为obj的一个函数,同时里面的 this 也指向了obj
两者主要的区别在于,当函数有多个参数时,call 是直接传入多个参数,而 apply 将多个参数组合成一个数组传输参数
手写call
原理:
- 首先,通过
Function.prototype.myCall将自定义的myCall方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。 - 在
myCall方法内部,首先通过typeof this !== "function"判断调用myCall的对象是否为函数。如果不是函数,则抛出一个类型错误。 - 然后,判断是否传入了上下文对象
context。如果没有传入,则将context赋值为全局对象。这里使用了一种判断全局对象的方法,先判断是否存在global对象,如果存在则使用global,否则判断是否存在window对象,如果存在则使用window,如果都不存在则将context赋值为undefined。 - 接下来,使用
Symbol创建一个唯一的键fn,用于将调用myCall的函数绑定到上下文对象的新属性上。 - 将调用
myCall的函数赋值给上下文对象的fn属性,实现了将函数绑定到上下文对象上的效果。 - 调用绑定在上下文对象上的函数,并传入
myCall方法的其他参数args。 - 将绑定在上下文对象上的函数删除,以避免对上下文对象造成影响。
- 返回函数调用的结果。
Function.prototype.myCall = function (context, ...args) {
// 判断调用myCall的是否为函数
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myCall - 被调用的对象必须是函数')
}
// 判断是否传入上下文对象,不传入则指定默认全局对象
context = context || (typeof global !== 'undefined' ? gloabl : typeof window !== 'undefined' ? window : undefined)
// 在上下文对象上绑定当前调用的函数,作为属性方法
// 不能直接调用this方法函数,原因在于如果不将这个方法绑定到上下文对象上
// 直接执行this函数,this函数里面的this上下文对象无法识别为绑定的对象
let fn = Symbol('key')
context[fn] = this
const result = context[fn](...args)
// 删除这个函数,避免对上下文对象造成影响
delete context[fn]
return result
}
const test = {
name: 'xxx',
hello: function () {
console.log(`hello,${this.name}!`)
},
add: function (a, b) {
return a + b
},
}
const obj = { name: 'world' }
test.hello.myCall(obj) //hello,world!
test.hello.call(obj) //hello,world!
console.log(test.add.myCall(null, 1, 2)) //3
console.log(test.add.call(null, 1, 2)) //3
手写apply
Function.prototype.myApply = function (context, argsArr) {
// 判断调用myApply的是否为函数
if (typeof this !== "function") {
throw new TypeError("Function.prototype.myApply - 被调用的对象必须是函数");
}
// 判断传入的参数是否为数组
if (argsArr && !Array.isArray(argsArr)) {
throw new TypeError("Function.prototype.myApply - 第二个参数必须是数组");
}
// 如果没有传入上下文对象,则默认为全局对象
//global:nodejs的全局对象
//window:浏览器的全局对象
context =
context ||
(typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: undefined);
// 用Symbol来创建唯一的fn,防止名字冲突
let fn = Symbol("key");
// this是调用myApply的函数,将函数绑定到上下文对象的新属性上
context[fn] = this;
// 传入myApply的多个参数
const result = Array.isArray(argsArr)
? context[fn](...argsArr)
: context[fn]();
// 将增加的fn方法删除
delete context[fn];
return result;
};
// 测试一下
const test = {
name: "xxx",
hello: function () {
console.log(`hello,${this.name}!`);
},
};
const obj = { name: "world" };
test.hello.myApply(obj); //hello,world!
test.hello.apply(obj); //hello,world!
const arr = [2,3,6,5,1,7,9,5,0]
console.log(Math.max.myApply(null,arr));//9
console.log(Math.max.apply(null,arr));//9
bind
最后来看看 bind,和前面两者主要的区别是,通过 bind 绑定的不会立即调用,而是返回一个新函数,然后需要手动调用这个新函数,来实现函数内部 this 的绑定
let name = 'window'
function getName(param1, param2) {
console.log(this.name)
console.log(param1)
console.log(param2)
}
let obj = {
name: 'easylee',
}
let fn = getName.bind(obj, 123, 234) // 通过绑定创建一个新函数,然后再调用新函数
fn()
除此之外, bind 还支持柯里化,也就是绑定时传入的参数将保留到调用时直接使用
let sum = (x, y) => x + y
let succ = sum.bind(null, 1) // 绑定时没有指定对象,但是给函数的第一个参数指定为1
succ(2) // 3, 调用时只传递了一个参数2,会直接对应到y,因为前面的1已经绑定到x上了
手写bind
原理:
- 首先,通过
Function.prototype.myBind将自定义的myBind方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。 - 在
myBind方法内部,首先通过typeof this !== "function"判断调用myBind的对象是否为函数。如果不是函数,则抛出一个类型错误。 - 然后,判断是否传入了上下文对象
context。如果没有传入,则将context赋值为全局对象。这里使用了一种判断全局对象的方法,先判断是否存在global对象,如果存在则使用global,否则判断是否存在window对象,如果存在则使用window,如果都不存在则将context赋值为undefined。 - 保存原始函数的引用,使用
_this变量来表示。 - 返回一个新的闭包函数
fn作为绑定函数。这个函数接受任意数量的参数innerArgs。(关于闭包的介绍可以看这篇文章->闭包的应用场景) - 在返回的函数
fn中,首先判断是否通过new关键字调用了函数。这里需要注意一点,如果返回出去的函数被当作构造函数使用,即使用new关键字调用时,this的值会指向新创建的实例对象。通过检查this instanceof fn,可以判断返回出去的函数是否被作为构造函数调用。这里使用new _this(...args, ...innerArgs)来创建新对象。 - 如果不是通过
new调用的,就使用apply方法将原始函数_this绑定到指定的上下文对象context上。这里使用apply方法的目的是将参数数组args.concat(innerArgs)作为参数传递给原始函数。
Function.prototype.myBind = function (context, ...args) {
// 判断调用myBind的是否为函数
if (typeof this !== "function") {
throw new TypeError("Function.prototype.myBind - 被调用的对象必须是函数");
}
// 如果没有传入上下文对象,则默认为全局对象
//global:nodejs的全局对象
//window:浏览器的全局对象
context =
context || (typeof global !== "undefined"
? global
: typeof window !== "undefined"
? window
: undefined);
// 保存原始函数的引用,this就是要绑定的函数
const _this = this;
// 返回一个新的函数作为绑定函数
return function fn(...innerArgs) {
// 判断返回出去的函数有没有被new
if (this instanceof fn) {
return new _this(...args, ...innerArgs);
}
// 使用apply方法将原函数绑定到指定的上下文对象上
return _this.apply(context,args.concat(innerArgs));
};
};
// 测试
const test = {
name: "xxx",
hello: function (a,b,c) {
console.log(`hello,${this.name}!`,a+b+c);
},
};
const obj = { name: "world" };
let hello1 = test.hello.myBind(obj,1);
let hello2 = test.hello.bind(obj,1);
hello1(2,3)//hello,world! 6
hello2(2,3)//hello,world! 6
console.log(new hello1(2,3));
//hello,undefined! 6
// hello {}
console.log(new hello2(2,3));
//hello,undefined! 6
// hello {}
总结一下,这三个函数都是用于改变函数内 this 对象的指向,只是使用方式有不同,其中 apply 传递多个参数使用数组的形式,call 则直接传递多个参数,而 bind 则可以将绑定时传递的参数保留到调用时直接使用,支持柯里化,同时 bind 不会直接调用,绑定之后返回一个新函数,然后通过调用新函数再执行。
JavaScript apply、call、bind 函数详解的更多相关文章
- c/c++ 标准库 bind 函数 详解
标准库 bind 函数 详解 bind函数:接收一个函数名作为参数,生成一个新的函数. auto newCallable = bind(callbale, arg_list); arg_list中的参 ...
- JavaScript中的apply和call函数详解(转)
每个JavaScript函数都会有很多附属的(attached)方法,包括toString().call()以及apply().听起来,你是否会感到奇怪,一个函数可能会有属于它自己的方法,但是记住,J ...
- JavaScript中的apply和call函数详解
本文是翻译Function.apply and Function.call in JavaScript,希望对大家有所帮助 转自“http://www.jb51.net/article/52416.h ...
- 《Javascript高级程序设计》读书笔记之bind函数详解
为什么需要bind var name = "The Window"; var object = { name: "My Object", getNameFunc ...
- call(),apply()和bind()的详解使用:
obj.call(thisObj, arg1, arg2, ...); obj.apply(thisObj, [arg1, arg2, ...]); 两者作用一致,都是把obj(即this)绑定到th ...
- JS中的call、apply、bind方法详解
bind 是返回对应函数,便于稍后调用:apply .call 则是立即调用 . apply.call 在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(co ...
- (十一)socket、connect、bind函数详解
一.socket函数 1.头文件: #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> 2.函数原型: ...
- jQuery.bind() 函数详解
bind()函数用于为每个匹配元素的一个或多个事件绑定事件处理函数. 此外,你还可以额外传递给事件处理函数一些所需的数据. 执行bind()时,事件处理函数会绑定到每个匹配元素上.因此你使用bind( ...
- bind函数详解(转)
var name = "The Window"; var object = { name: "My Object", getNameFunc: function ...
- JavaScript中的eval()函数详解
和其他很多解释性语言一样,JavaScript同样可以解释运行由JavaScript源代码组成的字符串,并产生一个值.JavaScript通过全局函数eval()来完成这个工作 eval(“1 ...
随机推荐
- npm install xxx 后加上-s、-d、-g之间的区别?
1.npm install xxx -s npm install xxx -s.npm install xxx -S是npm install xxx --save的简写形式 局部安装,记录在packa ...
- EarthChat SignalR原理讲解
SignalR原理讲解 SignalR是什么? SignalR 是 Microsoft 开发的一个库,用于 ASP.NET 开发人员实现实时 web 功能.这意味着服务端代码可以实时地推送内容到连接的 ...
- c语言代码练习2(2)
//利用for循环,输出1-10阶乘的和#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main( ) { int i = ...
- python3 gui 计时器
# -*- coding: utf-8 -*- # @Time : 2023/4/4 21:53 # @File : 定时器gui.py # @Software: PyCharm Community ...
- 2023_10_10_MYSQL_DAY_02_笔记
2023_10_10_MYSQL_DAY_02_笔记 #在 FROM 子句中使用子查询 SELECT a.ename, a.sal, a.deptno, b.salavg FROM emp a, (S ...
- Python - 字典3
修改字典项 您可以通过引用其键名来更改特定项的值: 示例,将 "year" 更改为 2018: thisdict = { "brand": "Ford ...
- 文心一言 VS 讯飞星火 VS chatgpt (124)-- 算法导论10.5 5题
五.用go语言,给定一个n结点的二叉树,写出一个 O(n)时间的非递归过程,将该树每个结点的关键字输出.要求除该树本身的存储空间外只能使用固定量的额外存储空间,且在过程中不得修改该树,即使是暂时的修改 ...
- 深入解析css-笔记
前言 本文章是根据<深入解析CSS>一书所作的学习笔记,书中的知识点基本都概括在这.希望对您有帮助,另外本博客是通过word笔记文档导入,虽然后续对内容和代码相关进行了一些格式处理,但还是 ...
- JAVA多线程(1)——线程
1.定义:线程是一个程序里面不同的执行路径 例子1:只有一个执行路径 (一个分支,即主线程)
- influxdb 连续查询使用总结
转载请注明出处: 1.定义: InfluxDB 连续查询(Continuous Query)是一种自动化查询类型,该查询会根据定义的时间间隔定期运行,并将结果存储在新的目标测量中.这样的查询通常用于处 ...