学习手写vue,理解原理

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>

原文地址:https://www.cnblogs.com/chengyunshen/p/11971211.html

时间: 2024-10-29 14:06:22

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

深度学习---手写字体识别程序分析(python)

我想大部分程序员的第一个程序应该都是"hello world",在深度学习领域,这个"hello world"程序就是手写字体识别程序. 这次我们详细的分析下手写字体识别程序,从而可以对深度学习建立一个基本的概念. 1.初始化权重和偏置矩阵,构建神经网络的架构 import numpy as np class network(): def __init__(self, sizes): self.num_layers = len(sizes) self.sizes =

手写Vue (1) 对象劫持

1.引入我们手写的Vue 拿到配置数据 import Vue from '../source/src/index'; let vm = new Vue({ el: '#app', data() { return { msg: 'hello', school: { name: 'zf', age: 10 }, arr: [1, 2, 3] } }, computed: { }, watch: { } }) console.log(vm) // setTimeout(() => { // vm.ar

手写vue中v-bind:style效果的自定义指令

自定义指令 什么是自定义指令 以 v- 为前缀,然后加上自己定义好的名字组成的一个指令就是自定义指令.为什么要有自定义指令呢?在有些时候,你仍然需要对普通的DOM元素进行底层的操作,这个时候就可以用到自定义指令. 自定义指令的语法 全局自定义指令 // 注册一个全局自定义指令 `v-focus` Vue.directive('focus', { // 当被绑定的元素插入到 DOM 中时-- inserted: function (el) { // 聚焦元素 el.focus() } }) 局部自

手写vue双向绑定数据

来一张原理图: 实现思路: (1)绑定data 种的数据,为每个数据添加指令.通过Object,defineProperty() 来通知属性是否更改 (2) 找到每个DOM节点的指令.绑定事件.并绑定watcher (3)  实现DOM事件改变之后, 响应data数据,实现视图更新 <!DocType> <html> <title>vue 的双向绑定事件</title> <body id="app"> <input ty

手写Vue (1) 准备工作

1.安装插件 "devDependencies": { "html-webpack-plugin": "^4.0.4", "webpack": "^4.42.1", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.10.3" } 2.配置 项目 的基本结果 (1)根目录下新

一文全解:利用谷歌深度学习框架Tensorflow识别手写数字图片(初学者篇)

笔记整理者:王小草 笔记整理时间2017年2月24日 原文地址 http://blog.csdn.net/sinat_33761963/article/details/56837466?fps=1&locationNum=5 Tensorflow官方英文文档地址:https://www.tensorflow.org/get_started/mnist/beginners 本文整理时官方文档最近更新时间:2017年2月15日 1.案例背景 本文是跟着Tensorflow官方文档的第二篇教程–识别手

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

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

全面理解Handler第一步:理解消息队列,手写消息队列

前言 Handler机制这个话题,算是烂大街的内容.但是为什么偏偏重拿出来"炒一波冷饭"呢?因为自己发现这"冷饭"好像吃的不是很明白.最近在思考几个问题,发现以之前对Handler机制的了解是在过于浅显.什么问题? Handler机制存在的意义是什么?能否用其他方式替换? Looper.loop();是一个死循环,为什么没有阻塞主线程?用什么样的方式解决死循环的问题? 如果透彻的了解Handler,以及线程的知识.是肯定不会有这些疑问的,因为以上问题本身就存在问题.

jQuery手写几个常见的滑动下拉菜单 分分秒秒学习JS

一般的企业网站再我们再实际工作中,有些特效,用jQuery来做,显得极其简单,除非一些大的公司,需要封装自己的类. 今天,我们讲解jQuery入门知识,来写几个简单jQuery滑动下拉菜单.感受一下jQuery的简单快速的神奇之处. 学完本章,可以书写最常见的下拉菜单写法. 案例1 效果如图所示: 在书写这个滑动的下拉菜单的时候,我们首先来认识下jQuery里面的滑动方法 :slideToggle() slideToggle(执行时间,运动方式,返回函数) 执行时间: 常用的是 "slow&qu