最近刚好我也在学习vue,这些是我的一些笔记,记录下来,方便用的时候查看,欢迎大家批评指正!

1. 简介

Vue.js是JavaScript MVVM(Model-View-ViewModel)框架 Vue核心只关注视图层,相对AngularJS提供更加简洁、易于理解的API Vue尽可能通过简单的API实现响应的数据绑定和组合的视图组件。

MVVM模式即Model-View-ViewModel。

MVVM拆分解释为:

- Model:负责数据存储

- View:负责页面展示

- View Model:负责业务逻辑处理(比如Ajax请求等),对数据进行加工后交给视图展示

- MVVM要解决的问题是将业务逻辑代码与视图代码进行完全分离,使各自的职责更加清晰,后期代码维护更加简单

 Vue.js特点 

简洁:页面由HTML模板+Json数据+Vue实例组成

数据驱动:自动计算属性和追踪依赖的模板表达式

组件化:用可复用、解耦的组件来构造页面

轻量:代码量小,不依赖其他库

快速:精确有效批量DOM更新

模板友好:可通过npm,bower等多种方式安装,很容易融入

相关面试题:

对于MVVM的理解:

MVVM 是 Model-View-ViewModel 的缩写。

Model 代表数据类型,也可以在 Model 中定义数据修改和操作的业务逻辑。

View 代表 UI 组件,它负责将数据模型转化成 UI 展现出来。

ViewModel 监听模型数据的改变和控制视图的行为、处理用户交互,简单理解就是一个同步View 和 model 的对象,连接 Model 和 view。

在 MVVM 架构下,view 和 model 之间没有直接的关系,而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互是双向的,View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反映到 View 上。

ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步 工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需手动操作 DOM 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来管理。


初见vue

<!DOCTYPE html><html lang="en">
<head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>体验vue</title>    <script src="js/vue221.js"></script>
</head>
<body>    <div id="app">        {{name}}    <!-- view视图 -->    </div>    <script>    // view Model用于数据和视图之间的绑定        var vm=new Vue({            el:"#app",            data:{                name:"小明" //model数据            }        })    </script>
</body>
</html>复制代码

2. 基本的指令

vue 指令以 v- 开头,作用在 HTML 元素上,将指令绑定在元素上时,会给绑定的元素添加一些特殊行为,可将指令视作特殊的HTML属性(attribute)

(1)v-on 绑定事件

(1)作用:绑定事件监听,表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略,用在普通的 html 元素上时,只能监听 原生 DOM 事件。用在自定义元素组件上时,也可以监听子组件触发的自定义事件。

(2) 常用事件

v-on:click
v-on:keydown
v-on:keyup
v-on:mousedown
v-on:mouseover
v-on:submit复制代码

(3)v-on提供了很多事件修饰符来辅助实现一些功能,例如阻止冒泡等

事件修饰符有如下:
.stop - 调用 event.stopPropagation()。
.prevent - 调用 event.preventDefault()。
<!-- 内联语句 -->
<button v-on:click="doThat('hello', $event)"></button>
<!-- 缩写 -->
<button @click="doThis"></button>
<!-- 停止冒泡 -->
<button @click.stop="doThis"></button>
<!-- 阻止默认行为 -->
<button @click.prevent="doThis"></button>
<!-- 阻止默认行为,没有表达式 -->
<form @submit.prevent></form>
<!--  串联修饰符 -->
<button @click.stop.prevent="doThis"></button>复制代码

(2)v-text 和 v-html

v-text 可以将一个变量的值渲染到指定的元素中,双大括号和 v-text 会将数据解释为纯文本,而非 HTML 。为了输出真正的 HTML ,你需要使用 v-html 指令
<div id="app">        <p v-html="mes"></p>        <p v-text="mes"></p>        <span v-text="str"></span>        <span v-html="str"></span>
</div>
<script>        let vm=new Vue({            el:"#app",            data:{                mes:"我是宋顶飞",                str:"<h1>我是带标签的</h1>"            }        })
</script>复制代码

(3)v-bind

1、作用:可以给html元素或者组件动态地绑定一个或多个特性,例如动态绑定 style 和 class

2. 举例:

<style>        .classA{            color: red;        }        .classB{            font-size: 30px;        }
</style><div id="app">        <p v-bind:class="[{classA:t},{classB:t}]">    <!--如果写成一个数组,里边两个值都是model层注册数据中的属性,如果是对象,前边是类名,后边是Boolean值    -->    <!--  v-bind的简写是 :    -->            {{mes}}        </p>        <p :style="obj">我是通过数据被绑定的</p>        <button @click="add()">增大字号</button>        <p :style="{color:z,fontSize:size+'px'}">看看我吧</p>
</div>
<script>        let vm=new Vue({            el:"#app",            data:{                mes:"我是士大夫",                x:"classA",                y:"classB",                t:true,                obj:{                    color:"green",                    fontSize:"50px",                },                z:"orange",                size:20,            },            methods:{                add:function(){                    this.size++                }            }        })
</script>复制代码

(4)双向数据绑定(用来做提交注册)

在 Vue 中使用 v-model 在表单元素上实现双向绑定。
 <!--  v-model 通常用于表单元素,数据的双向绑定  -->
<div id="app">        <input type="text" v-model="mes">        <input type="checkbox" v-model="t">        <p>{{mes}}</p>        <span>{{t}}</span>
</div>
<script>        let vm=new Vue({            el:"#app",            data:{                mes:"宋顶飞",                t:true,            }        })
</script>复制代码

(5)v-if 和 v-show

1) v-if 作用:根据表达式的值的真假条件来决定是否渲染元素,如果条件为false不渲染(达到隐藏元素的目的),为true则渲染。在切换时元素及它的数据绑定被销毁并重建
2)  v-show 作用:根据表达式的真假值,切换元素的 display CSS 属性,如果为 false,则在元素上添加 display:none 来隐藏元素,否则移除 display:none 实现显示元素
<div id="app">        <p v-if="t">我是宋顶飞</p>        <p v-else>I am SongDingfei</p>        注意:v-else 元素必须紧跟在 v-if 元素否则它不能被识别。        <button @click="change()">切换</button>
</div>
<script>        let vm=new Vue({            el:"#app",            data:{                t:true,            },           methods:{                change:function(){                    this.t=!this.t;                }            }        })
</script>复制代码
3)  总结
v-if 和 v-show 都能够实现对一个元素的隐藏和显示操作,但是 v-if 是将这个元素添加或者移除

到 dom 中,而 v-show 是在这个元素上添加 style="display:none" 和移除它来控制元素的显

示和隐藏的。v-show在切换上,性能更好,v-show初次加载时性能更好

(6)v-for

1、作用:通常是根据数组中的元素遍历指定模板内容生成内容
2、用法举例:

<ul><li v-for="(item,index) in arr1" :key="index">{{item}}</li>
</ul>复制代码
<div id="app">        <ul>            <li v-for="(item,index) in arr2" :key="index">                {{item}}                <p v-for="(x,y) in arr2" v-if="item>=x">                    {{x}}*{{item}}={{x*item}}                </p>            </li>        </ul>
</div>
<script>        let vm=new Vue({            el:"#app",            data:{                arr1:["haha","xixi","huhu","wuwu","gaga"],                arr2:["1","2","3","4","5","6","7","8","9"]            }        })
</script>复制代码

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue 将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。这个类似 Vue 1.x 的 trackby="$index"

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有唯一 id。这个特殊的属性相当于 Vue 1.x 的 track-by ,但它的工作方式类似于一个属性,所以你需要用 v-bind 来绑定动态值(在这里使用简写):           

3. 自定义指令,computed,watch,template,extend,component

(1) 自定义指令

bind : 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个绑定时执行

一次的初始化动作。

inserted : 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。

update : 被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的

绑定值,可以忽略不必要的模板更新。

componentUpdated : 被绑定元素所在模板完成一次更新周期时调用。

unbind : 只调用一次,指令与元素解绑时调用。

bind:function(){//被绑定console.log('1 - bind');
},
inserted:function(){//绑定到节点console.log('2 - inserted');
},
update:function(){//组件更新console.log('3 - update');
},
componentUpdated:function(){//组件更新完成console.log('4 - componentUpdated');
},
unbind:function(){//解绑console.log('5 - unbind');
}解绑:写原生的js方法
app.$destroy() //vue实例名字
复制代码

(2)computed 计算选项

computed 的作用主要是对原数据进行改造输出。改造输出:包括格式的编辑,大小写转换,顺序重排,添加符号……。
 <div id="app">        <input type="text" v-model="myName">        <p>            {{myName}}        </p>
</div>
<script>        let vm=new Vue({            el:"#app",            data:{                firstName:"Song",                lastName:"Dingfei",            },            computed:{// 计算属性,通过原有的数据,产生新的数据                myName:{                    get:function(){                        return this.firstName +" "+this.lastName;                    },                    set:function(value){                        console.log(value)                        let arr=value.split(" ");                        this.firstName=arr[0];                        this.lastName=arr[1];                   }                }            }        })
</script>
复制代码

(3) watch 选项,监控数据

数据变化的监控经常使用,我们可以先来看一个简单的数据变化监控的例子。
例如天气预报的穿衣指数,它主要是根据温度来进行提示的,当然还有其它的,咱们就不考虑了。
26度以上 提示穿半袖
0-26度 提示穿T恤
0度一下 提示穿棉服

<div id="app">        <button @click="add()">+</button>        <button @click="reduce()">-</button>        {{x}}        {{y}}
</div>
<script>        let vm=new Vue({            el:"#app",            data:{                x:15,                y:"T恤",            },            methods:{                add:function(){                    this.x++                },                reduce:function(){                    this.x--                }            },           watch:{                x:function(){                    if(this.x>26){                        this.y="半袖";                    }else if(this.x>0&&this.x<26){                        this.y="T恤"                    }else{                        this.y="棉服"                    }                }            }        })
</script>
复制代码


相关的面试题:

computed 和 watched 的区别:

computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。

watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作( 比如请求数据 )。

所以一般来说需要依赖别的属性来动态获得值的时候可以使用 computed,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用 watch。

(4) template 模板 

三种定义方式:
1.构造器内部定义template:`<h1 style="color:red;">我是选项模板</h1>
`2.在标签中建立模板
<template id="dd2"><h1 style="color:red;">我是标签模板</h1>
</template>    template:"#dd2"    3. 在script中写<script type="x-template" id="add2"><h1 style="color:red;">我是选项模板</h1>
</script?         template:"#dd2"
复制代码

(5) extend

Vue.extend 返回的是一个“扩展实例构造器”,也就是预设了部分选项的Vue实例构造器。经常服务于Vue.component用来生成组件,可以简单理解为当在模板中遇到该组件名称作为标签的自定义元素时,会自动调用“扩展实例构造器”来生产组件实例,并挂载到自定义元素上。 

var authorExtend = Vue.extend({template:"<p><a :href='authorUrl'>{{authorName}}</a></p>",data:function(){return{authorName:zzn,authorUrl:'http://www.kgc.cn"}}
});这时html中的标签还是不起作用的,因为扩展实例构造器是需要挂载的,我们再进行一次挂载。new authorExtend().$mount('author'); //直接绑定标签 也可以绑定选择器复制代码

(6) component

组件:是vue中最强大的功能之一,可以拓展元素 也可以封装重用的代码

1). 全局化注册组件

全局化就是在构造器的外部用Vue.component来注册,我们注册现在就注册一个<author></author>的组件来体验一下。

Vue.component(author,{template:`<div style="color:red;">author</div>`} Vue.component('author',{template:"#app",data:function(){return {}}
})
复制代码

2). 局部注册组件局部注册组件和全局注册组件是向对应的,局部注册的组件只能在组件注册的作用域里进行使用,其他作用域使用无效。

var app=new Vue({el:'#app',components:{"panda":{template:`<div style="color:red;">局部注册的panda标签</div>`}}})复制代码

父组件传值子组件,

Vue 组件间的参数传递,

父组件传值给子组件子组件通过 props 方法接受数据;

子组件传值给父组件:$emit参数方法传递参数

<body>    <div id="app">        <input type="text" placeholder="请输入内容" v-model="mes">        <button @click="publish">发表</button>
<!-- <ul>            <li v-for="(item,index) in arr" :key="index">                {{index+1}}                {{item}}            </li>
</ul> -->        <!-- 把外层数据,传到组件中去,父子组件传值   ,  :content,通过绑定传值 -->
<hd v-for="(item,index) in arr" :key="index" :content="item" :x="index" @handle="remove()"></hd><!--  子组件传值给父组件  -->
</div>
<script>        Vue.component("hd",{            props:["content","x"],// 规定父组件可以传过来的值            template:"<div>{{x+1}}{{content}}<button @click='del()'>删除</button></div>",            data:function(){               return {   }            },            methods:{                del:function(){//删除子集对应的数据                    this.$emit("handle",this.x); //hangdle就是一个事件名称                }            }        })        let vm=new Vue({            el:"#app",            data:{                arr:["滴水能把石穿透,万事功到自然成","每天进步一点点"],                mes:""            },            methods:{                publish:function(){                    // console.log(this)                    this.arr.push(this.mes);                    this.mes="";                },                remove:function(index){                    this.arr.splice(index,1)                }            }        })
</script>
</body>复制代码

组件化的购物车,代码太长,点击查看源码

动态切换

 <div id="app"><component :is="name"></component ></div><script>//view Model  用于数据和视图之间的绑定var vm=new Vue({el:"#app",data:{name:"panda1"},methods:{},components:{"panda":{template:`<div style="color:red;">局部注册的panda标签</div>`},"panda1":{template:`<div style="color:red;">局部注册的1panda标签</div>`}}})
复制代码


相关面试题:

Vue 组件间的参数传递

(1)父组件与子组件传值

(2)父组件传给子组件:子组件通过 props 方法接受数据;

(3)子组件传给父组件:$emit 方法传递参数

Vue 常考基础知识点

这一章节我们将来学习 Vue 的一些经常考到的基础知识点。

生命周期钩子函数

在 beforeCreate 钩子函数调用的时候,是获取不到 props 或者 data 中的数据的,因为这些数据的初始化都在 initState 中。

然后会执行 created 钩子函数,在这一步的时候已经可以访问到之前不能访问到的数据,但是这时候组件还没被挂载,所以是看不到的。

接下来会先执行 beforeMount 钩子函数,开始创建 VDOM,最后执行 mounted 钩子,并将 VDOM 渲染为真实 DOM 并且渲染数据。组件中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。

接下来是数据更新时会调用的钩子函数 beforeUpdate 和 updated,这两个钩子函数没什么好说的,就是分别在数据更新前和更新后会调用。

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

最后就是销毁组件的钩子函数 beforeDestroy 和 destroyed。前者适合移除事件、定时器等等,否则可能会引起内存泄露的问题。然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed 钩子函数。

组件通信

组件通信一般分为以下几种情况:

  • 父子组件通信
  • 兄弟组件通信
  • 跨多层级组件通信
  • 任意组件

对于以上每种情况都有多种方式去实现,接下来就来学习下如何实现。

父子通信

父组件通过 props 传递数据给子组件,子组件通过 emit 发送事件传递数据给父组件,这两种方式是最常用的父子通信实现办法。

这种父子通信方式也就是典型的单向数据流,父组件通过 props 传递数据,子组件不能直接修改 props, 而是必须通过发送事件的方式告知父组件修改数据。

另外这两种方式还可以使用语法糖 v-model 来直接实现,因为 v-model 默认会解析成名为 value 的 prop 和名为 input 的事件。这种语法糖的方式是典型的双向绑定,常用于 UI 控件上,但是究其根本,还是通过事件的方法让父组件修改数据。

当然我们还可以通过访问 $parent 或者 $children 对象来访问组件实例中的方法和数据。

另外如果你使用 Vue 2.3 及以上版本的话还可以使用 $listeners 和 .sync 这两个属性。

$listeners 属性会将父组件中的 (不含 .native 修饰器的) v-on 事件监听器传递给子组件,子组件可以通过访问 $listeners 来自定义监听器。

.sync 属性是个语法糖,可以很简单的实现子组件与父组件通信

<!--父组件中-->
<input :value.sync="value"/>
<!--以上写法等同于-->
<input :value="value"@update:value="v => value = v"></comp>
<!--子组件中--><script>this.$emit('update:value', 1)
</script>复制代码

兄弟组件通信

对于这种情况可以通过查找父组件中的子组件实现,也就是 this.$parent.$children,在 $children中可以通过组件 name 查询到需要的组件实例,然后进行通信。

跨多层次组件通信

对于这种情况可以使用 Vue 2.2 新增的 API provide / inject,虽然文档中不推荐直接使用在业务中,但是如果用得好的话还是很有用的。

假设有父组件 A,然后有一个跨多层级的子组件 B

// 父组件
Aexport default {    provide: {        data: 1    }}// 子组件 Bexport default {    inject:['data'],    mounted() {        // 无论跨几层都能获得父组件的 data 属性        console.log(this.data)//=> 1
}}复制代码

任意组件

这种方式可以通过 Vuex 或者 Event Bus 解决,另外如果你不怕麻烦的话,可以使用这种方式解决上述所有的通信情况

extend 能做什么

这个 API 很少用到,作用是扩展组件生成一个构造器,通常会与 $mount 一起使用。

// 创建组件构造器
let Component = Vue.extend({    template:'<div>test</div>'})// 挂载到 #app 上new Component().$mount('#app')// 除了上面的方式,还可以用来扩展已有的组件let SuperComponent = Vue.extend(Component)new SuperComponent({    created() {        console.log(1)    }})new SuperComponent().$mount('#app')mixin 和 mixins 区别mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。Vue.mixin({    beforeCreate() {        // ...逻辑        // 这种方式会影响到每个组件的 beforeCreate 钩子函数    }
})复制代码

虽然文档不建议我们在应用中直接使用 mixin,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 ajax 或者一些工具函数等等。

mixins 应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。

另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并,具体可以阅读 文档。

computed 和 watch 区别

computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。

watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。

所以一般来说需要依赖别的属性来动态获得值的时候可以使用 computed,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用 watch。

另外 computed 和 watch 还都支持对象的写法,这种方式知道的人并不多。

vm.$watch('obj', {    // 深度遍历    deep: true,    // 立即触发    immediate: true,    // 执行的函数    handler: function(val, oldVal){}
})
var vm = new Vue({    data: { a: 1 },    computed: {        aPlus: {            // this.aPlus 时触发            get: function () {                return this.a + 1            },            // this.aPlus = 1 时触发            set: function (v) {                this.a = v - 1            }        }    }
})复制代码

keep-alive 组件有什么作用

如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。

对于 keep-alive 组件来说,它拥有两个独有的生命周期钩子函数,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

v-show 与 v-if 区别

v-show 只是在 display: none 和 display: block 之间切换。无论初始条件是什么都会被渲染出来,后面只需要切换 CSS,DOM 还是一直保留着的。所以总的来说 v-show 在初始渲染时有更高的开销,但是切换开销很小,更适合于频繁切换的场景。

v-if 的话就得说到 Vue 底层的编译了。当属性初始为 false 时,组件就不会被渲染,直到条件为 true,并且切换条件时会触发销毁/挂载组件,所以总的来说在切换时开销更高,更适合不经常切换的场景。

并且基于 v-if 的这种惰性渲染机制,可以在必要的时候才去渲染组件,减少整个页面的初始渲染开销。

组件中 data 什么时候可以使用对象

这道题目其实更多考的是 JS 功底。

组件复用时所有组件实例都会共享 data,如果 data 是对象的话,就会造成一个组件修改 data 以后会影响到其他所有组件,所以需要将 data 写成函数,每次用到就调用一次函数获得新的数据。

当我们使用 new Vue() 的方式的时候,无论我们将 data 设置为对象还是函数都是可以的,因为 new Vue() 的方式是生成一个根组件,该组件不会复用,也就不存在共享 data 的情况了。

小结

总的来说这一章节的内容更多的偏向于 Vue 的基础,下一章节我们将来了解一些原理性方面的知识。

Vue 常考进阶知识点

这一章节我们将来学习 Vue 的一些经常考到的进阶知识点。这些知识点相对而言理解起来会很有难度,可能需要多次阅读才能理解。

响应式原理

Vue 内部使用了 Object.defineProperty() 来实现数据响应式,通过这个函数可以监听到 set 和 get 的事件。

var data = { name: 'yck' }observe(data)let name = data.name // -> get valuedata.name = 'yyy' // -> change valuefunction observe(obj) {    // 判断类型    if (!obj || typeof obj !=='object') {        return    }    Object.keys(obj).forEach(key=> {        defineReactive(obj, key,obj[key])    })
}
function defineReactive(obj, key, val) {    // 递归子属性    observe(val)    Object.defineProperty(obj,key, {        // 可枚举        enumerable: true,        // 可配置        configurable: true,        // 自定义函数       get: function reactiveGetter() {            console.log('get value')            return val       },        set: function reactiveSetter(newVal) {            console.log('changevalue')            val = newVal        }    }
)}复制代码

以上代码简单的实现了如何监听数据的 set 和 get 的事件,但是仅仅如此是不够的,因为自定义的函数一开始是不会执行的。只有先执行了依赖收集,才能在属性更新的时候派发更新,所以接下来我们需要先触发依赖收集。

<div>    {{name}}
</div>复制代码

在解析如上模板代码时,遇到 {{name}} 就会进行依赖收集。

接下来我们先来实现一个 Dep 类,用于解耦属性的依赖收集和派发更新操作。

// 通过 Dep 解耦属性的依赖和更新操作class
Dep {    constructor() {        this.subs = []    }    // 添加依赖    addSub(sub) {        this.subs.push(sub)    }     // 更新    notify() {        this.subs.forEach(sub =>{            sub.update()        })    }
}
// 全局属性,通过该属性配置 WatcherDep.target = null复制代码

以上的代码实现很简单,当需要依赖收集的时候调用 addSub,当需要派发更新的时候调用 notify。

接下来我们先来简单的了解下 Vue 组件挂载时添加响应式的过程。在组件挂载时,会先对所有需要的属性调用 Object.defineProperty(),然后实例化 Watcher,传入组件更新的回调。在实例化过程中,会对模板中的属性进行求值,触发依赖收集。

因为这一小节主要目的是学习响应式原理的细节,所以接下来的代码会简略的表达触发依赖收集时的操作。

class Watcher {    constructor(obj, key, cb) {        // 将 Dep.target 指向自己        // 然后触发属性的 getter 添加监听       // 最后将 Dep.target 置空        Dep.target = this        this.cb = cb        this.obj = obj        this.key = key        this.value = obj[key]        Dep.target = null    }    update() {        // 获得新值        this.value =this.obj[this.key]        // 调用 update 方法更新 Dom        this.cb(this.value)    }
}复制代码

以上就是 Watcher 的简单实现,在执行构造函数的时候将 Dep.target 指向自身,从而使得收集到了对应的 Watcher,在派发更新的时候取出对应的 Watcher 然后执行 update 函数。

接下来,需要对 defineReactive 函数进行改造,在自定义函数中添加依赖收集和派发更新相关的代码。

function defineReactive(obj, key, val) {    // 递归子属性    observe(val)    let dp = new Dep()    Object.defineProperty(obj,key, {        enumerable: true,        configurable: true,        get: function reactiveGetter() {            console.log('get value')            // 将 Watcher 添加到订阅            if (Dep.target) {               dp.addSub(Dep.target)            }            return val        },        set: function reactiveSetter(newVal) {            console.log('changevalue')            val = newVal            // 执行 watcher 的 update 方法            dp.notify()        }    })
}复制代码

以上所有代码实现了一个简易的数据响应式,核心思路就是手动触发一次属性的 getter 来实现依赖收集。

现在我们就来测试下代码的效果,只需要把所有的代码复制到浏览器中执行,就会发现页面的内容全部被替换了。

var data = { name: 'yck'
}
observe(data)function update(value) {   document.querySelector('div').innerText = value}// 模拟解析到 `{{name}}` 触发的操作new Watcher(data, 'name', update)// update DominnerTextdata.name = 'yyy'复制代码

Object.defineProperty 的缺陷

以上已经分析完了 Vue 的响应式原理,接下来说一点 Object.defineProperty 中的缺陷。

如果通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作,更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。

对于第一个问题,Vue 提供了一个 API 解决

export function set (target: Array<any> | Object, key:any, val:any): any {   // 判断是否为数组且下标是否有效    if (Array.isArray(target)&&isValidArrayIndex(key)) {        // 调用 splice 函数触发派发更新        // 该函数已被重写        target.length = Math.max(target.length,key)        target.splice(key, 1, val)        return val    }    // 判断 key 是否已经存在    if (key in target&&!(key in Object.prototype)) {        target[key] = val        return val    }    const ob = (target: any).__ob__    // 如果对象不是响应式对象,就赋值返回    if (!ob) {        target[key] = val        return val    }    // 进行双向绑定    defineReactive(ob.value, key,val)    // 手动派发更新    ob.dep.notify()    return val
}
对于数组而言,Vue 内部重写了以下函数实现派发更新// 获得数组原型
const arrayProto = Array.prototypeexport
const arrayMethods = Object.create(arrayProto)// 重写以下函数
const methodsToPatch = [    'push',    'pop',    'shift',    'unshift',    'splice',    'sort',    'reverse']methodsToPatch.forEach(function(method) {    // 缓存原生函数    const original =arrayProto[method]    // 重写函数    def(arrayMethods, method,function mutator (...args) {        // 先调用原生函数获得结果        const result =original.apply(this,args)        const ob = this.__ob__        let inserted        // 调用以下几个函数时,监听新数据        switch (method) {            case 'push':            case 'unshift':            inserted = args            break            case 'splice':            inserted = args.slice(2)           break        }        if (inserted)ob.observeArray(inserted)        // 手动派发更新        ob.dep.notify()        return result    })
})复制代码

编译过程

想必大家在使用 Vue 开发的过程中,基本都是使用模板的方式。那么你有过「模板是怎么在浏览器中运行的」这种疑虑嘛?

首先直接把模板丢到浏览器中肯定是不能运行的,模板只是为了方便开发者进行开发。Vue 会通过编译器将模板通过几个阶段最终编译为 render 函数,然后通过执行 render 函数生成 Virtual DOM 最终映射为真实 DOM。

接下来我们就来学习这个编译的过程,了解这个过程中大概发生了什么事情。这个过程其中又分为三个阶段,分别为:

1. 将模板解析为 AST

2. 优化 AST

3. 将 AST 转换为 render 函数

在第一个阶段中,最主要的事情还是通过各种各样的正则表达式去匹配模板中的内容,然后将内容提取出来做各种逻辑操作,接下来会生成一个最基本的 AST 对象

{    // 类型    type: 1,    // 标签    tag,    // 属性列表    attrsList: attrs,    // 属性映射    attrsMap: makeAttrsMap(attrs),    // 父节点    parent,    // 子节点    children: []
}复制代码

然后会根据这个最基本的 AST 对象中的属性,进一步扩展 AST。

当然在这一阶段中,还会进行其他的一些判断逻辑。比如说对比前后开闭标签是否一致,判断根组件是否只存在一个,判断是否符合 HTML5 Content Model 规范等等问题。

接下来就是优化 AST 的阶段。在当前版本下,Vue 进行的优化内容其实还是不多的。只是对节点进行了静态内容提取,也就是将永远不会变动的节点提取了出来,实现复用 Virtual DOM,跳过对比算法的功能。在下一个大版本中,Vue 会在优化 AST 的阶段继续发力,实现更多的优化功能,尽可能的在编译阶段压榨更多的性能,比如说提取静态的属性等等优化行为。

最后一个阶段就是通过 AST 生成 render 函数了。其实这一阶段虽然分支有很多,但是最主要的目的就是遍历整个 AST,根据不同的条件生成不同的代码罢了。

NextTick 原理分析

nextTick 可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。

在 Vue 2.4 之前都是使用的 microtasks,但是 microtasks 的优先级过高,在某些情况下可能会出现比事件冒泡更快的情况,但如果都使用 macrotasks 又可能会出现渲染的性能问题。所以在新版本中,会默认使用 microtasks,但在特殊情况下会使用 macrotasks,比如 v-on。

对于实现 macrotasks ,会先判断是否能使用 setImmediate ,不能的话降级为 MessageChannel ,以上都不行的话就使用 setTimeout

if (typeof setImmediate !== 'undefined' && isNative(setImmediate)){    macroTimerFunc = () => {        setImmediate(flushCallbacks)    }} else if(typeof MessageChannel !== 'undefined'&&(isNative(MessageChannel) ||// PhantomJSMessageChannel.toString() === '[object MessageChannelConstructor]')) {    const channel = newMessageChannel()    const port = channel.port2    channel.port1.onmessage =flushCallbacks    macroTimerFunc = () => {        port.postMessage(1)    }} else {    macroTimerFunc = () => {        setTimeout(flushCallbacks,0)    }
}复制代码

以上代码很简单,就是判断能不能使用相应的 API。

小结

以上就是 Vue 的几个高频核心问题了,如果你还想了解更多的源码相关的细节,强烈推荐黄老师的 Vue 技术揭秘。

VUE 笔记(持续更新中...)相关推荐

  1. typescript-----javascript的超集,typescript学习笔记持续更新中......

    Typescript,冲! Typescript 不是一门全新的语言,Typescript是 JavaScript 的超集,它对 JavaScript进行了一些规范和补充.使代码更加严谨. 一个特别好 ...

  2. SpringCloud学习笔记,课程源自黑马程序员,笔记持续更新中...

    @SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式: 学习内容 1.服务拆分-服务远程调用: 2.搭建eureka服务: 2.1.eureka服务注册-client 2 ...

  3. JS逆向学习笔记 - 持续更新中

    JS逆向学习笔记 寻找深圳爬虫工作,微信:cjh-18888 文章目录 JS逆向学习笔记 一. JS Hook 1. JS HOOK 原理和作用 原理:替换原来的方法. (好像写了句废话) 作用: 可 ...

  4. 专升本 计算机 公共课学习笔记(持续更新中...)

    计算机公共课学习笔记 第一章 计算机基础知识(30分) 1.计算机概述 计算机(Computer)的起源与发展 计算机(Computer)也称"电脑",是一种具有计算功能.记忆功能 ...

  5. Python学习小甲鱼视频做的笔记(持续更新中)

    Python BIF :Built-in functions(内建函数) Python与大多数其他计算机语言的做法稍有不同,他并不是把值存储在变量中,而更像是把名字贴在值的上边. 在使用变量之前,必须 ...

  6. Docker快速入门学习笔记-持续更新中

    Docker安装 #1.卸载旧的版本 yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker ...

  7. [ES笔记]持续更新中

    工作中使用es的报错问题记录及常用语法记录,仍在整理中,由于es每次版本更新api变化都会很大,所以这里的整理对于很多api都不适用,这里使用的es版本为6.8.4,使用的spring-data-el ...

  8. 渗透测试笔记 -------------持续更新中~

    文章目录 渗透测试 1.Windows基础 1.1 渗透测试介绍 1.2 渗透测试的特点 1.3 渗透测试流程 1.4 Windows网站篇 http协议 http头讲解 静态网站 动态网站 1.5 ...

  9. Java学习笔记(持续更新中)

    文章目录 项目实战 mall项目(SpringBoot项目) 1. 添加Swagger-UI配置,修改MyBatis Generator注释的生成规则 2. redis基础配置 3. SpringSe ...

  10. 计算机组成原理笔记(持续更新中!)

    (标三角号的相比之下不是非常重要) 文章目录 1 导论 ▲1.1 计算机的发展历程 1.1.1 计算机软硬件的发展 1.1.2计算机的分类与发展方向 1.2 计算机系统的组成 1.2.1 系统结构 1 ...

最新文章

  1. 26个复古风格网站设计欣赏
  2. 72岁奶奶在抖音教物理火了,百万粉丝追更,网友:小时候要有这种老师就好了...
  3. android 相册 uri空,android拍照获得图片URI为空的处理方法
  4. ZOJ 3829 贪心 思维题
  5. Python实现迭代器协议
  6. hubliderx如选择相同单词_高考英语,十六种高效单词记忆法,建议人手一份!
  7. 19岁中专学历是怎么在广州找到前端工作的?
  8. 使用阿里云火车票查询接口案例——CSDN博客
  9. 谁会最先陨落:Google,苹果,Facebook,还是微软?
  10. 软件技术架构:通过限流与熔断,打造一个“靠谱”的系统
  11. Fragstats4.2之计算景观格局指数(一)
  12. HTML网页的基本结构
  13. Photoshop 渐变工具使用
  14. OneNet平台创建应用
  15. Unity 接入天气系统
  16. 618大促,我把知识星球的价格调错了……
  17. jav中spark迁移hive到mongo(更新数据)
  18. 个人怎么制作微信小程序,微信小程序可以免费制作吗?微信小程序制作教程
  19. NLP的“第四范式”之Prompt Learning总结:44篇论文逐一梳理
  20. 将同一文件夹内的所有txt文件内容合并到一个txt中

热门文章

  1. linux条件判断:常用练习添加用户
  2. [New Portal]Windows Azure Virtual Machine (8) Virtual Machine高可用(上)
  3. 对比MySQL表数据内容方式汇总
  4. mysql innodb表分区
  5. VC字体安装相关方法总结
  6. 学习计划20190509
  7. hadoop hive 安装
  8. JavaSE学习笔记(三)——运算符与控制语句
  9. python下py2exe打包笔记
  10. Nginx+Lua服务端合并静态文件