前端面试题

1,055 阅读8分钟

某上市公司前端面试流程可以完善如下:

  1. 简历投递筛选:可以通过直接投递、猎头推荐或内部员工推荐的方式进行简历投递。简历通过初筛后(建议简历要写的尽可能漂亮一些,懂得都懂......),HR会联系候选人了解基本情况。

  2. 笔试或线上评测:现场笔试大概四五十分钟,时间基本足够的,我是现场笔试,线上具体情况不知。

  3. 技术面试:技术面试可能会涉及到具体的开发技术栈相关的问题,如JS数组的常用方法(foreach、map、filter、reduce等)、闭包、JS事件循环机制、。

  4. 专业面试:通过笔试或者线上测评后,会进入专业面试环节,面试官可能会根据简历中的项目相关问题进行详细询问。

  5. HR面试:HR面试环节,HR会询问候选人的个人情况、实习经历、职业规划等,并讨论薪资待遇问题。

  6. 综合评估:面试结束后,公司会对候选人进行综合评估。

  7. 背调+体检:在某些情况下,公司可能会进行背景调查和体检。

  8. Offer发送:然后就是HR提offer审批流程,走完审批会向候选人发送Offer。

  9. 入职:接受offer并上传入职资料,候选人正式入职。

g02_blue.png以上为本人前端面试相对完整的流程。下面也简单分享下一些笔试面试题,可能不是原题,但具体的知识点大概就是这些,可以当作参考。

选择题:

  • 主要涉及以下知识点

    • 声明变量的关键字var/let/const, 各自特点和区别
    • var变量提升
    • 闭包。
    • 原型链。
    • 箭头函数。
    • 作用域链
    • this指向

1. 关于varletconst的变量声明,以下代码的输出是什么?

var a = 10;
let b = 20;
const c = 30;

if (true) {
  var a = 5;
  let b = 6;
  const c = 7;
  console.log(a + b + c); // ?
}

A. 10 + 20 + 30 B. 5 + 6 + 7 C. 5 + 20 + 30 D. 10 + 6 + 7

答案:B 解析:var声明的变量a被提升并重新赋值为5,而letconst声明的变量bc不会提升,它们只在它们所在的块级作用域内有效,所以bc的值分别是6和7。

2. 下列关于JavaScript变量提升的代码,输出是什么?

console.log(foo); // ?
var foo = 1;

A. undefined B. 1 C. ReferenceError D. null

答案:A 解析:var声明的变量会发生变量提升,但是不会提升赋值,所以当尝试打印未赋值的foo时,会是undefined

3. 关于JavaScript闭包,以下代码的输出是什么?

function outer() {
  var x = 10;
  function inner() {
    console.log(x);
  }
  inner();
}
outer(); // ?

A. undefined B. 10 C. ReferenceError D. null

答案:B 解析:闭包允许内部函数inner访问外部函数outer的变量x,所以输出是10。

4. JavaScript原型链的以下代码输出是什么?

function Person(name) {
  this.name = name;
}
Person.prototype.sayName = function() {
  console.log(this.name);
};
var person = new Person('Kimi');
person.sayName(); // ?

A. undefined B. Kimi C. Person D. ReferenceError

答案:B 解析:sayName方法通过this关键字访问了Person实例的name属性,输出是Kimi

5. 下列关于箭头函数的代码,输出是什么?

const add = (a, b) => a + b;
console.log(add(1, 2)); // ?

A. 3 B. undefined C. "12" D. ReferenceError

答案:A 解析:箭头函数没有自己的this,它们会捕获其所在上下文的this值,这里this不是需要的,因为add函数只是简单地返回两个参数的和。

6. 关于JavaScript作用域链的以下代码,输出是什么?

var x = 10;
function test() {
  var x = 20;
  function doTest() {
    var x = 30;
    console.log(x);
  }
  doTest();
}
test(); // ?

A. 10 B. 20 C. 30 D. undefined

答案:C 解析:doTest函数访问了它自己的局部变量x,其值为30。

7. 下列关于this指向的代码,输出是什么?

function showThis() {
  console.log(this);
}
showThis.call(window); // ?

A. window B. undefined C. showThis D. null

答案:A 解析:call方法显式地设置了this的值,这里this被设置为window对象。

填空题:

  • 主要也是关于作用域链、闭包等知识点 以下是一些关于作用域链和闭包的填空题,结合代码:

1. 下面代码的输出是______________________

function outerFunction() {
  var outerVar = "I am outer";
  function innerFunction() {
    var innerVar = "I am inner";
    console.log(outerVar + " " + innerVar); // ?
  }
  innerFunction();
}

outerFunction();

填空:上述代码的输出是 "I am outer I am inner"。这是因为innerFunction通过作用域链可以访问到其外部函数outerFunction的变量outerVar

2. 下面代码的输出是______________________

function createClosure() {
  var secret = "I am a secret";
  return function() {
    console.log(secret); // ?
  };
}

var closure = createClosure();
closure();

填空:上述代码的输出是 "I am a secret"。这是因为返回的函数是一个闭包,它捕获了createClosure函数的词法环境,包括变量secret

3. 下面代码的输出是______________________

function foo() {
  if (typeof bar === "undefined") {
    var bar = "Local variable";
  }
  console.log(bar);
}
foo();

填空:上述代码的输出是 "Local variable"。这是因为barfoo函数的作用域中被声明,且变量提升只提升了声明,没有提升赋值。

4. 闭包和循环填空题

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i); // ?
  }, 100);
}

填空:上述代码的输出是 "5" 五次。这是因为setTimeout回调函数中的i是被闭包捕获的,且由于var的函数作用域特性,所有的回调函数共享同一个i

5. 下面代码的输出是______________________

function bindContext(someContext) {
  let outerStr = 'nav'
  return function() {
    console.log(outerStr === someContext); // ?
  };
}

const boundContext = bindContext('nav');
boundContext();

填空:上述代码的输出是 true。这是因为bindContext函数返回了一个闭包,这个闭包捕获了bindContext被调用时的outerStr值。

6. 作用域链和全局变量填空题

var myVar = "I am global";

function outerFunction() {
  myVar = "I am outer";

  function innerFunction() {
    var myVar = "I am inner";
    console.log(myVar); //?
  }

  innerFunction();
}

outerFunction();

填空:上述代码的输出是 "I am inner"。这是因为myVar变量在innerFunction函数的作用域内被声明,即使在outerFunction语句块中重新赋值,也不会影响innerFunction内部的myVar变量。

7. 下面代码的输出是______________________

function generateFunctions() {
  var result = [];
  for (var i = 0; i < 5; i++) {
    result.push(function() {
      return i; // ?
    });
  }
  return result;
}

var functions = generateFunctions();
functions[0]();

填空:上述代码的输出是 5。这是因为所有的回调函数共享同一个i变量,而这个变量在循环结束时被赋值为 5。

代码实现题目

1、要统计一个字符串中出现最多的字符及其个数:

function findMostFrequentChar(str) {
    // 创建一个对象用于存储每个字符的出现次数
    let charCount = {};

    // 遍历字符串,将每个字符的出现次数记录到对象中
    for (let char of str) {
        charCount[char] = (charCount[char] || 0) + 1;
    }

    // 初始化变量存储出现最多的字符和次数
    let maxChar = '';
    let maxCount = 0;

    // 遍历字符出现次数的对象,找出出现次数最多的字符
    for (let char in charCount) {
        if (charCount[char] > maxCount) {
            maxChar = char;
            maxCount = charCount[char];
        }
    }

    return { maxChar, maxCount };
}

// 测试
let str = "abbbccddeeeffgggghhh";
let result = findMostFrequentChar(str);
console.log(`出现最多的字符是 '${result.maxChar}',共出现了 ${result.maxCount} 次。`);

代码解释:

  1. charCount 对象用于记录每个字符的出现次数。
  2. 遍历字符串 str,将每个字符的出现次数记录在 charCount 中。
  3. 遍历 charCount 对象,找到出现次数最多的字符,并更新最大值。
  4. 返回最多字符和其出现次数。

2、实现一个JS的继承(可以自由发挥,可以使用ES5的组合式继承,也可以使用ES6的语法糖)

在ES6中,我们可以使用class关键字和extends关键字来实现继承。以下是一个使用ES6语法糖实现继承的示例:

// 父类(基类)
class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

// 子类(派生类)
class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类的constructor方法
    this.breed = breed;
  }

  speak() {
    console.log(`${this.name} barks.`);
  }

  // 子类特有的方法
  fetch() {
    console.log(`${this.name} fetches the ball.`);
  }
}

// 使用
const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak(); // 输出:Buddy barks.
dog.fetch(); // 输出:Buddy fetches the ball.

在这个例子中:

  1. Animal类是一个父类,它有一个speak方法。
  2. Dog类通过extends关键字继承自Animal类。
  3. Dog类的构造函数中,使用super(name)调用了父类的构造函数,这是必要的,因为子类的构造函数需要确保父类的构造函数被执行。
  4. Dog类覆盖了父类的speak方法,并添加了一个新方法fetch

这样,Dog类就继承了Animal类的属性和方法,并且还可以有自己的特定属性和方法。

3、将多端英文句子处理成每个单词首字母大写

如果句子包含多行,并且需要将每行中的每个单词首字母大写,可以使用以下函数来处理:

function capitalizeText(text) {
  // 将文本按行分割成数组
  const lines = text.split('\n');
  // 处理每一行,将每个单词的首字母大写
  const capitalizedLines = lines.map(line => 
    line.split(' ').map(word => 
      word.charAt(0).toUpperCase() + word.slice(1)
    ).join(' ')
  );
  // 将处理后的行重新组合成一个字符串,并用换行符连接
  return capitalizedLines.join('\n');
}

// 使用示例
const text = `hello world,
this is kimi.
how are you today?`;
const capitalizedText = capitalizeText(text);
console.log(capitalizedText);

输出将会是:

Hello World,
This Is Kimi.
How Are You Today?

在这个函数中:

  1. text.split('\n')将输入的多行文本按照换行符分割成数组。
  2. map(line => line.split(' ').map(...).join(' '))对每一行进行处理,将每个单词的首字母大写。
  3. join('\n')将处理后的行重新组合成一个字符串,并用换行符连接。