当前位置 博文首页 > 九号窗口的博客:前端面试题总结(vue)

    九号窗口的博客:前端面试题总结(vue)

    作者:[db:作者] 时间:2021-09-02 22:17

    vue的优点

    vue是个轻量级的框架,是一个构建数据的视图集合,大小只有几十Kb
    vue是组件化开发,适合多人开发
    vue中的双向数据绑定更方便操作表单数据
    因为vue是MVVM的框架,视图,数据,结构分离使数据的更改更为简单
    vuex可以管理所有组件用到的数据,不需要借助之前props在组件间传值
    官方文档通俗易懂,易于理解和学习;
    

    vue的核心

     数据驱动和组件化。
    

    mvvm框架的理解和相对mvc的优点

    理解

    model是数据模型,管理数据和处理业务逻辑
    view是视图,负责显示数据
    ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,负责监听Model中数据的改变并且控制视图的更新
    model中的数据改变,view也会跟着改变;用户通过交互操作改变view中的数据,model也会跟着改变。其中的dom操作viewmodel帮我们完成了,不需要自己操作dom
    

    优点

    mvvm 主要解决了 mvc 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
    当 Model 频繁发生变化,开发者需要主动更新到 View
    

    vue组件封装的过程

    首先,使用Vue.extend()创建一个组件
    然后,使用Vue.component()方法注册组件
    接着,如果子组件需要数据,可以在props中接受定义
    最后,子组件修改好数据之后,想把数据传递给父组件,可以使用emit()方法
    

    vue父子组件之间传递数据

    1-1、父组件向子组件传递数据

    通过props,例子如下

    1. 父组件 在引用子组件时,通过属性绑定(v-bind:)的形式,把需要传递给子组件的数据,传递到子组件内部,供子组件使用
    2. 在 props数组 中定义父组件传递过来的数据
    3. 在该子组件中使用props数组 中定义好的数据
    <template>
      <div>
        <div>{{message}}(子组件)</div>
      </div>
    </template>
    <script>
    export default {
      props: {
        message: { type: String, default: '默认值' } ,//定义传值的类型
    }
    </script>
    
    <template>
      <div>
        <div>父组件</div>
        <child :message="parentMsg"></child>  
      </div>
    </template>
    <script>
    import child from './child' //引入child组件
    export default {
      data() {
          return {
            parentMsg: 'a message from parent' //在data中定义需要传入的值
          }
        },
        components: {
          child
        }
    }
    </script>
    

    1-2、父组件调用子组件的方法

    父:
    <child ref="childMethod"></child>
    子:
    method: {
      test() {
         alert(1)
      }
    }
    在父组件里调用test即 this.$refs.childMethod.test()
    

    2-1、子组件向父组件传递方法

    1. 父组件在组件上定义了一个自定义事件,用于接受子组件传过来的值
    2. 在子组件中定义一个方法,利用 $emit 触发 父组件传递过来的事件把值传给父组件
    <template>
        <div class="app"> 
           <input @click="sendMsg" type="button" value="给父组件传递值">
        </div>
    </template>
    <script>
    export default {
        data () {
            return {
                //将msg传递给父组件
                msg: "我是子组件的msg",
            }
        },
         methods:{
             sendMsg(){
                 //func: 是父组件指定的传数据绑定的函数,this.msg:子组件给父组件传递的数据
                 this.$emit('func',this.msg)
             }
         }
    }
    </script>
    
    <template>
        <div class="app">
            <child @func="getMsgFormSon"></child> //父组件调用方法
        </div>
    </template>
    <script>
    import child from './child.vue'
    export default {
        data () {
            return {
                msgFormSon: "this is msg"
            }
        },
        components:{
            child,
        },
        methods:{
                getMsgFormSon(data){
                  //此处的data即为子组件传过来的数据
                    this.msgFormSon = data
                    console.log(this.msgFormSon)
                }
        }
    }
    </script>
    

    2-2 、子组件调用父组件的方法

    直接在子组件中通过this.$parent.event来调用父组件的方法
    

    兄弟组件之间传值

    bus方式
    1.新建bus.js

    import Vue from 'vue'
    export default  new Vue
    

    2.在需要传值和接受值的vue文件中,各自引入bus.js

    import bus from '../util/bus'
    

    3.定义传值的方法,使用bus.$emit(‘methodName’,data), methodName是自定义的方法名

    <button @click="trans()">传值</button>
    
    methods: {
        trans(){
          bus.$emit('test',this.helloData)
        }
      },
    

    4.在要接收值的组件里,使用bus.on(‘methodName’,val =>{ }) ,val 就是传过来的值

     mounted(){
        bus.$on('test',val=>{
          console.log(val);
          this.cdata = val
        })
      }
    

    vuex方式

    1、安装vuex :cnpm install vuex --save
    2、创建一个 vuex 文件夹,并在里面新建一个 store.js 写入以下代码:

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    

    3、state 定义数据:state在vuex中用于存储数据

    var state={ //存放数据,数据中心
        count:1,
        // 其他数据格式:orderList: [],
        // 其他数据格式:params: {}
    }
    

    4、 getters 类似计算属性:

    var getters= {
        computedCount: (state) => {
            return state.count*2
        }
    }  
    

    5、mutations里面放的是方法,方法主要用于改变state里面的数据

    var mutations={
        incCount(){
        ++state.count;
        }
    } 
    

    6、异步操作,Action 提交的是 mutation,而不是直接变更状态

    var actions= {
      incMutationsCount(context) {    /*因此你可以调用 context.commit 提交一个 mutation*/
        context.commit('incCount');    /*执行 mutations 里面的incCount方法 改变state里面的数据*/
        //此处按照实际情况扩展~
      }
    }
    

    7、暴露参数

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

    8、 组件里去使用 Vuex:

    (1). 获取state里面的数据

    this.$store.state.数据
    

    (2). 获取 getters里面方法返回的的数据 (一般vue 和 store 进行交互 用 $store.getters, getters的值放在计算属性里,动态绑定在计算属性computed里)

      this.$store.getters.computedCount
    

    (3). 触发 mutations 改变 state里面的数据

    this.$store.commit('incCount');
    

    (4). 触发 actions里面的方法

     this.$store.dispatch('incMutationsCount'); 
      //这个 incMutationsCount 会再去 执行 mutations 里面的incCount方法
    

    vuex与本地存储的区别

    1.区别:vuex存储在内存,localstorage(本地存储)则以文件的方式存储在本地,永久保存;
    localStorage和sessionStorage只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理

    2.应用场景:vuex用于组件之间的传值,localstorage,sessionstorage则主要用于不同页面之间的传值。

    3.永久性:当刷新页面(这里的刷新页面指的是 --> F5刷新,属于清除内存了)时vuex存储的值会丢失,sessionstorage页面关闭后就清除掉了,localstorage不会。

    vue页面级组件之间传值

    1.使用vue-router通过跳转链接带参数传参。
    
    2.使用本地缓存localStorge。
    
    3.使用vuex数据管理传值。
    

    vue-router的两种模式(hash和history)及区别

    1:hash 模式下,仅hash符号之前的内容会被包含在请求中,如http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误。
    
    history模式下,前端的URL必须和实际向后端发起请求的URL一致。
    history 模式需要后端配合将所有访问都指向 index.html,否则用户刷新页面,会导致 404 错误
    

    vue路由权限控制实现

    实现路由权限控制的两种方式

    配置静态的路由表,比如登录、注册页,其他路由通过动态注入
    登录的时候过滤后台返回的路由列表,拿到符合路由规则的路由表
    通过 addRoutes() 这个方法把路由给注入到路由表,这样就可以访问已注入的路由了
    

    Vue项目中实现用户登录及token验证

    1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
    
    2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
    
    3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面
    
    4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
    
    5、每次调后端接口,都要在请求头中加token
    
    6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
    
    7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面
    
    // 导航守卫
    // 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
    router.beforeEach((to, from, next) => {
      if (to.path === '/login') {
        next();
      } else {
        let token = localStorage.getItem('Authorization');
     
        if (token === 'null' || token === '') {
          next('/login');
        } else {
          next();
        }
      }
    });
    
    // 添加请求拦截器,在请求头中加token
    axios.interceptors.request.use(
      config => {
        if (localStorage.getItem('Authorization')) {
          config.headers.Authorization = localStorage.getItem('Authorization');
        }
     
        return config;
      },
      error => {
        return Promise.reject(error);
      });
    
    //如果前端拿到状态码为401,就清除token信息并跳转到登录页面
          localStorage.removeItem('Authorization');
          this.$router.push('/login');
    

    说说微信公众号项目授权登录过程

    调微信的授权登录地址,成功之后微信会重定向回到我们开发的页面并返回code在回调的url中
    拿到code以后,传给后台,让后台去获取用户信息再传给前端。我们拿到用户信息后,比如openId,头像等,可以用localStorage缓存起来
    

    v-show 和 v-if指令的不同点

    v-show 本质就是通过控制 css 中的 display 设置为 none,控制隐藏,只会编译一次;v-if 是动态的向 DOM 树内添加或者删除 DOM 元素,若初始值为 false ,就不会编译了。而且 v-if 不停的销毁和创建比较消耗性能。

    总结:如果要频繁切换某节点,使用 v-show (切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用 v-if(初始渲染开销较小,切换开销比较大)

    说出几种vue当中的指令和它的用法

    v-model 双向数据绑定;

    v-for 循环;

    v-if v-show 显示与隐藏;

    v-on 事件;v-once : 只绑定一次。

    Vue中双向数据绑定是如何实现的

    当data 有变化的时候它通过Object.defineProperty()方法中的set方法进行监控,并调用在此之前已经定义好data 和view关系的回调函数,来通知view进行数据的改变
    而view 发生改变则是通过底层的input 事件来进行data的响应更改

    vue的生命周期

    概念:Vue 实例从创建到销毁的过程

    生命周期钩子的一些使用方法:

    beforecreate : 可以在这加个loading事件,在加载实例时触发 
    created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用 
    mounted : 挂载元素,获取到DOM节点 
    updated : 如果对数据统一处理,在这里写上相应函数 
    beforeDestroy : 可以做一个确认停止事件的确认框 nextTick : 更新数据后立即操作dom
    

    vue父子组件生命周期执行顺序

    父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

    created和mounted的区别

    created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
    mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

    计算属性computed和watch的区别

    计算属性和侦听属性都可以实现当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,但在特定场景下的处理还是有一些微妙的区别

    data: {
        firstName: 'Liu',
        lastName: 'lu'
      },
      computed: {
      fullName:{
       get(){//回调函数 当需要读取当前属性值时执行,根据相关数据计算并返回当前属性的值
          return this.firstName + ' ' + this.lastName
        },
       set(val){//监视当前属性值的变化,当属性值发生变化时执行,更新相关的属性数据
           //val就是fullName的最新属性值
           console.log(val)
            const names = val.split(' ');
            console.log(names)
            this.firstName = names[0];
            this.lastName = names[1];
       }
       }
      }
    
        watch: {
            pagination: {
                handler (val) {
                    this.paginationInfo = val
                },
                immediate: true,
                deep: true
            }
        }
    
    
    computed常用于值的计算,如简化tempalte里面{{}}计算和处理props或$emit的传值,页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数。
    watch常用来观察动作,听props,$emit或本组件的值执行异步操作,页面重新渲染时值不变化也会执行
    computed名称不能与data里对象重复,只能用同步,必须有return,是多个值变化引起一个值变化,多多对一
    watch名称必须与data里对象一样,可以用于异步,没有return,是一对多,监听一个值,一个值变化引起多个值变化
    
    
    1、watch中的函数名称必须是所依赖data中的属性名称
    2、watch中的函数是不需要调用的,只要函数所依赖的属性发生了改变 那么相对应的函数就会执行
    3、watch中的函数会有2个参数 一个是新值,一个是旧值
    4、watch默认情况下无法监听对象的改变,如果需要进行监听则需要进行深度监听 深度监听需要配置handler函数以及deep为true。(因为它只会监听对象的地址是否发生了改变,而值是不会监听的)
    5、watch默认情况下第一次的时候不会去做监听,如果需要在第一次加载的时候也需要去做监听的话需要设置immediate:true
    6、watch在特殊情况下是无法监听到数组的变化
    - 通过下标来更改数组中的数据
    - 通过length来改变数组的长度
    

    v-for和v-if优先级问题与解决方法

    原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
    不推荐:

      <ul>
         <li v-for="user in users"  v-if="user.isActive" :key="user.id">
            {{ user.name }}
         </li>
      </ul>
    

    推荐:

    computed: {
    activeUsers: function () {
    return this.users.filter(function (user) {
      return user.isActive
    })
    }
    }
    
    
    
    <ul>
       <li v-for="user in activeUsers" :key="user.id">
         {{ user.name }}
       </li>
    </ul>
    

    keep-alive内置组件的作用

    可以让当前组件或者路由不经历创建和销毁,而是进行缓存,凡是被keep-alive组件包裹的组件,除了第一次以外。不会经历创建和销毁阶段的。第一次创建后就会缓存到缓存当中

    初次进入时:created > mounted > activated;退出后触发 deactivated
    再次进入:会触发 activated;事件挂载的方法等,只执行一次的放在 mounted 中;组件每次进去执行的方法放在 activated 中

    keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。

    routers: [{
      path: '/',
      name: 'Home',
      meta: {
        keepAlive: false // 不需要缓存
      }
    },{
      path: '/page',
      name: 'Page',
      meta: {
        keepAlive: true  // 需要缓存
      }
    },]
    

    $route和 $router的区别是什么?

    $router为VueRouter的实例,是一个全局路由对象,包含了路由跳转的方法、钩子函数等。

    $route 是路由信息对象||跳转的路由对象,每一个路由都会有一个route对象,是一个局部对象,包含path,params,hash,query,fullPath,matched,name等路由信息参数

    vuex知识点总结

    • vuex的理解
      vuex是一个专为vue.js应用程序开发的状态管理模式(它采用集中式存贮管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化)
    • vuex的作用
      由于传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致代码无法维护。所以我们需要把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。
    • vuex核心属性
      state,getter,mutation,action,module

    state:存储数据,存储状态;在根实例中注册了store 后,用 this.$store.state 来访问;对应vue里面的data;存放数据方式为响应式,vue组件从store中读取数据,如数据发生变化,组件也会对应的更新。

    组件访问State中数据的两种方式
    1、this.$store.state.全局数据名称
    2、从vuex中按需导入mapState函数,将当前组件需要的全局数据映射为当前组件的computed计算属性

    import { mapState } from 'vuex'
    
    computed:{
    ...mapState( ['count'] ) 
    }
    

    getter:可以认为是 store 的计算属性,它的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

    const store = new Vuex.store({
       state:{
          count : 0
      },
       getters:{
          countAfter: state=>{
            return count*2
          }
       }
    })
    
    //使用getters的第一种方式
    this.$store.getters.countAfter
    

    mutation:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

    const store = new Vuex.store({
        state:{
          count : 0
      },
      mutation:{
          add(state){
           //变更状态
          state.count++
          }
       }
    })
    
    nethods:{
         handle(){
           //触发mutation的第一种方式
              this.$store.commit('add')
    }
    
    //1、从vuex中按需导入mapMutation函数
    import { mapMutation } from 'vuex'
    //2、将指定的mutation函数,映射为当前组件的methods函数
    methods:{
       ...mapMutation(['add','addN'])
       handle(){
          //触发mutation的第二种方式
          this.add()
       }
    }
    

    action:包含任意异步操作,通过提交 mutation 间接更变状态。

    const store = new Vuex.store({
    mutation:{
      add(state){
         state.count++
      }
    },
    actions:{
      addAsync(context){
      //在actions中不能直接修改state中的数据
      //必须通过context.commit()触发某个mutation才行
        setTimeout(()=>{
           context.commit('add')
        },1000)
      }
    }
    
    })
    
    //触发Action
    methods:{
      handle(){
        //触发actions的第一种方式
        this.$store.dispatch('addAsync')
      }
    }
    
    //1、从vuex中按需导入mapActions函数
    import { mapActions} from 'vuex'
    //2、将指定的mapAtions函数,映射为当前组件的methods方法
    methods:{
       ...mapMutation(['addAsync','addNAsync'])
       handle(){
          //触发mutation的第二种方式
          this.addAsync()
       }
    }
    

    module:将 store 分割成模块,每个模块都具有state、mutation、action、getter、甚至是嵌套子模块。

    vue中的修饰符

    事件修饰符

    1. stop:阻止冒泡(通俗讲就是阻止事件向上级DOM元素传递)
    2. prevent:阻止默认事件的发生(默认事件指对DOM的操作会引起自动执行的动作,比如点击超链接的时候会进行页面的跳转,点击表单提交按钮时会重新加载页面等,使用".prevent"修饰符可以阻止这些事件的发生)
    3. capture:捕获冒泡,即有冒泡发生时,有该修饰符的dom元素会先执行,如果有多个,从外到内依次执行,然后再按自然顺序执行触发的事件
    4. self:将事件绑定到自身,只有自身才能触发,通常用于避免冒泡事件的影响
    5. native:在父组件中给子组件绑定一个原生的事件,就将子组件变成了普通的HTML标签,不加’. native’事件是无法触 发的。

    表单元素修饰符

    1. trim能够去除输入内容左右两边的空格
    <input type="text" v-model.trim="msg">
    
    1. lazy只有标签的change事件执行后才会执行数据的双向绑定
    <input type="text" v-model.lazy="msg" @input="input" @change="change">
    
    1. .number 修饰符, 可以将用户输入的值,转换成Number类型。

    less和sass的区别

    首先,sass和less都是css的预编译处理语言,他们引入了mixins,参数,嵌套规则,运算,颜色,名字空间,作用域,JavaScript赋值等 加快了css开发效率,当然这两者都可以配合gulp和grunt等前端构建工具使用,但是他们两者有什么不同呢?

    1.编译环境不同

    less是通过js编译 是在客户端处理
    
    sass同通过ruby 是在服务器端处理
    

    2.变量符不一样

    less是用@,sass是用$
    

    3.sass支持条件语句,可以使用if{}else{},for{}循环等等。而less不支持。

    常用的ES6新语法

    let const

    var 存在变量提升而let和const不存在变量提升
    var存在变量覆盖,而let和const在同级作用域中不能重复定义
    var声明的变量会挂载到window上,会放在全局,let 和 const声明的变量不会
    let和const声明的变量会形成块级作用域,var不会
    const有一个很好的应用场景,当我们引用第三方库的时声明的变量,用const来声明可以避免未来不小心重命名而导致出现bug
    
    console.log(a);//undefined
    
    var a = "hey I am now hoisting";
    
    console.log(a);//Uncaught ReferenceError: a is not defined
    
    let a = "hey I am now hoisting";
    
        var a="show";
        function hah(){
            alert(a);//undefined
            var a=4;
            alert(a);//4
        }
        hah();
    
    function hah(number){
            var a="show";
            while(number!=0){
                alert(a);//show
                var a=4;
                alert(a);//4
                number--;
            }
        }
       hah(1);   
    

    模板字符串

    $("#result").append(`
     There are <b>${basket.count}</b> items
      in your basket, <em>${basket.onSale}</em>
     are on sale!
    `);
    

    箭头函数

    function(x, y) {
       x++;
       y--;
       return x + y;
    }
    
    (x, y) => {x++; y--; return x+y}
    

    字符串方法

    // 3.新增字符串方法
    let str = "hello.vue";
    // 开头
    console.log(str.startsWith("hello"));//true
    // 后缀
    console.log(str.endsWith(".vue"));//true
    // 包含
    console.log(str.includes("e"));//true
    console.log(str.includes("hello"));//true
    

    当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,它的this是继承外面的,因此内部的this就是外层代码块的this

    普通函数中的this总是代表它的直接调用者,在默认情况下,this指的是window

    node.js相关

    • 概念
      Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境
    • npm
      即一个包管理工具,我们一般利用它来安装一些包
    • nodejs模块系统
      我们接触到的require是nodejs提供的一个接受对象,与之相对的是exports倒出对象

    webpack相关

    • 作用
      webpack是一个模块打包的工具,它的作用是把互相依赖的模块处理成静态资源
    • 优点
    依赖管理:方便引用第三方模块、让模块更容易复用、避免全局注入导致的冲突、避免重复加载或加载不需要的模块。
    合并代码:把各个分散的模块集中打包成大文件,减少 HTTP 的请求链接数,配合 UglifyJS 可以减少、优化代码的体积。
    各路插件:babel 把 ES6+ 转译成 ES5 ,eslint 可以检查编译期的错误……
    
    • 原理
    由于 webpack 并不支持除 .js 以外的文件,
    从而需要使用 loader 转换成 webpack 支持的模块,
    plugin 用于扩展 webpack 的功能,
    在 webpack 构建生命周期的过程在合适的时机做了合适的事情。
    
     简单的说就是分析代码,找到“require”、“exports”、“define”等关键词,并替换成对应模块的引用。
     把你的项目当成一个整体,通过一个给定的主文件(index.js),
     webpack将从这个文件开始找到你的项目的所有的依赖文件,
     使用loaders处理他们,最后打包为一个浏览器可以识别的js文件
    
    • 有哪些常见的Loader?他们是解决什么问题的?
    file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
    url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
    source-map-loader:加载额外的 Source Map 文件,以方便断点调试
    image-loader:加载并且压缩图片文件
    babel-loader:把 ES6 转换成 ES5
    css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
    style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
    eslint-loader:通过 ESLint 检查 JavaScript 代码
    
    • 配置开发环境和生产环境?
    process.env是Node.js用于存放当前进程环境变量的对象;
    而NODE_ENV则可以让开发者指定当前的运行时环境,
    当它的值为production时即代表当前为生产环境
    库和框架在打包时如果发现了它就可以去掉一些开发环境的代码,如警告信息和日志等。这将有助于提升代码运行速度和减小资源体积
    
    • 打包优化
    首先可以优化Loader的搜索范围,只在src文件夹下查找,node_modules下的代码是编译过的,没必要再去处理一遍
    使用HappyPack可以将Loader的同步执行转为并行,从而执行Loader时的编译等待时间
    代码压缩相关,启用gzip压缩
    

    说下对 Virtual DOM(虚拟dom) 算法的理解

    概念

    虚拟dom就是一个能代表dom树的js对象,通常含有标签名、标签属性还有一些子元素等等
    

    优点

    虚拟dom借助dom diff算法可以减少不必要的dom操作
    diff算法是在新虚拟DOM和老虚拟DOM进行diff(精细化比对),实现最小量更新,最后反映到真正的DOM上
    

    v-for循环中key的作用

    v-for默认使用就地复用策略,列表数据修改的时候,他会根据key值去判断某个值是否修改,
    如果修改,则重新渲染这一项,否则复用之前的元素
    key的作用主要是为了高效的更新虚拟DOM
    

    this指向问题

    函数作为对象本身属性调用的时候,this 指向对象
    
      1.call:参数1 this指向,参数2 任意类型
    
      2.apply:参数1 this指向,参数2 数组 (参数一为null指向的是本身)
    
      3.var一个变量保存this指向
    
      4.使用es6的箭头函数
    
    
    	let obj={
            a:222,
            fn:function(){ 
                   console.log(this)    //obj
                setTimeout(function(){
                   console.log(this)    //window
                })
            }
        };
        obj.fn();
    
    
    
    
    
    
    
    let obj={
        a:222,
        fn:function(){           
            setTimeout(()=>{
                console.log(this) //obj
            	console.log(this.a) //222
            });
        }
    };
    obj.fn();
    
    
    
    
    
    
    
    //被嵌套的函数独立调用时this默认指向window
    var obj = {
    	a:2,
    	foo:function(){
    		   console.log(this) // obj
    		  function text(){
    		  	console.log(this)  //window
    		  }
    		  text();
    	}
    }
    obj.foo()
    
    
    
    
    
    
    
    //自执行函数内部中的this指向window
    
    var a = 10;
    function foo(){
    	(function test(){
    		console.log(this);  //window
    	})()
    }
    var obj = {
    	a:2,
    	foo:foo
    }
    obj.foo();
    
    
    
    
    
    
    //闭包 this默认指向了window
    
    var a = 10;
    var obj = {
    	a:2,
    	foo:function(){
    		var c  = this.a
    		return function test(){
    			console.log(this)
    			return c
    		}
    	}
    }
    
    var fn = obj.foo()
    fn()
    
    
    
    
    // 隐式丢失this的五种情况
    // 1、函数赋值给另外变量
    var a = 0;
    function foo(){
    	console.log(this) //window
    	console.log(this.a)
    }
    
    var obj = {
    	a:1,
    	foo:foo
    }
    var bar = obj.foo;
    bar();
    
    
    //2、参数传递
    var a = 0;
    function foo(){
    	console.log(this)
    }
    
    function bar(fn){
           fn()
    }
    var obj={
    	a:1,
    	foo:foo,
    }
    bar(obj.foo);
    
    // 3、内置函数  setTimeout、setInterval第一个参数的回调函数中的this默认指向window
    var a  = 0;
    var obj = {
    	a:1,
    	foo:function(){
    		console.log(this.a)
    	}
    }
    
    setTimeout(obj.foo, 2000)
    
    
    
    
    
    // 4、间接调用
    function foo(){
        console.log(this.a)
    }
    var a = 0;
    var obj={
    	a:1,
    	foo:foo
    }
    
    var p = {a:4};
    obj.foo(); //1
    (p.foo = obj.foo)(); //0
    
    
    
    
    
    // 显式绑定
    1、call,apply, bind
    2、数组的forEach等方法
    

    promise对象与回调地狱

    • 回调地狱概念
      回调函数是作为参数传递给另一个函数的函数,然后在外部函数内调用该函数以完成某种例程或操作
      函数作为参数层层嵌套就是回调地狱

    promise对象采用链式的 then方法,可以指定一组按照次序调用的回调函数。
    前一个 then 里的一个回调函数,返回的可能还是一个 Promise对象,(即有异步操作)这时后面的回调函数,就会等待该 Promise对象的状态发生变化才会被调用,由此实现异步操作按照次序执行。

    • 缺点:

    Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

    // 采用ajax异步回调的方式改装成promise
    
    function getIp(){
       var promise = new Promise(function(resolve,reject){
          var xhr = new XMLHttpRequest()
          xhr.open('GET','https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getIp', true) //设置一个ajax的参数)
          xhr.onload = function(){
             var retJson = JSON.parse(xhr.responseText)     // {"ip":"58.100.211.137"} 数据到来对数据进行解析
             resolve(retJson.ip)      //初始化-完成状态-变为成功状态
          }
          xhr.onerror = function(){
             reject('获取IP失败')     //初始化-拒绝状态-变为失败状态
          }
          xhr.send()
       })
          return promise
    }
    function getCityFromIp(ip){
       var promise = new Promise(function(resolve,reject){
          var xhr = new XMLHttpRequest()
          xhr.open('GET','https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getCityFromIp?ip='+ip, true)
          xhr.onload = function(){
             var retJson = JSON.parse(xhr.responseText)      // {"city": "hangzhou","ip": "23.45.12.34"}
             resolve(retJson.city)
          }
          xhr.onerror = function(){
             reject('获取city失败')
          }
          xhr.send()
    })
       return promise
    }
    function getWeatherFromCity(city){
       var promise = new Promise(function(resolve,reject){
          var xhr = new XMLHttpRequest()
          xhr.open('GET','https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getWeatherFromCity?city='+city, true)
          xhr.onload = function(){
             var retJson = JSON.parse(xhr.responseText)   // {"weather": "晴天","city": "beijing"}
             reslove(retJson)
          }
          xhr.onerror = function(){
             reject('获取天气失败')
          }
          xhr.send()
       })
       return promise
    }
    // getIp获取IP-IP获取城市-城市获取天气
    getIp().then(function(ip){
       return getCityFromIp(ip)    // 得到ip
    }).then(function(city){
        return getWeatherFromCity(city)    // 得到城市
    }).then(function(){
       console.log(data)    // 得到具体的城市其他状况(如天气、人口等等)
    }).catch(function(e){
       console.log('出现了错误',e)
    })