自己实现 一个 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函数会触发,通知界面中所有模型的依赖的更新。
所以界面中的,跟这个数据相关的部分就更新了

*/

原文地址:https://www.cnblogs.com/lguow/p/12146316.html

时间: 2024-11-09 12:11:48

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

Vue框架(一)——Vue导读、Vue实例(挂载点el、数据data、过滤器filters)、Vue指令(文本指令v-text、事件指令v-on、属性指令v-bind、表单指令v-model)

Vue导读 1.Vue框架 vue是可以独立完成前后端分离式web项目的js框架 三大主流框架之一:Angular.React.Vue vue:结合其他框架优点.轻量级.中文API.数据驱动.双向绑定.MVVM设计模式.组件化开发.单页面应用 Vue环境:本地导入和cdn导入 2.Vue是渐进式js框架 通过对框架的了解与运用程度,来决定其在整个项目中的应用范围,最终可以独立以框架方式完成整个web前端项目.3.怎么使用vue 去官网下载然后导入 <div id="app">

简单实现一个rpc框架

与其说框架不如讲是个小demo,废话不多说直接上代码 package com.tang.rpc; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Pr

理解vue实现原理,实现一个简单的Vue框架

参考: 剖析Vue实现原理 - 如何实现双向绑定mvvm Vue.js源码(1):Hello World的背后 Vue.js官方工程 本文所有代码可以在git上找到. 其实对JS我研究不是太深,用过很多次,但只是实现功能就算了.最近JS实在是太火,从前端到后端,应用越来越广泛,各种框架层出不穷,忍不住也想赶一下潮流. Vue是近年出的一个前端构建数据驱动的web界面的库,主要的特色是响应式的数据绑定,区别于以往的命令式用法.也就是在var a=1;的过程中,拦截'='的过程,从而实现更新数据,w

vue框架(一)

vue是什么? vue:一个构建用户界面的框架. 1在HTML元素显示数据 {{}} v-text v-html 2指令: 通过指令,来给DOM元素赋值或者其它操作:v-text v-html 根据表达式的真假值,动态地插入.移除元素:v-text v-html v-if\v-else 根据表达式的真假值,动态地显示.隐藏元素:v-show 根据数值渲染元素列表:v-for 绑定元素的属性,可以动态改变:v-bind 根据命令监听且执行事件:v-on v-model:数据双向绑定 它是把视图和数

Vue框架tab切换高亮最简易方法

以往我们实现tab切换高亮通常是循环遍历先把所有的字体颜色改变为默认样式,再点亮当前点击的选项,而我们在vue框架中实现tab切换高亮显示并不需要如此,只需要将当前点击选项的index传入给一个变量,再将这个变量和当前index匹配,若true则显示高亮,否则默认样式,代码如下: 如需转载请注明出处:http://www.cnblogs.com/zishang91/p/7580204.html,以便有错误可以及时修改,若有错漏不足之处,请见谅并且指点,谢谢!!!

一个基于ES6+webpack的vue小demo

上一篇文章<一个基于ES5的vue小demo>我们讲了如何用ES5,vue-router做一个小demo,接下来我们来把它变成基于ES6+webpack的demo. 一.环境搭建及代码转换 我们先搭建一下vue 的开发环境,根据我的一篇随笔<Vue开发环境搭建及热更新>,我们一步步搭建开发环境,project名为ES6-demo. 在之前我发表的一篇随笔< 理解最基本的Vue项目>中,说到了在放置组件和入口文件的src文件夹中,main.js文件就是入口文件,App.v

Vue框架axios请求(类似于ajax请求)

Vue框架axios get请求(类似于ajax请求) 首先介绍下,这个axios请求最明显的地方,通过这个请求进行提交的时候页面不会刷新 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="vue.js"></scr

Vue框架下的node.js安装教程

Vue框架下的node.js安装教程 python服务器.php  ->aphche.java ->tomcat.   iis -->它是一个可以运行JAVASCRIPTR 的运行环境 -->它可以作为后端语言(websocket \ ) --强大的包管理工具npm,可以使用它安装插件 -->VUE框架是基于node.js平台运行的 --->它是基于chrome浏览器的V8引擎,运行速度快,性能高效 安装淘宝镜像:npm install cnpm -g --regist

Vue框架的使用。

使用VUE首先需要下载安装一些列的环境. 第一步: 安装Node.js 下载并安装:node-v8.9.0-x64.msi 第二步: 安装Vue脚手架: cmd以管理员身份执行 npm install vue-cli -g 或者安装淘宝版 cnpm install vue-cli -g vue -V  查看是否安装成功 第三步: 创建项目: vue init webpack myProject  (项目名字) 提示内容: 然后初始化: vue init webpack myProject 第四步

前端VUE框架

一.什么是VUE?  它是一个构建用户界面的JAVASCRIPt框架  vue不关心你页面上的是什么标签,它操作的是变量或属性 为什么要使用VUE? 在前后端分离的时候,后端只返回json数据,再没有render方法,前端发送ajax请求(api=url)得到数据后,要在页面渲染数据,如果你js+css实现就太麻烦了,这时候VUE就出现了.二.怎么样使用VUE 1)引入vue.js <script src=vue.js></script> 2) 展示html <div id=