前端面试基础知识整理(持续更新)

一、CSS
1.css盒模型

w3c盒模型:content + padding + border + margin。元素宽高(css)等于content的宽高。
IE盒模型:元素的宽高(css)等于content + padding + border的宽度。

2.BFC块级格式化上下文

形成条件:根元素;position: absolute/fixed; float;overflow:not visible;display: inline-block / table。
应用:清除浮动

3.居中布局

水平居中行内元素:text-align: center;块级元素:margin:auto;
垂直居中line-height; absolute; flex;
水平垂直居中absolute;伪元素形成行内元素居中;flex + justify-content + align-items

4.清除浮动
设置父元素高度;形成bfc;伪元素clear: both;
5.flex常用api
引用:Flex 布局教程:语法篇 容器元素display: flex;容器属性
flex-direction // 决定主轴的方向(即项目的排列方向)。
flex-wrap // 如果一条轴线排不下,如何换行。
flex-flow // 是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
justify-content // 定义了项目在主轴上的对齐方式。
align-items // 属性定义项目在交叉轴上如何对齐。
align-content // 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
元素属性
order // 属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
flex-grow // 属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
flex-shrink // 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
flex-basis // 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)
flex // flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto
align-self // align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。
二、JavaScript
1.原型与继承
原型:prototype
函数的对象属性,包含一个constructor属性,指向函数自身:Fun.prototype.constructor === Fun。
原型链
原型链由原型对象构成,是js对象继承的机制。每个对像都有[[prototype]]属性,主流浏览器以__proto__暴露给用户。__proto__指向对象的构造函数的原型属性(prototype)。prototype的__proto__又指向它的构造函数的原型;通过__proto__属性,形成一个引用链。在访问对象的属性时,如果对象本身不存在,就会沿着这条[[prototype]]链式结构一层层的访问。
构造函数
可以通过new来新建一个对象的函数。
原型与继承
函数拥有原型(prototype);而对象可以通过原型链关系([[prototype]])实现继承。函数是一种特殊对象,也拥有__proto__,即函数也会继承。函数的原型链是:函数静态方法—>原生函数类型的原型(拥有函数原生方法)—>原生对象原型(拥有对象原生方法) —> null class A {}; class B extends A {};
类的继承
B.__proto__ = A.prototype; B.prototype.__proto__ = A.prototype;
new Fun操作符原理

Object.create(Fun.prototype);
执行Fun,即类似类中的调用constructor()(为什么是执行constructor,因为constructor指向函数本身);
如果constructor没有明确返回函数或对象,则返回第一步创建的对象。

2.函数执行相关
堆内存与栈内存

栈内存:由编译器自动分配与释放。我们可以直接操作栈内存中的值。js的基本类型存放在栈内存中,按值引用;
堆内存:链表结构的类型,可以动态分配大小,js引用类型占用内存空间的大小不固定,存储在堆内存中。由于JS不允许直接访问堆内存中的位置,因此我们不能直接操作js的引用类型。而是生成一个指针,并将它放到栈内存中,通过这个指针来操作引用类型。

垃圾回收
类型:标记引用(无法解决循环引用问题);标记清除(现在主流回收算法)。标记清除算法的核型概念是:从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
内存泄漏
对于不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。常见内存泄漏:

不合理的计时器(引用过时的外部变量)
被共享的闭包作用域(函数内部的函数共享一个闭包环境)
脱离dom引用(dom中元素被清除,但是变量还保持这对dom元素的引用)
意外的全局变量(未声明或者指向全局的this.xxx)

事件循环
事件循环是指: 执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表

微任务 microtask(jobs): promise / ajax / Object.observe(该方法已废弃)

宏任务 macrotask(task): setTimout / script / IO / UI Rendering

每次事件循环:

执行完主执行线程中的任务。
取出micro-task中任务执行直到清空。
取出macro-task中一个任务执行。
取出micro-task中任务执行直到清空。
重复3和4。

执行环境及作用域
执行环境
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境斗鱼一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。 全局执行环境是最外围的执行环境,在web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法被创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。(全局执行环境直到应用程序推出时才会被销毁)。
作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链,保证对执行环境有权访问的所有变量和函数的有序访问。 作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象,其在最开始的时候只包含一个变量:arguments。作用域链的下一个变量来自包含环境,而️再下一个变量对象则来自下一个包含环境一直延续到全局执行环境。作用域链在创建的时候就会被生成,保存在内部的[[scope]]属性当中。其本质是一个指向变量对象的指针列表,它指引用但不实际包含变量对象。
[[scope]]
函数创建过程中,会创建一个预先包含外部活动对象的作用域链,并始终包含全局环境的变量对象。这个作用域链被保存在内部的[[scope]]属性中,当函数执行时,会通过复制该属性中的对象构建起执行环境的作用域链。
执行上下文
定义
执行上下文是指当前Javascript代码被解析和执行时所在环境的抽象概念,JavaScript 任何代码的运行都是在执行上下文中。
类型

全局执行上下文
函数执行上下文
eval函数执行上下文

如何工作
执行上下文分为两个过程:

创建阶段
执行阶段

创建阶段
主要做了三件事

this绑定(这解释了为什么this的值取决于函数的调用方式)
创建词法环境。(创建活动对象,变量和函数声明的位置标记(声明提升过程);根据[[scope]]创建作用域链)
创建变量环境(变量环境也是词法法环境,用于存储var声明的变量。)

执行阶段
变量赋值,语句执行等。
闭包
闭包是指有权访问另一个函数作用域中的变量的函数。其本质是函数的作用域链中保存着外部函数变量对象的引用。可以通过[[scope]]属性查看。因此,即使外部函数被销毁,但是由于外部环境中定义的变量在闭包的scope中还保持着引用,所以这些变量暂时不会被垃圾回收机制回收,因此依然可以在闭包函数中正常访问。注意:同一个函数内部的闭包函数共享同一个外部闭包环境。从下图可以看出,即使返回的闭包里面没有引用c,d变量,但是由于内部函数closure1中引用链,所以即使closure1未调用,闭包作用域中依然保存这些变量的引用。

3.js引用方式

html 静态<script>引入
js 动态插入<script>

<script defer>: 延迟加载,元素解析完成后执行

<script async>: 异步加载,但执行时会阻塞元素渲染

其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。 更多分析见下文浏览器->页面的加载与渲染
4.对象的拷贝
浅拷贝
拷贝一层,内部如果是对象,则只是单纯的复制指针地址。

Object.assign()
{ …obj }

深拷贝

JSON.parse(JSON.stringify(source)),缺点层次太深会爆栈,无法解决循环引用问题。
参考深拷贝的终极探索(99%的人都不知道),采用循环代替递归实现深层次的拷贝。大致思想如下:

// 保持引用关系
function cloneForce(x) {
// =============
const uniqueList = []; // 用来去重
// =============

let root = {};

// 循环数组
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];

while(loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;

// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== ‘undefined’) {
res = parent[key] = {};
}

// =============
// 数据已经存在
let uniqueData = find(uniqueList, data);
if (uniqueData) {
parent[key] = uniqueData.target;
break; // 中断本次循环
}

// 数据不存在
// 保存源数据,在拷贝数据中对应的引用
uniqueList.push({
source: data,
target: res,
});
// =============

for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === ‘object’) {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}

return root;
}

function find(arr, item) {
for(let i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i];
}
}

return null;
}
5.模块化

es6模块
commonJS/AMD模块

es6模块与CommonJS模块的差异

CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。
CommonJS模块是运行时加载,ES6模块是编译时输出接口。

6.防抖和节流
防抖 (debounce)
将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可
function debounce(fn, wait, immediate) {
let timer = null;

return function(…args) {
if (immediate && !timer) {
fn.apply(this, args);
}

if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args);
}, wait);
};
}
节流(throttle)
每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。
function throttle(fn, wait, immediate) {
let timer = null;
let callNow = immediate;

return function(…args) {
if (callNow) {
fn.apply(this, args);
callNow = false;
}

if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null;
}, wait);
}
}
}
7.ES6/ES7
let/const
let和const可以形成块级作用域,并且不存在声明提升。并且在同一个块级作用域内不可重复声明。
解构赋值
比如 let [a, b, c] = [1, 2, 3]; let { foo } = { foo: ‘bar’ };
函数扩展

参数默认值
rest参数
箭头函数

数组扩展

Array.from() // 将类数组转化为数组
Array.of() // 更语义明确的数组构建函数:用于将一组值,转换为数组。
entries(),keys() 和 values()
实例方法includes
flat(num?)// 数组扁平化
数组空位,es6明确将数组空位转化成undefined

对象扩展

属性简写
属性名表达式

可枚举性

for…in循环:只遍历对象自身的和继承的可枚举的属性。
Object.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举

Object.is()
Object.assign()
Object.keys(),Object.values(),Object.entries()

Symbol
let s = Symbol();

typeof s
// “symbol”
Symbol.for(), Symbol.keyFor()
Set,Map
Set
结构类似数组,成员唯一。
const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
console.log(i);
}
// 2 3 5 4
操作方法:

add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。

WeapSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。 首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
Map
类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
const m = new Map();
const o = {p: ‘Hello World’};

m.set(o, ‘content’)
m.get(o) // “content”

m.has(o) // true
m.delete(o) // true
m.has(o) // false
WeakMap
WeakMap与Map的区别有两点。
首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。
Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
Promise
实现一个简单的Promise
const FULLFILLED = ‘FULLFILLED’;
const REJECTED = ‘REJECTED’;
const PENDING = ‘PENDING’;
const isFn = val => typeof val === ‘function’;

class Promise {
constructor(handler) {
this._state = PENDING;
this._value = null;
this.FULLFILLED_CBS = [];
this.REJECTED_CBS = [];

try {
handler(this._resolve.bind(this), this._reject.bind(this));
} catch (err) {
this._reject(err);
}
}

_resolve(value) {
const async = () => {
if (this._state !== PENDING) {
return;
}
this._state = FULLFILLED;

const fullfilLed = (val) => {
this._value = val;
this.FULLFILLED_CBS.forEach(cb => cb(val));
};
const rejected = (err) => {
this._value = err;
this.REJECTED_CBS.forEach(cb => cb(err));
};

if (value instanceof Promise) {
value.then(fullfilLed, rejected);
} else {
fullfilLed(value);
}
}

requestAnimationFrame(async);
}

_reject(err) {
const async = () => {
if (this._state !== PENDING) {
return;
}
this._state = REJECTED;
this._value = err;
this.REJECTED_CBS.forEach(cb => cb(err));
}
requestAnimationFrame(async);
}

then(onFullfilled, onRejected) {
return new Promise((resolve, reject) => {
const handerResolve = (value) => {
try {
if (!isFn(onFullfilled)) {
resolve(value);
}
const res = onFullfilled(value);

if (res instanceof Promise) {
res.then(resolve, reject);
} else {
resolve(res);
}
} catch (err) {
reject(err);
}
};
const handlerReject = (err) => {
try {
if (!isFn(onRejected)) {
reject(err);
}
const res = onRejected(err);
if (res instanceof Promise) {
res.then(resolve, reject);
} else {
reject(res);
}
} catch (err) {
reject(err);
}
};
switch(this._state) {
case PENDING:
this.FULLFILLED_CBS.push(handerResolve);
this.REJECTED_CBS.push(handlerReject);
break;
case FULLFILLED:
handerResolve(this._value);
break;
case REJECTED:
handlerReject(this._value);
break;
default: break;
}
});
}

catch(onRejected) {
return this.then(undefined, onRejected);
}

finally(cb) {
const P = this.constructor;
return this.then(
/** 使用then保证promise的流是正常的,因为promise的下一步总是建立在上一步执行完的基础上 */
value => P.resolve(cb()).then(() => value),
reason => P.resolve(cb()).then(() => { throw reason })
);
}

static all(list) {
return new Promise(resolve, reject) {
let count = 0;
const values = [];
const P = this.constructor;

for (let [key, fn] of list) {
P.resolve(fn).then(res => {
values[key] = res;
count++;
if (count === list.length) {
resolve(values);
}
}, err => reject(err));
}
}
}

static race(list) {
return new Promise((resolve, reject) => {
const values = [];
let count = 0;
const P = this.constructor;
for (let fn of list) {
P.resolve(fn).then(res => {
resolve(res);
}, err => {
reject(err);
})
}
})
}

static resolve(value) {
if (value instanceof Promise) {
return value;
}

return new Promise(resolve => resolve(value));
}

static reject(value) {
return new Promise((resolve, reject) => reject(value));
}
}
async/await
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
async函数返回一个promise对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。
Class
es6的类语法是es5构造函数的语法糖,但是也有一些不同点

class的原型属性不可枚举
不使用new操作符,class会报错
class新增存取函数
不存在声明提升

Class的继承
抓住两点即可:假设A为父类,B为子类那么
B.__proto__ = A; // 子类的__proto__属性,表示构造函数的继承,总是指向父类。
B.prototype.__proto__ = A.prototype; // 子类prototype属性的__proto__属性,表示方法(实例)的继承,总是指向父类的prototype属性。
Module

es6模块
commonJS/AMD模块

es6模块与CommonJS模块的差异

CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。
CommonJS模块是运行时加载,ES6模块是编译时输出接口。

8.函数柯里化
在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。
const add = function add(x) {
return function (y) {
return x + y
}
}

const add1 = add(1)

add1(2) === 3
add1(20) === 21
9.正则表达式
3.浏览器
1.页面的加载与渲染
从输入 url 到展示的过程

DNS查找相应的ip地址 浏览器DNS缓存->电脑本地host文件查找->运营商DNS的解析

三次握手

客户端发送 syn(同步序列编号) 请求,进入 syn_send 状态,等待确认
服务端接收并确认 syn 包后发送 syn+ack 包,进入 syn_recv 状态
客户端接收 syn+ack 包后,发送 ack 包,双方进入 established 状态

发送请求,分析 url,设置请求报文(头,主体)
服务器返回请求的文件 (html)

浏览器渲染

HTML parser –> DOM Tree
CSS parser –> Style Tree
attachment –> Render Tree
layout: 布局

浏览器加载与渲染
浏览器下载html文件并进行编译,转化成类似下面的结构
图片来源再谈 load 与 DOMContentLoaded 浏览器会对转化后的数据结构自上而下进行分析:首先开启下载线程,对所有的资源进行优先级排序下载(注意,这里仅仅是下载,区别于解析过程,解析过程可能被阻塞)。同时主线程会对文档进行解析
css文件的解析

header中的css的下载不会阻塞html的解析,当css文件下载完成,将阻塞html解析,优先解析css,会阻碍渲染。
body中的css下载和解析都不会阻碍html的解析,会阻碍渲染。(存在特殊情况,见下文)

js文件的解析
1. 普通的script:
遇到 script 标签时,首先阻塞后续内容的解析(但是不会阻塞下载,chrome对同一个域名支持6个http同时下载),当下载完成后,便执行js文件中的同步代码。然后接着解析html
2. defer和async
如果script标签设置了async或者defer标签,下载过程不会阻塞文档解析。defer执行将会放到html解析完成之后,dcl事件之前。async则是下载完就执行,并且阻塞解析。
body中的首个script/link
在 body 中第一个 script 资源下载完成之前,浏览器会进行首次渲染。将该 script 标签前面的 DOM 树和 CSSOM 合并成一棵 Render 树,渲染到页面中。这是页面从白屏到首次渲染的时间节点,比较关键。如果是第一个标签是link,表现有点奇怪,下载和解析会阻塞html的解析,并有可能进行首次渲染,也可能不渲染。
渲染过程大致为:计算样式Recalculate style->布局:layout->更新布局树update layer tree -> 绘制paint
3.相关概念
dom构建
DOM 构建的意思是,将文档中的所有 DOM 元素构建成一个树型结构。
css构建
将文档中的所有 css 资源合并。生成css rule tree
render 树
将 DOM 树和 CSS 合并成一棵渲染树,render 树在合适的时机会被渲染到页面中。(比如遇到 script 时, 该 script 还没有下载到本地时)。
4.总结
如下图:

浏览器首先下载该地址所对应的 html 页面。(如果html很大,浏览器并不会等待html完全下载完,而是采用分段下载,分段解析)
浏览器解析 html 页面的 DOM 结构。
开启下载线程对文档中的所有资源按优先级排序下载。

主线程继续解析文档,到达 head 节点 ,head 里的外部资源无非是外链样式表和外链 js。
发现有外链 css 或者外链 js,如果是外链 js ,则停止解析后续内容,等待该资源下载,下载完后立刻执行。如果是外链 css,继续解析后续内容。

解析到 body

在 body 中第一个 script 资源下载完成之前,浏览器会进行首次渲染。将该 script 标签前面的 DOM 树和 CSSOM 合并成一棵 Render 树,渲染到页面中。这是页面从白屏到首次渲染的时间节点,比较关键。如果是第一个标签是link,表现有点奇怪,下载和解析会阻塞html的解析,并有可能进行首次渲染,也可能不渲染。随后js的下载和执行依然会阻塞解析,但是css的下载和解析则不会阻塞html的解析。

文档解析完毕,页面重新渲染。当页面引用的所有 js 同步代码执行完毕,触发 DOMContentLoaded 事件。然后执行布局:layout->更新布局树update layer tree -> 绘制paint
html 文档中的图片资源,js 代码中有异步加载的 css、js 、图片资源都加载完毕之后,load 事件触发(与DOMContentLoaded没有必然的先后顺序关系)。

2.跨域解决方案
4.计算机网络基础
5.前端工程化
6.Vue源码相关
7.React源码相关
8.算法相关
引用参考

ECMAScript 6 入门
中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)
JS事件循环
Flex 布局教程:语法篇
前端笔记–JS执行上下文
script标签中defer和async属性的区别
[译]页面生命周期:DOMContentLoaded, load, beforeunload, unload解析
再谈 load 与 DOMContentLoaded
面试带你飞:这是一份全面的 计算机网络基础 总结攻略
深拷贝的终极探索(99%的人都不知道)

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理