# vue公共函数

vue源码有很多值得学习的地方,编码规范风格,思想等等,先学习下vue的一些公共的函数

打开vue.common.js,定义了一些后续会用到的函数,在源码的shared/util.js

//空对象,用object.freeze冻结一个对象,冻结指的是不能向这个对象添加新的属性,
//不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。该方法返回被冻结的对象。
let emptyObject = Object.freeze({});

//判断是否为undefined 或者null
function isUndef (v) {
  return v === undefined || v === null
}
//相反
function isDef (v) {
  return v !== undefined && v !== null
}

/**
 * Check if value is primitive
 * 值是否为原始类型
 */
function isPrimitive (value) {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    // $flow-disable-line
    typeof value === 'symbol' ||
    typeof value === 'boolean'
  )
}

// 是否是对象
function isObject (obj) {
  return obj !== null && typeof obj === 'object'
}

const _toString = Object.prototype.toString;

//获取值的原始类型 
// 例如 "[object String]" 经过slice(8,-1)获取到的是String
function toRawType (value) {
  return _toString.call(value).slice(8, -1)
}
/**
 * Strict object type check. Only returns true
 * for plain JavaScript objects.
 * 简单对象(plain object),也就是用 {} 或 new Object() 创建的对象。
 */
function isPlainObject (obj) {
  return _toString.call(obj) === '[object Object]'
}
//是否是正则
function isRegExp (v) {
  return _toString.call(v) === '[object RegExp]'
}
/**
 * Check if val is a valid array index.
 * 是否是合法的数组索引
 */
function isValidArrayIndex (val) {
 let n = parseFloat(String(val));
 //isFinite用来检查一个数值是否为有限的(finite),即不是Infinity。
 //Math.floor(2.2)===2.2 false
 //Math.floor(2=== 2) true 判断为整数
 return n >= 0 && Math.floor(n) === n && isFinite(val)
}

/** JSON.stringify(value[, replacer [, space]])
   *后面参数还没用过... 
   * replacer 可选
   如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
   如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
   如果该参数为null或者未提供,则对象所有的属性都会被序列化;
   * space 可选
   指定缩进用的空白字符串,用于美化输出(pretty-print);
   如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;
   如果该参数为字符串(字符串的前十个字母),该字符串将被作为空格;
   如果该参数没有提供(或者为null)将没有空格。

   另外 vue 这种三元运算符的写法结构比较清晰,值得学习
   */
function toString (val) {
 return val == null
   ? ''
   : typeof val === 'object'
     ? JSON.stringify(val, null, 2)
     : String(val)
}
//转为数字
function toNumber (val) {
 let n = parseFloat(val);
 return isNaN(n) ? val : n
}

/**
* Remove an item from an array
* 删除数字元素
*/
function remove (arr, item) {
 if (arr.length) {
   let index = arr.indexOf(item);
   if (index > -1) {
     return arr.splice(index, 1)
   }
 }
}
/**
 * Check whether the object has the property.
 * 指示对象自身属性中是否具有指定的属性,所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。
  这个方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
 */
let hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn (obj, key) {
  return hasOwnProperty.call(obj, key)
}
function makeMap (
  str: string,
  expectsLowerCase?: boolean//是否大小写
): (key: string) => true | void {
  //定义一个对象 map
  const map = Object.create(null)
  //根据逗号,将 str 分隔成数组并保存到 list 变量
  const list: Array<string> = str.split(',')
  // 遍历 list 并以 list 中的元素作为 map 的 key,将其设置为 true
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true
  }
  //返回一个函数,并且如果 expectsLowerCase 为 true 的话,将 map 的 key 小写
  return expectsLowerCase
    ? val => map[val.toLowerCase()]
    : val => map[val]
}
//为一个纯函数创建一个缓存版本的函数 是一个函数式编程的玩法
export function cached<F: Function> (fn: F): F {
  const cache = Object.create(null)
  return (function cachedFn (str: string) {
    //这个函数与原函数 fn 的区别就在于:先读取缓存
    const hit = cache[str]
    //命中则直接返回缓存的值,否则采用原函数 fn 计算一次并且设置缓存,然后返回结果
    return hit || (cache[str] = fn(str))
  }: any)
}

//全局匹配字符串中 中横线及连字符后的一个字符
const camelizeRE = /-(\w)/g
//连字符转驼峰
export const camelize = cached((str: string): string => {
  //连字符后有字符,则将匹配到的内容使用该字符的大写形式替换,否则使用空字符串替换即可
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})
//全局匹配字符串中的大写字母,大写字母前必须不是单词的边界
const hyphenateRE = /\B([A-Z])/g
//驼峰转连字符
export const hyphenate = cached((str: string): string => {
  //匹配的内容使用连字符和捕获组的字符替换,最后转为小写
  return str.replace(hyphenateRE, '-$1').toLowerCase()
})

//首字母大写
export const capitalize = cached((str: string): string => {
  return str.charAt(0).toUpperCase() + str.slice(1)
})
/**
 * Convert an Array-like object to a real Array.类似数组转为真数组
 */
function toArray (list, start) {
  start = start || 0;
  let i = list.length - start;
  let ret = new Array(i);
  while (i--) {
    ret[i] = list[i + start];
  }
  return ret
}
/**
 * Mix properties into target object. -from对象 赋给to对象
 */
function extend (to, _from) {
  for (let key in _from) {
    to[key] = _from[key];
  }
  return to
}
//将一个对象数组合并到一个对象中,并返回该对象
export function toObject (arr: Array<any>): Object {
  const res = {}
  for (let i = 0; i < arr.length; i++) {
    //如果 arr[i] 存在,则调用 extend 函数合并对象属性
    if (arr[i]) {
      extend(res, arr[i])
    }
  }
  return res
}
/**
 * Always return false. 永远为false
 */
let no = function (a, b, c) { return false; };

/**
 * Return same value 返回原值
 */
let identity = function (_) { return _; };
// can we use __proto__? 还可以使用 Object.getPrototypeOf()
const hasProto = '__proto__' in {};

// Browser environment sniffing 浏览器环检测
const inBrowser = typeof window !== 'undefined';
const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform;
const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
const UA = inBrowser && window.navigator.userAgent.toLowerCase();
const isIE = UA && /msie|trident/.test(UA);
const isIE9 = UA && UA.indexOf('msie 9.0') > 0;
const isEdge = UA && UA.indexOf('edge/') > 0;
const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');
const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');
const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;

// Firefox has a "watch" function on Object.prototype... 
//火狐浏览器有个watch,做相关处理避免和vue的watch 冲突
const nativeWatch = ({}).watch;
/**
 * Define a property.
 */
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,//如果不传递 enumerable 参数则代表定义的属性是不可枚举的 !!undefined ==false
    writable: true, 
    configurable: true
  })
}

# vue生命周期

  • beforeCreate
    在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
  • created
    实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
  • beforeMount
    在挂载开始之前被调用:相关的 render 函数首次被调用。
  • mounted
    el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
  • beforeUpdate
    数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • updated
    由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。 当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。
  • beforeDestroy
    实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed
    Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。