{"id":16490538,"url":"https://github.com/dave-wind/easyvue","last_synced_at":"2026-05-06T16:33:56.993Z","repository":{"id":174889510,"uuid":"533662898","full_name":"dave-wind/easyvue","owner":"dave-wind","description":"vue2 ","archived":false,"fork":false,"pushed_at":"2022-09-20T12:41:08.000Z","size":2223,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-27T13:37:54.736Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dave-wind.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-07T07:51:32.000Z","updated_at":"2022-09-13T15:56:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"53b3ec05-ebd5-4c29-9987-cb5244dacc07","html_url":"https://github.com/dave-wind/easyvue","commit_stats":null,"previous_names":["dave-wind/easyvue"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dave-wind/easyvue","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dave-wind%2Feasyvue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dave-wind%2Feasyvue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dave-wind%2Feasyvue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dave-wind%2Feasyvue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dave-wind","download_url":"https://codeload.github.com/dave-wind/easyvue/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dave-wind%2Feasyvue/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32702106,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-06T08:33:17.875Z","status":"ssl_error","status_checked_at":"2026-05-06T08:33:17.221Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T13:48:21.410Z","updated_at":"2026-05-06T16:33:56.974Z","avatar_url":"https://github.com/dave-wind.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"### VUE 2.0 学习\n\n### Rollup\n\n##### 什么时候使用rollup\n```js\n1.不需要代码分割\n2.不需要[模块热替换（HMR)][https://webpack.js.org/concepts/hot-module-replacement/]，\n3.比较适合打包 js 的 sdk 或者封装的框架\n那么你可以首选使用rollup\n```\n\n```js\n\n1.项目初始化\n\nnpm init\nnpm install rollup @babel/core @babel/preset-env  @rollup/plugin-babel rollup-plugin-serve cross-env -D\n\nrollup: 打包工具\n\n@babel/core; babel 核心库\n\n\n@babel/preset-env: babel预设 可以把使用的js高级语法 转化为es5 \n\n@rollup/plugin-babel: 是rollup 与 babel的 bridge 桥梁\n\nrollup-plugin-serve: 开启 静态服务\n\ncross-env: 环境变量设置\n\n\nrollup-plugin-commonjs: 在 rollup 中引用 commonjs 规范的包。该插件的作用是将 commonjs 模块转成 es6 模块\n\n```\n\n\n#### rollup 注意\n\n```js\n1.commonjs:\n引用第三方库时，好像是因为库并没有严格的遵循 ES6 Module 输出模块，所以 import 的时候会报错,解决方案: 需要使用到 rollup-plugins-commonjs 的 namedExports 定义输出的模块名\n\n```\n\n\n### Vue 原理\n\n\n#### 步骤\n```js\nVue入口文件,当实例化Vue时 会进行初始化操作,内部会调用各种Mixin方法给vue 的原型添加方法\n\n\n1.initMixin:增加初始化方法:\n\n    0.声明 _$init方法\n    1.对 new Vue 传进来的对象 上进行初始化 \n    2.对data 数据进行 数据监控用的 就是 Object.defineproperty, 每一个key上都 设置 get set\n    3.对对象和数组都进行 数据监控,对数组里 对象也会进行监控\n    4.声明$mount 方法, 有el挂载元素 就在$init 内调用\n    5. 没有render方法 就自己生成,通过compileToFunction 编译方法 生成 render模版方法 \n    6.步骤:\n    将 html 通过 paeseHTML-\u003east语法树 通过generate-\u003e 字符串模版 (字符串拼接)-\u003e with + new Function 生成render方法(模版引擎)\n    \n2.renderMixin: 增加渲染方法,调用render方法 ==\u003e  render方法 生成虚拟dom    \n    0.里面拓展了原型方法,_c,_v,_s 辅助 render方法里的方法;最后生成虚拟dom\n    1.render方法从何而来, 当init时候,没有render 方法 去重新生成的\n    2.在哪里调用的? 在lifecycleMixin里 挂载的时候调用的\n    3. render 内部函数 其实也是调用 option 里的render方法, 用户不用 vue-compile-template生成 就要vue里自己生成render方法\n    \n 3.lifecycleMixin:\n    0.增加_update方法,将虚拟dom 渲染成真实的dom  \n    1.调用时机: 在init.js里 $mount里 进行了 初次挂载渲染 调用 mountComponent\n    2. mountComponent 内 =\u003e 声明 new Watcher,会调用 updateComponent =\u003e 调用 _update方法  vm._update(vm._render()) 把 vm._render()返回的虚拟dom 再调用patch方法 转化为 真实的dom\n\n```\n\n\n\n\n#### 异步批量更新\n\n```js\n\n$nextTick 方法\n\n当修改属性的时候, 内部会调用 nextTick; 如果用户在外面也写 $nextTick 也会被调用; 执行顺序 看调用顺序;\n\n```\n\n\n#### Vue组件原理\n\n```js\n0.原型知识:\n// 声明一个类\n function Rabbit(){\n    this.name = 'rabbit';\n\n    this.say = function() {\n        console.log('say')\n    }\n }\n\n Rabbit.prototype.eat = function() {\n    console.log('eat');\n }\n\n let r1 = new Rabbit;\n /*\n * 因为一个实例的构造函数指向其函数类本身;\n * default prototype 是一个对象,其唯一属性构造函数指向函数本身 \n * Rabbit.prototype (default prototype)\n */\n r1.constructor === Rabbit.prototype.constructor; // true\n\n// 实例可以通过实例的构造函数去创建; 因为实例的构造函数指向函数本身\n let r2 = new r1.constructor();\n r2.constructor == r1.constructor; // true\n\n// 实例原型链指针 指向类的原型\nr1.__proto__ === Rabbit.prototype // true\nRabbit.prototype.__proto__ == Object.prototype // true\nObject.prototype.__proto__ == null\n\n/**\n *  一般没有人会问 构造函数的 指针指向哪? \n * 和原型上任意方法指向都是一样的\n * eg: [].slice.__proto__; Rabbit.prototype.eat.__proto__\n * 都是 native 原生方法 非js代码 返回结果被 FunctionSourceString了\n **/\nRabbit.prototype.constructor.__proto__ //  ƒ () { [native code] }\n\n\n/*\n* 继承 原型链\n*/\n\nfunction Sub() {}\n/**\n * 1.通过 Object.create 继承; \n * 2.把构造函数指向其本身\n */\nSub.prototype = Object.create(Rabbit.prototype);\nSub.prototype.constructor = Sub;\n\n\n\n1.组件声明\nVue.component 会被注册成一个全局方法;调用也是全局组件;\n都会定义在 Vue.options 上\n原理:\n    0.首先他会把 Vue.component('',options)里的 options 内部 转变为构造函数; 使用 this.options._base.extend, 这里实例的_base 指的是统一的父类Vue,因为考虑可能组件也有VueComponent.component;目的是保证 子组件是继承Vue组件;在其拓展不会影响Vue\n\n2.Vue.extend:\n    0.在内部声明VueComponent 子组件;目的是 内部初始化 调用 _this.init() 方法; 该方法从父组件而来所以需要继承\n        const Sub = function Vuecomponent(options) {\n            this._init(options)\n        }\n        Sub.prototype = Object.create(Vue.prototype)\n        Sub.prototype.constructor = Sub;\n        // 子类 options 与Vue.options 合并\n        // 一些方法 比如 component directive filter 子组件都会继承\n        Sub.options = mergeOptions(..) \n\n    1.更改组件合并策略:(子组件的options 采用了和生命周期类似的合并策略;)\n        如果不进行合并,可能子组件重名全局组件就会被覆盖;\n        解决:\n        function mergeAssets(parentVal, childVal) {\n        // 继承了父级 res.__proto__ = parentVal; \n        const res = Object.create(parentVal); \n            if (childVal) {\n                for (let key in childVal) {\n                    // 让组件 先找自己的局部组件,找不到根据原型链 网上找\n                    res[key] = childVal[key]\n                }\n            }\n            return res;\n        }\n        合并完 子组件先从自身找, 找不到 再根据 __proto__ 向上找;所以全局组件可以在任何地方调用\n\n3.组件渲染:\n\n    1.render 方法里去渲染组件需要特殊判断,原先是只渲染原生标签\n    在createElement方法中 先判断非原生标签 需要去找到组件的定义,通过构造函数 去创建组件 生成虚拟节点\n\n    function createElement(vm, tag, data = {}, ...children) {\n\n        if(原生组件) {\n            return vnode(xxx)\n        }else {\n            // vm是实例,在实例上的$options方法上找到子组件的定义\n             let Ctor = vm.$options.components[tag];\n             // 通过构造函数 去创建组件 生成虚拟节点\n              return createComponent(vm, tag, data, key, children, Ctor)\n        }\n    }\n\n    function createComponent(vm, tag, data, key, children, Ctor) {\n        // 如果传的是对象 需要 通过 extend 变成构造函数, 继承父组件方法和属性等\n        if (isObject(Ctor)) {       \n            Ctor = vm.$options._base.extend(Ctor)\n        }\n\n        // 定义的 组件内部的生命周期\n        data.hook = {\n            init(vnode) {\n                // 当前组件的实例, 虚拟节点上 componentInstance 表示的就是vue实例\n                let child = vnode.componentInstance = new Ctor({ _isComponent: true });\n                // 组件的挂载 \n                child.$mount();\n            }\n            // ....\n        }\n\n        let componentObj = {\n            Ctor, // 组件的构造函数 方便子组件调用其他方法\n            children // 子组件children 是插槽\n        }\n\n        // 组件内部会自动调用 extend; 创建虚拟节点; 组件是没有孩子children, 只有插槽\n        return vnode(`vue-component-${Ctor.cid}-${tag}`, data, key, undefined, undefined, componentObj)\n    }\n\n    2.在子组件 执行init的时候 是需要挂载的,都会执行 _update 所以每个组件都会创建Watcher. 需要手动调用$mount, 因为他们options上没有传el;所以自定义了组件生命周期,要先内部调用.\n\n\n    3. 渲染会调用 patch方法 把结果 放在 $el上;\n\n    4.总结: Vue.component || 子组件声明 ==\u003e 传的options 内部执行Vue.extend继承, 通过组件原型链的合并策略 ==\u003e 渲染的时候,判断是组件, 去创建组件,就会new构造函数 ==\u003e 就会调用init初始化, 然后拿到构造函数实例,放到虚拟节点 vnode.componentInstance上 还有手动调用$mount方法; 就会把真实dom放到实例上==\u003e 最后渲染的时候 返回el即可\n    \n    // 所以父子组件渲染,是拿整个vnode去渲染,有子节点递归调用.生命周期执行顺序: 都是创建,先父后子,再挂载, 先子后父 最后 输出el\n\n注意⚠️:\n组件渲染 会调用当前组件对于的构造函数产生一个实例,\n每个组件使用 都会调用Vue.extend,创建构造函数\n实例化子组件,会将当前options 和用户定义选项合并 mergeOptions\n通过创建实例,内部会调用 子类_init() 内部会创建渲染watcher 将渲染结果放到 vm.$el上 = 组件渲染结果\n\n```\n\n#### 数据劫持 \u0026\u0026 依赖收集\n\n```js\n\nnew Vue 的时候 内部会调用 _init方法,把options里传的data 进行initData 初始化\n1. 对data上所有的key 进行代理,转移取值 把data 转移到_data上 这样直接访问vm.xxx\n\n2. MVVM模式数据劫持,调用Object.defineProperty;  new Observer(data); 内部区别对象和数组进行数据劫持.\n    a.对象情况下 直接调用 walk方法, 对对象每一个key进行深度遍历,如果对象下的值还是对象 需要进行递归调用observe 继续劫持. set方法也需要判断,新值是对象 也需要递归调用.\n\n    b. new Dep() Vue里数据劫持 data里每一个对象对应一个dep,dep主要用来做发布订阅的, 在访问属性get时,收集watcher ,在修改时 调用wathcer update方法 更新视图.\n\n    c. 当我们初始化渲染时候, 会 new Watcher  内部会创建一个watcher,存到栈当中, 当我们 访问vm实例任何属性时候, 都会走get方法, 判断 Dep.target 是否存在, 存在就需要调用 dep.depend方法:\n    depend() {\n        Dep.target.addDep(this) // this是dep实例 为后面用\n    }\n    这里的 Dep.target 就是watcher实例, 调用其addDep方法,顾名思义就是存dep, 在Watcher 类内部 也会去去重 存dep;最后调用dep 去存一个watcher; \n\n    d. 在只有一个组件实例情况下,相当于 dep是关联一个个不重复的属性,然后存放的一个watcher 俩者都避免重复,就会避免重复渲染; 而数据劫持在其中 冲当观察者,当依赖存好以后, 去set 更新属性时, 通知dep notify 发布, 就会调用watcher 去update; \n\n    e. 当我们 new Watcher 渲染页面的时候, 都会访问data里的属性, 那时候已经把依赖收集起来了, 这里的watcher用的是渲染watcher收集的, 当我们set 修改属性的时候, 就直接去拿出 watcher 去更新视图了\n \n\n```\n\n\n\n\n\n#### Vue2 Dom-diff\n\n```js\n\nVue dom-diff 主要是平级比对,采用的是双指针 对旧vnode和新vnode进行比对, 准确说 是四个指针,四个节点, 旧头指针,尾指针, 新vnode的头和尾; 以及旧新的头尾节点.\n\npatch方法,是一个把旧节点和新虚拟节点 对比 生成真实dom的方法\n1.首先 if 判断 是否为组件, 如果没有旧vnode 那么认为是组件 会 空挂载 return dom\n2. 如果是真实html标签,含有nodeType 就用 oldvnode 的parent.insertBefore 插入元素; 替换删除 老dom\n3.如果不是真实标签 那继续走虚拟节点思路:\n    第一判断 俩虚拟节点 标签是否一致,不一致 直接替换\n    第二 判断是否为文本, 是文本 在内部对比, 文本不一样 替换掉文本\n    第三 ,复用节点,vnode.el = oldVnode.el 去更新属性, 如果都有子节点,调用updateChildren,\n    第四, 如果只有新节点有children, 旧节点没有, 直接循环新节点替换,转化为真实dom,  el.appendChild(createElm(child));\n    第五, 如果老的有children, 新没有 直接全部置空\n4.调用 updateChildren 方法步骤:\n       // 第一个指针指向oldVnode 开头\n    let oldStartIndex = 0;\n    let oldStartVnode = oldChildren[0];\n    // 第二个指针指向oldVnode 尾部\n    let oldEndIndex = oldChildren.length - 1;\n    let oldEndVnode = oldChildren[oldEndIndex];\n\n    // 第三个指针指向 newVnode 开头\n    let newStartIndex = 0;\n    let newStartVnode = newChildren[0];\n     // 第四个指针指向 newVnode 尾部\n    let newEndIndex = newChildren.length - 1;\n    let newEndVnode = newChildren[newEndIndex];\n\n    A.优化向后插入元素情况,表示俩虚拟节点,开头节点相同 对比\n     A B C D\n     A B C E\n     ++ oldStartIndex,++newStartIndex,指针直到D和E,发现节点不同,新旧头节点不同,尾节点也不同,头尾都不同, 所以采用暴力比对,发现E节点在旧vnode没有 直接在旧startIndex前插入元素(因为oldstartIndex 一直在移动循环), 最后while循环完, , oldStartIndex \u003c= oldEndIndex 时,删除 不必要的oldChid 元素 D 即可\n\n\n    B.优化向前插入元素情况,结尾节点相同 对比, 倒着比\n\n    C. 头移尾 情况,表示开头,结尾都不相同,那旧vnode开头 和 新vnode 结尾对比, 当标签和key一样,就把A插入到后面,然后指针移动, ++oldStartIndex, 新vnode 尾指针 从右往左, --newEndIndex;主要是移动旧vnode;后续就是 B与B比对 一样 C与C比对一样 结束 只移动dom一次\n       A B C\n       B C A\n\n     D.尾移头 一样;把旧vnode 最后一个D 移动到 旧vnode前面\n       A B C D\n       D A B C\n    parent.insertBefore(oldEndVnode.el, oldStartVnode.el);\n    移动完以后 移动指针, --oldEndIndex; ++newStartIndex\n\n    E.暴力对比 针对以上都不同的情况:\n        现根据老节点key,做一个映射表,拿新的虚拟节点去映射表查找,如果可以查到,则进行移动(移到头指针前面位置)如果找不到就在头指针插入新元素)\n\n        A B C E\n        Q A C F\n\n      过程表示: \n      1.新vnode里的Q在 旧vnode的keymap里找不到 直接 插入到startIndex指针也就是A之前:\n      [Q]A B C E\n        .. A C F\n          ++ newStartIndex 为 1 指向A\n      2. 发现A与A相同; 旧指针也++i 指向 B,新指针指向C\n        [Q][A] B C E\n             ..C F\n\n       3. 如上发现,头与头节点,头与尾,尾与头都不同,走暴力对比, 且C能在 旧vnodekeyMap里找到; 所以要复用该vnode,就是moveVnode 走到了暴力对比else逻辑; 为了防范数组塌陷,把 C设置为undefined; 再把当前C 移动到旧指针之前,如果有子内容 比如text等 需要patch 比较子元素;最后 新指针,新节点指向下一个 F;\n         [Q][A][C]B undefined E\n                ..F\n        4.F同理; 最后新vnode 循环完; 如果oldStartIndex \u003c= oldEndIndex; 需要循环 oldChildren  去remove 那些child,得   Q A C F \n\n      // map是oldvnode 根据key做的映射对象, 拿到新vnode 去查找, 找不到 旧在oldvnode节点前插入\n      let moveIndex = map[newStartVnode.key];\n       if (!moveIndex) { \n           parent.insertBefore(createElm(newStartVnode), oldStartVnode.el);\n       } \n       // 如果找的到 就复用该vnode\n       else { \n            let moveVnode = oldChildren[moveIndex];\n            // 为了避免循环塌陷,还要移动完以后, 设置为空\n            oldChildren[moveIndex] = undefined;\n            parent.insertBefore(moveVnode.el, oldStartVnode.el);// 放在头指针前面\n            patch(moveVnode,newStartVnode)\n         }\n        // 最后 移动指针 准备下次循环\n        newStartVnode = newChildren[++newStartIndex];   \n\n#注在比对当中,因为避免循环塌陷,移动完以后设置为空的节点; 当俩vnode对比节点相同后移会遇到undefined的节点, 需要开头做判断 跳过 undefined节点; 还要考虑到反向操作;尾移头那种 即可;\n    if (!oldStartVnode) {\n            oldStartVnode = oldChildren[++oldStartIndex];\n    } else if (!oldEndVnode) { // 反向操作\n            oldEndVnode = oldChildren[--oldEndIndex]\n    } else ...\n\n\n#最后在 lifecycle.js _update方法里;加入判断是否为初次渲染和非初次渲染需要在 实例vm上挂载一个 _vnode; 保证下次 vnode对比 即可:\n\nVue.prototype._update = function(vnode) {\n    const vm = this;\n    const prevVnode = vm._vnode; // 保存上一次虚拟节点为了实现对比\n     vm._vnode = vnode; // 真实渲染内容 \u003cdiv\u003e\u003c/div\u003e...\n\n    // 第一次 prevVnode没有值 走初次渲染\n     if (!prevVnode) {\n            // 通过虚拟节点 渲染真实的dom, 去替换 真实的el\n            vm.$el = patch(vm.$el, vnode);\n        }else {\n            // 拿到保存的上一次 真实内容 去对比\n            vm.$el =  patch(prevVnode, vnode);\n     }\n}\n\n注:\nvm._vnode.parent = vm.$vnode \n$vnode 是注册的组件, _vnode为组件真实渲染的内容 div ...\n\n\n\n\n\n#key的重要性:\n// 旧\n\u003cli\u003ea\u003c/li\u003e\n\u003cli\u003eb\u003c/li\u003e\n\u003cli\u003ec\u003c/li\u003e\n\n// 变新\n\u003cli\u003ec\u003c/li\u003e\n\u003cli\u003eb\u003c/li\u003e\n\u003cli\u003ea\u003c/li\u003e\n\n#没有key\n如上, 没有key,会复用,会做3次dom操作,但是如果有key 就会在基于老的vdom上位移 元素 提高性能.\n\n#key索引\n如果key 有 用索引, 当dom 进行序列操作,比如倒叙操作等 但key还是按索引排列, 前后索引一样,标签一样就会去比对孩子children 就会去替换孩子,\n但是key用唯一id 比如如上图, 他只需要移动dom2次位移 即可实现. dom-diff 是基于 旧vnode 去生成 dom,无需重新创建dom,操作去移动dom,多的dom删掉即可.\n\n\n```\n\n\n#### watcher的原理\n\n本质是调用是基于$watch的 里的new Watcher 对数据改变进行响应\n```js\n\n Watcher 在 observer/watcher.js 里\n watcher里做了什么?\n 1.id 记录不同的watcher用的\n 2.\nclass Watcher {\n\n}\n\nwatch 监听数据变化 一般有几种写法\n\n watch:{\n     name: [{\n                handler: 'handler',\n                sync: true\n        }],\n        name2(newVal,oldVal) {\n\n        },\n         name3: {\n             handler(newVal,oldVal){\n\n             },\n            sync: true // 每次改变都会触发, 为同步标记\n        }\n }\n\n this.$watch('xx',()=\u003e{})\n\n有 数组型, 函数型, 对象型 还有直接调用$watch \n\n1: 是对参数的格式化操作 把其都转为 key handler的形式\n如上 拿到 key为name, handler 为一个 callback\n\n2. 调用 new Watcher(vm,exprOrFn,cb,options)\n\n给 user 调用的 wathcer 加上标识 表示 是user的wathcer \noptions.user = true;\n\n3.一般 new Wathcer 就会调用 getter方法 里面会进行依赖收集; 所以需要判断exprOrFn, \n    a.如果是渲染wathcer,exprOrFn 类型为 function 直接调用\n    b.如果是用户写的watcher 穿的exprOrFn为字符串,所以我们需要把 getter方法 封装成 取vm实例下属性的取值函数\n    eg: 'obj.user.name'\n\n    this.getter = funcion() {\n         let patch = exprOrFn.split(\".\");\n            let val = vm;\n              // 循环取值 xxx.xx.x \n            for (let i = 0; i \u003c patch.length; i++) {\n                    val = val[patch[i]];\n           }\n           return val;\n    }\n\n4.取到的值 就会成为oldVal 当下一次 watcher update的时候 就会触发用户写的 callback 把 新值 和老值 都传递过去, 如果标记sync 为true 则会修改一次,触发一次.不会异步渲染;\n\n```\n\n\n#### Computed 计算属性原理\n\n\n```js\n1.根据用户传入的 options computed 参数 进行 initComputed初始化\n会循环遍历 computed 传入的对象每一个属性 都会挂载一个 new watcher,并且通过 Object.defineProperty属性描述器 把每一个key 的定义的实例上,取值走get,设置走set;\n\n2.但是有问题,这样定义每次取值,都会造成 用户写的计算属性函数重新执行,想设计一个缓存机制,给 属性描述器的get方法定义了一个高阶函数,AOP切片:\nObject.defineProperty(target, key, sharedPropwetyDefinition)\nsharedPropwetyDefinition.get = createComputedGetter(key)\nfunction createComputedGetter(key) {\n    // 添加了缓存机制\n    return function () {\n        // 拿到了刚才的watcher\n        let watcher = this._computedWatcher[key];\n        if (watcher.dirty) { // 默认第一次取值, 为true\n\n            watcher.evaluate();\n            console.log(\"dirty--\", watcher.value)\n        }\n        return watcher.value;\n    }\n}\n设置切片目的就是 当watcher实例 dirty为true 会去获取值,,取完值,设置为false 用户再取值 就走缓存\n\n3.第一次走watcher get方法时,就已经把当前watcher存起来了, 再做取值操作,这样就把 依赖的属性和当前的计算属性watcher对应起来了, 相当于 msg 和name也收集了introduce属性收集了, 所以不管谁变了都会造成计算属性的更新操作 update\n\n\n4. update 不需要做其他操作,只需要把 dirty 设置为true 就会走之前老的逻辑 重新取值\n\ncomputed: {\n        introduce() {\n           return this.msg + ' ' + this.name\n       }\n}\n\nclass Watcher {\n\n    get() {\n\n        pushTarget(this); // 先把watcher 存起来\n        let val = this.getter.call(this.vm); // 渲染watcher的执行 || 用户写的watcher执行 || 计算属性函数执行\n\n        popTarget(); // 移除watcher\n\n        return val;\n    }\n}\n\n\n5. 综上, 当我们定义计算属性的时候, 内部会创建 计算属性watcher, 当我们执行计算属性方法的时候, 就会去访问 this实例里的变量,就会走数据劫持,就会收集计算属性的依赖, 内部依赖不更新会设置缓存,多次取值会直接拿缓存值, 当内部依赖改变的时候 触发 watcher update时候,内部改变dirty状态, 就会重新get()新值 重新计算并返回\n\n\n\n6. 计算属性收集依赖\n\n写到这里, 计算属性虽然可以触发,打印结果也变了,但是 页面并不会触发渲染,因为我们并没有把 渲染watcher 和 属性关联起来\n\nDep类中 有个 队列是专门存放 Watcher的, 每次添加一个都会pop 在剔除一个,当计算属性watcher被踢出了, 指针就会指向前一个\n[渲染wather,计算属性watcher]\n\n因为调用 Watcher get()方法的时候, 会调用 popTarget;\n\nlet stack = []\n\nfunction popTarget() {\n    stack.pop();\n    Dep.target = stack[stack.length - 1];\n}\n\n所以我们调用计算属性方法时候,需要判断是否还有 渲染Watcher Dep.target, 如果存在 让其计算属性watcher存放的 deps 循环调用 去存放 渲染watcher, 最后异步更新.\n\n这里强调一下 假如计算属性依赖100个属性,100个属性同步改变100次, 但是渲染只会是一次, 因为是内部调用 $nextTick 异步渲染!\n\n\nclass Watcher {\n\n    depend() {\n        // 如果还有deps 依赖的属性,循环他们\n        let index = this.deps.length;\n        while(index--) {\n            // 调用把每一个dep属性  都去存放当前 watcher\n            // 这里又回到了 Watcher 和dep 依赖关系那里\n            this.deps[index].depend()\n        }\n    }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdave-wind%2Feasyvue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdave-wind%2Feasyvue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdave-wind%2Feasyvue/lists"}