Vue面试涉及的原理问题

 1.双向绑定原理

Vue内部通过Object.defineProperty方法属性拦截的方式,把data对象里每个数据的读写转化成getter/setter,当数据变化时通知视图更新。

实现MVVM的数据双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来给各个属性添加setter,getter并劫持监听,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:
1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

 

实现双向绑定原理

index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<h1 id="name"></h1>
	<input type="text">
	<input type="button" value="改变data内容" onclick="changeInput()">
	
<script src="observer.js"></script>
<script src="watcher.js"></script>
<script>
	function myVue (data, el, exp) {
	    this.data = data;
	    observable(data);                      //将数据变的可观测
	    el.innerHTML = this.data[exp];           // 初始化模板数据的值
	    new Watcher(this, exp, function (value) {
	        el.innerHTML = value;
	    });
	    return this;
	}

	var ele = document.querySelector('#name');
	var input = document.querySelector('input');
	
    var myVue = new myVue({
		name: 'hello world'
	}, ele, 'name');
 	
	//改变输入框内容
    input.oninput = function (e) {
    	myVue.data.name = e.target.value
    }
	//改变data内容
	function changeInput(){
		myVue.data.name = "难凉热血"
	
	}
</script>
</body>
</html>

 observer.js

	/**
	 * 把一个对象的每一项都转化成可观测对象
	 * @param { Object } obj 对象
	 */
	function observable (obj) {
		if (!obj || typeof obj !== 'object') {
        	return;
    	}
		let keys = Object.keys(obj);
		keys.forEach((key) =>{
			defineReactive(obj,key,obj[key])
		})
		return obj;
	}
	/**
	 * 使一个对象转化成可观测对象
	 * @param { Object } obj 对象
	 * @param { String } key 对象的key
	 * @param { Any } val 对象的某个key的值
	 */
	function defineReactive (obj,key,val) {
		let dep = new Dep();
		Object.defineProperty(obj, key, {
			get(){
				dep.depend();
				console.log(`${key}属性被读取了`);
				return val;
			},
			set(newVal){
				val = newVal;
				console.log(`${key}属性被修改了`);
				dep.notify()                    //数据变化通知所有订阅者
			}
		})
	}
	class Dep {
		
		constructor(){
			this.subs = []
		}
		//增加订阅者
		addSub(sub){
			this.subs.push(sub);
		}
        //判断是否增加订阅者
		depend () {
		    if (Dep.target) {
		     	this.addSub(Dep.target)
		    }
		}

		//通知订阅者更新
		notify(){
			this.subs.forEach((sub) =>{
				sub.update()
			})
		}
		
	}
	Dep.target = null;

watcher.js

	class Watcher {
		constructor(vm,exp,cb){
		    this.vm = vm;
		    this.exp = exp;
		    this.cb = cb;
		    this.value = this.get();  // 将自己添加到订阅器的操作
		}
		get(){
			Dep.target = this;  // 缓存自己
        	let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
        	Dep.target = null;  // 释放自己
        	return value;
		}
		update(){
			let value = this.vm.data[this.exp];
        	let oldVal = this.value;
        	if (value !== oldVal) {
                this.value = value;
                this.cb.call(this.vm, value, oldVal);
			}
	}
}

2.nextTick原理 

在vue中有nextTick,官方解释,它可以在DOM更新完毕之后执行一个回调。

涉及到vue的DOM更新,下面解释一下vue的更新数据原理

Vue异步更新队列(数据驱动视图)

由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,
只要监听数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更,在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。
然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。
为了确保得到更新后的DOM,所以设置了 Vue.nextTick()方法。

nextTick内部原理

  • nextTick主要使用了宏任务微任务
  • Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0)代替。

vue是如何知道DOM更新完成

vue源码: 

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

步骤:

  1. 先判断Promise
  2. 在判断MutationObserver
  3. 在判断setImmediate
  4. 最后setTimeout

vue更新DOM是异步操作,等所有数据更新后才会渲染DOM。

所以要在更新DOM后操作,vue提供了nextTick。nextTick方法在源码内会通过各种方法检测DOM是否更新完成。

如果有promise.then就用promise监听,没有就降级成MutationObserver,还不支持就降级setImmediate,都不支持就用setTimeout

 总结

vue的nextTick方法的实现原理:

  1. vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
  2. 微任务因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
  3. 因为浏览器和移动端兼容问题,vue不得不做了微任务(microtask)和宏任务(macrotask)的兼容(降级)方案

 3.vue-router原理

实现原理:vue-router 的原理就是更新视图而不重新请求页面

两种路由模式

  1. hash 模式。默认是 hash 模式,基于浏览器 history api,使用 window.addEventListener("hashchange", callback, false) 对浏览进行监听。当调用 push 时,把新路由添加到浏览器访问历史的栈顶。使用 replace 时,把浏览器访问历史的栈顶路由替换成新路由 hash 的值(等于 url 中 # 及其以后的内容)。浏览器是根据 hash 值的变化,将页面加载到相应的 DOM 位置。锚点变化只是浏览器的行为,每次锚点变化后依然会在浏览器中留下一条历史记录,可以通过浏览器的后退按钮回到上一个位置。
  2. history 模式。基于浏览器 history api,使用 window.onpopstate 对浏览器地址进行监听。对浏览器 history api 中的 pushState()、replaceState() 进行封装,当方法调用,会对浏览器的历史栈进行修改。从而实现 URL 的跳转而无需加载页面,但是它的问题在于当刷新页面的时候会走后端路由,所以需要服务端的辅助来完成,避免 url 无法匹配到资源时能返回页面。

两种模式的比较,history模式更有优势

1)hash模式有#,history模式没有#,更美观
2)pushState设置的新url是和当前url同源的任意url,hash模式只可以修改#后面的内容,也就是只可以设置与当前同文档的url
3)pushState设置的新url和当前url相同时也会把记录添加进记录栈中,而hash只有新的和当前的不同的时候才会添加到栈中
4)如果是history模式,他要匹配/,如果匹配不到,就会报错
 

  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值