5个经典JS面试题(略译)

本文略译自《5 Typical JavaScript Interview Exercises》,并作少许评注。

在Web开发领域,前端界面开发的职位已经成为业界最灸手可热的职位(之一),只要你掌握了前端开发核心技术,那么你就有了很多提高自己的薪水的机会。当然怎么证明你掌握了核心技术呢?在开启你的前端开发新职业,你得通过重重的面试。以下是前端面试中,有关JS核心技术的5个经典的面试题。我们来看看。

问题1:符号作用域(Scope)

以下代码片断的输出是什么?

(function() {
   var a = b = 5;
})();

console.log(b);

答案:上面的代码的输出是 5 。

最常见的掉坑结果是认为b是undefined ,因为看到立即调用函数(IIFE)划出了一个独立的作用域,并且使用了var定义。陷阱在于,a是var定义,而b不是。在非严格模式(strict mode),这是一个大坑,b没有声明直接初始化会被认为是全局的。如果使用了严格模式,那么这个代码会报错:

Uncaught ReferenceError: b is not defined.

所以你必须这么写:

(function() {
   'use strict';
   var a = window.b = 5;
})();

console.log(b);

P.S.这个题考的更多是JS的执行模式,不是符号作用域。有关JS执行模式请参考《Javascript 严格模式详解

问题2:当一回JS实现者——为语言创建新“native”API方法

请为语言内置的String对象创建一个新的实例方法 repeatify,这个方法的功能是“重复打印字符串的值”——接受一个重复次数的整型入参,返回入参指定次数的字串,结果类似于:

console.log('hello'.repeatify(3));

打印三次:hellohellohello.

答案:这是个“无什么用”的功能,只是演示。以下是实现的一种:

String.prototype.repeatify = String.prototype.repeatify || function(times) {
   var str = '';

   for (var i = 0; i < times; i++) {
      str += this;
   }

   return str;
};

这道题主要考你对JS(原型)继承的认识,和扩展现有类型对象功能的能力。

这道题还有一个重要考点。由于你不清楚原对象实现的细节,所以你必须有意识你的新创建有可能与现有的功能方法有冲突——可能已经存在一个叫repeatify实例方法,故必须使用一个或逻辑测试(||)来规避。

String.prototype.repeatify = String.prototype.repeatify || function(times) {/* code here */};

这个规避方案特别适用于「shim一个JS函数」,让一个新的API在旧环境中可用。

何为shim一个函数

Shim 指的是在一个旧的环境中模拟出一个新 API ,而且仅靠旧环境中已有的手段实现,以便所有的浏览器具有相同的行为。主要特征:

  • 该 API 存在于现代浏览器中;
  • 浏览器有各自的 API 或 可通过别的 API 实现;
  • API 的所有方法都被重新实现;
  • 拦截 API 调用,并提供自己的实现;
  • 是一个优雅降级。

请参考《JavaScript术语:shim 和 polyfill》https://www.html.cn/archives/8339

  • A shim is any piece of code that performs interception of an API call and provides a layer of abstraction. It isn't necessarily restricted to a web application or HTML5/CSS3.
  • A polyfill is a type of shim that retrofits legacy browsers with modern HTML5/CSS3 features usually using Javascript or Flash.

https://stackoverflow.com/questions/6599815/what-is-the-difference-between-a-shim-and-a-polyfill

问题3:符号/变量提升(Hoisting)

以下代码的运行结果是什么?为什么?

function test() {
   console.log(a);
   console.log(foo());

   var a = 1;
   function foo() {
      return 2;
   }
}

test();

答案:结果是 undefined and 2

这个结果的原因是,函数内的所有符号(包括变量和内部函数)会被“提升“(就像移动到函数的顶部),而变量还没有初始化,所以当a被访问时,它声明了但还在未定义状态。代码提升的形式类似于下面:

function test() {
   var a;
   function foo() {
      return 2;
   }

   console.log(a);
   console.log(foo());

   a = 1;
}

test();

P.S.上面对「符号提升」的解释还是比较表面的。根本原因是,编译器会扫描源代码两遍,第一遍是搜集所有符号,第二篇才使用符号进行实际编译,所以会有一种符号被提升的表象。

问题4:JS中的this是怎么用的?

以下代码的执行结果是什么,请解释你的答案:

var fullname = 'John Doe';
var obj = {
   fullname: 'Colin Ihrig',
   prop: {
      fullname: 'Aurelio De Rosa',
      getFullname: function() {
         return this.fullname;
      }
   }
};

console.log(obj.prop.getFullname());

var test = obj.prop.getFullname;

console.log(test());

答案: Aurelio De Rosa and John Doe. 原因是,在JS中,所有函数都有一个执行上下文,这个上下文就是则this指定的,但是这个this并不是固定的,在JS中,函数的this取决于函数被谁调用,不在于函数定义在哪里(因为函数可以作对象对被传递引用)。

所以,上面代码中,第一个getFullname()被调用时是在obj.prop 上调用的,所以返回的是prop对象上的fullname;而第二个调用转移于一个全局变量test上的,众所周知,全局变量是全局window对象的属性,所在第二个getFullname()的this引用的是window对象。

P.S.这段演示代码相当枯燥,纯粹为演示this的特性,所有符号没有任何实质性意义,这是枯燥的原因之一;另外,对象字面量在内存创建了什么,这对初学JS的人造成一定的困难。

问题5:call() and apply() 使用和区别

在前一个问题里,如果原意是想打印出 Aurelio De Rosa ,怎么修正?

答案:使用Function的实例方法call()apply() 显式指定函数的上下文对象。例如:

console.log(test.call(obj.prop));

call和apply区别在于,call是在apply之上的语法糖(call较appy友好),除了第一个参数都是函数上下文对象,call罗列多个数据参数,apply则是一个参数数组。

裸男
Nakeman.cn 2023 Build by Gatsby and Tailwind, Deploy on Netlify.