目录
1. rollup打包工具环境的搭建
为什么需要打包?
1. 我们在学习vue2源码的工程中,为了有条理会将js代码写道许多的文件当中,那么在html上引入就需要多个script标签,使用打包过后将许多零碎的文件打包成一个整体,页面只需请求一次,js文件中使用模块化互相引用(export、import ),这样能在一定程度上提供页面渲染效率。
2. 打包同时也将高级语法如es6,箭头函数,let关键字等,转换为es5的语法更好的兼容低级的浏览器。
1.1install rollup
在webstrom软件上,创建一个新项目后,点Terminal
1. npm init
生成package.json文件,并在文件中更改配置如下图,-c指定配置文件,w表示监控文件更新。
2. npm install rollup rollup-plugin-babel @babel/core @babel/preset-env --save-dev
npm install rollup :安装rollup,
rollup-plugin-babel:要在rollup里面使用babel
@babel/core:babel的核心模块
@babel/preset-env:高版本语法转成低版本语法
--save-dev:表示开发时依赖
1.2配置rollup
1.在项目目录下创建src目录,然后创建入口文件index.js
2. 在项目目录下创建rollup的配置文件
3. 在rollup.config.js中写如下配置,然后npm run dev即可有dest文件夹生成
// 导出打包的配置文件
import babel from "rollup-plugin-babel"
export default {
input: "./src/index.js",//入口
output: {
file: "./dist/vue.js",//出口
name: "Vue", // 在全局上增加一个Vue属性
format: "umd", // 兼容(common.js amd)
sourcemap: true // 表示可以调试源代码
},
// 表示在打包时依赖的插件
plugins: [
babel({
"presets": [
"@babel/preset-env"
],
exclude: "node_modules/**"
})
]
}
2. 初始化数据
这个部分讲述的是,对于new Vue之后发生的一些事情,首先我们应该收集依赖
const vue = new Vue({
data() {
return {
name: 'aaa',
age: 18
}
}
})
我们首先对应就需要一个叫做Vue的构造函数,采用了es5的一种写法function Vue(){} 然后通过Vue.prototype.xxx = function(){} 的方式去增加它的方法。而不是通过es6的class Vue的这种方式去创建一个类
主要的原因就是因为,class的方式需要将方法写在class内,太过于耦合,且不利于分不同js文件去处理
在index.js内的代码
import {initMixin} from "./init";
function Vue(options) {
this._init(options)
}
initMixin(Vue)
export default Vue
构造函数Vue要求传入一个options的参数,也就是配置对象,然后通过initMixin函数去给他的原型上面加一个_init的初始化方法。由于在另一个文件内找不到这个Vue所以需要将Vue传到initMixin里面
在init.js内的代码
import {initState} from "./state";
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this
vm.$options = options
initState(vm)
}
}
存储this,然后将options挂载到实例上面,使原型上的方法都可以看到这个options,减少参数传递
在state.js里的代码
export function initState(vm) {
if(vm.$options.data) {
initDate(vm)
}
}
function initDate(vm) {
let data = vm.$options.data
data = typeof data === 'function' ? data.call(vm) : data
console.log(data);
}
初始化状态,查看options里面是否有data,有就初始化data,由于在vue2中,根实例可以是对象也可以是函数,所以对这个data进行一下判断。
3. 对象的响应式原理
经过上面的操作,现在已经拿到了data,那么我们需要对data进行一下观测,也就是对于data进行一个属性劫持
那么我们会使用Object.defineProperty方法,对属性进行重写,为什么要这样做呢?
好处就是Object.defineProperty提供了两个方法,get,set能够在属性值发生变化或者查看属性值时监控到
class Observe {
constructor(data) {
this.walk(data)
}
walk(data) {
// 重写属性
Object.keys(data).forEach(key => defineActive(data,key,data[key]))
}
}
export function defineActive(target,key,value) {
// 如果属性是一个对象,那么就再次调用观测方法
observe(value)
Object.defineProperty(target,key,{
get() {
return value
},
set(newValue) {
if(newValue === value) return
value = newValue
}
})
}
export function observe(data) {
// 如果说是对象类型就对他进行观察
if(typeof data != 'object' || data == null) return
return new Observe(data);
}
state.js 的代码
function proxy(vm,target,key) {
Object.defineProperty(vm,key,{
get() {
return vm[target][key]
},
set(v) {
// 如果新值是对象,就代理
observe(v)
vm[target][key] = v
}
})
}
function initDate(vm) {
let data = vm.$options.data
data = typeof data === 'function' ? data.call(vm) : data
// 将用户数据挂载到实例上
vm._data = data
// 观测方法进行数据劫持
observe(data)
// 将_data上的属性代理到vm上
for (const dataKey in data) {
proxy(vm,'_data',dataKey)
}
}
4. 对数组进行劫持
根据上面写的代码,data里的属性是对象时,会导致将数组里的所有元素都添加上,get和set方法,如下图所示
这样的话看起来好像不错,但是会导致过多的资源浪费。因为用户在操作数组时基本使用的是数组的方法,如push shift等,一般不会使用索引的形式去操作数据,那么我们就需要对数组做另外的处理。
4.1 将数组里的每一个对象都添加上响应式
代码实现
4.2重写7个变异方法
保留数组的原有特性并重写数组的部分方法,也就是要调用原有的方法,然后参杂着我们自己所需的代码
在observe目录下新建array.js文件
let oldArrayProto = new Array()
// 保留数组原有特性
export let newArrayProto = Object.create(oldArrayProto)
let methods = [
'push',
'shift',
'unshift',
'pop',
'reverse',
'sort',
'splice'
]
methods.forEach(method =>{
// 重写部分方法
newArrayProto[method] = function (...args) {
let result = oldArrayProto[method].call(this,...args)
// 需要对新增的属性再次劫持
let inserted;
let ob = this.__ob__;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2)
break
default:
break
}
// 有添加的内容,就对其增加
if(inserted) {
// 添加响应式
ob.observeArray(inserted);
}
return result
}
})
observe目录下index.js文件新内容
import {newArrayProto} from "./array.js";
class Observe {
constructor(data) {
// 添加一个ob属性给数组新添加的元素加响应式用,要调observeArray
Object.defineProperty(data,'__ob__',{
value: this,
enumerable:false
})
if(Array.isArray(data)) {
// 改变数组原型,增加变异方法
data.__proto__ = newArrayProto;
this.observeArray(data)
}else {
this.walk(data)
}
}
walk(data) {
// 重写属性
Object.keys(data).forEach(key => defineActive(data,key,data[key]))
}
observeArray(data) {
data.forEach(item => {
observe(item)
})
}
}
export function defineActive(target,key,value) {
// 如果属性是一个对象,那么就再次调用观测方法
observe(value)
Object.defineProperty(target,key,{
get() {
return value
},
set(newValue) {
if(newValue === value) return
value = newValue
}
})
}
export function observe(data) {
// 如果说是对象类型就对他进行观察
if(typeof data != 'object' || data == null) return
// 因为加了一条新的标识,可以对其进行验证是否观测过
if(data.__ob__ instanceof Observe) return data.__ob__
return new Observe(data);
}
这样数组的变异方法就重写完毕,可以对其进行测试,接下来会在下一篇博客,写解析模板参数,生成ast语法树,虚拟DOM转真实DOM,敬请期待........