面试题:JavaScript
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
欢迎阅读第二篇关于面试题的文章。您可以阅读上一篇文章,其中涵盖了有关 HTML、CSS 和一些无障碍设计的常见问题。
在这篇文章中,我想重点介绍一些常见的 JavaScript 问题和简单的代码测试,这些问题和测试可能会在面试中出现。以下列表包含各种类型的问题,面试官可以通过这些问题来评估你的经验水平。
什么是即时函数表达式(IIFE)?为什么要使用即时函数表达式?
IIFE 代表“立即调用函数表达式”。
使用它的主要原因是,在 JavaScript 代码中,为了确保不会覆盖任何全局变量,需要在函数内部保留一个私有作用域。
!(() => {
console.log("Hello from IIFE!");
})();
列表迭代/循环类型
- 为了
- 尽管
- 执行
- 的
- 在
解释吊装
- 提升(Hoisting)是 JavaScript 的一种机制,它将变量和函数声明在代码执行之前移动到其作用域的顶部。
- 函数表达式只有在解释器执行到该行代码时才会加载。因此,如果您尝试在函数表达式加载之前调用它,就会出错!
- 如果改为调用函数声明,则总是可以正常工作,因为在所有声明加载完毕之前,任何代码都无法调用。
hoistedFunction(); // Hello! I am defined immediately!
notHoistedFunction(); // ReferenceError: notHoistedFunction is not defined
// Function Decalration
function hoistedFunction () {
console.log('Hello! I am defined immediately!');
}
// Function Expression
const notHoistedFunction = function () {
console.log('I am not defined immediately.');
}
列出 ES6 特性
- 箭头函数
- 课程
- 模板字符串
- 解构赋值 -解构赋值语法是一种 JavaScript 表达式,它允许将数组中的值或对象的属性解包到不同的变量中。
- 默认值
- 扩展运算符——扩展语法允许在需要零个或多个参数(对于函数调用)或元素(对于数组字面量)的地方展开可迭代对象(例如数组表达式或字符串),或者在需要零个或多个键值对(对于对象字面量)的地方展开对象表达式。例如:[...iterableObj, '4', 'five', 6];
- let、const、var
列出 JavaScript 数据类型
- 数字
- 细绳
- 布尔值
- 无效的
- 不明确的
- 象征
JavaScript 的运行时环境是如何工作的?描述一下事件循环机制。JavaScript 有多少个线程?
JavaScript 运行时由以下几个部分组成:
- 堆——一块面积较大、结构化程度较低的内存区域,用于分配变量。
- 调用栈——函数调用构成帧栈的地方。
- 队列——一个待处理消息的列表。每条消息都有一个关联的函数,该函数会被调用来处理该消息。
调用栈中的函数按照“先进先出”规则执行,这意味着最上面的函数将首先执行。
Web API 提供并执行异步操作(例如 `get()`fetch或 ` get()`),因此 JS 运行时线程在等待超时或请求完成时可以保持空闲。已完成的异步操作会被放入队列,并在队列为空时推回调用栈。这意味着,如果超时时间为 0,则可能不会立即输出结果。setTimeoutsetTimeout
参考:
- https://flaviocopes.com/javascript-event-loop/
- https://www.youtube.com/watch?v=8aGhZQkoFbQ&feature=emb_title
什么是 Web Workers?
Web Worker 是一种在后台运行的脚本,页面无需等待其完成。当应用程序中运行耗时较长且开销较大的操作时,Web Worker 非常有用,因为它不会阻塞 JavaScript 运行时,从而允许用户与页面进行交互。
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
什么是闭幕式?
闭包是一个内部函数,它可以访问外部(包含)函数的变量——即作用域链。闭包有三个作用域链:
- 它可以访问自己的作用域(花括号内定义的变量)。
- 它可以访问外部函数的变量。
- 它可以访问全局变量。
const siteName = 'lukaszpietraszek.com';
function outerFunc() {
const title = 'Interview Questions';
function innerFunc() {
console.log(siteName);
console.log(title );
}
return innerFunc;
}
const myFunc = outerFunc();
myFunc();
// lukaszpietraszek.com
// Interview Questions
var 和 let 的区别
let允许您声明作用域仅限于其使用的代码块、语句或表达式的变量。var关键字可以定义全局变量,也可以定义整个函数范围内的局部变量,而不管其作用域是代码块还是函数本身。
map、filter 和 reduce 的区别
- 该
Map对象存储键值对,并记住键的原始插入顺序。Map对象按插入顺序遍历其元素——for ...of[key, value]循环每次迭代都会返回一个数组。 - map() 函数通过对输入数组中的每个元素传递一个函数来返回一个新数组。
- 该
filter()方法会创建一个新数组,其中包含所有通过所提供函数实现的测试的元素。如果没有元素通过测试,则返回一个空数组。 - 数组对象的 reduce 方法用于将数组缩减为单个值。该方法对数组的每个成员
reduce()执行reducer函数(由您提供),从而产生单个输出值。 - reducer 函数接受四个参数:
Accumulator(acc) 当前值、(cur) 索引Current Index(idx) 和Source Array(src)。reducer 函数的返回值会被赋给累加器,累加器的值会在数组的每次迭代中被记住,并最终成为最终的唯一结果值。
解释原型继承的工作原理
JavaScript 只有一个结构:对象。每个对象都有一个指向另一个对象的内部链接,称为它的原型。这个原型对象本身也有一个原型,依此类推,直到到达一个以null自身原型为核心的对象。null根据定义,`__` 对象没有原型,并且充当这个原型链的最后一个环节。
- 原型继承的核心思想是,一个对象可以指向另一个对象并继承其所有属性。
- 其主要目的是允许对象的多个实例共享公共属性,因此有了单例模式。
解释 Getter 和 Setter
- `getter` 和 `setter`分别
getter是获取特定属性值的方法和setter设置特定属性值的方法。您可以在任何预定义的核心对象或支持添加新属性的用户自定义对象上定义 `getter` 和 `setter`。定义 `getter` 和 `setter` 的语法使用对象字面量语法。 - 使用 getter 或 setter 与使用标准函数的区别在于,getter/setter 会在赋值时自动调用。因此,它看起来就像一个普通的属性,但实际上,你可以在赋值之前或之后运行额外的逻辑(或检查)。
const person = {
firstName: 'John',
lastName: 'Doe',
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(name) {
const words = name.toString().split(' ');
this.firstName = words[0] || '';
this.lastName = words[1] || '';
}
};
person.fullName = 'Mark Smith';
console.log(person.fullName); // Mark Smith
console.log(person.firstName); // Mark
console.log(person.lastName) // Smith
什么是回调函数?
JavaScript 函数可以作为参数,也可以被其他函数返回。
能够执行这种操作的函数称为高阶函数。任何作为参数传递的函数都称为回调函数。
const allUserData = [];
function logStuff(userData) {
if (typeof userData === "string") {
console.log(userData);
} else if (typeof userData === "object") {
for (const item in userData) {
console.log(`${item}: ${userData[item]}`);
}
}
}
function getInput(options, callback) {
allUserData.push(options);
if (typeof callback === "function") {
callback(options);
}
}
getInput({ firstName: "John", lastName: "Doe" }, logStuff);
// firstName: John
// lastName: Doe
什么是承诺
它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个Promise,承诺在某个时间点提供该值。
承诺处于以下状态之一:
- 待定:初始状态,既未完成也未拒绝。
- 已完成:表示操作已成功完成。
- 已拒绝:表示操作失败。
const promise = new Promise((resolve, reject) => {
if (/* everything turned out fine */) {
resolve("Stuff worked!");
} else {
reject(Error("It broke"));
}
});
promise.then(
result => {
console.log(result); // "Stuff worked!"
},
err => {
console.log(err); // Error: "It broke"
},
);
异步/等待
异步函数是对 Promise 语法的一种改进。你可以把它看作是 Promise 的语法糖,它只是让编写 Promise 变得更简单。
异步函数返回一个 Promise——如果函数返回一个值,Promise 将被解析并返回该值;但如果异步函数抛出错误,Promise 将被拒绝并返回错误值。让我们来看一个异步函数:
`await` 只能用于异步函数。`await` 关键字用于异步函数中,以确保异步函数返回的所有 Promise 都是同步的,也就是说,它们会互相等待。`await` 可以避免在 `.then()` 和 `.catch()` 中使用回调函数。在使用 `async` 和 `await` 时,返回 Promise 时需要加上 `async`,调用 Promise 时需要加上 `await`。`try` 和 `catch` 也用于获取异步函数的拒绝值。让我们用日期示例来看一下:
async function myDate() {
try {
let dateDetails = await date;
let message = await orderUber(dateDetails);
console.log(message);
} catch (error) {
console.log(error.message);
}
}
空值、未定义值和未声明值这三种变量之间有什么区别?
- 未声明的变量是指尚未声明的任何变量。控制台会对此抛出错误。
- Undefined 表示已声明但尚未赋值的变量。
- 空值是指已分配给变量的值。
什么是单例模式
单例模式允许你创建一个对象实例。如果该实例已存在,则无法创建第二个实例。此外,该对象的初始化仅在程序需要时才会发生。这是该模式最重要的两个特性。如果一个结构体缺少这两个特性,则它不是单例模式。最好将单例模式想象成一个模块(无论如何,它最终都会是一个模块),该模块封装了初始化该单例实例的整个机制。
const mySingleton = (() => {
// Instance stores a reference to the Singleton
let instance;
function init() {
// Singleton
// Private methods and variables
function privateMethod() {
console.log("I am private");
}
const privateVariable = "Im also private";
return {
// Public methods and variables
publicMethod() {
console.log("The public can see me!");
},
publicProperty: "I am also public",
};
}
return {
// Get the Singleton instance if one exists
// or create one if it doesn't
getInstance() {
if (!instance) {
instance = init();
}
return instance;
},
};
})();
// Usage:
const singleA = mySingleton.getInstance();
const singleB = mySingleton.getInstance();
console.log(singleA === singleB); // true
范围类型
JavaScript 中的作用域定义了你可以访问哪些变量。作用域分为两种:全局作用域和局部作用域。
局部作用域可以是函数作用域和代码块作用域。
“this”关键字是什么?它是如何使用的?
this总是指代一个对象。this指的是调用它自身包含的函数的对象。- 在全局上下文中,
this它指的是 window 对象;如果使用“严格模式”,则它未定义。
JavaScript编程范式有哪些?
JavaScript 是一种多范式语言,支持命令式/过程式编程,以及面向对象编程 (OOP) 和函数式编程。JavaScript 通过原型继承支持面向对象编程。
- 原型继承(也称原型、OLOO - 对象链接到其他对象)。
- 函数式编程(也称为:不可变性、纯函数、函数组合、诅咒、闭包、一等函数、lambda 表达式)。
函数式编程
函数式编程是一种编程形式,它允许你将函数作为参数传递给其他函数,也可以将它们作为返回值返回。在函数式编程中,我们以函数为中心进行思考和编码。
面向对象编程
面向对象编程的基本思想是,我们使用对象来模拟我们想要在程序中表示的现实世界的事物,和/或提供一种简单的方法来访问原本难以或不可能使用的功能。
对象可以包含相关数据和代码,这些数据和代码代表了你试图建模的事物的信息,以及你希望它具备的功能或行为。对象数据(通常也包括函数)可以整齐地存储在对象包中(官方术语是封装),对象包可以指定一个特定的名称来引用,有时也称为命名空间,这使得数据结构化和访问变得容易;对象也常被用作数据存储,可以轻松地通过网络传输。
高阶函数
高阶函数是指接收函数作为参数或将函数作为输出的函数。
const double = n => n * 2
[1, 2, 3, 4].map(double) // [ 2, 4, 6, 8 ]
什么是对象字面量语法?
对象字面量是用花括号括起来的、以逗号分隔的名称-值对列表。
对象字面量封装数据,将其包裹在一个整洁的包中。
JavaScript 类示例
class Person {
constructor(name, surname) {
this.name = name;
this.surname = surname;
}
get fullName() {
return this.name + " " + this.surname;
}
set fullName(name) {
var words = name.toString().split(" ");
this.name = words[0];
this.surname = words[1];
}
sayHello() {
console.log(`My name is ${this.name} ${this.surname}`);
}
}
确定哪些内容会记录到控制台。
// What would be logged out to the console?
var num = 50;
function logNumber() {
console.log(num);
var num = 100;
}
logNumber();
// Undefined
// When this function is ran in what order the four numbers will be logged out?
function logNumbers() {
console.log(1);
setTimeout(function(){console.log(2)}, 1000);
setTimeout(function(){console.log(3)}, 0);
console.log(4);
}
logNumbers();
// 1 4 3 2
// Determine what would be logged out to the console.
(() => {
const x = y = 100;
})();
console.log('y: ', y);
console.log('x: ', x);
// y: 100
// x is not defined
编写一个函数,检查这个数是否为质数。
质数(或称素数)是指大于 1 的自然数,它除了 1 和它本身之外没有其他正约数。
const isPrime = num => {
for(let i = 2; i < num; i++)
if(num % i === 0) return false;
return num > 1;
}
isPrime(5) //true
isPrime(6) //false
FizzBuzz
编写一个程序,打印 1 到 100 之间的数字。但是,对于 3 的倍数,打印“Fizz”而不是数字本身;对于 5 的倍数,打印“Buzz”;对于既是 3 又是 5 的倍数的数字,打印“FizzBuzz”。
// Solution 1
for (let i = 1; i <= 100; i++) {
const f = i % 3 == 0;
const b = i % 5 == 0;
console.log(f ? (b ? "FizzBuzz" : "Fizz") : b ? "Buzz" : i);
}
// Solution 2
for (let i = 1; i <= 100; i++) {
let res = "";
if (i % 3 === 0) {
res += "Fizz";
}
if (i % 5 === 0) {
res += "Buzz";
}
console.log(res || i);
}
反转字符串
编写一个函数来反转字符串
// Solution 1
function revert(string) {
const _reverted = string.split("").reverse().join("");
return _reverted;
}
console.log(revert("Lukasz")); //zsakuL
// Solution 2
function revertTwo(string) {
let _reverted = "";
for (let i = string.length - 1; i >= 0; i--) _reverted += string[i];
return _reverted;
}
console.log(revertTwo("Interview")); //weivretnI