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

原理:

  1. 首先,通过 Function.prototype.myCall 将自定义的 myCall 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。
  2. myCall 方法内部,首先通过 typeof this !== "function" 判断调用 myCall 的对象是否为函数。如果不是函数,则抛出一个类型错误。
  3. 然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象。这里使用了一种判断全局对象的方法,先判断是否存在 global 对象,如果存在则使用 global,否则判断是否存在 window 对象,如果存在则使用 window,如果都不存在则将 context 赋值为 undefined
  4. 接下来,使用 Symbol 创建一个唯一的键 fn,用于将调用 myCall 的函数绑定到上下文对象的新属性上。
  5. 将调用 myCall 的函数赋值给上下文对象的 fn 属性,实现了将函数绑定到上下文对象上的效果。
  6. 调用绑定在上下文对象上的函数,并传入 myCall 方法的其他参数 args
  7. 将绑定在上下文对象上的函数删除,以避免对上下文对象造成影响。
  8. 返回函数调用的结果。
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

原理:

  1. 首先,通过 Function.prototype.myBind 将自定义的 myBind 方法添加到所有函数的原型对象上,使得所有函数实例都可以调用该方法。
  2. myBind 方法内部,首先通过 typeof this !== "function" 判断调用 myBind 的对象是否为函数。如果不是函数,则抛出一个类型错误。
  3. 然后,判断是否传入了上下文对象 context。如果没有传入,则将 context 赋值为全局对象。这里使用了一种判断全局对象的方法,先判断是否存在 global 对象,如果存在则使用 global,否则判断是否存在 window 对象,如果存在则使用 window,如果都不存在则将 context 赋值为 undefined
  4. 保存原始函数的引用,使用 _this 变量来表示。
  5. 返回一个新的闭包函数 fn 作为绑定函数。这个函数接受任意数量的参数 innerArgs。(关于闭包的介绍可以看这篇文章->闭包的应用场景
  6. 在返回的函数 fn 中,首先判断是否通过 new 关键字调用了函数。这里需要注意一点,如果返回出去的函数被当作构造函数使用,即使用 new 关键字调用时,this 的值会指向新创建的实例对象。通过检查 this instanceof fn,可以判断返回出去的函数是否被作为构造函数调用。这里使用 new _this(...args, ...innerArgs) 来创建新对象。
  7. 如果不是通过 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 函数详解的更多相关文章

  1. c/c++ 标准库 bind 函数 详解

    标准库 bind 函数 详解 bind函数:接收一个函数名作为参数,生成一个新的函数. auto newCallable = bind(callbale, arg_list); arg_list中的参 ...

  2. JavaScript中的apply和call函数详解(转)

    每个JavaScript函数都会有很多附属的(attached)方法,包括toString().call()以及apply().听起来,你是否会感到奇怪,一个函数可能会有属于它自己的方法,但是记住,J ...

  3. JavaScript中的apply和call函数详解

    本文是翻译Function.apply and Function.call in JavaScript,希望对大家有所帮助 转自“http://www.jb51.net/article/52416.h ...

  4. 《Javascript高级程序设计》读书笔记之bind函数详解

    为什么需要bind var name = "The Window"; var object = { name: "My Object", getNameFunc ...

  5. call(),apply()和bind()的详解使用:

    obj.call(thisObj, arg1, arg2, ...); obj.apply(thisObj, [arg1, arg2, ...]); 两者作用一致,都是把obj(即this)绑定到th ...

  6. JS中的call、apply、bind方法详解

    bind 是返回对应函数,便于稍后调用:apply .call 则是立即调用 . apply.call 在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(co ...

  7. (十一)socket、connect、bind函数详解

    一.socket函数 1.头文件: #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> 2.函数原型: ...

  8. jQuery.bind() 函数详解

    bind()函数用于为每个匹配元素的一个或多个事件绑定事件处理函数. 此外,你还可以额外传递给事件处理函数一些所需的数据. 执行bind()时,事件处理函数会绑定到每个匹配元素上.因此你使用bind( ...

  9. bind函数详解(转)

    var name = "The Window"; var object = { name: "My Object", getNameFunc: function ...

  10. JavaScript中的eval()函数详解

    和其他很多解释性语言一样,JavaScript同样可以解释运行由JavaScript源代码组成的字符串,并产生一个值.JavaScript通过全局函数eval()来完成这个工作     eval(“1 ...

随机推荐

  1. Flask框架——flask介绍

    文章目录 1 什么是flask? 2 为什么要有flask? 3 学前准备:虚拟环境 3.1 虚拟环境是什么? 3.2 如何使用虚拟环境? 3.2.1 搭建虚拟环境 3.2.1 在虚拟环境中安装我们的 ...

  2. [SWPUCTF 2021 新生赛]老鼠走迷宫(详细版

    附件下载 https://wwvc.lanzouj.com/iYLez1br84jg 解题思路 用pyinstxtrator解析exe 重点:将无后缀的5先修改后缀为pyc,然后随便找一个pyc文件补 ...

  3. codeforce 827div4

    第一次在codeforce上打题,补一下题记录成长 D题 分析:求数组中两个互质的数的最大下标和: 思路:观察到数据范围n是2e5暴力做n^2会超时,再观察数据a[i]最大为1000,所以这2e5个数 ...

  4. SyntaxError: Non-ASCII character 与 Cannot decode using encoding "ascii" 错误解决

    转载请注明出处: python调试时遇到的两个相同的编码错误进行总结: 1.错误:Cannot decode using encoding "ascii", unexpected ...

  5. Linux下redis的安装下载以及连接RESP

    一.环境 Centos7.redis-6.2.6.gcc依赖.管理员权限 将防火墙放通6379/tcp端口或直接关闭防火墙 二.安装具体步骤 1. 安装依赖 redis是由C语言开发,因此安装之前必须 ...

  6. 微信小程序动态生成表单来啦!你再也不需要手写表单了!

    dc-vant-form 由于我们在小程序上涉及到数据采集业务,需要经常使用表单,微信小程序的表单使用起来非常麻烦,数据和表单是分离的,每个输入框都需要做数据处理才能实现响应式数据,所以我开发了dc- ...

  7. C语言求s=1+(1+2)+…+(1+2+3+…+n)的值,其中n由键盘输入。

    #include<stdio.h> void main() { int n, s=0; scanf_s("%d", &n); for (n; n > 0; ...

  8. Langchain使用自己定义的tool

    Langchain使用自己定义的tool 快速开始 tool是agent可用于与世界交互的功能.这些工具可以是通用实用程序(例如搜索).其他链,甚至是其他代理. 目前,可以使用以下代码片段加载工具: ...

  9. 【Javaweb】了解link标签

    link标签的属性 标签就是定义文档和外部的关系,常见用途是链接样式表.通常指存在于head部分. 规定被连接文档的位置 <link rel='stylesheet' href='./ease. ...

  10. [Codechef REBXOR]Nikitosh and xor (Trie,异或)

    题目传送门 分析:首次考虑暴力枚举 \(l_{1},r_{1},l_{2},r_{2}\),配合前缀和时间复杂度 \(O(N^{4})\),需要想办法优化.对于这种两段区间不重合的,我们考虑枚举两段区 ...