前言:最近由于疫情原因,导致久久未开学,在家本着闲着也是闲着的心情,就开始研究起了vue的源码。看了一下,才发现所谓的源码并没有想象的那么难,就是代码量比较多,都是一些基础的代码,难就难在作者的设计思想,挺佩服作者的设计思想的。话不多说,直接进入正题: 解释:因为vue在渲染页面的时候,需要把 元素节点:例如: 明确一下我们需要的东西: 需要灵活的使用操作字符串: 需要理解es6中的结构赋值: 需要灵活的使用数组的reduce方法: 我们需要的的知识点: 我在这说一下replace()和正则表达式中的括号的配合使用: 结果: 有些时候,我们有以下需求,我们需要获取<>包裹中的内容,即上面的例子的span。我们就可以结合正则表达式的括号来获取了。(在我们需要的地方加个括号) 结果: vue渲染实现js代码:
vue渲染页面的两种方式:
第一种:通过{{}}的形式
第二种:通过指令的形式:如v-text、v-html、v-model等
实现的细节:
1.利用文档碎片,减少页面的重绘和回流
{{}}
和指令替换成对面的变量值,这样就需要进行大量的dom的操作,很消耗性能。所以vue就把要渲染的区域先获取过来(这就是为什么vue要让我们申明一个区域),把该区域放进文档碎片中,对该区域渲染完后再放回页面中,这样我们整个过程就操作了两次dom,大大的优化了浏览器性能。(文档碎片:在我看来,就是用来存储dom元素的变量,该变量的所有的属性和方法都和dom元素的一样。)2.利用元素节点和文本节点去区分{{}}和指令
<h1 v-text='msg'>2121</h1>
,这就是一个元素节点。
文本节点:上面那个例子的2121
,这就是一个文本节点。
解释:当我们知道元素节点和文本节点的基本概念,我们就可以这么想了:{{}}
是在文本节点中,指令一般以属性的形式放在元素节点中的。我们使用dom元素.nodeType
属性来区分它是元素节点还是文本节点。区分出来就可以进行对应得渲染。3.渲染指令
v-text=’msg‘
中的msg变量名。
split()
方法:例如:我们需要在v-text
中分离出text
,我们就可以使用'v-text'.split('-')
,把字符串'v-text'
分割成数组['v','text']
这种形式。
dom.attributes
的属性,就元素节点的所有属性都获取过来,但是你要知道,attributes
这个属性返回的是一个伪数组,由于我们需要遍历这个数组,所以要把这个数组转成真数组,使用es6的解构赋值:[...dom.attributes]
。
msg
这样一层的,有时候会遇到person,name
这样两层的。这个时候如果我们直接vm.$data[变量名]
(vm.$data
是用户传过来的data),获取
msg是获取的到的,但是
person.name`是获取不到的。split('.')
以点的形式把获取到的变量名弄成数组,利用reduce方法去一层一层的获取。3.渲染页面上的{{}}
replace()
方法
注:我们在使用replace方法的时候,我们一般都认为第一个参数是要替换的字符或者正则表达式,第二个参数是换成什么的字符串。其实第二个参数,还可以写成函数的形式。我举个例子: let span = '<span>123abc' span.replace(/<.+?>/g, (...args) => { console.log(args) })
我们会发现第二参数写成函数的形式的时候,这个函数有个默认参数,这个参数我们扩展运算符来接收,不限制参数的个数。我们发现:
let span = '<span>123abc' span.replace(/<(.+?)>/g, (...args) => { console.log(args) })
正则表达式加了括号之后,我们获取到的参数就多了一个,获取到的args[1]就是我们需要的<>包裹的内容。
如果你能理解上面的知识点,你就可以看的懂渲染的代码了:
html代码:<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title></title> </head> <body> <div id="app"> <h2>{{person.name}} -- {{person.age}}</h2> <h3>{{person.fav}}</h3> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <h3>{{msg}}</h3> <div v-text='msg'></div> <div v-text='person.fav'></div> <div v-html='htmlStr'></div> <div v-html='person.fav'></div> <input type="text" v-model='person.name'> <button v-on:click='add'>v-on</button> <button @click='add'>@click</button> <p v-text='af'></p> </div> </body> <script src="./My.js"></script> <script> let vm = new Vue({ el: '#app', data: { person: { name: '小明', age: 15, fav: '女' }, msg: '你好', htmlStr: '<h3>html字符串</h3>' }, methods: { add() { console.log('add方法调用了') } } }) </script> </html>
const compileUtil = { getVal (arg, vm) { // 使用reduce一层一层的取.如果你直接vm.$data[arg],其中arg是person.fav这样的话,取不到的。一层就可以取到。 return arg.split('.').reduce((data, currentData) => { return data[currentData] }, vm.$data) }, on (node, methodName, vm, eventName) { // 1.获取方法 // 2.添加事件 let method = vm.options.methods && vm.options.methods[methodName] node.addEventListener(eventName, method) }, text (node, arg, vm) { // 1.获取值 // 2.更新页面 let textContent if (arg.indexOf('{{') !== -1) { // 正则表达式中的()是为了标识子字符串,replace函数的第二个参数用得到。 textContent = arg.replace(/{{(.+?)}}/g, (...args) => { return this.getVal(args[1], vm) }) } else { textContent = this.getVal(arg, vm) } node.textContent = textContent }, html (node, arg, vm) { // 1.获取值 // 2.更新页面 const htmlContent = this.getVal(arg, vm) node.innerHTML = htmlContent }, model (node, arg, vm) { // 1.获取值 // 2.更新页面 const inputVal = this.getVal(arg, vm) node.value = inputVal } } class Compile { constructor(el, vm) { this.el = el.nodeType === 1 ? el : document.querySelector(el) this.vm = vm // 1.创建一个文档碎片,方便操作document,减少性能消耗 this.f = this.createFragment(this.el) // 2.编译模板 this.compile(this.f) // 3.将文档碎片放回容器中 this.el.appendChild(this.f) } // 创建文档碎片 createFragment (node) { let f = document.createDocumentFragment() let firstChild // 循环遍历根元素的子节点添加到文档碎片中 while (firstChild = node.firstChild) { // 注:appendChild()方法会把已存在的节点删除 f.appendChild(firstChild) } return f } compile (node) { // 编译文档步骤: // 1.遍历每一个子节点 // 2.编译指令 // 3.编译文本节点中的'{{}}' const childNodes = node.childNodes // 遍历每一个子节点 childNodes.forEach(item => { // 元素节点 if (item.nodeType === 1) { // 递归遍历每一个子节点 this.compile(item) // 编译属性上的指令:v-text、v-html、v-model、v-on、@等等 this.compileDir(item) } else { // 文本节点 // 编译文本中的{{}} this.compileText(item) } }) } // 编译指令 compileDir (node) { // node.attributes是伪数组,使用es6的解构赋值弄成数组 const attributes = [...node.attributes] attributes.forEach(item => { // item是个object属性,有name和value,所以我们再结构赋值。item:{name:v-text,value: msg} let { name, value } = item if (name.startsWith('v-')) { // 处理v-开头的指令。假如指令是v-on:click,下面的值则是: let directive = name.split('-')[1] // on:click let dirName = directive.split(':')[0] // on let eventName = directive.split(':')[1] // click compileUtil[dirName](node, value, this.vm, eventName) } else if (name.startsWith('@')) { // @click let eventName = name.split('@')[1] // click compileUtil.on(node, value, this.vm, eventName) } }) } // 编译{{}} compileText (node) { // 获取文本 const text = node.textContent if ((/{{.+?}}/).test(text)) { // 匹配有{{}}的字符串 compileUtil.text(node, text, this.vm) } } } class Vue { constructor(options) { // 绑定数据 this.$el = options.el this.$data = options.data this.options = options if (this.$el) { // 2.实现编译器 new Compile(this.$el, this) } } }
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算