<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{name}}</div>
<span>{{age}}</span>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
data(){
return {
name:'zs',
age:12,
addres:['北京市','昌平区','回龙观'],
hobby:['a',{b:1}],
}
},
template:`<div>123</div>` // template渲染模板
el:'#app' // 将数据解析到el元素上
})
vm.$mount('#app') // 实现挂载
</script>
</body>
</html>
下面我们主要用来解析模板,同时将DOM结构的模板通过outerHTML的方法,转义为字符串进行编译。
?
import {observe} from './observe/index' // 这里先引入后期再下面要创建的方法
export const initMiXin = (Vue){
Vue.prototype._init(options) { // 这里接收的是src/index.js中传递的参数
const vm = this // 这里的this是Vue
vm.$options = options // 把数据挂载在vm.$options的属性上面
initState(vm) // 初始化数据
}
}
function initState(vm){
const ops = vm.$options
if(ops.data){ // 如果有data这个属性
initData(vm) // 初始化Data
}
// 这里增加处理模板的思路
if(ops.el){ ++++++++++++ 处理el模板的数据
vm.$mount(ops.el) // 将el进行挂载
}
}
Vue.prototype.$mount = function (el) { +++++ 原型上创建$mount的方法 进行模板数据的处理
const vm = this // this执行创建Vue构造函数的实例对象
el= document.queryselector(el) // 获取元素
let ops = vm.$options
if(!ops.render) { // 不存在render渲染模板
let template;
if(!ops.template && el){ // options参数中只存在 el
template = el.outerHTML // outerHTML 这个方法是将DOM节点转化为了字符串来显示
}else {
if(el) {
template = ops.template // 这是由template渲染的模板
}
}
if(template) {
const render = compileToFunction(template) // 对字符串的模板进行编译
ops.render = render // 往$options上面挂载一个render方法
}
}
// 这里最终可以获得ops.render的方法
}
function initData(vm){
let data = vm.$options.data
data = typeof data === 'function'? data() : data // 判断data类型如果是函数的话就执行
vm._data= data //再往Vue上面挂载一个_data的属性
observe(data) // 这里对数据进行劫持
}
// 标签名 a-aaa
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
// 命名空间标签 aa:aa-xxx
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
// 开始标签-捕获标签名
const startTagOpen = new RegExp(`^<${qnameCapture}`);
// 结束标签-匹配标签结尾的 </div>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
// 匹配属性
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// 匹配标签结束的 >
const startTagClose = /^\s*(\/?)>/;
// 匹配 {{ }} 表达式
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;
// 在这里 我们可以接受到字符串的DOM结构 然后进行编译
export const compileToFunction(template){
// 这里主要是转template转化为ast语法数
let ast = parseHTML(template) // 通过parseHTML方法转为ast语法数
// 然后生成render方法 返回值是虚拟DOM
}
// parseHTML方法创建ast语法树
function parseHTML(html) {
// 第三步 创建删除html字符串的方法
advance(num){
html = html.substring(num) // 给删除<div字符串的DOM结构重新赋值
}
// 第二步 创建parseStartTag方法
parseStartTag(){
const start = html.match(startTagOpen) // 查找标签
if(start) {
const match = { tagName:start[1], attrs:[] } // tagName标签名 attrs属性值
// 这里得到的match是一个数组 ['<div','div']
advance(start[0].length) // 调用删除的方法
let attr, end;
// 下面是继续遍历剩余字符串的DOM结构 符合的追加到match这个对象中去 end代表">"
// attr代表属性
while(!(end=html.match(startTagClose))&&(attr = html.match(attribute))){
advance(attr[0].length)
match.attrs.push({name:attr[1],value:attr[3]||attr[4] || sttr[5]} )
}
if(end) {
advance(end[0].length)
}
return match
}
return false // 如果不是开始标签就停止
}
// 第一步 循环html字符串DOM结构
while(html){
let textEnd = html.indexof('<') // 如果索引是0 则说明是个标签
if(textEnd == 0) { // 索引为0 说明开始标签为 <
// 这里的返回值已经是 {tagName:'div', attrs:{id:app}}
const startTagMatch = parseStartTag()
if(startTagMatch){continue} // 上面的结果执行完有返回值 然后继续下面的执行
let endTagMatch = html.match(endTag) // 寻找结束标签
if(endTagMatch){
advance(endTagMatch[0].length) // 清除结束标签
continue
}
}
if(textEnd > 0) { // 索引大于0 的
let text = html.substring(0,textEnd) // 解析文本内容 中间内容为文本内容
if(text){
chars(text) // 处理文本标签**** 我们下面继续
advance(text.length) // 清除文本内容
}
}
}
}
?转化为ast语法树
// 此代码依旧在compile/index.js 文件中执行
function parseHTML(html){
const ELEMENR_TYPE = 1
const TEXT_TYPE = 3
const stack = [] // 存放元素
let currentParent; // 指向栈中的最后一个
let root;
// 最终转化为ast语法树
function createASTElement(tag,attrs){
return {
tag,
type:ELEMENT_TYPE,
children:[],
attrs,
parent:null
}
}
function start(tag,attrs){ // 开始
let node = createASTElement(tag,attrs) // 创建一个ast节点
if(!root){
root = node // 如果为空则为数的跟节点
}
if(currentparent){
node.parent = currentparent
currentparent.children.push(node)
}
stack.push(node) // 存放元素里面
currentParent = node // 栈中的最后一个元素
}
function chars(text){ // 处理文本
text = text.replace(/\s/g,'') // 去掉空格的
text && currentParent.children.push({
type:TEXT_TYPE,
text,
parent:currentParent
})
}
function end(tag){ // 结束
let node = stack.pop() // 删除最后一个元素
currentParent = stack[stack.length-1] // 最后一个元素
}
return root // 最后将ast语法树返回出去
}
?