亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

目錄
如何閱讀原始碼
怎麼找到起點(diǎn)
如何閱讀源碼
帶有目的的閱讀源碼
源碼分析
initState
initProps
initMethods
initData
initComputed
動手時(shí)間
總結(jié)
首頁 web前端 Vue.js 聊聊Vue2為什麼能透過this存取各種選項(xiàng)中屬性

聊聊Vue2為什麼能透過this存取各種選項(xiàng)中屬性

Dec 08, 2022 pm 08:22 PM
vue vue.js this

這篇文章帶大家解讀vue原始碼,來介紹一下Vue2中為什麼可以使用 this 存取各種選項(xiàng)中的屬性,希望對大家有幫助!

聊聊Vue2為什麼能透過this存取各種選項(xiàng)中屬性

下方的如何閱讀原始碼不感興趣可以不用看,可以透過這個(gè)直接定位到【原始碼分析

如何閱讀原始碼

網(wǎng)路上有很多關(guān)於原始碼閱讀的文章,每個(gè)人都有自己的方式,但是網(wǎng)路上的文章都是精煉之後的,告訴你哪個(gè)文件、那個(gè)函數(shù)、那個(gè)變數(shù)是做什麼的;【相關(guān)推薦:vuejs影片教學(xué)web前端開發(fā)

但是沒有告訴你這些是怎麼找到的,這些是怎麼理解的,這些是怎麼驗(yàn)證的,這些是怎麼記憶的,這些是怎麼應(yīng)用的。

我也不是什麼大神,也是在摸索的過程中,逐漸找到了自己的方式,我在這裡就分享一下我的方式,希望能幫助到大家。

怎麼找到起點(diǎn)

萬事開始很難,找到起點(diǎn)是最難的,對於前端項(xiàng)目,我們想要找到入口文件,一般都是從 package.json中的main欄位開始找;

package.json中的main欄位代表的是這個(gè)套件的入口文件,通常我們可以透過這個(gè)欄位的值來找到我們要閱讀的起點(diǎn)。

但是對於Vue來說,這個(gè)欄位是dist/vue.runtime.common.js,這個(gè)文件是編譯後的文件,我們是看不懂的,所以需要找到原始碼的入口檔案;

這時(shí)候我們就需要看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這兩個(gè)腳本;

dev腳本是用來開發(fā)的,build腳本是用來打包的,我們可以看到dev腳本中有一個(gè)TARGET的環(huán)境變量,這個(gè)環(huán)境變數(shù)的值是full-dev,我們可以在scripts/config. js中找到這個(gè)值;

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

聊聊Vue2為什麼能透過this存取各種選項(xiàng)中屬性

這樣就可以找到這個(gè)值對應(yīng)的配置:

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字段就是我們要找的入口文件,這個(gè)文件就是Vue的源碼入口文件,後面的值是web/entry-runtime-with-compiler.ts,我們可以在web目錄下找到這個(gè)文件;

但是並沒有在根目錄下找到web目錄,這時(shí)候我們就大膽猜測,是不是有別名配置,這時(shí)候我也剛好在scripts下看到了一個(gè)alias.js 文件,開啟這個(gè)文件,發(fā)現(xiàn)裡面有一個(gè)web的別名;

聊聊Vue2為什麼能透過this存取各種選項(xiàng)中屬性

#程式碼如下:

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

為了驗(yàn)證我們的猜測,我們可以在config.js中搜尋一下alias,發(fā)現(xiàn)確實(shí)有引入這個(gè)檔案:

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 ,發(fā)現(xiàn)確實(shí)有配置別名:

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

這樣我們就可以確認(rèn),web就是src/platforms/web這個(gè)目錄,我們可以在這個(gè)目錄下找到entry-runtime-with-compiler.ts這個(gè)文件;

聊聊Vue2為什麼能透過this存取各種選項(xiàng)中屬性

#這樣我們就成功的找到了Vue的源碼入口文件,接下來我們就可以開始閱讀源碼了;

如何閱讀源碼

#上面找到了入口文件,但是還是不知道如何閱讀源碼,這個(gè)時(shí)候我們就需要一些技巧了,這裡我就分享一下我自己的閱讀源碼的技巧;

像我們現(xiàn)在看的源碼幾乎都是使用esm模組化或commonjs模組化的,這些都會有一個(gè)exportmodule.exports,我們可以透過這個(gè)來看導(dǎo)出了什麼;

只看導(dǎo)出的內(nèi)容,其他的暫時(shí)不用管,直接找到最終導(dǎo)出的內(nèi)容,例如Vue的源碼:

  • entry-runtime-with-compiler.ts的導(dǎo)出內(nèi)容:

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

export default Vue

這個(gè)時(shí)候就去找runtime-with-compiler.ts的導(dǎo)出內(nèi)容:

  • runtime-with-compiler.ts的導(dǎo)出內(nèi)容:

import Vue from './runtime/index'

export default Vue as GlobalAPI

這個(gè)時(shí)候就去找runtime/index.ts的導(dǎo)出內(nèi)容:

  • runtime/index.ts的導(dǎo)出內(nèi)容:

import Vue from 'core/index'

export default Vue

這個(gè)時(shí)候就去找core/index.ts的導(dǎo)出內(nèi)容:

  • core/index.ts的導(dǎo)出內(nèi)容:

import Vue from './instance/index'

export default Vue

這個(gè)時(shí)候就去找instance/index.ts的導(dǎo)出內(nèi)容:

  • instance/index.ts的導(dǎo)出內(nèi)容:

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的構(gòu)造函數(shù)了,這個(gè)時(shí)候我們就可以開始閱讀源碼了;

帶有目的的閱讀源碼

閱讀源碼的目的一定要清晰,當(dāng)然你可以說目的就是了解Vue的實(shí)現(xiàn)原理,但是這個(gè)目的太寬泛了,我們可以把目的細(xì)化一下,例如:

  • Vue的生命周期是怎么實(shí)現(xiàn)的

  • Vue的數(shù)據(jù)響應(yīng)式是怎么實(shí)現(xiàn)的

  • Vue的模板編譯是怎么實(shí)現(xiàn)的

  • Vue的組件化是怎么實(shí)現(xiàn)的

  • Vue的插槽是怎么實(shí)現(xiàn)的

  • 等等...

例如我們的這次閱讀計(jì)劃就是了解Vuethis為什么可以訪問到選項(xiàng)中的各種屬性,這里再細(xì)分為:

  • Vuethis是怎么訪問到data

  • Vuethis是怎么訪問到methods

  • Vuethis是怎么訪問到computed

  • Vuethis是怎么訪問到props

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

源碼分析

上面已經(jīng)找到了Vue的入口文件,接下來我們就可以開始閱讀源碼了,這里我就以Vuethis為什么可以訪問到選項(xiàng)中的各種屬性為例,來分析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的時(shí)候,通常是下面這樣的:

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

vm.say();

也就是Vue的構(gòu)造函數(shù)接收一個(gè)選項(xiàng)對象,這個(gè)選項(xiàng)對象中有datamethods;

我們要知道Vuethis為什么可以訪問到datamethods,那么我們就要找到Vue的構(gòu)造函數(shù)中是怎么把datamethods掛載到this上的;

很明顯構(gòu)造函數(shù)只做了一件事,就是調(diào)用了this._init(options)

this._init(options)

那么我們就去找_init方法,這個(gè)方法在哪我們不知道,但是繼續(xù)分析源碼,我們可以看到下面會執(zhí)行很多xxxMixin的函數(shù),并且Vue作為參數(shù)傳入:

//@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)

盲猜一波,見名知意:

  • initMixin:初始化混入

  • stateMixin:狀態(tài)混入

  • eventsMixin:事件混入

  • lifecycleMixin:生命周期混入

  • renderMixin:渲染混入

我們就去找這些混入的方法,一個(gè)一個(gè)的找,找到initMixin,直接就找了_init方法:

export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    const vm: Component = this
    // a uid
    vm._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
    // check
    vm._isVue = true
    // avoid instances from being observed
    vm.__v_skip = true
    // effect scope
    vm._scope = new EffectScope(true /* detached */)
    vm._scope._vm = 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 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 self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, &#39;beforeCreate&#39;, undefined, false /* setContext */)
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, &#39;created&#39;)

    /* 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的實(shí)例,也就是我們的vm;

繼續(xù)往下看,我們有目的的看代碼,只需要看有vmoptions組合出現(xiàn)的代碼,于是就看到了:

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

_isComponent前面帶有_,說明是私有屬性,我們通過new Vue創(chuàng)建的實(shí)例時(shí)走到現(xiàn)在是沒有這個(gè)屬性的,所以走到else分支;

resolveConstructorOptions(vm.constructor)中沒有傳遞options,所以不看這個(gè)方法,直接看mergeOptions

export function mergeOptions(parent, child, vm) {
  if (__DEV__) {
    checkComponents(child)
  }

  if (isFunction(child)) {
    // @ts-expect-error
    child = 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&#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 < 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
}

記住我們的目的,只需要關(guān)心vmoptions組合出現(xiàn)的代碼,child就是optionsvm就是vm,簡化之后:

export function mergeOptions(parent, child, vm) {

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  return options
}

可以看到只剩下了normalizeProps、normalizeInjectnormalizeDirectives這三個(gè)方法,值得我們關(guān)注,但是見名知意,這三個(gè)方法可能并不是我們想要的,跟進(jìn)去看一眼也確實(shí)不是;

雖然沒有得到我們想要的,但是從這里我們也得到了一個(gè)重要信息,mergeOptions最后會返回一個(gè)options對象,這個(gè)對象就是我們的options,最后被vm.$options接收;

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

現(xiàn)在我們分析要多一步了,參數(shù)只有vm的函數(shù)也是需要引起我們的注意的,繼續(xù)往下看:

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

操作了vm,但是內(nèi)部沒有操作$options,跳過,繼續(xù)往下看:

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

initLifecycleinitEvents、initRender、initInjections、initState、initProvide這些方法都是操作vm的;

盲猜一波:

  • initLifecycle:初始化生命周期
  • initEvents:初始化事件
  • initRender:初始化渲染
  • initInjections:初始化注入
  • initState:初始化狀態(tài)
  • initProvide:初始化依賴注入
  • callHook:調(diào)用鉤子

這里面最有可能是我們想要的是initState,跟進(jìn)去看一下:

export function initState(vm) {
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)

  // Composition API
  initSetup(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)
  }
}

已經(jīng)找到我們想要的了,現(xiàn)在開始正式分析initState。

initState

根據(jù)代碼結(jié)構(gòu)可以看到,initState主要做了以下幾件事:

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

我們可以用this來訪問的屬性是props、methods、data、computed;

看到這里也明白了,為什么在props中定義了一個(gè)屬性,在data、methods、computed中就不能再定義了,因?yàn)?code>props是最先初始化的,后面的也是同理。

initProps

initProps的作用是初始化props,跟進(jìn)去看一下:

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 converted
  if (!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&#39;s ` +
              `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component&#39;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)
}

代碼很多,我們依然不用關(guān)心其他的代碼,只關(guān)心props是怎么掛載到vm上的,根據(jù)我上面的方法,簡化后的代碼如下:

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

這里真正有關(guān)的就兩個(gè)地方:

  • validateProp:看名字就知道是驗(yàn)證props,跳過

  • proxy:代理,很可疑,跟進(jìn)去看一下:

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就是_props,key就是props的屬性名;

這里通過Object.definePropertyvm的屬性代理到_props上,這樣就可以通過this訪問到props了。

不是很好理解,那我們來自己就用這些代碼實(shí)現(xiàn)一下:

var options = {
    props: {
        name: {
            type: String,
            default: &#39;default name&#39;
        }
    }
}

function Vue(options) {
    const vm = this
    initProps(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 = &#39;name&#39;

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

聊聊Vue2為什麼能透過this存取各種選項(xiàng)中屬性

上面的代碼只是為了方便理解,所以會忽略一些細(xì)節(jié),比如props的驗(yàn)證等等,真實(shí)掛載在_props上的props是通過defineReactive實(shí)現(xiàn)的,我這里直接是空的,這些超出了本文的范圍。

initMethods

initMethods的代碼如下:

function initMethods(vm, methods) {
  const props = vm.$options.props
  for (const key in methods) {
    if (__DEV__) {
      if (typeof methods[key] !== &#39;function&#39;) {
        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] !== &#39;function&#39; ? noop : bind(methods[key], vm)
  }
}

跟著之前的思路,我們忽略無關(guān)代碼,簡化后的代碼如下:

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

這里的noopbind在之前的文章中有出現(xiàn)過,可以去看一下:【源碼共讀】Vue2源碼 shared 模塊中的36個(gè)實(shí)用工具函數(shù)分析

這里的vm[key]就是methods的方法,這樣就可以通過this訪問到methods中定義的方法了。

bind的作用是把methods中定義的函數(shù)的this指向vm,這樣就可以在methods中使用this就是vm了。

簡單的實(shí)現(xiàn)一下:

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

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

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

function noop() {}

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

    boundFn._length = fn.length
    return 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.data
  data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
  if (!isPlainObject(data)) {
    data = {}
    __DEV__ &&
      warn(
        &#39;data functions should return an object:\n&#39; +
          &#39;https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function&#39;,
        vm
      )
  }
  // 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 (__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 data
  const ob = observe(data)
  ob && ob.vmCount++
}

簡化之后的代碼如下:

function initData(vm) {
    let data = vm.$options.data

    // proxy data on instance
    const keys = Object.keys(data)
    let i = keys.length
    while (i--) {
        const key = keys[i]
        proxy(vm, `_data`, key)
    }
}

這里的實(shí)現(xiàn)方式和initProps是一樣的,都是通過proxydata中的屬性代理到vm上。

注意:initData的獲取值的地方是其他的不相同,這里只做提醒,不做詳細(xì)分析。

initComputed

initComputed的代碼如下:

function initComputed(vm, computed) {
  // $flow-disable-line
  const watchers = (vm._computedWatchers = Object.create(null))
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    const getter = isFunction(userDef) ? userDef : userDef.get
    if (__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 = userDef

        defineComputed(vm, key, userDef)
    }
}

這里的實(shí)現(xiàn)主要是通過defineComputed來定義computed屬性,進(jìn)去瞅瞅:

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)
      : noop
    sharedPropertyDefinition.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)
}

仔細(xì)看下來,其實(shí)實(shí)現(xiàn)方式還是和initPropsinitData一樣,都是通過Object.defineProperty來定義屬性;

不過里面的gettersetter是通過createComputedGettercreateGetterInvoker來創(chuàng)建的,這里不做過多分析。

動手時(shí)間

上面我們已經(jīng)分析了props、methods、data、computed的屬性為什么可以直接通過this來訪問,那么我們現(xiàn)在就來實(shí)現(xiàn)一下這個(gè)功能。

上面已經(jīng)簡單了實(shí)現(xiàn)了initProps、initMethods,而initDatainitComputed的實(shí)現(xiàn)方式和initProps的方式一樣,所以我們直接復(fù)用就好了:

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

Vue.prototype._init = function (options) {
    const vm = this
    vm.$options = options
    initState(vm)
}

function initState(vm) {
    const opts = vm.$options
    if (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].default
        proxy(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] !== &#39;function&#39; ? noop : bind(methods[key], vm)
    }
}

function noop() {}

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

    boundFn._length = fn.length
    return 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 = userDef

        defineComputed(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)

注意:上面的代碼對比于文章中寫的示例有改動,主要是為了實(shí)現(xiàn)最后打印結(jié)果正確,增加了賦值操作。

總結(jié)

通過上面的分析,讓我們對構(gòu)造函數(shù)的this有了更深的理解,同時(shí)對于this指向的問題也有了更深的理解。

(學(xué)習(xí)視頻分享:vuejs入門教程、編程基礎(chǔ)視頻

以上是聊聊Vue2為什麼能透過this存取各種選項(xiàng)中屬性的詳細(xì)內(nèi)容。更多資訊請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強(qiáng)大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Laravel 教程
1597
29
PHP教程
1488
72
VUE中的服務(wù)器端渲染SSR是什麼? VUE中的服務(wù)器端渲染SSR是什麼? Jun 25, 2025 am 12:49 AM

Server-Serdendering(SSR)InvueImProvesperformandSeobyGeneratingHtmlonTheserver.1.TheserverrunsvueApcodeAmpCodeAndGeneratesHtmlbBasedonThecurrentRoute.2.thathtmlssenttothebrowserimmed.3.vuehirative eveirtive eveirtive eveirtive eveirtive eveirtive eveirtive eveirtive eveirtiveThepage evepage evepage

如何將sublime文本與vue.js一起使用? 如何將sublime文本與vue.js一起使用? Jun 26, 2025 am 12:12 AM

toenhancevue.jsdevelopmentInSubliMeText,installvuesyntaxhighlightingviapackagecontrol,setupemmetforfasterhtmltmltplating,IntegrateEsLintAnd and prettierForLintingformatting和配置

如何使用VUE構(gòu)建組件庫? 如何使用VUE構(gòu)建組件庫? Jul 10, 2025 pm 12:14 PM

搭建Vue組件庫需圍繞業(yè)務(wù)場景設(shè)計(jì)結(jié)構(gòu),並遵循開發(fā)、測試、發(fā)布的完整流程。 1.結(jié)構(gòu)設(shè)計(jì)應(yīng)按功能模塊分類,包括基礎(chǔ)組件、佈局組件和業(yè)務(wù)組件;2.使用SCSS或CSS變量統(tǒng)一主題與樣式;3.統(tǒng)一命名規(guī)範(fàn)並引入ESLint和Prettier保證代碼風(fēng)格一致;4.配套文檔站點(diǎn)展示組件用法;5.使用Vite等工具打包為NPM包並配置rollupOptions;6.發(fā)佈時(shí)遵循semver規(guī)範(fàn)管理版本與changelog。

如何用PHP開發(fā)問答社區(qū)平臺 PHP互動社區(qū)變現(xiàn)模式詳解 如何用PHP開發(fā)問答社區(qū)平臺 PHP互動社區(qū)變現(xiàn)模式詳解 Jul 23, 2025 pm 07:21 PM

1.PHP開發(fā)問答社區(qū)首選Laravel MySQL Vue/React組合,因生態(tài)成熟、開發(fā)效率高;2.高性能需依賴緩存(Redis)、數(shù)據(jù)庫優(yōu)化、CDN和異步隊(duì)列;3.安全性必須做好輸入過濾、CSRF防護(hù)、HTTPS、密碼加密及權(quán)限控制;4.變現(xiàn)可選廣告、會員訂閱、打賞、傭金、知識付費(fèi)等模式,核心是匹配社區(qū)調(diào)性和用戶需求。

Vue成品資源網(wǎng)站免費(fèi)入口 完整Vue成品永久在線觀看 Vue成品資源網(wǎng)站免費(fèi)入口 完整Vue成品永久在線觀看 Jul 23, 2025 pm 12:39 PM

本文為Vue開發(fā)者和學(xué)習(xí)者精選了一系列頂級的成品資源網(wǎng)站。通過這些平臺,你可以免費(fèi)在線瀏覽、學(xué)習(xí)甚至復(fù)用海量高質(zhì)量的Vue完整項(xiàng)目,從而快速提升開發(fā)技能和項(xiàng)目實(shí)踐能力。

如何用PHP開發(fā)AI智能表單系統(tǒng) PHP智能表單設(shè)計(jì)與分析 如何用PHP開發(fā)AI智能表單系統(tǒng) PHP智能表單設(shè)計(jì)與分析 Jul 25, 2025 pm 05:54 PM

選擇合適的PHP框架需根據(jù)項(xiàng)目需求綜合考慮:Laravel適合快速開發(fā),提供EloquentORM和Blade模板引擎,便於數(shù)據(jù)庫操作和動態(tài)表單渲染;Symfony更靈活,適合複雜系統(tǒng);CodeIgniter輕量,適用於對性能要求較高的簡單應(yīng)用。 2.確保AI模型準(zhǔn)確性需從高質(zhì)量數(shù)據(jù)訓(xùn)練、合理選擇評估指標(biāo)(如準(zhǔn)確率、召回率、F1值)、定期性能評估與模型調(diào)優(yōu)入手,並通過單元測試和集成測試保障代碼質(zhì)量,同時(shí)持續(xù)監(jiān)控輸入數(shù)據(jù)以防止數(shù)據(jù)漂移。 3.保護(hù)用戶隱私需採取多項(xiàng)措施:對敏感數(shù)據(jù)進(jìn)行加密存儲(如AES

VUE中的自定義插件是什麼? VUE中的自定義插件是什麼? Jun 26, 2025 am 12:37 AM

要創(chuàng)建一個(gè)Vue自定義插件,請按以下步驟操作:1.定義包含install方法的插件對象;2.在install中通過添加全局方法、實(shí)例方法、指令、混入或註冊組件來擴(kuò)展Vue;3.導(dǎo)出插件以便在其他地方導(dǎo)入使用;4.在主應(yīng)用文件中通過Vue.use(YourPlugin)註冊插件。例如,可創(chuàng)建一個(gè)為所有組件添加$formatCurrency方法的插件,在install中設(shè)置Vue.prototype.$formatCurrency。使用插件時(shí)應(yīng)注意避免過度污染全局命名空間、減少副作用,並確保每個(gè)插件

如何構(gòu)建生產(chǎn)的VUE應(yīng)用程序? 如何構(gòu)建生產(chǎn)的VUE應(yīng)用程序? Jul 09, 2025 am 01:42 AM

部署Vue應(yīng)用到生產(chǎn)環(huán)境需優(yōu)化性能、確保穩(wěn)定性並提升加載速度。 1.使用VueCLI或Vite構(gòu)建生產(chǎn)版本,生成dist目錄並設(shè)置正確環(huán)境變量;2.若使用VueRouter的history模式,需配置服務(wù)器回退至index.html;3.將dist目錄部署至Nginx/Apache、Netlify/Vercel或結(jié)合CDN加速;4.啟用Gzip壓縮與瀏覽器緩存策略以優(yōu)化加載;5.實(shí)施懶加載組件、按需引入U(xiǎn)I庫、開啟HTTPS、防止XSS攻擊、添加CSP頭及限制第三方SDK域名白名單以增強(qiáng)安全性。

See all articles