LLwanran / front_end_studying

前端知识要点
https://llwanran.github.io/front_end_studying/
2 stars 1 forks source link

51 信用卡: apply,call,bind 的区别(一面) #54

Open LLwanran opened 4 years ago

LLwanran commented 4 years ago

面试公司: 51信用卡

面试环节: 一面

问题: 请说明 apply,call,bind 的区别及用法

LLwanran commented 4 years ago

所有准备面试的同学,乍一看这个题目,嘿嘿一笑,so easy!我觉得十个同学,有九个都能回答上来。

如果这道题满分是 10 分,我们尝试来回答一下,看看能拿几分?

初级回答(4 分)

这三个函数都是改变了当前函数的 this 指向。

此时我的内心 OS:“妥妥的!心里美滋滋,正好是我准备的题目,下一题把”!
but,此时面试官再次发问:“你能讲讲它们三个的实现原理吗?能自己实现一下这三个函数吗?”
我:.....

进阶回答(8分)

我们重点讲一下 call 的用法和源码实现,其余的 apply 和 bind 道理一样。

call

call 的基本用法

function add(c, d) {
    console.log(this.a + this.b + c + d);
}

var o = {a: 1, b: 2};

add.call(o, 3, 4);

如果我们不用 call,怎么实现这样的效果?

function add(c, d) {
    console.log(this.a + this.b + c + d);
}

var o = {a: 1, b: 2};

// add.call(o, 3, 4);

o.add = add;

o.add(3, 4);

delete o.add;

我们能看到就三步:

  1. 把函数变成 object 的一个属性。
  2. 执行这个 object 下面的函数。
  3. 删除这个 object 下的这个函数。

其实就是利用了,object 的属性的 this 指向该 object 的特性来实现的哦。

我们尝试用源码实现一下

Function.prototype.call(context, ...args)
{
    context = context || window;  // context 如果是 null,则指向 window
    context.fn = this;
    var result = context.fn(...args);
    delete context.fn;
    return result;
}

apply

apply 和 call 是一个道理

Function.prototype.apply(context, args)
{
    context = context || window;
    context.fn = this;
    var result = context.fn(...args);
    delete context.fn;
    return result;
}

bind

bind 有一点特殊,但是道理都是一个道理。

我们先写个例子看看 bind 怎么用

function add(c, d) {
    console.log(this.a + this.b + c + d);
}

var o = {a: 1, b: 2};

var bindAdd = add.bind(o, 3);
bindAdd(4);

来看看源码怎么实现

Function.prototype.bind = function (context, ...rest) {
    var self = this;
    return function F(...args) {
        return self.apply(context, rest.concat(args)); // 两次的参数 rest,args 合并到一起,作为函数的参数
    }
}

此时我的内心 OS:“哈哈哈哈哈,我好机智,准备的太全了!”!
面试官内心 OS:“还行吧,不过不要高兴的太早了。”
面试官问:你能告诉下面程序的输出是什么吗?

function add(c, d) {  
  console.log(this.a + this.b + c + d);
}
var o = {a: 1, b: 2};

var bindAdd = add.bind(o, 3);
new bindAdd(4);

再次进阶(9 分)

我们可以看到上面的程序仅仅是增加了一个new,但是它的输出变成了NaN,为什么?这是因为:" bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效"

我们再来丰富下bind的实现

Function.prototype.bind = function (context, ...rest) {
    var self = this;

    return function F(...args) {
        /*如果是 new 的,则不要之前的 context 啦*/
        if (this instanceof F) {
            return self(...rest, ...args);
        }
        return self.apply(context, rest.concat(args));
    }
}

到这里应该已经结束了,但你以为你能拿满分了?too native!

面试官继续提问了:"你能讲讲 new 的原理吗?"

我:"….."

LLwanran commented 4 years ago

其实这道题最想教的不是这道题怎么来回答,而是想说明几点:

  1. 同样一道题目,每个层级的同学答的深度都不一样。
  2. 面试官会从一个很简单的问题,层层深入,挖到你不会为止,看看你的深浅。
  3. 任何面试题,不要仅仅停留在表面,要知其然,知其所以然。

打个广告:去年写的一个面试题汇总《前端小白半年准备,成功进入BAT