Vue双向数据绑定的原理

Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:

  1. 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter,当给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
  2. compile解析模版指令,将模版中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  3. Watcher订阅者是Observer和Compile之间通信的桥梁,在自身实例化时往属性订阅器里面添加自己,自身必须有一个update()方法,待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调。
  4. MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

Computed、Watch、Methods的区别

  1. Computed
  • 支持缓存,只有依赖的数据发生了变化,才会重新计算
  • 不支持异步,当Computed中有异步操作时,无法监听数据的变化
  • computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed
  • 如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。
  1. Watch
  • 它不支持缓存,数据变化时,它就会触发相应的操作
  • 支持异步监听
  • 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
    当一个属性发生变化时,就需要执行相应的操作
  • 监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数:
    immediate:组件加载立即触发回调函数
    deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。
  1. methods
    调用总会执行该函数

常见的事件修饰符及其作用

  • .stop:等同于JavaScript中的event.stopPropagation(),防止事件冒泡;
  • .prevent :等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
  • .capture:与事件冒泡的方向相反,事件捕获由外到内;
  • .self:只会触发自己范围内的事件,不包含子元素;
  • .once:只会触发一次

v-model是如何实现的

  1. 作用在表单元素上,动态绑定了input的value指向了message变量,并且在触发input事件的时候去动态把message设置为目标值
    1
    2
    3
    4
    5
    6
    <input v-model="sth" />
    // 等同于
    <input
    v-bind:value="message"
    v-on:input="message=$event.target.value"
    />
  2. 作用在组件上,在自定义组件中,v-model默认会利用名为value的prop和名为input的事件。本质是一个父子组件通信的语法糖,通过prop和$emit实现。因此父组件 v-model 语法糖本质上可以修改为:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <child :value="message" @input="function(e){message = e}"></child>

    // 自定义组件
    <my-input v-model="searchValue"></my-input>
    // 相当于
    <my-input
    v-bind:value="searchValue"
    v-on:input="searchValue = $event"
    ></my-input>

data为什么是一个函数而不是对象

JavaScript中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。而在Vue中,更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。
所以组件的数据不能写成对象的形式,而是要写成函数的形式。数据以函数返回值的形式定义,这样当每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行。
根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况
组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式

nextTick()使用场景

  • 在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的DOM结构的时候,这个操作就需要方法在nextTick()的回调函数中。
  • 在vue生命周期中,如果在created()钩子进行DOM操作,也一定要放在nextTick()的回调函数中。因为在created()钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM。

assets和static的区别

  • 相同点:assetsstatic两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下
  • 不同点:assets中存放的静态资源文件在项目打包时,也就是运行npm run build时会将assets中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在 static 文件中跟着 index.html 一同上传至服务器。static 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是 static 中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于 assets 中打包后的文件提交较大点。在服务器中就会占据更大的空间
  • 建议:将项目中 template需要的样式文件js文件等都可以放置在 assets 中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css 等文件可以放置在 static中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传。

delete和Vue.delete删除数组的区别

  • delete只是被删除元素变成了empty/undefined其他的元素的键值还是不变
  • Vue.delete直接删除了数组,改变了数组的键值

vue如何监听对象或者数组某个属性的变化

当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为Object.defineProperty()限制,监听不到变化。
解决方式:

  • this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么value)
    1
    this.$set(this.arr, 0, "OBKoro1"); // 改变数组this.$set(this.obj, "c", "OBKoro1"); // 改变对象
  • 调用以下几个数组的方法
    1
    splice()、 push()、pop()、shift()、unshift()、sort()、reverse()
    vue源码里缓存了array的原型链,然后重写了这几个方法,触发这几个方法的时候会observer数据,意思是使用这些方法不用再进行额外的操作,视图自动进行更新。推荐使用splice方法会比较好自定义,因为splice可以在数组的任何位置进行删除/添加操作
    vm.$set 的实现原理是:
  • 如果目标是数组,直接使用数组的splice方法触发响应式
  • 如果目标是对象,会先判断属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 gettersetter 的功能所调用的方法)

对SSR的理解

SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把HTML直接返回给客户端
优点:

  • 更好的SEO
  • 首屏加载速度更快

缺点:

  • 开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;
  • 当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;
  • 更多的服务端负载。

Vue性能优化

  1. 编码阶段
  • 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
  • v-if和v-for不能连用
  • key保证唯一
  • 使用路由懒加载、异步组件
  • 防抖、节流
  • 第三方模块按需导入
  • 长列表滚动到可视区域动态加载
  • 图片懒加载
  1. SEO优化
  • 预渲染
  • 服务端渲染SSR
  1. 打包优化
  • 压缩代码
  • 使用cdn加载第三方模块
  • 多线程打包happypack
  • splitChunks抽离公共文件
  • sourceMap优化
  1. 用户体验
  • 骨架屏
  • PWA
  • 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

vue初始化页面闪动问题

使用vue开发时,在vue初始化之前,由于div是不归vue管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于的字样,虽然一般情况下这个时间很短暂,但是还是有必要让解决这个问题的。
在css里加上以下代码

1
[v-cloak] {    display: none;}

如果没有彻底解决问题,则在根元素加上style="display: none;" :style="{display: 'block'}"

Vue的生命周期

  1. beforeCreate(创建前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
  2. created(创建后):实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。
  3. beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
  4. mounted(挂载后):在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
  5. beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
  6. updated(更新后):在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  7. beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。
  8. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

(注意:还有keep-alive独有的生命周期,分别为activateddeactivated。用keep-alive包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行deactivated钩子函数,命中缓存渲染后会执行activated钩子函数)

组件通信

  1. props / $emit
    父组件通过props向子组件传递数据,子组件通过$emit和父组件通信
  • 父组件向子组件传值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 父组件
    <template>
    <div id="father">
    <son :msg="msgData" :fn="myFunction"></son>
    </div>
    </template>

    <script>
    import son from "./son.vue";
    export default {
    name: father,
    data() {
    msgData: "父组件数据";
    },
    methods: {
    myFunction() {
    console.log("vue");
    }
    },
    components: {
    son
    }
    };
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 子组件
    <template>
    <div id="son">
    <p>{{msg}}</p>
    <button @click="fn">按钮</button>
    </div>
    </template>

    <script>
    export default {
    name: "son",
    props: ["msg", "fn"]
    };
    </script>
  • 子组件向父组件传值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 父组件
    <template>
    <div class="section">
    <son @getValue="getValue"></son>
    </div>
    </template>

    <script>
    import son from "./son.vue";
    export default {
    components: { son },
    data() {
    return {
    value: '123'
    }
    },
    methods: {
    getValue(data) {
    this.value = data
    }
    }
    }
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 子组件
    <template>
    <div>
    </div>
    </template>
    <script>
    export default {
    mounted(){
    this.$emit('getValue', this.getValue)
    },
    methods:{
    getValue(data){
    // do something
    },
    }
    }
    </script>
  1. eventBus事件总线($emit / $on)
    这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。
  2. 依赖注入(provide / inject)
    这种方式就是Vue中的依赖注入,该方法用于父子组件之间的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。
    provide / inject是Vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。
    provide 钩子用来发送数据或方法
    inject钩子用来接收数据或方法
  3. ref / $refs
    这种方式也是实现父子组件之间的通信。
    ref: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。
  4. $parent / $children
    使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
    使用$children可以让组件访问子组件的实例,但是,$children并不能保证顺序,并且访问的数据也不是响应式的。
  5. $attrs / $listeners
  6. vuex

路由懒加载

非懒加载

1
2
3
4
5
6
import List from '@/components/list.vue'
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})

懒加载

  1. 使用箭头函数+import动态加载
    1
    2
    3
    4
    5
    6
    const List = () => import('@/components/list.vue')
    const router = new VueRouter({
    routes: [
    { path: '/list', component: List }
    ]
    })
  2. 使用箭头函数+require动态加载
    1
    2
    3
    4
    5
    6
    7
    8
    const router = new Router({
    routes: [
    {
    path: '/list',
    component: resolve => require(['@/components/list'], resolve)
    }
    ]
    })
  3. 使用webpack的require.ensure技术,也可以实现按需加载。 这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // r就是resolve
    const List = r => require.ensure([], () => r(require('@/components/list')), 'list');
    const router = new Router({
    routes: [
    {
    path: '/list',
    component: List,
    name: 'list'
    }
    ]
    }))

路由的hash和history模式的区别

默认的路由模式是hash模式

  1. hash模式
  • hash模式是开发中默认的模式,它的URL带着一个#,例如:www.abc.com/#/vue,它的hash值就是#/vue。
  • 特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。
  • 原理:hash模式的主要原理就是onhashchange()事件:
    1
    2
    3
    4
    window.onhashchange = function(event){
    console.log(event.oldURL, event.newURL);
    let hash = location.hash.slice(1);
    }
    使用onhashchange()事件的好处就是,在页面的hash值发生变化时,无需向后端发起请求,window就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后端服务器,但是页面的hash值和对应的URL关联起来了。
  1. history模式
  • history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
  • 特点: 当使用history模式时,URL就像这样:abc.com/user/id。相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。
  • API
    修改历史状态:pushState()replaceState()方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。如果要做到改变url但又不刷新页面的效果,就需要用上这两个API。
    切换历史状态: 包括forward()、back()、go()三个方法,对应浏览器的前进,后退,跳转操作。

如何获取页面的hash变化

  1. 监听$router的变化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 监听,当路由发生变化的时候执行
    watch: {
    $route: {
    handler: function(val, oldVal){
    console.log(val);
    },
    // 深度观察监听
    deep: true
    }
    }
  2. window.location.hash读取#值, window.location.hash 的值可读可写,读取来判断状态是否改变,写入时可以在不重载网页的前提下,添加一条历史访问记录。

$route和$router的区别

  • $route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数
  • $router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。

如何定义动态路由?如何获取传过来的动态参数?

param方式

  • 配置路由格式:/router/:id
  • 传递的方式:在path后面跟上对应的值
  • 传递后形成的路径:/router/123
  1. 路由定义
    1
    2
    3
    4
    5
    6
    7
    8
    //在APP.vue中
    <router-link :to="'/user/'+userId" replace>用户</router-link>

    //在index.js
    {
    path: '/user/:userid',
    component: User,
    }
  2. 路由跳转
    1
    2
    3
    4
    5
    6
    7
    8
    // 方法1:
    <router-link :to="{ name: 'users', params: { uname: zhang }}">按钮</router-link>

    // 方法2:
    this.$router.push({name:'users',params:{uname:zhang}})

    // 方法3:
    this.$router.push('/user/' + zhang)
  3. 参数获取通过 $route.params.userid 获取传递的值

query方式

  • 配置路由格式:/router,也就是普通配置
  • 传递的方式:对象中使用query的key作为传递方式
  • 传递后形成的路径:/route?id=123
  1. 路由定义
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //方式1:直接在router-link 标签上以对象的形式
    <router-link :to="{path: '/list', query: { name: 'abc', age: 18 }}">信息</router-link>

    // 方式2:写成按钮以点击事件形式
    <button @click='listClick'>我的</button>

    listClick(){
    this.$router.push({
    path: '/list',
    query: {
    name: 'abc',
    age: '18',
    }
    })
    }
  2. 跳转方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 方法1:
    <router-link :to="{ name: 'users', query: { uname: 'abc' }}">按钮</router-link>

    // 方法2:
    this.$router.push({ name: 'users', query:{ uname: 'abc' }})

    // 方法3:
    <router-link :to="{ path: '/user', query: { uname: 'abc' }}">按钮</router-link>

    // 方法4:
    this.$router.push({ path: '/user', query:{ uname: 'abc' }})

    // 方法5:
    this.$router.push('/user?uname=' + abc)
  3. 获取参数
    通过$route.query获取传递的值

Vue-Router导航守卫

  1. 全局路由钩子
    vue-router全局有三个路由钩子
  • router.beforeEach 全局前置守卫 进入路由之前
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    router.beforeEach((to, from, next) => {  
    let ifInfo = Vue.prototype.$common.getSession('userData') // 判断是否登录的存储信息
    if (!ifInfo) {
    // sessionStorage里没有储存user信息
    if (to.path == '/') {
    //如果是登录页面路径,就直接next()
    next();
    } else {
    //不然就跳转到登录
    Message.warning("请重新登录!");
    window.location.href = Vue.prototype.$loginUrl;
    }
    } else {
    return next();
    }
    })

  • router.beforeResolve 全局解析守卫在 beforeRouteEnter 调用之后调用
  • router.afterEach 全局后置钩子 进入路由之后
    1
    2
    3
    4
    router.afterEach((to, from) => {  
    // 跳转之后滚动条回到顶部
    window.scrollTo(0,0);
    });
  1. 单个路由独享钩子
    beforeEnter 如果不想全局配置守卫的话,可以为某些路由单独配置守卫,有三个参数∶ to、from、next
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export default [    
    {
    path: '/',
    name: 'login',
    component: login,
    beforeEnter: (to, from, next) => {
    console.log('即将进入登录页面')
    next()
    }
    }
    ]
  2. 组件内钩子
    beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave这三个钩子都有三个参数∶to、from、next
  • beforeRouteEnter∶ 进入组件前触发
  • beforeRouteUpdate∶ 当前地址改变并且该组件被复用时触发
  • beforeRouteLeave∶ 离开组件被调用
    ⚠️注意:beforeRouteEnter组件内还访问不到this,因为该守卫执行前组件实例还没有被创建,需要传一个回调给 next来访问,例如:
    1
    2
    3
    4
    5
    6
    7
    beforeRouteEnter(to, from, next) {      
    next(target => {
    if (from.path == '/list') {
    target.isFromProcess = true
    }
    })
    }

Vue-router跳转和location.href有什么区别

  • 使用 location.href= /url 来跳转,简单方便,但是刷新了页面;
  • 使用 history.pushState( /url ) ,无刷新页面,静态跳转;
  • 引进 router ,然后使用 router.push( /url ) 来跳转,使用了 diff 算法,实现了按需加载,减少了 dom 的消耗。其实使用 router 跳转和使用 history.pushState() 没什么差别的,因为vue-router就是用了 history.pushState() ,尤其是在history模式下。

params和query的区别

  • query要用path来引入,params要用name来引入(因为 path 会忽略 params 这个属性)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //query传参,使用path跳转
    this.$router.push({
    path: 'first',
    query: {
    queryId: '123',
    queryName: 'abc'
    }
    })
    //query传参接收
    this.queryName = this.$route.query.queryName;
    this.queryId = this.$route.query.queryId;

    //params传参 使用name
    this.$router.push({
    name:'second',
    params: {
    id:'456',
    name: 'def'
    }
    })
    //params接收参数
    this.id = this.$route.params.id
    this.name = this.$route.params.name
  • url地址显示:query更加类似于ajax中get传参,params则类似于post,前者在浏览器地址栏中显示参数,后者则不显示
  • query刷新不会丢失query里面的数据 params刷新会丢失 params里面的数据。

Vuex 和 localStorage 的区别

  1. 区别
  • vuex存储在内存中
  • localstorage 则以文件的方式存储在本地,只能存储字符串类型的数据,存储对象需要 JSON的stringify和parse方法进行处理。读取内存比读取硬盘速度要快
    (⚠️注意:对于不变的数据可以用localstorage可以代替vuex,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage无法做到)
  1. 应用场景
  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex用于组件之间的传值。
  • localstorage是本地存储,是将数据存储到浏览器的方法,一般是在跨页面传递数据时使用 。
  • Vuex能做到数据的响应式,localstorage不能
  1. 永久性
    刷新页面时vuex存储的值会丢失,localstorage不会。

Vuex组件访问State中数据的两种方式

1
2
3
4
5
6
//创建store数据源,提供唯一公共数据
const store = new Vuex.store({
state: {
num:0
}
})
  1. this.$store.state.全局数据名称
    1
    this.$store.state.num
  2. 导入 mapState 函数 将当前组件需要的全局数据 映射为当前组件的 computed 计算属性
    1
    2
    3
    4
    5
    6
    7
    //在需要使用的组件里,从Vuex中按需导入mapState函数
    import { mapState } from 'vuex'

    //通过刚才导入的mapState函数,将当前组件需要的全局数据,映射为当前组件的computed计算属性。
    computed: {
    ...mapState(['num'])
    }

HTTP状态码

1xx 信息,服务器收到请求,需要请求者继续执行操作
2xx 成功,操作被成功接收并处理
3xx 重定向,需要进一步的操作以完成请求
4xx 客户端错误,请求包含语法错误或无法完成请求
5xx 服务器错误,服务器在处理请求的过程中发生了错误
常见的状态码
200 OK,请求已正常处理
204 请求处理成功,但没有任何资源可以返回给客户端
206 是对资源某一部分的请求
301 永久性重定向,请求的资源已经被分配了新的URI,以后应使用资源现在所指的URI。
302 临时性重定向
303 资源的URI已更新,你是否能临时按新的URI访问。该状态码表示由于请求对应的资源存在着另一个URL,应使用GET方法定向获取请求的资源。
304 资源已找到,但未符合条件请求
400 服务器端无法理解客户端发送的请求,请求报文中可能存在语法错误。
401 发送的请求需要有通过HTTP认证(BASIC认证,DIGEST认证)的认证信息。
403 不允许访问那个资源。该状态码表明对请求资源的访问被服务器拒绝了。(权限,未授权IP等)
404 服务器上没有请求的资源
500 服务器端在执行请求时发生了错误
503 服务器暂时处于超负载或正在停机维护,现在无法处理请求。

浏览器本地存储方式及使用场景

  1. Cookie
    Cookie的大小只有4kb,它是一种纯文本文件,每次发起HTTP请求都会携带Cookie。
    特性:
  • Cookie一旦创建成功,名称就无法修改
  • Cookie是无法跨域名的
  • 每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb
  • 有安全问题,如果Cookie被拦截了,那就可获得session的所有信息,即使加密也于事无补,无需知道cookie的意义,只要转发cookie就能达到目的
  • Cookie在请求一个新的页面的时候都会被发送过去
    如果需要域名之间跨域共享Cookie:
  • 使用Nginx反向代理
  • 在一个站点登陆之后,往其他网站写Cookie。服务端的Session存储到一个节点,Cookie存储sessionId
    Cookie的使用场景:
  • 最常见的使用场景就是Cookie和session结合使用,我们将sessionId存储到Cookie中,每次发请求都会携带这个sessionId,这样服务端就知道是谁发起的请求,从而响应相应的信息。
  • 可以用来统计页面的点击次数
  1. LocalStorage
    优点:
  • LocalStorage的大小一般为5MB,可以储存更多的信息
  • LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在
  • 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带
    缺点:
  • 存在浏览器兼容问题,IE8以下版本的浏览器不支持
  • 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage
  • LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问
    常用API:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 保存数据到 localStorage
    localStorage.setItem('key', 'value');

    // 从 localStorage 获取数据
    let data = localStorage.getItem('key');

    // 从 localStorage 删除保存的数据
    localStorage.removeItem('key');

    // 从 localStorage 删除所有保存的数据
    localStorage.clear();

    // 获取某个索引的Key
    localStorage.key(index)
    使用场景:
  • 在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中
  1. SessionStorage
    SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。
    特性:
  • 在本地进行数据存储;
  • 有同源策略的限制,只有在同一浏览器的同一窗口下才能够共享;
  • 不能被爬虫爬取;
    常用API:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 保存数据到 sessionStorage
    sessionStorage.setItem('key', 'value');

    // 从 sessionStorage 获取数据
    let data = sessionStorage.getItem('key');

    // 从 sessionStorage 删除保存的数据
    sessionStorage.removeItem('key');

    // 从 sessionStorage 删除所有保存的数据
    sessionStorage.clear();

    // 获取某个索引的Key
    sessionStorage.key(index)
    使用场景:
  • 由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。

解决跨域问题

  1. CORS
    CORS需要浏览器和服务器同时支持,整个CORS过程都是浏览器完成的,无需用户参与。因此实现CORS的关键就是服务器,只要服务器实现了CORS请求,就可以跨源通信了。

  2. JSONP
    jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

  • js实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var script = document.createElement('script');
    script.type = 'text/javascript';
    // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
    document.head.appendChild(script);
    // 回调执行函数
    function handleCallback(res) {
    alert(JSON.stringify(res));
    }
    服务端返回:
    1
    handleCallback({"success": true, "user": "admin"})
  • Vue axios实现
    1
    2
    3
    4
    5
    6
    7
    this.$http = axios;
    this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'handleCallback'
    }).then((res) => {
    console.log(res);
    })
    后端node.js代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var querystring = require('querystring');
    var http = require('http');
    var server = http.createServer();
    server.on('request', function(req, res) {
    var params = querystring.parse(req.url.split('?')[1]);
    var fn = params.callback;
    // jsonp返回设置
    res.writeHead(200, { 'Content-Type': 'text/javascript' });
    res.write(fn + '(' + JSON.stringify(params) + ')');
    res.end();
    });
    server.listen('8080');
    console.log('Server is running at port 8080...');
    JSONP的缺点:
  • 具有局限性, 仅支持get方法
  • 不安全,可能会遭受XSS攻击
  1. postMessage 跨域
    postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递
  • 上面三个场景的跨域数据传递
    用法:postMessage(data,origin)方法接受两个参数:
  • data:html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
  • origin:协议+主机+端口号,也可以设置为”*”,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/“。
  1. nginx代理跨域
    nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。

  2. node.js中间件代理跨域
    node中间件实现跨域代理,通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

  3. document.domain + iframe跨域
    此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

  4. location.hash + iframe跨域
    a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

  5. window.name + iframe跨域
    window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

  6. WebSocket协议跨域
    它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

宏任务和微任务分别有哪些

  • 微任务包括: promise 的回调、process.nextTick 、对 Dom 变化监听的 MutationObserver。
  • 宏任务包括: script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。