CSS
三大特性
继承、层叠、优先级
继承属性
可继承属性(Inherited Properties):
-
字体相关属性: 包括
font-family
、font-size
、font-weight
、font-style
、font-variant
、line-height
等。 -
文本相关属性: 包括
color
、text-align
、text-transform
、text-decoration
、letter-spacing
、word-spacing
等。 -
行高(line-height): 子元素会继承父元素的行高值。
-
可见性(visibility): 子元素会继承父元素的可见性状态,但这不同于
display
属性。 -
光标(cursor): 子元素会继承父元素的光标样式。
-
表格布局的属性:border-spacing
-
列表的属性:list-style
-
页面样式属性:page
-
声音的样式属性
不可继承属性(Non-Inherited Properties):
-
盒模型属性: 包括
width
、height
、margin
、padding
、border
等。 -
定位属性: 包括
position
、top
、left
、right
、bottom
等。 -
背景属性: 包括
background-color
、background-image
、background-repeat
等。 -
定制属性: 自定义属性,例如
--custom-property
,通常不会继承。 -
文本排版属性: 包括
text-indent
、white-space
、word-break
等。 -
转换属性: 包括
transform
、transition
等。
强制继承: inherit
关键字,font-size: inherit; /* 强制继承父元素的字体大小 */
px
px是像素,显示器上给我们呈现画面的像素,每个像素的大小是一样的,绝对单位长度。
rem和em的区别
rem 和 em 都是用于网页中设置字体大小的单位
相对性:rem(root em)是相对于根元素(通常是 <html> 元素)的字体大小进行计算的,在默认情况下,浏览器的根元素的字体大小通常是 16 像素。而 em(em)则是相对于当前元素的字体大小进行计算的。
继承性:em 具有继承性,即子元素的字体大小会相对于父元素的字体大小进行计算。而 rem 不具有继承性,子元素的字体大小不会受到父元素的影响。
使用场景:rem 更适合用于全局的布局设置,特别是在响应式设计中,通过设置根元素的字体大小来调整整个页面的布局比例。em 更适合局部的字体大小调整,特别是在相对于父元素的情况下,可以方便地进行元素的嵌套和调整。
元素水平垂直居中的方式
1、flex布局
display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */
2、绝对定位
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); /* 使用 transform 属性进行居中 */
3、表格布局
display: table-cell; text-align: center; /* 水平居中 */ vertical-align: middle; /* 垂直居中 */
4、Grid布局
display: grid; place-items: center; /* 水平和垂直居中 */ height: 100vh; /* 设置容器高度为视口高度 */
预处理器
预处理器(Preprocessor)是一种用于增强和扩展 CSS 的工具,包括sass、less等。
优点:可以提高 CSS 的可维护性和可重用性、允许开发者使用变量、函数、嵌套、混入、条件语句、模块化开发等。
缺点:学习成本、需要将预处理器代码编译成纯CSS,增加了构建步骤和复杂性、性能损失、小型项目过于复杂、浏览器兼容性问题。
position定位总结
定位模式 是否脱标 移动位置 是否常用 static: 静态定位 占有位置 relative :相对定位 占有位置 相对于自身位置移动 absolute:绝对定位 不占有位置 相对于父级位置移动,要和定位父级元素搭配使用(子绝父相) fixed :固定定位 不占有位置 sticky :粘性定位 占有位置 兼容性较差,必须添加 top 、left、right、bottom 其中一个才有效
盒模型
标准盒模型:margin+border+padding+content
IE(怪异)盒模型 :margin+content(border+padding)
标准盒模型转IE盒模型:box-sizing:border-box;
行内元素、块级元素、空元素
行内元素通常不会独占一行,而块级元素通常会独占一行,而空元素则没有内容。
行内元素(Inline Elements):
-
<a>
:超链接元素。 -
<span>
:用于分组或包装文本的容器。 -
<strong>
和<em>
:分别表示强调文本和斜体文本。 -
<img>
:用于嵌入图像。 -
<br>
:换行元素。 -
<input>
:表单输入元素。 -
<button>
:按钮元素。
块级元素(Block-level Elements):
-
<div>
:用于分组或包装块级内容的容器。 -
<p>
:段落元素。 -
<h1>
,<h2>
,<h3>
,<h4>
,<h5>
,<h6>
:标题元素。 -
<ul>
和<ol>
:无序列表和有序列表元素。 -
<li>
:列表项元素。 -
<table>
:表格元素。 -
<form>
:表单元素。
空元素(Void Elements):
空元素是没有任何内容的元素,它们通常是自闭合的,不需要闭合标签。
-
<img>
:用于嵌入图像。 -
<br>
:换行元素。 -
<input>
:表单输入元素。 -
<hr>
:水平线元素。 -
<meta>
:用于提供关于文档的元信息。 -
<link>
:用于引入外部资源,如样式表。 -
<area>
:用于定义图像映射区域。 -
<base>
:指定文档中所有相对URL的基本URL。 -
<col>
和<colgroup>
:用于表格列的定义。
img 中的alt 和 title 区别
title:鼠标移入显示的值
alt:图片加载不出来显示的文字(加入alt利于SEO优化)
png jpg gif webp
png:无损压缩,体积要比jpg大,清晰度高,适合做小图标
jpg:采用压缩算法,有一点失真,比png体积要小,适合中大图片
gif:动图
webp:同时支持有损压缩和无损压缩,相同质量的图片,体积会更小,加载速度快,兼容性不好
css画三角形
1、使用边框(Border)绘制三角形
2、使用伪元素(::before 或 ::after)绘制三角形
.triangle { width: 0; height: 0; border-left: 50px solid transparent; /* 左边框透明 */ border-right: 50px solid transparent; /* 右边框透明 */ border-bottom: 100px solid red; /* 底边框颜色和高度 */ }
一个盒子不给宽高,如何居中
1、flex布局
2、绝对定位absolute
清除浮动的方式
1、触发BFC
2、overflow:hidden;
==和=== 的区别
==(相等操作符):比较时会进行类型转换,尝试将两个操作数转换为相同的类型,然后进行值的比较。
===(严格相等操作符):比较时不进行类型转换,仅当两个操作数的类型相同且值相等时,返回true。
作用域
JavaScript 的作用域(Scope)是指变量在代码中访问和可见的范围。
全局作用域(Global Scope):全局作用域是指变量在整个代码中都可访问的范围。在全局作用域中声明的变量可以被任何函数或代码块访问。
函数作用域(Function Scope):函数作用域是指变量在函数内部声明时可见的范围。在函数作用域中声明的变量只能在函数内部被访问,外部的代码无法直接访问这些变量。
块级作用域(Block Scope):块级作用域是指变量在代码块(如 if 语句、for 循环、函数内部的块等)内部声明时可见的范围。
作用域链(Scope Chain)是指查找变量时所遵循的规则。当访问一个变量时,JavaScript 引擎会按照作用域链从内到外依次查找变量,直到找到匹配的变量或达到全局作用域。
作用域是在变量被声明时确定的,而不是在变量被使用时确定的。当变量在作用域外部被访问时,JavaScript 引擎会按照作用域链去查找该变量。
自适应和响应式的区别
自适应设计和响应式设计都依靠不同的技术和方法来实现适应不同屏幕尺寸和设备类型。
自适应设计(Adaptive Design):
自适应设计通常使用以下技术和方法来实现:
-
多个预定义布局: 自适应设计通常会为每个目标设备尺寸或设备类型创建不同的预定义布局。每个布局都针对特定的屏幕宽度或设备类型进行了优化。
-
CSS媒体查询(CSS Media Queries): 自适应设计可以使用CSS媒体查询来检测用户设备的特性,如屏幕宽度、分辨率等。然后,根据媒体查询的结果应用不同的CSS样式。
-
服务器端检测: 自适应设计也可以在服务器端进行设备检测,根据用户的设备类型或特性来提供不同的内容或布局。
-
JavaScript: JavaScript可以用于检测设备属性并根据检测结果执行不同的操作。虽然不是自适应设计的核心技术,但在某些情况下可能会用到。
响应式设计(Responsive Design):
响应式设计通常使用以下技术和方法来实现:
-
流式布局(Fluid Layout): 响应式设计使用相对单位(如百分比)来定义元素的宽度,使布局能够自动适应不同的屏幕宽度。这种方式使内容能够流畅地填充可用的屏幕空间。
-
CSS媒体查询(CSS Media Queries): 响应式设计的关键部分是使用CSS媒体查询。媒体查询允许根据屏幕尺寸和其他特性应用不同的CSS样式。
-
弹性图像和媒体: 使用CSS属性如
max-width: 100%
,可以确保图像和媒体元素自动缩放以适应其容器的宽度,而不会溢出或变形。 -
断点(Breakpoints): 在响应式设计中,通常会定义一些断点,即在不同屏幕宽度范围内切换布局。这些断点通常与媒体查询结合使用。
-
适应性文本和字体: 使用相对单位(如
rem
或em
)来设置字体大小,以确保文本在不同屏幕尺寸下具有良好的可读性。
总之,自适应设计和响应式设计都使用CSS媒体查询,但它们的关注点不同。自适应设计更关注为不同的设备类型创建不同的布局,而响应式设计更关注在同一个布局中以流式方式适应各种屏幕尺寸。同时,响应式设计通常更加灵活,因为它可以适应未来可能出现的新设备和屏幕尺寸。
区别: 自适应:需要开发多套界面; 响应式:只需开发一套界面。
自适应设计通常需要前后端的协作,前端负责检测设备特性并向后端发送请求,后端负责根据请求生成或选择适当的页面内容,并进行html文档的返回。
响应式缺点
响应式布局是一种非常强大的网页设计方法,但它也有一些潜在的缺点和挑战,包括:
-
复杂性和开发成本: 响应式设计可能需要更多的开发时间和成本。创建适应各种屏幕尺寸的布局,编写多个媒体查询,以及确保在各种设备上都有一致的用户体验,都需要额外的工作。
-
性能问题: 在某些情况下,响应式设计可能导致性能问题。网页可能会加载不必要的资源,例如在大屏幕设备上加载了用于小屏幕设备的图像,这可能会导致页面加载时间延长。
-
复杂的CSS: 随着屏幕尺寸和布局的变化,响应式设计的CSS文件可能变得复杂。这使得代码维护和调试变得更加困难。
-
妥协和设计权衡: 在不同屏幕尺寸上提供一致的用户体验可能需要某种程度的妥协。在小屏幕上隐藏某些内容或功能可能会影响用户体验。
-
图片处理: 处理响应式图像可能是一项具有挑战性的任务。需要提供不同尺寸和分辨率的图像,以确保在各种屏幕上显示清晰而高效。
-
测试和维护: 响应式设计需要在多种设备和浏览器上进行广泛的测试,以确保一切正常工作。此外,随着时间的推移,需要不断维护和更新以适应新的设备和屏幕尺寸。
-
某些设计不适用: 对于某些特定设计和交互模式,响应式设计可能不太适用。在这些情况下,可能需要采用其他设计方法,如自适应设计或独立的移动应用程序。
虽然响应式设计具有挑战,但它仍然是一种非常流行和有用的设计方法,可以确保网站在各种设备上都提供良好的用户体验。开发人员和设计师需要在项目的需求和目标之间找到平衡,选择适当的设计方法。
scoped原理
作用:使得组件之间的样式不互相污染。
为组件实例生成一个唯一标识,给组件中的每个标签对应的dom元素添加一个标签属性,data-v-xxxx 给<style scoped>中的每个选择器的最后一个选择器添加一个属性选择器,原选择器[data-v-xxxx],如:原选择器为.container #id div,则更改后选择器为.container #id div[data-v-xxxx]
伪元素和伪类
伪类(:hover等):只是给子元素添加样式。
伪元素(::before等):相当于伪造了一个元素。
区别:
1、伪类本质上是为了弥补常规CSS选择器的不足,伪元素本质上是创建了一个有内容的虚拟容器。
2、CSS3中伪类和伪元素的语法不同。
3、可以同时使用多个伪类,而只能同时使用一个伪元素。
4、其中伪类和伪元素的根本区别在于:是否创造了新的元素,伪元素不存在在DOM文档中,是虚拟的元素,而伪类存在DOM文档中。
JS
组成部分
ECMAScript :JS核心内容 ,描述了语言的基础语法,比如var、for循环、数据类型(数组、字符串)
文档对象模型(DOM): 把整个HTML页面规划为元素构成文档
浏览器对象模型(BOM): 对浏览器窗口进行访问和操作
内置对象
String Boolean Number Array Object Function Math Date RegExp(正则表达式对象) Error(错误对象)
Date: new Date() getFullYear() getMount() getHours() Math: abs() sqrt() random() max() min() Array: indexOf() Array.isArray() sort() splice() includes() String: concat() length slice() split()
箭头函数和普通函数的区别
箭头函数:外形不同、都是匿名函数、不能用于构造函数,不能new、本身没有this(永远指向其上下文的 this ),也不能使用call,apply,bind等方法改变this指向、不存在函数提升、不绑定arguments、不具有prototype原型对象、不具有super、不能Generator函数,不能使用yeild关键字、不具有new.target。
闭包
内部函数访问外部函数中的变量,此时就会有闭包产生,该内部函数就称之为闭包函数。(当外部函数执行完毕后,通常情况下,其局部变量应该会被销毁。但是,如果内部函数仍然存在并且可以访问外部函数的变量,那么这些变量不会被销毁,它们会被内部函数形成的闭包所引用)
优点:延长外部函数局部变量的生命周期、可以重复使用变量,且不会造成变量污染(封装性)、减少全局变量、保护数据不被外部直接访问和修改,增强了数据的安全性(数据封装)
缺点:保留了外部作用域:内存消耗很大,会占用额外内存、可能导致内存泄露、更大的性能开销。
应用场景:函数防抖节流的时候,setTimeout函数调用外部函数、函数作为参数被传递、函数作为返回值被返回
宏任务和微任务
宏任务:
-
setTimeout
-
setInterval
-
requestAnimationFrame (浏览器独有)
-
I/O
-
UI rendering (浏览器独有)
微任务 :
-
Promise
-
Object.observe
-
MutationObserver
Event Loop 事件循环机制
1、执行所有同步任务,调用栈stack变空
2、执行所有微任务(调入stack执行)
3、执行一个宏任务
(重复2、3)
var、let、const区别
let和const具有块级作用域(由 { }包括),var为函数作用域、var可在变量声明之前使用(undefined)、let不存在变量提升(只能在声明之后使用),不能重复声明、const一定要赋初值且不能再更改(对象可以,绑定的是地址)、var声明的变量为全局变量,并且会将该变量添加为全局对象的属性、在使用let、const命令声明变量之前,该变量都是不可用的(暂时性死区)
回调地狱
把函数作为参数层层嵌套请求,代码阅读性非常差。
信任问题
回调函数不能保证什么时候去调用回调,以及使用什么方式去调用回调;而Promise一旦被确认成功或失败,就不能再被更改。Promise成功之后仅调用一次resolve(),不会产生回调多次执行的问题。除非Promise再次调用。所以Promise很好地解决了第三方工具导致的回调多次执行(控制反转)的问题,这个问题也称为信任问题。(promise可以解决信任问题,对于回调过早、回调过晚或没有调用和回调次数太少或太多,由于promise只能决议一次,决议值只能有一个,决议之后无法改变,任何then中的回调也只会被调用一次,所以这就保证了Promise可以解决信任问题)
Promise
js中处理异步操作的一种方式,代表了一个异步操作最终的结果,Promise具有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。可以通过调用Promise的then()和catch()方法来处理成功和失败的结果。
练习题:【异步系列三】10道 Promise 面试题彻底理解 Promise 异步执行顺序_promise面试题_小刘加油!的博客-CSDN博客
作用/优点:非阻塞操作(异步操作不阻塞主线程,可以同时执行多个异步任务)、代码结构更清晰,可读性更好,解决回调地狱、.then
和.all
解决串行并行问题、更好的控制(可以在需要的时候延迟或取消异步操作)、.catch
统一处理所有可能发生的异常,而不需要在每个回调函数中单独处理错误,且Promise的错误可以通过.catch
方法可以一直传递到Promise链的末端,不易丢失和混淆、现代浏览器和Node.js中广泛支持。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数、Promise对象提供统一的接口,使得控制异步操作更加容易。
缺点:
缺乏取消机制:无法中途取消Promise,一旦新建它就会立即执行。Promise 本身并没有提供用于取消异步操作的内置机制。虽然可以手动添加取消逻辑,但这需要额外的工作,并且不够直观。
如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
无法处理同步异常:Promise 的错误处理机制只能处理异步操作中的错误,不能捕获同步代码块中的异常。这意味着如果您的代码同时包含同步和异步部分,需要额外的异常处理机制。
Promise.all: 接收一个由Promise对象组成的可迭代对象(通常是数组),并返回一个新的Promise对象。
-
返回的Promise对象将在所有输入的Promise都成功解决(即状态变为
fulfilled
)时才被解决,它的结果是一个包含所有Promise解决值的数组。 -
如果任何一个输入的Promise被拒绝(即状态变为
rejected
),则返回的Promise会立即被拒绝,并将第一个被拒绝的Promise的拒绝原因作为其原因。 -
Promise.all
适用于需要等待多个异步操作全部完成后才继续的情况。
Promise.race:也接收一个由Promise对象组成的可迭代对象,并返回一个新的Promise对象。
-
返回的Promise对象将在第一个输入的Promise解决或拒绝时立即解决或拒绝,不会等待其他Promise的状态变化。
-
如果第一个Promise解决,结果将是该Promise的解决值。如果第一个Promise拒绝,结果将是第一个拒绝的Promise的拒绝原因。
Promise.finally:Promise.finally()
方法用于指定无论Promise状态如何(无论是解析还是拒绝),都需要执行的回调函数。无论Promise是成功解析还是被拒绝,.finally()方法都会在Promise链中的最后被执行。
如何中断Promise链的执行:可以抛出一个错误,或者返回一个被拒绝的Promise。
如何将回调函数转换为Promise:可以使用new Promise()
来将一个接受回调函数的异步操作转换为一个Promise。
如何处理同时需要并发执行和顺序执行的异步操作:可以使用Promise.all()
方法来处理并发执行的异步操作,使用Promise.then()
方法来依次连接多个异步操作。
其他解决异步问题的方法:
1、setTimeout:缺点不精确,只是确保在一定时间后加入到任务队列,并不保证立马执行。只有执行引擎栈中的代码执行完毕,主线程才会去读取任务队列
2、事件监听:任务的执行不取决于代码的顺序,而取决于某个事件是否发生
3、Generator函数虽然将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。即如何实现自动化的流程管理
4、async/await(解决promise的问题)
继承
1、原型链继承:原型链继承是js中最基本的继承方式。每个对象都有一个原型对象,通过原型链将属性和方法沿着对象链传递下来。在原型链继承中,通过将子构造函数的原型对象指向父构造函数的实例,实现了继承。这意味着子对象可以访问父对象原型链上的属性和方法。
2、构造函数继承(经典继承):构造函数继承是通过在子构造函数中调用父构造函数来实现继承。在构造函数继承中,通过在子构造函数中使用call()或apply()方法,将父构造函数的上下文设置为子对象的上下文,从而实现继承。注意:构造函数继承只能继承父构造函数的实例属性,无法继承父构造函数的原型对象上的方法。
3、组合继承:组合继承结合了原型链继承和构造函数继承,既继承了父构造函数的属性,又继承了父构造函数原型对象上的方法。在组合继承中,通过调用父构造函数的方式实现属性的继承,通过将子构造函数的原型对象指向父构造函数的实例实现方法的继承。组合继承的优点是既能够继承父构造函数的属性,又能够继承父构造函数原型对象上的方法。然而,缺点是在创建子对象时会调用两次父构造函数,一次是在设置原型时,一次是在创建子对象时。这样会产生一些不必要的开销。
4、原型式继承:原型式继承是通过使用一个临时构造函数和Object.create()方法来实现继承。原型式继承的本质是创建一个新对象,将其原型对象指向另一个已有的对象。这种方式可以实现属性和方法的继承,但是不能传递构造函数的参数。
5、寄生式继承:寄生式继承的思路与(寄生) 原型式继承
和 工厂模式
似, 即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
优点: 写法简单,不需要单独创建构造函数。
缺点: 通过寄生式继承给对象添加函数会导致函数难以重用。使用寄生式继承来为对象添加函数, 会由于不能做到函数复用而降低效率;这一点与构造函数模式类似.
6、寄生组合式继承:组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数;一次是在创建子类型的时候,一次是在子类型的构造函数内部。寄生组合继承就是为了降低父类构造函数的开销而实现的。通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
优点: 高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变;
缺点: 代码复杂
7、类继承(ES6 ):在ES6中引入了类的概念,通过class关键字和extends关键字可以实现类的继承。在这个例子中,我们定义了一个Parent类,通过extends关键字实现子类Child对父类Parent的继承。子类使用super关键字调用父类的构造函数,并可以访问父类的属性和方法。ES6类继承提供了更加语法简洁和面向对象的继承方式。
null和undefined的区别
null表示一个空对象引用,表示一个变量不再指向任何对象地址。
undefined 是一个没有设置值的变量,同时也是所有没有赋值变量的默认值,并且是自动赋值。(typeof 一个没有值的变量会返回 undefined)
共同点:都是原始类型,保存在栈中变量本地。null
和 undefined
都用于表示变量或属性的缺失值或未定义值。null
和 undefined
都用于表示变量或属性的缺失值或未定义值。
区别: 1、null 和 undefined 的值相等,但类型不等:
typeof undefined // undefined typeof null // object
2、在使用方式上: (1)undefined——表示变量声明过但并未赋过值。
(2)null——表示一个变量将来可能指向一个对象。null值则是表示空对象指针。 一般用于主动释放指向对象的引用。 例如:var emps = [‘ss’,‘nn’]; emps = null; //释放指向数组的引用
undefined看作是空的变量,而null看作是空的对象。
内存泄露
内存泄漏:不再用到的内存,没有及时释放(无用的对象占据着内存空间,使得实际可使用内存变小),最终可能导致应用程序性能下降或崩溃。
内存溢出:内存一共就剩1MB,要存个1GB的数据
发生内存泄漏的场景:
1、全局变量在页面关闭之前是不会被浏览器所回收的。它们就成了占用内存的冗余代码。
2、未释放对象引用:当你不再需要一个对象时,如果没有将其引用置为 null
或销毁它,它仍然会占用内存。这通常发生在长时间运行的应用程序中,如后端服务器。
3、循环引用:如果对象之间形成循环引用,即相互引用而没有被释放,垃圾回收器可能无法识别这些对象不再被引用,从而导致内存泄漏。
4、未关闭资源:未关闭的文件、数据库连接、网络连接或其他系统资源会占用内存。确保在使用完这些资源后正确关闭它们,以释放相关的内存。
5、定时器和事件监听器:忘记清除不再需要的定时器或事件监听器会导致内存泄漏。
6、缓存:过度使用缓存可以导致内存泄漏。定期清理或过期缓存可以缓解这个问题。
7、非托管资源:在使用非托管资源(如 COM 对象、外部库或操作系统资源)时,必须确保及时释放这些资源,否则它们可能会导致内存泄漏。
8、大对象:如果你创建了大型对象,但在使用后未及时释放它们,会导致内存泄漏。尤其是在循环中创建大对象时要格外小心。
9、闭包。
解决方法:
1、内存泄漏检测工具:使用工具来检测内存泄漏,如浏览器的开发者工具或第三方的内存泄漏检测库。这些工具可以帮助你识别和修复潜在的内存泄漏问题。
2、减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收(即赋值为null)
3、注意程序逻辑,避免“死循环”之类的 ; 避免循环引用等发生源
4、避免创建过多的对象
5、减少层级过多的引用
6、事件监听导致的内存泄露,监听后移除
v-if指令产生的内存泄露:(v-if
指令本身并不会直接导致内存泄漏)
-
条件不稳定或未清理的 DOM 片段: 当你使用
v-if
来根据某个条件动态渲染或移除元素时,如果条件频繁变化或不稳定,就可能导致 DOM 片段的频繁创建和移除,从而增加内存使用。例如,在一个循环中使用v-if
渲染列表项,如果列表项的条件经常变化,可能会导致内存泄漏。 -
事件监听器未清理: 如果在使用
v-if
时向元素添加了事件监听器,但在条件为假时没有清理这些监听器,就会导致事件监听器引用了不再需要的元素,从而阻止了垃圾回收。 -
第三方库未正确处理: 如果你在使用
v-if
时将元素从 DOM 中移除,但没有正确处理由第三方库创建的 DOM 片段,这可能导致内存泄漏。第三方库可能会继续引用这些未移除的 DOM 片段,而不会释放它们。
解决方法:
-
稳定的条件: 确保
v-if
的条件是相对稳定的,不会频繁变化,以减少 DOM 片段的创建和移除次数。 -
手动销毁: 如果在使用
v-if
时创建了资源或添加了事件监听器,确保在条件为假时手动销毁或清理这些资源。 -
使用 Vue 生命周期: 在 Vue 组件中,你可以使用生命周期钩子函数(如
beforeDestroy
)来清理资源或解除事件监听器。 -
第三方库文档: 如果在与第三方库一起使用
v-if
时遇到内存泄漏问题,查看该库的文档以了解正确的清理方式。
重绘和回流
应用于浏览器渲染页面时,与性能优化相关。
-
重绘(Repaint):
-
定义: 重绘是指当元素样式发生改变,但不影响其布局(即不改变元素的位置和尺寸)时,浏览器会重新绘制元素的可见部分,以反映新的样式。
-
触发条件: 重绘通常发生在改变元素的颜色、背景、字体等样式属性时,但不影响布局的情况下。
-
性能影响: 重绘的性能开销相对较小,因为它只涉及重新绘制元素的可见部分,而不需要重新计算布局。
-
回流(Reflow):
-
定义: 回流是指当页面的布局发生改变,浏览器需要重新计算元素的位置和尺寸时,会触发回流操作。回流不仅会影响单个元素,还会影响其父元素、子元素以及整个文档的布局。
-
触发条件: 回流通常发生在以下情况下:改变窗口大小、改变字体大小、添加或删除元素、修改元素的尺寸、修改元素的内容等可能影响布局的操作。
-
性能影响: 回流的性能开销较大,因为它涉及重新计算和重新布局整个页面或部分页面。因此,频繁的回流操作会导致页面性能下降。
-
区别:
1、触发条件和性能开销:
-
重绘发生在样式改变但不影响布局的情况下,性能开销较小。
-
回流发生在布局改变的情况下,需要重新计算元素位置和尺寸,性能开销较大。
2、影响范围:
-
回流的影响范围更广泛,可能会影响多个元素和整个页面的布局,而重绘通常只影响单个元素。
优化方法:
-
避免频繁读取布局信息(如使用
offsetTop
、offsetLeft
、clientWidth
、clientHeight
等属性),因为这会触发回流。 -
使用 CSS 动画代替 JavaScript 动画,因为 CSS 动画通常只触发重绘,而不触发回流。 JavaScript 动画会导致频繁的重绘和回流,而 CSS3 动画可以利用硬件加速,减少重绘和回流的次数。因此,尽量使用 CSS3 动画来实现网页动画效果。
-
使用 CSS3 的
transform
和opacity
属性来进行动画,因为它们可以在不触发回流的情况下实现平滑动画。 transform 和 opacity 属性可以利用硬件加速,减少重绘和回流的次数。例如,使用 transform 属性来实现平移、旋转、缩放等效果,使用 opacity 属性来实现透明度变化效果。 -
批量处理 DOM 操作,避免多次操作触发回流,可以先将操作保存在变量中(收集到一个操作队列中),然后一次性应用到 DOM。
-
使用虚拟 DOM 技术,如在 React、Vue 等框架中,以最小化回流和重绘的次数。
-
在 Chrome 开发者工具的 "Performance" 面板中,可以使用 "Paint" 和 "Layout" 事件来监测页面的重绘和回流,从而定位性能瓶颈。
-
避免使用 table 布局,table 布局会导致浏览器进行频繁的重绘和回流,因此尽量避免使用 table 布局。可以使用 div+css 布局来代替 table 布局。
-
使用 requestAnimationFrame 方法,requestAnimationFrame 方法可以让浏览器在下一次重绘之前执行 JavaScript 代码,从而减少重绘和回流的次数。例如,可以使用 requestAnimationFrame 方法来实现网页滚动效果。
-
虚拟滚动:当处理大量数据列表或表格时,使用虚拟滚动技术,只渲染可见区域的内容,而不是渲染整个列表。这可以显著减少 DOM 元素数量,降低回流次数。
-
使用 CSS Grid 和 Flexbox 布局:使用 CSS Grid 和 Flexbox 布局可以更轻松地实现复杂的布局,而不需要频繁地修改元素的样式属性。
-
将动画元素独立到图层中:将具有动画效果的元素放入单独的图层中,可以使用
will-change
或transform: translateZ(0)
来触发硬件加速,从而减少重绘和回流。 -
使用事件委托:对于大量的事件监听器,可以使用事件委托技术,将事件监听器绑定到父元素上,然后根据事件的目标来处理不同的事件。
事件委托
又叫事件代理,原理就是利用了事件冒泡机制来实现,也就是说把子元素的事件绑定到父元素的身上。
子元素阻止了事件冒泡,那么事件委托也就不成立
阻止事件冒泡:event.stopPropagation()
addEventListener('click',函数名,true/false) 默认是false(事件冒泡),true(事件捕获)
好处:提高性能,减少事件的绑定,也就减少了内存的占用
事件冒泡和事件捕获
事件冒泡:当一个元素上的事件被触发后,该事件会从该元素开始向上冒泡,依次触发父元素的相同事件,直到冒泡到文档根节点为止。
事件捕获:当一个元素上的事件被触发后,该事件会从文档根节点开始向下捕获,依次触发子元素的相同事件,直到捕获到该元素为止。
基本数据和引用数据类型。
基本数据类型:String Number Boolean undefined null
基本数据类型保存在栈内存当中,保存的就是一个具体的值
引用数据类型(复杂数据类型): Object Function Array
引用数据类型是保存在堆内存中,声明一个引用数据类型的变量,它保存的是引用数据类型的地址
假如声明两个引用类型同时指向了一个地址的时候,修改其中一个那么另外一个也会改变
new操作符都做了什么
1.创建一个空对象:new 操作符会创建一个空对象,这个对象会继承自构造函数的原型对象。
2.设置原型链关系:新创建的对象的 _ Prototype _ 属性会被设置为构造函数的 prototype 属性,从而建立起对象与构造函数原型之间的链接。
3.绑定 this 指向:new 操作符会将构造函数内部的 this 关键字绑定到新创建的对象上,使构造函数内部的代码可以访问和操作该对象的属性和方法。
4.执行构造函数代码:new 操作符会调用构造函数,并传入任何参数。构造函数内部的代码会被执行,可以用来初始化对象的属性和方法。
5.返回新对象:如果构造函数没有显式地返回其他对象,那么 new 操作符会隐式地返回新创建的对象实例;否则,如果构造函数返回了一个非原始值的对象,则该对象会成为 new 表达式的结果,而新创建的对象实例会被丢弃。
Js中关于this指向的问题
1.全局对象中的this:指向的是window
2.全局作用域或者普通函数中的this:指向全局window
3.this永远指向最后调用它的那个对象(不是箭头函数)
4.new关键词:指向新创建的对象
5.apply,call,bind:可以改变this指向(不是箭头函数)
6.箭头函数中的this:没有this,指向在定义的时候就已经确定,看外层是否有函数,有就是外层函数的this,没有就是window
7.匿名函数中的this:执行环境具有全局性,因此this指向window
script标签里的async和defer有什么区别
没有async和defer:浏览器会立刻加载并执行指定的脚本。
有async:加载和渲染后面元素的过程将和script的加载和执行并行进行(异步)
有defer:加载和渲染后面元素的过程将和script的加载并行进行(异步),但是它的执行事件要等 所有元素解析完成之后才会执行
定时器最小执行时间
setTimeout :4ms setInterval :10ms
ES6新增特性
1.新增声明变量关键词(let const) 2.class类 3.解构赋值 4.扩展运算符 5.箭头函数 6.数组新增API 7.Promise封装 8.新增模块化(import,export) 9.新增set和map数据结构
call apply bind
都是用于改变this指向和函数的调用
call和apply的功能类似,只是传参的方法不同(逗号分隔的参数列表vs. 数组),二者使用call比较多
bind不会调用函数,返回有指定this值和初始化参数改造的原函数拷贝(新函数)
深拷贝和浅拷贝
深拷贝:在进行复制操作时,创建一个完全独立的新对象,并递归地复制原始对象及其所有子对象。换句话说,深拷贝会复制对象的所有层级,包括对象的属性、嵌套对象、引用等。因此,原始对象和复制对象是完全独立的,修改其中一个对象不会影响另一个对象。
浅拷贝:创建一个新对象,然后将原始对象的内容逐个复制到新对象中。在浅拷贝中,只有最外层对象被复制,而内部的嵌套对象只是引用而已,没有被递归复制。这意味着原始对象和浅拷贝对象之间共享内部对象,修改其中一个对象的内部对象会影响到另一个对象。
如何实现一个深拷贝
深拷贝就是完全拷贝一份新的对象,会在堆内存中开辟新的空间,拷贝的对象被修改后,原对象不受影响 主要针对的是引用数据类型(以下就是改变深拷贝方法) 1.扩展运算符 2.JSON.parse(JSON.stringify()) 3.利用递归函数实现 4.lodash :cloneDeep 5.Object.assgin(对象只有一层) 6.structuredClone(兼容性问题)
防抖和节流
防抖:在事件被触发后,等待一段时间,如果在这段时间内没有再次触发该事件,那么才执行相应的操作。如果事件在这段时间内再次触发,那么重新等待新的一段时间。
节流:无论事件触发频率如何,每隔一段时间就执行一次事件处理函数。(通常会在间隔时间的开始被触发执行)
ajax是什么?怎么实现的
在不重新加载整个页面的前提下,与服务器交换数据并更新部分内容。 通过XmlHttpRequest对象向服务器发送异步请求,然后从服务器拿到数据,最后通过js操作DOM更新页面 1.创建XmlHttpRequest对象 xmh 2.通过xmh对象里的open()方法和服务器建立连接 3.构建请求所需的数据,并通过xmh对象的send()发送给服务器 4.通过xmh对象的onreadystate changes事件监听服务器和你的通信状态 5.接收并处理服务器响应的数据结果 6.把处理的数据更新到HTML页面上
TS
Typescript的缺点
需要很长时间来编译代码(需要一个编译步骤将TypeScript转换成JavaScript)、不支持抽象类、使用任何第三方库必须使用定义文件,不是所有第三方库都有可用的定义文件、类型定义文件的质量是一个问题(即如何确保定义是正确的)
JS和TS的区别
1、语法层面:TypeScript = JavaScript + Type(TS 是 JS 的超集),对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。而且完全兼容JS,换言之,任何的TS代码都可以直接当成JS使用。
2、执行环境层面:浏览器、Node.js 可以直接执行 JS,但不能执行 TS
3、 编译层面:TS 有编译阶段,要通过编译器编译为JS,然后再交由JS解析器执行。JS 没有编译阶段(只有转译阶段和 lint 阶段)
4、 编写层面:TS拥有了静态类型,更加严格的语法,更强大的功能,更难写一点,但是类型更安全,允许在编译时而不是运行时检测到错误,减小了运行时异常的出现的几率。js不支持强类型或静态类型,ts支持强类型或静态类型特性。 js只是一种脚本语言。 ts支持面向对象的编程概念,如类、接口、继承、泛型等,可以通过使用继承来支持可重用性。
5、TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;
6、同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。
类型
Boolean、Number、String、Boolean、Array、Tuple(元组:允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。)、enum(枚举)、Any(在编程阶段还不清楚类型的变量)、Void(没有任何类型)、Null、Undefined、Never(永不存在的值)、Object(非原始类型,也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型。)、接口
编译Typescript文件
1、例如,编译“hello .ts”
$ tsc helloworld.ts
结果是helloworld.js
2、将多个.ts文件合并成一个.js文件
$ tsc --outFile comman.js file1.ts file2.ts file3.ts
上面的命令将编译所有这三个.ts文件和结果将存储在一个comman.js文件中
3、自动编译.ts文件
tsc --watch file1.ts
类的特征
-
继承
-
封装
-
多态
-
抽象
如何从TypeScript的子类调用基类构造函数
super()函数的作用是: 从子类中调用父类或基类构造函数。
接口和类
在 TypeScript 中,它们用于定义和组织代码的结构和行为。
接口(Interfaces):用来定义对象的形状或结构的方式,它描述了对象应该具有哪些属性和方法。接口本身不包含实际的实现,它只是一种契约,用于强制对象符合指定的结构。
interface Person { firstName: string; lastName: string; sayHello(): void; }
类(Classes):用于创建对象。它包含了属性和方法的定义,以及用于创建对象的构造函数。
区别:接口用于定义对象的结构,而类用于创建具有属性和方法的对象。
继承
继承是一种从另一个类获取一个类的属性和行为的机制。它是面向对象语言的一个重要方面,并且具有从现有类创建新类的能力,继承成员的类称为基类,继承这些成员的类称为派生类。继承可以通过使用extend关键字来实现。
模块
一种用于组织和封装代码的机制,它允许你将相关的代码分组到单独的文件中,以便更好地管理和组织项目。模块还提供了封装性和可重用性,有助于减少全局命名空间的污染,并支持代码的分发和复用。
特点和用法:
-
导出(Export): 通过使用
export
关键字,你可以将变量、函数、类等从一个模块导出,使其在其他模块中可用。例如:// math.ts 模块 export function add(a: number, b: number): number { return a + b; }
-
导入(Import): 通过使用
import
关键字,你可以在一个模块中导入来自其他模块的导出成员。例如:// app.ts 模块 import { add } from "./math"; const result = add(3, 5); console.log(result); // 输出 8
-
默认导出(Default Export): 一个模块可以有一个默认导出,它可以是函数、类、对象等。默认导出不需要使用
{}
包裹,而且在导入时可以使用任意名称。例如:// utils.ts 模块 export default function sayHello(name: string): void { console.log(`Hello, ${name}!`); }
// app.ts 模块 import sayHi from "./utils"; sayHi("Alice"); // 输出 Hello, Alice!
-
模块的文件扩展名: 在 TS中,模块文件的扩展名通常可以省略,但有时需要根据模块系统的规则指定扩展名。例如,如果使用 CommonJS,则
.js
扩展名是必须的。
优点:TS的模块系统使得代码的组织和模块化变得更容易,有助于降低代码的复杂度并提高可维护性。模块还有助于构建大型应用程序,因为它们可以让你将代码拆分为多个文件,并在需要时按需加载。
内部模块和外部模块的区别
内部模块(命名空间):是一种在 TypeScript 中用来组织代码的方式。它使用 namespace
关键字来创建一个封装的命名空间,用于防止全局变量和函数之间的冲突。内部模块可以包含变量、函数、类等,并可以嵌套定义。内部模块在编译后会被转换为一个 JavaScript 对象,这个对象包含了模块中的所有成员。
特点:使用 namespace
关键字定义命名空间、命名空间中的成员可以通过命名空间名称来访问、通过 export
关键字可以将命名空间中的成员导出,使其在其他文件中可用。
// 定义内部模块(命名空间) namespace MyNamespace { export function sayHello() { console.log("Hello from MyNamespace!"); } } // 在另一个文件中使用内部模块 MyNamespace.sayHello();
外部模块(模块):是一种用于组织和管理 TypeScript 代码的方式,它遵循一种模块系统(如 CommonJS、ES6 模块系统),并且在编译后生成对应的模块导入和导出语句。
特点:
-
使用
import
和export
关键字来导入和导出模块中的成员。 -
每个文件都是一个独立的模块,文件之间通过导入和导出来连接。
-
外部模块可以被 TypeScript 编译器识别,并按照模块系统的规则进行转换和加载。
区别:
-
内部模块使用
namespace
关键字,而外部模块使用import
和export
关键字。 -
内部模块主要用于防止全局命名冲突,而外部模块用于组织、导入和导出模块中的代码。
-
外部模块的代码更符合通用的模块系统规范,可以在不同的环境中使用,例如 Node.js 或浏览器中。外部模块也更加推荐和现代。
函数重载
为同一个函数提供多个不同的函数签名,这些函数签名可以具有不同的参数类型和返回值类型。这样,你可以根据不同的参数类型来调用同一个函数,TS编译器会根据函数重载的定义来确定正确的函数调用。
function greet(name: string): string; function greet(age: number): string; function greet(value: string | number): string { if (typeof value === 'string') { return `Hello, ${value}!`; } else { return `Hello, you are ${value} years old!`; } } console.log(greet("Alice")); // 输出: Hello, Alice! console.log(greet(25)); // 输出: Hello, you are 25 years old!
在这个示例中,greet
函数有两个重载的函数签名,一个接受一个字符串参数,另一个接受一个数字参数。然后,函数实现部分根据参数类型来执行不同的逻辑。
注意:
1、函数重载的声明通常放在函数实现之前。
2、函数实现部分只出现一次,并且按照最通用的形式编写,不需要函数体的重载版本。
3、函数重载的目的是提供更多的类型信息,以便 TS编译器能够在编译时进行类型检查。
tsconfig.json
TS项目的配置文件,用于指定 TypeScript 编译器的编译选项和项目配置。主要目的是提供一种配置方式,以确保 TypeScript 编译器在项目中以一致的方式工作。放置在项目的根目录下。编译器会自动识别和使用这个配置文件。
常见的可配置选项:
-
compilerOptions: 这个对象中包含了一系列编译选项,用于控制 TypeScript 编译器的行为。例如,你可以设置目标 JavaScript 版本、是否启用严格模式、输出目录等等。
{ "compilerOptions": { "target": "ES6", "strict": true, "outDir": "./dist" } }
-
include 和 exclude: 这两个选项用于指定哪些文件应该包括在编译中,以及哪些文件应该排除在编译之外。通常,你可以使用通配符来匹配文件。
{ "include": ["src/**/*.ts"], "exclude": ["node_modules"] }
-
files: 这个选项允许你显式地列出应该被编译的文件。如果指定了这个选项,那么只有列出的文件会被编译。
{ "files": ["src/main.ts", "src/util.ts"] }
-
extends: 可以继承另一个
tsconfig.json
文件中的配置选项,以便复用通用的配置。{ "extends": "./tsconfig.base.json", "compilerOptions": { "outDir": "./dist" } }
-
references: 如果你的项目使用了 TS项目引用(Project References)特性,可以使用这个选项来定义项目之间的依赖关系。
{ "references": [ { "path": "./other-project" } ] }
JSX
JSX(JavaScript XML)是一种可嵌入的类似xml的语法扩展,用于描述用户界面的结构,它提供了一种更直观和可读性更高的方式来创建复杂的 UI 层次结构。它将被转换成有效的JavaScript。JSX随着React框架而流行起来。TypeScript支持嵌入、类型检查和直接将JSX编译成JavaScript。TS 提供了对 JSX 的原生支持,而且它可以与各种库和框架一起使用,不仅限于 React。
前提:使用.tsx扩展名命名文件、启用jsx选项。
在 JSX 中,你可以使用类似 HTML 的标签和属性来描述组件,例如:
const element = <div className="container">Hello, JSX!</div>;
然后,JSX 会被转译为普通的 JavaScript 代码,以便浏览器能够理解和渲染。
范型
在 TS中,泛型是一种用于参数化类型的机制,它允许你编写可重用的、通用的函数、类或接口,从而增加了代码的灵活性和类型安全性。通过使用泛型,你可以在编写代码时不需要事先知道具体的数据类型,而是在运行时或编译时动态地确定数据类型。(将类型参数放在尖括号<T>
内)
其他问题
1、TS类中属性/方法的默认可见性是什么:Public
2、TS是如何在函数中支持可选参数的:用?,如:res.data?.data
Vue
Vue 在更新 DOM 时是异步执行的
虚拟DOM
原生的JS对象去描述一个DOM节点,对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。它的表达方式就是把每一个标签都转为一个对象,这个对象可以有三个属性:tag
(标签、组件或函数)、props
(属性和方法)、children
(内容或者子节点)。
为什么要使用虚拟DOM:提升性能,DOM 发生变化的时候,通过 diff 算法和数据改变前的 DOM 对比,计算出需要更改的 DOM,然后只对变化的 DOM 进行操作,而不是更新整个视图。
虚拟 DOM 的数据更新机制:采用的是异步更新队列,就是把变更后的数据变装入一个数据更新的异步队列,就是 patch
,用它来做新老 vnode 对比。
步骤:
1、通过编译将模板(template)转成渲染函数(render)
2、调用 render 函数,生成一个虚拟 dom
3、数据发生改变的时候 render 函数会生成一个新的虚拟 dom, 利用diff算法
对比新旧虚拟DOM,记录差异到patch
对象, 最后创建真实的DOM找到要要修改的虚拟 dom 的部分,去修改相对应部分的真实 dom
diff 算法:diff 算法就是对虚拟 dom 进行对比,并返回一个 patch 对象,这个对象的作用是存储两个节点不同的地方,最后用 patch 里记录的信息去局部更新真实的 dom(深度优先,同层比较的策略)
pach(patching)算法:虚拟DOM最核心的部分,它可以将vNode渲染成真实的DOM,这个过程是对比新旧虚拟节点之间有哪些不同,然后根据对比结果找出需要更新的的节点进行更新
VNode虚拟节点: 它可以代表一个真实的dom节点。通过 createElement 方法能将 VNode 渲染成 dom 节点。简单地说,vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点
将虚拟dom转化为真实dom:一般Vue会自动处理这个过程,$el 属性、$refs
属性、手动操作DOM
优点:
1、具备跨平台的优势(虚拟DOM 是以 JavaScript对象
为基础而不依赖真实平台环境)
2、解决浏览器性能问题
3、在大量、频繁的数据更新下提升渲染性能,更高效更新视图
4、减少DOM操作,提供运行效率(操作 DOM 慢,js运行效率高)
缺点:首次渲染慢、无法极致优化、复杂性增加,性能开销大,学习成本高、内存占用大、不一定总是更快
虚拟dom中key的作用:key是虚拟DOM对象的标识,当数据发生变化时,vue会根据新数据生成新的虚拟DOM,随后进行新旧虚拟DOM的差异比较 对比规则:1、旧虚拟DOM中找到了与新虚拟DOM相同的key: 若虚拟DOM中内容没变,直接使用之前的真实DOM, 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
2、旧虚拟DOM中未找到与新虚拟DOM相同的key 创建新的真实DOM,随后渲染到页面。
用index作为key可能会引发的问题:
1、若对数据进行,逆序添加,逆序删除等破坏顺序的操作:会产生没有必要的真实DOM更新,页面效果没问题,但效率低。
2、如果结构中还包含输入类的DOM:会产生错误DOM更新,界面有问题。
Diff 算法的优化:
1、只比较同一层级,不跨级比较
2、如果同一层级的标签名不同,就直接移除老的虚拟 DOM 对应的节点,不继续按这个树状结构做深度比较
3、如果标签名相同,key 也相同,就会认为是相同节点,也不继续按这个树状结构做深度比较
vue2和vue3diff算法的不同:
1、Vue 2 的 diff 算法对于列表渲染(v-for)时的元素重新排序会比较低效,需要通过给每个元素设置唯一的 key 来提高性能。而 Vue 3 的 diff 算法在列表渲染时,通过跟踪元素的移动,可以更好地处理元素的重新排序,无需设置 key。
2、Vue 3 的 diff 算法对于静态节点的处理更加高效,静态节点只会在首次渲染时被处理,后续更新时会直接跳过比较和更新操作,减少了不必要的计算。
3、Vue 2 的 diff 算法会对整个组件树进行完整的遍历和比较,而 Vue 3 的 diff 算法会跳过静态子树的比较,只对动态节点进行更新。
4、Vue 2 使用的是基于递归的双指针的 diff 算法,而 Vue 3 使用的是基于数组的动态规划的 diff 算法。
vue2响应式
响应式就是数据的变化能够自动反映在ui界面上。
原理:通过数据劫持 defineProperty + 发布订阅者模式,当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染(通知 render 函数,数据发生了变化,然后就会重新运行 render 函数,重新生成虚拟 dom 树)。
缺陷:只能够监听初始化实例中的 data 数据,动态添加值不能响应,数组下标修改元素、数组修改长度等,不能很好的实现对数组下标的监控,要使用对应的 Vue.set()。
vue3响应式
Proxy 天然的能够对整个对象做监听(劫持整个对象并返回一个新的对象),而不需要对数据行遍历后做监听。
定义基本数据类型的响应式,我们使用 ref
函数。接受的数据是基本类型:响应式依然是靠 Object.defineProperty()
的 get
与 set
完成的。接受的数据是对象类型:内部【求助】了 Vue3 中的一个新函数 —— reactive
函数。
reactive
函数:定义一个【引用类型】的响应式数据。reactive 定义的响应式数据是“深层次的”,内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
reactive 响应式原理 - Proxy:通过 Proxy(代理) 拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。通过 Reflect(反射)对源对象的属性进行操作。
Proxy顾名思义是代理的意思, 其功能也名副其实, 在目标对象之前设置一层代理, 进行对象访问的拦截, 由此提供了一种机制,就是可以对外界的访问进行过滤和改写. 这个功能很强大, 等于可以改变一些对象原来底层的访问, 从而修改某些操作的默认行为。
-
Proxy(代理):
Proxy 是 ECMAScript 6 引入的特性,它允许你创建一个代理对象,用于拦截对目标对象的操作。在 Vue 3 中,Proxy 用于实现响应式数据对象的拦截和观察。
-
拦截器:你可以在代理对象上设置各种拦截器,例如
get
拦截器用于捕获属性的读取操作,set
拦截器用于捕获属性的写入操作,等等。这使得 Vue 3 能够跟踪属性的访问和修改。 -
深层代理:Proxy 支持深层代理,意味着嵌套对象的属性也可以被代理,而不仅仅是顶级对象。
-
自定义行为:你可以为代理对象的各种操作定义自定义行为,例如在属性访问时触发副作用、验证写入操作,等等。
Vue 3 中的响应式系统使用了 JavaScript 的 Proxy 对象来实现。Proxy 是一种用于创建代理对象的内置对象,它可以捕获对代理对象的操作,从而实现自定义的操作行为。下面是 Vue 3 中 Proxy 响应式系统的基本原理:
-
对象代理: 在 Vue 3 中,每个响应式的对象都会被包装成一个 Proxy 对象。这个 Proxy 对象充当了对象的代理,拦截了对对象属性的访问、修改、添加和删除等操作。
-
Getter 拦截: 当访问响应式对象的属性时,Proxy 会触发 Getter 拦截器。Getter 拦截器会将访问操作反映到底层的响应式数据,同时将访问标记为依赖,以便在属性变化时触发相应的更新。
-
Setter 拦截: 当修改响应式对象的属性时,Proxy 会触发 Setter 拦截器。Setter 拦截器会更新底层响应式数据,并通知依赖于该属性的组件进行重新渲染。
-
Track 和 Trigger: Vue 3 中还引入了 Track 和 Trigger 概念,用于跟踪属性的依赖关系和触发依赖更新。当属性被访问时,Vue 会跟踪哪些组件依赖于这个属性,然后在属性变化时触发依赖的组件更新。
-
递归代理: Vue 3 的响应式系统会递归地代理对象的属性,确保对象的嵌套属性也是响应式的。这意味着你可以在嵌套对象上使用相同的响应式语法。
-
数组操作拦截: 对于数组,Proxy 也提供了拦截器来捕获数组的变化操作,如 push、pop、shift、unshift、splice、sort 和 reverse 等。这些操作会触发数组的响应式更新。
总的来说,Vue 3 中的 Proxy 响应式系统通过代理对象、Getter 和 Setter 拦截器、Track 和 Trigger 等机制,实现了高效的响应式数据管理。当你访问或修改响应式对象的属性时,Vue 3 可以追踪依赖关系并触发相应的更新,从而实现了响应式的界面渲染。这一机制是 Vue 3 的核心,使得开发者可以方便地构建响应式的应用。
-
-
Reflect(反射):
Reflect 是一个内置对象,提供了一组用于操作对象的方法。在 Vue 3 中,Reflect 通常与 Proxy 一起使用,以执行一些默认操作或确保操作的一致性。
-
Reflect 与 Proxy 配合:当你使用 Proxy 拦截器时,通常会在拦截器中使用 Reflect 的方法来执行默认行为。例如,在设置属性值的 Proxy 拦截器中,你可以使用
Reflect.set()
来确保属性被正确设置,以保持一致性。
-
-
使用默认操作:Reflect 方法通常用于执行默认的操作,例如获取属性值、设置属性值、删除属性等。
-
提供一致性:Reflect 的方法提供了一种一致的方式来执行对象操作,这在与 Proxy 结合使用时非常有用,因为它确保了拦截器的行为与默认操作一致。
-
响应式过程:
创建响应式对象:
首先,你可以使用 Vue
的 reactive
函数或 ref
函数(如果需要包装原始值)来创建响应式对象或变量。
访问和修改数据:
当你访问或修改响应式对象的属性时,Proxy 会拦截这些操作,并通知 Vue 3。
触发更新:
当数据被修改时,Vue 3 会触发视图更新。这是通过内部的虚拟 DOM 比较机制实现的,Vue 3 会比较新旧虚拟 DOM 树的差异,并只更新需要变化的部分,以提高性能。
自动更新视图:
一旦视图需要更新,Vue 3 会自动将变更应用到视图上,确保数据的修改实时显示在页面上。
Object.defineProperty和Proxy的区别
Object.defineProperty无法监控到数组方法,导致通过数组添加元素不能实时响应。Object.defineProperty只能劫持对象的属性,从而需要对每个对象每个属性进行遍历,如果属性值是对象,还需要深度遍历。
Proxy劫持整个对象并返回一个新的对象,不仅可以代理对象,还可以代理数组,还可以代理动态增加的属性。
MVVM
-
M: model 应用的核心(数据)
-
V: view 应用的界面
-
VM: viewmodel,视图模型。他是整个mvvm的核心,他是一个桥梁,连接M与V,能够做到数据变了、页面自动更新(vue)。
MVVM 是 Model-View-ViewModel 的缩写。
Model:代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
View: 代表UI 组件,它负责将数据模型转化成UI 展现出来。
ViewModel: 监听数据模型的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。
View 和 Model 之间并没有直接的联系 而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来, 而View 和 Model 之间的同步工作完全是自动的,无需人为干涉, 因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题, 复杂的数据状态维护完全由 MVVM 来统一管理。
vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式。
通过Object.defineProperty()来劫持各个属性的setter,getter;在数据变动时发布消息给订阅者,触发相应监听回调。
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。
用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。 通过Object.defineProperty将data里的每一个属性的访问与修改都变成了一个函数,在函数get和set中我们即可监听到data的属性发生了改变。
Data为什么必须是函数
因为当 data 是函数时,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,实例化几次就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。如果data是对象时,这些实例用的是同一个构造函数,由于JavaScript的特性所导致,所有的组件实例共用了一个data,就会造成一个变了全都会变的结果。
computed与watch的区别
1、computed擅长处理的场景:一个数据受多个数据影响;watch擅长处理的场景:一个数据影响多个数据。 2、功能上:computed是计算属性,watch是监听一个值的变化,然后执行对应的回调。 3、是否调用缓存:computed支持缓存,只有依赖数据发生改变,才会重新进行计算;而watch不支持缓存,数据变,直接会触发相应的操作。 4、是否调用return:computed中的函数必须要用return返回,watch中的函数不是必须要用return。 5、computed不支持异步 ,当computed内有异步操作时无效,无法监听数据的变化;而watch支持异步。 6、computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,如果需要第一次加载做监听,添加immediate属性,设置为true(immediate:true)
computed、watch、methods的区别
作用机制上: watch 和 computed 都是以 Vue 的依赖追踪机制为基础的,当某一个数据发生变化的时候, 所有依赖这个数据的相关数据,自动发生变化,也就是自动调用相关的函数去实现数据的变动 methods 里面是用来定义函数的,它需要手动调用才能执行。而不像 watch 和 computed 那样, “自动执行”预先定义的函数,相比于 watch / compute;methods 不处理数据逻辑关系,只提供可调用的函数,类似储存函数的一个库。 从性质上 methods里面定义的是函数,你显然需要像"fuc()"这样去调用它(假设函数为fuc)。 computed是计算属性,事实上和和data对象里的数据属性是同一类的(使用上)。 watch:类似于监听机制+事件机制,回调函数自动调用。
ref的作用
1、ref是一个用于访问组件或DOM元素的属性(获取DOM),在mounted里面拿到dom,在vue2中this.$ref.元素,就可以获取dom 。
this.$refs.text.value = 'Hello Vue!'
2、在setup语法糖里面是定义一个响应式变量。
nextTick
nextTick
是 Vue.js 中一个非常重要的异步操作工具,它的主要作用是让你在 DOM 更新之后执行回调函数。Vue.js 在更新 DOM 时是异步执行的,这意味着在某些情况下,你无法立即获取到更新后的 DOM 状态,因此需要 nextTick
来确保在 DOM 更新完成后再执行某些操作。下面是 nextTick
的主要作用:
-
确保 DOM 更新完成后执行回调函数: 在 Vue 组件中,当你修改数据后,Vue 不会立即更新 DOM,而是将更新放入一个队列中,然后异步执行 DOM 更新。这意味着如果你想在数据更新后立即操作 DOM,可能会得到更新之前的状态。
nextTick
允许你等待 Vue 完成 DOM 更新后再执行回调函数,确保操作的时机是正确的。 -
避免数据变化检测的问题: 在 Vue 组件中,当你修改数据时,Vue 使用异步队列来批量处理数据变化,以提高性能。这意味着在同一事件循环中多次修改数据时,只有最后一次数据变化会触发 DOM 更新。使用
nextTick
可以确保你在同一事件循环中修改数据后立即获取到更新后的 DOM 状态。 -
在 Vue 生命周期钩子中使用: 在 Vue 生命周期钩子中,
nextTick
可以确保在特定生命周期阶段的 DOM 更新之后执行回调,以便你可以操作最新的 DOM。
nextTick
在 Vue.js 中用于处理异步 DOM 更新,确保在数据变化后或生命周期钩子中获取到正确的 DOM 状态。这对于处理 Vue 组件中的 DOM 操作非常有用。
Vue2生命周期
new Vue()实例化一个vue实例,然后init初始化event 和 lifecycle, 其实这个过程中分别调用了3个初始化函数(initLifecycle(), initEvents(), initRender()),分别初始化了生命周期,事件以及定义createElement函数,初始化生命周期时,定义了一些属性,比如表示当前状态生命周期状态得isMounted ,isDestroyed ,isBeingDestroyed,表示keep-alive中组件状态的inactive,而初始化event时,实际上就是定义了$once、$off、$emit、$on几个函数。而createElement函数是在初始化render时定义的(调用了initRender函数) 执行beforeCreate生命周期函数 beforeCreate执行完后,会开始进行数据初始化,这个过程,会定义data数据,方法以及事件,并且完成数据劫持observe以及给组件实例配置watcher观察者实例。这样,后续当数据发生变化时,才能感知到数据的变化并完成页面的渲染 执行created生命周期函数,所以,当这个函数执行的时候,我们已经可以拿到data下的数据以及methods下的方法了,所以在这里,我们可以开始调用方法进行数据请求了 created执行完后,我们可以看到,这里有个判断,判断当前是否有el参数(这里为什么需要判断,是因为我们后面的操作是会依赖这个el的,后面会详细说),如果有,我们再看是否有template参数。如果没有el,那么我们会等待调用$mount(el)方法(后面会详细说)。 确保有了el后,继续往下走,判断当有template参数时,我们会选择去将template模板转换成render函数(其实在这前面是还有一个判断的,判断当前是否有render函数,如果有的话,则会直接去渲染当前的render函数,如果没有那么我们才开始去查找是否有template模板),如果没有template,那么我们就会直接将获取到的el(也就是我们常见的#app,#app里面可能还会有其他标签)编译成templae, 然后在将这个template转换成render函数。 之后再调用beforeMount, 也就是说实际从creted到beforeMount之间,最主要的工作就是将模板或者el转换为render函数。并且我们可以看出一点,就是你不管是用el,还是用template, 或者是用我们最常用的.vue文件(如果是.vue文件,他其实是会先编译成为template),最终他都是会被转换为render函数的。 beforeMount调用后,我们是不是要开始渲染render函数了,首先我们会先生产一个虚拟dom(用于后续数据发生变化时,新老虚拟dom对比计算),进行保存,然后再开始将render渲染成为真实的dom。渲染成真实dom后,会将渲染出来的真实dom替换掉原来的vm.$el(这一步我们可能不理解,请耐心往下看,后面我会举例说明),然后再将替换后的$el append到我们的页面内。整个初步流程就算是走完了 之后再调用mounted,并将标识生命周期的一个属性isMounted 置为true。所以mounted函数内,我们是可以操作dom的,因为这个时候dom已经渲染完成了。 再之后,只有当我们状态数据发生变化时,我们在触发beforeUpdate,要开始将我们变化后的数据渲染到页面上了(实际上这里是有个判断的,判断当前的isMounted是不是为ture并且isDestroyed是不是为false,也就是说,保证dom已经被挂载的情况下,且当前组件并未被销毁,才会走update流程) beforeUpdate调用之后,我们又会重新生成一个新的虚拟dom(Vnode),然后会拿这个最新的Vnode和原来的Vnode去做一个diff算,这里就涉及到一系列的计算,算出最小的更新范围,从而更新render函数中的最新数据,再将更新后的render函数渲染成真实dom。也就完成了我们的数据更新 然后再执行updated,所以updated里面也可以操作dom,并拿到最新更新后的dom。不过这里我要插一句话了,mouted和updated的执行,并不会等待所有子组件都被挂载完成后再执行,所以如果你希望所有视图都更新完毕后再做些什么事情,那么你最好在mouted或者updated中加一个$nextTick(),然后把要做的事情放在$netTick()中去做 再之后beforeDestroy没啥说的,实例销毁前,也就是说在这个函数内,你还是可以操作实例的 之后会做一系列的销毁动作,解除各种数据引用,移除事件监听,删除组件watcher,删除子实例,删除自身self等。同时将实例属性isDestroyed置为true 销毁完成后,再执行destroyed
beforeDestroy 销毁前 改名 onBeforeUnmount,可在此进行清除定时器,进行事件监听,发布订阅。
Vue3中的生命周期
1、setup() : 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
2、onBeforeMount() : 组件挂载到节点上之前执行的函数;
3、onMounted() : 组件挂载完成后执行的函数;
4、onBeforeUpdate(): 组件更新之前执行的函数;
5、onUpdated(): 组件更新完成之后执行的函数;
6、onBeforeUnmount(): 组件卸载之前执行的函数;
7、onUnmounted(): 组件卸载完成后执行的函数;
8、onActivated(): 被包含在 <keep-alive> 中的组件,会多出两个生命周期钩子函数,被激活时执行;
9、onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;
10、onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数。 二、Vue2.X和Vue3.X对比
vue2 -------> vue3 beforeCreate --------> setup(()=>{}) created --------> setup(()=>{}) beforeMount --------> onBeforeMount(()=>{}) mounted --------> onMounted(()=>{}) beforeUpdate --------> onBeforeUpdate(()=>{}) updated --------> onUpdated(()=>{}) beforeDestroy --------> onBeforeUnmount(()=>{}) destroyed --------> onUnmounted(()=>{}) activated --------> onActivated(()=>{}) deactivated --------> onDeactivated(()=>{}) errorCaptured --------> onErrorCaptured(()=>{})
总结: Vue2和Vue3钩子变化不大,beforeCreate 、created 两个钩子被setup()钩子来替代。
vue2.0生命周期: 创建阶段 beforeCreate()创建前阶段,这个时候还不能使用data中的数据。 created()创建完成 最早可以使用data中的数据 挂载阶段 beforeMount:在挂载开始之前被调用: 相关的 render 函数首次被调用 mounted: 挂载完成,DOM节点挂载到实例上去之后调用该钩子 更新阶段 beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 updated: 数据更新完成并且DOM更新完成后调用销 销毁阶段 beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用 destroved:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定
Vue.js的生命周期钩子(Lifecycle Hooks)是一组特定的方法,允许你在Vue实例的不同阶段添加自定义代码。这些生命周期钩子允许你控制和操作Vue实例的行为,以满足你的业务逻辑需求。每个生命周期钩子都在Vue实例的不同阶段触发,以下是一些常见的Vue生命周期钩子以及它们的作用:
-
beforeCreate(创建前): 在实例初始化之后,数据观测和事件配置之前被调用。在这个阶段,你可以做一些初始化工作,但此时还无法访问到
data
和props
中的数据。 -
created(创建后): 在实例创建完成后被立即调用。在这个阶段,Vue实例已经初始化完成,可以访问
data
、props
和methods
等属性。通常在这里进行一些异步操作、数据初始化或订阅事件。 -
beforeMount(挂载前): 在挂载开始之前被调用。在这个阶段,模板已经编译完成,但尚未将虚拟DOM渲染到页面上。可以在这里进行一些DOM操作或准备数据。
-
mounted(挂载后): 在挂载完成后被调用。在这个阶段,Vue实例已经挂载到页面上,可以访问DOM元素。通常在这里进行一些与DOM相关的操作,如获取元素、设置定时器等。
-
beforeUpdate(更新前): 在数据更新之前被调用,发生在虚拟DOM重新渲染之前。在这里可以进行一些数据准备或修改。
-
updated(更新后): 在数据更新之后被调用,发生在虚拟DOM重新渲染之后。在这里可以进行DOM操作,但要注意避免无限循环更新。
-
beforeDestroy(销毁前): 在实例销毁之前被调用。在这个阶段,实例仍然可用,可以做一些清理工作,如取消订阅、清除定时器等。
-
destroyed(销毁后): 在实例销毁之后被调用。在这个阶段,Vue实例和所有相关的DOM已经被销毁,可以进行最终的清理工作。
这些生命周期钩子允许你在Vue实例的不同阶段执行代码,以适应你的业务逻辑需求。你可以在这些钩子中执行各种操作,包括数据处理、DOM操作、异步请求等,从而实现对应阶段的逻辑。了解和使用生命周期钩子是Vue.js开发中的重要部分,有助于更好地管理和控制应用程序的行为。
虚拟DOM源码简单示例
1、创建虚拟DOM节点
// 创建一个虚拟DOM节点 const vnode = { tag: 'div', props: { id: 'app', class: 'container' }, children: [ { tag: 'p', text: 'Hello, Vue!' } ] };
2、渲染虚拟DOM到实际DOM
// 渲染虚拟DOM到实际DOM function render(vnode, container) { // 创建实际DOM元素 const el = document.createElement(vnode.tag); // 设置属性 for (const key in vnode.props) { el.setAttribute(key, vnode.props[key]); } // 递归渲染子节点 vnode.children.forEach(child => { render(child, el); }); // 插入到容器中 container.appendChild(el); }
3、更新虚拟DOM
// 更新虚拟DOM function updateVNode(oldVNode, newVNode) { // 对比标签名 if (oldVNode.tag !== newVNode.tag) { // 标签名不同,创建新的实际DOM元素 const newEl = document.createElement(newVNode.tag); // 复制新虚拟DOM的属性到新元素 for (const key in newVNode.props) { newEl.setAttribute(key, newVNode.props[key]); } // 替换旧的实际DOM元素 oldVNode.el.parentNode.replaceChild(newEl, oldVNode.el); // 更新虚拟DOM的el属性为新元素 newVNode.el = newEl; } // 对比文本内容 if (newVNode.text !== oldVNode.text) { // 更新实际DOM的文本内容 newVNode.el.textContent = newVNode.text; } // 对比子节点 const oldChildren = oldVNode.children; const newChildren = newVNode.children; if (Array.isArray(oldChildren) && Array.isArray(newChildren)) { // 对比子节点数组 // 这里可以使用更复杂的Diff算法来处理子节点的更新 // 这里简化为示例,只处理子节点数量不同的情况 if (newChildren.length < oldChildren.length) { // 新子节点数量较少,需要删除多余的旧子节点 for (let i = newChildren.length; i < oldChildren.length; i++) { oldChildren[i].el.parentNode.removeChild(oldChildren[i].el); } } else if (newChildren.length > oldChildren.length) { // 新子节点数量较多,需要添加新子节点 for (let i = oldChildren.length; i < newChildren.length; i++) { render(newChildren[i], oldVNode.el); } } } }
v-if和v-for的优先级
不要把v-if和v-for同时用在同一个元素上,会带来性能方面的浪费。可以使用computed解决。 vue 2:当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级 vue 3:当 v-if 与 v-for 一起使用时,v-if 具有比 v-for 更高的优先级
v-for中key的作用
key属性是DOM元素的唯一标识。当数组发生增删时, 默认需要把发生改动的项目全部进行重绘 --- 浪费资源;当添加唯一标识之后, 一旦发生 增删操作之后,重绘之前会检测新绘制的元素 和 已有的元素 是否存在相同的 key , 相同则复用。
常用的修饰符
事件修饰符: stop(阻止事件冒泡) prevent(阻止默认行为) self(只触发本身) once(只触发一次) captrue(事件捕获,与事件冒泡的方向相反,事件捕获由外向内。) 表单修饰符: lazy(当光标离开标签之后,才会赋值给value) trim(过滤掉两边的空格) number(自动将用户输入的值转化为数值类型。) 键盘修饰符: 普通键(enter、tab、delete、space、esc、up…) 系统修饰键(ctrl、alt、meta、shift…) 鼠标修饰符:left,right,middle
自定义指令
为DOM元素添加额外的行为和交互,通常用于操作DOM、绑定事件、修改元素属性等。
-
什么是自定义指令: Vue.js的自定义指令允许你扩展Vue的核心功能,为DOM元素添加额外的行为和交互。自定义指令通常用于操作DOM、绑定事件、修改元素属性等。
-
自定义指令的生命周期钩子: Vue自定义指令具有一些生命周期钩子函数,用于定义指令的行为。主要的生命周期钩子包括:
-
bind
:只调用一次,当指令第一次绑定到元素时调用,可以在这里进行一次性的初始化工作。-
inserted
:被绑定元素插入父节点时调用,常用于初始化设置。 -
update
:在元素更新时调用,可能会被多次触发。 -
componentUpdated
:在组件更新完成后调用,可能会被多次触发。 -
unbind
:只调用一次,指令与元素解绑时调用,可以在这里进行清理工作。
-
// 注册一个全局自定义指令 `v-focus` Vue.directive('focus', { // 当被绑定的元素插入到 DOM 中时 inserted: function (el) { // 聚焦元素 el.focus(); }, });
然后,在模板中使用这个自定义指令:
<input v-focus>
slot插槽
1、默认插槽:又称匿名插槽,当slot没有指定name属性值的时候,一个组件内只能有一个匿名插槽。 2、具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。 3、作用域插槽:可以匿名也可以具名,该插槽的不同点在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件传递过来的数据决定如何渲染该插槽。
mixin、组件和插槽的区别
组件在引用之后相当于在父组件内开辟了一块单独的空间,来根据父组件props过来的值进行相应的操作,单本质上两者还是泾渭分明,相对独立。
mixins则是在引入组件之后,则是将组件内部的内容如data等方法、method等属性与父组件相应内容进行合并。相当于在引入后,父组件的各种属性方法都被扩充了。Mixin是一种可重用Vue组件的方式。
组件插槽则提供了一种在组件之间共享内容的方式。
v-clock:解决初始化页面闪动问题
如果网速较慢,vue元素还没有渲染情况下,页面会显示源代码的 例如:{{mesage}} 我们可以使用 v-clock,作用就是为Vue绑定的元素上添加该属性,只需要配合CSS设置样式就可以解决屏幕闪烁问题。
<style> [v-clock]{ display: none; } </style>
v-text
v-text也是显示文本信息,但是该指令不存在闪烁问题,另外该指令会覆盖信息展示区域,插值表达式只会替换自己的占位符
vue双向数据绑定的原理
Vue.js中的双向数据绑定是通过Vue的响应式系统和指令来实现的。其基本原理如下:
-
数据劫持(Data Observing):Vue会遍历组件的数据对象(data),并使用
Object.defineProperty
或Proxy
等机制在属性的getter和setter上设置侦听器。这意味着当数据发生变化时,Vue能够检测到,并触发相应的更新操作。 -
虚拟DOM(Virtual DOM):Vue使用虚拟DOM来优化DOM操作。当数据发生变化时,Vue首先会生成一个新的虚拟DOM树,然后与之前的虚拟DOM树进行比较,找出需要更新的部分(差异),然后只对这些差异进行实际的DOM更新操作,而不是直接操作整个DOM树。
-
指令和插值表达式(Directives and Interpolation):Vue中的指令(例如
v-model
、v-bind
、v-on
等)允许开发者在模板中声明性地将数据和DOM元素进行绑定。其中,v-model
指令是实现双向数据绑定的关键。 -
双向绑定的实现:
v-model
指令用于表单元素(如<input>
、<textarea>
和<select>
),它在内部使用了一个称为model
的变量来维护数据和DOM元素之间的双向绑定关系。当输入框的值发生变化时,v-model
会自动更新model
中的数据,反之亦然。这样,数据的变化会自动反映在DOM中,实现了双向绑定。
总结起来,Vue的双向数据绑定原理基于数据劫持、虚拟DOM和指令的组合。数据劫持用于监听数据变化,虚拟DOM用于高效更新DOM,而指令和特定的v-model
指令用于实现双向数据绑定,使数据与DOM元素之间的变化同步。
Fragment
减少标签层级, 减小内存占用。
如果在 vue
页面中有多个元素节点,那么编译时 vue
会在这些元素节点上添加一个 <Fragment></Fragment>
标签,并且该标签不会出现在 dom
树中。
在前端开发中,Fragment(片段)是一种轻量级的 DOM 结构,它不会在页面中创建实际的 DOM 元素,但可以用来包裹一组 DOM 元素,使它们在逻辑上形成一个整体,而不会增加额外的层级。
Fragment 主要用于以下情况:
-
性能优化: 当需要操作一组 DOM 元素,但不希望创建多余的 DOM 节点时,Fragment 可以减少内存和性能开销。例如,在循环中创建一组列表项时,将它们包装在一个 Fragment 中可以提高性能,因为不会在每个列表项之间插入多余的父节点。
-
防止样式污染: 在某些情况下,特别是在使用第三方组件库或外部库时,为了避免 CSS 样式污染,可以使用 Fragment 将组件的模板内容包裹在一个隔离的容器中。
-
组织代码: 使用 Fragment 可以更清晰地组织代码,使逻辑上相关的 DOM 结构在代码中更易于识别和维护。
在 Vue.js 和 React 等前端框架中,Fragment 是常见的元素,用于包装组件的模板内容,或者用于包装列表项、表单元素等,以提高性能和代码可读性。在 Vue.js 中,你可以使用 <template>
标签来创建 Fragment。在 React 中,你可以使用 <>
或 <React.Fragment>
来创建 Fragment。不同的框架和语言可能有不同的语法和实现方式,但概念都是相似的,即创建一个轻量级的包装容器,而不引入额外的 DOM 层级。
Vue2和 Vue3有什么区别
性能:Vue 3 比 Vue 2 更快,因为它采用了新的渲染引擎,这使得它在大型应用程序中更快。 语法:Vue 3 使用了更简单的语法,并移除了一些 Vue 2 中的不常用功能,这使得代码更容易维护和阅读。 设计:Vue 3 采用了更加模块化的设计,把各个组件的功能分离开,使得应用程序更加灵活和可扩展。 TypeScript 支持:Vue 3 原生支持 TypeScript,可以更轻松地与其他 TypeScript 项目集成。 Composition API:Vue 3 引入了 Composition API Vue2 在模板中如果使用多个根节点时会报错, Vue3 支持多个根节点,也就是 fragment
1.双向数据绑定原理不同
Vue2 的双向数据绑定是利用ES5的一个APIObject.definePropert() 对数据进行劫持,结合发布订阅模式的方式来实现的。
Vue3 中使用ES6的Proxy API对数据代理。
Vue3 使用数据代理的优势有以下几点:1)definePropert 只能监听某个属性,不能对整个对象进行监听 2)可以省去for in,闭包等内容来提升效率(直接绑定整个对象即可)3)可以监听数组,不用再单独的对数组做特异性操作,Vue3可以检测到数组内部数据的变化
2.是否支持碎片
Vue2 不支持碎片。Vue3 支持碎片,就是说可以拥有多个根节点
3.API 类型不同
Vue2 使用选项类型api,选项型api 在代码里分割了不同的属性:data,computed,method等。
Vue3 使用合成型api,新的合成型api 能让我们使用方法来分割,相比于旧的api 使用属性来分组,这样代码会更加简便和整洁。
4定义数据变量和方法不同
Vue2是把数据放到了data 中,在 Vue2中 定义数据变量是data(){},创建的方法要在method:{}
Vue3 就需要使用一个新的setup()方法,此方法在组件初始化构造的时候触发。使用以下三个步骤来建立反应性数据:1)从vue 引入 reactive;2)使用 reactive ()方法来声明数据为响应性数据;3) 使用setup()方法来返回我们的响应性数据,从而template 可以获取这些响应性数据。
5.生命周期钩子函数不同
Vue2 中的生命周期:beforeCreate 组件创建之前;created 组建创建之后;beforeMount 组件挂载到页面之前执行;Mounted 组件挂载到页面之后执行,beforeUpdate 组件更新之前;updated组件更新之后
Vue3 中的生命周期:setup 开始创建组件;onBeforeMount 组件挂载到页面之前执行;onMounted 组件挂载到页面之后执行;onBeforeUpdate 组件更新之前;onUpdated 组件更新之后;
而且 Vue3 生命周期在调用前需要先进行引入。除了这些钩子函数外,Vue3 还增加了 onRenderTracked 和onRenderTriggered 函数。
6.父子传参不同
Vue2 父传子,用props ;子传父用事件Emitting Events。在Vue2 中,会调用this$emit 然后传入事件名和对象。
Vue3 父传子,用props;子传父用Emitting Events 。在Vue3 中的setup()中的第一参数content 对象中就有 emit,那么我们只要在setup()接收第二个参数中使用分解对象法取出emit 就可以在setup 方法中随意使用了。
7.指令与插槽不同
Vue2 中使用slot 可以直接使用slot ;v-for 与v-if 在Vue2中优先级高的是v-for 指令,而且不建议一起使用。
Vue3 中必须是使用v-slot的形式;vue 3中v-for 与v-if ,只会把当前v-if 当作v-for 的一个判断语句,不会相互冲突;
vue2和vue3的区别_vue2和vue3区别-CSDN博客
reactive与ref的区别
从定义数据角度对比: ref用来定义:基本类型数据 reactive用来定义对象(或数组)类型数据 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。
从原理角度对比: ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。 reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
从使用角度对比: ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。 reactive定义的数据:操作数据与读取数据:均不需要.value。
$route 和 $router 区别:
1、$route: $route表示当前激活的路由信息 可以获取当前路由的path,name ,params , query 属性。$route是一个跳转的路由对象(路由信息对象),每一个路由都会有一个$route对象,是一个局部的对象。
主要的属性有: this.$route.path 字符串,等于当前路由对象的路径,会被解析为绝对路径,如/home/ews this.$route.params 对象,包含路由中的动态片段和全匹配片段的键值对,不会拼接到路由的url后面 this.$route.query 对象,包含路由中查询参数的键值对。会拼接到路由url后面 this.$route.router 路由规则所属的路由器 this.$route.name 当前路由的名字,如果没有使用具体路径,则名字为空
2、$router:是VueRouter的一个实例,他包含了所有的路由,包括路由的跳转方法(push go replace),钩子函数等,也包含一些子对象(例如history)
//常规方法 this.$router.push("/login"); //使用对象的形式 不带参数 this.$router.push({ path:"/login" }); //使用对象的形式,参数为地址栏上的参数 this.$router.push({ path:"/login",query:{username:"jack"} }); 使用对象的形式 ,参数为params 不会显示在地址栏 this.$router.push({ name:'user' , params: {id:123} });
computed原理
computed的实现原理基于Vue的响应式原理。当某个响应式数据被访问时,会触发getter函数,将当前Watcher对象添加到该响应式数据的依赖列表中,当该响应式数据发生改变时,会触发setter函数,通知依赖列表中的所有Watcher更新。
在computed中,其属性值所依赖的数据对象会被设置为响应式数据,当这些响应式数据发生改变时,会通知存储computed的Dep对象,标记该computed为dirty。当下次访问该computed的属性值时,computed会检查依赖的响应式数据是否发生了变更,如果没有则直接返回已缓存的属性值;如果依赖的数据发生了变更,则重新计算属性值,并将计算后的结果缓存起来。此时,该computed会将dirty标记重置为false,等待下次依赖项发生变化时再次重新计算。
因此,computed具有缓存和惰性求值的特点,能够避免重复计算,提高了程序性能。
Vuex
vue的状态管理模式和库。用于管理应用程序的状态(如数据、状态信息、配置等),并提供了一种可预测的方式来处理状态的变化。Vuex 是特别设计用于大型单页应用程序(SPA),它提供了一个集中式的状态存储和一组规则来确保状态的一致性。
核心概念和组件:
-
State(状态): State 是应用程序中的数据源,它包含了应用程序中需要共享和管理的所有状态数据。通常是一个对象,可以包含各种数据,如用户信息、应用程序配置、缓存数据等。
-
Getters(获取器): Getters 允许你从 State 中派生出一些值,以便在组件中使用。它们是一种类似于计算属性的功能,允许你在需要时对 State 进行计算。
-
Mutations(突变): Mutations 是用于修改 State 的唯一方法。它们是同步的函数,每个 Mutation 接收一个 State 对象和一个 payload(负荷),用于描述 State 的变化。Mutations 提供了一个可跟踪的状态变更历史,以便调试和开发过程中的问题排查。
-
Actions(动作): Actions 允许你执行异步操作,例如数据获取或 API 调用,然后再提交 Mutations 来修改 State。它们提供了一个更灵活的方式来处理异步操作,同时保持 State 修改的单一来源原则。
-
Modules(模块): Modules 允许你将应用程序的 State、Getters、Mutations 和 Actions 组织到模块化的结构中。这对于大型应用程序尤其有用,因为它可以帮助你保持代码的整洁性和可维护性。
Vuex 的核心思想是通过统一的数据流,将 State 变更变得可追踪和可控,使得多个组件可以共享和修改应用程序的状态,而不会导致混乱和不一致的状态。这对于构建大型单页应用程序,特别是需要复杂状态管理的应用程序来说,是非常有帮助的。
路由
route 和 router 区别:
route: route表示当前激活的路由信息 可以获取当前路由的path,name ,params , query 属性
router: router是全局的router实例 主要用来进行路由跳转 push go replace
Vue3 Vue2 编程式导航 与 声明式导航区别:
编程式就是 Vue-router 这个包提供的vue组件 <router-link to="路径">
声明式导航是通过函数的方式跳转 常见的方法有 this.$router.push('/路径')
router 回退上一个页面方法:
router.go(-1)
router.back()
Vue2 / 3 路由方法的不同:
Vue2 获取当前页面是 this.$route 路由跳转页面是 this.$router
Vue3 获取当前页面是 useRoute 路由跳转页面是 useRouter 然后需要导入,需要调用函数 定义变量
hash模式和history模式的区别:
1:hash 模式下,仅hash符号之前的内容会被包含在请求中,如http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误。
2:history模式下,前端的URL必须和实际向后端发起请求的URL一致。如htttp://www.abc.com/book/id。如果后端缺少对/book/id 的路由处理,将返回404错误
3.hash路由在地址栏URL上有#,用 window.location.hash 读取。而history路由没有。
4.hash的特点在于它虽然出现在了URL中,但是不包括在http请求中,所以对于后端是没有一点影响的,所以改变hash不会重新加载页面,所以这也是单页面应用的必备。
1.原理不同。
hash模式的实现原理是通过监听hashChange事件来实现的。history模式是通过调用 history.pushState方法(或者replaceState) 并且 监听popstate事件来实现的。history.pushState会追加历史记录,并更换地址栏地址信息,但是页面不会刷新,需要手动调用地址变化之后的处理函数,并在处理函数内部决定跳转逻辑;监听popstate事件是为了响应浏览器的前进后退功能。
2.表现不同。
hash模式会在地址栏中有#号,而history模式没有;同时由于history模式的实现原理用到H5的新特性,所以它对浏览器的兼容性有要求(IE >= 10)。
组件间的通信方式
【精选】超详细 vue组件通信的10种方式_鹏多多.的博客-CSDN博客
网络
DNS:域名系统
域名系统为Internet上的主机分配域名地址和IP地址,是域名和IP地址相互映射的一个分布式数据库。每一个域名都对应一个唯一的IP地址,用户使用域名地址,DNS就是进行域名解析的服务器,自动把域名地址转为IP地址。
五层模型
七层结构中的每一层使用下一层提供的服务,并且向其上层提供服务。
应用层的传输单位是报文(message),为用户提供所需要的各种服务。 传输层的传输单位是报文段(segment),为应用层实体提供端到端的通信功能。 网络层的传输单位是数据包(packet)或分组(datagram),主要用于解决主机到主机的通信问题,为数据包选择路由。 数据链路层的传输单位是帧(frame),传输有MAC地址的帧。 物理层的传输单位是比特(bit),使用双绞线、中继器、集线器等物理媒介。
三次握手
第一次:客户端发送SYN包到服务器,等待服务端确认
第二次:服务器收到SYN包,必须确认客户的SYN,同时也发送一个SYN+ACK包
第三次:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,发送完毕,客户端和服务端连接成功,完成三次握手
在TCP/IP协议中,SYN和ACK是用于建立TCP连接的控制标志(Flag)。SYN标志用于请求建立连接。ACK标志用于确认收到对方的数据包。
四次挥手
第一次:浏览器发送完数据后,发送FIN,请求断开连接。(使用自己的序列号u)
第二次:服务器发送ACK到客户端,表示已收到客户端的断开请求,进入CLOSE-WAIT阶段
(确认客户端的序列号u(返回u+1),并将自己的序列号设置为v。)
第三次:服务器发请求断开FIN+ACK的请求(使用自己的序列号w)
第四次:客户端确认服务器的断开ACK
(确认服务器的序列号w,并将自己的序列号设置为u+1)
FIN标志表示发起一方希望关闭连接,不再发送数据。 ACK标志用于确认收到对方的数据包。
序列号: 在TCP通信中,每个数据包都有一个序列号(Sequence Number),用于保证数据的顺序传输和可靠性。序列号用来标识每个数据包在数据流中的位置。在连接关闭的过程中,序列号也会被用来确认对方的数据包。序列号用于标识每个数据包的位置和状态,确保数据包的可靠传输和连接的正确关闭。TCP序列号是TCP协议中用来管理和控制数据包传输的重要机制。通过序列号,TCP可以确保数据的有序传输、及时重传丢失的数据包、控制流量和确认数据的到达。这些功能使得TCP成为一种可靠的数据传输协议,适用于需要确保数据可靠性和有序性的应用,如Web浏览、文件传输等。
Http和Https区别
超文本传输协议 与 超文本传输安全协议,虽叫传输协议,但在应用层。
默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
HTTP 信息是明文传输,存在安全风险的问题。
HTTPS 在 TCP 和 HTTP 应用层之间(会话层)加入了 SSL/TLS 安全协议,使得报文能够加密传输。
HTTP 连接建立相对简单, TCP 三次握手之后便可进行报文传输。
而 HTTPS 在三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
HTTPS 协议中,服务器需要向 CA(证书权威机构)申请数字证书SSL,来保证自己的身份是可信的。
混合加密的方式实现信息的机密性,解决了窃听的风险。
HTTP(Hypertext Transfer Protocol)和 HTTPS(HTTP Secure)是两种用于在网络上传输数据的协议,它们之间的主要区别在于安全性和数据传输方式:
-
安全性:
-
HTTP(非安全): HTTP 是一种不安全的协议,传输的数据是明文的,容易被窃听者拦截和查看。这意味着如果你使用 HTTP 传输敏感信息,如登录凭证或信用卡信息,这些信息可能会被黑客窃取。
-
HTTPS(安全): HTTPS 是 HTTP 的加密版本,使用了 TLS/SSL(Transport Layer Security/Secure Sockets Layer)协议来加密数据传输。这意味着在 HTTPS 连接上,传输的数据是加密的,难以被第三方窃听。因此,HTTPS 更适合处理敏感信息,如用户登录和支付信息。
-
-
数据传输方式:
-
HTTP: HTTP 数据传输是明文的,请求和响应数据都可以被拦截者查看,因此不太适合传输敏感信息。
-
HTTPS: HTTPS 使用加密来保护数据,请求和响应在传输过程中加密和解密,因此更安全。
-
-
证书和身份验证:
-
HTTP: HTTP 不提供服务器身份验证机制,无法验证你是否连接到真正的服务器。
-
HTTPS: HTTPS 使用数字证书来验证服务器的身份,确保你连接到的是正确的服务器。这有助于防止中间人攻击。
-
-
搜索引擎排名:
-
HTTP: Google和其他搜索引擎更倾向于排名使用 HTTPS 的网站,因为它们提供了更好的用户隐私和安全性。使用 HTTPS 可能有助于提高搜索引擎排名。
-
总之,HTTPS 提供了更高的安全性和隐私保护,因此在处理敏感信息和保护用户隐私方面更受推荐。如果你拥有网站,特别是一个需要用户登录或传输敏感信息的网站,强烈建议使用 HTTPS 来提供更好的安全性和信誉。
UDP和TCP的区别
UDP(User Datagram Protocol)和TCP(Transmission Control Protocol)是两种网络传输协议,它们之间有许多关键区别,主要涉及到可靠性、连接性和传输效率等方面。
以下是 UDP 和 TCP 的主要区别:
-
连接性:
-
TCP: TCP 是一种面向连接的协议,它要求在数据传输之前建立连接。在通信的两端,必须建立一个连接,然后才能进行数据传输。连接是可靠的,确保数据按顺序传递和完整接收。
-
UDP: UDP 是一种非连接的协议,数据可以立即发送,而无需建立连接。UDP 不提供连接状态或可靠性保证。
-
-
可靠性:
-
TCP: TCP 提供了可靠性的数据传输。它使用确认、重传以及序列号等机制来确保数据的可靠交付,以及按顺序传递。
-
UDP: UDP 不提供可靠性保证。数据包可能会丢失、乱序或重复,而无需任何恢复机制。
-
-
数据流控制:
-
TCP: TCP 提供了流控制机制,确保发送方和接收方之间的数据传输速度协调一致,避免了数据的过度拥塞。
-
UDP: UDP 不提供流控制,发送方可能以任何速度发送数据,而接收方需要自己处理数据的接收和处理速度。
-
-
头部开销:
-
TCP: TCP 头部较大,包含了用于连接管理和可靠性的多种字段,因此头部开销较高。
-
UDP: UDP 头部较小,只包含了基本的源端口、目标端口、长度和校验和字段,头部开销较低。
-
-
应用场景:
-
TCP: 适用于要求可靠性、按顺序传递的应用,如网页浏览、电子邮件传输、文件下载等。
-
UDP: 适用于实时性要求高、无需可靠性的应用,如音频和视频流、在线游戏、DNS 查询等。
-
长连接与短连接的概念
长连接:指在一个TCP连接上可以连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接;一般需要自己做在线维持。 短连接:指通信双方有数据交互时,就建立一个TCP连接,发送数据完成后,则断开此TCP连接;一般银行都用短连接。 短连接的优点:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段,比如:http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。 其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态。
长连接与短连接的操作过程 通常的短连接的操作步骤是:连接->数据传输->关闭连接; 而长连接通常就是:连接->数据传输->保持连接(心跳)->数据传输->保持连接(心跳)->......->关闭连接; 这就要求长连接在没有数据通信时,定时发送数据包(心跳),以维持连接状态,短连接在没有数据传输时直接关闭就行了。 长连接多用于操作频繁,点对点的通信,而且连接数不能太多的情况。因为每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,在操作的话,那么处理速度会降低很多,所以每个操作完后都不断开,下次处理时直接发送数据包就OK了,不用建立TCP连接。如:数据库的连接用长连接,如果用短连接频繁地通信会造成socket错误,而且频繁的socket创建也是对资源的浪费。 短连接的使用场合如http,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。
CDN
CDN 是内容分发网络(Content Delivery Network)的缩写。它帮助网站和应用程序更快地将内容(例如网页、图像、视频、样式文件等)传送给用户,提供更好的性能和用户体验。
CDN 通过在全球各地分布的服务器上存储网站或应用程序的内容,使用户可以从距离他们更近的服务器获取这些内容,从而提高了加载速度。
当你访问一个使用 CDN 的网站时,网站的静态资源(例如图片、脚本、样式表等)通常不会从原始服务器直接传输到你的设备。相反,这些资源会被缓存在离你更近的 CDN 服务器上,这个 CDN 服务器可以更快地响应你的请求,因此网站加载速度更快。
总之,CDN 就像是一个网络内容加速器,它通过将内容存储在全球多个地点的服务器上,提供更快、更可靠的内容传输,以改善网站和应用程序的性能,减少延迟,提高用户体验。
CDN 的设计目标是提高网站性能和可用性,而不是导致内容泄露或安全漏洞。使用 CDN 不应该导致内容泄露或受到攻击,但需要采取一些安全措施来确保安全性。
安全性的考虑:
-
安全配置:确保你的 CDN 配置是安全的。这包括使用 HTTPS 来加密传输数据,以及采取其他安全措施,如网络防火墙、DDoS 防护等。
-
内容验证:确保在 CDN 上存储的内容是受信任的,并且不包含恶意代码。定期检查和验证内容以防止潜在的安全漏洞。
-
跨域问题:在某些情况下,使用 CDN 可能涉及到跨域问题。浏览器通常会执行跨域安全策略,但你需要确保你的网站和 CDN 之间进行适当的跨域配置,以防止跨域问题。
-
访问控制:限制对 FCDN 内容的访问权限,确保只有授权用户可以访问你的内容。这可以通过配置 CDN 来实现。
性能
监控
前端监控是指对前端应用程序的各种方面进行监视和收集数据,以便及时发现问题、优化性能以及改进用户体验。前端监控通常包括以下几个方面:页面买点监控、性能监控和异常监控。让我详细介绍每个方面,并举例说明应用场景:
-
页面埋点监控:
页面埋点监控旨在跟踪和分析用户在网站或应用程序上的行为和交互。这包括用户点击、页面浏览、表单提交等关键事件的跟踪和记录。具体的应用场景和示例包括:
-
用户行为分析:通过监控用户的点击和页面访问路径,您可以了解用户如何与您的应用程序交互,哪些功能受欢迎,哪些可能存在问题。例如,您可以使用Google Analytics或自定义事件跟踪库来监控特定按钮的点击次数。
-
转化率优化:监控购物车添加、支付按钮点击等关键事件,以识别用户在购买流程中的退出点,并采取措施来提高转化率。例如,如果您运营电子商务网站,可以跟踪用户将商品添加到购物车并进入结账流程的次数。
-
A/B测试:在A/B测试中,您可以使用页面买点监控来跟踪不同版本的页面或功能的用户交互。通过比较不同版本的页面的转化率和用户行为,您可以确定哪个版本更有效。例如,您可以测试不同颜色的购买按钮,然后分析点击率。
-
-
性能监控:
性能监控旨在测量和分析前端应用程序的性能指标,以确保应用程序加载快速且响应迅速。应用场景和示例包括:
-
页面加载时间:监控页面的加载时间,包括首次加载时间、页面完全加载时间以及各种资源(如CSS、JavaScript、图像)的加载时间。例如,使用工具如WebPageTest或Lighthouse来评估页面性能。
-
资源使用情况:跟踪资源的加载情况,包括检测不必要的资源加载、优化图像大小以及合并和压缩JavaScript和CSS文件。这有助于减少页面加载时间。例如,使用Webpack或Gulp进行资源优化。
-
响应时间分析:监控用户与应用程序的交互的响应时间,包括页面上的点击、表单提交等。如果用户等待时间过长,可能会导致用户流失。例如,使用前端性能监控工具如New Relic或Datadog来分析响应时间。
-
-
异常监控:
异常监控旨在捕获和分析前端应用程序中的错误和异常情况,以及用户遇到的问题。具体应用场景和示例包括:
-
JavaScript错误追踪:监控前端JavaScript错误,包括未捕获的异常、脚本加载错误等。当错误发生时,可以收集堆栈跟踪和错误信息,以便开发团队及时修复。例如,使用Sentry、Rollbar或自定义错误捕获机制。
-
Ajax请求错误:监控异步请求(例如,Ajax请求)的错误,以便及时发现并处理与后端通信相关的问题。例如,如果API请求返回错误状态码,应该捕获并记录。
-
用户问题报告:允许用户报告问题和异常情况,以便快速响应和解决。例如,为应用程序添加反馈表单或报告问题的功能。
-