【源码共读】Vue2 中为什么可以使用 this 访问各种选项中的属性?

news2024/12/22 22:04:52

如何阅读源码

网上有很多关于源码阅读的文章,每个人都有自己的方式,但是网上的文章都是精炼之后的,告诉你哪个文件、那个函数、那个变量是干什么的;

但是没有告诉你这些是怎么找到的,这些是怎么理解的,这些是怎么验证的,这些是怎么记忆的,这些是怎么应用的。

我也不是什么大神,也是在摸索的过程中,逐渐找到了自己的方式,我这里就分享一下我的方式,希望能帮助到大家。

怎么找到起点

万事开头难,找到起点是最难的,对于前端项目,我们想要找到入口文件,一般都是从package.json中的main字段开始找;

package.json中的main字段代表的是这个包的入口文件,通常我们可以通过这个字段的值来找到我们要阅读的起点。

但是对于Vue来说,这个字段是dist/vue.runtime.common.js,这个文件是编译后的文件,我们是看不懂的,所以需要找到源码的入口文件;

这个时候我们就需要看package.json中的scripts字段:

{
"scripts": {"dev": "rollup -w -c scripts/config.js --environment TARGET:full-dev","dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:runtime-cjs-dev","dev:esm": "rollup -w -c scripts/config.js --environment TARGET:runtime-esm","dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:server-renderer","dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:compiler ","build": "node scripts/build.js","build:ssr": "npm run build -- runtime-cjs,server-renderer","build:types": "rimraf temp && tsc --declaration --emitDeclarationOnly --outDir temp && api-extractor run && api-extractor run -c packages/compiler-sfc/api-extractor.json","test": "npm run ts-check && npm run test:types && npm run test:unit && npm run test:e2e && npm run test:ssr && npm run test:sfc","test:unit": "vitest run test/unit","test:ssr": "npm run build:ssr && vitest run server-renderer","test:sfc": "vitest run compiler-sfc","test:e2e": "npm run build -- full-prod,server-renderer-basic && vitest run test/e2e","test:transition": "karma start test/transition/karma.conf.js","test:types": "npm run build:types && tsc -p ./types/tsconfig.json","format": "prettier --write --parser typescript "(src|test|packages|types)/**/*.ts"","ts-check": "tsc -p tsconfig.json --noEmit","ts-check:test": "tsc -p test/tsconfig.json --noEmit","bench:ssr": "npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js","release": "node scripts/release.js","changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"}
 } 

可以看到Vuepackage.json中有很多的scripts,这些相信大家都可以看得懂,这里我们只关注devbuild这两个脚本;

dev脚本是用来开发的,build脚本是用来打包的,我们可以看到dev脚本中有一个TARGET的环境变量,这个环境变量的值是full-dev,我们可以在scripts/config.js中找到这个值;

直接在scripts/config.js中搜索full-dev

这样就可以找到这个值对应的配置:

var config = {'full-dev': {entry: resolve('web/entry-runtime-with-compiler.ts'),dest: resolve('dist/vue.js'),format: 'umd',env: 'development',alias: { he: './entity-decoder' },banner}
} 

entry字段就是我们要找的入口文件,这个文件就是Vue的源码入口文件,后面的值是web/entry-runtime-with-compiler.ts,我们可以在web目录下找到这个文件;

但是并没有在根目录下找到web目录,这个时候我们就大胆猜测,是不是有别名配置,这个时候我也正好在scripts下看到了一个alias.js文件,打开这个文件,发现里面有一个web的别名;

代码如下:

module.exports = {vue: resolve('src/platforms/web/entry-runtime-with-compiler'),compiler: resolve('src/compiler'),core: resolve('src/core'),web: resolve('src/platforms/web'),weex: resolve('src/platforms/weex'),shared: resolve('src/shared')
} 

为了验证我们的猜测,我们可以在config.js中搜一下alias,发现确实有引入这个文件:

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)}
} 

再搜一下aliases,发现确实有配置别名:

// 省略部分代码
const config = {plugins: [alias({entries: Object.assign({}, aliases, opts.alias)}),].concat(opts.plugins || []),
} 

这样我们就可以确认,web就是src/platforms/web这个目录,我们可以在这个目录下找到entry-runtime-with-compiler.ts这个文件;

这样我们就成功的找到了Vue的源码入口文件,接下来我们就可以开始阅读源码了;

如何阅读源码

上面找到了入口文件,但是还是不知道如何阅读源码,这个时候我们就需要一些技巧了,这里我就分享一下我自己的阅读源码的技巧;

像我们现在看的源码几乎都是使用esm模块化或者commonjs模块化的,这些都会有一个export或者module.exports,我们可以通过这个来看导出了什么;

只看导出的内容,其他的暂时不用管,直接找到最终导出的内容,例如Vue的源码:

1.entry-runtime-with-compiler.ts的导出内容:

import Vue from './runtime-with-compiler'

export default Vue 

这个时候就去找runtime-with-compiler.ts的导出内容:

2.runtime-with-compiler.ts的导出内容:

import Vue from './runtime/index'

export default Vue as GlobalAPI 

这个时候就去找runtime/index.ts的导出内容:

3.runtime/index.ts的导出内容:

import Vue from 'core/index'

export default Vue 

这个时候就去找core/index.ts的导出内容:

4.core/index.ts的导出内容:

import Vue from './instance/index'

export default Vue 

这个时候就去找instance/index.ts的导出内容:

5.instance/index.ts的导出内容:

function Vue(options) {if (__DEV__ && !(this instanceof Vue)) {warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}

export default Vue as unknown as GlobalAPI 

这样我们就找到Vue的构造函数了,这个时候我们就可以开始阅读源码了;

带有目的的阅读源码

阅读源码的目的一定要清晰,当然你可以说目的就是了解Vue的实现原理,但是这个目的太宽泛了,我们可以把目的细化一下,例如:

1.Vue的生命周期是怎么实现的
2.Vue的数据响应式是怎么实现的
3.Vue的模板编译是怎么实现的
4.Vue的组件化是怎么实现的
5.Vue的插槽是怎么实现的
6.等等…

例如我们的这次阅读计划就是了解Vuethis为什么可以访问到选项中的各种属性,这里再细分为:

1.Vuethis是怎么访问到data
2.Vuethis是怎么访问到methods
3.Vuethis是怎么访问到computed
4.Vuethis是怎么访问到props

上面顺序不分先后,但是答案一定是在源码中。

源码分析

上面已经找到了Vue的入口文件,接下来我们就可以开始阅读源码了,这里我就以Vuethis为什么可以访问到选项中的各种属性为例,来分析Vue的源码;

首先看一下instance/index.ts的源码:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
import type { GlobalAPI } from 'types/global-api'

function Vue(options) {if (__DEV__ && !(this instanceof Vue)) {warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}

//@ts-expect-error Vue has function type
initMixin(Vue)
//@ts-expect-error Vue has function type
stateMixin(Vue)
//@ts-expect-error Vue has function type
eventsMixin(Vue)
//@ts-expect-error Vue has function type
lifecycleMixin(Vue)
//@ts-expect-error Vue has function type
renderMixin(Vue)

export default Vue as unknown as GlobalAPI 

有这么多东西,我们不用管,要清晰目的,我们在使用Vue的时候,通常是下面这样的:

const vm = new Vue({data() {return {msg: 'hello world'}},methods: {say() {console.log(this.msg)}}
});

vm.say(); 

也就是Vue的构造函数接收一个选项对象,这个选项对象中有datamethods

我们要知道Vuethis为什么可以访问到datamethods,那么我们就要找到Vue的构造函数中是怎么把datamethods挂载到this上的;

很明显构造函数只做了一件事,就是调用了this._init(options)

this._init(options) 

那么我们就去找_init方法,这个方法在哪我们不知道,但是继续分析源码,我们可以看到下面会执行很多xxxMixin的函数,并且Vue作为参数传入:

//@ts-expect-error Vue has function type
initMixin(Vue)
//@ts-expect-error Vue has function type
stateMixin(Vue)
//@ts-expect-error Vue has function type
eventsMixin(Vue)
//@ts-expect-error Vue has function type
lifecycleMixin(Vue)
//@ts-expect-error Vue has function type
renderMixin(Vue) 

盲猜一波,见名知意:

1.initMixin:初始化混入
2.stateMixin:状态混入
3.eventsMixin:事件混入
4.lifecycleMixin:生命周期混入
5.renderMixin:渲染混入

我们就去找这些混入的方法,一个一个的找,找到initMixin,直接就找了_init方法:

export function initMixin(Vue: typeof Component) {Vue.prototype._init = function (options?: Record<string, any>) {const vm: Component = this// a uidvm._uid = uid++let startTag, endTag/* istanbul ignore if */if (__DEV__ && config.performance && mark) {startTag = `vue-perf-start:${vm._uid}`endTag = `vue-perf-end:${vm._uid}`mark(startTag)}// a flag to mark this as a Vue instance without having to do instanceof// checkvm._isVue = true// avoid instances from being observedvm.__v_skip = true// effect scopevm._scope = new EffectScope(true /* detached */)vm._scope._vm = true// merge optionsif (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 as any)} else {vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor as any),options || {},vm)}/* istanbul ignore else */if (__DEV__) {initProxy(vm)} else {vm._renderProxy = vm}// expose real selfvm._self = vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate', undefined, false /* setContext */)initInjections(vm) // resolve injections before data/propsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(vm, 'created')/* istanbul ignore if */if (__DEV__ && 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)}}
} 

代码这么多没必要全都看,记住我们的目的是找到datamethods是怎么挂载到this上的;

先简化代码,不看没有意义的代码:

export function initMixin(Vue) {Vue.prototype._init = function (options) {const vm = this}
} 

传递过来的Vue并没有做太多事情,只是把_init方法挂载到了Vue.prototype上;

_init方法中,vm被赋值为this,这里的this就是Vue的实例,也就是我们的vm

继续往下看,我们有目的的看代码,只需要看有vmoptions组合出现的代码,于是就看到了:

if (options && options._isComponent) {initInternalComponent(vm, options)
} else {vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)
} 

_isComponent前面带有_,说明是私有属性,我们通过new Vue创建的实例时走到现在是没有这个属性的,所以走到else分支;

resolveConstructorOptions(vm.constructor)中没有传递options,所以不看这个方法,直接看mergeOptions

export function mergeOptions(parent, child, vm) {if (__DEV__) {checkComponents(child)}if (isFunction(child)) {// @ts-expect-errorchild = child.options}normalizeProps(child, vm)normalizeInject(child, vm)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) {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 keyfor (key in parent) {mergeField(key)}for (key in child) {if (!hasOwn(parent, key)) {mergeField(key)}}function mergeField(key) {const strat = strats[key] || defaultStratoptions[key] = strat(parent[key], child[key], vm, key)}return options
} 

记住我们的目的,只需要关心vmoptions组合出现的代码,child就是optionsvm就是vm,简化之后:

export function mergeOptions(parent, child, vm) {normalizeProps(child, vm)normalizeInject(child, vm)normalizeDirectives(child)return options
} 

可以看到只剩下了normalizePropsnormalizeInjectnormalizeDirectives这三个方法,值得我们关注,但是见名知意,这三个方法可能并不是我们想要的,跟进去看一眼也确实不是;

虽然没有得到我们想要的,但是从这里我们也得到了一个重要信息,mergeOptions最后会返回一个options对象,这个对象就是我们的options,最后被vm.$options接收;

vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm) 

现在我们分析要多一步了,参数只有vm的函数也是需要引起我们的注意的,继续往下看:

if (__DEV__) {initProxy(vm)
} else {vm._renderProxy = vm
} 

操作了vm,但是内部没有操作$options,跳过,继续往下看:

initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') 

initLifecycleinitEventsinitRenderinitInjectionsinitStateinitProvide这些方法都是操作vm的;

盲猜一波:

  • initLifecycle:初始化生命周期
  • initEvents:初始化事件
  • initRender:初始化渲染
  • initInjections:初始化注入
  • initState:初始化状态
  • initProvide:初始化依赖注入
  • callHook:调用钩子

这里面最有可能是我们想要的是initState,跟进去看一下:

export function initState(vm) {const opts = vm.$optionsif (opts.props) initProps(vm, opts.props)// Composition APIinitSetup(vm)if (opts.methods) initMethods(vm, opts.methods)if (opts.data) {initData(vm)} else {const ob = observe((vm._data = {}))ob && ob.vmCount++}if (opts.computed) initComputed(vm, opts.computed)if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}
} 

已经找到我们想要的了,现在开始正式分析initState

initState

根据代码结构可以看到,initState主要做了以下几件事:

  • 初始化props
  • 初始化setup
  • 初始化methods
  • 初始化data
  • 初始化computed
  • 初始化watch

我们可以用this来访问的属性是propsmethodsdatacomputed

看到这里也明白了,为什么在props中定义了一个属性,在datamethodscomputed中就不能再定义了,因为props是最先初始化的,后面的也是同理。

initProps

initProps的作用是初始化props,跟进去看一下:

function initProps(vm, propsOptions) {const propsData = vm.$options.propsData || {}const props = (vm._props = shallowReactive({}))// cache prop keys so that future props updates can iterate using Array// instead of dynamic object key enumeration.const keys = (vm.$options._propKeys = [])const isRoot = !vm.$parent// root instance props should be convertedif (!isRoot) {toggleObserving(false)}for (const key in propsOptions) {keys.push(key)const value = validateProp(key, propsOptions, propsData, vm)/* istanbul ignore else */if (__DEV__) {const hyphenatedKey = hyphenate(key)if (isReservedAttribute(hyphenatedKey) ||config.isReservedAttr(hyphenatedKey)) {warn(`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,vm)}defineReactive(props, key, value, () => {if (!isRoot && !isUpdatingChildComponent) {warn(`Avoid mutating a prop directly since the value will be ` +`overwritten whenever the parent component re-renders. ` +`Instead, use a data or computed property based on the prop's ` +`value. Prop being mutated: "${key}"`,vm)}})} else {defineReactive(props, key, value)}// static props are already proxied on the component's prototype// during Vue.extend(). We only need to proxy props defined at// instantiation here.if (!(key in vm)) {proxy(vm, `_props`, key)}}toggleObserving(true)
} 

代码很多,我们依然不用关心其他的代码,只关心props是怎么挂载到vm上的,根据我上面的方法,简化后的代码如下:

function initProps(vm, propsOptions) {vm._props = shallowReactive({})for (const key in propsOptions) {const value = validateProp(key, propsOptions, propsData, vm)if (!(key in vm)) {proxy(vm, `_props`, key)}}
} 

这里真正有关的就两个地方:

1.validateProp:看名字就知道是验证props,跳过
2.proxy:代理,很可疑,跟进去看一下:

export function proxy(target, sourceKey, key) {sharedPropertyDefinition.get = function proxyGetter() {return this[sourceKey][key]}sharedPropertyDefinition.set = function proxySetter(val) {this[sourceKey][key] = val}Object.defineProperty(target, key, sharedPropertyDefinition)
} 

这里的target就是vmsourceKey就是_propskey就是props的属性名;

这里通过Object.definePropertyvm的属性代理到_props上,这样就可以通过this访问到props了。

不是很好理解,那我们来自己就用这些代码实现一下:

var options = {props: {name: {type: String,default: 'default name'}}
}

function Vue(options) {const vm = thisinitProps(vm, options.props)
}

function initProps(vm, propsOptions) {vm._props = {}for (const key in propsOptions) {proxy(vm, `_props`, key)}
}

function proxy(target, sourceKey, key) {Object.defineProperty(target, key, {get() {return this[sourceKey][key]},set(val) {this[sourceKey][key] = val}})
}

const vm = new Vue(options)
console.log(vm.name);
console.log(vm._props.name);

vm.name = 'name'

console.log(vm.name);
console.log(vm._props.name); 

上面的代码只是为了方便理解,所以会忽略一些细节,比如props的验证等等,真实挂载在_props上的props是通过defineReactive实现的,我这里直接是空的,这些超出了本文的范围。

initMethods

initMethods的代码如下:

function initMethods(vm, methods) {const props = vm.$options.propsfor (const key in methods) {if (__DEV__) {if (typeof methods[key] !== 'function') {warn(`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +`Did you reference the function correctly?`,vm)}if (props && hasOwn(props, key)) {warn(`Method "${key}" has already been defined as a prop.`, vm)}if (key in vm && isReserved(key)) {warn(`Method "${key}" conflicts with an existing Vue instance method. ` +`Avoid defining component methods that start with _ or $.`)}}vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)}
} 

跟着之前的思路,我们忽略无关代码,简化后的代码如下:

function initMethods(vm, methods) {for (const key in methods) {vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)}
} 

这里的vm[key]就是methods的方法,这样就可以通过this访问到methods中定义的方法了。

bind的作用是把methods中定义的函数的this指向vm,这样就可以在methods中使用this就是vm了。

简单的实现一下:

var options = {methods: {say() {console.log('say');}}
}

function Vue(options) {const vm = thisinitMethods(vm, options.methods)
}

function initMethods(vm, methods) {for (const key in methods) {vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)}
}

function noop() {}

function polyfillBind(fn, ctx) {function boundFn(a) {const l = arguments.lengthreturn l? l > 1? fn.apply(ctx, arguments): fn.call(ctx, a): fn.call(ctx)}boundFn._length = fn.lengthreturn boundFn
}

function nativeBind(fn, ctx) {return fn.bind(ctx)
}

const bind = Function.prototype.bind ? nativeBind : polyfillBind

const vm = new Vue(options)
vm.say() 

initData

initData的代码如下:

function initData(vm) {let data = vm.$options.datadata = vm._data = isFunction(data) ? getData(data, vm) : data || {}if (<img src="https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (__DEV__) {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`, vm)}}if (props && hasOwn(props, key)) {__DEV__ &&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 dataconst ob = observe(data)ob && ob.vmCount+" style="margin: auto" />
} 

简化之后的代码如下:

function initData(vm) {let data = vm.$options.data// proxy data on instanceconst keys = Object.keys(data)let i = keys.lengthwhile (i--) {const key = keys[i]proxy(vm, `_data`, key)}
} 

这里的实现方式和initProps是一样的,都是通过proxydata中的属性代理到vm上。

注意:initData的获取值的地方是其他的不相同,这里只做提醒,不做详细分析。

initComputed

initComputed的代码如下:

function initComputed(vm, computed) {// $flow-disable-lineconst watchers = (vm._computedWatchers = Object.create(null))// computed properties are just getters during SSRconst isSSR = isServerRendering()for (const key in computed) {const userDef = computed[key]const getter = isFunction(userDef) ? userDef : userDef.getif (__DEV__ && getter == null) {warn(`Getter is missing for computed property "${key}".`, vm)}if (!isSSR) {// create internal watcher for the computed property.watchers[key] = new Watcher(vm,getter || noop,noop,computedWatcherOptions)}// 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 (__DEV__) {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)} else if (vm.$options.methods && key in vm.$options.methods) {warn(`The computed property "${key}" is already defined as a method.`,vm)}}}
} 

简化之后的代码如下:

function initComputed(vm, computed) {for (const key in computed) {const userDef = computed[key]const getter = userDefdefineComputed(vm, key, userDef)}
} 

这里的实现主要是通过defineComputed来定义computed属性,进去瞅瞅:

export function defineComputed(target, key, userDef) {const shouldCache = !isServerRendering()if (isFunction(userDef)) {sharedPropertyDefinition.get = shouldCache? createComputedGetter(key): createGetterInvoker(userDef)sharedPropertyDefinition.set = noop} else {sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): createGetterInvoker(userDef.get): noopsharedPropertyDefinition.set = userDef.set || noop}if (__DEV__ && sharedPropertyDefinition.set === noop) {sharedPropertyDefinition.set = function () {warn(`Computed property "${key}" was assigned to but it has no setter.`,this)}}Object.defineProperty(target, key, sharedPropertyDefinition)
} 

仔细看下来,其实实现方式还是和initPropsinitData一样,都是通过Object.defineProperty来定义属性;

不过里面的gettersetter是通过createComputedGettercreateGetterInvoker来创建的,这里不做过多分析。

动手时间

上面我们已经分析了propsmethodsdatacomputed的属性为什么可以直接通过this来访问,那么我们现在就来实现一下这个功能。

上面已经简单了实现了initPropsinitMethods,而initDatainitComputed的实现方式和initProps的方式一样,所以我们直接复用就好了:

function Vue(options) {this._init(options)
}

Vue.prototype._init = function (options) {const vm = thisvm.$options = optionsinitState(vm)
}

function initState(vm) {const opts = vm.$optionsif (opts.props) initProps(vm, opts.props)if (opts.methods) initMethods(vm, opts.methods)if (opts.data) initData(vm)if (opts.computed) initComputed(vm, opts.computed)
}

function initProps(vm, propsOptions) {vm._props = {}for (const key in propsOptions) {vm._props[key] = propsOptions[key].defaultproxy(vm, `_props`, key)}
}

function proxy(target, sourceKey, key) {Object.defineProperty(target, key, {get() {return this[sourceKey][key]},set(val) {this[sourceKey][key] = val}})
}

function initMethods(vm, methods) {for (const key in methods) {vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)}
}

function noop() {}

function polyfillBind(fn, ctx) {function boundFn(a) {const l = arguments.lengthreturn l? l > 1? fn.apply(ctx, arguments): fn.call(ctx, a): fn.call(ctx)}boundFn._length = fn.lengthreturn boundFn
}

function nativeBind(fn, ctx) {return fn.bind(ctx)
}

const bind = Function.prototype.bind ? nativeBind : polyfillBind

function initData(vm) {vm._data = {}for (const key in vm.$options.data) {vm._data[key] = vm.$options.data[key]proxy(vm, `_data`, key)}
}

function initComputed(vm, computed) {for (const key in computed) {const userDef = computed[key]const getter = userDefdefineComputed(vm, key, bind(userDef, vm))}
}

function defineComputed(target, key, userDef) {Object.defineProperty(target, key, {get() {return userDef()},})
}

const vm = new Vue({props: {a: {type: String,default: 'default'}},data: {b: 1},methods: {c() {console.log(this.b)}},computed: {d() {return this.b + 1}}
})

console.log('props a: default',vm.a)
console.log('data b: 1', vm.b)
vm.c() // 1
console.log('computed d: 2', vm.d) 

注意:上面的代码对比于文章中写的示例有改动,主要是为了实现最后打印结果正确,增加了赋值操作。

总结

通过上面的分析,让我们对构造函数的this有了更深的理解,同时对于this指向的问题也有了更深的理解。

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/111838.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

港科夜闻|叶玉如校长回应「香港创科发展蓝图」

关注并星标每周阅读港科夜闻建立新视野 开启新思维1、香港科大校长叶玉如教授回应「香港创科发展蓝图」。近日&#xff0c;粤港澳大湾区院士联盟表示希望特区政府切实落实「蓝图」内容&#xff0c;设立具体行动措施和可量化的指标&#xff0c;以更大的魄力和决心实现当中的目标…

C++11标准模板(STL)- 算法(std::iota)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 用从起始值开始连续递增的…

10.1、Django框架入门--后台管理

文章目录预备知识MVC模式和MTV模式MVC模式MTV 模式Django框架Django框架简介Django框架中的后台管理启动后台admin站点管理数据库迁移创建管理员用户管理界面本地化创建并使用一个应用bookapp项目的数据库模型创建数据库模型生成数据库表数据库上的基本操作启用后台admin站点管…

【源码共读】如何优雅的处理 Promise 的错误

Promise解决了优雅的解决了回调地域的问题&#xff0c;现在已经大范围的使用Promise&#xff0c;但是在使用Promise的过程中&#xff0c;最令人头疼的就是错误处理的方式。 Promise 的错误处理方式 据我对Promise的了解&#xff0c;Promise的错误处理分为下面的几种方式&…

324页13万字高校数字化校园大数据中心及大数据平台建设方案

一、 数据中心总体规划 云资源中心加大数据分析与高性能主要分为计算资源、内存资源、存储资源、网络资源&#xff0c;大数据分析系统&#xff0c;高性能作业调度系统&#xff0c;本项目在充分整合XXX高校数据中心资源的基础上&#xff0c;配置必要软硬件设备&#xff0c;为XXX…

我国军靴行业现状分析:两大利好因素推动市场良好发展

根据观研报告网发布的《中国军靴市场发展趋势分析与投资前景预测报告&#xff08;2022-2029年&#xff09;》显示&#xff0c;军靴是指供军事单位在行军&#xff0c;作战时穿着的鞋靴&#xff0c;一般采用头层牛皮制作&#xff0c;具有耐用、舒适以及良好的防水透气效果特点。军…

对Python的学习【如何查看路径和安装包】

1&#xff1a;怎么查看本地电脑的Python版本号及安装路径&#xff1a; 对于Windows平台&#xff0c;打开cmd 使用命令py -0p 【其中0是零】 显示已安装的 python 版本且带路径的列表&#xff0c;参见下图&#xff1a; 其中带星号*的为默认版本。 2:怎么查看python pip…

Fragment

Fragment简单认识 1.简介 在大屏幕设备上支持更加动态和灵活的UI设计就是一种卡片的设计思路一个Activity可以有多个Fragment&#xff0c;一个Fragment可以被多个Activity使用可以进行动态的添加&#xff0c;替换和删除Fragment有着自己的生命周期&#xff0c;同时受到Activity…

python循环语句(三)

一.while 循环 条件满足无限执行 (1) 定义格式 while 条件&#xff1a;条件为True时重复执行# 写法要求与if语句类似使用示例&#xff1a; i 0 while i < 100:print("观止study")i 1 # 等效于 i i 1 # 需要设置循环终止的条件&#xff0c;如i 1配合 i <…

【Pandas入门教程】如何读取和写入表格数据

如何读取和写入表格数据 来源&#xff1a;Pandas官网&#xff1a;https://pandas.pydata.org/docs/getting_started/intro_tutorials/index.html 文章目录如何读取和写入表格数据导包【1】如何读取和写入表格数据【2】小结导包 import pandas as pd数据介绍&#xff1a; 使用存…

【信管5.2】估算活动资源与持续时间

估算活动资源与持续时间在经过上次课程的学习后&#xff0c;我们已经了解到了进度、活动的概念及定义&#xff0c;并且简单地学习了下活动顺序如何排列的一些工具技术。今天&#xff0c;我们学习的主要方向是估算活动资源与估算活动持续时间这两个过程&#xff0c;另外我们还会…

python:写你的第一个爬虫代码

什么是爬虫 爬虫spider&#xff0c;是指向网站或者网络发出请求&#xff0c;获取资源后分析并提取对自己有用的数据的程序。 request&#xff1a;是指用户将自己的信息通过浏览器发送给服务器。 response&#xff1a;服务器收到用户的请求分析后&#xff0c;返回的数据。 注意&…

【Pandas入门教程】如何选择DataFrame的子集

如何选择DataFrame的子集 来源&#xff1a;Pandas官网&#xff1a;https://pandas.pydata.org/docs/getting_started/intro_tutorials/index.html 文章目录如何选择DataFrame的子集导包数据准备【1】如何从DataFrame中选择特定列&#xff1f;【2】如何从DataFrame中筛选特定行【…

读研2年,我选择从中科院退学转行做码农

从入学天坑材料专业到退学 先自我介绍一下&#xff1a;我天坑材料专业&#xff0c;本科某211&#xff0c;保研到中科院&#xff0c;但是我真是菜的抠脚&#xff0c;还懒&#xff0c;也不喜欢科研&#xff0c;论文达不到毕业要求&#xff0c;纠结之下研三退学转码农了。 读了2…

基于51单片机的电子秤(5KG+上限报警)

电子秤5KG上限警报设计 原理图&#xff1a; 程序运行图&#xff1a; 部分程序&#xff1a; #include "main.h" #include "LCD1602.h" #include "HX711.h" #include "EEPROM.H" //定义变量 unsigned char KEY_NUM 0; //用来存放按…

Java并发——ThreadLocal总结

概述 并发问题&#xff0c;有时候&#xff0c;可以用ThreadLocal方式来避免。 ThreadLocal&#xff0c;顾名思义&#xff0c;就是线程自己的&#xff0c;独享的&#xff0c;就像线程栈是线程独享的一样。 本文讨论三点&#xff1a; 基本用法设计原理父子线程 基础用法 考虑…

JMS规范及落地产品

目录 一、JMS是什么 二、MQ中间件的其他落地产品 三、JMS的组成结构和特点 四、JMS的可靠性 &#xff08;重要&#xff09; &#xff08;一&#xff09;PERSISTENT&#xff1a;持久性 &#xff08;二&#xff09;事务 &#xff08;三&#xff09;Acknowledge&#xff1a…

C#,图像二值化(01)——二值化算法综述与二十三种算法目录

图像二值化&#xff0c;就是把彩色&#xff08;先转为灰色图&#xff09;最终转为黑白两色图片的计算过程。 看似极其简单&#xff0c;但人们研究了几十年&#xff0c;却始终未达到至臻境界的问题。 本文简要介绍了 图像二值化的算法原理、分类及二十三种算法的目录&#xff…

freeswitch的gateway配置方案

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 在voip的网络模型中&#xff0c;网关是我们经常会遇到的概念。 在freeswitch中&#xff0c;如何配置gateway&#xff0c;如何使用好gateway的模型和功能。 本节简单介绍fs中gateway相关的配置方案。 环境 centos&am…

csp-202209

202209题目一&#xff1a;如此编码【100分】题目二&#xff1a;何以包邮&#xff1f;【100分】题目三&#xff1a;防疫大数据【100分】题目一&#xff1a;如此编码【100分】 比较简单的题&#xff0c;根据题意计算一遍就行 一定要关注csp题目中的提示&#xff0c;这个是很有用…