[{"content":"#响应式\ninitState方法与 响应式 initState方法 props=\u0026gt;methods=\u0026gt;data=\u0026gt;computed=\u0026gt;watch\nexport function initState (vm: Component) { vm._watchers = [] const opts = vm.$options // 初始化Props 并添加响应式，代理到vm实例上，this.key获取props[key] if (opts.props) initProps(vm, opts.props) // 初始化methods 判断方法名是否已定义或方法名对应的是否为函数 vm.key=\u0026gt;methods[key] if (opts.methods) initMethods(vm, opts.methods) // 初始化data if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } // computed if (opts.computed) initComputed(vm, opts.computed) // watch if (opts.watch \u0026amp;\u0026amp; opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } } initProps方法，传入vm, vm.$options.props\nfunction initProps (vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. // 缓存prop键，以便将来的prop更新可以使用Array而不是动态对象键枚举进行迭代。 const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted if (!isRoot) { toggleObserving(false) // 改变shouldObserve的值 } for (const key in propsOptions) { keys.push(key) // 检查值的类型，先获取传入值，判断是否是boolean类型(没有传入值和默认值赋予false), 传入值为undefined 赋予默认值，验证类型[,不一致就报错] const value = validateProp(key, propsOptions, propsData, vm) // Object.defineProperty(props=\u0026gt; vm._props,key=\u0026gt;props键值,{...}) //vm._props.key 给props中的属性添加响应式 defineReactive(props, key, value) // static props are already proxied on the component\u0026#39;s prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here.在 Vue.tended ()期间，静态props 已经在组件的原型上进行了代理。我们只需要在这里的实例化中定义代理props 。 if (!(key in vm)) { // Object.defineProperty(vm, key, {...}) // vm.key =\u0026gt; vm._props.key 即this.key proxy(vm, `_props`, key) } } toggleObserving(true) } initData (vm: Component)方法\nfunction initData (vm: Component) { let data = vm.$options.data // 函数形式返回mergedInstanceDataFn.call(vm, vm) data = vm._data = typeof data === \u0026#39;function\u0026#39; ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== \u0026#39;production\u0026#39; \u0026amp;\u0026amp; warn( \u0026#39;data functions should return an object:\\n\u0026#39; + \u0026#39;https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function\u0026#39;, vm ) } // proxy data on instance 在实例上代理data const keys = Object.keys(data) let i = keys.length while (i--) { const key = keys[i] // Object.defineProperty(vm, key, {...}) // vm.key =\u0026gt; vm._props.key 即this.key proxy(vm, `_data`, key) } // observe data observe(data, true /* asRootData */) } proxy(target: Object, sourceKey: string, key: string)方法，在target对象上以key为键值，获取target.sourceKey.key的值\nexport function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) } observe 获取观察者（创建或返回原有） observe (value: any, asRootData: ?boolean)方法\nexport function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } // 观察者 let ob: Observer | void ob = new Observer(value) if (asRootData \u0026amp;\u0026amp; ob) { ob.vmCount++ } return ob } /** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. * 尝试为一个值创建一个观察者实例，如果成功观察到，则返回新的观察者，如果该值已经有观察者，则返回现有的观察者。 */ export function observe (value: any, asRootData: ?boolean): Observer | void { // 检查类型 if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void // 做过响应式处理 直接返回现有观察者 ob=\u0026gt;value.__ob__ if (hasOwn(value, \u0026#39;__ob__\u0026#39;) \u0026amp;\u0026amp; value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve \u0026amp;\u0026amp; !isServerRendering() \u0026amp;\u0026amp; (Array.isArray(value) || isPlainObject(value)) \u0026amp;\u0026amp; Object.isExtensible(value) \u0026amp;\u0026amp; !value._isVue ) { // 创建观察者 ob = new Observer(value) } if (asRootData \u0026amp;\u0026amp; ob) { ob.vmCount++ } return ob } Observer 观察者 Observer类\nexport class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value // 实例化dep // 添加响应式时会遍历属性，每个属性创建一个dep this.dep = new Dep() this.vmCount = 0 // 给value对象添加__ob__属性，值为this Object.defineProperty def(value, \u0026#39;__ob__\u0026#39;, this) if (Array.isArray(value)) { // value 为数组 if (hasProto) { // 判断__proto__ 能否使用 // value.__proto__=Object.create(Array.prototype) 重写数组方法 protoAugment(value, arrayMethods) } else { // Object.defineProperty 重写数组方法 copyAugment(value, arrayMethods, arrayKeys) } // observe数组的每一项 this.observeArray(value) } else { // value为对象 对象响应式处理 this.walk(value) } } /** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. * 遍历所有属性并将它们转换为getter/setter。仅当值类型为Object时，才应调用此方法。 */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i \u0026lt; keys.length; i++) { defineReactive(obj, keys[i]) } } /** * Observe a list of Array items. */ observeArray (items: Array\u0026lt;any\u0026gt;) { for (let i = 0, l = items.length; i \u0026lt; l; i++) { observe(items[i]) } } } arrayMethods\n/* * not type checking this file because flow doesn\u0026#39;t play well with * dynamically accessing methods on Array prototype */ import { def } from \u0026#39;../util/index\u0026#39; // arrayMethods的原型是数组的原型，避免重写原型方法影响原有数组方法 const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) // 这7个方法会改变原数组 const methodsToPatch = [ \u0026#39;push\u0026#39;, \u0026#39;pop\u0026#39;, \u0026#39;shift\u0026#39;, \u0026#39;unshift\u0026#39;, \u0026#39;splice\u0026#39;, \u0026#39;sort\u0026#39;, \u0026#39;reverse\u0026#39; ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { // 执行原方法 const result = original.apply(this, args) // 获取观察者 const ob = this.__ob__ let inserted switch (method) { case \u0026#39;push\u0026#39;: case \u0026#39;unshift\u0026#39;: inserted = args break case \u0026#39;splice\u0026#39;: inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change // 通知更新 ob.dep.notify() return result }) }) defineReactive(obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean) 定义对象上的响应式属性\nexport function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { // 每个响应式属性存在一个dep，类似消息中介，会存储相关watcher并通知更新 const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property \u0026amp;\u0026amp; property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property \u0026amp;\u0026amp; property.get const setter = property \u0026amp;\u0026amp; property.set if ((!getter || setter) \u0026amp;\u0026amp; arguments.length === 2) { val = obj[key] } let childOb = !shallow \u0026amp;\u0026amp; observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal \u0026amp;\u0026amp; value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== \u0026#39;production\u0026#39; \u0026amp;\u0026amp; customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter \u0026amp;\u0026amp; !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow \u0026amp;\u0026amp; observe(newVal) dep.notify() } }) } Dep类\nlet uid = 0 /** * A dep is an observable that can have multiple * directives subscribing to it. * dep是可观察的，可以有多个指令订阅它。 */ export default class Dep { static target: ?Watcher; id: number; subs: Array\u0026lt;Watcher\u0026gt;; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first 首先维护订阅者列表 const subs = this.subs.slice() if (process.env.NODE_ENV !== \u0026#39;production\u0026#39; \u0026amp;\u0026amp; !config.async) { // subs aren\u0026#39;t sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) =\u0026gt; a.id - b.id) } for (let i = 0, l = subs.length; i \u0026lt; l; i++) { subs[i].update() } } } // The current target watcher being evaluated. 当前目标观察者正在被计算 // This is globally unique because only one watcher // can be evaluated at a time. 这是全局唯一的，因为一次只能计算一个观察者 Dep.target = null const targetStack = [] export function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target } export function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1] } 计算属性初始化\ninitComputed(vm: Component, computed: Object)\nfunction initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === \u0026#39;function\u0026#39; ? userDef : userDef.get if (process.env.NODE_ENV !== \u0026#39;production\u0026#39; \u0026amp;\u0026amp; getter == null) { warn( `Getter is missing for computed property \u0026#34;${key}\u0026#34;.`, vm ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. 组件定义的计算属性已经在组件原型上定义。我们只需要在这里定义实例化时定义的计算属性。 if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== \u0026#39;production\u0026#39;) { if (key in vm.$data) { warn(`The computed property \u0026#34;${key}\u0026#34; is already defined in data.`, vm) } else if (vm.$options.props \u0026amp;\u0026amp; key in vm.$options.props) { warn(`The computed property \u0026#34;${key}\u0026#34; is already defined as a prop.`, vm) } } } } ndz-vue源码\n","permalink":"https://gwj-git.github.io/%E6%96%87%E7%AB%A0/vue%E6%BA%90%E7%A0%812/","summary":"#响应式 initState方法与 响应式 initState方法 props=\u0026gt;methods=\u0026gt;data=\u0026gt;computed=\u0026gt;watch export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options // 初始化Props 并添加响应式，代理到vm实例上，t","title":"vue源码(2)"},{"content":"[todo] 看完后需要再梳理一遍思路 [toc]\nvue2 初始化 src目录\n├─compiler // 模板编译相关文件，将 template 编译为 render 函数 │ ├─codegen // 把AST(抽象语法树)转换为Render函数 │ ├─directives // 生成Render函数之前需要处理的东西 │ └─parser // 模板编译成AST ├─core // Vue核心代码，包括了组件、全局API、Vue实例化、响应式原理、vdom(虚拟DOM)、等等。 │ ├─components │ ├─global-api │ ├─instance │ ├─observer // 响应式核心 │ ├─util │ └─vdom // 虚拟DOM相关的代码 ├─platforms ├─server // 服务端渲染相关内容（ssr） ├─sfc // 转换单文件组件（*.vue） └─shared // 全局共享的方法和常量 构造函数 调试查找构造函数 new Vue实例时，先调用Vue的构造函数\nVue构造函数\n// src/core/instance/index.js // 构造函数 function Vue (options) { // ... this._init(options) } initMixin(Vue) // 初始化混入 stateMixin(Vue) // state混入 eventsMixin(Vue) // events混入 lifecycleMixin(Vue) // 生命周期混入 renderMixin(Vue) //render混入 export default Vue 入口文件查找构造函数 先前自己查看源码时是通过调试直接找到构造函数再一步一步看的，后面梳理过程中参考一些大佬博客，通过打包入口文件一层一层查看，学到了一些新内容\n// package.json \u0026#34;scripts\u0026#34;: { \u0026#34;dev\u0026#34;: \u0026#34;rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap\u0026#34; ...... } 打包工具：rollup, 配置文件：scripts/config.js，环境变量中TARGET值为：\nscripts/config.js中target 为 web-full-dev\n// Runtime+compiler development build (Browser) \u0026#39;web-full-dev\u0026#39;: { entry: resolve(\u0026#39;web/entry-runtime-with-compiler.js\u0026#39;), dest: resolve(\u0026#39;dist/vue.js\u0026#39;), format: \u0026#39;umd\u0026#39;, env: \u0026#39;development\u0026#39;, alias: { he: \u0026#39;./entity-decoder\u0026#39; }, banner } // 最终导出config opts为\u0026#39;web-full-dev\u0026#39; const config = { input: opts.entry, external: opts.external, plugins: [ flow(), alias(Object.assign({}, aliases, opts.alias)) ].concat(opts.plugins || []), output: { file: opts.dest, format: opts.format, banner: opts.banner, name: opts.moduleName || \u0026#39;Vue\u0026#39; }, onwarn: (msg, warn) =\u0026gt; { if (!/Circular/.test(msg)) { warn(msg) } } } resolve方法\nconst aliases = require(\u0026#39;./alias\u0026#39;) // resolve方法底层依赖的是path.resolve()方法 const resolve = p =\u0026gt; { const base = p.split(\u0026#39;/\u0026#39;)[0] // aliases[base] =\u0026gt; src/platforms/web if (aliases[base]) { return path.resolve(aliases[base], p.slice(base.length + 1)) } else { return path.resolve(__dirname, \u0026#39;../\u0026#39;, p) } } 打包入口文件为src/platforms/web/entry-runtime-with-compiler.js\n入口文件\nimport config from \u0026#39;core/config\u0026#39; import { warn, cached } from \u0026#39;core/util/index\u0026#39; import { mark, measure } from \u0026#39;core/util/perf\u0026#39; import Vue from \u0026#39;./runtime/index\u0026#39; import { query } from \u0026#39;./util/index\u0026#39; import { compileToFunctions } from \u0026#39;./compiler/index\u0026#39; import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from \u0026#39;./util/compat\u0026#39; // 获取id对应的元素 const idToTemplate = cached(id =\u0026gt; { // 获取元素 const el = query(id) return el \u0026amp;\u0026amp; el.innerHTML }) // $mount const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el \u0026amp;\u0026amp; query(el) const options = this.$options // resolve template/el and convert to render function // 解析 template/el 并转换为渲染函数 // 不存在render选项 if (!options.render) { let template = options.template // template存在 if (template) { if (typeof template === \u0026#39;string\u0026#39;) { if (template.charAt(0) === \u0026#39;#\u0026#39;) { // 如果是选择器 template = idToTemplate(template) } } else if (template.nodeType) { template = template.innerHTML } else { return this } } else if (el) { // el 存在 template = getOuterHTML(el) } if (template) { const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== \u0026#39;production\u0026#39;, shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns } } return mount.call(this, el, hydrating) } // 同时设置了 el、template、render的时候，优先级的判断为：render \u0026gt; template \u0026gt; el /** * Get outerHTML of elements, taking care * of SVG elements in IE as well. */ function getOuterHTML (el: Element): string { if (el.outerHTML) { return el.outerHTML } else { const container = document.createElement(\u0026#39;div\u0026#39;) container.appendChild(el.cloneNode(true)) return container.innerHTML } } Vue.compile = compileToFunctions export default Vue 入口文件中的Vue来自./runtime/index\n// src/platforms/web/runtime/index.js /* @flow */ import Vue from \u0026#39;core/index\u0026#39; import config from \u0026#39;core/config\u0026#39; import { extend, noop } from \u0026#39;shared/util\u0026#39; import { mountComponent } from \u0026#39;core/instance/lifecycle\u0026#39; import { devtools, inBrowser } from \u0026#39;core/util/index\u0026#39; import { query, mustUseProp, isReservedTag, isReservedAttr, getTagNamespace, isUnknownElement } from \u0026#39;web/util/index\u0026#39; import { patch } from \u0026#39;./patch\u0026#39; import platformDirectives from \u0026#39;./directives/index\u0026#39; import platformComponents from \u0026#39;./components/index\u0026#39; // install platform specific utils 安装平台特有的utils Vue.config.mustUseProp = mustUseProp Vue.config.isReservedTag = isReservedTag Vue.config.isReservedAttr = isReservedAttr Vue.config.getTagNamespace = getTagNamespace Vue.config.isUnknownElement = isUnknownElement // install platform runtime directives \u0026amp; components 安装平台运行时指令和组件 extend(Vue.options.directives, platformDirectives) extend(Vue.options.components, platformComponents) // install platform patch function 补丁 // 主要作用：虚拟dom 转化为真实的dom（vdom =\u0026gt; dom) Vue.prototype.__patch__ = inBrowser ? patch : noop // public mount method // 实现了 $mount 方法：调用mountComponent()方法 // $mount最终目的：把虚拟dom 转化为真实的dom，并且追加到宿主元素中去（vdom =\u0026gt; dom =\u0026gt; append） Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el \u0026amp;\u0026amp; inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) } export default Vue Vue来自core/index\n// src/core/index.js import Vue from \u0026#39;./instance/index\u0026#39; import { initGlobalAPI } from \u0026#39;./global-api/index\u0026#39; import { isServerRendering } from \u0026#39;core/util/env\u0026#39; import { FunctionalRenderContext } from \u0026#39;core/vdom/create-functional-component\u0026#39; // 初始化全局api，set/delete/nextTick/observable等 initGlobalAPI(Vue) Object.defineProperty(Vue.prototype, \u0026#39;$isServer\u0026#39;, { get: isServerRendering }) Object.defineProperty(Vue.prototype, \u0026#39;$ssrContext\u0026#39;, { get () { /* istanbul ignore next */ return this.$vnode \u0026amp;\u0026amp; this.$vnode.ssrContext } }) // expose FunctionalRenderContext for ssr runtime helper installation Object.defineProperty(Vue, \u0026#39;FunctionalRenderContext\u0026#39;, { value: FunctionalRenderContext }) Vue.version = \u0026#39;__VERSION__\u0026#39; export default Vue 主要是initGlobalAPI,初始化全局api\ninitGlobalAPI方法\nimport config from \u0026#39;../config\u0026#39; import { initUse } from \u0026#39;./use\u0026#39; import { initMixin } from \u0026#39;./mixin\u0026#39; import { initExtend } from \u0026#39;./extend\u0026#39; import { initAssetRegisters } from \u0026#39;./assets\u0026#39; import { set, del } from \u0026#39;../observer/index\u0026#39; import { ASSET_TYPES } from \u0026#39;shared/constants\u0026#39; import builtInComponents from \u0026#39;../components/index\u0026#39; import { observe } from \u0026#39;core/observer/index\u0026#39; import { warn, extend, nextTick, mergeOptions, defineReactive } from \u0026#39;../util/index\u0026#39; export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} configDef.get = () =\u0026gt; config if (process.env.NODE_ENV !== \u0026#39;production\u0026#39;) { configDef.set = () =\u0026gt; { warn( \u0026#39;Do not replace the Vue.config object, set individual fields instead.\u0026#39; ) } } Object.defineProperty(Vue, \u0026#39;config\u0026#39;, configDef) // exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. Vue.util = { warn, extend, mergeOptions, defineReactive } //vue的set/delete/nextTick方法 Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 2.6 explicit observable API Vue.observable = \u0026lt;T\u0026gt;(obj: T): T =\u0026gt; { observe(obj) return obj } Vue.options = Object.create(null) ASSET_TYPES.forEach(type =\u0026gt; { Vue.options[type + \u0026#39;s\u0026#39;] = Object.create(null) }) // this is used to identify the \u0026#34;base\u0026#34; constructor to extend all plain-object // components with in Weex\u0026#39;s multi-instance scenarios. Vue.options._base = Vue extend(Vue.options.components, builtInComponents) initUse(Vue) // vue.use initMixin(Vue) // Vue.mixin initExtend(Vue) // Vue.extend initAssetRegisters(Vue) } core/index的Vue来自./instance/index，即构造函数所在\nimprot Vue时，会调用initMixin(Vue),stateMixin(Vue),eventsMixin(Vue),lifecycleMixin(Vue),renderMixin(Vue)这些方法，给Vue的原型添加属性及方法\ninitMixin为Vue的原型添加_init方法\nstateMixin 通过Object.defineProperty()劫持vue原型的$data和$props,为Vue的原型添加$set ,$delete和$watch\neventsMixin为Vue的原型添加$on ,$once,$off ,$emit\nlifecycleMixin为Vue的原型添加_update,$forceUpdate,$destroy\nrenderMixin为Vue的原型添加render相关方法及$nextTick,_render,\ninitMixin initMixin方法\nlet uid = 0 export function initMixin (Vue: Class\u0026lt;Component\u0026gt;) { Vue.prototype._init = function (options?: Object) { // 实例对象 const vm: Component = this // uid vm._uid = uid++ // a flag to avoid this being observed vm._isVue = true // 合并options(传入options和构造器本身options) 初始化vm.$options if (options \u0026amp;\u0026amp; options._isComponent) { // 子组件 // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. // 优化内部组件实例化，因为动态选项合并非常缓慢，并且没有一个内部组件选项需要特殊处理。 initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } // vm._renderProxy // expose real self vm._self = vm // 初始化render，生命周期状态等及调用生命周期 // 初始化生命周期状态 vm._isMounted vm._isDestroyed 等 // 组件关系属性的初始化 定义：$root、$parent、$children、$refs initLifecycle(vm) // 初始化 vm._events，vm._hasHookEvent // 存在父监听，添加到该实例上 initEvents(vm) // 初始化 vm._vnode,vm._staticTrees ,vm.$slots,vm.$scopedSlots,vm.$createElement等 // 初始化render渲染所需的slots、渲染函数等。其实就两件事1、插槽的处理、2、$createElement 也就是 render 函数中的 h 的声明 initRender(vm) // 调用beforeCreate生命周期 callHook(vm, \u0026#39;beforeCreate\u0026#39;) // 初始化Injection 先注入上级提供的Provide数据 initInjections(vm) // resolve injections before data/props // 初始化 props=\u0026gt;methods=\u0026gt;data=\u0026gt;computed=\u0026gt;watch，响应式处理 initState(vm) // 初始化Provide initProvide(vm) // resolve provide after data/props //created初始化完成 callHook(vm, \u0026#39;created\u0026#39;) if (vm.$options.el) { vm.$mount(vm.$options.el) } } } 合并options方法处 mergeOptions\nif (options \u0026amp;\u0026amp; options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } initInternalComponent(vm, options)\n对于组件（options._isComponent为true）调用\ninitInternalComponent(vm, options)传入实例和构造函数的options\nexport function initInternalComponent (vm: Component, options: InternalComponentOptions) { const opts = vm.$options = Object.create(vm.constructor.options) // doing this because it\u0026#39;s faster than dynamic enumeration. const parentVnode = options._parentVnode opts.parent = options.parent opts._parentVnode = parentVnode const vnodeComponentOptions = parentVnode.componentOptions // 父传向子的props propsData opts.propsData = vnodeComponentOptions.propsData opts._parentListeners = vnodeComponentOptions.listeners opts._renderChildren = vnodeComponentOptions.children opts._componentTag = vnodeComponentOptions.tag if (options.render) { opts.render = options.render opts.staticRenderFns = options.staticRenderFns } } 以vm.constructor.options为原型，在vm上挂载$options,并添加父级实例(parent)、Vnode(_parentVnode)、Listeners(_parentListeners)，props(propsData)，_renderChildren，_componentTag,option.render存在，添加render，staticRenderFns\nfunction mergeOptions (parent: Object,child: Object,vm?: Component): Object\n非组件情况下mergeOptions 传入父级options,子级，options及vm实例 resolveConstructorOptions 递归返回 vm.constructor.options\nvm.constructor.super存在，调用 resolveConstructorOptions(vm.constructor.super)，进行一些判断赋值\nexport function resolveConstructorOptions (Ctor: Class\u0026lt;Component\u0026gt;) { let options = Ctor.options if (Ctor.super) { const superOptions = resolveConstructorOptions(Ctor.super) const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // super option changed, // need to resolve new options. Ctor.superOptions = superOptions // check if there are any late-modified/attached options (#4976) const modifiedOptions = resolveModifiedOptions(Ctor) // update base extend options if (modifiedOptions) { extend(Ctor.extendOptions, modifiedOptions) } options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options } mergeOptions (parent: Object,child: Object,vm?: Component)\nparent=\u0026gt; vm.constructor.options child=\u0026gt;options\n规范props,inject,Directive,合并extends，mixins的options\noption上部分属性(components,directives,filters,_base),以parent--(vm.constructor.options)为原型\noption上部分属性（el,props,template）,值为child[key]--(options)\nexport function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object { if (process.env.NODE_ENV !== \u0026#39;production\u0026#39;) { checkComponents(child) } if (typeof child === \u0026#39;function\u0026#39;) { child = child.options } // 规范数组、对象形式的prop 均转换成对象形式(name:{type: null}) normalizeProps(child, vm) // 规范inject normalizeInject(child, vm) // 规范自定义指令 normalizeDirectives(child) // Apply extends and mixins on the child options, // but only if it is a raw options object that isn\u0026#39;t // the result of another mergeOptions call. // Only merged options has the _base property. if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i \u0026lt; l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } } const options = {} let key for (key in parent) { mergeField(key) } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key) } } function mergeField (key) { const strat = strats[key] || defaultStrat options[key] = strat(parent[key], child[key], vm, key) } return options } ","permalink":"https://gwj-git.github.io/%E6%96%87%E7%AB%A0/vue%E6%BA%90%E7%A0%811/","summary":"[todo] 看完后需要再梳理一遍思路 [toc] vue2 初始化 src目录 ├─compiler // 模板编译相关文件，将 template 编译为 render 函数 │ ├─codegen // 把AST(抽象语","title":"vue源码(1)"},{"content":"UDP和TCP区别 TCP是面向链接的，而UDP是面向无连接的。 TCP仅支持单播传输，UDP 提供了单播，多播，广播的功能。 TCP的三次握手保证了连接的可靠性; UDP是无连接的、不可靠的一种数据传输协议，首先不可靠性体现在无连接上，通信都不需要建立连接，对接收到的数据也不发送确认信号，发送端不知道数据是否会正确接收。 UDP的头部开销比TCP的更小，数据传输速率更高，实时性更好。 UDP TCP 是否连接 无连接 面向连接 是否可靠 不可靠传输，不使用流量控制和拥塞控制 可靠传输，使用流量控制和拥塞控制 连接对象个数 支持一对一，一对多，多对一和多对多交互通信 只能是一对一通信 传输方式 面向报文 面向字节流 首部开销 首部开销小，仅8字节 首部最小20字节，最大60字节 适用场景 适用于实时应用（IP电话、视频会议、直播等） 适用于要求可靠传输的应用，例如文件传输 UDP的应用场景：即时通信。面向数据报方式；网络数据大多为短消息；拥有大量客户端；对数据安全性无特殊要求；网络负担重但对响应速度要求高的场景。eg: IP电话、实时视频会议等。\nTCP的应用场景：对数据准确性要求高，速度可以相对较慢的。eg: 文件传输、邮件的发送与接收等。\nTCP报文 （1）序号：seq序号，占32位，用来标识从TCP源端向目的端发送的字节流，发起方发送数据时对此进行标记。 （2）确认序号：ack序号，占32位，只有ACK标志位为1时，确认序号字段才有效，Ack=Seq+1。 （3）标志位：共6个，即URG、ACK、PSH、RST、SYN、FIN等，具体含义如下： （A）URG：紧急指针（urgent pointer）有效。 （B）ACK：确认序号有效。 （C）PSH：接收方应该尽快将这个报文交给应用层。 （D）RST：重置连接。 （E）SYN：发起一个新连接。 （F）FIN：释放一个连接。\n三次握手 主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。\n刚开始客户端处于 Closed 的状态，服务端处于 Listen 状态。 进行三次握手：\n第一次握手：客户端给服务端发一个 SYN 报文，并指明客户端的初始化序列号 ISN(c)。此时客户端处于 SYN_SEND 状态。\n标志位SYN=1，初始序号seq=x，SYN=1的报文段不能携带数据，但要消耗掉一个序号。\n第二次握手：服务器收到客户端的 SYN 报文之后，会以自己的 SYN 报文作为应答，并且也是指定了自己的初始化序列号 ISN(s)。同时会把客户端的 ISN + 1 作为ack (确认序号)的值，表示自己已经收到了客户端的 SYN，此时服务器处于 SYN_RCVD 的状态。\n在确认报文段中SYN=1，ACK=1，确认号ack=x+1，初始序号seq=y。\n第三次握手：客户端收到 SYN 报文之后，会发送一个 ACK 报文，当然，也是一样把服务器的 ISN + 1 作为 ack 的值，表示已经收到了服务端的 SYN 报文，此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后，也处于 ESTABLISHED 状态，此时，双方已建立起了连接。\n确认报文段ACK=1，确认号ack=y+1，序号seq=x+1（初始为seq=x，第二个报文段所以要+1），ACK报文段可以携带数据，不携带数据则不消耗序号。\n四次挥手 刚开始双方都处于 ESTABLISHED 状态，假如是客户端先发起关闭请求。四次挥手的过程如下：\n第一次挥手：客户端发送一个 FIN 报文，报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。 即发出连接释放报文段（FIN=1，序号seq=p），并停止再发送数据，主动关闭TCP连接，进入FIN_WAIT1（终止等待1）状态，等待服务端的确认。 第二次挥手：服务端收到 FIN 之后，会发送 ACK 报文，且把客户端的序列号值 +1 作为 ACK 报文的序列号值，表明已经收到客户端的报文了，此时服务端处于 CLOSE_WAIT 状态。 即服务端收到连接释放报文段后即发出确认报文段（ACK=1，确认号ack=p+1），服务端进入CLOSE_WAIT（关闭等待）状态，此时的TCP处于半关闭状态，客户端到服务端的连接释放。客户端收到服务端的确认后，进入FIN_WAIT2（终止等待2）状态，等待服务端发出的连接释放报文段。 第三次挥手：如果服务端也想断开连接了，和客户端的第一次挥手一样，发给 FIN 报文，且指定一个序列号。此时服务端处于 LAST_ACK 的状态。 即服务端没有要向客户端发出的数据，服务端发出连接释放报文段（FIN=1，ACK=1，序号seq=q，确认号ack=p+1），服务端进入LAST_ACK（最后确认）状态，等待客户端的确认。 第四次挥手：客户端收到 FIN 之后，一样发送一个 ACK 报文作为应答，且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值，此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态，服务端收到 ACK 报文之后，就处于关闭连接了，处于 CLOSED 状态。 即客户端收到服务端的连接释放报文段后，对此发出确认报文段（ACK=1，seq=p+1，ack=q+1），客户端进入TIME_WAIT（时间等待）状态。此时TCP未释放掉，需要经过时间等待计时器设置的时间2MSL后，客户端才进入CLOSED状态。 （ISN）是固定的吗 三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number)，以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的，攻击者很容易猜出后续的确认号，因此 ISN 是动态生成的。\n三次握手过程中可以携带数据吗？ 第一次握手不可以放数据，其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话，此时客户端已经处于 ESTABLISHED 状态。对于客户端来说，已经建立起连接，并且也已经知道服务器的接收、发送能力是正常的了，所以能携带数据。\n挥手为什么需要四次？ 当服务端收到客户端的SYN连接请求报文后，可以直接发送SYN+ACK报文。其中ACK报文是用来应答的，SYN报文是用来同步的。但是关闭连接时，当服务端收到FIN报文时，很可能并不会立即关闭链接，所以只能先回复一个ACK报文，告诉客户端，\u0026ldquo;你发的FIN报文我收到了\u0026rdquo;。只有等到我服务端所有的报文都发送完了，我才能发送FIN报文，因此不能一起发送。故需要四次挥手。\n四次挥手释放连接时，等待2MSL的意义? 保证客户端发送的最后一个ACK报文段能够到达服务端。\n防止“已失效的连接请求报文段”出现在本连接中。\n","permalink":"https://gwj-git.github.io/%E6%96%87%E7%AB%A0/udp%E5%92%8Ctcp/","summary":"UDP和TCP区别 TCP是面向链接的，而UDP是面向无连接的。 TCP仅支持单播传输，UDP 提供了单播，多播，广播的功能。 TCP的三次握手保证","title":"UDP和TCP"},{"content":"模块化 早期 普通函数 function fn1(){ //... } function fn2(){ //... } function fn3() { fn1() fn2() } 模块成员之间看不出直接关系,后期维护困难，无法解决命名冲突问题\n命名空间 var myModule = { name: \u0026#34;isboyjc\u0026#34;, getName: function (){ console.log(this.name) } } // 使用 myModule.getName() 内部状态可以被外部更改\n立即执行函数（IIFE） // module.js文件 (function(window) { let data = \u0026#39;www.baidu.com\u0026#39; //操作数据的函数 function foo() { //用于暴露有函数 console.log(`foo() ${data}`) } function bar() { //用于暴露有函数 console.log(`bar() ${data}`) otherFun() //内部调用 } function otherFun() { //内部私有的函数 console.log(\u0026#39;otherFun()\u0026#39;) } //暴露行为 window.myModule = { foo, bar } //ES6写法 })(window) ---------------- // otherModule.js模块文件 var otherModule = (function(){ return { a: 1, b: 2 } })() // myModule.js模块文件 - 依赖 otherModule 模块 var myModule = (function(other) { var name = \u0026#39;isboyjc\u0026#39; function getName() { console.log(name) console.log(other.a, other.b) } return { getName } })(otherModule) 依赖注入 依赖注册器\nlet injector = { dependencies: {}, // 保存依赖 register: function(key, value) { this.dependencies[key] = value; }, // 注册依赖 // 绑定依赖 resolve: function(deps, func, scope) { var args = []; for(var i = 0; i \u0026lt; deps.length, d = deps[i]; i++) { if(this.dependencies[d]) { // 存在此依赖 args.push(this.dependencies[d]); } else { // 不存在 throw new Error(\u0026#39;不存在依赖：\u0026#39; + d); } } return function() { func.apply(scope || {}, args.concat(Array.prototype.slice.call(arguments, 0))); } } } 使用\n// 添加 injector.register(\u0026#39;fnA\u0026#39;, fnA) injector.register(\u0026#39;fnB\u0026#39;, fnB) // 注入 (injector.resolve([\u0026#39;fnA\u0026#39;, \u0026#39;fnB\u0026#39;], function(fnA, fnB){ let a = fnA() let b = fnB() console.log(a, b) }))() 模块化规范 commonjs 每个文件就是一个模块，有自己的作用域。在一个文件里面定义的变量、函数、类，都是私有的，对其他文件不可见,外部想要调用，必须使用 module.exports 主动暴露，而在另一个文件中引用则直接使用 require(path) 即可\nCommonJS 规范适用于服务端(nodejs),同步加载\n特点：\n所有代码都运行在模块作用域，不会污染全局作用域。 模块可以多次加载，但是只会在第一次加载时运行一次，然后运行结果就被缓存了，以后再加载，就直接读取缓存结果。要想让模块再次运行，必须清除缓存。 模块加载的顺序，按照其在代码中出现的顺序。 加载某个模块，其实是加载该模块的module.exports属性。\n// example.js var x = 5; var addX = function (value) { return value + x; }; module.exports.x = x; module.exports.addX = addX; --------------------- var example = require(\u0026#39;./example.js\u0026#39;);//如果参数字符串以“./”开头，则表示加载的是一个位于相对路径 console.log(example.x); // 5 console.log(example.addX(1)); // 6 console.log(example.x); // 5 CommonJS模块的加载机制是，输入的是被输出的值的拷贝\nAMD require.js依赖前置\n异步加载模块，一般用于浏览器\ndefine(id?: String, dependencies?: String[], factory: Function|Object) id 即模块的名字，字符串，可选 dependencies 指定了所要依赖的模块列表，它是一个数组，也是可选的参数，每个依赖的模块的输出将作为参数一次传入 factory 中。如果没有指定 dependencies，那么它的默认值是 [\u0026quot;require\u0026quot;, \u0026quot;exports\u0026quot;, \u0026quot;module\u0026quot;] factory 包裹了模块的具体实现，可为函数或对象，如果是函数，返回值就是模块的输出接口或者值 // 定义依赖 myModule，该模块依赖 JQ 模块 define(\u0026#39;myModule\u0026#39;, [\u0026#39;jquery\u0026#39;], function($) { // $ 是 jquery 模块的输出 $(\u0026#39;body\u0026#39;).text(\u0026#39;isboyjc\u0026#39;) }) // 引入依赖 require([\u0026#39;myModule\u0026#39;], function(myModule) { // todo... }) CMD Sea.js 依赖就近\nCMD规范专门用于浏览器端，模块的加载是异步的，模块使用时才会加载执行。整合了CommonJS和AMD规范的特点\ndefine(function(require, exports, module) { var a = require(\u0026#39;./a\u0026#39;) a.doSomething() // 依赖就近原则：依赖就近书写，什么时候用到什么时候引入 var b = require(\u0026#39;./b\u0026#39;) b.doSomething() }) define(function(require, exports, module) { // 同步引入 var a = require(\u0026#39;./a\u0026#39;) // 异步引入 require.async(\u0026#39;./b\u0026#39;, function (b) { }) // 条件引入 if (status) { var c = requie(\u0026#39;./c\u0026#39;) } // 暴露模块 exports.aaa = \u0026#39;hahaha\u0026#39; }) 对于依赖的模块，AMD 是提前执行，CMD 是延迟执行\nUMD Universal Module Definition\n通用模块定义\n((root, factory) =\u0026gt; { if (typeof define === \u0026#39;function\u0026#39; \u0026amp;\u0026amp; define.amd) { // AMD define(factory); } else if (typeof exports === \u0026#39;object\u0026#39;) { // CommonJS module.exports = factory(); } else if (typeof define === \u0026#39;function\u0026#39; \u0026amp;\u0026amp; define.cmd){ // CMD define(function(require, exports, module) { module.exports = factory() }) } else { // 都不是 root.umdModule = factory(); } })(this, () =\u0026gt; { console.log(\u0026#39;我是UMD\u0026#39;) // todo... }); ES module // 模块定义 add.js export function add(a, b) { return a + b; } // 模块使用 main.js import { add } from \u0026#34;./add.js\u0026#34;; console.log(add(1, 2)); // 3 参考文章 模块化：isboyjc\n[模块化：浪里行舟](\n","permalink":"https://gwj-git.github.io/%E6%96%87%E7%AB%A0/%E6%A8%A1%E5%9D%97%E5%8C%96%E5%AD%A6%E4%B9%A0/","summary":"模块化 早期 普通函数 function fn1(){ //... } function fn2(){ //... } function fn3() { fn1() fn2() } 模块成员之间看不出直接关系,后期维护困难，无法解决命名冲突问题 命名空间 var myModule = { name: \u0026#34;isboyjc\u0026#34;, getName: function (){ console.log(this.name) } }","title":"模块化学习"},{"content":"npm 生态 npm,cnpm,yarn,pnpm node的包管理工具,用于node插件管理（包括安装、卸载、管理依赖等）\n//递归依赖 ├── node_modules │ ├── A@1.0.0 │ │ └── node_modules │ │ │ └── D@1.0.0 │ ├── B@1.0.0 │ │ └── node_modules │ │ │ └── D@2.0.0 │ └── C@1.0.0 │ │ └── node_modules │ │ │ └── D@2.0.0 -------------------- // 扁平化依赖 ├── node_modules │ ├── A@1.0.0 │ │ └── node_modules │ │ │ └── D@1.0.0 │ ├── B@1.0.0 │ ├── C@1.0.0 │ └── D@2.0.0 npm ,yarn npm为node自带，npm1,2采用递归依赖，npm3开始采用扁平化依赖\nnpm原有问题：\n安装速度慢 可能出现相同模块大量重复冗余 不支持离线安装 最新版的npm和yarn安装速度和使用体验并没有多大的差距，yarn稍好一些\nnpm3+和yarn\n依赖结构的不确定性。 扁平化算法本身的复杂性很高，耗时较长。 项目中仍然可以非法访问没有声明过依赖的包(幽灵依赖) 可能会导致有大量的依赖的被重复安装 cnpm 淘宝镜像 国内下载速度快 构建私人npm cli\npnpm 网状 + 平铺的node_modules结构\nstore（基于内容寻址CAS,Virtual store）+link(symbolic link)\n速度快 高效利用磁盘空间。利用硬链接和符号链接来避免复制所有本地缓存源文件。 安全性高。避免了非法访问 ni npm i in a yarn project, again? F**k!\nni - use the right package manager // 使用正确的包管理器\nni假设使用锁文件(而且您应该使用)\n在运行之前，它将检测您yarn.lock / pnpm-lock.yaml / package-lock.json / bun.lockb来了解当前的包管理器(如果指定，则在 packages.json 中指定 packageManager 字段) ，并运行相应的命令。\ninstall ni # npm install # yarn install # pnpm install # bun install --------------------------------------- ni vite # npm i vite # yarn add vite # pnpm add vite # bun add vite -------------------------------------- ni @types/node -D # npm i @types/node -D # yarn add @types/node -D # pnpm add -D @types/node # bun add -d @types/node ------------------------------------ ni --frozen # npm ci # yarn install --frozen-lockfile (Yarn 1) # yarn install --immutable (Yarn Berry) # pnpm install --frozen-lockfile # bun install --no-save ----------------------------------- ni -g eslint # npm i -g eslint # yarn global add eslint (Yarn 1) # pnpm add -g eslint # bun add -g eslint # this uses default agent, regardless your current working directory run nr dev --port=3000 # npm run dev -- --port=3000 # yarn run dev --port=3000 # pnpm run dev -- --port=3000 # pnpm dev pnpm可以别名运行 # bun run dev --port=3000 ---------------- nr # interactively select the script to run # supports https://www.npmjs.com/package/npm-scripts-info convention ---------------- nr - # rerun the last command 重新执行最后一次执行的命令 execute nx vitest # (not available for bun) # npx vitest # yarn dlx vitest # pnpm dlx vitest upgrade nu # (not available for bun) # npm upgrade # yarn upgrade (Yarn 1) # yarn up (Yarn Berry) # pnpm update ----------------------- nu -i # (not available for npm \u0026amp; bun) # yarn upgrade-interactive (Yarn 1) # yarn up -i (Yarn Berry) # pnpm update -i uninstall nun webpack # npm uninstall webpack # yarn remove webpack # pnpm remove webpack # bun remove webpack -------------------- nun -g silent # npm uninstall -g silent # yarn global remove silent # pnpm remove -g silent # bun remove -g silent clean install nci # npm ci # yarn install --frozen-lockfile # pnpm install --frozen-lockfile # bun install --no-save 如果没有相应的node管理器，这个命令将在全局范围内安装它\nagent alias na # npm # yarn # pnpm # bun ------------------------ na run foo # npm run foo # yarn run foo # pnpm run foo # bun run foo Change Directory ni -C packages/foo vite nr -C playground dev Config ; ~/.nirc ; fallback when no lock found defaultAgent=npm # default \u0026#34;prompt\u0026#34; ; for global installs globalAgent=npm ---------------------- # ~/.bashrc # custom configuration file path export NI_CONFIG_FILE=\u0026#34;$HOME/.config/ni/nirc\u0026#34; package.json { \u0026#34;name\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;2.6.0\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;author\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;license\u0026#34;: \u0026#34;Apache-2.0\u0026#34;, \u0026#34;scripts\u0026#34;: { \u0026#34;dev\u0026#34;: \u0026#34;vue-cli-service serve\u0026#34;, \u0026#34;build:prod\u0026#34;: \u0026#34;vue-cli-service build\u0026#34;, \u0026#34;build:stage\u0026#34;: \u0026#34;vue-cli-service build --mode staging\u0026#34;, \u0026#34;preview\u0026#34;: \u0026#34;node build/index.js --preview\u0026#34;, \u0026#34;lint\u0026#34;: \u0026#34;eslint --ext .js,.vue src\u0026#34;, \u0026#34;test:unit\u0026#34;: \u0026#34;jest --clearCache \u0026amp;\u0026amp; vue-cli-service test:unit\u0026#34;, \u0026#34;svgo\u0026#34;: \u0026#34;svgo -f src/assets/icons/svg --config=src/assets/icons/svgo.yml\u0026#34;, \u0026#34;new\u0026#34;: \u0026#34;plop\u0026#34; }, \u0026#34;husky\u0026#34;: { \u0026#34;hooks\u0026#34;: { \u0026#34;pre-commit\u0026#34;: \u0026#34;lint-staged\u0026#34; } }, \u0026#34;lint-staged\u0026#34;: { \u0026#34;src/**/*.{js,vue}\u0026#34;: [ \u0026#34;eslint --fix\u0026#34;, \u0026#34;git add\u0026#34; ] }, \u0026#34;repository\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;git\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;bugs\u0026#34;: { \u0026#34;url\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;dependencies\u0026#34;: { \u0026#34;@riophae/vue-treeselect\u0026#34;: \u0026#34;^0.4.0\u0026#34;, \u0026#34;axios\u0026#34;: \u0026#34;^0.21.1\u0026#34;, \u0026#34;clipboard\u0026#34;: \u0026#34;2.0.4\u0026#34;, \u0026#34;codemirror\u0026#34;: \u0026#34;^5.49.2\u0026#34;, \u0026#34;core-js\u0026#34;: \u0026#34;^2.6.12\u0026#34;, \u0026#34;echarts\u0026#34;: \u0026#34;^4.2.1\u0026#34;, \u0026#34;echarts-wordcloud\u0026#34;: \u0026#34;^1.1.3\u0026#34;, \u0026#34;element-ui\u0026#34;: \u0026#34;^2.15.8\u0026#34;, \u0026#34;file-saver\u0026#34;: \u0026#34;1.3.8\u0026#34;, \u0026#34;fuse.js\u0026#34;: \u0026#34;3.4.4\u0026#34;, \u0026#34;js-beautify\u0026#34;: \u0026#34;^1.10.2\u0026#34;, \u0026#34;js-cookie\u0026#34;: \u0026#34;2.2.0\u0026#34;, \u0026#34;jsencrypt\u0026#34;: \u0026#34;^3.0.0-rc.1\u0026#34;, \u0026#34;jszip\u0026#34;: \u0026#34;^3.7.1\u0026#34;, \u0026#34;mavon-editor\u0026#34;: \u0026#34;^2.9.1\u0026#34;, \u0026#34;normalize.css\u0026#34;: \u0026#34;7.0.0\u0026#34;, \u0026#34;nprogress\u0026#34;: \u0026#34;0.2.0\u0026#34;, \u0026#34;path-to-regexp\u0026#34;: \u0026#34;2.4.0\u0026#34;, \u0026#34;qs\u0026#34;: \u0026#34;^6.10.1\u0026#34;, \u0026#34;screenfull\u0026#34;: \u0026#34;4.2.0\u0026#34;, \u0026#34;sortablejs\u0026#34;: \u0026#34;1.8.4\u0026#34;, \u0026#34;vue\u0026#34;: \u0026#34;^2.6.14\u0026#34;, \u0026#34;vue-count-to\u0026#34;: \u0026#34;^1.0.13\u0026#34;, \u0026#34;vue-cropper\u0026#34;: \u0026#34;0.4.9\u0026#34;, \u0026#34;vue-echarts\u0026#34;: \u0026#34;^5.0.0-beta.0\u0026#34;, \u0026#34;vue-image-crop-upload\u0026#34;: \u0026#34;^2.5.0\u0026#34;, \u0026#34;vue-router\u0026#34;: \u0026#34;3.0.2\u0026#34;, \u0026#34;vue-splitpane\u0026#34;: \u0026#34;1.0.4\u0026#34;, \u0026#34;vuedraggable\u0026#34;: \u0026#34;2.20.0\u0026#34;, \u0026#34;vuex\u0026#34;: \u0026#34;3.1.0\u0026#34;, \u0026#34;wangeditor\u0026#34;: \u0026#34;^4.7.11\u0026#34;, \u0026#34;xlsx\u0026#34;: \u0026#34;^0.17.4\u0026#34; }, \u0026#34;devDependencies\u0026#34;: { \u0026#34;@babel/parser\u0026#34;: \u0026#34;^7.7.4\u0026#34;, \u0026#34;@babel/register\u0026#34;: \u0026#34;7.0.0\u0026#34;, \u0026#34;@vue/babel-plugin-transform-vue-jsx\u0026#34;: \u0026#34;^1.2.1\u0026#34;, \u0026#34;@vue/cli-plugin-babel\u0026#34;: \u0026#34;3.5.3\u0026#34;, \u0026#34;@vue/cli-plugin-eslint\u0026#34;: \u0026#34;^3.9.1\u0026#34;, \u0026#34;@vue/cli-plugin-unit-jest\u0026#34;: \u0026#34;3.5.3\u0026#34;, \u0026#34;@vue/cli-service\u0026#34;: \u0026#34;3.5.3\u0026#34;, \u0026#34;@vue/test-utils\u0026#34;: \u0026#34;1.0.0-beta.29\u0026#34;, \u0026#34;autoprefixer\u0026#34;: \u0026#34;^9.5.1\u0026#34;, \u0026#34;babel-core\u0026#34;: \u0026#34;7.0.0-bridge.0\u0026#34;, \u0026#34;babel-eslint\u0026#34;: \u0026#34;10.0.1\u0026#34;, \u0026#34;babel-jest\u0026#34;: \u0026#34;23.6.0\u0026#34;, \u0026#34;babel-plugin-dynamic-import-node\u0026#34;: \u0026#34;2.3.0\u0026#34;, \u0026#34;babel-plugin-transform-remove-console\u0026#34;: \u0026#34;^6.9.4\u0026#34;, \u0026#34;chalk\u0026#34;: \u0026#34;2.4.2\u0026#34;, \u0026#34;chokidar\u0026#34;: \u0026#34;2.1.5\u0026#34;, \u0026#34;connect\u0026#34;: \u0026#34;3.6.6\u0026#34;, \u0026#34;compression-webpack-plugin\u0026#34;: \u0026#34;5.0.2\u0026#34;, \u0026#34;eslint\u0026#34;: \u0026#34;5.15.3\u0026#34;, \u0026#34;eslint-plugin-vue\u0026#34;: \u0026#34;5.2.2\u0026#34;, \u0026#34;html-webpack-plugin\u0026#34;: \u0026#34;3.2.0\u0026#34;, \u0026#34;http-proxy-middleware\u0026#34;: \u0026#34;^0.19.1\u0026#34;, \u0026#34;husky\u0026#34;: \u0026#34;1.3.1\u0026#34;, \u0026#34;lint-staged\u0026#34;: \u0026#34;8.1.5\u0026#34;, \u0026#34;plop\u0026#34;: \u0026#34;2.3.0\u0026#34;, \u0026#34;sass\u0026#34;: \u0026#34;1.32.13\u0026#34;, \u0026#34;sass-loader\u0026#34;: \u0026#34;10.2.0\u0026#34;, \u0026#34;script-ext-html-webpack-plugin\u0026#34;: \u0026#34;2.1.3\u0026#34;, \u0026#34;script-loader\u0026#34;: \u0026#34;0.7.2\u0026#34;, \u0026#34;serve-static\u0026#34;: \u0026#34;^1.13.2\u0026#34;, \u0026#34;svg-sprite-loader\u0026#34;: \u0026#34;4.1.3\u0026#34;, \u0026#34;svgo\u0026#34;: \u0026#34;1.2.0\u0026#34;, \u0026#34;tasksfile\u0026#34;: \u0026#34;^5.1.1\u0026#34;, \u0026#34;vue-template-compiler\u0026#34;: \u0026#34;2.6.14\u0026#34; }, \u0026#34;engines\u0026#34;: { \u0026#34;node\u0026#34;: \u0026#34;\u0026gt;=8.9\u0026#34;, \u0026#34;npm\u0026#34;: \u0026#34;\u0026gt;= 3.0.0\u0026#34; }, \u0026#34;browserslist\u0026#34;: [ \u0026#34;\u0026gt; 1%\u0026#34;, \u0026#34;last 2 versions\u0026#34; ] } name:项目名\nversion：包唯一的版本号\nresolved：安装源\nintegrity：表明包完整性的hash值（验证包是否已失效）\ndev：如果为true，则此依赖关系仅是顶级模块的开发依赖关系或者是一个的传递依赖关系\nrepository：项目的仓库地址以及版本控制信息\ndescription：项目描述\nkeywords：技术关键词\nhomepage：主页链接\nlicense：开源许可证\nauthor：作者\nfiles：指定发布的内容\ntype：声明npm包遵循的模块化规范 type: \u0026quot;module\u0026quot;||\u0026quot;commonjs\u0026quot; 不指定，默认为commonjs module =\u0026gt; ESModule 所有.js后缀结尾的文件，都遵循type所指定的模块化规范 以.mjs结尾的文件就是使用的ESModule规范，以.cjs结尾的遵循的是commonjs规范 main：指定项目入口文件，不设置 main 字段，入口文件为根目录下的 index.js。browser 和 Node 环境\nmodule：定义 npm 包的 ESM 规范的入口文件\nbrowser：指定browser环境项目入口文件\n\u0026#34;browser\u0026#34;: { \u0026#34;./dist/index.js\u0026#34;: \u0026#34;./dist/index.browser.js\u0026#34;, // browser+cjs \u0026#34;./dist/index.mjs\u0026#34;: \u0026#34;./dist/index.browser.mjs\u0026#34; // browser+mjs } --------------------- \u0026#34;browser\u0026#34;: \u0026#34;./dist/index.browser.js\u0026#34; // browser scripts：脚本命令\ndependencies：依赖包node_modules中依赖的包，与顶层的dependencies一样的结构\ndependencies ：-运行依赖，项目生产环境下需要用到的依赖\ndevDependencies： 开发依赖，项目开发环境需要用到而运行时不需要的依赖，用于辅助开发，通常包括项目工程化工具比如 webpack，vite，eslint 等。\npeerDependencies ： 同伴依赖，一种特殊的依赖，不会被自动安装，通常用于表示与另一个包的依赖与兼容性关系来警示使用者。\nbundledDependencies 打包依赖，在发布包时，bundleDependencies 里面的依赖都会被一起打包\noptionalDependencies ：可选依赖\nbrowserslist：设置项目的浏览器兼容情况\nunpkg:开启 CDN 服务\nrequires：依赖包所需要的所有依赖项，对应依赖包package.json里dependencies中的依赖项\n本地包调试 使用npm link/yarn link/yalc 等工具，通过软链接的方式进行调试 原理：\n在全局包路径（Global Path）下创建一个软连接(Symlinked)指向本地包的dist包; 在测试项目里通过软连接，将全局的软链接指向其node_modules/xxxxx npm link/yarn link # 第一步 在本地包中执行： npm link # or yarn link # 第二步 在测试项目中中执行： npm link \u0026lt;packageName\u0026gt; # or yarn link \u0026lt;packageName\u0026gt; yalc 安装 npm i yalc -g # or yarn global add yalc 使用 # 本地包发布 yalc publish # 调试项目中添加 yalc add \u0026lt;packageName\u0026gt; # 调试项目中导入 # import { Xxx } from \u0026#39;packageName\u0026#39;; # 移除依赖 yalc remove \u0026lt;packageName\u0026gt; # 更新部署 yalc publish --push # 简化为： yalc push 优点：\n原有依赖被缓存，依赖移除后即还原 HRM webpack-bundle-analyzer 包分析工具，查看项目打包情况，每个包的体积\n安装 npm install webpack-bundle-analyzer --save-dev 配置webpack.config.js文件 const BundleAnalyzerPlugin = require(\u0026#39;webpack-bundle-analyzer\u0026#39;).BundleAnalyzerPlugin module.exports={ plugins: [ // ... 其他 plugin 配置 new BundleAnalyzerPlugin() // 使用默认配置 ] } package.json \u0026#34;scripts\u0026#34;: { \u0026#34;analyzer\u0026#34;: \u0026#34;cross-env ENV_TYPE=staging ANALYZER=true node scripts/build.js\u0026#34; } 执行 npm run analyzer\nrollup-plugin-visualizer 结合vite\n安装 npm install --save-dev rollup-plugin-visualizer vite.config.js const { visualizer } = require(\u0026#34;rollup-plugin-visualizer\u0026#34;); // .... export default defineConfig({ plugins: [ vue(), visualizer({ open:true, //注意这里要设置为true，否则无效 gzipSize:true, brotliSize:true }), // ...其他插件 ] }) package.json\nyacl\n","permalink":"https://gwj-git.github.io/%E6%96%87%E7%AB%A0/npm%E7%9B%B8%E5%85%B3%E5%AD%A6%E4%B9%A0/","summary":"npm 生态 npm,cnpm,yarn,pnpm node的包管理工具,用于node插件管理（包括安装、卸载、管理依赖等） //递归依赖 ├── node_modules │ ├── A@1.0.0 │ │ └── node_modules │ │ │ └── D@1.0.0 │ ├","title":"npm相关学习"},{"content":"原文\n#执行上下文\n简单地说，执行上下文是可以计算和执行 Javascript 代码的一个环境的抽象概念。任何代码在 JavaScript 中运行时，它都是在执行上下文中运行的。\n##执行上下文类型\n三种\nGlobal Execution Context（全局执行上下文）\n这是默认的或基本的执行上下文。不在任何函数中的代码位于全局执行上下文中。它执行两个操作: 创建一个全局对象，它是一个window对象(在浏览器中) ，并将this值设置为等于全局对象。一个程序中只能有一个全局执行上下文\nFunctional Execution Context(函数执行上下文)\n每次调用一个函数时，都会为该函数创建一个全新的执行上下文。每个函数都有自己的执行上下文，但它是在执行或调用（when the function is invoked or called）该函数时创建的。可以有任意数量的函数执行上下文。无论何时创建一个新的执行上下文，它都会按照已定义的顺序经历一系列步骤，我将在本文后面讨论这些步骤。\nEval Function Execution Context（Eval函数执行上下文）\n在 eval 函数中执行的代码也有自己的执行上下文，但是 eval 通常不被 JavaScript 开发人员使用\n##执行栈（Execution Stack）\n执行堆栈，在其他编程语言中也称为“调用堆栈”，是一个具有 LIFO (Last in, First out后进先出)结构的堆栈，用于存储在代码执行期间创建的所有执行上下文。\n当 JavaScript 引擎第一次遇到脚本时，它会创建一个全局执行上下文并将其推送到当前执行堆栈。每当引擎发现一个函数调用时，它就为该函数创建一个新的执行上下文，并将其推送到栈的顶部。\n引擎执行其执行上下文位于堆栈顶部的函数。当这个函数完成时，它的执行栈从栈中弹出，控件到达当前堆栈中它下面的上下文。\nlet a = \u0026#39;Hello World!\u0026#39;; function first() { console.log(\u0026#39;Inside first function\u0026#39;); second(); console.log(\u0026#39;Again inside first function\u0026#39;); } function second() { console.log(\u0026#39;Inside second function\u0026#39;); } first(); console.log(\u0026#39;Inside Global Execution Context\u0026#39;); global =\u0026gt;first(执行中调用second)=\u0026gt;second(执行完弹出)=\u0026gt;first(执行完弹出)=\u0026gt; global（执行console）\n创建执行上下文 执行上下文创建分为两个阶段:\n创建阶段（Creation Phase）和执行阶段（Execution Phase）。\n创建阶段（Creation Phase） 执行上下文是在创建阶段创建的。在创建阶段发生的事情如下\nLexicalEnvironment（词法环境） 组件被创建. **VariableEnvironment（变量环境） ** 组件被创建. 执行上下文可以在概念上表示如下\nExecutionContext = { LexicalEnvironment = \u0026lt;ref. to LexicalEnvironment in memory\u0026gt;, VariableEnvironment = \u0026lt;ref. to VariableEnvironment in memory\u0026gt;, } 词法环境Lexical Environment ES6官方文档将 Lexical Environment 定义为\nA Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.\n词法环境(Lexical Environment)是一种规范类型，用于根据 ECMAScript 代码的词法嵌套结构定义标识符与特定变量和函数的关联。词法环境由一个环境记录和一个可能为空的外部词法环境引用组成。\n简而言之，词法环境是一种保存标识符-变量映射的结构。(这里的标识符是指变量/函数的名称，而变量是对实际对象(包括函数对象和数组对象)或原始值的引用)。\n考虑以下代码\nvar a = 20; var b = 40; function foo() { console.log(\u0026#39;bar\u0026#39;); } lexicalEnvironment = { a: 20, b: 40, foo: \u0026lt;ref. to foo function\u0026gt; } 每个词法环境有三个组成部分:\n环境记录（Environment Record） 外部环境的引用（Reference to the outer environment） this绑定（This binding） 环境记录（Environment Record） 环境记录是变量和函数声明存储在词法环境中的位置。\n环境记录也有两种类型:\n声明环境记录(Declarative environment record)\n函数代码的词法环境包含一个声明型环境记录。存储变量和函数声明\n对象环境记录（Object environment record）\n全局代码的词法环境包含一个对象型环境记录。除了变量和函数声明之外，对象环境记录还存储一个全局绑定对象(浏览器中的window对象)。因此，对于每个绑定对象的属性(在浏览器中，它包含由浏览器提供给窗口对象的属性和方法) ，在记录中创建一个新条目。\n注意: 对于函数代码，环境记录还包含一个参数(arguments)对象，该对象包含传递给函数的索引和参数之间的映射，以及传递给函数的参数的长度(数目)。例如，下面函数的参数对象如下所示:\nfunction foo(a, b) { var c = a + b; } foo(2, 3); // argument object Arguments: {0: 2, 1: 3, length: 2}, 外部环境的引用(Reference to the Outer Environment) 对外部环境的引用意味着它可以访问外部词法环境。这意味着如果在当前的词法环境中找不到变量，JavaScript 引擎可以在外部环境中查找变量。\nthis绑定（This Binding） 在此组件中，确定或设置 this 的值。\n在全局执行上下文中，this 的值指向全局对象。(在浏览器中，this指的是 Window 对象)。\n在函数执行上下文中，this 的值取决于如何调用函数。如果由对象引用调用，则 this 的值设置为该对象，否则，this 的值设置为全局对象或未定义(在严格模式下)。例如:\nconst person = { name: \u0026#39;peter\u0026#39;, birthYear: 1994, calcAge: function() { console.log(2018 - this.birthYear); } } person.calcAge(); // \u0026#39;this\u0026#39; refers to \u0026#39;person\u0026#39;, because \u0026#39;calcAge\u0026#39; was called with //\u0026#39;person\u0026#39; object reference const calculateAge = person.calcAge; calculateAge(); // \u0026#39;this\u0026#39; refers to the global window object, because no object reference was given 抽象地说，伪代码中的词法环境类似于这样\nGlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: \u0026#34;Object\u0026#34;, // Identifier go here } outer: \u0026lt;null\u0026gt;, this: \u0026lt;global object\u0026gt; } } FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: \u0026#34;Declarative\u0026#34;, // Identifier bindings go here } outer: \u0026lt;Global or outer function environment reference\u0026gt;, this: \u0026lt;depends on how function is called\u0026gt; } } 变量环境（Variable Environment） 它也是一个词法环境，它的环境记录（EnvironmentRecord ）保存了在执行上下文中由变量声明（VariableStatements）创建的绑定\n变量环境也是一个词法环境，因此它具有上面定义的词法环境的所有属性和组件。\n在 ES6中，词法环境（LexicalEnvironment ）组件和 变量环境（VariableEnvironment ）组件的一个不同之处在于，前者用于存储函数声明和变量(let 和 const)绑定，而后者仅用于存储变量(var)绑定。\n执行阶段 在这个阶段，所有这些变量的分配都完成了，代码也最终执行了。\n例子 let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30); 执行上述代码时，JavaScript 引擎创建一个全局执行上下文来执行全局代码。所以全局执行上下文在创建阶段是这样的:\nGlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: \u0026#34;Object\u0026#34;, // 对象环境记录 // Identifier bindings go here a: \u0026lt; uninitialized \u0026gt;, b: \u0026lt; uninitialized \u0026gt;, multiply: \u0026lt; func \u0026gt; } outer: \u0026lt;null\u0026gt;, //外部环境引用 ThisBinding: \u0026lt;Global Object\u0026gt; // this绑定 }, VariableEnvironment: { EnvironmentRecord: { Type: \u0026#34;Object\u0026#34;, // Identifier bindings go here c: undefined, } outer: \u0026lt;null\u0026gt;, ThisBinding: \u0026lt;Global Object\u0026gt; } } 在执行阶段，变量分配完成。因此，在执行阶段，全局执行上下文将类似于下面的内容。\nGlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: \u0026#34;Object\u0026#34;, // Identifier bindings go here a: 20, b: 30, multiply: \u0026lt; func \u0026gt; } outer: \u0026lt;null\u0026gt;, ThisBinding: \u0026lt;Global Object\u0026gt; }, VariableEnvironment: { EnvironmentRecord: { Type: \u0026#34;Object\u0026#34;, // Identifier bindings go here c: undefined, } outer: \u0026lt;null\u0026gt;, ThisBinding: \u0026lt;Global Object\u0026gt; } } 当遇见函数调用multiply(20, 30)，创建一个新的函数执行上下文来执行函数代码。\n函数执行上下文创建阶段：\nFunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: \u0026#34;Declarative\u0026#34;, // 声明环境记录 // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer: \u0026lt;GlobalLexicalEnvironment\u0026gt;, ThisBinding: \u0026lt;Global Object or undefined\u0026gt;, }, VariableEnvironment: { EnvironmentRecord: { Type: \u0026#34;Declarative\u0026#34;, // Identifier bindings go here g: undefined }, outer: \u0026lt;GlobalLexicalEnvironment\u0026gt;, ThisBinding: \u0026lt;Global Object or undefined\u0026gt; } } 在此之后，执行上下文将经历执行阶段，这意味着完成对函数内部变量的赋值。\n函数执行上下文执行阶段：\nFunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: \u0026#34;Declarative\u0026#34;, // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer: \u0026lt;GlobalLexicalEnvironment\u0026gt;, ThisBinding: \u0026lt;Global Object or undefined\u0026gt;, }, VariableEnvironment: { EnvironmentRecord: { Type: \u0026#34;Declarative\u0026#34;, // Identifier bindings go here g: 20 }, outer: \u0026lt;GlobalLexicalEnvironment\u0026gt;, ThisBinding: \u0026lt;Global Object or undefined\u0026gt; } } 函数完成后，返回值存储在c中。因此全局执行上下文更新。之后，全局代码完成程序结束。\n注意: 您可能已经注意到，let 和 const 定义的变量在创建阶段没有任何与它们相关联的值，但是 var 定义的变量被设置为未定义的。\n这是因为在创建阶段，代码被扫描以查找变量和函数声明，函数声明被完整的存储在环境中，变量最初被设置为undefined(var)或保持未初始化（let和const）\n这就是为什么可以在声明var定义的变量之前访问它们（undefined），但是在声明let和const变量之前访问它们会出现引用错误的原因。\n这就是我们说的提升（hoisting）\n注意：在执行阶段，如果 JavaScript 引擎无法在源代码中声明的实际位置找到 let 变量的值，那么它将为其赋予未定义的值。\n原文结束\n执行 Javascript 代码的一个环境的抽象概念，\njs引擎第一次遇见脚本时会创建一个全局执行上下文并压入执行栈。执行中遇见函数会创建函数执行上下文并压入栈中，执行完会弹出。\n执行上下文的创建分为创建和执行两个阶段。\n创建阶段会创建词法环境和变量环境两个组件，两个组件都由环境记录，外部环境引用和this绑定三部分组成，其中词法环境的环境记录存储函数声明和变量let，const,而变量环境仅存储变量var。\n函数执行上下文词法环境的环境记录是声明型，存储变量和函数声明，和一个arguments对象。全局执行上下文的词法环境包含一个对象型的环境记录，除了存储变量和函数声明外，还有一个全局绑定对象（window）。\n外部环境引用使执行上下文可以访问外部词法环境。this绑定，全局执行上下文的this指向全局对象window对象，而函数执行上下文的this取决于如何调用函数。\n然后是执行阶段，分配变量，执行代码。\n","permalink":"https://gwj-git.github.io/%E6%96%87%E7%AB%A0/%E8%AF%91%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/","summary":"原文 #执行上下文 简单地说，执行上下文是可以计算和执行 Javascript 代码的一个环境的抽象概念。任何代码在 JavaScript 中运行时，它都是在执行上下文中运行的。 ##执行上","title":"[译]执行上下文"},{"content":"混入基础 mixins: [presenter(), header(), form(defaultForm), crud()]\n在这些混入完毕后，presenter()中的created()钩子触发toQuery()\n接着触发refresh()方法\n// 刷新 refresh() { if (!callVmHook(crud, CRUD.HOOK.beforeRefresh)) { return; } return new Promise((resolve, reject) =\u0026gt; { //crud加载状态，表格中的加载 crud.loading = true; // 请求数据 //crud.url在cruds()中初始化 initData(crud.url, crud.getQueryParams()).then(res =\u0026gt; { // getTable方法 return this.findVM(\u0026#39;presenter\u0026#39;).$refs.table; const table = crud.getTable(); if (table \u0026amp;\u0026amp; table.lazy) { // 懒加载子节点数据，清掉已加载的数据 table.store.states.treeData = {}; table.store.states.lazyTreeNodeMap = {}; } //表格绑定数据 期望数据 res.data中的数据数组或res.data.records（分页） crud.page.total = res.data.pages === undefined ? 0 : res.data.total; crud.data = res.data.pages === undefined ? res.data : res.data.records; // 重置数据状态 编辑和删除的状态值 crud.resetDataStatus(); // time 毫秒后显示表格 setTimeout(() =\u0026gt; { crud.loading = false; callVmHook(crud, CRUD.HOOK.afterRefresh); }, crud.time); // 清除上次搜索的条件 crud.query.startTime = null; crud.query.endTime = null; resolve(data); }).catch(err =\u0026gt; { crud.loading = false; reject(err); }); }); }, toquery()中有refresh()方法\n主要刷新表格及清空DateRangePicker组件的查询条件\n对于查询问题\n/** * 获取查询参数 */ getQueryParams: function() { // 清除参数无值的情况 Object.keys(crud.query).length !== 0 \u0026amp;\u0026amp; Object.keys(crud.query).forEach(item =\u0026gt; { if (crud.query[item] === null || crud.query[item] === \u0026#39;\u0026#39;) crud.query[item] = undefined; // 对creatime参数进行修改，createtime数组改为 startTime与endTime if (crud.query[item] !== undefined \u0026amp;\u0026amp; item === \u0026#39;createTime\u0026#39;) { const timeArr = crud.query[item]; crud.query[\u0026#39;startTime\u0026#39;] = timeArr[0]; crud.query[\u0026#39;endTime\u0026#39;] = timeArr[1]; } }); Object.keys(crud.params).length !== 0 \u0026amp;\u0026amp; Object.keys(crud.params).forEach(item =\u0026gt; { if (crud.params[item] === null || crud.params[item] === \u0026#39;\u0026#39;) crud.params[item] = undefined; }); return { current: crud.page.page, size: crud.page.size, orders: JSON.stringify(crud.sort), ...crud.query, ...crud.params }; }, 有时我们对于时间范围的需求需要不同的参数值，而DateRangePicker组件绑定的createTime\n的只能转化为startTime和endTime,改组件会影响原有功能，重新写一个又不至于\n所以我们可以在getQueryParams方法中添加同级判断（避免影响原有功能），来处理我们自己的需求\n// 对creatime参数进行修改，createtime数组改为 startTime与endTime if (crud.query[item] !== undefined \u0026amp;\u0026amp; item === \u0026#39;createTime\u0026#39;) { const timeArr = crud.query[item]; crud.query[\u0026#39;startTime\u0026#39;] = timeArr[0]; crud.query[\u0026#39;endTime\u0026#39;] = timeArr[1]; } // 以`DateRangePicker`组件绑定query.Time为例 if (crud.query[item] !== undefined \u0026amp;\u0026amp; item === \u0026#39;Time\u0026#39;) { // 按自己的需求更改 下面字段名 const timeArr = crud.query[item]; crud.query[\u0026#39;beginTime\u0026#39;] = timeArr[0]; crud.query[\u0026#39;endTime\u0026#39;] = timeArr[1]; ----- //或直接传递数组,按需处理 crud.query[\u0026#39;times\u0026#39;] = crud.query[item] } 然后是新增功能\n通常用的是crud.operation组件\n新增按钮触发\n/** * 启动添加 */ toAdd() { // 重置表单 crud.resetForm(); //验证crud钩子 if (!(callVmHook(crud, CRUD.HOOK.beforeToAdd, crud.form) \u0026amp;\u0026amp; callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) { return; } // 修改新增状态为PREPARED crud.status.add = CRUD.STATUS.PREPARED; // 调用生命周期钩子 callVmHook(crud, CRUD.HOOK.afterToAdd, crud.form); callVmHook(crud, CRUD.HOOK.afterToCU, crud.form); }, status: { add: CRUD.STATUS.NORMAL, edit: CRUD.STATUS.NORMAL, // 添加或编辑状态 //cu-get方法返回新增或编辑的状态值 get cu() { if (this.add === CRUD.STATUS.NORMAL \u0026amp;\u0026amp; this.edit === CRUD.STATUS.NORMAL) { return CRUD.STATUS.NORMAL; } else if (this.add === CRUD.STATUS.PREPARED || this.edit === CRUD.STATUS.PREPARED) { return CRUD.STATUS.PREPARED; } else if (this.add === CRUD.STATUS.PROCESSING || this.edit === CRUD.STATUS.PROCESSING) { return CRUD.STATUS.PROCESSING; } throw new Error(\u0026#39;wrong crud\\\u0026#39;s cu status\u0026#39;); }, // 标题，弹窗的标题 get title() { return this.add \u0026gt; CRUD.STATUS.NORMAL ? `新增${crud.title}` : this.edit \u0026gt; CRUD.STATUS.NORMAL ? `编辑${crud.title}` : crud.title; } }, :visible.sync=\u0026quot;crud.status.cu \u0026gt; 0\u0026quot;弹窗组件绑定的cu，此时显示\n提交表单执行crud.submitCU方法\u0026ndash;进行表单验证，.$refs['form'],并根据状态执行 crud.doAdd()方法（新增按钮toadd修改新增状态值）\n/** * 执行添加 */ doAdd() { if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) { return; } //修改新增状态 crud.status.add = CRUD.STATUS.PROCESSING; //请求接口 crud.crudMethod.add(crud.form).then(() =\u0026gt; { //修改状态为正常 crud.status.add = CRUD.STATUS.NORMAL; //重置表单 crud.resetForm(); //成功通知 crud.addSuccessNotify(); callVmHook(crud, CRUD.HOOK.afterSubmit); //查询--刷新--刷新表格 crud.toQuery(); }).catch(() =\u0026gt; { crud.status.add = CRUD.STATUS.PREPARED; callVmHook(crud, CRUD.HOOK.afterAddError); }); }, 编辑是一样的流程\n","permalink":"https://gwj-git.github.io/%E6%96%87%E7%AB%A0/el-admin%E6%A1%86%E6%9E%B6%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%932/","summary":"混入基础 mixins: [presenter(), header(), form(defaultForm), crud()] 在这些混入完毕后，presenter()中的created()钩子触发toQuery() 接着触发refresh()方法 // 刷","title":"el-admin框架学习总结(2)"},{"content":"个人学习思路，先模仿给出的那些管理页面写，大致清楚如何写一个页面后，根据页面加载或生命周期看每一步在做什么\n个人感觉 crud.js(src/components/Crud/crud.js)值得一看,是该后台系统各种增删改查的核心\n其他动态路由等也是学习点，暂时未总结\n项目文件夹结构 -node_modules\t依赖安装文件夹（要进行git忽略） -dist\tbuild后的文件夹（要进行git忽略） -public 主要放置入口index.html文件和项目LOGO -src --api **请求api封装（封装请求-常用） --assets\t静态资源 --components\t**自定义组件封装（封装所需要的组件-常用） --router\t**路由（路由跳转-常用） --store\tvuex配置 --utils\t通用工具封装 --views\t**页面开发（主要的页面开发-常用） -App.vue\tvue根页面 -main.js\tvue全局配置 -settings.js\t项目配置 -.env.development\t开发环境配置 -.env.production\t生产环境配置 -package.json\t依赖配置 -vue.config.js vue项目环境配置文件 页面流程\nimport { mapGetters } from \u0026#39;vuex\u0026#39; import CRUD, { presenter, header, form, crud } from \u0026#39;@crud/crud\u0026#39; // 默认表单，设置数据格式 最好与接口返回的格式一致，直接暴露，不要封装 const defaultForm = { id: null, username: null, // appId: null 自定义id字段，要在cruds 中添加idField } export default { name: \u0026#39;User\u0026#39;, components: { Treeselect, }, // 自定义的 property cruds() { // crud.js中的CRUD方法 return CRUD({ title: \u0026#39;用户\u0026#39;, url: \u0026#39;api/users\u0026#39;, // idField: \u0026#39;appId\u0026#39; 自定义id字段 crudMethod: { ...crudUser } }) }, // 混入 mixins: [presenter(), header(), form(defaultForm), crud()], data() { // 自定义验证 const validPhone = (rule, value, callback) =\u0026gt; { if (!value) { callback(new Error(\u0026#39;请输入电话号码\u0026#39;)) } else if (!isvalidPhone(value)) { callback(new Error(\u0026#39;请输入正确的11位手机号码\u0026#39;)) } else { callback() } } return { deptName: \u0026#39;\u0026#39;, //权限 permission: { add: [\u0026#39;admin\u0026#39;, \u0026#39;user:add\u0026#39;], }, // 验证规则 rules: { username: [ { required: true, message: \u0026#39;请输入用户名\u0026#39;, trigger: \u0026#39;blur\u0026#39; }, { min: 2, max: 20, message: \u0026#39;长度在 2 到 20 个字符\u0026#39;, trigger: \u0026#39;blur\u0026#39; } ] }, computed: { //返回store中的user对应的字段 ...mapGetters([\u0026#39;user\u0026#39;]) }, created() { this.crud.msg.add = \u0026#39;新增成功，默认密码：123456\u0026#39; }, mounted: function() { const that = this window.onresize = function temp() { that.height = document.documentElement.clientHeight - 180 + \u0026#39;px;\u0026#39; } }, methods: { //crud钩子在callVmHook中调用 [CRUD.HOOK.afterAddError](crud) { this.afterErrorMethod(crud) }, // 新增与编辑前做的操作 [CRUD.HOOK.afterToCU](crud, form) { //..... }, // 打开编辑弹窗前做的操作 [CRUD.HOOK.beforeToEdit](crud, form) { //........ }, } 15行，自定义option\n// 自定义的 property cruds() { // crud.js中的CRUD方法 return CRUD({ title: \u0026#39;用户\u0026#39;, url: \u0026#39;api/users\u0026#39;, // idField: \u0026#39;appId\u0026#39; 自定义id字段 crudMethod: { ...crudUser } }) }, mixins: [presenter(), header(), form(defaultForm), crud()]\n混入模式下\npresenter()\nfunction presenter(crud) { if (crud) { console.warn(\u0026#39;[CRUD warn]: \u0026#39; + \u0026#39;please use $options.cruds() { return CRUD(...) or [CRUD(...), ...] }\u0026#39;) } return { data() { // 在data中返回crud，是为了将crud与当前实例关联，组件观测crud相关属性变化 return { crud: this.crud } }, beforeCreate() { // 储存主页实例 this.$crud = this.$crud || {} //此处将实例里cruds中返回的CRUD函数初始化 let cruds = this.$options.cruds instanceof Function ? this.$options.cruds() : crud if (!(cruds instanceof Array)) { cruds = [cruds] } cruds.forEach(ele =\u0026gt; { if (this.$crud[ele.tag]) { console.error(\u0026#39;[CRUD error]: \u0026#39; + \u0026#39;crud with tag [\u0026#39; + ele.tag + \u0026#39; is already exist\u0026#39;) } this.$crud[ele.tag] = ele ele.registerVM(\u0026#39;presenter\u0026#39;, this, 0) }) this.crud = this.$crud[\u0026#39;defalut\u0026#39;] || cruds[0] }, methods: { parseTime }, created() { for (const k in this.$crud) { if (this.$crud[k].queryOnPresenterCreated) { // 默认查询 this.$crud[k].toQuery() } } }, destroyed() { for (const k in this.$crud) { this.$crud[k].unregisterVM(\u0026#39;presenter\u0026#39;, this) } }, mounted() { // 如果table未实例化（例如使用了v-if），请稍后在适当时机crud.attchTable刷新table信息 if (this.$refs.table !== undefined) { this.crud.attchTable() } } } } header(),pagination(),form(),crud\n关于页面中[CRUD.HOOK.afterToCU](crud, form) 之类的钩子如何执行 之前一直不太清楚是如何触发的，今天又翻了翻crud.js 本以为不在callVmHook中触发的，没想到还是在这里 js基础还是要经常学习巩固\n//[CRUD.HOOK.beforeToEdit]如何触发 [CRUD.HOOK.afterToCU](crud, form) { //..... } ------------- //crud.js // hook VM function callVmHook(crud, hook) { if (crud.debug) { console.log(\u0026#39;callVmHook: \u0026#39; + hook) } const tagHook = crud.tag ? hook + \u0026#39;$\u0026#39; + crud.tag : null let ret = true const nargs = [crud] for (let i = 2; i \u0026lt; arguments.length; ++i) { nargs.push(arguments[i]) } // 有些组件扮演了多个角色，调用钩子时，需要去重 const vmSet = new Set() crud.vms.forEach(vm =\u0026gt; vm \u0026amp;\u0026amp; vmSet.add(vm.vm)) vmSet.forEach(vm =\u0026gt; { if (vm[hook]) { ret = vm[hook].apply(vm, nargs) !== false \u0026amp;\u0026amp; ret } if (tagHook \u0026amp;\u0026amp; vm[tagHook]) { ret = vm[tagHook].apply(vm, nargs) !== false \u0026amp;\u0026amp; ret } }) return ret } ----------------- //关键在于apply方法 方法重用 //vm[hook].apply(vm, nargs) //将vm[hook]也就是实例中的[CRUD.HOOK.beforeToEdit]之类的方法在vm和nargs(也就是[crud])中重用 ","permalink":"https://gwj-git.github.io/%E6%96%87%E7%AB%A0/el-admin%E6%A1%86%E6%9E%B6%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%931/","summary":"个人学习思路，先模仿给出的那些管理页面写，大致清楚如何写一个页面后，根据页面加载或生命周期看每一步在做什么 个人感觉 crud.js(src/c","title":"el-admin框架学习总结(1)"}]