(关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导)html
Vue进阶系列汇总以下,欢迎阅读,欢迎加高级前端进阶群一块儿学习(文末)。前端
根据第一篇文章介绍的响应式原理,以下图所示。github
在初始化阶段,本质上发生在auto run
函数中,而后经过render
函数生成Virtual DOM
,view
根据Virtual DOM
生成Actual DOM
。由于render
函数依赖于页面上全部的数据data
,而且这些数据是响应式的,全部的数据做为组件render
函数的依赖。一旦这些数据有所改变,那么render
函数会被从新调用。面试
在更新阶段,render
函数会从新调用而且返回一个新的Virtual Dom
,新旧Virtual DOM
之间会进行比较,把diff以后的最小改动应用到Actual DOM
中。编程
Watcher负责收集依赖,清除依赖和通知依赖。在大型复杂的组件树结构下,因为采用了精确的依赖追踪系统,因此会避免组件的过分渲染。浏览器
Actual DOM 经过document.createElement('div')生成一个DOM节点。性能优化
document.createElement('div') // 浏览器原生对象(开销大) "[object HTMLDivElement]"
Virtual DOM 经过 vm.$createElement('div')生成一个JS对象,VDOM对象有一个表示div的tag属性,有一个包含了全部可能特性的data属性,可能还有一个包含更多虚拟节点的children列表。
vm.$createElement('div') // 纯JS对象(轻量) { tag: 'div', data: { attrs: {}, ...}, children: [] }
由于Virtual DOM的渲染逻辑和Actual DOM解耦了,因此有能力运行在的非浏览器环境中,这就是为何Virtual DOM出现以后混合开发开始流行的缘由,React Native 和 Weex可以实现的原理就是这个。
JSX和Template都是用于声明DOM和state之间关系的一种方式,在Vue中,Template是默认推荐的方式,可是也可使用JSX来作更灵活的事。
JSX更加动态化,对于使用编程语言是颇有帮助的,能够作任何事,可是动态化使得编译优化更加复杂和困难。
Template更加静态化而且对于表达式有更多约束,可是能够快速复用已经存在的模板,模板约束意味着能够在编译时作更多的性能优化,相对于JSX在编译时间上有着更多优点。
要求使用以下
<example :tags="['h1', 'h2', 'h3']"></example>
要求输出以下
<div> <h1>0</h1> <h2>1</h2> <h3>2</h3> </div>
上面这个需求能够经过render
函数来作,官方提供了createElement
函数用来生成模板。createElement('div', {}, [...])
可接受的参数以下。
// @returns {VNode} createElement( // {String | Object | Function} // 一个 HTML 标签字符串,组件选项对象,或者 // 解析上述任何一种的一个 async 异步函数。必需参数。 'div', // {Object} // 一个包含模板相关属性的数据对象 // 你能够在 template 中使用这些特性。可选参数。 { }, // {String | Array} // 子虚拟节点 (VNodes),由 `createElement()` 构建而成, // 也可使用字符串来生成“文本虚拟节点”。可选参数。 [ '先写一些文字', createElement('h1', '一则头条'), createElement(MyComponent, { props: { someProp: 'foobar' } }) ] )
知道了用法以后,就能够在render
中返回createElement
生成的虚拟节点,外层是div
,内层是三个锚点标题h1 h2 h3
,因此内层须要遍历下,使用两个createElement
就能够完成了。
一般使用h
做为createElement
的别名,这是Vue
的通用惯例,也是JSX
的要求。
实现以下
<!--引用--> <script src="../node_modules/vue/dist/vue.js"></script> <!--定义template --> <div id="app"> <example :tags="['h1', 'h2', 'h3']"></example> </div> <script> // 定义example组件 Vue.component('example', { props: ['tags'], render (h) { // 第二个参数是一个包含模板相关属性的数据对象,可选参数 // 子虚拟节点(VNodes)参数能够传入字符串或者数字, // 经过createElement生成,可选参数 return h('div', this.tags.map((tag, i) => h(tag, i))) } }) // 实例化 new Vue({ el: '#app' }) </script>
<example>
组件要求以下
Foo
组件渲染<div>foo</div>
,实现一个Bar
组件渲染<div>bar</div>
。<example>
组件,根据属性ok
动态渲染Foo
组件或者Bar
组件。若是属性ok
是true
,那么最终的渲染应该是<div>foo</div>
。ok
,经过这个属性让<example>
在Foo
或者Bar
之间切换。根据上面的要求,在模板中调用<example>
组件,而后定义<button>
组件,同时绑定属性ok
。
实现以下
<!--引用--> <script src="../node_modules/vue/dist/vue.js"></script> <!--定义template --> <div id="app"> <!--绑定属性ok--> <example :ok="ok"></example> <!--绑定点击事件--> <button @click="ok = !ok">toggle</button> </div> <script> // 定义Foo const Foo = { render (h) { return h('div', 'foo') } } // 定义Bar const Bar = { render (h) { return h('div', 'bar') } } // 定义example组件 // 根据ok属性动态切换 Vue.component('example', { props: ['ok'], render (h) { return h(this.ok ? Foo : Bar) } }) // 实例化 new Vue({ el: '#app', data: { ok: true } }) </script>
要求以下
withAvatarURL
函数,要求传入一个带有url
属性的组件,返回一个接收username
属性的高阶组件,这个高阶组件主要负责获取相应的头像URL。http://via.placeholder.com/200x200
传递给内部组件。例子以下
const SmartAvatar = withAvatarURL(Avatar) // 使用这个方式 <smart-avatar username="vuejs"></smart-avatar> // 替换下面的方式 <avatar url="/path/to/image.png"></avatar>
withAvatarURL
函数返回一个对象,接收username
属性,在生命周期created
获取头像URL。Avatar
对象接收src
属性,src
的内容从withAvatarURL
中获取,而后展现在<img>上。实例化的时候,传入新定义的组件名SmartAvatar
。
实现以下
<!--引用--> <script src="../node_modules/vue/dist/vue.js"></script> <!--定义template--> <div id="app"> <smart-avatar username="vuejs"></smart-avatar> </div> <script> // 获取头像URL function fetchURL (username, cb) { setTimeout(() => { // 获取头像并回传 cb('https://avatars3.githubusercontent.com/u/6128107?v=4&s=200') }, 500) } // 传递的InnerComponent const Avatar = { props: ['src'], template: `<img :src="src">` } function withAvatarURL (InnerComponent) { return { props: ['username'], inheritAttrs: false, // 2.4 only,组件将不会把未被注册的props呈现为普通的HTML属性 data () { return { url: null } }, created () { // 获取头像URL并回传给this.url fetchURL(this.username, url => { this.url = url }) }, render (h) { return h(InnerComponent, { attrs: this.$attrs, // 2.4 only,获取到没有使用的注册属性 props: { src: this.url || 'http://via.placeholder.com/200x200' } }) } } } const SmartAvatar = withAvatarURL(Avatar) // 实例化,新构造组件名为SmartAvatar或smart-avatar new Vue({ el: '#app', components: { SmartAvatar } }) </script>
本文内容参考自VUE做者尤大的付费视频
Vue官网之渲染函数 & JSX
本人Github连接以下,欢迎各位Star
http://github.com/yygmind/blog
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
若是你想加群讨论每期面试知识点,公众号回复[加群]便可