第2期|【源码】vue3工具函数源码解读

学习笔记📒
源码解读第二期: 知其然,知其所以然

  • 这个解读核心主要是shared模块,
  • 对应的文件路径是:vue-next/packages/shared/src/index.ts
  • 也可以用github1s访问,速度更快github1spackages/shared/src/index.ts
    参考
打包构建

按照贡献指南 的方法打包,把ts 转成js
打包前注意事项:

  • Node.js 版本是 10+
  • yarn 的版本是 1.x Yarn 1.x。
node -v
# v14.16.0
# 全局安装 yarn
# 克隆项目
git clone https://github.com/vuejs/vue-next.git
cd vue-next
yanr
yarn build

执行完以上操作以后可以得到:vue -next/packages/shared/dist/shared.esm-bundler.js,文件也就是纯js文件。

生成 sourcemap 调试源码

调试看源码可以一步步感受过程,所以学习如何生成sourcemap ,如何配置就很重要,虽然(贡献指南里面描述了)[],但是写的仔细点快记在小本本上: 在vue-next/package.json追加 "dev:sourcemap": "node scripts/dev.js --sourcemap"yarn dev:sourcemap执行,即可生成sourcemap,或者直接 build。

// package.json
{
    "version": "3.2.1",
    "scripts": {
        "dev:sourcemap": "node scripts/dev.js --sourcemap"
    }
}

其中packages/vue/dist/vue.global.js.map 就是sourcemap文件了。

工具函数解读
  1. babelParserDefaultPlugins babel 解析默认插件
/**
* 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'
];
  1. EMPTY_OBJ 空对象
const EMPTY_OBJ = (process.env.NODE_ENV !== 'production')
    ? Object.freeze({})
    : {};

  • process.env.NODE_ENV node 项目中的一个环境变量,一般定义为: developmentproduction,根据环境写代码。比如开发环境,有报错等信息,生产环境则不需要这些报错警告。
  • Object.freeze 是 冻结对象, 冻结的对象最外层无法修改。
  • 当为production的时候返回false 其余true6 ,
  1. EMPTY_ARR 空数组
const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];

// 例子:
EMPTY_ARR.push(1) // 报错,也就是为啥生产环境还是用 []
EMPTY_ARR.length = 3;
console.log(EMPTY_ARR.length); // 0
  1. NOOP 空函数
const NOOP = () => { };

// 很多库的源码中都有这样的定义函数,比如 jQuery、underscore、lodash 等
// 使用场景:1. 方便判断, 2. 方便压缩
  1. 永远返回false 的函数NO
const NO = () => false;
  1. isOn 判断字符串是不是 on 开头, 并且 on 后首字母是不是小写字母
const onRE = /^on[^a-z]/;
const isOn = (key) => onRE.test(key);
console.log(isOn('onclick'))  //false
console.log(isOn('onClick'))  //true
console.log(isOn('on..123'))  //true
  • ^on是以on开头 $是结尾,[^a-z]表示不是az的小写字母
  1. isModelListener 监听器
const isModelListener = (key) => key.startsWith('onUpdate:');
  • 字面解读,判断key这个字符串是不是以onUpdate开头的
  • startsWith ES6 中的方法链接
  1. extend 继承 合并
const extend = Object.assign;

// 相同属性会被覆盖,
const obj1 = { name: dyy }
const obj2 = { name: huahua, value, ‘1岁’}
console.log(extend(obj1,obj2)) // { name: huahua, value, ‘1岁’}
  1. remove 移除数组中指定元素
const remove = (arr, el) => {
    const i = arr.indexOf(el);
    if (i > -1) {
        arr.splice(i, 1);
    }
};


// 例子:
const arr = [1, 2, 3,4,5];
remove(arr, 4);
console.log(arr); // [1, 2,3,5]
  • 字面意思:判断数组中有没有el 这个元素,如果有就删除
  • splice删除数组中的一项,其他的都要移动位置,很耗性能
  • 在这里插入图片描述
    所以也可以用这个思想,把删除的元素设为 null,在使用执行时为 null 的不执行,也可达到相同的效果。
  1. hasOwn 判断是否是自己本身所拥有的属性
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
  • 用原型的API:hasOwnProperty来判断 key 是否是 obj 本身的属性。
  • __proto__ 是浏览器实现的原型写法
    +.call则是函数里this显示指定以为第一个参数,并执行函数。
  1. is XXX 判断某种类型
// 判断是否为数组
const isArray = Array.isArray;

isArray([]); // true
const fakeArr = { __proto__: Array.prototype, length: 0 };
isArray(fakeArr); // false
fakeArr instanceof Array; // true
// 所以 instanceof 这种情况 不准确


const isMap = (val) => toTypeString(val) === '[object Map]';

// 例子:
const map = new Map();
const o = { p: 'aa' };

map.set(o, 'bb');
map.get(o); // 'bb'
isMap(map); // true

// 判断是否 Set 对象
const isSet = (val) => toTypeString(val) === '[object Set]';

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
// 判断是否 Date 对象
const isDate = (val) => val instanceof Date;
// 例子:
isDate(new Date()); // true

// `instanceof` 操作符左边是右边的实例。但不是很准,但一般够用了。原理是根据原型链向上查找的。

isDate({__proto__ : new Date()); // true
// 实际上是应该是 Object 才对。
// 所以用 instanceof 判断数组也不准确。
// 再比如
({__proto__: [] }) instanceof Array; // true
// 实际上是对象。
// 所以用 数组本身提供的方法 Array.isArray 是比较准确的。
// 判断是否函数
const isFunction = (val) => typeof val === 'function';
// 判断数组有多种方法,但这个是比较常用也相对兼容性好的。

// 判断是否字符串
const isString = (val) => typeof val === 'string';

// 判断是否 Symbol
const isSymbol = (val) => typeof val === 'symbol';
// 原始数据类型Symbol,表示独一无二的值。
//Symbol是函数,不需要new 调用

// 判断是否对象(不包括 null)
const isObject = (val) => val !== null && typeof val === 'object';
// 例子:
isObject(null); // false
isObject({name: '若川'}); // true
// 判断不为 null 的原因是 typeof null 其实 是 object


// 判断是否 Promise
const isPromise = (val) => {
    return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
  • is被称为类型谓词,用来判断一个变量属于某个接口或类型
  1. objectToString 对象转字符串
const objectToString = Object.prototype.toString;
  1. toTypeString 对象转字符串
const toTypeString = (value) => objectToString.call(value);

// call 是一个函数,第一个参数是 执行函数里面 this 指向。
// 通过这个能获得 类似  "[object String]" 其中 String 是根据类型变化的
  1. toRawType 对象转字符串 截取后几位
const toRawType = (value) => {
    // extract "RawType" from strings like "[object RawType]"
    return toTypeString(value).slice(8, -1);
};

// 截取到
toRawType('');  'String'

  • 可以截取到 String Array 等这些类型,这个函数可以用来做类型判断。
  1. isPlainObject 判断是不是纯粹的对象
const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
// 
const isPlainObject = (val) => toTypeString(val) === '[object Object]';

// 前文中 有 isObject 判断是不是对象了。
// isPlainObject 这个函数在很多源码里都有,比如 jQuery 源码和 lodash 源码等,具体实现不一样

  1. isIntegerKey:判断是不是数字型的字符串 key 值
const isIntegerKey = (key) => isString(key) &&
    key !== 'NaN' &&
    key[0] !== '-' &&
    '' + parseInt(key, 10) === key;
  • 先判断key是不是字符串,排查NaN, 排除(- 值)负数,把 key 转换成数字再隐式转换为字符串,与原 key 对比。
  1. 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.
 */
function makeMap(str, expectsLowerCase) {
    const map = Object.create(null);
    const list = str.split(',');
    for (let i = 0; i < list.length; i++) {
        map[list[i]] = true;
    }
    return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val];
}

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")
  1. cacheStringFunction 缓存字符串的函数
const cacheStringFunction = (fn) => {
    const cache = Object.create(null);
    return ((str) => {
        const hit = cache[str];
        return hit || (cache[str] = fn(str));
    });
};


// 使用例子:
// "-"连字符转小驼峰
// \w:0-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 作为key的值,那么就直接返回,否则的就先调用fn(str)这个函数,在赋值给cache[str],这样可以将经过fn的值缓存起来,从而避免重复处理字符串
  1. hasChanged 判断是否有变化
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
  • Object.is 方法判断两个值是否为同一个值。
  1. invokeArrayFns 执行数组里的函数
const invokeArrayFns = (fns, arg) => {
    for (let i = 0; i < fns.length; i++) {
        fns[i](arg);
    }
};

//例子
const arr = [
    function(val){
        console.log(val + '你好');
    },
    function(val){
        console.log('mycode' + val);
    },
    function(val){
        console.log('啦啦啦啦啦啦' + val);
    },
]
invokeArrayFns(arr, 'hello');
  • 方便统一执行多个函数
  1. def:定义一个不可枚举的对象
const def = (obj, key, value) => {
    Object.defineProperty(obj, key, {
        configurable: true,
        enumerable: false,
        value
    });
};
  • Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
  • 原型语法 Object.defineProperty(obj, prop, descriptor)
  • ES3中,除了一些内置属性(如:Math.PI),对象的所有的属性在任何时候都可以被修改、插入、删除。
  • ES5中,我们可以设置属性是否可以被改变或是被删除——在这之前,它是内置属性的特权。
  • ES5中引入了属性描述符的概念,我们可以通过它对所定义的属性有更大的控制权。这些属性描述符(特性)包括:
    value —— 获取属性时所返回的值。
    writable —— 该属性是否可写。
    enumerable —— 该属性在 for in 循环中是否会被枚举。
    configurable —— 该属性是否可被删除。
    set() —— 该属性的更新操作所调用的函数。
    get() —— 获取属性值时所调用的函数。
    另外,数据描述符(其中属性为:enumerableconfigurablevalue,writable)与存取描述符(其中属性为enumerable,configurable,set(),get())之间是有互斥关系的。在定义了set()get()之后,描述符会认为存取操作已被定义了,其中再定义 valuewritable 会引起错误。
  1. toNumber 转数字
const toNumber = (val) => {
    const n = parseFloat(val);
    return isNaN(n) ? val : n;
};
  1. getGlobalThis:全局对象
let _globalThis;
const getGlobalThis = () => {
    return (_globalThis ||
        (_globalThis =
            typeof globalThis !== 'undefined'
                ? globalThis
                : typeof self !== 'undefined'
                    ? self
                    : typeof window !== 'undefined'
                        ? window
                        : typeof global !== 'undefined'
                            ? global
                            : {}));
};
  • 获取全局 this 指向。
  • 第一次调用这个函数时,_globalThis 肯定为 “undefined”,接着执行【||】后的语句。
    typeof globalThis !== 'undefined' 如果globalThis不是 undefined,返回 globalThis:MDN globalThis。否则 ->
    typeof self !== 'undefined' 如果self不是 undefined,返回 self。否则 ->
    typeof window !== 'undefined' 如果 window 不是 undefined,返回 widow。否则 ->
    typeof global !== 'undefined' 如果 global 不是 undefined,返回 global。否则 ->
    返回 {}
    第二次调用这个函数,就直接返回 _globalThis,不需要第二次继续判断
总结
  • cacheStringFunction 函数,通过闭包的形式缓存,性能优化
  • _globalThis 获取全局对象的方法
  • 以及ts 转换 js de 方法, 生成 sourcemap 调试源码等
  • 好多小细节,好记性不如烂笔头,抄一遍都有收获
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值