一、vue的双向绑定原理是什么?里面的关键点在哪里?
Vue的双向绑定原理是通过数据劫持结合发布-订阅模式来实现的。 Vue在初始化数据时,会对数据进行递归遍历,把每一个属性都转换成getter和setter,通过Object.defineProperty()方法实现数据劫持。当数据变化时,会触发setter,setter会通知所有订阅者,订阅者就会更新页面视图,从而实现了数据双向绑定。
关键点总结:
- 数据劫持:通过Object.defineProperty()方法重写属性的getter和setter函数,拦截数据的读取和修改操作。
- 依赖追踪:维护一个依赖追踪系统,用于跟踪属性与其依赖的关系。
- 发布-订阅模式:当属性变化时,通知依赖该属性的订阅者进行更新。
这些机制共同作用,实现了Vue的双向数据绑定功能。
二、实现水平垂直居中的方式?
1.绝对定位和负边距 / 绝对定位和移动负距离:利用绝对定位,将元素的 top 和 left 属性都设为 50%,再利用 margin 边距,将元素回拉它本身高宽的一半,实现垂直居中。
2. 绝对定位和margin的auto:position: absolute; left: 0; top: 0; bottom: 0; right: 0; margin: auto;
总结:默认设置 margin: auto 可以让块级元素水平居中,auto 默认只会计算左右边距。而上下如果设置为 auto 时默认是取 0,也就是说,margin:auto 和margin:0 auto 在一般情况下没有区别,不能实现垂直居中。
但是有了绝对定位后,margin-top 和 margin-bottom 的值就不是 0 了,也是通过计算所得。所以能实现垂直居中。
3.flex布局:display: flex; justify-content: center; align-items: center;
4.display: table-cell; 和 vertical-align: middle; 可以垂直居中,text-align: center; 进行水平居中。
5.display:grid; place-items: center;
三、常用伪元素有哪些?
常用的伪元素包括::before、::after、::first-line、::first-letter和::selection。其中 ::before 和 ::after 伪元素分别用于在元素的内容之前和之后插入内容。这两个伪元素是CSS中最常用的,它们可以用来装饰元素的前后,比如添加引号、装饰性图标等
四、移动端如何适配不同屏幕尺寸?
1.使用响应式布局:使用css媒体查询和弹性布局来根据屏幕尺寸调整页面布局和元素大小,设置百分比、em和rem单位来实现元素的相对大小。
2.vw单位适配
100vw相当于整个视口宽度,1vw相当于视口宽度1%,将px转换为vw即可完成适配,其实rem就是模仿vw方案,是一中过渡
px与vw的单位转换:
3.使用flex的弹性布局
4.rem单位+动态html的font-size:
在开发过程中,我们只需要思考两个问题:
(1) 针对不同屏幕,设置html不同的font-size
a. 媒体查询设置html的font-szie,但是动态改变尺寸,不会实时更新,只是一个区间
b. js代码,通过监听屏幕尺寸的变化来动态修改html元素的font-size,并设置到html上
c. 使用lib-flexible库,是淘宝团队出品的一个移动端自适应解决方案,通过动态计算viewport设置font-size实现不同屏幕宽度下的ui自适应缩放。
(2) 将原来设置的尺寸,转化成rem单位:
五、本地存储有哪些?他们三者有什么区别?
本地存储主要有三种:cookie、sessionStorage和localStorage。它们之间的主要区别体现在存储大小、有效期、数据与服务器之间的交互方式以及存储位 置上。
-
存储大小:
- Cookie的数据大小不能超过4KB。
- SessionStorage和LocalStorage的存储空间可以达到5MB或者更大。
- 有效期:
- LocalStorage可以长期存储数据,浏览器关闭后数据不丢失,除非手动删除。
- SessionStorage存储的数据在浏览器关闭时自动删除。
- Cookie可以设置过期时间,在过期时间之前,即使浏览器关闭,Cookie仍然有效。
- 数据与服务器之间的交互方式:
- Cookie的数据会自动传递到服务器,服务器端也可以写入Cookie到客户端。
- SessionStorage和LocalStorage的数据不会自动发送给服务器,仅在本地保存。
- 存储位置:
- LocalStorage和SessionStorage把数据存储到本地。
- Cookie把数据存储在服务器端。
综上所述,选择使用哪种本地存储方式取决于具体的应用场景和需求。例如,如果需要长期存储大量数据且不需要实时同步到服务器,LocalStorage是较好的选择;而如果需要跟踪用户会话状态且数据不需要长期保存,SessionStorage则更为合适;Cookie则适 用于需要跨设备或会话间保持用户状态的情况。
六、JS的数据类型?如何判断js的数据类型
数据类型有: Number、String、Boolean、Object、Null、undefined、symbol。可以使用typeof、instanceof和Object.prototype.toString.call()等方法来判断数据类型。
七、说一下ES6的新特效有哪些?
箭头函数:提供了一种简洁的函数定位语法,没有自己的this绑定,其this值取决于上下文,还简化了匿名函数的编写(()=>{})
模板字符串:允许在字符串中嵌入表达式,并支持多行文本,无需用拼接或转义字符(`${}`)
解构赋值:允许从数组或对象中快速提取值并将其分配给多个变量,简化了数据交换和属性提取。(const [a,b,c] = [1,2,3,4)
let与const声明变量:let和const是新的变量声明关键字,分别用于块级作用域变量和常量声明。
let变量在指定作用域内有效,避免了var可能导致的变量提升和作用域混乱问题。
const声明的变量一旦赋值后就不能在修改,常用于不可变的引用或基本类型值。(注意对象是可以改变属性值的,里面是通过改变指针指向)
类和继承:class
模块化:modules,通过import和export关键字来导入导出模块成员,实现代码的复用和组织
promise:Promise 构造函数是 JavaScript 中用于创建 Promise 对象的内置构造函数。
Promise 构造函数接受一个函数作为参数,该函数是同步的并且会被立即执行,所以我们称之为起始函数。起始函数包含两个参数 resolve 和 reject,分别表示 Promise 成功和失败的状态。
起始函数执行成功时,它应该调用 resolve 函数并传递成功的结果。当起始函数执行失败时,它应该调用 reject 函数并传递失败的原因。
Promise 构造函数返回一个 Promise 对象,该对象具有以下几个方法:
-
- then:用于处理 Promise 成功状态的回调函数。
- catch:用于处理 Promise 失败状态的回调函数。
- finally:无论 Promise 是成功还是失败,都会执行的回调函数。
八、数组去重有哪些方法?
利用构造函数Set的去重功能、或者使用数组的循环遍历和对象属性去去重。
九、说一下深浅拷贝,如何自己实现一个深拷贝?
深浅拷贝是指Object和Array这样的复杂类型,在复制对象时,是否对其内部的子对象进行递归复制。
a和b指向同一块内存,但修改其中任意一个值,另外一个调用的值,不会受到影响是深拷贝,否则是浅拷贝,浅拷贝通过使用Object.assign(),深拷贝使用JSON.parse(JSON.stringify())给我们一个基本解决方法,但是函数不能被正确处理,也可以自己写递归复制等方法。
十、组件通讯方式有哪些?
1.父子传参:子组件通过props接受数据,$emit传递事件通知和数据
2.父组件直接通过$refs调用子组件里面的数据和方法
3.provide和inject配合完成祖孙传值
4.全局bus:发送的一方使用$bus.$emit('funName',传递的数据),接收方使用$bus.$on('funName',回调函数(接收的数据)=>{})
十一、watch和computed区别?
watch主要是监听数据变化,可以监听数据的来源三部分:props/data/computed。还可监听路由变化。
computed主要是处理逻辑运算,它有缓存机制,只有改变才执行,有返回值。
十二、vue的导航守卫有哪一些?
全局前置守卫、全局后置钩子、路由独享守卫、组内的守卫等
Vue的导航守卫包括全局前置守卫、全局解析守卫、路由独享的守卫和组件内的守卫。
- 全局前置守卫:通过
router.beforeEach
注册,用于在路由跳转前进行判断,决定是否继续导航或取消导航。 - 全局解析守卫:通过
beforeresolve
注册,这个守卫在所有的路由守卫中最早执行,通常用于一些需要先于路由变化之前完成的操作,如身份验证等。 - 路由独享的守卫:通过
beforeEnter
在单个路由上定义,用于控制该路由的访问权限。 - 组件内的守卫:包括
beforeRouteEnter
、beforeRouteUpdate
和beforeRouteLeave
,这些守卫在路由组件内部定义,用于控制组件进入、更新和离开时的行为。
十三、什么是闭包?闭包的作用?
闭包一句话概括就是跨作用域访问变量,例如:函数内部访问函数外部,当上级函数执行完之后,作用域还在被内部函数使用,所以上级作用域内存变量不会被销毁,这种就叫闭包。
闭包的存在,解决了函数内部和函数外部的联通问题,他实现了缓存上级作用域,打破了函数作用域的束缚,比如平时使用的一个ajax请求的成功回调,普通事件的回调函数(callBack && callBack()),一个setTimeout的延时回调,或者一个函数内部返回另外一个匿名函数。这些都是闭包。
闭包缺点:由于打破了函数作用域的束缚,导致里面的数据无法被清除,当数据过大时会导致数据溢出,内存泄漏。
十四、哪些遍历方法会改变原数组?
forEach、map、filter、reduce等遍历不会改变原数组;push、pop、shift、unshift、splice等方法会改变原数据。
十五、如何改变改变函数的this指向,它们有什么区别?
call和apply在改变this时,还会立即调用一次当前函数,它们传参也不一样一个参数列表,一个数组,函数.call(新的this,参数1,参数2、参数3...),函数.apply(新的this,[参数1,参数2、参数3...])
bind只会改变this,不会立即执行函数,函数.bind(新的this,参数1,参数2),bind
方法会创建一个新的函数,当这个新的函数被调用时,它的this
关键字会被设置为bind
方法的第一个参数,无论这个新的函数是如何被调用的。
十六、父子组件声明周期大概有哪些?
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。
十七、keep-alive是什么?有哪几个生命周期阶段?
keep-alive是vue中的一个组件,用于缓存组件实例,提高组件的渲染效率,其生命周期包含,activated(组件激活)和deactivated(组件停用)等阶段。路由中可以使用keepAlive来判断是否需要缓存组件 meta: {keepAlive: true}。
十八、JS原型和原型链的理解?
原型对象:每个对象(函数也是对象)都有一个特殊的属性叫做原型(prototype),它指向另外一个对象,这个对象被称为原型对象,原型对象是用来共享属性和方法的。原型对象有一个constructor属性指向构造函数本身,原型对象是一个普通对象,它包含属性和方法,原型对象的属性和方法会被继承到所有通过原型链与它相连的对象。
原型链:每个对象都有一个_proto_(隐式原型)属性,指向它的原型对象,而指向的对象又存在属性_Proto_指向另外一个对象,当我们访问对象的时候,就会先在对象定义的属性中查询,没有找到就会沿着_proto_一路向上查找,最终形成一个链式结构,这个链就叫原型链。Object.prototype作为原型链的顶端,它不在有自己的原型,所以他的_proto_指向null,表示原型链的终点,所有的对象,最终都继承自Object.prototype,这也是js原型链的顶点。
十九、new操作在创建实例的时候经历了哪几个阶段?
创建一个空对象---->设置原型链---->让实例化对象中的this指向该对象,执行函数体---->判断实例化对象的返回值类型
二十、promise和async/await的区别?
Promise和async/await的主要区别在于它们的语法风格、错误处理方式、代码可读性、并发处理能力以及适用场景。
- 语法风格:
- Promise使用
then()
和catch()
方法来处理异步操作的结果和错误,而async/await使用async
和await
关键字以更直观的方式编写异步代码,使得异步代码看起来更像同步代码,逻辑更清晰,可读性更好。
- Promise使用
- 错误处理:
- Promise需要通过链式调用的
catch()
方法来捕获错误,而async/await可以使用try-catch
语句来捕获错误,这使得异常处理更加方便,代码更加清晰明了。
- Promise需要通过链式调用的
- 并发处理能力:
- Promise可以并行执行多个异步操作,例如使用
Promise.all()
方法。 - async/await默认是串行执行异步操作,不适合并发执行多个异步任务。
- Promise可以并行执行多个异步操作,例如使用
- 代码可读性:
- Promise的链式调用语法有时会使代码阅读和维护变得困难。(可解决回调地狱)
- async/await让异步代码看起来更像同步代码,逻辑更清晰,可读性更好。
- 适用场景:
- Promise更适用于一连串的异步请求。
- async/await更适合串行的异步请求之间的依赖关系比较复杂或需要控制执行顺序的情况。
综上所述,Promise和async/await各有优势,适用于不同的场景。选择使用哪一种取决于具体的业务逻辑和需求。async/await是es7的产物,是基于promise的语法糖。
二十一、ES6数据结构中的Set和Map有什么区别?
Set是一种存储唯一值的集合,它的成员值只能出现一次,不重复,可以存储任何类型的值,包含原始值和引用值。
而Map是键值对的集合,它将键与值相关联,键是唯一的,而每个键只能出现一次,但值可以重复,可以使用各种类型的值作为键和值。
Set使用场景主要是判断元素是否存在(has),去重等,Map适用于需要将值与特定键关联的场景,例如,构建字典,缓存等,可以通过get方法取值。
二十二、url到浏览器的一个过程有哪些步骤?
1、URL解析:浏览器首先会解析输入的URL,将其分解为各个组成部分,包括协议(如HTTP或HTTPS)、域名、端口号(如果未指定,则使用默认端口)、路径、查询参数和片段标识符
2、DNS解析:如果URL中包含域名,浏览器会进行DNS解析,将域名转换为相应的IP地址。这一过程涉及查询浏览器的缓存、系统的DNS缓存以及向本地DNS服务器发送请求,如果这些步骤都无法找到对应的IP地址,则会向根域名服务器发送请求,最终找到权威域名服务器来获取IP地址
3、建立TCP连接:一旦获得服务器的IP地址,浏览器会尝试与该IP地址建立TCP连接。这个过程通常涉及“三次握手”,即浏览器向服务器发送一个连接请求,服务器确认请求并回复,最后浏览器再次确认服务器的回复,从而完成连接的建立
4、发送HTTP请求:TCP连接建立后,浏览器会向服务器发送HTTP请求。这个请求包括请求行(方法、路径、版本)、请求头以及可能的请求体(如在POST请求中)
5、服务器处理请求并返回HTTP报文:服务器接收到请求后进行处理,并将结果以HTTP报文的形式发送回浏览器
6、浏览器解析并渲染页面:浏览器接收到HTTP报文后,会解析报文内容并渲染页面,最终呈现给用户
7、HTTP连接断开:页面加载完成后,HTTP连接断开
二十三、什么是防抖与节流? 应用场景举例
防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间,应用场景:提交事件,用户注册时候的手机号验证、邮箱验证。
节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行效率。应用场景:window对象的resize/scroll事件,拖拽时候的mousemove,射击游戏重负的mousedown,keydown等,文字输入,自动完成的keyup事件。
二十四、JS是单线程,为啥会有异步任务?
JavaScript 是单线程的,意味着它只有一个执行线程来执行代码。这样设计的原因是为了简化浏览器的线程管理,避免复杂的同步问题。
然而,单线程的环境下如何处理异步任务(如网络请求、setTimeout 计时器等)呢?这是通过事件循环(Event Loop)和回调队列(Callback Queue)来实现的。
JavaScript 运行时,会有一个主线程和一个或多个任务队列(Event Loop)。当 JavaScript 执行到一个异步任务时,会将这个异步任务放到对应的任务队列中,并在适当的时候将其移回主线程执行。这样就实现了异步任务的并发执行。
二十五、常见的spa首屏优化方法有哪些?
1、路由懒加载 2、组件库局部按需引用 3、节流防抖 4、提升代码复用,封装自定义组件 5、预处理器,预加载 6、图片不放本地,用服务器存储,后端返回访问网站(CDN加速) 7、骨架屏loading(加载占位图,过渡用的) 8、使用雪碧图 等等...
二十六、vue3与vue2的对比?
1、跟节点不同:vue2中必须要有跟节点 ,Vue3中可以没有跟标签,会默认将多个跟标签包裹在一个fragement虚拟标签中,有利于减少内存。
2、组合式api和选项式api:
在vue2中采用选项式api,将数据和函数集中起来处理,将功能点切割了,当逻辑复杂的时候不利于代码阅读。
在vue3中采用组合式api,将同一个功能的代码集中起来处理,使得代码更加有序,有利于代码的书写和维护。
3、生命周期的变化
创建前:beforeCreate -> 使用setup()
创建后:created -> 使用setup()
挂载前:beforeMount -> onBeforeMount
挂载后:mounted -> onMounted
更新前:beforeUpdate -> onBeforeUpdate
更新后:updated -> onUpdated
销毁前:beforeDestroy -> onBeforeUnmount
销毁后:destroyed -> onUnmounted
异常捕获:errorCaptured -> onErrorCaptured
被激活:onActivated 被包含在<keep-alive>
中的组件,会多出两个生命周期钩子函数。被激活时执行。
切换:onDeactivated 比如从 A 组件,切换到 B 组件,A 组件消失时执行
我们通常会用 onMounted
钩子在组件挂载后发送异步请求,获取数据并更新组件状态。
这是因为onMounted
钩子在组件挂载到DOM后调用,而发送异步请求通常需要确保组件已经挂载,以便正确地操作DOM或者更新组件的状态。
4、v-if和v-for的优先级:
vue2中v-for的优先级高于v-if,可以放在一起使用,但是不建议这么做,会带来性能上的浪费
vue3中v-if的优先级高于v-for,一起使用会报错。可以在外部添加一个标签,将v-for移动到外层
5、diff算法不同:
vue2中的diff算法是遍历每一个虚拟节点,进行虚拟节点对比,返回patch对象,用来存储两个节点不同的地方。用patch记录的消息去更新dom,缺点是非常消耗性能的。
vue3中的diff算法是在初始化的时候就会给每个虚拟节点添加一个patchFlags,是一种优化的标识,只会比较patchFlags发生变化的节点,进行视图更新,而对于patchFlags没有变化的元素作静态标记,在渲染的时候直接复用。
6、响应式原理不同:
vue2通过Object.definedProperty()的get()和set()来做数据劫持、结合和发布订阅者模式来实现,Object.definedProperty()会遍历每个属性。
vue3通过proxy代理的方式实现:
proxy的优势:不需要像Object.definedProperty()的那样遍历每一个属性,有一定的性能提升proxy可以理解为在目标对象之前架设一层“拦截”,外界对该对象的访问都必须通过这一层拦截。这个拦截可以对外界的访问进行过滤和改写。当属性过多的时候利用Object.definedProperty()要通过遍历的方式监听每一个属性。利用proxy则不需要遍历,会自动监听所有属性,有利于性能的提升
总结: 更快的渲染性能,更小的体积,更好的TypeScript支持,更灵活的组合式API,更好的响应式系统