/* @flow */

import { hasOwn, isObject, isPlainObject, capitalize, hyphenate } from 'shared/util'
import { observe, observerState } from '../observer/index'
import { warn } from './debug'

type PropOptions = {
  type: Function | Array<Function> | null,
  default: any,
  required: ?boolean,
  validator: ?Function
};

export function validateProp (
  key: string,
  propOptions: Object,
  propsData: Object,
  vm?: Component
): any {
const prop
= propOptions[key]

//如果 key不在 propsData中 const absent
= !hasOwn(propsData, key)
let value
= propsData[key] // handle boolean props
//如果 传入的属性是 bool类型
if (isType(Boolean, prop.type)) { if (absent && !hasOwn(prop, 'default')) { value = false } else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) { value = true } } // check default value if (value === undefined) { value = getPropDefaultValue(vm, prop, key) // since the default value is a fresh copy, // make sure to observe it. const prevShouldConvert = observerState.shouldConvert observerState.shouldConvert = true observe(value) observerState.shouldConvert = prevShouldConvert } if (process.env.NODE_ENV !== 'production') { assertProp(prop, key, value, vm, absent) } return value } /** * Get the default value of a prop. */ function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any { // no default, return undefined if (!hasOwn(prop, 'default')) { return undefined } const def = prop.default // warn against non-factory defaults for Object & Array if (process.env.NODE_ENV !== 'production' && isObject(def)) { warn( 'Invalid default value for prop "' + key + '": ' + 'Props with type Object/Array must use a factory function ' + 'to return the default value.', vm ) } // the raw prop value was also undefined from previous render, // return previous default value to avoid unnecessary watcher trigger if (vm && vm.$options.propsData && vm.$options.propsData[key] === undefined && vm._props[key] !== undefined) { return vm._props[key] } // call factory function for non-Function types // a value is Function if its prototype is function even across different execution context return typeof def === 'function' && getType(prop.type) !== 'Function' ? def.call(vm) : def } /** * Assert whether a prop is valid. */ function assertProp ( prop: PropOptions, name: string, value: any, vm: ?Component, absent: boolean ) { if (prop.required && absent) { warn( 'Missing required prop: "' + name + '"', vm ) return } if (value == null && !prop.required) { return } let type = prop.type let valid = !type || type === true const expectedTypes = [] if (type) { if (!Array.isArray(type)) { type = [type] } for (let i = 0; i < type.length && !valid; i++) { const assertedType = assertType(value, type[i]) expectedTypes.push(assertedType.expectedType || '') valid = assertedType.valid } } if (!valid) { warn( 'Invalid prop: type check failed for prop "' + name + '".' + ' Expected ' + expectedTypes.map(capitalize).join(', ') + ', got ' + Object.prototype.toString.call(value).slice(8, -1) + '.', vm ) return } const validator = prop.validator if (validator) { if (!validator(value)) { warn( 'Invalid prop: custom validator check failed for prop "' + name + '".', vm ) } } } /** * Assert the type of a value */ function assertType (value: any, type: Function): { valid: boolean, expectedType: ?string } { let valid let expectedType = getType(type) if (expectedType === 'String') { valid = typeof value === (expectedType = 'string') } else if (expectedType === 'Number') { valid = typeof value === (expectedType = 'number') } else if (expectedType === 'Boolean') { valid = typeof value === (expectedType = 'boolean') } else if (expectedType === 'Function') { valid = typeof value === (expectedType = 'function') } else if (expectedType === 'Object') { valid = isPlainObject(value) } else if (expectedType === 'Array') { valid = Array.isArray(value) } else { valid = value instanceof type } return { valid, expectedType } } /** * Use function string name to check built-in types, * because a simple equality check will fail when running * across different vms / iframes.

    Number.toString()
    "function Number() { [native code] }"

  Boolean.toString()
"function Boolean() {[native code]}"

toString 深入研究
*/ function getType (fn) { const match = fn && fn.toString().match(/^\s*function (\w+)/) return match && match[1] }
//如果传入的是数组, 只要数组中有一个是type, 就判断为相等, 因为如果type: [Boolean, Fuction, Number] 说明这个组件的属性满足其中一个即可
function isType (type, fn) { if (!Array.isArray(fn)) { return getType(fn) === getType(type) } for (let i = 0, len = fn.length; i < len; i++) { if (getType(fn[i]) === getType(type)) { return true } } /* istanbul ignore next */ return false }