怎么免费创建一个网站,怎么下载文章到wordpress,什么网站会更有浏览量,大连市建设工程招标信息网大家好#xff0c;我是若川。最近组织了源码共读活动。每周读 200 行左右的源码。很多第一次读源码的小伙伴都感觉很有收获#xff0c;感兴趣可以加我微信ruochuan12#xff0c;拉你进群学习。初学者也能看懂的 Vue3 源码中那些实用的基础工具函数本文是纪年小姐姐源码共读第… 大家好我是若川。最近组织了源码共读活动。每周读 200 行左右的源码。很多第一次读源码的小伙伴都感觉很有收获感兴趣可以加我微信ruochuan12拉你进群学习。初学者也能看懂的 Vue3 源码中那些实用的基础工具函数本文是纪年小姐姐源码共读第二期写的笔记非常好可先收藏后学习。1. 解读前的准备粗略阅读了川哥的文章之后感觉这期跟上一期不一样。上一期主要学习如何实现某个功能而这一期主要是学习 Vue3 源码中的工具函数以及 Vue3 源码的一些调试技巧。虽然看起来偏基础但我觉得很考验一个程序员的基本功和耐心。学习目标1调试源码之打包构建项目代码生成 sourcemap 调试源码2学习源码中的工具函数目标跟着川哥的文章走完一遍调试的流程动手敲工具函数对外输出记录文档。资源准备Vue3 源码地址https://github.com/vuejs/vue-next2. 源码调试2.1 阅读开源项目的 README.md 和贡献指南 contributing.md我觉得这两个文件对阅读源码的开发者来说十分重要。README.md 描述的是项目的基本信息它可以快速了解这个项目的全貌。贡献指南 contributing.md 会包含如何参与项目开发项目打包/运行命令项目目录结构等等它能帮助你更好地调试/参与开发源码。在 contributing.md 中我看到了一些比较感兴趣的知识点比如打包构建格式/配置包依赖处理。2.2 打包构建项目代码安装完依赖直接运行yarn build就可以打包 Vue3 的项目代码了打包的产物如下以 shared 模块为例打包后的产物这里的 cjsesm 是 JS 里用来实现【模块化】的不同规则JS 的模块化标准还有 amdumdiife。CJSCommonJS只能在 NodeJS 上运行使用 require(module) 读取并加载模块不支持浏览器ESMECMAScript Module现在使用的模块方案使用 import export 来管理依赖浏览器直接通过 script typemodule 即可使用该写法。NodeJS 可以通过使用 mjs 后缀或者在 package.json 添加 type: module 来使用2.3 生成 sourcemap 调试 vue-next 源码在贡献指南 contributing.md 文件中描述了如何生成 sourcemap 文件添加【--sourcemap】参数即可。node scripts/dev.js --sourcemap
packages/vue/dist/vue.global.js.map 就是 sourcemap 文件了。sourcemap 是一个信息文件里面储存着位置信息转换后的代码的每一个位置所对应的转换前的位置。有了它出错时出错工具将直接显示原始代码而不是转换后的代码方便调试。3. 工具函数TS 版3.1 babelParserDefaultPluginsbabel 解析默认插件/*** List of babel/parser plugins that are used for template expression* transforms and SFC script transforms. By default we enable proposals slated* for ES2020. This will need to be updated as the spec moves forward.* Full list at https://babeljs.io/docs/en/next/babel-parser#plugins*/
const babelParserDefaultPlugins [bigInt,optionalChaining,nullishCoalescingOperator
] as const
它定义了三个默认插件 as const 这个语法叫 const 断言它可以创建完整的 readonly 对象只读状态编译器可以通过 as const 推断出可用于的最具体的表达类型。3.2 EMPTY_OBJ空对象EMPTY_ARR空数组export const EMPTY_OBJ: { readonly [key: string]: any } __DEV__? Object.freeze({}): {}export const EMPTY_ARR __DEV__ ? Object.freeze([]) : []
Object.freeze 冻结对象不可修改对象的最外层这样的写法可以降低在开发过程中发生错误。DEV 是一个环境变量为了避免在生产环境报错生产环境使用的还是 {} 和 []。3.3 NOOP空函数export const NOOP () {}
3.4 NO永远返回 false 的函数export const NO () false
3.5 isOn判断字符串是否以 on 开头并且 on 后首字母是非小写字母const onRE /^on[^a-z]/
export const isOn (key: string) onRE.test(key)
【^】符号在开头表示是指【以什么开头】在其他地方是指【非】。与之相反的是【$】符合在结尾则表示是以什么结尾。日常开发中我们也经常会用到正则判断可以收集起来积累的数量多了就不用每次都去搜索了????。3.6 isModelListener监听器export const isModelListener (key: string) key.startsWith(onUpdate:)
判断字符串是不是以【onUpdate:】开头3.7 extend合并对象export const extend Object.assign
其实 extend 就是 Object.assign用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。3.8 remove移除数组的一项export const remove T(arr: T[], el: T) {const i arr.indexOf(el)if (i -1) {arr.splice(i, 1)}
}
看源码的实现很好理解传入一个数组和一个元素判断元素是否存在在数组中如果存在将其删除。川哥的文章里有说到splice 是一个很耗性能的方法删除数组中的一项其他元素都要移动位置。所以在考虑性能的情况下可以将删除的元素设为 null在使用执行时为 null 的不执行也可达到相同的效果。3.9 hasOwn判断一个属性是否属于某个对象const hasOwnProperty Object.prototype.hasOwnProperty
export const hasOwn (val: object,key: string | symbol
): key is keyof typeof val hasOwnProperty.call(val, key)
函数本身很好理解利用原型的 APIhasOwnProperty 来判断 key 是否是 obj 本身的属性。但【key is keyof typeof val】可能会有些迷惑这里包含了三个 typescript 的语法意思是函数返回的 key 是 属于 val 对象的键的联合类型。【is】关键字它被称为类型谓词用来判断一个变量属于某个接口或类型比如const isNumber (val: unknown): val is number typeof val number
const isString (val: unknown): val is string typeof val string
【keyof】关键字用于获取某种类型的所有键其返回类型是联合类型比如interface Person {name: string;age: number;
}
type K keyof Person; // name | age
【typeof】关键字js 中的 typeof 只能获取几种类型而在 ts 中 typeof 用来获取一个变量声明或对象的类型比如interface Person {name: string;age: number;
}const sem: Person { name: semlinker, age: 30 };
type Sem typeof sem; // - Person
3.10 判断是否某种类型// 判断数组
export const isArray Array.isArray// 对象转字符串
export const objectToString Object.prototype.toString
export const toTypeString (value: unknown): string objectToString.call(value)// 判断是否 Map 对象
export const isMap (val: unknown): val is Mapany, any toTypeString(val) [object Map]// 判断是否 Set 对象
export const isSet (val: unknown): val is Setany toTypeString(val) [object Set]// 判断是否 Date 对象
export const isDate (val: unknown): val is Date val instanceof Date// 判断是否函数
export const isFunction (val: unknown): val is Function typeof val function// 判断是否字符串
export const isString (val: unknown): val is string typeof val string// 判断是否 Symbol
export const isSymbol (val: unknown): val is symbol typeof val symbol// 判断是否对象不包括 null
export const isObject (val: unknown): val is Recordany, any val ! null typeof val object// 判断是否 Promise
export const isPromise T any(val: unknown): val is PromiseT {return isObject(val) isFunction(val.then) isFunction(val.catch)
}
有了这些函数就可以在工作中用起来啦。3.11 toRawType对象转字符串截取后第八位到倒数第二位。export const toRawType (value: unknown): string {// extract RawType from strings like [object RawType]return toTypeString(value).slice(8, -1)
}
可以截取到 String Array 等这些类型这个函数可以用来做类型判断。3.12 isPlainObject判断是否纯粹的对象export const isPlainObject (val: unknown): val is object toTypeString(val) [object Object]
3.13 isIntegerKey判断是不是数字型的字符串 key 值export const isIntegerKey (key: unknown) isString(key) key ! NaN key[0] ! - parseInt(key, 10) key
第一步先判断 key 是否是字符串类型作为 key 值有两种类型string 和 symbol第二步排除 NaN 值第三步排除 - 值排除负数第四步将 key 转换成数字再隐式转换为字符串与原 key 对比。3.14 isReservedProp判断该属性是否为保留属性/*** Make a map and return a function for checking if a key* is in that map.* IMPORTANT: all calls of this function must be prefixed with* \/\*#\_\_PURE\_\_\*\/* So that rollup can tree-shake them if necessary.*/
export function makeMap(str: string,expectsLowerCase?: boolean
): (key: string) boolean {const map: Recordstring, boolean Object.create(null)const list: Arraystring str.split(,)for (let i 0; i list.length; i) {map[list[i]] true}return expectsLowerCase ? val !!map[val.toLowerCase()] : val !!map[val]
}export const isReservedProp /*#__PURE__*/ makeMap(// the leading comma is intentional so empty string is also included,key,ref, onVnodeBeforeMount,onVnodeMounted, onVnodeBeforeUpdate,onVnodeUpdated, onVnodeBeforeUnmount,onVnodeUnmounted
)// 使用
isReservedProp(key) // true
isReservedProp(test) // false
isReservedProp() // true
如何解读这个函数先看 makeMap它传入一个字符串将这个字符串转换成数组并循环赋值 key 給一个空对象map然后返回一个包含参数 val 的闭包用来检查 val 是否是存在在字符串中。isReservedProp(key) 其实就相当于 makeMap(str)(key)。3.15 cacheStringFunction 缓存字符串的函数const cacheStringFunction T extends (str: string) string(fn: T): T {const cache: Recordstring, string Object.create(null)return ((str: string) {const hit cache[str]return hit || (cache[str] fn(str))}) as any
}// 使用例子
// -连字符转小驼峰
// \w0-9a-zA-Z_表示由数字大小写字母和下划线组成
const camelizeRE /-(\w)/g
export const camelize cacheStringFunction((str: string): string {return str.replace(camelizeRE, (_, c) (c ? c.toUpperCase() : ))
})
camelize(text-node) // textNode// 大写字母转-连字符
// \B 是指 非 \B 单词边界。
const hyphenateRE /\B([A-Z])/g;
const hyphenate cacheStringFunction((str) str.replace(hyphenateRE, -$1).toLowerCase());
hyphenate(WordPress) // word-press// 首字母转大写
const capitalize cacheStringFunction((str: string) str.charAt(0).toUpperCase() str.slice(1)
)
const toHandlerKey cacheStringFunction((str) (str ? on${capitalize(str)} : ));
toHandlerKey(click) // onClick
这个函数和上面 makeMap 函数类似传入一个 fn 参数返回一个包含参数 str 的闭包将这个 str 字符串作为 key 赋值给一个空对象 cache闭包返回 cache[str] || (cache[str] fn(str))。【cache[str] || (cache[str] fn(str))】的意思是如果 cache 有缓存到 str 这个 key直接返回对应的值否则先调用 fn(str)再赋值给 cache[str]这样可以将需要经过 fn 函数处理的字符串缓存起来避免多次重复处理字符串。3.16 hasChanged判断值是否有变化const hasChanged (value: any, oldValue: any): boolean !Object.is(value, oldValue)
Object.is 方法判断两个值是否为同一个值。3.17 invokeArrayFns执行数组里的函数export const invokeArrayFns (fns: Function[], arg?: any) {for (let i 0; i fns.length; i) {fns[i](arg)}
}
这种写法方便统一执行多个函数。3.18 def定义一个不可枚举的对象export const def (obj: object, key: string | symbol, value: any) {Object.defineProperty(obj, key, {configurable: true,enumerable: false,value})
}
Object.defineProperty语法Object.defineProperty(obj, prop, descriptor)它是一个非常重要的 API经常会在源码中看见它。在 ES3 中除了一些内置属性如Math.PI对象所有的属性在任何时候都可以被[修改、插入、删除。在ES5 中我们可以设置属性是否可以被改变或是被删除——在这之前它是内置属性的特权。ES5 中引入了属性描述符的概念我们可以通过它对所定义的属性有更大的控制权这些属性描述符特性包括value —— 获取属性时所返回的值。writable —— 该属性是否可写。enumerable —— 该属性在 for in 循环中是否会被枚举。configurable —— 该属性是否可被删除。set() —— 该属性的更新操作所调用的函数。get() —— 获取属性值时所调用的函数。另外数据描述符其中属性为enumerableconfigurablevaluewritable与存取描述符其中属性为enumerableconfigurableset()get()之间是有互斥关系的。在定义了set()和get()之后描述符会认为存取操作已被定义了其中再定义 value 和 writable 会引起错误。3.19 toNumber转数字 export const toNumber (val: any): any {const n parseFloat(val)return isNaN(n) ? val : n}
3.20 getGlobalThis全局对象let _globalThis: any
export const getGlobalThis (): any {return (_globalThis ||(_globalThis typeof globalThis ! undefined? globalThis: typeof self ! undefined? self: typeof window ! undefined? window: typeof global ! undefined? global: {}))
}
第一次调用这个函数时_globalThis 肯定为 undefined接着执行【||】后的语句。typeof globalThis ! undefined 如果 globalThis 不是 undefined返回 globalThisMDN globalThis。否则 -typeof self ! undefined 如果 self 不是 undefined返回 self。否则 -typeof window ! undefined 如果 window 不是 undefined返回 widow。否则 -typeof global ! undefined 如果 global 不是 undefined返回 global。否则 -返回 {}第二次调用这个函数就直接返回 _globalThis不需要第二次继续判断了????4. 感想很多工具函数可以通过做缓存以达到优化性能的目的Object 对象 API 解析 无论什么时候都不过时适合反复阅读加深对 Object 的理解工作中如果有用到类似的工具函数可参考这些写法学习了一些 typescript 不太常见的语法【! 非空断言操作符】【?? 空值合并运算符】生成 sourcemap 调试 ts 代码最近组建了一个湖南人的前端交流群如果你是湖南人可以加我微信 ruochuan12 私信 湖南 拉你进群。推荐阅读我在阿里招前端该怎么帮你可进面试群我读源码的经历面对 this 指向丢失尤雨溪在 Vuex 源码中是怎么处理的老姚浅谈怎么学JavaScript················· 若川简介 ·················你好我是若川毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》多篇在知乎、掘金收获超百万阅读。从2014年起每年都会写一篇年度总结已经写了7篇点击查看年度总结。同时活跃在知乎若川掘金若川。致力于分享前端开发经验愿景帮助5年内前端人走向前列。识别上方二维码加我微信、拉你进源码共读群今日话题略。欢迎分享、收藏、点赞、在看我的公众号文章~