一步一步实现自己的vue

本文详细介绍了如何一步步实现一个简单的Vue框架,包括准备工作、数据监听(涉及数据代理、解决多层级监听问题、监听数据变化)、重写数组方法、初始化渲染页面、更新数据渲染页面、批量更新防止重复渲染、实现数组依赖收集、watch和computed的实现。通过这个过程,读者可以深入理解Vue的数据响应机制和渲染原理。
摘要由CSDN通过智能技术生成

1.准备工作

** 我们先利用webpack构建项目:**

  • 初始化项目

    npm init -y

  • 安装webpack

    npm i webpack webpack-cli webpack-dev-server html-webpack-plugin --save

  • 配置webpack

    // webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
   
    entry:'./src/index.js',// 以src下的index.js 作为入口文件进行打包
    output:{
   
        filename:'bundle.js',
        path:path.resolve(__dirname,'dist')
    },
    devtool:'source-map', // 调试的时候可以快速找到错误代码
    resolve:{
   
        // 更改模块查找方方式(默认的是去node_modules里去找)去source文件里去找
        modules:[path.resolve(__dirname,'source'),path.resolve('node_modules')]
    },
    plugins:[
        new HtmlWebpackPlugin({
   
            template:path.resolve(__dirname,'public/index.html')
        })
    ]
}
  • 配置package.json
    "scripts": {
   
    "start": "webpack-dev-server",
    "build": "webpack"
  },

2 实现数据监听

2.1 创建构造函数MyVue

并初始化用户传入的参数options,我们先假设用户传入的options是只有data属性和el属性的。

export function initState(vm) {
   
    let opt = vm.$optios
    if (opt.data){
   
        initData(vm);
    }
}

function initData(vm) {
   
    // 获取用户传入的data
    let data = vm.$optios.data
    // 判断是不是函数,我们知道vue,使用data的时候可以data:{}这种形式,也可以data(){return{}}这种形式
    // 然后把把用户传入的打他数据赋值给vm._data
    data = vm._data = typeof data === 'function' ? data.call(vm) : data ||{
   }

    observe(data)
}

到这里我们实现的是new MyVue的时候,通过_init方法来初始化options, 然后通过initData方法将data挂到vm实例的_data上去了,接下来,我们要对data实现数据监听,上面的代码中observe代码就是用来实现数据监听的。

2.2 实现数据监听
export function observe(data) {
   
    if (typeof data !== 'object' || data == null){
   
        return
    }
    return new Observe(data)
}

在这段代码observe方法的代码中,observe()将传入的data先进行判断,如果data是对象,则new 一个Observe对象来使这个data 实现数据监听,我们再看下Observe是怎么实现的

class Observe {
   
    constructor(data){
    // data就是我们定义的data vm._data实例
        // 将用户的数据使用defineProperty定义
        this.walk(data)
    }
    walk(data){
   
        let keys = Object.keys(data)
        for (let i = 0;i<keys.length;i++){
   
            let key  = keys[i]; // 所有的key
            let value = data[keys[i]] //所有的value
            defineReactive(data,key,value)
        }
    }
}

可见,Observe 将data传入walk方法里,而在walk方法里对data进行遍历,然后将data的每一个属性和对应的值传入defineReactive,我们不难猜测,这个defineReactive就是将data的每一个属性实现监听。我们再看下defineReactive

export function defineReactive(data,key,value) {
   
  
    Object.defineProperty(data,key,{
   
        get(){
   
            return value
        },
        set(newValue){
   
            if (newValue === value) return
            value = newValue
            observe(value)
        }
    })
}

可见,这是通过defineProperty,=将每个key进行数据监听了。但是这里有一个问题,就是,这里只能监听一个层级,比如

data = {
   
  wife:"迪丽热巴"
}

这时没问题的,但是

data = {
   
  wife:{
   
    name:"迪丽热码",
    friend:{
   
      name:"古力娜和"
    }
  }
}

我们只能监听到wife.friend和wife.name是否改变与获取,无法监听到wife.friend.name这个属性的变化,因此,我们需要判断wife.friend是不是对象,然后将这个friend对象进行遍历对它的属性实现监听

2.3 解决多层级监听的问题

因此我们在上面代码的基础上,添加上observe(value)就实现了递归监听

export function defineReactive(data,key,value) {
   
    // 观察value是不是对象,是的话需要监听它的属性。
    observe(value)

    Object.defineProperty(data,key,{
   
        get(){
   
            return value
        },
        set(newValue){
   
            if (newValue === value) return
            value = newValue
        }
    })
}

基本完成。

但是到这里,还有一个问题,就是我们上面的data都是new MyVue的时候传进去的,因此要是我们再new 完 改变data的某个值,如下面将message改成迪丽热巴对象,此时虽然我们依旧可以监听message,但是message.name是监听不到的

let vm = new MyVue({
   
    el: '#app',
    data(){
   
        return{
   
            message:'大家好',
            wife:{
   
                name:"angelababy",
                age:28
            }
        }
    }
})
vm._data.message = {
   
    name:'迪丽热巴',
    age:30
}
2.4 解决data中某个属性变化后无法监听的问题

我们知道 message这个属性已经被我们监听了,所以改变message的时候,会触发set()方法,因此我们只需要将wife再放进observe()中重新实现监听一遍即可,如代码所示

export function defineReactive(data,key,value) {
   
    // 观察value是不是对象,是的话需要监听它的属性。
    observe(value)

    Object.defineProperty(data,key,{
   
        get(){
   
            return value
        },
        set(newValue){
   
            if (newValue === value) return
            value = newValue
            observe(value)
        }
    })
}
2.5 实现数据代理

我们用过vue的都知道,我们获取data中的属性的时候,都是直接通过this.xxx,获取值的,而我们上面只实现了想要获取值需要通过this._data.xxx,所以这一节来实现是数据代理,即将data中的属性挂载到vm上,我们可以实现一个proxy方法,该方法将传入的数据挂载到vm上,而当我们访问this.xxx的时候,其实是访问了this._data.xxx,这就是代理模式。
增加proxy后代码如下

function proxy(vm,source,key) {
   
    Object.defineProperty(vm,key,{
   
        get(){
   
            return vm[source][key]
        },
        set(newValue){
   
            return vm[source][key] = newValue
        }
    })
}
function initData(vm) {
   
    // 获取用户传入的data
    let data = vm.$optios.data
    // 判断是不是函数,我们知道vue,使用data的时候可以data:{}这种形式,也可以data(){return{}}这种形式
    // 然后把把用户传入的打他数据赋值给vm._data
    data = vm._data = typeof data === 'function' ? data.call(vm) : data ||{
   }

    for (let key in data) {
   
        proxy(vm,"_data",key)
    }

    observe(data)
}

实现原理非常简单,实际上就是但我们想要获取this.wife时,其实是去获取this._data.wife

至此,我们已经实现了数据监听,但是还有个问题,即Object.defineProperty的问题,也是面试常见的问题,即Object.defineProperty是无法监听数组的变化的

3 重写数组方法

如图所示,我们企图往数组arr中添加值,结果发现新添加进去的值是没办法被监听到的,因此,我们需要改写push等方法

let vm = new MyVue({
   
    el: '#app',
    data(){
   
        return{
   
            message:'大家好',
            wife:{
   
                name:"angelababy",
                age:28
            },
            arr:[1,2,{
   name:"赵丽颖"}]
        }
    }
})
vm.arr.push({
   hah:'dasd'})

基本思路就是之前我们调用push方法时,是从Aarray.prototype寻找这个方法,我们改成用一个空对象{} 继承 Aarray.prototype,然后再给空对象添加push方法

{
    push:function(){}
}

这样,我们调用push的时候,实际上就是调用上面{}中的push

现在,我们先区分出用户传入的Observe中接受监听的data是数组还是对象,如果是数组,则改变数组的原型链,这样才能改变调用push时,是调用我们自己设置的push,
只需要在Observe添加判断是数组还是对象即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值