vue2源码解析之第一步(对数据进行劫持)
###环境搭建
第一步 创建项目:
? ? ? ? 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.Vue
format:'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是Vue
vm.$options = options // 把数据挂载在vm.$options的属性上面
initState(vm) // 初始化数据
}
}
function initState(vm){
const ops = vm.$options
if(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是Vue
vm.$options = options // 把数据挂载在vm.$options的属性上面
initState(vm) // 初始化数据
}
}
function initState(vm){
const ops = vm.$options
if(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创建原型prototype
let 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)
}
}
?
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!