Vue3工具函数源码踩坑记

·  阅读 790
Vue3工具函数源码踩坑记

一、学习前的准备

下面内容来自若川-源码共读群~~

1.1 我开始啦

step1:

    git clone https://github.com/vuejs/vue-next.git
复制代码

听川川的,查看一下版本

admin@ayuandeMacBook-Pro vue-next % node -v
v10.19.0
admin@ayuandeMacBook-Pro vue-next % yarn -v 
zsh: command not found: yarn
复制代码

竟然没装yarn,安排一下

    npm install --global yarn
    
复制代码

OK啦:

admin@ayuandeMacBook-Pro vue-next % yarn build
yarn run v1.22.11
    
复制代码

一切准备就绪: 结果。。。 npm install??

npm run build ??

yarn install?? 没反应,一直报错,心态爆炸,认真审题很重要,仔细看文档也一样!

后来发现,傻啦吧唧的全搞错了,人家川川文章里那么明细的写着(赶紧抄过来记笔记):

为了降低文章难度,我按照贡献指南中方法打包把ts转成了js。如果你需要打包,也可以参考下文打包构建。

你需要确保 Node.js 版本是 10+, 而且 yarn 的版本是 1.x Yarn 1.x。

你安装的 Node.js 版本很可能是低于 10。最简单的办法就是去官网重新安装。也可以使用 nvm等管理Node.js版本

    node -v
    # v14.16.0
    # 全局安装 yarn

    # 推荐克隆我的项目
    git clone https://github.com/lxchuan12/vue-next-analysis.git
    cd vue-next-analysis/vue-next

    # 或者克隆官方项目
    git clone https://github.com/vuejs/vue-next.git
    cd vue-next

    npm install --global yarn
    yarn # install the dependencies of the project
    yarn build
复制代码

最后安装好了yarn之后只需要执行一下操作:

yarn

yarn build

复制代码

好了运行结束后, 终于成功将ts打包成js了。 绝绝子,想不通自己浪费大把时间在这较什么劲呢!

1.2 插曲之安装环境小总结:

之前自己的node版本是v.10.X,一些高级的东西需要高版本,为了不来回的装node不同版本,我需要用nvm工具!!!可是我滴Mac上没有装过啊,于是就有了痛苦面具下的下面的操作总结

1、macos,因为涉及升级node版本,为了方便管理node版本切换,需要安装nvm node版本管理工具。

2、安装nvm时,尝试查询资料说不macos系统安装nvm前必须要卸载npm 和node,卸载干干净净。

3、卸载完成后, 根据官网教程安装:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
复制代码

结果抛出 SSL_ERROR_SYSCALL in connection to raw.githubusercontent.com:443错误,脑阔疼!

4、搜来搜去,说需要修改hosts文件,新增dns域名配置:配置后更新无果还是443,生气ing(原因:据说mac不能通过外部链接来安装,也有一种说法说是dns污染,需要配置host,但是把配置了host也是无效啊。)

199.232.68.133	raw.githubusercontent.com
复制代码

5、最终找到有效方法:使用gitee镜像安装

使用gitee镜像安装

1、安装nvm
git clone https://gitee.com/mirrors/nvm.git ~/.nvm && cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`
复制代码
2、配置nvm环境变量

a、进入.bash_profile文件设置环境变量:

vi ~/.bash_profile
复制代码

b、配置环境变量:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
复制代码

c、执行命令生效:

source ~/.bash_profile
复制代码

如果shell使用的是非zsh,以上三步配置完即可,如果是zsh环境,需要多配置以下

如何查看shell:

echo $SHELL

---------
如果显示:
/bin/zsh
复制代码

则执行d、

d、zsh配置: 打开.zshrc文件,操作如下:

vim ~/.zshrc
复制代码

配置信息如下:

source ~/.bash_profile
复制代码

保存后退出执行如下命令行使之生效:

source ~/.zshrc
复制代码

经过如上步骤配置,nvm也配置成功了: 可查看nvm版本:

nvm -v

0.38.0
复制代码

抱头痛哭,这么简单的东西终于给我装好了。 !!!

好了, 之前运行的npm install --global yarn 的yarn也没有了,需要重新安装下, 巴拉巴拉一顿操作后迎接我的又是一片终端报错红彤彤,逐渐失去耐心,回头猛然想起,我的macos啊!!!没权限访问它了。

sudo npm install --global yarn
复制代码

一顿操作猛如虎,终于把这个vuex-next的打包搞定了,绝绝子,这下可以安心看小工具源码了,不费川川被我骚扰千百回解决的问题啊,好了下面我要开始认真学习看源码了!

二、 重点:工具函数

看了下文章目录vue-next/packages/shared/src/index.ts 按照这个文件里的代码顺序讲解,那我就慢慢看吧

1、 babelParserDefaultPlugins 是babel解析默认插件

来来来copy一份源代码

/**
 * 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
 */
export const babelParserDefaultPlugins = [
  'bigInt',
  'optionalChaining', 
  'nullishCoalescingOperator'
] as const

复制代码

不懂就问系列,上面的是TS代码中, as const 是什么意思呢? 查阅了下,哦吼,原来是TS中的类型断言啊!

对比转换为js后代码如下,顺便自带翻译一下注释:

/**
*用于模板表达式的@babel/parser插件列表
*转换和SFC脚本转换。默认情况下,我们启用计划的提案
*以下都是针对ES2020新特性做babel转换用的。
*详细babel插件列表查看:https://babeljs.io/docs/en/next/babel-parser#plugins
*/
const babelParserDefaultPlugins = [
    'bigInt', //ES2020新增特性 
    'optionalChaining', // 可选链`?.`
    'nullishCoalescingOperator' //空值合并运算符`??`
];
复制代码

1.1babel解析默认插件

1.1.1 bigInt

我们已经用Number来表示JS中的数字,问题在于最大的数字是2⁵³,再往上的数字就不可靠了。

const x = Number.MAX_SAFE_INTEGER; // 9007199254740991
const y = x + 1; // 9007199254740992 • equal to 2^53
const z = x + 2; // 9007199254740992 • well, it's broken
复制代码

BigInt提供了一种方法,来表示大于2⁵³的数字。通过在整数末尾添加n来创建

const aBigInteger = 9007199254740993n;
// There is also a constructor:
const evenBigger = BigInt(9007199254740994); // 9007199254740994n
const fromString = BigInt("9007199254740995"); // 9007199254740995n
复制代码

BigInt通常的操作与数字相同,但不能在运算中一同使用:

let sum = 1n + 2, multiplication = 1n * 2;
// TypeError: Cannot mix BigInt and other types, use explicit conversions
复制代码

可使用构造函数Number()将BigInt转化为Number,但在某些情况下可能会失去精度。因此,建议仅在代码中相应预期数值较大时再使用BigInt。

Number(900719925474099267n); // 900719925474099300 • ????‍♂️

复制代码

@babel/plugin-syntax-bigint: 官网-@babel/plugin-syntax-bigint

1.1.2 optionalChaining-可选链运算符?.

例如原来遍历对象如下:

// Checking for intermediate nodes:
const deeplyNestedValue = obj && obj.prop1 && obj.prop1.prop2;// Checking if the node exists in the DOM:
const fooInputEl = document.querySelector('input[name=foo]');
const fooValue = fooInputEl && fooInputEl.value;
复制代码

采用可选链运算符后:

// Checking for intermediate nodes:
const deeplyNestedValue = obj?.prop1?.prop2;// Checking if the node exists in the DOM:
const fooValue = document.querySelector('input[name=foo]')?.value;
复制代码

可选链运算符是一个短路计算操作,即当左侧?.检查到null或undefined时,就会停止表达式的运行

// x is incremented if and only if 'a' is not null or undefined
a?.[++x] 
复制代码

参考借鉴: 可选链plugin-proposal-optional-chaining的使用

1.1.3 nullishCoalescingOperator -空值合并运算符??

当执行属性访问时尝试提供默认值,新的方法便是采用空值合并运算符。与or运算符不同,我们在两个操作数之间以 ??来代替||操作符。

const test = {
  null: null,
  number: 0,
  string: '',
  boolean: false
};
const undefinedValue = test.dog || "Cat"; // "Cat"
const undefinedValue = test.dog ?? "Cat"; // "Cat"const nullValue = test.null || "Default"; // "Default"
const nullValue2 = test.null ?? "Default"; // "Default"const numberValue = test.number || 1; // 1
const numberValue2 = test.number ?? 1; // 0const stringValue = test.string || "Hello"; // "Hello"
const stringValue2 = test.string ?? "Hello"; // ""const booleanValue = test.boolean || true; // true
const booleanValue2 = test.boolean ?? true; // false
复制代码

如上所示,空值合并运算符仅在 ??左侧的操作数为null或undefined时,返回右侧的操作数。

官网:@babel/plugin-proposal-nullish-coalescing-operator

1.2 TS语法-TS的类型断言

1.2.1 TS断言概念及作用?

有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。

1.2.2 TS断言语法?

类型断言有两种形式。 其一是“尖括号”语法:

let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;

//有兼容性问题, 在使用到了JSX的时候兼容性不是很好
复制代码

另一个为as语法:

let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;
复制代码

两种形式是等价的。

至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。

延伸参考:TS的类型断言

1.3 总结

第一个工具方法:babelParserDefaultPlugins- babel 解析器默认插件 看着没几行代码, 但是平时自己用的并不多,写TS语法的代码也少。babel中的三个都是用于转换ES2020新增特性用的。其中详细看了下对应几个babel插件作用,以及复习下TS断言的作用,一步一步操作理解,印象比较深刻。


截止2021年10月08日,接下来继续!

2、EMPTY_OBJ 空对象 & EMPTY_ARR 空数组

2.1 、EMPTY_OBJ 空对象

源码:

export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
  ? Object.freeze({})
  : {}
复制代码

转换js:

const EMPTY_OBJ = Object.freeze({});
复制代码

2.2 EMPTY_ARR 空数组

TS源码:

export const EMPTY_ARR = __DEV__ ? Object.freeze([]) : []

复制代码

转JS后代码:

const EMPTY_ARR = Object.freeze([]) ;
复制代码

2.3 Object.freeze()

Object.freeze() 方法可以冻结一个对象。 一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。

此外,冻结一个对象后的原型也不能被修改。 freeze()返回和传入的参数相同的对象。

语法
Object.freeze(obj)
复制代码

参数: obj 要被冻结的对象 返回值: 被冻结的对象

冻结对象-举个栗子
var obj = {
    prop:()=>{},
    foo:'bar'
};

//新点属性会被添加,已存在点属性可能会被修改或移除
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;

//作为参数传递的对象与返回点对象都被冻结,所以不必保存返回的对象(因为两个对象全等)
var  o = Object.freeze(obj);

o === obj; //true
Object.isFrozen(obj); // === true

// 再次操作修改、新增或删除对象属性都会失效。
obj.foo = 'Quux' //不做任何操作
obj.hecate = 'my'; //静默点不添加此属性

// 在严格模式,如此行为将抛出TypeErrors
function fail(){
    'use strict';
    obj.foo = 'sparky'; // throws a TypeError
    delete obj.hecate; // 返回true, 因为hecate属性从来未被添加
    obj.sparky = 'arf'; //throws a TypeError
}

fail();

// 试图通过Object.defineProperty 更改属性
// 下面两个语句都会抛出 TypeError
Object.defineProperty(obj,'ohai',{value:17});
Object.defineProperty(obj,'foo',{value:'eit'});

// 不能更改原型
// 下面两个语句都会抛出 TypeError
Object.setPrototypeOf(obj,{x:20})
obj.__proto__ = {x:20}

复制代码
冻结数组-举个栗子

在chrome浏览器内操作运行:

let a = [0];
Object.freeze(a); //现在数组a不能被修改了

a[0]=1; //不报错,不更新
a.push(2); // 抛TypeError: Cannot add property 1. object is not extensible

//在严格模式下,抛TypeError
function fail(){
    'use strict'
     a[0] = 1; // 抛异常 TypeError: Cannot assign to read only property '0' of object '[object Array]'
     a.push(2);
}

fail();

复制代码

被冻结的对象是不可变的。但也不总是这样。

例如(下面栗子的冻结对象不是常量对象)(浅冻结)

更多Object.freeze() 方法详情可点击MDN

推荐阅读:Vue性能提升之Object.freeze()

3、NOOP 空函数

源码:

export const NOOP = () => {}

复制代码

使用场景: 1、方便判断、 2、方便压缩

例如很多库的源码中都有这样的定义函数,如 jQuery、 underscore、lodash 等

方便判断咱来举个栗子:

const NOOP = () => {};

const instance = {
    render: NOOP
};

//条件
const dev = true;
if(dev){
    instance.render = function(){
        console.log('render')
    }
}

// 可以用作判断
if(instance.render === NOOP){
    console.log('i');
}

复制代码

川川说如果是function(){} 不方便压缩? 那么为什么这样子就不方便压缩了呢? 川川解答:

fn = function
fn1 = fn
fn2 = fn
依次类推

然后:
fn = function
fn1 = function
fn2 = function
依次类推
复制代码

结果我还是不李姐, 于是翻了下群里聊天记录,明轩和川川的对话。 于是:似乎理解了一些:

const fn = function(){}
const fn1 = funciton(){}
const fn2 = funciton(){}
const fn3 = funciton(){}
const fn4 = funciton(){}


//对比:
const fn = function(){}
const fn1 = fn
const fn2 = fn
const fn3 = fn
const fn4 = fn

复制代码

ps: 如果fn1~fn4都是自己写的匿名函数的话, 需要压缩好几份,但是有一个指向的话,实际压缩一个匿名函数就可以了。

好吧,这么一说, 我理解了

4、NO 永远返回false的函数

源码:

/**
 * Always return false.
 */
export const NO = () => false
复制代码

ps:这么写的好处, 一是方便代码压缩,二是永远返回false

4.1来扒一扒使用场景

全局搜了下vue-next里的NO方法,只找到了两个地方:

// 目录/packages/server-renderer/src/helpers/ssrCompile.ts

const { code } = compile(template, {
    isCustomElement: instance.appContext.config.isCustomElement || NO,
    isNativeTag: instance.appContext.config.isNativeTag || NO,
    onError(err: CompilerError) {
      if (__DEV__) {
        const message = `[@vue/server-renderer] Template compilation error: ${err.message}`
        const codeFrame =
          err.loc &&
          generateCodeFrame(
            template as string,
            err.loc.start.offset,
            err.loc.end.offset
          )
        warn(codeFrame ? `${message}\n${codeFrame}` : message)
      } else {
        throw err
      }
    }
  })
复制代码

就是false作用

5、 isOn 判断字符串是不是on开头,并且on后首字母不是小写字母

代码:

const onRE = /^on[^a-z]/
export const isOn = (key: string) => onRE.test(key)

//举个栗子:
isOn('onChange'); // true;
isOn('onchange'); //fasle;
isOn('on3change'); //true;
复制代码

转JS


const onRE = /^on[^a-z]/;
const isOn = (key) => onRE.test(key);
复制代码

正则: onRE ^符号在开头,表示以什么开头。在其他地方是指非。

与之相反的是 : $符合在结尾,表示以什么结尾 [^a-z]是指不是 az的小写字母。

5.1 复习正则

可查看 老姚《JavaScript正则表达式迷你书》问世了

分类:
前端
标签:
分类:
前端
标签: