面试真题

自己整理的面试真题

Featured image

自己整理的面试题

unknown和any的区别

北京

一、北京北领无线科技

​ 1. 做过页面静态化吗?举例,一个动态新闻站,如何实现页面静态化,生成变成html页面? 前端写不同的模板页面,调用服务端数据,生成不同的静态页网站,这个需求(7.25号)

答:我们在常规模式:就是前端写好的静态页,里面需要后台的数据,通过ajax请求获取数据,【动态】进行渲染! 所谓的前后端分离

页面静态化:我们前端写好的静态页,没有ajax,交给后台,他们把我们的html当做模版,直接把后台数据插入好,用户请求连接的时候,直接把页面+数据一次性给了用户。

二、 同方泰德

1.什么是响应式布局:

​ 网页能够适应不同屏幕大小和设备类型,提供一致的用户体验。

  1. 流式布局(Fluid Layout):使用百分比或相对单位(如em、rem)来定义网页元素和容器的宽度,使其能够根据屏幕尺寸自动调整大小。
  2. 媒体查询(Media Queries):通过在 CSS 中使用媒体查询,可以根据不同的屏幕尺寸、分辨率、设备类型等条件来应用不同的样式规则。例如,可以针对不同的屏幕宽度范围修改字体大小、隐藏某些元素或调整布局。
  3. 弹性网格(Flexible Grid):使用 CSS 中的弹性盒子(Flexbox)或网格布局(Grid Layout),使网页布局能够根据屏幕尺寸和内容长度自动调整,实现灵活的栅格系统。
  4. 图片响应式(Responsive Images):通过使用 CSS 或 HTML 的属性,可以根据不同设备的像素密度或屏幕尺寸,选择并显示适当大小的图像,以提高加载速度和显示效果。

2.什么是bfc

​ BFC 是页面上的一个独立容器,有一套自己的渲染规则,它决定了其中的元素如何布局、相互影响和与外部元素交互。

​ BFC 具有以下特性和作用:

  1. 清除浮动:当一个元素的 overflow 属性被修改的话,那么这个元素就会创建一个 BFC,并且可以清除浮动。那么他就不会溢出到这个元素的外部,只是会影响它内部的布局。

  2. 垂直边距重叠:在同一个 BFC 内部的块级元素之间,相邻的垂直边距会发生重叠。但是,不同 BFC 的元素之间的垂直边距不会重叠。

  3. 自适应高度:当一个元素的子元素都是浮动元素时,该元素将会包含子元素,撑开高度,防止父元素塌陷。

  4. 阻止文本环绕:一个元素创建的 BFC 会阻止文本环绕在其周围,使文本只能在 BFC 区域内流动。

  5. 分隔上下文:不同的 BFC 之间的元素在布局时互相独立,不会相互影响。这可以用于实现复杂的布局效果和解决一些布局问题。

    ​ 可以通过以下方式触发 BFC:

    • 设置元素的 display 属性为 inline-blocktable-celltable-caption 等非 block 值。
    • 将元素的 position 属性设置为 absolutefixed
    • 将元素的 float 属性设置为 leftright
    • 将元素的 overflow 属性设置为非 visible 值。

3、布局溢出怎么解决

  1. 使用溢出隐藏(Overflow Hidden):将容器元素的 overflow 属性设置为 hidden,这会隐藏容器内部超出边界的内容。这种方法适用于不希望溢出内容被显示出来的情况。
  2. 使用溢出滚动(Overflow Scroll):将容器元素的 overflow 属性设置为 scroll,这会在需要时显示滚动条,使用户可以滚动查看溢出的内容。这种方法适用于希望用户能够查看全部内容的情况。
  3. 使用溢出自动(Overflow Auto):将容器元素的 overflow 属性设置为 auto,这会根据内容大小自动决定是否显示滚动条。如果内容溢出,则显示滚动条;否则,不显示滚动条。
  4. 使用弹性布局(Flexbox)或网格布局(Grid Layout):使用 CSS 中的弹性布局或网格布局可以更灵活地控制容器内部元素的布局,并自动调整内容的位置和尺寸,以适应容器的大小。
  5. 调整布局结构:如果溢出问题是由于布局结构不合理导致的,可以尝试重新设计布局或优化页面结构,以避免内容溢出。
  6. 使用媒体查询(Media Queries):针对不同的屏幕尺寸或设备类型,适当地调整布局样式和元素大小,以确保内容在各种情况下都能得到恰当的显示。

4、css高度塌陷,怎么解决splice

** 清除浮动(Clearfix)是一种技术,通过对父元素应用一些样式,使其正确包含浮动元素,并避免高度塌陷的问题。**

  1. 使用空元素和清除浮动样式:

    .clearfix::after {
      content: "";
      display: table;
      clear: both;
    }
    

    在需要清除浮动的父元素上添加 .clearfix 类,并将其 ::after 伪元素设置为 display: table; clear: both;,以创建一个空的块级元素,并清除浮动。

  2. 使用 clearfix 类:

    .clearfix {
      overflow: auto;
    }
    

    在需要清除浮动的父元素上添加 .clearfix 类,并将其 overflow 属性设置为 auto。这样可以触发 BFC(块级格式化上下文),从而清除浮动。

  3. 使用 clearfix 插件或框架:

    一些 CSS 框架如 Bootstrap 或 Foundation 提供了内置的 clearfix 类或 mixin,可以直接在需要清除浮动的元素上应用相应的类名或样式。

5、什么是盒子模型 分几块属性

内容区域、内边距、边框和外边距。这四个结合在一起就是盒子模型

6、js线程,循环机制

线程:有一个主线程(也称为 UI 线程),它负责处理用户交互和更新页面的任务。除了主线程之外,JavaScript 还支持通过 Web Workers 创建额外的线程。

事件循环的基本流程如下

  1. 当代码开始执行时,首先会执行同步任务,并将相关的函数调用推入调用栈。
  2. 如果遇到异步任务(如定时器、网络请求等),会将其移出调用栈,并在指定条件满足时,将其回调函数添加到任务队列中。
  3. 当调用栈为空时,事件循环开始工作。它会检查微任务队列,如果有微任务存在,则依次执行所有的微任务。
  4. 执行完毕微任务后,事件循环会从任务队列中取出一个任务(通常是在任务队列中最先进入的任务)并将其推入调用栈中执行。
  5. 不断重复步骤 3 和步骤 4,直到任务队列为空。

7、常用的同步、异步任务有哪些

同步
  1. 函数调用:代码按照函数调用的顺序依次执行,每个函数必须等待上一个函数执行完毕后才能执行。
  2. 同步网络请求:使用 XMLHttpRequest 或者 Fetch API 发起网络请求,但是代码会等待请求响应返回后才继续执行下面的代码。
  3. 文件操作:读取或写入文件时,代码会等待文件操作完成后再进行后续操作。
  4. 数据库查询:通过数据库查询语句获取数据时,代码会等待查询结果返回后再进行后续操作。
  5. 循环和条件语句:例如 for 循环、while 循环、if-else 条件语句等,在执行这些语句期间,代码会按照顺序逐行执行。
异步
  1. 函数调用:代码按照函数调用的顺序依次执行,每个函数必须等待上一个函数执行完毕后才能执行。
  2. 同步网络请求:使用 XMLHttpRequest 或者 Fetch API 发起网络请求,但是代码会等待请求响应返回后才继续执行下面的代码。
  3. 文件操作:读取或写入文件时,代码会等待文件操作完成后再进行后续操作。
  4. 数据库查询:通过数据库查询语句获取数据时,代码会等待查询结果返回后再进行后续操作。
  5. 循环和条件语句:例如 for 循环、while 循环、if-else 条件语句等,在执行这些语句期间,代码会按照顺序逐行执行。

8、vuex 怎么存储的? localStorage能一直存在吗?

  1. 在 Vue.js 应用程序中,可以通过 Vue.use(Vuex) 来安装 Vuex 插件,并创建一个 store 实例。这个 store 对象中包含了应用程序的状态和一些用于修改状态的方法。
  2. 在 Vue 组件中,可以使用计算属性或者从 store 导入辅助函数(mapState、mapMutations、mapActions)来访问 store 中的状态数据。通过使用提交 mutations 或者分发 actions 的方式,可以修改 store 中的状态。

9、封装过路由吗

​ 封装路由是常见的一种做法,可以提高代码的可读性和可维护性。通过封装路由,可以将路由的配置和处理逻辑进行分离,使得代码结构更清晰。常见的封装方式包括使用路由框架或自定义路由类,将路由的路径、请求方法和处理函数进行关联,并提供参数传递和中间件支持等功能。这样,在应用程序中使用封装后的路由,可以更方便地进行路由配置和管理

10、token原理,安全机制是什么

token原理:

当用户进行身份认证成功后,服务器会生成一个包含用户信息和权限的令牌,并返回给客户端。生成令牌的方式可以是基于对称加密算法(如HMAC)或非对称加密算法(如RSA)实现的签名。

安全性机制:

11、简单说一下vue2和vue3的区别

  1. 性能改进:Vue 3在性能方面进行了优化,提高了渲染速度和响应性能。
  2. Composition API:Vue 3引入了Composition API,使得组件内部逻辑的组织更加直观和易于维护。
  3. TypeScript支持:Vue 3对TypeScript有更好的支持,并提供了更准确的类型推导和类型检查。
  4. Teleport组件:Vue 3引入了Teleport组件,方便将子组件移到DOM中的其他位置。
  5. 更小的包大小:Vue 3使用了更好的Tree-shaking技术,减小了代码库的体积。
  6. 改进的响应式系统:Vue 3的响应式系统更高效和可预测,特别适用于处理深层嵌套、大型数据对象。
  7. 错误处理:Vue 3引入了新的错误处理机制,更容易捕获和调试错误。

12、了解过跨域吗

什么是跨域

跨域就是浏览器从一个“域”向另一个“域”的服务器发出请求,来访问另一个“域”上的资源。但是,由于请求的文件可能会存在恶意攻击,浏览器并不允许直接访问另一个“域”上的资源,只能访问同一个“域”上的资源,这个就是“同源策略”。而所谓的“同源”,指的是“协议、域名、端口号”一致

怎么解决跨域
  1. JSONP:通过动态创建 <script> 标签来获取跨域的数据。后端接口需要返回一段JavaScript代码,并在其中调用一个预先定义好的回调函数,将数据作为参数传递回来。前端通过定义该回调函数,即可获取到跨域请求的数据。

  2. 代理服务器:前端通过向自己的服务器发送请求,再由服务器转发请求到目标服务器,从而绕过浏览器的同源策略限制。在实际项目中,可以配置反向代理服务器如Nginx或使用Web服务器框架如Express.js来完成转发。

  3. WebSocket:使用WebSocket建立长连接,可以实现跨域通信。WebSocket协议并不受同源策略的限制。

  4. 使用第三方库:一些前端库或框架提供了处理跨域请求的方法,例如axios、Fetch等,它们可以帮助简化跨域请求的过程。

  5. CORS(跨域资源共享):服务器端设置响应头部,允许指定的域名访问资源。在服务器的响应中添加如下头部信息:

    Access-Control-Allow-Origin: <允许的域名>
    

    其中,<允许的域名>可以是具体的域名,也可以是通配符 *,表示允许所有域名访问。

13、用promise封装过什么吗

  1. 异步请求:使用 Promise 封装 AJAX 请求,可以更方便地处理请求成功和失败的情况。例如,可以封装一个函数,使用 Promise 返回 AJAX 请求的结果。
  2. 定时器:使用 Promise 封装定时器操作,可以更便捷地处理定时器的完成状态。例如,可以封装一个函数,返回一个 Promise,在定时器结束后 resolve。
  3. 文件操作:使用 Promise 封装读取文件或写入文件的操作,可以更好地处理文件操作的结果和错误。例如,可以封装一个函数,使用 Promise 返回文件的内容或异步写入文件。
  4. 浏览器 API:使用 Promise 封装浏览器提供的一些异步 API,例如获取位置信息、访问摄像头等。这样可以更便捷地处理这些异步操作的结果。
  5. 复杂异步操作:当需要依次执行多个异步操作,并在全部完成后获取结果时,可以使用 Promise 的链式调用来串联这些操作。通过封装这些异步操作为 Promise,可以更清晰地表达异步操作之间的依赖关系。

三、北京腾信软创科技股份有限公司

1.如何实现深、浅拷贝

深拷贝
  1. 判断原始对象的类型,如果是基本数据类型或null,则直接返回该值。
  2. 创建一个新的目标对象,可以是对象字面量{}或使用构造函数创建新的实例。
  3. 遍历原始对象的属性,对每个属性进行深拷贝:
    • 如果属性值是对象或数组,则递归调用深拷贝函数进行复制。
    • 否则,直接将属性值赋给目标对象的对应属性名。
  4. 返回目标对象。
浅拷贝
  1. 扩展运算符(…):使用扩展运算符可以快速实现对象或数组的浅拷贝。
  2. Object.assign():使用 Object.assign() 方法也可以进行浅拷贝。
  3. 数组的 slice() 方法:对于数组,可以使用 slice() 方法进行浅拷贝。

2.跨域

同上

3.不使用for循环 如何声明一个1-100的数组

可以使用数组方法 Array.from() 来声明一个包含1到100的数组,而不使用显式的循环。以下是一种实现方式:

const arr = Array.from({ length: 100 }, (_, index) => index + 1);

上述代码中,我们使用了 { length: 100 } 来指定要创建的数组的长度为100。然后,通过映射函数 (_, index) => index + 1 将每个索引位置的值设置为对应的索引加1,从而生成了1到100的数组。

这种方式利用了 Array.from() 方法的第一个参数可以是类数组对象或可迭代对象的特性,结合箭头函数来生成递增的数字数组。

4.foreach能否退出循环

forEach() 方法不能直接退出循环

使用 for 循环可以通过 break 语句来中断循环,

另一种方式是使用 some() 方法,它会在回调函数返回 true 时中断循环,

5.出现Undefined的情况 Undefined和null有啥区别

Undefined的情况

1.声明变量但没赋值

2.函数没有return 返回值

3.对象属性不存在

4.数组索引超出范围:当尝试访问数组中不存在的索引位置时,将返回 undefined

5.函数形参不传值,那么这个形参默认就是undefined

6.for in 和 for of 有啥区别

  1. for...in 循环用于遍历对象的可枚举属性,以及继承自原型链的可枚举属性。它遍历的是对象的键(key)。

  2. for...of 循环用于遍历可迭代对象(数组、字符串、Set、Map 等),它遍历的是对象的值(value)。

    3.for…in 返回的是属性名(key),而 for…of` 返回的是属性值(value)。

    4.for…in 循环是用于遍历对象的属性,而 for…of` 循环是用于遍历可迭代对象的值,无法用于普通对象的遍历。

7.Ref 和reactive的区别

  1. ref 是用于创建一个单一的可响应的数据引用,它会将传入的值包装在一个响应式对象中。可以通过 .value 属性访问和修改其值。
  2. reactive 是用于创建一个可响应的对象。它会递归地对对象进行响应式转换,使得整个对象以及嵌套的属性都具有响应式能力。可以直接访问和修改对象的属性。
  3. ref 的作用更加简单明确,适合处理基本类型的数据,而 reactive 可以处理复杂的对象和嵌套结构。
  4. 在模板中使用 refreactive 的方式略有不同。对于 ref,需要在模板中直接使用 .value 来获取或更新值;而对于 reactive,可以在模板中直接访问和修改属性。

8.Setup是什么

使用 setup 替代了之前版本中的 beforeCreatecreated 钩子函数和 data 选项,将它们合并到了一个统一的函数中。

` setup` 函数接收两个参数:

  1. props:父组件传递给子组件的属性值。在 setup 函数中可以直接使用 props 访问属性值。
  2. context:当前组件的上下文对象,包含了一些常用的属性和方法,如 attrsslotsemit 等。

使用 setup 函数的好处是能够在函数内部使用更自然的 JavaScript 语法,而不必在选项中分散编写代码。它提供了一种更直观和灵活的方式来配置和初始化组件,同时也使得组件的逻辑更容易测试和重用。

9. Pinna是什么 和Vuex有啥区别

  1. 支持 Composition API:Pinna 充分利用了 Vue 3 的 Composition API,使得在使用 Pinna 时能够更好地结合组合函数和逻辑。而 Vuex 在 Vue 2 中依赖于选项式 API。
  2. 更简化的 API:Pinna 提供了一组简单而直观的 API,使得状态管理变得更加简洁和易于理解。相比之下,Vuex 的 API 相对更复杂一些。
  3. 更好的类型支持:Pinna 在设计上更注重类型支持,内置了 TypeScript 类型定义,提供了更好的类型推导和安全性。Vuex 在 Vue 2 中对类型支持较弱,需要额外的配置来实现类型安全。
  4. 更小的包体积:由于 Pinna 是专门为 Vue 3 设计的,在设计和实现上更加精简,所以其包体积相对较小。而 Vuex 是兼容 Vue 2 和 Vue 3 的通用状态管理库,因此包含了更多兼容层,所以包体积相对较大。
  5. 对响应式能力的优化:Pinna 在响应式更新方面做了一些优化,可以更高效地追踪和更新状态。Vuex 的响应式系统相对较为简单。

四、 北京久其软件股份有限公司

1.声明变量方式有哪些,简单介绍下区别?

答:Var 存在变量提升,没有块级作用域的限制,可以多次声明一个同样的变量

let

Const const声明的变量不能重新赋值

2.什么是箭头函数,并简单代码举例?

代码示例:()=>{}

箭头函数没有this,使用上一层作用域的this 没有原型对象 不能当作构造函数进行使用 没有agurments对象

3.什么是 Promise,并简单代码举例?

romise 是 JavaScript 中的一个异步操作处理机制,用于管理和处理异步操作的结果。它表示一个尚未完成但最终会完成的操作,并可以获取其最终的结果或错误信息。

一个 Promise 对象有以下三个状态:

  1. Pending(进行中):初始状态,表示操作尚未完成。
  2. Fulfilled(已成功):表示操作已经成功完成。
  3. Rejected(已失败):表示操作因某些原因失败。

Promise 对象具有以下方法:

4.简单介绍Vue 的生命周期钩子函数有哪些,分别在什么时候触发

  1. beforeCreate(创建前)
  2. created(创建后)
  3. beforeMount(挂载前)
  4. mounted(挂载后)
  5. beforeUpdate(更新前)
  6. updated(更新后)
  7. beforeUnmount(销毁前
  8. unmounted(销毁后)

5.请说说你对 Vue Router 的理解,以及它的作用和原理?

​ Vue Router 是 Vue.js 官方提供的前端路由管理插件,它可以帮助我们实现单页面应用(SPA)中的路由功能。它的作用是通过映射 URL 和组件的关系,实现页面之间的无刷新跳转和导航。

​ Vue Router 的原理是基于浏览器的 History API 或 Hash 模式来监听 URL 的变化,并根据预先定义好的路由规则,将不同的 URL 映射到对应的组件上进行渲染。当用户进行页面切换或跳转时,Vue Router 会根据 URL 的变化,加载相应的组件,并将其渲染到页面中的指定位置。

Vue Router 的核心概念包括:

  1. 路由器(Router):负责管理整个应用的路由配置和状态,通过创建一个路由器实例来实现。
  2. 路由(Route):表示当前正在访问的路由信息,包括路径、参数、查询参数等,可以在组件中通过 $route 访问。
  3. 路由组件(Route Component):与路由关联的 Vue 组件,根据不同的路由,加载相应的组件进行渲染。
  4. 路由规则(Route Rule):定义了 URL 和路由组件之间的映射关系,决定了在特定 URL 下应该加载哪个组件。

​ 通过配置路由规则,我们可以实现各种复杂的路由需求,如动态路由、嵌套路由、命名路由等。Vue Router 还提供了导航守卫(Navigation Guards)功能,用于在路由切换前后执行一些逻辑,例如权限验证、页面加载状态管理等。

​ 总结起来,Vue Router 是一个用于管理前端路由的工具,通过监听 URL 变化并根据预定义的路由规则,将不同的 URL 映射到对应的组件上进行渲染,从而实现单页面应用中的页面跳转和导航功能。它是 Vue.js 开发中必备的插 件之一,能够提高开发效率和用户体验。

6.父组件向子组件如何传递数据,子组件向父组件如何传递数据,非父子组件之间如何传递数据,

父=>子 通过属性向子组件进行传值,子组件通过props接收数据

子=>父 同自定义事件传值,父组件监听子组件触发的自定义事件,可以在自定义事件方法的形参中获取传过来的值

非父子组件之间
  1. 使用Vuex:Vuex是一个专为Vue.js应用程序开发的状态管理模式库。可以在其中定义一个全局的状态(state),然后在任何组件中都可以访问和修改该状态。通过Vuex的状态管理,可以在非父子组件之间共享数据。
  2. 使用localStorage或sessionStorage:如果数据较小且无需实时通信,可以使用浏览器提供的本地存储功能(localStorage或sessionStorage)。一个组件将数据存储在本地存储中,另一个组件从本地存储中读取该数据。
  3. 使用URL参数:通过URL参数传递数据是一种简单的方式。一个组件将数据附加到URL参数中,另一个组件从URL参数中获取数据。可以使用query参数或路由参数来传递数据。
  4. 使用第三方库或插件:有一些第三方库或插件可以帮助在非父子组件之间进行数据传递,如PubSubJS、vue-bus等。

你在UniApp开发中用到过哪些组件和插件?

组件:
  1. 视图容器:View、ScrollView、Swiper等。
  2. 基础内容:Text、Icon、Button等。
  3. 表单组件:Input、Textarea、Radio、Checkbox等。
  4. 列表组件:List、Cell、Grid、IndexList等。
  5. 弹出层:Modal、Toast、ActionSheet、Popup等。
  6. 导航组件:Navbar、Tabbar、Tab、SegmentedControl等。
  7. 图片相关:Image、Video、Audio等。
  8. 下拉刷新和上拉加载:Refresh、Loadmore等。
  9. 转场动画:Transition、Animation等。
  10. 日期时间选择器:Picker、Datepicker、Timepicker等。
插件:
  1. 跨平台API:uni.request(网络请求)、uni.showToast(消息提示)、uni.navigateTo(页面跳转)等。
  2. 本地存储:uni.setStorage(设置本地缓存)、uni.getStorage(获取本地缓存)等。
  3. 地理位置:uni.getLocation(获取当前位置信息)、uni.chooseLocation(选择位置)等。
  4. 图片选择和拍照:uni.chooseImage(选择图片)、uni.chooseVideo(选择视频)、uni.chooseMessageFile(选择文件)等。
  5. 扫码功能:uni.scanCode(扫码)、uni.getClipboardData(获取剪贴板数据)等。
  6. 支付功能:uni.requestPayment(发起支付请求)、uni.showActionSheet(显示操作菜单)等。
  7. 分享功能:uni.share(调起分享面板)、uni.openShare(打开分享页面)等。

UniApp如何实现多端兼容?

  1. 统一的开发语法:UniApp使用Vue.js作为开发框架,开发者可以采用一致的Vue.js开发语法来编写跨平台的代码。这使得开发者无需学习各个平台的独有语法和特性,降低了学习成本。
  2. 平台扩展能力:UniApp提供了一套平台扩展API,可以通过这些API调用各个目标平台提供的原生能力和特性。在编写跨平台代码时,可以根据当前运行的平台使用不同的API来实现特定的功能,从而兼容各个平台的特性。
  3. 编译和打包:UniApp将跨平台代码编译打包成不同平台所需的具体代码。在编译过程中,UniApp会根据不同平台的特点进行适配和转换,生成对应平台的代码。这样,在不同平台上运行时可以获得最佳的性能和用户体验。
  4. 样式兼容:UniApp使用了类似于CSS预处理器的stylus语法,在编写样式时可以使用预定义的类名来实现跨平台样式的兼容。通过这种方式,可以根据不同平台生成对应的样式代码,保证在各个平台上的一致性和美观性。
  5. 组件适配:UniApp提供了一套跨平台的组件库,这些组件在不同平台上的外观和交互行为已经进行了兼容性处理。开发者只需要调用相应的组件API,无需关心具体实现细节,即可实现多端兼容。

五、网易游戏

1. 两链一包以及使用场景

  1. 作用域链的使用场景:
    • 变量访问:在 JavaScript 中,作用域链决定了变量的可访问性。当访问一个变量时,JavaScript 引擎会按照作用域链一级一级地查找,直到找到对应的变量或者查找到全局作用域。因此,在编写函数内部的代码时,可以利用作用域链来访问上层作用域中的变量,实现数据的共享和封装。
    • 闭包:作用域链也是 JavaScript 中闭包的基础。通过在一个函数内部定义另一个函数,并返回这个内部函数,内部函数可以访问外部函数的变量和参数。这样的闭包结构可以延长变量的生命周期并保护私有数据,常见的应用包括事件处理、模块化开发等。
  2. 原型链的使用场景:
    • 对象继承:JavaScript 中的对象继承是通过原型链来实现的。当访问一个对象的属性或方法时,如果该对象自身没有,则会沿着原型链向上查找,直到找到对应的属性或方法,或者查找到原型链的末端(null)。通过原型链,可以实现对象之间的继承关系,减少重复代码,提高代码复用性。
    • 原型继承:JavaScript 中的原型继承是一种基于原型链的继承方式。通过构造函数创建对象时,可以利用原型链将对象的属性和方法共享给多个实例。这种原型继承的方式比较灵活,适用于需要创建多个相似或者相关对象的场景。

2. HTTPS 和http 区别

HTTPS(Hypertext Transfer Protocol Secure)和HTTP(Hypertext Transfer Protocol)是用于在网络上传输数据的协议,它们之间主要有以下区别:

  1. 安全性:
    • HTTP:HTTP是明文传输协议,数据在传输过程中是以明文形式进行传输,容易被窃听、篡改或伪装。因此,HTTP在安全性方面较差,不适合传输敏感信息。
    • HTTPS:HTTPS通过使用SSL(Secure Sockets Layer)或TLS(Transport Layer Security)等加密协议,在HTTP的基础上添加了加密和身份认证的功能。数据在传输过程中经过加密处理,能够有效防止数据的被窃听和篡改,提供更高的安全性。
  2. 协议端口:
    • HTTP:HTTP使用的默认端口号是80,即在URL中不指定端口号时,默认为80。
    • HTTPS:HTTPS使用的默认端口号是443,即在URL中不指定端口号时,默认为443。
  3. 证书认证:
    • HTTP:HTTP协议不需要证书认证,任何人都可以建立一个HTTP服务。
    • HTTPS:为了确保通信的安全性,HTTPS在建立连接时需要使用数字证书进行服务器身份验证。只有经过证书颁发机构(CA)签发的有效证书,浏览器才会信任,确保通信的安全可信。
  4. 性能:
    • HTTP:HTTP协议简单直接,通信时消耗的资源较少,所以相对而言传输速度较快。
    • HTTPS:HTTPS在传输过程中需要进行加密和解密的计算,会增加数据传输的延迟,并且需要额外的计算资源来处理加密解密操作。因此,相对于HTTP来说,HTTPS传输速度略慢一些。

综上所述,HTTPS相对于HTTP来说更加安全可靠,适用于需要保护隐私和传输敏感信息的场景,如网上银行、电子商务等。而HTTP适用于不需要保密的场景,如普通网页浏览等。一般情况下,为了保障数据的安全性,推荐使用HTTPS来进行数据传输。

3. H5c3对比H4c2的区别

  1. 标准和语义化:H5c3引入了一些新的标签和属性,<header><nav><section><article><footer>
  2. 多媒体支持:H5c3增加了对多媒体元素的原生支持,例如<video><audio>标签,使得在网页中嵌入视频和音频更加容易和灵活。
  3. 绘图和动画效果:H5c3引入了Canvas和SVG两种绘图技术,
  4. 本地存储:H5c3提供了Web Storage和IndexedDB等本地存储技术,使得网页能够在客户端存储大量的数据,减少对服务器的请求,提高性能和用户体验。
  5. 客户端数据库:H5c3引入了Web SQL Database技术,允许网页在客户端使用SQL语句进行数据库操作,从而实现更复杂的数据管理和处理。
  6. 响应式设计:如媒体查询(media queries)和弹性盒子布局(flexbox)

4. 防抖节流意义以及应用场景

  1. 防抖(Debounce):
    • 意义:防抖主要用于减少频繁触发的事件的处理次数,当事件连续触发时,只有在一定的间隔内没有新的触发时才会执行对应的操作,有效地降低了事件处理的频率。
    • 应用场景:
      • 浏览器窗口大小调整事件(resize):避免频繁触发事件导致不必要的计算或布局更新,而只在调整停止后执行相应的操作,如重新布局或调整UI元素尺寸。
      • 输入框输入事件(input):当用户在输入框连续输入时,可以延迟触发关联的搜索、验证或自动完成等操作,以提高性能和减少无效请求。
      • 按钮点击事件(click):避免用户误操作或连续点击按钮导致多次执行相同的操作,只有在一定间隔内没有新的点击时才执行操作,确保只执行一次。
  2. 节流(Throttle):
    • 意义:节流主要用于限制函数的执行频率,以确保函数在一定时间间隔内只执行一次,有效地控制了函数的调用频率和资源消耗。
    • 应用场景:
      • 滚动事件(scroll):当用户滚动页面时,限制触发事件处理操作的频率,以提高滚动性能和响应速度。
      • 鼠标移动事件(mousemove):在需要实时更新UI元素位置的情况下,限制鼠标移动事件的处理频率,减少计算量和重绘次数。
      • 定时器相关的操作:如定时轮播、自动保存等场景中,限制定时器触发回调函数的频率,避免无谓的计算和操作。

5. 跨域原因以及解决方案

​ 当一个网页的脚本请求访问另一个源(域、协议或端口)的资源时,会发生跨域问题。跨域问题是由浏览器的同源策略引起的安全机制,用于保护用户信息和防止恶意脚本的运行。

解决跨域问题的方法:
  1. JSONP(JSON with Padding):通过动态创建<script>标签,将跨域请求的数据包装在回调函数中返回,利用<script>标签的src属性不受同源策略限制的特点进行跨域请求。但是,JSONP只支持GET请求且只能获取限定格式的数据,并且存在安全性和可控性方面的隐患。
  2. CORS(Cross-Origin Resource Sharing):在服务器响应头中添加合适的Access-Control-Allow-Origin、Access-Control-Allow-Methods等字段,用于告知浏览器允许来自其他域的请求访问资源。CORS支持各种HTTP请求方法,并且安全可靠。需要服务器端进行配置和支持。
  3. 代理服务器:通过在同源服务器上设置一个代理,将跨域请求转发到目标服务器,然后再将响应返回给客户端。客户端只与同源服务器通信,解决了跨域问题。但是需要配置代理服务器,增加了一定的工作量。
  4. WebSocket:使用WebSocket协议进行跨域通信,WebSocket协议不受同源策略限制。通过建立WebSocket连接,实现实时的双向数据传输。但这需要在服务器端和客户端都有相应的支持。
  5. iframe跨域通信:通过在主页面中嵌入一个隐藏的iframe,并在iframe中加载目标页面,利用JavaScript的postMessage方法,在主页面和iframe之间传递消息进行跨域通信。

6. Axios 的封装

Axios 的封装可以包括以下几个方面:

  1. 创建实例:使用 axios.create() 方法创建一个 Axios 实例,可以在该实例上设置一些全局配置,如基本 URL、超时时间等。
  2. 请求拦截器:通过 instance.interceptors.request.use() 方法添加请求拦截器,可以在发送请求之前对请求进行处理,比如添加请求头、设置 token 等。请求拦截器可以用来统一处理请求相关的需求。
  3. 响应拦截器:通过 instance.interceptors.response.use() 方法添加响应拦截器,可以在获取到响应数据后对其进行处理,比如解析、过滤、错误处理等。响应拦截器可以用来统一处理响应相关的需求。
  4. 封装请求方法:根据项目需求,可以封装常用的请求方法,如 GET、POST、PUT、DELETE 等,以及其他自定义的请求方法。封装请求方法时,可以使用 Axios 实例提供的相应方法,如 instance.get()instance.post() 等。
  5. 处理错误:在封装过程中,可以考虑如何处理请求和响应的错误。例如,可以在响应拦截器中统一处理错误的状态码、错误信息等,或者在封装的请求方法中捕获和处理请求过程中的异常。
  6. 使用封装的方法:在其他文件中引入封装好的方法,以便在业务代码中使用。通过调用封装的请求方法,并传入相应的参数,即可发送请求并处理响应。

7. 数据类型

  1. 字符串(String)
  2. 数字(Number)
  3. 布尔值(Boolean)
  4. 数组(Array)
  5. 对象(Object)
  6. 空(Null)
  7. 未定义(Undefined)
  8. 符号(Symbol)

六、动次科技面试题

1、Vue项目怎么做优化

  1. 减少HTTP请求:合并和压缩静态资源文件,减少页面需要请求的文件数量和大小。
  2. 使用异步组件:将页面按需加载,只加载当前视图所需的组件,提高首屏加载速度。
  3. 利用路由懒加载:将页面按路由拆分成多个异步加载的模块,根据需要动态加载,减少初始加载的资源量。
  4. 图片优化:压缩图片大小、使用WebP格式、使用懒加载等方法来减少图片资源对页面加载速度的影响。
  5. 使用CDN加速:将静态资源部署到CDN上,加快资源加载速度。
  6. 前端缓存:使用浏览器缓存、HTTP缓存等机制,减少对服务器的请求,提高页面加载速度。
  7. 代码拆分和按需加载:利用Webpack等构建工具实现代码拆分,按需加载,减少不必要的代码加载和执行。
  8. 节流和防抖:在处理频繁触发的事件(如滚动、输入)时,使用节流和防抖技术,限制事件处理的频率,降低性能消耗。
  9. 避免不必要的计算和渲染:在Vue组件中,尽量避免不必要的计算和渲染操作,只对必要的数据进行响应式更新。
  10. 使用虚拟列表:当列表数据较多时,使用虚拟列表技术(如vue-virtual-scroll-list)进行优化,只渲染可见区域的列表项,提高性能。
  11. 代码优化:优化Vue组件代码,减少不必要的操作和计算,避免冗余代码,提高代码执行效率。
  12. 打包优化:配置Webpack等构建工具,进行合理的打包优化,如代码分割、模块依赖分析、Tree Shaking等。
  13. 使用Vue Devtools进行性能分析:借助Vue Devtools插件,分析应用中的性能瓶颈,找到需要改进的地方。

2、图片懒加载怎么实现的

  1. 将待加载的图片的src属性替换为一个占位符,例如一个小的loading图或者透明的gif:
<img src="placeholder.jpg" data-src="image.jpg">
  1. 使用CSS将图片的占位符设置为合适的高度和宽度,以保持页面布局的稳定性:
.img {
  width: 100%;
  height: auto;
}
  1. 在页面加载完成后,使用JavaScript监听滚动事件或其他滚动容器的滚动事件。
  2. 当滚动事件触发时,获取所有带有data-src属性的图片元素,并检查是否进入了可视区域。
  3. 如果图片元素进入了可视区域,将data-src的值赋给src属性,从而加载真实的图片:
const lazyImages = document.querySelectorAll('img[data-src]');
lazyImages.forEach(img => {
  if (isElementInViewport(img)) {
    img.src = img.dataset.src;
    img.removeAttribute('data-src');
  }
});

其中,isElementInViewport是一个用来判断元素是否进入可视区域的函数,可以通过计算元素在视窗内的位置来实现。

继续监听滚动事件,并重复步骤4和步骤5,以加载更多的图片。同时,为了性能考虑,可以使用节流函数来控制滚动事件的频率。

3、后台管理系统的路由权限怎么做的

  1. 定义用户角色和权限:首先需要根据后台管理系统的需求,定义不同的用户角色和对应的权限。例如,管理员用户可能具有全部权限,而普通用户只能访问部分页面或执行特定的操作。
  2. 设计路由结构:根据用户的角色和权限,设计系统的路由结构,将不同权限的页面和功能与对应的路由关联起来。
  3. 前端路由配置:在前端应用中,根据设计好的路由结构,进行路由的配置。通常使用的前端框架(如Vue、React)都提供了路由机制,可以根据用户角色动态生成对应的路由配置。
  4. 路由守卫/中间件:在前端应用中,使用路由守卫或中间件来拦截路由导航,判断用户是否有权限访问该路由或执行该操作。
  5. 后端权限控制:除了前端路由权限控制,后台管理系统还需要通过后端进行权限控制。在服务端,根据用户的角色和权限,验证用户请求的合法性,限制用户对数据和功能的访问权限。

4、登陆是怎么保持的

  1. 会话保持:当用户成功登录后,服务器会创建一个会话,并将该会话的唯一标识符(通常是一个会话ID)返回给客户端。客户端会将该会话ID保存在Cookie或者本地存储中。每次客户端发起请求时,会携带该会话ID。服务器根据会话ID验证用户的身份,并在会话中保存相关的登录状态信息。会话是在服务器端进行管理,因此较为安全,但需要使用Cookie或其他方式来传递会话ID。
  2. 令牌保持:当用户成功登录后,服务器会生成一个令牌,并将该令牌返回给客户端。客户端通常会将令牌保存在本地存储(如LocalStorage或SessionStorage)或者在每次请求的请求头中携带。服务器在接收到请求时,从令牌中解析出用户的信息,并验证令牌的合法性和有效期。令牌通常使用加密算法进行签名或者加密,以确保令牌的安全性。
  3. 双token 【访问令牌(Access Token)和刷新令牌(Refresh Token)】
    1. 用户成功登录后,服务器颁发一个访问令牌和一个刷新令牌给客户端。
    2. 客户端在每次请求中携带访问令牌来访问受保护的资源。服务器验证访问令牌的有效性和权限,如果有效则返回所需的数据。
    3. 如果访问令牌过期,客户端可以使用刷新令牌向服务器请求获取新的访问令牌。
    4. 服务器验证刷新令牌的有效性,如果有效则颁发一个新的访问令牌给客户端,并更新刷新令牌的有效期。

5、cookie和locelStorage区别

  1. 存储方式:Cookie通过在HTTP请求头中传输数据,将数据存储在客户端和服务器之间进行交互。而LocalStorage是HTML5引入的Web Storage API的一部分,将数据以键值对的形式存储在浏览器本地。
  2. 容量限制:Cookie的容量限制通常为4KB左右,且每个域名下的Cookie数量也有限制。而LocalStorage的容量限制通常为5MB或更大,取决于浏览器的具体实现。
  3. 数据传输:Cookie会在每次HTTP请求中自动发送给服务器,无法禁止其发送。而LocalStorage的数据不会自动发送给服务器,只存在于浏览器端。
  4. 生命周期:Cookie可以设置过期时间,使得数据在特定时间后失效。LocalStorage中存储的数据没有过期时间,除非被手动清除或通过代码删除。
  5. 访问权限:Cookie可以设置访问限制,例如HttpOnly属性可以防止客户端脚本直接访问,增加了一定的安全性。而LocalStorage的数据可以通过客户端脚本直接读取和修改。
  6. 用途:Cookie主要用于在客户端和服务器之间传递信息,例如会话管理、用户跟踪等。而LocalStorage主要用于在浏览器端存储持久化数据,例如用户偏好设置、本地缓存等。

6、宽高盒子100px padding20px 设置背景色,背景色多大

背景色的大小应该基于总体的盒子尺寸,即宽度和高度都为100像素减去内边距的值。在这种情况下,背景色的大小将是60像素 x 60像素,因为减去内边距的20像素后,剩余的空间为80像素 x 80像素。

7、怎么判断数组中存不存在一个值

1.使用Array.includes()方法:这个方法返回一个布尔值,指示数组中是否包含指定的值。

2.使用Array.indexOf()方法:这个方法返回要查找的值在数组中的索引,如果不存在则返回-1。

3.使用Array.find()方法:这个方法返回数组中满足条件的第一个元素,如果找不到则返回undefined

8、哪几种方法可以求出a数组和b数组的交集

1.使用Array.filter()Array.includes()方法:使用filter()方法遍历一个数组,并使用includes()方法检查另一个数组是否包含当前元素。

2.使用Array.filter()Set对象:使用Set对象创建一个集合,并利用filter()方法过滤出同时存在于两个数组中的元素。

3.使用Array.reduce()Array.includes()方法:使用reduce()方法迭代其中一个数组,并使用includes()方法判断另一个数组是否包含当前元素,从而构建出交集数组。

9、一个数组某一个值最接近数组的最大最小值的平均值

10、闭包的优点和缺点、闭包占内存会占别人的吗

优点:
  1. 记忆状态:闭包可以存储函数的内部状态,使得在函数执行完后,仍然可以访问和修改这些状态。这对于实现记忆化、延续等功能非常有用。
  2. 封装变量:闭包可以隐藏变量,只暴露必要的接口,提高了代码的模块性和安全性。
  3. 实现私有成员:通过闭包,可以创建私有变量和方法,限制对其的访问,从而实现封装和信息隐藏。
缺点:
  1. 内存占用:闭包会导致函数的词法环境一直存在于内存中,直到闭包不再被引用时才会被垃圾回收。如果闭包被大量使用或者不适当地使用,可能会导致内存泄漏或增加内存消耗。
  2. 性能损耗:由于闭包涉及到跨作用域的变量访问,相比普通函数调用,闭包的性能开销较大。因此,在性能要求较高的场景下,过度使用闭包可能会影响代码的执行效率。
  3. 安全性问题:闭包可能带来变量共享和意外的副作用,特别是在多线程或异步环境中。如果不正确地使用闭包,可能会导致出现意想不到的错误。

11、怎么在闭包外边读取到闭包里的值

  1. 返回函数:在闭包内部定义一个函数,并将其作为闭包的返回值。这样,在外部可以接收闭包返回的函数,并在外部调用该函数来获取闭包内的值。
  2. 使用对象属性:可以将闭包内的值存储在一个对象的属性上,然后通过访问对象的属性来获取闭包内的值。

12、怎么解决闭包内存泄漏

  1. 及时释放闭包引用:当不再需要使用闭包时,确保将对其的引用置为 null 或者解除引用,以便让垃圾回收机制回收相关的内存。
  2. 移除事件监听器:如果闭包中包含注册的事件监听器,需要在不再需要时手动移除这些监听器,以防止因为闭包引用导致的内存泄漏。
  3. 避免循环引用:在闭包和外部变量之间存在循环引用时,可能会导致内存泄漏。要注意避免此类情况的发生,确保及时解除循环引用。
  4. 使用 WeakMap 或 WeakSet:如果闭包需要引用外部对象,但不希望外部对象因闭包引用而无法被垃圾回收,可以使用 WeakMap 或 WeakSet 来存储外部对象。这样,当外部对象没有其他引用时,垃圾回收机制会自动清理相应的内存。
  5. 将需要持久化的数据移出闭包:如果闭包需要引用的数据比较大或者需要长时间保持,可以将这部分数据提取出闭包,并考虑其他合适的方式进行管理,避免闭包不必要地持有大量内存。

13、还有哪些会导致内存泄漏

14、innerText和innnerHtml区别

15、1 === true 返回true还是false

false 一个布尔值一个数字

16、怎么判断一个变量是数组还是函数还是对象

  1. 使用 typeof 运算符:

    • typeof 运算符返回一个表示变量类型的字符串。例如,typeof variable 返回 "number""string""boolean""function""object""undefined"
    • 使用 typeof 运算符可以判断变量是否为函数或对象。如果 typeof variable 返回 "function",则该变量是一个函数。如果返回 "object",则该变量是一个对象,但需要进一步判断其是否为数组。
  2. 使用 Array.isArray() 方法:

    • Array.isArray(variable) 是一个静态方法,用于判断变量是否为数组。
    • 如果 Array.isArray(variable) 返回 true,则该变量是一个数组。反之,如果返回 false,则该变量不是数组。

    七、中星智慧云企马

1、 Vue2和Vue3的区别

  1. 性能改进:Vue 3 在性能方面进行了一些优化。Vue 3 引入了基于 Proxy 的响应式系统,相较于 Vue 2 的基于 Object.defineProperty 的响应式系统,Proxy 具有更高的性能和更好的扩展性。
  2. Composition API:Vue 3 引入了 Composition API,这是一个新的组合式 API,允许开发者以逻辑功能为单位组织代码。相比于 Vue 2 的 Options API,Composition API 提供更灵活、可复用和可组合的代码组织方式。
  3. 更好的 TypeScript 支持:Vue 3 对 TypeScript 的支持更加完善,包括更准确的类型推导和更好的编辑器支持。
  4. 更小的体积:Vue 3 在体积方面进行了优化,相比 Vue 2,它的体积更小,同时还引入了 Tree-shaking 支持,可以更好地消除未使用的代码。
  5. 更好的渲染性能:Vue 3 使用了虚拟 DOM 的优化算法,提升了渲染性能。它引入了静态树提升和全局编译优化等技术,减少了不必要的 DOM 操作,提高了渲染效率。
  6. 更好的 TypeScript 支持:Vue 3 对 TypeScript 的支持更加完善,包括更准确的类型推导和更好的编辑器支持。
  7. 其他改进:除了上述几点,Vue 3 还对许多其他方面进行了改进,例如提供了更好的错误处理机制、改进的组件状态更新和变更追踪等。

2、对组合式API的理解,以及优点。

  1. 更好的代码组织:组合式 API 允许将相关的逻辑功能放在一个函数内,使得代码更加清晰、结构化和易于维护。不再需要按照选项的方式散落在不同的生命周期钩子函数中编写代码,而是按照功能进行组织。
  2. 更好的复用性:由于逻辑功能被划分为更小的函数,我们可以更容易地复用这些函数。可以在多个组件中使用相同的逻辑,避免了代码冗余。
  3. 更灵活的组合:组合式 API 提供了更灵活的组合方式。我们可以根据需要自由地组合和调用不同的函数,在不同的组件中共享逻辑,提高了代码的可组合性。
  4. 更好的类型推导和编辑器支持:组合式 API 在 TypeScript 支持方面更加完善,包括更准确的类型推导和更好的编辑器支持。这使得在开发过程中能够更早地发现错误,并提供更好的代码提示和自动补全功能。
  5. 更好的测试性:由于逻辑被划分为更小、独立的函数,我们可以更容易地对这些函数进行单元测试。这使得测试变得更加简单和可靠。

3、Vue3的 hopst和vueuse库

  1. @vueuse/core 库:这是一个 Vue 3 生态系统中的常用实用工具集合库。它提供了许多功能强大、易于使用的函数和组合式 API,用于增强 Vue 3 的开发体验。该库包含了各种强大的函数和组合式 API,涵盖了从状态管理到表单处理、副作用管理、时间处理等方面的工具。通过使用 @vueuse/core 库,你可以轻松地扩展和增强你的 Vue 3 应用程序。这个库是由 Vue.js 社区提供的,可以通过 npm 安装并在你的项目中使用。
  2. hoist-non-react-statics 库:这是一个用于 React 开发的库。它提供了一种将非静态方法从一个 React 组件复制到另一个组件的方法。虽然这个库与 Vue.js 本身没有直接关系,但在某些情况下,当你在 Vue 3 中使用 React 组件或在 Vue 3 和 React 之间进行混合开发时,可能会用到这个库。它可以确保在复制组件时,非静态方法也被正确传递和继承,以确保组件行为的一致性。

需要注意的是,虽然这两个库在不同的框架和场景中有其用途,但它们没有直接依赖关系。如果你在 Vue 3 的开发中使用 @vueuse/core 库,你无需关注 hoist-non-react-statics 库。同样地,如果你在 Vue 3 的开发中没有涉及 React 组件,则无需关注 hoist-non-react-statics 库。

4、Vue3如何实现Vue2的混入功能?(混入有什么问题?为什么业界不推荐用混入)

在 Vue 3 中,混入的功能被废弃了,取而代之的是组合式 API 的引入。混入是 Vue 2 中用于在多个组件中共享代码的一种方式,通过将选项对象混入到组件的配置中。

在 Vue 3 中,可以使用组合式 API 来实现类似的功能。你可以将逻辑相关的代码封装为一个独立的函数,然后在组件中使用 setup 函数来调用这些函数。这样可以更清晰地组织代码,并且提供了更好的可维护性和复用性。

为什么业界不推荐用混入
  1. 命名冲突:如果多个混入对象具有相同的属性或方法,可能会导致命名冲突和难以预测的行为。
  2. 全局影响:混入的内容会被注入到所有组件中,包括第三方组件,这可能会导致意外的行为和副作用。
  3. 代码可读性和维护性:当一个组件使用了多个混入对象时,可能会使代码变得复杂、难以理解和维护。特别是当混入对象之间存在依赖关系时,排查问题会变得困难。
  4. 隐式依赖关系:混入会引入隐式的组件依赖关系,使得组件的行为和状态变得难以追踪。

5、如何复用各组件的状态、逻辑?

  1. 首先,在你的Vue项目中安装并引入Vuex。
  2. 创建一个store.js文件,并定义你的状态和逻辑。
  3. 在main.js中引入并使用store。

6、项目中开发过哪些公用组件?

有的,在项目的common文件下会存放项目公用组件(如:页面的头组件、页面底部组件等)项目里的feature文件下则是放项目的功能组件(轮播图、分页器、模态框这些功能组件)把这些页面重复的部分抽离出来进度单独的封装,有效减少代码量,提升了项目的开发效率。解决了传统项目中效率低、难维护、复用性低等问题。

7、防抖的实现

防抖(Debounce)的实现方式有多种,下面介绍另一种常见的实现方式:基于时间戳的防抖。

function debounce(func, delay) {
  let timerId;
  
  return function(...args) {
    const context = this;
    
    clearTimeout(timerId);
    
    timerId = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

在这种实现方式中,使用了一个 timerId 变量来存储定时器的 ID,并且通过调用 clearTimeout 来清除之前设置的定时器。

与上述实现方式不同的是,延迟函数的执行是在延迟时间结束后进行,并且会在每次触发事件时先清除之前的定时器。这样做的好处是,如果在延迟时间内事件再次被触发,就会重新设置定时器,只有当事件停止触发的一段时间后,才会执行函数处理。

使用基于时间戳的防抖函数的示例代码如下:

function handleInput() {
  // 处理输入事件
}

const debouncedHandleInput = debounce(handleInput, 300);

inputElement.addEventListener('input', debouncedHandleInput);

在上述示例中,handleInput 函数将被防抖处理,并且在输入事件触发时执行。通过使用基于时间戳的防抖函数,可以确保 handleInput 函数在用户连续输入时只会在一定的延迟时间后执行,从而减少函数的频繁调用。

无论是基于定时器的防抖实现方式还是基于时间戳的防抖实现方式,选择哪种方式取决于具体需求和场景。两种方式都能达到防抖的效果,你可以根据实际情况选择适合的方式进行使用。

8、组件使用时如何进行通信

  1. Props/属性:通过父组件向子组件传递数据,可以通过在子组件上定义属性,父组件使用时传递相应的值。子组件可以通过this.props来访问传递的属性值。
  2. Callbacks/回调函数:父组件可以通过回调函数的方式将方法传递给子组件,使得子组件可以调用该方法与父组件进行通信。
  3. Context/上下文:上下文提供了一种跨组件层级传递数据的方式,适用于全局或跨多个组件共享的数据。
  4. Pub/Sub模式:通过订阅/发布机制实现组件之间的通信。可以使用第三方库(如Redux、EventEmitter等)或自行实现。

9、开发组件时注意哪些问题

  1. 设计良好的API:组件的API应该简洁、一致,并且易于理解和使用。考虑传递给组件的属性、事件处理函数等,确保它们的命名和用法符合预期。
  2. 可组合性和可重用性:设计组件时要考虑它们的可组合性和可重用性。尽量将组件设计成独立的、可嵌套的模块,以便能够在不同的上下文中复用。
  3. 状态管理:仔细考虑组件的状态管理方式。对于需要维护内部状态的组件,选择合适的状态管理方案,如React Hooks或状态容器(如Redux),确保状态变更的可控性和一致性。
  4. 性能优化:关注组件的性能,避免不必要的重新渲染和计算。使用PureComponent或React.memo来减少不必要的渲染,并使用shouldComponentUpdate生命周期方法来进行精确的控制。
  5. 错误处理:考虑组件可能出现的错误情况,并提供适当的错误处理机制。可以使用错误边界(Error Boundary)或错误处理回调函数来捕获和处理组件内的错误。
  6. 样式隔离:为组件提供样式隔离的机制,以确保组件的样式不会被外部的样式影响,并且组件的样式定义与其他组件的样式不冲突。
  7. 文档和示例:提供清晰、详细的文档和示例代码,帮助其他开发人员了解组件的用法和API。良好的文档能够降低学习成本并促进组件的广泛使用。
  8. 单元测试:编写适当的单元测试,覆盖组件的各种功能和边界情况。这可以帮助捕捉潜在的错误并确保组件在各种情况下的行为符合预期。
  9. 可访问性(Accessibility):关注组件的可访问性,确保组件对残障用户友好,并遵循Web Content Accessibility Guidelines(WCAG)的要求。
  10. 思考未来需求:在设计组件时,要考虑未来可能的需求变化和扩展。灵活的架构和可扩展性可以使组件在应对未来需求时更加容易维护和修改。

八、电投云碳**

1. 什么是箭头函数

答:

箭头函数是JavaScript中一种简化函数表达式的语法形式,它是在ES6(ECMAScript 2015)中引入的。

格式是 ()=> { }

箭头函数可以具有零个或多个参数,用括号括起来,如果只有一个参数,括号可以省略。箭头之后是函数体,可以是一个代码块或一个表达式。如果函数体只有一条简单的返回语句,可以简化为省略大括号的形式。

箭头函数没自己的this,和上级普通函数作用域或者全局函数作用域的this保持一致。

普通函数的this和普通函数的调用方式有关

箭头函数没有arguments,普通函数有arguments

箭头函数不能当做构造函数使用。箭头函数不能当成原型对象上的函数

2. 判断数据类型的方式

答:

1、typeof 操作符:可以通过typeof操作符检查一个值的类型,并返回一个字符串表示。

2、instanceof 操作符:可以用来检查一个对象是否属于某个特定的构造函数的实例。例如:const arr = []; arr instanceof Array // true

3、constructor 属性:每个对象都有一个 constructor 属性,可以通过比较该属性与某个构造函数来判断对象的类型。例如:const str = “Hello”; str.constructor === String // true

4、Object.prototype.toString() 方法:可以通过调用该方法来获取一个对象的内部类型标签。 例如:Object.prototype.toString.call([1, 2, 3]) // “[object Array]”

5、Array.isArray() 方法:可以判断一个值是否为数组类型。例如:Array.isArray([1, 2, 3]) // true

6、使用 typeof 和 null 来判断 null 类型:由于使用 typeof null 会返回 “object”,所以需要额外判断是否为 null 值。例如:const value = null; value === null // true

3. 什么是闭包

答:

闭包的形成:内部函数访问了外部函数的变量

闭包的作用:可以保护私有变量避免被全局污染

闭包的问题:因为标记清除法始终可以找到函数内部的私有变量,所以内存不会被回收,不会进行内存释放

解决闭包的问题: 把接受外部函数的变量重置为null

  1. call apply bind 的区别

答:

apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次;

call方法的第一个参数也是this的指向,后面传入的是一个参数列表,跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次;

bind,第一参数是this的指向,后面传入的是一个参数列表(但是这个参数列表可以分多次传入),改变this指向后不会立即执行,而是返回一个永久改变this指向的函数

5. 深浅拷贝

答:

浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝,如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址

深拷贝开辟一个新的栈,两个对象属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性;

常用克隆方式:

_.cloneDeep()

jQuery.extend()

JSON.stringify() 这个方式有一定问题,拷贝时会丢失undefined和函数,会把Date类型的数据 转成string

手写循环递归

6. 怎么解决跨域

答:

\1. 使用代理服务器:在服务器端设置一个代理服务器,将前端请求发送到代理服务器,再由代理服务器转发到目标服务器,这样就避免了跨域问题。

\2. JSONP:通过动态创建一个

\3. CORS(跨域资源共享):在目标服务器的响应头中设置Access-Control-Allow-Origin字段,允许指定的域名访问该资源。

\4. WebSocket:使用WebSocket协议进行通信,WebSocket协议没有同源策略限制,可以实现跨域通信。

7. 原型和原型链

答:

原型是JavaScript中的一个概念,它是用来实现对象之间的继承关系的。

在 JavaScript 中,每个对象中都有一个 proto 属性,这个属性指向了当前对象的构造函数的原型。对象可以通过自身的 proto__属性与它的构造函数的原型对象连接起来,而因为它的原型对象也有 __proto,因此这样就串联形成一个链式结构,也就是我们称为的原型链。

8. data为什么是一个函数

答:

在JavaScript中,data通常被用作一个函数的参数或返回值。这是因为data函数通常用于处理和操作数据。通过将数据作为参数传递给data函数,可以对数据进行处理、转换或过滤等操作,并返回处理后的结果。

9. computed和watch 的区别

答:

computed 是一个计算属性,它会根据依赖的数据动态地计算出一个新的值,并将该值缓存起来。当依赖的数据发生变化时, computed 会重新计算并返回新的值;watch 是一个侦听器,它会监听指定的数据变化,并在数据变化时执行相应的操作。

computed 适用于根据其他数据生成衍生数据的场景,而 watch 适用于监听数据变化并执行相应操作的场景。

10. vue中传值的方式

答:

\1. Props(父子组件通信):通过在父组件中使用属性绑定的方式将数据传递给子组件。子组件通过 props 选项接收父组件传递的值。

\2. Emit(子父组件通信):子组件可以通过 $emit 方法触发自定义事件,并将需要传递的值作为参数传递给父组件。父组件通过监听子组件触发的事件来获取传递的值。

\3. Vuex(全局状态管理):Vuex是Vue.js的官方状态管理库,用于在应用程序的各个组件之间共享状态。通过在Vuex中定义和修改状态,可以实现组件之间的数据传递。

\4. Provide/Inject(祖先后代组件通信):通过在祖先组件中使用 provide 选项提供数据,然后在后代组件中使用 inject 选项注入数据,实现祖先和后代组件之间的通信。

\5. Router(路由传参):在Vue Router中,你可以通过路由参数或查询参数来传递值。路由参数是在路由路径中定义的动态片段,而查询参数则是在URL中以键值对的形式出现。

11. vue中的路由模式

答:

1.Hash 模式(默认模式):在这种模式下,URL 中的路由路径会被格式化为 “#” 开头,后面跟着路由路径,Hash 模式的好处是它不需要服务器配置,可以直接在前端进行路由管理。缺点是 URL 中会有一个额外的 # 符号;

\2. History 模式:在这种模式下,URL 中的路由路径不再有 # 符号,而是直接使用正常的路径形式,为了支持 History 模式,需要在服务器上进行相应的配置,以确保在刷新页面 或直接访问某个路由时,服务器能正确地返回对应的页面。History 模式没有额外的 # 符 号,看起来更加美观,但需要服务器的支持。

  1. vue中的性能优化

答:

\1. 使用v-if和v-show指令:v-if指令会根据条件动态地添加或移除DOM元素,而v-show指令仅通过CSS来控制元素的显示与隐藏。如果需要频繁地切换元素的可见性,使用v-show会比v-if更高效。

\2. 避免不必要的响应式数据:在Vue组件中,所有的响应式数据都会被观察和追踪,当数据发生变化时,会触发重新渲染。因此,避免将不需要响应式的数据定义为响应式属性,可以减少不必要的观察和渲染开销。

\3. 使用computed属性:computed属性是基于它们的响应式依赖进行缓存的,只有在依赖发生变化时才会重新计算。因此,将一些复杂的计算逻辑放在computed属性中,可以避免重复计算,提高性能。

\4. 合理使用v-for指令:在使用v-for指令渲染列表时,尽量避免在每个循环中进行复杂的计算或方法调用。可以通过计算属性或在渲染之前对数据进行预处理,减少循环中的计算量。

\5. 使用key属性:在使用v-for指令渲染列表时,为每个项添加唯一的key属性。这样可以帮助Vue识别每个项的身份,减少不必要的DOM操作。

\6. 异步组件:对于一些较大的组件或不常用的组件,可以使用异步组件来延迟加载。这样可以提高初始加载速度,并在需要时才加载组件。

\7. 使用keep-alive组件:keep-alive组件可以缓存组件的状态和DOM结构,避免重复渲染和销毁。特别是对于频繁切换的组件,使用keep-alive可以提高性能。

\8. 优化网络请求:合理使用缓存、压缩和CDN等技术,减少网络请求的数量和大小,提高页面加载速度。

\9. 使用Vue Devtools进行性能分析:Vue Devtools是一个浏览器扩展,可以帮助你分析Vue应用程序的性能瓶颈和优化点。通过分析组件的渲染时间、触发的更新次数等指标,可以找到性能问题的根源并进行优化。

13. 怎么把远程图片绘制到canvans

答:

​ \1. 创建一个Canvas元素和一个2D上下文;

​ \2. 创建一个Image对象,并设置其src属性为远程图片的URL;

​ \3. 等待图片加载完成后,将其绘制到Canvas中;

​ \4. 最后,可以将Canvas元素添加到页面中,或者将Canvas转换为图片;

​ \5. 通过调用 toDataURL() 方法,可以将Canvas转换为一个Base64编码的图片数据URL,可以用于显示图片或保存图片

14. 响应式布局

答:

响应式布局是指在不同的设备和屏幕尺寸下,网站或应用程序可以自动调整布局和样式,以适应不同的屏幕大小和设备类型。

实现响应式布局的关键是使用CSS媒体查询。媒体查询是一种CSS技术,可以根据不同的设备和屏幕尺寸来应用不同的样式。

15. 后台审批的流程 介绍一下 具体怎么实现的

答:

\1. 用户提交请求:用户通过前台界面或表单提交请求或申请;

\2. 后台接收请求:后台服务器接收到用户提交的请求,并将其存储在数据库中,或者将请求信息发送给相关审批人员;

\3. 审批人员处理请求:审批人员通过后台管理界面查看待审批的请求列表,选择一个请求进行处理;

\4. 审批操作:审批人员可以对请求进行不同的操作,例如批准、拒绝、转交等。这些操作可以通过按钮、下拉菜单或其他交互元素实现;

\5. 更新请求状态:根据审批人员的操作,后台服务器更新请求的状态,并将结果存储在数据库中;

\6. 通知用户:根据请求的状态更新,后台服务器可以发送通知给相关用户,告知他们审批结果;

\7. 审批流程追踪:后台管理界面可以提供审批流程追踪功能,让审批人员和管理员查看每个请求的审批历史和状态。

16. 有多少种不同分辨率的大屏

答:

\1. Full HD (1920x1080):这是目前最常见的大屏分辨率之一,它提供了1920个水平像素和1080个垂直像素的显示;

\2. 2K (2560x1440):这是一种中等分辨率的大屏,提供了比Full HD更高的像素密度,适用于需要较高清晰度的应用;

3.4K Ultra HD (3840x2160):这是一种高分辨率的大屏,提供了比Full HD四倍多的像素,具有更高的清晰度和细节;

4.5K (5120x2880):这是一种较高分辨率的大屏,提供了比4K Ultra HD更高的像素密度,适用于需要更高清晰度和更大工作空间的应用;

\5. 8K Ultra HD (7680x4320):这是一种更高级别的大屏分辨率,提供了比4K Ultra HD四倍多的像素,更加逼真和细腻的图像显示。

十一、北京星球节点传媒文化有限公司

1.如何解决闭包内存泄漏问题

闭包内存泄漏是指由于闭包引用了外部函数的变量,导致无法释放这些变量的内存,从而造成内存泄漏。下面是一些解决闭包内存泄漏问题的方法:

  1. 及时释放引用:在不需要使用闭包时,手动将闭包引用的外部变量置为null。这样可以告诉JavaScript引擎,这些变量可以被垃圾回收。
  2. 使用模块化的方式:将闭包限制在模块的作用域内,避免将闭包暴露到全局作用域。当模块不再被使用时,模块的作用域及其中的变量会被销毁,从而避免内存泄漏。
  3. 避免循环引用:确保闭包中不引用外部函数本身或其作用域链上的变量。这样可以避免形成循环引用,使得闭包无法被释放。
  4. 解绑事件处理器:如果闭包被用作事件处理器,在不需要使用时,应该手动解绑事件绑定。否则,事件处理器会持有对DOM元素的引用,导致内存泄漏。
  5. 使用WeakMap或WeakSet:在某些情况下,可以使用WeakMap或WeakSet来存储闭包以及外部对象的引用。WeakMap和WeakSet会自动处理键或值的引用,当引用被垃圾回收时,相应的条目也会被自动删除,从而避免内存泄漏。
  6. 尽量减少闭包的使用:尽量避免滥用闭包,只在必要的情况下使用。如果没有特殊需求,可以考虑使用其他方法替代闭包,以减少潜在的内存泄漏问题。

2.es6新增的语法有哪些

  1. 块级作用域(Block Scope):引入了let和const关键字,可以在块级作用域内声明变量和常量,避免了变量提升和污染全局作用域的问题。
  2. 箭头函数(Arrow Functions):使用箭头函数语法可以更简洁地定义函数,并且自动绑定上下文中的this值。
  3. 默认参数(Default Parameters):函数参数可以指定默认值,如果调用函数时没有传递对应参数,则会使用默认值。
  4. 模板字符串(Template Strings):使用反引号``包裹字符串,可以在字符串中嵌入变量、表达式,并支持多行字符串和字符串插值。
  5. 解构赋值(Destructuring Assignment):可以将数组或对象的属性解构赋值给变量,方便地提取和使用其中的值。
  6. 扩展运算符(Spread Operator):使用…语法可以在函数调用、数组、对象等地方进行展开操作,方便地合并数组、复制数组或对象,并且可以用于获取剩余参数。
  7. 类(Class):引入了class关键字,可以更方便地定义类和创建对象,支持面向对象的特性如继承、封装和多态。
  8. 模块化(Modules):引入了import和export关键字,可以将代码分割成多个模块,从而实现更好的组织、管理和重用。
  9. Promise:Promise是一种处理异步操作的新的机制,通过Promise对象可以更优雅地编写异步代码,并且支持链式调用、错误处理等。
  10. 迭代器和生成器(Iterators and Generators):引入了迭代器协议和生成器函数,使得遍历数据结构变得更加灵活和可控。

5.const定义的对象能不能修改

使用const关键字声明的对象是不允许被重新赋值的,即不能将整个对象指向新的对象。这意味着,一旦用const声明一个对象后,不能再将其指向另一个对象。例如:

const obj = { name: 'Alice', age: 25 };

obj = { name: 'Bob', age: 30 }; // 错误,不能将obj指向新的对象

但是,const并不表示对象本身是不可变的,只是保证了变量在声明后不能重新赋值。这意味着,虽然不能修改const对象的指向,但可以修改对象中的属性值。例如:

const obj = { name: 'Alice', age: 25 };

obj.age = 30; // 可以修改对象的属性值

console.log(obj); // 输出:{ name: 'Alice', age: 30 }

总结起来,使用const声明的对象保持了对相同对象的引用不变,但是可以修改对象中的属性值。如果需要完全禁止修改对象,可以使用Object.freeze()方法对对象进行冻结,使其属性不可修改。

6.Set和Map有什么区别[此Map非彼map]

Set和Map是两种不同的数据结构

  1. Set(集合):

    • Set是一种无重复元素的集合,其中每个元素只能出现一次。它类似于数组,但与数组不同,Set没有索引,不能通过索引访问元素。
    • Set中的元素是唯一的,如果尝试向Set中添加已存在的元素,则该操作将被忽略。
    • Set是可迭代的,可以使用for…of循环遍历其中的元素。
    • Set的主要用途是去除数组或字符串中的重复元素,判断某个元素是否存在,以及对数据进行去重等操作。
  2. Map(映射):

    • Map是一种键值对的集合,其中的每个元素由一个键(key)和一个值(value)组成。键和值可以是任意类型的值,允许重复的值存在,但键必须是唯一的。
    • Map中的元素是按添加顺序排序的,与对象不同,Map保留了插入顺序。
    • Map提供了一系列的操作方法,如添加元素、删除元素、获取元素以及判断某个键是否存在等。
    • Map是可迭代的,可以使用for…of循环遍历其中的键或值。
    • Map的主要用途是存储和查找键值对,常用于存储需要快速查找的数据结构。

    7.Symbol、generator、promise有了解吗

  3. Symbol(符号):

    • Symbol是ECMAScript 6引入的一种新的原始数据类型。
    • 每个Symbol值都是唯一的,可以用作对象属性的标识符。
    • Symbol可以用来创建对象的私有属性或类的方法名,以避免命名冲突。
    • Symbol还提供了一些内置的符号(如Symbol.iterator),用于自定义对象的行为。
  4. Generator(生成器):

    • Generator是一种特殊类型的函数,它可以生成多个值的序列。
    • Generator函数使用function*关键字定义,并通过yield关键字产生多个值。
    • 调用Generator函数时,它返回一个迭代器对象,可以使用next()方法依次获取生成器中的值。
    • 生成器函数可以通过暂停和恢复的机制,实现更灵活的控制流程,比如异步编程中的协程等。
  5. Promise(承诺):

    • Promise是一种处理异步操作的机制,用于更优雅地处理和组织异步代码。
    • Promise表示一个异步操作的最终结果,它可以是成功的值或失败的原因。
    • Promise具有三个状态:Pending(进行中)、Fulfilled(已成功)和Rejected(已失败)。
    • 可以通过调用then()方法注册回调函数,处理Promise的成功和失败情况。
    • Promise还支持链式调用,通过返回新的Promise对象实现对异步操作的串行或并行组合。

8. promise常用的方法有哪些

  1. then(onFulfilled, onRejected)

    • 该方法用于注册Promise的成功和失败的回调函数。
    • 第一个参数 onFulfilled 是当Promise成功时要执行的回调函数。
    • 第二个参数 onRejected 是当Promise失败时要执行的回调函数。
    • 可以通过链式调用多个then()方法来处理Promise的连续操作。
  2. catch(onRejected)

    • 该方法用于注册Promise的失败回调函数,相当于then(null, onRejected)
    • 当Promise被拒绝时(即发生错误),就会调用该回调函数。
  3. finally(onFinally)

    • 该方法用于注册Promise的最终回调函数,无论Promise是成功还是失败都会执行。
    • 通常用于清理工作或执行一些不管成功与否都需要完成的操作。
  4. Promise.resolve(value)

    • 该方法返回一个已解析(已完成)的Promise对象,将给定的值作为成功的结果。
    • 如果给定的值本身就是一个Promise对象,则直接返回该对象。
  5. Promise.reject(reason)

    • 该方法返回一个拒绝(失败)的Promise对象,将给定的原因作为失败的结果。
  6. Promise.all(iterable)

    • 该方法返回一个由所有Promise对象组成的新Promise对象。
    • 当所有的Promise都成功解决时,新的Promise对象才会解决,并将所有Promise的结果作为一个数组传递给回调函数。
    • 如果其中任何一个Promise被拒绝,则新的Promise对象将立即被拒绝,并将第一个被拒绝的Promise的原因作为失败的结果。
  7. Promise.race(iterable)

    • 该方法返回一个新的Promise对象,一旦迭代器中的任何一个Promise解决或拒绝,新的Promise对象就会解决或拒绝。
    • 结果是第一个完成的Promise的结果(无论是解决还是拒绝)。

    9.created中能操作dom吗

    在Vue的created钩子函数中,可以进行DOM相关操作,但是需要注意一些细节。

    created钩子函数中,Vue实例已经创建完成,但尚未挂载到DOM上。因此,在这个阶段,你可以访问Vue实例上的数据和方法,但无法直接访问或修改DOM元素。

    如果需要在created钩子中进行DOM操作,可以通过Vue提供的$nextTick方法来实现延迟执行。$nextTick会在DOM更新后执行回调函数,可以确保在该回调函数中进行DOM操作时,DOM已经完全渲染完成。

    下面是一个示例,演示了在created钩子中使用$nextTick进行DOM操作:

    created() {
      this.$nextTick(() => {
        // DOM已经更新完毕,可以操作DOM
        const element = document.getElementById('myElement');
        // 进行DOM操作...
      });
    }
    

    需要注意的是,在created钩子中进行DOM操作可能会导致一些潜在的问题,特别是在组件被多次复用的情况下。因为在Vue中,组件的created钩子在每次组件实例化时都会被触发,而不仅仅是在初始化时。因此,如果在created钩子中注册事件监听器或进行其他DOM操作,请确保在组件销毁时进行相应的清理工作,以避免内存泄漏或其他问题。

    总之,在created钩子中可以进行DOM操作,但需要使用$nextTick来确保操作在DOM更新后执行,同时注意处理好相关的生命周期和清理工作。

    11. vue中双向绑定的语法有哪些 (v-model,.sync)

    1. 使用v-model指令:
      • v-model指令是Vue提供的一种简单的双向绑定语法。
      • 它可以直接应用在表单元素上(如input、textarea、select等),将表单元素的值与Vue实例的属性进行双向绑定。
      • 例如:<input v-model="message">
    2. 使用.sync修饰符:
      • .sync修饰符是Vue提供的另一种双向绑定语法。
      • 它可以应用在自定义组件上,通过与父组件之间进行属性的双向绑定。
      • 必须使用.sync修饰符和$emit方法配合使用,使得父组件能够接收到子组件的更新,并更新父组件的属性。
      • 例如:<my-component :value.sync="message"></my-component>
    3. 使用.lazy修饰符和input事件:
      • 在使用v-model绑定输入框时,默认情况下会通过input事件实时更新绑定的属性值。
      • 使用.lazy修饰符可以改变默认行为,将更新推迟到change事件触发时。
      • 例如:<input v-model.lazy="message">
    4. 使用.number修饰符和.trim修饰符:
      • .number修饰符可以将用户输入的值自动转换为数字类型。
      • .trim修饰符可以自动去除用户输入的首尾空格。
      • 例如:<input v-model.number="quantity"><input v-model.trim="name">

    12.computed和watch的区别

    在Vue中,computedwatch都是用于监听数据变化并做出相应处理的功能,但它们之间有一些重要的区别。

    1. 计算属性(computed):
      • computed是一个计算属性,它根据依赖的数据动态计算衍生出一个新的值。
      • computed会缓存计算结果,在依赖的数据没有变化时,多次访问该计算属性会直接返回缓存的结果,不会重新计算。
      • computed一般用来处理需要进行复杂逻辑运算或基于多个属性计算得出的结果。
      • 它可以像普通属性一样在模板中使用,通过this访问Vue实例上的其他属性,并且会在依赖发生改变时自动更新。
    2. 侦听属性(watch):
      • watch用于监听指定的数据变化,并在数据变化时执行相应的回调函数。
      • watch适用于当需要执行异步操作或复杂逻辑处理时,或者需要在数据变化时执行特定的副作用操作时。
      • 监听的数据发生变化时,回调函数将被调用,可以获取到新值(newVal)和旧值(oldVal)。
      • watch还可以监听嵌套对象或数组内部的属性的变化。

    综上所述,computed适用于计算衍生数据,而watch适用于观察数据的变化并作出相应的响应。通常情况下,应该优先考虑使用computed,因为它具有缓存和自动更新的特性,能够减少不必要的计算,提高性能。而watch则更适合处理异步、开销较大或需要特殊处理的情况。

    13.事件的修饰符有哪些

    在Vue中,事件修饰符是一种用于修改事件行为的特殊标记。下面列举了常用的事件修饰符:

    1. .stop:阻止事件继续传播,相当于调用了 event.stopPropagation()
    2. .prevent:阻止事件的默认行为,相当于调用了 event.preventDefault()
    3. .capture:使用事件捕获模式,即在祖先元素上触发事件,然后再由内向外进行传播。
    4. .self:只有当事件发生在绑定了事件的元素自身时才触发事件回调,而不是子元素触发的事件。
    5. .once:事件只会触发一次,之后自动解绑该事件。
    6. .passive:告知浏览器该事件监听器不会调用 event.preventDefault(),从而提高滚动性能。

    这些修饰符可以同时使用,以点号(.)分隔。例如,.stop.prevent 可以同时阻止事件传播和阻止默认行为。

    示例用法:

    <!-- 阻止事件传播和默认行为 -->
    <button @click.stop.prevent="doSomething">Click</button>
       
    <!-- 使用事件捕获模式 -->
    <div @click.capture="doSomething">...</div>
       
    <!-- 只有点击元素自身才触发事件 -->
    <div @click.self="doSomething">...</div>
       
    <!-- 事件只会触发一次 -->
    <button @click.once="doSomething">Click</button>
       
    <!-- 提高滚动性能 -->
    <div @touchstart.passive="handleTouch">...</div>
    

    通过使用这些事件修饰符,我们可以更灵活地控制和修改事件的行为,以满足特定的需求。

    14.前端优化层面有哪些手段

    1. 减少HTTP请求:合并、压缩和缓存文件,减少页面的请求数量。
    2. 压缩和优化资源:对CSS、JavaScript和图像等静态资源进行压缩和优化,减小文件体积。
    3. 使用CDN加速:使用内容分发网络(CDN)来分发静态资源,从离用户更近的服务器加载资源,提高加载速度。
    4. 延迟加载:将不是首次访问即可见的内容(如图片、视频等)进行延迟加载,以提高页面初始加载速度。
    5. 使用缓存机制:合理设置资源的缓存策略,利用浏览器缓存和服务端缓存,减少重复请求和数据传输。
    6. 优化CSS和JavaScript:避免使用过多的样式和脚本,尽量精简代码,消除冗余。
    7. 异步加载脚本:将页面中的JavaScript脚本标记为异步加载,防止阻塞页面渲染。
    8. 懒加载:仅加载当前视窗中可见的部分内容,滚动到可见区域时再加载其他内容,提升页面加载速度。
    9. 优化图片:选择合适的图片格式、使用适当的压缩率,以及应用响应式图片等技术来优化图像加载和显示。
    10. 减少重绘和回流:避免频繁的DOM操作和样式变更,以及使用CSS动画替代JavaScript动画等方式,减少浏览器的重绘和回流。
    11. 代码拆分和按需加载:将代码按照模块划分,分割成多个小文件,并根据需要进行按需加载,减少初始加载量。
    12. 使用Web Workers:将一些耗时的计算任务放到Web Workers中执行,避免阻塞主线程,提高页面的响应性。

    15.高德、百度的Api只能定位到省市区,想要精确到街道怎么做?

十二、模拟面试

1.组件里data为什么一定要函数

在Vue组件中,data选项可以是一个对象或一个函数。然而,官方推荐将data选项定义为一个函数的原因是为了确保每个组件实例都有独立的数据副本。

data选项是一个对象时,这个对象会被所有的组件实例共享,这意味着如果一个组件实例修改了data中的属性,其他组件实例也会受到影响,因为它们引用的是同一个对象。

为了避免这种共享数据的情况,我们可以将data选项定义为一个返回对象的函数。每次创建组件实例时,Vue都会调用data函数来返回一个全新的数据对象,这样每个组件实例都会有自己的独立的数据副本,彼此之间不会互相影响。

使用函数形式的data选项,示例如下:

Vue.component('my-component', {
  data: function() {
    return {
      message: 'Hello, world!'
    };
  }
});

需要注意的是,data函数中的返回的对象中的属性将成为组件实例的响应式数据。这意味着当这些属性的值发生变化时,Vue能够检测到并更新相关的视图。如果直接在data函数中返回一个非响应式的对象,那么这些属性的变化将不会触发视图的更新。

综上所述,使用函数形式的data选项可以保证每个组件实例都具有独立的数据副本,避免了共享数据带来的问题,是一种更安全和推荐的做法。

2.new操作符做了什么

  1. 创建一个空的普通JavaScript对象。
  2. 将这个新对象的原型链指向构造函数(即创建这个对象的函数)的原型对象。这允许新对象继承构造函数的属性和方法。
  3. 使用新对象作为this上下文,调用构造函数,并传入任何参数。构造函数中的代码可以操作新对象,将属性和方法添加到新对象上。
  4. 如果构造函数返回一个对象,那么这个对象将被作为new表达式的结果返回。否则,将返回创建的新对象。

3.pinia持久化插件

在Vue应用程序中使用Pinia状态管理库时,可以通过插件来实现状态的持久化。下面是一个简单的示例,展示了如何使用pinia-plugin-persist插件将状态保存到本地存储中:

首先,安装pinia-plugin-persist插件:

bash复制代码npm install pinia-plugin-persist

然后,在你的Vue应用程序的入口文件(通常是main.js或main.ts)中引入和使用插件:

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import { createPersist } from 'pinia-plugin-persist';

const app = createApp(App);

const pinia = createPinia();
pinia.use(createPersist({
  // 配置持久化插件的选项
  // 这里我们使用localStorage作为存储引擎,可以根据需要选择其他引擎
  storage: localStorage, 
}));

app.use(pinia);

app.mount('#app');

最后,在Pinia的store模块中,可以通过在store的persist属性上添加装饰器来指定要进行持久化的状态:

import { defineStore } from 'pinia';

export const useMyStore = defineStore({
  id: 'myStore',
  state: () => ({
    count: 0,
  }),
  persist: true, // 将该状态进行持久化
});

在上述示例中,createPersist函数用于创建一个持久化插件的实例,并通过pinia.use()方法将插件添加到Pinia实例中。然后,在store模块中,通过设置persist属性为true来指定要进行持久化的状态。

这样,在使用Pinia时,通过pinia-plugin-persist插件将被标记为persist: true的状态自动保存在指定的存储引擎中,以便在页面刷新或重新加载后仍然保持状态。

请注意,持久化插件的配置选项可以根据需要进行调整,比如设置键名、过期时间等。详细的配置信息可以参考pinia-plugin-persist插件的文档。

07-29补充

unknown和any的区别

js和ts中的typeof

ts中的typeof可以获取类型

ts中的keyof

keyof 一般用于获取 对象类型 键的联合类型

keyof any这个比较特殊可以了解下

类型守卫与extends约束

    function fn<T>(value: T) {
        console.log(value);
    }
    fn(1)
    fn('1')
    fn([1, 2, 3])

如果我们打印value.length会发生什么事,发现直接就报错了,解决方式可以有2种

    // 类型守卫
    function fn<T>(value: T) {
        if (typeof value === 'string' || Array.isArray(value)) {
            console.log(value.length);
        }
    }
    // extends做约束
	interface ILength {
        length: number
    }

    function fn<T extends ILength>(value: T) {
        console.log(value.length);
    }
    // fn(1) // error
    fn('1')
    fn([1, 2, 3])

初识类型编程

需求 实现getVal,获取对象中的健的值

let obj1 = {a: 1, b: 2}
// getVal(obj1, 'a')

但我们发现,调用getVal的时候,对于对象的键没有提示,该如何做呢,这里就要用到类型编程了

function getVal <O extends object, K extends keyof O>(obj: O, key: K) {
        return obj[key]
}

初识Performance

Performance 工具最重要的是分析主线程的 Event Loop,分析每个 Task 的耗时、调用栈等信息。

准备代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>worker performance optimization</title>
</head>
<body>
    <script>
        function a() {
           b();
        }
        function b() {
            let total = 0;
            for(let i = 0; i< 10*10000*10000; i++) {
                total += i;
            }
            console.log('b:', total);
        }

        a();
    </script>
    <script>
        function c() {
            d();
        }
        function d() {
            let total = 0;
            for(let i = 0; i< 1*10000*10000; i++) {
                total += i;
            }
            console.log('c:', total);
        }
        c();
    </script>
</body>
</html>

初识worker

接下去就是要优化long task了,有同学可能会问:为什么要优化 long task 呢?

因为渲染和 JS 执行都在主线程,在一个 Event Loop 中,会相互阻塞,如果 JS 有长时间执行的 Task,就会阻塞渲染,导致页面卡顿。所以,性能分析主要的目的是找到 long task,之后消除它。

那么大循环能不能不放在主线程跑,放到其他线程跑呢?浏览器的 web worker 就是做耗时计算的性能优化的。

关于worker可以查看mdn文档

worker.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        const worker = new Worker('worker.js')
        worker.postMessage('你好呀')
        worker.addEventListener('message', e => {
            console.log(e.data);
        })
    </script>
</body>
</html>
// worker.js
addEventListener('message', e => {
    console.log(e.data);
    postMessage('你也好呀')
})

封装Worker代码

worker.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function runWorker(url, num = 100) {
            return new Promise((resolve, reject) => {
                const worker = new Worker(url)
                worker.postMessage(num)
                worker.addEventListener('message', e => {
                    resolve(e.data)
                })
                worker.addEventListener('error', e => {
                    reject(e)
                })
            })
        }
        runWorker('worker.js').then(res => {
            console.log({ res });
        })
    </script>
</body>

</html>
// worker.js
addEventListener('message', e => {
    const num = e.data
    let total = 0;
    for (let index = 1; index <= num; index++) {
        total += index   
    }
    postMessage(total)
})

用Worker优化

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>worker performance optimization</title>
</head>

<body>
    <script>
        function runWorker(url, num = 100) {
            return new Promise((resolve, reject) => {
                const worker = new Worker(url)
                worker.postMessage(num)
                worker.addEventListener('message', e => {
                    resolve(e.data)
                })
                worker.addEventListener('error', e => {
                    reject(e)
                })
            })
        }
    </script>
    <script>
        function a() {
            b();
        }
        function b() {
            // let total = 0;
            // for (let i = 0; i < 10 * 10000 * 10000; i++) {
            //     total += i;
            // }
            // console.log('b:', total);
            runWorker('worker.js', 10 * 10000 * 10000).then(total => {
                console.log('b:', total);
            })
        }

        a();
    </script>
    <script>
        function c() {
            d();
        }
        async function d() {
            // let total = 0;
            // for (let i = 0; i < 1 * 10000 * 10000; i++) {
            //     total += i;
            // }
            // console.log('c:', total);
            const total = await runWorker('worker.js', 10000 * 10000)
            console.log('c:', total); 
        }
        c();
    </script>
</body>

</html>