面试 |call, apply, bind的模拟实现和经典面试题

推荐阅读地址:掘金

欢迎 Start

思维导图

大家好,我是林一一。下面的这一篇是关于 JS 中 call,apply,bind 原理和模拟实现和场景的面试题文章,一起开始阅读吧。🧐

callandapplyandbind.png

call,apply,bind 都可以改变 this 的指向

关于this 指向问题可以看看这篇 面试 | 你不得不懂的 JS this 指向

一、call 格式 [function].call([this], [param]...),一句话概括:call() 将函数的 this 指定到 call() 的第一个参数值和剩余参数指定的情况下调用某个函数或方法。

原理:[function].call([this]),执行 call() 会将函数 [function] 中的 this 绑定到第一个参数。而函数 call() 中的 this 是来源于 [function] 的,[function] 是在 call() 函数内部执行的,是 call() 通过操控 this 来执行函数 [function],同时给 [function] 传递剩余的参数。

思考

1. 热身题1

function fn(a, b) {
    console.log(this, a, b)
}

var obj = {
    name: '林一一'
}

fn.call(obj, 20, 23)   // {name: "林一一"} 20 23

fn.call(20, 23) // Number {20} 23 undefined

fn.call()   //Window {0: global, window: …} undefined undefined     | 严格模式下为 undefined

fn.call(null)   //Window {0: global, window: …} undefined undefined       | 严格模式下为 null

fn.call(undefined)  //Window {0: global, window: …} undefined undefined     | 严格模式下为 undefined

fn调用了callfnthis 指向 obj,最后 fn 被执行;this 指向的值都是引用类型,在非严格模式下,不传参数或传递 null/undefinedthis 都指向 window。传递的是原始值,原始值会被包装。严格模式下,call 的一个参数是谁就指向谁

2. 热身题 2

var obj1 = {
    a: 10,
    fn: function(x) {
        console.log(this.a + x)
    }
}

var obj2 = {
    a : 20,
    fn: function(x) {
        console.log(this.a - x)
    }
}

obj1.fn.call(obj2, 20) //40

稍微变量一下,原理不变obj1.fnfnthis 指向到 obj2,最后还是执行 obj1.fn 中的函数。

二、apply 和 call 基本一致

两者唯一不同的是:apply 的除了一个this指向的参数外,第二个参数是数组[arg1, arg2...],call的第二参数是列表(arg1, arg2...)

var name = '二二'
var obj = {
    name: '林一一',
    fn: function() {
        return `${this.name + [...arguments]}`
    }
}
obj.fn.apply(window, [12, 23, 34, 56])    // "二二12,23,34,56"

apply 第二个参数接收的是数组

面试题

1. 模拟实现内置的 call(),apply()方法。

  • call 的模拟实现

模拟实现 call 需要明白 call 的原理,1. this 的指向改变,call 函数中执行调用的函数。
下面代码参考来自 讶羽大佬的

Function.prototype.myCall = function (context, ...args){
    context = context || window
    // 这里的 this 是指向 fn 的,通过 this 就可以获取 fn,context 是我们的 obj,可以直接给 obj 添加一个函数属性
    context.fn = this
    delete context.fn(...args)
    return
}

var name = '二二'
var obj = {
    name: '林一一',
}

function fn() {
    console.log(this.name, ...arguments)
}

fn.myCall(null)
fn.myCall(obj, 12, 23, 45, 567)

上面的模拟 call 其实并没有考虑基本类型的情况,原生的 call 函数也可以处理基本类型比如上面的热身1 fn.call(20, 23) 输出并不会报错。但是这里的 myCall 会直接报错,提供一个更加全面模拟 call 有兴趣的可以看看 彻底搞懂闭包,柯里化,手写代码,金九银十不再丢分!

  • apply 的模拟实现
Function.prototype.myApply = function (context, args){
    context = context || window
    context.fn = this
    delete context.fn(args)
    return
}

类似上面的模拟 call 写法

2. call 和 apply 区别

call 方法的语法和作用与 apply 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

var name = '二二'
var obj = {
    name: '林一一'
}

function fn(){
    console.log(this.name, ...arguments)
}

fn.apply(obj, [12, 34, 45, 56]) //fn(12, 23, 45, 56) 林一一   12 34 45 56

要注意的是,剩余的数组参数会以单个参数的形式传递给函数,fn.apply(obj, [12, 34, 45, 56]) ==> fn(12, 23, 45, 56)

三、bind

引用MDN的话: bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

  • 返回一个新函数,这个新函数执行时的 this 才指定到 bind 的第一个参数
  • bind 的剩余参数,传递给新的函数
  • 返回后的新函数是自我调用的

小思考

1. 上面说的这个新函数是啥?

其实这个新函数就是调用 bind 的函数,bind 调用后会将调用 bind 的函数拷贝一份返回。

一个小栗子

var name = '二二'
var obj = {
    name: '林一一'
}

function fn(){
    return `${this.name} ` + [...arguments]
}

let f = fn.bind(obj, 12, 23, 45, 67, 90)
f() // "林一一 12,23,45,67,90"

上面的新函数就是 f()f() 就是 bind 拷贝函数 fn后返回的。

2. bind 是怎么实现拷贝 fn 的?

简单地说:通过 this 的获取,再 return 回这个this获取的函数,参考 call

面试题

1. bind() 和 call()、apply() 的区别

通过 applycall 改变函数的 this 指向,他们两个函数的第一个参数都是一样的表示要
改变指向的那个对象,第二个参数,apply 是数组,而 call 则是 arg1,arg2... 这种形式。通
bind 改变 this 作用域会返回一个新的函数,这个函数不会马上执行

2. 模拟实现内置的 bind() 方法。

下面的代码来自 JavaScript深入之bind的模拟实现

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

模拟实现 api,最重要的是思维过程,这是 copy 不了的。

思考题

1. 求数组中的最大值和最小值

  • 使用 Math 的 max/min 求最大最小值
    js let arr = [12, 45, 65, 3, 23, 11, 76, 8, 9, 56, 70] let max = Math.max(...arr) // 76 let min = Math.min(...arr) // 3

  • 使用数组 sort 方法求最大最小值
    js let arr = [12, 45, 65, 3, 23, 11, 76, 8, 9, 56, 70] let list = arr.sort(function(a, b) { return b - a }) let max = list[0] // 76 let min = list[list.length - 1] // 3

  • 使用 apply 求数组最大值最小值
    js let arr = [12, 45, 65, 3, 23, 11, 76, 8, 9, 56, 70] let max = Math.max.apply(null, arr) // 76 let min = Math.max.apply(null, arr) // 3

2. 如何判断一个数组

Object.prototype.toString.call()instanceof。特别要注意 typeof 不可以判断数组类型

let arr = []
Object.prototype.toString.call(arr)

3.Object.prototype.toString.call() 为什么可以用来判断类型

因为 Object.prototype.toString() 方法会返回对象的类型字符串,输出 "[object Object]" 其中第二个 Object 是传入参数的构造函数。所以使用 call 就可以指定任意的值和结合 toString 将组成的构造函数类型返回来判断类型。同样道理换成 apply/bind 同样也可以判断

Object.prototype.toString.call('str')   // "[object String]"
Object.prototype.toString.call(123)   // "[object Number]"
Object.prototype.toString.call({})      //  "[object Object]"
Object.prototype.toString.call([])      //  "[object Array]"

Object.prototype.toString.apply({})      //  "[object Object]"
Object.prototype.toString.apply([])      //  "[object Array]"

var f = Object.prototype.toString.bind({})
f()     //  "[object Object]"
var fn = Object.prototype.toString.bind([])
fn()   //  "[object Array]"

4.使用 call() 实现将类数组转化成数组

使用 call(),[].slice / Array.prototype.slice()

let array = [12, 23, 45, 65, 32]
function fn(array){
    var args = [].slice.call(arguments)
    return args[0]
}
fn(array)   // [12, 23, 45, 65, 32]

上面利用 call 改变了 slicethis 指向 arguments 来遍历输出。

参考

JavaScript深入之call和apply的模拟实现

JavaScript深入之bind的模拟实现

MDN bind

结束

感谢阅读到这里,如果这篇文章能对你有一点启发或帮助,欢迎 star, 我是林一一,下次见。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容