1.rollup项目搭建
1.1.依赖
rollup
:打包工具@babel/core
:babel核心模块@babel/preset-env
:es6->es5rollup-plugin-babel
:rollup和babel之间的桥梁
yarn add rollup @babel/core @babel/preset-env rollup-plugin-babel
1.2.rollup.config.js
import babel from 'rollup-plugin-babel';
export default {
input:'./src/index.js',
output:{
format:'umd',//支持amd和commonjs
file:'dist/vue.js',
sourcemap:true,//es5->es6映射文件
name:'Vue'
},
plugins:[
babel({//使用babel进行转化,排除node_moduels
exclude:'node_modules/**'
})
]
}
1.3..babelrc
{
"presets": ["@babel/preset-env"]
}
1.4.package.json
{
"scripts":{
"serve":"rollup -c -w"
}
}
2.响应式数据
<body>
<div id="app"></div>
<script src="./dist/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
name:'zhangsan',
family:{
father:'李四',
mather:'王武'
}
}
})
vm._data.family={
father:'章六'
}
console.log(vm)
</script>
</body>
2.1.index.js
import { initMixin } from "./init";
/**
*
* @param {*} options 用户传入的选项
*/
function Vue(options){
//初始化操作
this._init(options);
}
//扩展原型方法
initMixin(Vue);
export default Vue;
2.2.init.js
import { initState } from "./state";
/**
* 初始化操作
* @param {*} Vue 类
*/
export function initMixin(Vue){
Vue.prototype._init=function(options){
const vm=this;
vm.$options=options;
//对数据进行初始化
initState(vm);
}
}
2.3.state.js
import { observe } from "./observer/index";
import {
isFunction
} from "./utils";
/**
* 初始化状态
* @param {*} vm 实例
*/
export function initState(vm) {
const opt = vm.$options;
if (opt.data) {
initData(vm);
}
}
/**
* 初始化Data数据
* @param {*} vm 实例
*/
function initData(vm) {
let data = vm.$options.data;
/**
* //TODO
* 1.如果data是方法,需要执行,并不this依然是Vue实例
* 2.需要通过_data将劫持到的数据关联起来
*/
data = vm._data = isFunction(data) ? data.call(vm) : data;
//对数据进行劫持
observe(data);
}
2.4.observer/index.js
import {
isObject
} from "../utils";
class Observer {
constructor(data) {
this.walk(data);
}
/**
* 对象数据劫持
* @param {*} data 数据
*/
walk(data) {
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
})
}
}
/**
* TODO:Vue2为什么性能不好,主要原因就是数据的劫持的全量劫持
* @param {*} data 原数据
* @param {*} key key
* @param {*} value 值
*/
function defineReactive(data, key, value) {
observe(value); //TODO:如果value是一个对象,需要对value进行深层次的劫持操作
Object.defineProperty(data, key, {
get() {
return value;
},
set(newVal) {
if (newVal === value) return;
observe(newVal); //TODO:重新设置的值可能是一个对象,这个时候需要重新对其进行劫持处理
value = newVal;
}
})
}
export function observe(data) {
//TODO:data必须是一个对象,默认最外层必须是一个对象
if (!isObject(data)) return;
return new Observer(data);
}
2.5.utils.js
export function isFunction(val) {
return typeof val === 'function';
}
export function isObject(val) {
return typeof val === 'object' && val !== null;
}
3.数据代理
state.js
/**
* 初始化Data数据
* @param {*} vm 实例
*/
function initData(vm) {
let data = vm.$options.data;
/**
* //TODO
* 1.如果data是方法,需要执行,并不this依然是Vue实例
* 2.需要通过_data将劫持到的数据关联起来
*/
data = vm._data = isFunction(data) ? data.call(vm) : data;
//对vm._data上的数据进行代理,方便用户后续的取值和设值
for (let key in data) {
proxy(vm, '_data', key)
}
//对数据进行劫持
observe(data);
}
/**
* 对数据进行一层代理,方便用户对数据取值和设值,「vm._data.name='李四',可以直接用vm.name='李四'」
* @param {*} vm
* @param {*} source _data
* @param {*} key
*/
function proxy(vm, source, key) {
Object.defineProperty(vm, key, {
get() {
return vm[source][key];
},
set(newVal) {
vm[source][key] = newVal;
}
})
}
4.数组响应式
4.1.测试
- 1.数组里面是对象类型的需要被劫持
- 2.数组新增的是对象类型需要被劫持
<body>
<div id="app"></div>
<script src="./dist/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
arr:[1,2,{name:'zhangsan'}]
}
})
vm.arr.push({age:18});
console.log(vm.arr)
</script>
</body>
4.2.observe/index.js
import {
arrayMethods
} from "./array";
class Observer {
constructor(data) {
//给data上添加__ob__属性,值为Observer实例,并且不可枚举,不然死循环
Object.defineProperty(data, '__ob__', {
value: this,
enumerable: false
})
if (Array.isArray(data)) {
//TODO:数组劫持,数组原来方法的重写
data.__proto__ = arrayMethods;
//TODO:如果数组中的数据也可能是对象类型
this.observeArray(data);
} else {
this.walk(data);
}
}
/**
* TODO:
* 1.对数组中的数据进行观察,如果是对象需要继续进行劫持
* 2.新增的数据可能是对象,也需要进行劫持
* @param {*} data 数组
*/
observeArray(data) {
data.forEach(item => observe(item));
}
}
export function observe(data) {
//TODO:data必须是一个对象,默认最外层必须是一个对象
if (!isObject(data)) return;
//如果观察的数据已经有了__ob__属性,说明这个数据已经被劫持过了,不用再劫持
if (data.__ob__) return;
return new Observer(data);
}
4.3.observer/array.js
//原始Array的原型
const oldArrayPrototype = Array.prototype;
//创建一个新的数组原型,arrayMethods.__proto__=Array.propotype
export const arrayMethods = Object.create(oldArrayPrototype);
//需要重写的方法 7个
const methods = [
'push',
'unshift',
'shift',
'pop',
'splice',
'sort',
'reverse'
];
methods.forEach(method => {
//用户调用的如果是上面的7种方法,会先走自己重新的方法
arrayMethods[method] = function (...args) {
//原始的数组方法调用
oldArrayPrototype[method].call(this, ...args);
let inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args; //新增内容
break;
case 'splice':
inserted = args.slice(2);
break;
}
//新增的数据需要对其进行劫持 「this.__ob__是Observer实例」
if (inserted) this.__ob__.observeArray(inserted);
}
})
5.生成编译模板
5.1.init.js
import { compileToFunction } from "./compiler/index";
import {
initState
} from "./state";
/**
* 初始化操作
* @param {*} Vue 类
*/
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options;
//对数据进行初始化
initState(vm);
if (options.el) {
/**
* TODO:将数据挂载到模板上
* 用户挂载可以通过两种方式,
* 1.一种自动挂载,new Vue({el:'#app'})
* 2.手动挂载,vm.$mount('#app')
*/
vm.$mount(options.el);
}
}
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
/**
* TODO:
* 1.把模板字符串转化成对应的渲染函数
* 2.渲染函数执行生成虚拟DOM
* 3.diff算法,更新虚拟DOM
* 4.产生真是节点,更新
*/
if (!options.render) {
let template = options.template;
if (!template && el) {
//获取模版字符串 <div id="app"></div>
template = el.outerHTML;
options.render = compileToFunction(template);
}
}
}
}
6.模板解析「词法解析」
6.1.compiler/index.js
import { parserHTML } from "./parser";
/**
* 模板编译
* @param {*} template 模板
*/
export function compileToFunction(template){
parserHTML(template);
}
6.2.compiler/parser.js
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // 标签名
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; // 用来获取的标签名的 match后的索引为1的
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 匹配开始标签的 <div
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配闭合标签的 </div>
// aa = " xxx " | ' xxxx ' | xxx
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // a=b a="b" a='b'
const startTagClose = /^\s*(\/?)>/; // > />
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // {{aaaaa}}
/**
* 词法解析
* @param {*} html
*/
export function parserHTML(html) { //<div id="app">{{name}}</div>
while (html) {
//<当前的位置
const textEnd = html.indexOf('<');
if (textEnd === 0) { //开始位置
const startTagMatch = parseStartTag(); //解析开始标签
if (startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs);
continue;
}
// ["</div>", "div", index: 0, input: "</div>", groups: undefined]
const endTagMatch = html.match(endTag);
if (endTagMatch) {
end(endTagMatch[1]);
advance(endTagMatch[0].length);
continue;
}
}
let text;
if (textEnd > 0) {
text = html.substring(0, textEnd);
}
if (text) {
chars(text);
advance(text.length); //</div>
}
}
function parseStartTag() {
//<div", "div", index: 0, input: "<div id=\"app\">{{name}}</div>", groups: undefined]
const start = html.match(startTagOpen);
if (start) {
const match = {
tagName: start[1],
attrs: []
}
advance(start[0].length); // id="app">{{name}}</div>
let end, attr;
//不是结束标签,并且有属性
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
//attr= [" id=\"app\"", "id", "=", "app", undefined, undefined, index: 0, input: " id=\"app\">{{name}}</div>", groups: undefined]
match.attrs.push({
name: attr[1],
value: attr[3] || attr[4] || attr[5]
})
advance(attr[0].length); // >{{name}}</div>
}
if (end) {
advance(end[0].length); // {{name}}</div>
}
return match;
}
}
function advance(len) {
html = html.substring(len)
}
}
function start(tagName, attrs) {
console.log('start', tagName, attrs)
}
function chars(text) {
console.log('chars', text)
}
function end(tagName) {
console.log('end', tagName)
}
7.构建AST树
7.1.compiler/parser.js
/构建AST树,栈型结构
let root = null,
stack = [];
function start(tagName, attrs) {
//获取父节点
const parent = stack[stack.length - 1];
//创建节点
const element = createAstElement(tagName, attrs);
if (!root) { //树里还没用东西
root = element;
}
if (parent) { //与父亲建立关联
element.parent = parent;
parent.children.push(element);
}
stack.push(element);
}
function chars(text) {
//去除空格
text = text.replace(/\s/g, '');
if (text) {
const parent = stack[stack.length - 1];
parent.children.push({
type: 3,
text
})
}
}
function end(tagName) {
const last = stack.pop();
if (last.tag !== tagName) throw new Error('标签错误');
}
/**
* 创建节点
* @param {*} tagName 标签名称
* @param {*} attrs 属性
*/
function createAstElement(tagName, attrs) {
return {
tag: tagName,
type: 1, //TODO:标签是1,文本是3
attrs,
parent: null,
children: []
}
}
8.codeGen生成
8.1.compiler/index.js
import { generate } from "./generate";
import { parserHTML } from "./parser";
/**
* 模板编译
* @param {*} template 模板
*/
export function compileToFunction(template){
const root=parserHTML(template);
generate(root);
}
8.2.compiler/generate.js
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // {{aaaaa}}
/**
* 属性处理
* @param {*} attrs 属性对象 [{name:'id',value:'app'},{name: "style", value: "color:red;background:green"}]
*/
function genProps(attrs) {
let str = '';
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (attr.name === 'style') { //{name: "style", value: "color:red;background:green"}
let styleObj = {};
attr.value.replace(/([^;:]+)\:([^;:]+)/g, function () {
styleObj[arguments[1]] = arguments[2];
})
attr.value = styleObj;
}
str += `${attr.name}:${JSON.stringify(attr.value)},`;
}
return `{${str.slice(0,-1)}}`
}
/**
* // hello {{name}} world 转化为=> _v("hell"+_s(name)+"world")
* @param {*} el
* @returns
*/
function gen(el) {
if (el.type === 1) { //标签
return generate(el);
} else { //文本
const text = el.text;
if (!defaultTagRE.test(text)) { //不是{{}}包裹的
return `_v('${text}')`
} else {
const tokens = [];
let match;
//TODO:defaultTagRE.lastIndex需要制为零,exec会改变下标,每次进入时需要现重新拨回0的位置
let lastIndex = defaultTagRE.lastIndex = 0;
while (match = defaultTagRE.exec(text)) {
let index = match.index; //开始索引
if (index > lastIndex) {
tokens.push(JSON.stringify(text.slice(lastIndex, index))); //hello
}
tokens.push(`_s(${match[1].trim()})`); //{{name}}
lastIndex = index + match[0].length;
}
if (lastIndex < text.length) { //
tokens.push(JSON.stringify(text.slice(lastIndex))); //world
}
return `_v(${tokens.join('+')})`;
}
}
}
/**
* 处理孩子
* @param {*} el =[{"type":3,"text":"{{name}}"}]
*/
function genChildren(el) {
const children = el.children;
if (children) {
return children.map(c => gen(c)).join(',');
}
return false;
}
/**
{
"tag":"div",
"type":1,
"attrs":[
{
"name":"id",
"value":"app"
},
{
"name":"style",
"value":"color:red;background:green"
}
],
"parent":null,
"children":[
{
"type":3,
"text":"hell{{name}}world"
}
]
}
*/
export function generate(el) {
console.log(JSON.stringify(el))
const children = genChildren(el);
const code = `_c('${el.tag}',${el.attrs.length?genProps(el.attrs):'undefined'}${children?`,${children}`:''})`;
console.log(code)
}
9.虚拟DOM实现
9.1.生成render函数
compiler/index.js
import {
generate
} from "./generate";
import {
parserHTML
} from "./parser";
/**
* 模板编译,生成render函数
* @param {*} template 模板
*/
export function compileToFunction(template) {
//生成AST树
const root = parserHTML(template);
//通过AST树构建codegen 「_('div',{'id':'#app'},'hello')」
const code = generate(root);
console.log(code)
//Function +with构建方式 this=vm
let render = new Function(`with(this){return ${code}}`);
return render;
}
9.2.挂载
init.js
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
/**
* TODO:
* 1.把模板字符串转化成对应的渲染函数
* 2.渲染函数执行生成虚拟DOM
* 3.diff算法,更新虚拟DOM
* 4.产生真是节点,更新
*/
if (!options.render) {
let template = options.template;
if (!template && el) {
//获取模版字符串 <div id="app"></div>
template = el.outerHTML;
options.render = compileToFunction(template);
}
}
//组件挂载流程
mountComponent(vm, el);
}
lifecycle.js
export function lifecycleMixin(Vue){
Vue.prototype._update=function(vnode){
const vm=this;
console.log('vnode',vnode);
}
}
/**
* 组件挂载
* @param {*} vm
* @param {*} el <div id='app'></div>
*/
export function mountComponent(vm,el){
/**
* TODO:更新函数
* 1.调用_render生成vdom
* 2.调用_update进行更新操作
*/
const updateComponent=()=>{
vm._update(vm._render());
}
updateComponent();
}
9.3.初始化
index.js
//扩展原型方法
initMixin(Vue);
renderMixin(Vue);//_render
lifecycleMixin(Vue);//_update
9.4.生成虚拟DOM
render.js
import {
createElement,
createTextElement
} from "./vdom/index";
export function renderMixin(Vue) {
//处理元素
Vue.prototype._c = function () {
const vm = this;
return createElement(vm, ...arguments);
}
//处理文本
Vue.prototype._v = function (text) {
const vm = this;
return createTextElement(vm, text);
}
//处理{{}}
Vue.prototype._s = function (val) {
if (typeof val === 'object') return JSON.stringify(val);
return val;
}
//render函数,返回虚拟节点
Vue.prototype._render = function () {
const vm = this;
const render = vm.$options.render;
const vnode = render.call(vm);
return vnode;
}
}
vdom/index.js
export function createElement(vm, tag, data = {}, ...children) {
return vnode(vm, tag, data, data.key, children, undefined);
}
export function createTextElement(vm, text) {
return vnode(vm, undefined, undefined, undefined, undefined, text);
}
//创建虚拟节点
function vnode(vm, tag, data, key, children, text) {
return {
vm,
tag,
data,
key,
children,
text,
//...TODO
}
}
10.vdom创建真实dom
10.1.将el绑定到vm上
init.js
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
vm.$el = el;
}
10.2.虚拟DOM转为真实DOM
lifecycle.js
import { patch } from "./vdom/patch";
export function lifecycleMixin(Vue){
Vue.prototype._update=function(vnode){
const vm=this;
patch(vm.$el,vnode);
}
}
vdom/patch.js
/**
* 根据虚拟DOM创建真实DOM
* @param {*} vnode 虚拟DOM
*/
function createElem(vnode) {
const {
tag,
children,
text
} = vnode;
if (typeof tag === 'string') { //元素
//vdom会添加一个el属性,对应真实节点
vnode.el = document.createElement(tag);
children.forEach(child => {
vnode.el.appendChild(createElem(child));
})
} else { //文本
vnode.el = document.createTextNode(text);
}
return vnode.el;
}
/**
* 添加新的虚拟DOM,删除老得DOM
* @param {*} el 可能是真是dom,也可能是虚拟DOM
* @param {*} vnode 新的虚拟DOM
*/
export function patch(el, vnode) {
if (el.nodeType === 1) { //是真是DOM
const parentNode = el.parentNode;
const elem = createElem(vnode);
//添加
parentNode.insertBefore(elem, el.nextSibling);
//删除老得
parentNode.removeChild(el);
return elem;
} else {
}
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!