随着Vue框架状态越来越火热,不少feder已经不仅仅满足于vue框架的使用,有很多人都打算阅读vue源码来提升自己。那么提到源码阅读就不免产生了一个问题,到底要从何处开始源码阅读呢?
package的入口:
// package.json
"main": "dist/vue.runtime.common.js"
经过全局搜索文件名找到入口源码的打包配置在scripts/config.js
// scripts/config.js
'web-runtime-cjs-dev': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.dev.js'),
format: 'cjs',
env: 'development',
banner
},
'web-runtime-cjs-prod': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.prod.js'),
format: 'cjs',
env: 'production',
banner
}
即项目入口为:
// scripts/config.js
resolve('web/entry-runtime.js')
可以看到这个地址并非是直接目录,同文件内找到解析函数resolve:
// scripts/config.js
const aliases = require('./alias')
const resolve = p => {
const base = p.split('/')[0]
if (aliases[base]) {
return path.resolve(aliases[base], p.slice(base.length + 1))
} else {
return path.resolve(__dirname, '../', p)
}
}
可以看到这个地址解析函数引入了别名文件,并且取’/‘之前的作为别名检查存在性,如果不存在则整体进行解析,可以看到入口文件的’/'之前是web,打开别名文件./alias
// scripts/alias.js
module.exports = {
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
compiler: resolve('src/compiler'),
core: resolve('src/core'),
shared: resolve('src/shared'),
web: resolve('src/platforms/web'),
weex: resolve('src/platforms/weex'),
server: resolve('src/server'),
sfc: resolve('src/sfc')
}
可以看到web这个别名是存在的,因此入口地址
resolve('web/entry-runtime.js')
将被解析为:
src/platforms/web/entry-runtime.js
打开该文件:
// src/platforms/web/entry-runtime.js
import Vue from './runtime/index'
export default Vue
即入口文件在此处的操作是引入./runtime/inde并暴露出去,因此核心文件的解析将从src/platforms/web/runtime/index.js开始
// src/platforms/web/runtime/index.js
/* @flow */
import Vue from 'core/index'
import config from 'core/config'
import {
extend, noop } from 'shared/util'
import {
mountComponent } from 'core/instance/lifecycle'
import {
devtools, inBrowser } from 'core/util/index'
import {
query,
mustUseProp,
isReservedTag,
isReservedAttr,
getTagNamespace,
isUnknownElement
} from 'web/util/index'
import {
patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'
// install platform specific utils
// 标签及属性使用限制
Vue.config.mustUseProp = mustUseProp
// 判定是否为html或svg标签
Vue.config.isReservedTag = isReservedTag
// 判定是否为style、class
Vue.config.isReservedAttr = isReservedAttr
// 获取tag的命名空间
Vue.config.getTagNamespace = getTagNamespace
// 判定标签是否为未定义标签
Vue.config.isUnknownElement = isUnknownElement
// install platform runtime directives & components
// 给vue原型链上进行方法挂载
// 将platformDirectives对象可枚举属性拷贝给Vue.options.directives
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
setTimeout(() => {
// 开发者工具init事件触发及提示
if (config.devtools) {
if (devtools) {
devtools.emit('init', Vue)
} else if (
process.env.NODE_ENV !== 'production' &&
process.env.NODE_ENV !== 'test'
) {
console[console.info ? 'info' : 'log'](
'Download the Vue Devtools extension for a better development experience:\n' +
'https://github.com/vuejs/vue-devtools'
)
}
}
if (process.env.NODE_ENV !== 'production' &&
process.env.NODE_ENV !== 'test' &&
config.productionTip !== false &&
typeof console !== 'undefined'
) {
console[console.info ? 'info' : 'log'](
`You are running Vue in development mode.\n` +
`Make sure to turn on production mode when deploying for production.\n` +
`See more tips at https://vuejs.org/guide/deployment.html`
)
}
}, 0)
}
export default Vue
可以看到该入口文件只干了两件事
重点进入了下一个阶段,到底给Vue上挂载了啥
那先来看看引入的Vue是个啥和上面本来有啥。
import Vue from 'core/index'
根据引入语句和别名解析核心文件应当在src/core/index
// src/core/index
import Vue from './instance/index'
import {
initGlobalAPI } from './global-api/index'
import {
isServerRendering } from 'core/util/env'
import {
FunctionalRenderContext } from 'core/vdom/create-functional-component'
// vue全局属性的挂载及初始化
initGlobalAPI(Vue)
// 判定是否为服务端渲染
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
// 判定ssrContext存在性
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
// expose FunctionalRenderContext for ssr runtime helper installation
// 暴露FunctionalRenderContext给ssr运行时
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
Vue.version = '__VERSION__'
export default Vue
可以看到这个文件依然是引入Vue对象然后对Vue对象进行初始化包括原型链的方法挂载,那么我们先继续追查这个Vue对象给是如何定义初始化的。跟随定义可知目录为 src/core/instance/index
// src/core/instance/index
import {
renderMixin } from './render'
import {
eventsMixin } from './events'
import {
lifecycleMixin } from './lifecycle'
import {
warn } from '../util/index'
function Vue (options) {
// 实例检测如果不是Vue实例
// 且不在生产环境下进行警告
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 使用传入参数进行初始化
this._init(options)
}
// 向Vue原型链挂载_init方法
initMixin(Vue)
// 将_data及_props、$watch挂载到vue原型对象当中
stateMixin(Vue)
// 挂载$on事件绑定
eventsMixin(Vue)
// 向Vue原型对象上挂载_update、$forceUpdate、$destroy方法
// $forceUpdate 迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件
lifecycleMixin(Vue)
// 向Vue原型对象上挂载_render和$nextTick方法
renderMixin(Vue)
export default Vue
可以看到,在这里首先是有个构造函数,然后在构造函数的原型链上添加方法及属性,包括实例基础属性的初始化、数据系统、时间系统、声明周期系统、渲染系统等。下一个部分来看看初始化的部分都做了什么。进入src/core/instance/init.js来看看。
可以看到在实例初始化的过程当中,在vue的原型链当中挂载了_init方法,该方法当中进行了uid递增,
performance标记标记,_isVue赋值,
判定实例化是否传入组件化参数,是则进行内部组件初始化,否则进行传入参数覆盖式合并构造器参数的数据。
在非生产环境对实例进行代理,在生产环境则将实例自身赋值给_renderProxy
将实例赋值给_self属性,并进行一系列初始化操作
非生产环境下存在性能属性则进行监测
实例的$options对象存在el属性则挂载
// src/core/instance/init.js
/* @flow */
import config from '../config'
import {
initProxy } from './proxy'
import {
initState } from './state'
import {
initRender } from './render'
import {
initEvents } from './events'
import {
mark, measure } from '../util/perf'
import {
initLifecycle, callHook } from './lifecycle'
import {
initProvide, initInjections } from './inject'
import {
extend, mergeOptions, formatComponentName } from '../util/index'
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// 将当前实例复制给vm
const vm: Component = this
// a uid
// 为vm赋值递增的uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
// 生产环境下,且开启performance、并支持mark时,进行标记
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${
vm._uid}`
endTag = `vue-perf-end:${
vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && 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
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
// 如果在非生产环境,初始化代理
initProxy(vm)
} else {
// 否则将自身赋值给_renderProxy属性
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${
vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
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
}
}
export function resolveConstructorOptions (Ctor: Class<Component>) {
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
}
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {
}
modified[key] = latest[key]
}
}
return modified
}
关于参数与构造器参数的合并过程:
进行子操作项的序列化过程(将props,inject,directive等可以进行简化参数传递的进行标准序列化)
如果子选项存在混入或者拓展,则将其混入和拓展的部分继续与其父操作项进行合并,
直到子项为基本数据项
对父操作项与子操作项进行数据域合并后,用子操作项覆盖式合并父操作项
// src/core/util/options.js
/* @flow */
import config from '../config'
import { warn } from './debug'
import { set } from '../observer/index'
import { unicodeRegExp } from './lang'
import { nativeWatch, hasSymbol } from './env'
import {
ASSET_TYPES,
LIFECYCLE_HOOKS
} from 'shared/constants'
import {
extend,
hasOwn,
camelize,
toRawType,
capitalize,
isBuiltInTag,
isPlainObject
} from 'shared/util'
/**
* Option overwriting strategies are functions that handle
* how to merge a parent option value and a child option
* value into the final value.
*/
const strats = config.optionMergeStrategies
/**
* Options with restrictions
*/
if (process.env.NODE_ENV !== 'production') {
strats.el = strats.propsData = function (parent, child, vm, key) {
if (!vm) {
warn(
`option "${key}" can only be used during instance ` +
'creation with the `new` keyword.'
)
}
return defaultStrat(parent, child)
}
}
/**
* Helper that recursively merges two data objects together.
*/
function mergeData (to: Object, from: ?Object): Object {
if (!from) return to
let key, toVal, fromVal
const keys = hasSymbol
? Reflect.ownKeys(from)
: Object.keys(from)
for (let i = 0; i < keys.length; i++) {
key = keys[i]
// in case the object is already observed...
if (key === '__ob__') continue
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (
toVal !== fromVal &&
isPlainObject(toVal) &&
isPlainObject(fromVal)
) {
mergeData(toVal, fromVal)
}
}
return to
}
/**
* Data
*/
export function mergeDataOrFn (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
// in a Vue.extend merge, both should be functions
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
// when parentVal & childVal are both present,
// we need to return a function that returns the
// merged result of both functions... no need to
// check if parentVal is a function here because
// it has to be a function to pass previous merges.
return function mergedDataFn () {
// 子选项覆盖式合并其父选项
return mergeData(
typeof childVal === 'function' ? childVal.call(this, this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
)
}
} else {
return function mergedInstanceDataFn () {
// instance merge
const instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
/**
* Hooks and props are merged as arrays.
*/
function mergeHook (
parentVal: ?Array,
childVal: ?Function | ?Array
): ?Array {
const res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
return res
? dedupeHooks(res)
: res
}
function dedupeHooks (hooks) {
const res = []
for (let i = 0; i < hooks.length; i++) {
if (res.indexOf(hooks[i]) === -1) {
res.push(hooks[i])
}
}
return res
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
/**
* Assets
*
* When a vm is present (instance creation), we need to do
* a three-way merge between constructor options, instance
* options and parent options.
*/
function mergeAssets (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): Object {
const res = Object.create(parentVal || null)
if (childVal) {
// 非生产环境且childVal类型为对象
process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
return extend(res, childVal)
} else {
return res
}
}
ASSET_TYPES.forEach(function (type) {
strats[type + 's'] = mergeAssets
})
/**
* Watchers.
*
* Watchers hashes should not overwrite one
* another, so we merge them as arrays.
*/
// 监测合并
strats.watch = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
// work around Firefox's Object.prototype.watch...
if (parentVal === nativeWatch) parentVal = undefined
if (childVal === nativeWatch) childVal = undefined
/* istanbul ignore if */
if (!childVal) return Object.create(parentVal || null)
if (process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)
}
if (!parentVal) return childVal
const ret = {}
extend(ret, parentVal)
for (const key in childVal) {
let parent = ret[key]
const child = childVal[key]
// 父参数存在且其不为数组,则将其赋值为数组
if (parent && !Array.isArray(parent)) {
parent = [parent]
}
// 父参数存在则将其子参数进行合并,否则返回子参数的数组形式
ret[key] = parent
? parent.concat(child)
: Array.isArray(child) ? child : [child]
}
return ret
}
/**
* Other object hashes.
*/
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
// 不在生产环境时对子选项的类型进行断言,不为对象时抛出警告
if (childVal && process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)
}
// 父选项不存在时返回子选项
if (!parentVal) return childVal
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
// 用子选项覆盖式拓展父选项
return ret
}
strats.provide = mergeDataOrFn
/**
* Default strategy.
*/
// 子项存在则返回否则返回父项
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}
/**
* Validate component names
*/
function checkComponents (options: Object) {
for (const key in options.components) {
validateComponentName(key)
}
}
export function validateComponentName (name: string) {
if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'should conform to valid custom element name in html5 specification.'
)
}
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
)
}
}
/**
* Ensure all props option syntax are normalized into the
* Object-based format.
*/
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
if (Array.isArray(props)) {
i = props.length
while (i--) {
val = props[i]
if (typeof val === 'string') {
name = camelize(val)
res[name] = { type: null }
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
for (const key in props) {
val = props[key]
name = camelize(key)
res[name] = isPlainObject(val)
? val
: { type: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "props": expected an Array or an Object, ` +
`but got ${toRawType(props)}.`,
vm
)
}
options.props = res
}
/**
* Normalize all injections into Object-based format
*/
function normalizeInject (options: Object, vm: ?Component) {
const inject = options.inject
if (!inject) return
const normalized = options.inject = {}
// 如果是数组则解析为对象形式
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] }
}
} else if (isPlainObject(inject)) {
// 如果inject本身为对象,则进行遍历解析
for (const key in inject) {
const val = inject[key]
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val }
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid value for option "inject": expected an Array or an Object, ` +
`but got ${toRawType(inject)}.`,
vm
)
}
}
/**
* Normalize raw function directives into object format.
*/
function normalizeDirectives (options: Object) {
const dirs = options.directives
if (dirs) {
for (const key in dirs) {
const def = dirs[key]
// 如果键值为function类型,则将该函数同时绑定给bind和update两个钩子函数
if (typeof def === 'function') {
dirs[key] = { bind: def, update: def }
}
}
}
}
function assertObjectType (name: string, value: any, vm: ?Component) {
if (!isPlainObject(value)) {
warn(
`Invalid value for option "${name}": expected an Object, ` +
`but got ${toRawType(value)}.`,
vm
)
}
}
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
*/
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
// 非生产环境进行组件检测
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
// 子组件为函数,则将其赋值为其options
if (typeof child === 'function') {
child = child.options
}
// 将props序列化为对象形式,进行赋值解析
normalizeProps(child, vm)
// inject序列化
normalizeInject(child, vm)
// directive序列化,解析其bind和upload的简写形式
normalizeDirectives(child)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
// 如果子项_base为false且存在extends,则遍历解析父项
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
// 如果不为基础项,且存在混入,则遍历后递归解析父项
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < 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
}
/**
* Resolve an asset.
* This function is used because child instances need access
* to assets defined in its ancestor chain.
*/
export function resolveAsset (
options: Object,
type: string,
id: string,
warnMissing?: boolean
): any {
/* istanbul ignore if */
if (typeof id !== 'string') {
return
}
const assets = options[type]
// check local registration variations first
if (hasOwn(assets, id)) return assets[id]
const camelizedId = camelize(id)
if (hasOwn(assets, camelizedId)) return assets[camelizedId]
const PascalCaseId = capitalize(camelizedId)
if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
// fallback to prototype chain
const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
warn(
'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
options
)
}
return res
}
开发环境进行实例对象的代理,会对不合理的数据调用进行错误抛出,具体过程如下:
如果存在内置对象Proxy,则对实例对象进行代理,否则返回实例对象
如果参数中存在render,则进行取值拦截,否则进行遍历拦截
遍历代理的句柄为:如果值存在于目标对象上且不为保留属性,则返回,否则根据情况报错
取值代理的句柄为:如果属性存在于 d a t a 中 则 抛 出 保 留 前 缀 错 误 , 取 不 到 且 不 存 在 data中则抛出保留前缀错误,取不到且不存在 data中则抛出保留前缀错误,取不到且不存在data中则进行不存在报错,可以取值时直接返回
// src/core/instance/proxy.js
/* not type checking this file because flow doesn't play well with Proxy */
import config from 'core/config'
import {
warn, makeMap, isNative } from '../util/index'
let initProxy
if (process.env.NODE_ENV !== 'production') {
const allowedGlobals = makeMap(
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
'require' // for Webpack/Browserify
)
const warnNonPresent = (target, key) => {
warn(
`Property or method "${
key}" is not defined on the instance but ` +
'referenced during render. Make sure that this property is reactive, ' +
'either in the data option, or for class-based components, by ' +
'initializing the property. ' +
'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
target
)
}
const warnReservedPrefix = (target, key) => {
warn(
`Property "${
key}" must be accessed with "$data.${
key}" because ` +
'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
'prevent conflicts with Vue internals' +
'See: https://vuejs.org/v2/api/#data',
target
)
}
const hasProxy =
typeof Proxy !== 'undefined' && isNative(Proxy)
if (hasProxy) {
// 将字符串中的数据转为数组
const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
config.keyCodes = new Proxy(config.keyCodes, {
// 赋值拦截
set (target, key, value) {
if (isBuiltInModifier(key)) {
// 如果赋值的属性在isBuiltInModifier中则进行报错
warn(`Avoid overwriting built-in modifier in config.keyCodes: .${
key}`)
return false
} else {
// 成功赋值
target[key] = value
return true
}
}
})
}
const hasHandler = {
// 遍历拦截
has (target, key) {
// 判断key是否存在于目标当中
const has = key in target
// 如果为保留的属性 或者为开头为_且目标$data中不存在的属性
const isAllowed = allowedGlobals(key) ||
(typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
// 既不在目标对象及其原型链上,也不是被允许的key
if (!has && !isAllowed) {
// 如果key存在于目标$data, 抛出保留前缀警告
if (key in target.$data) warnReservedPrefix(target, key)
// 抛出非存在性警告
else warnNonPresent(target, key)
}
// 存在且不为保留属性的可被遍历
return has || !isAllowed
}
}
const getHandler = {
// 取值拦截
get (target, key) {
// 如果数据key为字符串,且在目标中无法取得
if (typeof key === 'string' && !(key in target)) {
// 如果key存在于目标$data, 抛出保留前缀警告
if (key in target.$data) warnReservedPrefix(target, key)
// 抛出非存在性警告
else warnNonPresent(target, key)
}
// 取出数据
return target[key]
}
}
initProxy = function initProxy (vm) {
// Proxy为内置对象
if (hasProxy) {
// determine which proxy handler to use
const options = vm.$options
// render且render的_withStripped属性存在 则拦截句柄为取值拦截,否则为遍历拦截
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
// 对vm进行代理
vm._renderProxy = new Proxy(vm, handlers)
} else {
// proxy不存在则将_renderProxy属性设置为其vm实例自身
vm._renderProxy = vm
}
}
}
export {
initProxy }
对以上内容做一总结: