哇哇哇哇。。。一边擦泪,一边誊写,早已忘却的面试题,需要在隆冬忆起。。
vue
vue自定义指令
- 通过自定义指令,我们可以扩展Vue的行为,让它在渲染DOM元素时添加额外的特性和事件,从而更好地完成业务需求
- vue自定义指令分为两种类型:全局指令和局部指令(组件内指令)
- 全局指令会注册到Vue.directive上,可以全局使用,局部指令则只能在组件内使用
下面是一个全局自定义指令的示例:注册一个名为v-focus的全局自定义指令
注册了一个名为v-focus的全局自定义指令,并实现了inserted钩子函数,当该指令所绑定的元素插入到DOM中时,该钩子函数会被调用,从而实现元素的聚焦功能
Vue.directive("focus",{
// 当绑定元素插入到DOM中执行
inserted:function(el){
//聚焦元素
el.focus()
}
})
下面是一个局部自定义指令的示例:在组件内定义一个名为v-highlight的局部自定义指令
export default {
derectives:{
highlight:{
//当绑定元素插入到DOM中时执行
inserted:function(el){
//添加样式类
el.classList.add("highlight")
},
//当绑定元素从DOM中移出时执行
unbind:function(el){
//移除样式类
el.classList.remove("highlight")
}
}
}
}
Vue 中hash路由与history路由的区别
hash模式
在hash模式下,路由路径会带有一个#符号
hash模式的路由通过监听 window.location.hash的变化来进行路由切换。
hash模式的好处是兼容性较好,可以在不支持HTML5 History API的浏览器中正常运行
缺点是URL中带有#符号,不够美观
History模式:
在history模式下,路由路径不带有#符号
history模式利用HTML5 HitoryAPI中的pushState和replaceState方法来实现路由切换
history模式的好处是URL更加美观,没有#符号
缺点是兼容性较差,需要在服务器端进行配置,以防止在刷新页面时出现404错误
自定义指令在权限控制方面的应用
- 可以根据用户的角色信息来控制某些按钮或者表格中的行列是否可见,可编辑,可删除,
这个时候就可以通过自定义指令来实现这样的权限控制. - 定义一个名为v-permission的全局自定义指令,并实现了bind钩子函数,在该函数中
通过当前用户的角色信息,判断该用户是否具有该元素的权限,如果没有,则将该元素隐藏
下面是一个示例代码:
//定义一个名为v-permission的全局自定义指令
Vue.directive("permission",{
//bind 钩子函数只在指令第一次绑定到元素时执行一次
//如果需要对指令的绑定值进行响应式的操作,应该在update钩子函数中进行
bind:function(el,binding,vnode){
//获取当前登录用户的角色信息
const currentUser = getUserInfoFromLocalStorage().role;
//获取绑定的值
const {value} = binding
//判断当前用户是否有该按钮的权限
if(value&&value.length&&!value.includes(currentUser)){
el.style.display = "none"; //隐藏该元素
}
}
})
<button v-permission="['admin','superAdmin']">Delete</button>
Vue的动态路由
Vue中的动态路由是指在路由中使用参数来匹配路径的一种方式,通过动态路由,我们可以轻松实现页面参数传递和多个类似页面的复用
{
path:'/user/:id',
name:'user',
component:User
}
:id表示该路由是一个动态路由,所以其被称为参数,它的值会被传递给User组件进行处理
Vue的key的作用
key是用来唯一标识一个节点的属性。当Vue渲染Dom时,它会根据节点的key来判断是否需要重新渲染
当Vue发现节点的key发生变化时。它会将该节点从DOM树中移出,然后重新创建一个新的节点插入到合适的位置,这样可以减少DOM操作次数,提高渲染性能。
Router路由守卫
Router中的一项重要功能,它允许开发者在导航到某个路由或离开当前路由时执行一些控制和验证逻辑。Vue Router提供了全局的、路由级别的和组件级别的三种不同类型的路由守卫,包括:
- 全局前置守卫beforeEach用于验证用户是否登录等全局控制
- 全局解析守卫beforeResolve用于在全局前置守卫之后在组件渲染之前被调用
- 全局后置钩子afterEach用于在路由完成后进行清理
- 路由独享的守卫beforeEnter用于在特定路由进入之前进行验证
组件内部的守卫beforeRouteEnter、beforeRouteUpdate和beforeRouteLeave用于处理页面内部控制逻辑
javascript
Event Loop
事件循环(Event Loop)是一种用于处理异步任务的机制,它是js运行时的一部分,用于管理和调度任务的执行顺序。
在js中,任务可以分为两种类型:
1.同步任务:按照代码的顺序依次执行,直到执行完成
2.异步任务:不会立即执行,而是在将来的某个时间执行。异步任务通常涉及网络请求、定时器、事件监听等。
事件循环的工作原理如下:
1 执行同步任务,直到遇到第一个异步任务。
2 将异步任务放入相应的任务队列(如宏任务队列,微任务队列)中。
3 继续执行后续的同步任务,直到执行栈为空。
4 检查微任务队列,如果有任务则按顺序执行所有的微任务。
5 执行宏任务队列中的一个人物
6.回到第三步,重复以上步骤
在每个事件循环中,会先执行所有的微任务,然后执行一个宏任务。这样的机制保证了异步任务的执行顺序,并且能够及时响应用户的交互。
常见的宏任务包括setTimeout, setInterval,网络请求等,而微任务包括Promise、MutationObserver等。
理解实际那循环对于编写高效的异步代码非常重要,它能够帮助我们合理地处理任务并避免阻塞主线程。
作用域链:
- 在js中每个函数都有自己的作用域,
- 当在一个函数内部引用一个变量,js会按照代码中出现的顺序,从当前作用域开始依次向上查找
- 直到找到第一个包含这个变量的作用域为止,这个过程被称为作用域链的查找
- 如果整个作用域链都没有找到,就会报错抛出ReferenceError异常
function outer(){
const a = 10
function inner(){
console.log(a);
console.log(b);//Uncaught: ReferenceError:b is not defined
}
inner()
}
outer()
js在转时间时时分秒不满10补0的几种方式
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
1 使用ES6模版字符串
padStart指定字符串的长度,如果不满,则在前面补上自己定义的字符串
`${h.toString().padStart(2,'0')}:${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`
2 使用三元运算符
(h<10?"0"+h:h)+ ":" + (m<10?"0"+m:m)+":"+(s<10?"0"+s:s)
3 使用Array.map和Array.join方法
const timeArr = [h,m,s].map(value=>{
return value <10?"0"+value:value
})
timeArr.join(":")
当我们在浏览器地址栏中输入一个网址时,发生了什么
1.DNS解析: 浏览器会检查自己有无缓存,如果有该域名对应的IP地址就直接使用,否则则向DNS发起请求
2.发起Http请求,浏览器会根据URL的协议头向服务器发起相应的网络请求,同时浏览器也会发送一些请求头和请求参数
3.建立TCP连接:在HTTP请求建立之前,浏览器需要和服务器建立TCP连接,TCP是一种面向连接的、可靠的、基于字节流的传输协议,通过三次握手来确保数据能够准确
可靠的发送到服务端,而不会出现数据丢失或乱序的情况
4.发送HTTP请求,在建立了TCP连接之后,浏览器就能够向服务器发送HTTP请求报文。HTTP请求报文包括请求头,请求行,和请求体
5.接受服务器相响应报文:当服务器收到请求报文后,会解析请求,然后返回相应的响应报文给客户端。HTTP响应报文包括状态行、响应头和响应体三个部分,其中状态行包含了该请求的结果状态码。
6.解析渲染页面:当浏览器接收到服务器返回的响应报文之后,会根据相应报文中的内容(如HTML,CSS、javascript等资源),解析出对应的DOM树、CSS规则树和javascript代码
并且根据它们构建出一个渲染树。最后将这些内容交给浏览器的渲染引擎进行渲染,生成我们最终看到的页面。
7.断开TCP连接,当浏览器接收到服务器返回的响应报文后,会关闭该连接,释放资源。如果浏览器需要请求更多的资源,则需要重新建立新的TCP连接
如何减少重排和重绘
重排和重绘是浏览器渲染过程中的两个关键步骤
重排是指浏览器计算元素的位置和大小属性,并重新布局页面的过程。
重绘是指根据元素的样式属性,重新绘制元素的外观
重排和重绘是耗费性能的操作,因此减少重排和重绘可以提高页面的性能和响应速度
1. 批量修改样式:避免对元素的样式属性进行频繁的单独修改,而是尽量将多个样式的修改合并为一个操作。可以使用Css类名的方式一次性地修改多个样式属性。
2. 使用文档片段:如果需要通过js动态地插入一系列元素,可以先将它们添加到文档片段(Document Fragment)中,然后再一次性的讲文档片段添加到文档中,这样可以减少重排的次数。
3. 避免频繁读取布局信息,当获取元素的位置、大小等布局信息时,尽量避免在每次操作中都去读取这些信息,而是将其缓存起来,以减少重排的次数
4. 使用CSS3动画和过渡:CSS3动画和过渡是基于浏览器的硬件加速,可以减少重绘和重排的开销。尽量使用CSS3动画和过渡来实现动画效果,而不是使用js来操作样式属性。
5. 使用requestAnimationFrame: 使用requestAnimationFrame来执行动画可以更好的与浏览器的重绘机制结合,减少不必要的重绘和重排操作
6. 避免频繁的DOM操作:DOM操作会导致重排和重绘,尽量避免频繁的对DOM进行增删改操作,可以先将需要操作的DOM元素从文档中移除,进行批量操作后再重新插入。
7. 使用css布局工具:使用Css的flexBox和Grid等布局工具可以更好地控制页面布局,减少重排的次数。
import 和require是js两种不同的模块化规范
- 用法不同
import时ES6中新增的模块化语法,用于在代码中引入其他ES6模块的导出对象。它是一个顶级声明
只能出现在js代码最外层或其他类似的顶级位置,不能在其他代码块中使用 import {debounce} from "lodash"
require 是CommonJS规范中的模块化语法,用于在代码中引入CommonJS模块的导出对象,它可以在任何位置使用,包括函数内部或代码内部 const debounce = require("lodash/debounce") - 加载时机不同
import 在代码编辑时就会被处理,因此在代码执行前就已经加载了相应的模块。这使得import可以在代码运行之前进行静态分析,从而在构建时进行优化
require 是在运行时才会被加载,这意味着当代码中使用require时,它会在代码被执行的到的时候加载模块,并将其导出对象作为结果返回 语法不同
import的语法相对简洁,并且可以进行模块的命名空间分离和解构。同时,它也支持异步加载模块//引入lodash模块中的debounce方法并重命名为myDebounce //解构 只导出需要的内容,减少导出对象的大小 import {debounce as myDebounce} from "lodash" import * as myModule from "./model.js" //命名空间分离 通过一个对象承载模块的所有导出内容。从而实现命名空间分离 //使用异步函数动态引入模块 const someAsyncModule = await import ('./path/to/module')
require的语法相对复杂,特别是在需要进行多层路径嵌套时更为明显,它不能进行命名空间分离,也不支持ES6中的解构语法。同时也不能异步加载模块,需要额外的库或者手动编写异步加载逻辑
总之,如果需要使用Es6中的新特性或者异步加载模块,使用import, 如果需要兼容node或者CommonJS环境,可以使用require
WebSocket的使用
WebSocket是一种在Web浏览器和服务器之间进行全双工通信的协议。它提供了更强大的实时数据传输能力,相对于传统的HTTP请求-响应模式,WebSocket允许服务器主动向客户端推送数据,实现了真正的双向通信。
//创建WebSocket连接 得到一个Websocket对象,url是websocket服务器的地址
const socket = new WebSocket("ws://example.com/socket")
监听事件: WebSocket对象支持多个事件,如`open`,'message',"error"和'close'。通过给WebSocket对象添加对应的事件监听器函数,
可以处理连接打开、接收到消息、出现错误和连接关闭等情况
// 监听连接打开事件
socket.addEventListener("open",event=>{
console.log("WebSocket 连接已打开");
})
// 监听接受到消息事件
socket.addEventListener("message",event=>{
console.log("收到消息",event.data);
})
// 监听连接错误事件
socket.addEventListener("error",error=>{
console.log("WebSocket 错误:",error);
})
//监听连接关闭事件
socket.addEventListener("close",event=>{
console.log("Websocket 连接已关闭");
})
//发送消息 send(data)方法可以向服务器发送消息。服务器接收到消息后,可以通过‘message’ 事件监听器处理。
//客户端可以使用event.data获取服务器发送的消息内容
socket.send("Hello,Websocket")
//关闭连接:当不再需要连接时,可以使用WebSocket对象的close方法来关闭连接
socket.close()
WebSocket连接需要服务器支持,服务器需要实现相应的WebSocket协议来处理连接和消息的传输。
在实际使用中,可以使用诸如Node.js的ws模块或者Websocket框架来实现服务器端的功能
微任务和宏任务以及使用场景
微任务和宏任务是用来管理js异步任务执行顺序的机制。它们决定了任务在事件循环中的执行顺序。
微任务是由js引擎提供的任务队列,它的执行优先级高于宏任务,常见的微任务有Promise的回调函数,MutationObserver和progress.nextTick
宏任务是由浏览器提供的任务队列,它的执行优先级较低,常见的宏任务有定时器setTimeout,setinterval, DOM事件回调和Ajax请求等
使用场景:
微任务的使用场景:
1 需要在当前事件循环的末尾执行的任务,可以使用微任务,例如Promise的回调函数。
2 需要立即执行的任务,可以使用微任务,例如MutationObserver监听DOM变化并立即做出相应
宏任务的使用场景:
1 需要延迟执行的任务,可以使用宏任务,例如定时器的回调函数。
2 需要在事件循环中的下一个循环中执行的任务,可以使用宏任务,例如DOM时间回调和Ajax请求
在一个事件循环中,当所有的宏任务执行完毕后,会先执行所有的微任务,然后再进行下一个宏任务的执行,这样可以保证微任务的优先级高于宏任务,确保及时响应和更新
总结起来,微任务和宏任务是用来管理异步任务执行顺序的机制,微任务的执行优先级高于宏任务,适合处理需要立即执行或在当前循环末尾执行的任务。宏任务适合处理需要延迟执行或在下一个循环中执行的任务
ES6中一些常用的特性和语法
- let 和 const 关键字
- 箭头函数
- 解构赋值
- 扩展运算符
- 类和继承
- 模版字符串
- promise 和async/await
- 模块化
Symbol、Map、Set、Proxy、Reflect
React
Vue和React的区别
分五个方面来说:
- 模版语法:
Vue使用了HTML的模版语法来编写组件模版,比较易于理解和学习,使得开发者可以快速的编写出页面。这样做的优点在于,将HTML和JavaScript代码分离开来,
有利于代码的维护性和阅读性。Vue的模版语法也提供了一些强大的功能,如条件渲染,循环渲染,事件处理等.
React则推崇:一切都是javascript,它采用了jsx语法,即Javascript和Xml的混合语法,使用jsx可以轻松地创建复杂的UI,并提高了应用程序的性能,
不过使用jsx的同事,你需要学习更多的语法和编程范式 - 数据绑定
Vue提供了双向数据绑定的功能,可以实现视图和数据的自动同步,极大方便了数据管理和操作。双向绑定包括两个部分:数据模型和视图模型。在Vue中,数据模型即组件实例中的数据;视图模型则负责数据模型中的数据绑定到视图中去。
React的数据流是则是单向的,采用了组件间props和State的传递和管理数据,通过props向组件传递属性值,并监听这个属性的更改事件来更新UI
从而实现数据的单向流动;而state则是保存组件内部状态的地方,是可变的数据,推荐在合适的情况下尽量使用不可变数据。 - 组件分类
Vue将组件分为有状态组件和无状态组件 functional两种,有状态组件包含了应用程序中的逻辑和数据,可以实现更复杂的操作和交互;无状态组件只提供了一个对应的UI界面,没有数据和逻辑的处理,通常用于小组件或纯展示类组件。Vue的组件具有生命周期函数,可以在组件的不同阶段执行不同的处理逻辑。
React没有严格的组件分类,但通常会将组件分为函数式组件和Class组件两种。函数式组件是一种纯javascript函数,接受props对象作为参数,返回一个React元素,不支持状态和生命周期函数;class组件则是使用面向对象编程的方式来构建组件,支持状态和生命周期函数。所以React组件也具有生命周期,可以在组件的不同阶段执行相关操作。 - 生命周期
Vue和React都拥有各自的生命周期函数,并分别在不同的组件阶段调用这些函数。Vue的生命周期函数包括了created、mounted、updated和destroyed等每个函数都有特定的用途和功能
React的生命周期函数包含了componentWillMount、componentDidMount、shouldComponentUpdate、componentWillUnmount等也都有各自的特点和用途。
生命周期函数可以用于解决组件挂载、更新、销毁等过程的一系列问题和逻辑 - 渲染效率
Vue采用了虚拟DOM和异步渲染等技术来提高程序的性能,虚拟DOM是将真实的DOM抽象成js对象,可以快速的进行对比和计算,从而减少DOM操作带来的性能损耗;
异步渲染则是让浏览器在空闲时间渲染组件,从而提高渲染效率。
React则使用了一种名为reconciliation的算法在不重新渲染整个组件数的情况下更新UI,这也使得React具有较高的渲染效率。
在组件更新时,React会通过对比虚拟DOM的变化来更新UI,从而避免大量的DOM操作
Vue和React都是非常优秀的前端框架,采用不同的实现方式,适用于不同的开发场景。Vue更加注重开发体验和易用性,适合快速开发小型和中型的应用;React更加注重应用程序的可维护性和性能,适合大型应用或需要更好的可扩展性的应用。选择哪个框架取决于项目的需求和团队的偏好
Webpack
webpack的摇树
摇树(tree Shaking)是指在打包过程中,通过静态分析的方式,去掉没有使用的代码,从而减小最终打包后的文件体积
在Webpack中摇树是通过ES6模块化语法和静态分析工具(如UglifyJS)来实现的。
当Webpack打包时,它会分析模块之间的依赖关系,并且检查哪些代码被实际使用了,哪些去除掉没有被使用的代码
摇树的原理是基于ES6模块化的静态特性,它可以在编译时进行静态分析,因为ES6模块化的导入和导出是静态的,而CommonJS模块化的导入和导出是动态的
要实现摇树,需要满足以下条件:
1 使用ES6模块化语法进行导入和导出
2 代码中的导入必须是静态的,不能使用动态导入
3 代码中的导出必须是静态的,不能使用动态导出
当满足这些条件时,Webpack在打包的过程中会自动进行摇树优化,去掉没有使用的代码,从而减小打包后的文件体积
需要注意的是,摇树只能去掉没有使用的代码,而不能去除没有被导入但被使用的代码。
npm run dev时,webpack做了什么
当你运行npm run dev命令时,webpack会执行一系列的操作来构建和打包你的项目。
1. 根据配置文件(通常是webpack.config.js),webpack会读取配置中的入口文件(entry)和出口文件(output)的路径信息。
2. 根据入口文件的路径,webpack会分析项目的依赖关系,找到所有需要打包的模块。
3. webpack会根据配置中的加载器(loader)对模块进行处理。加载器可以将非js文件(如css,图片等)转换为js模块,或者对js模块进行预处理(如使用Babel进行ES6转换)
4. webpack会根据配置中的插件(plugin)对模块进一步的处理。插件可以用于优化打包结果、拓展webpack的功能等。
5. webpack会根据配置中的出口文件路径和文件名,将打包后的模块输出到指定的目录中
6. 在开发模式下,webpack会启动一个开发服务器( dev server),并监听文件的变化,当文件发生变化时,webpack会自动重新构建并刷新浏览器
7 webpack 还会生成一个包含构建信息的统计文件,可以用于分析打包结果、性能优化等
总的就是:webpack在运行npm run dev 时,会根据配置文件对项目进行构建和打包,并提供开发服务器以及自动编译的功能,方便开发人员进行实时调试和开发
webpack缓存
Webpack提供了缓存机制,可以通过缓存来提高投建性能。webpack的缓存机制有两个方面:
Loader缓存:
在webpack构建过程中,Loader可以使用缓存来提高性能
Loader缓存可以避免对于同一个文件的重复处理,从而加快构建速度。
通过设置 cache:true 来开启Loader缓存,例如:module:{ rules:[{ test:/\.js$/, use:'babel-loader', options:{ cacheDirectory:true } }] }
- Module缓存:
在webpack构建过程中,Webpack会根据模块的内容生成一个唯一的标识符(hash).
如果模块内的内容没有发生变化,Webpack会复用之前的构建结果,避免重新构建该模块,从而提高构建速度。
Module缓存是基于文件的,只有在文件内容发生变化时才会重新构建
Module缓存是默认开启的,可以通过设置cache:true 来显示启用缓存。
通过使用Loader缓存和Module缓存,Webpack可以避免重复处理和构建不变的模块,从而提高构建的性能和速度。
需要注意的是,缓存机制只有在构建过程找那个文件内容没有发生变化时才会生效,如果文件内容发生变化时,Webpack会重新构建整个模块和依赖树。
TypeScript
ts中如何新增一个类型
在ts中有几种方式可以创建新类型:
1. 类型别名(Type Alias):
使用type 关键字创建一个类型别名,可以给现有类型起一个新的名字
例如: type myType = string|Number;
2.接口(interface):
使用interface 关键字创建一个接口,用于定义一个对象的结构。
例如interface MyInterface{name:string;age:number;}
3.类(Class):
使用class 关键字创建一个类,用于定义一个对象的结构和行为。
例如: class MyClass{name:string;age:number}
4.枚举(Enum):
使用enum关键字创建一个枚举类型,用于定义一组具名的常量值。
例如:enum MyEnum{A,B,C}
any与unkown的区别
在Typescript中,any和unkown都是用来表示不确定类型的,都是表示任意类型,但是它们之间有一些区别:
1 可赋值性:
any 类型可以赋值给任何类型,也可以从任何类型中获取值
unknown类型只能被赋值给unkown和any类型,不能直接从中获取值。
2 类型检查和类型判断:
any类型的变量不会进行类型检查,编译器不会对其进行类型推断或类型检查。
unknow类型的变量在编译器中会进行类型检查,使用之前必须进行类型检查或类型断言
3 方法和属性的调用:
any类型的变量可以调用任何方法和属性,而不会报错。
unknown类型的变量不能调用任何方法或属性,除非先进行类型断言或类型检查
4 类型安全性:
使用any类型会丧失类型安全性,因为它可以接受任何类型的值。
使用unkonw类型可以提供更好的类型安全性,因为在使用之前必须进行类型检查。
因此,unkown类型相比于any类型提供了更好的类型检查和类型推断,以及更好的类型安全性,因此,尽量使用unkown类型来表示不确定类型,只有在必要的情况下才使用any类型。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。