今天给各位分享vue企业网站源码分享的知识,其中也会对vue个人网站进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
作者:前端森林
转发连接:https://mp.weixin.qq.com/s/4g8XCx0olYaqY78q3ravIA
写在前面(不看错过一个亿)
最近一直在读Vue源码,也写了一系列的源码探秘文章。
但,收到很多朋友的反馈都是:源码晦涩难懂,时常看着看着就不知道我在看什么了,感觉缺乏一点动力,如果你可以出点面试中会问到的源码相关的面试题,通过面试题去看源码,那就很棒棒。
看到大家的反馈,我丝毫没有犹豫:安排!!
我通过三篇文章整理了大厂面试中会经常问到的一些Vue面试题,通过源码角度去回答,抛弃纯概念型回答,相信一定会让面试官对你刮目相看。
请说一下响应式数据的原理?
Vue实现响应式数据的核心API是Object.defineProperty。
其实默认Vue在初始化数据时,会给data中的属性使用Object.defineProperty重新定义所有属性,当页面取到对应属性时。会进行依赖收集(收集当前组件的watcher)如果属性发生变化会通知相关依赖进行更新操作。
这里,我用一张图来说明Vue实现响应式数据的流程:
首先,第一步是初始化用户传入的data数据。这一步对应源码src/core/instance/state.js的112行
functioninitData(vm:Component){\nletdata=vm.$options.data\ndata=vm._data=typeofdata===&39;\n?getData(data,vm)\n:data||{}\nif(!isPlainObject(data)){\n//…\n}\n//proxydataoninstance\nconstkeys=Object.keys(data)\nconstprops=vm.$options.props\nconstmethods=vm.$options.methods\nleti=keys.length\nwhile(i–){\n//…\n}\n//observedata\nobserve(data,true/*asRootData*/)\n}
第二步是将数据进行观测,也就是在第一步的initData的最后调用的observe函数。对应在源码的src/core/observer/index.js的110行
/**\n*Attempttocreateanobserverinstanceforavalue,\n*returnsthenewobserverifsuccessfullyobserved,\n*ortheexistingobserverifthevaluealreadyhasone.\n*/\nexportfunctionobserve(value:any,asRootData:?boolean):Observer|void{\nif(!isObject(value)||valueinstanceofVNode){\nreturn\n}\nletob:Observer|void\nif(hasOwn(value,&39;)&&value.__ob__instanceofObserver){\nob=value.__ob__\n}elseif(\nshouldObserve&&\n!isServerRendering()&&\n(Array.isArray(value)||isPlainObject(value))&&\nObject.isExtensible(value)&&\n!value._isVue\n){\nob=newObserver(value)\n}\nif(asRootData&&ob){\nob.vmCount++\n}\nreturnob\n}
这里会通过newObserver(value)创建一个Observer实例,实现对数据的观测。
第三步是实现对对象的处理。对应源码src/core/observer/index.js的55行。
/**\n*Observerclassthatisattachedtoeachobserved\n*object.Onceattached,theobserverconvertsthetarget\n*object&39;__ob__&39;tplaywellwith\n*dynamicallyaccessingmethodsonArrayprototype\n*/\n\nimport{def}from&39;\n\nconstarrayProto=Array.prototype\nexportconstarrayMethods=Object.create(arrayProto)\n\nconstmethodsToPatch=[//这里列举的数组的方法是调用后能改变原数组的\n&39;,\n&39;,\n&39;,\n&39;,\n&39;,\n&39;,\n&39;\n]\n\n/**\n*Interceptmutatingmethodsandemitevents\n*/\nmethodsToPatch.forEach(function(method){//重写原型方法\n//cacheoriginalmethod\nconstoriginal=arrayProto[method]//调用原数组方法\ndef(arrayMethods,method,functionmutator(…args){\nconstresult=original.apply(this,args)\nconstob=this.__ob__\nletinserted\nswitch(method){\ncase&39;:\ncase&39;:\ninserted=args\nbreak\ncase&39;:\ninserted=args.slice(2)\nbreak\n}\nif(inserted)ob.observeArray(inserted)//进行深度监控\n//notifychange\nob.dep.notify()//调用数组方法后,手动通知视图更新\nreturnresult\n})\n})\n
为什么Vue采用异步渲染?
我们先来想一个问题:如果Vue不采用异步更新,那么每次数据更新时是不是都会对当前组件进行重写渲染呢?
答案是肯定的,为了性能考虑,会在本轮数据更新后,再去异步更新视图。
通过一张图来说明Vue异步更新的流程:
第一步调用dep.notify()通知watcher进行更新操作。对应源码src/core/observer/dep.js中的37行。
notify(){//通知依赖更新\n//stabilizethesubscriberlistfirst\nconstsubs=this.subs.slice()\nif(process.env.NODE_ENV!==&39;&&!config.async){\n//subsaren&39;s\n*pushedwhenthequeueisbeingflushed.\n*/\nexportfunctionqueueWatcher(watcher:Watcher){\nconstid=watcher.id//过滤watcher,多个属性可能会依赖同一个watcher\nif(has[id]==null){\nhas[id]=true\nif(!flushing){\nqueue.push(watcher)//将watcher放到队列中\n}else{\n//ifalreadyflushing,splicethewatcherbasedonitsid\n//ifalreadypastitsid,itwillberunnextimmediately.\nleti=queue.length-1\nwhile(i>index&&queue[i].id>watcher.id){\ni–\n}\nqueue.splice(i+1,0,watcher)\n}\n//queuetheflush\nif(!waiting){\nwaiting=true\n\nif(process.env.NODE_ENV!==&39;&&!config.async){\nflushSchedulerQueue()\nreturn\n}\nnextTick(flushSchedulerQueue)//调用nextTick方法,在下一个tick中刷新watcher队列\n}\n}\n}
第四步就是执行nextTick(flushSchedulerQueue)方法,在下一个tick中刷新watcher队列
谈一下nextTick的实现原理?
Vue.js在默认情况下,每次触发某个数据的setter方法后,对应的Watcher对象其实会被push进一个队列queue中,在下一个tick的时候将这个队列queue全部拿出来run(Watcher对象的一个方法,用来触发patch操作)一遍。
因为目前浏览器平台并没有实现nextTick方法,所以Vue.js源码中分别用Promise、setTimeout、setImmediate等方式在microtask(或是task)中创建一个事件,目的是在当前调用栈执行完毕以后(不一定立即)才会去执行这个事件。
nextTick方法主要是使用了宏任务和微任务,定义了一个异步方法.多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。
?
所以这个nextTick方法是异步方法。
?
通过一张图来看下nextTick的实现:
首先会调用nextTick并传入cb。对应源码src/core/util/next-tick.js的87行。
exportfunctionnextTick(cb?:Function,ctx?:Object){\nlet_resolve\ncallbacks.push(()=>{\nif(cb){\ntry{\ncb.call(ctx)\n}catch(e){\nhandleError(e,ctx,&39;)\n}\n}elseif(_resolve){\n_resolve(ctx)\n}\n})\nif(!pending){\npending=true\ntimerFunc()\n}\n//$flow-disable-line\nif(!cb&&typeofPromise!==&39;){\nreturnnewPromise(resolve=>{\n_resolve=resolve\n})\n}\n}
接下来会定义一个callbacks数组用来存储nextTick,在下一个tick处理这些回调函数之前,所有的cb都会被存在这个callbacks数组中。下一步会调用timerFunc函数。对应源码src/core/util/next-tick.js的33行。
lettimerFunc\n\nif(typeofPromise!==&39;&&isNative(Promise)){\ntimerFunc=()=>{\n//…\n}\nisUsingMicroTask=true\n}elseif(!isIE&&typeofMutationObserver!==&39;&&(\nisNative(MutationObserver)||\n//PhantomJSandiOS7.x\nMutationObserver.toString()===&39;\n)){\n\ntimerFunc=()=>{\n//…\n}\nisUsingMicroTask=true\n}elseif(typeofsetImmediate!==&39;&&isNative(setImmediate)){\ntimerFunc=()=>{\nsetImmediate(flushCallbacks)\n}\n}else{\n//FallbacktosetTimeout.\ntimerFunc=()=>{\nsetTimeout(flushCallbacks,0)\n}\n}
来看下timerFunc的取值逻辑:
1、我们知道异步任务有两种,其中microtask要优于macrotask,所以优先选择Promise。因此这里先判断浏览器是否支持Promise。
2、如果不支持再考虑macrotask。对于macrotask会先后判断浏览器是否支持MutationObserver和setImmediate。
3、如果都不支持就只能使用setTimeout。这也从侧面展示出了macrotask中setTimeout的性能是最差的。
?
nextTick中if(!pending)语句中pending作用显然是让if语句的逻辑只执行一次,而它其实就代表callbacks中是否有事件在等待执行。
?
这里的flushCallbacks函数的主要逻辑就是将pending置为false以及清空callbacks数组,然后遍历callbacks数组,执行里面的每一个函数。
nextTick的最后一步对应:
if(!cb&&typeofPromise!==&39;){\nreturnnewPromise(resolve=>{\n_resolve=resolve\n})\n}
这里if对应的情况是我们调用nextTick函数时没有传入回调函数并且浏览器支持Promise,那么就会返回一个Promise实例,并且将resolve赋值给_resolve。回到nextTick开头的一段代码:
let_resolve\ncallbacks.push(()=>{\nif(cb){\ntry{\ncb.call(ctx)\n}catch(e){\nhandleError(e,ctx,&39;)\n}\n}elseif(_resolve){\n_resolve(ctx)\n}\n})
当我们执行callbacks的函数时,发现没有cb而有_resolve时就会执行之前返回的Promise对象的resolve函数。
你知道Vue中computed是怎么实现的吗?
这里先给一个结论:计算属性computed的本质是computedWatcher,其具有缓存。
一张图了解下computed的实现:
首先是在组件实例化时会执行initComputed方法。对应源码src/core/instance/state.js的169行。
constcomputedWatcherOptions={lazy:true}\n\nfunctioninitComputed(vm:Component,computed:Object){\n//$flow-disable-line\nconstwatchers=vm._computedWatchers=Object.create(null)\n//computedpropertiesarejustgettersduringSSR\nconstisSSR=isServerRendering()\n\nfor(constkeyincomputed){\nconstuserDef=computed[key]\nconstgetter=typeofuserDef===&39;?userDef:userDef.get\nif(process.env.NODE_ENV!==&39;&&getter==null){\nwarn(\n`Getterismissingforcomputedproperty&34;.`,\nvm\n)\n}\n\nif(!isSSR){\n//createinternalwatcherforthecomputedproperty.\nwatchers[key]=newWatcher(\nvm,\ngetter||noop,\nnoop,\ncomputedWatcherOptions\n)\n}\n\n//component-definedcomputedpropertiesarealreadydefinedonthe\n//componentprototype.Weonlyneedtodefinecomputedpropertiesdefined\n//atinstantiationhere.\nif(!(keyinvm)){\ndefineComputed(vm,key,userDef)\n}elseif(process.env.NODE_ENV!==&39;){\nif(keyinvm.$data){\nwarn(`Thecomputedproperty&34;isalreadydefinedindata.`,vm)\n}elseif(vm.$options.props&&keyinvm.$options.props){\nwarn(`Thecomputedproperty&34;isalreadydefinedasaprop.`,vm)\n}\n}\n}\n}
initComputed函数拿到computed对象然后遍历每一个计算属性。判断如果不是服务端渲染就会给计算属性创建一个computedWatcher实例赋值给watchers[key](对应就是vm._computedWatchers[key])。然后遍历每一个计算属性调用defineComputed方法,将组件原型,计算属性和对应的值传入。
defineComputed定义在源码src/core/instance/state.js210行。
//src/core/instance/state.js\nexportfunctiondefineComputed(target:any,\nkey:string,\nuserDef:Object|Function){\nconstshouldCache=!isServerRendering();\nif(typeofuserDef===&34;){\nsharedPropertyDefinition.get=shouldCache\n?createComputedGetter(key)\n:createGetterInvoker(userDef);\nsharedPropertyDefinition.set=noop;\n}else{\nsharedPropertyDefinition.get=userDef.get\n?shouldCache&&userDef.cache!==false\n?createComputedGetter(key)\n:createGetterInvoker(userDef.get)\n:noop;\nsharedPropertyDefinition.set=userDef.set||noop;\n}\nif(\nprocess.env.NODE_ENV!==&34;&&\nsharedPropertyDefinition.set===noop\n){\nsharedPropertyDefinition.set=function(){\nwarn(\n`Computedproperty&34;wasassignedtobutithasnosetter.`,\nthis\n);\n};\n}\nObject.defineProperty(target,key,sharedPropertyDefinition);\n}
首先定义了shouldCache表示是否需要缓存值。接着对userDef是函数或者对象分别处理。这里有一个sharedPropertyDefinition,我们来看它的定义:
//src/core/instance/state.js\nconstsharedPropertyDefinition={\nenumerable:true,\nconfigurable:true,\nget:noop,\nset:noop,\n};
sharedPropertyDefinition其实就是一个属性描述符。
回到defineComputed函数。如果userDef是函数的话,就会定义getter为调用createComputedGetter(key)的返回值。
?
因为shouldCache是true
?
而userDef是对象的话,非服务端渲染并且没有指定cache为false的话,getter也是调用createComputedGetter(key)的返回值,setter则为userDef.set或者为空。
所以defineComputed函数的作用就是定义getter和setter,并且在最后调用Object.defineProperty给计算属性添加getter/setter,当我们访问计算属性时就会触发这个getter。
?
对于计算属性的setter来说,实际上是很少用到的,除非我们在使用computed的时候指定了set函数。
?
无论是userDef是函数还是对象,最终都会调用createComputedGetter函数,我们来看createComputedGetter的定义:
functioncreateComputedGetter(key){\nreturnfunctioncomputedGetter(){\nconstwatcher=this._computedWatchers&&this._computedWatchers[key];\nif(watcher){\nif(watcher.dirty){\nwatcher.evaluate();\n}\nif(Dep.target){\nwatcher.depend();\n}\nreturnwatcher.value;\n}\n};\n}
?
我们知道访问计算属性时才会触发这个getter,对应就是computedGetter函数被执行。
?
computedGetter函数首先通过this._computedWatchers[key]拿到前面实例化组件时创建的computedWatcher并赋值给watcher。
?
在newWatcher时传入的第四个参数computedWatcherOptions的lazy为true,对应就是watcher的构造函数中的dirty为true。在computedGetter中,如果dirty为true(即依赖的值没有发生变化),就不会重新求值。相当于computed被缓存了。
?
接着有两个if判断,首先调用evaluate函数:
/**\n*Evaluatethevalueofthewatcher.\n*Thisonlygetscalledforlazywatchers.\n*/\nevaluate(){\nthis.value=this.get()\nthis.dirty=false\n}
首先调用this.get()将它的返回值赋值给this.value,来看get函数:
//src/core/observer/watcher.js\n/**\n*Evaluatethegetter,andre-collectdependencies.\n*/\nget(){\npushTarget(this)\nletvalue\nconstvm=this.vm\ntry{\nvalue=this.getter.call(vm,vm)\n}catch(e){\nif(this.user){\nhandleError(e,vm,`getterforwatcher&34;`)\n}else{\nthrowe\n}\n}finally{\n//&34;everypropertysotheyarealltrackedas\n//dependenciesfordeepwatching\nif(this.deep){\ntraverse(value)\n}\npopTarget()\nthis.cleanupDeps()\n}\nreturnvalue\n}
get函数第一步是调用pushTarget将computedWatcher传入:
//src/core/observer/dep.js\nexportfunctionpushTarget(target:?Watcher){\ntargetStack.push(target);\nDep.target=target;\n}
可以看到computedWatcher被push到targetStack同时将Dep.target置为computedWatcher。而Dep.target原来的值是渲染Watcher,因为正处于渲染阶段。回到get函数,接着就调用了this.getter。
回到evaluate函数:
evaluate(){\nthis.value=this.get()\nthis.dirty=false\n}
执行完get函数,将dirty置为false。
回到computedGetter函数,接着往下进入另一个if判断,执行了depend函数:
//src/core/observer/watcher.js\n/**\n*Dependonalldepscollectedbythiswatcher.\n*/\ndepend(){\nleti=this.deps.length\nwhile(i–){\nthis.deps[i].depend()\n}\n}
这里的逻辑就是让Dep.target也就是渲染Watcher订阅了this.dep也就是前面实例化computedWatcher时候创建的dep实例,渲染Watcher就被保存到this.dep的subs中。
在执行完evaluate和depend函数后,computedGetter函数最后将evaluate的返回值返回出去,也就是计算属性最终计算出来的值,这样页面就渲染出来了。
推荐Vue学习资料文章:
《聊聊昨晚尤雨溪现场针对Vue3.0Beta版本新特性知识点汇总》
《【新消息】Vue3.0Beta版本发布,你还学的动么?》
《Vue真是太好了壹万多字的Vue知识点超详细!》
《Vue+Koa从零打造一个H5页面可视化编辑器——Quark-h5》
《深入浅出Vue3跟着尤雨溪学TypeScript之Ref【实践】》
《手把手教你深入浅出vue-cli3升级vue-cli4的方法》
《Vue3.0Beta和React开发者分别杠上了》
《手把手教你用vuedragchart实现一个可以拖动/缩放的图表组件》
《Vue3尝鲜》
《总结Vue组件的通信》
《手把手让你成为更好的Vue.js开发人员的12个技巧和窍门【实践】》
《Vue开源项目TOP45》
《2020年,Vue受欢迎程度是否会超过React?》
《尤雨溪:Vue3.0的设计原则》
《使用vue实现HTML页面生成图片》
《实现全栈收银系统(Node+Vue)(上)》
《实现全栈收银系统(Node+Vue)(下)》
《vue引入原生高德地图》
《Vue合理配置WebSocket并实现群聊》
《多年vue项目实战经验汇总》
《vue之将echart封装为组件》
《基于Vue的两层吸顶踩坑总结》
《Vue插件总结【前端开发必备】》
《Vue开发必须知道的36个技巧【近1W字】》
《构建大型Vue.js项目的10条建议》
《深入理解vue中的slot与slot-scope》
《手把手教你Vue解析pdf(base64)转图片【实践】》
《使用vue+node搭建前端异常监控系统》
《推荐8个漂亮的vue.js进度条组件》
《基于Vue实现拖拽升级(九宫格拖拽)》
《手摸手,带你用vue撸后台系列二(登录权限篇)》
《手摸手,带你用vue撸后台系列三(实战篇)》
《前端框架用vue还是react?清晰对比两者差异》
《Vue组件间通信几种方式,你用哪种?【实践】》
《浅析React/Vue跨端渲染原理与实现》
《10个Vue开发技巧助力成为更好的工程师》
《手把手教你Vue之父子组件间通信实践讲解【props、$ref、$emit】》
《1W字长文+多图,带你了解vue的双向数据绑定源码实现》
《深入浅出Vue3的响应式和以前的区别到底在哪里?【实践】》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现》
《手把手教你D3.js实现数据可视化极速上手到Vue应用》
《吃透Vue项目开发实践|16个方面深入前端工程化开发技巧【上】》
《吃透Vue项目开发实践|16个方面深入前端工程化开发技巧【中】》
《吃透Vue项目开发实践|16个方面深入前端工程化开发技巧【下】》
《Vue3.0权限管理实现流程【实践】》
作者:前端森林
转发连接:https://mp.weixin.qq.com/s/4g8XCx0olYaqY78q3ravIA
关于vue企业网站源码分享,vue个人网站的介绍到此结束,希望对大家有所帮助。