https://github.com/mewcoder/toy-vue2

一.使用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

  1. - **rollup.config.js文件编写**
  2. ```javascript
  3. import babel from 'rollup-plugin-babel';
  4. import serve from 'rollup-plugin-serve';
  5. export default {
  6. input: './src/index.js',
  7. output: {
  8. format: 'umd', // 模块化类型
  9. file: 'dist/umd/vue.js',
  10. name: 'Vue', // 打包后的全局变量的名字
  11. sourcemap: true
  12. },
  13. plugins: [
  14. babel({
  15. exclude: 'node_modules/**'
  16. }),
  17. process.env.ENV === 'development'?serve({
  18. open: true,
  19. openPage: '/public/index.html',
  20. port: 3000,
  21. contentBase: ''
  22. }):null
  23. ]
  24. }
  • 配置.babelrc文件

    1. {
    2. "presets": [
    3. "@babel/preset-env"
    4. ]
    5. }
  • 执行脚本配置

    1. "scripts": {
    2. "build:dev": "rollup -c",
    3. "serve": "cross-env ENV=development rollup -c -w"
    4. }

    二.Vue响应式原理

  • 导出Vue构造函数

instance/index.js

  1. import {initMixin} from './init';
  2. function Vue(options) {
  3. this._init(options);
  4. }
  5. initMixin(Vue); // 给原型上新增_init方法
  6. export default Vue;
  • init 方法中初始化 vue 状态

instance/init.js

  1. import {initState} from './state';
  2. export function initMixin(Vue){
  3. Vue.prototype._init = function (options) {
  4. const vm = this;
  5. vm.$options = options
  6. // 初始化状态
  7. initState(vm)
  8. }
  9. }
  • 根据不同属性进行初始化操作

instance/state.js

  1. export function initState(vm){
  2. const opts = vm.$options;
  3. if(opts.props){
  4. initProps(vm);
  5. }
  6. if(opts.methods){
  7. initMethod(vm);
  8. }
  9. if(opts.data){
  10. // 初始化data
  11. initData(vm);
  12. }
  13. if(opts.computed){
  14. initComputed(vm);
  15. }
  16. if(opts.watch){
  17. initWatch(vm);
  18. }
  19. }
  20. function initProps(){}
  21. function initMethod(){}
  22. function initData(){}
  23. function initComputed(){}
  24. function initWatch(){}

1.初始化数据

instance/state.js

  1. import {observe} from './observer/index.js'
  2. function initData(vm){
  3. let data = vm.$options.data;
  4. data = vm._data = typeof data === 'function' ? data.call(vm) : data;
  5. observe(data);
  6. }

2.递归属性劫持

  1. class Observer { // 观测值
  2. constructor(value){
  3. this.walk(value);
  4. }
  5. walk(data){ // 让对象上的所有属性依次进行观测
  6. let keys = Object.keys(data);
  7. for(let i = 0; i < keys.length; i++){
  8. let key = keys[i];
  9. let value = data[key];
  10. defineReactive(data,key,value);
  11. }
  12. }
  13. }
  14. function defineReactive(data,key,value){
  15. observe(value);
  16. Object.defineProperty(data,key,{
  17. get(){
  18. return value
  19. },
  20. set(newValue){
  21. if(newValue == value) return;
  22. observe(newValue);
  23. value = newValue
  24. }
  25. })
  26. }
  27. export function observe(data) {
  28. if(typeof data !== 'object' && data != null){
  29. return;
  30. }
  31. return new Observer(data);
  32. }

3.数组方法的劫持

  1. import {arrayMethods} from './array';
  2. class Observer { // 观测值
  3. constructor(value){
  4. if(Array.isArray(value)){
  5. value.__proto__ = arrayMethods; // 重写数组原型方法
  6. this.observeArray(value);
  7. }else{
  8. this.walk(value);
  9. }
  10. }
  11. observeArray(value){
  12. for(let i = 0 ; i < value.length ;i ++){
  13. observe(value[i]);
  14. }
  15. }
  16. }

重写数组原型方法

  1. let oldArrayProtoMethods = Array.prototype;
  2. export let arrayMethods = Object.create(oldArrayProtoMethods);
  3. let methods = [
  4. 'push',
  5. 'pop',
  6. 'shift',
  7. 'unshift',
  8. 'reverse',
  9. 'sort',
  10. 'splice'
  11. ];
  12. methods.forEach(method => {
  13. arrayMethods[method] = function (...args) {
  14. const result = oldArrayProtoMethods[method].apply(this, args);
  15. const ob = this.__ob__;
  16. let inserted;
  17. switch (method) {
  18. case 'push':
  19. case 'unshift':
  20. inserted = args;
  21. break;
  22. case 'splice':
  23. inserted = args.slice(2)
  24. default:
  25. break;
  26. }
  27. if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测
  28. return result
  29. }
  30. })

增加__ob__属性

  1. class Observer {
  2. constructor(value){
  3. Object.defineProperty(value,'__ob__',{
  4. enumerable:false,
  5. configurable:false,
  6. value:this
  7. });
  8. // ...
  9. }
  10. }

给所有响应式数据增加标识,并且可以在响应式上获取Observer实例上的方法

4.数据代理

  1. function proxy(vm,source,key){
  2. Object.defineProperty(vm,key,{
  3. get(){
  4. return vm[source][key];
  5. },
  6. set(newValue){
  7. vm[source][key] = newValue;
  8. }
  9. });
  10. }
  11. function initData(vm){
  12. let data = vm.$options.data;
  13. data = vm._data = typeof data === 'function' ? data.call(vm) : data;
  14. for(let key in data){ // 将_data上的属性全部代理给vm实例
  15. proxy(vm,'_data',key)
  16. }
  17. observe(data);
  18. }

三.模板编译

  1. Vue.prototype._init = function (options) {
  2. const vm = this;
  3. vm.$options = options;
  4. // 初始化状态
  5. initState(vm);
  6. // 页面挂载
  7. if (vm.$options.el) {
  8. vm.$mount(vm.$options.el);
  9. }
  10. }
  11. Vue.prototype.$mount = function (el) {
  12. const vm = this;
  13. const options = vm.$options;
  14. el = document.querySelector(el);
  15. // 如果没有render方法
  16. if (!options.render) {
  17. let template = options.template;
  18. // 如果没有模板但是有el
  19. if (!template && el) {
  20. template = el.outerHTML;
  21. }
  22. const render= compileToFunctions(template); //将template编译成render函数
  23. options.render = render;
  24. }
  25. }

1.解析标签和内容

compiler/parser.js

  1. const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
  2. const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
  3. const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
  4. const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
  5. const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
  6. const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 >
  7. const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
  8. function start(tagName,attrs){
  9. console.log(tagName,attrs)
  10. }
  11. function end(tagName){
  12. console.log(tagName)
  13. }
  14. function chars(text){
  15. console.log(text);
  16. }
  17. function parseHTML(html){
  18. while(html){
  19. let textEnd = html.indexOf('<');
  20. if(textEnd == 0){
  21. const startTagMatch = parseStartTag();
  22. if(startTagMatch){
  23. start(startTagMatch.tagName,startTagMatch.attrs);
  24. continue;
  25. }
  26. const endTagMatch = html.match(endTag);
  27. if(endTagMatch){
  28. advance(endTagMatch[0].length);
  29. end(endTagMatch[1]);
  30. continue;
  31. }
  32. }
  33. let text;
  34. if(textEnd >= 0){
  35. text = html.substring(0,textEnd);
  36. }
  37. if(text){
  38. advance(text.length);
  39. chars(text);
  40. }
  41. }
  42. function advance(n){
  43. html = html.substring(n);
  44. }
  45. function parseStartTag(){
  46. const start = html.match(startTagOpen);
  47. if(start){
  48. const match = {
  49. tagName:start[1],
  50. attrs:[]
  51. }
  52. advance(start[0].length);
  53. let attr,end;
  54. while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))){
  55. advance(attr[0].length);
  56. match.attrs.push({name:attr[1],value:attr[3]});
  57. }
  58. if(end){
  59. advance(end[0].length);
  60. return match
  61. }
  62. }
  63. }
  64. }
  65. export function compileToFunctions(template){
  66. parseHTML(template);
  67. return function(){}
  68. }

2.生成ast语法树

语法树就是用对象描述js语法

  1. {
  2. tag:'div',
  3. type:1,
  4. children:[{tag:'span',type:1,attrs:[],parent:'div对象'}],
  5. attrs:[{name:'zf',age:10}],
  6. parent:null
  7. }

compiler/parser.js

  1. let root;
  2. let currentParent;
  3. let stack = [];
  4. const ELEMENT_TYPE = 1;
  5. const TEXT_TYPE = 3;
  6. function createASTElement(tagName,attrs){
  7. return {
  8. tag:tagName,
  9. type:ELEMENT_TYPE,
  10. children:[],
  11. attrs,
  12. parent:null
  13. }
  14. }
  15. function start(tagName, attrs) {
  16. let element = createASTElement(tagName,attrs);
  17. if(!root){
  18. root = element;
  19. }
  20. currentParent = element;
  21. stack.push(element);
  22. }
  23. function end(tagName) {
  24. let element = stack.pop();
  25. currentParent = stack[stack.length-1];
  26. if(currentParent){
  27. element.parent = currentParent;
  28. currentParent.children.push(element);
  29. }
  30. }
  31. function chars(text) {
  32. text = text.replace(/\s/g,'');
  33. if(text){
  34. currentParent.children.push({
  35. type:TEXT_TYPE,
  36. text
  37. })
  38. }
  39. }

3.生成代码

template转化成render函数的结果

  1. <div style="color:red">hello {{name}} <span></span></div>
  2. render(){
  3. return _c('div',{style:{color:'red'}},_v('hello'+_s(name)),_c('span',undefined,''))
  4. }

compiler/generate.js

  1. function gen(node) {
  2. if (node.type == 1) {
  3. return generate(node);
  4. } else {
  5. let text = node.text
  6. if(!defaultTagRE.test(text)){
  7. return `_v(${JSON.stringify(text)})`
  8. }
  9. let lastIndex = defaultTagRE.lastIndex = 0
  10. let tokens = [];
  11. let match,index;
  12. while (match = defaultTagRE.exec(text)) {
  13. index = match.index;
  14. if(index > lastIndex){
  15. tokens.push(JSON.stringify(text.slice(lastIndex,index)));
  16. }
  17. tokens.push(`_s(${match[1].trim()})`)
  18. lastIndex = index + match[0].length;
  19. }
  20. if(lastIndex < text.length){
  21. tokens.push(JSON.stringify(text.slice(lastIndex)))
  22. }
  23. return `_v(${tokens.join('+')})`;
  24. }
  25. }
  26. function getChildren(el) { // 生成儿子节点
  27. const children = el.children;
  28. if (children) {
  29. return `${children.map(c=>gen(c)).join(',')}`
  30. } else {
  31. return false;
  32. }
  33. }
  34. function genProps(attrs){ // 生成属性
  35. let str = '';
  36. for(let i = 0; i<attrs.length; i++){
  37. let attr = attrs[i];
  38. if(attr.name === 'style'){
  39. let obj = {}
  40. attr.value.split(';').forEach(item=>{
  41. let [key,value] = item.split(':');
  42. obj[key] = value;
  43. })
  44. attr.value = obj;
  45. }
  46. str += `${attr.name}:${JSON.stringify(attr.value)},`;
  47. }
  48. return `{${str.slice(0,-1)}}`;
  49. }
  50. function generate(el) {
  51. let children = getChildren(el);
  52. let code = `_c('${el.tag}',${
  53. el.attrs.length?`${genProps(el.attrs)}`:'undefined'
  54. }${
  55. children? `,${children}`:''
  56. })`;
  57. return code;
  58. }
  59. let code = generate(root);

4.生成render函数

  1. export function compileToFunctions(template) {
  2. parseHTML(template);
  3. let code = generate(root);
  4. let render = `with(this){return ${code}}`;
  5. let renderFn = new Function(render);
  6. return renderFn
  7. }

四.创建渲染watcher

1.初始化渲染Watcher

  1. import {mountComponent} from './lifecycle'
  2. Vue.prototype.$mount = function (el) {
  3. const vm = this;
  4. const options = vm.$options;
  5. el = document.querySelector(el);
  6. // 如果没有render方法
  7. if (!options.render) {
  8. let template = options.template;
  9. // 如果没有模板但是有el
  10. if (!template && el) {
  11. template = el.outerHTML;
  12. }
  13. const render= compileToFunctions(template);
  14. options.render = render;
  15. }
  16. mountComponent(vm,el);
  17. }

lifecycle.js

  1. export function lifecycleMixin() {
  2. Vue.prototype._update = function (vnode) {}
  3. }
  4. export function mountComponent(vm, el) {
  5. vm.$el = el;
  6. let updateComponent = () => {
  7. // 将虚拟节点 渲染到页面上
  8. vm._update(vm._render());
  9. }
  10. new Watcher(vm, updateComponent, () => {}, true);
  11. }

render.js

  1. export function renderMixin(Vue){
  2. Vue.prototype._render = function () {}
  3. }

watcher.js

  1. let id = 0;
  2. class Watcher {
  3. constructor(vm, exprOrFn, cb, options) {
  4. this.vm = vm;
  5. this.exprOrFn = exprOrFn;
  6. if (typeof exprOrFn == 'function') {
  7. this.getter = exprOrFn;
  8. }
  9. this.cb = cb;
  10. this.options = options;
  11. this.id = id++;
  12. this.get();
  13. }
  14. get() {
  15. this.getter();
  16. }
  17. }
  18. export default Watcher;

先调用_render方法生成虚拟dom,通过_update方法将虚拟dom创建成真实的dom

2.生成虚拟dom

instance/render.js

  1. import {createTextNode,createElement} from './vdom/create-element'
  2. export function renderMixin(Vue){
  3. Vue.prototype._v = function (text) { // 创建文本
  4. return createTextNode(text);
  5. }
  6. Vue.prototype._c = function () { // 创建元素
  7. return createElement(...arguments);
  8. }
  9. Vue.prototype._s = function (val) {
  10. return val == null? '' : (typeof val === 'object'?JSON.stringify(val):val);
  11. }
  12. Vue.prototype._render = function () {
  13. const vm = this;
  14. const {render} = vm.$options;
  15. let vnode = render.call(vm);
  16. return vnode;
  17. }
  18. }

创建虚拟节点
vdom/index.js

  1. export function createTextNode(text) {
  2. return vnode(undefined,undefined,undefined,undefined,text)
  3. }
  4. export function createElement(tag,data={},...children){
  5. let key = data.key;
  6. if(key){
  7. delete data.key;
  8. }
  9. return vnode(tag,data,key,children);
  10. }
  11. function vnode(tag,data,key,children,text){
  12. return {
  13. tag,
  14. data,
  15. key,
  16. children,
  17. text
  18. }
  19. }

3.生成真实DOM元素

将虚拟节点渲染成真实节点

  1. import {patch} './observer/patch'
  2. export function lifecycleMixin(Vue){
  3. Vue.prototype._update = function (vnode) {
  4. const vm = this;
  5. vm.$el = patch(vm.$el,vnode);
  6. }
  7. }

vdom/patch.js

  1. export function patch(oldVnode,vnode){
  2. const isRealElement = oldVnode.nodeType;
  3. if(isRealElement){
  4. const oldElm = oldVnode;
  5. const parentElm = oldElm.parentNode;
  6. let el = createElm(vnode);
  7. parentElm.insertBefore(el,oldElm.nextSibling);
  8. parentElm.removeChild(oldVnode)
  9. return el;
  10. }
  11. }
  12. function createElm(vnode){
  13. let {tag,children,key,data,text} = vnode;
  14. if(typeof tag === 'string'){
  15. vnode.el = document.createElement(tag);
  16. updateProperties(vnode);
  17. children.forEach(child => {
  18. return vnode.el.appendChild(createElm(child));
  19. });
  20. }else{
  21. vnode.el = document.createTextNode(text);
  22. }
  23. return vnode.el
  24. }
  25. function updateProperties(vnode){
  26. let newProps = vnode.data || {}; // 获取当前老节点中的属性
  27. let el = vnode.el; // 当前的真实节点
  28. for(let key in newProps){
  29. if(key === 'style'){
  30. for(let styleName in newProps.style){
  31. el.style[styleName] = newProps.style[styleName]
  32. }
  33. }else if(key === 'class'){
  34. el.className= newProps.class
  35. }else{ // 给这个元素添加属性 值就是对应的值
  36. el.setAttribute(key,newProps[key]);
  37. }
  38. }
  39. }

五.依赖收集

每个属性都要有一个dep,每个dep中存放着watcher,同一个watcher会被多个dep所记录。

1.在渲染时存储watcher

observer/watcher.js

  1. class Watcher{
  2. // ...
  3. get(){
  4. pushTarget(this);
  5. this.getter();
  6. popTarget();
  7. }
  8. }

observer/dep.js

  1. let id = 0;
  2. class Dep{
  3. constructor(){
  4. this.id = id++;
  5. }
  6. }
  7. let stack = [];
  8. export function pushTarget(watcher){
  9. Dep.target = watcher;
  10. stack.push(watcher);
  11. }
  12. export function popTarget(){
  13. stack.pop();
  14. Dep.target = stack[stack.length-1];
  15. }
  16. export default Dep;

2.对象依赖收集

observer/dep.js

  1. let dep = new Dep();
  2. Object.defineProperty(data, key, {
  3. get() {
  4. if(Dep.target){ // 如果取值时有watcher
  5. dep.depend(); // 让watcher保存dep,并且让dep 保存watcher
  6. }
  7. return value
  8. },
  9. set(newValue) {
  10. if (newValue == value) return;
  11. observe(newValue);
  12. value = newValue;
  13. dep.notify(); // 通知渲染watcher去更新
  14. }
  15. });

Dep实现
observer/dep.js

  1. class Dep{
  2. constructor(){
  3. this.id = id++;
  4. this.subs = [];
  5. }
  6. depend(){
  7. if(Dep.target){
  8. Dep.target.addDep(this);// 让watcher,去存放dep
  9. }
  10. }
  11. notify(){
  12. this.subs.forEach(watcher=>watcher.update());
  13. }
  14. addSub(watcher){
  15. this.subs.push(watcher);
  16. }
  17. }

observer/watcher.js

  1. constructor(){
  2. this.deps = [];
  3. this.depsId = new Set();
  4. }
  5. addDep(dep){
  6. let id = dep.id;
  7. if(!this.depsId.has(id)){
  8. this.depsId.add(id);
  9. this.deps.push(dep);
  10. dep.addSub(this);
  11. }
  12. }
  13. update(){
  14. this.get();
  15. }

3.数组的依赖收集

observer/index.js

  1. this.dep = new Dep(); // 专门为数组设计的
  2. if (Array.isArray(value)) {
  3. value.__proto__ = arrayMethods;
  4. this.observeArray(value);
  5. } else {
  6. this.walk(value);
  7. }
  8. function defineReactive(data, key, value) {
  9. let childOb = observe(value);
  10. let dep = new Dep();
  11. Object.defineProperty(data, key, {
  12. get() {
  13. if(Dep.target){
  14. dep.depend();
  15. if(childOb){
  16. childOb.dep.depend(); // 收集数组依赖
  17. }
  18. }
  19. return value
  20. },
  21. set(newValue) {
  22. if (newValue == value) return;
  23. observe(newValue);
  24. value = newValue;
  25. dep.notify();
  26. }
  27. })
  28. }
  29. arrayMethods[method] = function (...args) {
  30. // ...
  31. ob.dep.notify()
  32. return result;
  33. }

递归收集数组依赖
observer/index.js

  1. if(Dep.target){
  2. dep.depend();
  3. if(childOb){
  4. childOb.dep.depend(); // 收集数组依赖
  5. if(Array.isArray(value)){ // 如果内部还是数组
  6. dependArray(value);// 不停的进行依赖收集
  7. }
  8. }
  9. }
  10. function dependArray(value) {
  11. for (let i = 0; i < value.length; i++) {
  12. let current = value[i];
  13. current.__ob__ && current.__ob__.dep.depend();
  14. if (Array.isArray(current)) {
  15. dependArray(current)
  16. }
  17. }
  18. }

六.实现Vue异步更新之nextTick

1.实现队列机制

observer/watcher.js

  1. update(){
  2. queueWatcher(this);
  3. }

observer/scheduler.js

  1. import {
  2. nextTick
  3. } from '../util/next-tick'
  4. let has = {};
  5. let queue = [];
  6. function flushSchedulerQueue() {
  7. for (let i = 0; i < queue.length; i++) {
  8. let watcher = queue[i];
  9. watcher.run()
  10. }
  11. queue = [];
  12. has = {}
  13. }
  14. export function queueWatcher(watcher) {
  15. const id = watcher.id;
  16. if (has[id] == null) {
  17. has[id] = true;
  18. queue.push(watcher);
  19. nextTick(flushSchedulerQueue)
  20. }
  21. }

2.nextTick实现原理

util/next-tick.js

  1. let callbacks = [];
  2. function flushCallbacks() {
  3. callbacks.forEach(cb => cb());
  4. }
  5. let timerFunc;
  6. if (Promise) { // then方法是异步的
  7. timerFunc = () => {
  8. Promise.resolve().then(flushCallbacks)
  9. }
  10. }else if (MutationObserver) { // MutationObserver 也是一个异步方法
  11. let observe = new MutationObserver(flushCallbacks); // H5的api
  12. let textNode = document.createTextNode(1);
  13. observe.observe(textNode, {
  14. characterData: true
  15. });
  16. timerFunc = () => {
  17. textNode.textContent = 2;
  18. }
  19. }else if (setImmediate) {
  20. timerFunc = () => {
  21. setImmediate(flushCallbacks)
  22. }
  23. }else{
  24. timerFunc = () => {
  25. setTimeout(flushCallbacks, 0);
  26. }
  27. }
  28. export function nextTick(cb) {
  29. callbacks.push(cb);
  30. timerFunc();
  31. }

http://www.zhufengpeixun.com/jiagou/vue-analyse/one.html#%E4%B8%80-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA

http://www.zhufengpeixun.com/jiagou/vue-analyse/source.html