1、将某个对象赋值给vue实例中的data属性时是浅拷贝
var obj= { a: 1 } var vm = new Vue({ data: obj }) obj.a = 2; console.log(vm.a) //2
2、vue上的字段:computed、method、watch
2.1、计算属性(computed)
计算属性作用一般是用来返回 data 属性里数据通过一系列计算过后返回的新数据,将会自动根据data属性里的数据发生改变而改变。computed里面的属性值重新进行计算的时机是方法体里面的任一响应式数据发生改变,无论该数据是否影响到该属性值。
var vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { reversedMessage: function () { return this.message.split('').reverse().join('') } } })
计算属性有 getter 和 getter,但是默认只有 getter 。
当获取计算属性时,getter 被触发,返回计算值。
2.1.1、计算属性的 getter
计算属性有 getter 和 getter,但是默认只有 getter 。如果该计算属性也声明了 setter,当给计算属性赋值时(比如上面的 vm.reversedMessage = 'aaa'),计算属性值并不会被赋值为等号后面的值,而是执行里面的函数体。同时有getter和setter时,getter 的触发同默认情况(即没有setter时)一致。
要想修改计算属性的值,可以通过在 setter 方法里面响应式修改依赖的值(computed一般依赖于data里面的数据),由此触发计算属性的 getter ,计算属性的值得以更新。
注意,必须是响应式修改依赖的值。当修改数组某个索引的值时需要用 Vue.$set() 方法,当往对象里面添加属性时需要用 Vue.$set() 或者 Object.assign() 方法,删除时用 Vue.$delete() 方法。(请注意,使用Object.assign添加属性时,必须是往一个新对象里面添加属性,然后再将返回的新对象赋值给需要修改的对象)
data () { return { firstName: '张' } } computed: { name: { get () { return this.firstName + '三' }, set (newVal) { this.firstName = '李' //此处修改firstName,计算属性name的getter将会被触发,导致重新计算,name得以更新 } } }
不建议将 v-model 的值绑定到计算属性上。如果这样做了,你会发现输入框中的值改变但是计算属性的值并没有发生变化,而且此时你还要给计算属性添加 setter 属性才能编译成功,所以非常不建议将 v-model 绑定到计算属性上。
2.2、方法字段(methods)
方法字段里一般是定义一些dom可以调用的方法。
虽然方法也可以返回数据而达到跟计算属性一样的作用,不同的是计算属性基于它们的依赖而进行缓存,只在相关依赖发生改变时它们才会重新求值。这就意味着只要依赖的数据还没有发生改变,多次访问 reversedMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数。而每当触发重新渲染时,调用方法将总会再次执行函数。
所以如果是为了返回跟data数据里依赖的数据的话最好还是用计算属性返回。
//index.html <p>Reversed message: "{{ reversedMessage() }}"</p> //index.js var vm = new Vue({ el: '#example', data: { message: 'Hello' }, methods: { show: function () { console.log('showtime') }, reversedMessage: function () { return this.message.split('').reverse().join('') } } })
2.3、侦听属性(watch)
侦听属性可以监听data属性里的数据变化,当数据改变时调用函数
var vm = new Vue({ el: '#demo', data: { firstName: 'Foo', lastName: 'Bar', fullName: 'Foo Bar' }, watch: { firstName: function (val) { console.log('firstname数据发生改变了') }, lastName: function (val) { this.fullName = this.firstName + ' ' + val } } })
vue 中也可以监听 data 里的数据的属性的变化:
var vm = new Vue({ el: '#demo', data: { obj: { name: '张三' } }, watch: { //监听数据的属性要用引号 'obj.name'() { console.log(111) } } })
2.3.1、深度监听(deep)
在 vue 中监听数据,如果数据是一个对象,当对象的属性发生改变时,监听函数并不会被触发
<button @click="change">修改信息</button> var vm = new Vue({ el: '#demo', data: { obj: { name: '张三' } }, methods: { change() { this.obj.name = '李四' } } watch: { obj() { console.log(111) } } })
上面代码中改变 obj.name 后,监听函数并不会被触发。
此时就可以使用深度监听来解决这个问题,当然通过直接监听数据的属性也是可以的。
watch: { obj: { deep: true, handler() { console.log('深度监听修改'); } } }
此时修改 obj.name 监听函数就可以被触发。数组不需要使用 deep 属性也能监听到变化。
2.3.2、绑定时立即执行(immediate)
监听还有一个属性是 immediate。
在默认情况下,绑定时是不执行函数的,但是有时候可能也需要再绑定时就执行函数。比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将 immediate 设为 true。
watch: { firstName: { handler(newName, oldName) { console.log(newName) }, immediate: true } }
3、vue中模板语法
在vue中给绑定的特性赋值,应该写在双引号里面,双引号里面的是JavaScript表达式
//index.html <div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }" ></div> //index.js data: { isActive: true, hasError: false }
4、vue中管理可复用元素
给每个元素定义一个唯一的 KEY ,这样 vue 将会把该元素视为独立唯一的,将不会简单地复用其他元素来渲染它。
参考:https://cn.vuejs.org/v2/guide/conditional.html 用key管理复用元素
5、Vue中的响应式更改数据
5.1、更改数组
以下方法更改数组可以触发视图更新:
不更改原始数组,返回新数组:filter()、concat()、slice(),可以用新数组取代原数组
直接更改数组某个索引的值并不会触发视图更新:
var vm = new Vue({ data: { items: ['a', 'b', 'c'] } }) vm.items[1] = 'x' // 不是响应性的 vm.items.length = 2 // 不是响应性的
由于 Vue 只是监听了vue实例中的属性的根属性,比如上面 data 中的 items 数组,而并没有监听根属性内的数据的改变,所以对 items 进行修改会触发 items 属性的 set 方法,而对 items 属性中的某个值进行修改则不会触发 set 方法,因此也不会触发视图更新。
// JS代码模拟Vue中对数据的监听 let person = { get name() { return ['张三','李四'] }, set name(val) { console.log(111); } } person.name = [11,22] //会触发set方法 person.name[1] = 11 //不会触发set方法 person.name.splice(1,1) //不会触发set方法
Vue 中使用变异方法来修改根属性的值时可以触发视图更新,比如 pop、push等方法,值得注意的是Vue在底层中已经重写了这些方法
5.2、更改对象
由于 JavaScript 的限制,Vue 不能检测 data 属性里根级别的属性的添加或删除。比如:
var vm = new Vue({ data: { a: 1 } }) // `vm.a` 现在是响应式的 vm.b = 2 // `vm.b` 不是响应式的
5.2.1、响应式添加对象属性
对于已经创建的实例,Vue 不能动态添加 data 属性里的根级别的响应式属性。但是,可以使用 Vue.set(object, key, value) 方法向根级别里的属性的对象属性添加响应式属性。 比如:
var vm = new Vue({ data: { userProfile: { name: 'Anika' } } }) Vue.set(vm.userProfile, 'age', 27) //可以添加一个新的 age 属性到嵌套的 userProfile 对象: vm.$set(vm.userProfile, 'age', 27) //还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:
一次性往嵌套对象添加多个属性可以使用 Object.assign() 方法。注意,使用 Object.assign() 方法时必须新生成一个对象,然后往该对象里添加属性,再将该对象赋值给需要修改的对象。不能使用下面的第二、三种方法,修改对象属性时可以使用这两种方法。
// 应该使用的方法 vm.userProfile = Object.assign({}, vm.userProfile, { age: 27, favoriteColor: 'Vue Green' }) // 错误的使用方法,下面并不会响应式修改或者添加userProfile的属性 Object.assign(vm.userProfile, { age: 27, favoriteColor: 'Vue Green' }) //这样也不行 vm.userProfile = Object.assign(vm.userProfile, { age: 27, favoriteColor: 'Vue Green' })
5.2.2、响应式删除对象属性
响应式删除对象属性可以使用 Vue.delete() 方法。
6、Vue中的事件及修饰符
使用 v-on 为DOM元素绑定的事件相当于使用 onclick 方式,绑定的事件是在冒泡阶段触发的。
6.1、事件修饰符(stop、prevent、capture、self、once、passive)
6.2、修饰符串联使用
//在没有默认行为的元素里看不出区别 <div @click.prevent.self="show('aaa')">点击</div> //点击后将输出aaa //在有默认行为的元素里就能看出区别 //点击百度,先是判断是否自身行为,结果不是,所以不会触发click事件,也不会执行后面的阻止默认行为,所以 a 标签会跳转
//输出:3、1、然后跳转 <div @click="show(1)"> <a href="http://www.baidu.com" @click.self.prevent="show(2)"> <div @click="show(3)">百度</div> </a> </div>
//点击百度,先阻止了默认行为,所以 a 标签不会跳转。然后判断是否自身行为,结果不是,所以不会输出 2
//输出:3、1,不跳转 <div @click="show(1)"> <a href="http://www.baidu.com" @click.prevent.self="show(2)"> <div @click="show(3)">百度</div> </a> </div> show: function(str){ console.log(str); }
6.3、sync修饰符
官方文档:https://cn.vuejs.org/v2/guide/components-custom-events.html#.sync修饰符
sync 只是一个语法糖:
//父组件代码 <testComponent :title.sync="title"></testComponent> //等价于 <testComponent :title="title" @update:title="updateTitle"></testComponent> //子组件代码 <template> <div> {{dateMsg}} <button @click="updateTitle">修改title</button> </div> </template> props: { title: { type: Date } }, data() { return { dateMsg: this.title } }, methods: { updateTitle() { let newVal = new Date(); this.dateMsg = newVal; this.$emit('update:title',newVal); //当数据更新时传递给父组件,实现数据同步 } },
sync 修饰符的功能只是为了在父组件给子组件传 props 值时,可以实现父子组件的数据同步更新。
sync 修饰符只是一个语法糖,它的扩展有点像 v-model 在组件上使用时的情况。
7、Vue生命周期与Vue.nextTick()
7.1、vue的生命周期
Vue 生命周期有八个:beforeCreate、created、beforeMount、mounted、beforeUpdate、update、beforeDestroy、destroyed
created:在这个时期,实例已经创建完成。在生命周期中,要想访问 methods 或者 data 中的方法和数据,最早需要在 created 钩子中操作。
beforeMount:此时只是完成了模板的编译,但是编译完成的 HTML 还没挂载到页面中,此时页面并没有内容
mounted:此时编译好的 HTML 已经挂载到了页面中,要想操作页面中的 DOM 节点,最早得在 mounted 钩子中操作
参考:https://blog.csdn.net/mqingo/article/details/86031260
7.2、Vue.nextTick() 方法
vue 更新 DOM 时是异步执行的。(在更新 DOM 时,vue 会尝试使用原生的 Promise.then、MutationObserver 和 setInmmediate,如果执行环境不支持这些方法,则会采用 setTimeout 方法代替)
Vue.nextTick() 方法会在 Vue 更新完 DOM 之后触发。
注意:在组件中应该使用 this.$nextTick() 来代替,并且 this.$nextTick() 里面的回调函数的 this 自动绑定到该组件实例上。
使用 Vue.nextTick() 方法的场景:
1、在数据更新之后
vm.someData = 'new value'
,DOM
并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的 DOM 更新。如果你不用这个方法而是直接想要根据更新的 DOM
状态去做某些事情,就会出现问题,因此直接操作时 DOM 还未更新。为了在数据变化之后等待 Vue
完成更新 DOM
,可以在数据变化之后立即使用 Vue.nextTick(callback)
。这样回调函数在 DOM
更新完成后就会调用。
<div id="example">{{message}}</div> let vm = new Vue({ el: "#example", data: { message: '123' } }) vm.message = 'new message' console.log(vm.$el.textContent) //输出 'message' 直接获取时,此时DOM还未更新 Vue.nextTick(function (){ console.log(vm.$el.textContent) //输出 'new message' 使用该方法,会在 DOM 更新后才触发,获取到的是 DOM 更新后的数据 })
2、在 created() 生命周期进行 DOM 操作
Vue
生命周期的created()钩子函数进行的DOM
操作一定要放在Vue.nextTick()
的回调函数中。原因是什么呢,原因是在created()
钩子函数执行的时候DOM
其实并未进行任何渲染,而此时进行DOM
操作无异于徒劳,所以此处一定要将DOM
操作的js
代码放进Vue.nextTick()
的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM
挂载和渲染都已完成,此时在该钩子函数中进行任何DOM
操作都不会有问题 。参考:https://www.jianshu.com/p/46c9d777cab1
7.3、Vue.nextTick 和 setTimeout 的执行顺序
Vue 中 nextTick 比 setTimeout 先执行
可以参考:https://segmentfault.com/q/1010000018001188
8、v-cloak避免页面闪烁
某些情况下浏览器可能会在系统未解析完数据时就已经显示页面,此时就会显示出一些Vue的标签,比如可能会显示出双大括号 {{}},此时可以使用 v-cloak 来避免这种情况。
将 v-cloak 添加至元素中,通过 css 控制 v-cloak 属性的元素为隐藏,因为当 Vue 解析完后一些指令包括 v-cloak 不会出现在元素内,所以当 Vue 解析完后 css 将不会起作用,该元素也将显示出来,这样就避免了先显示然后再闪烁的情况。
参考:https://www.jianshu.com/p/f56cde007210?utm_source=oschina-app
9、父子组件通过 v-model 通信
官方文档:https://cn.vuejs.org/v2/guide/components.html#在组件上使用-v-model
即 v-model 只是一个语法糖,v-model 真正的靠的还是 v-bind 绑定数据,触发 input 传递数据,这不管是在组件还是表单元素中都是一样的。
组件之间通过 v-model 进行通信的代码示例:
//父组件代码 import TodoItem from './components/TodoItem.vue'
data(){ return { modelMsg: 'v-model信息' } } <todo-item v-model="modelMsg"></todo-item> //上面的代码相当于: <todo-item v-bind:value="modelMsg" v-on:input="modelMsg = arguments[0]"></todo-item> //子组件代码 //不一定要绑定在表单元素上,传过来的value就是一个普通的 props 属性值,可以任意使用 <input type="text" v-model="modelMsg2" @input="handleInput($event)"> data () { return { //将value赋值给modelMsg,否则修改value时会报错 modelMsg2: this.value } } props: { //接受value属性 value: { type: String } }
methods: { //当 modelMsg 发生改变时,将值传给父组件 handleInput(e) { this.$emit('input',e.target.value) } }
由上面代码可以得知,需要手动设置一个函数,当子组件的props属性 value 值发生改变时,通过 $emit 传递给父组件,父组件中的 v-model 已经自动监听了 input 事件,并且会自动同步父组件的数据,由此父子组件之间的数据双向绑定得以实现。
参考:https://www.cnblogs.com/rachelch/p/8944367.html
10、vue 中的函数参数
10.1、原生事件的传递
10.1.1、没有括号时默认会传原生事件
vue 中调用事件,如果不传参,默认会将原生 DOM 事件作为参数传过去
<button @click="a">事件尝试</button> methods: { a (e) { console.log(e); //输出原生事件 MouseEvent {isTrusted: true, screenX: 427, screenY: 491, clientX: 388, clientY: 362, …} } }
10.1.2、加上括号默认不会传原生事件
如果加上了括号以后,不管你是否在后面添加参数,都不会自动将原生事件传过去
<button @click="a()">事件尝试</button> methods: { a (e) { console.log(e); //输出 undefined } } <button @click="a('aaa')">事件尝试</button> methods: { a (e) { console.log(e); //输出 aaa } }
10.1.3、如何手动传原生事件
有时候可能我们也需要访问原始的 DOM 事件,这时可以用特殊变量 $event 把它传入方法。($event 参数不一定要在最后传递)
<button @click="a('aaa','bbb',$event)">事件尝试</button> methods: { a (a,b,event) { console.log(a,b,event); //输出 aaa bbb MouseEvent {isTrusted: true, screenX: 425, screenY: 507, clientX: 386, clientY: 376, …} } }
10.2、父组件如何接收子组件的参数
10.2.1、父组件只接收子组件的参数,不传递额外的参数
如果在父组件中只接收子组件的参数,并不传递额外的参数,此时父组件的接收函数可以不写参数,组件默认会将子组件传过来的参数作为参数传递。
/*** 子组件只传递一个参数的情况: ***/ //子组件 触发父组件的事件 this.$emit('childBtnClick','hahahaha') //父组件 <child @childBtnClick="childClick"></child> childClick (msg) { console.log(msg); //输出 'hahahaha' } /*** 子组件传递多个参数的情况: ***/ //子组件 触发父组件的事件 this.$emit('childBtnClick', 'hahahaha', '2222') //父组件 <child @childBtnClick="childClick"></child> childClick (msg, msg2) { console.log(msg, msg2); //输出 'hahahaha' '2222' }
10.2.2、父组件接收子组件参数并且传递额外的参数
当父组件接收子组件参数,并且自身也往函数中传递额外的参数时,可以在函数中使用一些关键字来接收子组件传递的参数。
当子组件只传递一个参数时,可以使用 $event 来接收子组件传递的参数。当子组件传递多个参数时,可以使用 arguments 来接收多个参数,该关键字接收到的是一个数组,子组件传递的参数都在里面。
/*** 子组件只传递一个参数的情况: ***/ //子组件 触发父组件的事件 this.$emit('childBtnClick', 'hahahaha') //父组件 <child @childBtnClick="childClick($event, 'aaa')"></child> childClick (e, msg) { console.log(e, msg); //输出 'hahahaha' 'aaa' } /*** 子组件传递多个参数的情况: ***/ //子组件 触发父组件的事件 this.$emit('childBtnClick', 'hahahaha', 'haha2222') //父组件 <child @childBtnClick="childClick(arguments, 'aaa')"></child> childClick (arr, msg) { console.log(arr, arr[0], arr[1], msg); //输出 Arguments(2)["hahahaha", "haha222", callee: (...), Symbol(Symbol.iterator): ƒ] "hahahaha" "haha222" "aaa" }
可参考:https://www.cnblogs.com/lalalagq/p/9901139.html、https://github.com/vuejs/vue/issues/5735