一、如何在created钩子函数中对DOM进行操作?
created里面怎么获取dom这里我用了两种方法一是 $nextTick 和 setTimeout ,一个是次 DOM 更新循环之后执行,一个是可以转换异步操作。
created()钩子函数中进行DOM操作时一般人会这样写:
<template>
<div class="hello">
<h1 id="nextTick" ref="hello">hello boy!</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: 'HelloWorld'
}
},
created(){
let that=this;
that.$refs.aa.innerHTML="hello girl!"; //写入到DOM元素
},
}
</script>
<style scoped>
h1 {
font-weight: normal;
}
</style>
但这样写会报错:
[Vue warn]: Error in created hook: "TypeError: Cannot set property 'innerHTML' of undefined"
原因:在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。
nextTick正确的写法应该是:
<template>
<div class="hello">
<h1 id="nextTick" ref="hello">hello boy!</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: 'HelloWorld'
}
},
created(){
let that=this;
that.$nextTick(function(){ //不使用this.$nextTick()方法会报错
that.$refs.hello.innerHTML="hello girl!"; //写入到DOM元素
});
},
}
</script>
<style scoped>
h1 {
font-weight: normal;
}
</style>
setTimeout正确的写法应该是:
created: function() {
setTimeout(function() {
var body = document.getElementById('ID');
console.log(body);
})
},
nextTick与setTimeout都是异步函数 不同的是nextTick比setTimeout优先执行
原因:
异步执行顺序先执行微任务队列再执行宏任务队列
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick
process.nextTick:process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。
javascript是单线程
setTimeout:因为要入微任务队列,即使是0最快也要4ms,要等待出队。
二、v-model的实现原理?
2.1什么是v-model?
v-model本质上只是一颗语法糖,可以用 v-model 指令在表单 <input>、<textarea> 及 <select>元素上创建双向数据绑定。
2.2 v-model实现原理
v-bind:绑定响应式数据
触发oninput 事件并传递数据
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text 和 textarea 元素使用 value 属性和 input 事件;
checkbox 和 radio 使用 checked 属性和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
2.3实现实例
三、介绍一下插槽?
slot就是插槽,主要的作用就是拓展组件,在重复使用一个组件的时候可以通过少量的修改就达到复用的效果。
分成默认插槽、具名插槽和作用域插槽。其中前两个都是元素在父组件中,拓展的结构也在父组件中,直接在子组件中占位,在父组件中添加结构即可,区别就是具名插槽给插槽取了名字,多个插槽存在时可以一一对应。而作用域插槽的数据在子组件中,扩展的结构要在父组件中,这时就要利用slot进行子===>父的通信,给数据一个新的作用域,因此叫作用域插槽。
vue3在父组件中使用具名插槽使用v-slot,而vue2使用slot
vue3必须把v-slot写在template标签中,而vue2中的slot可以写在任意标签中
四、Vue组件通信的方式
4.1 Props和$emit
父传子是props接收数据,然后双花括号引用到标签中,子传父是,父用@自定义事件名=“父method函数” 子:this.$emit(“自定义事件名”,传值)。
4.2 $parent/$children
子实例可以通过this.$parent访问父实例,父实例可以通过$children访问子实例。
4.3 provide/inject
父组件给子孙组件传值,一个组件修改状态所有组件都会影响,使用provide/inject编写组件。
4.4 ref/refs
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据
4.5 eventBus
eventBus兄弟之间的传参,创建一个Vue实例eventBus作为媒介。在相互通信的兄弟组件中都引入eventBus,让各兄弟组件共用一个事件机制。
通过eventBus.$emit(事件名,值)传递数据,eventBus.$on(事件名,函数体)接收数据
4.6 localStorage/sessionStorage
localStorage.setItem保存数据
localStorage.getItem获取数据
localStorage.removeItem删除数据
4.7 $attrs/$listeners
父组件给子孙组件传值,二次封装组件
4.8 Vuex 状态管理
State是数据源,view以声明方式将state映射到视图,actions:响应动作
State状态类似data
mutations状态更新,同步请求
actions 异步请求,要改state需要提交给mutations
getters 计算属性类似computed
modules 模块拆分
五、为什么data属性是一个函数而不是一个对象?
如果是对象,所有组件的data都会指向同一个内存地址,一个组件的data修改,其他的也会受影响。如果是函数的话,复用一次,就会返回新的data,保护各自的数据互不影响。
在我们定义好一个组件的时候,vue最终都会通过Vue.extend()构成组件实例
这里我们模仿组件构造函数,定义data属性,采用对象的形式
function Component(){
}
Component.prototype.data = {
count : 0
}
创建两个组件实例
const componentA = new Component()
const componentB = new Component()
修改componentA组件data属性的值,componentB中的值不受影响
console.log(componentB.data.count) // 0
componentA.data.count = 1
console.log(componentB.data.count) // 1
产生这样的原因是两者共用了同一个内存地址,componentA 修改的内容,同样对componentB 产生了影响
如果我们采用函数的形式,则不会出现这种情况(函数返回的对象内存地址并不相同)
function Component(){
this.data = this.data()
}
Component.prototype.data = function (){
return {
count : 0
}
}
修改componentA组件data属性的值,componentB中的值不受影响
console.log(componentB.data.count) // 0
componentA.data.count = 1
console.log(componentB.data.count) // 0
vue组件可能会有很多个实例,采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染
六、this指向的问题?
普通函数=>window
定时器=>window
立即执行函数=>window
setTimeout 中传入的函数=>window
setInterval 中传入的函数=>window
构造函数=>实例对象 原型里面的方法也指向实例对象
对象方法=>指向该对象
事件绑定方法=>绑定事件对象
箭头函数中的this指向他的父级
七、Vue的生命周期
四阶段、八状态
初始化beforeCreate,created、
挂载beforeMount,mounted、
更新beforeUpdate,updated、
销毁beforeDestroy,destroyed
7.1 beforeCreate
官网:在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。
详细:在这个阶段,数据是获取不到的,并且真实dom元素也是没有渲染出来的
7.2 created
官网:在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。
详细:在这个阶段,可以访问到数据了,但是页面当中真实dom节点还是没有渲染出来,在这个钩子函数里面,可以进行相关初始化事件的绑定、发送请求操作
7.3. beforeMount
官网:在挂载开始之前被调用:相关的 render 函数首次被调用。
详细:代表dom马上就要被渲染出来了,但是却还没有真正的渲染出来,这个钩子函数与created钩子函数用法基本一致,可以进行相关初始化事件的绑定、发送ajax操作
7.4 mounted
官网:实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。
注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick:
详细:挂载阶段的最后一个钩子函数,数据挂载完毕,真实dom元素也已经渲染完成了,这个钩子函数内部可以做一些实例化相关的操作
7.5 beforeUpdate
官网:在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。
详细:这个钩子函数初始化的不会执行,当组件挂载完毕的时候,并且当数据改变的时候,才会立马执行,这个钩子函数获取dom的内容是更新之前的内容
7.6 updated
官网:在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
详细:这个钩子函数获取dom的内容是更新之后的内容生成新的虚拟dom,新的虚拟dom与之前的虚拟dom进行比对,差异之后,就会进行真实dom渲染。在updated钩子函数里面就可以获取到因diff算法比较差异得出来的真实dom渲染了。
7.7 beforeDestroy
官网:实例销毁之前调用。在这一步,实例仍然完全可用。
详细:当组件销毁的时候,就会触发这个钩子函数代表销毁之前,可以做一些善后操作,可以清除一些初始化事件、定时器相关的东西。
7.8 destroyed
官网:实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
详细:Vue实例失去活性,完全丧失功能
<template>
<div id="app">
<p id="box">{{msg}}</p>
<button @click="change">更新</button>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'hello'
}
},
methods: {
change () {
this.msg = 'hello world'
}
},
beforeCreate () {
console.log('---------------->beforeCreate')
console.log(this.msg, document.getElementById('box'))
},
created () {
console.log('---------------->created')
console.log(this.msg, document.getElementById('box'))
},
beforeMount () {
console.log('---------------->beforeMount')
console.log(this.msg, document.getElementById('box'))
},
mounted () {
console.log('---------------->mounted')
console.log(this.msg, document.getElementById('box'))
},
beforeUpdate () {
console.log('---------------->beforeUpdate')
console.log(this.$el.innerHTML)
console.log(this.msg, document.getElementById('box'))
},
updated () {
console.log('---------------->updated')
console.log(this.$el.innerHTML)
console.log(this.msg, document.getElementById('box'))
}
}
</script>
当页面初始化挂载完成之后,
当数据改变之后又会触发beforeUpdate,updated两个钩子函数
八、Vue中的两大核心是啥?
8.1 数据驱动视图
v-model双相绑定的原理,通过Object.defineProperty()来拦截各个属性的get和 set,在数据变动时发布消息给订阅者,触发相应的监听回调。
通过v-bind绑定data数据,通过v-on监听表单的input事件,通过$event.target.value获取输入的内容,然后赋值给data数据,从而模拟实现v-model
8.2 组件化系统
就是把页面拆分成多个组件,每个组件依赖的 CSS、JS、模板、图片等资源放在一起开发和维护。 因为组件是资源独立的,所以组件在系统内部可复用,组件和组件之间可以嵌套,如果项目比较复杂,可以极大简化代码量,并且对后期的需求变更和维护也更加友好。
九、computed和watch的区别?
Computed是计算属性,同步操作,有缓存、computed中的函数必须用return返回
watch是监听值的变化,然后执行回调,异步操作,没缓存
使用场景:computed=>当一个属性受多个属性影响的时候,使用computed---购物车商品结算。watch=>当一条数据影响多条数据的时候,使用watch---搜索框
9.1 示例
我们要实现 第三个表单的值 是第一个和第二个的拼接,并且在前俩表单数值变化时,第三个表单数值也在变化
html:
<div id="myDiv">
<input type="text" v-model="firstName">
<input type="text" v-model="lastName">
<input type="text" v-model="fullName">
</div>
js: 用watch方法来实现
new Vue({
el: '#myDiv',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
js: 利用computed 来写
new Vue({
el:"#myDiv",
data:{
firstName:"Den",
lastName:"wang",
},
computed:{
fullName:function(){
return this.firstName + " " +this.lastName;
}
}
})
很容易看出 computed 在实现上边的效果时,是更简单的。
9.2 详解 comouted 计算属性
在vue的 模板内({{}})是可以写一些简单的js表达式的 ,很便利。但是如果在页面中使用大量或是复杂的表达式去处理数据,对页面的维护会有很大的影响。这个时候就需要用到computed 计算属性来处理复杂的逻辑运算。
1.优点:
在数据未发生变化时,优先读取缓存。computed 计算属性只有在相关的数据发生变化时才会改变要计算的属性,当相关数据没有变化时,它会读取缓存。而不必想 motheds方法 和 watch 方法是否每次都去执行函数。
2.setter 和 getter方法:(注意在vue中书写时用set 和 get)
setter 方法在设置值时触发。
getter 方法在获取值时触发。
computed:{
fullName:{
//这里用了es6书写方法
set(){
alert("set");
},
get(){
alert("get");
return this.firstName + " " +this.lastName;
},
}
}
9.3 watch 方法
虽然计算属性在大多数情况下是非常适合的,但是在有些情况下我们需要自定义一个watcher,在数据变化时来执行异步操作,这时watch是非常有用的。
十、改变this指向的方法有几种?
call 立即调用 任意参数 用途:继承
apply 立即调用 参数数组形式 用途:数学公式求最大最小
bind 不立即调用 任意参数 用途:想改变this指向,但是不想立即执行
10.1 call方法
1、call()方法可以进行普通函数的调用
2、call()方法可以改变this的指向,如果没有参数,this指向window
3、call()方法可以改变this的指向,如果有一个参数,this指向该参数
4、call()方法可以改变this的指向,如果有多个参数,this指向第一个参数,剩下的是个参数列表
实例:
<script>
function add(a,b){
alert(a+b);
}
function sub(a,b){
alert(a-b);
}
document.getElementById("id1").onclick = function(){
add.call(sub,3,1);
}
</script>
10.2 apply方法
1、 apply()方法可以进行普通函数的调用
2、apply()方法可以改变this的指向,如果没有参数,this指向window
3、apply()方法可以改变this的指向,如果有一个参数,this指向该参数
4、apply()方法可以改变this的指向,如果有多个参数,第一个参数是null或者window,第二个参数是数组
实例:
var arr = [11, 55, 33, 66, 88];
console.log(Math.max(11, 22, 33));//33
console.log(Math.max(arr));//NaN
console.log(Math.max.apply(window, arr));//88
console.log(Math.max.apply(null, [11, 55, 33, 66, 88]));//88
10.3 bind方法
推荐使用第二种形式,第三种用的相对较少,但也是必须掌握的内容。
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
1、bind()不能进行函数的调用
2、可以改变this指向
实例:
var name = "小明";
var obj3 = {
name: "小刚",
getName: function () {
var name = "小熊";
console.log(this.name);//小刚
console.log(name);//
that = this;
/* that = this
var fn2 = function () {
console.log(this.name);//小明 //小刚
}.bind(that); */
var fn2 = function () {
var name = "小李";
console.log(this.name);//小明
console.log(name);//小李
}.bind(that);
fn2();
}
}
obj3.getName();