越是漫长,越要淡然。
念两句诗
天秋木叶下,月冷莎鸡悲。
—— 李白《秋思》
[置顶] 前端模块化总结
摘要:CommonJs 主要用在nodeJS中,它是通过同步的方式加载模块。因为在服务端模块文件都存在本地磁盘,读取非常快。 加载机制:输入是被输出的值的拷贝,一旦输出这一个值模块内部的变化就影响不到这个值 // test.js var count=0 function add() { count++ } module.exports = { add, count } var test=require('./test.js') console.log(test.count) //0 test.add() console.log(test.count) //0 但这个拷贝只是浅拷贝,当这个对象层次更深时会怎样呢? // test.js var obj={ count:0 } function add() { obj.count++ } module.exports = { add, obj } var test=require('./test.js') console.log(test.obj.count) //0 test.add() console.log(test.obj.count) //1 因为只是浅层拷贝,当test模块中的count改变,由于对象的引用,main中的count也会改变 module.exports和exports:每一个模块都有一个module对象,真正负责导出的是module.exports。内部实现是module.exports=exports。因此当exports.xxx时会给module.exports添加属性。当module.exports={name:2}时,module.exports是一个新对象,之前exports.xxx将不会再被导出。 AMD 采用异步加载模块 使用requirejs <script src="./require.js" data-main="./main.js"></script> 加载完require.js后立即执行main.js。main.js是入口文件 //main.js (function(){ require.config({ baseUrl:'', paths:{ "bar":"./bar", //模块对应的路径 不需要加.js "test":"./test" } }) require(['test'],function(test){ console.log('main',test.a) //main 1 }) })() //bar.js define(function(){ const name='bar' const count=0 return { name, count } }) //test.js define([ 'bar', ], function(bar) { console.log(bar.name) //bar console.log(bar.count) //0 return { a:1 } }); CMD 采用异步加载模块,结合了CommonJS优点。 使用seajs <script src="./sea.js"></script> <script> seajs.use('./main.js') //入口文件 </script> //bar.js define(function(require,exports,module){ var obj={ count:0 } function add(){ obj.count++ } module.exports={ obj, add } }); //main.js define(function(require,exports,module){ const bar=require('./bar.js') console.log(bar.obj.count) //0 bar.add() console.log(bar.obj.count) //1 }); ES Module 1.模块输出的是值的引用 //main.js import {count,add} from './test.js' console.log(count) //0 add() console.log(count) //1 //test.js export var count=0 export function add (){ count++ } ES Module和CommonJs区别 CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 CommonJS 模块是运行时加载,ES6 模块是编译(解析)时加载。 UMD 严格上说,umd不能算是一种模块规范,因为它没有模块定义和调用,这是AMD、CMD和CommonJS的结合体,保证模块可以被amd、cmd和commonjs调用。 (function(root, factory) { if (typeof module === 'object' && typeof module.exports === 'object') { var depModule = require('./test.js') module.exports = factory(depModule); //是commonjs模块规范,nodejs环境 } else if (typeof define === 'function' && define.amd) { define(['test'], factory) //是AMD模块规范,如require.js } else if (typeof define === 'function' && define.cmd) { define(function(require, exports, module) { var depModule = require('./test.js') module.exports = factory(depModule) //是CMD模块规范,如sea.js }) } else { root.umdModule = factory(root.depModule); //没有模块环境,直接挂载在全局对象上 } }(this, function(depModule) { console.log('我调用了依赖模块', depModule) return { name: '我自己是一个umd模块' } }))
posted @2021-07-20 23:13:12 淡然 点赞数 (3) 阅读全文
Vue2.x源码学习笔记(二)——initState
摘要:这里初始化了props,methods,data,computed以及watch。 function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } } initProps function initProps (vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent if (!isRoot) { toggleObserving(false) } for (const key in propsOptions) { keys.push(key) const value = validateProp(key, propsOptions, propsData, vm) defineReactive(props, key, value) if (!(key in vm)) { proxy(vm, `_props`, key) } } toggleObserving(true) } 循环用户提供的props对象,将props的每一个key缓存在vm.$options._propKeys上来优化性能。 通过validateProp获取props的默认值,通过defineReactive将props响应式的添加到vm._props上。然后通过proxy将_props代理到实例上。通过this.xxx就能获取props。 initMethods function initMethods (vm: Component, methods: Object) { const props = vm.$options.props for (const key in methods) { if (process.env.NODE_ENV !== 'production') { if (typeof methods[key] !== 'function') { //error } if (props && hasOwn(props, key)) { //error } if ((key in vm) && isReserved(key)) { //error } } vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) } } 这里会校验用户提供的methods中每一项是否是函数,名称是否与props上的重名。校验methos中的key与Vue实例上已有的方法是否重叠,一般是一些内置方法,比如以 $ 和_开头的方法。最后将方法绑定到实例上。可以通过this.xxx()调用。 initData function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } observe(data, true /* asRootData */) } 首先判断用户提供的data是函数还是对象,如果是函数则通过getData去获取data并赋值给vm._data。然后校验data中的key是否与methods和props重名。然后将_data代理到实例上。最后通过observe去观测,使data响应式。 initWatch function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } } function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) } 根据用户提供的watch通过createWatcher去创建watcher,如果是函数数组,则循环调用createWatcher,最终去调用$watch方法。 initComputed const computedWatcherOptions = { lazy: true } function initComputed (vm: Component, computed: Object) { const watchers = vm._computedWatchers = Object.create(null) const isSSR = isServerRendering() for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } if (!isSSR) { watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } } } } 如果计算属性的值是一个函数就将函数赋值给getter,如果是一个对象,就获取它的get函数。然后创建一个watcher,watcher的option中有lazy属性,同时将这个watcher存放在vm._computedWatchers中。如果不与data,props,或实例中的其他属性重名,就调用defineComputed。 defineComputed const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } function defineComputed ( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop sharedPropertyDefinition.set = userDef.set || noop } if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } Object.defineProperty(target, key, sharedPropertyDefinition) } 首先构造属性描述符(get、set),对计算属性值时函数还是对象进行特殊处理,对于get进行处理成createComputedGetter。 createComputedGetter function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } } 首先获取之前保存的watcher。因为之前创建watcher时的option中lazy为true,所以Watcher内部的dirty属性也为true,因此首次会调用watcher.evaluate(),evaluate函数中会调用计算属性的get去获取值,并将dirty置为false。并将值保存在watcher的value上。计算属性就是获取的这个value。因此第二次执行时因为dirty是false所以不会再调用get方法。待页面更新后,wathcer.update方法会将watcher.dirty重新置为 true。 proxy const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } export 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) } 这里通过Object.defineProperty来将key代理到target上。
posted @2022-03-20 15:47:00 淡然 点赞数 (0) 阅读全文
Vue2.x源码学习笔记(一)——Vue初始化
摘要:Vue入口 Vue的入口是在src/core/instance/index.js中,它是一个Function 在此文件中还进行了一系列的初始化操作: initMinxin:在Vue原型上定义了_init方法。_init方法在new Vue()时会调用,用来初始化。 stateMixin:在Vue原型上定义了$data和$props两个实例属性。并定义了$del、$set、$watch实例方法。 eventsMinin:在Vue原型上定义了$on、$once、$off、$emit实例方法。 lifecycleMixin:在Vue原型上定义_update、$forceUpdate、$destroy实例方法。 renderMixin:在Vue原型上定义了_render、$nextTick实例方法。 在src/core/global-api/index.js中,通过initGlobalAPI()方法,给 Vue 这个对象本身扩展全局的静态方法。 _init初始化 此方法在initMinxin(Vue)中挂载到Vue原型,并在new Vue()时调用。 Vue.prototype._init = function (options?: Object) { const vm: Component = this vm._uid = uid++ vm._isVue = true if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) initState(vm) initProvide(vm) callHook(vm, 'created') if (vm.$options.el) { vm.$mount(vm.$options.el) } } 每个vue实例都有一个_uid,并且是依次递增的。然后处理组件配置项,这里将用户传递的options选项与当前构造函数的options属性及其父级实例构造函数的options合并生成一个新的options赋值给$options。然后设置代理,将vm实例上的属性代理到vm._renderProxy。接着进行一系列初始化: initLifecycle:初始化vm.$parent、vm.$root、vm.$children、vm.$refs等属性值。 initEvents:初始化事件中心:vm._events = Object.create(null)。 initRender:初始化渲染定义vm._c(用于用户使用template模式) vm.$createElement(用于用户手写render函数)。 callHook(vm, ‘beforeCreate’):生命周期beforeCreate 。 initInjections:初始化inject。 initState:初始化 state, props, methods, computed, watch 等等。响应式就是这里处理的。 initProvide:初始化provide。 callHook(vm, ‘created’):生命周期created。 如果发现配置项上有el选项,则自动调用$mount方法,也就是说有了el选项,就不需要再手动调用$mount,反之,没有el则必须手动调用$mount。 initLifecycle function initLifecycle (vm: Component) { const options = vm.$options let parent = options.parent if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) } vm.$parent = parent vm.$root = parent ? parent.$root : vm vm.$children = [] vm.$refs = {} vm._watcher = null vm._inactive = null vm._directInactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false } 如果当前组件不是抽象组件,并且$options存在parent,那么将不断向上层查找直到找到非抽象类的父级,然后赋值给$parent属性。并且将自己添加到父级的$children属性中。如果当前组件没有父级,那么它自己就是根组件,也就是$root就是它本身。如果存在父级,那么它的$root就沿用父级的$root。然后定义一系列属性的默认值。 initEvents function initEvents (vm: Component) { vm._events = Object.create(null) vm._hasHookEvent = false const listeners = vm.$options._parentListeners if (listeners) { updateComponentListeners(vm, listeners) } } 这里初始化了事件系统,创建了一个用于存放事件的空对象。之后注册的事件都会存放于_events中。 在模板编译时,如果解析到组件标签,会实例化子组件。并将标签上注册的事件解析成object并通过参数传递给子组件。这些事件则保存在vm.$options._parentListeners中。当它不为空时则调用updateComponentListeners方法去注册事件。 updateComponentListeners let target: any function add (event, fn) { target.$on(event, fn) } function remove (event, fn) { target.$off(event, fn) } function createOnceHandler (event, fn) { const _target = target return function onceHandler () { const res = fn.apply(null, arguments) if (res !== null) { _target.$off(event, onceHandler) } } } function updateComponentListeners ( vm: Component, listeners: Object, oldListeners: ?Object ) { target = vm updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm) target = undefined } target用于存放当前实例,add用于给当前实例注册事件,remove用于解除事件。createOnceHandler用来创建一个只调用一次的函数,当执行完这个函数会立即解绑。将这三个方法,以及用于存放旧事件的参数传入到updateListeners中。 updateListeners function updateListeners ( on: Object, oldOn: Object, add: Function, remove: Function, createOnceHandler: Function, vm: Component ) { let name, def, cur, old, event for (name in on) { def = cur = on[name] old = oldOn[name] event = normalizeEvent(name) if (isUndef(cur)) { process.env.NODE_ENV !== 'production' && warn( `Invalid handler for event "${event.name}": got ` + String(cur), vm ) } else if (isUndef(old)) { if (isUndef(cur.fns)) { cur = on[name] = createFnInvoker(cur, vm) } if (isTrue(event.once)) { cur = on[name] = createOnceHandler(event.name, cur, event.capture) } add(event.name, cur, event.capture, event.passive, event.params) } else if (cur !== old) { old.fns = cur on[name] = old } } for (name in oldOn) { if (isUndef(on[name])) { event = normalizeEvent(name) remove(event.name, oldOn[name], event.capture) } } } 这里主要的逻辑就是如果新的事件不存在于老的事件对象中,那就通过add注册。如果老的事件不存在新的事件对象中,则通过remove解除。
posted @2021-09-09 18:01:33 淡然 点赞数 (0) 阅读全文
Vue2.x源码学习笔记(八)——实例方法(生命周期篇)
摘要:在/src/core/instance/lifecycle.js的lifecycleMixin函数中往Vue的原型上挂载了$forceUpdate和$destroy方法。 vm.$forceUpdate Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } } //Watcher类中有这样处理: if (isRenderWatcher) { vm._watcher = this } vm._watcher存放了当前实例的渲染watcher。调用渲染watcher的update方法,迫使组件重新渲染。它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。 vm.$destory Vue.prototype.$destroy = function () { const vm: Component = this if (vm._isBeingDestroyed) { return } callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while (i--) { vm._watchers[i].teardown() } if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } vm._isDestroyed = true vm.__patch__(vm._vnode, null) callHook(vm, 'destroyed') vm.$off() if (vm.$el) { vm.$el.__vue__ = null } if (vm.$vnode) { vm.$vnode.parent = null } } 为了防止$destroy被反复执行,所以当vm._isBeingDestroyed为true时表示实例已经开始销毁直接return。 先调用beforeDestroy生命周期函数,将_isBeingDestroyed置为true,表示开始销毁实例。如果父组件没有被销毁,那么就将自己从父组件的children列表中移除,解除与父组件的关系。将当前实例的_watcher和_watchers中的watcher都通过teardown()移除监听。 _isDestroyed置为true表示实例已经销毁,调用 __patch__,销毁节点。调用destroyed生命周期函数。调用$off移除所有事件。 vm.$nextTick Vue.prototype.$nextTick = function (fn: Function) { return nextTick(fn, this) } nextTick方法请看第Vue2.x源码学习笔记(五) vm.$mount
posted @2022-03-16 23:37:34 淡然 点赞数 (0) 阅读全文
Vue2.x源码学习笔记(八)——实例方法(事件篇)
摘要:在Vue初始化(_init)时会调用initEvents函数来对事件进行初始化。initEvents中在Vue实例上创建了一个空对象用来存放事件 vm._events = Object.create(null) vue的事件系统定义在/src/core/instance/events.js中的eventsMixin函数里。 vm.$on Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { const vm: Component = this if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$on(event[i], fn) } } else { (vm._events[event] || (vm._events[event] = [])).push(fn) if (hookRE.test(event)) { vm._hasHookEvent = true } } return vm } 第一个参数代表了事件名,如果传入的是一个数组,那就新循环这个数组递归调用$on。将注册的事件和回调以键值对的形式存储到 vm._events 对象中:vm._events = { eventA: [fn1, …] }。 vm.$emit Vue.prototype.$emit = function (event: string): Component { const vm: Component = this if (process.env.NODE_ENV !== 'production') { const lowerCaseEvent = event.toLowerCase() if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { //报错 } } let cbs = vm._events[event] if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs const args = toArray(arguments, 1) const info = `event handler for "${event}"` for (let i = 0, l = cbs.length; i < l; i++) { invokeWithErrorHandling(cbs[i], vm, args, vm, info) } } return vm } 从 vm._events 对象上根据事件名拿到当前事件的回调函数数组。获得参数后,循环回调函数数组,通过invokeWithErrorHandling包裹后调用每一个函数并捕获异常。 vm.$off Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { const vm: Component = this if (!arguments.length) { vm._events = Object.create(null) return vm } if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$off(event[i], fn) } return vm } const cbs = vm._events[event] if (!cbs) { return vm } if (!fn) { vm._events[event] = null return vm } let cb let i = cbs.length while (i--) { cb = cbs[i] if (cb === fn || cb.fn === fn) { cbs.splice(i, 1) break } } return vm } 如果调用$off时不传递任何参数,则会清空整个事件对象(vm._events)。 如果第一个参数是一个数组,怎会循环这个数组,递归调用$off。 如果参数只传了一个事件名,则将事件对象中这个事件的函数数组清空。 如果参数传递了一个事件名,并切第二个参数是一个具体函数,那么循环这个事件名对应的函数数组,找到对应的函数并移除。 vm.$once Vue.prototype.$once = function (event: string, fn: Function): Component { const vm: Component = this function on () { vm.$off(event, on) fn.apply(vm, arguments) } on.fn = fn vm.$on(event, on) return vm } once只调用一次,实际还是调用了on方法,在执行回调函数时会先移除本身,然后再执行第二个参数对应的函数
posted @2022-03-16 23:10:34 淡然 点赞数 (0) 阅读全文
Vue2.x源码学习笔记(七)——实例方法(数据篇)
摘要:在/src/core/instance/state.js中的stateMixin函数里我们在Vue原型上挂在了$delete,$set和$watch方法。 vm.$delete Vue.prototype.$delete = del del方法请看第Vue2.x源码学习笔记(五) vm.$set Vue.prototype.$set = set set方法请看第Vue2.x源码学习笔记(五) vm.$watch Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {} options.user = true const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { try { cb.call(vm, watcher.value) } catch (error) { handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`) } } return function unwatchFn () { watcher.teardown() } } function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) } 对于第二个参数cb,我们会进行兼容式处理:如果cb是一个对象,我们会把这个的对象的handler函数作为第二个参数。options如果不传,则配置都为false。 然后将这个watcher标记为用户watcher。通过new Watcher创建一个Watcher实例。如果options中immediate为true,则立即执行第二个参数:cb函数。 最后返回一个函数,执行watcher的teardown方法,取消监听。 watcher.teardown() class Watcher{ ...... teardown () { if (this.active) { if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } } 将本身这个watcher从当前实例的watchers列表中删除。watcher之前有维护一个deps数组用于记录哪些deps依赖中保存了当前watcher。将这些dep的subs数组(存放watcher数组)中的当前watcher移除。
posted @2022-03-16 22:00:54 淡然 点赞数 (0) 阅读全文
Vue2.x源码学习笔记(六)——watcher异步更新
摘要:queueWatcher 在watcher的update方法中当没有lazy和sync配置时,watcher更新会通过queueWatcher去更新。 const queue: Array<Watcher> = [] let waiting = false let flushing = false function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } if (!waiting) { waiting = true if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } nextTick(flushSchedulerQueue) } } } queueWatcher就是将watcher加入一个队列的过程。根据watcher的id来判断是否加入过队列。 如果当前不处于刷新队列的状态,则watcher直接push入队。如果已经处在刷新队列状态,则将id从队列后往前比较,插在对应的顺序,使queue按照id从小到大排序。然后通过nextTick包裹执行flushSchedulerQueue。 flushSchedulerQueue function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { watcher.before() } id = watcher.id has[id] = null watcher.run() } const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) if (devtools && config.devtools) { devtools.emit('flush') } } function resetSchedulerState () { index = queue.length = activatedChildren.length = 0 has = {} if (process.env.NODE_ENV !== 'production') { circular = {} } waiting = flushing = false } 执行flushSchedulerQueue时表示正在刷新队列,所以flushing为true。将队列中的watcher按id从小到大排序,这么做是为了: 组件的更新顺序为从父级到子级,因为父组件总是在子组件之前被创建。 一个组件的用户 watcher 在其渲染 watcher 之前被执行,因为用户 watcher 先于 渲染 watcher 创建。 如果一个组件在其父组件的 watcher 执行期间被销毁,则它的 watcher 可以被跳过。 排序以后在刷新队列期间新进来的 watcher 也会按顺序放入队列的合适位置。 紧接着循环队列,如果watcher中有before这个配置则执行before钩子,然后执行watcher的run方法。并且将执行过的watcher的id从has这个缓存map中移除。 然后通过resetSchedulerState将has重置为{},清空队列,flushing和waiting重置为false。 waiting = flushing = false,表示刷新队列结束,表示可以像 callbacks 数组中放入新的flushSchedulerQueue 函数,并且可以向浏览器的任务队列放入下一个 flushCallbacks 函数了。 最后调用actived和update生命周期函数 补充: waiting用来判断callbacks数组中是否存放了flushSchedulerQueue函。callbacks 数组中只能存在一个 flushSchedulerQueue 函数,多了没意义。
posted @2022-03-14 22:45:59 淡然 点赞数 (0) 阅读全文
Vue2.x源码学习笔记(五)——全局方法原理
摘要:Vue.use Vue.use = function (plugin: Function | Object) { const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) > -1) { return this } // additional parameters const args = toArray(arguments, 1) args.unshift(this) if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { plugin.apply(null, args) } installedPlugins.push(plugin) return this } installedPlugins表示已经安装的插件,如果已经安装了则直接return。 将 this也就是Vue构造函数放到第一个参数位置,然后将这些参数传递给install方法。如果插件存在install方法就调用install方法,如果没有则调用plugin这个方法进行安装插件,然后将插件添加到已安装插件列表。 Vue.set function set (target: Array<any> | Object, key: any, val: any): any { if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ if (!ob) { target[key] = val return val } defineReactive(ob.value, key, val) ob.dep.notify() return val } 如果是数组则使用数组的splice方法来响应式更新。 如果key原本就存在target对象上,直接修改值。 如果target对象上没有__ob__属性,即不是响应式对象,直接修改值。 如果key不存在target上并且target是响应式对象,那么通过defineReactive使其添加到target上,并且使其响应式。然后触发target的依赖更新。 Vue.delete function del (target: Array<any> | Object, key: any) { if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = (target: any).__ob__ if (!hasOwn(target, key)) { return } delete target[key] if (!ob) { return } ob.dep.notify() } 如果是数组,则通过数组的splice方法去删除。 如果属性不存在对象上,直接return结束。 如果属性存在对象上,通过delete方法删除属性,然后通过target对应的__ob__上的dep来触发更新。 Vue.nextTick const callbacks = [] let pending = fals function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } 将传入的回调函数通过try catch包裹成一个新函数,然后添加到callbacks数组中。 当pending为false,即浏览器任务队列中没有flushCallbacks函数了。将pending重置为true并通过执行timerFunc将flushCallbacks放入浏览器任务队列中。 也可以这样使用:Vue.nextTick().then(()=>{}),因此当环境支持Promise时,会返回一个promise,并执行_resolve timerFunc let timerFunc if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) /** * 在有问题的UIWebViews中,Promise.then不会完全中断,但是它可能会陷入怪异的状态, * 在这种状态下,回调被推入微任务队列,但队列没有被刷新,直到浏览器需要执行其他工作,例如处理一个计时器。 * 因此,我们可以通过添加空计时器来“强制”刷新微任务队列。 */ if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { setTimeout. timerFunc = () => { setImmediate(flushCallbacks) } } else { timerFunc = () => { setTimeout(flushCallbacks, 0) } } 当浏览器支持Promise时首选Promise.resolve.then(),在微任务队列中放入 flushCallbacks。然后考虑MutationObserver。再考虑setImmediate(宏任务)。最后用setTimeout(cb,0)兜底。不管用哪种方式最终都是调用flushCallbacks函数。 flushCallbacks function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } 将pending置为false,表示浏览器任务队列已经执行完了flushCallbacks,循环callbacks,执行之前存放的包裹后的函数。
posted @2022-03-13 21:32:17 淡然 点赞数 (0) 阅读全文
Vue2.x源码学习笔记(五)——响应式原理之数组的侦测
摘要:Observer export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { } } observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } } observer类中如果是数组,则会先劫持数组的原型,对数组原型进行改造。然后再通过observeArray方法去循环调用observe方法。使数组中的每一项都被侦测。 数组原型劫持 const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function (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 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) ob.dep.notify() return result }) }) 通过Object.create继承数组的原型得到arrayMethods,然后对数组的7个方法进行重写并添加到arrayMethods的原型上。 先保存一份原来的数组方法,并获取原来数组方法的返回值。然后重写方法。对于push,unshift,splice有插入值操作的方法,会对插入的值继续通过observeArray侦测。然后获取数组的响应式标记__ob__,即observer类。因为observer类也有dep实例,所以通过this.ob.dep.notify()去派发更新。最后返回原先数组方法的返回值。 protoAugment和copyAugment protoAugment中将数组的__proto__指向新的原型对象。 如果不兼容的情况下可以使用copyAugment,将每一项手动添加到数组新原型。 dependArray function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } } 在defineReactive中对于value是数组的单独处理。循环数组,对于数组的每一项进行收集依赖。
posted @2022-03-13 14:33:17 淡然 点赞数 (0) 阅读全文
Vue2.x源码学习笔记(四)——响应式原理之对象的侦测
摘要:observe函数 在初始化的initData函数中,通过observe(data)来使data响应式。observe函数内部会判断目标对象中有没有__ob__属性。如果没有会通过new Observer来添加一个不可枚举的属性__ob__,值就是本身这个Observer类。之后会通过__ob__作为响应式的标记。最后observe函数返回__ob__。 Observer class Observer { value: any; dep: Dep; vmCount: number; constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { //数组侦测 } else { this.walk(value) } } walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } } 在new Observer过程中,他不仅会给目标对象添加__ob__这个响应式标记,还会new Dep便于后续收集依赖。对于object类型他会执行walk方法,而walk方法则是循环object中的key。然后执行defineReactive来使其响应式。 defineReactive function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && 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 && value !== value)) { return } if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) } 首先我们通过new Dep创建一个dep,用于给这个属性收集依赖,然后再递归执行observe(val)。当读取属性时会调用get方法,get中通过dep.depend()来收集依赖。如果这个值有__ob__,则__ob__对应的Observer类也会收集依赖。在修改值时会调用set,set中会通过observe(newVal)来对新赋的值进行响应式化,并通过dep.notify通知依赖更新。 Object.defineProperty 对象的侦测是靠Object.defineProperty(obj,prop, descriptor)来实现的。第一个参数是操作的对象,第二个参数是对象的key,第三个参数是配置。descriptor有以下配置: configurable:true时可以修改属性,可以delete属性。默认为false。 enumerable:true时表示key可枚举。默认为false。 writable:true时属性的value才能被赋值运算改变,默认为false。 get:属性读取时触发。Vue就是在这里收集依赖。 set:属性设置值时触发。Vue在这里触发更新。 value:属性值。 Dep class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; 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 () { const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { subs.sort((a, b) => a.id - b.id) } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } 在new Dep时会给每一个dep绑定一个自增id,并且创建一个subs空数组,用于收集watcher依赖。 上面我们说到当读取数据时,会调用dep的depend方法去收集依赖。该方法中会通过Dep.target获得当前的watcher。然后调用当前watcher的addDep方法,addDep其实就是将dep和watcher绑定:给wathcer的deps数组中添加当前dep,并在dep的subs数组中添加当前watcher。 当数据更新时通过notify去派发更新。它遍历排序好的subs数组,也就是调用每一个watcher的update方法去更新时图。 Watcher class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) this.cb = cb this.id = ++uid this.active = true this.dirty = this.lazy this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop } } this.value = this.lazy ? undefined : this.get() } get () { //核心 } addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } update () { if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } run () { if (this.active) { const value = this.get() if ( value !== this.value || isObject(value) || this.deep ) { const oldValue = this.value this.value = value this.cb.call(this.vm, value, oldValue) } } } } } update方法中如果sync属性为true则立即执行run方法。否则会通过queueWatcher异步更新队列去执行run方法。 run方法会调用get方法去获得watcher的新值,当新值和旧值不同时会去调用cb回调函数。 get get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } get方法中会先将当前操作的watcher,存放在Dep.target,然后调用传入的参数的getter方法,调用getter其实就是会去更新试图,并且会触发收集依赖的过程。最后再将Dep.target置为null,更新完后,清除旧的依赖。
posted @2022-03-08 22:44:37 淡然 点赞数 (1) 阅读全文
  • 1
  • 2
  • 3