class Compiler{
constructor(el,vm){
// 判断el属性 是不是 一个元素, 如果不是就获取
this.el = this.isElementNode(el)?el:document.querySelector(el);
// console.log(this.el); this.vm = vm; // 把当前节点放到内存中
let fragment = this.node2fragment(this.el);
// console.log(fragment,"fragment") // 把节点中的内容进行替换 // 用数据编译模板
this.compile(fragment) // 把结果再塞到页面中
this.el.appendChild(fragment); } isDirective(attrName){
return attrName.startsWith('v-');
}
// 编译元素的
compileElement(node){
let attributes = node.attributes;
[...attributes].forEach(attr=>{
let {name,value:expr} = attr;
// console.log(name,value,"attr");
// 判断是不是指令
if(this.isDirective(name)){
let [,directive] = name.split("-"); let [directiveName,eventName] = directive.split(":"); //v-on:click
CompileUtils[directiveName](node,expr,this.vm, eventName);
}
})
} // 编译文本的
compileText(node){
let content = node.textContent;
// console.log(content)
if(/\{\{(.+?)\}\}/.test(content)){
console.log(content,"文本"); //找到所有文本
CompileUtils['text'](node,content,this.vm);
}
} // 用来编译内存中的dom节点 核心编译方法
compile(node){
let childNodes = node.childNodes;
[...childNodes].forEach(child=>{
if(this.isElementNode(child)){
this.compileElement(child);
// console.log("element",child);
// 如果是元素 需要再次编译子节点
this.compile(child);
}else{
this.compileText(child);
// console.log("text",child)
}
})
// console.log(childNodes)
} // 把节点移动到内存中
node2fragment(node){
// 创建一个文档碎片
let fragment = document.createDocumentFragment();
let firstChild;
while(firstChild = node.firstChild){
// appendChild 具有移动性
fragment.appendChild(firstChild);
}
return fragment;
}
isElementNode(node){ //是不是元素节点
return node.nodeType === 1;
}
} CompileUtils = {
// 根据表达式取到对应的数据
getVal(vm,expr){
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){
data[current] = value;
}
return data[current];
},vm.$data)
},
// 解析v-model指令
model(node,expr,vm){ //node是节点 expr 是表达式 vm是实例
// 给输入框赋予value node.value = xxx
let fn = this.updater['modelUpdater']; // 给输入框加一个观察者,如果数据更新了 会触发此方法,会拿新值给输入框赋值
new Watcher(vm,expr,(newVal)=>{
fn(node,newVal);
}); node.addEventListener('input',(e)=>{
let value = e.target.value; //获取用户输入的内容
this.setValue(vm,expr,value);
})
let value = this.getVal(vm,expr);
fn(node,value);
},
html(node,expr,vm){
let fn = this.updater['htmlUpdater']; // 给输入框加一个观察者,如果数据更新了 会触发此方法,会拿新值给输入框赋值
new Watcher(vm,expr,(newVal)=>{
fn(node,newVal);
});
let value = this.getVal(vm,expr);
fn(node,value);
}, on(node,expr,vm,eventName){ //expr 就是 change
node.addEventListener(eventName,(e)=>{
vm[expr].call(vm,e); //这里需要把 methods 绑定到 vm上
})
}, getContentValue(vm,expr){
// 遍历表达式 将内容重新替换成完整的内容
return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getVal(vm,args[1]);
})
},
text(node,expr,vm){
let fn = this.updater['textUpdater'];
let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{ // 给表达式每个{{}} 都加上观察者
new Watcher(vm,args[1],()=>{
fn(node,this.getContentValue(vm,expr)); //返回一个全的字符串
})
return this.getVal(vm,args[1]);
});
fn(node,content);
},
updater:{
// 把数据插入到节点中
modelUpdater(node,value){
node.value = value;
},
htmlUpdater(node,value){ //xss攻击
node.innerHTML = value;
},
//处理文本节点
textUpdater(node,value){
node.textContent = value;
}
}, } // 实现数据劫持的功能
class Observer{
constructor(data){
console.log(data);
this.observe(data);
}
observe(data){
if(data && typeof data == "object"){
// 如果是对象
for(let key in data){
this.defineReactive(data,key,data[key]);
}
}
} defineReactive(obj,key,value){
this.observe(value);
let dep = new Dep(); //给每个属性都加上 发布订阅的功能
Object.defineProperty(obj,key,{
get(){ // 创建watcher时 会取到对应的内容, 并且把watcher放到了全局上
Dep.target && dep.subs.push(Dep.target);
return value;
},
set:(newVal)=>{
if(newVal != value){
this.observe(newVal);
value = newVal;
dep.notify();
}
}
})
}
} // 观察者 (发布订阅)
class Dep{
constructor() {
this.subs = []; //存放所有的watcher
}
// 订阅
addSub(watcher){ //添加watcher
this.subs.push(watcher);
}
// 发布
notify(){
this.subs.forEach(watcher=>watcher.update());
} }
class Watcher{
constructor(vm,expr,cb){
this.vm = vm;
this.expr = expr;
this.cb = cb;
// 默认先存放一个老值
this.oldValue = this.get();
} get(){
Dep.target = this; //先把自己放在Dep.target上 // 取值,把这个观察者和数据关联起来
let value = CompileUtils.getVal(this.vm,this.expr);
Dep.target = null; //不取消, 任何值取值 都会添加 watcher
return value;
}
update(){ //更新操作,数据变化后 会调用观察者的update方法
let newVal = CompileUtils.getVal(this.vm,this.expr);
if(newVal !== this.oldValue){
this.cb(newVal);
}
}
} class Vue{
constructor(options){
this.$el = options.el;
this.$data = options.data; let computed = options.computed; let methods = options.methods; // 这个根元素 存在编译模板
if(this.$el){ // 把数据全部转化成用Object.defineProperty来定义.
new Observer(this.$data); // 计算属性
for(let key in computed){ //有依赖关系
Object.defineProperty(this.$data,key,{
get:() => {
return computed[key].call(this);
}
})
} // 绑定到vm上 进行代理
for(let key in methods){
Object.defineProperty(this,key,{
get(){
return methods[key];
}
})
} //把数据获取操作 vm上的取值操作 都代理到 vm.$data
this.proxyVm(this.$data); console.log(this.$data,"$data")
new Compiler(this.$el,this);
}
}
// 实现可以通过vm取到对应的data中的值
proxyVm(data){
for(let key in data){
Object.defineProperty(this,key,{
get(){
return data[key]; //进行转化操作
},
set(newVal){ //设置代理方法
data[key] = newVal;
}
})
}
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<input v-model="school.name" />
{{school.name}}
<div>{{school.name}}</div>
<div>teacher:{{teacher.name}}</div>
<!-- 如果数据不变化 视图就不会刷新 -->
{{getNewName}} <button v-on:click="change">更新</button> <div v-html="message"></div>
</div>
<script src="./vue.js"></script>
<script>
let vm = new Vue({
el:'#app',
data:{
school:{
name:"chance",
age:30
},
teacher:{
name:'123'
},
message:"<h1>welcome</h1>"
},
methods:{
change(){
this.school.name = "希望小学"
}
},
computed:{
getNewName(){
return this.school.name + "附属学校"
}
}
})
</script>
</body>
</html>

学习手写vue,理解原理的更多相关文章

  1. 剖析手写Vue,你也可以手写一个MVVM框架

    剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...

  2. 手写 Vue 系列 之 Vue1.x

    前言 前面我们用 12 篇文章详细讲解了 Vue2 的框架源码.接下来我们就开始手写 Vue 系列,写一个自己的 Vue 框架,用最简单的代码实现 Vue 的核心功能,进一步理解 Vue 核心原理. ...

  3. 手写 Vue 系列 之 从 Vue1 升级到 Vue2

    前言 上一篇文章 手写 Vue 系列 之 Vue1.x 带大家从零开始实现了 Vue1 的核心原理,包括如下功能: 数据响应式拦截 普通对象 数组 数据响应式更新 依赖收集 Dep Watcher 编 ...

  4. 30个类手写Spring核心原理之动态数据源切换(8)

    本文节选自<Spring 5核心原理> 阅读本文之前,请先阅读以下内容: 30个类手写Spring核心原理之自定义ORM(上)(6) 30个类手写Spring核心原理之自定义ORM(下)( ...

  5. 手写webpack核心原理,再也不怕面试官问我webpack原理

    手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...

  6. 推荐使用并手写实现redux-actions原理

    @ 目录 一.前言 二.介绍 2.1 创建action 2.2 reducer 2.3 触发action 三. 认识与手写createAction() 3.1 用法 3.2 原理实现 四.认识hand ...

  7. 30个类手写Spring核心原理之AOP代码织入(5)

    本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...

  8. Go组件学习——手写连接池并没有那么简单

    1.背景 前段时间在看gorm,发现gorm是复用database/sql的连接池. 于是翻了下database/sql的数据库连接池的代码实现,看完代码,好像也不是很复杂,但是总觉得理解不够深刻,于 ...

  9. 30个类手写Spring核心原理之环境准备(1)

    本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...

随机推荐

  1. windows 下OPENSSL 生成秘钥和公钥的方法

    1. 生成原始 RSA私钥文件 private_key.pem openssl genrsa -out private_key.pem 1024 2. 将原始 RSA私钥转换为 pkcs8格式 ope ...

  2. 事理学神器PDCA

    做事情都按PDCA循环来做,基本就是一个靠谱的人. 这个方法论其实也符合架构师的思维中的分治理论.把大事拆分成一件件小事,并把小事做好. Plan Do Check Action

  3. vc++中播放声音

    http://www.cnblogs.com/xuemaxiongfeng/articles/2462560.html http://bbs.csdn.net/topics/390729457 htt ...

  4. 【ARM-Linux开发】 pkg-config的用法

    pkg-config 是一个提供从源代码中编译软件时查询已安装的库时使用的统一接口的计算机软件.pkg-config原本是设计用于Linux的,但现在在各个版本的BSD.windows.Mac OS ...

  5. mui 关键词查询

    <div class="pop-schwrap"> <div class="ui-scrollview"> <div class= ...

  6. 《xv6 Appendices: PC Hardware and Boot loader》学习笔记

    MIT 6.828 Lecture 2的preparation要求阅读<xv6 book>的附录部分,附录包括"PC Hardware"和"The Boot ...

  7. [LuoguP2124]奶牛美容_bfs_floyd_曼哈顿距离

    奶牛美容 题目链接:https://www.luogu.org/problem/P2124 数据范围:略. 题解: 发现数据范围只有$50$,显然可以直接$bfs$求出联通块,$floyd$求出相邻两 ...

  8. AndroidStudio下载安装教程(图文教程)

    场景 Android Studio 中文社区: http://www.android-studio.org/ 下载安装包,这里选择64位Windows 等待下载完成. 注: 博客: https://b ...

  9. 南昌网络赛J. Distance on the tree 树链剖分

    Distance on the tree 题目链接 https://nanti.jisuanke.com/t/38229 Describe DSM(Data Structure Master) onc ...

  10. 第九章 MIZ702 ZYNQ片上ADC的使用

      9.0难度系数★☆☆☆☆☆☆ 9.1实验概述 这次借助zynq的内嵌的XADC来采集zynq内部的一些参数: •VCCINT:内部PL核心电压 •VCCAUX:辅助PL电压 •VREFP:XADC ...