前端面试练习24.3.1
目录
一.进程和线程的区别
二.懒加载如何实现的
1.图片的懒加载
2.无限滚动懒加载
3.组件懒加载
4.音视频懒加载
三.重绘/重排(回流),哪些操作会引起对应的流程
四.双向数据绑定的原理
五.vue3为什么要用proxy
1.拦截功能更加强大
2.代码更加简洁
3.更高的性能:
4.更好的扩展性:
六.vue2劫持的单个属性,那是怎么更新整个对象的?
七.vue中key的作用
八.组件之间通信方式
九.vue为什么要mutation、 action操作
十.插槽、具名插槽、作用域插槽
十一.树用js如何实现?
十二.用set求两个数组的交集
十三.求数据的最长不重复子串
一.进程和线程的区别
进程:是程序的一次执行过程,拥有独立的内存空间
线程:是进程中的一个执行单元,共享所属进程的内存空间和系统资源
进程(Process)和线程(Thread)是操作系统中的重要概念,它们是实现并发执行的基本单位,但它们有一些关键的区别:
-
定义:
- 进程:是程序的一次执行过程,是操作系统进行资源分配和调度的基本单位。一个进程可以包含多个线程。
- 线程:是进程中的一个执行单元,是操作系统进行运算调度的基本单位。一个进程中至少包含一个线程,可以包含多个线程。
-
资源分配:
- 进程:拥有独立的内存空间、文件句柄、设备和其他系统资源。
- 线程:共享所属进程的内存空间和系统资源,包括文件句柄、设备等。
-
切换代价:
- 进程:切换代价相对较高,因为需要切换整个内存空间和系统资源。
- 线程:切换代价相对较低,因为线程共享所属进程的资源,只需要切换执行上下文即可。
-
通信和同步:
- 进程:进程间通信(IPC)相对复杂,通常需要借助操作系统提供的机制,如管道、消息队列、信号量等。
- 线程:线程间通信和同步相对简单,可以通过共享内存、信号量、互斥锁等机制实现。
-
独立性:
- 进程:各个进程之间相对独立,一个进程崩溃通常不会影响其他进程。
- 线程:同一进程中的线程共享相同的地址空间和资源,一个线程的崩溃可能会影响到其他线程。
-
创建和销毁开销:
- 进程:创建和销毁进程的开销较大,因为需要分配和释放独立的内存空间、系统资源。
- 线程:创建和销毁线程的开销较小,因为线程共享所属进程的资源,只需要分配和释放少量的内存空间。
总的来说,进程是系统资源分配的基本单位,而线程是操作系统进行调度的基本单位。进程之间相对独立,线程共享进程的资源。在设计并发应用程序时,需要根据具体的需求和场景选择合适的并发模型,合理地利用进程和线程来实现任务的并发执行。
二.懒加载如何实现的
1.图片的懒加载
src部分使用默认图片占位,然后等待接口数据请求到或是图片到达视口区域时,通过js代码将src替换
2.无限滚动懒加载
假如一次接口只返回十条信息,那么,就通过代码进行滚动条的检测,当滚动条到达页面底部时,新将page+1,然后发送请求,然后每次到达底部都重新发送请求获取数据,以此达到滚动条的懒加载
3.组件懒加载
动态导入
在代码中通过import()函数进行组件的引入加载
路由懒加载
这里就是单页面的应用了,就拿vue来说,指的就是vue-router的使用,在路由文件里引入了相关的组件,只有当用户点击或是进行相关操作才会进入相应的组件页面
4.音视频懒加载
类似图片懒加载的原理,可以将视频和音频的 src 属性设置为占位符,当它们进入视窗范围时,再动态加载真实的媒体资源。
三.重绘/重排(回流),哪些操作会引起对应的流程
重绘:指的是页面的元素非几何样式发生改变或是不影响其他元素布局的改变,从而引起的当前元素的重新渲染,这个过程是重绘
重排(回流):指的是元素的几何属性发生了变化,或是新增/删除等操作元素使得页面的布局发生了变化,从而引起的整个页面元素的重新渲染,这个过程是重排
-
改变元素的样式:
- 例如修改元素的颜色、背景、字体大小、边框等样式属性会触发重绘。
- 修改元素的尺寸、位置、显示状态等会触发重排。
-
DOM 操作:
- 添加、删除、修改 DOM 元素的结构会触发重排。
- 获取某些 DOM 属性(例如宽度、高度、偏移量等)也会导致重排。
-
改变窗口尺寸:
- 浏览器窗口大小改变会影响页面的布局,从而触发重排和重绘。
-
激活 CSS 伪类:
- 激活某些 CSS 伪类,如 :hover、:active 等,会改变元素的样式,可能会触发重绘。
-
修改 CSS 样式表:
- 修改 CSS 样式表会影响整个页面的样式,可能会触发重绘和重排。
四.双向数据绑定的原理
vue2 与 vue3 的双向绑定原理略有不同
vue2是通过指令“v-model”绑定数据,然后基于Object.definedProperty()方法,对“data”里的数据进行劫持,添加相应的getter()与setter方法,然后在数据变化时更新视图
vue3 是基于 ES6 的原生 Proxy 对象和 Reflect 对象的响应式系统,使用 Proxy 对象来监听数据的变化,并在数据变化时触发视图的更新,从而实现了双向数据绑定
拓展
下面是 Vue.js 2 中对数据进行监听的基本过程:
-
初始化阶段:当创建一个 Vue 实例时,Vue 会遍历数据对象中的每个属性,并使用 Object.defineProperty() 方法为每个属性设置 getter 和 setter。这样一来,当这些属性被访问或者修改时,Vue 能够捕获到这些操作,并触发相应的 getter 和 setter。
-
数据劫持:Vue 通过递归遍历数据对象的每个属性,为每个属性设置 getter 和 setter,从而实现对数据的劫持。这样一来,当属性被访问或者修改时,Vue 能够拦截这些操作,并触发相应的 getter 和 setter。
-
依赖收集:在模板编译阶段,Vue 会解析模板中的表达式,并为每个表达式创建一个 Watcher 对象。Watcher 对象会在数据被访问时收集依赖,当数据发生变化时,Watcher 对象会触发更新。
-
发布-订阅模式:当数据发生变化时,Vue 会使用发布-订阅模式通知所有相关的 Watcher 对象进行更新。这些 Watcher 对象负责更新视图中与数据相关的部分,从而实现视图的更新。
在 Vue.js 3 中,双向数据绑定的实现基于 ES6 的原生 Proxy 对象和 Reflect 对象。下面是 Vue.js 3 中实现双向数据绑定的大致步骤:
-
数据劫持:在 Vue.js 3 中,当创建一个 Vue 实例时,Vue 会使用 Proxy 对象来代理数据对象。这样一来,当访问或修改数据对象的属性时,Proxy 对象会拦截这些操作,并触发相应的处理。
-
创建 Proxy:Vue 在内部会使用 Proxy 对象来代理数据对象,这样就能够捕获到对数据的读取和修改操作。
-
设置拦截器:在创建 Proxy 对象时,需要提供一个 handler 对象,其中包含一组拦截器,用于拦截对数据对象的不同操作,比如 get 用于拦截属性的读取操作,set 用于拦截属性的修改操作等。
-
依赖追踪:与 Vue.js 2.x 类似,在 Vue.js 3 中,当访问数据对象的属性时,Proxy 对象会触发相应的 get 拦截器。在 get 拦截器中,Vue 会收集依赖关系,即建立属性与对应 Watcher 对象之间的关系。
-
更新视图:当数据对象的属性被修改时,Proxy 对象会触发相应的 set 拦截器。在 set 拦截器中,Vue 会通知相关的 Watcher 对象进行更新,从而更新视图。
五.vue3为什么要用proxy
1.拦截功能更加强大
这就要对比vue2 的Object.defineProperty(),它只是通过拦截数据,添加getter()与setter()方法,这样只能够实现对属性的读取和赋值操作的拦截,对于数组,它就无法通过这种方式实现双向绑定,它就只能是在底层重写数组的一些方法,比如push()或是pop(),shift()等方法,当使用时它会先执行对应的原生方法,然后再通知 Vue 去更新视图。
而vue3就没有这么麻烦,Proxy 对象提供了更多的拦截器(例如 get、set、deleteProperty 等),可以监听更多类型的操作,如属性的删除、原型链上的属性访问等,从而提供了更丰富和灵活的数据监听能力。
2.代码更加简洁
这部分体现在底层代码,如上。
3.更高的性能:
Proxy 对象的性能通常比 Object.defineProperty() 方法更高。Proxy 对象是 JavaScript 引擎的内置对象,其底层实现和性能优化更为高效,能够更快地捕获对对象的操作并触发相应的拦截器,从而提高数据监听的效率。
Object.defineProperty() 方法需要为每个属性分别设置 getter 和 setter,而 Proxy 对象则能够一次性为整个对象设置拦截器。这使得 Proxy 对象能够更快地捕获到对象的操作,并触发相应的拦截器,从而提高了数据监听的效率。
Proxy 对象的拦截器能够直接在引擎层面进行处理,而不需要额外的中间层来实现数据的监听和拦截。这使得 Proxy 对象的性能更高,能够更快地捕获到对象的操作并触发相应的拦截器。
4.更好的扩展性:
Proxy 对象提供了更多的拦截器,可以拦截更多类型的操作,同时也支持动态增加和删除拦截器,使得 Vue.js 的响应式系统更加灵活和可扩展。这样一来,开发者可以根据实际需求定制拦截器,实现自定义的数据监听和响应逻辑。
示例
我们可以自行修改,而如果想要在 Vue.js 2 中实现类似于 Vue.js 3 中动态添加和删除拦截器的功能,需要使用 Vue.set() 和 Vue.delete() 方法来手动触发响应式更新,或者使用 $set() 和 $delete() 在组件中操作数据。这些方法可以触发 Vue.js 2 的响应式系统进行更新,但不同于 Proxy,它们只是手动触发更新,而不是动态地修改拦截器。
let proxy = new Proxy(data, { // 定义一个拦截器,在获取属性时触发 get(target, property) { console.log(`Getting ${property}`); return target[property]; // 返回原始数据对象的属性值 }, // 定义一个拦截器,在设置属性时触发 set(target, property, value) { console.log(`Setting ${property} to ${value}`); target[property] = value; // 更新原始数据对象的属性值 return true; // 表示设置成功 } });
六.vue2劫持的单个属性,那是怎么更新整个对象的?
在 Vue.js 2 中,当一个对象被响应式地劫持(即通过 Vue.observable() 或者在组件中声明的 data 属性),Vue.js 会为对象的每个属性都设置 getter 和 setter,以便在属性被访问或者修改时触发更新。这样的劫持是针对对象的每个属性而不是对象本身。
当你修改了对象中的一个属性时,Vue.js 2 的响应式系统会自动检测到属性的变化,并触发更新,从而更新整个组件或者视图中依赖于该属性的部分。这意味着,尽管 Vue.js 2 对象劫持的是单个属性,但其影响会传播到整个组件或者视图中依赖于该属性的部分,从而实现了整个对象的更新。
具体来说,当你修改了对象的一个属性时,Vue.js 2 会首先检测到属性的变化,然后根据该属性所属的组件或者视图进行更新。这个更新过程可能会涉及到虚拟 DOM 的重新渲染和比对,以及实际 DOM 的更新操作,最终将最新的数据反映到用户界面上。
总的来说,Vue.js 2 的响应式系统通过为对象的每个属性设置 getter 和 setter,实现了对单个属性的劫持,并通过属性的变化来触发整个组件或者视图的更新,从而实现了对整个对象的更新。
七.vue中key的作用
在vue中key是很重要的,它主要用于优化vue的虚拟DOM树,当我们的DOM更新时,它会去对比key值,比如当key值发生变化时,它会认为这是一个新的元素,而不是一个需要更新的元素,它会去删除旧的元素,创建一个新的元素,并添加到DOM树里,这样最大程度地减少了DOM的操作,提高了页面的渲染能力。
另外,在使用Vue的过程中,key还可以用于在同一个组件中切换不同的数据源,例如在使用v-for指令循环渲染列表时,可以使用key来确保每个子组件的唯一性,从而避免出现数据混乱的问题。
key 的作用是帮助 Vue 优化虚拟 DOM 的 diff 过程,通过精确比对节点的 key 值,最小化对真实 DOM 的操作,提高页面的渲染性能
八.组件之间通信方式
在 Vue 中,组件之间有多种通信方式,以下是其中的几种:
Props 和事件:父组件可以通过 Props 将数据传递给子组件,在子组件中通过事件将数据传递回父组件。
Vuex:Vuex 是一个状态管理库,用于管理 Vue 应用程序中的全局状态。组件可以使用 Vuex 的 store 来获取和更新共享状态。
$emit 和 $on:$emit 和 $on 是 Vue 实例提供的方法,可以用于在组件之间进行事件通信。$emit 用于触发事件,而 $on 用于监听事件。
$parent 和 $children:$parent 和 $children 可以访问组件的父组件和子组件,这样就可以在组件之间传递数据。
$refs:$refs 可以用于在组件之间传递引用。通过给组件设置 ref 属性,就可以在父组件中访问该组件的实例。
这些方法可以根据应用程序的需求来选择使用哪种方法来实现组件之间的通信。
九.vue为什么要mutation、 action操作
Vue是一款响应式的前端框架,它提供了一种非常简单且直观的方式来管理应用程序的状态。而这种状态管理是通过使用Vuex这个状态管理库来实现的。在Vuex中,有一个核心概念叫做store,它是一个包含了应用程序中所有状态的容器,而mutation和action就是用来修改和操作store中的状态的。
具体来说,mutation是用来直接修改store中的状态的方法,它只能进行同步操作,也就是说不能进行异步操作。而action则是用来提交mutation的方法,它可以进行异步操作,比如发起一个网络请求等。当action执行时,它可以在操作完成之后再调用一个mutation来修改store中的状态。
这种将状态管理和业务逻辑分离的方式可以使得我们的应用程序更加清晰和易于维护,而且也可以使得我们的代码更加可测试和可复用。所以,Vue之所以要使用mutation和action,主要是为了更好地管理和控制应用程序的状态,让我们的代码更加健壮、可维护和可扩展。
十.插槽、具名插槽、作用域插槽
在Vue中,插槽(slot)是一种功能,用于在组件中分发内容。通过使用插槽,我们可以将组件的某些部分替换为父组件传递的内容。
Vue中的插槽有三种类型:默认插槽、具名插槽和作用域插槽。
默认插槽: 默认插槽是最基本的插槽类型。当我们在父组件中使用子组件时,可以在子组件内部使用默认插槽,将父组件中的内容插入到子组件中的特定位置。默认插槽可以没有名字,直接使用“slot”作为插槽的名称。
具名插槽: 当子组件需要在不同位置接收不同内容时,我们可以使用具名插槽。具名插槽允许我们在父组件中使用不同名称的插槽,以便在子组件中将内容分发到正确的位置。在子组件中,我们可以使用“slot”元素的“name”属性来定义具名插槽。
作用域插槽: 作用域插槽是一种特殊类型的插槽,允许子组件将数据传递到父组件中。使用作用域插槽,我们可以在父组件中定义一个插槽,然后在子组件中使用一个具有特定属性的“template”元素,来向插槽中传递数据。在父组件中,我们可以使用特殊的“slot-scope”属性来定义插槽的作用域。
十一.树用js如何实现?
在JavaScript中,可以通过创建节点对象的方式来构建一棵树。下面是一个示例:
class TreeNode { constructor(val) { this.val = val; this.left = null; this.right = null; } } // 创建树的根节点 let root = new TreeNode(1); // 添加子节点 root.left = new TreeNode(2); root.right = new TreeNode(3); // 添加子节点的子节点 root.left.left = new TreeNode(4); root.left.right = new TreeNode(5);
在这个示例中,我们使用TreeNode类来定义每个节点,每个节点包含一个值val和两个指向左右子节点的指针left和right。我们可以通过创建节点实例来构建树,然后使用指针来链接各个节点。在这个示例中,我们创建了一个根节点,然后添加了两个子节点和两个子节点的子节点,从而创建了一棵具有5个节点的树。 需要注意的是,这种方式构建的树是二叉树,即每个节点最多有两个子节点。如果要构建其他类型的树,可以使用不同的节点定义。
十二.用set求两个数组的交集
具体思路如下: 将一个数组转为 Set 类型 遍历另一个数组,判断该元素是否在 Set 中 如果在,则将该元素加入结果集中
代码示例:
function intersection(arr1, arr2) { const set1 = new Set(arr1); const result = []; for (let i = 0; i十三.求数据的最长不重复子串
const fun1 = (data) => { let str = ''; if (typeof data === 'object') { if (Array.isArray(data)) { str = data.join(''); } else if (data instanceof Set || data instanceof Map) { str = Array.from(data.values()).join(''); } else { str = JSON.stringify(data); } } else { str = data.toString(); } let length = str.length let maxLength = 0 let dp = Array(length).fill(0) let charIndex = {} let start = 0 let end = 0 for (let i = 0; i maxLength) { maxLength = dp[i] start = checkIndex === -1 ? 0 : checkIndex + 1 end = i } } return { maxLength: maxLength, start: start, end: end, substring: str.substring(start, end + 1) } } let str = 'qweqrwetgdgasdgfdhh' let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 3, 2, 1, 23, 12, 41, 4, 4, 241, 4, 3424, 24, , 23412, 3] let set = new Set([1, 2, 3, 4, 3, 2, 1]); let map = new Map([['a', 1], ['b', 2], ['c', 3], ['d', 1], ['e', 2]]); console.log(fun1(str)); console.log(fun1(arr)); console.log(fun1(set)); console.log(fun1(map));