Vue2数据劫持原理

Vue2数据劫持原理

rollup打包Vue源码配置

类库,工具库一般使用rollup打包,打包出的文件比较干净

1
2
#                rollup和babel桥梁    babel核心模块   es6=>es5       启动webpack服务
yarn add rollup rollup-plugin-babel @babel/core @babel/preset-ent rollup-plugin-serve -D

rollup简单配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// rollup.config.js
import babel from 'rollup-plugin-babel'
import serve from 'rollup-plugin-server'
export default {
input:"./src/index.js", // 入口
output:{
format:'umd', // 支持amd commonjs window.Vue
file:"./dist/vue.js", // 打包后的文件存放地址
name:"Vue", // 全局的名字就是vue
sourcemap:true // es6->es5源码映射模块
},
plugins:[
babel({
exclude:'node_modules/**' // 此目录不需要babel转化
}),
serve({
open:true,
port:3000,
contentBase:'',
openPage:'/public/index.html' // 默认打开目录
})
]
}

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

如何在原型上扩展方法

1
2
3
4
5
6
7
8
import { initMixin } from './init'
function Vue(options){
// options 为用户传入的参数
this._init(options); // 入口方法,初始化操作
}

// 写成一个个插件对原型进行扩展
initMixin(Vue);
1
2
3
4
5
6
7
8
9
// init.js
export function initMixin(Vue){
Vue.prototype._init = function(options){
// ...
}
Vue.prototype.$mount = function(el){
// ...
}
}

对象的数据劫持

初始化时调用initState(vm)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// init.js
import { initState } from './state';
export function initMixin(Vue){
Vue.prototype._init = function(options){
const vm = this; // vm 是 Vue的实例
vm.$options = options; // 把用户传入的参数挂载到vm上

initState(vm); // options 已经挂载在 vm上
}
}

// state.js
import { observe } from './observer/index';

export function initState(vm){
const opts = vm.$options; // 上面挂载了这里可以取

if(opts.data){ // 有传递data 就需要对数据进行劫持
initDate(vm);
}
}

function initDate(vm){
let data = vm.$options.data
// 把数据代理到 vm._data上方便拿取
vm._data = data = typeof data == 'function'?data.call(vm):data; // 如果是函数取返回值,不是的话就是对象

// 当去vm取值时,将属性取值代理到vm._data上
Object.keys(data).forEach(key=>{
proxy(vm,'_data',key)
})

observe(data);
}

function proxy(vm,data,key){
Object.defineProperty(vm,key,{
get(){
return vm[data][key]
},
set(newVal){
vm[data][key] = newVal;
}
})
}

// observer/index.js
class Observer{
constructor(value){
this.walk(value);
}
walk(data){
Object.keys(data).forEach(key=>{
defineReactive(data,key,data[key])
})
}
}

function defineReactive(target,key,value){
observe(value); // data中某个值还为对象需要监控
Object.defineProperty(target,key,{
get(){
return value
},
set(newValue){
if(value!=newValue){
observe(newValue); // data中某个值修改为对象需要监控新赋的值
value = newValue;
}
}
})
}


export function observe(data){
// 对象才监测
if(typeof data!= 'object' || data == null){
return; // 如果是普通值直接返回(不能返回data)
}
// 开始数据劫持
return new Observer(data);
}

数组的数据劫持

  • 核心思想是把数组的原有方法重写,在执行之前先执行自己写的方法 (AOP切片编程)
  • 只有data中的数组才会先执行自己定义的,在外部不受影响 (所有不能直接修改数组原型)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// observer/array.js
let oldArrayMethods = Array.prototype; // 保存数组原有方法

export let arrayMethods = Object.create(Array.prototype); // 通过Array.__proto__ 还能找到数组原有方法

// 只有这7中方法是因为这几个方法才会改变原有数组,vue是数据改变刷新视图的
let methods = [
'pop',
'push',
'shift',
'unshift',
'splice',
'revert',
'sort',
]

methods.forEach(method => {
arrayMethods[method] = function(...args){
let result = oldArrayMethods[method].call(this,args); // 数组老的方法
// 新插入数组中的值也需要变成响应式 => 调用Observer中的observeArray
let insert;
let ob = this.__ob__;

switch (method){
case "push":
case "unshift":
insert = args;
break;
case "splice":
insert = args.slice(2); // 第三个以后是新增的
break;
default:
break;
}
if(insert) ob.observeArray(insert) // 数组新增的值有可能是对象 也需要深层监控 (调用 Observe中的 arrayObserve 监控)

return result
}
})


// observer/index.js
import { arrayMethods } from './array.js'

class Observer{
constructor(value){
// 给每个值增加__ob__指向Observer这个类(方便value通过__ob__调用observeArray方法)
Object.defineProperty(value,'__ob__',{
value:this,
enumerable:false, // 不可枚举,防止被循环出来
configurable:false
})
// 如果是数组的话如果数组长度很长每个都监控很费时间
if(Array.isArray(value)){

value.__proto__ = arrayMethods; // 数组的原型指向修改后的那些方法
// Object.setPrototypeOf(value,arrayMethods);
this.observeArray(value); // 数组中的对象变化了也需要监控
}else{
this.walk(value);
}
}
observeArray(value){ // 数组的每一项都 observer 所以效率低
value.forEach(val=>{
observe(val)
})
}
...
}

Object.create()与new Object()的区别