什么是vue
- vue 是一个前端的js库,也是一个mvvm的框架。它简化对应的js原生的操作,提高了对应的浏览器性能(虚拟dom,diff算法)
- vue作者:尤雨溪,vue2诞生于2015年,vue3诞生于2020年6月,被阿里巴巴维护。
- 在vue中: Model:指的是js中的数据,如对象,数组等等。 View:指的是页面视图 viewModel:指的是vue实例例化对象
特点
Vue.js 最显著的特点就是响应式和数据驱动,也就是将Model和View进行单向绑定或者双向绑定。
单向绑定(v-bind)
单向数据流在
Vue
中实际表现就是:当Model
中的data
发生变化的时候会单向修改View
中的值,而View
中的值发生变化的时候,Model
不会感知。实际应用就是v-bind
单向数据。
主要操作:
- vue2的劫持 (es5语法)
- Object.defineProperty
- 数组不能进行劫持,需要采用重写数组的方法也就是改变数组的七个方法来劫持
- vue3的劫持 (es6语法)
- proxy(万物皆对象:都能劫持)
双向绑定MVVM(v-model)
- 双向数据绑定多了
View
变化会通知到Model
层。MVVM
的具体实现:无论Model
还是View
中的值发生变化,都会通过ViewModel
通知到对方并实现同步。- 一是将模型转化成视图,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
- 二是将视图转化成模型,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。
- 实际应用就是
v-model
双向数据绑定。
双向数据绑定的实现
-
vue的双向数据绑定
<!-- 准备容器 -->
<div id="app">
<!-- v-model 是vue中实现双向数据绑定的指令 -->
<input type="text" v-model="message">
<!-- 查看对应message的值 模板语法-->
{{message}}
</div>
<!-- 链入vue.js -->
<script src="./vue.js"></script>
<script>
new Vue({
// 挂载地址
el: '#app',
// 数据
data: {
message: 'hello'
}
})
</script>
-
vue2的双向数据绑定
数据劫持(Object.defineProperty) + obServer
【实现流程】
- 获取所有的 input 框
- 过滤有 v-model 属性的 input
- 遍历所有过滤后的 input
- 找这个 v-model 属性的属性值 data,递归 data 中的数据来进行劫持
- 使用 ObServer 监听 input 输入框的内容变化
- 重新设置对应的 data 中的数据(this._data)
- 使用 Object.defineProperty 的 set 来监听数据的变化
- 在数据变化的时候,重新渲染对应的 input 的值
- 渲染应该解析对应{ { } } 找到对应的属性名进行替换
class Vue{
constructor(option){
this.el = document.querySelector(option.el) //找到对应渲染的元素
this.data = option.data
//用于Object.defineProperty的使用
this.model = Object.assign({},option.data)
//拿到传入的元素的里面显示的所有的内容
this.template = document.querySelector(option.el).innerHTML
this.render()
this. _obServer()
}
//观察者 观察data的数据变化
_obServer(){
let _this = this
//遍历所有的属性
for(let key in this.data){
Object.defineProperty(this.data,key,{
get(){
return _this.model[key]
},
set(v){
_this.model[key] = v
//渲染
_this.render()
}
})
}
}
//渲染的方法
render(){
let _this = this
//找到所有的{{}} 括起来的内容 进行替换
this.el.innerHTML = this.template.replace(/\{\{\w+\}\}/g,(str)=>{
//str {{msg}}
let propertyName = str.substring(2,str.length-2).trim() //msg
return _this.data[propertyName]
})
//找到对应el里面的input框
Array.from(this.el.querySelectorAll('input'))
.filter((input)=>{
//找到input中是否具备v-model属性
return input.getAttribute('v-model')
})
.forEach((input)=>{
let _this = this
//遍历所有的input框 (都是有v-model属性的)
//获取对应的v-model的属性值
let propertyName = input.getAttribute('v-model').trim()
//给这个input添加onchange事件或者是oninput
input.oninput = function(){
//获取他的value 设置给对应的data里面的属性
_this.data[propertyName] = this.value
}
//将对应的input的value 进行设置(对应的data里面属性的数据)
input.value = this.data[propertyName]
})
}
}
diff 算法(脏检查器)
- diff 算法用于比对新旧虚拟dom,采用深度优先,时间复杂度为O(n),利用patch补丁包的形式来进行重新渲染。
- 先比对自身,通过key来找到自身 (key是唯一的下标不能作为key)
- 再比对自身的属性,比对文本,再比对子元素,递归比到低
- 实际DOM
<ul id="list">
<li class="item">哈哈</li>
<li class="item">呵呵</li>
<li class="item">林三心哈哈哈哈哈</li> // 修改
</ul>
- 虚拟DOM
- 虚拟dom顾名思义就是虚拟的dom对象,这个虚拟的dom对象跟实体的dom对象没有直接的关系。
- 如果直接操作实体dom会造成大量的重绘和回流(页面渲染次数增加,渲染速度就慢)。
- 所以为了解决这个问题,vue就是先操作对应的虚拟dom,再通过虚拟dom渲染实体dom,这个渲染到实体dom的过程只会进行一次。
- 虚拟dom在内存中的,所以渲染快,虚拟dom的形成是抽取对应的实体dom(模仿实体dom创建的对象)。
let newVDOM = { // 新虚拟DOM
tagName: 'ul', // 标签名
props: { // 标签属性
id: 'list'
},
children: [ // 标签子节点
{
tagName: 'li', props: { class: 'item' }, children: ['哈哈']
},
{
tagName: 'li', props: { class: 'item' }, children: ['呵呵']
},
{
tagName: 'li', props: { class: 'item' }, children: ['林三心哈哈哈哈哈']
},
]
}
- 比对流程如下
深拷贝和浅拷贝
浅拷贝:创建快捷键
- 浅拷贝不等于赋值,它会开辟一个新的内存空间,和原本的地址不一致,所以拷贝的对象和原本的对象不是一个对象
- 浅拷贝里面的内容都是拷贝对应的地址,所以和原本的内容地址一致
浅拷贝的几种实现方法
对象拷贝:Object.assign
- let copyObj = Object.assign ( { } , obj )
let obj = {
user: {
age: 18
}
}
let newObj = obj
console.log(newObj == obj) //true 赋值的地址是共享的
//浅拷贝
let copyObj = Object.assign({}, obj)
//浅拷贝会产生一个新的对象 和原本的对象地址不一样
console.log(obj == copyObj) //false
//里面的内容的地址是共享的
console.log(obj.user == copyObj.user) //true
obj.user.age = 20
console.log(copyObj.user.age) //20
数组拷贝:concat、slice
- let concatArr = [ ].concat ( arr )
- let sliceArr = arr.slice ()
//使用数组的concat方法实现数组的浅拷贝
let concatArr = [].concat(arr)
console.log(concatArr == arr) //false
console.log(concatArr[0] == arr[0]) //true
//使用数组的slice方法
let sliceArr = arr.slice()
console.log(sliceArr == arr) //false
console.log(sliceArr[0] == arr[0]) //true
扩展运算符可以实现数组及对象的浅拷贝
- let copyObj1 = { ...obj }
- let copyArr = [ ...arr ]
//使用扩展运算符
let copyObj1 = {...obj}
console.log(copyObj1 == obj) //false
console.log(copyObj1.user == obj.user) //true
let arr = [{age:19},{name:'jack'}]
let copyArr = [...arr]
console.log(copyArr == arr) //false
console.log(copyArr[0] == arr[0]) //true
自定义函数
function clone(obj) {
let copyObj = {}
for (let key in obj) {
copyObj[key] = obj[key]
}
return copyObj
}
let obj1 = {
user: {
age: 18
}
}
let copyObj = clone(obj1)
console.log(obj1 == copyObj); //false
console.log(copyObj.user == obj1.user); //true
第三方插件 lodash.js (提供的clone方法):
- <script src="https://www.lodashjs.com/"></script>
- let cloneObj = _.clone ( obj1 )
<script src="https://www.lodashjs.com/"></script>
let obj1 = {
user: {}
}
let cloneObj = _.clone(obj1)
console.log(cloneObj == obj1)//false
console.log(cloneObj.user == obj1.user)//true
深拷贝:文件复制粘贴
- 拷贝的是对应的值,不拷贝地址。
【实现方式】
- JSON.parse、JSON.stringify
let copyObj = JSON.parse ( JSON.stringify ( obj ) )
let obj = {list:['1','2'],user:{name:'tom'}}
let copyObj = JSON.parse(JSON.stringify(obj))
console.log(obj == copyObj) //false
console.log(obj.list == copyObj.list) //false
console.log(obj.user == copyObj.user) //false
console.log(obj.user.name == copyObj.user.name) //true
- 使用 lodash.js 中的 _.cloneDeep 方法(第三方插件)
<script src="https://www.lodashjs.com/"></script>
let cloneObj = _.cloneDeep ( obj );
//使用lodash.js _.cloneDeep
let cloneObj = _.cloneDeep(obj);
console.log(obj == cloneObj) //false
console.log(obj.list == cloneObj.list) //false
console.log(obj.user == cloneObj.user) //false
console.log(obj.user.name == cloneObj.user.name) //true
- 自定义递归书写对应的深拷贝 (重点)
function deepClone(obj) {
//先判断是否为函数,必须放在判断是否为对象之前
if (typeof obj == 'function') {
return obj.bind(this)
}
// 判断是否为对象以及排除null这个特殊情况
if (typeof obj != 'object' || !obj) {
return obj
}
// 判断是否为正则对象
if (obj instanceof RegExp) {
return new RegExp(obj)
}
// 判断是否为日期对象
if (obj instanceof Date) {
return new Date(obj.getTime())
}
// 判断是否为数组对象或者Object
let newObj = obj.isArray ? [] : {}
for (var key in obj) {
// 利用递归
newObj[key] = deepClone(obj[key])
}
return newObj
}
let obj = {
name: {
id: {
regexp: /\w+/,
type() {
console.log('你好')
},
likes: {
color: [1, 2, 3, 5]
}
}
}
}
let copyObj = deepClone(obj)
console.log(copyObj == obj); //false
console.log(copyObj.name == obj.name); //false
console.log(copyObj.name.id == obj.name.id); //false
console.log(copyObj.name.id.regexp == obj.name.id.regexp); //false
console.log(copyObj.name.id.type == obj.name.id.type); //false
console.log(copyObj.name.id.likes == obj.name.id.likes); //false
console.log(copyObj.name.id.likes.color == obj.name.id.likes.color); //false
- vue是一个mvvm的框架,vm是内置的,不需要你去管理。
- vue的数据劫持是通过 Object.defineProperty (vue2) 重写了数组的7个方法(不能对于数组进行劫持) Proxy (vue3)。
- vue主要劫持是data中的数据 (_data的属性)递归去进行劫持。
- vue的双向数据绑定主要是通过数据劫持+obServer(观察者模式)。
- vue是利用虚拟dom来进行对应的比对(里面采用diff算法)使用模板引擎进行解析渲染。
- diff算法比对先比对自身(key),再比对对应的vnode,再比对对应的子节点(递归比对) 采用patch补丁包的形式来进行重新渲染。
- 深拷贝拷贝的是值,浅拷贝拷贝的是地址,不管深浅拷贝都会产生一个新的对象。