自己实现 一个 Vue框架,包含了Vue的核心原理

vue

Vue工作机制

  1. vue工作机制
  2. Vue响应式的原理
  3. 依赖收集与追踪
  4. 编译compile

html:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="utf-8">

</head>

<body>

<div id="app">

<!-- 插值绑定 -->

<p>{{name}}</p>

<!-- 指令系统 -->

<p k-text="name"></p>

<p>{{age}}</p>

<p>{{doubleAge}}</p>

<!-- 双向绑定 -->

<input type="text" k-model="name">

<!-- 事件处理 -->

<button @click="changeName">呵呵</button>

<!-- html内容解析 -->

<div k-html="html"></div>

</div>

<script src='./compile.js'></script>

<script src='./kvue.js'></script>

<script>

new KVue({   

el: '#app',

data: {    

name: "I am test.",

    age: 17,

    html: '<button>这是一个按钮</button>'  

},

created() {    

console.log('开始啦');    

setTimeout(() => {     

this.name = '我是异步事件'   

}, 1500)  

},

methods: {    

changeName() {     

this.name = '哈喽,哈哈哈哈';     

this.age = 20;     

this.id = 'xx' ;    

console.log(1, this)   ;

}  

})

</script>

</body>

</html>

kvue.js

/*

* @Author: liguowei01

* @Date: 2019-12-31 11:17:12

* @Last Modified by: liguowei01

* @Last Modified time: 2020-01-02 17:55:53

*/

// 用法: new KVue({data:{...}})

class KVue {

constructor(options) {

this.$options = options;

//数据的响应化

this.$data = options.data;

this.observe(this.$data); //观察数据

//模拟一下watcher创建

// new Watcher(); //实例一

// this.$data.test;

// new Watcher(); //实例二 实例二不等于实例一

// this.$data.foo.bar;

new Compile(options.el, this);

//生命周期函数

//created

if (options.created) {

//options.created(); //本来是这样执行,下面的调用call()方法,为函数指定执行作用域

options.created.call(this); //这样就可以在created函数中用this了。

}

}

observe(obj) {

//检验数据类型必须是对象

if (!obj || typeof obj !== 'object') {

return;

}

//遍历该对象

Object.keys(obj).forEach(key => {

this.defineReactive(obj, key, obj[key]);

//代理配置项 data 中的属性到vue实例上

this.proxyData(key);

})

}

//数据响应化(数据劫持)

defineReactive(obj, key, val) {

this.observe(val); //递归解决数据的嵌套

const dep = new Dep();

Object.defineProperty(obj, key, {

get() {

Dep.target && dep.addDep(Dep.target);

return val

},

set(newVal) {

if (newVal === val) {

return;

}

val = newVal;

// console.log(`${key}属性更新了:${newVal}`)

dep.notify();

}

})

}

//代理函数()

proxyData(key) {

Object.defineProperty(this, key, {

get() {

return this.$data[key];

},

set(newVal) {

this.$data[key] = newVal;

}

})

}

}

//vue 数据绑定的原理是什么?

//首先,把vue选项里的data中的每个属性都利用了Object.defineProperty()定义了一个属性,

//都定义了get和set这样的话让我们的机会监听数据和变化,

//当这些属性发生变化时,我们可以通知那些需要更新的地方去更新

//依赖搜集

//Dep: 用来管理 Watcher

class Dep {

constructor() {

//这里存在若干依赖(watcher,一个watcher对应一个属性)

this.deps = [];

}

//添加依赖的方法,搜集依赖时,往这里面放东西

addDep(dep) {

this.deps.push(dep)

}

//通知方法,用来通知所有的watcher 去更新

notify() {

this.deps.forEach(dep => dep.updata())

}

}

//Watcher 用来做具体更新的对象

class Watcher {

constructor(vm, key, cb) {

this.vm = vm;

this.key = key;

this.cb = cb;

//将当前watcher实例指定到Dep静态属性target

Dep.target = this;

this.vm[this.key]; //触发getter,添加依赖

Dep.target = null;

}

updata() {

// console.log('属性更新了');

this.cb.call(this.vm, this.vm[this.key])

}

}

Compile.js

/*

* @Author: liguowei01

* @Date: 2020-01-02 10:34:50

* @Last Modified by: liguowei01

* @Last Modified time: 2020-01-03 09:18:12

*/

//用法 new Compile(el,vm)

class Compile {

constructor(el, vm) {

//要遍历的宿主节点

this.$el = document.querySelector(el);

this.$vm = vm; //在其他方法中方便使用

//编译

if (this.$el) {

//转换内部内容为片段Fragment

this.$fragment = this.node2Fragment(this.$el);

//执行编译

this.compile(this.$fragment);

//将编译完的html追加到$el

this.$el.appendChild(this.$fragment);

}

}

//将宿主元素中的代码片段拿出来遍历,这样做比较高效

node2Fragment(el) {

//创建一个代码块

const frag = document.createDocumentFragment();

//将el中所有子元素“搬家”(移动)到frag中

let child;

while (child = el.firstChild) {

frag.appendChild(child);

}

return frag;

}

compile(el) {

const childNodes = el.childNodes;

Array.from(childNodes).forEach(node => {

//判断类型

if (this.isElement(node)) {

//元素

console.log('编译元素', node.nodeName);

//查找k-, @, :

const nodeAttrs = node.attributes;

Array.from(nodeAttrs).forEach(attr => {

const attrName = attr.name;

const exp = attr.value;

if (this.isDirective(attrName)) {

//k-text

const dir = attrName.substring(2);

//执行指令

this[dir] && this[dir](node, this.$vm, exp);

}

if (this.isEvent(attrName)) {

//@click

let dir = attrName.substring(1); // text

this.eventHandler(node, this.$vm, exp, dir);

}

})

} else if (this.isInterpolation(node)) {

//插值文本{{}}

console.log('编译文本', node.nodeName);

this.compileText(node);

}

//递归子节点

if (node.childNodes && node.childNodes.length > 0) {

this.compile(node)

}

})

}

isDirective(attr) {

return attr.indexOf('k-') == 0;

}

isEvent(attr) {

return attr.indexOf('@') == 0;

}

isElement(node) {

return node.nodeType === 1;

}

//插值文本

isInterpolation(node) {

return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);

}

//编译文本

compileText(node) {

//console.log(RegExp.$1); //正则对象RegExp的静态属性$1就是第一个匹配的值 就是上面'name'

//node.textContent = this.$vm.$data[RegExp.$1];

this.updata(node, this.$vm, RegExp.$1, 'text');

}

/*

* @作用: 更新函数 根据指令决定是哪个更新器 它将来需要知道(参数)

* @params: node 更新的节点

* @params: vm kvue的实例

* @params: exp 正则表达式 匹配的结果 如:name

* @params: dir 指令(文本、事件、其他) 如:text,html,model

* 这个方法是个通用方法,将来要被调用很多次

*/

updata(node, vm, exp, dir) {

const updaterFn = this[dir + 'Updater']; //在当前的类里面组合一个函数名

/*这种写法和 this.a 一样,this是代表当前对象,也是一个对象,

对象名.方法名 或 对象名.属性名 调用对象中的属性和方法

还有一种调用方式:对象名['方法名'] 或 对象名['属性名']

也可以使用 对象名['方法名']() 执行此方法

*/

//先判断updaterFn是否存在,如果存在则执行

updaterFn && updaterFn(node, vm[exp]); //初始化(第一次)

//依赖收集

new Watcher(vm, exp, function(value) {

//观察vm 里的exp(属性),并在属性变化时,如何更新

updaterFn && updaterFn(node, value);

})

}

//更新的具体操作

textUpdater(node, value) {

node.textContent = value;

}

text(node, vm, exp) {

this.updata(node, vm, exp, 'text');

// 事件处理   

eventHandler(node, vm, exp, dir) {    

let fn = vm.$options.methods && vm.$options.methods[exp];    

if (dir && fn) {      

node.addEventListener(dir, fn.bind(vm), false); 

}

model(node, vm, exp) {

this.updata(node, vm, exp, 'model');    

let val = vm.exp;    

node.addEventListener('input', (e) => {      

let newValue = e.target.value;      

vm[exp] = newValue;      

val = newValue;

})

}  

modelUpdater(node, value) {

node.value = value; 

}

html(node, vm, exp) {

this.updata(node, vm, exp, 'html'); 

}

htmlUpdater(node, value) {

node.innerHTML = value; 

}

}


/*

问题1:vue编译过程是怎样的?

遵循3W1H原则,什么是编译,为什么要编译。

首先写的这些模板的语句,html根本就不能识别,

我们通过编译的过程,可以进行依赖的收集,

进行依赖收集以后,我们就把data中的数据模型和视图之间产生了绑定关系

产生了依赖关系,那么以后模型发生变化的时候,

我们就会通知这些依赖的地方让他们进行更新,

这就是我们执行编译的目的,这样就做到了模型驱动视图的变化。

问题2:双向绑定的原理是什么?

做双向绑定时,通常在表单元素上绑定一个v-model,

我们在编译的时候,可以解析到v-model

操作时做了两件事:

1.在表单元素上做了事件监听(监听input、change事件)

2.如果值发生变化时,在事件回调函数把最新的值设置到vue的实例上

3.因为vue的实例已经实现了数据的响应化,

它的响应化的set函数会触发,通知界面中所有模型的依赖的更新。

所以界面中的,跟这个数据相关的部分就更新了

*/

以上是 自己实现 一个 Vue框架,包含了Vue的核心原理 的全部内容, 来源链接: utcz.com/z/380654.html

回到顶部