Vue源码解析(1)
一.src入口代码
//index.js
import {initMixin} from './init'
import {lifecycleMixin} from './lifecycle'
import {renderMixin} from '../vdom'
function Vue(options){
this._init(options)
}
initMixin(Vue) // 只有执行了这个方法才能将_init方法挂载到Vue的原型上
lifecycleMixin(Vue) // 你虽然在原型上挂载了方法 但是你得执行
renderMixin(Vue) // 为了使_render方法生效
let vm = new Vue({
el:"#app",
data(){
return {
name:'zcl',
age:23,
teacher:[[1,2,3],{name:'zcl',age:22}],
info:{a:123}
}
}
})
// 测试用例
console.log(vm.teacher[0].push(4))
console.log(vm.teacher[1].name = 'zzz')
console.log(vm.info.a=2)
console.log(vm)
// init.js
import {initState} from '../vue/state'
import {compileToRenderFunction} from '../compiler'
import {mountComponent} from './lifecycle'
function initMixin(Vue){
Vue.prototype._init = function(options){ // 在Vue这个构造函数的原型上添加一个_init方法
const vm = this // this肯定是构造函数Vue的一个实例对象 例如vm
vm.$options = options // 将传进来的options参数 挂载到vm.$options上
initState(vm) // 把整个实例都传递进去 这样想获取实例上的任何参数都比较方便
if(vm.$options.el){
vm.$mount(vm.$options.el)
}
}
Vue.prototype.$mount = function(el){
const vm = this,
options = this.$options;
el = document.querySelector(el)
vm.$el = el
if(!options.render){
// 先找是否有render函数
let template = options.template
if(!template && el){
// 是否有template 如果没有只能获取html
template = el.outerHTML
}
const render = compileToRenderFunction(template)
options.render = render
}
mountComponent(vm) // 上树
}
}
export {initMixin}
//lifecycle.js
import {patch} from '../vdom/patch'
function mountComponent(vm){
vm._update(vm._render())
}
function lifecycleMixin(Vue){
Vue.prototype._update = function(vnode){
const vm = this;
patch(vm.$el,vnode) // 这里的vm.$el是原本html上的根节点
}
}
export {lifecycleMixin,mountComponent}
二.数据劫持
//state.js
import proxyData from "./proxy"
import observe from './observe'
function initState(vm){
var options = vm.$options
if(options.data){
initData(vm)
}
}
function initData(vm){
var data = vm.$options.data // 获取options选项中的data
data = vm._data = typeof data === 'function' ? data.call(vm) : data || {} // 将data挂载到vm._data上
// 这里因为Vue实例中的data会呈现两种形式 一种是函数和一种是对象 所以需要进行处理
for(var key in data){
proxyData(vm,'_data',key) // 进行数据代理 使数据的获取方式变成简单的vm.属性的方式
}
observe(vm._data) // 进行数据劫持
}
export {initState}
//proxy.js
function proxyData(vm,target,key){
Object.defineProperty(vm,key,{
// 因为我们在使用defineProperty之后
// 访问数据的时候 通过vm.age访问 是通过get方法返回数据的
get(){
return vm[target][key]
},
set(newValue){
vm[target][key] = newValue
}
})
}
export default proxyData
//observe.js
import Observer from './observer'
function observe(data){
// 这里其实是判断是否是深层次对象 如果是就递归到底
if(typeof data !== 'object' || data === null) return
return new Observer(data) // 这里才是真正的数据劫持操作
}
export default observe
//observer.js
import defineReactiveData from './defineReactiveData'
import observeArr from './observeArr'
import { arrMethods } from './array'
function Observer(data){
if(Array.isArray(data)){
// 如果data是数组形式
data.__proto__ = arrMethods // 只有真正的[]形式才添加扩展的数组方法 让其在使用这些方法的时候 进行重新劫持
observeArr(data) // 虽然是数组形式但是里面还有可能还有数组或{} 例如[[],[],{},{}]
}else{
// 如果data是{}形式
this.walk(data)
}
}
Observer.prototype.walk = function(data){
var keys = Object.keys(data)
for(var i=0;i<keys.length;i++){
var key = keys[i],
value = data[key]
defineReactiveData(data,key,value) // 对{}形式的数据进行劫持
}
}
export default Observer
//defineReactiveData.js
import defineReactiveData from './defineReactiveData'
import observeArr from './observeArr'
import { arrMethods } from './array'
function Observer(data){
if(Array.isArray(data)){
// 如果data是数组形式
data.__proto__ = arrMethods // 只有真正的[]形式才添加扩展的数组方法 让其在使用这些方法的时候 进行重新劫持
observeArr(data) // 虽然是数组形式但是里面还有可能还有数组或{} 例如[[],[],{},{}]
}else{
// 如果data是{}形式
this.walk(data)
}
}
Observer.prototype.walk = function(data){
var keys = Object.keys(data)
for(var i=0;i<keys.length;i++){
var key = keys[i],
value = data[key]
defineReactiveData(data,key,value) // 对{}形式的数据进行劫持
}
}
export default Observer
//config.js
var ARR_METHODS = [
// 以下方法会对数组的数据进行改变 我们要对新增的元素进行重新劫持
'push','pop','shift','unshift','splice','reverse','sort'
]
export {ARR_METHODS}
// observeArr.js
import observe from "./observe"
function observeArr(data){
for(var i=0;i<data.length;i++){
observe(data[i]) // 我只负责遍历出来 然后让observe去判断是否需要继续去深层递归
}
}
export default observeArr
//array.js
import {ARR_METHODS} from './config'
import observeArr from './observeArr'
var originArrMethods = Array.prototype,
arrMethods = Object.create(originArrMethods)
ARR_METHODS.map(function(m){
arrMethods[m] = function(){ // 扩展数组原型上的方法
var args = originArrMethods.slice.call(arguments), // 拷贝传递进来的参数也就是数据
rt = originArrMethods[m].apply(this,args) // 调用原型本身的方法
var newArr
switch(m){
case 'push':
case 'unshift':
newArr = args;
break;
case 'splice':
newArr = args.slice(2)
break;
default:
break;
}
newArr && observeArr(newArr)
return rt // 数组调用方法是时候返回的东西 返不返回都可以
}
})
export {arrMethods}
三.template->Ast->render
// index.js
import {parseHtmlToAst} from './astParser'
import {generate} from './generate'
function compileToRenderFunction(template){
const ast = parseHtmlToAst(template) // template -> ast
// ast -> render
const code = generate(ast)
const render = new Function(`with(this)return{${code}}`)
return render
}
export {compileToRenderFunction}
//astParser.js
// 匹配属性
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
// 匹配开始标签
const startTagOpen = new RegExp(`^<${qnameCapture}`)
// 匹配开始标签的结束标签
const startTagClose = /^\s*(\/?)>/
// 匹配真正的结束标签
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
function parseHtmlToAst(html){
let root,
currentParent,
text,
stack = [];
while(html){
let textEnd = html.indexOf('<') // 匹配<标签判断下面一段是标签还是文本
if(text === 0){ // 如果是标签
const startTagMatch = parseStartTag() // 是否能匹配到开始标签
if(startTagMatch){
//如果匹配到了开始标签
start(startTagMatch.tagName,startTagMatch.attrs) // 处理这段已匹配的标签
continue;
}
const endTagMatch = html.match(endTag) // 是否能匹配到真正的结束标签
if(endTagMatch){
advance(endTagMatch[0].length)
end() // 保存父子级关系
continue;
}
}
// 处理文本节点
if(textEnd>0){
text = html.substring(0,textEnd)
}
if(text){
advance(text.length)
chars(text)
}
}
function parseStartTag(){
const start = html.match(startTagOpen) // 匹配开始标签
let end,
attr;
if(start){
const match = {
tagName:start[1],
attrs:[]
}
advance(start[0].length) // 删除这段已匹配的标签
if(!(end=html.match(startTagClose)) && (attr=html.match(attribute))){
// 如果没有匹配到开始标签的结束标签但是匹配到了属性标签 处理属性
match.attrs.push({
name:attr[1],
value:attr[3] || attr[4] || attr[5]
})
advance(attr[0].length)
}
if(end){
// 如果匹配到了开始标签的结束标签
advance(end[0].length)
return match
}
}
}
function advance(length){
html = html.substring(length)
}
function start(tagName,attrs){
const element = createASTElement(tagName,attrs) // 组装AST树
if(!root){
root = element // 第一个节点作为根节点
}
currentParent = element // 保存当前节点为父亲节点
stack.push(element) // 保存当前节点
}
function end(){
const element = stack.pop()
currentParent = stack[stack.length-1]
if(currentParent){
element.parent = currentParent
currentParent.children.push(element)
}
}
function chars(text){
text = text.trim()
if(text.length>0){
currentParent.children.push({
type:3,
text
})
}
}
function createASTElement(tagName,attrs){
return{
tag:tagName,
attrs,
children:[],
parent,
type:1
}
}
return root;
}
export {parseHtmlToAst}
//generate.js
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;
function generate(el){
let children = getChildren(el)
return `_c('${el.tag}',${el.attrs.length>0?`${formatProps(el.attrs)}`:'undefined'}${children?`,${children}`:''})`
}
function getChildren(el){
// 获取孩子并将处理后的结果进行拼接
const children = el.children
if(children){
return children.map(child=>generateChild(child)).join(',')
}
}
function generateChild(node){
// 处理孩子
if(node.type===1){
// 如果是元素节点 则判断它是否还有孩子
return generate(node)
}else if(node.type === 3){
// 文本节点
let text = node.text
if(!defaultTagRE.test(text)){
// 纯文本
return `_v(${JSON.stringify(text)})`
}
// 处理带有差值表达式的问呗
let match,
index,
lastIndex = defaultTagRE.lastIndex = 0
let textArr = []
while(match = defaultTagRE.exec(text)){
index = match.index // 所以为插值表达式第一个{的位置
if(index>lastIndex){
// 纯文本部分
textArr.push(JSON.stringify(text.slice(lastIndex,index)))
}
// 插值表达式部分
textArr.push(`_s(${match[1].trim()})`)
lastIndex = index + match[0].length
}
if(lastIndex>text.length){
// 仍有纯文本
textArr.push(JSON.stringify(text.slice(lastIndex)))
}
return `_v(${textArr.join('+')})`
}
}
function formatProps(attrs){
// 处理属性
let attrStr = ""
for(var i=0;i<attrs.length;i++){
let attr = attrs[i]
if(attr.name === "style"){
let styleAttrs = {}
attr.value.split(';').map((styleAttr)=>{
let [key,value] = styleAttr.split(':')
styleAttrs[key] = value
})
attr.value = styleAttrs
}
attrStr += `${attr.name}:${JSON.stringify(attr.value)},`
}
return `{${attrStr.slice(0,-1)}}` // 去除多余的逗号
}
export {generate}
四.render->vnode->上树
//index.js
import {createElement,createTextVnode} from './vnode'
function renderMixin(Vue){
Vue.prototype._c = function(){
// 创建元素虚拟节点
return createElement(...arguments)
}
Vue.prototype._v = function(text){
// 创建文本虚拟节点
return createTextVnode(text)
}
Vue.prototype._s = function(value){
// 处理_s(name)
if(value === null) return
return typeof value === 'object' ? JSON.stringify(value) : value
}
Vue.prototype._render = function(){
render = this.$options.render
vnode = render() // 生成虚拟节点
return vnode
}
}
export {renderMixin}
//vnode.js
function vnode(tag,props,children,text){
// 组装虚拟节点
return {
tag,props,children,text
}
}
function createElement(tag,attrs={},...children){
// 返回元素节点 元素节点是没有文本的
return vnode(tag,attrs,children)
}
function createTextVnode(text){
return vnode(undefined,undefined,undefined,text)
}
export {createElement,createTextVnode}
//patch.js
function patch(oldNode,vNode){
let el = createElement(vNode),
parentElement = oldNode.parentNode; // body
parentElement.insertBefore(el,oldNode.nextSibling)
parentElement.removeChild(oldNode)
}
function createElement(vnode){
const {tag,props,children,text} = vnode;
if(typeof tag === 'string'){
// 处理标签
vnode.el = document.createElement(tag)
updateProps(vnode)
children.map((child)=>{
// 这已经是在真实DOM里面添加节点
vnode.el.appendChild(createElement(child))
})
}else{
vnode.el = document.createTextNode(text)
}
return vnode.el
}
function updateProps(vnode){
const el = vnode.el,
newProps = vnode.props || {}
for(let key in newProps){
if(key === 'style'){
// 如果key为style 则代表是深层次对象 需要在进行遍历
for(let sKey in newProps.style){
el.style[sKey] = newProps.style[sKey]
}
}else if(key === 'class'){
el.className = newProps[key]
}else{
el.setAttributes(key,newProps[key])
}
}
}
export {patch}
发表评论
还没有评论,快来抢沙发吧!