前端工程化实践——快速入门treeshaking

news2024/11/18 19:40:28

treeshaking       

treeshaking本质是去除多余代码算法。在前端的性能优化中,es6 推出了tree shaking机制,tree shaking就是当我们在项目中引入其他模块时,他会自动将我们用不到的代码,或者永远不会执行的代码摇掉,在Uglify阶段查出,不打包到bundle中。

        学习treeshaking的目的也是是为了后面学习rollup打包原理做铺垫。在 rollup 中,一个文件就是一个模块。每一个模块都会根据文件的代码生成一个 AST 语法抽象树,rollup 需要对每一个 AST 节点进行分析。分析 AST 节点,就是看看这个节点有没有调用函数或方法。如果有,就查看所调用的函数或方法是否在当前作用域,如果不在就往上找,直到找到模块顶级作用域为止。如果本模块都没找到,说明这个函数、方法依赖于其他模块,需要从其他模块引入。rollup只处理函数和顶层的import/export变量。

0前置知识-安装相关依赖

安装webpack

npm install webpack webpack-cli --save-dev

初始化项目

npm init -y

查看webpack打包后的内容

npx webpack ./test1.js

安装nodemon

nodemon 一个辅助node.js开发的工具,当目录中的文件更改时,会自动重启node应用程序

npm i nodemon -g

测试代码命令:nodemon ./index.js

参数:watch

安装acorn

npm i acorn -d -s

const acorn = require('acorn');

acorn的默认用法非常简单,直接来段代码字符串parse一下就出来AST结构了:

let acorn = require("acorn");

console.log(acorn.parse("for(let i=0;i<10;i+=1){console.log(i);}", {ecmaVersion: 2020}));

解析后的AST语法树

Node {
  type: 'Program',
  start: 0,
  end: 39,
  body: [
    Node {
      type: 'ForStatement',
      start: 0,
      end: 39,
      init: [Node],
      test: [Node],
      update: [Node],
      body: [Node]
    }
  ],
  sourceType: 'script'
}

可以看到这个 AST 的类型为 program,表明这是一个程序。body 则包含了这个程序下面所有语句对应的 AST 子节点。每个节点都有一个 type 类型,例如 Identifier,说明这个节点是一个标识符;

安装magic-string

magic-string是一个操作字符串和生成source-map的工具。magic-string 是 rollup 作者写的一个关于字符串操作的库。

安装命令:

npm i magic-string -D -S

下面是 github 上的示例:

var MagicString = require('magic-string');
var magicString = new MagicString('export var name = "beijing"');
//类似于截取字符串
console.log(magicString.snip(0,6).toString()); // export
//从开始到结束删除字符串(索引永远是基于原始的字符串,而非改变后的)
console.log(magicString.remove(0,7).toString()); // var name = "beijing"

//很多模块,把它们打包在一个文件里,需要把很多文件的源代码合并在一起
let bundleString = new MagicString.Bundle();
bundleString.addSource({
    content:'var a = 1;',
    separator:'\n'
});
bundleString.addSource({
    content:'var b = 2;',
    separator:'\n'
});
/* let str = '';
str += 'var a = 1;\n'
str += 'var b = 2;\n'
console.log(str); */
console.log(bundleString.toString());
// var a = 1;
//var b = 2;

 其中引入方法

const MagicString = require('magic-string');

magic-string的好处是会生成sourcemap

一、初识AST语法解析

        抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作。

        你可以简单理解为 它就是你所写代码的的树状结构化表现形式。webpack、UglifyJs、lint等工具的核心都是通过ast抽象语法书实现的,实现对代码的检查、分析。底层是调用的js parser 来生成抽象语法树。

AST工作流

  • Parse(解析) 将源代码转换成抽象语法树,树上有很多的estree节点
  • Transform(转换) 对抽象语法树进行转换
  • Generate(代码生成) 将上一步经过转换过的抽象语法树生成新的代码

新建文件source.js

const a = ()=>'a888888'
const b = ()=>'b'
a()

在AST生成树网站预览结果

AST explorer

可以根据AST生成的数据逐层分析出变量信息

{
  "type": "Program",
  "start": 0,
  "end": 46,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 23,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 6,
          "end": 23,
          "id": {
            "type": "Identifier",
            "start": 6,
            "end": 7,
            "name": "a"
          },
          "init": {
            "type": "ArrowFunctionExpression",
            "start": 10,
            "end": 23,
            "id": null,
            "expression": true,
            "generator": false,
            "async": false,
            "params": [],
            "body": {
              "type": "Literal",
              "start": 14,
              "end": 23,
              "value": "a888888",
              "raw": "'a888888'"
            }
          }
        }
      ],
      "kind": "const"
    },
    {
      "type": "VariableDeclaration",
      "start": 24,
      "end": 41,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 30,
          "end": 41,
          "id": {
            "type": "Identifier",
            "start": 30,
            "end": 31,
            "name": "b"
          },
          "init": {
            "type": "ArrowFunctionExpression",
            "start": 34,
            "end": 41,
            "id": null,
            "expression": true,
            "generator": false,
            "async": false,
            "params": [],
            "body": {
              "type": "Literal",
              "start": 38,
              "end": 41,
              "value": "b",
              "raw": "'b'"
            }
          }
        }
      ],
      "kind": "const"
    },
    {
      "type": "ExpressionStatement",
      "start": 42,
      "end": 45,
      "expression": {
        "type": "CallExpression",
        "start": 42,
        "end": 45,
        "callee": {
          "type": "Identifier",
          "start": 42,
          "end": 43,
          "name": "a"
        },
        "arguments": [],
        "optional": false
      }
    }
  ],
  "sourceType": "module"
}

可以看到上面的拆解中,每一个块都有 type、start、end、body 这几个字段。其中 type 表达当前块的类型。比如 FunctionDeclaration 表示函数定义,Identifier 表示标识符、BlockStatement 表示块语句、ReturnStatement 表示返回语句等。start 表示该块开始的位置,end 表示该块结束的位置,body 表示子块。其他的字段根据块的性质不同有所不同。

新增index.js文件

//文件形式读取source.js
//fs模块  node.js中文件处理工具
const fs = require('fs');//引入
//读文件 readFileSync方法是同步读取文件,第一个参数表示文件路径,第二个参数表示读文件的编码方式(可省略)
const code = fs.readFileSync('./source.js').toString();
console.log('-------------source code----------------')
console.log(code);

//acorn 一个将代码解析为AST语法树的工具
const acorn = require('acorn');
const ast = acorn.parse(code,{ecmaVersion:'7'});//指定解析的js ECMAScript版本
console.log('--------------parsed as AST-------------')
ast.body.map(node=>{
    console.log(node)
})

//MagicString 一个处理字符串的工具
const MagicString = require('magic-string');
const m = new MagicString(code);
console.log('--------------output node-------------')
console.log('index  info')
ast.body.map((node,index)=>{
    console.log(index+'     ',m.snip(node.start,node.end).toString())//打印每个节点的信息
})


//分离声明和调用类型
const VariableDeclaration = []
const ExpressionStatement =[]
//对象当做map用 key是变量名,value是变量对应的节点
const VariableObj ={}
//statement数组 存放变量的声明和使用
const statementArr = []
ast.body.map(node=>{
    if(node.type == 'VariableDeclaration') {
        VariableDeclaration.push(node);//声明节点数组
        //取声明数组的变量名key和节点value
        const key = node.declarations[0].id.name
        VariableObj[key] = node
    } else if (node.type == 'ExpressionStatement') {
        //对于引用的数组
        ExpressionStatement.push(node);//引用节点数组
    }
})
//取变量名
console.log('---------variableDeclaration name--------------')
VariableDeclaration.map(node=>{
    console.log(node.declarations[0].id.name)
    
})

console.log('----------expressionStatement --------------')
ExpressionStatement.map(node=>{
    // console.log(node.expression.callee.name)
    console.log(node)
})

ExpressionStatement.map(node=>{
    statementArr.push(VariableObj[node.expression.callee.name])//把表达式中使用的变量名的定义语句push到数组中
    statementArr.push(node)//将表达式也push到数组中,未在表达式中调用的变量将不会遍历其VariableObj数组,也即过滤掉
})
console.log('------------treeshaking result----------')
// console.log(statementArr)
statementArr.map((node,index)=>{
    console.log(index,m.snip(node.start,node.end).toString())
})

执行语句nodemon ./index.js

输出

-------------source code----------------
const a = ()=>'a888888'
const b = ()=>'b'
a()

--------------parsed as AST-------------
Node {
  type: 'VariableDeclaration',
  start: 0,
  end: 23,
  declarations: [
    Node {
      type: 'VariableDeclarator',
      start: 6,
      end: 23,
      id: [Node],
      init: [Node]
    }
  ],
  kind: 'const'
}
Node {
  type: 'VariableDeclaration',
  start: 25,
  end: 42,
  declarations: [
    Node {
      type: 'VariableDeclarator',
      start: 31,
      end: 42,
      id: [Node],
      init: [Node]
    }
  ],
  kind: 'const'
}
Node {
  type: 'ExpressionStatement',
  start: 44,
  end: 47,
  expression: Node {
    type: 'CallExpression',
    start: 44,
    end: 47,
    callee: Node { type: 'Identifier', start: 44, end: 45, name: 'a' },
    arguments: []
  }
}
--------------output node-------------
index  info
0      const a = ()=>'a888888'
1      const b = ()=>'b'
2      a()
---------variableDeclaration name--------------
a
b
----------expressionStatement --------------
Node {
  type: 'ExpressionStatement',
  start: 44,
  end: 47,
  expression: Node {
    type: 'CallExpression',
    start: 44,
    end: 47,
    callee: Node { type: 'Identifier', start: 44, end: 45, name: 'a' },
    arguments: []
  }
}
------------treeshaking result----------
0 const a = ()=>'a888888'
1 a()

二、treeShaking节点遍历方法

采用TDD测试驱动开发方式

新增walk.js函数

首先,测试一下进入与退出函数

const walk = (ast, callObj)=>{
    callObj.enter(ast)
    callObj.leave(ast)
}
module.exports = walk

编写walk.spec.js函数

测试ast是对象的情况

//walk.spec.js
//测试walk函数
describe('walk函数',()=>{
    test('单个节点',()=>{
        const ast = {
            a:1,
            // child:[{b:2}]
        }
        const walk = require('../walk')
        const mockEnter = jest.fn()//fn方法是jest工厂方法
        const mockLeave = jest.fn()
        //walk函数遍历ast对象,对于单个节点,进入时调用enter函数,退出时调用leave函数
        walk(ast,{
            enter:mockEnter,
            leave:mockLeave
        })
        //判断mockEnter是否被调用
        let calls = mockEnter.mock.calls //calls是数组,每调用一次增加一项
        expect(calls.length).toBe(1)//断言,ast={a:1}
        expect(calls[0][0]).toEqual({a:1})

        calls = mockLeave.mock.calls //在对leave是否调用进行判断
        expect(calls.length).toBe(1)//断言,ast={a:1}
        expect(calls[0][0]).toEqual({a:1})
    })
})

测试

由测试结果可以看出walk函数可以对{a:1}进行测试

使用--watchAll实时监听jest

jest --watchAll

实现打印所有变量的变量名

实现过程:调用walk节点变量方法,自定义walk中的入参enter函数:根据node中ast语法树变量名type属性为VariableDeclaration取出所有的变量

 

 新建test.js

//文件形式读取source.js
//fs模块  node.js中文件处理工具
const fs = require('fs');//引入
//读文件 readFileSync方法是同步读取文件,第一个参数表示文件路径,第二个参数表示读文件的编码方式(可省略)
const code = fs.readFileSync('./source.js').toString();
console.log('-------------source code----------------')
console.log(code);

//引入walk函数
const walk = require('./src/walk')
//acorn 一个将代码解析为AST语法树的工具
const acorn = require('acorn');
const ast = acorn.parse(code,{ecmaVersion:'7'});//指定解析的js ECMAScript版本
console.log('--------------walk ast-------------')
ast.body.map(node=>{
    walk(node,{
        enter:(node)=>{
            // console.log('enter---------------lalala')
            if(node && typeof node === 'object') {
                if(node.type === 'VariableDeclaration'){
                    console.log(node.declarations[0].id.name)
                    // console.log(JSON.stringify(node.declarations[0].id.name,'\t','\t'))
                }
            }
        },
        leave:(node) =>{
            // console.log('leave----------------lalala')
        }
    })
})

ast.body才是节点,打印结果如下

 提升:找出任意层级的变量名

示例:

输入
const a,b =1
if(true) {
const c ='123'
}
function fn1() {
const d =1
}
const e =3
-----------------------------------
输出
a
b
c
fn1 =>d
e
-------------------------------------

新增source2.js

存放原程序

const a=2
const b =1
if(true) {
const c ='123'
}
function fn1() {
const d =1
}

新增test.js

//test.js
//文件形式读取source2.js
//fs模块  node.js中文件处理工具
const fs = require('fs');//引入
//读文件 readFileSync方法是同步读取文件,第一个参数表示文件路径,第二个参数表示读文件的编码方式(可省略)
const code = fs.readFileSync('./source2.js').toString();
console.log('-------------source code----------------')
console.log(code);

//引入walk函数
const walk = require('./src/walk')
//acorn 一个将代码解析为AST语法树的工具
const acorn = require('acorn');
const ast = acorn.parse(code,{ecmaVersion:'7'});//指定解析的js ECMAScript版本
console.log('--------------walk ast-------------')
const statements = []
const parseAST = (ast)=>{
    ast.body.map(node=>{
        // console.log(node)
        walk(node,{
            enter:(node)=>{
                if(node.type === 'VariableDeclaration'){
                    console.log(node.declarations[0].id.name)
                }          
                //是个函数
                if(node.type === 'FunctionDeclaration'){
                    console.log('=>'+node.id.name)
                }    
            },
            leave:(node) =>{
          
            }
        })
    })
    
}
parseAST(ast)

测试结果

nodemon test.js

三、作用域模拟

 新建一个scope作用域实现类似于下面的结果

const a = '1'
function(){
    const b = 2
}

新增src/scope.js文件

module.exports = class Scope{
    //定义构造方法
    constructor(option){
        //初始化names
        this.names=[]
        if(option){
            this.parent = option.parent
        }
    }
    //新增方法,每次添加一个变量名到作用域中
    add(name){
        this.names.push(name)
    }
    //判断对象中是否包含某个变量名,谁调用,this指向谁
    contains(name){
        return this.names.includes(name) || this.parent && this.parent.contains(name)
    }
    findDefiningScope(name){
        //如果调用方的names中包含name,返回调用方本身,否则沿着调用方的作用域链逐层向上寻找
        if(this.names.includes(name)){
            return this
        } else if(this.parent){//如果存在父作用域,递归寻找父作用域中是否含有该方法
            return this.parent.findDefiningScope(name)
        } else {
            return null
        }
    }
}

 升级contains(name)方法

findDefiningScope方法可以获取调用对象,如果找到一个对象即该对象包含name属性

module.exports = class Scope{
    //定义构造方法
    constructor(option){
        //初始化names
        this.names=[]
        if(option){
            this.parent = option.parent
        }
    }
    //新增方法,每次添加一个变量名到作用域中
    add(name){
        this.names.push(name)
    }
    //判断对象中是否包含某个变量名,谁调用,this指向谁
    contains(name){
        // return this.names.includes(name) || this.parent && this.parent.contains(name)
        //等价与下面  其中!!表示取字符串的布尔类型表示
        return !! this.findDefiningScope(name)
    }
    //返回实际的作用域对象
    findDefiningScope(name){
        //如果调用方的names中包含name,返回调用方本身,否则沿着调用方的作用域链逐层向上寻找
        if(this.names.includes(name)){
            return this
        } else if(this.parent){//如果存在父作用域,递归寻找父作用域中是否含有该方法
            return this.parent.findDefiningScope(name)
        } else {
            return null
        }
    }
}

新增src/__test__/scope.spec.js文件

describe('scope',()=>{
    test('scope',()=>{
        const Scope = require('../scope')
        //实例化Scope,命名为route
        const root = new Scope()
        root.add('a')
        //定义一个child子作用域,嵌套在Parent父作用域
        const child = new Scope({'parent':root})
        child.add('b')

        //编写断言 
        expect(child.findDefiningScope('a')).toBe(root)//'child中是否有a',a在父作用域链上,按作用域规则可以找到
        expect(child.contains('a')).toEqual(true)//toEqual比较每项每个的值

        expect(child.findDefiningScope('b')).toBe(child)//toBe比较的是地址
        expect(child.contains('b')).toBe(true)

        // expect(child.findDefiningScope('c')).toBe(null)
        // expect(child.contains('c')).toEqual(false)
    })
})

增加三级作用域,测试

describe('scope',()=>{
    test('scope',()=>{
        const Scope = require('../scope')
        //实例化Scope,命名为route
        const root = new Scope()
        root.add('a')
        //定义一个child子作用域,嵌套在Parent父作用域
        const child = new Scope({'parent':root})
        child.add('b')
        //定义一个三级作用域,孙子节点
        const childChild = new Scope({'parent':child})
        childChild.add('c')

        //编写断言 
        expect(child.findDefiningScope('a')).toBe(root)//'child中是否有a',a在父作用域链上,按作用域规则可以找到
        expect(child.contains('a')).toEqual(true)//toEqual比较每项每个的值

        expect(child.findDefiningScope('b')).toBe(child)//toBe比较的是地址
        expect(child.contains('b')).toBe(true)

        expect(child.findDefiningScope('c')).toBe(null)
        expect(child.contains('c')).toEqual(false)

        expect(childChild.findDefiningScope('b')).toBe(child)
        expect(childChild.contains('b')).toEqual(true)
    })
})

四、整合节点遍历和作用域函数

新建analyze.js文件

//analyze.js
//输入是一个ast 输出是已经分析好的scope
const acorn = require('acorn')
const fs = require('fs')
const { node } = require('webpack')
const {walk} = require('../walk')
const code = fs.readFileSync('../source.js').toString()
const ast = acorn.parse(code,{ecmaVersion:7})
const Scope = require('./scope')

module.exports = function analyze(ast) {
    const root = new Scope()
    ast.body.map(node=>{
        walk(node,{
            enter:(node)=>{
                if(node.type === 'Identifier'){
                    root.add(node.name)
                    // console.log(node.Identifier.name)
                }          
                //是个函数 如果存在函数,函数在子作用域里
                if(node.type === 'FunctionDeclaration'){
                    // console.log('=>'+node.id.name)
                }    
            },
            leave:(node) =>{
          
            }
        })
    })
    return new Scope()
}

新增analyze.spec.js文件

describe('analyze',()=>{
    test('analyze 1',()=>{
        const analyze = require('../analyze')
        //测试ast到scope
        const acorn = require('acorn')
        const fs = require('fs')
        const {walk} = require('../walk')
        const code = `
const a =1`
        const ast = acorn.parse(code,{ecmaVersion:7})
        const root = analyze(ast)
        expect(root.findDefiningScope('a')).toBe(root)
        expect(root.contains('a')).toEqual(true)

    })
})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/77648.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java设计模式】用盖房子案例讲解建造者模式(生成器模式)

一、前言 今天学习了Java设计模式中的建造者模式&#xff0c;细心整理了学习笔记以及对这个模式的自我理解&#xff0c;希望大家喜欢&#xff01; 二、需求介绍 现在我们需要建房子&#xff0c;过程为打桩、砌墙、封顶。房子有各种各样的&#xff0c;比如普通房&#xff0c;…

【Java开发】 Spring 10 :Spring Boot 自动配置(Auto Configuration)原理及手动实现

用了这么久的 SpringBoot &#xff0c;我们再来回顾一下它&#xff0c;本文介绍 Spring Boot 的自动配置&#xff0c;这是它区别于 Spring 的最大的点&#xff0c;本文的自动配置项目包含三个项目&#xff0c;建议拉取仓库里的代码进行实践&#xff1a;尹煜 / AutoConfigDemo …

SOFA Weekly|MOSN v1.3.0 版本发布、公众号半自助投稿、本周 Contributor QA

SOFA WEEKLY | 每周精选 筛选每周精华问答&#xff0c;同步开源进展欢迎留言互动&#xff5e;SOFAStack&#xff08;Scalable Open Financial Architecture Stack&#xff09;是蚂蚁集团自主研发的金融级云原生架构&#xff0c;包含了构建金融级云原生架构所需的各个组件&#…

不接受反驳,性能最强,功能最强的Java日志框架

Logback 算是JAVA 里一个老牌的日志框架&#xff0c;从06年开始第一个版本&#xff0c;迭代至今也十几年了。不过logback最近一个稳定版本还停留在 2017 年&#xff0c;好几年都没有更新&#xff1b;logback的兄弟 slf4j 最近一个稳定版也是2017年&#xff0c;有点凉凉的意思。…

tep支持pytest-xdist分布式执行用例及合并Allure报告

tep近期更新频率较快&#xff0c;一方面是作者在积极投入到tep工具开发中&#xff1b;另一方面是我们聚集了20位小伙伴&#xff0c;一起合力打造EasyPytest测试平台&#xff0c;teprunner的FastAPI升级版本&#xff0c;依托于tep&#xff0c;帮你高效管理pytest测试用例。陆续也…

使用OpenGPT(ChatGPT)搭建 QQ 机器人

本教程来自&#xff1a;OpenGPT搭建QQ机器人-憨憨博客 有问题可来我博客询问&#xff1a;我的博客 准备 一个服务器&#xff1a;Windos&#xff0c;Centos&#xff0c;Ubuntu 环境&#xff1a;Python 一个 QQ 号用作机器人 一个 OpenAI 账号 (注册教程自行搜索) 搭建 这里我用…

Java最流行的Spring框架该怎么学?阿里、腾讯、字节跳动等大厂面试中关于Spring都会问什么?

Spring作为现在最流行Java 开发技术&#xff0c;其内部源码设计非常优秀。如果你不会Spring&#xff0c;那么很可能面试官会让你回家等通知。 Spring是什么&#xff1f; 有一个工地&#xff0c;几百号人在用铁锹铲子挖坑。 如果开一辆挖掘机来&#xff0c;用一天时间干的活就…

【数据结构与算法】图

目录 一、图的基本概念 二、图的存储结构 1、邻接矩阵 2、邻接表 三、图的遍历 1、DFS 2、BFS 四、最小生成树 1、Kruskal算法 2、Prim算法 五、最短路径问题 1、Dijkstra 2、Bellman-Ford 3、Floyd-Warshall 总结 一、图的基本概念 图是由顶点集合及顶点间的关…

tslearn学习:快速入门

文章目录前言一、安装二、时间序列格式2.1 格式化时间序列2.2 读取标准数据集三、机器学习算法3.1 分类3.2 回归3.3 最近邻搜索3.4 聚类前言 tslearn快速入门学习。官网&#xff1a;tslearn quick-start 一、安装 采用pip install安装tslearn库 pip install tslearn二、时间…

基于C#制作一个音乐播放器

此文主要基于C#制作音乐播放器&#xff0c;可实现导入本地歌曲、音乐播放、音量设置、歌词显示等。 实现流程1.1、创建项目1.2、准备素材1.3、功能开发实现流程 1.1、创建项目 打开Visual Studio&#xff0c;右侧选择创建新项目。 搜索框输入winform&#xff0c;选择windows窗…

测控一体化闸门 灌区智能控制闸门 渠道智能测控闸门系统解决方案

平升电子测控一体化闸门系统/灌区智能控制闸门/渠道智能测控闸门系统解决方案集闸门远程/自动控制、渠道水位流量监测、远程通信、图像/视频监控等功能于一体&#xff0c;具备多种闸门启闭控制方式和多种流量计量方式&#xff0c;应用于支渠、斗渠、农渠的精准用水控制与计量。…

数据库原理及MySQL应用 | 程序流程控制

解决复杂问题不可能通过一个SQL语句完成&#xff0c;我们需要执行多个SQL操作。流程控制语句的作用就是控制存储过程或存储函数中SQL语句的执行顺序&#xff0c;是我们完成复杂操作必不可少的一部分。 流程控制语句是指可以控制程序运行顺序的语句&#xff0c;程序运行顺序主要…

各种数据类型的SPI, UART, I2C等方式的通信传输以及存储到EEPROM、Flash等设备的简易实现方法

各种类型的数据传输和存储就涉及到大小端的问题&#xff0c;首先要简单说下芯片的大小端问题&#xff0c;我们这里主要讨论Cortex-M内核。 M内核支持大端或者小端&#xff0c;实际应用中大部分内核都是小端。以STM32为例&#xff0c;全部都是小端&#xff0c;而且是芯片设计之…

Spring Cloud Alibaba Nacos Config - - - >多配置文件/共享配置

源码地址(重点开源码中的 nacos8030 模块)&#xff1a;https://download.csdn.net/download/weixin_42950079/87264006 多配置文件 / 共享配置 在一个微服务架构应用系统中可能包含成百上千个微服务。而很多微服务可能都引入相同的中间件&#xff0c;当环境中引入的中间件较多时…

【eth uniswap】uniswap 自动路径(Auto Router)错误导致的swap超大损耗

____tz_zs 2022-06-09 稿 对于同时有v2池子和v3池子的Token&#xff0c;感觉最近uniswap的app的自动路由寻址&#xff08;Auto Router&#xff09;有点问题&#xff0c;找的永远是v3的&#xff08;如示例caw/weth&#xff09;池子。此时的情况是v3池子很小&#xff0c;只有几十…

用 HarmonyOS ArkUI 来开发一个健康饮食应用

本文演示如果在DevEco Studio 3里面&#xff0c;用HarmonyOS的ArkUI来开发一个健康饮食应用。体验HarmonyOS 3最新API 9&#xff01; 获取HarmonyOS应用 HarmonyOS的ArkUI来开发一个健康饮食的ArkUI程序“ArkUIHealthyDiet”&#xff0c;基础代码已经有了[1]&#xff0c;个人…

【数据结构初阶】八大排序算法+时空复杂度

学会控制自己是人生的必修课 文章目录一、插入排序1.直接插入排序2.希尔排序二、选择排序1.直接选择排序2.堆排序&#xff08;已经建好堆的基础之上&#xff09;三、交换排序&#xff08;Swap&#xff09;1.冒泡排序&#xff08;大学牲最熟悉的排序&#xff09;2.快速排序&…

Python 数据库开发实战-Mac系统下通过homebrew安装Redis数据库

此文章的前置条件是 “Mac系统已安装过Homebrew”&#xff0c;如果未安装&#xff0c;可访问 “Mac 安装 homebrew 详细教程” 一文&#xff0c;详细介绍Homebrew的用法。利用 “Homebrew” 对 “Redis” 进行安装管理&#xff0c;那是一个方便啊。 利用 homebrew 安装 Redis …

【Windows逆向】【Qt】日志信息打印

▒ 目录 ▒&#x1f6eb; 导读需求开发环境1️⃣ 示例程序Demo2️⃣ 编写功能&#xff08;QtCreator版本&#xff09;3️⃣ 编写功能&#xff08;VS版本&#xff09;&#x1f6ec; 文章小结&#x1f4d6; 参考资料&#x1f6eb; 导读 需求 调试是编程中常见的定位手段&#xf…

字节一面,操作系统这题没答好,可惜了

问题引入&#xff1a; 在曾经我们学习Linux的经历中&#xff0c;我们也是多次使用信号的。比如&#xff1a;当我们在使用xshell时&#xff0c;在命令行中按Ctrlc&#xff0c;这个键盘输入产生了一个硬件中断&#xff0c;被操作系统获取&#xff0c;解释成信号&#xff0c;发送…