image.png

    1. observer:监听者,监听视图模型data的变化
    2. 检测到变化,触发Object.defineProperty下面的set函数,循环通知观察者列表中的成员
    3. Dep:观察者列表,接收通知以后执行watcher给的回调
    4. watcher添加观察者列表
    5. 重点:三个角色是重点

    16-mvvm设计模式/生命周期 - 图2

    • 关于dom的操作在mounted之前都不能实现(this.$refs)
    • 数据双向绑定渲染在created里面以及之后的生命周期里实现
    • 关于dom的操作如果一定要写在mounted之前,则需使用this.$nextTick()

    index.html

    1. <html lang="en">
    2. <head>
    3. <meta charset="UTF-8">
    4. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    5. <meta http-equiv="X-UA-Compatible" content="ie=edge">
    6. <title>MVVM</title>
    7. </head>
    8. <body>
    9. <div id="app">
    10. <h2>{{title}}</h2>
    11. <button v-on:click="clickMe">点我</button><br/>
    12. <input type="text" v-model="name">
    13. <h1>{{name}}</h1>
    14. </div>
    15. <script src="js/observer.js"></script>
    16. <script src="js/watcher.js"></script>
    17. <script src="js/compile.js"></script>
    18. <script src="js/index.js"></script>
    19. <script>
    20. new Vue({
    21. el:'#app',
    22. data:{
    23. title:'vue source code',
    24. name:'mooc',
    25. // age:10
    26. },
    27. methods:{
    28. clickMe(){
    29. this.title="vue click "
    30. }
    31. },
    32. // mounted:function(){
    33. // window.setTimeout(()=>{
    34. // this.title="3000s later"
    35. // },3000)
    36. // }
    37. })
    38. </script>
    39. </body>
    40. </html>

    index.js

    1. function Vue (options) {
    2. var self = this;
    3. this.data = options.data;
    4. this.methods = options.methods;
    5. Object.keys(this.data).forEach(function (key) {
    6. self.proxyKeys(key);
    7. });
    8. observe(this.data);
    9. new Compile(options.el, this);
    10. // options.mounted.call(this); // 所有事情处理好后执行mounted函数
    11. }
    12. Vue.prototype = {
    13. proxyKeys: function (key) {
    14. var self = this;
    15. Object.defineProperty(this, key, {
    16. enumerable: false,
    17. configurable: true,
    18. get: function () {
    19. console.log("get",key)
    20. return self.data[key];
    21. },
    22. set: function (newVal) {
    23. console.log("set",key)
    24. self.data[key] = newVal;
    25. },
    26. });
    27. },
    28. };

    observer.js

    1. function Observer (data) {
    2. this.data = data;
    3. this.walk(data);
    4. }
    5. Observer.prototype = {
    6. walk: function (data) {
    7. var self = this;
    8. Object.keys(data).forEach(function (key) {
    9. self.defineReactive(data, key, data[key]);
    10. });
    11. },
    12. defineReactive: function (data, key, val) {
    13. var dep = new Dep();
    14. var childObj = observe(val);
    15. Object.defineProperty(data, key, {
    16. enumerable: true,
    17. configurable: true,
    18. get: function getter () {
    19. if (Dep.target) {
    20. dep.addSub(Dep.target);
    21. }
    22. return val;
    23. },
    24. set: function setter (newVal) {
    25. if (newVal === val) {
    26. return;
    27. }
    28. val = newVal;
    29. dep.notify();
    30. },
    31. });
    32. },
    33. };
    34. function observe (value, vm) {
    35. if (!value || typeof value !== 'object') {
    36. return;
    37. }
    38. return new Observer(value);
    39. }
    40. function Dep () {
    41. this.subs = [];
    42. }
    43. Dep.prototype = {
    44. addSub: function (sub) {
    45. this.subs.push(sub);
    46. },
    47. notify: function () {
    48. this.subs.forEach(function (sub) {
    49. sub.update();
    50. });
    51. },
    52. };
    53. Dep.target = null;

    watcher.js

    1. function Watcher (vm, exp, cb) {
    2. this.cb = cb;
    3. this.vm = vm;
    4. this.exp = exp;
    5. this.value = this.get(); // 将自己添加到订阅器的操作
    6. }
    7. Watcher.prototype = {
    8. update: function () {
    9. this.run();
    10. },
    11. run: function () {
    12. var value = this.vm.data[this.exp];
    13. var oldVal = this.value;
    14. if (value !== oldVal) {
    15. this.value = value;
    16. this.cb.call(this.vm, value, oldVal);
    17. }
    18. },
    19. get: function () {
    20. Dep.target = this; // 缓存自己
    21. var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数
    22. Dep.target = null; // 释放自己
    23. return value;
    24. },
    25. };

    compile.js

    1. function Compile (el, vm) {
    2. this.vm = vm;
    3. this.el = document.querySelector(el);
    4. this.fragment = null;
    5. this.init();
    6. }
    7. Compile.prototype = {
    8. init: function () {
    9. if (this.el) {
    10. this.fragment = this.nodeToFragment(this.el);
    11. this.compileElement(this.fragment);
    12. this.el.appendChild(this.fragment);
    13. } else {
    14. console.log('Dom元素不存在');
    15. }
    16. },
    17. nodeToFragment: function (el) {
    18. var fragment = document.createDocumentFragment();
    19. var child = el.firstChild;
    20. while (child) {
    21. // 将Dom元素移入fragment中
    22. fragment.appendChild(child);
    23. child = el.firstChild;
    24. }
    25. return fragment;
    26. },
    27. compileElement: function (el) {
    28. var childNodes = el.childNodes;
    29. var self = this;
    30. [].slice.call(childNodes).forEach(function (node) {
    31. var reg = /\{\{(.*)\}\}/;
    32. var text = node.textContent;
    33. if (self.isElementNode(node)) {
    34. self.compile(node);
    35. } else if (self.isTextNode(node) && reg.test(text)) {
    36. self.compileText(node, reg.exec(text)[1]);
    37. }
    38. if (node.childNodes && node.childNodes.length) {
    39. self.compileElement(node);
    40. }
    41. });
    42. },
    43. compile: function (node) {
    44. var nodeAttrs = node.attributes;
    45. var self = this;
    46. Array.prototype.forEach.call(nodeAttrs, function (attr) {
    47. var attrName = attr.name;
    48. if (self.isDirective(attrName)) {
    49. var exp = attr.value;
    50. var dir = attrName.substring(2);
    51. if (self.isEventDirective(dir)) { // 事件指令
    52. self.compileEvent(node, self.vm, exp, dir);
    53. } else { // v-model 指令
    54. self.compileModel(node, self.vm, exp, dir);
    55. }
    56. node.removeAttribute(attrName);
    57. }
    58. });
    59. },
    60. compileText: function (node, exp) {
    61. var self = this;
    62. var initText = this.vm[exp];
    63. this.updateText(node, initText);
    64. new Watcher(this.vm, exp, function (value) {
    65. self.updateText(node, value);
    66. });
    67. },
    68. compileEvent: function (node, vm, exp, dir) {
    69. var eventType = dir.split(':')[1];
    70. var cb = vm.methods && vm.methods[exp];
    71. if (eventType && cb) {
    72. node.addEventListener(eventType, cb.bind(vm), false);
    73. }
    74. },
    75. compileModel: function (node, vm, exp, dir) {
    76. var self = this;
    77. var val = this.vm[exp];
    78. this.modelUpdater(node, val);
    79. new Watcher(this.vm, exp, function (value) {
    80. self.modelUpdater(node, value);
    81. });
    82. node.addEventListener('input', function (e) {
    83. var newValue = e.target.value;
    84. if (val === newValue) {
    85. return;
    86. }
    87. self.vm[exp] = newValue;
    88. val = newValue;
    89. });
    90. },
    91. updateText: function (node, value) {
    92. node.textContent = typeof value === 'undefined' ? '' : value;
    93. },
    94. modelUpdater: function (node, value, oldValue) {
    95. node.value = typeof value === 'undefined' ? '' : value;
    96. },
    97. isDirective: function (attr) {
    98. return attr.indexOf('v-') == 0;
    99. },
    100. isEventDirective: function (dir) {
    101. return dir.indexOf('on:') === 0;
    102. },
    103. isElementNode: function (node) {
    104. return node.nodeType == 1;
    105. },
    106. isTextNode: function (node) {
    107. return node.nodeType == 3;
    108. },
    109. };