首页
Javascript
Html
Css
Node.js
Electron
移动开发
小程序
工具类
服务端
浏览器相关
前端收藏
其他
关于

vue源码阅读3 - shared/util.js

2019年11月25日 发布 阅读(605) 作者:Jerman

util.js

全局工具函数

  • emptyObject

  • isUndef

  • isDef

  • isTrue

  • isFalse

  • isPrimitive

  • isObject

  • toRawType

  • isPlainObject

  • isRegExp

  • isValidArrayIndex

  • isPromise

  • toString

  • toNumber

  • isBuiltInTag

  • isReservedAttribute

  • remove

  • hasOwnProperty

  • cached

  • camelize

  • capitalize

  • hyphenate

  • bind

  • toArray

  • extend

  • toObject

  • noop

  • no

  • identity

  • genStaticKeys

  • looseEqual

  • looseIndexOf

  • once

代码注释

  1. /* @flow */
  2. // 生成一个被冻结的新对象,此对象不可被修改
  3. export const emptyObject = Object.freeze({})
  4. // These helpers produce better VM code in JS engines due to their
  5. // explicitness and function inlining.
  6. // %checks为谓词函数,具体参考https://flow.org/en/docs/types/functions/
  7. // 判断未定义
  8. export function isUndef(v: any): boolean % checks {
  9. return v === undefined || v === null
  10. }
  11. // 判断已定义
  12. export function isDef(v: any): boolean % checks {
  13. return v !== undefined && v !== null
  14. }
  15. // 判断值为true
  16. export function isTrue(v: any): boolean % checks {
  17. return v === true
  18. }
  19. // 判断值为false
  20. export function isFalse(v: any): boolean % checks {
  21. return v === false
  22. }
  23. /**
  24. * Check if value is primitive.
  25. *
  26. * 判断值是简单的原始的数据类型
  27. */
  28. export function isPrimitive(value: any): boolean % checks {
  29. return (
  30. typeof value === 'string' ||
  31. typeof value === 'number' ||
  32. // $flow-disable-line
  33. typeof value === 'symbol' ||
  34. typeof value === 'boolean'
  35. )
  36. }
  37. /**
  38. * Quick object check - this is primarily used to tell
  39. * Objects from primitive values when we know the value
  40. * is a JSON-compliant type.
  41. *
  42. * 用于判断是不是一个json类型的object
  43. */
  44. export function isObject(obj: mixed): boolean % checks {
  45. return obj !== null && typeof obj === 'object'
  46. }
  47. /**
  48. * Get the raw type string of a value, e.g., [object Object].
  49. *
  50. * 获取值的原始类型字符串
  51. */
  52. const _toString = Object.prototype.toString
  53. export function toRawType(value: any): string {
  54. return _toString.call(value).slice(8, -1)
  55. }
  56. /**
  57. * Strict object type check. Only returns true
  58. * for plain JavaScript objects.
  59. *
  60. * 严格的对象类型检查,只返回true
  61. * 针对简单的javascript对象
  62. * 使用上面的toRawType也可以判断
  63. */
  64. export function isPlainObject(obj: any): boolean {
  65. // 可以用toRawType: return toRawType(obj) === 'Object'
  66. return _toString.call(obj) === '[object Object]'
  67. }
  68. // 判断是不是正则表达式
  69. export function isRegExp(v: any): boolean {
  70. return _toString.call(v) === '[object RegExp]'
  71. }
  72. /**
  73. * Check if val is a valid array index.
  74. *
  75. * 检查值是不是一个有效的数组索引
  76. */
  77. export function isValidArrayIndex(val: any): boolean {
  78. const n = parseFloat(String(val))
  79. // isFinite: 判断数值是不是一个有限数值
  80. return n >= 0 && Math.floor(n) === n && isFinite(val)
  81. }
  82. // 判断是否返回一个promise
  83. export function isPromise(val: any): boolean {
  84. return (
  85. isDef(val) &&
  86. typeof val.then === 'function' &&
  87. typeof val.catch === 'function'
  88. )
  89. }
  90. /**
  91. * Convert a value to a string that is actually rendered.
  92. *
  93. * 将值转化成它实际显示的字符串
  94. * 如果值为null,则转化成字符串
  95. * 如果是数组及对象,就使用JSON.strinify
  96. * 其他都使用String
  97. * 注意字面量对象不能直接使用{}.toString()这样写,会报错。可换成({}.toString())
  98. */
  99. export function toString(val: any): string {
  100. return val == null
  101. ? ''
  102. : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
  103. ? JSON.stringify(val, null, 2)
  104. : String(val)
  105. }
  106. /**
  107. * Convert an input value to a number for persistence.
  108. * If the conversion fails, return original string.
  109. *
  110. * 将输入的字符串值转换成一个数字,以实现持久性
  111. * 如果输入的值不能转换成数字,则返回输入的原始字符串
  112. */
  113. export function toNumber(val: string): number | string {
  114. const n = parseFloat(val)
  115. return isNaN(n) ? val : n
  116. }
  117. /**
  118. * Make a map and return a function for checking if a key
  119. * is in that map.
  120. *
  121. * @param {*} str 字符串,会以逗号分隔拆分成数组,数组的每个值是map的key,值全是true
  122. * @param {*} expectsLowerCase 是否转换成小写
  123. * @return 返回一个方法,方法接收一个val参数
  124. * eg: var map = makeMap('a,b,c,D,e'); console.log(map('a')) //true
  125. */
  126. export function makeMap(
  127. str: string,
  128. expectsLowerCase?: boolean
  129. ): (key: string) => true | void {
  130. const map = Object.create(null)
  131. const list: Array<string> = str.split(',')
  132. for (let i = 0; i < list.length; i++) {
  133. map[list[i]] = true
  134. }
  135. return expectsLowerCase
  136. ? val => map[val.toLowerCase()]
  137. : val => map[val]
  138. }
  139. /**
  140. * Check if a tag is a built-in tag.
  141. *
  142. * 检查标签是不是内置的标签,不区分大小写
  143. */
  144. export const isBuiltInTag = makeMap('slot,component', true)
  145. /**
  146. * Check if an attribute is a reserved attribute.
  147. *
  148. * 检查属性是否是保留属性,区分大小写
  149. */
  150. export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')
  151. /**
  152. * Remove an item from an array.
  153. *
  154. * 删除数组中的某一项(如果有重复的字,只会删除第一个)
  155. * @param {*} arr 数组
  156. * @param {*} item 要删除的数组值
  157. */
  158. export function remove(arr: Array<any>, item: any): Array<any> | void {
  159. // 判断数组是否为空
  160. if (arr.length) {
  161. const index = arr.indexOf(item)
  162. // 判断要删除的值是否存在
  163. if (index > -1) {
  164. return arr.splice(index, 1)
  165. }
  166. }
  167. }
  168. /**
  169. * Check whether an object has the property.
  170. *
  171. * 检查对象是否具有某个属性
  172. * @param {*} obj 对象可以是任意Object或Array数组
  173. * @param {*} key 必须是字符串
  174. */
  175. const hasOwnProperty = Object.prototype.hasOwnProperty
  176. export function hasOwn(obj: Object | Array<*>, key: string): boolean {
  177. return hasOwnProperty.call(obj, key)
  178. }
  179. /**
  180. * Create a cached version of a pure function.
  181. *
  182. * 对纯函数创建一个缓存版本
  183. * 这个函数是不是觉得有点不好理解?它的作用是什么?
  184. * 理解:缓存一个函数执行的结果,下次再执行相同的函数时,如果key相同,则不再执行原函数,直接从cache中通过key取值即可。
  185. * 可把下面test代码复制到浏览器console里执行
  186. */
  187. // -------------------test
  188. /*
  189. function cached(fn) {
  190. const cache = Object.create(null)
  191. return (function cachedFn(str) {
  192. console.log(cache)
  193. const hit = cache[str]
  194. var cc = hit || (cache[str] = fn(str))
  195. return cc
  196. })
  197. }
  198. var test = function(){
  199. console.log('执行计算');
  200. return 1+2;
  201. }
  202. var testCache = cached(test);
  203. console.log(testCache)
  204. console.log(testCache('sum1'))
  205. console.log(testCache('sum2'))
  206. console.log(testCache('sum1'))
  207. // 执行后打印结果:
  208. {}
  209. 执行计算
  210. 3
  211. {sum1: 3}
  212. 执行计算
  213. 3
  214. {sum1: 3, sum2: 3}
  215. 3
  216. // 从上面打印可以看出,执行两次计算,在第二次调用testCache('sum1')时,没有再执行计算,直接从缓存中取的结果
  217. */
  218. // -----------------test end
  219. export function cached<F: Function>(fn: F): F {
  220. const cache = Object.create(null)
  221. return (function cachedFn(str: string) {
  222. const hit = cache[str]
  223. return hit || (cache[str] = fn(str))
  224. }: any)
  225. }
  226. /**
  227. * Camelize a hyphen-delimited string.
  228. *
  229. * 把通过"-"连接符连接的字符串,转化成驼峰格式,第一个词不做处理
  230. * 如 'an-great-boy' => 'anGreatBoy','An-great-boy' => 'AnGreatBoy','An-_great-_boy' => 'An_great_boy'
  231. */
  232. // 匹配连接符(-)后面的 A-Za-z0-9_
  233. const camelizeRE = /-(\w)/g
  234. export const camelize = cached((str: string): string => {
  235. return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
  236. })
  237. /**
  238. * Capitalize a string.
  239. *
  240. * 转化字符串的首字母大写
  241. */
  242. export const capitalize = cached((str: string): string => {
  243. return str.charAt(0).toUpperCase() + str.slice(1)
  244. })
  245. /**
  246. * Hyphenate a camelCase string.
  247. *
  248. * 将驼峰字符串转换成连接符连接(-)
  249. * 如:anGreatBoy => an-great-boy
  250. */
  251. // 匹配非单词边界后面的大写字母(\B:非单词边界 \b:单词边界)
  252. const hyphenateRE = /\B([A-Z])/g
  253. export const hyphenate = cached((str: string): string => {
  254. return str.replace(hyphenateRE, '-$1').toLowerCase()
  255. })
  256. /**
  257. * Simple bind polyfill for environments that do not support it,
  258. * e.g., PhantomJS 1.x. Technically, we don't need this anymore
  259. * since native bind is now performant enough in most browsers.
  260. * But removing it would mean breaking code that was able to run in
  261. * PhantomJS 1.x, so this must be kept for backward compatibility.
  262. *
  263. * 为不支持bind方法的环境,提供简单的bind polyfill
  264. * 例如:PhantomJS 1.x。从技术上讲,我们不再需要这个了
  265. * 因为原生的bind 在大多数浏览器中性能已经足够用了
  266. * 但是删除它意味着破坏能够运行的代码
  267. * PhantomJS 1。为了向后兼容,这个必须保留
  268. */
  269. /* istanbul ignore next */
  270. function polyfillBind(fn: Function, ctx: Object): Function {
  271. function boundFn(a) {
  272. const l = arguments.length
  273. return l
  274. ? l > 1
  275. ? fn.apply(ctx, arguments) // arguments是一个类数组
  276. : fn.call(ctx, a)
  277. : fn.call(ctx)
  278. }
  279. boundFn._length = fn.length
  280. return boundFn
  281. }
  282. // 原生的bind方法
  283. function nativeBind(fn: Function, ctx: Object): Function {
  284. return fn.bind(ctx)
  285. }
  286. // 兼容的bind方法
  287. export const bind = Function.prototype.bind
  288. ? nativeBind
  289. : polyfillBind
  290. /**
  291. * Convert an Array-like object to a real Array.
  292. *
  293. * 转化一个类数组为真正的数组
  294. * @param {*} list 传入的类数组
  295. * @param {*} start 开始转化的index序号
  296. * @return {Array} 返回一个新数组
  297. */
  298. export function toArray(list: any, start?: number): Array<any> {
  299. start = start || 0
  300. let i = list.length - start
  301. const ret: Array<any> = new Array(i) // 定义一个length长度为i的空数组
  302. while (i--) {
  303. ret[i] = list[i + start]
  304. }
  305. return ret
  306. }
  307. /**
  308. * Mix properties into target object.
  309. *
  310. * 将属性混合到目标对象中
  311. */
  312. export function extend(to: Object, _from: ?Object): Object {
  313. for (const key in _from) {
  314. to[key] = _from[key]
  315. }
  316. return to
  317. }
  318. /**
  319. * Merge an Array of Objects into a single Object.
  320. *
  321. * 将对象数组(类似[{color:'red'},{width:'230px'}])合并为单个对象
  322. * eg: toObject([{color:'red'},{width:'230px'}]) => {color: "red", width: "230px"}
  323. */
  324. export function toObject(arr: Array<any>): Object {
  325. const res = {}
  326. for (let i = 0; i < arr.length; i++) {
  327. if (arr[i]) {
  328. extend(res, arr[i])
  329. }
  330. }
  331. return res
  332. }
  333. /* eslint-disable no-unused-vars */
  334. /**
  335. * Perform no operation.
  336. * Stubbing args to make Flow happy without leaving useless transpiled code
  337. * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
  338. *
  339. * 定义空操作
  340. */
  341. export function noop(a?: any, b?: any, c?: any) { }
  342. /**
  343. * Always return false.
  344. *
  345. * 始终返回false
  346. */
  347. export const no = (a?: any, b?: any, c?: any) => false
  348. /* eslint-enable no-unused-vars */
  349. /**
  350. * Return the same value.
  351. *
  352. * 传入什么参数,最后总是返回当前参数
  353. */
  354. export const identity = (_: any) => _
  355. /**
  356. * Generate a string containing static keys from compiler modules.
  357. *
  358. * 生成一个包含编译器模块静态键值的字符串
  359. */
  360. /*
  361. // eg:
  362. var test = genStaticKeys([{
  363. staticKeys:'a'
  364. },{
  365. staticKeys:'b'
  366. },{
  367. staticKeys:'c'
  368. }])
  369. console.log(test) => print: a,b,c
  370. */
  371. export function genStaticKeys(modules: Array<ModuleOptions>): string {
  372. return modules.reduce((keys, m) => {
  373. return keys.concat(m.staticKeys || [])
  374. }, []).join(',')
  375. }
  376. /**
  377. * Check if two values are loosely equal - that is,
  378. * if they are plain objects, do they have the same shape?
  379. * 判断两个对象内部结构是否相同(严格上来说两个对象是不相等,所以叫loose equal(松散相等?))
  380. */
  381. export function looseEqual(a: any, b: any): boolean {
  382. if (a === b) return true
  383. const isObjectA = isObject(a)
  384. const isObjectB = isObject(b)
  385. // A和B都是对象
  386. if (isObjectA && isObjectB) {
  387. try {
  388. const isArrayA = Array.isArray(a)
  389. const isArrayB = Array.isArray(b)
  390. // A和B都是数组
  391. if (isArrayA && isArrayB) {
  392. // 两个数组长度相等,循环递归
  393. return a.length === b.length && a.every((e, i) => {
  394. return looseEqual(e, b[i])
  395. })
  396. } else if (a instanceof Date && b instanceof Date) {
  397. // A和B是时间对象,则比较毫秒值
  398. return a.getTime() === b.getTime()
  399. } else if (!isArrayA && !isArrayB) {
  400. // A和B都不是数组
  401. // Object.keys=>处理对象,返回对象所有的KEY(会忽略子对象),组成一个数组,eg: var a = { name: {age:34}}; console.log(Object.keys(a)) // ['name']
  402. const keysA = Object.keys(a)
  403. const keysB = Object.keys(b)
  404. // 两个对象一级key长度相等,循环递归
  405. return keysA.length === keysB.length && keysA.every(key => {
  406. return looseEqual(a[key], b[key])
  407. })
  408. } else {
  409. /* istanbul ignore next */
  410. return false
  411. }
  412. } catch (e) {
  413. /* istanbul ignore next */
  414. return false
  415. }
  416. } else if (!isObjectA && !isObjectB) {
  417. // A 和B 都不是对象,则转换成字符串比较
  418. return String(a) === String(b)
  419. } else {
  420. return false
  421. }
  422. }
  423. /**
  424. * Return the first index at which a loosely equal value can be
  425. * found in the array (if value is a plain object, the array must
  426. * contain an object of the same shape), or -1 if it is not present.
  427. *
  428. * 返回松散相等的两个值的第一个索引,如果找不到则返回-1
  429. */
  430. export function looseIndexOf(arr: Array<mixed>, val: mixed): number {
  431. for (let i = 0; i < arr.length; i++) {
  432. if (looseEqual(arr[i], val)) return i
  433. }
  434. return -1
  435. }
  436. /**
  437. * Ensure a function is called only once.
  438. *
  439. * 确保一个函数只被调用了一次
  440. */
  441. export function once(fn: Function): Function {
  442. let called = false
  443. return function () {
  444. if (!called) {
  445. called = true
  446. fn.apply(this, arguments)
  447. }
  448. }
  449. }
版权声明:本站文章除特别声明外,均采用署名-非商业性使用-禁止演绎 4.0 国际 许可协议,如需转载,请注明出处