new Vue发生了什么
new Vue 背后发生了哪些事情。我们都知道,new 关键字在 Javascript 语言中代表实例化是一个对象,而 Vue 实际上是一个类,类在 Javascript 中是用 Function 来实现的
function Vue(options){
// options 为用户传入的选项
this._init(options); // 初始化操作, 组件
}
可以看到 Vue 只能通过 new 关键字初始化,然后会调用 this._init 方法
export function initMixin(Vue) { // 表示在vue的基础上做一次混合操作
Vue.prototype._init = function(options) {
// el,data
const vm = this; // var that = this;
vm.$options = options; // 后面会对options进行扩展操作
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
// 对数据进行初始化 watch computed props data ...
initState(vm)// vm.$options.data 数据劫持
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if(vm.$options.el){
// 将数据挂载到这个模板上
vm.$mount(vm.$options.el);
}
}
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options
el = document.querySelector(el);
vm.$el = el;
// 把模板转化成 对应的渲染函数 =》 虚拟dom概念 vnode =》 diff算法 更新虚拟dom =》 产生真实节点,更新
if(!options.render){ // 没有render用template,目前没render
let template = options.template;
if(!template && el){ // 用户也没有传递template 就取el的内容作为模板
template = el.outerHTML;
let render = compileToFunction(template);
options.render = render;
}
}
// options.render 就是渲染函数
// 调用render方法 渲染成真实dom 替换掉页面的内容
mountComponent(vm,el); // 组件的挂载流程
}
}
总结:Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等
initState初始化数据劫持
数组和对象类型当值变化时如何劫持到。对象内部通过 defineReactive 方法,使用
Object.defineProperty 将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。 多层对象是通过递归来实现劫持
状态初始化
export function initState(vm) { // 状态的初始化
const opts = vm.$options;
if (opts.data) {
initData(vm);
}
//先不管computed和watch中的数据,先只劫持data中的数据
// if(opts.computed){
// initComputed();
// }
// if(opts.watch){
// initWatch();
// }
}
//将_data中的数据,挂载到vm上面
function proxy(vm,source,key){
Object.defineProperty(vm,key,{
get(){
return vm[source][key];
},
set(newValue){
vm[source][key] = newValue
}
})
}
function initData(vm) { //
let data = vm.$options.data; // vm.$el vue 内部会对属性检测如果是以$开头 不会进行代理
// vue2中会将data中的所有数据 进行数据劫持 Object.defineProperty
// 这个时候 vm 和 data没有任何关系, 通过_data 进行关联
data = vm._data = isFunction(data) ? data.call(vm) : data;
// 用户去vm.xxx => vm._data.xxx
for(let key in data){ // vm.name = 'xxx' vm._data.name = 'xxx'
proxy(vm,'_data',key);
}
observe(data);
}
监听数据变化observe方法实现
- 如果数据是对象 会将对象不停的递归 进行劫持
- 如果是数组,会劫持数据的方法,并对数据中不是基本数据类型的进行检测
// 检测数据变化 类有类型 , 对象无类型
class Observer {
constructor(data) { // 对对象中的所有属性 进行劫持
Object.defineProperty(data,'__ob__',{
value:this,
enumerable:false // 不可枚举的
})
data.__ob__ = this; // 所有被劫持过的属性都有__ob__
if(Array.isArray(data)){
// 数组劫持的逻辑
// 对数组原来的方法进行改写, 切片编程 高阶函数
data.__proto__ = arrayMethods;
// 如果数组中的数据是对象类型,需要监控对象的变化
this.observeArray(data);
}else{
this.walk(data); //对象劫持的逻辑
}
}
observeArray(data){ // 对我们数组的数组 和 数组中的对象再次劫持 递归了
// [{a:1},{b:2}]
data.forEach(item=>observe(item))
}
walk(data) { // 对象
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
})
}
}
// vue2 会对对象进行遍历 将每个属性 用defineProperty 重新定义 性能差
function defineReactive(data,key,value){ // value有可能是对象
observe(value); // 本身用户默认值是对象套对象 需要递归处理 (性能差)
Object.defineProperty(data,key,{
get(){
return value
},
set(newV){
// todo... 更新视图
observe(newV); // 如果用户赋值一个新对象 ,需要将这个对象进行劫持
value = newV;
}
})
}
//调用监听方法
export function observe(data) {
// 如果是对象才观测
if (!isObject(data)) {
return;
}
if(data.__ob__){
return;
}
// 默认最外层的data必须是一个对象
return new Observer(data)
}
数组的方法监听
let oldArrayPrototype = Array.prototype
export let arrayMethods = Object.create(oldArrayPrototype);
// arrayMethods.__proto__ = Array.prototype 继承
let methods = [
'push',
'shift',
'unshift',
'pop',
'reverse',
'sort',
'splice'
]
methods.forEach(method =>{
// 用户调用的如果是以上七个方法 会用我自己重写的,否则用原来的数组方法
arrayMethods[method] = function (...args) { // args 是参数列表 arr.push(1,2,3)
oldArrayPrototype[method].call(this,...args); // arr.push(1,2,3);
let inserted;
let ob = this.__ob__; // 根据当前数组获取到observer实例
switch (method) {
case 'push':
case 'unshift':
inserted = args ; // 就是新增的内容
break;
case 'splice':
inserted = args.slice(2)
default:
break;
}
// 如果有新增的内容要进行继续劫持, 我需要观测的数组里的每一项,而不是数组
// 更新操作.... todo...
if(inserted) ob.observeArray(inserted)
// arr.push(1,2)
// arr.splice(0,1,xxxx)
}
})