vue面试题总结

一:什么是 vue 生命周期

vue实例对象从创建之初到销毁的过程,vue所有功能的实现都是围绕其生命周期进行的,在生命周期的不同阶段调用对应的钩子函数实现组件数据管理和DOM渲染两大重要功能

二:vue生命周期的作用是什么 

管理vue实例从创建到销毁的过程,并且提供给开发者加入自定义代码逻辑提供入口。(d)

三:第一次页面加载会触发哪几个钩子 

beforeCreate, created, beforeMount, mounted 

 四:简述每个周期具体适合哪些场景

1.beforeCreate  vue实例创建之前,此时获取不到监听数据和方法,如果有需要在初始化就执行的全局方法   可以在这里调用

2.created  vue实例创建完成,dom没有渲染,在这个阶段可以进行ajax调用

3.beforeMount 执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中

4.mounted 执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。 如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行

5.beforeUpdate  当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步

6.updated 页面显示的数据和data中的数据已经保持同步了,都是最新的

7.beforeDestory Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁,在这里可以处理清除定时器等功能

8.destroyed 这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。

 五:created和mounted的区别

他们是vue生命周期的两个不同的阶段,执行时机不同。

created:是在模板渲染成HTML前调用的,此时data已经准备完毕,el仍是undefined,因为没有渲染成HTML,所以不能操作dom节点,可以在这个阶段执行ajax;

mounted:是在模板渲染成HTML之后调用的,此时data,el都已准备好,可以操作html的dom节点,可以通过id什么的来查找页面元素;

六:vue获取数据在哪个周期函数 

一般 created/beforeMount/mounted 皆可.

比如如果要操作 DOM , 那肯定 mounted 时候才能操作

七:请详细说下你对vue生命周期的理解 

vue生命周期总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。

创建前/后:在beforeCreated阶段,vue实例的挂载完el还没有。

载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。

更新前/后:当data变化时,会触发beforeUpdate和updated方法。

销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在。

八:mvvm 框架是什么?

MVVM是Model-View-ViewModel的简写。即模型-视图-视图模型。

【Model】指的是后端传递的数据。

【View】指的是DOM。

【ViewModel】mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:一是将【模型】转化成【DOM】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将【DOM】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。

总结:在MVVM的框架下DOM和模型是不能直接通信的。它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的DOM做自动更新,而当用户操作DOM,ViewModel也能监听到DOM的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信。

这也是vue官网中提到的【虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。】 View指的是dom而非真正的视图

vue官方给了一个图

谈谈你对 MVVM的理解?

为什么要有这些模式:目的:职责划分、分层 ( 将Model层、View层进行分类 ) 借鉴 后端思想。对于前端而言就是如何将数据同步到页面上。

MVC 模式 : Backbone + underscore + jquery

对于前端而言,数据变化无法同步到视图中。需要将逻辑聚拢在controller 层

MVVM 模式 : 映射关系的简化 (隐藏controller)

九:vue-router 是什么?它有哪些组件

通俗来讲主要是来实现页面的跳转,通过设置不同的path,向服务器发送的不同的请求,获取不同的资源。

官网回答:

Vue Router 是 Vue.js 官方的路由管理器

包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

组件 router-viewrouter-link

十:active-class 是哪个组件的属性

router-link

默认值: "router-link-active"

设置链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。

十一:怎么定义 vue-router 的动态路由? 怎么获取传过来的值 

params传参:

  1. 配置路由格式:/router/:id
  2. 传递的方式:在path后面跟上对应的值
  3. 传递后形成的路径:/router/123


// 方法1:
<router-link :to="{ name: 'users', params: { uname: wade }}">按钮</router-link>
// 方法2:
this.$router.push({name:'users',params:{uname:wade}})
// 方法3:
this.$router.push('/user/' + wade)


可以通过$route.params.userid 获取你说传递的值
 

query传参:

  1. 配置路由格式:/router,也就是普通配置
  2. 传递的方式:对象中使用query的key作为传递方式
  3. 传递后形成的路径:/route?id=123


// 方法1:
<router-link :to="{ name: 'users', query: { uname: james }}">按钮</router-link>
// 方法2:
this.$router.push({ name: 'users', query:{ uname:james }})
// 方法3:
<router-link :to="{ path: '/user', query: { uname:james }}">按钮</router-link>
// 方法4:
this.$router.push({ path: '/user', query:{ uname:james }})
// 方法5:
this.$router.push('/user?uname=' + jsmes)


可以通过$route.query 获取你所传递的值

十二:vue-router 有哪几种导航钩子?

1.全局守卫: router.beforeEach

const router = new VueRouter({ ... })
  router.beforeEach((to, from, next) => {
  // ...
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中

每个守卫方法接收三个参数:

to: Route: 即将要进入的目标 路由对象

from: Route: 当前导航正要离开的路由

next: Function: 一定要调用该方法来resolve这个钩子。执行效果依赖 next 方法的调用参数。

  • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed (确认的)。

  • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

  • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在router-link的 to prop或 router.push中的选项。

  • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError()注册过的回调。

确保要调用 next方法,否则钩子就不会被 resolved

 

2.全局解析守卫: router.beforeResolve

用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是:在导航被确认之前,同时在所有组件内守卫异步路由组件被解析之后,解析守卫就被调用

即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。

 

3.全局后置钩子: router.afterEach

全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数不会改变导航本身

router.afterEach((to, from) => {
  // ...
})

 

4.路由独享的守卫: beforeEnter

在路由配置上直接定义 beforeEnter 守卫

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

 

5.组件内的守卫: beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave

路由组件内直接定义以下路由导航守卫

beforeRouteEnter

beforeRouteUpdate (2.2 新增)

beforeRouteLeave

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  //不过,你可以通过传一个回调给 next来访问组件实例。
  //在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
  beforeRouteEnter (to, from, next) {
    next(vm => {
      // 通过 `vm` 访问组件实例
    })
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

 

 

十二:$route 和 $router 的区别

一个是用来获取路由信息的,一个是用来操作路由的

$route

route是路由信息对象,里面主要包含路由的一些基本信息,包括name、meta、path、hash、query、params、fullPath、matched、redirectedFrom

$router

router是VueRouter的实例,包含了一些路由的跳转方法,钩子函数等

十三:vue-router响应路由参数的变化

当使用路由参数时,例如从 /user/foo 导航到 /user/bar原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用


复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:

const User = {
  template: '...',
  watch: {
    $route(to, from) {
      // 对路由变化作出响应...
    }
  }
}

或者使用 2.2 中引入的 beforeRouteUpdate 导航守卫

const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}

 

十四:vue-router传参

vue-router传递参数分为两大

  • 编程式的导航 router.push
  • 声明式的导航 <router-link>

编程式的导航 router.push

编程式导航传递参数有两种类型:字符串、对象。

字符串

字符串的方式是直接将路由地址以字符串的方式来跳转,这种方式很简单但是不能传递参数:

this.$router.push("home");

对象

想要传递参数主要就是以对象的方式来写,分为两种方式:命名路由、查询参数,下面分别说明两种方式的用法和注意事项。

命名路由

命名路由的前提就是在注册路由的地方需要给路由命名如:

命名路由传递参数需要使用params来传递,这里一定要注意使用params不是query。目标 页面接收传递参数时使用params

特别注意:命名路由这种方式传递的参数,如果在目标页面刷新是会出错的

使用方法如下:

this.$router.push({ name: 'news', params: { userId: 123 }})

查询参数

查询参数其实就是在路由地址后面带上参数和传统的url参数一致的,传递参数使用query而且必须配合path来传递参数而不能用name,目标页面接收传递的参数使用query。

注意:和name配对的是params,和path配对的是query

this.$router.push({ path: '/news', query: { userId: 123 }});

声明式的导航

声明式的导航和编程式的一样

字符串

<router-link to="news">click to news page</router-link>

命名路由

<router-link :to="{ name: 'news', params: { userId: 1111}}">click to news page</router-link>

查询参数

<router-link :to="{ path: '/news', query: { userId: 1111}}">click to news page</router-link>

路由传递参数和传统传递参数是一样的,命名路由类似表单提交而查询就是url传递,在vue项目中基本上掌握了这两种传递参数就能应付大部分应用了,最后总结为以下两点:
1.命名路由搭配params,刷新页面参数会丢失
2.查询参数搭配query,刷新页面数据不会丢失
3.接受参数使用this.$router后面就是搭配路由的名称就能获取到参数的值

十五:vue-router的两种模式

hash模式

hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件:

上面的代码可以通过改变hash来改变页面字体颜色,虽然没什么用,但是一定程度上说明了原理。

更关键的一点是,因为hash发生变化的url都会被浏览器记录下来,从而你会发现浏览器的前进后退都可以用了,同时点击后退时,页面字体颜色也会发生变化。这样一来,尽管浏览器没有请求服务器,但是页面状态和url一一关联起来,后来人们给它起了一个霸气的名字叫前端路由,成为了单页应用标配。

history路由

随着history api的到来,前端路由开始进化了,前面的hashchange,你只能改变#后面的url片段,而history api则给了前端完全的自由

history api可以分为两大部分,切换和修改

切换历史状态

包括back,forward,go三个方法,对应浏览器的前进,后退,跳转操作,有同学说了,(谷歌)浏览器只有前进和后退,没有跳转,嗯,在前进后退上长按鼠标,会出来所有当前窗口的历史记录,从而可以跳转(也许叫跳更合适):

history.go(-2);//后退两次
 
history.go(2);//前进两次
 
history.back(); //后退
 
hsitory.forward(); //前进

修改历史状态

包括了pushState,replaceState两个方法,这两个方法接收三个参数:stateObj,title,url

history.pushState({color:'red'}, 'red', 'red'})
 
window.onpopstate = function(event){
    console.log(event.state)
    if(event.state && event.state.color === 'red'){
        document.body.style.color = 'red';
    }
}
 
history.back();
 
history.forward();

通过pushstate把页面的状态保存在state对象中,当页面的url再变回这个url时,可以通过event.state取到这个state对象,从而可以对页面状态进行还原,这里的页面状态就是页面字体颜色,其实滚动条的位置,阅读进度,组件的开关的这些页面状态都可以存储到state的里面。

history模式怕啥

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面

nginx

location / {
  try_files $uri $uri/ /index.html;
}

 

十六:vue-router实现路由懒加载( 动态加载路由 )

VUE官网给出的解释:

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

首先,可以将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身):

const Foo = () => Promise.resolve({ /* 组件定义对象 */ })

第二,在 Webpack 2 中,我们可以使用动态 import语法来定义代码分块点 (split point):

import('./Foo.vue') // 返回 Promise

结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。

const Foo = () => import('./Foo.vue')

在路由配置中什么都不需要改变,只需要像往常一样使用 Foo

const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

把组件按组分块

有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

 (1)第一种写法:使用 AMD 风格的 require:

const routers = [
    {
        path: '/',
        name: 'index',
        component: (resolve) => require(['./views/index.vue'], resolve)
    }
]

(2)第二种写法:(使用import)

const Index = () => import(/* webpackChunkName: "group-home" */  '@/views/index')
const routers = [
    {
        path: '/',
        name: 'index',
        component: Index
    }
]

(3)第三种写法:使用webpack特有的require.ensure()。注:require.ensure 是 Webpack 的特殊语法,用来设置 code-split point

const Index = r => require.ensure([], () => r(require('./views/index')), 'group-home');
const routers = [
    {
        path: '/',
        name: 'index',
        component: Index
    }
]

十七:vue优点

1、轻量级框架

只关注视图层,是一个构建数据的视图集合,大小只有几十kb

Vue.js通过简洁的API提供高效的数据绑定和灵活的组件系统

2、简单易学

国人开发,中文文档,不存在语言障碍,易于理解和学习

3、双向数据绑定

也就是所谓的响应式数据绑定。这里的响应式不是@media 媒体查询中的响应式布局,而是指vue.js会自动对页面中某些数据的变化做出同步的响应。

也就是说,vue.js会自动响应数据的变化情况,并且根据用户在代码中预先写好的绑定关系,对所有绑定在一起的数据和视图内容都进行修改。而这种绑定关系,就是以input 标签的v-model属性来声明的,因此你在别的地方可能也会看到有人粗略的称vue.js为声明式渲染的模版引擎。

这也就是vue.js最大的优点,通过MVVM思想实现数据的双向绑定,让开发者不用再操作dom对象,有更多的时间去思考业务逻辑。

4、组件化

在前端应用,我们是否也可以像编程一样把模块封装呢?这就引入了组件化开发的思想。

Vue.js通过组件,把一个单页应用中的各种模块拆分到一个一个单独的组件(component)中,我们只要先在父级应用中写好各种组件标签(占坑),并且在组件标签中写好要传入组件的参数(就像给函数传入参数一样,这个参数叫做组件的属性),然后再分别写好各种组件的实现(填坑),然后整个应用就算做完了。

5、视图,数据,结构分离

使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作

6、虚拟DOM

现在的网速越来越快了,很多人家里都是几十甚至上百M的光纤,手机也是4G起步了,按道理一个网页才几百K,而且浏览器本身还会缓存很多资源文件,那么几十M的光纤为什么打开一个之前已经打开过,已经有缓存的页面还是感觉很慢呢?这就是因为浏览器本身处理DOM也是有性能瓶颈的,尤其是在传统开发中,用JQuery或者原生的JavaScript DOM操作函数对DOM进行频繁操作的时候,浏览器要不停的渲染新的DOM树,导致页面看起来非常卡顿。

而Virtual DOM则是虚拟DOM的英文,简单来说,他就是一种可以预先通过JavaScript进行各种计算,把最终的DOM操作计算出来并优化,由于这个DOM操作属于预处理操作,并没有真实的操作DOM,所以叫做虚拟DOM。最后在计算完毕才真正将DOM操作提交,将DOM操作变化反映到DOM树上。

7、运行速度更快

像比较与react而言,同样都是操作虚拟dom,就性能而言,vue存在很大的优势

十八:vue父组件向子组件传递数据

父组件中的操作:把数据放在子组件的 :xxxxx 上,这样就传过去了

<feed-section :recommend_feeds="recommend_feeds"></feed-section>

子组件的操作

props: [
  'recommend_feeds'
],

通过 props 中写一个recommend_feeds 这样就可以拿到父组件通过recommend_feeds穿过来的数据了

十九:子组件像父组件传递事件

 1.子组件需要使用this.$emit 将子组件的事件进行上级传递。

2. 父组件需要使用@事件名的方式,接收子组件的事件。

二十:v-show和v-if指令的共同点和不同点

  1. v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
  2. v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
  3. 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
  4. 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

 二十一:如何让CSS只在当前组件中起作用

当前组件<style>写成<style  scoped>

二十二: <keep-alive></keep-alive>的作用是什么?

<keep-alive></keep-alive> 包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。

比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情…这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用<keep-alive></keep-alive>进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染

二十三: 如何获取dom

1.使用ref,给相应的元素加ref=“name” 然后再this.$refs.name获取到该元素

2.使用原生js获取

3.对于动态元素 使用$nextTick 在该函数中获取

二十四: 说出几种vue当中的指令和它的用法?

1、v-if:判断是否隐藏

2、v-for:数据循环

3、v-bind:class:绑定一个属性

4、v-model:实现数据双向绑定

5、v-html: 更新元素的 innerHTML

6、v-text:更新元素的 textContent

7、v-on:绑定事件监听器

8、v-bind:动态地绑定一个或多个 attribute

 二十五: vue-loader是什么?使用它的用途有哪些?

一、vue-loader作用:
解析和转换.vue文件。提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理

二、用途
js可以写es6,style样式可以写scss或less

三、
vue-template-compiler:把vue-loader提取出的HTML模板编译成可执行的jacascript代码

二十六:为什么使用key 

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,更好地区别各个组件 key的作用主要是为了高效的更新虚拟DOM

 二十七:axios及安装

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中

特性

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

 

安装:npm install --save axios

fetch.js

import axios from 'axios'
import { BASE_URL } from '../api'


export default function fetch (url = '', data = {}, method = 'post') {
  method = method.toLowerCase()
  let ops = { params: data }
  if (method === 'post') {
    ops = { data: data }
  }
  let options = {
    url,
    ...ops,
    method
  }
  return new Promise((resolve, reject) => {
    const instance = axios.create({
      baseURL: BASE_URL,
      timeout: 100000,
      withCredentials: true,
      crossDomain: true,
      // headers: { 'Content-Type': 'application/json;charset=UTF-8' }
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
    })
    instance(options).then((res) => {
      resolve(res.data)
    }).catch((data) => {
      reject(data)
    })
  })
}

index.js

export const BASE_URL = '//****.**.com' // 接口域名

export const INTERFACE_NAME = BASE_URL + '/name'

service.js

import fetch from '../common/fetch'
import * as api from './index'

// 接口函数
export const intserfaceName = (params) => fetch(api.INTERFACE_NAME, params, 'post')

调用侧vue

  created () {
    interfaceName({}).then(res=>{
        // do 
    })
  },

 

二十八:axios解决跨域

一、Vuecli2 版本

 

this.$axios.get("https://www.v2ex.com/api/site/info.json")
.then(res=>{
    console.log(res)
})
.catch(err=>{
    console.log(err)
})

运行程序后,控制台报错如下:

跨域访问错误

Step1:配置BaseUrl

首先在main.js中,配置下我们访问的Url前缀:

'/api':{
    target: "https://www.v2ex.com/api",
    changeOrigin:true,
    pathRewrite:{
        '^/api':''
    }
}

配置代理

Step3:修改请求Url

修改刚刚的axios请求,把url修改如下

this.$axios.get("/site/info.json")
.then(res=>{
	console.log(res)
})
.catch(err=>{
	console.log(err)
})

Step4:重启服务

重启服务后,此时已经能够访问了:

原理:

因为我们给url加上了前缀 /api,我们访问 http://127.0.0.1:19323/site/info.json 就当于访问了:http://127.0.0.1:19323/api/site/info.json。(假设本地访问端口号为 19323)

又因为在 index.js 中的 proxyTable 中拦截了 /api ,并把 /api 及其前面的所有替换成了 target 中的内容,因此实际访问 Url 是https://www.v2ex.com/api/site/info.json

二、Vuecli3 版本

升级到 Vue3 后,会发现 Vue2 中存放配置的 config 文件夹没有了,大家不要慌张。可以在 package.json 文件的同级目录下创建 vue.config.js 文件。给出该文件的基础配置:

module.exports = {
    outputDir: 'dist',   //build输出目录
    assetsDir: 'assets', //静态资源目录(js, css, img)
    lintOnSave: false, //是否开启eslint
    devServer: {
        open: true, //是否自动弹出浏览器页面
        host: "localhost", 
        port: '8081', 
        https: false,   //是否使用https协议
        hotOnly: false, //是否开启热更新
        proxy: null,
    }
}

修改 vue.config.js 中 devServer 子节点内容,添加一个 proxy

devServer: {
    open: true, //是否自动弹出浏览器页面
    host: "localhost", 
    port: '8081',
    https: false,
    hotOnly: false, 
    proxy: {
        '/api': {
            target: 'https://www.v2ex.com/api', //API服务器的地址
            changeOrigin: true,
            pathRewrite: {
                '^/api': ''
            }
        }
    },
}

 

二十九:v-modal的使用

表单元素的双向绑定,本质是一个语法糖

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

三十:scss的安装以及使用

 

npm install node-sass sass-loader style-loader --save-dev

配置moudle下rules

 {
        test: /\.scss$/,
        loaders: ['style', 'css', 'sass']
 }

 

三十一:请说出vue.cli项目中src目录每个文件夹和文件的用法(脑残题)

vue-cli 项目中src目录每个文件夹和文件的用法

  • assets 文件夹是放静态资源
  • components 是放组件
  • router 是定义路由相关的配置
  • view 视图
  • app.vue 是一个应用主组件
  • main.js 是入口文件

 三十二:分别简述computed和watch的使用场景

1、区别

watch中的函数是不需要调用的

computed内部的函数调用的时候不需要加()

watch  属性监听 监听属性的变化

computed:计算属性通过属性计算而得来的属性

watch需要在数据变化时执行异步或开销较大的操作时使用

对于任何复杂逻辑或一个数据属性在它所依赖的属性发生变化时,也要发生变化,这种情况下,我们最好使用计算属性computed。 

computed 属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;

computed中的函数必须用return返回最终的结果

当computed中的函数所依赖的属性如果没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取

watch 一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;

2、使用场景

computed     

    当一个属性受多个属性影响的时候就需要用到computed

    最典型的例子: 购物车商品结算的时候

watch

    当一条数据影响多条数据的时候就需要用watch

    搜索数据

三十三:v-on可以监听多个方法吗

v-onc传一个对象,即可监听多个方法

<input
     type="text"
     v-on="{
     input:onInput,
     focus:onFocus,
     blur:onBlur,
     }"
>
 

 三十四:$nextTick的使用

将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

三十五:vue组件中data为什么必须是一个函数 

组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

三十六:vue事件对象的使用 

vue文档:

在监听原生 DOM 事件时,方法以事件为唯一的参数。如果使用内联语句,语句可以访问一个 $event property:v-on:click="handle('ok', $event)"

三十七: 组件间的通信

方法一、props/$emit

父组件 A 通过 props 的方式向子组件 B 传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。

方法二、$emit/$on

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案 vuex。

    var Event=new Vue();
    Event.$emit(事件名,数据);
    Event.$on(事件名,data => {});

方法三、vuex

1. 简要介绍 Vuex 原理

Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 进行,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。

2. 简要介绍各模块在流程中的功能:

  • Vue Components:Vue 组件。HTML 页面上,负责接收用户操作等交互行为,执行 dispatch 方法触发对应 action 进行回应。
  • dispatch:操作行为触发方法,是唯一能执行 action 的方法。
  • actions:操作行为处理模块,由组件中的$store.dispatch('action 名称', data1)来触发。然后由 commit()来触发 mutation 的调用 , 间接更新 state。负责处理 Vue Components 接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台 API 请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。
  • commit:状态改变提交操作方法。对 mutation 进行提交,是唯一能执行 mutation 的方法。
  • mutations:状态改变操作方法,由 actions 中的commit('mutation 名称')来触发。是 Vuex 修改 state 的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。
  • state:页面状态管理容器对象。集中存储 Vue components 中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。
  • getters:state 对象读取方法。图中没有单独列出该模块,应该被包含在了 render 中,Vue Components 通过该方法读取全局 state 对象。

3. Vuex 与 localStorage

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在 vuex 里数据改变的时候把数据拷贝一份保存到 localStorage 里面,刷新之后,如果 localStorage 里有保存的数据,取出来再替换 store 里的 state。

let defaultCity = "上海"
try {   // 用户关闭了本地存储功能,此时在外层加个try...catch
  if (!defaultCity){
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
  }
}catch(e){}
export default new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
      try {
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
      // 数据改变的时候把数据拷贝一份保存到localStorage里面
      } catch (e) {}
    }
  }
})

这里需要注意的是:由于 vuex 里,我们保存的状态,都是数组,而 localStorage 只支持字符串,所以需要用 JSON 转换

方法四、$attrs/$listeners

1. 简介

多级组件嵌套需要传递数据时,通常使用的方法是通过 vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此 Vue2.4 版本提供了另一种方法----$attrs/$listeners

  • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组
// index.vue
<template>
  <div>
    <h2>浪里行舟</h2>
    <child-com1
      :foo="foo"
      :boo="boo"
      :coo="coo"
      :doo="doo"
      title="前端工匠"
    ></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  }
};
</script>


// childCom1.vue
<template class="border">
  <div>
    <p>foo: {{ foo }}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  props: {
    foo: String // foo作为props属性绑定
  },
  created() {
    console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>


// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  created() {
    console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>


// childCom3.vue
<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  }
};
</script>



如上图所示$attrs表示没有继承数据的对象,格式为{属性名:属性值}。Vue2.4 提供了$attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。

简单来说:$attrs$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。

方法五、provide/inject

1. 简介

Vue2.2.0 新增 API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系

2. 举个例子

假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件
 

// A.vue
export default {
  provide: {
    name: '浪里行舟'
  }
}



// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // 浪里行舟
  }
}

 可以看到,在 A.vue 里,我们设置了一个 provide: name,值为 浪里行舟,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject 注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 浪里行舟。这就是 provide / inject API 最核心的用法。

需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue 官方文档
所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 浪里行舟。

方法六、$parent / $children与 ref

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父 / 子实例

总结

常见使用场景可以分为三类:

  • 父子通信:
    父向子传递数据是通过 props,子向父是通过 events($emit);
  • 通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
  • 兄弟通信:
    Bus;Vuex
  • 跨级通信:
    Bus;Vuex;provide / inject API、$attrs/$listeners

三十八:渐进式框架的理解

主张最少,也就是弱主张,他是在vue核心库(视图模板引擎)的基础上,去逐步添加所需要功能(如,组件系统、路由、状态机等)

vue“渐进式”:是指先使用vue核心库,在vue核心库的基础上,根据自己需要再去逐渐增加功能。

Vue的核心的功能,是一个视图模板引擎,但这不是说Vue就不能成为一个框架。

在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。

更重要的是,这些功能相互独立,你可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。

所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念。

 

比如说,Angular,它两个版本都是强主张的,如果你用它,必须接受以下东西:

- 必须使用它的模块机制
- 必须使用它的依赖注入
- 必须使用它的特殊形式定义组件(这一点每个视图框架都有,难以避免)

比如React,它也有一定程度的主张,它的主张主要是函数式编程的理念。

-比如说,你需要知道什么是副作用,什么是纯函数,如何隔离副作用

 

三十九:Vue中双向数据绑定是如何实现的

1.原理

在这里插入图片描述

  • View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。
  • Vue采用数据劫持&发布-订阅模式的方式,通过ES5提供的Object.defineProperty() 方法来劫持 (监控) 各属性的getter、setter,并在数据(对象)发生变动时通知订阅者,触发相应的监听回调。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。要实现Vue中的双向数据绑定,大 致 可 以 划 分 三 个 模 块:Observer、Compile、Watcher,如图:

在这里插入图片描述

  • Observer 数据监听器,负责对数据对象的所有属性进行监听(数据劫持),监听到数据发生变化后通知订阅者。
  • Compile 指令解析器,扫描模板,并对指令进行解析,然后绑定指定事件。
  • Watcher 订阅者,关联Observer和Complie,能够订阅并收到属性变动的通知,执行指令绑定的相应操作,更新视图。Updade()是它自身的一个方法,用于执行Compile中绑定的回调,更新视图。

2.版本比较

vue是基于依赖收集的双向绑定;
3.0之前的版本使用 Object。defineProperty,3.0新版本使用Proxy

1) 基于 数据劫持/依赖收集 双向绑定的优点

  • 不需要显示的调用,Vue利用数据劫持+发布订阅,可以直接通知变化并且驱动视图
  • 直接得到精确的变化数据,劫持了属性setter,当属性值改变我们可以精确的获取变化的 newVal,不需要额外的 diff 操作

2)object.defineProperty的缺点

  • 不能监听数组; 因为数组没有getter和setter,因为数组长度不确定。如果太长性能负担太大。
  • 只能监听属性,而不是整个对象;需要遍历属性;
  • 只能监听属性变化,不能监听属性的删减;

3)proxy好处

  • 可以监听数组;
  • 监听整个对象不是属性
  • 13种拦截方法,强的很多;
  • 返回新对象而不是世界修改元对象,更符合immutable;

4)proxy缺点

  • 兼容性不好,且无法用polyfill磨平;

四十:单页面应用和多页面应用区别及优缺点

单页面应用(SPA),通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端。

多页面(MPA),就是指一个应用中有多个页面,页面跳转时是整页刷新

单页面的优点:

1,用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小

2,前后端分离

3,页面效果会比较炫酷(比如切换页面内容时的专场动画)

单页面缺点:

1,不利于seo

2,导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)

3,初次加载时耗时多

4,页面复杂度提高很多

四十二:vue中过滤器有什么作用及详解

语法:

<div>{{ name | capitalize }}</div>
//或者
<div v-bind:id="rawId | formatId"></div>
filters: {
  capitalize: function (value) {
    return value
  }
}
  • 过滤器函数中的value,就是 |前面的值,它相当于第一个参数
  • 在过滤器函数中一定要返回一个值,他就是我们对格式处理后的结果
  • 通俗的来讲,当为一个数据绑定一个过滤器后,每一次渲染这个数据的时候,他都会调用相应过滤器的函数

四十三:v-if和v-for的优先级

v-for比v-if先执行  但是不推荐他们两个一起使用

v-for和v-if不应该一起使用,必要情况下应该替换成computed属性。原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。

四十四:assets和static的区别

assets和static两个都是用于存放静态资源文件。

放在static中的文件不会进行构建编译处理,也就不会压缩体积,在打包时效率会更高,但体积更大在服务器中就会占据更大的空间

放在assets中的文件会进行压缩体积、代码格式化,压缩后会放置在static中一同上传服务器。

因此建议样式文件放在assets中进行打包,引入的第三方文件放到static中,因为引入的文件已经做过打包处理。

四十五:vue常用的修饰符

一、v-model修饰符

1、.lazy:

输入框改变,这个数据就会改变,lazy这个修饰符会在光标离开input框才会更新数据:

2、.trim:

输入框过滤首尾的空格:

3、.number:

先输入数字就会限制输入只能是数字,先字符串就相当于没有加number,注意,不是输入框不能输入字符串,是这个数据是数字:

二、事件修饰符

4、.stop:

阻止事件冒泡,相当于调用了event.stopPropagation()方法:

5、.prevent:

阻止默认行为,相当于调用了event.preventDefault()方法,比如表单的提交、a标签的跳转就是默认事件:

6、.self:

只有元素本身触发时才触发方法,就是只有点击元素本身才会触发。比如一个div里面有个按钮,div和按钮都有事件,我们点击按钮,div绑定的方法也会触发,如果div的click加上self,只有点击到div的时候才会触发,变相的算是阻止冒泡:

7、.once:

事件只能用一次,无论点击几次,执行一次之后都不会再执行

8、.capture:

事件的完整机制是捕获-目标-冒泡,事件触发是目标往外冒泡

9、.sync

对prop进行双向绑定

10、.keyCode:

监听按键的指令,具体可以查看vue的键码对应表

四十六:数组更新检测

Vue 包含两种观察数组的方法分别如下

1.变异方法

  顾名思义,变异方法会改变被这些方法调用的原始数组,它们也将会触发视图更新,这些方法如下

    push()   pop()  shift() unshift() splice() sort() reverse()

 2.非变异方法

  非变异方法与变异方法的区别就是,非变异方法不会改变原始数组,总是返回一个新数组,

  当使用非变异方法时,可以用新数组替换旧数组,非变异方法大致有:filter()concat() 和 slice()

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

 1.当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue

 2.当你修改数组的长度时,例如:vm.items.length = newLength

vue针对这两个问题给出了相应的解决办法,使用这两种方法,也会触发状态更新

 1.使用vue全局方法Vue.set() 或者使用vm.$set() 实例方法

 2.使用 splice,caoncat等修改数组

四十七:Vue.set视图更新

参数

  • {Object | Array} target
  • {string | number} propertyName/index
  • {any} value

向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')

注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

四十八:自定义指令详解

 

Vue.directive

参数

  • {string} id
  • {Function | Object} [definition]

注册或获取全局指令。

执行顺序:

页面加载时

bind
inserted
组件更新时

update
componentUpdated
卸载组件时

unbind

 

/*  自定义指 */
import Vue from 'vue'
/**
 * 模板
 * v-lang
 * 五个注册指令的钩子函数
 */
Vue.directive('mydirective', {
  /**
   * 1.被绑定
   * 做绑定的准备工作
   * 比如添加事件监听器,或是其他只需要执行一次的复杂操作
   */
  bind: function(el, binding, vnode) {
    console.log('1 - bind');
  },
  // 2.绑定到节点
  inserted: function(el, binding, vnode) {
    console.log('2 - inserted');
  },
  /**
   * 3.组件更新
   * 根据获得的新值执行对应的更新
   * 对于初始值也会调用一次
   */
  update: function(el, binding, vnode, oldVnode) {
    console.log('3 - update');
  },
  // 4.组件更新完成
  componentUpdated: function(el, binding, vnode, oldVnode) {
    console.log('4 - componentUpdated');
  },
  /**
   * 5.解绑
   * 做清理操作
   * 比如移除bind时绑定的事件监听器
   */
  unbind: function(el, binding, vnode) {
    console.log('5 - bind');
  }
})
/**
钩子函数
1、bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个绑定时执行一次的初始化动作。
2、inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。
3、update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
4、componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
5、unbind:只调用一次,指令与元素解绑时调用。
*/

四十九:vue的两个核心点

1、数据驱动:

在vue中,数据的改变会驱动视图的自动更新。传统的做法是需要手动改变DOM来使得视图更新,而vue只需要改变数据。

2、组件

组件化开发,优点很多,可以很好的降低数据之间的耦合度。将常用的代码封装成组件之后(vue组件封装方法),就能高度的复用,提高代码的可重用性。一个页面/模块可以由多个组件所组成。

五十:vue和jQuery的区别

jQuery是使用选择器($)选取DOM对象,对其进行赋值、取值、事件绑定等操作,其实和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。比如需要获取label标签的内容:$("lable").val();,它还是依赖DOM元素的值。 

Vue则是通过Vue对象将数据和View完全分离开来了。对数据进行操作不再需要引用相应的DOM对象,可以说数据和View是分离的,他们通过Vue对象这个vm实现相互的绑定。这就是传说中的MVVM。

五十一:引进组件的步骤

1.导入组件

2.注册组件

3.使用组件

五十二:Vue-cli打包命令是什么?打包后导致路径问题,应该在哪里修改(脑残题)

vue.config.js

五十三:三大框架的对比

React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。它有以下的特性:

1.声明式设计:React采用声明范式,可以轻松描述应用。

2.高效:React通过对DOM的模拟,最大限度地减少与DOM的交互。

3.灵活:React可以与已知的库或框架很好地配合。

优点:

1. 速度快:在UI渲染过程中,React通过在虚拟DOM中的微操作来实现对实际DOM的局部更新。

2. 跨浏览器兼容:虚拟DOM帮助我们解决了跨浏览器问题,它为我们提供了标准化的API,甚至在IE8中都是没问题的。

3. 模块化:为你程序编写独立的模块化UI组件,这样当某个或某些组件出现问题是,可以方便地进行隔离。

4. 单向数据流:Flux是一个用于在JavaScript应用中创建单向数据层的架构,它随着React视图库的开发而被Facebook概念化。

5. 同构、纯粹的javascript:因为搜索引擎的爬虫程序依赖的是服务端响应而不是JavaScript的执行,预渲染你的应用有助于搜索引擎优化。

6.兼容性好:比如使用RequireJS来加载和打包,而Browserify和Webpack适用于构建大型应用。它们使得那些艰难的任务不再让人望而生畏。

缺点:

React本身只是一个V而已,并不是一个完整的框架,所以如果是大型项目想要一套完整的框架的话,基本都需要加上ReactRouter和Flux才能写大型应用。

 

Vue是尤雨溪编写的一个构建数据驱动的Web界面的库,准确来说不是一个框架,它聚焦在V(view)视图层。

它有以下的特性:

1.轻量级的框架

2.双向数据绑定

3.指令

4.插件化

优点:

1. 简单:官方文档很清晰,比 Angular 简单易学。

2. 快速:异步批处理方式更新 DOM。

3. 组合:用解耦的、可复用的组件组合你的应用程序。

4. 紧凑:~18kb min+gzip,且无依赖。

5. 强大:表达式 无需声明依赖的可推导属性 (computed properties)。

6. 对模块友好:可以通过 NPM、Bower 或 Duo 安装,不强迫你所有的代码都遵循 Angular 的各种规定,使用场景更加灵活。

缺点:

1. 新生儿:Vue.js是一个新的项目,没有angular那么成熟。

2. 影响度不是很大:google了一下,有关于Vue.js多样性或者说丰富性少于其他一些有名的库。

3. 不支持IE8

 

Angular是一款优秀的前端JS框架,已经被用于Google的多款产品当中。

它有以下的特性:

1.良好的应用程序结构

2.双向数据绑定

3.指令

4.HTML模板

5.可嵌入、注入和测试

优点:

1. 模板功能强大丰富,自带了极其丰富的angular指令。

2. 是一个比较完善的前端框架,包含服务,模板,数据双向绑定,模块化,路由,过滤器,依赖注入等所有功能;

3. 自定义指令,自定义指令后可以在项目中多次使用。

4. ng模块化比较大胆的引入了Java的一些东西(依赖注入),能够很容易的写出可复用的代码,对于敏捷开发的团队来说非常有帮助。

5. angularjs是互联网巨人谷歌开发,这也意味着他有一个坚实的基础和社区支持。

缺点:

1. angular 入门很容易 但深入后概念很多, 学习中较难理解.

2. 文档例子非常少, 官方的文档基本只写了api, 一个例子都没有, 很多时候具体怎么用都是google来的, 或直接问misko,angular的作者.

3. 对IE6/7 兼容不算特别好, 就是可以用jQuery自己手写代码解决一些.

4. 指令的应用的最佳实践教程少, angular其实很灵活, 如果不看一些作者的使用原则,很容易写出 四不像的代码, 例如js中还是像jQuery的思想有很多dom操作.

5. DI 依赖注入 如果代码压缩需要显示声明.

五十四:跨组件双向数据绑定

组件双向绑定应有以下2个特点:

1. 父组件只传输 prop,不定义事件接收。 

2. 由子组件更新传下来的值。

方法一:

<template>
  <input type="text" v-bind="$attrs" v-on="$listeners">
</template>

$attr: 向 子组件 传递,当前组件 没有接收的,父组件传递下来的 prop 。

$listeners: 向父组件传递,当前组件没有接收的,子组件抛出的自定义事件。

这样实现后,发现输入后会变成一个 [object InputEvent] 的事件对象。

是因为 v-on="input" 是内置事件,vue 判断 当前组件 的 tag 是 'input' 类的表单元素,才会关联 value

当前组件 经过封装后,tag 已经改变,父组件实际处理的是 this.$emit('input', el) 的传参,el 就是 [object InputEvent] 表单对象。

 

解决方案:拦截内置的 input 事件,改变引用,向 父组件 传递最终值,如下。

<template>
  <input type="text" v-bind="$attrs" @input="observerInput">
</template>
 
<script>
  export default {
    methods: {      
      // 拦截内置事件      
      observerInput (el) {
        this.$emit('input', el.target.value)
      },
    },  
  }
</script>

 方法二:通过watch

方法三:通过计算属性

五十五:delete和Vue.delete删除数组的区别

 

delete 删除数组元素,只是被删除的数组元素变成了empty/undefined,其它元素不变,数组长度不变,键值对应也不变

let arr1 = [1, 2, 3, 4];
delete arr1[1];
console.log(arr1);

vue.delete 删除数组元素,是直接删除了元素,改变了数组的键值 (很少用)

let arr2 = [1, 2, 3, 4];
this.$delete(arr2, 1);
console.log(arr2);

五十六:SPA首屏加载慢如何解决

1.减轻同一项目在服务器中的文件大小

map文件生产环境没必要保留,在vue.config.js中通过进行配置

/**
productionSourceMap
Type: boolean

Default: true

如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。

*/

productionSourceMap: false

2.vue-router 路由懒加载

懒加载即组件的延迟加载,通常vue的页面在运行后进入都会有一个默认的页面,而其他页面只有在点击后才需要加载出来。

使用懒加载可以将页面中的资源划分为多份,从而减少第一次加载的时候耗时。

懒加载配置:

非懒加载路由配置:

 路由懒加载打包后static-->js文件夹

如图所示为通过非懒加载和懒加载后打包的js文件。而非懒加载的打包后一般只有一个app.js 文件。

懒加载打包后

这个1-10.js就相当于你的10个路由对应的文件我是这么理解的哈(使用路由的按需加载,可把app.js分隔成多个小的js文件)

注意这个vendor.js文件很大这个怎么搞?每次打开会发现这个js很耗时,它是项目所有依赖的集成(包括vue vue-router axios echarts ele等)

vendor.js一般是将所有引用的库打包在了一起,首先就需要确定是哪些库文件太大

vue-cli打包优化之分析工具webpack-bundle-analyzer

// 1. 安装
cnpm install webpack-bundle-analyzer --save-dev

// 2. 在/build/webpack.prod.conf.js文件中引入
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
// 然后配置一下:
plugins: [
    new BundleAnalyzerPlugin()
]

// 3. 在package.json文件中配置:
"scripts": {
    "analyz": "NODE_ENV=production npm_config_report=true npm run build"
}

// 4. 运行(会自动在浏览器打开:http://127.0.0.1:8888/)
npm run build

然后就会出现一张类似这样的图片

这几个英文

stat size(统计尺寸)

parsed size(解析大小)

Gzipped size(压缩大小)

vendorjs中是项目中所有引用的依赖包,即使用的vue、eleui、axios、echarts等等插件框架什么的都在这里边。解决办法

  # cdn引入插件

  # 打包时使用Gzip

现在来解决它看能不能变小

先来试一下压缩,

webback.prod.config.js 中添加如下代码。(文件大小 10.5MB-->3.5MB)

webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      // deleteOriginalAssets:true, //删除源文件
      minRatio: 0.8
    })
  )

使用CDN加载

通过在index.html 中直接引入第三方资源来缓解我们服务器的压力,其原理是将我们的压力分给了其他服务器站点。

包后需要去掉代码中的console.log ,防止打开f12 的时候输出日志(可webpack配置也可以手动删除)

我这个项目小所以vendeor 请求响应时间少,别个项目大的情况下 这个文件请求响应时间就长了如上图5秒,这是要死人的,慢

解决方法

1、这里我们把import导入的方式删掉

2、通过cdn方式去引入import需要导入的库(我们不是不用import,而是换了另一种方式去引入,这样可以减轻vendor.js的负担)

3、这一步配置非常重要,我们在目录build/webpack.base.conf.js文件中配置externals

externals(翻译:外部环境)的作用是从打包的bundle文件中排除依赖。

换句话说就是让在项目中通过import引入的依赖在打包的时候不会打包到bundle包中去,而是通过script(CDN)的方式去访问这些依赖。

五十七:Vue-router跳转和location.href有什么区别

1.vue-router 中有两种模式  分别使用h5中history新增api来实现,静态跳转,页面不会重新加载,location.href会触发浏览器,页面重新加载一次

2.vue-router使用diff算法,实现按需加载,减少dom操作

3.vue-router是路由跳转或同一个页面跳转;location.href是不同页面间跳转;

4.vue-router是异步加载this.$nextTick(()=>{获取url});location.href是同步加载

五十八:vue slot

官网:

Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 <slot> 元素作为承载分发内容的出口。

通俗:

vue提供了组件插槽的用法,插槽就是子组件提供给父组件的一个占位符,当父组件使用子组件需要填充一些代码片段或者内容的时候可以使用slot进行占位填充。父组件中的内容就会将子组件中的slot替换。

1.简单举例

子组件

<template>
  <div>
    <h1>这是插槽的简单用法</h1>
    <div style="margin-top:30px;font-size:20px;">
    	<slot></slot>
    </div>
  </div>
</template>

父组件

<template>
  <div>
	<Child>
	   <div>简单用法简单用法简单用法</div>
	</Child>
  </div>
</template>

在这里插入图片描述

2.具名插槽(也就是带名称的插槽<slot name=“xxx”></slot>)

在父组件中使用子组件时,可能有不止一个地方使用插槽,这个时候就要使用具名插槽。如果不带名称的话,就不知道对应的是哪个地方。

子组件

<template>
  <div>
    <div>
      <h1>这是子组件的头部插槽</h1>
      <div style="margin-top:20px;font-size:20px;">
        <slot name="header"></slot>
      </div>
    </div>
    <div style="margin-top:30px;">
      <h1>这是子组件的尾部插槽</h1>
      <div style="margin-top:20px;font-size:20px;">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</template>

父组件

<template>
  <div>
	<Child>
      <div v-slot="header">头部头部头部</div>
      <div v-slot="footer">尾部尾部尾部</div>
    </Child>
  </div>
</template>

3.默认插槽

如果子组件中有一个插槽没有带名称,那么在父组件中使用时也不加名称的话,她默认是替换那个没有名称的插槽的。

子组件

<template>
  <div>
    <div>
      <h1>这是子组件的头部插槽</h1>
      <div style="margin-top:20px;font-size:20px;">
        <slot name="header"></slot>
      </div>
    </div>
    <div style="margin-top:30px;">
      <h1>这是默认插槽</h1>
      <div style="margin-top:20px;font-size:20px;">
        <slot></slot>
      </div>
    </div>
    <div style="margin-top:30px;">
      <h1>这是子组件的尾部插槽</h1>
      <div style="margin-top:20px;font-size:20px;">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</template>

 父组件

<template>
  <div>
	<Child>
      <div>默认默认默认</div>
      <div v-slot="header">头部头部头部</div>
      <div v-slot="footer">尾部尾部尾部</div>
    </Child>
  </div>
</template>

如果子组件中有多个默认插槽呢?

 子组件

<template>
  <div>
    <div>
      <h1>这是子组件的头部插槽</h1>
      <div style="margin-top:20px;font-size:20px;">
        <slot name="header"></slot>
      </div>
    </div>
    <div style="margin-top:30px;">
      <h1>这是默认插槽</h1>
      <div style="margin-top:20px;font-size:20px;">
        <slot></slot>
      </div>
    </div>
    <div style="margin-top:30px;">
      <h1>这是默认插槽</h1>
      <div style="margin-top:20px;font-size:20px;">
        <slot></slot>
      </div>
    </div>
    <div style="margin-top:30px;">
      <h1>这是子组件的尾部插槽</h1>
      <div style="margin-top:20px;font-size:20px;">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</template>

父组件

<template>
  <div>
	<Child>
      <div>默认默认默认</div>
      <div v-slot="header">头部头部头部</div>
      <div v-slot="footer">尾部尾部尾部</div>
    </Child>
  </div>
</template>

4.作用域插槽(slot-scope)

当同一些数据需要不同的展示效果的时候你可以使用作用于插槽,也就是数据插槽。

子组件

<template>
  <div>
    <div>
      <slot :data="slotData"></slot>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      slotData: [
        {
          name: "王一",
          age: "15"
        },
        {
          name: "钱二",
          age: "16"
        },
        {
          name: "赵三",
          age: "18"
        },
        {
          name: "李四",
          age: "22"
        }
      ]
    };
  }
};
</script>

父组件

<template>
	<div>
      <h1>第一种</h1>
      <Child>
        <template slot-scope="user">
          {{ user.data }}
        </template>
      </Child>
      <h1>第二种</h1>
      <Child>
        <template slot-scope="user">
          <div v-for="item in user.data" :key="item">
            <p>我叫{{ item.name }},今年{{ item.age }}岁。</p>
          </div>
        </template>
      </Child>
      <h1>第三种</h1>
      <Child>
        <template slot-scope="user">
          <div v-for="item in user.data" :key="item">
            <p>{{ item.name }}:{{ item.age }}</p>
          </div>
        </template>
      </Child>
    </div>
</template>

 

总结

  1. 如果父组件中使用的插槽名称在子组件不存在,那么内容将不会显示。
  2. 如果子组件中不存在默认插槽,父组件中使用不带名称的插槽,那么内容将不会显示。
  3. 子组件中若是有多个默认插槽,在父组件中使用时,她会填充到每一个默认插槽中。
  4. 父组件中使用名称插槽时显示位置时根据子组件的插槽位置来的,与父组件使用的位置无关。

五十九:你们vue项目是打包了一个js文件,一个css文件,还是有多个文件?

spa:

按需加载 多个js  多个css

多页:

按chunk打的js,c端项目将css打到js中避免上线发布时因时序(或cdn同步时间差)导致的css样式错乱

  pages: {
      index: {
          entry: "./src/pages/index/index.ts",
          template: 'public/index.html',
          minify: {
            minifyCSS: true,
            removeComments: true,
            collapseWhitespace: true,
            removeAttributeQuotes: false,
            minifyJS: true
          }
      },
      reminder: {
          entry: "./src/pages/test/reminder.ts",
          template: 'public/index.html',
          minify: {
            minifyCSS: true,
            removeComments: true,
            collapseWhitespace: true,
            removeAttributeQuotes: false,
            minifyJS: true
          }
      }
  },

 

六十:vue遇到的坑,如何解决的?(自由发挥)

  1. 使用keep-alive包裹的组件/路由,打开一次后created只会执行一次,有两种情况,一、如果要重新渲染部分数据,可以在activated中做处理;二、路由/组件重新重新created,可以使用官方推荐的:key="key" ,然后去改变key的值,组件就会重新挂载了
  2. beforeRouteEnter中的next函数的执行时间是在组件mounted之后,因此需要在此处处理的数据要注意了
  3. 网页刷新时vuex数据会丢失,需配合localStoragesessionStorage使用,把必须数据先存后取
  4. 对于权限及不确定路由,可以使用addRoutes(),可以避免抖动
  5. 熟练使用es6的数组map、find、filter等方法,对解构赋值、class继承、promise,及es7中的async和await
  6. 使用computed替代watchcomputed依赖于data属性的更改,是有缓存的
  7. 通过props传递的值,不要在子组件去更改。开发中,如果直接更改props,一、基本类型的值会报错,二、引用类型的值不会报错,但是不好去追溯数据的更改,很多人不太注意引用类型,可通过computedwatch去更改

六十一:Vue里面router-link在电脑上有用,在安卓上没反应怎么解决?

项目中没引入polypill

六十二:Vue2中注册在router-link上事件无效解决方法

使用 @click.native 。原因:router-link会阻止click事件,.native指直接监听一个原生事件。

六十三:RouterLink在IE和Firefox中不起作用(路由不跳转)的问题

1.传入对象

2.使用编程式导航  this.$router.push({ name: 'IdLogin' })

六十四:axios的特点有哪些

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

axios中的发送字段的参数是data跟params两个
两者的区别在于params是跟请求地址一起发送的,data的作为一个请求体进行发送
params一般适用于get请求
data一般适用于post put 请求

六十五:请说下封装 vue 组件的过程

● 首先,使用Vue.extend()创建一个组件

● 然后,使用Vue.component()方法注册组件

● 接着,如果子组件需要数据,可以在props中接受定义

● 最后,子组件修改好数据之后,想把数据传递给父组件,可以使用emit()方法

传送门:    组件封装的过程

六十六:vue 各种组件通信方法(父子 子父 兄弟 爷孙 毫无关系的组件)

一:父子组件通信

props 和 $emit 父子组件通信

父组件 $children 操作子组件

子组件 $parent 访问父组件

二:非父子组件通信

中央事件总线

三:provide/inject

适用于祖先和后代关系的组件间的通信,祖先元素通过 provide 提供一个值,后代元素则通过 inject 获取到这个值。这个值默认是非响应的,如果是对象那么则是响应式的

四:$root 直接访问根组件

根据官方的文档,我们可以通过 $root 来直接访问到 Vue 实例

六十七:params和query的区别

1.query传递参数会在url后面用?连接起来,且参数之间用&&符号连接然后显示在页面的url中;params传递参数不会显示在页面中;query有点像ajax中的get请求,而params像post请求。

2.在vue中使用query要搭配path路径,而params只能由命名路由name来引入

路由中path的配置

	{
			path: '/argu/:name/:id', //这种路由配置是params传递参数,且这个后面必须加参数,如果不加,刷新页面这些参数会消失
			name:'argu',
			component: () => import('@/views/argu.vue'),
		},

使用params传递参数注意要在path路径后面添加参数,不然刷新页面数据会丢失。

这是没有刷新之前;

在这里插入图片描述

如果path:'/argu'没有后面的参数,刷新页面数据就会丢失

这是刷新之后:可以看到数据消失了,变为了默认数据。

在这里插入图片描述

使用params传参的具体写法:

this.$router.push({
    				//params要name一起用,不然接收不到参数
					name: `argu`,
					params:{
						name:'xrw',
                        id:'123'
					}	
        });

使用query传递参数,在路由中设置path: ‘/argu’,后面可以不跟参数。

this.$router.push({ 
    path:'/argu', 
    //如果在路由中配置了name属性在这里也是可以用的 name:'argu' 效果同上
    query:{ 
        name:'xrw'
        id : '123'
    }
})	

两者接收参数的形式

query:

//query接收参数
{{ this.$route.query.name }}
{{ this.$route.query.id }}

params:

//params接受参数
{{ this.$route.params.name }}
{{ this.$route.params.id }} 

组件中也可以用props来进行接受参数,这种方式(推荐方法

这样需要在路由配置中设置props为true:

	{
			path: '/argu', 
			name:'argu',
			component: () => import('@/views/argu.vue'),
			//设置props为ture,代表将path后面的参数作为值,传递到组件中,组件中通过props属性接受这个值
			props:true,
		},

然后再组件中设置props来接收这个参数:

<template>
 <div>
    //props形式传递参数
	 {{ name }}
	 {{ id }}
	//$route形式传递参数
	 {{ this.$route.params.name }} 
	 {{ this.$route.params.id }}
	 {{ this.$route.query.name }}
	 {{ this.$route.query.id }} 
 </div>
</template>

<script>
export default {
	 props:{
		 name:{
			 type:String,
			 default:'lily' //默认情况
		 },
		 id:{
			 type:Number,
			 default:'0' //默认情况
		 }
	 }
}
</script>

这里我们需要来了解一下 $ route和$router的区别:

//$router : 是路由操作对象,只写对象
//$route : 路由信息对象,只读对象

//操作 路由跳转
this.$router.push({
      name:`argu`,
      params:{
          name:'xrw',
          age:'123'
     }
})

//读取 路由参数接收
this.name = this.$route.params.name;
this.age = this.$route.params.age;

总结
query和params是两种传参方式
使用params传参只能由name引入路由,如果写成path页面会显示undefined报错。
使用query传参的话可以使用path也可以使用name引入路由,不过建议使用path引入路由。
params是路由的一部分,一定要加路由后面添加参数,不添加刷新页面数据会丢失;而query是拼接在url后面的参数,路由后面不添加也没关系。

六十八:vue mock数据(除了mokejs是否存在其他更好用的办法)

1.安装mock http://mockjs.com/

npm install mock 

2.新建mock.js文件存储 模拟数据的方法

3.  模拟一个list数据

import Mock from 'mockjs'
Mock.mock('/meun', /post|get/i, {
  // /post|get/i 匹配post和get模式 也可以用'post'或'get'
  // 属性 list 的值是一个数组,其中含有 1 到 10 个元素
  'list|1-10': [{
    // 属性 id 是一个自增数,起始值为 1,每次增 1
    'id|+1': 1,
    // 随机数字1-100
    'number|1-100': 100,
    // 返回city数组,每次里面有三个
    'city|3': {
      '310000': '上海市',
      '320000': '江苏省',
      '330000': '浙江省',
      '340000': '安徽省'
    }
  }]
})

 

六十九:vue封装通用组件

一、数据从父组件传入

为了解耦,子组件本身就不能生成数据。即使生成了,也只能在组件内部运作,不能传递出去。

父对子传参,就需要用到 props,但是通用组件的的应用场景比较复杂,对 props 传递的参数应该添加一些验证规则

二、在父组件处理事件

在通用组件中,通常会需要有各种事件,

比如复选框的 change 事件,或者组件中某个按钮的 click 事件

这些事件的处理方法应当尽量放到父组件中,通用组件本身只作为一个中转

三、记得留一个或多个 slot

一个通用组件,往往不能够完美的适应所有应用场景

所以在封装组件的时候,只需要完成组件 80% 的功能,剩下的 20% 让父组件通过 solt 解决

四、不要依赖 Vuex

父子组件之间是通过 props 和 自定义事件 来传参,非父子组件通常会采用 Vuex 传参

但是 Vuex 的设计初衷是用来管理组件状态,虽然可以用来传参,但并不推荐

因为 Vuex 类似于一个全局变量,会一直占用内存

在写入数据庞大的 state 的时候,就会产生内存泄露

五、合理运用 scoped 编写 CSS

在编写组件的时候,可以在 <style> 标签中添加 scoped,让标签中的样式只对当前组件生效

但是一味的使用 scoped,肯定会产生大量的重复代码

所以在开发的时候,应该避免在组件中写样式

当全局样式写好之后,再针对每个组件,通过 scoped 属性添加组件样式

七十:vue初始化页面闪动问题

只未使用模块化的开发方式开发vue应用时,

{{}}语法会暴露在html中导致页面闪动

v-cloak指令和css规则如[v-cloak]{display:none}一起用时,这个指令可以隐藏未编译的Mustache标签直到实例准备完毕。

七十一:vue禁止弹窗后的屏幕滚动

1. 无需滑动弹窗,通过禁用body的滑动事件

export const utils = {
    pageLock: function () {
        document.addEventListener('touchmove', utils.pageLockHandler, {
            capture: false,
            passive: false
        })
    },
    pageUnlock: function () {
        document.removeEventListener('touchmove', utils.pageLockHandler, {
            capture: false
        })
    },
    pageLockHandler: function (e) {
        e.preventDefault()
    }
}

2.通过设置body position: fixed 和overflow:hidden来禁止页面滑动

七十二:vue更新数组时触发视图更新的方法

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

filter()concat()和 slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用这些非变异方法时,可以用新数组替换旧数组

vue中存在$set来监听数组长度及索引的变化来实现数组的响应式

七十三:vue常用的UI组件库

1.Element

2.iView 

3.Muse-UI

4.Vant

5.Mint UI

6.Vux UI

7.Ant Design Vue

七十四:vue如何引进本地背景图片

1.使用require的形式导入

2.使用import 形式导入

3.通过绑定vue变量操作

七十五: vue修改打包后静态资源路径的修改

vue-cli文档中的 assetsDir配置 

传送门

七十六:vuex是什么?怎么使用?哪种功能场景使用它?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

使用:

在main.js引入store,注入。新建了一个store目录,然后….. export 。

state
Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
mutations
mutations定义的方法动态修改Vuex 的 store 中的状态或数据。
getters
类似vue的计算属性,主要用来过滤一些数据。
action
actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。
modules
项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

七十七:vuex有哪几种属性

有五种,分别是State , Getter , Mutation , Action , Module

七十八:不使用Vuex会带来什么问题

对小型项目来说,没问题,它只是一个核心插件而已。

对大型项目来说,如果涉及较多的状态需要夸组件传递,用vuex是一个不错的选择。

七十九:Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?

在使用vuex时,异步执行的代码  写在actions

八十:vuex一个例子方法

index.js

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'




Vue.use(Vuex)

const store = new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
})

export default store

state.js

const state = {
  /**
   * 定单信息
   */
  applyDetail: {},
}

export default state

mutations-types.js

/**
 * 
 */

export const APPLY_DETAIL = 'APPLY_DETAIL' // 查询订单详情

mutations.js

import * as types from './mutations-types.js'

const mutations = {
  /**
   * 
   * @param {*} state 
   * @param {*} payload 
   * 查订单详情
   */
  [types.APPLY_DETAIL] (state, payload) {
    state.applyDetail = payload
  }
}

export default mutations

getters.js

const getters = {
  /**
   * 订单详情
   */
  applyDetail: state => state.applyDetail
}
export default getters

actions.js


import * as types from './mutations-types.js'
import * as serviceFang from '@api/fangService.js'

const actions = {
  /**
   * 
   * @param {*} context 
   * @param {*} params 
   * 订单详情
   */
  applyDetail (context, params) {
    return new Promise((resolve) => {
      serviceFang.applyDetail(params).then(res => {
        context.commit(types.APPLY_DETAIL, res)
        resolve()
      })
    })
  }
}

export default actions

 以上作为一个例子   对于简单的项目而言   对每一步的操作  可以分模块来写,

对于一个大型项目,可以采用module(官网推荐)的形式来写,不要死板

八十一:Vuex中如何异步修改状态

actions与mutations作用类似,都是可以对状态进行修改。不同的是actions是用来操作异步的。
actions是可以调用Mutations里的方法的。

在store.js中声明actions

const actions ={
    addAction(context){
        context.commit('add',10)

    },

    reduceAction({commit}){
        commit('reduce')

    }

}

在actions里写了两个方法addAction和reduceAction,在方法体里,我们都用commit调用了Mutations里边的方法。

  • context:上下文对象,这里你可以理解称store本身。
  • {commit}:直接把commit对象传递过来,可以让方法体逻辑和代码更清晰明了。

模板中的使用

需要在count.vue模板中编写代码,让actions生效。我们先复制两个以前有的按钮,并改成我们的actions里的方法名,分别是:addAction和reduceAction。

<p>

  <button @click="addAction">+</button>

  <button @click="reduceAction">-</button>

</p>

改造一下methods方法,首先还是用扩展运算符把mapMutations和mapActions加入。

methods:{
    ...mapMutations([  

        'add','reduce'

    ]),

    ...mapActions(['addAction','reduceAction'])

},

 

八十二:Vuex中actions和mutations的区别

Mutations

mutations 必须是同步函数,为什么?

  举个例子:  官方案例 

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。
然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:
因为当 mutation 触发的时候,回调函数还没有被调用,
devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的
状态的改变都是不可追踪的。

Actions

vuex为了解决mutations只有同步的问题,提出了actions(异步),专门用来解决mutations只有同步无异步的问题.

1、用于通过提交mutation改变数据

2、会默认将自身封装为一个Promise

3、可以包含任意的异步操作

vuex

八十三:顶部悬停效果

1.写入事件监听,监听滚动条。

 window.addEventListener('scroll', this.watchScroll)

2.判断卷入高度 

  watchScroll () {
        var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
        //  当滚动超过 50 时,实现吸顶效果
        if (scrollTop > 49) {
          this.navBarFixed = true
        } else {
          this.navBarFixed = false
        }
     }

3.对应样式

<div :class="navBarFixed == true ? 'navBarWrap' :''">
.navBarWrap {
    position:fixed;
    top:0;
    z-index:999;
  }

大体思路是这样(在秒杀项目中曾有实战)

八十四:电话本列表效果( 右边字母分类 上下滑动 旁边字母显示高亮)

1.data中定义

      letterData:  [
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
      ],
      pressDown: false, // 是否被按下
      liHeight: 0, // 每个li的高度 即元素之间的距离
      letterToast: {
        show: false,
        msg: ''
      },
      startClientY: 0 // 手按下时距离可视区顶部的高度

 2.实现js

    handleClick (letter) {
      this.$emit('letter-select', letter)
    },
    handler (e) {
      e.preventDefault();
    },
    touchstart (e, domIndex) {
      document.addEventListener('touchstart', this.handler(e), false);
      this.pressDown = true // 手势按下时的效果
      this.liHeight = e.target.offsetHeight  // 设置每个li的高度
      this.startClientY = e.targetTouches[0].clientY // 设置手按下时距离可视区顶部的高度
      this.letterToast = { show: true, msg: e.target.innerHTML }
    },
    touchmove (e, domIndex) {
      let touchmoveIndex = parseInt((e.touches[0].clientY - this.startClientY) / this.liHeight)  // 手势移动了多少个li的高度
      this.letterToast.msg = e.target.parentNode.children[domIndex + touchmoveIndex].innerHTML
      this.$emit('letter-select', this.letterToast.msg)
    },
    touchend (e, domIndex) {
      document.removeEventListener('touchmove', this.handler(e), false);
      this.pressDown = false // 手指松开移除ul效果
      this.letterToast = { show: false, msg: '' }
    }

大体思路是这样(曾在城市选择组件中实战)

八十五:vue做代理

  //vue-cli3.0 里面的 vue.config.js做配置
    devServer: {
        proxy: { //目的是解决跨域,若测试环境不需要跨域,则不需要进行该配置
            '/api2_1': {
                // 目标 API 地址
                target: 'https://api.taotiangou.cn/api2_1',
                // 如果要代理 websockets
                ws: true,
                // 将主机标头的原点更改为目标URL
                changeOrigin: true,
                pathRewrite: {
                    '^/api2_1': ''
                }
            },
            '/common': {
                // 目标 API 地址
                target: 'https://api.taotiangou.cn/common',
                // 如果要代理 websockets
                ws: true,
                // 将主机标头的原点更改为目标URL
                changeOrigin: true,
                pathRewrite: {
                    '^/common': ''
                }
            }
        }

官网内描述的很清楚(可能随着版本的迭代  会有差异)

八十六:Vue路由切换时的左滑和右滑效果示例

要实现的是左右切换的效果,所以要定义两种动画(左滑和右滑)

.transitionBody{
 transition: all 0.15s ease; /*定义动画的时间和过渡效果*/
}
 
.transitionLeft-enter,
.transitionRight-leave-active {
  -webkit-transform: translate(100%, 0);
  transform: translate(100%, 0);
   /*当左滑进入右滑进入过渡动画*/
}
 
.transitionLeft-leave-active,
.transitionRight-enter {
  -webkit-transform: translate(-100%, 0);
  transform: translate(-100%, 0);
}

HTML部分

 keep-alive 是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM,我们要把它也包裹在transition标签内,否则页面将重新渲染,切换的动画也会卡顿

<transition :name="transitionName">
  <keep-alive>
      <router-view class="transitionBody"></router-view>
  </keep-alive>
</transition>

JS部分

在Vue组件中,data必须是一个函数,将对象 {transitionName: ‘transitionLeft'} 挂载到Vue实例中,然后我们可以监听路由的 to 和 from 来判断此时应该左滑还是右滑,来动态切换transition的name值。

export default {
 data() {
   return {
    transitionName: 'transitionLeft'
   };
 },
 watch: {
  '$route' (to, from) {
   const arr = ['/goods','/ratings','/seller'];
   const compare = arr.indexOf(to.path)>arr.indexOf(from.path);
   this.transitionName = compare ? 'transitionLeft' : 'transitionRight';
  }
 }
}

 

八十七:请说一下Vue响应式数据的理解

vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。

利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)。
通过Object.defineProperty()来劫持属性的,使用属性的时候触发getter函数,收集依赖;修改属性的时候触发setter函数,触发相应的回调。

具体实现分四个模块
Observer:对数据的属性进行递归遍历,使用Object.defineProperty劫持并监听所有属性,属性发生变化时通知订阅者
Dep:收集订阅者,将Observer和watcher统一管理
Watcher:订阅者,负责接收属性的变化通知并执行相应的方法,从而更新视图,是Observer和Compiler之间通信的桥梁
自身实例化的时候,调用getter函数,向deps添加watch

当数据修改时,调用setter函数,调用deps.notify,执行watch的update函数
执行watch的update函数,重新生成虚拟DOM,并进行Diff对页面进行修改
Compile:解析者,解析每个节点的相关指令,对模板数据和订阅器进行初始化
用于将模板编译为渲染函数,并渲染视图页面
parse使用正则等方式解析template中的指令,class,style等数据,生成AST(抽象语法树)
optimize进行优化,标记静态节点,该节点会跳过diff
generate,把AST转化为渲染函数,渲染函数用于生成虚拟DOM

 

对象内部通过 defineReactive 方法,使用 Object.defineProperty 将属性进行劫持(只会劫持已经存在的属性),数组则是 通过重写数组方法来实现。 多层对象是通过递归来实现劫持

src/core/observer/index.js:135

export function defineReactive ( // 定义响应式数据
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
  // 如果不可以配置直接return
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 对数据进行观测
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () { // 取数据时进行依赖收集
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) { // 让对象本身进行依赖收集
          childOb.dep.depend() // {a:1} => {} 外层对象
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

八十八:Vue中如何检测数组变化?

数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择重写 数组( push,shift,pop,splice,unshift,sort,reverse )方法。

数组中如果是对象数据类型也会进行递归劫持 数组的索引和长度变化是无法监控到的

src/core/observer/index.js:47

src/core/observer/array.js:11

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

八十九:Vue中如何进行依赖收集?

  • 每个属性都拥有自己的dep属性,存放他所依赖的watcher,当属性变化后会通知自 己对应的watcher去更新
  • 默认在初始化时会调用render函数,此时会触发属性依赖收集 dep.depend
  • 当属性发生修改时会触发 watcher 更新 dep.notify()

九十:如何理解Vue中模板编译原理

问题核心:如何将template转换成render函数 ?

1.将template模板转换成 ast 语法树 - parserHTML

2.对静态语法做静态标记 - markUp

3.重新生成代码 - codeGen

src/compiler/index.js:11

export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  const ast = parse(template.trim(), options) // 1.解析ast语法树
  if (options.optimize !== false) {
    optimize(ast, options) // 2.对ast树进行标记,标记 静态节点
  }
  const code = generate(ast, options) // 3.生成代码
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})

九十一:Vue 生命周期钩子是如何实现的

  • Vue的生命周期钩子就是回调函数而已,当创建组件实例的过程中会调用对应的钩子 方法。
  • 内部会对钩子函数进行处理,将钩子函数维护成数组的形式

src/core/instance/init.js:38 初始化合并

src/core/util/options.js:388 合并选项

function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  const res = childVal // 儿子有
    ? parentVal
      ? parentVal.concat(childVal) // 父亲也有, 那就是合并
      : Array.isArray(childVal) // 儿子是数组
        ? childVal
        : [childVal] // 不是数组包装成数组
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}

 

 

 

九十二:待补充 欢迎留言

持续更新

 

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋哈哈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值