前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >vue原理及其手撸(二)

vue原理及其手撸(二)

作者头像
一粒小麦
发布2019-07-18 17:53:55
5860
发布2019-07-18 17:53:55
举报
文章被收录于专栏:一Li小麦一Li小麦

本项目将在GitHub上维护更新。

https://github.com/dangjingtao/FeRemarks


编译器(Complie)

编译器的工作流程:

来看基本的需求: 现在Due需要支持下列语句:

代码语言:javascript
复制
<body>
	<div id="app">
		<p>{{name}}</p>
		<!--插值语句-->
		<p d-text="name"></p>
		<p>{{doubleAge}}</p>

		<input type="text" d-model="name">
		<!--双向绑定-->
		<button @click="change">hehe</button>
		<!--事件-->
		<div d-html="html"></div>
	</div>
	<script src="Complie.js"></script>
	<script src="Due.js"></script>

	<script>
		const app = new Due({
			el: '#app',
			data: {
				name: 'i am djtao',
				age: 29,
				html: `<button>button</button>`
			},
			created() {
				setTimout(() => {
					this.name = 'dangjingtao'
				}, 2000)
			},
			methods: {
				change(){
					this.name = 'taotao';
					this.age = 30;
				}
			}
		})

	</script>

向往的compile

新建一个Complie.js:写构造函数吧!

代码语言:javascript
复制
class Compile{
    constructor(el,vm){
        this.$vm=vm; //拿到Due实例
        this.$el=document.querySelector(el)
        // 开始编译
        if(this.$el){
            // 提取宿主中模板内容,放到一个dom标签:
            this.$fragment=this.node2Fragement(this.$el)
            // 对碎片进行编译,收集依赖
            this.compile(this.$fragment);
            // 替换完成后,追加到目标宿主中
            this.$el.appendChild(this.$fragment)
        }
    }

一开始是要拿到宿主el(#app)和Due。

  • node2Fragement: 提取宿主里面的内容
  • complie,执行编译,收集依赖
  • 把编译后的碎片放回宿主。

那现在来实现这几个功能:

代码语言:javascript
复制
class Compile{
    //...
    node2Fragement(el){
        console.log(el)
        // 原生dom方法,把宿主的子元素全扔进去。
        const fragement=document.createDocumentFragment();
        let child;
        while (child=el.firstChild){
            fragement.appendChild(child)
        }
        return fragement
    }

    compile(el){
        const childNodes=el.childNodes;
        Array.from(childNodes).forEach(node=>{
            // nodetype 为1,是element节点
            if(node.nodeType===1){
                console.log('编译元素节点'+node.nodeName)
            }else if(this.isInterpolation(node)){
                // 是否双大括号
                console.log('编译插值文本'+node.textContent)
            }
            // 递归子节点
            if(node.childNodes&&node.childNodes.length>0){
                this.compile(node)
            }
        })
    }
    //判断插值文本
    isInterpolation(node){
        //是文本且符合双大括弧正则
        return node.nodeType===3 && /\{\{(.*)\}\}/.test(node.textContent);
    }
}
测试一下

在due.js中的Due构造函数中·new Complie(this.$option.el,this). 在浏览器环境秀泡一下:

发现都已经执行了。

开始编译

新建两个函数,分别处理元素节点和文本节点:

代码语言:javascript
复制
    compile(el){
        const childNodes=el.childNodes;
        Array.from(childNodes).forEach(node=>{
            // nodetype 为1,是element节点
            if(node.nodeType===1){
                this.compileElement()
            }else if(this.isInterpolation(node)){
                // 是否双大括号
                this.compileText(node)
            }

            // 递归子节点
            if(node.childNodes&&node.childNodes.length>0){
                this.compile(node)
            }
        })
    }
编译文本

编译文本相当简单,把vm对应的key值更新到视图即可。

编译元素节点

一再强调的,元素节点一定要对属性和标签进行区分:

绑定依赖更新(写完watcher)

在Due的构造函数最后,执行Created:

代码语言:javascript
复制
// 编译完成
        if(options.created){
            options.created.call(this)
        }

现在可以写完watcher的逻辑了。 watcher接收vm,key,和回调。

代码语言:javascript
复制
class Watcher{
    constructor(vm,key,cb){
        this.vm=vm;
        this.key=key;
        this.cb=cb;
        //hack:让dep拿到watcher的this,以方便调用方法;
        Dep.target=this;
        // 读一下触发get
        this.vm[this.key]
        Dep.target=null;
    }
    // 更新
    update(){
        console.log('属性已更新')
        this.cb.call(this.vm,this.vm[this.key])
    }
}

那么回调函数写什么呢?

代码语言:javascript
复制
    /**
     * 更新函数接收4个参数
     * @节点 {*} node 
     * @Due实例 {*} vm 
     * @表达式 {*} exp 
     * @指令 {*} dir 比如渲染文本为`text`
     */
    update(node,vm,exp,dir){
        //根据不同的指令调用方法
        let updaterFn=this[dir+'Updater'];
        updaterFn&&updaterFn(node,vm[exp]);
        // 收集
        new Watcher(vm,exp,function(value){
            updaterFn && updaterFn(node,value);
        })      
    }

    textUpdater(node,val){
        node.textContent=val
    }
更新

过多的具体业务逻辑就不下强调了。

代码语言:javascript
复制
    // 编译标签节点
    compileElement(node){
        // <div d-text="txt"></div>
        let nodeAttrs=node.attributes;

        Array.from(nodeAttrs).forEach(attr=>{
            const attrName=attr.name;
            const exp=attr.value;
            // 首先判断是事件还是指令
            if (this.isEvent(attrName)) {
                const dir=attrName.substring(1);//@后面的事件类型
                //在这里,exp是函数
                this.eventHandler(node,this.$vm,exp,dir);
            }
            // 指令
            if(this.isDir(attrName)){
                const dir=attrName.substring(2);//比如d-text中的text
                //在这里,exp是指令值
                this[dir]&&this[dir](node,this.$vm,exp);
            }
        })
    }

    isDir(attr){
        return attr.indexOf('d-')==0
    }

    isEvent(attr){
        return attr.indexOf('@')==0;
    }
    
    // v-text处理
    text(node,vm,exp){
        this.update(node,vm,exp,'text')
    }

    // 处理事件
    eventHandler(node,vm,exp,dir){
        const fn=vm.$options.methods&&vm.$options.methods[exp]
        console.log(123,node)
        node.addEventListener(dir,fn.bind(vm))
    }
其它指令方法

现在进行其它方法绑定:

代码语言:javascript
复制
    html(node,vm,exp){
        // console.log(ddd)
        this.update(node,vm,exp,'html')
    }

    model(node,vm,exp){
        this.update(node,vm,exp,'model');
        node.addEventListener('input',e=>{
            vm[exp]=e.target.value
        })
    }
    
    htmlUpdater(node,value){
        node.innerHTML=value;
    }

    textUpdater(node,val){
        node.textContent=val
    }

那么这里的核心就讲完了。虚拟Dom等到react再说

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 编译器(Complie)
    • 向往的compile
      • 测试一下
        • 开始编译
          • 编译文本
          • 编译元素节点
        • 绑定依赖更新(写完watcher)
          • 更新
            • 其它指令方法
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档