携程前端开发一面
-
自我介绍
-
介绍一下你的项目
-
你在项目过程中学到了什么
-
你在项目中学习到了哪些技术
-
怎么解决跨域问题的?
出现跨域问题:Access-Control-Allow-Origin — 受同源策略限制:同协议同域名同端口
在前端使用代理,通过代理访问后端,首先配置请求baseURL,然后在vue.config.js中配置proxy设置代理,target即接口域名,将changeOrigin设为true并在pathRewrite中将baseURL重写为""。
原理:浏览器禁止跨域,但服务器不禁止,proxyTable为我们提供了一个可以跨域的代理中转服务器服务,实际上是将请求发给自己的服务器,再由服务器转发给后端服务器,做了一层代理。
-
你说你使用了vue-Router?那你知道怎么动态定义path吗?
动态路由匹配:把某种模式匹配到的所有路由,使用动态路径参数映射到同个组件
// 有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染 const User = { template: '<div>User:{{ this.$route.params.id }}</div>' } // 一个“路径参数”使用冒号:标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。 const router = new VueRouter({ routes: [ // 动态路径参数 以冒号开头 { path: '/user/:id', component: User } ] }) // 页面跳转时 this.$router.push({ path: `/user/${value.id}`, });
在js中实现页面跳转并传参的方法:
1. this.$router.push({path: '/t',query:{ index:'1'}}); 接收参数:this.$route.query.index 2.this.$router.push({name: 'Test',params:{ index:'1'}}); 接收参数:this.$route.params.index
-
为什么要定义公共组件?
使用场景:项目中若多个页面都显示有相同的区域内容,则该公共区域内容可以封装成公共组件进行使用。
提升整个项目的开发效率,能够把页面抽象成多个相对独立的模块,让我们使用小型、独立和可复用的组件构建大型应用,让代码变得“高内聚”和“低耦合”,解决了项目开发中
效率低
、难维护
、复用性低
等问题。 -
vue的生命周期?
vue的生命周期分为四个阶段:创建、挂载、更新、销毁。
一个生命周期中有八个钩子函数:
初始化:生命周期、事件、但数据代理还未开始;
beforeCreate:无法访问data中的数据、methods中的方法;
初始化:数据监测、数据代理;
created:可访问data中的数据、methods中的方法,在这个阶段请求数据为mounted渲染做准备;
生成:在内存中生成虚拟dom;页面还不能显示解析好的内容;
beforeMount:此时dom未经Vue编译,所有对dom的操作都不奏效;
生成:将虚拟dom转为真实dom插入页面;
mounted:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作;
当data有更新时:
beforeUpdate:数据是新的,页面是旧的;
根据新数据生成新的虚拟dom树并与旧的虚拟dom比较,完成页面更新;
updated:数据是新的,页面也是新的;
当vm.$destroy() 被调用:
beforeDestroy:关闭定时器、取消订阅消息、解绑自定义事件。
destroyed:组件销毁时触发,vue实例解除事件监听以及和dom的绑定,但DOM节点依旧存在
-
destroyed时候vue会干什么?
Vue 实例指示的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
-
怎么清除定时器?
定时器(timer)由 setTimeout() 和 setInterval() 两个函数完成。
timer = setTimeout( func|code, delay) 用于在指定的毫秒数后调用函数或计算表达式; timer = setInterval()( func|code, delay) 用于每隔一定时间就调用函数,方法或对象; 清理定时器:clearInterval(timer),clearTimeout(timer);
-
事件处理过程?
一个事件的处理过程分为捕获、目标、冒泡三个阶段。
捕获阶段:当 DOM 树的某个节点发生了一个事件,这个事件会从 dom树根节点发出,不断经过下级节点直到触发事件的目标节点。在到达目标节点之前的过程,就是捕获阶段(Capture Phase)( 所有经过的节点,都会触发这个事件。捕获阶段的任务就是建立这个事件传递路线,以便后面冒泡阶段逆着这条路线返回根节点);
目标阶段:在目标节点上触发这个事件;
冒泡阶段:事件开始时,由目标节点接收事件,然后逐级传播到其父节点
-
怎么注册捕获阶段的事件处理函数和冒泡阶段的事件处理函数?
标准事件模型(DOM2级):
document.getElementById("myElement").addEventListener("event", eventHandler, flag); // flag : true:捕获阶段执行; false:冒泡阶段执行;
特性:可在同一DOM元素上绑定多个事件处理函数;
可设置在冒泡阶段执行还是在捕获阶段执行。
原始事件模型(DOM0级):
document.getElementById("myElement").onclick = function() {};
只支持冒泡、不支持捕获,同一类型事件只能绑定一次,但是绑定速度快。
-
怎么阻止捕获/冒泡?
要阻止事件冒泡,可以使用
event.stopPropagation()
方法。在事件监听器中调用该方法可以阻止事件继续向上冒泡到父级元素。这样,其他父级元素上的相同类型的事件监听器将不会被触发。但是使用
event.stopPropagation()
不能完全阻止事件的传播。事件仍会在当前目标元素上继续进行处理,例如执行默认行为,要完全取消事件传播并阻止默认行为,可以使用event.preventDefault()
方法。 -
事件代理是什么?
利用事件冒泡机制,将一个或一组元素的事件委托道它的父层或者更外层元素上。
-
你的项目有用到事件代理吗?你之后可以思考一下为什么没用
为什么没用:
适合事件委托的事件:
click
,mousedown
,mouseup
,keydown
,keyup
,keypress
;优点:1. 减少整个页面所需的内存,提升整体性能;
2. 减少重复工作;
局限性:1.
focus
、blur
这些事件没有事件冒泡机制,所以无法进行委托绑定事件; 2.
mousemove
、mouseout
这样的事件需要通过位置去计算定位,对性能消耗高,不适合; 3. 如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件;
-
你了解promise吗?
promise是实现异步编程的一种解决方案,可以解决传统回调函数引起的回调地狱的问题。
回调地狱是指回调函数中嵌套回调函数的情况,为实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护
Promise构造函数接收一个函数作为参数,这个函数就是我们要处理的异步任务,函数的两个参数是resolve,reject。
promise对象有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
异步任务执行成功时调用resolve函数返回结果,不成功则调用reject。
then()
方法是实例状态发生改变时的回调函数,第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数。then
方法返回的是一个新的Promise
实例,所以promise
能够链式书写。catch()
方法是用于指定发生错误时的回调函数,Promise
对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。总结一下,当我们写代码遇到异步回调时,我们想让异步代码按照我们想要的顺序执行,如果按照传统的嵌套方式,就会出现回调地狱,这样的代码不利于维护,我们可以通过 Promise 对象进行链式编程来解决,这样尽管可以解决问题,但是ES7给我们提供的 async/await 语法糖,可以使得异步代码看起来更像是同步代码。 //封装一个返回promise的异步任务 function fn(str) { var p = new Promise(function (resolve, reject) { var flag = true; setTimeout(function () { if (flag) { resolve(str) } else { reject('处理失败') } }) }) return p; } //封装一个执行上述异步任务的async函数 async function test(){ var res1 = await fn('武林要以和为贵'); //await直接拿到fn()返回的resolve的数据,并且赋值给res var res2 = await fn('要讲武德'); var res3=await fn('不要搞窝里斗'); console.log(res1,res2,res3); } //执行函数 test();
-
你用过npm包吗?
没用过,不打算准备这部分内容,所以不写了。
-
css怎么实现一个header?
都用的是elementUI框架做的,没咋写过css,不打算准备这部分内容,所以也不写了。
-
你知道html有哪些标签?
<font color="" size="" face="字体类型"> 字体标签 <br/> 换行 <p align="left/right/center"> 段落(段前段后自动加空白行) <h1> <h2> <h3>... 标题标签   空格 <!--html标签--> 注释 <img src="绝对路径/相对路径" width="" height=""> <ul> <li></li>列表条目项标签,用于在效果中定义一个列表的条目 </ul>无序列表标签 <ol> <li></li>列表条目项标签,用于在效果中定义一个列表的条目 </ol>有序列表标签 <a href="跳转路径">text</a> 超链接标签 <span></span> 行级的块标签,用于在效果中一行上定义一个块,进行内容显示。 <div></div> 块级的块标签,用于在效果中 定义一块,默认占满一行,进行内容的显示。 <table border="边框粗细" width="> <tr> //表格的行标签,用于在效果中定义一个表格行 <td>表格的单元格标签,用于在效果中定义一个表格行中的单元格</td> </tr> </table> <th>表格的表头单元格标签,用于在效果中定义一个表格行中的表头单元格</th> <th>和<td>唯一区别:<th>内容 居中加粗
-
你项目用的vue2还是vue3?
会根据回答问一些特性
-
method和computed有什么区别?
computed可以完成各种复杂的逻辑,包括运算、函数调用等,只要最终返回一个结果就可以。
methods: { sumScore: function () { console.log("methods方式调用!"); return (this.mathScore - 0) + (this.englishScore - 0); } }, computed: { // 默认是纯get方式,也是单项绑定 sumScore1: function () { console.log("compute的纯get方式调用"); return (this.mathScore - 0) + (this.englishScore - 0); }, // 采用get加set方式 sumScore2: { get: function () { console.log("compute的get方式调用"); return (this.mathScore - 0) + (this.englishScore - 0); }, // 当在输入框中更改了总分后,两项成绩就会分别取到新总分的平均值,从而实现双向绑定 set: function (newValue) { console.log("compute的set方式调用"); var avgScore = newValue / 2; this.avgScore = avgScore; } } }
用 computed 属性方法编写的逻辑运算,返回的结果直接当作一个变量值使用。computed 具有缓存功能,在系统刚运行的时候调用一次。只有当计算结果发生变化才会被调用。 用 methods 方法编写的逻辑运算,返回的是一个函数,调用时要加()。methods方法页面刚加载时调用一次,以后只有被调用的时候才会被调用。
methods与compute纯get方式都是单向绑定,不可以更改输入框中的值。compute的get与set方式是真正的双向绑定。
-
data和computed有什么区别?
data 和 computed 最核心的区别在于 data 中的属性并不会随赋值变量的改动而改动,而computed 会。
但是computed也监听不到数组或对象中元素的变化。
父组件改变props时: 子组件如果直接使用 props,会触发子组件更新; 子组件如果将 props 放进 data 中 再使用,不会触发子组件更新; 子组件如果将 props 放进 computed 中再使用,会触发子组件更新
Vue 把数据抽象成了两层,第一层就是简单的数据(data),第二层就是 computed (依赖于 data,也就是依赖于前一层)。第二层可以引用第一层的数据,而第一层却不能引用第二层的数据。
它的本质是 Vue 实例在渲染时数据解析的顺序为
props->methods->data->computed->watch->created。
关于 computed 和 watch 的差异:
1.computed 是计算一个新的属性 默认初始化会执行一次,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm 上的数据 默认需配置才会执行,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props) 2.computed 具有缓存性,只有当依赖变化后,才会计算新的值,computed 第一次加载就监听,而 watch 则是当数据发生变化便会调用执行函数 3.从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据
-
父子组件通信的方式?
父组件传递数据给子组件:
props: 1. 子组件设置props属性,定义接收父组件传递过来的参数; 2. 父组件在使用子组件标签中通过字面量来传递值; // Children.vue props:{ name:String // 接收的类型参数 age:{ type:Number, // 接收的类型为数值 default:18, // 默认值为18 require:true // age属性必须传递 } } // Father.vue <Children name="jack" age=18 />
子组件传递数据给父组件:
$emit: 1. 子组件通过$emit触发自定义事件,$emit第二个参数为传递的数值 2. 父组件绑定监听器获取到子组件传递过来的参数 // Children.vue this.$emit('add', good) // Father.vue <Children @add="cartAdd" /> components: {Children}, methods:{ cartAdd(good){//触发子组件城市选择-选择城市的事件 console.log('Father cartAdd:'+good) } }