1、vue的双向绑定原理是什么?里面的关键点在哪里?
Vue 2 的双向绑定原理:
数据劫持(Object.defineProperty):Vue 通过使用 Object.defineProperty()方法劫持数据对象,为每个属性添加 getter 和 setter。当数据发生变化时,会触发 setter,从而通知相关的视图进行更新。模板解析:Vue 解析模板,找到其中的插值表达式和指令,并建立与数据的依赖关系。监听器(Watcher):Vue 通过监听器(Watcher)来跟踪依赖关系。当数据发生变化时,Watcher
会接收到通知,并触发视图的更新。虚拟 DOM:Vue 使用虚拟 DOM 来提高渲染性能。在更新视图前,Vue 会先生成虚拟 DOM,然后与之前的虚拟 DOM
进行比较,找出差异并更新只有变化的部分。
Vue 3 的双向绑定原理: Vue 3 在双向绑定方面进行了优化,采用了 Proxy 对象来替代
Object.defineProperty,提供了更高效和更灵活的双向绑定机制。
具有更强大的拦截功能,可以拦截到对象的更多操作,比如属性的读写、删除、迭代等。通过 Proxy,Vue
可以实现对整个对象的监听,而不仅限于属性。依赖跟踪:Vue 3 使用了基于 Proxy
的依赖跟踪系统。当访问响应式数据时,会创建一个“依赖项”,用于追踪该数据的依赖关系。当数据发生变化时,可以精确地通知相关的“依赖项”进行更新,避免了不必要的更新操作。编译优化:Vue 3 引入了编译优化,利用静态模板分析和编译器的优化选项,生成更高效的渲染函数。这样可以减少运行时的开销,提升性能。
关键点:
数据劫持(Vue 2):通过 Object.defineProperty() 方法劫持数据对象,为每个属性添加 getter 和setter,实现对数据的观测和监听。Proxy 对象(Vue 3):使用 Proxy 对象来代理数据对象,实现对整个对象的监听,拦截对象的各种操作。虚拟 DOM:使用虚拟 DOM 来提高渲染性能,通过比较差异更新只有变化的部分,减少实际 DOM 操作。依赖跟踪:通过依赖跟踪系统,追踪数据的依赖关系,精确通知相关的依赖项进行更新,避免不必要的更新操作。编译优化(Vue 3):引入编译优化,生成更高效的渲染函数,减少运行时的开销。
总结:
Vue 2 使用 Object.defineProperty 进行数据劫持,利用虚拟 DOM 和监听器实现双向绑定。而 Vue 3 则采用了
Proxy 对象替代 Object.defineProperty,通过依赖跟踪系统来实现更高效和更灵活的双向绑定。同时,Vue 3
引入了编译优化,提升性能。
2、实现水平垂直居中的方式?
实现水平垂直居中的方式有很多种,以下是一些常见的方法:
Flex布局:将父元素设为flex布局,通过justify-content和align-items属性,可以轻松实现子元素的水平和垂直居中。.container { display: flex; justify-content: center; align-items: center;}
Grid布局:将父元素设为grid布局,通过place-items属性,可以轻松实现子元素的水平和垂直居中。 .container { display: grid; place-items: center;}
使用绝对定位和transform属性:将子元素设为绝对定位,然后通过transform的translate方法来实现居中。 .child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);}
使用margin: auto;当元素设置了绝对定位并且left/right和top/bottom都设置为0的时候,通过设置margin: auto可以实现居中。 .child { position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto;}
以上是一些最常见的实现方法,选择哪一种取决于具体的使用场景和兼容性需求。
3、常用伪元素有哪一些?
常用的伪元素有以下几种:
::before:用于在元素内容之前插入生成的内容,通常用于添加装饰性的元素或图标。
::after:用于在元素内容之后插入生成的内容,同样也常用于添加装饰性的元素或图标。
::first-line:选中元素的第一行文本,可以用于改变首行的样式,如颜色、字体大小等。
::first-letter:选中元素的第一个字母,可以用于改变首字母的样式,如颜色、字体大小等。
::selection:选中用户在页面上进行的文本选择,可以用于改变选中文本的样式,如背景色、前景色等。
::placeholder:选中表单元素的占位符文本,可以用于改变占位符文本的样式。
除了以上列举的常用伪元素,还有其他一些伪元素可以用于特定的情况。需要注意的是,伪元素必须配合 content 属性使用,来定义要插入的内容。
示例:
.element::before { content: "前置内容";}.element::after { content: "后置内容";}p::first-line { color: red;}p::first-letter { font-size: 24px;}input::placeholder { color: gray;}::selection { background-color: yellow; color: black;}
以上是一些常见的伪元素,它们可以通过 CSS 选择器和样式来对页面进行更加细致的控制和美化。
4、移动端如何适配不同屏幕尺寸?
移动端适配不同屏幕尺寸主要有以下几种方式:
媒体查询(Media Query): 是 CSS3 中的一项功能,可以根据设备的视口宽度来应用不同的 CSS 样式。例如:@media screen and (max-width: 480px) { body { background-color: lightgreen; }}
这段代码的意思是,当设备的屏幕宽度小于或等于 480 像素时,背景颜色将被设置为淡绿色。
百分比布局: 使用百分比来设置元素的宽度,可以使元素的宽度随着屏幕或父元素的宽度变化而变化,从而实现响应式布局。
REM 布局: rem 是一个相对单位,1rem 等于根元素(html)的字体大小。通过在 CSS 中使用 rem 单位,可以让元素的大小随着根元素的字体大小变化而变化。在 JavaScript 中动态设置 html 的字体大小,可以实现在不同屏幕尺寸下的适配。
Viewport 单位: vw、vh、vmin 和 vmax 是相对于视口的长度单位,可以实现元素大小随视口变化而变化。
<meta name="viewport" content="width=device-width, initial-scale=1.0">
使用框架: 有很多前端框架(如 Bootstrap、Foundation 等)已经内置了响应式设计的 CSS 样式,可以直接使用。
使用插件: 有些插件,如 postcss-px-to-viewport,可以将 px 单位转化为 vw 单位,也可以实现不同屏幕尺寸的适配。
5、本地存储有哪一些?他们三者有什么区别?
在Web开发中,常见的本地存储方式有Cookie、LocalStorage和SessionStorage。它们之间有以下区别:
Cookie:Cookie 是由服务器发送给浏览器并存储在用户计算机上的小型文本文件。它可以在浏览器和服务器之间传递数据,并在后续请求中被包含在HTTP头部中发送回服务器。Cookie通常用于存储用户的身份验证令牌、跟踪会话信息等。Cookie 的特点包括: 存储容量较小(通常为几KB)。可以设置过期时间,在过期前一直保留在用户计算机上。可以在浏览器和服务器之间传递数据。 LocalStorage:LocalStorage 是HTML5中提供的一种本地存储机制,可以在浏览器端长期存储数据。LocalStorage 的特点包括: 存储容量较大(通常为几MB)。数据存储在浏览器中,并且不会随着页面刷新或关闭而消失。可以通过JavaScript API进行读取和写入操作。存储的数据没有过期时间,除非手动清除。 SessionStorage:SessionStorage 也是HTML5中提供的一种本地存储机制,与LocalStorage 类似,但数据只在当前会话期间有效。SessionStorage 的特点包括: 存储容量与LocalStorage 相同(通常为几MB)。数据存储在浏览器中,在当前会话期间有效。可以通过JavaScript API进行读取和写入操作。数据在会话结束后会被清除,关闭浏览器或标签页也会导致数据丢失。
总结来说,Cookie 是一种在浏览器和服务器之间传递数据的机制,存储容量较小,可以设置过期时间;LocalStorage 和 SessionStorage 是HTML5 中提供的本地存储机制,存储容量较大,可以通过JavaScript API 进行读写操作,但 SessionStorage 的数据在会话结束后会被清除。根据实际需求选择合适的本地存储方式。
6、JS的数据类型?如何判断js的数据类型?
JavaScript 中的数据类型包括基本数据类型和引用数据类型:
基本数据类型(Primitive Data Types):它们的值会直接存储在栈内存
中
堆内存
中。当我们创建一个引用数据类型的变量时,栈内存中存储的是该变量对应对象在堆内存中的地址,而实际的值存储在堆内存中 Object(对象)Array(数组)Function(函数)Date(日期)RegExp(正则表达式)等等 要判断 JavaScript 中的数据类型,可以使用 typeof 操作符或 instanceof 操作符:
使用 typeof 操作符:
typeof "Hello" // 返回 "string"typeof 42 // 返回 "number"typeof true // 返回 "boolean"typeof null // 返回 "object"(这是 JavaScript 的一个历史遗留问题)typeof undefined // 返回 "undefined"typeof {} // 返回 "object"typeof [] // 返回 "object"(数组在 typeof 中被视为对象)typeof function(){} // 返回 "function"
使用 instanceof 操作符(用于判断对象的具体类型):
let arr = [];arr instanceof Array // 返回 truelet date = new Date();date instanceof Date // 返回 true
需要注意的是,typeof 操作符可以判断基本数据类型和 function 类型,但无法准确判断引用数据类型的具体类型(如数组、日期等)。而 instanceof 操作符可以用于判断特定对象是否属于某个类型,但不能有效判断基本数据类型。
综合使用 typeof 和 instanceof 操作符,可以比较全面地判断 JavaScript 中的数据类型。
7、说一下ES6的新特性有哪些?
ES6(ECMAScript 2015)是 JavaScript 的一个重要更新版本,引入了许多新特性和语法改进。下面是一些 ES6 的主要新特性:
块级作用域(Block Scope):引入了 let 和 const 关键字,可以在块级作用域内声明变量,解决了变量提升和作用域问题。
箭头函数(Arrow Functions):使用箭头函数定义函数,简化了函数的声明和使用,并且自动绑定上下文。
模板字符串(Template Strings):使用反引号 `` 包裹字符串,可以在字符串中插入变量和表达式,更加直观和方便。
解构赋值(Destructuring Assignment):可以从数组或对象中提取值,并赋给变量,简化了数据结构的操作和赋值过程。
默认参数(Default Parameters):在函数声明时为参数设置默认值,简化了函数调用时的参数传递。
扩展运算符(Spread Operator):使用 … 可以将数组或对象进行展开,方便地进行数组合并、对象拷贝等操作。
类与模块(Classes and Modules):引入了 class 关键字,支持面向对象编程的类和继承机制;同时,新增了 import 和 export 关键字,支持模块化开发。
迭代器和生成器(Iterators and Generators):提供了迭代器协议和生成器函数,可以更方便地进行迭代操作和异步编程
8、Let、const、var三者有什么区别?
在 JavaScript 中,let、const 和 var 是用来声明变量的关键字,它们之间有以下区别:
var:
var 是在 ES5 中引入的变量声明关键字。使用 var 声明的变量存在变量提升(hoisting)现象,即变量可以在声明之前被访问,但其值为 undefined。var 声明的变量作用域是函数作用域,而不是块级作用域(比如 if 语句、循环语句等)。let:
let 是在 ES6 中引入的块级作用域变量声明关键字。使用 let 声明的变量不存在变量提升,变量只能在声明后才能被访问。let 声明的变量作用域是块级作用域,比如 if 语句、循环语句等会形成一个新的块级作用域。const:
const 也是在 ES6 中引入的关键字,用于声明常量,一旦赋值就不能再改变。const 声明的变量必须在声明时初始化,并且尝试修改常量的值会导致运行时错误。const 声明的常量也具有块级作用域,同样不会存在变量提升。总结:
推荐优先使用 const 来声明常量,这样可以避免意外的修改。如果变量需要被修改,则使用 let。尽量避免使用 var,因为它存在变量提升和函数作用域的缺点,在大多数情况下 let 可以更好地取代 var。9、数组去重有哪些办法?ES6新增构造函数set,利用set具有天然去重 功能数组迭代
有多种方法可以对数组进行去重操作,其中包括利用 ES6 新增的 Set 构造函数、数组迭代方法等。下面分别介绍这些方法:
使用 Set:
const arr = [1, 2, 2, 3, 4, 4, 5];const uniqueArr = [...new Set(arr)];console.log(uniqueArr); // 输出 [1, 2, 3, 4, 5]
利用 Set 的特性,它只能存储唯一的值,通过将数组转换为 Set,然后再将 Set 转换回数组,就能实现去重效果。
使用 Array.from 和 Set:
const arr = [1, 2, 2, 3, 4, 4, 5];const uniqueArr = Array.from(new Set(arr));console.log(uniqueArr); // 输出 [1, 2, 3, 4, 5]
使用数组迭代方法 filter 和 indexOf:
const arr = [1, 2, 2, 3, 4, 4, 5];const uniqueArr = arr.filter((item, index) => arr.indexOf(item) === index);console.log(uniqueArr); // 输出 [1, 2, 3, 4, 5]
这里利用 filter 方法遍历数组,对于每个元素,只保留第一次出现的元素,即索引与 indexOf 返回的索引相等的元素。
使用数组迭代方法 reduce:
const arr = [1, 2, 2, 3, 4, 4, 5];const uniqueArr = arr.reduce((prev, curr) => { if (!prev.includes(curr)) { prev.push(curr); } return prev;}, []);console.log(uniqueArr); // 输出 [1, 2, 3, 4, 5]
reduce 方法遍历数组,对于每个元素,如果结果数组中不存在该元素,则将其添加到结果数组中。
以上是一些常见的数组去重方法,根据实际情况选择适合的方法。在 ES6 中,使用 Set 是最简单和高效的方法之一,但如果需要支持旧版本的 JavaScript,则可以考虑其他方法。
10、说一下深拷贝和浅拷贝,如何自己实现一个深拷贝?
深拷贝和浅拷贝是在 JavaScript 中常用的概念,用于复制对象或数组的方式。下面简单介绍一下两者的区别:
浅拷贝(Shallow Copy):
浅拷贝只会复制对象或数组的第一层结构,如果对象或数组中包含引用类型(如对象、数组),则只会复制引用,而不会复制引用指向的实际数据。当进行浅拷贝后,原对象和拷贝后的对象共享同一部分内存空间,修改拷贝后的对象可能会影响原对象。深拷贝(Deep Copy):
深拷贝会递归地复制对象或数组的所有层级结构,包括所有嵌套的对象和数组,确保复制后的对象与原对象完全独立,互不影响。要实现一个深拷贝函数,可以考虑以下方法:
function deepCopy(obj) { // 判断是否为对象或数组 if (typeof obj !== 'object' || obj === null) { return obj; } // 创建一个新的对象或数组 const copy = Array.isArray(obj) ? [] : {}; // 遍历原对象的属性或元素,递归进行深拷贝 for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { copy[key] = deepCopy(obj[key]); } } return copy;}// 使用示例const obj = { name: 'Alice', age: 30, address: { city: 'Beijing', country: 'China' }, hobbies: ['reading', 'coding']};const objCopy = deepCopy(obj);console.log(objCopy);
上面的代码实现了一个简单的深拷贝函数 deepCopy
,它能够递归地复制对象或数组的所有层级结构,确保拷贝后的对象与原对象完全独立。在实际开发中,可以根据需要对深拷贝函数进行优化和扩展,以满足不同情况下的需求。
11、Vue的生命周期有哪一些?说一下它们每个阶段做什么操作?
Vue 实例有以下生命周期钩子,它们按顺序在 Vue 实例的生命周期中被调用:
beforeCreate:
在实例初始化之后,数据观测 (data observer) 和事件配置 (event/watcher setup) 之前被调用。此时实例的 data 和 methods 都还未初始化。
created:
在实例创建完成后被立即调用。在这一阶段,实例已完成数据观测、属性和方法的运算,但是挂载阶段还未开始,$el 属性尚不可用。
beforeMount:
在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted:
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。实例现在完成了挂载,可以访问元素并操作 DOM。
beforeUpdate:
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会导致重渲染。
updated:
由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。当这一阶段被调用时,DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。
beforeDestroy:
实例销毁之前调用。在这一阶段,实例仍然完全可用。
destroyed:
实例销毁后调用。该钩子被调用后,Vue 实例指示所有的绑定和实例上的事件监听器都将被移除。
在实际开发中,可以利用这些生命周期钩子来执行特定的操作,例如在 created 钩子中进行数据初始化,mounted 钩子中进行 DOM 操作,beforeDestroy 钩子中进行清理工作等。这些生命周期钩子为开发者提供了丰富的扩展和控制能力,有助于管理应用程序的各个阶段。
12、组件通讯方式有哪一些?
props父向子组件通信
自定义事件 子向父组件通信
全局事件总线
v-model组件通信(父子组件数据同步)
绑定单个数据同步 绑定多个数据同步
useAttrs组件通信
ref与$parent
ref获取子组件实例对象
$parent获取父组件实例对象
provide-inject 可以实现隔辈传输
Pinia
选择式API:
组合式API:
组件通信详解
13、Vuex有几个属性及作用,piain有几个属性及作用?两者区别
在 Vuex 中,有以下几个核心属性及其作用:
state:
state 是存储应用程序状态的地方。它是响应式的,当 state 发生变化时,相关的组件会自动更新。
getters:
getters 可以理解为 store 的计算属性,可以对 state 进行一些派生处理,返回新的值。类似于 Vue 组件的 computed 属性。
mutations:
mutations 定义了修改 state 的方法,它们是同步的。通过提交 (commit) mutations 来修改 state 中的数据。
actions:
actions 类似于 mutations,但是可以执行异步操作。通过分发 (dispatch) actions 来触发一系列的 mutations。
modules:
modules 允许将 store 分割成模块,每个模块都拥有自己的 state、getters、mutations 和 actions。这样可以更好地组织和管理大型的 Vuex 应用。
而 Pinia 是一个基于 Vue 3 的状态管理库,它的设计灵感来自于 Vuex,但提供了更简洁、类型安全的 API。Pinia 的核心概念如下:
state:
state 是存储应用程序状态的地方,与 Vuex 中的 state 扮演相同的角色。
getters:
getters 为 state 提供了派生的响应式状态,与 Vuex 中的 getters 类似。
actions:
actions 用于执行异步操作或包含业务逻辑的操作。与 Vuex 中的 actions 类似。
mutations:
mutations 用于同步地修改 state 的方法,与 Vuex 中的 mutations 类似。
接下来,让我给出两者之间的区别并提供示例代码:
区别:
Vuex 是为 Vue.js 应用程序开发的官方状态管理库,而 Pinia 是一个基于 Vue 3 的状态管理库,提供了更简洁、类型安全的 API。Pinia 使用了 Vue 3 的新特性,提供了更好的 TypeScript 支持,而 Vuex 则是为 Vue 2 设计的。Pinia 的 API 更简洁和直观,使用起来更加方便,支持批量提交 mutations 和 actions,并具备更好的 TypeScript 类型推导能力。示例代码:
下面是一个使用 Vuex 的示例代码:
// store.jsimport { createStore } from 'vuex';export default createStore({ state: { count: 0, }, mutations: { increment(state) { state.count++; }, }, actions: { incrementAsync({ commit }) { setTimeout(() => { commit('increment'); }, 1000); }, }, getters: { doubleCount: (state) => { return state.count * 2; }, },});
下面是一个使用 Pinia 的示例代码:
// store.js// store.jsimport { defineStore } from 'pinia';export const useCounterStore = defineStore({ id: 'counter', state: () => ({ count: 0, }), getters: { doubleCount() { return this.count * 2; }, }, actions: { increment() { this.count++; }, incrementAsync() { setTimeout(() => { this.increment(); }, 1000); }, }, mutations: { setCount(value) { this.count = value; }, },});// main.jsimport { createPinia } from 'pinia';import { createApp } from 'vue';import App from './App.vue';const app = createApp(App);const pinia = createPinia();app.use(pinia);app.mount('#app');// App.vue<template> <div> <p>Count: {{ $store.counter.count }}</p> <p>Double Count: {{ $store.counter.doubleCount }}</p> <button @click="$store.counter.increment()">Increment</button> <button @click="$store.counter.incrementAsync()">Increment Async</button> <input type="number" v-model="newCount"> <button @click="updateCount">Update Count</button> </div></template><script>import { ref } from 'vue';export default { data() { return { newCount: 0, }; }, methods: { updateCount() { this.$store.counter.setCount(parseInt(this.newCount)); }, },};</script>
通过以上示例代码,你可以看到 Vuex 和 Pinia 在状态管理的实现方式上的一些不同之处。Vuex 需要定义 state、mutations、actions 和 getters,而 Pinia 则更加简洁,通过 createPinia() 即可创建一个 Pinia 实例,并开始定义你的状态和逻辑。
14、Vue的监听属性和计算属性有什么区别?
1 监听属性使用 watch 函数来监听数据的变化。
2 计算属性使用 computed 函数来定义一个依赖于其他数据的衍生属性。
3 监听属性的回调函数接收两个参数:新值和旧值,而计算属性的值是通过计算生成的。
4 在 Vue3中,计算属性的值不再支持缓存,而是在每次访问时重新计算。如果你需要缓存计算属性的值,请使用 computed 函数的第二个参数options 来配置缓存行为。
15、说一下防抖和节流。怎么实现?
防抖(Debounce)和节流(Throttle)是两种常用的前端性能优化技巧,用于控制事件触发频率,防止过多的函数调用导致性能问题。
防抖(Debounce):在防抖技术中,当事件被触发后,会等待一定的时间间隔(比如等待用户输入完成),如果在这个时间间隔内又有新的事件被触发,则之前的事件会被清除,并重新计时。只有当事件停止触发一段时间后(等待时间结束),才会执行相应的操作。
实现防抖的基本思路:
function debounce(func, delay) { let timeoutId; return function() { const context = this; const args = arguments; clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(context, args); }, delay); };}
节流(Throttle):节流技术是指在一定时间间隔内只触发一次函数执行,即限制函数的执行频率。无论事件触发多频繁,都会按照固定的频率执行对应操作。
实现节流的基本思路:
function throttle(func, delay) { let canRun = true; return function() { if (!canRun) return; const context = this; const args = arguments; canRun = false; setTimeout(() => { func.apply(context, args); canRun = true; }, delay); };}
在实际应用中,可以根据具体的需求选择使用防抖或节流来优化事件处理,比如输入框实时搜索建议可以使用防抖,滚动加载更多数据可以使用节流等。
16、Vue的导航守卫有哪一些?
在 Vue 中,导航守卫(Navigation Guards)是一组用于控制路由导航的钩子函数,它们可以在路由发生变化之前或之后执行相应的逻辑。Vue 提供了多种导航守卫来满足不同的需求。
Vue 的导航守卫包括以下几种:
全局前置守卫 (beforeEach):在路由切换开始之前调用,常用于进行全局的身份验证等操作。全局解析守卫 (beforeResolve):在路由被确认之前调用,常用于处理异步路由组件。全局后置守卫 (afterEach):在路由切换完成之后调用,常用于页面统计等操作。路由独享的守卫 (beforeEnter):在特定路由配置中定义的守卫,只对该路由生效。组件内的守卫 (beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave):在组件内部定义的守卫,用于处理组件相关的逻辑。具体来说,这些导航守卫的执行顺序如下:
全局前置守卫 (beforeEach)路由独享的守卫 (beforeEnter)解析守卫 (beforeResolve)组件内的守卫 (beforeRouteEnter)全局解析守卫 (beforeResolve)全局后置守卫 (afterEach)需要注意的是,导航守卫可以在路由配置中设置为一个函数或一个数组,以便执行多个守卫。
例如,在 Vue Router 中的使用示例:
const router = new VueRouter({ routes: [ { path: '/home', component: Home, beforeEnter: (to, from, next) => { // 路由独享的守卫 // ... next(); } }, { path: '/about', component: About, beforeEnter: [guard1, guard2] // 多个守卫 } ]});router.beforeEach((to, from, next) => { // 全局前置守卫 // ... next();});router.afterEach(() => { // 全局后置守卫 // ...});
这样,我们就可以根据需求来使用不同的导航守卫来控制路由的切换行为和添加相应的逻辑处理。
17、你的登录拦截怎么实现的?
路由守卫:使用路由守卫(Navigation Guards)来拦截需要登录的页面,当用户未登录时,将其重定向到登录页面。在 VueRouter 中,可以使用全局前置守卫(beforeEach)或路由独享的守卫(beforeEnter)来实现登录拦截。权限管理:在用户登录成功后,将登录凭证(如 token)保存在本地存储(localStorage 或
sessionStorage)中,并根据登录状态进行权限管理。例如,在每次发送请求时,可以在请求头中携带登录凭证,后端服务器根据凭证判断用户是否登录以及是否有权限访问对应资源。登录状态检查:在应用初始化时,可以检查本地存储中是否存在登录凭证,如果不存在或已过期,则视为未登录状态,需要用户重新进行登录。
需要注意的是,前端的登录拦截只是一种辅助手段,真正的安全验证还应该在后端服务器中进行。前端的登录拦截主要是为了提供更好的用户体验和简化后端的请求处理。
在实际应用中,登录拦截的实现方式会因具体的技术栈和需求而有所不同,以上只是其中一些常见的方法。具体的实现方式可以根据项目的需求和架构来进行选择和调整。
18、有用过图表吗组件吗?echarts和height-echarts的区别?
ECharts 和 Highcharts 之间的主要区别包括以下几点:
开发背景:
ECharts 是由百度开发和维护的开源项目,提供了丰富的图表类型和可定制化的配置选项,支持移动端和桌面端。Highcharts 是一款商业产品,也提供了丰富的图表类型和灵活的配置选项,拥有完善的文档和支持服务。技术实现:
ECharts 使用 Canvas 技术渲染图表,能够处理大规模数据和复杂的图表展示。Highcharts 则主要使用 SVG 技术渲染图表,具有良好的跨浏览器兼容性和可访问性。社区和生态:
ECharts 作为开源项目,拥有庞大的用户社区和丰富的插件扩展,可以满足不同的需求。
Highcharts 作为商业产品,提供了付费的技术支持和服务,同时也有一定数量的用户社区和插件扩展。
在选择使用 ECharts 还是 Highcharts 时,可以根据具体项目的需求、技术栈和预算等因素进行权衡和选择。两者都能够满足常见的数据可视化需求,并且都具有丰富的文档和示例可供参考。
19、闭包是什么?如何实现?
闭包是一个函数对象,它使得函数可以拥有自己的状态,即在函数内部引用了外部变量。
要实现一个闭包,你可以参考以下的代码:
function outerFunction() { var outerVariable = 'Hello'; function innerFunction() { console.log(outerVariable); } return innerFunction;}var closure = outerFunction(); // 调用外部函数,返回内部函数closure(); // Hello,内部函数仍然可以访问和操作外部函数的变量
20、Vue2.0和vue3.0有什么区别?
Vue.js 是一款流行的前端 JavaScript 框架,Vue 3.0 是其最新版本,与 Vue 2.0 相比有一些重要的变化和改进。以下是 Vue 2.0 和 Vue 3.0 的主要区别:
性能优化:
Vue 3.0 在性能方面进行了优化,包括虚拟 DOM 的改进、编译器优化、响应式系统的重写等,提升了整体性能。新的响应式系统使得 Vue 3.0 更加高效,并且在组件渲染和更新时具有更好的性能表现。Composition API:
Vue 3.0 引入了 Composition API,这是一个基于函数的 API,使得组件逻辑可以更好地组织和重用,更灵活地管理代码。Composition API 比 Options API 更容易理解和维护,特别适合复杂组件和大型项目。TypeScript 支持:
Vue 3.0 对 TypeScript 的支持更加友好,内置了更多的 TypeScript 类型定义,使用 TypeScript 开发 Vue 项目更加顺畅。更小的体积:
Vue 3.0 的体积相对较小,同时引入了 Tree-shaking 支持,可以更好地优化项目的构建大小。生命周期钩子的变化:
Vue 3.0 中一些生命周期钩子发生了变化,部分钩子被重命名或者调整了执行顺序,开发者需要注意这些变化。其他改进:
Vue 3.0 引入了 Fragments 和 Portals,使得组件的结构更加灵活。Suspense 和 Teleport 等新特性也为异步组件加载和组件位置移动提供了更好的支持。总的来说,Vue 3.0 在性能、开发体验和灵活性上都有所提升,特别是引入 Composition API 和对 TypeScript 的更好支持,使得开发 Vue 项目更加高效。如果你是新项目,推荐选择 Vue 3.0 进行开发;如果是已有项目,可以根据实际情况考虑是否升级到 Vue 3.0。
21、Vue常用的指令有哪些?
Vue 中常用的指令包括:
v-if
:根据表达式的真假条件来渲染元素。
v-for
:基于源数据多次渲染元素或模板块。
v-on
:绑定事件监听器,用于监听 DOM 事件。
v-bind
:动态地绑定一个或多个特性,或一个组件 prop 到表达式。
v-model
:在表单 <input>、<textarea>、<select>
元素上创建双向数据绑定。
v-show
:根据表达式的真假条件来切换元素的 display CSS 属性。
v-cloak
:这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
v-pre
:跳过这个元素和所有子元素的编译过程。
v-once
:只渲染元素和组件一次。
以上是 Vue 中常用的指令,它们可以帮助开发者实现各种丰富的交互和数据绑定效果。
24、你是如何封装一个组件的?
在封装组件时,还需要注意以下几点:
组件的可复用性:尽量将组件设计成可复用的,避免与具体业务逻辑耦合过深。
组件的封装粒度:封装组件时需要考虑组件的封装粒度,尽量保持组件的功能单一,方便维护和复用。
组件的props和事件:通过props向组件传递数据,通过事件向父组件通信,遵循单向数据流的原则。
组件的样式隔离:使用scoped属性对组件的样式进行隔离,避免样式冲突。
组件的命名规范:遵循一定的命名规范,例如使用驼峰式命名或短横线命名。
25、有自己从0到1搭建过项目吗?
随机应变
26、有用过uni-app吗?
随机应变
27、你会写后台吗?有搞过服务端渲染吗?
随机应变
28、说一下你项目中遇到的难点,如何解决?
随机应变
29、如何实现小程序的request封装及拦截?
wx.request
30、在vue的项目应用中,不使用框架,怎么封装?
31、什么是Js原型?原型链是什么?
原型(prototype)是对象之间共享属性和方法的一种机制。每个对象都有一个原型对象,它可以作为该对象的模板,用于继承属性和方法。通过原型,我们可以实现对象的属性和方法的复用,避免在每个对象中都创建一份相同的属性和方法。
原型链(prototype chain)是 JavaScript
中对象之间通过原型链接起来的一种机制。当访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript
引擎会沿着原型链向上查找,直到找到匹配的属性或方法,或者到达原型链的顶端(即
Object.prototype)。这样就实现了属性和方法的继承。
具体来说,每个对象都有一个 __proto__
属性,指向其构造函数的原型对象。而原型对象也有一个自己的 __proto__
属性,指向其构造函数的原型对象,以此类推,形成了一个原型链。
例如,当我们创建一个对象 obj
并调用其属性或方法时,JavaScript 引擎会按照以下步骤查找:
obj
自身是否有该属性或方法。如果没有,它会沿着 obj.__proto__
所指向的原型对象查找,即该对象的构造函数的原型对象。如果还没有找到,就继续沿着原型链向上查找,直到找到匹配的属性或方法,或者到达原型链的顶端(即 Object.prototype)。 这样,如果我们在构造函数的原型对象上添加属性和方法,所有通过该构造函数创建的对象都可以共享这些属性和方法。这样既节省了内存空间,又实现了属性和方法的复用。
需要注意的是,在 ES6 中,还引入了类(class)的概念,使得 JavaScript 中的对象和继承更加符合传统面向对象编程的语法和思想。但实质上,类和原型链是相辅相成的,类只是原型链的语法糖,底层仍然基于原型链来实现。
32、作用域是什么?
作用域(scope)是指在程序中定义变量的可访问范围。它规定了在何处和如何查找变量,以确定在特定位置对变量的引用应该绑定到哪个变量。
在 JavaScript 中,作用域可以分为全局作用域和局部作用域(函数作用域和块级作用域):
全局作用域:全局作用域是整个程序范围内都可访问的作用域。在全局作用域中定义的变量可以被程序中的任何部分访问。
函数作用域:函数作用域是在函数内部定义的作用域。在函数作用域中定义的变量只能在该函数内部访问,函数外部无法访问这些变量。
块级作用域:块级作用域是在代码块(如 {}
)内部定义的作用域。在 ES6 之前,JavaScript 中没有块级作用域,只有全局作用域和函数作用域。ES6 引入了 let
和 const
关键字,使得可以在块级作用域内定义变量。
作用域链决定了在当前作用域查找变量时的顺序。当访问一个变量时,JavaScript 引擎会首先在当前作用域查找变量,如果找到了就使用该变量,否则会向上级作用域继续查找,直到找到变量或者到达全局作用域。如果在全局作用域仍然没有找到变量,则会抛出 ReferenceError
。
作用域的概念对于理解变量的可见性、避免命名冲突以及理解函数和闭包等 JavaScript 特性非常重要。理解作用域可以帮助我们编写更加健壮和可维护的代码。
33、操作数组的方式有哪些?
在 JavaScript 中,我们可以使用以下几种方式来操作数组:
创建数组:可以使用数组字面量([]
)或 Array
构造函数来创建一个新数组。 // 使用数组字面量创建数组const arr1 = [1, 2, 3];// 使用 Array 构造函数创建数组const arr2 = new Array(1, 2, 3);
访问和修改数组元素:可以使用索引来访问和修改数组中的元素。 const arr = [1, 2, 3];// 访问数组元素console.log(arr[0]); // 输出 1// 修改数组元素arr[1] = 4;console.log(arr); // 输出 [1, 4, 3]
添加和删除数组元素:可以使用 push
、pop
、shift
和 unshift
方法来添加和删除数组中的元素。 const arr = [1, 2, 3];// 在末尾添加元素arr.push(4);console.log(arr); // 输出 [1, 2, 3, 4]// 删除末尾的元素arr.pop();console.log(arr); // 输出 [1, 2, 3]// 在开头添加元素arr.unshift(0);console.log(arr); // 输出 [0, 1, 2, 3]// 删除开头的元素arr.shift();console.log(arr); // 输出 [1, 2, 3]
数组遍历:可以使用 for...of
循环、forEach
方法、map
方法等方式来遍历数组。 const arr = [1, 2, 3];// 使用 for...of 循环遍历数组for (const item of arr) { console.log(item);}// 使用 forEach 方法遍历数组arr.forEach((item, index) => { console.log(`arr[${index}] = ${item}`);});// 使用 map 方法遍历数组并返回新数组const newArr = arr.map(item => item * 2);console.log(newArr); // 输出 [2, 4, 6]
数组排序和查找:可以使用 sort
方法、find
方法、filter
方法等方式对数组进行排序和查找。 const arr = [3, 1, 2];// 对数组进行排序arr.sort();console.log(arr); // 输出 [1, 2, 3]// 查找数组中第一个符合条件的元素const target1 = arr.find(item => item > 2);console.log(target1); // 输出 3// 查找数组中所有符合条件的元素const target2 = arr.filter(item => item > 1);console.log(target2); // 输出 [2, 3]
以上是一些常用的操作数组的方式和示例代码,还有很多其他的操作方式和方法,需要根据具体情况选择使用。
34、0.1 + 0.2 等于 0.3吗?为什么?如何解决?
在 JavaScript 中,0.1 + 0.2 不等于 0.3。这是因为 JavaScript 使用双精度浮点数来表示数字,而双精度浮点数的精度有限,无法精确表示所有的小数。
这是一个常见的浮点数精度问题,它是由于浮点数采用二进制表示,而在二进制中无法精确表示某些十进制小数(如 0.1 和 0.2)所导致的。
要解决这个问题,一种常见的方法是对浮点数进行舍入,通常可以使用 toFixed
方法将浮点数转换成指定精度的字符串表示,然后再进行数值运算。例如:
const result = (0.1 + 0.2).toFixed(1); // 将结果保留一位小数console.log(result); // 输出 "0.3"
另一种方法是使用第三方库,如 decimal.js
或 big.js
,这些库提供了更精确的数学计算功能,可以避免浮点数精度问题。
除此之外,还可以在实际计算中将浮点数转换成整数进行操作,最后再转换回浮点数,以减少浮点数的精度损失。
35、keep-alive是什么?有哪几个生命周期阶段?
keep-alive
是 Vue.js 中的一个内置组件,用于缓存动态组件或组件树,以便在组件切换时保持其状态,避免重复渲染和销毁。
keep-alive
组件具有以下几个生命周期阶段:
activated
:当包裹的组件被激活时触发。在组件切换到当前 keep-alive
组件时,会触发该生命周期钩子函数。
deactivated
:当包裹的组件被停用时触发。在组件从当前 keep-alive
组件切换出去时,会触发该生命周期钩子函数。
这两个生命周期钩子函数可以用来在组件缓存和恢复期间执行一些操作,比如保存和还原组件的状态、发送请求等。
以下是 keep-alive
组件的示例用法:
<template> <div> <button @click="toggleComponent">切换组件</button> <keep-alive> <component :is="currentComponent"></component> </keep-alive> </div></template><script>export default { data() { return { currentComponent: 'ComponentA', }; }, methods: { toggleComponent() { this.currentComponent = this.currentComponent === 'ComponentA' ? 'ComponentB' : 'ComponentA'; }, },};</script>
在上述示例中,点击按钮可以切换 ComponentA
和 ComponentB
两个组件。由于它们被包裹在 keep-alive
中,切换时不会触发组件的销毁和重新创建,而是通过激活和停用的方式来保持组件的状态。
36、判断一个变量是否是数组,有哪些办法?
在 JavaScript 中,可以使用多种方法来判断一个变量是否是数组。以下是几种常用的方法:
使用Array.isArray()
方法:Array.isArray()
方法是 JavaScript 提供的用于判断一个变量是否是数组的最可靠方法。它会返回一个布尔值,如果变量是数组,则返回 true
;否则返回 false
。 const arr = [1, 2, 3];console.log(Array.isArray(arr)); // 输出 trueconst notArr = 'hello';console.log(Array.isArray(notArr)); // 输出 false
使用 instanceof
操作符:instanceof
操作符可以用来检测某个对象是否是某个特定构造函数的实例,因此也可以用来判断一个变量是否是数组。 const arr = [1, 2, 3];console.log(arr instanceof Array); // 输出 trueconst notArr = 'hello';console.log(notArr instanceof Array); // 输出 false
使用 Object.prototype.toString.call()
方法:另一种方法是通过调用
Object.prototype.toString.call()
方法,并传入要检测的变量作为参数,然后比较返回的字符串结果是否为 "[object Array]"
。 const arr = [1, 2, 3];console.log(Object.prototype.toString.call(arr) === '[object Array]'); // 输出 trueconst notArr = 'hello';console.log(Object.prototype.toString.call(notArr) === '[object Array]'); // 输出 false
这些方法都可以用来判断一个变量是否是数组,建议优先使用 Array.isArray()
方法,因为它是最简洁和明确的方式。
37、判断一个变量是否是对象,有哪些办法?
在 JavaScript 中,可以使用多种方法来判断一个变量是否是对象。以下是几种常用的方法:
使用typeof
操作符:typeof
操作符可以返回一个变量的数据类型。当变量是对象时,使用 typeof
操作符会返回 "object"
。 const obj = { name: 'John', age: 30 };console.log(typeof obj === 'object'); // 输出 trueconst notObj = 'hello';console.log(typeof notObj === 'object'); // 输出 false
需要注意的是,typeof
对于数组也会返回 "object"
,所以该方法不能准确判断是否为对象。
instanceof
操作符:instanceof
操作符可以用来检测某个对象是否是某个特定构造函数的实例,因此也可以用来判断一个变量是否是对象。 const obj = { name: 'John', age: 30 };console.log(obj instanceof Object); // 输出 trueconst notObj = 'hello';console.log(notObj instanceof Object); // 输出 false
使用 Object.prototype.toString.call()
方法:类似于判断数组的方法,也可以通过调用
Object.prototype.toString.call()
方法,并传入要检测的变量作为参数,然后比较返回的字符串结果是否为 "[object Object]"
。 const obj = { name: 'John', age: 30 };console.log(Object.prototype.toString.call(obj) === '[object Object]'); // 输出 trueconst notObj = 'hello';console.log(Object.prototype.toString.call(notObj) === '[object Object]'); // 输出 false
这些方法都可以用来判断一个变量是否是对象,其中 typeof
和 instanceof
方法在某些情况下可能存在一定的局限性。建议优先使用 Object.prototype.toString.call()
方法,因为它是最可靠和通用的方式。
判断一个对象是否为空对象
在 JavaScript 中,可以使用多种方式来判断一个对象是否为空对象(即不包含任何属性)。以下是几种常用的方法:
Object.keys()
方法:Object.keys()
方法会返回一个给定对象自身可枚举属性的字符串数组,然后我们可以判断该数组的长度是否为 0,以确定对象是否为空。 const obj = {};console.log(Object.keys(obj).length === 0); // 输出 trueconst nonEmptyObj = { name: 'John', age: 30 };console.log(Object.keys(nonEmptyObj).length === 0); // 输出 false
使用 Object.entries()
方法:Object.entries()
方法会返回一个给定对象自身可枚举属性的键值对数组,我们也可以通过判断该数组的长度是否为 0 来确定对象是否为空。 const obj = {};console.log(Object.entries(obj).length === 0); // 输出 trueconst nonEmptyObj = { name: 'John', age: 30 };console.log(Object.entries(nonEmptyObj).length === 0); // 输出 false
使用 JSON.stringify()
方法:将对象转换为 JSON 字符串,然后判断字符串的长度是否为 2(即 “{}” 的长度),来判断对象是否为空对象。
const obj = {};console.log(JSON.stringify(obj) === '{}'); // 输出 trueconst nonEmptyObj = { name: 'John', age: 30 };console.log(JSON.stringify(nonEmptyObj) === '{}'); // 输出 false
这些方法都可以用来判断一个对象是否为空对象。需要注意的是,对于继承属性或不可枚举属性,上述方法可能存在一定的局限性。
38、对象/数组常用方法有哪些?
JavaScript 中对象和数组都有许多常用的方法,这些方法可以帮助你对它们进行操作、遍历和修改。以下是一些常用的对象和数组方法:
对象的常用方法:
Object.keys(obj)
返回一个包含给定对象所有可枚举属性的字符串数组。
Object.values(obj)
返回一个包含给定对象所有可枚举属性的值的数组。
Object.entries(obj)
返回一个包含给定对象所有可枚举属性的 [key, value] 数组。
Object.assign(target, ...sources)
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回目标对象。
Object.freeze(obj)
冻结一个对象,防止添加、删除或修改对象的属性。
Object.seal(obj)
封闭一个对象,防止添加或删除对象的属性,但允许修改现有属性。
数组的常用方法:
push()
将一个或多个元素添加到数组的末尾,并返回数组的新长度。
pop()
删除数组的最后一个元素,并返回该元素的值。
shift()
删除数组的第一个元素,并返回该元素的值,同时将数组长度减 1。
unshift()
将一个或多个元素添加到数组的开头,并返回数组的新长度。
concat()
用于合并两个或多个数组,并返回一个新数组。
slice(start, end)
返回一个新的数组对象,这一对象是一个由原数组中的 start 到 end(不包括 end)元素创建的,包括 start,但不包括 end。
splice(start, deleteCount, item1, item2, ...)
用于增加、删除或替换数组的元素。
forEach(callback)
遍历数组并对每个元素执行提供的回调函数。
以上列举的方法只是其中的一部分,JavaScript 中对象和数组还有很多其他有用的方法,可以根据具体的需求选择合适的方法来操作对象和数组。
43、Set和Map各是什么?
Set 和 Map 是 ES6 中新增的两种数据结构,用于存储和操作数据。它们有以下特点:
Set:
Set 是一种无重复值的集合,其中每个值只能出现一次。Set 中的元素按插入顺序进行存储。Set 中的值可以是任意类型,包括原始类型和对象引用。通过new Set()
创建一个空的 Set 对象,或者通过传入一个数组(或类数组对象)来初始化 Set 对象。 Set 提供了一些常用方法,例如:
add(value)
:向 Set 中添加一个值。delete(value)
:从 Set 中删除一个值。has(value)
:判断 Set 是否包含某个值。size
:返回 Set 中的元素数量。forEach(callback)
:遍历 Set 中的每个元素并执行回调函数。 Map:
Map 是一种键值对的集合,其中每个键只能出现一次。Map 中的键值对按插入顺序进行存储。Map 中的键和值可以是任意类型,包括原始类型和对象引用。通过new Map()
创建一个空的 Map 对象,或者通过传入一个二维数组(或类数组对象)来初始化 Map 对象。 Map 提供了一些常用方法,例如:
set(key, value)
:向 Map 中设置一个键值对。get(key)
:获取指定键的值。delete(key)
:从 Map 中删除指定键值对。has(key)
:判断 Map 是否包含某个键。size
:返回 Map 中键值对的数量。forEach(callback)
:遍历 Map 中的每个键值对并执行回调函数。 Set 和 Map 提供了高效的数据存储和检索方法,并且可以根据需要灵活地操作数据。
44、弹性布局,一行两列,一列固定宽,如何实现?
可以使用 CSS3 弹性布局实现一行两列,其中一列固定宽度的效果。具体实现步骤如下:
在父元素上设置display: flex;
,使其成为一个弹性容器。设置第二列的宽度为 0,以便在剩余空间中自适应。设置第一列的宽度为固定宽度,以确保其不会被压缩或拉伸。可以根据需要设置每个列的内边距、外边距和边框。 以下是一个示例代码:
<div class="container"> <div class="column-1">固定宽度列</div> <div class="column-2">自适应宽度列</div></div>
.container { display: flex;}.column-1 { width: 200px; /* 固定宽度 */ padding: 10px; margin-right: 10px; border: 1px solid #ccc;}.column-2 { flex: 1; /* 自动计算宽度 */ padding: 10px; border: 1px solid #ccc;}
以上代码中,.container
元素是一个弹性容器,.column-1
和 .column-2
是弹性项。.column-1
设置了固定的宽度,并在右侧留有 10px 的空隙;.column-2
则配置了 flex: 1;
,以自动适应剩余空间。
这样就可以实现一行两列,其中一列固定宽度的布局效果。
45、Flex:1 包含哪三种属性
flex: 1;
是 flex
属性的简写形式,表示同时设置了以下三种属性:
flex-grow
: 定义了弹性盒子(flex container)中剩余空间的分配比例。当设置为 1
时,表示弹性项(flex item)会根据剩余空间按比例进行扩展,默认值为 0
。flex-shrink
: 定义了弹性盒子中项目的缩小比例。当空间不足时,弹性项会按照 flex-shrink
的比例进行缩小,默认值为 1
。flex-basis
: 定义了弹性项在主轴方向上的初始大小。当设置为 0
时,表示弹性项的初始大小为零,默认值为 auto
。 使用 flex: 1;
可以将这三个属性一次性设置为相应的默认值,即 flex-grow: 1; flex-shrink: 1; flex-basis: 0%;
。这样弹性项就能够根据剩余空间按比例进行扩展,并在空间不足时按比例缩小。