一.使用Rollup搭建开发环境
1.什么是Rollup?
Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码, rollup.js更专注于Javascript类库打包 (开发应用时使用Webpack,开发库时使用Rollup)
2.环境搭建
- 安装rollup环境 ```javascript
 
npm install @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D
- **rollup.config.js文件编写**```javascriptimport babel from 'rollup-plugin-babel';import serve from 'rollup-plugin-serve';export default {input: './src/index.js',output: {format: 'umd', // 模块化类型file: 'dist/umd/vue.js',name: 'Vue', // 打包后的全局变量的名字sourcemap: true},plugins: [babel({exclude: 'node_modules/**'}),process.env.ENV === 'development'?serve({open: true,openPage: '/public/index.html',port: 3000,contentBase: ''}):null]}
配置.babelrc文件
{"presets": ["@babel/preset-env"]}
执行脚本配置
"scripts": {"build:dev": "rollup -c","serve": "cross-env ENV=development rollup -c -w"}
二.Vue响应式原理
导出Vue构造函数
instance/index.js
import {initMixin} from './init';function Vue(options) {this._init(options);}initMixin(Vue); // 给原型上新增_init方法export default Vue;
- init 方法中初始化 vue 状态
 
instance/init.js
import {initState} from './state';export function initMixin(Vue){Vue.prototype._init = function (options) {const vm = this;vm.$options = options// 初始化状态initState(vm)}}
- 根据不同属性进行初始化操作
 
instance/state.js
export function initState(vm){const opts = vm.$options;if(opts.props){initProps(vm);}if(opts.methods){initMethod(vm);}if(opts.data){// 初始化datainitData(vm);}if(opts.computed){initComputed(vm);}if(opts.watch){initWatch(vm);}}function initProps(){}function initMethod(){}function initData(){}function initComputed(){}function initWatch(){}
1.初始化数据
instance/state.js
import {observe} from './observer/index.js'function initData(vm){let data = vm.$options.data;data = vm._data = typeof data === 'function' ? data.call(vm) : data;observe(data);}
2.递归属性劫持
class Observer { // 观测值constructor(value){this.walk(value);}walk(data){ // 让对象上的所有属性依次进行观测let keys = Object.keys(data);for(let i = 0; i < keys.length; i++){let key = keys[i];let value = data[key];defineReactive(data,key,value);}}}function defineReactive(data,key,value){observe(value);Object.defineProperty(data,key,{get(){return value},set(newValue){if(newValue == value) return;observe(newValue);value = newValue}})}export function observe(data) {if(typeof data !== 'object' && data != null){return;}return new Observer(data);}
3.数组方法的劫持
import {arrayMethods} from './array';class Observer { // 观测值constructor(value){if(Array.isArray(value)){value.__proto__ = arrayMethods; // 重写数组原型方法this.observeArray(value);}else{this.walk(value);}}observeArray(value){for(let i = 0 ; i < value.length ;i ++){observe(value[i]);}}}
重写数组原型方法
let oldArrayProtoMethods = Array.prototype;export let arrayMethods = Object.create(oldArrayProtoMethods);let methods = ['push','pop','shift','unshift','reverse','sort','splice'];methods.forEach(method => {arrayMethods[method] = function (...args) {const result = oldArrayProtoMethods[method].apply(this, args);const ob = this.__ob__;let inserted;switch (method) {case 'push':case 'unshift':inserted = args;break;case 'splice':inserted = args.slice(2)default:break;}if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测return result}})
增加__ob__属性
class Observer {constructor(value){Object.defineProperty(value,'__ob__',{enumerable:false,configurable:false,value:this});// ...}}
给所有响应式数据增加标识,并且可以在响应式上获取Observer实例上的方法
4.数据代理
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;data = vm._data = typeof data === 'function' ? data.call(vm) : data;for(let key in data){ // 将_data上的属性全部代理给vm实例proxy(vm,'_data',key)}observe(data);}
三.模板编译
Vue.prototype._init = function (options) {const vm = this;vm.$options = options;// 初始化状态initState(vm);// 页面挂载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);// 如果没有render方法if (!options.render) {let template = options.template;// 如果没有模板但是有elif (!template && el) {template = el.outerHTML;}const render= compileToFunctions(template); //将template编译成render函数options.render = render;}}
1.解析标签和内容
compiler/parser.js
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;const qnameCapture = `((?:${ncname}\\:)?${ncname})`;const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 >const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/gfunction start(tagName,attrs){console.log(tagName,attrs)}function end(tagName){console.log(tagName)}function chars(text){console.log(text);}function parseHTML(html){while(html){let textEnd = html.indexOf('<');if(textEnd == 0){const startTagMatch = parseStartTag();if(startTagMatch){start(startTagMatch.tagName,startTagMatch.attrs);continue;}const endTagMatch = html.match(endTag);if(endTagMatch){advance(endTagMatch[0].length);end(endTagMatch[1]);continue;}}let text;if(textEnd >= 0){text = html.substring(0,textEnd);}if(text){advance(text.length);chars(text);}}function advance(n){html = html.substring(n);}function parseStartTag(){const start = html.match(startTagOpen);if(start){const match = {tagName:start[1],attrs:[]}advance(start[0].length);let attr,end;while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))){advance(attr[0].length);match.attrs.push({name:attr[1],value:attr[3]});}if(end){advance(end[0].length);return match}}}}export function compileToFunctions(template){parseHTML(template);return function(){}}
2.生成ast语法树
语法树就是用对象描述js语法
{tag:'div',type:1,children:[{tag:'span',type:1,attrs:[],parent:'div对象'}],attrs:[{name:'zf',age:10}],parent:null}
compiler/parser.js
let root;let currentParent;let stack = [];const ELEMENT_TYPE = 1;const TEXT_TYPE = 3;function createASTElement(tagName,attrs){return {tag:tagName,type:ELEMENT_TYPE,children:[],attrs,parent:null}}function start(tagName, attrs) {let element = createASTElement(tagName,attrs);if(!root){root = element;}currentParent = element;stack.push(element);}function end(tagName) {let element = stack.pop();currentParent = stack[stack.length-1];if(currentParent){element.parent = currentParent;currentParent.children.push(element);}}function chars(text) {text = text.replace(/\s/g,'');if(text){currentParent.children.push({type:TEXT_TYPE,text})}}
3.生成代码
template转化成render函数的结果
<div style="color:red">hello {{name}} <span></span></div>render(){return _c('div',{style:{color:'red'}},_v('hello'+_s(name)),_c('span',undefined,''))}
compiler/generate.js
function gen(node) {if (node.type == 1) {return generate(node);} else {let text = node.textif(!defaultTagRE.test(text)){return `_v(${JSON.stringify(text)})`}let lastIndex = defaultTagRE.lastIndex = 0let tokens = [];let match,index;while (match = defaultTagRE.exec(text)) {index = match.index;if(index > lastIndex){tokens.push(JSON.stringify(text.slice(lastIndex,index)));}tokens.push(`_s(${match[1].trim()})`)lastIndex = index + match[0].length;}if(lastIndex < text.length){tokens.push(JSON.stringify(text.slice(lastIndex)))}return `_v(${tokens.join('+')})`;}}function getChildren(el) { // 生成儿子节点const children = el.children;if (children) {return `${children.map(c=>gen(c)).join(',')}`} else {return false;}}function genProps(attrs){ // 生成属性let str = '';for(let i = 0; i<attrs.length; i++){let attr = attrs[i];if(attr.name === 'style'){let obj = {}attr.value.split(';').forEach(item=>{let [key,value] = item.split(':');obj[key] = value;})attr.value = obj;}str += `${attr.name}:${JSON.stringify(attr.value)},`;}return `{${str.slice(0,-1)}}`;}function generate(el) {let children = getChildren(el);let code = `_c('${el.tag}',${el.attrs.length?`${genProps(el.attrs)}`:'undefined'}${children? `,${children}`:''})`;return code;}let code = generate(root);
4.生成render函数
export function compileToFunctions(template) {parseHTML(template);let code = generate(root);let render = `with(this){return ${code}}`;let renderFn = new Function(render);return renderFn}
四.创建渲染watcher
1.初始化渲染Watcher
import {mountComponent} from './lifecycle'Vue.prototype.$mount = function (el) {const vm = this;const options = vm.$options;el = document.querySelector(el);// 如果没有render方法if (!options.render) {let template = options.template;// 如果没有模板但是有elif (!template && el) {template = el.outerHTML;}const render= compileToFunctions(template);options.render = render;}mountComponent(vm,el);}
lifecycle.js
export function lifecycleMixin() {Vue.prototype._update = function (vnode) {}}export function mountComponent(vm, el) {vm.$el = el;let updateComponent = () => {// 将虚拟节点 渲染到页面上vm._update(vm._render());}new Watcher(vm, updateComponent, () => {}, true);}
render.js
export function renderMixin(Vue){Vue.prototype._render = function () {}}
watcher.js
let id = 0;class Watcher {constructor(vm, exprOrFn, cb, options) {this.vm = vm;this.exprOrFn = exprOrFn;if (typeof exprOrFn == 'function') {this.getter = exprOrFn;}this.cb = cb;this.options = options;this.id = id++;this.get();}get() {this.getter();}}export default Watcher;
先调用_render方法生成虚拟dom,通过_update方法将虚拟dom创建成真实的dom
2.生成虚拟dom
instance/render.js
import {createTextNode,createElement} from './vdom/create-element'export function renderMixin(Vue){Vue.prototype._v = function (text) { // 创建文本return createTextNode(text);}Vue.prototype._c = function () { // 创建元素return createElement(...arguments);}Vue.prototype._s = function (val) {return val == null? '' : (typeof val === 'object'?JSON.stringify(val):val);}Vue.prototype._render = function () {const vm = this;const {render} = vm.$options;let vnode = render.call(vm);return vnode;}}
创建虚拟节点
vdom/index.js
export function createTextNode(text) {return vnode(undefined,undefined,undefined,undefined,text)}export function createElement(tag,data={},...children){let key = data.key;if(key){delete data.key;}return vnode(tag,data,key,children);}function vnode(tag,data,key,children,text){return {tag,data,key,children,text}}
3.生成真实DOM元素
将虚拟节点渲染成真实节点
import {patch} './observer/patch'export function lifecycleMixin(Vue){Vue.prototype._update = function (vnode) {const vm = this;vm.$el = patch(vm.$el,vnode);}}
vdom/patch.js
export function patch(oldVnode,vnode){const isRealElement = oldVnode.nodeType;if(isRealElement){const oldElm = oldVnode;const parentElm = oldElm.parentNode;let el = createElm(vnode);parentElm.insertBefore(el,oldElm.nextSibling);parentElm.removeChild(oldVnode)return el;}}function createElm(vnode){let {tag,children,key,data,text} = vnode;if(typeof tag === 'string'){vnode.el = document.createElement(tag);updateProperties(vnode);children.forEach(child => {return vnode.el.appendChild(createElm(child));});}else{vnode.el = document.createTextNode(text);}return vnode.el}function updateProperties(vnode){let newProps = vnode.data || {}; // 获取当前老节点中的属性let el = vnode.el; // 当前的真实节点for(let key in newProps){if(key === 'style'){for(let styleName in newProps.style){el.style[styleName] = newProps.style[styleName]}}else if(key === 'class'){el.className= newProps.class}else{ // 给这个元素添加属性 值就是对应的值el.setAttribute(key,newProps[key]);}}}
五.依赖收集
每个属性都要有一个dep,每个dep中存放着watcher,同一个watcher会被多个dep所记录。
1.在渲染时存储watcher
observer/watcher.js
class Watcher{// ...get(){pushTarget(this);this.getter();popTarget();}}
observer/dep.js
let id = 0;class Dep{constructor(){this.id = id++;}}let stack = [];export function pushTarget(watcher){Dep.target = watcher;stack.push(watcher);}export function popTarget(){stack.pop();Dep.target = stack[stack.length-1];}export default Dep;
2.对象依赖收集
observer/dep.js
let dep = new Dep();Object.defineProperty(data, key, {get() {if(Dep.target){ // 如果取值时有watcherdep.depend(); // 让watcher保存dep,并且让dep 保存watcher}return value},set(newValue) {if (newValue == value) return;observe(newValue);value = newValue;dep.notify(); // 通知渲染watcher去更新}});
Dep实现
observer/dep.js
class Dep{constructor(){this.id = id++;this.subs = [];}depend(){if(Dep.target){Dep.target.addDep(this);// 让watcher,去存放dep}}notify(){this.subs.forEach(watcher=>watcher.update());}addSub(watcher){this.subs.push(watcher);}}
observer/watcher.js
constructor(){this.deps = [];this.depsId = new Set();}addDep(dep){let id = dep.id;if(!this.depsId.has(id)){this.depsId.add(id);this.deps.push(dep);dep.addSub(this);}}update(){this.get();}
3.数组的依赖收集
observer/index.js
this.dep = new Dep(); // 专门为数组设计的if (Array.isArray(value)) {value.__proto__ = arrayMethods;this.observeArray(value);} else {this.walk(value);}function defineReactive(data, key, value) {let childOb = observe(value);let dep = new Dep();Object.defineProperty(data, key, {get() {if(Dep.target){dep.depend();if(childOb){childOb.dep.depend(); // 收集数组依赖}}return value},set(newValue) {if (newValue == value) return;observe(newValue);value = newValue;dep.notify();}})}arrayMethods[method] = function (...args) {// ...ob.dep.notify()return result;}
递归收集数组依赖
observer/index.js
if(Dep.target){dep.depend();if(childOb){childOb.dep.depend(); // 收集数组依赖if(Array.isArray(value)){ // 如果内部还是数组dependArray(value);// 不停的进行依赖收集}}}function dependArray(value) {for (let i = 0; i < value.length; i++) {let current = value[i];current.__ob__ && current.__ob__.dep.depend();if (Array.isArray(current)) {dependArray(current)}}}
六.实现Vue异步更新之nextTick
1.实现队列机制
observer/watcher.js
update(){queueWatcher(this);}
observer/scheduler.js
import {nextTick} from '../util/next-tick'let has = {};let queue = [];function flushSchedulerQueue() {for (let i = 0; i < queue.length; i++) {let watcher = queue[i];watcher.run()}queue = [];has = {}}export function queueWatcher(watcher) {const id = watcher.id;if (has[id] == null) {has[id] = true;queue.push(watcher);nextTick(flushSchedulerQueue)}}
2.nextTick实现原理
util/next-tick.js
let callbacks = [];function flushCallbacks() {callbacks.forEach(cb => cb());}let timerFunc;if (Promise) { // then方法是异步的timerFunc = () => {Promise.resolve().then(flushCallbacks)}}else if (MutationObserver) { // MutationObserver 也是一个异步方法let observe = new MutationObserver(flushCallbacks); // H5的apilet textNode = document.createTextNode(1);observe.observe(textNode, {characterData: true});timerFunc = () => {textNode.textContent = 2;}}else if (setImmediate) {timerFunc = () => {setImmediate(flushCallbacks)}}else{timerFunc = () => {setTimeout(flushCallbacks, 0);}}export function nextTick(cb) {callbacks.push(cb);timerFunc();}
