中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue2能通過this訪問各種選項中屬性的原因是什么

發布時間:2022-12-09 09:04:49 來源:億速云 閱讀:130 作者:iii 欄目:編程語言

今天小編給大家分享一下Vue2能通過this訪問各種選項中屬性的原因是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

怎么找到起點

萬事開頭難,找到起點是最難的,對于前端項目,我們想要找到入口文件,一般都是從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

Vue2能通過this訪問各種選項中屬性的原因是什么

這樣就可以找到這個值對應的配置:

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的別名;

Vue2能通過this訪問各種選項中屬性的原因是什么

代碼如下:

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這個文件;

Vue2能通過this訪問各種選項中屬性的原因是什么

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

如何閱讀源碼

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

像我們現在看的源碼幾乎都是使用esm模塊化或者commonjs模塊化的,這些都會有一個export或者module.exports,我們可以通過這個來看導出了什么;

只看導出的內容,其他的暫時不用管,直接找到最終導出的內容,例如Vue的源碼:

  • entry-runtime-with-compiler.ts的導出內容:

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

export default Vue

這個時候就去找runtime-with-compiler.ts的導出內容:

  • runtime-with-compiler.ts的導出內容:

import Vue from './runtime/index'

export default Vue as GlobalAPI

這個時候就去找runtime/index.ts的導出內容:

  • runtime/index.ts的導出內容:

import Vue from 'core/index'

export default Vue

這個時候就去找core/index.ts的導出內容:

  • core/index.ts的導出內容:

import Vue from './instance/index'

export default Vue

這個時候就去找instance/index.ts的導出內容:

  • 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的實現原理,但是這個目的太寬泛了,我們可以把目的細化一下,例如:

  • Vue的生命周期是怎么實現的

  • Vue的數據響應式是怎么實現的

  • Vue的模板編譯是怎么實現的

  • Vue的組件化是怎么實現的

  • Vue的插槽是怎么實現的

  • 等等...

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

  • Vuethis是怎么訪問到data

  • Vuethis是怎么訪問到methods

  • Vuethis是怎么訪問到computed

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

盲猜一波,見名知意:

  • initMixin:初始化混入

  • stateMixin:狀態混入

  • eventsMixin:事件混入

  • lifecycleMixin:生命周期混入

  • renderMixin:渲染混入

我們就去找這些混入的方法,一個一個的找,找到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, 'beforeCreate', undefined, false /* setContext */)
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(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-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'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
}

記住我們的目的,只需要關心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.$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)
  }
}

已經找到我們想要的了,現在開始正式分析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 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'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)
        }
    }
}

這里真正有關的就兩個地方:

  • validateProp:看名字就知道是驗證props,跳過

  • 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 = 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 = 'name'

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

Vue2能通過this訪問各種選項中屬性的原因是什么

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

initMethods

initMethods的代碼如下:

function initMethods(vm, methods) {
  const props = vm.$options.props
  for (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 = this
    initMethods(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.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(
        'data functions should return an object:\n' +
          'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        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)
    }
}

這里的實現方式和initProps是一樣的,都是通過proxydata中的屬性代理到vm上。

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

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

這里的實現主要是通過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)
      : 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)
}

仔細看下來,其實實現方式還是和initPropsinitData一樣,都是通過Object.defineProperty來定義屬性;

不過里面的gettersetter是通過createComputedGettercreateGetterInvoker來創建的,這里不做過多分析。

動手時間

上面我們已經分析了propsmethodsdatacomputed的屬性為什么可以直接通過this來訪問,那么我們現在就來實現一下這個功能。

上面已經簡單了實現了initPropsinitMethods,而initDatainitComputed的實現方式和initProps的方式一樣,所以我們直接復用就好了:

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] !== 'function' ? 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)

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

以上就是“Vue2能通過this訪問各種選項中屬性的原因是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

嘉祥县| 永和县| 威海市| 安岳县| 莱州市| 太谷县| 台中县| 惠安县| 石泉县| 阆中市| 文安县| 淅川县| 迁西县| 万宁市| 彭阳县| 安岳县| 贡山| 清徐县| 济南市| 蓝山县| 阿拉尔市| 怀远县| 无为县| 客服| 滦南县| 伊吾县| 同仁县| 星座| 乐至县| 定襄县| 唐河县| 邳州市| 鄯善县| 渝北区| 馆陶县| 璧山县| 临洮县| 遂川县| 开平市| 奉节县| 京山县|