zl程序教程

您现在的位置是:首页 >  前端

当前栏目

自己写的一个Vue

Vue 一个 自己
2023-09-14 08:58:55 时间

下面这里是我自己写的一个小型的vue,原理就是proxy:

//Proxy天生没有prototype,因此要加上,不然extends会报错
Proxy.prototype = Proxy.prototype || Object.prototype

class myVue extends Proxy {
    constructor(options) {
        let data = options.data || {}

        super(data, {
            get(target, name, proxy) {
                if(name in target) {
                    return target[name]
                } else {
                    throw new Error(`不存在data'${name}'`)
                }
            },
            set(target, name, value, proxy) {
                target[name] = value
                _container.render()
            }
        })
        // 不能够在constructor里面设置data的值,因此公共变量只能在constructor里面定义,通过函数调用传递
        // this.$el = document.querySelector(options.el)
        
        //data被类本身代理,而其他options经过初步处理后我们保存在一个变量对象_container中
        let _container = {}
        window.onload = function() {
            _container.data = data
            _container.el = document.querySelector(options.el)
            _container.oldEl = document.querySelector(options.el).cloneNode(true)
            _container.methods = options.methods || {}
            //特殊地我们需要一个重新渲染el的内部方法也要存在_container里面
            //绑定_container本身是为了render函数能够使用存在_container里面的options的内容
            _container.render = render.bind(_container)
            _container.render()
        }
    }
    //get只能get到data对象里面的值,所以在内部定义的函数也没办法用
    // zzz(){
    //     alert(123)
    // }
}

function render() {
    let _computer = (e) => {
        let val = ''
        with(this.data){
            val = eval(e)
        }
        return val
    }

    //先把dom上的替换成“备份”
    this.el.parentNode.replaceChild(this.oldEl, this.el);
    //"备份"切换到this.el上等待被渲染
    this.el = this.oldEl
    //再复制一份留“备份”
    this.oldEl = this.oldEl.cloneNode(true)

    //处理花括号
    this.el.innerHTML = this.el.innerHTML.replace(/\{\{[^\{\}]+\}\}/, (str) =>{
        let e = str.substring(2,str.length-2)
        return _computer(e)
    })

    //处理:属性
    let nodes = this.el.getElementsByTagName('*')
    Array.from(nodes).forEach(node => {
        Array.from(node.attributes).forEach(attr => {
            if(attr.nodeName.startsWith(':')){
                node.setAttribute(attr.nodeName.substring(1),_computer(attr.value)) 
                node.removeAttribute(attr.nodeName) 
            } else if (attr.nodeName.startsWith('@')) {
                if(!this.methods[attr.value]) {
                    throw new Error(`methods里面没有方法'${attr.value}'`)
                }
                node.addEventListener(attr.nodeName.substring(1),function(){
                    this.methods[attr.value]()
                }.bind(this),false) 
                node.removeAttribute(attr.nodeName)
            }
        })
    })
}

对应调用的HTML:

<!DOCTYPE html>
<html>
<head>
    <title>myVue</title>
    <script src="my-vue.js"></script>
    <script>
        let vm = new myVue({
            el: '#app',
            data: {
                aaa: 123,
                bbb: '你好啊,张啊咩'
            },
            methods:{
                sayHello() {
                    alert('hello')
                }
            }
        })
        console.log(vm.aaa)
    </script>
</head>
<body>
    <div id="app">
        {{aaa}}
        <span :title="bbb" @click="sayHello">
            hahaha
        </span>
    </div>
</body>
</html>