前端面试题目收集(持续更新)
引言
这里进行了一些常用面试题目的搜集,可供面试前参考,先罗列一遍,后期打算再进行分类整理
学习一个东西,需要带有目的、带有问题去学,不能为了学而学
知识树
之前每次面试前,看一篇自己的总结面试题,感觉很好,但是每次这样效果太低下,一个个题目看,然后再进行回想,每个题目在你脑中渐渐形成一个孤立的荒岛,为了加快效率,希望大家构建自己的知识体系
接下来,我自己罗列下前端知识体系,每次面试前根据知识体系回想回想一遍,把知识联系起来,形成一棵树,然后再用具体题目去充实上面的每个子树吧
- 浏览器相关 (加载过程、三级缓存、浏览器缓存、垃圾回收、v8垃圾回收、事件循环 其他参考、Ajax、浏览器工作原理、缓存)
- 网络 (tcp三次握手四次挥手、http、https 握手过程、网络五层协议、XSS/CSRF/sql 注入等网络安全问题)
- js (类型判断、原型链、this 指向、异步函数、继承、设计模式、设计原则、es6、浮点数、深拷贝浅拷贝)
- css (BFC、清除浮动、居中、css3 实现三角形、主题切换方案、css 样式权重、css3、浮动)
- html (语义化、canvas、空标签有哪些)
- node.js (express、koa 洋葱模型、eventEmitter的实现、事件循环、微任务与宏任务、async awiat、错误处理、pm2 的使用、docker的使用)
- Vue (双向绑定的原理 (1、2)、v-model 的原理、vue-router 的实现、vuex 的实现、vue3.0 的新特性)
- React (vue 和 react 的区别、vuex 和 redux的区别、react 的函数式组件、immutable、redux实现、fiber 的原理、React16 的新特性、hook 的使用、生命周期)
- 前端性能优化 (方案、性能监控)
- webpack (配置、各种 loader、原理、性能优化:多线程打包、代码分割、按需加载)
- 基础算法 (排序、链表、数、队列、栈)
- git (命令、cli、rebase 用法)
- 服务器 (服务端渲染、ssr)
- 客户端 :RN/Weex、原生 bridge
- 移动端(微信公众号开发、小程序、移动端问题:1px 问题、点击穿透、图片懒加载)
资料:字节飞书面试题
浏览器相关
cookie
- 什么是cookie
cookie 是由服务器生成并保存在客户端的小型文本文件,它的优点是:
- 高扩展性和可用性,比如,可以通过良好的编程控制存储在cookie的信息大小
- 数据持久性,cookie 可以长期保存在浏览器中
- 一定的安全性,可以设置失效时间,通过加密技术和安全传送技术,防止信息被恶意串改
缺点:
- cookie的长度和同一域名下的数量都有限制,长度不超过4k,数量firefox限制40,chrome没有限制
- 潜在的安全风险,cookie有可能被攻击者串改,或者被拦截
- 不适合保存状态,如一些防止表单重复提交的计时器
- 冗余,服务器不需要的信息也可能随cookie发送
- 与session的区别
session:服务端执行session机制时候会生成session的id值,客户端每次请求都会把这个id值放到http请求的头部发送给服务端,而这个id值在客户端会保存下来,保存的容器就是cookie(Session不一定必须依赖Cookie,也可以在url上)
页面性能指标
页面加载时长是被清晰的标在这个页面的底部的。它是指 DOM load
事件触发完成,它的优点有:
- 原生 API;
- 接受度高;
- 感知明显(浏览器 Tab 停止 loading)。
缺点是:
- 无法准确反映页面加载性能;
- 易受特殊情况影响。
为了解决这个问题,W3C 的工作小组引入了首次渲染 / 首次内容渲染。首次渲染是指第⼀个非网页背景像素渲染,⾸次内容渲染是指第一个⽂本、图像、背景图片或非白色 canvas/SVG 渲染。
相关:
使用chrome开发者工具中的performance面板解决性能瓶颈
高性能建站指南
- 减少http请求 方式:合并css、js文件,使用精灵图
- 使用cdn加快加载速度
- 开启浏览器缓存
- css放头部,减少白屏,防止其阻碍页面呈现,页面会等css加载完再呈现内容
- js放底部,防止其阻塞资源并行加载,不能并行加载的原因是保证js的顺序
- 开启服务器gzip压缩
ajax实现
获取DOM元素大小
偏移量,元素布局的大小
offsetWidth:偏移宽度 = content + padding + border
offsetHeight:偏移高度 = content + padding + border
offsetTop: 相对于offsetParent的偏移顶部 = border上边界外 ~ parent的border上边界内
offsetLeft:相对于offsetParent的偏移左侧部分 = border左边界外 ~ parent的border左边界内
客户区,元素里面的大小
- clientWidth: 客户区宽度 = content + padding
- clientHeight:客户区高度 = content + padding
滚动区大小
- scrollWidth: 滚动区域宽度 = content
- scrollHeight: 滚动区域高度 = content
- scrollTop: 滚动区域顶部被遮住的内容
- scrollLeft:滚动左边被遮住的内容
getBoundingClientRect 返回left、right、top、bottom、width、height(与offsetXXXX一致,不知道为啥叫clientRect)
前端跨页面通信
- 广播
- BoastcastChannel 进行页面广播,实例调用onmessage监听,postMessage发送
- Service Worker 建立消息中转站,通过addEventListener方式监听, postMessage发送
- LocalStorage ,通过storage方法监听,改变数据方式发送
- 共享数据轮询
- cookie和indexDB
- Shared Worker
- 口口相传:window.open 获取打开页面的引用,opener获的被打开页面的引用
- 跨域方案:利用页面可以获取iframe引用的方法,使iframe在同一域名下通信
网络
简述 HTTPS 的工作原理,如何实现的加密传输(SSL/TLS 的握手过程)
答:
简单描述下,https的是对网页整体内容进行加密,从而防止第三方恶意利用网站的加密传输技术
一般用户请求https时:
- 客户端,把支持的加密方法(非对称加密:RSC,ECC,对称加密:DES,RC)、协议版本,生成随机码(客户端随机码 )发送到服务器
- 服务端选择其中一种加密方式和证书以及用私钥加密的服务端随机码发送到客户端
- 客户端验证证书否有效后,用公钥解密服务端随机码,生成随机key,用公钥加密随机key,发送到服务端
- 服务端生用私钥解码字符串随机key ,然后使用所有密码生成 Session key (客户端随机码+服务端随机码+随机key ) 作为对称加密的密码加密网页,并返回网页
- 客户端获取加密网页,使用 Session key (客户端随机码+服务端随机码+随机key )对内容进行解码,得到正确数据并显示在浏览器上
上述过程只是根据个人记忆理解的描述,跟具体的步骤可能有些出入
这里有几个问题想说明下:
- 为什么不直接使用公钥加密网页?因为网页往往较大,用公钥加密一般比较费时,而对称加密效率高但安全性低,故此选择了非对称加密和对称加密结合的方式进行
- 为什么要加入随机key?随机key是最重要的钥匙,和客户端随机码、服务端随机码是明文传输不一样,它是唯一不会被中间人获取的key。
线程和进程的差别
http 2.0对于http 1.x有哪些优点?
多路复用,多个http请求复用一个连接
HTTP2采用二进制格式传输,取代了HTTP1.x的文本格式,二进制格式解析更高效。
多路复用代替了HTTP1.x的序列和阻塞机制,所有的相同域名请求都通过同一个TCP连接并发完成。在HTTP1.x中,并发多个请求需要多个TCP连接,浏览器为了控制资源会有6-8个TCP连接都限制。
HTTP2中- 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
- 单个连接上可以并行交错的请求和响应,之间互不干扰
二进制分帧,在应用层和传输层直接加了一个二进制分帧,把http请求的数据分割成更小的二进制帧,从而实现多个数据流复用同一个连接的目的,减轻了服务器的连接压力
首部压缩,采用了HPACH的新算法进行首部压缩,减轻报文体积
服务器推送,类似websocket
网络安全
常见Web漏洞及其防范
1.XSS(Cross Site Scripting)跨站脚本攻击
- 攻击方式:利用html的漏洞,让网页执行不存在的js代码,比如在评论里写进代码,用户浏览到当条评论就会执行此段代码
- 防范:客户端与服务端同时做htmlEncode和jsEncode。服务端保障安全,客户端提升体验。服务端可使用XSS Filter实现,nodejs可使用XSS相关中间件。
2.CSRF(Cross-site request forgery)跨站请求伪造
- 方式:伪装成用户发请求,如劫持用户cookie获取请求,伪装成用户进行银行卡转账
- 防范:通过HTTP的Referer字段验证请求是否来自信任网站
3.SQL注入
- 方式:SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作。例如:网页查询界面,在input注入sql
- 防范:服务器对查询的SQL语句进行预编译、校验请求参数。例如:nodejs使用sqlstring对sql语句进行预编译
4.弱口令
防范:使用规则限制用户使用弱口令5.非加密传输
防范:使用HTTPS(HTTPS原理:非对称加密交换密钥+对称加密数据+CA认证)6.CFS(Cross Frame Script跨框架脚本攻击)与Clickjacking(点击劫持)
方式:利用浏览器允许框架(frame)跨站包含其它页面的漏洞,在主框架的代码中加入scirpt,监视、盗取用户输入。
防范:服务端header设置X-Frame-Options为SAMEORIGIN
tip:使用 X-Frame-Options 有三个可选的值:
DENY:浏览器拒绝当前页面加载任何Frame页面
SAMEORIGIN:frame页面的地址只能为同源域名下的页面
ALLOW-FROM:允许frame加载的页面地址
http2和http1的区别
js
宏任务微任务
任务执行顺序: 同步任务、异步任务(顺序:微任务(promise,nextTick)、宏任务(setTimeout,setInterval、setImmediate))
参考:宏任务与微任务
new做了什么
新建一个对象,并把this指向这个它,同时调用构造函数,并继承构造函数
new的模拟实现
function parent(name) {
this.name = name
return {
name: '刘'
}
}
parent.prototype.car = 'bigCar'
child = new parent('张')
console.log('child', child)
// 模拟实现
function _new() {
const constructor = [].shift.call(arguments)
const arg = [].slice.call(arguments)
const obj = new Object()
const ret = constructor.apply(obj, arg)
obj.__proto__ = constructor.prototype
return ret instanceof Object ? ret : obj
}
child1 = _new(parent, '林')
console.log('child1', child1)
内存泄露
意外的全局变量,解决: 在JavaScript文件中添加
'use strict'
,开启严格模式被遗忘的定时器和回调函数,解决:在定时器完成工作的时候,手动清除定时器
DOM引用:js保留了DOM节点的引用,导致即使节点被删除,GC也没有回收
遗忘的事件监听器:如在单页面应用,组件销毁的时候,resize 事件还是在监听中,里面涉及到的内存都是没法回收,解决:需要在组件销毁的时候移除相关的事件
闭包
注意: 闭包本身没有错,不会引起内存泄漏.而是使用错误导致
如闭包return回来的函数没有被使用的话,就会造成内存泄露(没有使用,又回收不了)
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大
循环引用。Ps: 可以参考
function handle () { var element = document.getElementById(“testId”); // 闭包 element.onclick = function (){ alert(element.id) } } // 闭包会持有外部传入的变量,因此闭包持有了element对象,而element对象通过onclick属性持有了闭包,因此两个对象相互持有,造成内存泄漏。
参考 内存泄露
原型链的相关知识
什么是原型(prototype)?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型(也可以叫: 实例原型,原因是它也可以是构造函数创建的实例的原型)。
__proto__
和 prototype 是什么?__proto__
指向原型。js 中每一个对象都有
__proto__
属性,但是只有函数才有 prototype 属性 (函数也是对象)。例子:
// 函数对象 function Person() {} // 普通对象 var obj = {}; obj.__proto__ === Object.prototype;//true obj.prototype === undefined;//true Person.__proto__ === Function.prototype;//true Person.prototype !== undefined;//true
prototype 指向原型。
所有函数都有 prototype 属性,除了 Function.prototype 外,它是函数对象,但是没有prototype属性。
Function.prototype.prototype === undefined;//true
构造、实例、继承是指什么?
- 在 js 中如果 A 对象是由 B 函数构造的(也可以说是 B 是 A 的实例),那么
A.__proto__ === B.prototype
。 - 原型链是基于
__proto__
形成的,继承是通过prototype实现的。
- 在 js 中如果 A 对象是由 B 函数构造的(也可以说是 B 是 A 的实例),那么
Object 和 Function 的关系?
内置的 Object 其实是一个函数对象,它是由Function构造的。
Object.__proto__ === Function.prototype;
内置的Function也是一个函数对象,它是通过自己来构造自己的。
Function.__proto__=== Function.prototype;//true
Object.prototype 指向一个普通对象,它的
__proto__
是 null,它是 js 原型链的最顶端 。所以 Object.prototype 没有原型 。Object.prototype.__proto__=== null;//true Object.prototype.prototype === undefied;//true 对象没有prototype,除非是函数对象
函数也是对象,因为函数在创建时会继承
Function.prototype
,Function.prototype.__proto__
指向 Object.prototype。typeof Function.prototype.__proto__) === "object";//true Function.prototype.__proto__=== Object.prototype;//true function a() {} a.__proto__.__proto__ === Object.prototype; //true
instanceof
:instanceof
运算符用于检测当前构造函数的prototype
属性是否出现在当前实例对象的原型链- 一图理清关系
什么是事件捕获机制,DOM0和DOM2
1、DOM0级事件
就是直接通过 onclick 等方式实现相应的事件
这说明 DOM0 级添加事件时,后面的事件会覆盖前面的事件,而 DOM2级则不会,多个事件都会执行;
另外,DOM0级事件具有很好的跨浏览器优势,会以最快的速度绑定,但由于绑定速度太快,可能页面还未完全加载出来,以至于事件可能无法正常运行
2、DOM1级事件
因为DOM 1一般只有设计规范没有具体实现,所以一般跳过
3、
DOM2级事件
主流浏览器 DOM2 级事件是通过以下两个方法用于处理指定和删除事件处理程序的操作
只有DOM2事件流包括三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
即是事件捕获机制
理解变量提升
- 变量函数都会提升
- 函数声明提升(包括function(){}里面也算函数声明, 如果变量对象已经存在相同名称的属性,则完全替换这个属性)
- 变量声明提升,不会提升赋值( 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性)
作用域提升
- 变量声明、函数声明都会被提升到作用域顶处;
- 当出现相同名称时,优先级为:函数形参 > 函数声明 > 变量声明
- let const class都会提升,但是不会初始化,没有初始化(赋值)前会形成Temporal Dead Zone(暂缓性死区)
前端模块化:CommonJS,AMD,CMD,ES6
继承的几种方式
原型链继承 缺点:引用类型值的原型属性会被所有实例共享
function Parent() { this.name = '父亲'; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // (该属性,强调私有) } Parent.prototype.say = function() { // -- 将需要复用、共享的方法定义在父类原型上 console.log('hello') } function Child(like) { this.like = like; } Child.prototype = new Parent() // 核心 let boy1 = new Child() let boy2 = new Child()
补充:实际上所有类型的属性都会被共享,只不过值类型的属性,在修改时会在对象上新建属性覆盖,而值类型通过一些不改变原始地址的方式修改值时,所有实例都会被修改,因为此时实例记录的都是同一个原始地址
借用构造函数继承 缺点:父类的方法不能复用,每次都要新建一次方法
function Parent(name) { this.name = name; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // (该属性,强调私有) this.say = function() { // 实例引用属性 (该属性,强调复用,需要共享) console.log('hello') } } function Child(name,like) { Parent.call(this,name); // 核心 this.like = like; } let boy1 = new Child('小红','apple'); let boy2 = new Child('小明', 'orange ');
组合继承 缺点:两次调用构造方法
function Parent(name) { this.name = name; // 实例基本属性 (该属性,强调私有,不共享) this.arr = [1]; // (该属性,强调私有) } Parent.prototype.say = function() { // --- 将需要复用、共享的方法定义在父类原型上 console.log('hello') } function Child(name,like) { Parent.call(this,name) // 核心 第二次 this.like = like; } Child.prototype = new Parent() // 核心 第一次 <!--这里是修复构造函数指向的代码--> let boy1 = new Child('小红','apple') let boy2 = new Child('小明','orange')
创建对象的几种方式
Object对象函数和对象字面量
工厂模式
用函数创造一个对象
缺点:没有解决对象识别的问题?
function person(name) { let obj = new Object() obj.name = name obj.say = function(){ alert('say') } } let person1 = preson('钟汉良')
原型链模式
用类创造一个对象
缺点:共享了方法属性
function person() { } person.prototype.name = name person.prototype.say = function(){ alert('say') } let person1 = new preson()
构造函数模式
用类创造一个对象
缺点:每次新建时方法都会被创建一次
function person(name) { this.name = name this.say = function(){ alert('say') } } let person1 = new preson('钟汉良')
组合使用原型链模式和构造函数模式
用类创造一个对象function person(name) { this.name = name this.say = function(){ alert('say') } } person.prototype = { constructor: person, say: function(){ alert('say') } } let person1 = new preson('钟汉良')
关于 javascript 中的 this
javascript 中的 this 和 OO 语言(如:java、C#)的 this 有着比较大的差异。
- 1 请说明下方代码片段中对于 this 的使用是否正确,不正确的话,问题在哪里,如何修改?
- 2 写些小的代码片段说明一下你理解的 this。
class Handler {
get (req, res) {
const type = req.query.type
this[type](req, res)
}
typeA (req, res) {
res.send('hello a')
}
typeB (req, res) {
res.send('hello b')
}
}
const express = require('express')
const app = express()
const handler = new Handler()
app.use('/', handler.get)
答:
- 不正确,问题在执行
app.use('/', handler.get)
时,改变了this的指向, 因为此时函数中的this是根据上下文指定的 - 修改方式:
const handler = function (req, res) {
const hand = new Handler()
hand.get(req, res)
}
app.use('/', handler)
- 代码片段:
const fun = function() { console.log(this) }
fun() //指向全局
const obj = { objFun : fun }
obj.objFun() //指向obj
fun.call(obj) //指向obj
const person = function () { this.name = 'lili' }
myName = new person()
console.log(myName.name) //'lili'
const func = () => { console.log(this) }
func() //指向全局
func.call(obj) //指向全局
介绍一下 import 和 require 有什么区别
答:
import:es6的模块加载规范,可用于浏览器环境和node环境(需要babal)
require:commonJS的加载规范,主要用于node环境
如何修复输出值中的 undefined ?(阿里)
function LateBloomer(){
this.petalCount = Math.ceil(Math.random()*2 + 1);
}
LateBloomer.prototype.bloom = function(){
window.setTimeout(this.declare, 1000);
}
LateBloomer.prototype.declare = function(){
console.log('I am a beautiful flower with ' + this.petalCount + ' petals');
}
var flower = new LateBloomer();
flower.bloom();
普通函数和箭头函数this的区别
区别:
普通函数中的this:
- this总是代表它的直接调用者, 例如 obj.func ,那么func中的this就是obj
- 在默认情况(非严格模式下,未使用 ‘use strict’),没找到直接调用者,则this指的是 window
- 如果该函数是一个构造函数,this指针指向一个新的对象
- 在严格模式下,没有直接调用者的函数中的this是 undefined
- 使用call,apply,bind(ES5新增)绑定的,this指的是 绑定的对象
箭头函数中的this
- 箭头函数不会创建自己的
this
它只会从自己的词法作用域链的上一层继承this
(可能环境是window) - call,apply,bind不改变this指向
- 不能作为构造函数
- 没有argument属性
Proxy和defineProperty
Object.defineProperty :该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象
var obj = {}; Object.defineProperty(obj, "num", { value : 1, writable : true, enumerable : true, configurable : true }); // 对象 obj 拥有属性 num,值为 1
proxy :控制和修改Object的基本行为, 比起defindProperty可以重定义更多的行为,除了 get 和 set 之外,proxy 可以拦截多达 13 种操作
var proxy = new Proxy(target, handler); /* new Proxy()表示生成一个Proxy实例 target参数表示所要拦截的目标对象 handler参数也是一个对象,用来定制拦截行为。*/
参考:ES6 系列之 defineProperty 与 proxy
Promise.all()和Promise.race模拟实现
Promise.all()
function promiseAll(arg = []) { return new Promise((resolve, reject) => { let timer = 0 const rets = new Array(arg.length) for(let i = 0; i < arg.length; i++) { // Promise.resolve 为了兼容 arg中存在不是Promise的数 Promise.resolve(arg[i]).then((res) => { rets[i] = res timer++ // 重点,在最后一个完成事执行resolve,最终返回[] if(timer === arg.length) resovle(rets) }).catch(e => reject(e)) } }) } promiseAll([Promise.resolve(1), Promise.resolve(2), 3]).then((res) => console.log(res)) // promiseAll([Promise.resolve(1), Promise.reject(2), 3, () => 4]).then((res) => console.log(res))
Promise.race()
function promiseRace(arg = []) { return new Promise((resolve, reject) => { for(let i = 0; i < arg.length; i++) { Promise.resolve(arg[i]).then(res => { // 有返回直接返回 resolve(res) }).catch(e => reject(e)) } }) } promiseRace([new Promise(res => setTimeout(()=> res(1), 100)), new Promise(res => setTimeout(()=> res(2), 200))]) .then((res) => console.log(res))
关于Promise的更多实现可参考(此文章,实现有些许冗余)
Generators原理
通过利用闭包,保存生成器函数的上下文,并对生成器函数进行包装添加next方法,对next的调用会进入到对应状态机里,而实现next对生成器里面代码的暂停恢复控制。
简单的实例:
function* example() {
yield 1;
yield 2;
yield 3;
}
var iter=example();
iter.next();//{value:1,done:false}
iter.next();//{value:2,done:false}
iter.next();//{value:3,done:false}
iter.next();//{value:undefined,done:true}
异步加载JS的方式有哪些?
- defer:异步下载,不阻塞解析,HTML解析完成后执行,defer之间保证执行顺序
async
:异步下载,阻塞解析,下载完后尽快执行,async之间不保证顺序- 创建
script
,在onload后,插入到DOM
中,加载完毕后callBcak
判断js数据类型的方法
typeof
- 对于除 null(返回 object 类型) 以外的基本类型,,均可以返回正确的结果。
- 除 function(返回 function 类型) 以外的引用类型,一律返回 object 类型。
instanceof
- instanceof 检测的是原型
- instanceof 只能用来判断两个对象是否属于实例关系 , 而不能判断一个对象实例具体属于哪种类型。
toString
Object.prototype.toString.call()
js import的动态导入
import(module)
表达式加载模块并返回一个 promise,该 promise resolve 为一个包含其所有导出的模块对象。我们可以在代码中的任意位置调用这个表达式。
扩展:
css
CSS可以继承的属性
常用的css可继承的属性:
font:组合字体
font-family:规定元素的字体系列
font-weight:设置字体的粗细
font-size:设置字体的尺寸
font-style:定义字体的风格
text-indent:文本缩进
text-align:文本水平对齐
line-height:行高
color:文本颜色
visibility:元素可见性
光标属性:cursor
所有元素可以继承的属性:cursor
内联元素可以继承的属性:text-indent、text-align之外的文本系列属性
块级元素可以继承的属性:text-indent、text-align
CSS居中
- 内联元素:
- 水平:text-align
- 垂直:
- line-height:元素高度 (只对inline中文字有效)
- vertical-alight:middle;
- 块级元素:
- flex:justify-content:center; align-item: center;
- position
- 定宽:position: absolute; left: 50%; top: 50%; margin-left:负自身宽度的一半; margin-top:负自身高度的一半;
- 不定宽:
- position: absolute; left: 50%; top:50%; transfrom: translate(-50%,-50%);
- position: absolute; left: 0; rihgt: 0; top: 0 ; bottom: 0; margin: auto;
- table:diplay:table-cell;vertical-align: middle;
- margin:0 auto; (只能水平居中)
clientWidth和offsetWidth宽度
ele.clientWidth = 宽度 + padding
ele.offsetWidth = 宽度 + padding + border
ele.scrollTop = 被卷去的上侧距离
ele.scrollHeight = 自身实际的高度,可见部分加上不可见部分(不包括边框)
ele.offsetTop = 当前元素相对于其 offsetParent
元素的顶部内边距的距离
Ps:懒加载是利用图片的ele.offsetTop和父元素的scrollTop的差判断图片是否在屏幕范围内
什么是reflow和repaint
repaint(重绘):如果只是改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性,将只会引起浏览器 repaint。repaint 的速度明显快于 reflow
reflow(回流):例如某个子元素样式发生改变,直接影响到了其父元素以及往上追溯很多祖先元素(包括兄弟元素),这个时候浏览器要重新去渲染这个子元素相关联的所有元素的过程称为回流。
reflow:几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
下面情况会导致reflow发生
1:改变窗口大小
2:改变文字大小
3:内容的改变,如用户在输入框中敲字
4:激活伪类,如:hover
5:操作class属性
6:脚本操作DOM
7:计算offsetWidth和offsetHeight
8:设置style属性
相关扩展:在移动端使用transform: translate代替top left marg等做位移有好处么 ?
块级格式化上下文
BFC(Block formatting context)直译为”块级格式化上下文”。它是一个独立的渲染区域,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。
哪些情况会产生BFC:
- 根元素
- 浮动元素:float 不为 none
- 定位元素:position 不为 static
- overflow 不为 visible
- display 为 inline-block, table-cell, table-caption, flex, inline-flex
BFC 特性及应用:
- 同一个 BFC 下外边距会发生重叠,不同 BFC 不会重叠
- 内部第一层计算高度时浮动的元素参与计算(清除浮动)
- 内部第一层浮动元素不重叠(其实同上)
- 应用:用来实现两列自适应布局、清除浮动
CSS 百分比参照问题
- 参照父元素宽度的元素:padding margin width text-indent
- 参照父元素高度的元素:height
- 参照父元素属性:font-size line-height
- 参照transform-box(可能为当前元素)属性:transform: translate(30%, 50%);
- 特殊:相对定位的时候,top(bottom) left(right)参照的是父元素的内容区域的高度与宽度,而绝对定位的时候参照的是最近的定位元素包含padding的高度与宽度
渲染树的形成原理
注意点:
- CSS解析可以与DOM解析同进行
- 如果只有 CSS 和 HTML 的页面,CSS 不会影响 DOM 树的创建,但是如果页面中还有 JavaScript,结论就不一样了。
css样式优先级
!important > 内联 > id > class(=伪类、属性选择器) > 标签选择器(=伪元素)
html
http请求方式
方法 | 说明 |
---|---|
GET | GET请求会显示请求指定的资源。一般来说GET方法应该只用于数据的读取,而不应当用于会产生副作用的非幂等的操作中。它期望的应该是而且应该是安全的和幂等的。这里的安全指的是,请求不会影响到资源的状态。 |
HEAD | HEAD方法与GET方法一样,都是向服务器发出指定资源的请求。但是,服务器在响应HEAD请求时不会回传资源的内容部分,即:响应主体。这样,我们可以不传输全部内容的情况下,就可以获取服务器的响应头信息。HEAD方法常被用于客户端查看服务器的性能。 |
POST | POST请求会 向指定资源提交数据,请求服务器进行处理,如:表单数据提交、文件上传等,请求数据会被包含在请求体中。POST方法是非幂等的方法,因为这个请求可能会创建新的资源或/和修改现有资源。 |
PUT | PUT请求会身向指定资源位置上传其最新内容,PUT方法是幂等的方法。通过该方法客户端可以将指定资源的最新数据传送给服务器取代指定的资源的内容。 |
DELETE | DELETE请求用于请求服务器删除所请求URI(统一资源标识符,Uniform Resource Identifier)所标识的资源。DELETE请求后指定资源会被删除,DELETE方法也是幂等的。 |
CONNECT | CONNECT方法是HTTP/1.1协议预留的,能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信。 |
OPTIONS | OPTIONS请求与HEAD类似,一般也是用于客户端查看服务器的性能。 这个方法会请求服务器返回该资源所支持的所有HTTP请求方法,该方法会用’*’来代替资源名称,向服务器发送OPTIONS请求,可以测试服务器功能是否正常。JavaScript的XMLHttpRequest对象进行CORS跨域资源共享时,就是使用OPTIONS方法发送嗅探请求,以判断是否有对指定资源的访问权限。 |
TRACE | TRACE请求服务器回显其收到的请求信息,该方法主要用于HTTP请求的测试或诊断。 |
PATCH | PATCH方法出现的较晚,它在2010年的RFC 5789标准中被定义。PATCH请求与PUT请求类似,同样用于资源的更新。二者有以下两点不同:1.PATCH一般用于资源的部分更新,而PUT一般用于资源的整体更新。2.当资源不存在时,PATCH会创建一个新的资源,而PUT只会对已在资源进行更新。 |
node
node中间件的原理
一个请求发送到服务器后,它的生命周期是 先收到request(请求),然后服务端处理,处理完了以后发送response(响应)回去
app.use 加载用于处理http请求的middleware(中间件),当一个请求来的时候,会依次被这些 middlewares处理。
中间件其是一个函数,在响应发送之前对请求进行一些操作
这个函数有些不太一样,它还有一个next参数,而这个next也是一个函数,它表示函数数组中的下一个函数
express内部维护一个函数数组,这个函数数组表示在发出响应之前要执行的所有函数,也就是中间件数组
ps:koa与express的中间件机制揭秘 by 挥刀北上
node事件驱动模型
关于线程: 除了你的代码是单线程,其余都是多线程(线程池),nodejs本身是事件驱动,一个io事件完成会被放到一个事件队列中,主线程负责轮询这个队列,然后执行相应的回调函数。
Node.js 事件循环,定时器和 process.nextTick() by node.js
- process.nextTick
process.nextTick
这个名字有点误导,它是在本轮循环执行的,而且是所有异步任务里面最快执行的。
Node 执行完所有同步任务,接下来就会执行process.nextTick
的任务队列。
- 微任务和宏任务在Node的执行顺序
Node 10以前:
- 执行完一个阶段的所有任务
- 执行完nextTick队列里面的内容
- 然后执行完微任务队列的内容
Node 11以后:
和浏览器的行为统一了,都是每执行一个宏任务就执行完微任务队列。
setImmediate()
对比setTimeout()
setImmediate()
和 setTimeout()
很类似,但是基于被调用的时机,他们也有不同表现。
setImmediate()
设计为一旦在当前 轮询 阶段完成, 就执行脚本。setTimeout()
在最小阈值(ms 单位)过后运行脚本。
执行计时器的顺序将根据调用它们的上下文而异。如果二者都从主模块内调用,则计时器将受进程性能的约束(这可能会受到计算机上其他正在运行应用程序的影响)。
例如,如果运行以下不在 I/O 周期(即主模块)内的脚本(可以理解为初始化时),则执行两个计时器的顺序是非确定性的,因为它受进程性能的约束:
当 Node.js 启动后,它会初始化事件循环,处理已提供的输入脚本(或丢入 REPL,本文不涉及到),它可能会调用一些异步的 API、调度定时器,或者调用
process.nextTick()
,然后开始处理事件循环。
// timeout_vs_immediate.js
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate
$ node timeout_vs_immediate.js
immediate
timeout
但是,如果你把这两个函数放入一个 I/O 循环内调用,setImmediate 总是被优先调用:
// timeout_vs_immediate.js
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
$ node timeout_vs_immediate.js
immediate
timeout
$ node timeout_vs_immediate.js
immediate
timeout
amqp
Node 的 Event Loop: 6个阶段
timer 阶段: 执行到期的
setTimeout / setInterval
队列回调I/O 阶段: 执行上轮循环残流的
callback
idle, prepare
poll: 等待回调
- 执行回调
- 执行定时器
- 如有到期的
setTimeout / setInterval
, 则返回 timer 阶段 - 如有
setImmediate
,则前往 check 阶段
check
- 执行
setImmediate
- 执行
close callbacks
浏览器与Node的事件循环(Event Loop)有何区别
node跟浏览器端相差比较大,timers 阶段有几个 setTimeout/setInterval 都会依次执行,并不像浏览器端,每执行一个宏任务后就去执行一个微任务
vue
虚拟dom的原理
vue响应式原理
利用观察者模式,实现响应式。实现了 Observer 和 Watcher 对应观察者模式中的观察者和订阅者。
其中 Observer 利用 Object.defindePropotype 劫持了数据的 getter 和 setter 方法监听数据的变化,在 getter 中添加 Watcher 到依赖列表dep,在 setter 中在数据变化的时候,根据 dep 通知对应的 Watcher。
Watcher是订阅者,在vue 编译模板的时候添加到对应的依赖,里面包含 update 方法,用于在数据变化的时候调用更新。
virtualDOM是什么
virtualDOM是DOM节点树的一个映射
使用原因:真实dom依赖环境,操作开销大,节点渲染频繁
它的作用是,每次数据变化时,通过virtualDOM执行diff算法进行对比得到差异结果后,再一次性对DOM进行批量更新操作
diff算法不是传统意义上的通过深度优先遍历DFS算法算法,它做了以下几点改进:
同层级的节点进行比较
在新旧节点树之间按层级进行diff得到差异,可以算法复杂度降低到O(n)
按类型进行diff
VirtualDOM只对相同类型的同一个节点进行diff,当新旧节点发生了类型的改变时,则并不进行子树的比较,直接创建新类型的VirtualDOM,替换旧节点
例如同类型的组件照常进行比较,不同类型的组件不进行比较直接替换节点
列表按key进行diff
在没有key值的列表diff中,只能通过按顺序进行每个元素的对比,更新,插入与删除
如果有key就能够快速识别新旧列表之间的变化内容
vue组件通信的方式
父组件向子组件
props:从上往下传
$children:获取父组件额属性
Porvide/inject: 在父组件中通过
provider
来提供属性,然后在子组件中通过 inject 来注入变量,深层级应对子组件向父组件
$emit
: 传入事件和参数,父组件用$on
监听$parent
其他
中央事件总线EventBus:通过新建一个
Vue
事件bus
对象,然后通过bus.$emit
触发事件,bus.$on
监听触发的事件vuex
Vue 中的 computed 是如何实现的
computed内部实现了一个惰性的watcher,在实例化的时候不会去求值,其内部通过dirty属性标记计算属性是否需要重新求值。当computed依赖的任一状态(不一定是return中的)发生变化,都会通知这个惰性watcher,让它把dirty属性设置为true。所以,当再次读取这个计算属性的时候,就会重新去求值。
computed本身是通过代理的方式代理到组件实例上的,所以读取计算属性的时候,执行的是一个内部的getter,而不是用户定义的方法。
vue nextTick实现
等待下一次 DOM 更新刷新的工具方法。
利用事件循环中的微任务(或宏任务)实现:Promise || MutationObserve || setImmediate || setTimeout
注意:dom的更新是同步的,前一步更新后一步就能拿到结果,所以nextTick能获取dom操作后的结果
vue和react的区别
- 数据响应式: push-base VS pull-base
- 数据流方向:双向数据流 VS 单向数据流
- 代码组织方式:模板代码 VS 函数式编程
引申:vuex 和 react-redux 区别:数据可变性(vuex直接修改数据,reat-redux用的是不可变数据,每次返回一个新的state)
vue-router
react
React 组件的生命周期方法
一些最重要的生命周期方法是:
- componentWillMount**()** – 在渲染之前执行,在客户端和服务器端都会执行。
- componentDidMount**()** – 仅在第一次渲染后在客户端执行。
- componentWillReceiveProps**()** – 当从父类接收到 props 并且在调用另一个渲染器之前调用。
- shouldComponentUpdate**()** – 根据特定条件返回 true 或 false。如果你希望更新组件,请返回true 否则返回 false。默认情况下,它返回 false。
- componentWillUpdate**()** – 在 DOM 中进行渲染之前调用。
- componentDidUpdate**()** – 在渲染发生后立即调用。
- componentWillUnmount**()** – 从 DOM 卸载组件后调用。用于清理内存空间。
react hook的优缺点
- 优点
- 易于复用,可以组合各种hook,方便移植
- 代码量更少
- 缺点
- 响应式的useEffect,形成一条依赖链,没有componentDidmount生命周期好用。
- 不擅长异步。容易引入组件的旧值,类似闭包。解决:useRef保存引用。
- 不能放在if 语句里面使用。因为组件hookStates存放hooks数据,state判断调用哪个hook,是通过hook的调用顺序,if会使得调用顺序不可控。
- 避免缺点:
- useEffect尽量少依赖
- 组件划分小而完整
- 异步尝试传入从组件传入函数参数
redux 简述
一个状态管理容器,生成一个只能使用 action 通过 reduer 变更的 state (传入state和action返回一个新的state)
react hook
- useEffect 怎么替代 componentDidMount,可以第二个参数传入[],如:
useEffect(()=> {}, [])
react 合成事件
React Fiber
fiber 是一个 reconciliation engine,实现了 virtual DOM 的增量渲染,也就是在 reconciliation 阶段实现了 virtual DOM 结构的同时,也可以拆成多个子任务执行 diff 任务,最后统一在 commit 阶段执行 side-effect 和 相关浏览器 DOM 的渲染
setState是同步还是异步的
setState函数是本身是同步的,但注意:
- 在合事件中是类似异步的,因为事件在更新机制中触发了isBranchUpdate为true,次数多次setState会存入队列,在更新末统一执行,所以此时不能立即拿到结果
- 异步函数和原生事件中,根据js异步机制,如果在更新机制中,会等同步的更新机制更新完,再执行异步任务,此时更新完的isBranchUpdate为false,同理不在更新机制 isBranchUpdate 也为 false,所以此时setState后可以立即拿到结果
webpack
如果要实现一个组件的动态加载,会用webpack的什么方法
import()
webpack 中 loader 和 plugin 的区别是什么
loader,它是一个转换器,将A文件进行编译成B文件,比如:将A.less转换为A.css,单纯的文件转换过程。
plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务
[参考](While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables.)
性能优化 webpack 汇总
减少打包时间
- 优化loader配置
- happyPack插件开启多线程打包
减少打包后的体积
- 使用CDN放置部分资源
- UglifyJS代码压缩
开启 tree shaking 去除无用代码
优化打包后的加载速度
- 分割代码以按需加载
webpack异步加载原理
编程规范
组件设计原则
- 低耦合高内聚
- 分清业务组件和通用组件
- 健壮性,保证能在各种场合适用
前端常用的设计模式
工厂模式
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行
例子:父类就像工厂生产自行车,可能会有轮子、把手、踩踏板、刹车,但是不同型号的自行车会有不同的东西如变速器,这些交给子类生产线复制
单例模式
一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,并提供一个访问它的全局访问点
例子: 前端弹窗的实现,调用多次弹窗函数,只实例化一次弹窗
代理模式
为其他对象提供一种代理层以控制对这个对象的访问,不直接访问对象
例子:限制接口频繁访问访问数据库,可以做一层代理,一段时间
观察者模式
存在一对多关系时,则使用观察者模式
例子:vue的双向绑定,当一个对象被修改时,则会自动通知它的依赖对象。
vs:
发布/订阅模式
之前是观察者模式的别名,现在有所区别,发布订阅模式相比观察者模式多了个事件通道作为调度中心
状态模式
关键是区分事物内部的状态,事物内部状态往往会带来事物的行为改变,即允许对象在内部状态发生改变时改变它的行为
职责链模式
处理请求组成的一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象
策略模式
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
例子:表单验证的实现,表单的各种校验函数封装成策略对象,通过外部参数组合使用表单校验
在以类为中心的传统面向对象语言中,不同的算法或者行为被封装在各个策略类 中,Context 将请求委托给这些策略对象,这些策略对象会根据请求返回不同的执行结果,这样 便能表现出对象的多态性。
实际上在 JavaScript 这种将函数作为一等对象的语言里,策略模式已经融入到了语言本身当中,我们经常用高阶函数来封装不同的行为,并且把它传递到另一个函数中。当我们对这些函 数发出“调用”的消息时,不同的函数会返回不同的执行结果。
适配器模式
用来解决两个接口不兼容问题,由一个对象来包装不兼容的对象,比如参数转换,允许直接访问
js设计原则
单一职责原则
单一职责原则,一个类只提供一种功能,不要存在过多导致类变化的原因。
开放封闭原则
类,方法等应当对其扩展开放,对其修改封闭
里氏替换原则
子类必须能够替换它们的基类。
依赖倒置原则
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
接口分离原则
使用多个专门的接口来取代一个统一的接口
最少知识原则
就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话
网络
五层协议
应用层 报文 面向用户 http、FTP、DNS
传输层 报文段 端对端 TCP/UD、 socket
网络层 数据报 建立主机之间连接 ip、ARP、routing
数据链路层 帧 点对点 p2p、交换机
物理层 比特
其他
H5移动端兼容问题
遇到大量if的代码如何优化
记住下面的口诀:
互斥条件表驱动
嵌套条件校验链
短路条件早return
零散条件可组合
- 互斥条件,表示几个条件之间是冲突的,不可能同时达成的。比如说一个数字,它不可能同时是4和2。在这种情况下用表驱动就非常合适,表驱动的核心是key-handler,拿某个key去对应某个handler,只要条件符合key,那么就执行对应的handler。
- 嵌套条件,表示几个条件是必须同时达成的,比如,只有在手机号码格式正确的情况下才执行某个操作,格式正确就包括必须是字符串、长度为11位、所有位都是数字这些条件,那么就可以使用校验链,比如通过描述的校验链:”string&size:11&numchar:0,*”。能用的形式很多,你可以直接使用&&逻辑运算符拼接条件,也可以把规则写成Validator。
- 短路条件,表示只要某个条件满足了,那么就不用再继续下面的任何操作了。比如传进来的参数是null、参数如果为0,就不会有其他操作等情况,都可以聚合这些条件,在一开始的时候就判断并且直接返回,或者程序在中间会产生一些可以直接判断为不需要继续往下操作的条件,那么同样是直接在当场就return。
- 零散条件,是指存在以上三种任意组合的条件形式,那么就可以通过组合这些解决方案。例如,遇到null,直接短路返回,然后之后的代码使用表驱动来区分互斥条件,在达成某个互斥条件的时候,通过校验链来验证嵌套条件等。
什么是泛型
泛型是一个适用于多种类型的函数,其中T为类型变量
function identity<T>(arg: T): T {
return arg;
}
微前端框架
微前端(Micro-Frontends)是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。
方案 | 描述 | 优点 | 缺点 |
---|---|---|---|
Nginx路由转发 | 通过Nginx配置反向代理来实现不同路径映射到不同应用,例如www.abc.com/app1对应app1,www.abc.com/app2对应app2,这种方案本身并不属于前端层面的改造,更多的是运维的配置。 | 简单,快速,易配置 | 在切换应用时会触发浏览器刷新,影响体验 |
iframe嵌套 | 父应用单独是一个页面,每个子应用嵌套一个iframe,父子通信可采用postMessage或者contentWindow方式 | 实现简单,子应用之间自带沙箱,天然隔离,互不影响 | iframe的样式显示、兼容性等都具有局限性;太过简单而显得low |
Web Components | 每个子应用需要采用纯Web Components技术编写组件,是一套全新的开发模式 | 每个子应用拥有独立的script和css,也可单独部署 | 对于历史系统改造成本高,子应用通信较为复杂易踩坑 |
组合式应用路由分发 | 每个子应用独立构建和部署,运行时由父应用来进行路由管理,应用加载,启动,卸载,以及通信机制 | 纯前端改造,体验良好,可无感知切换,子应用相互隔离 | 需要设计和开发,由于父子应用处于同一页面运行,需要解决子应用的样式冲突,变量对象污染,通信机制等技术点 |
react 高阶组件
高阶组件是参数为组件,返回值为新组件的函数。
react 错误边界
错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。
- React内部其实也是通过
try...catch...
形式是捕获各阶段的异常 - 但是只在 fiber 的 reconciliation 和 commit 两个阶段的特定几处进行了异常捕获,这也是为什么异常边界只能捕获到子组件在构造函数、render函数以及所有生命周期函数中抛出的异常
KeepAlive
<KeepAlive>
是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
其通过在内置组件 render 方法缓存 vnode 和后面渲染的过程中缓存真实 dom 达到缓存组件实例的目的。
TCP 和 UDP 的区别
- TCP 是面向连接,UDP 是无连接的
- TCP 提供可靠交付,UDP 尽最大可能交付
- TCP 是面向字节流,UDP 是面向报文
- TCP 有拥堵控制而UPD没有
MVC 和 MVP 以及 MVVM 的区别
无论是 MVVM 还是 Presentation Model,其中最重要的不是如何同步视图和展示模型/视图模型之间的状态,是使用观察者模式、双向绑定还是其它的机制都不是整个模式中最重要的部分,最为关键的是展示模型/视图模型创建了一个视图的抽象,将视图中的状态和行为抽离出一个新的抽象,这才是 MVVM 和 PM 中需要注意的。
什么是突变和纯函数
Promise 和 await 内部的错误处理
Promise 内部产生错误过程:内部抛出错误 -> Promise 状态变为 reject -> throw Error 到上层 -> 有 catch 则捕获后处理,并且不再往上 throw -> 无 catch 抛出到上层可以被 unhandledrejection(浏览器环境) 或 uncaughtException(node 环境) 监听捕获 -> window.onerror
怎么处理前端异常
1.可疑区域增加 Try-Catch
2.全局监控 JS
异常 window.onerror
3.全局监控静态资源异常 window.addEventListener
4.捕获没有 Catch
的 Promise
异常:unhandledrejection
5.VUE errorHandler
和 React componentDidCatch
6.监控网页崩溃:window
对象的 load
和 beforeunload
7.跨域 crossOrigin
解决
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1249118795@qq.com