手写Vue核心原理(一)

文章介绍了如何使用Rollup搭建JavaScript开发环境,重点阐述了Rollup在库开发中的作用。同时,深入讲解了Vue的响应式原理,包括数据的初始化、数据代理、Observer类以及数组方法的劫持,展示了Vue如何实现数据的双向绑定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源码地址:gitee源码地址

一.使用Rollup搭建开发环境

1.什么是Rollup?

Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码, rollup.js更专注于Javascript类库打包 (开发应用时使用webpack,开发库时使用Rollup)

2.环境搭建

安装rollup环境

npm install @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D

rollup.config.js文件编写

import babel from 'rollup-plugin-babel';
import serve from 'rollup-plugin-serve';
export default {
    input: './src/index.js',
    output: {
        format: 'umd', // 模块化类型
        file: 'dist/umd/vue.js', 
        name: 'Vue', // 打包后的全局变量的名字
        sourcemap: true
    },
    plugins: [
        babel({
            exclude: 'node_modules/**'
        }),
        process.env.ENV === 'development'?serve({
            open: true,
            openPage: '/public/index.html',
            port: 3000,
            contentBase: ''
        }):null
    ]
}

配置.babelrc文件

{
    "presets": [
        "@babel/preset-env"
    ]
}

执行脚本配置

"scripts": {
    "build:dev": "rollup -c",
    "serve": "cross-env ENV=development rollup -c -w"
}

二.Vue响应式原理

导出vue构造函数

import {initMixin} from './init';

function Vue(options) {
    this._init(options);
}
initMixin(Vue); // 给原型上新增_init方法
export default Vue;

init方法中初始化vue状态

import {initState} from './state';
export function initMixin(Vue){
    Vue.prototype._init = function (options) {
        const vm  = this;
        vm.$options = options
        // 初始化状态
        initState(vm)
    }
}

根据不同属性进行初始化操作

export function initState(vm){
    const opts = vm.$options;
    if(opts.props){
        initProps(vm);
    }
    if(opts.methods){
        initMethod(vm);
    }
    if(opts.data){
        // 初始化data
        initData(vm);
    }
    if(opts.computed){
        initComputed(vm);
    }
    if(opts.watch){
        initWatch(vm);
    }
}
function initProps(){}
function initMethod(){}
function initData(){}
function initComputed(){}
function initWatch(){}

1.初始化数据

import {observe} from './observer/index.js'
function initData(vm){
    let data = vm.$options.data;
    data = vm._data = typeof data === 'function' ? data.call(vm) : data;
    observe(data);
}

2.递归属性劫持

class Observer { // 观测值
    constructor(value){
        this.walk(value);
    }
    walk(data){ // 让对象上的所有属性依次进行观测
        let keys = Object.keys(data);
        for(let i = 0; i < keys.length; i++){
            let key = keys[i];
            let value = data[key];
            defineReactive(data,key,value);
        }
    }
}
function defineReactive(data,key,value){
    observe(value);
    Object.defineProperty(data,key,{
        get(){
            return value
        },
        set(newValue){
            if(newValue == value) return;
            observe(newValue);
            value = newValue
        }
    })
}
export function observe(data) {
    if(typeof data !== 'object' || data == null){
        return;
    }
    return new Observer(data);
}

3.数组方法的劫持

import {arrayMethods} from './array';
class Observer { // 观测值
    constructor(value){
        if(Array.isArray(value)){
            value.__proto__ = arrayMethods; // 重写数组原型方法
            this.observeArray(value);
        }else{
            this.walk(value);
        }
    }
    observeArray(value){
        for(let i = 0 ; i < value.length ;i ++){
            observe(value[i]);
        }
    }
}

重写数组原型方法

//! 重写数组方法,进行数组函数劫持

// 获取原来的数组方法
let oldArrayProtoMethods = Array.prototype;

export let ArrMethods = Object.create(oldArrayProtoMethods);

let methods = ['push', 'pop', 'unshift', 'shift', 'splice','reverse','sort'];

methods.forEach((item) => {
    ArrMethods[item] = function (...args) {
        let result = oldArrayProtoMethods[item].apply(this, args);
        let inserted;
        switch (item) {
            case 'push':
            case 'unshift':
                inserted = args;
                break;
            case 'splice':
                inserted = args.splice(2);
                break;
        }
        let ob = this.__ob__;
        if (inserted) {
            ob.observerArray(inserted); // 对我们的添加的对象进行劫持
        }
        ob.dep.notify();
        return result;
    };
});

增加__ob__属性

class Observer { 
    constructor(value){
        Object.defineProperty(value,'__ob__',{
            enumerable:false,
            configurable:false,
            value:this
        });
        // ...
    }
 }

给所有响应式数据增加标识,并且可以在响应式上获取Observer实例上的方法

4.数据代理


//! Vue2 对data进行初始化
function initData(vm) {
  //> 会有两种初始化方式 1. 对象  2. 函数
  let data = vm.$options.data;
  data = vm._data = typeof data === "function" ? data.call(vm) : data;

  //> 将data上的数据全部代理到vm上
  for (const key in data) {
    if (Object.hasOwnProperty.call(data, key)) {
      proxy(vm, "_data", key);
    }
  }

  //> 对数据data进行劫持
  observer(data);
}

function proxy(vm, score, key) {
  Object.defineProperty(vm, key, {
    get() {
      return vm[score][key];
    },
    set(newValue) {
      vm[score][key] = newValue;
    },
  });
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龟中的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值