开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app" style="color: red;">
hello{{name}}
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
data() {
return {
name: '阿伟',
age: 26,
}
},
el: '#app',
})
</script>
</body>
</html>
// index.js
import { initMixin } from './init'
// option是用户的选项,data、methods等
function Vue(options) {
// 调用原型中的方法
this._init(options)
}
在Vue原型中添加_init方法
// init.js
export function initMixin(Vue) {
// 初始化
Vue.prototype._init = function (options) {
// 将选项保存到实例上,方便在其他原型方法中使用
const vm = this
vm.$options = options
// 初始化状态
initState(vm)
}
}
initState方法判断options中的data是否存在,存在就执行initData
initData函数中先判断data是函数还是对象,如果是函数的话就用call绑定vue的实例,循环data绑定到vue实例上就可以用vm.属性拿到vm.data上的数据
将data传入observe函数中
// state.js
import { observe } from './observe/index'
export function initState(vm) {
// 获取所有选项
const opts = vm.$options
// 如果data存在就执行初始化data操作
if (opts.data) {
initData(vm)
}
}
// 对data初始化
function initData(vm) {
let data = vm.$options.data
// data可能是一个函数、对象,函数需要修改修改this并执行
data = typeof data === 'function' ? data.call(vm) : data
// 将data复制给实例的_data
vm._data = data
// 对数据进行劫持
observe(data)
// 将_data中的数据代理到vm,相当于vm.name = vm._data.name
for (const key in data) {
proxy(vm, '_data', key)
}
}
function proxy(vm, target, key) {
// 用vm.来取属性时,会到vm._data中取
Object.defineProperty(vm, key, {
get() {
return vm[target][key]
},
set(newVal) {
vm[target][key] = newVal
},
})
}
observe先判断data是不是一个对象,如果不是就直接返回,是对象就执行Observe类
Observe类先判断data是否是数组对象还是obj对象,如果是数组对象就把data的隐式原型(proto)指向重写数组方法对象的显示原型(prototype),如果数组的值是对象的话就传入observeArray,observeArray把值递归observe,如果是obj对象walk方法将data遍历执行defineReactive
defineReactive函数首先会判断value传入observe递归判断是否是对象,如果是对象就递归。在获取值时会赋值一个newVal,首先判断新值跟旧值是否一样,再传入observe递归判断是否是对象
// observe/index.js
import { newArrayProto } from './array'
export function observe(data) { // {name: '阿伟',age: 26}
// 只对对象劫持
if (typeof data !== 'object' || data === null) {
return
}
// 执行劫持方法类
return new Observe(data)
}
class Observe {
constructor(data) { // {name: '阿伟',age: 26}
// 将Observe的this保存data自定义属性中,方便在执行数组方法中调用Observe的observeArray
// 给数据加了一个标识,如果数据有__ob__就说明这个属性已经被观测过了
Object.defineProperty(data, '__ob__', {
value: this,
// 将__ob__变成不可枚举,解决死循环问题
enumerable: false,
})
// 如果是数组
if (Array.isArray(data)) {
// 保留数组原有的特性,但是重写了7个可以修改原数组的方法(push、shift等)
data.__proto__ = newArrayProto
// 处理数组对象[{a:1}]
this.observeArray(data)
} else {
this.walk(data)
}
}
// 循环对象,对属性依次劫持
walk(data) {
Object.keys(data).forEach((key) => {
defineReactive(data, key, data[key])
})
}
// 循环数组,对属性依次劫持
observeArray(data) {
// 数组中的值也要劫持
data.forEach((item) => {
observe(item)
})
}
}
export function defineReactive(data, key, val) {
// 对所有的对象都进行属性劫持
observe(val)
Object.defineProperty(data, key, {
// 取值时会执行get
get() {
// console.log('获取值')
return val
},
// 修改值时会执行set
set(newVal) {
// console.log('设置值')
if (newVal === val) {
return
}
observe(newVal)
val = newVal
},
})
}
先获取到数组的显示原型(prototype)
使用Object.create创建一个空对象,这个空对象的隐式原型(proto)执行数组的显示原型
给newArrayProto添加push等修改原数组的方法
// array.js
// 获取数组原型
let oldArrayProto = Array.prototype
// 创建一个新对象,这个新对象的原型指向oldArrayProto,newArrayProto.__proto__ === oldArrayProto==>(Array.prototype),在newArrayProto添加push等方法,也不会修改数组原型上的方法
export let newArrayProto = Object.create(oldArrayProto)
// 可以修改原数组的方法
let methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice']
methods.forEach((item) => {
newArrayProto[item] = function (...args) {
// 执行数组中的方法,并将this绑定到执行该方法的数据(xxx.push,this为xxx)上
const result = oldArrayProto[item].call(this, ...args)
let inserted
let ob = this.__ob__
// 使用push等方法时需要对传入的参数进行劫持
switch (item) {
// 将传递的参数保存
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
default:
break
}
// 如果添加的数据是对象类型的话也要进行劫持
if (inserted) {
ob.observeArray(inserted)
}
return result
}
})