一、HTML
1. script标签中defer和async属性的区别?
- 共同点:它们都是去异步加载外部的js脚本文件,不会阻塞页面的解析
- 区别点:
- 执行顺序:async不能保证加载的顺序,defer按照加载顺序执行
- 脚本是否并行执行:async属性表示后续文档的加载和执行与js脚本的加载和执行是并行进行的,即异步执行;defer属性,加载后续文档和js脚本的加载是并行的,但是js脚本要等到文档解析完毕之后才执行,发生在DOMContentLoaded执行之前。
2. 常用的meta标签有哪些
- charset:文档的编码类型
- keywords:关键词
- description:页面描述
- viewport:控制视口属性
二、CSS
1. display:none和visibility:hidden的区别
- display会让元素从渲染树中消失,visibility不会让元素从渲染树中消失
- display为非继承属性,visibility为继承属性,可以修改子孙节点的visibility让他们显示
- 修改display会造成回流,而修改visibility只会造成重绘
2. 单行,多行文本溢出的隐藏?
单行文本溢出的隐藏
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
多行文本溢出的隐藏
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-lin-clamp: 3;
3. 两栏布局的实现?
- 利用flex布局,左边设置200px,右边设置flex:1;
- 利用浮动,左边设置200px,左浮动,右边设置overflow:hidden,形成BFC
- 利用浮动,左边200px,左浮动,右边margin-left:200px,宽度为auto
- 利用绝对定位,父元素设置为相对定位,左边设置为absolute定位,且宽度为200px,右边设置为margin-left:200px
- 利用绝对定位,父元素设置相对定位,左边元素设置为200px,右边元素设置为绝对定位,left:200px,其余方向为0
4. 三栏布局
- 绝对定位,左右两栏设置固定宽度,中间设置为对应方向的margin值
- 利用flex布局,左右两栏设置固定宽度,中间一栏设置为flex:1
- 利用浮动,左右两栏设置固定宽度,并设置对应方向的浮动,中间一栏放在最后,且设置左右两个方向的margin值
- 圣杯布局,利用浮动和负边距实现。父元素设置左右的padding,三列均设置为向左浮动,中间一列放最前面,设置宽度为100%,把后面两列挤到下一行,然后通过设置margin的负值来将它们移到上一行,再设置相对定位,移到两边。
5. 水平垂直居中
- 利用绝对定位,设置四个方向的定位为0,然后设置宽度为auto
- 利用绝对定位,设置top:50%;left:50%;然后利用margin-left,margin-top
- 利用绝对定位,设置top,left,最后利用transform:translate(-50%,-50%)
- 利用flex布局,align-items:center;justify-content:center
6. 为什么需要清除浮动,清除浮动的方式?
- 由于子元素浮动,脱离文档流,而容器没有设置固定的高度,其高度不能被内容撑开,此时内容会溢出到容器之外影响布局。
- 父级元素定义height
- 最后一个浮动元素之后添加div空标签,设置clear:both
- 使用:after伪元素
- 给父元素添加overflow:hidden或auto
7. 如何创建BFC?
- body元素
- 父元素设置浮动:float不为none就行
- 元素设置绝对定位:absolute,fixed
- overflow: hidden,auto,scroll
- display: table-cell, inline-block, flex
三、javaScript
1. new操作符的实现原理
- 创建一个新的对象
- 设置原型,将对象的原型设置为函数的prototype
- 让函数的this指向这个对象,并且执行构造函数
- 判断函数的返回值类型,如果是值类型,则返回创建的对象。如果是引用类型,则返回这个引用类型的对象。
2. 对ajax的理解,实现一个ajax请求?
- ajax是通过js的异步通信,从服务器获取xml文档的数据,从而实现刷新部分页面而不用刷新整个网页
- 实现步骤:(1)新建一个XMLHttpRequest对象(2)使用open方法新建一个http请求(3)设置头部信息(4)监听函数(5)onreadystatechange,readyState为4,请求处理完成,根据状态码status(6)sent发起请求
3. ES6与CommonJS有什么异同
- 相同点:都可以对引入的对象进行赋值
- 不同点:CommonJS是对模块的浅拷贝,ES6是对模块的引用。即es6不能改变指针指向。但是CommonJS可以重新赋值。
4. for of 和 for ... in的区别
- for...of遍历获取对象的键值,for...in遍历获取对象的键名
- for...in会遍历对象的整个原型链,for...of只遍历当前对象
- 对于数组的遍历,for...in遍历所有可枚举的属性,包括原型链上的可枚举属性;for...of只会遍历数组的下标对应的属性值
5. 对原型,原型链的理解
在js中都是通过构造函数来新建一个对象,每个构造函数内部都有一个prototype属性,它的值是一个对象,包含了属性和方法。对象内部指向构造函数的prototype的指针也就是对象的原型。如果这个对象没有属性,就会去它的原型对象里去找这个属性,原型对象又会有自己的原型,一直找下去也就是原型链的概念。
6. 异步编程的实现方式
- 回调
- promise
- generator
- async/await
7. async/await对比promise的优势
- 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调用也会带来额外的阅读负担
- Promise传递中间值比较麻烦,而async/await几乎是同步的写法
- 错误处理更加友好,async/await可以通过try catch方式捕获错误,而Promise的错误捕获比较冗余
- 调试更加友好,Promise不方便设置断点
四、计算机网络
1. OPTIONS请求方法及使用场景
- 它是在客户端资源请求前的预检请求
- 主要用途:
- 获取服务器支持的所有HTTP请求方法
- 用来检查访问权限。尤其是CORS时候,options用来判断是否有对资源访问的权限
2. HTTP1.0和HTTP1.1的区别?
- 连接方面:http1.0默认使用非持久连接,而http1.1默认使用持久连接来使多个http服用一个TCP,避免每次都要重新建立的时延
- 资源请求方面:http1.0会传输整个对象,而http1.1在请求头引入了range字段,允许请求某个部分,而且支持断点续传
- 缓存方面:http1.0主要使用expires作为缓存判断,而http1.1新增了etag等缓存策略
- http1.1新增了host字段,用来指定服务器域名
- http1.1支持了更多的请求方法,比如put,head,options
3. TCP与UDP的区别?
- UDP面向无连接,TCP面向连接
- UDP是不可靠传输,不使用流量控制和拥塞控制;TCP是可靠传输,使用流量控制和拥塞控制
- UDP支持单播,多播和广播;TCP支持一对一通信
- UDP面向报文,TCP面向字节流
- UDP首部开销小,仅8字节;TCP首部开销大,至少20字节
- UDP适用于实时场景,TCP适用于传输文件
五、性能优化
1. 回流与重绘的概念及触发条件
-
回流
- 当渲染树中的部分或全部元素的尺寸,结构等发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流。
- 导致发生回流的操作:页面首次渲染,浏览器的窗口大小发生变化,元素的内容发生变化,元素的尺寸或者位置发生变化,添加可见的DOM元素
-
重绘
- 当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。
- 导致发生重绘的操作:color,background等属性变化,border-radius等属性,visibility属性
2. 如何避免回流与重绘?
- 操作DOM时,尽量在低层级的节点进行操作
- 不要使用table布局
- 不要频繁操作元素的样式
- 避免频繁操作DOM,可以创建一个documentFragment
- 事件代理
- 可以先设置display:none,操作结束后再把它显示出来
3. 对节流与防抖的理解
- 防抖是指在2事件被触发n秒后再执行回调,如果在这n秒之内事件又被触发,则重新计时。通常用在用户的按钮点击上
- 节流是指在一定时间内,只有一次触发事件的回调函数执行,通常用在scroll上。
防抖函数的使用场景:
- 按钮提交,防止多次提交,只会执行最后一次的提交
- 服务端验证场景:input输入搜索功能
节流函数的使用场景:
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器的resize
- scrol滚动
4. 防抖和节流的实现
函数防抖的实现
function debounce(fn, wait) {
let timer = null;
return function () {
let context = this, args = [...arguments];
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn.apply(context, args);
}, wait)
}
}
函数节流的实现
function throttle(fn, wait) {
let preTime = Date.now();
return function () {
let context = this, args = [...arguments];
let nowTime = Date.now();
if (nowTime - preTime >= wait) {
preTime = Date.now();
return fn.apply(context, args);
}
}
}
5. 如何用webpack优化前端性能
- 压缩代码:可以使用uglifyjsplugin压缩js文件,css-loader的minimize来压缩css代码
- 利用CDN:将静态资源放在CDN上
- Tree Shaking:将代码中不会用到的片段删除掉。
- Code Spliting:将代码按照组件分块,可以做到按需加载
- 提取公共的第三方库:利用splitchunksplugin插件可以进行公共模块的抽取,利用浏览器缓存可以长期缓存公共代码
六、Vue
1. Vue的基本原理
当一个Vue实例创建的时候,Vue会遍历data中的属性,用Object.defineProperty(vue3使用Proxy)劫持它们的setter和getter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有对应的wtcher实例,它会在组件渲染过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使相关组件得到更新。
2. 双向数据绑定的原理
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
- 对需要observe的数据对象进行递归遍历,都加上setter和getter,就能监听数据变化
- compile解析模板指令,将模板中的变量替换成数据,然后初始化页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据变动,收到通知后会更新视图
- watcher订阅者是observe和compile之间的桥梁,主要做的事情有:(1)在自身实例化时往dep中添加自己(2)自身有一个update更新函数(3)属性变动的时候dep.notice(),调用自身的update方法,触发compile中绑定的回调。
3. nextTick的原理及作用
- 原理:基于js中的eventloop实现的,核心是利用了Promise,MutationObserver,setImmediate,setTimeout之类的方法来模拟微任务和宏任务,实现Vue内部的异步回调队列
- Vue引入该机制的原因:(1)如果是同步更新,多次对一个或多个属性赋值,会频繁触发UI的渲染,异步可以减少无用的渲染(2)vue内部使用虚拟dom,每次更新状态都需要进行diff算法,而这种无用功也将浪费更多的性能,所以异步渲染变得更加至关重要
- 作用:(1)created中进行dom操作,需要用到nextTick(2)数据变化后,需要同步更新dom时候,操作方法可以放在nextTick中进行
4. 对React和Vue的理解,它们的一同
相似之处:
- 都有丰富的社区库
- 都有自己的构建工具
- 都使用了Vitrual DOM
- 都有props的概念,允许组件间的数据传递
- 都有组件化思想,将应用拆分成一个个功能明确的模块
不同之处:
-
Vue是双向数据绑定,React支持单向数据流
-
虚拟DOM:
- Vue可以更快计算出Virtual DOM的差异,主要是因为它在实例化时,已经对数据进行了依赖收集,会跟踪每个组件的依赖关系,不必重新渲染整个组件树
- React,每次状态改变时,都会刷新整个组件树。当然也可以通过PureComponent/shouldComponentUpdate进行控制,优化渲染
-
组件化
- Vue支持特定的template模板的编写
- React推荐jsx,更加灵活
-
监听数据变化的实现原理不同
-
构建工具:Vue-Vue-cli React-Create React app
-
高阶组件
5. Vue3.0有什么更新
- 监测机制的改变:基于Proxy的observer实现,数据更全面覆盖,解决了Vue2中的很多限制
- 支持监听属性的添加和删除;支持监听数组的小标和长度的变化;支持map,set,weakmap,weakset数据结构的监听
- 模板:写法上的区别,composition api 相对于options api更加结构清晰,复用性增强
- 基于Tree-shaking优化
- 支持fragment
七、React
1. React事件机制
- React并不是将事件直接绑定在真实的dom上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交给真正的处理函数执行。
- React的合成事件,阻止冒泡的方式是event.preventDefault方法
实现合成事件的目的:
- 合成事件可以解决浏览器之间的兼容问题
- 原生浏览器事件,每个监听对象都会有一个处理函数,会造成高额的内存分配
2. React事件与普通的html事件的区别
区别:
- 对于事件名称命名方式,原生事件为全小写,react事件采用小驼峰
- react事件不能通过return false方法来阻止浏览器的默认行为,需调用preventDefault来阻止默认行为
优点:
- 兼容所有浏览器,更好的跨平台
- 将事件统一存放在一个数组,避免频繁的新增与删除
- 方便react统一管理和事务机制
3. React Fiber的理解,它解决了什么问题?
在React15,渲染时,会递归对比虚拟dom树,找出需要变动的节点,然后同步更新它们。这样一旦需要大量的节点,会导致用户触发的事件得不到响应,导致用户感觉卡顿。 通过React Fiber架构,让这个执行过程变成可中断,让出CPU执行权,让位给高优先级的任务,等浏览器空闲后再恢复渲染。requestAnimation
4. 类组件与函数组件有什么异同?
相同点:都可以复用,类组件可以改写成函数组件,函数组件也可以改写成类组件 不同点:
- 开发时的心智模型不同,类组件是面向对象编程,有继承,生命周期等核心概念,而函数组件是函数式编程
- 以前,需要使用生命周期的组件主推类组件,而现在组合优于继承,函数式组件也可以有生命周期的模拟,更加推崇函数组件
- 性能优化上,类组件依靠shouldComponentUpdate/PureComponent来阻止渲染提升性能,而函数组件依靠memo,callback来缓存结果提升性能
- 函数组件提供了更加细粒度的复用,更适应React的发展
5. React setState调用之后发生了什么? 是同步还是异步?
-
React中setState后发生了什么 (1)调用setState之后,React会将传入的参数与组件当前的状态进行合并,然后会以高效的方式根据新的状态构建DOM树 (2)React得到新的DOM树会与旧的DOM进行diff比较,得到差异的节点,实现最小化重新渲染 (3)如果在短时间内频繁触发setState,React会将state的改变放入队列中,在合适地时机进行批量更新state和视图。
-
setState是同步还是异步?
- 在React可以控制的地方,是异步,比如React的生命周期事件和合成事件中,都会走合并操作,延迟更新的策略
- 在React无法控制的地方,比如原生事件,也就是setTimeout,setInterval等事件中,就只能同步更新
6. React的工作流程
以store为核心,它可以看作一个数据存储中心,但是不能直接改变,修改数据需要通过action,组件中通过主动发送action,然后dispatch这个action方法,通过reducers去更新数据,从而重新渲染页面
7. redux中的异步请求怎么处理
- redux-thunk
- redux-saga
8. React的diff算法
React没有采用传统的遍历dom的方式,而是采用了分治的方式,将单一节点对比转为3种类型的节点的对比,分别是基于树的,基于组件的,基于元素节点的,以此来提升效率
- 树对比:对两棵虚拟dom树进行同层级的比较
- 组件对比:如果是同一组件类型,则进行树的对比;否则,直接放入到补丁中
- 元素对比:同一层级的子节点,通过key进行对比
自从react16引入了fiber之后,整个更新过程可以随时暂停恢复,节点和树就分别采用了FiberNode和FiberTree进行重构。FiberNode采用了双链表的结构,可以直接找到兄弟节点和子节点。
八、手写代码
1. 手写instanceof
function myInstance(left, right) {
let proto = Object.getPrototypeOf(left),
prototype = right.prototype;
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
2. 手写new操作符
function objectFactory() {
}
九、Node相关知识
1. Node中的EventLoop和浏览器中的EventLoop区别?
事件循环原理
-
node初始化
- 初始化node环境
- 执行输入代码
- 执行process.nextTick回调
- 执行microtasks
-
进入EventLoop
- timers阶段
- 检查timer队列是否有到期的timer(setTimeout/setInterval)回调,有的话将到期的回调按照timerId升序执行
- IO callback阶段
- 检查是否有等待的IO回调,有的话执行
- idle,prepare阶段
- nodejs内部调用
- poll阶段
- 检查是否存在尚未完成的回调,如果存在,分为两种情况:
- 如果队列不为空,执行可用回调
- 如果队列为空,检查是否有setImmediate回调,如果有,则退出poll阶段,进入check阶段;如果没有,超时之前node阻塞在这,等待新的事件通知
- 如果不存在尚未完成的回调,直接退出poll阶段
- 检查是否存在尚未完成的回调,如果存在,分为两种情况:
- check阶段
- 如果有setImmediate回调,执行回调
- closing callback阶段
- timers阶段
-
检查是否有活跃的handlers(定时器,IO事件句柄)
- 如果有,继续下一轮循环
- 没有,就退出事件循环,退出程序
2. 架构
对比下浏览器与Node的架构,如下图:
Chrome的中间层能力是有限的,而Node中,我们可以随意的操控文件,甚至搭建各种服务,虽然Node不处理UI层,但是与浏览器以相同的机制和原理运行,并且在中间层这里有着自己强大的功能
所以,简单直观的来讲 Node 就是脱离了浏览器的,但仍然基于 Chrome V8 引擎的一个 JavaScript 的运行环境。
轻量、高效、事件驱动、非阻塞 I/O是Node几个很重要的特性
基础架构可以大致分为下面三层: