33道面向初中级前端的基础面试题(持续更新中),鬼知道我经历了啥

77 阅读18分钟

总结

为了帮助大家更好温习重点知识、更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

内容包括html,css,JavaScript,ES6,计算机网络,浏览器,工程化,模块化,Node.js,框架,数据结构,性能优化,项目等等。

包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。

前端面试题汇总

JavaScript

性能

linux

开源分享:docs.qq.com/doc/DSmRnRG…

==是抽象相等运算符,而===是严格相等运算符。==运算符是在进行必要的类型转换后,再比较。===运算符不会进行类型转换,所以如果两个值不是相同的类型,会直接返回false。使用==时,可能发生一些特别的事情,例如:

1 == '1'; // true

1 == [1]; // true

1 == true; // true

0 == ''; // true

0 == '0'; // true

0 == false; // true

如果你对=====的概念不是特别了解,建议大多数情况下使用===

3.箭头函数和普通函数有什么区别

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象,用call apply bind也不能改变this指向

  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

  • 箭头函数没有原型对象prototype

4.白屏时间

白屏时间是指浏览器从输入网址,到浏览器开始显示内容的时间。

Performance 接口可以获取到当前页面中与性能相关的信息,该类型的对象可以通过调用只读属性 Window.performance 来获得。

performance.timing.navigationStart 是一个返回代表一个时刻的 unsigned long long 型只读属性,为紧接着在相同的浏览环境下卸载前一个文档结束之时的 Unix 毫秒时间戳。如果没有上一个文档,则它的值相当于 PerformanceTiming.fetchStart。

所以将以下脚本放在 </head> 前面就能获取白屏时间。

参考资料:

5.当你在浏览器输入一个地址后发生了什么

当···时发生了什么?

6.页面大量图片,如何优化加载,优化用户体验

  1. 图片懒加载。在页面的未可视区域添加一个滚动事件,判断图片位置与浏览器顶端的距离与页面的距离,如果前者小于后者,优先加载。

  2. 如果为幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。

  3. 如果图片为css图片,可以使用CSSsprite,SVGsprite等技术。

  4. 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩的特别厉害的缩略图,以提高用户体验。

  5. 如果图片展示区域小于图片的真实大小,应在服务器端根据业务需要先进行图片压缩,图片压缩后大小与展示一致。

7.js网络请求性能优化之防抖与节流

防抖(debounce)

在函数需要频繁触发时,只有当有足够空闲的时间时,才执行一次。就好像在百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发。

节流(thorttle)

预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。就好像你在淘宝抢购某一件限量热卖商品时,你不断点刷新点购买,可是总有一段时间你点上是没有效果,这里就用到了节流,就是怕点的太快导致系统出现bug。

区别

在发生持续触发事件时,防抖设置事件延迟并在空闲时间去触发事件,而节流则是隔一定的时间触发一次。

一个简单的防抖示例

let timer

input.on('input', () => {

clearTimeout(timer)

// 停止输入 500 毫秒后开始搜索

timer = setTimeout(() => {

// 搜索

}, 500)

})

一个简单的节流示例

let isClick = false

button.on('click', () => {

if (isClick) return

isClick = true

// 其他代码。。。

// 每 10 秒只允许点击一次

setTimeout(() => {

isClick = false

}, 10000)

})

参考资料:

8.如何做到修改url参数页面不刷新

HTML5引入了 history.pushState()history.replaceState() 方法,它们分别可以添加和修改历史记录条目。

let stateObj = {

foo: "bar",

};

history.pushState(stateObj, "page 2", "bar.html");

复制代码

假设当前页面为 foo.html,执行上述代码后会变为 bar.html,点击浏览器后退,会变为 foo.html,但浏览器并不会刷新。 pushState() 需要三个参数: 一个状态对象, 一个标题 (目前被忽略), 和 (可选的) 一个 URL. 让我们来解释下这三个参数详细内容:

  • 状态对象 — 状态对象 state 是一个 JavaScript 对象,通过 pushState () 创建新的历史记录条目。无论什么时候用户导航到新的状态,popstate 事件就会被触发,且该事件的 state 属性包含该历史记录条目状态对象的副本。

状态对象可以是能被序列化的任何东西。原因在于 Firefox 将状态对象保存在用户的磁盘上,以便在用户重启浏览器时使用,我们规定了状态对象在序列化表示后有640k的大小限制。如果你给 pushState() 方法传了一个序列化后大于 640k 的状态对象,该方法会抛出异常。如果你需要更大的空间,建议使用 sessionStorage 以及 localStorage.

  • 标题 — Firefox 目前忽略这个参数,但未来可能会用到。传递一个空字符串在这里是安全的,而在将来这是不安全的。二选一的话,你可以为跳转的 state 传递一个短标题。

  • URL — 该参数定义了新的历史URL记录。注意,调用 pushState() 后浏览器并不会立即加载这个 URL,但可能会在稍后某些情况下加载这个 URL,比如在用户重新打开浏览器时。新URL不必须为绝对路径。如果新URL是相对路径,那么它将被作为相对于当前 URL 处理。新 URL 必须与当前URL同源,否则 pushState() 会抛出一个异常。该参数是可选的,缺省为当前 URL。

参考资料:

9.请用js去除字符串空格

去除所有空格

str.replace(/\s/g, '')

去除两边空格

str.replace(/^\s+|\s+$/g, '')

// 原生方法

str.trim()

10.创建对象有几种方法

  • 字面量

const obj = {a: 1}

  • 构造函数

function Obj(val) {

this.a = val

}

const obj = new Obj(1)

  • Object.create

const obj = Object.create({a: 1})

11.null和undefined的区别

null 表示一个对象是“没有值”的值,也就是值为“空”

undefined 表示一个变量声明了没有初始化(赋值)

undefinednull 在if语句中,都会被自动转为false

undefined 不是一个有效的JSON,而 null

undefined 的类型(typeof)是 undefined

null 的类型(typeof)是 object

Javascript将未赋值的变量默认值设为 undefined

Javascript从来不会将变量设为 null。 它是用来让程序员表明某个用var声明的变量时没有值的

12.异步求和

要求

假设有一台本地机器,无法做加减乘除运算,因此无法执行 a + b、a+ = 1 这样的 JS 代码,然后我们提供一个服务器端的 HTTP API,可以传两个数字类型的参数,响应结果是这两个参数的和,这个 HTTP API 的 JS SDK(在本地机器上运行)的使用方法如下:

asyncAdd(3, 5, (err, result) => {

console.log(result); // 8

});

模拟实现:

function asyncAdd(a, b, cb) {

setTimeout(() => {

cb(null, a + b);

}, Math.floor(Math.random()*100))

}

现在要求在本地机器上实现一个 sum 函数,支持以下用法:

(async () => {

const result1 = await sum(1, 4, 6, 9, 1, 4);

const result2 = await sum(3, 4, 9, 2, 5, 3, 2, 1, 7);

const result3 = await sum(1, 6, 0, 5);

console.log([result1, result2, result3]); // [25, 36, 12]

})();

要求 sum 能在最短的时间里返回以上结果

实现

function asyncAdd(a, b, cb) {

setTimeout(() => {

cb(null, a + b);

}, Math.floor(Math.random()*100))

}

function sum(...args) {

const result = []

function _sum(resolve, reject) {

new Promise((r, j) => {

let a = args.pop()

let b = args.pop()

a = a !== undefined? a : 0

b = b !== undefined? b : 0 // 如果访问的元素超出了数组范围,则转为 0

asyncAdd(a, b, (err, res) => {

if (err) j(err)

r(res)

})

if (args.length) {

_sum(resolve, reject)

}

})

.then(val => {

result.push(val)

setTimeout(() => {

if (args.length <= 0) {

resolve(sum(...result))

}

}, 100)

})

}

return new Promise((resolve, reject) => {

if (!args || !args.length) resolve(0)

if (args.length == 1) resolve(args[0])

_sum(resolve, reject)

})

}

(async () => {

const result1 = await sum(1, 4, 6, 9, 1, 4)

const result2 = await sum(3, 4, 9, 2, 5, 3, 2, 1, 7)

const result3 = await sum(1, 6, 0, 5)

console.log([result1, result2, result3]) // [25, 36, 12]

})()

13.CommonJS,ES module 是什么,有什么区别?

它们都是一种模块规范,例如 Node 使用的就是 CommonJS 规范。ES module 则是语言标准上的模块规范。

区别:

  1. CommonJS 模块使用 require()module.exports,ES6 模块使用 importexport

  2. CommonJS 模块输出的是一个值的浅拷贝,ES6 模块输出的是值的引用。

  3. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

  4. CommonJS 模块的 require() 是同步加载模块,ES6 模块的 import 命令是异步加载,有一个独立的模块依赖的解析阶段。

  5. ES6 模块之中,顶层的 this 指向 undefined;CommonJS 模块的顶层 this 指向当前模块,

  6. 对于循环加载的处理方法不同

第 3 个差异是因为 CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。

而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

参考资料:

14.preload和prefetch

preload

preload<link> 标签 rel 属性的属性值,同时需要配合 as 属性使用。

as 指定将要预加载的内容的类型,使得浏览器能够:

  1. 更精确地优化资源加载优先级。

  2. 匹配未来的加载需求,在适当的情况下,重复利用同一资源。

  3. 为资源应用正确的内容安全策略。

  4. 为资源设置正确的 Accept 请求头。

看一下这个示例:

这种做法将把 <link> 标签塞入一个预加载器中。

这个预加载器在不阻塞页面 onload 事件的情况下,去加载资源。

我们可以通过以下两个示例来作一个对比:

Document

上面这个示例从加载到触发 onload 事件需要大概 1400 ms 的时间。再看一下使用 preload 预加载的时间:

window.onload = () => {

console.timeEnd('load') // load: 10.8818359375ms

}

用 preload 来加载资源,只需要 10 ms 就触发了 onload 事件。

说明同样是下载文件,使用 preload 不会阻塞 onload 事件。

prefetch

prefetchpreload 不同,使用 prefetch 属性指定的资源将在浏览器空闲时间下下载。

在资源的请求头如果发现有下面这个属性,就代表它是通过 prefetch 加载的:

purpose: prefetch

另外,空闲时间是如何确定、如何获取的,目前还没有相关 API。

15.preload 和 defer 的区别

preload 和 defer 的相同点是异步下载。那它们的不同点是什么呢?

preload 下载的资源只有在遇到同样的 script 标签时,才会执行对应的脚本。例如下面预加载的 vue.js

只有在遇到下面的标签时,才会执行加载的 vue.js

defer 则是异步下载资源,在所有元素解析完成后,触发 DOMContentLoaded 事件前执行。

16.window.onload 和 DOMContentLoaded 的区别

当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load事件。

它与 DOMContentLoaded不同,当纯HTML被完全加载以及解析时,DOMContentLoaded 事件会被触发,而不必等待样式表,图片或者子框架完成加载。

17.Object 与 Map 的区别

  1. Object 只能选择字符、数值、符号作为 key,Map 则可以使用任何类型的数据作为 key。

  2. Map 实例会维护键值对的插入顺序,因此可以根据插入顺序执行迭代操作。Chrome Opera 中使用 for-in 语句遍历 Object 属性时会遵循一个规律:它们会先提取所有 key 的 parseFloat 值为非负整数的属性,然后根据数字顺序对属性排序首先遍历出来,然后按照对象定义的顺序遍历余下的所有属性。其它浏览器则完全按照对象定义的顺序遍历属性。

选择 Object 还是 Map

对于多数Web开发任务来说,选择 Object 还是 Map 只是个人偏好问题,影响不大。不过,对于在乎内存和性能的开发者来说,对象和映射之间确实存在显著的差别。

1. 内存占用

Object 和 Map 的工程级实现在不同浏览器间存在明显差异,但存储单个键/值对所占用的内存数量都会随键的数量线性增加。批量添加或删除键/值对则取决于各浏览器对该类型内存分配的工程实现。

不同浏览器的情况不同,但给定固定大小的内存, Map 大约可以比 Object 多存储50%的键/值对。

2. 插入性能

向 Object 和 Map 中插入新键/值对的消耗大致相当,不过插入Map 在所有浏览器中一般会稍微快一点儿。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。

如果代码涉及大量插入操作,那么显然 Map 的性能更佳。

3. 查找速度

与插入不同,从大型 Object 和 Map 中查找键/值对的性能差异极小,但如果只包含少量键/值对,则 Object 有时候速度更快。在把 Object 当成数组使用的情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效的布局。

这对 Map 来说是不可能的。对这两个类型而言,查找速度不会随着键/值对数量增加而线性增加。如果代码涉及大量查找操作,那么某些情况下可能选择 Object 更好一些。

4. 删除性能

使用 delete 删除 Object 属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。为此,出现了一些伪删除对象属性的操作,包括把属性值设置为 undefined 或 null 。但很多时候,这都是一 种讨厌的或不适宜的折中。

而对大多数浏览器引擎来说, Map 的 delete() 操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择 Map 。

参考资料:

18.为什么 WeakMap 和 WeakSet 的键只能使用对象?

是为了保证只有通过键对象的引用来取得值。

const m = new WeakMap()

m.set({}, 100) // 由于 {} 没有在其他地方引用,所以在垃圾回收时,这个值也会被回收。

const a = {}

m.set(a, 100) // 如果使用这种方式,则不会被回收。因为 {} 有 a 变量在引用它。

a = null // 将 a 置为空后,m 里的值 100 在垃圾回收时将会被回收。

如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。

所以这句话的意思很明确:

const a = {} // 在创建对象时,分配了一块内存,并把这块内存的地址传给 a

m.set(a, 100) // 执行 set 操作时,实际上是将 a 指向的内存地址和 100 关联起来

const a = 'abc' // 由于基本数据类型在传递时,传递的是值,而不是引用。

m.set(a, 100) // 所以执行 set 操作时,实际上是将新的 'abc' 和 100 关联起来,而不是原来 a 变量指向的那个。

// 那这样就会有问题,m 里存储的永远是没有被引用的键,随时都会被回收。

参考资料:

HTML


19.HTML5语义化

什么是语义化?就是用合理、正确的标签来展示内容,比如h1~h6定义标题。

好处

  • 易于用户阅读,样式丢失的时候能让页面呈现清晰的结构。

  • 有利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权重。

  • 方便其他设备解析,如盲人阅读器根据语义渲染网页

  • 有利于开发和维护,语义化更具可读性,代码更好维护,与CSS3关系更和谐。

20.为什么最好把 CSS 的<link>标签放在<head></head>之间?为什么最好把 JS 的<script>标签恰好放在</body>之前,有例外情况吗?

<link>放在<head>

这种做法可以让页面逐步呈现,提高了用户体验。将样式表放在文档底部附近,会使许多浏览器(包括 Internet Explorer)不能逐步呈现页面。一些浏览器会阻止渲染,以避免在页面样式发生变化时,重新绘制页面中的元素。这种做法可以防止呈现给用户空白的页面或没有样式的内容。

<script>标签恰好放在</body>之前

脚本在下载和执行期间会阻止 HTML 解析。把<script>标签放在底部,保证 HTML 首先完成解析,将页面尽早呈现给用户。

如果一定要放在 <head> 中,可以让 <script> 标签使用 defer 属性。

21.什么是渐进式渲染(progressive rendering)?

渐进式渲染是用于提高网页性能(尤其是提高用户感知的加载速度),以尽快呈现页面的技术。

在以前互联网带宽较小的时期,这种技术更为普遍。如今,移动终端的盛行,而移动网络往往不稳定,渐进式渲染在现代前端开发中仍然有用武之地。

一些举例:

  • 图片懒加载——页面上的图片不会一次性全部加载。当用户滚动页面到图片部分时,JavaScript 将加载并显示图像。

  • 确定显示内容的优先级(分层次渲染)——为了尽快将页面呈现给用户,页面只包含基本的最少量的 CSS、脚本和内容,然后可以使用延迟加载脚本或监听DOMContentLoaded/load事件加载其他资源和内容。

  • 异步加载 HTML 片段——当页面通过后台渲染时,把 HTML 拆分,通过异步请求,分块发送给浏览器。更多相关细节可以在这里找到。

22… viewport

Viewport :字面意思为视图窗口,在移动web开发中使用。表示将设备浏览器宽度虚拟成一个特定的值(或计算得出),这样利于移动web站点跨设备显示效果基本一致。移动版的 Safari 浏览器最新引进了 viewport 这个 meta tag,让网页开发者来控制 viewport 的大小和缩放,其他手机浏览器也基本支持。

在移动端浏览器当中,存在着两种视口,一种是可见视口(也就是我们说的设备大小),另一种是视窗视口(网页的宽度是多少)。 举个例子:如果我们的屏幕是320像素 * 480像素的大小(iPhone4),假设在浏览器中,320像素的屏幕宽度能够展示980像素宽度的内容。那么320像素的宽度就是可见视口的宽度,而能够显示的980像素的宽度就是视窗视口的宽度。

为了显示更多的内容,大多数的浏览器会把自己的视窗视口扩大,简易的理解,就是让原本320像素的屏幕宽度能够容下980像素甚至更宽的内容(将网页等比例缩小)。

Viewport属性值

  • width 设置layout viewport 的宽度,为一个正整数,或字符串"width-device"

  • initial-scale 设置页面的初始缩放值,为一个数字,可以带小数

web浏览器中的javascript

window对象

  • 计时器

  • 浏览器定位和导航

  • 浏览历史

  • 浏览器和屏幕信息

  • 对话框

  • 错误处理

  • 作为window对象属性的文档元素