面试总结
- 1、自我介绍一下自己
- 2.面试1
- 2.面试2
- 3.优势和不足1
- 4.优势和不足2
- 5.虚拟列表
- 6.Webpack
- 7.面试3
-
- 1.判断数组的方法有哪些
- 2.如何实现深拷贝,浅拷贝
- 3.vue过滤器
- 4.子组件可以改变父组件的数据吗,有什么影响
-
- [4.1 子组件可以改变父组件的数据(但是违反了Vue组件之间应该保持独立的原则)](#4.1 子组件可以改变父组件的数据(但是违反了Vue组件之间应该保持独立的原则))
- [4.2 子组件直接改变父组件的数据,会导致以下问题](#4.2 子组件直接改变父组件的数据,会导致以下问题)
- 5.v-if和v-for的优先级
- 6.前端如何保存用户登录状态
- 7.数组中有哪些常用的方法,哪些会返回新数组
- [8.for in和for of 什么区别](#8.for in和for of 什么区别)
- 9.watch和computed有什么区别
- 10.css中哪些属性会引起重绘
- 11.flex布局
- 12.怎么遍历对象
- 13.const对象的属性可以修改吗
- 14.promise,有哪些api
- 8.面试4
-
- 1.前端排序
- 2.Vue中router和route的区别
- 3.Vue路由传参的几种方式
-
- 一、利用"router-link"路由导航
- [二、调用router.push实现路由传参](#二、调用router.push实现路由传参)
- 三、通过路由属性中的name匹配路由,再根据params传递参数
- 四、通过query来传递参数
- 3.常用的数组方法有哪些?
- 4.keep-alive
- 5.nextTick
1、自我介绍一下自己
面试官您好,我叫张三,非常高兴有机会参加这次面试,我在xx大学毕业,我的专业是计算机科学与技术。毕业后在xx公司担任前端开发工程师,有半年的工作经验,接触的是uniapp,。后来,我在xx公司有三年半的前端开发工作,参与了多个大型的后台管理系统的开发,积累了项目经验。
我的专业技能包括熟练掌握HTML5、CSS3、JavaScript、webpack、AJAX、echarts,ES6,熟练使用Git,Svn代码管理工具,熟练使用 element-ui库;熟练使用sass,css预处理器。
在工作中,我注重细节,善于分析和解决问题,并且乐于与团队合作。
我认为我有很强的学习能力和适应能力,能够快速适应新环境并掌握新技能。在这段时间里,我不仅提高了我的技术能力,也锻炼了我的团队协作和项目管理技能。
我非常感谢这次面试的机会,期待能够成为贵公司的一员。我相信我可以为贵公司带来价值,并期待与您共同成长。
2.面试1
1、css常用布局有哪些
- 标准流布局
- 浮动布局
- 定位布局
- 弹性布局
- 网格布局
- 多列布局
- 响应式布局
2、css常用的属性
在CSS中,常用的属性可以分为几个主要类别,包括颜色、字体、布局、边框和背景等。每个类别都有其特定的属性和用途,用于控制网页的外观和布局。以下是对这些常用属性的详细介绍:
颜色属性
- color:用于设置文本颜色,可以使用预定义的颜色名称(如red、blue)或RGB值。
- background-color:设置背景颜色,同样可以使用预定义的颜色名称或RGB值。
- border:用于设置边框样式、宽度和颜色,可以分别设置边框的宽度、样式和颜色。
字体属性
- font-size:设置字体大小,可以使用像素、em、rem等单位。
- font-family:设置字体类型。
- font-weight:设置字体的粗细。
布局属性
- display:设置元素的显示方式,如block、inline等。
- float:设置元素的浮动方式,如left、right。
- position:设置元素的定位方式,如static、relative、absolute等。
边框和背景属性
- border:设置边框样式、宽度和颜色。
- margin:设置元素周围的空间大小。
- padding:设置元素内部空隙的大小。
实用技巧和建议
使用CSS预处理器:如Sass或Less,可以更方便地管理和组织CSS代码。
响应式设计:使用媒体查询(Media Queries)来适应不同屏幕尺寸的设备。
代码优化:避免使用过多的嵌套规则,尽量使用简洁的选择器和属性值。
浏览器兼容性:测试在不同浏览器中的表现,确保网站在所有主流浏览器中都能正常工作。
注释和文档:为代码添加注释,以便于维护和更新。
通过合理使用这些属性,可以大大提升网页的外观和用户体验。同时,注意遵循最佳实践,确保代码的可读性和可维护性。
3.js原型链
原型链由三个部分组成:构造函数、实例、原型对象。
构造函数:由大写字母开头的函数。在创建构造过程中,js会自动为它添加prototype属性,值为拥有constructor属性原型对象,constructor属性值为该构造函数。只有函数才有prototype属性。
实例:由构造函数new(实例化)得到。
原型对象:构造函数会在创建的过程中自动创建。
javascript
function Person(){} // 构造函数
var person = new Person() // 实例对象
4、开发中遇到的技术难点
在前端开发中,开发者可能会遇到多种技术难点,这些难点通常涉及布局、数据处理、实时数据更新、安全性、跨平台兼容性、性能优化等方面。以下是一些常见的前端技术难点及其相应的解决方案:
布局问题:使用Flexbox和CSS Grid是解决复杂布局问题的有效方法。Flexbox提供了一个一维布局模型,适用于单行或单列布局,而CSS Grid则是一个二维布局系统,适用于创建复杂的二维布局结构。
大规模数据处理和可视化:对于工业互联网项目中的大规模数据处理和可视化需求,可以使用专业的数据可视化库如D3.js或ECharts,以及使用前端框架和状态管理库来有效组织和管理数据。采用分页和懒加载技术可以减少一次性加载大量数据带来的性能问题。
实时数据更新和通信:实现实时数据更新和通信,可以使用WebSocket或Server-Sent Events (SSE)与服务器建立实时通信,及时更新数据。结合现代化前端框架如React.js或Vue.js,支持组件化开发和响应式更新。
安全性和权限控制:保护数据传输安全,使用HTTPS加密协议。实现用户身份验证和授权机制,确保用户只能访问其具备权限的数据和功能。对用户输入进行有效验证和过滤,防止常见的安全攻击如XSS和CSRF。
跨平台和设备兼容性:采用响应式设计和布局,确保应用程序能够适配不同分辨率和屏幕尺寸。进行跨浏览器测试和兼容性测试,使用CSS预处理器和前端框架简化样式和布局的开发和维护过程。
性能优化和缓存策略:使用前端优化工具和技术,如代码压缩、图片优化(使用webp格式图片)和懒加载等,减少资源消耗和页面加载时间。实施缓存策略,如使用浏览器缓存,以提高网站加载速度和响应速度。
通过上述方法,开发者可以有效解决前端开发中遇到的技术难点,提升应用程序的性能、安全性和用户体验。
5、闭包
typescript
//1.闭包是什么-方法里面返回方法
闭包(closure)是一个函数以及它所引用环境的组合。创建闭包的常见方式是在一个函数内部创建另一个函数,而该内部函数可以访问外部函数的局部变量,即使外部函数已经执行完毕。
下面是一个使用JavaScript闭包的例子:
javascript
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 输出: 1
console.log(counter()); // 输出: 2
console.log(counter()); // 输出: 3
在这个例子中,createCounter 是一个外部函数,它返回一个内部函数。这个内部函数可以访问并修改 createCounter 中的 count 变量,即使 createCounter 已经执行完毕。这样,每次调用 counter,它都会记住上次调用的状态。这就是闭包的一个典型用法。
- 普通的闭包
javascript
// JS中的一个函数如果访问了外层的一个变量就会形成闭包
var sge=12;
function add(){
console.log(sge);//访问函数外部的变量就形成闭包
}
add()
严格闭包
javascript
function f(){
var name="zhangsan";
function s(){
console.log("abb",name);
}
return s;
}
var ff=f();
ff();
闭包的作用
- 延伸了函数的作用范围,读取函数内部的变量
- 让变量的值始终保持在内存中。不会在调用后被自动清除。
- 方便调用上下文的局部变量。利于代码封装。
闭包的优缺点
优点:
- 避免全局变量的污染
- 能够读取函数内部的变量
- 可以在内存中维护一个变量
缺点:
- 闭包会常驻内存,会增大内存使用量,导致一定程度上的内存泄露。
- 在游览器中因为回收机制无法回收闭包的函数以及闭包函数中储存的数据,会使游览器占用更多的性能开销
javascript
<script>
// 1. 我们可以利用动态添加属性的方式
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
lis[i].index = i;
lis[i].onclick = function() {
// console.log(i);
console.log(this.index);
}
}
// 2. 利用闭包的方式得到当前小li 的索引号
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function(i) {
// console.log(i);
lis[i].onclick = function() {
console.log(i);
}
})(i); //注意这里必须传递i
}
</script>
6、ts了解什么呢
7.git都用什么命令
8、vue怎么打包
打包方式,产物有多少,产物是多大(MB)(有没有进行优化),解决总包大小
ngnix 配置在哪个目录下,什么命令可以看呢
9.vue启动一个项目需要什么
10、vue怎么创建一个项目
启动项目需要解决哪些问题(都需要配置什么(router store app.vue...))
前端项目需要解决哪些问题就可以启用了
请求的话,用什么方法
2.面试2
1.vue2和vue3有什么区别
Vue2和Vue3的主要区别包括构建工具、项目结构、生命周期、API、脚手架工具、项目目录和关键文件等方面。
- 构建工具和项目结构:Vue2使用的是Webpack 3.x作为构建工具,而Vue3则升级到了Webpack
4.x,带来了性能上的优化和调整,如更快的构建速度和更低的资源占用。此外,Vue3对项目结构进行了一些调整,使得项目结构更加简洁清晰。 - 生命周期:Vue3的生命周期钩子与Vue2有所不同。Vue2的生命周期钩子包括beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed等。而Vue3引入了新的生命周期钩子,如onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted等,去掉了beforeCreate和created,将beforeDestroy和destroyed替换为onBeforeUnmount和onUnmounted。
- API:Vue2采用选项式API,实现一个功能涉及的数据、方法、计算属性等分散在不同的地方,不利于代码的管理和维护。Vue3引入了组合式API,将实现一个功能所需的数据、方法、计算属性等放在同一个地方,使得代码更加集中和易于管理。
- 脚手架工具:Vue2使用Vue
CLI脚手架,底层构建工具是Webpack。Vue3则采用了create-vue脚手架,底层构建工具升级为Vite,提供了更快的整体开发体验。 - 项目目录和关键文件:Vue2和Vue3的项目目录结构有所不同,包括配置文件(Vue2使用vue.config.js,Vue3使用[vite.config.js)以及入口文件(Vue2使用main.js,Vue3使用createApp函数)等也有所不同。
这些变化旨在提供更好的开发体验、性能优化以及更易于维护的代码结构,使开发者能够更高效地开发Vue项目。
2.复杂组件的封装,核心是啥,难点是啥,步骤,思路
https://blog.csdn.net/weixin_45046532/article/details/140501924
Vue2复杂组件的封装通常涉及以下核心:
- 组件的接口设计(Props 和 Events)
- 组件的功能拆分和模块化
- 状态管理(Reactive Data)
- 组件的可复用性和可组合性
- 组件的性能优化
难点可能包括:
- 状态管理:复杂组件的状态可能会很复杂,需要合理地管理它们。
- 事件处理:组件间通信可能涉及到事件的监听和触发。
- 组件树的维护:组件树层级太深或组件复用率低可能会导致维护困难。
- 性能优化:需要合理地避免不必要的重渲染和优化内部计算。
封装步骤:
- 分析需求,确定组件的功能和接口。
- 设计组件的模块结构,划分为多个小组件。
- 编写组件模板、样式和逻辑。
- 使用Props传递父组件的数据和方法。
- 通过自定义Events与父组件通信。
- 使用Vue的响应式系统管理内部状态。
- 进行单元测试以确保组件的稳定性。
- 性能优化(如使用v-if/v-show、懒加载等)。
3.window自带的计算器
4.性能优化(资源压缩,图片优化)
vue性能优化(资源压缩,图片优化)
在Vue项目中进行性能优化,可以从以下几个方面入手:
代码压缩:使用工具如Webpack的TerserPlugin和CssMinimizerWebpackPlugin来压缩JavaScript和CSS代码。
图片优化:使用如image-webpack-loader的Webpack插件来优化图片大小。
懒加载与预加载:使用vue-lazyload或Keep-Alive来优化图片懒加载,以及预加载关键资源。
Webfont优化:优化字体文件,如使用Base64编码减少HTTP请求。
使用SSR(服务器端渲染):对于SEO友好的页面,可以开启SSR。
以下是一个简单的Webpack配置示例,展示如何进行资源压缩与图片优化:
javascript
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 移除console语句
},
},
}),
new CssMinimizerPlugin(),
],
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb的图片会被Base64编码
},
},
generator: {
filename: 'images/[contenthash][ext][query]',
},
// 使用image-minimizer-webpack-plugin优化图片
plugins: [
ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminMinify,
options: {
plugins: [
'imagemin-gifsicle',
'imagemin-jpegtran',
'imagemin-optipng',
'imagemin-svgo',
],
},
},
}),
],
},
],
},
};
在.vue文件中使用懒加载和预加载:
javascript
<template>
<div>
<!-- 懒加载图片 -->
<img v-lazy="imageUrl">
<!-- 预加载关键资源 -->
<link rel="preload" href="/path/to/critical.js" as="script">
</div>
</template>
<script>
export default {
data() {
return {
imageUrl: 'path/to/image.jpg'
};
}
};
</script>
确保在vue.config.js中配置好相应的插件:
javascript
// vue.config.js
const VueLazyload = require('vue-lazyload');
module.exports = {
chainWebpack: config => {
// 配置图片懒加载
config.plugin('vue-lazyload-plugin').use(VueLazyload, [{
preLoad: 1.3,
error: 'dist/error.png',
loading: 'dist/loading.gif',
attempt: 2,
}]);
},
configureWebpack: {
plugins: [
// 实例化Keep-Alive
new webpack.KeepAlivePlugin(),
],
},
};
以上代码提供了压缩JavaScript和CSS、优化图片、懒加载与预加载的配置示例
5.css
6.border
7.双向数据绑定(数据劫持)
https://blog.csdn.net/sanhewuyang/article/details/138436044
8.钩子函数
vue2生命周期钩子函数有哪些
Vue 2.x 的生命周期钩子包括:
beforeCreate:实例初始化之后,数据观测(data observer)和事件/watcher 设置之前被调用。
created:实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
beforeMount:模板编译/挂载之前被调用。在这个阶段,$el 属性还不可见。
mounted:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。在这一步,组件的 el 已经挂载到 DOM 上,可以进行 DOM 相关的操作。
beforeUpdate:数据更新时调用,但是在虚拟 DOM 重新渲染和打补丁之前。
updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
activated:在使用 vue-router 时,路由变化(切换到当前组件)时被调用。
deactivated:在使用 vue-router 时,路由变化(从当前组件切换到其他组件)时被调用。
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑,所有的事件监听器会被移除,所有的子实例也会被销毁。
实例代码
javascript
new Vue({
data: {
message: 'Hello Vue!'
},
beforeCreate: function () {
console.log('beforeCreate: 实例完全被创建出来之前。')
},
created: function () {
console.log('created: 实例创建完成,属性和方法都已经设置完成。')
},
beforeMount: function () {
console.log('beforeMount: 模板已经在内存中编译完成,但还没被挂载到DOM上。')
},
mounted: function () {
console.log('mounted: 模板编译挂载完成,DOM已经渲染完成。')
},
beforeUpdate: function () {
console.log('beforeUpdate: 数据更新时调用,但DOM还未重新渲染。')
},
updated: function () {
console.log('updated: 数据更新导致的DOM重新渲染完成。')
},
beforeDestroy: function () {
console.log('beforeDestroy: 实例销毁之前。')
},
destroyed: function () {
console.log('destroyed: Vue实例销毁后调用。')
}
})
vue基础,技术,原理,需要加强
问技术,项目不要展开说,做过几个项目,有没有银行工作经验,主要用哪些技术,项目中,自认为做的比较好的地方,不足,优势;三到五个技术性的问题,自我介绍方面需要加强
性能优化,用户体验认为自己做的哪些比较好的地方
3.优势和不足1
个人在vue2项目中,自认为做的比较好的地方,不足,优势
在Vue2项目中,您可能做得比较好的地方通常是组件的逻辑分割、代码组织和性能优化。以下是一些可能的方面:
- 组件逻辑分割:将复杂组件的逻辑拆分为更小的、可复用的组件。
- 代码模块化:使用模块化的方式组织代码,例如使用Vuex进行状态管理,使用vue-router进行路由管理。
- 优化性能:使用虚拟列表等方法提高列表渲染的性能。
- 使用mixins或高阶组件进行代码复用。
- 使用SSR或静态生成等服务端渲染技术提高SEO和首屏加载速度。
然而,在Vue2项目中,您也可能遇到一些不足或劣势:
- 生态系统支持:相比Vue3,Vue2的生态系统可能不如Vue3全面和先进。
- 性能问题:Vue2的响应式系统可能会导致在大型应用中的性能问题。
- 组件通信:Vue2的组件通信方式可能不如Vue3的Composition API方便。
综上所述,Vue2项目中您做得好的地方需要继续保持和发展,您可能需要关注的不足可以通过升级到Vue3来解决。Vue3在性能优化、组件通信、生态系统等方面提供了显著的改进。
4.优势和不足2
在vue项目中遇到的最大的困难
一些常见的Vue项目挑战及其解决方案:
- 组件通讯: 组件间如何通讯和共享数据是一个常见问题。解决方案是使用props和$emit,或者使用Vuex进行状态管理。
- 路由管理: Vue Router的使用和配置可能会让人头疼。解决方案是仔细阅读文档并正确设置路由。
- 性能问题: 对于大型应用,可能会遇到性能瓶颈。解决方案是使用虚拟滚动、懒加载组件和优化响应式数据。
- 构建配置: 项目构建时遇到的问题(如资源加载错误、第三方库的引入等)。解决方案是仔细检查webpack配置和加载规则。
- 状态管理: 复杂应用的状态管理是一个挑战。解决方案是使用Vuex进行状态管理,并遵循单向数据流的原则。
- 国际化与本地化: 如何实现应用的国际化是一个常见问题。解决方案是使用vue-i18n插件。
- 构建工具链: 如果你的构建工具链(如Webpack)太旧可能会导致兼容性问题或构建错误。解决方案是升级构建工具链。
- 第三方库: 集成第三方库可能会遇到兼容性问题或配置上的困难。解决方案是查看库的文档,并按照推荐的方式进行集成。
5.虚拟列表
虚拟列表的实现原理主要基于以下几个关键点:
- 区域划分:虚拟列表将整个列表划分为几个区域,包括上缓冲区、可视区和下缓冲区。可视区是用户当前能够看到的部分,而上缓冲区和下缓冲区则用于在滚动时动态加载或卸载元素,以减少不必要的渲染和内存占用。
- 动态渲染:虚拟列表只渲染当前可视区域内的元素,随着用户的滚动,动态计算并更新需要渲染的元素。这通过监听滚动事件,根据滚动的偏移量来决定哪些元素需要被渲染或隐藏。
- 高度管理:对于元素高度固定的情况,可以直接通过计算确定每个元素的位置和大小。对于元素高度不固定的情况,需要预先计算或实时测量每个元素的高度,以便准确控制渲染范围。
- 性能优化:通过减少DOM操作和渲染次数,虚拟列表显著提高了性能,特别是在处理大量数据时。它避免了传统列表渲染中一次性加载所有数据导致的性能瓶颈和内存占用问题。
- 技术实现:虚拟列表的实现可以通过监听滚动事件、计算可视区域、动态加载和卸载元素等方式来实现。具体实现可能涉及使用特定的库或框架提供的组件,如React中的react-window库,它提供了多种虚拟列表的实现方式,包括固定高度和不定高度的虚拟列表。
综上所述,虚拟列表的实现原理主要围绕动态渲染、区域管理和性能优化展开,通过这些技术手段,虚拟列表能够在处理大量数据时提供流畅的用户体验,同时减少资源消耗。
6.Webpack
Webpack 是一个模块打包工具,它将根据模块的依赖关系进行静态分析,然后生成一个或多个bundle。它可以处理各种类型的模块,包括 JavaScript、CSS、图片等,并且可以通过加载相应的插件进行处理。
Webpack 的核心概念包括:
Entry:入口点,Webpack 从这里开始构建其内部依赖图。
Output:输出结果,在这里定义 Webpack 如何生成输出文件。
Loaders:模块转换器,比如将 ES6 转换为 ES5,将 CSS 转换成 commonjs 模块。
Plugins:扩展插件,用于打包过程中的各个阶段。
Chunk:代码块,一个 chunk 代表了打包过程中的一个代码块。
Module:模块,在 Webpack 中,任何类型的文件都可以视作模块。
7.面试3
1.判断数组的方法有哪些
判断数组的4种方法
1.通过instanceof判断
instanceof运算符用于检验构造函数的prototype属性是否出现在对象的原型链中的任何位置,返回一个布尔值
javascript
let a = [];
a instanceof Array; //true
let b = {};
b instanceof Array; //false
//instanceof 运算符检测Array.prototype属性是否存在于变量a的原型链上
//显然a是一个数组,拥有Array.prototype属性,所以为true
2.通过constructor判断
实例的构造函数属性constructor指向构造函数,通过constructor属性可以判断是否为一个数组
javascript
let a = [7,8,9];
a.constructor === Array; //true
3.通过Object.prototype.toString.call()判断
Object.prototype.toString.call()可以获取到对象的不同类型
javascript
let a = [7,8,9];
Object.prototype.toString.call(a) === '[Object Array]'; //true
4.通过Array.isArray()判断
Array.isArray()用于确定传递的值是否是一个数组,返回一个布尔值
javascript
let a = [7,8,9];
Array.isArray(a); //true
有个问题是Array.isArray()是ES5新增的方法,目的就是提供一个稳定可用的数组判断方法,对于ES5之前不支持此方法的问题,我们其实可以做好兼容进行自行封装,如下:
javascript
if(!Array.isArray){
Array.isArray = function(argument){
return Object.prototype.toString.call(argument) === '[object Array]';
}
};
5.补充:typeof
typeof 只能检测 基本数据类型,包括boolean、undefined、string、number、symbol,而null ,Array、Object ,使用typeof出来都是Object,函数的typeof 是function 无法检测具体是哪种引用类型。
javascript
console.log(typeof(100),1); result: number 1
console.log(typeof("name"),2); result: string 2
console.log(typeof(false),3); result: boolean 3
console.log(typeof(null),4); result: object 4
console.log(typeof(undefined),5); result: undefined 5
console.log(typeof([]),6); result: object 6
console.log(typeof(Function),7); result: function 7
2.如何实现深拷贝,浅拷贝
JavaScript中实现深拷贝和浅拷贝的方法如下:
浅拷贝:
对象的浅拷贝可以通过展开运算符(...)或Object.assign()来实现。(一层为深拷贝,多层为浅拷贝。)
对象的浅拷贝,拷贝的仅仅是"引用地址",不是值。
引用类型的浅拷贝,拷贝的仅仅是"引用地址",两个对象的引用地址对应的还是同一个值,所以,无论改变哪个对象的值,另一个对象对应的值也会改变。
浅拷贝的方法
(1)、直接赋值法
(2)、局部作用域直接使用全局作用域变量
javascript
// 使用展开运算符
const shallowCopy = { ...originalObject };
// 使用Object.assign()
const shallowCopy = Object.assign({}, originalObject);
深拷贝:
对于简单类型(如String,Number,Boolean,Symbol)的深拷贝是默认深的,直接赋值或使用Object.assign()即可。
对于对象和数组,可以通过递归的方式实现深拷贝。
对象的深拷贝,把引用地址和值一起拷贝过来。
2、深拷贝的方法
(1)、JSON.parse(JSON.stringify(obj))
javascript
const obj = { a: undefined, b: null };
console.log(obj); // 输出 { a: undefined, b: null }
console.log(JSON.stringify(obj)); // 输出 '{ b: null }'
【拓展】
在 JavaScript 中,当对象中的属性值为 undefined 时,在执行 JSON.stringify(obj) 时,这些属性会被忽略,因此最终的 JSON 字符串中不会包含这些属性。对象中的属性值为 null 没问题
(2)、完美的深拷贝方法------递归
javascript
// 封装一个深拷贝的函数
const deepClone = (obj) => {
let cloneObj = Array.isArray(obj) ? [] : {};
for (let k in obj) {
if (obj.hasOwnProperty(k)) { // 判定 obj 里是否有 k 这个属性。
if(typeof obj[k] === "object"){ // 判定 k 是不是对象(广义)
cloneObj[k] = deepClone(obj[k]);
} else {
cloneObj[k] = obj[k];
}
}
}
return cloneObj;
}
// 测试
let obj = {
a: "hello",
b: {
c: 21
},
d: ["Bob", "Tom", "Jenny"],
e: function() {
alert("hello world");
}
};
const clone = deepClone(obj);
console.log(clone);
obj.a = "changed";
obj.b.c = 25;
obj.d = [1, 2, 3];
obj.e = () => { alert("changed") };
console.log(clone);
// 可见,改变原对象并不影响深拷贝的对象:
// {
// a: "hello",
// b: {
// c: 21,
// },
// d: ["Bob", "Tom", "Jenny"],
// e: function(){
// alert("hello world");
// }
// }
javascript
function deepCopy(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof Array) {
return obj.reduce((arr, item, i) => {
arr[i] = deepCopy(item);
return arr;
}, []);
}
if (obj instanceof Object) {
return Object.keys(obj).reduce((newObj, key) => {
newObj[key] = deepCopy(obj[key]);
return newObj;
}, {});
}
}
// 使用deepCopy函数
const deepCopy = deepCopy(originalObject);
注意:深拷贝会创建一个新的对象,并复制对象中的所有值,如果值是基本类型,则复制值本身;如果值是对象,则递归地复制这个对象。这意味着原始对象和新对象之间将不会有引用共享,修改新对象不会影响原始对象。
javascript
function deepClone(obj){
let objClone = Array.isArray(obj)?[]:{};
if(obj && typeof obj === "object"){
for(key in obj){
if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制
if(obj[key] && typeof obj[key] === "object"){
objClone[key] = deepClone(obj[key]);
}else{ //如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let obj = {
a: "hello",
b: {
a: "hello",
b: 21
}
};
// 拷贝
let cloneObj = deepClone(obj);
console.log('-----原a', cloneObj.a); // hello
console.log('-----原b', cloneObj.b); // {a: "hello", b: 21}
只有引用数据类型才有深拷贝与浅拷贝之说。
- 引用类型的浅拷贝:拷贝的仅仅是"引用地址",两个对象的引用地址对应的还是同一个值,所以,无论改变哪个对象的值,另一个对象对应的值也会改变。
- 引用类型的深拷贝:把引用地址和值一起拷贝过来,一个对象的值改变,另一个对象的值不受影响。
3.vue过滤器
Vue.js 2.x 中的过滤器用于格式化文本,它们可以用在两个地方:双花括号插值和 v-bind 表达式。过滤器可以用于实现一些简单的文本转换,但它们并不能用于响应式数据处理,因为它们不是响应式的。
定义过滤器的方式如下:
javascript
// 定义全局过滤器
Vue.filter('filterName', function(value) {
// 返回处理后的值
return value.toUpperCase(); // 示例:将文本转换为大写
});
// 或者在组件选项中定义局部过滤器
filters: {
filterName: function(value) {
// 返回处理后的值
return value.toUpperCase(); // 示例:将文本转换为大写
}
}
使用过滤器的方式如下:
javascript
<!-- 在双花括号中使用 -->
{{ message | filterName }}
<!-- 在 v-bind 表达式中使用 -->
<div :id="id | filterName"></div>
以下是一个简单的 Vue 2.x 应用示例,展示了如何定义和使用过滤器:toUpperCase 过滤器将文本转换为大写。这个过滤器可以在插值或者绑定属性中使用,将相应的数据转换为大写形式。
javascript
<!DOCTYPE html>
<html>
<head>
<title>Vue 2.x 过滤器示例</title>
</head>
<body>
<div id="app">
<!-- 在文本插值中使用过滤器 -->
<p>{{ message | toUpperCase }}</p>
<!-- 在 v-bind 中使用过滤器 -->
<p :title="title | toUpperCase">将鼠标悬停查看过滤效果。</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
<script>
// 定义全局过滤器
Vue.filter('toUpperCase', function(value) {
return value.toUpperCase();
});
// 创建 Vue 实例
new Vue({
el: '#app',
data: {
message: 'hello vue!',
title: 'this is a title'
}
});
</script>
</body>
</html>
备注
过滤器传递多个参数
过滤器也可以接收额外参数,但是第一个参数一定是 | 前面的值。
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="../js/dayjs.min.js"></script>
</head>
<body>
<div id="root">
<h2>现在是:{{time}}</h2>
<h2>现在是:{{time | timeFormater}}</h2>
<h2>现在是:{{time | timeFormater('YYYY年MM月DD日')}}</h2>
</div>
<script type='text/javascript'>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data: {
time:1658137279155
},
filters:{
// 过滤器的本质就是一个函数
timeFormater(value,str="YYYY-MM-DD HH:mm:ss"){
return dayjs(value).format(str)
}
}
})
</script>
</body>
</html>
多个过滤器也可以串联
格式:{{变量名 | 过滤器1| 过滤器2|...}}
过滤器1的结果作为过滤器2的参数
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="../js/dayjs.min.js"></script>
</head>
<body>
<div id="root">
<h2>现在是:{{time}}</h2>
<h2>现在是:{{time | timeFormater}}</h2>
<h2>现在是:{{time | timeFormater('YYYY年MM月DD日') | mySlice()}}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: "#root",
data: {
time: 1658137279155,
},
filters: {
// 过滤器的本质就是一个函数
timeFormater(value, str = "YYYY-MM-DD HH:mm:ss") {
return dayjs(value).format(str);
},
mySlice(value) {
return value.slice(0, 4);
},
},
});
</script>
</body>
</html>
4.子组件可以改变父组件的数据吗,有什么影响
4.1 子组件可以改变父组件的数据(但是违反了Vue组件之间应该保持独立的原则)
在Vue.js中,子组件可以通过$parent属性访问父组件的实例,进而直接修改父组件的数据。但是这种做法违反了Vue组件之间应该保持独立的原则,可能会导致难以维护和理解的代码。
如果确实需要子组件直接修改父组件的数据,可以通过以下方式实现:
javascript
// 父组件
<template>
<div>
<ChildComponent />
<p>父组件的数据: {{ parentData }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentData: 'initial data'
};
}
};
</script>
// 子组件
<template>
<button @click="changeParentData">改变父组件的数据</button>
</template>
<script>
export default {
methods: {
changeParentData() {
this.$parent.parentData = '新的数据';
}
}
};
</script>
在这个例子中,子组件通过this.$parent.parentData直接访问并修改了父组件的数据。尽管这样做可以实现功能,但是不建议在实际项目中使用,因为它破坏了组件之间的耦合。
更好的做法是使用事件来通信,或者使用Vuex来管理状态。这样可以保持组件之间的清晰分离,并且使得状态的变化可以更容易地被跟踪和理解。
4.2 子组件直接改变父组件的数据,会导致以下问题
子组件不应该直接改变父组件的数据。在 Vue.js 中,数据流是单向的,父组件通过 props 将数据传递给子组件,子组件可以通过触发事件来通知父组件进行数据的修改。
如果子组件直接修改了父组件的数据,会导致以下问题:
- 数据流混乱:直接修改父组件的数据会破坏数据的单向流动,使得数据流变得不可预测和难以维护。
- 追踪困难:当组件数量增多时,直接在子组件中修改父组件的数据会使得代码变得难以追踪,因为你不清楚哪个组件修改了数据。 调试困难:当出现bug 时,直接在子组件中修改父组件的数据会增加调试的难度,因为你需要逐个检查所有子组件。
为了解决这个问题,Vue.js提供了两种解决方案:
- 通过事件进行通信:子组件通过 $emit方法触发一个自定义事件,父组件监听这个事件,并在事件处理函数中修改数据。这样可以保持数据的单向流动,同时使得数据修改的来源清晰可见。
- 使用.sync 修饰符 :Vue.js 提供了 .sync 修饰符,可以简化父子组件之间的数据双向绑定。但是需要注意,使用 .sync
时仍然需要通过触发事件来更新父组件的数据,不建议直接在子组件中修改。
通过事件进行通信
下面是一个示例,展示了如何通过事件进行父子组件通信:
父组件:
javascript
<template>
<div>
<p>父组件数据:{{ data }}</p>
<child-component :childData="data" @updateData="updateData"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
data() {
return {
data: '父组件数据'
};
},
components: {
ChildComponent
},
methods: {
updateData(newData) {
this.data = newData;
}
}
};
</script>
子组件:
javascript
<template>
<div>
<p>子组件数据:{{ childData }}</p>
<button @click="updateParentData">修改父组件数据</button>
</div>
</template>
<script>
export default {
props: ['childData'],
methods: {
updateParentData() {
const newData = '修改后的父组件数据';
this.$emit('updateData', newData);
}
}
};
</script>
在这个示例中,子组件通过点击按钮触发 updateParentData 方法,并通过 $emit 触发了一个名为 updateData 的自定义事件,将新的数据传递给父组件。父组件监听这个事件,并在事件处理函数中更新了数据。这样保持了父子组件之间的通信,同时避免了直接修改父组件数据的问题。
子组件父组件.sync 修饰符
在Vue.js中,.sync修饰符允许你创建一个会同步父组件与子组件之间数据的属性。这是通过语法糖实现的,它自动生成一个更新父组件数据的事件监听器。
使用.sync修饰符的基本语法如下:
javascript
<child :foo.sync="parentData" />
这相当于父组件监听子组件发出的update:foo事件,并在事件触发时更新本地的parentData数据。
下面是一个简单的例子:
父组件:
javascript
<template>
<div>
<label>Parent Message: {{ parentMessage }}</label>
<ChildComponent :message.sync="parentMessage" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: 'Hello from parent'
}
}
}
</script>
子组件:
javascript
<template>
<div>
<label>Child Message: {{ message }}</label>
<button @click="updateMessage">Change Message</button>
</div>
</template>
<script>
export default {
props: ['message'],
methods: {
updateMessage() {
this.$emit('update:message', 'Updated message from child');
}
}
}
</script>
在这个例子中,当在子组件中点击按钮时,子组件会发出一个update:message事件,并将新的消息作为参数。父组件监听这个事件,并更新parentMessage数据。
5.v-if和v-for的优先级
在Vue.js中,和v-for的优先级根据Vue的版本有所不同:
- 在Vue 2中,v-for的优先级高于v-if。这意味着在同一个元素上同时使用v-for和v-if时,v-for会先执行,然后v-if再根据v-for的结果进行条件判断。这种优先级顺序可能会导致不必要的渲染开销,因为会先创建所有的DOM元素,然后再根据条件过滤掉一些不需要显示的元素。
- 在Vue3中,情况发生了变化,v-if的优先级高于v-for。这意味着在同一个元素上同时使用v-for和v-if时,v-if会先执行,然后v-for再根据v-if的结果进行循环。这种优先级顺序是为了避免不必要的渲染开销,因为可以先判断是否需要渲染整个列表,然后再进行循环渲染。
- 不建议同时使用 v-if 和v-for,因为这可能会导致性能问题,并且使得代码难以理解和维护。如果确实需要根据条件显示或隐藏循环中的元素,建议将 v-if放在循环的外层,或者使用其他方法如计算属性(computed properties)来处理数据,这样可以避免同时使用 v-if 和v-for 时的性能问题,并使代码更加清晰易懂。
- 关于 v-if 和 v-show 的区别和使用场景:
v-if 是条件性地渲染元素。如果条件为假,元素不会被渲染到DOM中。适合用于不经常切换的条件渲染场景。
v-show 则是简单地切换元素的CSS属性 display。元素始终会被渲染到DOM中,只是根据条件显示或隐藏。适合用于频繁切换的条件渲染场景。
选择使用 v-if 还是 v-show 应根据具体的使用场景和性能考虑来决定。
6.前端如何保存用户登录状态
vue前端如何保存用户登录状态
在Vue前端应用中,保持用户登录状态通常有以下几种方法:
使用localStorage或sessionStorage保存token。
使用Vuex管理登录状态。
使用Vue Router的导航守卫来控制访问权限。
以下是使用localStorage保存token的示例:
javascript
// 用户登录后保存token
login({ commit }, userInfo) {
return new Promise((resolve, reject) => {
axios.post('/api/login', userInfo)
.then(response => {
const token = response.data.token;
localStorage.setItem('user-token', token); // 保存token到localStorage
commit('SET_TOKEN', token);
resolve();
})
.catch(error => {
reject(error);
});
});
},
// 检查用户是否已经登录
checkLogin({ commit }) {
const token = localStorage.getItem('user-token');
if (token) {
commit('SET_TOKEN', token);
}
}
// Vue Router导航守卫示例
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('user-token');
if (to.matched.some(record => record.meta.requiresAuth) && !token) {
// 需要登录的路由,且用户未登录
next({
path: '/login',
query: { redirect: to.fullPath } // 将目标路由作为重定向参数
});
} else {
// 用户已登录或不需要登录的路由
next();
}
});
在实际应用中,你可能还需要对token进行定时刷新,以保持登录状态的连续性。
7.数组中有哪些常用的方法,哪些会返回新数组
js数组中有哪些常用的方法,哪些会返回新数组
JavaScript 数组中有许多有用的方法,可以根据是否返回新数组将它们分为两类:
返回新数组的方法:
concat():连接两个或更多数组,并返回一个新数组。
filter(): 返回通过测试的所有元素的数组。
map():返回一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
slice(): 从某个已有的数组中返回选定的元素。
splice(): 通过删除现有元素和/或添加新元素来更改一个数组的内容。
不返回新数组的方法:
pop():移除数组的最后一个元素并返回该元素。
push(): 向数组的末尾添加一个或更多元素,并返回新的长度。
shift():移除数组的第一个元素并返回该元素。
unshift(): 向数组的开头添加一个或更多元素,并返回新的长度。
reverse(): 颠倒数组中元素的顺序。
sort(): 对数组的元素进行排序。
以下是一些示例代码:
javascript
// 返回新数组的方法
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let concatenatedArr = arr1.concat(arr2); // [1, 2, 3, 4, 5, 6]
let numbers = [1, 2, 3, 4, 5];
let evenNumbers = numbers.filter(num => num % 2 === 0); // [2, 4]
let doubledNumbers = numbers.map(num => num * 2); // [2, 4, 6, 8, 10]
let copiedArr = numbers.slice(); // [1, 2, 3, 4, 5]
// 返回新数组的方法
let removed = numbers.splice(2, 1); // 移除从索引2开始的1个元素,即数字3
// 不返回新数组的方法
let lastElement = numbers.pop(); // 移除并返回数组的最后一个元素,即5
numbers.push(6); // 在数组末尾添加6,numbers现在是[1, 2, 3, 4, 6]
let firstElement = numbers.shift(); // 移除并返回数组的第一个元素,即1
numbers.unshift(0); // 在数组开头添加0,numbers现在是[0, 2, 3, 4, 6]
numbers.reverse(); // 颠倒数组中元素的顺序,numbers现在是[6, 4, 3, 2, 0]
numbers.sort((a, b) => a - b); // 对数组的元素进行排序,numbers现在是[0, 2, 3, 4, 6]
改变数组的方法:
push 、pop
shift 、unshift
sort、reverse
splice:添加或删除数组中的元素
不会改变数组的方法:
concat
join
toString
every 、some
includes
find
filter
reduce
map、forEach
findIndex 、 indexOf、lastIndexOf
slice:截取元素
flat
8.for in和for of 什么区别
在JavaScript中,for...in和for...of都是用来遍历集合的循环控制结构,但它们之间存在一些重要的区别:
用途不同:
for...in循环用于遍历对象的属性。
for...of循环用于遍历可迭代对象(如数组,字符串,Set,Map等)的值。
遍历的内容不同:
for...in会遍历对象所有的可枚举属性,包括原型链上的属性。
for...of遍历的是可迭代对象的实际值,不包括原型链上的值。
循环控制不同:
for...in循环使用对象的属性名作为循环变量的值。
for...of循环使用迭代器的值作为循环变量的值。
语法结构不同:
for...in语法:
javascript
for (let 变量 in 对象) {
// 使用变量来引用属性名
}
for...of语法:
javascript
for (let 变量 of 可迭代对象) {
// 使用变量来引用迭代器的值
}
迭代的可选性不同:
for...in循环中,即使属性是undefined或原型链上的属性,只要可枚举,也会被遍历到。
for...of循环中,只有可迭代对象中实际存在的值才会被遍历到。
与数组的索引关系:
for...in不直接与数组的索引相关联,所以不能直接获取索引。
for...of可以与数组的索引相关联,通过数组的entries()方法,可以同时获取索引和值。
javascript
// 对象
const obj = { a: 1, b: 2, c: 3 };
// 使用 for...in 遍历
for (let key in obj) {
console.log(key, obj[key]); // 输出 a=1, b=2, c=3
}
// 数组
const arr = [10, 20, 30];
// 使用 for...of 遍历
for (let value of arr) {
console.log(value); // 输出 10, 20, 30
}
// 使用 for...of 遍历数组索引和值
for (let [index, value] of arr.entries()) {
console.log(index, value); // 输出 0=10, 1=20, 2=30
}
9.watch和computed有什么区别
computed是计算属性,watch是监听属性。
它们的共同点都是用来监听数据的变化。
它们有两个区别:
1、是否有缓存功能,computed是有缓存的,只有它依赖的属性值改变的时候,它才会进行计算。而watch是没有缓存功能的,只要监听的数据变化了,它就会触发相应的操作。
2、是否支持异步,computed是不支持异步的,当computed内有异步操作的时候,它是监听不到数据变化的。watch是支持异步操作的,适合监听路由和设置计时器等。
1、computed是计算属性;watch是监听,监听data中的数据变化。
2、computed支持缓存,当其依赖的属性的值发生变化时,计算属性会重新计算,反之,则使用缓存中的属性值;watch不支持缓存,当对应属性发生变化的时候,响应执行。
3、computed不支持异步,有异步操作时无法监听数据变化;watch支持异步操作。
4、computed第一次加载时就监听;watch默认第一次加载时不监听。immediate设置成true时,第一次加载才会监听
5、computed中的函数必须调用return;watch不是。
6、使用场景:
computed:一个属性受到多个属性影响,如:购物车商品结算。
watch:一个数据影响多条数据,如:搜索数据。
数据变化响应,执行异步操作,或高性能消耗的操作,watch为最佳选择。
10.css中哪些属性会引起重绘
css中哪些属性会引起重绘和重排
在CSS中,重排(reflow)和重绘(repaint)是浏览器渲染过程中的两个性能问题。
重排:当DOM的变动影响了元素的尺寸、布局或者位置时,浏览器需要重新计算元素的位置,这个过程就叫重排。
重绘:当DOM的变动仅影响了元素的样式,而不影响其布局时,浏览器只需要重新绘制已改变的元素,这个过程叫重绘。
引起重排的CSS属性:
- 定位属性(position)
- 浮动(float)
- 宽度/高度(width/height)
- 显示属性(display)
- 字体大小(font-size)
- 边框属性(border)
- 内外边距(margin/padding)
- 盒模型属性(box-sizing)
- 滚动条的出现与消失
引起重绘的CSS属性:
- 颜色(color)
- 背景色(background-color)
- 颜色渐变(gradients)
- 字体(font-family)
- 文本装饰(text-decoration)
- 文本字体大小(font-size)
- 文本样式(font-style)
- 文本加粗(font-weight)
- 文本行高(line-height)
- 文本对齐(text-align)
- 文本阴影(text-shadow)
- 线条样式(border-style)
- 线条宽度(border-width)
- 线条颜色(border-color)
- 圆角(border-radius)
- 背景图像(background-image)
- 背景位置(background-position)
- 背景大小(background-size)
- 背景重复(background-repeat)
- 背景Origin(background-origin)
- 背景Clip(background-clip)
- 背景颜色(background-color)
- 外边距断层(margin fragmentation)
- 内边距断层(padding fragmentation)
- 变形(transform)
- 动画(animation)
- 伪元素(::before, ::after)
- 渐变颜色(gradient backgrounds)
- will-change
注意:并非所有的属性变化都会引起重排和重绘,有时候浏览器的优化策略会使得某些改变只导致一种问题。
为了优化性能,可以尽量减少重排和重绘的次数,常见的方法包括:
- 使用CSS3动画和Transitions来替代频繁的JavaScript动画。
- 使用CSS变量(Custom Properties)。
- 在DOM外进行复杂的DOM操作。
- 缓存布局信息,避免重复的计算。
- 使用requestAnimationFrame、setTimeout等方法来批量DOM更改。
- 使用window.getComputedStyle来读取样式,而不是直接访问style属性,因为前者不会引起重排。
对于复杂动画,可以使用离屏canvas技术。
使用will-change属性来提示浏览器在未来的时间点进行重排优化。
11.flex布局
Flex布局,也称为弹性布局(Flexible Box Layout),是一种CSS布局模式,旨在提供一种更有效的方式来布局、对齐和分配容器中项目之间的空间,即使它们的大小未知或动态变化。
Flex布局的核心概念
- 容器与项目:采用Flex布局的元素称为Flex容器(flex container),其所有子元素自动成为容器成员,称为Flex项目(flex item)。
- 主轴与交叉轴:Flex容器中默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴是Flex项目的排列方向,交叉轴则是垂直于主轴的方向。
Flex布局的属性
- flex-direction:决定主轴的方向,有四个值:row(默认,水平排列)、row-reverse(水平反向排列)、column(垂直排列)、column-reverse(垂直反向排列)。
- flex-wrap:定义如果一条轴线排不下时如何换行,有三个值:nowrap(不换行)、wrap(换行)、wrap-reverse(反向换行)。
- justify-content:定义项目在主轴上的对齐方式,如flex-start、flex-end、center、space-between、space-around等。
- align-items:定义项目在交叉轴上的对齐方式,如flex-start、flex-end、center、baseline等。
- align-content:定义多根轴线的对齐方式,如flex-start、flex-end、center、space-between、space-around等。
Flex布局的优势
- 灵活性:允许项目在容器中灵活地扩展和收缩,以填充可用空间或调整大小以适应内容。
- 对齐方式:提供了各种对齐选项,可以轻松实现水平和垂直对齐。
- 方向控制:可以方便地改变主轴的方向,使项目在水平或垂直方向上排列。
- 空间分配:自动处理项目之间的空间分配,使其看起来更加整洁和平衡。
Flex布局与传统的布局方式的对比
Flex布局相比于传统的布局方式(如浮动和定位),更加灵活,易于调整,也更加适应不同的设备和屏幕尺寸。它解决了传统布局方式在响应式设计中的局限性,提供了一种更有效的方式来布局、对齐和分配容器中项目之间的空间。
12.怎么遍历对象
13.const对象的属性可以修改吗
在 JavaScript 中,使用 const 关键字定义的对象本身是不可重新赋值的,但对象内部的属性是可以修改的。这是因为 const 保证的是变量引用的不变性,而不是对象本身的不变性。
14.promise,有哪些api
Promise是一种用于处理异步操作的对象,它代表了一个异步操作的最终完成(或失败)及其结果值的状态。Promise可以链式调用,并能处理多个异步任务,使得代码更加清晰易读。Promise对象是一个构造函数,用来生成Promise实例。Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,它们是两个函数,由JavaScript引擎提供,不需要自己部署。
resolve函数的作用是,将Promise对象的状态从"未完成"变为"成功",在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用是,将Promise对象的状态从"未完成"变为"失败",在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。需要注意的是,它代表异步操作最终完成或者失败及其结果值的状态,那么我们就会有疑问,promise会有几种状态呢?
Promise的状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,才能决定当前是哪一种状态,任何其他操作都无法改变这个状态。
关于Promise常用的api
Promise.all()
总结:Promise.all(),当且仅当Promise 都是 fulfilled 状态时,才会被then 接收到一个数组,数组内容分别对应Promise resolve的结果,当某一个Promise出现rejected时,catch会捕获第一个rejected的error。
javascript
// 示例 1
const Promise1 = new Promise((resolve, reject) => {
resolve("promise1 完成");
});
const Promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "Promise2 完成");
});
const Promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve("Promise3 完成"), 100);
});
Promise.all([Promise1, Promise2, Promise3]).then((value) => {
console.log(value);
});
// 1s 后打印 ['promise1 完成', 'Promise2 完成', 'Promise3 完成']
Promise.any()
总结:Promise.any(),接受一个promise数组,当这些promise中,其中有第一个状态改变成fullFilled时,就能在then里面被接收到,如果都是rejected时,能被catch到,并返回一个AggregateError。
javascript
const Promise1 = new Promise((resolve, reject) => {
reject("promise1 完成");
});
const Promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "promise2 完成");
});
const Promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve("Promise3 完成"), 100);
});
Promise.any([Promise1, Promise2, Promise3])
.then((result) => {
console.log("Promise: ",result);
})
.catch(err => {
console.log("err:", err)
})
// 100ms之后出入 Promise: Promise3 完成
Promise.race()
总结:Promise.race() 接收一个promise 数组,通过赛跑的方式,看谁先完成(不管是fullFilled还是rejected)就获取相应的值resolve(res)被then接收,reject(err)会被catch到。
javascript
const Promise1 = new Promise((resolve, reject) => {
resolve("promise1 完成");
});
const Promise2 = new Promise((resolve, reject) => {
setTimeout(reject, 1000, "promise2 完成");
});
const Promise3 = new Promise((resolve, reject) => {
setTimeout(() => resolve("Promise3 完成"), 100);
});
Promise.race([Promise1, Promise2, Promise3])
.then((result) => {
console.log("Promise: ",result); // Promise: promise1 完成
})
.catch(err => {
console.log("err:", err)
})
// Promise: promise1 完成
8.面试4
1.前端排序
插入排序
typescript
<!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 pickArr = [5, 2, 1, 3, 6, 8, 4, 5, 7, 0, 15];
function pickFun(params) {
debugger
let preIndex = 0; // 进行大小对比的基准数据的下标
let current = 0; // 进行大小对比的当前选中的剩余数量值
for (let g = 1; g < params.length; g++) {
preIndex = g - 1; // 进行基准数据赋值
current = params[g]; // 获取当前进行对比的剩余数量值
while (preIndex >= 0 && params[preIndex] > current) {
console.log('params',params)
params[preIndex + 1] = params[preIndex];
preIndex--;
}
params[preIndex + 1] = current;
}
return params;
}
const pickAns = pickFun(pickArr);
console.log(pickAns, '----------------------插入排序');
// [0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 15] '----------------------插入排序'
</script>
</body>
</html>
快速排序
typescript
<!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 quickArr = [5,2,1,3,6,8,4,5,7,0,15];
function quickFun(params) {
debugger
//当进行递归的数组的长度小于等于 1 的时候直接返回该数组
if(params.length<=1){
return params
}
let middleIndex = Math.floor(params.length/2)
let middleNum = params.splice(middleIndex,1)[0]
let leftList = []
let rightList = []
for (let i = 0; i < params.length; i++) {
// const element = params[i];
if(middleNum>params[i]){
leftList.push(params[i])
}else{
rightList.push(params[i])
}
}
return quickFun(leftList).concat(middleNum,quickFun(rightList))
}
const quickAns = quickFun(quickArr);
console.log(quickAns,'----------------------快速排序');
</script>
</body>
</html>
冒泡排序
typescript
<!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>
let choseArr = [5, 2, 1, 3, 6, 8, 4, 5, 7, 0, 15];
for (let i = 0; i < choseArr.length; i++) {
console.log('choseArr1',choseArr[i],i,choseArr)
for (let j = 0; j < choseArr.length-1; j++) {
console.log('choseArr2',choseArr[j],j)
if(choseArr[j]>choseArr[j+1]){
[choseArr[j+1],choseArr[j]]=[choseArr[j],choseArr[j+1]]
}
}
}
console.log('choseArr',choseArr)
</script>
</body>
</html>
希尔排序
typescript
<!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 hillArr = [5, 2, 1, 3, 6, 8, 4, 5, 7, 0, 15];
function hillFun(arr) {
debugger
//第一层循环,确定间隔数
// 这里的 gap 相当于插入排序中的 1 ,所以在第二层循环中 preIndex = i-gap; 相当于插入排序中的 preIndex = g - 1;
for (let gap = parseInt(arr.length / 2); gap > 0; gap = parseInt(gap / 2)) {
//第二层循环,使用插入排序
for (let i = gap; i < arr.length; i++) {
let preIndex = i - gap;
let current = arr[i]
while (preIndex >= 0 && current < arr[preIndex]) {
arr[preIndex + gap] = arr[preIndex];
preIndex -= gap;
}
arr[preIndex + gap] = current
}
}
return arr;
}
const hillAns = hillFun(hillArr);
console.log(hillAns, '----------------------希尔排序');
// [0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 15] '----------------------希尔排序'
</script>
</body>
</html>
选择排序
typescript
<!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>
let choseArr = [5, 2, 1, 3, 6, 8, 4, 5, 7, 0, 15];
for (let i = 0; i < choseArr.length; i++) {
let minIndex = i
for (let j = i+1; j < choseArr.length; j++) {
if(choseArr[j]<choseArr[minIndex]){
minIndex = j
}
}
[choseArr[i],choseArr[minIndex]]=[choseArr[minIndex],choseArr[i]]
}
console.log('choseArr',choseArr)
</script>
</body>
</html>
2.Vue中router和route的区别
router:
- 定义:Vue Router是一个Vue.js插件,用于实现单页应用(SPA)的路由功能。它通过URL路径匹配到对应的Vue组件,并将其渲染到页面上。
- 使用方法:需要通过vue.use(VueRouter)和VueRouter构造函数得到一个实例对象,它是一个全局的对象。
- 功能:用于声明和处理路由规则、监听路由变化、跳转路由等。
route:
- 定义:route是Vue Router在URL路径匹配后生成的一个对象,表示当前路由的状态和信息。
- 使用方法:通过this.$route访问,用于获取当前路由的路径、参数、查询参数等信息。
- 功能:用于表示当前路由状态,包含path、params、query、hash、fullPath、matched等属性。
router和route的具体应用场景和代码示例
在实际应用中,$router
用于路由跳转和管理,而$route
用于获取当前路由的信息。例如:
javascript
使用this.$router.push('/home')跳转到首页。
使用this.$router.replace('/about')替换当前页面。
通过this.$route.path获取当前路由的路径。
通过this.$route.params.id获取路由参数。
通过this.$route.query.id获取查询参数。
通过this.$route.hash获取URL的哈希部分。
3.Vue路由传参的几种方式
vue路由传参是指嵌套路由时父路由向子路由传递参数,否则操作无效
一、利用"router-link"路由导航
父组件:
使用 <router-link to = "/跳转路径/传入的参数"></router-link>
例如:<router-link to="/a/123">routerlink传参</router-link>
子组件:
使用 this.$route.params.num
接收父组件传递过来的参数
javascript
mounted () {
this.num = this.$route.params.num
}
路由配置:{ path: '/a/:num', name: A, component: A }
地址栏中的显示:http://localhost:8080/#/a/123
二、调用$router.push实现路由传参
父组件: 绑定事件,编写跳转代码
javascript
<button @click="deliverParams(123)">push传参</button>
methods: {
deliverParams (id) {
this.$router.push({
path: `/d/${id}`
})
}
}
子组件:this.$route.params.id接收父组件传递过来的参数
javascript
mounted () {
this.id = this.$route.params.id
}
路由配置:{path: '/d/:id', name: D, component: D}
地址栏中的显示:http://localhost:8080/#/d/123
三、通过路由属性中的name匹配路由,再根据params传递参数
父组件: 匹配路由配置好的属性名
javascript
//编程式
<button @click="deliverByName()">params传参</button>
deliverByName () {
this.$router.push({
name: 'B',
params: {
sometext: '熊出没'
}
})
}
//声明式
<router-link :to="{ name: 'B', params: { sometext: '熊出没' } }">
子组件:
javascript
<template>
<div id="b">
This is page B!
<p>传入参数:{{this.$route.params.sometext}}</p>
</div>
</template>
路由配置:{path: '/b', name: 'B', component: B} 路径后面不需要再加传入的参数,但是name必须和父组件中的name一致
地址栏中的显示: http://localhost:8080/#/b 可以看出地址栏不会带有传入的参数,且再次刷新页面后参数会丢失
四、通过query来传递参数
父组件
javascript
//编程式
<button @click="deliverQuery()">query传参</button>
deliverQuery () {
this.$router.push({
path: '/c',
query: {
sometext: '我是光头强'
}
})
}
//声明式
<router-link :to="{ path: '/c', query: { sometext: '我是光头强' } }">
子组件
javascript
<template>
<div id="C">
This is page C!
<p>这是父组件传入的数据: {{this.$route.query.sometext}}</p>
</div>
</template>
路由配置:{path: '/c', name: 'C', component: C}
无需做任何修改
地址栏中的显示:http://localhost:8080/#/c?sometext=这是小羊同学
相当于再url地址后面拼接参数(中文转码)
3.常用的数组方法有哪些?
改变原数组:push、pop、shift、unshift、sort、splice、reverse
不改变原属组:concat、join、map、forEach、filter、slice
4.keep-alive
在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性。
5.nextTick
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM