简单手写实现Vue2.x

github: https://github.com/OUDUIDUI/vue-source-code-study/tree/vue

Vue的设计思想

Vue设计思想参考了MVVM模型,即将视图View和行为Model抽象化,即将视图UI和业务逻辑分开来,然后通过ViewModel层来实现双向数据绑定。

MVVMMVC 最大的不同就是MVVM实现了 ViewModel 的自动同步,也就是当Model 的属性改变时,我们不用再自己手动操作 Dom 元素,来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变。

MVVM框架的三个要素:数据响应式、模板引擎及其渲染

  • 数据响应式
    • 监听数据变化并在视图中更新
    • Vue2.x中,是根据Object.defineProperty()来实现数据响应式的
  • 模板引擎
    • 提供描述视图的模板语法
    • Vue的插槽{ {}}和指令v-bindv-onv-model
  • 渲染
    • 将模板渲染成HTML进行显示

数据响应式原理

JavaScript的对象Object中有一个属性叫访问器属性,其中有[[Get]][[Set]]特性,它们分别是获取函数或设置函数,即在获取对象特定属性的时候回调用到。

而访问器属性是不能直接定义的,必须使用Object.defineProperty()进行定义。

const obj = {
   
  	_name: 'Matt'
};
Object.defineProperty(obj, 'name', {
   
  	get() {
   
      	return this._name;
    },
  	set(newVal) {
   
      	console.log('set name')
       	this._name = newVal;
    }
})

console.log(obj.name);   // 'Matt'
obj.name = 'OUDUIDUI';   // 'set name'
console.log(obj.name);   // 'Henry'

Vue2.x就是在set函数中进行监听,当数据发生变化了,就会进行响应操作。

因此,我们可以简单实现一个Vue中的defineReactive函数。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>reactive app</title>
</head>
<body>
<div id="app"></div>
<script>
      /**
     * defineReactive : 将对象中某一个属性设置为响应式数据
     * @param obj<Object>: 对象
     * @param key<any>: key名
     * @param val<any>: 初始值
     */
    function defineReactive(obj, key, val) {
    
        Object.defineProperty(obj, key, {
    
            get() {
    
                console.log(`get ${
      key}`)
                return val;   // 此时val存在obj的闭包里面
            },
            set(newVal) {
    
                console.log(`set ${
      key}`)
                if (newVal !== val) {
     
                    val = newVal;
                    update();    // 更新函数
                }
            }
        })
    }
  
  	/**
     * update : 更新函数,重新渲染app DOM
     */
    function update() {
    
        const app = document.getElementById('app');
        app.innerHTML = `obj.time = ${
      obj.time}`
    }
  
  
		const obj = {
    }; 
		defineReactive(obj, 'time', new Date().toLacaleTimeString());  // 将obj进行响应式处理
  	setInterval(() => obj.time = new Date().toLacaleTimeString(), 1000);   // 定时更新obj.time的值
</script>

在代码中,我们在set中,调用了update更新函数,因此我们定时器每更新obj.time一次,update函数就会被调用一次,因此页面数据也会更新一次。这时候,我们就简单的实现了数据响应式。

defineReactive函数有个问题,就是一次只能对一个属性值进行响应式处理,而且如果这个属性是个对象的话,我们更改对象里面的值的时候,是实现不了响应式的。

const obj = {
   };
defineReactive(obj, 'info', {
   name: 'OUDUIDUI', age: 18});  // 将obj进行响应式处理
setTimeout(() => obj.info.age++, 1000);  // 这时候不会触发set函数

img

因此,我们需要一个新的方法去实现对整个对象进行响应式处理,在Vue中这个方法叫observe

在这个函数中,我们先需要对传入的obj进行类型判断,然后对对象进行遍历,对每一个属性进行响应式处理。这个地方需要对数组做处理,这个放到后面再说。

/**
 * observe: 将整个对象设置为响应式数据
 * @param obj<Object>: 对象
 */
function observe(obj) {
   
    // 如果obj不是对象的话,跳出函数
    if (typeof obj !== "object" || obj === null) {
   
        return;
    }

    // 判断传入obj的类型
    if(Array.isArray(obj)){
   
        // TODO
    }else {
   
        // 遍历obj所有所有key,做响应式处理
        Object.keys(obj).forEach(key => {
   
            defineReactive(obj, key, obj[key]);
        })
    }
}

同时,我们需要实现对这个对象一个递归处理,因此我们需要修改一下defineReactive函数。我们只需要在最开始的地方,调用一次observe函数,如果传入的val是对象,就会进行递归响应式处理,如果不是就返回。

function defineReactive(obj, key, val) {
   
    observe(val);  // 递归处理:如果val是对象,继续做响应式处理

    Object.defineProperty(obj, key, {
   
        ...
    })
}

我们来测试一下:

const obj = {
   
    time: new Date().toLocaleTimeString(),
    info: {
   
        name: 'OUDUIDUI',
        age: 18
    }
};
observe(obj);

setInterval(() => {
   
    obj.time = new Date().toLocaleTimeString();
}, 1000)

setTimeout(() => {
   
    obj.info.age++;
}, 2000)

img

这里还有一个小问题,就是如果obj原本有一个属性是常规类型,即字符串、数值等等,然后再将其改为引用类型时,如对象、数值等,该引用类型内部的属性,是没有响应式的。比如下来这种情况:

const obj = {
   
    text: 'Hello World',
};
observe(obj);  // 响应式处理

obj.text = {
    en: 'Hello World' };    // 将obj.text由字符串改成一个对象

setTimeout(() => {
   
    obj.text.en = 'Hi World';   // 此时修改text对象属性页面是不会更新的,因为obj.text.en不是响应式数据
}, 2000)

对于这种情况,我们只需要在defineReactive函数中,set的时候调用一下observe函数,将newVal传入,如果是对象就进行响应式处理,否则就直接返回。

function defineReactive(obj, key, val) {
   
    observe(val); 

    Object.defineProperty(obj, key, {
   
        get() {
   
            console.log(`get ${
     key}`)
            return val;
        },
        set(newVal) {
   
            console.log(`set ${
     key}`)
            if (newVal !== val) {
   
                observe(newVal);  // 如果newVal是对象,再次做响应式处理
                val = newVal;
                update();
            }
        }
    })
}

我们测试一下。

function update() {
   
    const app = document.getElementById('app');
    app.innerHTML = `obj.text = ${
     JSON.stringify(obj.text)}`
}

const obj = {
   
    text: 'Hello World'
};

// 响应式处理
observe(obj);

setTimeout(() => {
   
    obj.text = {
        // 将obj.text由字符串改成一个对象
        en: 'Hello World'
    }
}, 2000)

setTimeout(() => {
   
    obj.text.en = 'Hi World';
}, 4000)

img

最后我们来完成前面楼下的一个问题,就是数组的响应式处理。

之所以数组需要特殊处理,因为数组有七个自带方法可以去处理数组的内容,分别是

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值