###环境搭建
第一步 创建项目:
npm init -y
第二步 安装对应的插件:
npm i rollup rollup-plugin-babel @babel/core @babel/preset-env --save-dev
第三步 全局下创建rollup配置文件 rollup.config.js
import babel from 'rollup-plugin-babel'
export default {input:'./src/index.js', // 入口文件output:{file:'./dist/vue.js', //出口文件name:'Vue', // global.Vueformat:'umd', // umd格式sourcemap:true, // 调式代码 debug}, plugins:[babel({excludes:'node_modules/**'}) // 忽略文件]}
第四步 修改package.js文件的配置:
将代码修改成
"script":{"dev":"rollup -c -w" // 启动rollup的命令 }
第五步 创建.babelrc文件
{"presets":["@babel/preset-env"]
}
### 搭建基本的目录结构
项目根目录下src文件夹创建index.js文件, 项目根目录创建dist文件夹创建vue.js文件和index.html文件。
这时候index.js 文件中随便输入代码, 运行npm run dev将会把打包的代码,同步在dist/vue.js文件中。
dist文件下的index.html代码引入vue.js文件,并创建vue的实例对象传递参数,参数是一个对象,有data el methods coputed等方法
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script src="./vue.js"></script><script>const vm = new Vue({data(){return { name:'zs',age:20 }}})</script>
</body>
</html>
src/index.js 文件代码如下: 需要创建一个Vue的构造函数
function Vue(options){// 挂载一个初始化数据的方法_init()this._init(options) // this执行创建出来的Vue的实例对象 也就是dist/index.js中的vm
}
export default Vue //将这个构造函数导出
此刻项目结构如下
###初始化数据 对数据做劫持
首先创建initMiXin的方法接受一个Vue作为参数,给Vue原型添加一个初始化数据的_init的方法
import {initMiXin} from './init' // 导入一个方法 在init.js文件中
function Vue(options){this._init(options) // this执行创建出来的Vue的实例对象 也就是dist/index.js中的vm
}
initMiXin(Vue) //将Vue实例作为参数 传递出去
export default Vue //将这个构造函数导出
在与src/index.js文件 同级目录下创建一个state.js初始化数据的方法
import {observe} from './observe/index' // 这里先引入后期再下面要创建的方法export const initMiXin = (Vue){Vue.prototype._init(options) { // 这里接收的是src/index.js中传递的参数const vm = this // 这里的this是Vuevm.$options = options // 把数据挂载在vm.$options的属性上面initState(vm) // 初始化数据} }function initState(vm){const ops = vm.$optionsif(ops.data){ // 如果有data这个属性initData(vm) // 初始化Data}}function initData(vm){ let data = vm.$options.data data = typeof data === 'function'? data() : data // 判断data类型如果是函数的话就执行vm._data= data //再往Vue上面挂载一个_data的属性observe(data) // 这里对数据进行劫持
}
在src目录下创建observe文件夹创建index.js文件
export const observe = (data)=>{if(typeof data !=='object' || data=null){ // 对data数据进行处理return false // 后面的代码不用执行因为data返回值需要是一个对象} return new Observer(data) // 创建一个Observer的类 来对数据进行处理
}class Observer{constructor(data){this.wark(data)}wark(data){ // 挂载在Observe原型上面的方法// 循环每一项 创建defineReactive 劫持对象中每一个属性Object.keys(data).forEach(key=>defineReactive(data,key,data[key]))}
}export function defineReactive(data,key,value) {observe(value) // 如果属性值或者数据也要遍历进行劫持Object.defineProperty(data,key,{get(){return value},set(newValue){if(newValue === value) return // 不相同的时候再重新赋值observe(newValue) // 对设置的新的属性值也要劫持value = newValue}})
}
如果此刻我们访问vm实例对象中的数据的时候,还需要使用vm._data.name vm._data.age才能访问到,此刻我们实现vue中 只需要this.name this.age就可了 。我们需要在state.js文件中实现
import {observe} from './observe/index' // 这里先引入后期再下面要创建的方法export const initMiXin = (Vue){Vue.prototype._init(options) { // 这里接收的是src/index.js中传递的参数const vm = this // 这里的this是Vuevm.$options = options // 把数据挂载在vm.$options的属性上面initState(vm) // 初始化数据} }function initState(vm){const ops = vm.$optionsif(ops.data){ // 如果有data这个属性initData(vm) // 初始化Data}}function initData(vm){ let data = vm.$options.data data = typeof data === 'function'? data() : data // 判断data类型如果是函数的话就执行vm._data= data //再往Vue上面挂载一个_data的属性observe(data) // 这里对数据进行劫持// ++++++++++++++++++++++增加的代码for(let key in data){ // +++++ 增加的代码 使用vm来代理就可以proxy(vm,_data,key) +++++} ++++++
} proxy(vm,target,key){ // 以下都是增加的代码 ++++++++Object.defineProperty(vm,key,{ // 这里相当于后期执行 vm.name vm.age get(){return vm[target][key] // vm._data.name vm._data.age},set(newValue){vm[target][key] = newValue // vm.name='ls' === vm._data.name = 'ls'}})
}
此刻 我们如果对象中的某个属性值不再是对象 而是 数组的话 我们就需要重新写数组的方法了。
我们在observe/index.js文件下继续修改代码 增加后期修改的属性值是对象的判断。
export const observe = (data)=>{if(typeof data !=='object' || data=null){ // 对data数据进行处理return false // 后面的代码不用执行因为data返回值需要是一个对象} return new Observer(data) // 创建一个Observer的类 来对数据进行处理
}class Observer{constructor(data){++++ 这里我们需要对data数据进行判断是否为数组类型了if(Array.isArray(data)){ +++ 如果是数组 重写数组方法修改数组本身this.observeArray(data) }else { +++ 不是数组继续执行下面数据劫持的方法this.wark(data)}}wark(data){ // 挂载在Observe原型上面的方法// 循环每一项 创建defineReactive 劫持对象中每一个属性Object.keys(data).forEach(key=>defineReactive(data,key,data[key]))}obseerveArray(data){ +++ 观察数据 如果数组中有对象的话会被劫持没有对象就不会被劫持data.forEach(item=>observe(item))}
}export function defineReactive(data,key,value) {observe(value) // 如果属性值或者数据也要遍历进行劫持Object.defineProperty(data,key,{get(){return value},set(newValue){if(newValue === value) return // 不相同的时候再重新赋值observe(newValue) // 对设置的新的属性值也要劫持value = newValue}})
}
接下来我们重写数组的方法:observe文件夹下创建array.js文件
let oldArrayProto = Array.prototype //将array原型上面的所有属性和方法赋值一份export let newArrayProto = Object.create(oldArrayProto) // 给newArrayProto创建原型prototypelet methods = ['push','pop','shift','unshift','reverse','sort','splice'] // 重写数组方法methods.forEach(method=>{newArrayProto[method] = function (...ags) { // 重写了数组的方法const result = oldArrayproto[method].call(this,...ags) let inserted;let ob = this.__ob__;switch(method){case 'push':case 'unshift':inserted = ags;break;case 'splice':inserted = args.slice(2)default:brack;}if(inserted) {ob.observeArray(inserted)}return result }
})
接下来将数组重写的方法 挂载在data的__proto__的属性上面; 在observe/index.js文件下
import {newArrayProto} from './array.js'
class Observer{constructor(data){data.__ob__= this // 给数据增加一个标识 如果有__ob__说明数据被观察过了if(Array.isArray(data)){ // 将这里的代码修改如下 data.__proto__ = newArrayProto // +++++增加代码this.observeArray(data)}else { this.wark(data)}}
在observe./index.js文件中可以增加判断数据是否被检测过了
export const observe = (data)=>{if(typeof data !=='object' || data=null){ // 对data数据进行处理return false // 后面的代码不用执行因为data返回值需要是一个对象} if(data.__ob__ instanceof Observer) { +++++ // 判断数据是否被检测过了吗return data.__ob__ }return new Observer(data) // 创建一个Observer的类 来对数据进行处理
}
将__ob__变为不可枚举的这样在遍历的时候就不会遍历到了 observe/index.js文件下
import {newArrayProto} from './array.js'class Observer{constructor(data){Oject.defineProperty(data,'__ob__',{ ++++++++++++++++++++value:this,enumerable:false // 不可枚举})// ------------- 替换这行代码 data.__ob__= this if(Array.isArray(data)){ // 将这里的代码修改如下 data.__proto__ = newArrayProto // +++++增加代码this.observeArray(data)}else { this.wark(data)}}