什么是 vue 的响应式?
vue数据响应式的设计的初衷是为了实现数据和函数的联动。 当数据变化后,用到该数据的联动函数会自动重新运。
具体在vue的开发中,数据和<组件的render函数>关联在一起,从而实现了数据变化自动运行render,在的感官上 就看到了组件的重新渲染。
除了vue自动关联的render函数,还有其他很多使用到vue响应式的场景,比如computed,watch等,不能仅仅把vue的数据响应式想象成和render的关联。
route和router的区别
作用:route提供当前路由信息(包括 path、params、query等信息),router控制路由跳转和全局管理,是个vueRouter的实例对象,有push/replace/go等方法
典型场景:route获取当前页面路由的参数:route.params.id;router通过push来跳转页面:router.push('/detail')
vue组件传参方法
vue3:
1:props/$emit
2:expose/ref
3:attrs(子组件使用useAttrs()接收非props属性集合)
4:v-model
5:provide(向后代提供数据)/inject(从祖先处拿数据)
6:vuex/pinia
7:mitt(Eventbus的替代方案,需安装依赖)
vue2.x:
1:props
2:v-model
3:ref
4:$emit/v-on
5:$attrs/$listeners
6:$children/$parent
7:provide/inject
8:EventBus
9:vuex
对于MVVM的理解
是一种专注于分离用户界面和业务逻辑的架构模式。
| 角色 | 比喻餐厅 | 职责与对应的部分 |
|---|---|---|
| Model | 后厨与仓库 | 数据与业务核心。负责数据的获取、存储、验证和业务逻辑,不关心数据的具体展示。 |
| View | 菜单、装潢 | 用户界面。直接面向用户,负责展示信息和接受指令。 |
| ViewModel | 服务员、传菜员 | 视图的抽象与状态管理。是连接数据业务和用户界面的桥梁。负责将Model中的数据转换成View层中易于显示的形式(e.g. 日期格式化),并处理来自View层的交互指令(e.g. 用户的单击事件),然后调用Model层中的逻辑进行处理。 |
核心机制:数据绑定。
MVVM模式高效运作的关键,在于“数据绑定”这个自动化机制。可以理解成把‘餐厅前厅用餐区’和‘服务员’连接起来的一套自动化的传菜系统。
当ViewModel里的数据变化时,系统会自动更新到View的显示上。同样的,用户在View上操作后,变化也会自动同步到ViewModel。(e.g. 仓库进了新食材,能出更多的餐,这个时候,前厅的点餐页面上的餐食余量会自动更新到最新数据。同样的,用户在点完餐下单后,后厨那边的食材量会自动扣除相应数量。)这就是双向绑定,可以极大减少需要手动编写来同步 数据和UI的冗余代码。
数据绑定除了刚刚说到的数据双向绑定外,还有一个是命令绑定。用户界面上的操作(e.g. 单击事件)会直接绑定到 ViewModel 中的命令上,从而触发相应的业务逻辑。
vue生命周期
vue3:
1:onBeforeMount
2:onMounted
3:onBeforeUpdate
4:onUpdated
5:onBeforeUnmount
6:onUnmounted
vue2:
1:beforeCreate
2:created
3:beforeMount
4:mounted
5:beforeUpdate
6:updated
7:beforDestory
8:destoried
JS的数据类型有哪些?
原始类型:Number、String、Boolen、Null、Undefined、BigInt、Symbol
引用类型:Object
判断JS的数据类型的方案有哪些?
-
typeof
注意:对于对象、数组和null,
typeof返回 "object" -
instanceof
该方法基于原型链查找
-
Object.prototype.toString.call
返回一个类似[Object Function]的字符串,返回值可被修改
// 该方法返回的是[Symbol.toStringTag]字段,可做修改 let o1 = {} console.log(Object.prototype.toString.call(o1)); // [Object Object] let o2 = { [Symbol.toStringTag]:'嘿嘿嘿' } console.log(Object.prototype.toString.call(o2)); // 嘿嘿嘿 -
Array.isArray
专门用于数组判断
-
使用null检查
使用全等于null判断是否是null
哪些数据存储在堆上?哪些数据存储在栈上?
-
栈:变量、参数
-
堆:对象
原型的作用是什么?
之所以存在原型,是因为JS语言要实现面向对象,而原型是实现面向对象的手段之一。一个能支持面向对象的语言必须要做到的1点就是:能判定一个实例的类型。 在JS中,通过原型就可以知晓某个对象从属于哪个类型。换句话说就是:原型的存在能避免数据类型的丢失。
GET和POST有什么区别?
协议层面:只有单词拼写的区别
应用层面:GET一般不带请求体(或者叫请求体留空)
浏览器层面:
1:GET用于检索或者获取资源,不会对服务器状态产生修改,属于“安全”方法。 POST用于提交数据以创建/更新资源,可能会修改服务器状态,属于“非安全”方法。
2:GET请求的参数直接拼接在URL后面(query传参),POST方法的参数是封装在http请求体中,不可见
3:数据长度方面,GET请求会受URL长度限制,而POST方式在理论上是无限制的
4:GET请求只支持ASCLL码,POST可传输二进制数据
5:GET参数直接暴露在URL中,不适合传输敏感数据,POST参数封装在请求体中,相对来说更安全
6:GET请求会被浏览器缓存,且参数会被保留在历史记录中。POST默认不缓存,且不保留历史记录
Promise解决了什么问题?
Promise的出现最重要的是为了统一 JS 中的异步实现方案。
异步是JS中的常见场景,统一实现方案,不仅可以有效降低心智负担,更重要的是可以让不同的异步场景相互联动。
Promise也无法消除回调,它只不过通过链式调用的方式让回调变得可控。
数组去重,要求如果两个对象它们的属性相同也认为是重复的
要点:Set去重的原理是利用Object.is()方法,Object.is类似于「===」,又有些区别。
+0 === -0 true
NaN === NaN false
{} === {} false
Object.is(+0,-0) false
Object.is(NaN,NaN) true
Object.is({},{}) false
let和var的区别
1:全局污染,var申明的变量会被挂载到全局,let不会,但是两者都可以跨script标签
2:块级作用域,在ES6以前作用域大致就是分为全局作用域和函数作用域
3:TDZ(暂时性死区)
4:重复申明,var允许重复申明,let不允许重复申明
==比较规则
1:两端存在NaN,直接false
2:undefined和null只有在跟自身比较或者两者互相比较时为true,其他为false
3:两端类型相同比较值
4:两端都是原始类型,则转换成数字后比较
5:一端是原始类型一端是对象类型,把对象类型转换成原始类型后比较
对象如何转原始类型?
1:如果对象拥有[Symbol.toPrimitive]方法,调用该方法。
若该方法能得到原始值,使用该原始值
若得不到原始值,抛出异常
2:调用对象的valueOf方法
若该方法能得到原始值,使用该原始值
若得不到原始值,进入下一步
3:调用对象的toString方法
若该方法能得到原始值,使用该原始值
若得不到原始值,抛出异常
如何把一个class转成function
class Example {
construct(name){
this.name = name
}
func(){
console.log(this.name)
}
}
'use strict';
function Example(name){
// 如果没有通过new调用就抛出错误
if(new.target === undefined){
throw new TypeError('只能通过new调用');
}
this.name = name;
}
Object.defineProperty(Example.prototype,'func',{
value:function(){
if(new.target){
throw new TypeError('不能通过new调用');
}
console.log(this.name)
},
enumerable:false
})
路由守卫
1:全局守卫
1). beforeEach:在路由跳转前执行的操作,可以进行权限验证,登录判断等
2). afterEach:在路由跳转后执行的操作
3). beforeResolve:在导航被确认之前,所有组件内守卫和异步路由组件被解析之后调用
接收参数都是3个:to,from,next
to参数:
path:目标路由的路径
params:目标路由的参数对象
query:目标路由的查询参数对象
fullPath:path+params+query
from参数:
path:当前路由的路径
params:当前路由的参数对象
query:当前路由的查询参数对象
fullPath:path+params+query
next参数:
next():允许路由跳转
next(false):取消路由导航
next('/'):重定向到指定路由,例如:当登录判断不通过时,next('login')去登录页面
next({...}):重定向到指定路由对象
2:路由级别守卫
1). beforeEnter:路由进入前执行的操作
2). beforeLeave:路由离开前执行的操作
3). beforeRouteEnter:在路由进入前执行的操作,在组件实例创建前调用,所以无法获取到组件实例
4). beforeRouteUpdate:在路由更新时执行操作,比如路由参数发生变化
prototype和__proto__的区别和联系
| 概念 | 归属对象 | 本质/类型 | 存在的目的 | 典型示例 |
|---|---|---|---|---|
| prototype | 只有函数(构造函数) | 普通对象 | 存放供所有实例继承的公共属性/方法,是构造函数的原型仓库 | Function.prototype |
| __proto__ | 所有对象(包括函数) | 原型指针 | 连接实例与原型对象,是实例的原型导航,用于原型链查找 | {}.__proto__ |
关键结论:
1,普通对象/数组 没有prototype。例如 const obj = {};obj.prototype就是undefined,只有 function F(){} 这样的函数才有 F.protytype
2,函数身兼两职。函数既是一个可执行代码块,也是对象,因此,函数既有prototype(作为构造函数),也有__proto__(作为对象的导航指针,指向 Function.prototype)
3,__proto__ 是访问器属性。并非是对象的直接属性,而是,Object.prototype 上的get/set 方法,本质是调用 Object.getPrototypeOf()/Object.setPrototypeOf()
关联关系:
__proto__是实例与 prototype 的桥梁。原型链的本质,是通过__proto__把多个 prototype 串联起来的链式结构。
webpack从npm run build 到 输出文件的过程做了哪些
当执行npm run build命令时,webpack会启动构建流程,从读取配置、编译模块到输出文件,整个过程可概括为3个核心阶段:初始化、编译构建和输出。
1:命令触发阶段:读取package.json的scripts字段,执行对应的命令
2:webpack cli阶段
1.cli解析命令行参数
2.加载配置文件
3.验证配置有效性
4.创建webpack compiler实例
3:编译准备阶段
1.合并默认配置、cli参数、配置文件
2.初始化插件系统
3.注册所有插件到相应的生命周期钩子
4:编译:entry->module->dependencies->more modules
1.触发compile钩子,创建compilation对象-代表一次具体的编译
2.根据entry配置找到所有入口文件
3.解析文件路径(处理别名、扩展名等等)
4.读取文件内容
5.应用配置的loader
6.将非js资源转换为js模块
5:依赖图构建:入口文件->解析AST->发现import/require等->递归处理依赖
1.解析 AST
2.识别所有依赖关系
3.构建完整的依赖图
6:模块转换
1.loader 处理链:示例 CSS 处理(style.css->css-loader->style-loader->js 模块)
2.转换结果缓存
7:优化阶段
1.代码分割(
入口点分割:每个入口生成单出的 chunk;
动态分割:import语法创建新 chunk;
提取公共代码:commonschunkplugin/splitchunksplugin;
)
2.各种优化插件执行(
tree shaking
代码压缩:terserwebpackplugin 压缩 js
CSS 优化:minicssextractplugin
)
3.chunk 生成(
将模块分组到不同的 chunk
确定 chunk 的包含关系和加载顺序
)
8:生成阶段
1.资源生成,生成内容:
运行时代码/模块代码/source map
2.模板渲染
3.哈希计算
9:输出阶段
1.文件写入
创建输出目录
按照 output.filename 规则生成文件名
将内存中的资源写入磁盘
2.生成的文件
典型输出结构:
dist/
main.js
vendor.js
runtime.js
main.css
index.html
chunk.js
10:完成阶段
统计信息/触发完成钩子/输出日志
webpack如何对代码进行拆分的
1.基于入口的代码分割,通过配置多个入口点,将模块拆分到不同的bundle中,但是可能会导致重复代码,需配合splitchunks解决。
2.动态导入。通过import语法,动态加载模块,webpack会自动将其拆分为单独的chunk。
3.使用splitChunks配置。splitChunks可自动拆分公共代码、第三方库等,是最常用的代码分割方式。
代码示例
splitChunks: {
chunks: 'all', // 对所有类型的chunks生效
cacheGroups: {
// 拆分第三方库(如node_modules中的依赖)
vendor: {
test: /[\\/]node_modules[\\/]/, // 匹配 node_modules 目录
name: 'vendors', // 生成的 chunks 名称
chunks: 'all', // 对所有 chunks 生效
priority: 10. // 优先级(数字越大越优先)
},
// 拆分公共代码(被多次引用的模块)
common: {
name: 'common',
minChunks: 2, // 至少被引用 2 次才会被拆分
chunks: 'all',
priority: 5,
reuseExistingChunk: true. // 复用已有的 chunk,避免重复打包
}
}
}
如何禁止用户复制
// 禁用文本选择
user-select: none;
// 监听并阻止复制
document.addEventListener('copy',(e) => {
e.preventDefault();
alert('复制已被阻止');
})
// 将内容嵌入图片或者canvas
// 使用CSS伪元素显示内容
.protected-content:before {
content: '内容文字'
}
为什么需要虚拟DOM?
0.性能优化
1. 减少直接操作真实DOM的开销
2. 批量更新与最小化变更
3.避免“删了重写”式渲染
1.框架设计
vue和react框架设计的颗粒度只能够到组件,不能准确定位到精确的DOM,组件中有可能会有大量的真实DOM,所以就绕了个远路,最好的方案肯定是直接操作真实DOM,但是vue和react的设计没办法定位到具体的真实DOM,有可能我这边数据变化了,与其绑定的真实DOM只有一个,但是由于颗粒度只达到组件级,如果说该组件有1000个真实DOM,如果没有虚拟DOM,那就会更新1000个DOM,这个效率是难以接受的,所以才会有虚拟DOM,来进行新旧虚拟DOM树的对比,来找出最小更新,提高渲染效率。
2.解耦运行环境
vue和react框架在设计的时候没有说 我这框架就一定只能运行在浏览器环境中,他是希望可以把这个框架代码呢可以移植到其他环境的,像小程序啊,移动端啊之类的,所以这边就不能去绑定真实DOM,因为真实DOM的话他只有在浏览器环境里面才有。那如果框架跟真实DOM绑定在一起了,那你的框架不是就只能用在浏览器环境里面了吗。所以为了解耦运行环境,就把真实DOM提出去了,转而用js对象来描述界面,因为js对象是属于ES范畴,只要你的环境是支持ES的就一定支持js对象,这样的话不管是在移动端/小程序/网页中,就可以用同一套 js 对象来描述页面,具体在各个环境中就用虚拟DOM来渲染成各自的页面。
cookie的组成和配置
名称(Name):
Cookie的名称
值(Value):
与名称相关联的数据
过期时间(Expires/Max-Age):
Cookie的有效期,可以是具体时间或者是一个相对时间
路径(Path):
定义哪些路径下的页面可以接收这个Cookie
域(Domain):
指定哪些域名可以接收这个Cookie
安全标识(Secure):
当设置为Secure时,标识这个Cookie仅在HTTPS和SSL等安全协议下传输
HttpOnly标识:当设置为HttpOnly时,通过HTTP(S)协议传输的请求中会带上这个Cookie,但通过脚本无法访问,只能增加安全性。
SameSite属性:
用于防止CSRF攻击,有3种值:Strict、Lax、None。(Strict、Lax跨站请求不会携带,None跨站请求会自动携带,所以如果SameSite=None的话,必须设置Secure)
JS示例:
document.cookie =
"
user=JohnDoe;
expires=Thu, 18 Dec 2023 12:00:00 UTC;
path=/;
domain=example.com;
secure;
HttpOnly;
SameSite=Strict
";
cookie,sessionStorage,localStorage之间的区别
一,值存储的区别
sessionStorage:会话级存储,关闭浏览器再次进入值就没了
cookie、localStorage:可以做到持久化存储
二,过期时间
sessionStorage:关闭会话就过期
cookie:可以设置过期时间
localStorage:不可以设置过期时间(可以使用程序来进行删除)
三,访问限制
cookie:子域可以访问父域和自己的cookie
localStorage:同域下都可以访问
sessionStorage:只限当前窗口
JWT原理
全称:Json Web Token
假设现在没有JWT:用户登录,服务端验证成功,这个时候服务器一般有两种方案
1.在服务器那边建立一个session表格,这会极大的消耗服务器的资源,一般不会采用该方案
2.直接把登录结果返回给客户端,让客户端进行存储。这样会有以下问题:
1).数据极易被篡改(可以将张三的登录信息直接改成李四的登录信息)
2).数据极易被伪造(在未登录的情况下,直接按照已知规则去生成登录信息)
以上两种情况服务器不知道是否传过来的登录信息值得信任
为了解决数据被篡改以及伪造,就需要通过签名的方式,具体流程如下
1).服务器验证登录后,生成登录信息,并且生成一个密钥
2).把登录信息和密钥,通过第三方库例如crypto,进行加密,生成签名
3).把登录信息和签名发送回客户端,进行存储,密钥存储在服务器
4).这样客户端再回传验证的时候,可以拿到登录信息和签名,通过登录信息和服务端存储的密钥,进行重新加密,加密结果如果与回传的密钥不一致则验证不通过。
至于JWT是什么,可以理解为一个约定俗成的字符串格式。该字符串由3个部分组成,前两个就是我们登录信息,后一个部分就是签名。
1).第一部分是 header 字符串,包含算法声明等信息,通过base64编码转换成字符串
2).第二部分是 payload 字符串,包含登录信息,通过base64编码转换成字符串
3).第三部分是 signature 字符串,就是 header+payload+密钥生成签名,通过base64编码转换成字符串
4).上面 3 段字符串用点号连接,就是一个完整的JWT。
RAG原理
TODO
promise的状态吸收
Promise的状态吸收规则指的是:当一个Promise(p2)依赖于另一个Promise(p1)的状态时,p2会吸收p1的状态,也就是p1的完成或者拒绝状态会直接影响p2的状态。
具体规则包括:
1).p2的状态会跟随p1的状态变化
2).p2的结果也会继承自p1
3).状态吸收发生在p1的状态确定后,p2的状态从pending变成resolved或者rejected
4).状态吸收的时间点由浏览器或者引擎实现自行决定
例如,当p2 = p1.then(...)时,p2会吸收p1的状态,如果p1已经resolved,p2会立即resolved;如果p1是pending,p2会保持pending,直到p1状态确定。
V8引擎将状态吸收分为两个步骤,均放入微队列执行:第一步准备,第二步吸收。
1).准备阶段,被吸收的Promise(p1)的状态信息被整理
2).吸收阶段,目标Promise(p2)正式获取p1的状态,完成状态转换
前端权限问题
只能访问授权的功能
-
权限模型设计
- 基于角色访问控制 RBAC:权限与角色关联,用户分配对应角色获取相应权限
- 基于属性访问控制 ABAC:用户属性、资源属性、环境属性等动态判断权限
- 基于资源等访问控制 ReBAC:围绕资源定义权限
-
权限控制层次
- 路由权限:页面
- 试图权限:控制页面上元素的显示隐藏
- 操作权限:可执行操作
- 数据权限:可查看和操作的数据范围
-
权限实现方式
- 前端路由守卫:跳转前进行权限检查
- 组件级权限控制:通过指令或者高阶组件实现
- API拦截请求:在正式查询数据前先查询当前用户的权限是否合规
js数据隐式转换规则
1.尝试调用[Symbol.toPrimitive]方法
2.尝试调用valueOf方法
3.尝试调用toString方法
HTTP2
在http1中,为了性能考虑,经常会引入雪碧图、小图片直接内连、使用多个域名等方案。主要是因为浏览器对同一个域名下的请求数量会有限制(谷歌浏览器好像是6个还是8个,IE好像是差不多13个),当页面中需要请求很多资源的时候,会有一个问题就是队头阻塞,会导致在到达最大请求数之后剩余资源需要等待其他资源请求完成后才能继续发起请求。
在http2中引入了一个技术,叫多路复用,这个技术可以只通过一个tcp连接就可以传输所有的请求数据。多路复用很好的解决了浏览器限制同一个域名下的请求数量的问题,同时也更容易实现全速传输,因为新开一个tcp连接需要慢慢提升传输速度(就像下电影,如果你网速很好,下载速度也不是一下子就能到达顶峰的,比如说最高100m/s,那也是需要从100k提升到1m,5m,10m,100m,当你达到100m之后如果不中断就会保持在这个传输速度,如果中断后再连(新开一个tcp连接)还是会从100k提升到100m,一个道理)。
多路复用
在http2中有两个非常重要的概念,分别是祯(frame)和流(stream)。
祯代表最小的数据单位,每一帧都会标识出自己属于哪个流,流也就是多个祯组成的数据流
多路复用也就是在一个tcp连接中可以存在多个数据流。换句话说就是可以发送多个请求。这样就可以避免http1中的对头阻塞问题,提高传输性能。
二进制传输
http2中所有加强性能的核心点就在于二进制传输。在之前的版本中,数据都是通过文本的方式进行传输。在http2中引入了新的编码机制,所有数据都会被分割,并采用二进制格式编码。
header压缩
在http1中我们使用文本的形式传输header,在header需要携带cookie的情况下,可能每次都需要重复传输几百上千的字节。
在http2中,使用了HPACK压缩格式对传输的header进行编码,减小了header的大小。
服务端push
在http2中,服务端可以在接收到客户端的某个请求后,除了响应对应请求外,还可以主动推送其他资源。
可以想象一下,在用户发送一个请求后,有一些其他的资源一定是用户在后续会需要的,会来发起请求的,这时就可以采取服务端push技术,提前给用户推送这些必要资源,这样可以相对减少一点延迟时间。
prefetch和preload
preload:是一个声明式命令,告诉浏览器 当前页面渲染必定需要某个资源,要求浏览器以高优先级提前获取并缓存,确保资源在需要时可以立即使用,而不阻塞页面渲染。
prefetch:是一个提示性的指令,告诉浏览器 用户后续可能访问的页面或者进行的操作需要某个资源,浏览器会在空闲时间以低优先级的次序预加载,主要是准备优化后续导航或者交互的体验。
什么时候使用webworker
1.大量的CPU密集型任务(e.g.大文件上传)
2.任务可被独立拆分(e.g. 大文件上传中,计算第100个分片和计算第200个分片是互不干扰,相对比较独立)
Object.prototype.toString.call()是输出的哪个属性?
是传入内容的prototype(原型)上的Symbol.toStringTag的值
对于作用域链的理解
作用域链的作用是保证执行环境里面有权访问的变量和函数是有序的。作用域内的变量只能沿着作用域链向上寻找访问,直到windows截止,作用域链向下寻找访问变量是不被允许的。
前端图片优化
3个纬度考虑
1,资源的选择
选择合适的格式以及内容
格式:在当前这个时间点,如果项目 目标/用户 都是使用现代浏览器,那么 webp 绝对是首选。它在压缩率和兼容性方面都取得了非常好的平衡。在同等画质下,它的体积比jpeg这种格式的位图能减少 25% 左右。如果需要追求极致的性价比,极致的压缩比,并且能够接受部分浏览器的兼容性问题,那么还可以选用更加激进的avif格式。当前我们肯定是需要有一个回退方案,可以采用经典的picture标签。我们可以把可能支持的资源全部放到这个标签里面,浏览器会自上而下地进行检查。比如说:如果浏览器支持avif就加载avif格式,如果不支持就再看看是否支持webp,如果都不支持,最后再加载通用的jpeg。这个方案还是算比较优雅的。
内容:对于logo图标这类非写实的,颜色单一的图形,可以果断使用svg。svg是矢量格式,以为它无限缩放且不失真,体积也非常小,我们还可以通过css来直接修改其颜色和大小,所以一般来说都会直接用svg来作为图标来使用。
2,加载的策略
在正确是时间加载正确的内容。核心思想:按需加载,避免浪费。对应的有两个强大的武器:
1)响应式图片:为不同的设备提供最合适的尺寸。在手机上如果我们加载的是一张pc端使用的4k大图,这是对用户流量和永和设备性能非常大的浪费,所以image标签的srcset和sizes属性就是为了解决这个问题的。首先srcset提供了一个图片资源清单,sizes则告诉浏览器在不同视口宽度下图片会占用多大的空间。浏览器会根据这些信息,结合设备,自己计算出加载哪张图最合适。
2)懒加载:延迟加载飞视口区域的图片。我们最简单的方式就是给image标签加上loading=lazy这样的属性,这是浏览器原生支持的一个能力。我们通过一行代码就能搞定,去大大提升我们首屏加载的速度。那么对于那些需要更加精细化控制的场景,比如说自定义的占位符啊添加加载动画啊,我们可以用IntersectionObserver API。它可以高效的去监听一个元素是否进入了视口,从而去触发图片加载。
3,分发的链路
核心思想:让专业的人去做专业的事。当一个应用体积、用户量变得非常庞大,用户可能会遍布全国甚至全球。这个时候优化就不能只停留在前端代码层面。我们应该从架构的层面和角度上 来考虑一个分发效率的问题。这里的核心就是CDN。我们需要将图片部署到CDN,让用户可以从物理距离最近的边缘节点去获取资源,来非常大的降低一个网络延迟。但是我想说的是 现代的CDN远不止是缓存服务器,很多CDN服务商都提供了强大的图片处理服务,这就意味着,你只要往CDN上上传一张最高清的原图,我们在请求图片的时候,我们通过URL参数实现一个裁剪缩放添加水印甚至是自动格式转换的功能。比如说你请求的时候携带一个参数,你请求比如说 宽度400px,那么cdn就会实时返回一个宽度为400px的webp格式的图片。这种方式的巨大优势就在于 它将处理图片的需求复杂性 从你的业务代码中彻底剥离了出去,前端开发载也不需要关系图片的各种版本,只需要请求自己想要的尺寸和格式就行了。这是工程化的一个巨大进步,至少是在图片处理模块是这样的。当然在分发链路里面还有像css雪碧图这样的传统手艺可以选择使用。
层叠解决样式冲突
1,优先级
作者样式表 important
默认样式表 important
作者样式表
默认样式表
2,特殊性(计算出4个数字,e.g.(a,b,c,d))
a == 0 || 1;
if a == 1
行内样式,最终数字(1,0,0,0)
else
也就是 a == 0,则
b为该选择器中ID选择器的数量
c为该选择器中类选择器、伪类选择器、属性选择器的数量
d为该选择器中元素选择器、伪元素选择器的数量
3,源次序
按源代码中书写顺序,后写的覆盖先写的
SSE 和 websocket 的优缺点
SSE的优缺点
优点:
单向通信高效:专为服务器->客户端推送设计,适合实时通知,数据大屏,日志流等场景
基于HTTP协议:无需额外协议支持,可复用现有的HTTP基础设施
实现简单:客户端只需要使用原生的 EventSource API,后端只需要设置ContentType:text-event-stream
内置断线重连机制:浏览器自动处理连接中断后的重连,降低开发复杂度
轻量低开销:相比websocket,协议更轻,资源占用更小
缺点
单向通信:不支持客户端向服务器发送数据,需要额外的HTTP请求来完成交互
文本为主:默认只支持UTF-8文本,二进制数据需编码
IE 不兼容
连接状态不可控:客户端无法手动关闭或重连(由浏览器自动管理)
websocket的优缺点
优点
全双工通信:客户端和服务器可同时双向发送数据,适合聊天、在线游戏、协同编辑等交互场景
低延迟、高性能:基于TCP持久连接,数据传输快,适合低延迟、高频需求
支持二进制与文本:可传输任意类型数据,灵活性高
现代浏览器广泛支持:主流浏览器均支持
缺点
实现复杂:需处理握手,祯解析,连接状态管理等,开发成本较高
无内置重连:断线后需手动实现重连逻辑
协议较重:相比SSE,协议开销较大,部署需额外支持(ws/wss)
跨域配置复杂:需要显式配置CORS或者允许跨域
典型适用场景
选SSE
实时股票行情
新闻推送/系统通知
日志流(例如deepseek的流响应)
选websocket
在线聊天/语音/视频/会议
对人协作编辑(例如腾讯在线表格)
promise.all 和 promise.allsettled 的区别
all: 采用快速失败策略,只要有一个Promise失败了,整个Promise直接进入rejected状态,不再等待其他promise
allSettled: 采用等待全部完成策略,无论成功或着失败,都会等待所有promise全部完成,并返回每个promise的结果
如何选(简单说):
要么都要,要么都不要:all
1小时内,我要见到它的全部信息:allsettled
普通函数/箭头函数/匿名函数的用法和区别
基本定义
普通函数:使用 function 关键字定义,可以是命名函数或者匿名函数
箭头函数:ES6引入的简洁语法,使用 => 定义,本质上是匿名函数的一种
匿名函数:没有名字的函数,常作为值 赋给变量 或者 作为参数 传递或者立即执行
核心区别对比
| 特征 | 普通函数 | 箭头函数 | 匿名函数 |
|---|---|---|---|
| 语法 | function foo(){} | const foo = () => {} | const foo = function (){} |
| 是否可命名 | 可命名 | 不能命名 | 可命名 *1 |
| this绑定 | 动态绑定 | 继承外层作用域 | 动态绑定 |
| 可作为构造函数 | 可用new | 报错 | 可用new |
| arguments对象 | 有 | 无,需用 ...args | 有(普通函数) |
| prototype属性 | 有 | 无 | 有(普通函数) |
| new.target | 支持 | 报错 | 支持(普通函数) |
| 变量提升 | 提升 | 不提升 | 函数表达式不提升 |
*1:const foo = function bar() {} 命名函数表达式,仍属于匿名函数范畴
使用建议
-
优先使用箭头函数
- 简单逻辑(如数组方法 map/filter)
- 需保留外层this上下文(如事件处理器、回调)
- 代码简洁优先
-
使用普通函数
- 需要动态this(如对象方法)
- 作为构造函数
- 需要arguments或者new.target
-
匿名函数适用
- 一次性回调
- 立即执行函数表达式
- 避免污染全局命名空间
什么是闭包?在实际开发中有什么应用场景?
- 数据封装和私有变量
通过闭包模式模拟私有变量,避免全局污染,实现信息隐藏
const counter = (function(){
let privateCount = 0;
return {
increase(){ privateCount++ },
getNum(){ return privateCount }
}
})()
counter.increase();
console.log(counter.getNum()) // 1
- 事件处理与回调函数
闭包确保回调函数能访问定义时的上下文变量
function setupButton(btnId) {
let count = 0;
document.getElementById(btnId).addEventListener('click',() => {
console.log(`点击了${++count}次`);
})
}
- 防抖/节流
通过闭包保存定时器状态,控制高频事件触发频率。
function debounce(fn,delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {fn.apply(this,args),delay});
}
}
- 柯里化
将多参数函数转换为一系列单参数函数,利用闭包保存已传入的参数
const multiply = (x) => {
return (y) => {
return x * y
}
}
const triple = multiply(3);
console.log(triple(2)); // 3 * 2 = 6
注意事项
-
内存泄露风险
若闭包引用了大型对象或不再需要的变量,可能阻止垃圾回收,应手动释放引用
-
性能考量
避免在高频调用函数中创建闭包,以减少内存开销
-
变量作用域选择
优先使用let和const替代var,避免因变量提升导致的闭包陷阱
在AI视频生成场景中前端如何管理复杂的prompt模版与参数(千问)
在AI视频生成场景中,前端管理复杂的Prompt模板与参数,需兼顾结构化表达、参数化复用、一致性控制和高效调试。
一、Prompt模板的核心结构 参考视频生成工具(如Veo 3.1)及Prompt工程最佳实践,推荐采用结构化模板包含以下要素:
- 主题:视频核心内容(如“赛博朋克城市夜景”)
- 场景环境:地点、时间、天气等
- 光线氛围:如“黄金时刻光”“霓虹反射”
- 镜头运动方式:如“缓慢横移”“无人机俯冲”
- 画面风格:如“电影级”“胶片颗粒”“日系动漫”
- 人物/物体细节:关键角色或物体的特征
- 运动细节:如“慢动作浪花”“粒子光效”
- 时长与画质要求:如“4K, 24fps, 8秒”
- 否定提示(可选):避免人脸扭曲、多余肢体等
示例模板:
电影级场景, 日出海岸, 柔和金色光线洒在海面上, 微风吹动海浪, 航拍推进镜头, 慢速景深, 真实光影与反射, 清新宁静氛围, 4K, 胶片颗粒, 真实物理光照, 细节丰富
二、前端管理策略
-
模板化与参数化分离
- 将Prompt拆分为固定结构(模板)和动态变量(参数),通过JSON或配置文件管理。
- 例如:
{ "template": "电影级场景, {scene}, {lighting}, {cameraMovement}, {style}, {quality}", "params": { "scene": "赛博朋克城市夜景", "lighting": "霓虹灯闪烁, 雨夜", "cameraMovement": "镜头缓慢横移" } }
-
使用Prompt框架统一规范
- 采用如RBTRO(角色、背景、任务、要求、输出) 或六要素法(背景、角色、目标、输入、输出、约束),确保完整性。
- 前端可提供表单或配置面板,引导用户填写各字段,自动生成完整Prompt。
-
参数调优与场景预设
- 针对不同生成目标(如“电影”“广告”“动漫”),预设参数组合模板(类似中的温度、Top-p等配置):
- 电影级:
temperature=0.2, top_p=0.8, max_tokens=5000 - 创意实验:
temperature=0.6, top_p=0.9, max_tokens=3000
- 电影级:
- 提供滑块或下拉菜单,允许用户微调关键参数(如镜头速度、风格强度)。
- 针对不同生成目标(如“电影”“广告”“动漫”),预设参数组合模板(类似中的温度、Top-p等配置):
-
版本控制与复用
- 将常用Prompt模板保存为可命名的配置(如“Veo_电影模板_v1”),支持加载、编辑、共享。
- 参考的Python模板化思路,前端可用本地存储(LocalStorage)或云同步实现持久化。
三、推荐工具与资源
- Veo 3.1提示词库:包含中文速查表、镜头语言词典、风格模板等,可直接复用。
- 阿里云百炼Prompt优化工具:自动扩写和优化Prompt,提升相关性。
- 前端框架建议:使用React/Vue构建配置表单,结合状态管理(如Redux)维护模板状态。
💡 提示:在AI视频生成中,Prompt的细节决定画面质量。例如,“慢速推焦”比“镜头移动”更精准,“体积光”比“光线”更专业。优先使用电影术语和物理描述。
vite为什么比webpack快?
-
开发模式差异
- webpack:启动开发服务器前必须全量打包整个应用,形成一个或者多个bundle文件,项目越大,启动越慢
- vite:直接启动HTTP服务器,不预打包源码;仅当浏览器请求某个模块时,才实时编译并返回该模块
-
底层语言优势
- vite使用esbuild预构建依赖,比webpack快
- esbuild利用多线程、内存高效等特性,在依赖处理上大幅提速
-
按需编译机制
- vite只编译当前页面实际用到的模块,避免无用工作;修改文件后,仅重编译该模块及其直接依赖
- webpack即使修改一行代码,也可能触发整个依赖图,重新构建
vite的构建原理
-
开发环境
-
原生ESM支持
浏览器直接通过<script type="module">直接加载源码,vite不打包,仅作为一个轻量的代理服务器
-
按需编译
- 浏览器请求/src/app.vue -> vite拦截请求 -> 实时编译为js -> 返回浏览器
- 支持 .vue、.jsx、.ts 等文件等即时转换
-
依赖预构建
- 首次启动时,用 esbuild 将
node_modules中的 CommonJS/UMD 依赖(如 React、Vue)转为 ESM 格式 - 合并大量小文件,减少 HTTP 请求数
- 缓存结果至
node_modules/.vite/deps,后续启动直接复用
- 首次启动时,用 esbuild 将
-
HMR精准更新
文件修改后,Vite 通过 ESM import chain 定位受影响模块,仅推送该模块更新,浏览器重新请求即可
-
-
生产环境
-
Vite 不使用 Webpack,而是基于 Rollup 进行构建。
-
Rollup 优势:
- Tree-shaking 更彻底:基于 ESM 静态分析,自动移除未使用代码。
- 输出更小:生成更精简的 bundle。
- 代码分割更高效
-
简单请求和预检请求的区别
-
简单请求:
- 请求方法为:GET、POST、HEAD
- 头部字段满足CORS安全规范(简单记:只要不去动浏览器自动组装的头部信息就满足)
- 请求头的Content-Type为:text/plain、multipart/form-data、application/x-www-form-urlencoded
- 必须同时满足以上3条要求
-
预检请求
除开简单请求
Proxy和defineProperty到底好在哪?
defineProperty第一有效率问题,该方法是针对属性的而不是针对对象的,所以需要深度遍历对象的属性,而且如果某个属性对应的值也是个对象的时候,还需要递归,这是第一点。第二点,它没办法观察到当前不存在的属性,比如默认obj={a:1,b:2},如果设置obj.c = 3的时候由于默认没有c属性,该方法就观察不到c属性的set和后续的get,只能再次调用一下该方法,把c属性加入到可观察范围内(也就是vue2的 this.$set的一种情况)。
proxy针对的是整个对象,返回一个代理对象,它不看对象内部有没有哪个属性,就算是当前没有的属性也可以拦截到get和set的操作(当然还有其他操作),而且虽然说内部实现也存在递归问题,但是proxy的递归是只有当属性被读取时才会去做递归,而不是从一开始就把递归做掉(类比一下可以类比成:按需加载,我只有需要才回去做,而不是直接做管你需不需要)。
所以proxy解决的是:第一,不需要去做深度遍历,因为不需要监听属性了,而是监听整个对象;第二,由于监听的是对象,所以就可以监听这个对象的所有操作,包括用户去读写一些不存在的属性。
白屏问题排查
- 常见原因分类
- 资源加载失败: js/css资源加载404,容易被network面板发现
- js执行错误 代码报错导致DOM渲染中断,控制台能看到
- DOM未渲染 JS执行了,但没有dom节点呗添加到页面
- 样式问题 dom存在,但被隐藏了(display:none或者被遮盖)
- 路由问题 匹配不到组件,渲染了空路由
- 无措白屏的排查手段
- DOM节点检查 在elements面板搜索关键节点,如果dom存在则是css问题,如果不存在则是js没执行到位
- 关键路径js分析 页面的核心渲染逻辑是否被异步加载的js阻塞了,用performance面板查看comcontentloaded和load事件的执行情况
- 移动端远程调试 在用户手机页面注入vConsole,直接看到console日志和网络请求
- 防患于未然的监控体系
- 白屏监控SDK 在核心页面埋点,监听dom变化,如果超时还没有关键节点出现就上报问题
- 骨架屏兜底 就算js加载再慢,骨架屏也能让用户知道页面正在加载,而不是一脸懵比地看着白屏,不知道是出问题了还是在运行
- 降级渲染 如果核心js出错,至少展示一个提示,“页面加载失败,请刷新重试”,这至少比让用户盯着白屏发呆好。
git的常见配置
- user.name:【必配】配置用户名。后续提交会使用该用户名
- user.email:【必配】配置邮箱。后续提交会使用该邮箱
- core.editor:配置默认文本编辑器
- core.ignorecase:配置是否忽略文件名的大小写。默认是true,即忽略大小写
- init.defaultbranch:配置初始化后默认的分支名
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
git config --global core.editor "trae"
git config --global core.ignorecase false
git config --global init.defaultbranch "main"