fortran中call的用法_浅析Js中call,apply,bind

前言

  • 了解this的指向(文章中的调用方法与this息息相关)
  • 了解过函数调用
  • 了解过call()、apply()、bind()
迦南:深入理解JS中this(一)​zhuanlan.zhihu.com
7bef6598995e2b90f5ae798b33514e8c.png
迦南:深入理解JS中this(二)​zhuanlan.zhihu.com
7bef6598995e2b90f5ae798b33514e8c.png

目录:

  • call、apply、bind 的区别分别是什么?
  • call、apply、bind 的用法分别是什么?
  • call、apply、bind 的用途在那些方面?

回顾

指针

“指向”、“指针”怎么理解?你可能会读到类似“这只是对象的指针,虽然删除了指向对象的指针,但对象依旧占用着内存”的语句。

打个比方:我们上学时(小学),老师会给学生们安排一个固定的座位号,目的是为了方便老师让学生回答问题时不用记住学生姓名,直接喊号,提高效率。那么,每个学生对应着一个座位号,如1,2,3分别代表小明,小红,小三,这里需要知道,座位号1,2,3和小明,小红,小三并不是完全相同的事物,前者是一个具有代表性的数字(宾语是数字),后者是实实在在的人(对象),但是两者又存在一一对应的关系,这时候,我们可以这样说,数字1指向小明,数字2指向小红,数字3指向小三(“指向”是动词),那么用图解的方式画出来就是1->小明,2->小红,3->小三。看看,数字与对象之间的箭头,就是指针(名词)。也就是说,“指针”就是数字与对象之间的关系的一种名词性的说法。

e87aed091b8d0d3ead26e8d9ecfed2e7.png
  1. “执行环境”怎么理解?你可能会读到类似“函数在被调用的时候,会被推入到它的执行环境中,函数的执行环境中存在哪些变量”的语句

打个比方:ECMAScript是一个导演,对象是演员(就那么几个如Object,Array,Math等),变量是道具。运行JavaScript代码相当于“导演让演员按照剧本借助一定的道具在舞台上演绎出一部话剧”,我们分析这句话,ECMAScript、对象、变量都有对应的喻体了,那剧本和舞台又是什么呢?剧本就是ECMAScript中制定的规则,舞台就是我们说的“执行环境”!一场话剧,在不同的阶段,需要上场的演员和需要使用的道具是不同的,所谓“你方唱罢我登场”,“执行环境”在不同阶段也是不同对象的表演舞台,存放的变量道具也不同。

■ call、apply、bind 的区别分别是什么?

三者异同

  • 相同点:

三者都是用来改变函数的上下文,也就是this指向的。

  • 不同点:

fn.call:立即调用,返回函数执行结果,this指向第一个参数,后面可有多个参数,并且这些都是fn函数的参数。】

 fn.call(this参数,形参1,形参2...)

fn.apply:立即调用,返回函数的执行结果,this指向第一个参数,第二个参数是个数组,这个数组里内容是fn函数的参数。

fn.apply(this参数,参数的数组)

fn.bind: 不会立即调用,而是返回一个绑定后的新函数。

const newFn=fn.bind(this参数,函数参数1,函数参数2...)

应用场景

  • 需要立即调用使用call/apply
  • 要传递的参数不多,则可以使用fn.call(thisObj, arg1, arg2 ...)
  • 要传递的参数很多,则可以用数组将参数整理好调用fn.apply(thisObj, [arg1, arg2 ...])
  • 不需要立即执行,而是想生成一个新的函数长期绑定某个函数给某个对象使用,使用const newFn = fn.bind(thisObj); newFn(arg1, arg2...)

实现原理

call的实现

Function.prototype.myCall = function(thisObj = window){
    thisObj.fn = this //此处this是指调用myCall的function
    // 处理arguments
    let arg = [...arguments].slice(1)
    let result = thisObj.fn(...arg) // 执行该函数
    delete thisObj.fn
    return result
}

验证一下

var foo = {
    value: 1
};
function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
bar.myCall(foo, 'kevin', 18);
// kevin
// 18
// 1

c452213fd3c041ce1ae1187a11d8dd36.png

apply的实现,和call类似

Function.prototype.myApply = function (context = window, arr){
    context.fn = this
    let result
    if(!arr){
        result = context.fn()
    } else {
        result = context.fn(...arr)
    }
    delete context.fn
    return result
}

bind的实现

bind的实现要借助刚才的 apply
Function.prototype.myBind = function(context){
    //  类型判断
    if(typeof this !== 'function'){
        throw new TypeError('must be a function')
    }
    let self = this // 这个this是调用bind的函数
    let argsArr = [...arguments].slice(1)

    return function(){
        let bindFuncArg = [...arguments]
        // 合并两次传的参数
        let totalArgs = argsArr.concat(bindFuncArg)
        return self.apply(context, totalArgs)
    }
}
  1. call(), apply()方法几乎一样,只是传入参数的方式不一样,后者第二个参数是一个数组,那为什么要存在着两个几乎一样的东西?这不是重复造轮子么?

这需要介绍它们使用场景来告诉你原因:Math对象有个max() 方法: 可以返回传入数字中的最大者:

var max = Math.max(1, 8, 3, 15, 4, 5); // 调用Math的max()取得传入的最大参数并赋值给max
console.log(max); // 打印出15 

6454b2c1cd054cead504d2cbd7826173.png

上面的例子可以写成这样,效果与上面的完全一样:

var max = Math.max.call(Math, 1, 8, 3, 15, 4, 5); // 调用Math的max()取得传入的最大参数并赋值给max(函数调用的小动作)
 console.log(max); // 打印出15

8fbd695040d85a3fbda602a302767de1.png

那么接下来我换个需求,我想要让你结合max()方法,找出一个数字数组中最大的数字。你可能会说,简单啊,然后给出了这些方案!

var arr = [1, 8, 3, 15, 4, 5]; //声明数组表达式
var max = Math.max.call(Math, ...arr); // ES6解构语法======(这里有疑问看正文后再回来消化下)
console.log(max); // 打印15

b04b89eb3ea8189b6740268f54e1fb3b.png

可以的,很机智地实现了需求。但是你回想一下apply() 的第二个参数是什么?数组!看代码

var arr = [1, 8, 3, 15, 4, 5]; //声明数组表达式
var max = Math.max.apply(Math, arr); // apply方法
console.log(max); // 打印15

704024db6262db54a08ff404e0e94c47.png

有没有那种 “我正好需要,你正好专业” 的感觉~!

正文:function调用的“小秘密”
不要被开篇的东西吓到,本文的正文很简单的。就是告诉你function调用时你不知道的“小动作”(跟this相关的)。
先要知道: 函数中,thisarguments这两个函数的属性,只要在函数执行的时候才会知道它们分别是指向谁(好好琢磨一下这话)。
1. 一般函数的调用(全局环境中函数的调用)

首先,一般地,我们在全局环境中声明函数和执行函数的过程如下:

function hello(someone) {
    console.log(this + "你好啊 " + someone);
} // 函数声明

hello("掘金果酱淋"); // 函数调用,打印出 //[object Window]你好啊 掘金果酱

其实,函数在内部执行的时候,还做了个小动作,也就是我们要说的小秘密

function hello(someone) {
    console.log(this + "你好啊 " + someone);
} // 函数声明

hello.call(window, "掘金果酱淋"); // 函数调用,打印出 //[object Window]你好啊 掘金果酱淋

对比一下,发现重点了么?函数在执行的时候,自动将this指向了window这个全局对象(注意node环境下全局对象是global),与我们手动让this指向window打印出来的结果一样!
那你可能还会反驳,书上不是说,在严格模式下(“use strict”)时,this时指向了undefinded,那是因为在严格模式下,函数调用的小动作是这样的:

function hello(someone) {
    'use strict';
    console.log(this + "你好啊 " + someone);
} // 函数声明

hello("掘金果酱淋"); // 函数调用,打印出 //undefined你好啊 掘金果酱淋
hello.call(undefined, "掘金果酱淋"); // 打印出 //undefined你好啊 掘金果酱淋

怎么样,有没有豁然开朗的感觉?

2. 对象方法的调用(对象里面的函数的调用)

首先,存在这样一个对象,平常的调用这样的:

var person = {
    name: "掘金果酱淋",
    hello: function(someone) {
      console.log(this + " 你好啊 " + someone);
    }
  };
// 正常调用
 person.hello("world");// [object Object] 你好啊 世界

有了之前的解密,相信你能理解函数在调用时的小动作是这样的:

var person = {
    name: "掘金果酱淋",
    hello: function(someone) {
      console.log(this + " 你好啊 " + someone);
    }
  };
// 小动作
 person.hello.call(person, "world");// [object Object] 你好啊 世界

call(), apply(), bind()的原理很简单啊!

经过前面解析函数调用的小秘密,我们知道它们都“偷偷地”调用了call()

我们再看一下它们在MDN中的定义:

现在我们在回想一下前面的函数调用,那就很好理解了,我们在声明函数时,thisarguments无法知道是谁(前面说过),那就是undefinded或者null,所以根据MDN的定义,在调用函数是,函数的小动作偷偷调用call()方法,为函数设置了this对象。apply()同理!只不过你要注意它接收参数的方式不同。
bind()呢,也很好理解了,我们不想在函数执行时才被函数的小动作指定this对象,而是要固定this对象,那么bind()方法就是在内部调用了call()或者apply()方法主动指定this对象,同时为了函数可以复用,借用了闭包来保存这个this对象(闭包这里不多说),以下是模拟bind()方法的示例:

// 定义一个对象
var person = {
    name: "掘金果酱淋",
    hello: function(thing) {
      console.log(this.name + " 你好啊 " + thing);
    }
};
// 模拟bind方法的操作,接收一个函数和一个this对象(执行环境)
var bind = function(func, thisValue) {
    return function() {
        return func.apply(thisValue, arguments); // 注意apply()和arguments的妙用
    };
};
  
var boundHello = bind(person.hello, person);
boundHello("世界"); // 打印出// 掘金果酱淋 你好啊 世界

怎么样,挺简单的吧!


使用 Function.prototype.bind

因为有时引用具有持久性this值的函数可能会很方便,所以人们一直使用简单的闭包技巧将函数转换为不变的函数this

var person = {
  name: "Brendan Eich",
  hello: function(thing) {
    console.log(this.name + " says hello " + thing);
  }
}

var boundHello = function(thing) { return person.hello.call(person, thing); }

boundHello("world");

即使我们的boundHello呼叫仍然不满意boundHello.call(window, "world"),我们也会转过来使用原始call方法将this值更改回我们想要的值。

我们可以通过一些调整使此技巧通用:

var bind = function(func, thisValue) {
  return function() {
    return func.apply(thisValue, arguments);
  }
}

var boundHello = bind(person.hello, person);
boundHello("world") // "Brendan Eich says hello world"

为了理解这一点,您只需要另外两个信息。首先,arguments是一个类似数组的对象,它表示传递给函数的所有参数。其次,该apply方法的工作方式与call原始方法完全相同,不同之处在于它采用了一个类似于Array的对象,而不是一次列出一个参数。

我们的bind方法只是返回一个新函数。调用它时,我们的新函数将简单地调用传入的原始函数,将原始值设置为this。它还通过参数传递。

因为这是一个有点普遍的习惯用法,所以ES5 bind在所有Function实现此行为的对象上引入了一种新方法:

var boundHello = person.hello.bind(person);
boundHello("world") // "Brendan Eich says hello world"

当您需要一个原始函数作为回调传递时,这是最有用的:

var person = {
  name: "Alex Russell",
  hello: function() { console.log(this.name + " says hello world"); }
}

$("#some-div").click(person.hello.bind(person));

// when the div is clicked, "Alex Russell says hello world" is printed

当然,这有些笨拙,并且TC39(负责ECMAScript下一版本的委员会)继续致力于开发一种更加优雅,仍然向后兼容的解决方案。

JavaScript中call,apply,bind方法的总结。 - 追梦子 - 博客园​www.cnblogs.com Function.prototype.bind()​developer.mozilla.org
c5abb13461a7fdd23656cc189f1b4732.png
js基础知识---call,apply,bind的用法​www.jianshu.com
1bc5e20fb2f8d70a5d68111f6bb08d7f.png
JavaScript bind() 的用法​www.jianshu.com
152c73a0decd3673d0aa1e0c21f25a6d.png
[译] JavaScript 中至关重要的 Apply, Call 和 Bind​hijiangtao.github.io https://medium.com/@realdennis/javascript-%E8%81%8A%E8%81%8Acall-apply-bind%E7%9A%84%E5%B7%AE%E7%95%B0%E8%88%87%E7%9B%B8%E4%BC%BC%E4%B9%8B%E8%99%95-2f82a4b4dd66​medium.com ECMAScript ¼Ì³Ð"úÖÆʵÏÖ​www.w3school.com.cn
a9bbca918bfcab53353b7a67bf326134.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值