写在前面:这些面试题都是我个人在24届秋招中所遇到的高频且有难度的面试题,答案仅供参考,请不要尝试去背八股。希望各位读者能独立思考总结出自己的答案,收集不易,请不要吝啬你的赞和关注!
HTML CSS
HTML 页面渲染过程
- 解析HTML文件,创建DOM树
- 解析CSS文件,创建CSSOM树
- 合并形成渲染树
- 布局与绘制
简述输入网址到页面显示的过程
- 通过DNS将URL解析成对应的IP地址
- 再通过IP地址和目标服务器建立连接
- 浏览器向服务器端发送HTTP请求
- 服务器接受请求后,进行处理向浏览器响应
- 浏览器接收服务器的响应,渲染页面。
页面导入样式时,使用link和@import有什么区别?
页面被加载时,link会同时被加载,而@import引用的css会等到页面被加载完再加载。
常见meta元素
- charset: 指定文档编码
- name content:
-
- author 作者
- keywords 关键词
- description 描述
- viewport 网页整体描述
行内元素、块级元素、行内块元素、空元素
- 行内元素:a span br
- 块级元素:div p h
- 内联元素:img input
- 空元素:br hr
导致页面加载白屏时间长的原因有哪些,怎么进行优化?
白屏时间:即用户点击一个链接或打开浏览器输入URL地址后,从屏幕空白到显示第一个画面的时间。
解释一下:输入URL到页面展示的过程。
- DNS:
-
- DNS缓存优化
- DNS预加载策略
- 好一点的DNS服务器
- TCP | 服务端 优化
- 浏览器解析优化:
-
- 尽可能精简的HTML代码结构
- 尽可能优化CSS代码结构
- js代码异步加载或者放在body后面
- 首屏关键css内联
- 首屏不需要的图片懒加载
跨域怎么带上cookie
响应头
Access-Control Allow-Origin:
Access-Control-Allow-Credentials: true
如何实现单行/多行文本溢出的省略样式?
- 单行文本溢出
white-space: nowrap; /* 防止换行 */
overflow: hidden; /* 隐藏溢出部分 */
text-overflow: ellipsis; /* 显示省略号 */
- 多行文本溢出
display: -webkit-box;
-webkit-box-orient: vertical; /* 设置为垂直方向 */
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 3; /* 最大显示行数 */
行内元素和块级元素 行内块元素有什么区别
- 块级元素:独占一行,默认宽度撑满父元素。可以设置宽高内外边距。
- 行内元素:不独占一行,宽度取决于内容大小,只能设置外边距。
- 行内块元素:不独占一行,宽度取决于内容大小,可以设置宽高内外边距。
说说对 CSS 预编语言的理解,以及它们之间的区别?
CSS 预编译语言是一种用来增强和扩展纯 CSS 的工具,它们在编写样式时引入了变量、嵌套、函数、混合等功能,使得样式表更加易于维护和组织。
回流和重绘
渲染页面的两个概念,也会相互影响。
- 回流:浏览器对元素和尺寸等属性进行计算。
- 重绘:浏览器根据元素的样式属性进行绘制。
CSS选择器及优先级
- 选择器:内联样式、ID选择器、类选择器、属性选择器、伪类选择器、元素选择器、通用选择器。
- 优先级:内联样式>ID>类>元素
如何实现单行文本两端对齐。
可以使用text-align: justify; 最后一行不希望换行,所以把最后一行之前加上一个空行。
BFC 如何触发 应用场景
定义:BFC 是指一个独立的渲染区域,不会和外部布局相互影响。
触发:
- 浮动元素
- 绝对定位元素
- display: flow-root
- overflow: hidden
- flex grid
应用场景:
- 清除浮动
- 防止外边距折叠
- 包裹浮动元素
- 创建隔离的环境
CSS中的 “flex:1;” 是什么意思?
快捷属性,将flex-grow flex-shrink flex-basis 设置成相同的值,意味着子元素会以相同的宽度填满父元素。
定位方式
- static: 是元素的默认定位方式,元素出现在正常的文档流中,会占用页面空间。
- relative:相对父级元素定位。
- absolute:绝对定位,相对于非static的父元素进行定位。
- fixed:绝对定位,相对于浏览器窗口进行定位
- sticky:可视范围是relative,其他是fixed
Flex
弹性盒子布局:
- flex-direction: row | row-reverse | column | column-reverse; 决定主轴的方向
- flex-wrap: nowrap | wrap | wrap-reverse:决定容器内项目是否可以换行
- flex-flow:上面两种的简写
- justify-content: flex-start | flex-end | center | space-between | space-around; 主轴上的对齐方式。
- align-items:交叉轴上的对齐方式
- flex-grow: :元素放大数值
- flex-shrink:定义了项目的缩小比例(容器宽度<元素总宽度时如何收缩),默认为1,即如果空间不足,该项目将缩小
- flex-basis: | auto; /* default auto */:设置的是元素在主轴上的初始尺寸
HTML、XML和XHTML有什么区别
- HTML: 超文本标记语言,用来描述和定义网页内容的标记语言。
- XML:可拓展标记语言,给文档加上标签,说明每段文字是干什么的。
- XHTML:可拓展的超文本标记语言,用XML语法写HTML,更加严格。
JavaScript
Es6中对象新增了哪些扩展?
- 属性的简写:ES6中,当对象键名与对应值名相等的时候,可以进行简写。
- 属性名表达式:ES6 允许字面量定义对象时,将表达式放在括号内。
- super关键字:this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象
- 新增方法:
-
- Object.keys(obj)
- Object.is()
- Object.assign()
- Object.getOwnPropertyDescriptors()
- Object.setPrototypeOf()
- Object.getPrototypeOf()
ES6中函数新增了哪些扩展?
- 函数参数的默认值
- length属性,等于没有设置默认值的参数数
- 箭头函数
如何理解Promise?应用场景?
如何理解?
- 定义:
-
- Promise ,译为承诺,是异步编程的一种解决方案
- Promise 对象有三种状态:
-
-
- pending
- fulfilled
- rejeceted
-
-
- 特点:
-
-
- promise的状态一旦改变,就不能再次修改
-
- 使用:Promise对象是一个构造函数,用来生成Promise实例。Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
-
- Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject
- 实例方法:
-
- then()
- catch()
- finally()
- all()
- allSattled()
- race()
应用场景
- 网络请求
- 图片懒加载
如何理解Proxy?应用场景?
- 定义:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义。
- 用法:
-
- const p = new Proxy(target, handler)
- target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
- handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
- 常用 handler 方法:
-
- get()
- set()
- defineProperty()
- 应用场景:
-
- 拦截和监视外部对对象的访问
- 在复杂操作前对操作进行校验或对所需资源进行管理
- 观察者模式
你是怎么理解ES6中Module的?使用场景有哪些?
- 定义:是能够单独命名并独立地完成一定功能的程序语句的集合。
- 作用:
-
- 避免全局变量被污染
- 便于代码编写和维护
- 方案:
-
- Common.js:common.js规范是node.js的模块化规范
- AMD/CMD/UMD:AMD / CMD 规范是一种异步模块加载规范
- ES6 MODULE:ES6 module功能主要由两个命令构成:export和import。
- 对比
-
- CommonJS 模块输出的是一个值的拷贝,ESModule 输出的是值的引用。
- ESModule 的模块化是静态的,也就是说在编译阶段就需要确定模块之间的依赖关系
Javascript字符串的常用方法有哪些?
- 增
-
- concat:用于将一个或多个字符串拼接成一个新字符串。
- 删
-
- slice()
- subStr()
- subString()
- 改
-
- toLowerCase()、 toUpperCase()
- trim()
- repeat()
- 查
-
- chatAt()
- indexOf()
- startWith()
- includes()
- 转换成数组
-
- split:把字符串按照指定的分割符,拆分成数组中的每一项
- 模板匹配方法
-
- match
- search
- replace
谈谈 Javascript 中的类型转换机制
- 显示转换
-
- Number()
- parseInt()
- String()
- Boolean()
- 隐式转换:比较运算和算术运算都会发生隐式转换
-
- 转换成布尔值:【undefined,null,false,+0,-0,NaN,""】以上会转换成 false,其他都为 true。
- 转换成字符串:
- 自动转换成数值:
== 和 ===有什么区别,分别在什么情况使用?
- 区别:相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换
- 应用场景:除了在比较对象属性为null或者undefined的情况下,我们可以使用相等操作符(==),其他情况建议一律使用全等操作符(===)。
JavaScript中的原型,原型链分别是什么?
- 原型:JavaScript是基于原型的我们创建的每个函数都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
- JavaScript 中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链。
Javascript如何实现继承?
- 原型链继承
- 构造函数继承:创建子类实例的时候调用父类的构造函数
- 组合继承:用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。
- 寄生组合继承:
- extends
JavaScript中执行上下文和执行栈是什么?
- 执行上下文:
-
- 定义:执行上下文是一种对Javascript代码执行环境的抽象概念,也就是说只要有Javascript代码运行,那么它就一定是运行在执行上下文中。
- 类型:全局执行上下文、函数执行上下文、Eval 函数执行上下文
- 生命周期:
-
-
- 创建阶段:
-
-
-
-
- 确定this指向
- 词法环境
- 变量环境
-
-
-
-
- 执行阶段:执行变量赋值、代码执行。
- 回收阶段:执行上下文出栈等待虚拟机回收执行上下文。
-
- 执行栈:执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
typeof 与 instanceof 有什么区别
- typeof 操作符返回一个字符串,表示未经计算的操作数的类型。
- instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
什么是事件代理,以及它的应用场景有哪些?
- 事件代理:事件代理,俗地来讲,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素。
- 应用场景:点击列表项的时候触发一个事件。
说一说一个 new 关键字做了哪些事情?
- 创建一个空对象。
- 将这个空对象的__proto__属性链接到构造函数的原型对象。
- 将构造函数的this绑定成这个新对象,并接收返回值。
- 如果返回值存在并且是引用数据类型则返回函数返回值,否则返回创建的对象。
说说ajax的原理,以及如何实现?
- 定义:Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面。
- 用法:
-
- 创建 Ajax 的核心对象 XMLHttpRequest 对象
- 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
- 构建请求所需的数据内容,并通过 XMLHttpRequest 对象的 send() 方法发送给服务器端
- 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
- 接受并处理服务端向客户端响应的数据结果
- 将处理结果更新到 HTML 页面中
事件循环
- 作用:为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)
- 定义:同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就是事件循环。
- 其中异步任务中的微任务的优先级高于宏任务。
DOM 的常见操作
- 创建节点:createElement
- 获取节点:querySelector、querySelectorAll
- 更新节点:innerHTML、innerText、textContent
- 删除节点:调用父节点的removeChild把自己删掉
Javascript本地存储的方式有哪些,有什么区别,及有哪些应用场景?
- 存储方式
-
- cookie
- sessionStorage
- localStorage
- 区别
-
- 存储大小:
-
-
- Cookie数据大小不能超过4KB(千字节)。
- sessionStorage和localStorage存储的数据限制较大,可以达到5MB(兆字节)或更大。
-
-
- 有效时间:
-
-
- localStorage存储是持久的。
- sessionStorage存储只在当前浏览器窗口关闭后自动删除,也就是说,当用户关闭浏览器标签页或窗口时,存储的数据会被清除。
- cookie可以设置过期时间,在该时间到达之前一直有效,即使用户关闭浏览器或计算机。如果未设置过期时间,它将成为一个会话cookie,仅在浏览器会话期间存在,一旦用户关闭浏览器,cookie将被删除。
-
-
- 数据与服务器之间的交互方式:
-
-
- cookie的数据会自动在每个HTTP请求中传递到服务器,因此服务器可以读取和写入cookie。这使得cookie在处理会话管理、用户跟踪和状态维护等方面非常有用。
- sessionStorage和localStorage不会自动将数据传递给服务器。它们仅在浏览器本地保存数据,主要用于客户端存储和管理数据,与服务器进行数据交互时,需要通过其他方式(例如AJAX或表单提交)将数据显式地发送到服务器。
-
- 应用场景
-
- 标记用户与跟踪用户行为的情况,推荐使用cookie
- 适合长期保存在本地的数据(令牌),推荐使用localStorage
- 敏感账号一次性登录,推荐使用sessionStorage
说说 Javascript 为什么会存在数字精度丢失的问题,以及如何进行解决?
- 原因:由于0.1和0.2在二进制表示中是无限循环小数,因此在进行浮点数运算时会引起舍入误差。
- 用第三方库
如何判断一个元素是否在可视区域中?
使用Intersection Observer API(更现代的方法):
步骤:
- 创建一个 IntersectionObserver 对象:首先,我们需要创建一个 IntersectionObserver 对象,这可以通过构造函数来完成。我们需要传入一个回调函数和一个可选的配置对象作为参数。回调函数会在目标元素进入或离开可视区域时被调用。
- 观察目标元素:使用 IntersectionObserver 的 observe() 方法,将要观察的目标元素传递给观察者。这样,观察者就会开始监测目标元素的交叉状态。
- 回调函数的执行:当目标元素与视口(或根元素)发生交叉时,观察者会触发回调函数。回调函数会传递一个观察器实例的数组,每个实例都包含了有关目标元素的交叉信息。
- 判断交叉状态:在回调函数中,我们可以使用 IntersectionObserverEntry 对象来获取目标元素的交叉状态。常用的属性有:
-
- isIntersecting:一个布尔值,表示目标元素是否与视口或根元素交叉(部分或全部进入可视区域)。
- intersectionRatio:一个介于0和1之间的小数,表示目标元素在可视区域内的可见程度(1表示完全可见,0表示完全不可见)。
- boundingClientRect:一个 DOMRect 对象,表示目标元素的位置和尺寸信息。
- rootBounds:一个 DOMRect 对象,表示根元素(或视口)的位置和尺寸信息。
- 停止观察:如果不再需要观察目标元素,可以调用 unobserve() 方法,将目标元素从观察者中移除。
- 销毁观察器:当观察者不再需要使用时,可以调用 disconnect() 方法,彻底销毁观察器。
大文件的断点续传
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传。
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
- 初始化一个分片上传任务,返回本次分片上传唯一标识;
- 按照一定的策略(串行或并行)发送各个分片数据块;
- 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
下拉刷新和上拉加载的实现
下拉刷新:
- 监听页面或容器的下拉动作:使用JavaScript监听页面或特定容器的下拉动作。通常,这可以通过监听touchstart、touchmove和touchend事件来实现。
- 判断下拉距离:在touchmove事件中,计算用户下拉的距离,并根据一定的阈值来判断是否执行刷新操作。
- 触发刷新:如果用户下拉的距离超过设定的阈值,触发刷新操作,并加载更新的数据。
- 更新页面内容:一旦获取到新数据,使用JavaScript更新页面内容,显示新加载的数据。
- 停止刷新:在数据加载完成后,停止刷新状态,恢复页面的正常滚动。
上拉加载:
- 监听页面或容器的滚动事件:使用JavaScript监听页面或特定容器的滚动事件。
- 判断滚动位置:在滚动事件中,计算用户滚动的位置,并判断是否接近容器底部,通常可以通过比较scrollTop、clientHeight和scrollHeight等属性来判断。
- 触发加载:当用户接近容器底部时,触发加载更多数据的操作。
- 加载更多数据:执行加载更多数据的逻辑,获取新数据并将其添加到页面内容的末尾。
- 停止加载:一旦新数据加载完成,停止加载状态,允许用户继续滚动。
什么是单点登录,以及如何进行实现?
- 定义:SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
- 实现
-
- 同域名下实现:需要将Cookie的 domain属性设置为父域的域名(主域名),同时将 Cookie 的path属性设置为根路径,将 Session ID(或 Token)保存到父域中。这样所有的子域应用就都可以访问到这个Cookie。不过这要求应用系统的域名需建立在一个共同的主域名之下,如 tieba.baidu.com 和 map.baidu.com,它们都建立在 baidu.com 这个主域名之下,那么它们就可以通过这种方式来实现单点登录。
- 不同域名下的单点登录:
-
-
- 用户在认证中心进行登录,登录成功后,认证中心生成一个 Token 并将其写入认证中心的 Cookie(不可访问)。
- 用户访问应用系统 A,应用系统 A 检查是否有 Token。如果没有 Token,将用户重定向至认证中心。
- 由于重定向,认证中心的 Cookie 会自动带到认证中心。认证中心检查 Cookie 中的 Token,如果发现用户已登录,则重定向回应用系统 A,并在 URL 中携带生成的 Token。
- 应用系统 A 收到 Token 后,向认证中心确认 Token 的合法性。确认无误后,应用系统 A 记录用户登录状态,并将 Token 写入应用系统 A 的 Cookie。
- 用户再次访问应用系统 A 时,自动携带应用系统 A 的 Cookie(包含 Token)。应用系统 A 验证 Token,发现用户已登录,无需再次跳转到认证中心。
-
null是对象吗?为什么?
null不是对象。
'1'.toString()为什么不会报错?
'1'.toString() 不会报错,因为在JavaScript中,字符串对象(String)是一个包含原始字符串值的对象,它具有与原始字符串值相关的一些方法。
什么是BigInt?
BigInt是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。
typeof 是否能正确判断类型?
- 对于原始类型来说,除了 null 都可以调用typeof显示正确的类型。
- 但对于引用数据类型,除了函数之外,都会显示"object"。
Object.is和===有什么区别?
Object在严格等于的基础上修复了一些特殊情况下的失误,具体来说就是+0和-0,NaN和NaN。
forEach中return有效果吗?如何中断forEach循环?
在forEach中用return不会返回,函数会继续执行。
npm 是什么?
npm是Node.js的包管理工具。
Babel 是什么?
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
Promise中,resolve后面的语句是否还会执行?
会被执行。如果不需要执行,需要在 resolve 语句前加上 return。
NaN 是什么,用 typeof 会输出什么?
NaN:Not a Number,表示非数字
typeof NaN === 'number'
如何判断一个对象是不是空对象?
// 方法1
Object.keys(obj).length === 0
// 方法2
JSON.stringify(obj) === '{}'
箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?
- 箭头函数:箭头函数省去了function关键字,采用箭头=>来定义函数。
- 与普通函数的区别:
-
- 语法更加简洁、清晰
- 箭头函数不会创建自己的this
- 箭头函数继承而来的this指向永远不变
- .call()/.apply()/.bind()无法改变箭头函数中this的指向
- 箭头函数不能作为构造函数使用
- 箭头函数没有自己的arguments
什么是变量提升?
变量提升是JavaScript中的一种行为,它指的是在代码执行前,JavaScript引擎会将变量和函数声明提升到其作用域的顶部,而不是在代码中实际定义的位置。
Object.create 和 new 有什么区别?
| 比较 | new | Object.create |
|---|---|---|
| 构造函数 | 保留原构造函数属性 | 丢失原构造函数属性 |
| 原型链 | 原构造函数prototype属性 | 原构造函数/(对象)本身 |
| 作用对象 | function | function和object |
如何中断 promise?
使用 Promise.all()
说说你对 Object.defineProperty 的理解
- 定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
- 用法:第一个参数是 obj:要定义属性的对象,第二个参数是 prop:要定义或修改的属性的名称或 Symbol,第三个参数是 descriptor:要定义或修改的属性描述符。
如何使用js计算一个html页面有多少种标签?
- 知识点:
-
- 获取所有的DOM节点
- 转化成数组
- 获取数组每个元素的标签名
- 去重
new Set([...document.querySelectorAll('*')].map(ele=> ele.tagName)).size
Promise中的值穿透是什么?
.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。
实现每隔一秒输出1,2,3
- 定时器
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
isNaN 和 Number.isNaN 函数有什么区别?
和全局函数 isNaN() 相比,Number.isNaN() 不会自行将参数转换成数字,只有在参数是值为 NaN 的数字时,才会返回 true。
Object.is() 与比较操作符 “===”、“==” 的区别?
使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的。
数据类型检测方式有哪些?
- typeof:其中数组、对象、null都会被判断为object,其他判断都正确。
- instaceof:instanceof可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。
- constructor:constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了。
- Object.prototype.toString.call():Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型。
箭头函数的 this 指向哪⾥?
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。
JavaScript脚本延迟加载的方式有哪些?
- defer 属性: 给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
- async 属性: 给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
- 动态创建 DOM 方式: 动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
- 使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件
- 让 JS 最后加载: 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
什么是类数组对象?
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。
for...in和for...of有什么区别?
- for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
- for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
- 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值
浏览器的同源策略是什么?
同源是指:域名、协议、端口相同。
- DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
- XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
js中如何判断一个值是否是数组类型?
- instanceof
- Array.isArray
- Object.prototype.isPrototypeOf
- Object.getPrototypeOf
- Object.prototype.toString
JS中怎么阻止事件冒泡和默认事件?
- event.stopPropagation()
- event.stopPropagation()
- return false
Promise.all 和 Promise.allSettled 有什么区别?
Promise.allSettled和Promise.all的最大不同:Promise.allSettled永远不会被reject。
toPrecision 和 toFixed 和 Math.round 有什么区别?
- toPrecision 用于处理精度,精度是从左至右第一个不为 0 的数开始数起。
- toFixed 是对小数点后指定位数取整,从小数点开始数起。
- Math.round 是将一个数字四舍五入到一个整数。
怎么使用 setTimeout 实现 setInterval?
setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。
// 思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果
function mySetInterval(fn, timeout) {
// 控制器,控制定时器是否继续执行
var timer = {
flag: true
};
// 设置递归函数,模拟定时器执行。
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
// 启动定时器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
什么是“前端路由”?什么时候适合使用“前端路由”?“前端路由”有哪些优点和缺点?
前端路由(Frontend Routing)是指在前端(客户端)应用中,根据 URL 的变化来动态地加载不同的页面内容,而不需要向服务器发起新的请求。
- 应用:
- 单页应用
- 快速响应
- 频繁交互
- 优缺点
-
- 优点:更好的用户体验、减少服务期负担、更灵活地路由控制
- 缺点:除此加载时耗时、不利于SEO、浏览器兼容性不好
什么是 Polyfill ?
Polyfill 指的是用于实现浏览器并不支持的原生 API 的代码。
toPrecision 和 toFixed 和 Math.round 有什么区别?
- toPrecision 用于处理精度,精度是从左至右第一个不为 0 的数开始数起。
- toFixed 是对小数点后指定位数取整,从小数点开始数起。
- Math.round 是将一个数字四舍五入到一个整数。
使用Promise封装一个异步加载图片的方法
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = function() {
reject(new Error('Could not load image at' + url));
};
img.src = url;
});
如何获取到一个实例对象的原型对象?
- 从构造函数的原型对象
- 实例的__proto__
- es6的obj.getPrototypeOf()
如何保证你的构造函数只能被new调用?
判断new.target这个参数是否为undefined
promise.catch后面的.then还会执行吗?
会,
如果.catch(onRejected)的onRejected回调中返回了一个状态为rejected的Promise实例,那么.catch返回的Promise实例的状态也将变成rejected。
如何区分数组和对象?
- Array.isArray()
- instanceOf
- constructor
- Object.prototypeOf.toString.call()
e.target 和 e.currentTarget 有什么区别?
- e.target:触发事件的元素
- e.currentTarget:绑定事件的元素
Map和WeakMap区别
- Map的键可以是任意类型,WeakMap只接受对象作为键,不接受其它类型的值作为键
- Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键;WeakMap的键是弱引用,键所指向的对象是可以被垃圾回收,此时键是无效的。
- Map可以被遍历,WeakMap不能被遍历
说说你对闭包的理解,以及闭包使用场景
- 定义:闭包让你可以在一个内层函数中访问到其外层函数的作用域。
- 使用场景:
-
- 保护数据:闭包可用于创建私有变量,即外部函数中的变量只能通过内部函数进行访问和修改,不会被外部代码直接访问,从而保护数据的安全性。
- 延迟执行:通过闭包,可以在一个函数执行完成后,将其内部变量保留在内存中,供以后调用使用。这在某些情况下可以实现状态的持久化,例如使用闭包模拟一个计数器或迭代器。
- 封装:闭包可以用于创建具有公共接口和私有实现的模块,允许一些变量和功能对外可见,而将其他变量和功能隐藏起来,达到信息隐藏和模块化的目的。
- 回调函数:在异步编程中,闭包常用于实现回调函数,因为它们允许函数记住和访问回调时所需的上下文信息。
- 在函数式编程中:闭包是函数式编程的一个重要概念,可用于将函数作为参数传递,或者返回一个函数,从而实现更高阶的函数组合
- 内存泄漏问题:如果一个闭包长时间持有不必要的引用,那么该引用将一直存在,从而消耗内存。
-
- 解决方法:可以将不使用的对象设置为null,让js回收。
bind、call、apply 有什么区别
- 三者都可以改变函数的this对象指向
- 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入
- bind 是返回绑定this之后的函数,apply 、call 则是立即执行
cookie、localStorage和sessionStorage 三者之间有什么区别
- 生命周期:
-
- cookie:具有过期时间,可以设置在某个特定的时间点失效,可以在客户端长期存储数据,即使关闭浏览器再次打开也能保持。
- localStorage:没有过期时间,数据会永久存储在客户端,除非代码或用户手动删除。
- sessionStorage:数据仅在当前会话(当前标签页或窗口)中有效,关闭标签页或窗口后数据就会被清除。
- 存储容量:
-
- cookie:最大存储容量通常为 4KB,每个域名下的 cookie 数量也有限制。
- localStorage:一般支持至少 5MB 或更大的存储容量。
- sessionStorage:与 localStorage 类似,一般也支持至少 5MB 或更大的存储容量。
- 网络通信:
-
- cookie:每次请求都会携带对应域名下的 cookie 数据,因此在网络通信中会增加一定的数据量。
- localStorage 和 sessionStorage:不会自动随着请求发送到服务器,仅在客户端使用。
- 访问权限:
-
- cookie:可以设置为具有访问权限的 cookie,在服务器端设置 HttpOnly 属性可以防止客户端(JavaScript)访问某个 cookie,提高安全性。
- localStorage 和 sessionStorage:可以被客户端(JavaScript)直接访问,但也受到同源策略的限制,只有在相同的协议、主机和端口下才能访问。
- 用途:
-
- cookie:主要用于在客户端存储会话信息、用户偏好设置等,也可以用于跟踪用户行为。
- localStorage:适合用于永久性保存数据,比如保存用户的登录状态、缓存数据等。
- sessionStorage:适合用于临时保存数据,比如在页面刷新时需要保留的一些数据,但不需要长期存储的情况。
set map 定义及常用方法及使用场景及与weakmap weakset区别
- Set:
定义:Set 是一种集合,它允许存储任意类型的唯一值(即不重复的值)。
常用方法:
- add(value):向集合中添加一个值,如果值已存在,则不进行重复添加。
- delete(value):从集合中删除一个值。
- has(value):检查集合中是否包含指定的值。
- clear():清空集合中的所有值。
- size:获取集合中值的个数。
使用场景:
- 去重:用于去除数组中重复的元素。
- 列表操作:可以对列表进行交集、并集、差集等操作。
- 存储临时数据:临时存储一组数据,并且不需要键-值对的结构。
- Map:
定义:Map 是一种键-值对的集合,允许存储任意类型的键和值,键是唯一的,值可以重复。
常用方法:
- set(key, value):向映射中添加一个键值对,如果键已存在,则更新对应的值。
- get(key):根据键获取对应的值。
- delete(key):根据键删除对应的键值对。
- has(key):检查映射中是否包含指定的键。
- clear():清空映射中的所有键值对。
- size:获取映射中键值对的个数。
使用场景:
- 数据存储与检索:用于存储和检索键值对数据,比普通对象在存储大量数据时更高效。
- 映射关系:用于存储键值之间的映射关系,如字典等。
- 与 WeakMap 和 WeakSet 的区别:
- 引用关系:Map 和 Set 中的键都是强引用,即键的引用会阻止垃圾回收器回收该对象。WeakMap 和 WeakSet 中的键都是弱引用,即键的引用不会阻止垃圾回收器回收该对象。因此,如果键被回收了,相应的键值对或集合中的元素也会被自动移除。
- 键类型限制:Map 和 WeakMap 的键可以是任意类型的对象,包括原始类型和对象引用。Set 和 WeakSet 的值可以是任意类型的对象,包括原始类型和对象引用。
- 迭代:Map 和 Set 支持迭代方法(如 forEach),可以遍历键值对或集合中的元素。WeakMap 和 WeakSet 不支持迭代方法,因为键的引用不稳定,无法保证在遍历过程中键是否还存在。
使用场景:
- Map 和 Set 适用于需要稳定引用的场景,且需要对数据进行遍历和操作。
- WeakMap 和 WeakSet 适用于临时存储一些临时数据,不需要稳定引用的场景,且无需遍历数据。通常用于防止内存泄漏,因为当键被回收时,相关联的数据也会被自动清除。
什么是作用域链?
在JavaScript中,作用域链是一种用于查找和访问变量的机制。当代码在执行过程中引用一个变量时,JavaScript引擎会根据当前执行上下文的作用域链来寻找该变量。
作用域链是由多个执行上下文对象组成的链式结构。每个执行上下文对象都有一个指向其父级执行上下文的引用。当在当前执行上下文中查找变量时,如果当前执行上下文中没有该变量,JavaScript引擎会沿着作用域链向上查找,直到找到该变量或到达全局执行上下文。
var、let、const之间有什么区别?
- 作用域:
-
- var:var 声明的变量具有函数作用域(function scope),即在声明变量的函数内部有效,如果在函数内部声明的变量不使用 var,它会成为全局变量。
- let 和 const:let 和 const 声明的变量具有块级作用域(block scope),即在声明变量的块(通常是由 {} 包裹的代码块)内部有效,包括 for 循环、if 语句等。
- 变量提升:
-
- var:在函数作用域内,var 声明的变量存在变量提升,即在函数内部任何位置声明的变量,在整个函数中都可见,但在实际声明之前访问该变量会返回 undefined。
- let 和 const:在块级作用域内,let 和 const 声明的变量不存在变量提升,如果在实际声明之前访问该变量,会抛出 ReferenceError 错误。
- 重复声明:
-
- var:允许在同一个作用域内重复声明同名的变量,并且后面的声明会覆盖前面的声明。
- let 和 const:不允许在同一个作用域内重复声明同名的变量,否则会抛出 SyntaxError 错误。
- 赋值和修改:
-
- var 和 let:声明的变量可以重新赋值和修改。
- const:声明的常量在声明时必须进行初始化,并且一旦赋值后就不能再修改,否则会抛出 TypeError 错误。
- 全局对象属性:
-
- var:通过 var 声明的全局变量会成为全局对象的属性。
- let 和 const:通过 let 和 const 声明的全局变量不会成为全局对象的属性,这些变量在全局作用域中仅作用于当前的全局执行上下文。
浏览器缓存
对 this 对象的理解
定义:this 的值是在函数被调用时确定的,它指向当前执行上下文中的一个对象。
分类:
- 全局上下文: 当在全局作用域中使用 this,它会指向全局对象
- 函数调用: 在函数内部,this 的值取决于函数的调用方式:
-
- 函数调用:如果一个函数被作为独立函数调用,this 将指向全局对象(在严格模式下是 undefined)。
- 方法调用:如果一个函数是作为对象的方法被调用,this 将指向调用该方法的对象。
- 构造函数调用:如果一个函数被用作构造函数来创建一个新对象,this 将指向新创建的实例对象。
- 箭头函数: 在箭头函数中,this 的值是在定义函数时确定的,而不是在调用时。它会继承外层作用域的 this 值。
深拷贝 浅拷贝
- 浅拷贝:浅拷贝是指创建一个新对象,并将原始对象中的属性复制到新对象中。
- 深拷贝:实现深拷贝需要递归地复制原始对象的所有属性,包括嵌套的属性,以确保生成的新对象与原始对象完全独立,不共享引用。
Babel 是什么?
Babel 是一个 JavaScript 编译器。
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中
Web worker 是什么?
Web Worker 是 HTML5 标准中提供的一项技术,它可以让 JavaScript 脚本在后台线程运行,从而避免阻塞 UI 线程。Web Worker 可以创建一个独立的线程来执行脚本,从而使得主线程可以专注于用户交互和响应。
计算机基础
操作系统
什么是进程,什么是线程?
进程是计算机中正在运行的程序的实例,一个进程就是一个程序运行实例。它拥有独立的内存空间、代码和数据,并且由操作系统负责调度和管理。
线程是进程的子任务,一个进程可以包含多个线程。它们共享相同的代码和数据,但拥有独立的执行栈和寄存器集合。多个线程可以在同一进程内并发执行,共享进程的资源,如内存空间、打开的文件等。
进程和线程的区别?
- 进程和线程都可以实现并发执行,但进程是独立的执行实体,而线程是依赖于进程的。
- 进程之间资源相互隔离,线程共享所属进程的资源。
- 创建和销毁线程的开销较小,而创建和销毁进程的开销较大。
- 多线程程序的编程复杂度通常比单线程程序高,但多线程可以更好地利用多核处理器来提高程序的执行效率。
进程间的通信方式?
- 管道通信
- 消息队列通信
- 信号量通信
- 共享内存通信
- 套接字通信
进程状态
- 就绪态:进程已经具备运行的所有条件,等待操作系统的调度
- 运行态:进程被操作系统调度
- 阻塞态:进程由于依赖第三方资源而受阻塞
- 结束态:进程运行结束的状态
浏览器渲染进程的线程有哪些?
- GUI 渲染线程
- JS 引擎线程
- 定时器触发线程
- 事件触发线程
- 异步 http 请求线程
- 合成线程
- IO 线程
死锁及解决方法
为了保证资源安全,确保一次仅有一个线程对共享资源进行修改,有两个及以上的线程对同一个资源进行争夺,结果两个线程没有一个让步,并且没有任何的外力进行协调导致的一种僵局。
产生死锁的必要条件:
1.互斥条件,进程要求对所分配的资源进行排它性控制,即该资源一次仅能为一个进程所享用。
2.请求并保持,即该进程对已获得的资源保持不放,又对新的资源保持请求的状态,就像个贪心的人,吃着碗里,看着锅里。
3.不可剥夺,该资源仅能当前进行释放,偏偏这个贪心的人房产证名字写的是他。
4.环路等待条件,闭环了,兄弟们,他想要的东西是他老婆的,他老婆又想要他手里的房产证。
解决死锁的基本方法:
- 破坏《已拥有的资源不可被剥夺》:当某个进程申请不到某个资源时,操作系统强制或者进程自己主动释放已经拥有的资源,或者操作系统强制让用用这个资源的进程释放资源
- 破坏《已拥有的资源不会主动放弃》:在申请资源的时候,主动释放掉已经拥有的资源,即使马上就要用到
- 破坏《等待》:在进程开始运行前先申请好所有的资源,要么都申请到,要么一个都不要。这样就不会有等待的需求了
Ajax 相关面试题
Ajax 是什么?优缺点?
允许在不需要刷新整个页面的情况下,通过在后台与服务器进行数据交换来更新部分页面内容。
优点:
- 异步通信
- 减少带宽消耗,减轻服务器负载
- 提高用户体验
缺点:
- 安全问题:XSS 攻击和 CSRF 攻击
- 不利于 SEO 优化:AJAX 请求的异步内容可能不会被爬取
如何实现一个 AJAX?
- 创建一个 XMLHttpRequest()对象
- 指定请求类型和 URL
- 设置回调函数处理服务器的响应和错误
- 发送请求
function getJson(url){
let promise = new Promise((resolve,reject)=>{
let xhr = new XMLHttpRequest()
//创建一个http请求
xhr.open('Get',url,true)
xhr.onreadystatechange=function(){
if(this.readyState!==4)return
if(this.status>=200&&this.status<400){
resolve(this.response)
}else{
reject(new Error(this.statusText))
}
}
//设置错误监听函数
xhr.onerror=function(){
reject(new Error(this.statusText))
}
//设置响应数据类型
xhr.setRequestHeader('Accept',"application/json")
//发送http请求
xhr.send(null)
})
return promise
}
同源和跨域
什么是同源策略?
同源是指两个页面的协议(protocol)、域名(domain)和端口号(port)完全相同。
什么是跨域?
跨域(Cross-Origin)是指在浏览器中,一个网页的脚本(如 JavaScript)在访问与其不同源的资源时触发的一种安全机制。
跨域如何解决?
-
JSONP
-
WebSocket
-
cors
-
Nginx
JSONP 如何实现?
JSONP(JSON with Padding)是一种跨域请求数据的方法,通常用于获取来自不同域的数据。它通过动态创建
function jsonp(url, jsonpCallback, success) {
let script = document.createElement('script');
script.src = url;
script.async = true;
window[jsonpCallback] = function(data) {
success && success(data);
};
document.body.appendChild(script);
}
CORS 跨域前后端如何设置?
前端一般不需要特殊设置,如果需要发送 cookie 的话需要设置 withCredentials 这个属性为 true。
后端需要设置 header 中的 Access-Control-Allow-Origin: example.com 和 Access-Control-Allow-Origin: true
HTTP 相关
状态码
- 2XX:成功状态码
a. 200:OK
b. 204:No Content - 3XX:重定向状态码
a. 301:永久重定向
b. 302:临时重定向
c. 303:
d. 304:没有修改
- 4XX:客户端错误状态码
a. 400:请求语法错误
b. 401:没有认证
c. 403:该请求被拒绝
d. 404:请求资源不存在
e. 405:请求方法被服务器禁止 - 5XX:服务端错误状态码
a. 500 Internal Server Error
b. 502 Bad Gateway
c. 503 Service Unavailable
d. 504 Gateway Timeout
请求方法
● GET: 向服务器获取数据
● POST:将实体提交到指定的资源,通常会造成服务器资源的修改。
● PUT:上传文件,修改数据
● DELETE:删除服务器上的对象
● HEAD:获取报文首部,与GET相比,不返回报文主体部分
● OPTIONS:询问支持的请求方法,用来跨域请求
● TRACE:追踪 请求—响应 的传输路径
● CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信
GET POST 区别?
- GET 是一个幂等请求,不会对服务器资源产生影响。POST 一般用于修改服务器资源。
- GET 请求结果一般会进行缓存
- GET 请求实体部分为空
OPTIONS 请求方法及应用场景
在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能
● 获取服务器支持的所有HTTP请求方法
● 用来检查访问权限
HTTP 1.0 1.1 2.0 区别
1.1 相比较 1.0 多了:
- 缓存处理:
-
- HTTP1.0:If-Modified-Since 和 Expires
- HTTP1.1:Entity tag If-Unmodified-Since If-Match If-None-Match
- 带宽优化:1.1 支持断点续传
- 长连接:1.1 默认支持长连接,1.0 需要在请求头里加上 keep-alive
- 新增请求方式:
-
- PUT:请求服务器存储一个资源
- DELETE:请求服务器删除标识的资源
- OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项和需求
- CONNECT:保留请求以供将来使用
- TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断
2.0 相比较 1.1 多了:
- 引入了二进制传输,将数据分割为更小的帧,并进行二进制编码,提高了传输效率。
- 多路复用(Multiplexing)允许在一个连接上同时发送多个请求和响应,解决了 HTTP 1.1 中的响应阻塞问题。
- 头部压缩,减少了传输过程中的头部大小,进一步提高传输效率。
- 服务器推送(Server Push)允许服务器在客户端请求之前将数据主动推送到客户端,减少了等待时间。
- 支持优先级和依赖,可以指定哪些请求更重要,提高了资源加载的效率。
缓存相关
强制缓存:如果存在强制缓存,并且缓存没有过期,则直接使用缓存,不需要向服务器发送请求。(字段:cache-control: max-age | expries)
协商缓存:如果强制缓存未命中,但协商缓存可用,则会向服务器发送条件请求,询问资源是否更新。如果服务器返回 304 Not Modified 响应,则直接使用缓存。(字段:If-None-Match 和 ETag | Last Modified)
优缺点
优点:
- 无连接
- 无状态
- 灵活
缺点:
- 无状态
- 明文传输
HTTP 和 HTTPS 区别
- 安全性:HTTP是不安全的协议,数据以明文形式传输,容易被窃听和篡改。HTTPS通过使用SSL/TLS加密协议来保护数据传输的安全性。所有传输的数据都会被加密,因此更难以被窃听或篡改。
- 端口号不同:HTTP 80 端口,HTTPS 443 端口
- HTTPS 需要数字证书验证服务器身份
- HTTPS SEO 排名高
HTTPS 是如何加密通信的?
- 在HTTPS中,对称密钥通常用于加密实际的数据传输,因为它们的加密和解密速度非常快。
- 服务器通常拥有一个数字证书,其中包含了服务器的公钥和其他信息。客户端使用服务器的公钥来加密一个会话密钥,然后将加密的会话密钥发送给服务器。服务器使用其私钥来解密这个会话密钥,从而建立了安全的通信通道。
Websocket 是什么? 和 sse 的区别?
Websocket 是一个全双工、持久化的通信协议。
和 SSE 区别:
- 全双工通信: WebSocket提供了全双工通信,这意味着客户端和服务器之间可以同时发送和接收数据,而不需要客户端首先请求数据。
- 双向通信: WebSocket允许服务器主动向客户端推送数据,这使得它非常适合实时通信应用程序,如聊天应用、在线游戏等。
- 低延迟: 由于WebSocket是持久性连接,它可以实现低延迟的实时通信,响应速度非常快。
- 协议支持: WebSocket使用自己的协议,与HTTP和HTTPS不同,需要特殊的服务器支持。
TCP
什么是TCP?
TCP(Transmission Control Protocol)是一种面向连接的、可靠的传输层协议。
TCP 三次握手?
第一次:
- 客户端发送一个带有 SYN(同步序列号)标志的数据包给服务器。
- 这个数据包告诉服务器客户端想要建立连接,并指明客户端的初始序列号。
第二次:
- 服务器接收到客户端的 SYN 数据包后,会发送一个带有 SYN 和 ACK(确认序号)标志的数据包给客户端。
- 这个数据包表示服务器接受了客户端的请求,并且服务器也准备好了建立连接。服务器也会为自己分配一个初始序列号。
第三次:
- 客户端接收到服务器的 SYN+ACK 数据包后,会发送一个带有 ACK 标志的数据包给服务器。
- 这个数据包告诉服务器,客户端接受了服务器的确认,并确认连接建立成功。
为什么会采用三次握手,而不是两次或者四次呢?
- 两次握手的情况下:服务端并不知道客户端是否收到了同步序号和确认序号
- 四次握手不会更安全,还会浪费资源
TCP 四次挥手
- 客户端发送fin给服务端,表示要断开通信,自身进入等待结束链接状态。
- 服务端收到fin后发送一个ack应答报文给客户端,服务端进入等待结束状态
- 服务端发送fin给客户端,进入最后确认状态
- 客户端收到fin后,发送ack,然后经过一段时间自动关闭。服务端收到ack之后关闭。
TCP 是如何判断丢包的?
TCP 使用确认机制来判断数据包是否丢失。当发送方发送数据包时,接收方会发送一个确认(ACK)回来,表明已经成功接收到这个数据包。如果发送方在一定时间内没有收到确认,就会认为数据包丢失,然后触发重传机制。
TCP UDP的区别?
- TCP 面向连接,UDP 无连接
- TCP 有确认机制,提供可靠的数据传输
- TCP 流式传输,UDP 数据包传输
- TCP 有拥塞控制
DNS
运行在UDP之上,端口53
域名解析系统(Domain Name System)-分布式数据库- 根域名服务器-顶级域名(TLD)服务器-权限域名服务器 - 主机进行DNS查询时,查询被发送到本地域名服务器 分为迭代查询和递归查询 递归查询:本地域名服务器依次访问根/顶级/权限域名服务器 - 缓存在一段时间(TTL)后消失
浏览器存储
浏览器存储方式
- cookie
- sessionStorage
- localStorage
- indexDB
cookie 是什么?有什么属性?
Cookie 是一种在客户端(浏览器)和服务器之间传递数据的机制,用于在不同的 HTTP 请求之间保持状态信息。它是通过在客户端的浏览器中存储一小段数据来实现的。
- 名称(Name): 每个 Cookie 都有一个唯一的名称,用于标识这个 Cookie。
- 值(Value): 每个 Cookie 都有一个与之关联的值,存储在客户端的浏览器中。
- 域名(Domain): 指定了哪些域名可以访问这个 Cookie。默认情况下,Cookie 是绑定在创建它的域名下的。
- 路径(Path): 指定了哪些路径下的页面可以访问这个 Cookie。默认情况下,Cookie 是绑定在创建它的页面路径下的。
- 过期时间(Expires 或 Max-Age): 指定了这个 Cookie 的过期时间。如果没有设置过期时间,Cookie 会在浏览器会话结束时失效。
- 安全性标志(Secure): 如果设置了安全性标志,那么只有在使用 HTTPS 连接的情况下,浏览器才会发送这个 Cookie。
- HttpOnly 标志: 如果设置了 HttpOnly 标志,那么 JavaScript 将无法访问这个 Cookie,从而增加了安全性,防止 XSS 攻击。
- SameSite 属性: 用于控制 Cookie 是否可以在跨站点请求中发送,可以设置为 Strict、Lax 或 None。
四层和七层网络模型
四层
- 物理层:物理层负责传输数据的物理媒体和信号传输。
- 数据链路层:数据链路层负责将数据帧从一个物理节点传输到下一个物理节点。
- 网络层:网络层负责数据包的路由和转发。它决定了数据包从源节点到目标节点的路径,并提供了寻址和路由的功能。
- 传输层:传输层负责端到端的通信。
七层
- 物理层:与四层模型的物理层类似,负责传输数据的物理特性。
- 数据链路层:与四层模型的数据链路层类似,提供了数据帧的处理和传输。
- 网络层:与四层模型的网络层类似,负责数据包的路由和寻址。
- 传输层:与四层模型的传输层类似,提供端到端的通信和流控制。
- 会话层(第五层):会话层负责建立、维护和终止会话,以确保数据的可靠传输。
- 表示层(第六层):表示层负责数据的编码、加密和解密,以确保数据的格式和安全性。
- 应用层( 第七层):应用层包括了各种应用协议,如HTTP、FTP、SMTP等,它们负责提供具体的应用服务。
网络安全
XSS 攻击及如何防范
XSS 跨站脚本攻击,在浏览器中恶意执行脚本,从而拿到用户信息并进行操作(窃取 cookie 监听行为 伪造登录表单)
防范:
- 不信任用户输入
- CSP
- httpOnly:只能服务器操作 cookie
CSRF
CSRF攻击(Cross-site request forgery)跨站请求伪造--诱导用户点击黑站,利用用户目前登陆状态发起跨站请求
防范措施--cookie
- 利用cookie的samesite属性对cookie携带进行限制,禁止第三方携带
- 验证站点来源--请求头中对Origin(域名)和Referer(url)
- CSRF Token
DDos 攻击
DDos攻击(Distributed Denial of Service)分布式拒绝服务--在较短时间发布大量请求,大规模消耗目标网站的主机资源,让它无法正常服务。
- 定期扫描可能存在的安全漏洞
- 骨干节点配置防火墙
- 设置黑名单
- CDN加速--将网站访问流量分配到了各个节点中,这样一方面隐藏网站的真实 IP,另一方面即使遭遇 DDoS 攻击,也可以将流量分散到各个节点中,防止源站崩溃
数据结构
数组
数组是可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问,数组下标从0开始。
栈
栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。
队列
队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出。
链表
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。
树
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做 “树” 是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
散列表
散列表,也叫哈希表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。
堆
堆是一种比较特殊的数据结构,可以被看做一棵树的数组对象,具有以下的性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
图
图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系。
排序算法
冒泡排序
把相邻的元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置;当一个元素小于或者等于右侧相邻元素时,位置不变。
function bubbleSort(arr){
const len = arr.length;
for(let i = 0; i < len - 1; i++){
for(let j = 0; j < len - i - 1; j++){
if(arr[j] > arr[j+1]){
const tmp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = tmp;
}
}
}
return arr;
}
选择排序
每一轮选择最小者,直接交换到数组最左边。
function selectionSort(arr){
const len = arr.length;
for(let i = 0; i < len-1; i++){
let minIndex = i;
for(let j = i+1; j < len; j++){
if(arr[j] < arr[minIndex]){
minIndex = j; // 保存最小数的下标
}
}
const tmp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = tmp;
}
return arr;
}
插入排序
维护一个有序区,把元素一个一个插入到有序区的适当位置,直到所有元素有序。
快速排序
时间复杂度为O(nlogn)(平均情况),空间复杂度为O(logn)。快排是一种高效的排序算法,但在最坏情况下时间复杂度会退化为O(n^2)。
function quickSort(arr){
sort(arr, 0, arr.length - 1);
return arr;
function sort(arr, low, high){
if(low >= high){
return;
}
let i = low;
let j = high;
const x = arr[i]; // 取出比较值x,当前位置i空出,等待填入
while(i < j){
// 从数组尾部,找出比x小的数字
while(arr[j] >= x && i < j){
j--;
}
// 将空出的位置,填入当前值, 下标j位置空出
// ps:比较值已经缓存在变量x中
if(i < j){
arr[i] = arr[j]
i++;
}
// 从数组头部,找出比x大的数字
while(arr[i] <= x && i < j){
i++;
}
// 将数字填入下标j中,下标i位置突出
if(i < j){
arr[j] = arr[i]
j--;
}
// 一直循环到左右指针i、j相遇,
// 相遇时,i==j, 所以下标i位置是空出的
}
arr[i] = x; // 将空出的位置,填入缓存的数字x,一轮排序完成
// 分别对剩下的两个区间进行递归排序
sort(arr, low, i - 1);
sort(arr, i+1, high);
}
}
堆排序
堆排序是基于二叉堆这种具有自我调整能力的排序算法,总结为:把无序数组构建成为二叉堆,需要升序,则构建最大二叉堆,需要降序则构建最小二叉堆。
归并排序
把数组拆成两两一组,有序合并,并递归,经典的分治思想。
希尔排序
希尔排序是插入排序的升级版,对原数组进行一些"预处理",使原数组大部分元素变得有序。
计数排序
基于元素下标来确定元素位置,不依靠元素的比较和交换
O(n+m)
桶排序
桶排序是计数排序的升级版,弥补了计数排序的局限性(计数排序不适用于非整数排序)
O(n)
基数排序
基数排序也是解决计数排序的局限性问题,例如给英文单词排序,思想为:把工作拆分成为多轮进行,每一轮对单个字符使用计数排序
O(k(n+m))
常见算法
二分查找
二分查找是一种查找算法,用于在有序数组或列表中查找特定元素的位置。
它的工作原理很简单:首先,将目标元素与数组中间的元素进行比较。如果目标元素小于中间元素,就在数组的左半部分继续查找;如果目标元素大于中间元素,就在数组的右半部分继续查找。重复这个过程,每次都将查找范围缩小一半,直到找到目标元素或确定目标元素不在数组中。
分治算法
分治算法是一种问题解决方法,它将一个大问题分解成许多小问题,解决小问题后再将它们的结果合并以得到最终答案。分治算法通常包含以下三个步骤:
- 分解(Divide):将原问题划分为若干个相同或相似的子问题。
- 解决(Conquer):递归地解决每个子问题。如果子问题足够小,就可以直接求解。
- 合并(Combine):将每个子问题的解合并成原问题的解。
动态规划
动态规划算法(Dynamic Programming)是一种通过将复杂问题分解成更小的子问题,并存储子问题的解以避免重复计算的优化算法。动态规划算法通常包括以下步骤:
- 定义子问题:将原问题划分成若干个子问题,确定子问题之间的关系。
- 寻找状态转移方程:定义如何从子问题的解推导出原问题的解,通常以递推方式表达。
- 初始化:确定初始条件,通常是问题规模最小的情况。
- 自底向上求解或使用递归:可以采用迭代的方式自底向上求解子问题,或者使用递归方式自顶向下求解,但需要记忆化以避免重复计算。
- 返回最终解:得到原问题的解。
贪心算法
贪心算法(Greedy Algorithm)是一种解决问题的算法策略,它通过每一步选择当前状态下的最优解来逐步构建问题的解决方案。
贪心算法的特点包括:
- 每一步都采取最优的局部选择。
- 不进行回溯,不考虑之前的选择对当前选择的影响。
- 通过一系列局部最优选择,逐步构建全局最优解。
Vue
什么是 Vue,优缺点?
Vue 是基于 js 的渐进式框架。
优点:
- 组件化开发
- 数据驱动视图
- 双向数据流
- 虚拟 DOM
- 良好的生命周期钩子
缺点:
- 社区相对于 React 没有那么丰富
Vue 生命周期
Vue 的生命周期是指在一个 Vue 实例从创建到销毁的整个过程中,系统自动调用的一系列钩子函数。这些钩子函数可以让你在不同阶段执行特定的操作,例如初始化数据、挂载视图、更新数据、销毁实例等。
创建阶段(Creation):
beforeCreate:在实例初始化之后,数据观测和事件配置之前调用。
created:在实例创建完成后被立即调用。可以访问数据,但无法进行 DOM 操作。
挂载阶段(Mounting):
beforeMount:在挂载开始之前被调用,此时模板编译已完成,但尚未将组件挂载到页面上。
mounted:在挂载完成后被调用,此时组件已经被挂载到页面上,可以进行 DOM 操作。
更新阶段(Updating):
beforeUpdate:在数据更新之前被调用,发生在虚拟 DOM 重新渲染和打补丁之前。
updated:在数据更新之后被调用,发生在虚拟 DOM 重新渲染和打补丁之后。
销毁阶段(Destruction) :
beforeDestroy:在实例销毁之前被调用。可以在这里进行一些清理工作。
destroyed:在实例销毁之后被调用。此时实例中的所有东西都已解绑,监听器也已被移除。
watch 和 computed 的区别
computed:从组件状态衍生出新的状态,具备缓存性,依赖没改变就不会重新计算。
watch:可以检测某个响应式数据的变化并执行副作用。
v-for 没有 key 会发生什么问题?
- diff 算法更加耗时
- 不复用组件的话会导致组件状态失效。
Vue 双向绑定
采用数据劫持结合发布者-订阅者模式的方式,data数据在初始化的时候,会实例化一个Observe类,在它会将data数据进行递归遍历,并通过Object.defineProperty方法,给每个值添加上一个getter和一个setter。在数据读取的时候会触发getter进行依赖(Watcher)收集,当数据改变时,会触发setter,对刚刚收集的依赖进行触发,并且更新watcher通知视图进行渲染。
Vue 的 v-model 作用?
v-model 是 Vue.js 框架中的一个指令,它用于在表单输入元素和 Vue 实例的数据之间建立双向绑定关系。
Vue 通信方式?
- 父子:props
- 子父:$emit
- 复杂关系:vuex
Vue 修饰符
- .trim
- .lazy
- .stop
- .prevent
- .once
- .keyCode
Vue $nextTick 作用,实现?
nextTick是将回调函数放到一个异步队列(微队列)中,保证在下一次 DOM 更新后自动调用。
Vue3 中nextTick则是利用promise的链式调用,将用户放入的回调放在更新视图之后的then里面调用,用户调用多少次nextTick,就接着多少个then。
Vue keep-alive 作用
keep-alive 是一个抽象组件能把不活动的组件实例保存在内存中,而不是销毁。能缓存的主要原因是在于 actived 和 deactived 两个钩子函数,组件激活是 actived,失活就是 deactived,避免了组件重新渲染带来的性能损失。
Vue data 为什么是函数不是对象?
js 中的对象是引用类型,多个 Vue 实例会相互造成数据污染。函数 data 会以返回值的形式返回,每个实例都有自己的私有数据,不会造成数据污染。
Vue3.0 里为什么要用 Proxy API 替代 defineProperty API ?
defineProperty 缺点:
● 无法监听属性的新增和删除: 使用 defineProperty 无法监听对象属性的新增和删除操作。只有已经存在的属性才能被劫持成响应式属性,新增的属性无法被监听。
● 需要初始化时递归设置: 使用 defineProperty 对象嵌套属性进行劫持时,需要在初始化时递归设置每一层的属性,较为繁琐。
● 数组的索引和长度变化无法监听: 在 Vue 2 中,数组的索引和长度的变化无法被劫持成响应式,需要使用特殊方法来处理数组操作。
● 性能较低: 使用 defineProperty 时,需要逐一遍历对象的每个属性,并且递归设置嵌套属性,导致性能较低。
Proxy 优点:
● 更好的性能: Proxy API 提供了更直接的拦截器,可以捕获更多类型的操作,比 defineProperty 更高效。Vue 2 中使用 defineProperty 进行数据劫持时,需要逐一遍历对象的每个属性,而 Proxy 可以一次性地拦截整个对象。
● 更多的拦截操作: Proxy API 支持更多类型的操作,如新增属性、删除属性、迭代属性等,而 defineProperty 只能劫持属性的读取和修改。
● 嵌套属性的响应: 使用 Proxy 可以实现深层嵌套属性的响应式,而在 Vue 2 中,深层嵌套属性的响应式需要递归地设置 defineProperty,较为复杂。
Vue 3.0 Composition API 和 Options API 区别?
● 逻辑组合方式: Options API 是将逻辑按选项划分,而 Composition API 是基于功能划分,将相关逻辑组合在一起。
● 逻辑复用: Composition API 更容易实现逻辑的复用,可以将相关功能封装成自定义函数。
● 可读性和可维护性: Composition API 使得组件逻辑更加集中和模块化,提高了可读性和可维护性。
● TypeScript 支持: Composition API 提供了更好的 TypeScript 支持,可以实现更准确的类型推断和代码提示。
Vue3.0 Compositon API 和 HOOKS 异同点?
相同点:
- 函数式组件:Composition API 和 React Hooks 都是为了函数式组件而设计的。它们允许你在函数式组件中组织和复用状态逻辑、副作用等功能。
- 逻辑复用:两者都鼓励将组件逻辑分割成可复用的部分,以便更好地维护和测试组件。
- 更清晰的组件结构:使用 Composition API 或 React Hooks 可以使组件的结构更加清晰,将相关的逻辑放在一起,降低了组件的复杂度。
差异点:
- 语法和API:Composition API 引入了setup() 函数、ref()、reactive() 等,用于管理组件的状态和副作用。而 React Hooks 提供了一系列以 use 开头的钩子函数,如 useState、useEffect、useContext 等。
- 响应式系统:Vue 使用了 Proxy 对象来实现响应式,而 React 使用了状态和 setState 函数来管理状态。这意味着在 Vue 中,你不需要显式调用一个函数来更新状态,而 React 中需要。
- 组件生命周期:Vue 依然保留了传统的生命周期钩子函数 onMounted、onUpdated、onUnmounted 。在 React 中,生命周期函数被合并为 useEffect。
Vue 3.0 响应式 API 如何实现?
relative 和 ref
Vue 3.0 和 Vue 2.0 的区别?
- vue3 的生命周期少了 create 前后
- 可以存在多根节点
- composition API 代替 options API
- Suspense 异步组件,比如增加组件的 loading 状态 #fallback
- Teleport 可以将组件移动到其他位置
- 响应式原理的 difineProperty 被 Proxy 代替
- 更好的 TS 支持
- 打包优化
Vue 项目性能优化?
代码层面上的:
- v-if v-show 的正确使用,v-for 和 v-if 不要用在同一个元素上。
- 使用唯一值 key 标记元素
- 使用 computed 和 watch
- 有些作为数据展示的页面,且数据不会有变化,可以对数据进行 Object.freeze。
- 如果添加了原生事件一定要记得销毁
- 图片压缩,图片懒加载
- 路由懒加载
- 插件按需引入
Vue 初始化都做了哪些工作?
2.0 初始化
3.0 初始化
SPA 及优化
SPA(Single Page Application)是一种Web应用程序架构,它在加载初始页面后,通过Ajax、WebSocket等技术动态地加载并更新页面的内容,而不是通过传统的多个页面切换。
(单个页面,路由控制,无刷新动态内容)
优化:
- 代码拆分,按需加载
- 路由懒加载
- 相关资源压缩
- 合理缓存
React
React 和 Vue 的区别
- 一个说自己是库一个说自己是框架,React 提供核心的 React.js 其他都是社区提供,Vue 则提供一整套前端开发方案。
- React 是 JSX,Vue 是 HTML 模板语法
- DIFF 算法不同
- 响应式原理不同
选择 Vue 还是 React 作为项目开发的技术栈?
- 第一点肯定是团队的适用性,当然是要选择团队更加熟悉的技术栈来编写。
- 兼容性,特别注意如果业务对移动端兼容性有要求最好不要使用 Vue3 因为它的 Proxy 在 ios9 不兼容。
- 老生常谈讨论 React 适合大项目,Vue 适合小项目。因为我个人项目经验不是超级丰富,只能借用之前一位前辈说过的话:React 想写好比 Vue 复杂一些,写坏很容易。
什么是 React?
React 是一个高效的 JavaScript UI库,拥有着组件化开发、虚拟DOM、数据驱动视图等特点。
什么是 JSX?
JSX 既 JavaScript XML,是JavaScript的一种拓展。
用于在react组建内部构建标签,是React.createElement的语法糖。
优点在于有更高的可读性。
什么是虚拟 DOM?
是指用 js 对象实现对真是 DOM 的抽象,在 React 中 jsx 会被 babel 编译成 react.createElement ,执行后返回一个 object,这个 object 有着自己的 tag type props 以及 children,这就是虚拟 DOM 树。
优点:
- 改善大规模操作 DOM 的性能
- 规避直接操作 DOM 的风险
- 实现跨平台
缺点:
- 内存占用高
- 微量更新速度慢于直接操作 DOM
React DIFF
当 React 状态变更后,会将变更前后的虚拟 DOM 树进行差异比较,这个过程就是 DIFF,计算完成后渲染对真实 DOM 的操作。
React实现DIFF算法,主要依赖于DFS和分治策略:
React将虚拟DOM节点分为三个类型,树节点 组件节点 元素节点。
- 树节点如果不同的话直接删除并更新
- 组件节点如果类型相同比较元素节点,类型不同直接替换
- 元素节点会进行同级比较,根据KEY值进行增 删 更新等操作
React 生命周期React 生命周期主要有三个阶段:挂载,更新,卸载。
- 挂载
a. constructor():在组件创建时调用,用于初始化组件的状态和绑定事件。
b. static getDerivedStateFromProps():在新的props到达时调用,用于基于传入的新的props来更新组件状态。
c. render():用于生成组件的初始虚拟DOM。
d. componentDidMount():在render方法之后、组件被插入到DOM中时调用,用于执行一些只需要在组件首次渲染后执行的操作,如网络请求、订阅事件等。 - 更新
a. static getDerivedStateFromProps():在新的props到达时调用,用于基于传入的新的props来更新组件状态。
b. shouldComponentUpdate()
c. render():根据新的状态和props重新生成虚拟DOM。
d. getSnapshotBeforeUpdate()
e. componentDidUpdate():在渲染完成后、组件状态或props发生变化时调用,用于执行一些需要在组件更新后执行的操作,如界面更新、DOM操作等。 - 卸载
a. componentWillUnmount():在组件从DOM中移除之前调用,用于执行一些需要在组件卸载前执行的操作,如清理定时器、取消网络请求等。
React 组件通信
- 父传子:传递 state 给子组件就可以
- 子传父:一般在父组件使用useState hook 定义状态,再将setState函数传递给子组件,子组件通过修改这个函数来通信。
- 其他:Redux, useContext, pubsub
React 性能优化
- 使用React.Memo来缓存组件。
- 使用useMemo缓存大量的计算。
- 避免使用匿名函数。
- 延迟加载不是立即需要的组件。
- props 进了保持单一职责,精细化渲染。
受控组件和非受控组件
受控组件:组件中的状态受到react控制的组件。比如说input中的value需要通过onChange函数来修改状态来控制。
非受控组件:组件中的状态不受到react的控制。比如说input中的值只能通过ref获取真实DOM来获取。
类组件和函数组件的区别
React 组件的最终目的都是为了构建 UI 页面。
区别在于:
- 编写方法不同:函数组件在书写上更加繁杂一些。
- 生命周期不同:函数组件没有生命周期,凡事可以使用 hooks 模拟。
- 设计模式不同:函数组件在书写上更加繁杂一些。
如何设计 React 组件
无状态组件 面向 ui 比如代理组件 样式组件 布局组件
有状态组件 面向业务 比如容器组件 高阶组件
展示组件和业务组件的区别
展示组件的数据应该源自容器组件,只做数据的展示用途;
容器组件用来处理数据的逻辑并传递给展示组件。
如何区分业务组件和技术组件
业务组件:
业务组件是直接与应用程序的业务逻辑相关的组件。它们通常包含特定的功能和用户界面的业务逻辑。
- 业务组件是根据业务需求来划分的,例如登录表单、购物车、产品列表等。
- 业务组件应该是独立的、可重用的,并且可以在应用程序的不同部分使用。
技术组件:
技术组件是与底层技术或通用功能相关的组件。它们通常处理常见的任务,如表单验证、数据请求、路由等。
- 技术组件是辅助业务组件的,们提供了通用的功能和服务,以便在各个业务组件中复用。
- 技术组件应该是通用的、可扩展的,并且可以在整个应用程序中使用。
props 和 state 的区别
- props 是传递给组件的(类似于函数的形参),而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。
- props 是不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。 由于 props 是传入的,并且它们不能更改,因此我们可以将任何仅使用 props 的 React 组件视为 pureComponent(纯组件),也就是说,在相同的输入下,它将始终呈现相同的输出。
- state 是在组件中创建的,一般在 constructor 中初始化 state。
- state 是多变的、可以修改,每次 setState 都异步更新的。
React Hook 解决了什么问题?
- 没有麻烦的 this
- 副作用代码更加清晰
- 函数组件有了自己的副作用和状态
Refs 的作用?
获取页面真实 DOM 元素的引用。
Redux 是什么?简单介绍一下
Redux 是状态管理工具,遵循三大原则:
- 单一数据源
- state 只可读
- 纯函数 reducer 执行修改
具体属性和方法:
- Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store。
- State:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种时点的数据集合,就叫做State。
- Action:State的变化,会导致View的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。
- Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator。
- Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数,它接受Action和当前State作为参数,返回一个新的State。
- dispatch:是View发出Action的唯一方法。
Redux 工作流程
- 首先,用户(通过View)发出Action,发出方式就用到了dispatch方法。
- 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
- State一旦有变化,Store就会调用监听函数,来更新View。
其他
组件化、模块化区别?
- 模块化主要关注代码的组织和结构,它将代码分割成独立的模块单元,用于管理和组织代码文件,以提高代码的可维护性和可复用性。
- 组件化主要关注用户界面的构建和复用,它将用户界面拆分成独立的组件单元,用于构建可复用的UI元素,以提高用户界面的可复用性和可维护性。
如何理解前后端分离?
目前主流是前后端分离,相互之间通过 api 接口通信。
优点在于:
- 前后端都可以独立开发,增加了技术栈的多样性和代码的可维护性
- 前后端是可以独立上线,避免了复杂性
Webpack 相关
Webpack 是什么?优缺点?
Webpack的主要功能是分析项目的依赖关系,将多个模块打包成一个或多个静态资源文件(通常是JavaScript、CSS、图片等文件),以提高前端项目的性能和可维护性。
优点:
- 模块化支持: Webpack支持模块化开发,能够将项目代码分割成多个模块,使得代码组织更清晰,提高了可维护性。
- 强大的插件系统: Webpack拥有丰富的插件系统,开发者可以使用插件来扩展Webpack的功能,满足项目的需求,例如压缩代码、代码分割、热模块替换等。
- 自动化构建: Webpack可以自动化执行一系列的构建任务,例如代码压缩、图片优化、资源合并等,减少了手动操作的工作量。
- 热模块替换(HMR): Webpack支持热模块替换,允许在开发过程中无需刷新页面即可实时预览代码变更,提高开发效率。
- 生态系统: Webpack拥有广泛的生态系统和社区支持,有大量的第三方插件和加载器可供使用,可以满足不同项目的需求。
缺点:
- 配置复杂,维护起来很麻烦
- 学习成本高一些
webpack 流程
初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
编译:从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中
你在项目中如何配置 webpack?
Loader:
- image-loader:加载并且压缩图片文件
- json-loader 加载 JSON 文件(默认包含)
- babel-loader:把 ES6 转换成 ES5
- ts-loader: 将 TypeScript 转换成 JavaScript
- awesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loader
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
- style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
- eslint-loader:通过 ESLint 检查 JavaScript 代码
- vue-loader:加载 Vue.js 单文件组件
列表无限滚动 数据越来越多 页面卡顿 如何优化?
- 分页
- 虚拟列表,只渲染可视区域的 DOM 元素
for 循环的次数很多 阻塞页面渲染了 如何解决?
把这个任务给 web woker 解决,释放浏览器主线程
性能指标 FP FCP FMP
FP(First Paint): FP是指浏览器首次开始绘制页面的时间点。在这一刻,用户可能看到了页面的背景色或其他元素,但页面的大部分内容可能仍在加载中。FP是页面性能的一个早期指标,通常与用户感知的页面加载速度相关。更早的FP通常被认为是更好的用户体验。
FCP(First Contentful Paint): FCP是指浏览器首次绘制页面上第一个有意义的内容的时间点。这个内容可以是文本、图像、SVG等,反映了用户能够看到有用信息的时间。FCP通常比FP更具指导意义,因为它表示页面开始呈现实际内容,而不仅仅是背景或结构。
FMP(First Meaningful Paint): FMP是指页面首次呈现用户可感知为有意义的内容的时间点。这个内容通常是与页面主题或功能相关的内容,例如新闻网站的新闻文章或电子商务网站的产品列表。FMP反映了用户认为页面已经变得有用的时间点,对于用户体验非常重要。
UNIAPP
uniapp 是什么?
UniApp是一个多端应用开发框架,适用于希望在不同移动平台上构建应用程序的开发者。通过UniApp,开发者可以节省开发成本,提高开发效率,同时在不同平台上提供一致的用户体验。
除了 uniapp 还了解其他的跨平台框架吗?
- React Native: 由Facebook开发的框架,使用JavaScript和React构建原生移动应用。它允许开发者使用React的组件模型来创建iOS和Android应用。
- Flutter: 由Google开发的UI工具包,用于构建高性能、美观且跨平台的移动、Web和桌面应用。Flutter使用Dart编程语言。
uniapp 如何实现一套代码 多端应用的呢?
当应用开发完成后,你可以使用 UniApp 的打包工具来生成不同平台的发布包。每个平台都有独立的打包配置,以确保应用在该平台上运行良好。
微信小程序登录
1.先获取用户信息——用户授权允许后,通过调用uni.login 可以获取到code。
2.拿着获取到的code去调用——登录接口,可以获取到token。
3.把token存入缓存。就可以在页面判断是否登录了。
移动端开发相关
响应式开发
通过使用CSS媒体查询(media queries)来根据设备屏幕的宽度和高度应用不同的根元素 fontsize,其他关于像素的开发用 rem 开发。
缺点在于:兼容性问题
前端开发文档如何编写
- 需求文档
-
- 页面的路由及相应的 query
- 页面的整体功能与结构设计
-
-
- state
- UI
- logic
-
- 设计视觉稿
- 服务端 API 接口
- 测试 Case
- 走查及验收文档
ESLINT
用户规范代码风格,检查语法错误等功能。当然还有一定的自动修复功能支持。
GIT
说说你知道的 git 的命令
- git init:初始化
- git add: 添加变动到暂存区
- git commit: 提交到本地仓库
- git push: 推送到远程仓库
- git clone: 克隆远程仓库
- git status:查看工作 暂存 本地仓库状态
- git log:打印日志
- git branch:列出本地分支
-
- -d 删除分支
- git checkout:切换分支
-
-
- -b 创建并切换分支
-
需要合并别人代码到本地来怎么做
拉去最新代码,git merge 解决冲突
需要取消上次的合并请求
git reset --hard 分支名
git fetch和git pull
- git fetch是将远程最新代码拉到本地,但是没有合并到本地分支上,需要手动执行合并
- git pull是将远程代码拉到本地,同时执行了merge操作,如果有冲突需要处理冲突。
git merge和git rebase有什么区别
- git merge 会创建一个新的合并提交,将两个分支的更改合并在一起,保留了分支历史,形成一个合并节点。
- git rebase 会将当前分支的提交一个个应用在目标分支的最新提交之上,创建一条线性的提交历史,看起来像是在目标分支上进行的提交
git reset和git revert
git reset 是用于移动分支引用并可能删除提交历史的命令,而 git revert 是用于创建撤销更改的新提交,保持提交历史的完整性。
设计模式
常见的设计模式?
- 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。
- 工厂模式(Factory Pattern):使用工厂方法创建对象,而不是直接调用构造函数。
- 抽象工厂模式(Abstract Factory Pattern):提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定其具体类。
- 建造者模式(Builder Pattern):将一个复杂对象的构建与其表示分离,使同样的构建过程可以创建不同的表示。
- 原型模式(Prototype Pattern):通过克隆现有对象来创建新对象,而不是创建一个新实例。
- 适配器模式(Adapter Pattern):将一个类的接口转换为客户端所期望的另一个接口,以使不兼容的接口能够一起工作。
- 装饰器模式(Decorator Pattern):动态地将责任附加到对象上,以扩展其功能。它是继承的替代方案。
- 观察者模式(Observer Pattern):定义了对象之间的一对多依赖关系,当一个对象状态发生改变时,其所有依赖者都会受到通知并自动更新。
- 策略模式(Strategy Pattern):定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式可以使算法独立于使用它的客户端。
- 命令模式(Command Pattern):将请求或操作封装为一个对象,以支持撤销、重做、队列等功能。
- 状态模式(State Pattern):允许对象在内部状态改变时改变其行为,对象看起来好像修改了其类。
- 访问者模式(Visitor Pattern):将算法与其所操作的对象分离,使算法独立于对象。这种模式通常用于操作复杂对象结构。
- 备忘录模式(Memento Pattern):允许在不暴露对象实现细节的情况下,捕获并恢复对象的内部状态。
- 组合模式(Composite Pattern):将对象组合成树状结构以表示部分-整体层次结构,使客户端可以统一处理单个对象和组合对象。
- 迭代器模式(Iterator Pattern):提供一种方法来访问一个聚合对象中的各个元素,而不暴露其内部表示