前端开发vue.js常用知识点整理

144 阅读9分钟

前端学习之前端开发vue.js常用知识点整理

前端学习之前端开发vue.js常用知识点整理

vue常用知识点细节整理,让你彻底会用vue.js。本文不是vue.js的基础入门文,本文是把大家平常开发中常用的vue.js知识点进行系统的整理,需要各位看客大概了解vue.js 。

不理解的点也可留言,有空一定为大家解答

最近自己也去面试了几家公司,写了两篇前端面经,希望对大家找工作有帮助

人生在勤,不索何获

$set

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

  1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

举个例子:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:

vm.$set(vm.items, indexOfItem, newValue)

还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` 现在是响应式的
​
vm.b = 2
// `vm.b` 不是响应式的

对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。例如,对于:

var vm = new Vue({
  data: {
    userProfile: {
      name: 'Anika'
    }
  }
})

你可以添加一个新的 age 属性到嵌套的 userProfile 对象:

Vue.set(vm.userProfile, 'age', 27)

你还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:

vm.$set(vm.userProfile, 'age', 27)

有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:

Object.assign(vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

你应该这样做:

vm.userProfile = Object.assign({}, vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

$nextTick

在dom渲染完成之后执行

  • 在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中

created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。

  • 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。

具体原因在Vue的官方文档中详细解释:

Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0)代替。
例如,当你设置vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

this.$nextTick(() => {
                    this.contentWidth = this.$refs.content.clientWidth;
                });

\

$forceUpdate()

this.$forceUpdate(); //强制刷新,解决页面不会重新渲染的问题

有时我们明明改变了数据,但是页面就是没有重新渲染,可以用此方法进行强制刷新。至于原因,会在以后的vue原理解析文章中为大家讲解,敬请期待。。。

生命周期

Vue 实例有一个完整的生命周期,生命周期也就是指一个实例从开始创建到销毁的这个过程。

  • beforeCreated():在实例创建之间执行,数据未加载状态。
  • created():在实例创建、数据加载后,能初始化数据,DOM 渲染之前执行。
  • beforeMount():虚拟 DOM 已创建完成,在数据渲染前最后一次更改数据。
  • mounted():页面、数据渲染完成,真实 DOM 挂载完成。
  • beforeUpadate():重新渲染之前触发。
  • updated():数据已经更改完成,DOM 也重新 render 完成,更改数据会陷入死循环。
  • beforeDestory() 和 destoryed():前者是销毁前执行(实例仍然完全可用),后者则是销毁后执行。

\

大家可以把这个列子自己敲一遍看看效果,那样记忆会更深刻一些。

<!DOCTYPE html>
<html>
<h
    <title></title>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
</head>
<body>
<div id="app">
     <p>{{ message }}</p>
</div>
<script type="text/javascript">
  var app = new Vue({
      el: '#app',
      data: {
          message : "xuxiao is boy" 
      },
       beforeCreate: function () {
                console.group('beforeCreate 创建前状态===============》');
               console.log("%c%s", "color:red" , "el     : " + this.$el); //undefined
               console.log("%c%s", "color:red","data   : " + this.$data); //undefined 
               console.log("%c%s", "color:red","message: " + this.message) //undefined
        },
        created: function () {
            console.group('created 创建完毕状态===============》');
            console.log("%c%s", "color:red","el     : " + this.$el); //undefined
               console.log("%c%s", "color:red","data   : " + this.$data);      //已被初始化 
               console.log("%c%s", "color:red","message: " + this.message);    //已被初始化
        },
        beforeMount: function () {
            console.group('beforeMount 挂载前状态===============》');
            console.log("%c%s", "color:red","el     : " + (this.$el));        //已被初始化
         console.log(this.$el);   //<div id="app"><p>{{ message }}</p></div>,数据还未渲染到html页面
               console.log("%c%s", "color:red","data   : " + this.$data);     //已被初始化  
               console.log("%c%s", "color:red","message: " + this.message);    //已被初始化  
        },
        mounted: function () {
            console.group('mounted 挂载结束状态===============》');
            console.log("%c%s", "color:red","el     : " + this.$el);           //已被初始化
         console.log(this.$el);  //<div id="app"><p>xuxiao is boy</p></div>,数据已渲染到html页面 
               console.log("%c%s", "color:red","data   : " + this.$data);      //已被初始化
               console.log("%c%s", "color:red","message: " + this.message);    //已被初始化 
        },
        beforeUpdate: function () {
            console.group('beforeUpdate 更新前状态===============》');
            console.log("%c%s", "color:red","el     : " + this.$el);
            console.log(this.$el);   
               console.log("%c%s", "color:red","data   : " + this.$data); 
               console.log("%c%s", "color:red","message: " + this.message); 
        },
        updated: function () {
            console.group('updated 更新完成状态===============》');
            console.log("%c%s", "color:red","el     : " + this.$el);
            console.log(this.$el); 
               console.log("%c%s", "color:red","data   : " + this.$data); 
               console.log("%c%s", "color:red","message: " + this.message); 
        },
        beforeDestroy: function () {
            console.group('beforeDestroy 销毁前状态===============》');
            console.log("%c%s", "color:red","el     : " + this.$el);
            console.log(this.$el);    
               console.log("%c%s", "color:red","data   : " + this.$data); 
               console.log("%c%s", "color:red","message: " + this.message); 
        },
        destroyed: function () {
            console.group('destroyed 销毁完成状态===============》');
            console.log("%c%s", "color:red","el     : " + this.$el);
            console.log(this.$el);  
               console.log("%c%s", "color:red","data   : " + this.$data); 
               console.log("%c%s", "color:red","message: " + this.message)
        }
    })
</script>
</body>
</html>

指令

v-html   //html
v-text   //元素里要显示的内容
v-bind:data    //绑定动态数据   :data
v-on:click      //绑定事件       @click
v-for
v-model    //双向绑定,用于表单

v-if v-show区别

v-if 是“真正的”条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下, v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

一般来说, v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件不太可能改变,则使用 v-if 较好。

v-model

.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步:

<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >

.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="age" type="number">

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<input v-model.trim="msg">

v-on事件修饰符

<!-- 方法处理器 -->
<button v-on:click="doThis"></button><!-- 内联语句 -->
<button v-on:click="doThat('hello', $event)"></button><!-- 缩写 -->
<button @click="doThis"></button><!-- 停止冒泡 -->
<button @click.stop="doThis"></button>        //调用 event.stopPropagation()
​
<!-- 阻止默认行为 -->
<button @click.prevent="doThis"></button>      //调用 event.preventDefault()
​
<!-- 阻止默认行为,没有表达式 -->
<form @submit.prevent></form><!--  串联修饰符 -->
<button @click.stop.prevent="doThis"></button><!-- 键修饰符,键别名 -->
<input @keyup.enter="onEnter"><!-- 键修饰符,键代码 -->
<input @keyup.13="onEnter"><!-- 点击回调只会触发一次 -->
<button v-on:click.once="doThis"></button><!-- 对象语法 (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>

watch

键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。

 watch: {
    a: function (val, oldVal) {
      console.log('new: %s, old: %s', val, oldVal)
    },
    // 方法名
    b: 'someMethod',
    // 深度 watcher
    c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true, //深度观察
      immediate:true, //绑定的时候就会执行一次
    },
    // 该回调将会在侦听开始之后被立即调用
    d: {
      handler: function (val, oldVal) { /* ... */ },
      immediate: true
    },
    e: [
      function handle1 (val, oldVal) { /* ... */ },
      function handle2 (val, oldVal) { /* ... */ }
    ],
    // watch vm.e.f's value: {g: 5}
    'e.f': function (val, oldVal) { /* ... */ }
  }

computed

计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例。
计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的。

//aDouble、aPlus不需要在data中声明
​
computed: {
    // 仅读取
    aDouble: function () {
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }

计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter :

// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。

计算属性缓存vs方法

computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      return this.message.split('').reverse().join('')
    }
  }

你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:

<p>Reversed message: "{{ reversedMessage() }}"</p>



// 在组件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

相比之下,每当触发重新渲染时,调用方法总会再次执行函数。(性能较差)

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

组件

声明及使用

import Vue from 'vue'

const compoent = {
  props: {        //类型检查
    active: {
      // type: Boolean,   //类型
      // required: true,   //是否必传
      // default:false,    //默认值
      validator (value) {   //定义一个方法,进行更严格的检查
        return typeof value === 'boolean'
      }
    },
    propOne: String
  },
 
  //props:['active','propOne']
  template: `
    <div>
      <input type="text" v-model="text">
      <span @click="handleChange">{{propOne}}</span>
      <span v-show="active">see me if active</span>
    </div>
  `,
  data () {
    return {
      text: 0
    }
  },
  methods: {
    handleChange () {
      this.$emit('change')  //触发父组件的事件
    }
  }
}

//全局定义一个组件
// Vue.component('CompOne', compoent)
//任何组件内通过<comp-one></comp-one>使用全局组件,不用引入

new Vue({
  components: {    //组件内声明并使用一个组件
    CompOne: compoent
  },
  data: {
    prop1: 'text1'
  },
  methods: {
    handleChange () {
      this.prop1 += 1
    }
  },
  mounted () {
    //ref用在组件上返回的是属性和方法
    //ref用在标签上返回的是dom
    console.log(this.$refs.comp1)
  },
  el: '#root',
  template: `
    <div>
      <comp-one ref="comp1" :active="true" :prop-one="prop1" @change="handleChange"></comp-one>
      <comp-one :active="true" propOne="text2"></comp-one>
    </div>
  `
})

插槽slot

import Vue from 'vue'
//插槽
const ChildComponent = {
  template: '<div>child component: {{data.value}}</div>',
  //跨级传值,接受值
  inject: ['yeye', 'data'],
  mounted () {
    // console.log(this.yeye, this.value)
  }
}
​
const component = {
  name: 'comp',
  components: {
    ChildComponent
  },
  //具名插槽
  // template: `
  //   <div>
  //     <div class="header">
  //       <slot name="header"></slot>
  //     </div>
  //     <div class="body">
  //       <slot name="body"></slot>
  //     </div>
  //   </div>
  // `,
  //作用域插槽
  template: `
    <div>
      <slot :value="value" aaa="111"></slot>
      <child-component />
    </div>
  `,
  data () {
    return {
      value: 'component value'
    }
  }
}
​
new Vue({
  components: {
    CompOne: component
  },
  provide () {    //跨级传值,用于祖辈关系
    const data = {}
    Object.defineProperty(data, 'value', {
      get: () => this.value,
      enumerable: true
    })
    return {
      yeye: this,
      data
    }
  },
  el: '#root',
  data () {
    return {
      value: '123'
    }
  },
  mounted () {
    console.log(this.$refs.comp.value, this.$refs.span)
  },
  template: `
    <div>
      <comp-one ref="comp">
        <span slot-scope="props" ref="span">{{props.value}} {{props.aaa}} {{value}}</span>
      </comp-one>
      <input type="text" v-model="value" />
    </div>
  `
});
​
//没有任何关系的组件之间传值,可以new Vue实例,
// app.$on('test',()=>{  //事件监听})
// app.$emit('test')   //事件触发
//进行传值

extends继承

import Vue from 'vue'

const compoent = {
  props: {
    propOne: String
  },
  template: `
    <div>
      <input type="text" v-model="text">
      <span @click="handleChange">{{propOne}}</span>
    </div>
  `,
  data () {
    return {
      text: 0
    }
  },
  mounted () {
    console.log('comp mounted')
  },
  methods: {
    handleChange () {
      this.$emit('change')
    }
  }
}

const componet2 = {
  extends: compoent,   //第一种继承方式
  propsData: {
    propOne: 'xxx'    //传值给继承组件的props
  },
  data () {       //会和继承组件的data合并
    return {
      text: 1
    }
  },
  mounted () {    //会和继承组件的mounted方法合并
    console.log(this.$parent.$options.name)  //通过this.$parent调用父组件的值
  }
};

//第二种继承方式
// const CompVue = Vue.extend(compoent)

// new CompVue({
//   el: '#root',
//   propsData: {
//     propOne: 'xxx'
//   },
//   data: {
//     text: '123'
//   },
//   mounted () {
//     console.log('instance mounted')
//   }
// })

renderFunction

import Vue from 'vue'
//template 会调用render Function进行编译,使用creatElement去创建元素
const component = {
  props: ['props1'],
  name: 'comp',
  // template: `   
  //   <div :style="style">
  //     <slot></slot>
  //   </div>
  // `,
  //template等价于这个render
  render (createElement) {
    return createElement('div', {
      style: this.style
      // on: {
      //   click: () => { this.$emit('click') }
      // }
    }, [
      this.$slots.header,
      this.props1
    ])
  },
  data () {
    return {
      style: {
        width: '200px',
        height: '200px',
        border: '1px solid #aaa'
      },
      value: 'component value'
    }
  }
}

keep-alive

keep-alive是Vue.js的一个内置组件。它能够将不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。

当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行

<keep-alive>
    <loading></loading>
</keep-alive>

被keep-alive包裹的动态组件或router-view会缓存不活动的实例,再次被调用这些被缓存的实例会被再次复用,对于我们的某些不是需要实时更新的页面来说大大减少了性能上的消耗,不需要再次发送HTTP请求,但是同样也存在一个问题就是被keep-alive包裹的组件我们请求获取的数据不会再重新渲染页面,这也就出现了例如我们使用动态路由做匹配的话页面只会保持第一次请求数据的渲染结果,所以需要我们在特定的情况下强制刷新某些组件

mixins

mixins就是混入。

一个混入对象可以包含任意组件选项。同一个生命周期,混入对象会比组件的先执行。

1.创建一个test.js,用export暴露出mixins对象

export const mixinsTest = {
    methods:{
        hello(){
            console.log("hello");
        }
    },
    created(){
        this.hello()
    }
}

2.在组件中引入这个mixins对象,通过mixins:[xxx],使用mixins对象

<template>
<div>
    home
</div>
</template>
<script>
import {mixinsTest} from '../util/test.js'
export default {
  name: "Home",
  data () {
    return {
    };
  },
  created(){
      console.log("home");
  },
  //mixins的created会先被调用,然后再执行组件的created
  mixins:[mixinsTest]
}
</script>