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

vue原理及其手撸(一)

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

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

https://github.com/dangjingtao/FeRemarks


工作比的是内力,而不是一味的模仿。如果你深入弄懂了造飞机的流程,给坦克造个拧螺丝钉还是绰绰有余的。靠卖关键零件赚钱也够了。但如果你说飞机坦克都能组装,就是不会自主研发生产任何东西。就形成不了自身的技术竞争力。在解决关键问题时还得靠大神同事。这就是技术壁垒。

本节将尝试手撸一个核心的vue代码。不妨称之为Due,因为这是同事的口头禅。也是我火大时唾上最狠的一句。

工作机制

如果之前对vue的机制一无所知,很有必要先研究下这么一张图:

初始化(new Vue())时做了什么?

$mount:挂载:执行编译函数,做三件事

  • parse:正则把指令转化为ast(抽象语法树)
  • optimise 标记静态节点
  • generate

template是一个js。 render渲染出一个虚拟dom树

代码语言:javascript
复制
render(createElement){
    return createElement('标签名',attrs:{},[])
}
响应式

vue核心在于:响应式机制。而响应式的核心在于defineProperty 初始化通过defineProperty定义对象gettersetter,设置通知机制。 当编译生成的函数被实际渲染的时候,会触发getter进行依赖收集,数据变化时,触发setter进行更新。

看一下原理。 在浏览器环境下运行下列代码:

代码语言:javascript
复制
<div id="name"></div>
<script>
var obj={};
Object.defineProperty(obj,'name',{
    get(){
        console.log('获取name');
        return document.querySelector('#name').innerHTML;
    },
    set(nick){
        console.log('设置name')
        document.querySelector('#name').innerHTML='nick';
    },
})

obj.name='djtao'
console.log(obj.name)
</script>

当你运行obj.name='djtao时,执行set,还顺带夹杂了“私货”:把div#name的内容设置为djtao。 当你打印出obj.name时,执行get。因为我们的设置,get还顺便返回了div#name内的值。 那么,一个响应式雏形就有了。我就不用去做dom操作了。 这里在做的实际上就是observer做的事情。

对比,更新,

虚拟dom

由react首创,就是用JavaScript对象来描述dom结构。数据修改时,先修改虚拟dom中的数据,然后数组做diff。最后再汇总所有的diff。力求把dom操作减少到最少。

手写(due)

对于手写核心来说,需要做到以下内容:

创建一个编译器(complie),编译html。由watcher负责更新。

依赖管理器dep:(管理watcher),每个watcher创建后都跟新到dep中

数据劫持:

做之前必须分析来自Due开发者的需求:

代码语言:javascript
复制
new Due({
    data:{
        msg:'helloworld'
    }
})

在这个Due里面,核心需求就是对data运用观察者模式。然而和上节的obj不同,这个observe可能拥有多个。因此还需要做遍历:

代码语言:javascript
复制
class Due{
    constructor(options){

        this.$options=options;

        //处理data
        this.$data=options.data;

        // 响应式监听
        this.observe(this.$data)
    }

observe方法中,接收的是this.$data,拿到之后先别急着动,,做个数据类型检测:

代码语言:javascript
复制
    observe(data){
        // 数据类型检测
        if(!data||typeof(data)!=='object'){
            return ;
        }
        
        // this.$data可能存在多个键值对
        // 因此需要遍历对象
        Object.keys(data).forEach((key)=>{
            return this.defineReactive(data,key,data[key])
        })
        //等效于以下代码
        // for(let attr in data){
            // this.defineReactive(data,key,data[key])
        // }
    }

然后呢,遍历每个对象,执行defineProperty,在此处我把它放到了this.defineReactive中。注意:this.$data可能包含深层次的数据对象,因此需要递归调用this.observe

代码语言:javascript
复制
    // 执行深层次的数据劫持
    defineReactive(obj,key,val){
        Object.defineProperty(obj,key,{
            get(){
                //直接获取
                return val;
            },
            set(newVal){
                // 判断是否更新
                if(newVal!==val){
                    val=newVal;
                    console.log(`${key}更新了:${newVal}`)
                }
            }
        })
        //递归,允许多层嵌套
        this.observe(val)
    }
测试一下

一口气写了那么多代码,是时候测试一下了:在js文件中声明我们想要实现的需求:

代码语言:javascript
复制
const app =new Due({
	data:{text:'djtao'}
})
console.log(app)
app.$data.text='dangjingtao'

发现我们每一步都能走得通:

依赖收集

目前,我们只是实现了数据的响应式跟踪,还没有真正劫持数据去干点事情。 设想使用Due的开发者有个需求是:

代码语言:javascript
复制
new Due({
    tamplate:`
        <div>
            <span>{{name1}}</span>
            <span>{{name2}}</span>
            <span>{{name1}}</span>
        </div>
    `,
    data:{
        name1:'',
        name2:'',
        name3:''
    },
    created(){
        this.name1='djtao';
        this.name2='dangjingtao';
    }
})

你认为要完成上述需求,我们的数据劫持需要哪些依赖呢?

  • name1存在于两个地方,那么两个地方都要做更新;
  • name3没出现,因此不需要去做管理。
  • 更新template内的插值绑定
  • 劫持created方法; 要做依赖收集,我们要新建一个工厂函数Dep来收集各种依赖,并对其具体位置进行监控(Watcher)。 有一个key,就有一个dependency,template一个地方出现了几次,就有几个watcher。 在上述的需求中,name1出现了两次,它的dep中就有两个watcher。name3压根没出现,它的dep中就没有watcher。
代码语言:javascript
复制
class Dep{
    constructor(){
        // 这个数组用来放依赖
        this.deps=[]
    }
    // 增加watcher的方法
    addDep(watcher){
        this.deps.push(watcher)
    }
    //  通知视图更新
    notify(){
        this.deps.forEach(watcher=>watcher.update())
    }
}

class Watcher{
    constructor(){
        //hack
        Dep.target=this;
    }
    // 更新(做dom操作)
    update(){
        console.log('属性已更新')
    }
}

有了Dep这个类,在Due内的set就不用去直接操作数据了。完全可以丢给dep。 在类Due中,每set一次,都应该去通知Dep。每次get一次,都去把watcher的this扔进Dep中!因此需要重构defineReactive:

代码语言:javascript
复制
// Due
// 执行数据劫持
    defineReactive(obj,key,val){
        const dep=new Dep()// 新建实例
        Object.defineProperty(obj,key,{
            get(){
                if(Dep.target){
                    // 把watcher扔进dep!
                    // 注意函数作用域:
                    // 这个dep和key是一对一的关系
                    dep.addDep(Dep.target)
                }
                return val;
            },
            set(newVal){
                // 判断是否更新
                if(newVal!==val){
                    val=newVal;
                    dep.notify();//通知deps更新
                    //console.log(`${key}更新了:${newVal}`)
                }
            }
        })
        //递归,允许多层嵌套
        this.observe(val)
    }
测试一下

在Due的构造函数中添加下列代码,让程序读一次text属性:

代码语言:javascript
复制
        new watcher();
        this.$data.text;

成功打印出了watcher.update的执行过程。依赖通知成功

代理

来点轻松功能吧。开发者出于书写方便,更愿意使用app.text而非app.$data.text。接到这个需求应如何实现呢?

代码语言:javascript
复制
    proxyData(key){
        Object.defineProperty(this,key,{
            get(){
                return this.$data[key];
            },
            set(newVal){
                // 判断是否更新
                if(newVal){
                    this.$data[key]=newVal;
                }
            }
        })
    }

注意,第一个参数由this.$data(data)变成了this。 功能实现。

下节将阐述Due另外一个核心——编译器的实现

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 工作机制
    • 初始化(new Vue())时做了什么?
      • 响应式
        • 虚拟dom
        • 手写(due)
          • 数据劫持:
            • 测试一下
          • 依赖收集
            • 测试一下
          • 代理
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档