1 MVC 和 MVVM
MVC:
以前是通过接口将数据存在后台,然后后台再给一些反馈。
缺点: 前后端无法独立开发;前端过于依赖后端的数据;
MVVM:
vue中的data让前端有了自己的数据管理器,可以相对独立开发,实现前后端分离
2 v-model
表现形式:
<div id="app">
<input v-model="message"/>
{{message}}
</div>
<script>
new Vue({
el: '#app',
data() {
return {
message: 'hello world'
}
}
})
</script>
原理: 双向数据绑定
<input placeholder="请输入名字" id="username"/>
<p id="uname"></p>
const obj = {}
const inputEl = document.querySelector('#username')
const textEl = document.querySelector('#uname')
inputEl.addEventListener('input', function(e) {
obj.username = e.target.value
})
Object.defineProperty(obj, 'username', {
get: function() {
console.log('取值')
},
set: function(val) {
console.log('设置值')
textEl.innerText = val
}
})
3 data为什么是个函数?
data函数是个闭包的设计,这样就可以保证每一个组件都有自己的私有作用域,确保各组件的之间数据不会相互干扰;
而如果是纯对象的方式,则组件之间的数据就容易相互干扰。
4 v-if和v-show
v-if: 不满足条件,则不会渲染dom,单次判断;
v-show: 隐藏dom,适合多次切换
5 $nextTick
在vue中,数据更新后,dom并不会立即更新。
如果想对新的dom进行js操作,则需要使用$nextTick。
这样就可以确保dom在更新后再执行延迟回调。
6 单页与多页的区别
单页应用:只有一个主页面的应用
优点:
1.体验好,快。
2.因为分成了很多组件,所以改动内容,不用加载整个页面
3.前后端分离
缺点:
1.不利于seo
2.初次加载慢
3.页面的复杂度较高
多页应用:页面跳转整个刷新,和单页相反
7 v-if 和 v-for
不推荐同时使用。
因为 v-for的优先级高于v-if,所以会导致if语句运行在每个循环之间,影响性能。
8 vue-router和location.href区别
1.vue-router是静态跳转,页面不会重新记载;location.href会触发浏览器,页面重新加载
2.vue-router使用diff算法,实现按需加载,减少dom操作
3.vue-rouer是同一个页面跳转;location.href是不同页面跳转
4.vue-router是异步加载this.$nextTick(()=>{获取url}),loaction.href是同步加载
9 vue响应式原理
响应式:数据联动(双向绑定);能捕获到数据的修改
发布订阅模式 + 数据劫持
vue通过Object.defineProperty实现双向数据绑定,get用来依赖收集,set当数据进行更改时做一个通知;之所以能够进行通知,是因为提前对数据进行了订阅。所以只有发布订阅模式结合数据劫持,才能实现响应式。
<div id="app">
订阅视图-1: <span class="box-1"></span><br/>
订阅视图-2: <span class="box-2"></span>
</div>
<script>
let obj = {}
dataHi({
data: obj,
tag: 'view-1',
datakey: 'one',
selector: '.box-1'
})
dataHi({
data: obj,
tag: 'view-2',
datakey: 'two',
selector: '.box-2'
})
// 初始化赋值:
obj.one = '这是视图1'
obj.two = '这是视图2'
// 2 更新时,劫持数据,更新这负责重新渲染
setTimeout(() => {
obj.one = '修改后的值'
}, 3000)
</script>
// 订阅器模型
/**
* clientList:容器
* listen:添加订阅的方法。
* trigger:发布的方法
*/
let Dep = {
clientList: {},
listen: function(key, fn) {// 比如:key是一个人的id,fn是他订阅的多个东西
// if(!this.clientList[key]) {
// this.clientList[key] = []
// }
// this.clientList[key].push(fn)
// 上面的可以简化为下面的短路表达式:
(this.clientList[key] || (this.clientList[key] = [])).push(fn)
},
tigger: function() {
let key = Array.prototype.shift.call(arguments), // shift用于把数组的第一个元素删除,并返回第一个元素的值,会改变原数组的长度
fns = this.clientList[key];
if (!fns || fns.length === 0) {
return false
}
for (let i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments)
}
}
}
/**
* 数据劫持
*/
let dataHi = function({data, tag, datakey, selector}) {
let value = '',
el = document.querySelector(selector);
Object.defineProperty(data, datakey, {
get() {
console.log('取值')
return value
},
set(val) {
console.log('改值')
value = val
// 通知模板
Dep.tigger(tag, val)
}
})
// 订阅
Dep.listen(tag, function(text) {
el.innerText = text
})
}
10 数据驱动原理(如何劫持、监听所有数据)
通过一个观测类,让每一个属性都可以被观测
获取对象中所有的key值,然后逐个遍历,利用Object.defineProperty的get和set方法实现所有数据的监听
以下是期望达成的效果:
const obj = new Observer({
name: '张三',
age: 18,
demo: {
a: 'aaa',
b: 'bbb'
}
})
console.log(obj.value)
console.log(obj.value.name)
obj.value.age = 25
console.log(obj.value.demo.a)
obj.value.demo.b = '1234'
实现
class Observer {
constructor(value) {
this.value = value
this.fn(value)
}
fn(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
function defineReactive(obj, key, val) {
if (arguments.length === 2) {
val = obj[key]
}
if (typeof val === 'object') {
new Observer(val) // 递归
}
Object.defineProperty(obj, key, {
get() {
console.log('取值',key)
return val
},
set(newVal) {
console.log('赋值',key, newVal)
val = newVal
}
})
}
11 虚拟dom
是什么?
虚拟dom是在vue2.x版本加入的;本质是js对象,所以可以跨平台。
vue的渲染过程
vue中的render函数会把template转化为真实的dom,然后会根据真实的dom生成虚拟dom,最后再变成真实dom。
在vue中做了什么?
将真实dom转化为虚拟dom;更新的时候做对比
虚拟dom是如何提升vue的渲染效率的
局部更新(节点数据);将直接操作dom的地方拿到两个js对象中去做比较
diff中的patch方法
// 初始化 patch(container, vnode)
// 更新 update(vnode, newVnode)
function creatElement(vnode) {
/**
* 三要素,目标元素(tag),属性(attrs),子节点(children)
*/
const tag = vnode.tag
const attrs = vnode.attrs || {}
const children = vnode.children || []
if (!tag) {
return null
}
// 1 创建对应的dom
const elem = document.createElement(tag)
let attrName;
// 2 给dom添加属性
for(attrName in attrs) {
if (attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName])
}
}
// 3 将子元素添加到目标之上
children.forEach(childVnode => {
elem.appendChild(createElement(childVnode))
});
return elem
}
function update(vnode, newVnode) {
let children = vnode.children || [] // 现有节点
let newChildren = newVnode.children || [] // 新节点
children.forEach((childVnode, index) => {
let newChildVnode = newChildren[index]
// 判断第一层没有变化
if (childVnode.tag === newChildVnode.tag) {
update(childVnode, newChildVnode)
} else {
replaceNode(childVnode, newChildVnode)
}
})
}
diff算法
概念
对比新旧虚拟dom,并根据对比后的结果更新真实dom
虚拟dom是什么
一个用来描述真实dom的对象。
它有六个属性,sel表示当前节点标签名,data内是节点的属性,children表示当前节点的其他子标签节点,elm表示当前虚拟节点对应的真实节点,key即为当前节点的key,text表示当前节点下的文本,结构类似这样:
let vnode = {
sel: 'ul',
data: {},
children: [
{
sel: 'li', data: { class: 'item' }, text: 'son1'
},
{
sel: 'li', data: { class: 'item' }, text: 'son2'
},
],
elm: undefined,
key: undefined,
text: undefined
}