前言
面试题在网络上有如海洋之深,对于同一知识点,每个人的理解也各有千秋。我们在面试中常常会遇到一个瞬息间脑海里一片空白的情况,其实这并不是因为我们不懂,而是因为我们在回答的时候缺乏一个清晰的思路。那么问题来了,我们如何能逐步唤醒自己的记忆,娓娓道来呢?
方法展开
本篇从十个JavaScript面试题案例逐步分解回答步骤,简单概括即为从一句简概的描述出发,将一个问题往自己擅长的部分引导。
比如JavaScript实现继承的问题,其实我们在准备过程中总会遇到这种多种途径解决同一个问题的情况,引导式先将自己擅长的部分提出,后加展开描述,其实剩下的就是我们相对记忆薄弱的区块,就像答题我们先将会的答完,再挖掘自己需要"争取"的另一部分,这样既保证一部分得分,又不会因为没有章法而慌张。
JavaScript
题目汇总
- JavaScript中类型转换是怎样的?
- 描述JavaScript的事件冒泡和事件捕获。
- JavaScript中的''和'='有什么区别?
- 如何在JavaScript中实现继承?
- 描述JavaScript中的闭包是什么以及它的用途。
- JavaScript中的异步编程和Promise是什么?
- 描述JavaScript中的 "hoisting"(变量提升)。
- 如何在JavaScript中检测一个变量是否是数组?
- 什么是JavaScript中的 "use strict";指令?
- 解释JavaScript中的 this 关键字。
解答篇
1. JavaScript中类型转换是怎样的?
引导式回答:
在JavaScript中,类型转换主要有两种:隐式类型转换和显式类型转换。
展开式回答:
- 隐式类型转换:当 JavaScript 遇到不同类型的值进行操作时,它会自动将这些值转换为适当的类型。
例如,当你尝试将一个字符串和一个数字相加时,JavaScript 会将数字隐式地转换为字符串,然后进行字符串连接,而不是数字加法。
js
let result = '3' + 2; // '32',而不是5
- 显式类型转换:你可以使用特定的方法来显式地将值从一种类型转换为另一种类型。
例如,你可以使用 Number()
、String()
和 Boolean()
等函数来进行显式类型转换。
js
let num = '3';
let result = Number(num) + 2; // 5,而不是'32'
2. 描述JavaScript的事件冒泡和事件捕获。
引导式回答:
事件冒泡和事件捕获是JavaScript事件处理的两种机制。
展开式回答:
- 事件冒泡(Event Bubbling):事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上转播至最不具体的元素(文档节点)。例如,如果你点击了一个内部链接,那么浏览器会先触发该链接的点击事件,然后是它父级元素的点击事件,一直冒泡到document对象。
js
document.querySelector('#inner').addEventListener('click', function() {
console.log('inner');
}, false);
document.querySelector('#outer').addEventListener('click', function() {
console.log('outer');
}, false);
因为
addEventListener
的第三个参数为false
。这意味着如果该元素内部还有其他元素,当这些内部元素被点击时,事件会从最内层元素开始,逐级向外(或者说向上)传播,直到被这个监听器捕获。
当你点击inner元素时,控制台将会打印出inner,然后是outer。
- 事件捕获(Event Capturing):事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的实际运作与其原理正好相反:最具体的节点首先接收到事件,然后再冒泡到不太具体的节点。
js
document.querySelector('#inner').addEventListener('click', function() {
console.log('inner');
}, true);
document.querySelector('#outer').addEventListener('click', function() {
console.log('outer');
}, true);
因为
addEventListener
的第三个参数是true
。这意味着,如果在这个元素内部还有其他元素,当这些内部元素被点击时,事件将从最外层元素开始,逐级向内传播,直到被这个监听器捕获。
当你点击inner元素时,控制台将会打印出outer,然后是inner。
3. JavaScript中的''和'='有什么区别?
引导式回答:
主要区别是比较过程中,是否进行类型转换。
展开式回答:
在JavaScript中,==
和===
都是比较运算符,但它们在进行比较时有一些重要的区别。
==
是等于运算符,它会进行类型转换,如果两个操作数类型不同,会试图把它们转换为同一类型,然后进行比较。
js
console.log(1 == '1'); // 输出 true,因为在比较前,字符串 '1' 被转换为了数字 1
===
是全等运算符,也被称为严格等于。它在比较两个操作数时,不仅比较它们的值,还会比较它们的类型。如果两个操作数的类型不同,它们一定不全等。
js
console.log(1 === '1'); // 输出 false,因为虽然它们的值相等,但是类型不同
因此,在进行比较时,使用===
可以避免因类型转换带来的一些意外结果,使代码更加可靠。
4. 如何在JavaScript中实现继承?
引导式回答:
原型链继承、构造函数继承、组合继承、类继承。(先说能展开回答的,不要给自己挖坑)
展开式回答:
- 原型链继承:
核心点:
Child.prototype = new Parent();
js
function Parent() {
this.name = 'parent';
}
function Child() {
this.age = 18;
}
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.name); // 输出:parent
- 构造函数继承:
核心点:
Parent.call(this);
js
function Parent() {
this.name = 'parent';
}
function Child() {
Parent.call(this);
this.age = 18;
}
var child2 = new Child();
console.log(child2.name); // 输出:parent
- 组合继承:
组合继承(又称经典继承)是构造函数继承和原型链继承的结合。
它的主要思路是使用原型链继承原型上的属性和方法,然后使用构造函数继承实例属性。
优势:
这样做既可以复用父类的方法,又可以保持每个实例的属性独立。
js
function Parent() {
this.name = 'parent';
}
Parent.prototype.sayName = function() {
console.log(this.name);
}
function Child() {
Parent.call(this);
this.age = 18;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child3 = new Child();
child3.sayName(); // 输出:parent
- 类继承:
核心点:
class Child extends Parent
js
class Parent {
constructor() {
this.name = 'parent';
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor() {
super();
this.age = 18;
}
}
let child4 = new Child();
child4.sayName(); // 输出:parent
5. 描述JavaScript中的闭包是什么以及它的用途。
引导式回答:
闭包是指有权访问另一个函数作用域中变量的函数。
展开式回答:
闭包通常用于:
- 创建私有变量和私有方法:闭包可以帮助我们封装和保护不应该被外部访问的变量和函数。
- 实现模块化:通过使用闭包,我们可以创建包含私有变量和函数的独立模块,有助于我们编写组织良好且易于管理的代码。
js
function createCounter() {
let count = 0;
return function() {
count++;
return count;
}
}
const counter1 = createCounter();
console.log(counter1()); // 输出:1
console.log(counter1()); // 输出:2
const counter2 = createCounter();
console.log(counter2()); // 输出:1
console.log(counter2()); // 输出:2
在这个例子中,createCounter
函数返回一个匿名函数,这个匿名函数可以访问到createCounter
函数的count
变量,即使createCounter
函数已经执行结束。
我们创建了两个计数器counter1
和counter2
,它们各自都维护着自己的count
状态,互不影响。
6. JavaScript中的异步编程和Promise是什么?
- 异步编程:
引导式回答:
异步编程是相较于同步编程的。
展开式回答:
异步编程是一种编程范式,它允许我们编写非阻塞的代码。在同步编程中,代码会按照一种预定的顺序执行,即每一条指令必须在下一条指令开始前完成。但在异步编程中,我们可以启动一个长时间运行的操作,然后在它完成时再继续执行代码,而不是等待它完成。这对于网络请求,读写文件,定时器等操作特别有用,因为这些操作可能需要很长时间才能完成,而我们不希望它们阻塞我们的程序执行。
- promise
引导式回答:
Promise是一种在JavaScript中处理异步操作的方法。
展开式回答:
Promise有三种状态:pending(待定)、fulfilled(已完成)和rejected(已拒绝)。
Promise的工作方式如下:
- 当你创建一个Promise时,它的状态会被设为pending。
- 然后,你可以定义当Promise完成或失败时要执行的两个回调函数。
- 当异步操作完成,Promise会变为fulfilled状态,并执行成功的回调函数。如果操作失败,Promise则变为rejected状态,并执行失败的回调函数。
- 一旦Promise的状态从pending变为fulfilled或rejected,它的状态就不能再改变,且对应的回调函数只能被调用一次。
7. 描述JavaScript中的 "hoisting"(变量提升)。
引导式回答:
变量提升是JavaScript中的一个特性,指的是变量和函数声明在代码的执行前就已经被提升到其作用域的顶部。
展开式回答:
这就意味着无论你在哪里声明变量或函数,JavaScript解释器都会把它们的声明提升到当前作用域的最顶部。需要注意的是,仅仅是声明被提升了,而初始化(赋值)则会留在原地。
例如,以下的代码片段:
js
console.log(myVar); // 输出 undefined
var myVar = 5;
console.log(myVar); // 输出 5
实际上在执行时,JavaScript解释器看到的是这样:
js
var myVar;
console.log(myVar); // 输出 undefined
myVar = 5;
console.log(myVar); // 输出 5
变量myVar的声明被提升到了顶部,但是赋值操作还是在原来的位置。这就是所谓的"hoisting"或者变量提升。 需要注意的是,let 和 const 声明的变量并不会被提升。
8. 如何在JavaScript中检测一个变量是否是数组?
引导式回答:
Array.isArray()
展开式回答:
在JavaScript中,可以使用Array.isArray()方法来检测一个变量是否是数组。这个方法会返回一个布尔值,如果变量是数组,返回true,否则返回false。
例如:
js
let arr = [1, 2, 3];
console.log(Array.isArray(arr)); // 输出 true
let notArr = "Hello, world!";
console.log(Array.isArray(notArr)); // 输出 false
在这个例子中,Array.isArray(arr)返回true,因为arr是一个数组。而Array.isArray(notArr)返回false,因为notArr不是一个数组,而是一个字符串。
其他方法:
- 使用instanceof操作符
- 使用Object.prototype.toString.call()
- ...
9. 什么是JavaScript中的 "use strict";指令?
引导式回答:
"use strict"是一个在JavaScript中用来启用严格模式的指令。
展开式回答:
当你在代码的开始添加了这个指令,那么整个脚本或者函数就会在严格模式下运行。在严格模式下,JavaScript会更加严格地执行代码。比如,它会禁止使用未声明的变量,禁止删除变量或函数,禁止重复参数名,禁止特定的语法等等。这些限制有助于我们写出更安全、更清晰的代码,并且有助于避免一些常见的错误。
10. 解释JavaScript中的 this 关键字
引导式回答:
在JavaScript中,
this
关键字是一个特殊的关键字,它引用的是函数执行的上下文。
展开式回答:
this
的值取决于函数的调用方式。这里有一些常见的情况:
- 在全局作用域中,或者在一个函数中(如果这个函数不是作为对象的方法被调用),
this
通常指向全局对象,在浏览器中这通常是window
。
js
console.log(this === window); // 输出 true
function test() {
console.log(this === window);
}
test(); // 输出 true
- 当一个函数被作为一个对象的方法调用时,
this
指向调用这个方法的对象。
js
let obj = {
prop: "Hello",
func: function() {
console.log(this === obj); // 输出 true
console.log(this.prop); // 输出 "Hello"
}
}
obj.func();
- 在一个构造函数中,
this
指向新创建的对象。
js
function Person(name) {
this.name = name;
}
let john = new Person("John");
console.log(john.name); // 输出 "John"
- 当使用
call
、apply
或bind
方法时,this
会被显式地设置为你传递给这些方法的第一个参数。
js
function greet() {
console.log(`Hello, ${this.name}`);
}
let person = {name: "John"};
greet.call(person); // 输出 "Hello, John"
需要注意的是,在箭头函数中,this
的行为和常规函数不同。箭头函数不会创建自己的this
,它会从自己的作用域链上一级继承this
。