Appearance
二、响应式原理
1、定义并导出构造函数
js
// src\index.js
import { initMixin } from "./init";
// Vue构造函数
function Vue(options) {
// 入口方法,做初始化操作
this._init(options)
}
// 插件思想:对原型进行扩展
initMixin(Vue)
export default Vue2、定义初始化混合方法
js
// src\init.js
import { initState } from "./state"
// 定义init方法,进行扩展Vue的原型
export function initMixin(Vue) {
// 初始化方法
Vue.prototype._init = function(options) {
// 拿到当前实例
const vm = this
// 拿到配置参数挂载到$options
vm.$options = options
// 初始化状态:将数据做一个初始化的劫持,数据改变就去更新视图
initState(vm)
// 其他初始化方法
// initEvents
}
}3、初始化状态
js
// src\state.js
/**
* 初始化状态
* 顺序:props > methods > data > computed > watch
*/
export function initState(vm) {
const opts = vm.$options
// 按照顺序依次拆分初始化
if (opts.props) {
initProps(vm)
}
if (opts.methods) {
initMethods(vm)
}
if (opts.data) {
initData(vm)
}
if (opts.computed) {
initComputed(vm)
}
if (opts.watch) {
initWatch(vm)
}
}
function initProps(vm) {}
function initMethods(vm) {}
function initData(vm) {}
function initComputed(vm) {}
function initWatch(vm) {}4、初始化数据
js
// src\state.js
import { observe } from "./observer/index"
// 初始化数据方法
function initData(vm) {
let data = vm.$options.data;
// 拿到data属性,如果是函数直接执行,其余放行
vm._data = data = typeof data === 'function' ? data.call(vm) : data;
// 数据劫持方案
// 对象Object.defineProperty
// 数组 单独处理:拦截可以改变数组的方法进行操作
// 观测数据
observe(data);
}5、对象劫持-递归属性
- data本身为对象,需要观测
- data对象里面还有对象,需要递归观测
- 给data设值的新值也是对象,需要进行递归观测
js
// src\observer\index.js
/**
* 数据观测类
* 使用defineProperty 重新定义属性
*/
class Observer {
constructor(value) {
// 判断一个对象是否被观测过,看他有没有__ob__这个属性
Object.defineProperty(value, '__ob__', {
enumerable: false, // 不能被枚举,不能被循环出来
configurable: false,
value: this // 注入当前的实例对象
})
// 对象处理
this.walk(value);
}
walk(data) {
// 遍历对象,进行循环观测
let keys = Object.keys(data);
keys.forEach(key => {
defineReactive(data, key, data[key]); // 源码对应 > Vue.util.defineReactive
})
}
}
// ES5的双向数据绑定类
// vue2慢的核心原因就是这个方法
// vue2 应用了defineProperty需要一加载的时候 就进行递归操作,所以好性能,如果层次过深也会浪费性能
// 1.性能优化的原则:
// 1) 不要把所有的数据都放在data中,因为所有的数据都会增加get和set
// 2) 不要写数据的时候 层次过深, 尽量扁平化数据
// 3) 不要频繁获取数据
// 4) 如果数据不需要响应式 可以使用Object.freeze 冻结属性
function defineReactive(data, key, value) {
// 如果值是对象进行递归观测
observe(value);
// 定义双向绑定,进行get和set的观测
Object.defineProperty(data, key, {
get() {
console.log('取值');
return value
},
set(newValue) {
console.log('设值');
// 值没变化就跳过
if (newValue == value) return;
// 如果用户设值的值是对象,需要再次进行递归观测
observe(newValue);
// 更新值
value = newValue;
}
})
}
/**
* 数据观测
*/
export function observe(data) {
// 对象数据校验:不是对象 或 null 就返回
if (typeof data !== 'object' || data === null) {
return data
}
// 如果数据被观测过,直接返回,防止重复观测
if (data.__ob__) {
return data
}
// 数据观测
new Observer(data);
}6、数组劫持-重写原型
- 如果data是数组,需要重写能改变数组的七个方法
- 如果data数组里面每个item都是对象,那么需要循环遍历,观测每一项item数据
- 如果给data数组新增的数据也是对象,那么新增的对象也需要进行观测
js
// src\observer\index.js
import { arrayMethods } from "./array";
// 1、改写观测类
class Observer {
constructor(value) {
if (Array.isArray(value)) {
// 数组处理:函数劫持、切片编程思想
// 重写push shift pop unshift splice sort reverse
value.__proto__ = arrayMethods;
// 观测数组中的对象类型
this.observeArray(value);
} else {
// 对象处理
this.walk(value);
}
}
// 数组观测方法
observeArray(value) {
// 遍历数组的每一项进行观测
value.forEach(item => {
observe(item);
})
}
}js
// src\observer\array.js
// 拿到数组原型上的方法
let oldArrayProtoMethods = Array.prototype
// 原型继承 arrayMethods.__proto__ = oldArrayProtoMethods
export let arrayMethods = Object.create(oldArrayProtoMethods)
let methods = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]
// 数组方法重写
methods.forEach(method => {
arrayMethods[method] = function(...args) {
console.log('数组调用');
// this为observer里面的value
const result = oldArrayProtoMethods[method].apply(this, args);
let inserted;
let ob = this.__ob__;
switch (method) {
case 'push':
case 'unshift':
// 如果追加的内容也是对象,需要再次进行对象劫持
inserted = args;
break;
case 'splice':
// 截取参数下标为2到末尾:arr.splice(0, 1, {a: 1})
inserted = args.slice(2);
default:
break;
}
// 如果给数组新增的值是对象要继续进行观测
if (inserted) ob.observeArray(inserted)
return result
}
})7、数据代理
- 将取值全部代理到vm上面 vm.message = vm._data.message
js
// src\state.js
// 代理方法
function proxy(vm, data, key) {
Object.defineProperty(vm, key, {
get() {
return vm[data][key]; // vm_data.a
},
set(newValue) { // vm.a = 100
vm[data][key] = newValue; // vm._data.a = 100
}
})
}
// 初始化数据
function initData(vm) {
let data = vm.$options.data;
vm._data = data = typeof data === 'function' ? data.call(vm) : data;
// 用代理,从vm取属性,代理到vm_data上
for (let key in data) {
proxy(vm, '_data', key)
}
// 观测数据
observe(data);
}