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 ...
随机推荐
- Navicat连接MySQL,出现2059 - authentication plugin 'caching_sha2_password'的解决方法
cmd(管理员)登录mysql执行如下 ALTER USER 'YOURUSERNAME'@'localhost' IDENTIFIED WITH mysql_native_password BY ' ...
- Oracle-降低表的高水位线
在应用中存在一系列的表,对表的操作是批量插入又批量删除,最终导致表的水位线很高.高水位线影响全索引扫描的SQL.即影响系统的性能. 现有方法降低表的水位线: 1.降低表的高水位线 select 'al ...
- Python基础——二分法、面向过程编程思想、有名函数、lambda、max、_min的应用、sorted排序、map的应用、filter的应用、reduce的应用
文章目录 内容回顾 二分法 伪代码模板 面向过程编程思想 函数式 def用于定义有名函数 lambda用于定义匿名函数 调用匿名函数 匿名函数作用 匿名函数的示范 max的应用 min的应用 sort ...
- Jmeter调用Python脚本,实现参数互相传递
首先传值到python 1:新增取样器--->os进程取样器 2:新建.bat文件 (写python脚本的路径和传参的个数:思路是:jmeter调用shell,用shell执行py) F:cd ...
- Go函数介绍与一等公民
Go函数介绍与一等公民 函数对应的英文单词是 Function,Function 这个单词原本是功能.职责的意思.编程语言使用 Function 这个单词,表示将一个大问题分解后而形成的.若干具有特定 ...
- 虹科喜报 | 虹科技术工程师【国内首批】拿下Redis认证开发者证书!
要说虹科数据库技术工程师有多强悍,认证考试2022年12月上线,次年2月就以全国首批速度强势通过考试,并于两周后正式收到[Redis认证开发人员]证书! 虹科小云忍不住浅浅炫耀一下: 或许大家对Red ...
- 《最新出炉》系列初窥篇-Python+Playwright自动化测试-23-处理select下拉框-下篇
1.简介 上一篇中宏哥主要讲解和分享了一下,我们常见或者传统的select下拉框的操作,但是近几年又出现了了一种新的select下拉框,其和我们传统的select下拉框完全不一样,那么我们如何使用pl ...
- RLChina理论三:强化学习基础
强化学习基础 马尔可夫决策过程就是在,环境自发做出转变,是个随波逐流的过程,At是智能体的行动,在St环境状态下加入At动作,c才进入下个状态S(t+1),即环境有自己的变化,也加入了智能体的决策. ...
- Redis项目搭建
Redis项目搭建 Redis下载 搭建redis首先需要下载Redis,可是Redis官方并没有Windows安装,好在网上从不缺大牛,Github上可以找到Redis的Windows版 下载地址: ...
- AtCoder F - Parenthesis Checking
原题链接:AtCoder F - Parenthesis Checking 一个全由\('('\)和\(')'\)构成的字符串,由以下两个操作: 1 l r交换字符串第\(l\)个和第\(r\)个字符 ...