1 Star 0 Fork 1

sjf/Vue.js 源码解析

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
MVue.js 8.03 KB
一键复制 编辑 原始数据 按行查看 历史
杨益林 提交于 2020-05-05 17:08 . VUE MVVMM
// 解析指令的方法
let CompierUtil = {
getValue(vm, expr) {
// 根据表达式获取值(school.name => alex,message => <h1>哈哈</h1>)
return expr.split('.').reduce((data, current) => {
return data[current]
}, vm.$data)
},
setValue(vm,expr,value) {
// 对表达式对应的属性重新赋值
expr.split('.').reduce((data, current, index, arr) => {
if (index === arr.length -1 ) {
return data[current] = value
}
return data[current]
}, vm.$data)
},
model(node, expr, vm) {
// 定义更新元素内容的方法
let fn = this.updater['modelUpdater']
// 根据表达式获取值
let value = this.getValue(vm, expr)
// 初始化视图渲染
fn(node, value)
// 对输入框(v-model)订阅
new Watcher(vm, expr, (newValue) => {
// 数据变化执行,将视图更新
fn(node, newValue)
})
// 监测视图的更新
node.addEventListener('input', (e) => {
let newValue = e.target.value;
// 将新值更新到数据中(vm.$data)
this.setValue(vm, expr, newValue)
})
},
html(node,expr,vm) {
// 定义更新元素内容的方法
let fn = this.updater['htmlUpdater']
// 根据表达式获取值
let value = this.getValue(vm, expr)
// 初始化视图渲染
fn(node, value)
// 对v-html订阅
new Watcher(vm, expr, (newValue) => {
// 数据变化执行,将视图更新
fn(node, newValue)
})
},
on(node,expr,vm,eventName) {
// 对on指令对应的元素进行事件监听 v-on:click="change"
node.addEventListener(eventName, (e) => {
// expr => change
// change 方法内的 this 指向 vm
vm[expr].call(vm,e)
})
},
// 获取文本框表达式对应的数据
getContentValue(vm,expr) {
let value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
// console.log(args) ["{{school.name}}", "school.name", 0, "{{school.name}}{{school.age}}"]
return this.getValue(vm, args[1])
})
return value
},
text(node, expr, vm) {
// 定义更新文本内容的方法
let fn = this.updater['textUpdater']
// 对每一文本进行数据替换
// expr => {{school.name}}{{school.age}}
let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
// 给每一个{{}}加入观察者
new Watcher(vm, args[1], () => {
// 对每一个{{}}所在的元素节点更新
fn(node, this.getContentValue(vm,expr) )
})
let value = this.getValue(vm, args[1])
return value
})
// 初始化视图渲染
fn(node, content)
},
updater: {
// 输入框更新方法
modelUpdater(node, value) {
node.value = value
},
// 文本更新方法
textUpdater(node, value) {
node.textContent = value
},
// 富文本更新方法
htmlUpdater(node,value) {
node.innerHTML = value
}
}
}
// 编译模版(核心代码)
class Compiper {
constructor(el, vm) {
// document.getElementById获取到的是动态的 document.querySelector获取的是静态的
// 获取根元素
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
// 将根元素获取到内存中
let fragment = this.nodeToFragment(this.el)
// 编译模版,将数据替换模版中的表达式({{school.name}}\v-model="school.name")
this.compier(fragment)
// 将替换后的内容塞到页面
this.el.appendChild(fragment)
}
compier(node) {
let childNode = node.childNodes; // 一层子元素,不包括儿子的儿子 类数组
[...childNode].forEach(child => {
if (this.isElementNode(child)) {
// 元素节点处理
this.compierElement(child)
// 递归将所有元素编译
this.compier(child)
} else {
// 文本节点处理
this.compierText(child)
}
})
}
// 判断属性是否为指令
isDirective(attrName) {
// startsWith es6的方法
return attrName.startsWith('v-')
}
// 编译元素
compierElement(node) {
// 获取元素属性
let attributes = node.attributes; // 类数组
[...attributes].forEach(attr => {
// console.log(attr); type=text v-model=school.name
let {
name,
value: expr
} = attr
// 判断属性是否为指令
if (this.isDirective(name)) { // v-model v-html v-bind v-on:click
let [, directive] = name.split('-') // directive:model\html\bind\on:click
// 如果是on:click进行分割
let [directiveName, eventName] = directive.split(':')
// 解析指令
CompierUtil[directiveName](node, expr, this.vm, eventName)
}
})
}
// 编译文本
compierText(node) {
// 获取文本节点的内容
let content = node.textContent;
if (/\{\{(.+?)\}\}/.test(content)) {
// 解析指令
CompierUtil['text'](node, content, this.vm)
}
}
nodeToFragment(node) {
// 创建文档碎片
let fragment = document.createDocumentFragment()
let firstChild
// 将模版添加到文档碎片这种
while (firstChild = node.firstChild) {
// appendChild可以将模版元素移到文档碎片中
fragment.appendChild(firstChild)
}
return fragment
}
isElementNode(node) {
// 判断是不是元素
//1.元素节点 2.属性节点 3.文本节点
return node.nodeType === 1
}
}
// 创建自己的vue类
class Vue {
constructor(options) {
// options:实例化时传进来的参数
this.$el = options.el;
this.$data = options.data;
let computed = options.computed;
let methods = options.methods;
// 判断根元素是否存在
if (this.$el) {
// 数据劫持,给每一个属性添加一个dep
new Observer(this.$data)
// 代理 computed 数据到this.$data上,以便可以直接通过this.xxx访问数据
for (let key in computed) {
Object.defineProperty(this.$data, key, {
get:() => {
return computed[key].call(this)
}
})
}
// 代理 methods 数据到实例上,以便可以直接通过this.xxx访问数据
for (let key in methods) {
Object.defineProperty(this, key, {
get:() => {
return methods[key]
}
})
}
// 将this.$data 上的数据代理到this上
this.proxyVm(this.$data)
// 编译模版数据
new Compiper(this.$el, this)
}
}
proxyVm(data) {
// 访问this.xxx 即 this.$data.xxx
for (let key in data) {
Object.defineProperty(this, key, {
// 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
configurable: false,
// 当且仅当该属性的 enumerable 为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
enumerable: false,
get(){
return data[key]
},
set(newValue){
data[key] = newValue
}
})
}
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/sujf/Vuejs_source_learn.git
git@gitee.com:sujf/Vuejs_source_learn.git
sujf
Vuejs_source_learn
Vue.js 源码解析
master

搜索帮助