前端面试题:Call的用法及实现

更新日期: 2022-07-27阅读: 731标签: 面试题作者: 前端西瓜哥

大家好,我是前端西瓜哥。

我之前写了一篇手写 bind 的文章,里面直接使用了原生 call 方法。

有读者说他面试的时候这个 call 也要求自己实现的。

那我们今天来手写 call。apply 的实现也是一样,只是调用形式有点区别。

call 的用法

我们先看看 Function.prototype.call() 的用法。

call() 可以修改函数调用时 this 的指向,其余参数则会作为原函数的参数。

call 接收的参数:

  1. 第一个参数 thisArg。代表 this 将会被指向的值。如果不是对象,也会通过 Object() 方法转换为对象。如果是 null 或 undefined,this 则会指向全局对象(即 window 或 global),或在严格模式( "use strict;" )下,保持 undefined 或 null;
  2. 其余参数。第二个往后的参数则会传入到原函数中。

例子:

function sum(num1, num2) {
  return this.val + num1 + num2;
}
const obj = { val: 1 };
sum.call(obj, 2, 3);  // 6

上面代码中,this 指向了 obj。

Function.prototype.apply 也是类似,但它的参数是以数组的形式存在的。上面的 call 写法等价于:

sum.call(obj, [2, 3]);

call 的实现

JS 函数中的 this 指向是在运行时决定的,里面的规则比较多,但其中有一条是:

如果是通过 obj.fn() 执行时,this 会指向前面的 obj 对象。

那我们只要将传入对象和原方法进行拼接,拼成上面这个  对象.方法 的形式,执行时,this 就能乖乖指向我们传入的 thisArg 了。

实现如下:

Function.prototype.myCall = function(thisArg, ...args) {
  const context = Object(thisArg) || window;
 // 构造唯一 key
  const fn = Symbol();
  // 组装成"对象.方法"形式并调用,来改变 this 
  context[fn] = this;
  const ret = context[fn](...args);
  // 删掉临时加的 key,复原 thisArg
  delete context[fn];
  return ret;
}

这里我们用 Symbol() 创建了一个唯一的 key,是为了防止覆盖掉 thisArg 原有的同名属性。

执行完后,记得将这个 key 移除掉,防止污染 thisArg 对象。

如果面试官要你用 ES5 实现,那会复杂很多,我这里也给出实现吧。

在这之前,我们先来学点前置知识。

判断是否为严格模式

var strict = (function() { return !this })();

利用了严格模式下,如果没有指定 this(通过 bind、call、前面带对象等方式),就会得到 undefined 的机制。如果是非严格模式,this 会拿到全局变量。

fn(...args) 的 ES5 实现

ES6 的扩展运算符  ... 能够将数组 args,进行拆分按顺序放到函数中。

const args = [4, 5, 6];
fn(...args);
// 等价于
fn(4, 5, 6);

那我们用 ES5,也能将数组拆分成一个参数塞到函数中吗?

可以,但我们要用一点奇技淫巧:Function 方法。

Function 方法用得比较少。它可以在运行时创建一个函数,最后一个参数是函数体内容,前面的参数则是函数的参数。

const sum = new Function('a', 'b', 'return a + b');
sum(2, 6) // 8

fn(...args) 的 ES5 实现为:

function construct(fn, args) {
  var list = [];
  for (var i = 0; i < args.length; i++) {
    list[i] = 'a[' + i + ']';
  }
  var f = new Function('fn', 'a', 'return fn(' + list.join(', ') + ')');
  return f(fn, args);
}

Function 方法可以根据参数长度,动态生成  new Function('fn', 'a', 'return fn(a[0], a[1])') 形式的函数,来实现类似扩展运算符的效果。

还有种写法是用 eval,也能根据字符串动态生成可执行代码。

function construct(fn, a) {
  var list = [];
  for (var i = 0; i < a.length; i++) {
    list[i] = 'a[' + i + ']';
  }
  return eval('fn(' + list.join(', ') + ')');
}

但这种封装成一个函数的写法,会有 this 隐式丢失问题。比如执行 construct(dog.bark, ['bark!']),执行时 this 将不再指向对象 dog。

关于 this 的指向问题还是比较复杂的,以后我会专门写一篇文章来讲解 this。

call 的 ES5 实现

Function.prototype.myCall = function(thisArg/*, ...args */) {
  var context = Object(thisArg) || window;
  context.fn = this;
  // 偷懒用了 Array.prototype.slice + 原生 call
  // 请读者自行实现 slice
  var a = Array.prototype.slice.call(arguments, 1);
  var list = [];
  for (var i = 0; i < a.length; i++) {
    list[i] = 'a[' + i + ']';
  }
  var ret = eval('context.fn(' + list.join(',') + ')');
  delete context.fn; // 复原
  return ret;
}

为了不被干扰,上面的代码实现 忽略掉了一些细节。

  • 这里我没有用前面实现的 construct 方法,因为会丢失 this,所以直接用了 eval。
  • slice 请自行实现,不能用  Array.prototype.slice.call ,因为用了原生的 call。
  • 我们用了一个字符串  'fn' 来临时挂载函数,可能会和 thisArg 上的属性名冲突,但 ES5 又不能用 Symbol,这种情况下。更好的做法是生成一个随机的长字符串,用 hasOwnProperty 判断对象是否存在该属性,如果不存在就使用它。
  • this 不可调用时(即不是函数时),要抛出错误。

另外我的实现,没有考虑严格模式。严格模式下,如果 thisArg 是 undefined 或 null,直接执行原函数就行了,不需要拼装成  obj.fn 形式。

结尾

手写 call,核心在于通过另一种修改 this 指向的方式: obj.fn() 执行时 this 会指向 obj 对象。

手写 apply 也是一样的逻辑,还能少写一个 slice 方法。

来源:https://www.toutiao.com/article/7119813067956945411

链接: https://www.fly63.com/article/detial/11950

史上最全的Javascript面试题总结(内附答案)

近年来,从事JavaScript的程序员越来越多,JavaScript的曝光率也越来越高,如果你想转行试试JavaScript,不妨收下这份面试题及答案,没准用得上。当然,如果针对这些问题,你有更棒的答案,欢迎移步至评论区。

高级前端面试题汇总

面试的公司分别是:阿里、网易、滴滴、今日头条、有赞、挖财、沪江、饿了么、携程、喜马拉雅、兑吧、微医、寺库、宝宝树、海康威视、蘑菇街、酷家乐、百分点和海风教育。以下是面试题汇总

web前端常见的面试题,基础知识点

web前端常见的面试题:包括:HTML 常见题目、CSS类的题目、JavaScript类的题目、面试官爱问的问题。原来公司工作流程是怎么样的,如何与其他人协作的?如何夸部门合作的?你遇到过比较难的技术问题是?你是如何解决的?

前端面试题汇总(主要为Vue)

毕业之后就在一直合肥小公司工作,没有老司机、没有技术氛围,在技术的道路上我只能独自摸索。老板也只会画饼充饥,前途一片迷茫看不到任何希望。于是乎,我果断辞职,在新年开工之际来到杭州,这里的互联网公司应该是合肥的几十倍吧。。。。

js常见面试题

javascript的typeof返回哪些数据类型;例举3种强制类型转换和2种隐式类型转换?split() join() 的区别; 数组方法pop() push() unshift() shift();IE和标准下有哪些兼容性的写法

Js字符串类面试题

解析 URL Params 为对象;模板引擎实现;转化为驼峰命名;查找字符串中出现最多的字符和个数;字符串查找请使用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中

35道必须要清楚的 React面试题

虚拟 DOM (VDOM)是真实 DOM 在内存中的表示。UI 的表示形式保存在内存中,并与实际的 DOM 同步。这是一个发生在渲染函数被调用和元素在屏幕上显示之间的步骤,整个过程被称为调和。函数组件和类组件当然是有区别的

23 个 Vue.js 初级面试题

使用渐进式框架的代价很小,从而使现有项目(使用其他技术构建的项目)更容易采用并迁移到新框架。 Vue.js 是一个渐进式框架,因为你可以逐步将其引入现有应用,而不必从头开始重写整个程序。

AJAX原理及常见面试题

AJAX 即 Asynchronous Javascript And XML(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。AJAX 是一种用于创建快速动态网页的技术。它可以令开发者只向服务器获取数据(而不是图片,HTML文档等资源)

12道vue高频原理面试题,你能答出几道?

本文分享 12 道 vue 高频原理面试题,覆盖了 vue 核心实现原理,其实一个框架的实现原理一篇文章是不可能说完的,希望通过这 12 道问题,让读者对自己的 Vue 掌握程度有一定的认识(B 数),从而弥补自己的不足,更好的掌握 Vue

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!