mapStateToProps 和 mapDispatchToProps的区别

背景

最近开始实习,部门主要使用的是一个内部的跨端框架,这东西本质上还是React Native,只不过是做了一层套娃重命名了标签名称(像极了ReactRouter和ReactRouter-Dom的关系),并且封装📦了自己的一堆私有的API。由于导师就是负责开发这个框架,因此很无奈只能转React,做React开发了(印证了之前的猜想)。

今天这篇文章主要就分享一下我在使用Redux过程中的一些笔记📒或者使用经验。

(本文章不会详细阐述其具体的使用方法,只是会记录一些实际应用的效果)

区别

  1. 相同点
  • 两者都是函数
  • 用于将Redux的Store映射到组件props(入口参数)上
  1. 异同点
函数名称官方描述个人理解
mapStateToProps建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系将Store上的State挂载到props上 (实现了单向数据流)
mapDispatchToProps建立 UI 组件的参数到store.dispatch方法的映射将Store上的Dispatch函数挂载到(调用的时候,相当于是在触发Store上的函数)

实例

(此处引用阮一峰老师提供的“计数器”代码,作为本文章的例子,感谢阮老师的分享🙏)

初始化环境

 import React, { Component } from 'react'
 import ReactDOM from 'react-dom'
 import { createStore } from 'redux'
 import { Provider, connect } from 'react-redux'

创建计数器组件

 class Counter extends Component {
   // 创建渲染器
   render() {
     // 从入口参数上获取mapState和mapDispatch挂载的属性和方法
     const { value, onIncreaseClick } = this.props
     // 返回JSX
     return (
       <div>
         <span>{value}</span>
         <button onClick={onIncreaseClick}>Increase</button>
       </div>
    )
  }
 }

创建Action

Action说明了Redux中可以做什么动作。

 const increaseAction = { type: 'increase' }

有点类似于Java中的抽象类的概念

创建Reducer

Reducer说明了Action做什么动作。

 function counter(state = { count: 0 }, action) {
   // 读取Redux中管理的count变量
   const count = state.count
   // 检测dispatch触发了哪一个动作
   switch (action.type) {
       // 触发"增加"动作
     case 'increase':
       // 将值+1并返回一个state到Redux的Store中
       return { count: count + 1 }
     default:
       return state
  }
 }

有点类似于Java中的实现类

PS:

Action和Reducer的关系,在我看来有点类似于抽象实现的关系,不知道🤷‍♂️对不对,以后再继续深入探究一下。

创建Store

 const store = createStore(counter)

实现挂载函数

 // Redux的角度: 将Redux管理的全局State(Store)上的值挂载到了组件上
 // 组件的角度: 获取(订阅)了Redux管理的全局State(Store)上的值
 function mapStateToProps(state) {
   return {
     value: state.count
  }
 }
 ​
 // Redux的角度: 将操作全局State(Store)上的方法挂载到了组件上
 // 组件的角度: 获取了管理的全局State(Store)的方法
 function mapDispatchToProps(dispatch) {
   return {
     onIncreaseClick: () => dispatch(increaseAction)
  }
 }

挂载到组件上

 // Connected Component
 const App = connect(
   mapStateToProps,
   mapDispatchToProps
 )(Counter)

页面渲染

 ReactDOM.render(
   <Provider store={store}>
     <App />
   </Provider>,
   document.getElementById('root')
 )

流程图例

Redux与组件之间是如何传递reducer和state以及组件如何dispatch reducer的
  • 通过mapStateToProps将全局State挂载到了组件的Props上,组件因此可以直接读取到Store中管理的state值。
  • 通过mapDispatchToProps将管理全局State的方法挂载到了组件的Props上,组件就可以从入参中获取操作方法,进而在组件中dispatch(触发)State中的Reducer进而修改State中的值。

结尾

这篇文章主要是梳理了一下本周解决的一个知识点,加深自私点在脑海中的印象。文章中阐述不清楚的点,欢迎透过邮箱📮与发起讨论,希望透过讨论也能让你明白这个过程发生了什么。

延伸资料

  1. React-Redux 的用法(中文)
  2. 使用mapStateToProps导出数据(官方英文)
  3. 使用mapDispatchToProps触发Actions(官方英文)
  4. 计算器代码(Github)PS:建议可以先不看🙈这两个函数入参的描述,因为很容易把人搞晕😵,可以着重看看[1]中,阮一峰老师提供的实例,搭配[2] [3]思考🤔一下,相信你很快也能get到这个函数在干什么,有什么用🤷‍♂️。

React脚手架V1.0

背景

最近开始捯饬React,想着学会了React就能把React Native给学习了,然后就能写App。为了能够在React Native上少踩坑,我搭建这个脚手架之后,准备开发一个博文发布系统,作为DEMO练练手。

依赖构成

  1. React核心
  2. React-router-dom 路由
  3. Redux 状态管理
  4. ESlint 代码格式管理
  5. Vite 打包工具
  6. Axios 网络请求库
  7. Antd UI库

搭建流程

1. 初始化React

1.1 模板初始化

我们可以直接使用Vite自带的模板,初始化React,在本教程中我使用的是template指令来初始化项目。

运行以下指令初始化项目

 # npm 6.0
 npm init @vitejs/app gublog(项目名称) --template react(模板名称)
 ​
 # npm 7.0+ (需要额外的双横线)
 npm init @vitejs/app gublog(项目名称) --(额外的双横线) --template react(模板名称)

运行指令后,npm将创建一个以项目名称命名的文件夹📁,来初始化项目。

初始化完成后的文件树结构为:

 root
 └── gublog(项目名称)
    ├── dist
    ├── node_modules
    ├── src
    ├── index.html
    ├── package-lock.json
    ├── package.json
    └── vite.config.js

1.2 启动环境

  1. cd 项目文件夹
  2. npm install #下载依赖
  3. npm run dev #启动开发环境
环境启动成功

出现上图的网页时,我们就已经搭建出Vite + React的开发环境

2. 安装React-router-dom

为了能够实现无刷新切换页面,此处我的脚手架也会引入官方提供的Router来做前端路由管理。

2.1 安装依赖

运行指令安装路由组件

 npm install react-router-dom --save-dev

2.2 注意⚠️

React-Router-Dom就是React Router V4的浏览器版本,原来文档的Router要被替换成BrowserRouter才能使用。为了不使用方面,我们可以使用as关键字来修改别名。

 import 'BrowserRouter' as 'Router' from 'react-router-dom'

这里我真的很想吐槽,搞一个React-Router就好了嘛🤷‍♂️,干嘛还搞一个React-Router-Dom。这东西本质上就是套娃再封装📦。而且最近的V4版本还舍弃了旧的Router标签,改成了BrowserRouter。真的是NT。

解决方法来源:

3. 安装Redux

安装Redux主要是为了使得组件的耦合降低,使得更容易的管理状态,因此被打包到脚手架当中。

3.1 安装依赖

 npm install redux --save-dev

4. 安装ESlint

4.1 安装依赖

 npm install eslint --save-dev

5. 安装Axios网络请求库

5.1 安装依赖

 npm install axios --save-dev

6. 安装AntdUI库

AntdUI是阿里针对React设计的一个UI库,开箱即用,有丰富的文档。(而且还能改颜色)

6.1 安装依赖

 npm install redux --save-dev

6.2 引入环境

  1. main.css中添加import 'antd/dist/antd.css'导入CSS库
  2. Main.jsx的代码头部添加import {组件名称} from antd导入组件

结尾

这篇文章就此告一段落。Axios和ESlint的使用我还在摸索中,后面还会继续更新,敬请期待。目前在使用这个脚手架重写GuBlog日志程序当中,后面时机成熟后也会同新版本的脚手架文章一同推出。

npm 安装、删除依赖命令

npm安装依赖

  • npm install xxx 利用 npm 安装xxx依赖到当前命令行所在目录
  • npm install xxx -g 利用npm安装全局依赖xxx
  • npm install xxx –save 安装并写入package.json的”dependencies”中
  • npm install xxx –save-dev 安装并写入package.json的”devDependencies”中

npm删除依赖

  • npm uninstall xxx 删除xxx依赖
  • npm uninstall xxx -g 删除全局依赖xxx

PPG式心率测量小程序的技术分享

故事

背景

去年6月份的时候,参加了微信小程序官方举办的《高校微信小程序开发大赛》,做出了《活力健身房》这个小程序。按照初版设计,除了可以检测用户的跑步数据外,还可以测量用户的静止心率。但由于个人除了要准备比赛,还要准备痛苦的概率论补考,所以导致日程太过于紧凑,因此在该情况下,我就只完成了第一个目标而忽略了第二个心率测量的目标没有实现。

改进

在暑假期间,处于追求完美的想法,我花了一部分的时间继续学习并完成了心率检测算法的V1.0版本。而后再在这周,时隔一年的时间,重写了检测算法,推出了V1.1的版本。(至于为什么算是1.1版,在文中会说明的😂)。

技术分享

今天我写这篇文章的主要目的是跟大家分享以下的三点内容:

  1. PPG式心率测量的原理
  2. PPG式心率测量的实现
  3. 应用到小程序端的难点

1.技术原理

光体积变化描记图法 (Photoplethysmography,简称PPG) 是借光电手段在活体组织中检测血液容积变化的一种无创检测方法。当一定波长的光束照射到指端皮肤表面,每次心跳时,血管的收缩和扩张都会影响光的透射 (例如在透射PPG中,通过指尖的光线) 或是光的反射 (例如在反射PPG中,来自手腕表面附近的光线)。当光线透过皮肤组织然后再反射到光敏传感器时,光照会有一定的衰减。像肌肉、骨骼、静脉和其他连接组织对光的吸收是基本不变的 (前提是测量部位没有大幅度的运动),但是动脉会不同,由于动脉里有血液的脉动,那么对光的吸收自然也会有所变化。[1]

PPG检测的核心是通过心脏跳动影响血管中血液光线的吸收率变化,进而影响血液对光的反射率,估算受测者的心率数据。用人话说,心脏的跳动,会影响血液反射光的强度。反映到图表上,就如下图所示,心脏的舒张和收缩,会影响光强变化,形成下图的波动曲线。而心率的估算,则是通过计算60s内,有多少个波峰或者波谷。比如60s内,图表上产生了60个个波峰,则意味着1s中,心脏收缩和扩张了一次,即跳动了一次,进而可以得出60BPM的心率数据。

PPG传感器工作原理

2.技术实现

根据技术原理可以知道,PPG检测中我们需要做的事情是,计算一段时间内出现了多少个波峰,即可估算出受测者的心率。由此我们需要考虑实现以下的几个功能:

  1. 获取视频流中的某一帧
  2. 检测传感器是否被覆盖
  3. 获取当前帧的光强数据
  4. 计算波峰个数
  5. 计算BPM

1.获取视频中的某一帧

在当今社会中,人人都会有一台的智能手机。而手机上最好的颜色传感器就是摄像头了,因此在小程序端的实现中,对于数据的来源主要是摄像头。

为了实现方便,这次我依旧是使用小程序作为我的开发平台。之所以使用小程序的核心原因是官方提供了摄像头的API,使得我可以通过简单的$API$就可以获取到来自用户摄像头的视频流数据,并且不用考虑兼容性的问题。

在小程序中,我是用的是wx.createCameraContext()API来获取到视频流,同时借助onCameraFrame()来直接获取到每一帧的视频信息,极大的降低了开发的难度。通过前述的API,我们可以直接获取到当前帧Uint8数组,也就是每个像素的RGBA值。

2.检测传感器是否被覆盖

为了避免外部光照影响血管对光线的反射,因此需要受测者的手指完全覆盖摄像头。为了检测摄像头是否覆盖,我们可以通过经验主义设定一个阈值,当每个像素的实际光强小于阈值,则代表传感器完全被覆盖,此时就可以开始测试。

3.获取当前帧的光强数据

通过API,我们可以获得视频流中特定的一帧的Uint8数据。之所以使用Uint8是因为在该RGBA中,每一个通道的颜色只有256种,而我们仅仅是需要获取该帧中的光强。因此选用Uint8可以大幅度减少运算量,提高运行效率。

通过经验主义的测量以及结合论文[2] [3]的理论支持,我们可知血液在红光和绿光的照射下的吸收率是最大的,也就意味着强度值是最大的。因此可以更好的检测到强度变化,所以在后续的开发当中,我们都将使用单个像素中的红绿通道的强度值,来作为数据的来源。由于只使用了其中两个通道的强度值,我们的运算量就可以几何级数减少。

从原来的 2^{32} 个数据转变为了 2^{16} 个数据

获取光强的方法是:

该帧的光强值 = \frac{单个通道的强度值}{像素的个数}

4.计算波峰个数

在1.0版本中我的实现方法是通过经验值测算出一条固定的基线,来判断历史采集的光强值,有多少个跃出基线的数据,就计作多少个波峰。

V1.0 固定基线法

但这种设计是有问题的,比如最右侧的波峰,因为各种外部原因强度没有达到基线,导致丢失。当通过人眼的辨识,这个波峰不属于噪声,应该是要被记录的。

另外通过测试我们可以很容易的发现下图的假波峰和假波谷的噪声信号,这种变化幅度过大的数据也应该是被丢弃的。

噪声曲线

因此在1.1版本中,基线的计算方法做了改进。通过动态基线可以适应复杂环境光线的变化,提高测量的效率和准确性。

V1.1 动态基线法

通过动态基线,判断有多少多少次实际的跃出,进而换算出有多少个波峰。

5.计算BPM

仅仅记录波峰个数是远远不够的,我们还要计算一次跃出所消耗的时间(从波谷到跃出基线到波峰所消耗的时间)。因为受测者的光线环境如果不稳定,含有频繁的光线变化,则可能导致光强变化幅度过大,造成数据的计算误差。因此我们还需要将跃出时间作为计算的一个因素,计算平均的跃出时间。最后计算出每分钟的心率次数:

BPM = \frac{测量最大时间}{平均跃出时间}

小程序端的技术难点

到目前为止,小程序端最大的难点不在于测量算法的开发上。反而是在优雅的展示上画了大量的时间。

  1. FrameData大小
  2. Canvas绘图效率低

1.FrameData大小。通过Camera组件,我们可以直接获取到手机的视频流。由于我们这个算法中仅仅需要计算帧的光强,所以仅仅需要最低质量的FrameData即可。即降低了资源的消耗也提升了算法运行的效率。通过frame-data属性,修改为small即可启用最低质量的FrameData。

2.Canvas绘图效率低。在去年小程序退出了Canvas 2D的绘图API,可以实现同层渲染。为了更高的渲染效率,我自然就是选择并使用了最新的API进行曲线的绘制。在电脑模拟器上的表现确实印证了效率更好的这一说法。但诡异的是在Android端上,Canvas2D的运行效率非常低,导致经常性假死,不知道怎么修复。因此只能暂时被搁置,待到日后有能力后再做修复。

收获

这次的开发通过学习更多前人的研究结论来帮助我将算法优化出来,收获满满。感觉到非常的振奋人心。期待后面能继续搞点好玩的DEMO。

未来的方向

最近看到Tensorflow的BlazeFace。可以用很小的体积的模型来识别人脸,感觉可以借此,结合PPG算法,实现rPPG式的检测算法,日后有时间可以继续搞一下,感觉会非常的好玩。同时Canvas的问题也需要被修复,或许能够通过WebGL来修复?但是我还不肯定,需要后续的实验才能证明,待到后面再修吧。

结尾

由于本人不是电子工程专业,所以上文中可能会涉及到部分不规范的说明和解释。如果您能够在评论中帮助我指出来并帮助我改正,我将十分感激您,谢谢。

参考

[1] ECG/PPG量测解决方案

[2] 不同波长近红外光下手掌静脉图像质量分析

[3] 血液成分对光吸收规律的实验研究

M1 Mac 配置&初始化 Nginx+PHP+MySQL环境

环境

  1. Homebrew🍺
  2. Xcode命令行工具
  3. PHP 8.0
  4. Nginx
  5. MySQL

1.安装Xcode命令行工具

执行命令 Xcode-select --install

2.安装Homebrew


访问 Homebrew 官网,点击访问官网
按照指引,安装Homebrew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Homebrew常用指令

  • 安装package

brew install 包名

  • 卸载package

brew uninstall 包名

  • 检查package的信息

brew info 包名

  • Homebrew升级

sudo brew update

  • Homebrew搜索package

brew search 包名

  • 检视已安装的package

brew list

3.使用Homebrew安装Nginx

  • 安装Nginx

sudo brew install nginx

  • 启动Nginx

nginx
安装&启动完成后,从浏览器中输入localhost:8080即可访问到Nginx服务器的欢迎页面

  • 关闭Nginx

nginx -s sotp

  • 重启Nginx

nginx -s reload

  • 查看Nginx版本

nginx -v

4.安装PHP 8.0

我作为一个从5.5时代就开始做php开发的开发者,也是第一次使用8.0的稳定版。直接一步到位的原因是:更好的语法&JIT的支持,希望能够在8.0时代获得更好的性能。因此本教程,将会采用8.0版本的PHP进行安装。

  • 使用Homebrew安装PHP

brew install php
如果需要特定版本的php则包名改为`php@指定的版本号`即可

  • 启动&重启

php-fpm

  • 强制关闭php-fpm

sudo pkill -INT -o php-fpm

5.配置PHP

  • 进入php配置文件夹

通过brew info找到php配置文件的位置
brew info php

  • 编辑php.ini文件

vim php.ini

  • 守护PHP进程

默认安装的PHP仅仅会在前台运行,如果此时我们关闭命令窗口的话,php-fpm将会自动关闭,无法继续处理我们的php请求。
开启 `守护模式` 即可解决这一问题

1. 配置php-fpm.conf
vim php-fpm.conf
2. 查找 daemonize
直接键入/daemonize
3. 修改值为yes并且保存
daemonize = yes
虽然注释处,写了默认值应该为yes,但是不知道是什么原因,导致了默认值是no

  • 安装php拓展

通过Homebrew安装的php,自带了pecl。这是一个php拓展的包管理器,后续的拓展安装,仅仅需要通过该管理器安装即可。一般开发用不到,主要是用于实现加密或者网络的时候,可能需要通过该tab来安装

pecl version 查看版本信息

pecl help 可以查看命令帮助

pecl search xdebug 搜索可以安装的扩展信息

pecl install xdebug 安装扩展

pecl install http://pecl.php.net/get/redis-4.2.0.tgz 安装指定版本扩展

6.配置Nginx

  • 通过brew info找到Nginx的配置目录
  • 打开nginx.conf

vim nginx.conf

1. 修改启动端口为80

  • 找到listen关键词
server{ 
    listen 8080; // Nginx需要监听的接口 
    server_name localhost; 
    ...
}
  • 修改为80端口
server{ 
    listen 80; // Nginx需要监听的接口 
    server_name localhost; 
    ...
}

2. 修改默认入口文件
在index这一行中,加入index.php,将其也作为入口文件。

location / { 
    root html; 
    index index.html index.htm index.php;
}

3. 启用php流量转发
找到location ~.php$关键词,将注释删掉(删掉代码前面的’#’),启用php流量转发

location ~ .php$ { 
    root html; 
    fastcgi_pass 127.0.0.1:9000; 
    fastcgi_index index.php; 
    fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 
    include fastcgi_params;
}

4. 配置php流量转发规则
修改location ~.php$中的fastcgi_param配置项

将其从SCRIPT_FILENAME /scripts$fastcgi_script_name

修改为SCRIPT_FILENAME $document_root$fastcgi_script_name

Nginx常用命令

  • 查看报错文件的路径

nginx -V

nginx version: nginx/1.19.10
built by clang 12.0.0 (clang-1200.0.32.29)
built with OpenSSL 1.1.1k 25 Mar 2021
TLS SNI support enabled
configure arguments: 
...
--error-log-path=/opt/homebrew/var/log/nginx/error.log 
...

找到error-log-path关键字,后面的值即为错误报告的地址。
使用tail -f 文件名查看即可。

  • 检查配置文件正确性

nginx -t

  • 检查Nginx进程运行状态

ps -aux | grep nginx

7.验证PHP环境

  • 启动Nginx服务器
  • 启动php-fpm服务
  • 通过brew info nginx查询网页根目录
  • 在网页根目录下创建index.php

index.php代码:

<?php phpinfo(); ?>
  • 通过浏览器访问http://localhost/index.php

若出现PHP Version + 版本号 以及 php相关的信息则安装完成

php安装成功

8.安装MySQL

我当前的系统版本是BigSur,如果你是Catalina也是兼容的,下载最新版就好了。

  • 点击”No thanks, just start my download.”开始下载即可
  • 下载完成,解压双击运行pkg即可,按照步骤执行即可

管理MySQL

  • 通过访问系统偏好设置的MySQL即可管理MySQL服务
  • 通过GUI管理服务的启停

通过命令行管理MySQL

  • 查看MySQL版本

mysql -v

  • 登录MySQL服务器

mysql -u 用户名 -p
出现`Enter password`后输入你的密码回车,即可登录MySQL服务器。

  • 登出MySQL服务器

exit

通过phpMyAdmin管理MYsql

  • 访问官网下载phpMyAdmin即可,点击访问
  • 下载完成后,将phpmyadmin文件夹拖入网站根目录
  • 通过浏览器访问http://localhost/phpmyadmin即可

常见问题

  • 执行mysql显示command not found: mysql

这是由于没有将mysql指令加入到命令行窗口中的原因

解决方法:

  1. cd ~ 切换到用户目录
  2. 输入vim .bash_profile 打开命令行配置文件
  3. 输入export PATH=$PATH:/usr/local/mysql/bin
  4. 保存文件
  5. 运行source ~/.bash_profile加载配置文件
  6. 此时就可以顺利使用MySQL了

结尾

这篇教程主要目的是为了帮助我记录下Mac系统中配置环境的流程而写。方便我日后如果需要重装或者更换电脑的时候可以快速将环境搭建完成。希望这篇教程也能帮助到你,谢谢。

为什么图片转Base64会多出30%的数据量

背景

由于最近在准备🐧厂的面试,被问及是否知道为什么url-loader将图片转换成Base64后,会有增加33%的数据量。因此,我开始从晚上搜集这方面的资料,但遗憾的是,大多的博文的关注点基本都是在实际应用(Webpack,前端优化…)或者是Base64如何进行编码,没有很清晰的阐述为什么会多出33%的数据量的过程和原因。因此,我希望可以通过这篇博文补充这方面的讯息。

为什么会多出30%的数据

当我们把3个Byte的数据转换成为Base64的字符时,根据Base64的规则,3个Byte的字符会转换成24个二进制位。Base64一位只有6个位二进制位,因此会产生4个Byte的字符。

但是现代计算机系统当中,一个字符要有8个二进制位来存储因此,4个字符对应的6个二进制位都要补充2个0,来转换成8个二进制位的字符存储到计算机当中。因此3个Byte(24位二进制位)的数据要用4个Byte(32位二进制位)来存储。并且32位中只有24位是用来存储数据的,剩下8位都是无用的补充位。

因此我们可以很简单的计算出输出:输入 = 4:3 (33% 数据冗余). 与此同时,我们同样可以总结出,输入n Bytes,输出就有(下图)那么长。

{\textstyle 4\left\lceil {\frac {n}{3}}\right\rceil }

总结

核心思路,原来三个Byte的数据,转换成Base64后,要用四个Byte来存储。多了1/3(33%)的数据要存储,所以数据量会增多。

把照片打包进bundle.js中,看来还是要因地制宜的。url-loader默认数值应该是8192bit(1024 byte),增大33%后就是增加了300byte。因此,url-loader主要还是用于打包只有几个像素的小文件比较适合。不会对整体的加载速度有过多的影响。

引用

https://en.wikipedia.org/wiki/Base64

http://www.ruanyifeng.com/blog/2008/06/base64.html

[笔记]小程序JS 变量的生命周期探究

序言

今天闲逛”小程序开发社区”,无意中看到有一个同行贴出了以下的一段代码。

https://developers.weixin.qq.com/s/mqa4fHme7ZnX

从A页面当中跳转到B页面,点击按钮后,再返回上一页就不能触发按钮对应的事件了。

探究代码

第一次渲染

第一次渲染成功时,用户点击setData测试,按钮能正常触发事件(按钮内容变成setData成功),点击”点击另外打开当前页面”按钮之后,跳转到新的页面。

跳转到第二个页面

此时nodeid发生变化,证实跳转到了新的页面。此时点击左上角的返回按钮后,原来的setData按钮无法触发事件。

分析输出

从代码中我们很容易就能分析出,第一次渲染成功时,that和this指向的页面nodeid应该是acf9ce91,当我们跳转到第二个页面时,此时的that和this就变成了bd622626。

此时神奇的事情就发生了,当我们点击返回按钮回到上一个页面,我们就能看到that的输出值依然是 bd622626 ,但this的输出值就变回了 acf9ce91.

返回第一个页面

由此我的一个想法是,跳转页面只会初始化一个新的页面实例,而不是重新运行这个JS文件.

证明想法

辅助代码

为了证明跳转页面后不是重新运行这个JS文件的想法,我在Page构造器的外部增加了如下的代码.

但我重新渲染页面时,console页面成功输出了2

证明第一次渲染时,小程序是运行过这个JS文件的。但当我们点击跳转到新的页面时,2没有继续出现,此时我认为小程序在新的页面当中,小程序是没有运行过这个JS文件的。

查阅文档

小程序为了实现对视图层标签的管控,开发了一套内置的框架”Exparser框架”。在该框架当中,页面的渲染是基于Exparser框架提供的Page和Component构造器的注入的数据的。

文档链接:https://developers.weixin.qq.com/ebook?action=get_post_info&token=935589521&volumn=1&lang=zh_CN&book=miniprogram&docid=0000aac998c9b09b00863377251c0a

暂时性结论

按照我的个人理解,启动的时候,小程序就已经提前把页面的模板注册好了。如果后面再次访问该页面时,仅仅是会基于页面模板创建出新的页面实例。也就是只有Page()构造器里面定义的data,在同一个页面下,实例与实例之间是独立的,但是Page外部定义的变量是共用的

后续

如果未来有能力的话,希望可以反编译一下小程序的基础库,从源码正向来理解小程序页面渲染的流程。

引用

  1. 教程|《小程序开发指南》
  2. 同一page页面重复打开时,页面js里声明的变量会互相污染?

[NodeJS]非抢占式多级反馈队列调度算法

前序

《操作系统》课程最近布置了一个大作业,要求我们每人实现一个非抢占式多级反馈队列调度算法的模拟程序,作为期末考核的一部分。(u1s1,真的是爽到,老师开心,我们也写的开心!)

实现代码

//Powered By 7gugu

//每一级队列的可运行时间
let timeSlice = [1, 2, 3];

//准备运行的队列
let preRunQueue = [];
//3级运行队列
let runQueue = [
  [],
  [],
  []
];

//准备运行的程序[开始时间, 运行时间]
let progs = [
  [0, 8],
  [1, 4],
  [5, 1],
  [3, 7],
  [4, 2]
];

for (let i = 0; i < progs.length; i++) {
  let prog = {};
  prog.id = "P" + i;
  prog.startTime = progs[i][0];
  prog.runTime = progs[i][1];
  prog.priority = 0; //设置最高优先级
  preRunQueue.push(prog); //把待运行的程序导入运行序列
}

//总运行时间
let totalTime = 0;

while (true) {
  //如果待运行队列中仍然有程序 & 程序已到达开始时间
  if (preRunQueue.length > 0) {
    let prog = preRunQueue[0];
    if (prog.startTime <= totalTime) {
      preRunQueue.shift(); //直接弹出队头的元素
      runQueue[0].push(prog); //把程序加入0级运行队列中
      console.log(prog.id + "开始运行,开始时间为:" + totalTime);
    }
  }

  for (let i = 0; i < runQueue.length; i++) {
    //如果各级队列还有程序的话,就继续运行
    if (runQueue[i].length > 0) {
      let prog = runQueue[i].shift(); //获取各级队列中第一个程序
      if (prog.runTime > timeSlice[i]) {
        //程序运行时间比时间片大
        totalTime = totalTime + timeSlice[i]; //总运行时间累加
        prog.runTime = prog.runTime - timeSlice[i]; //减去每一次运行的时间
        if (i != runQueue.length - 1) {
          //如果未处于最低优先级,则把程序放在下一个优先级队列中
          runQueue[i + 1].push(prog);
        } else {
          //如果处于最低优先级,则把程序放回最低优先级中运行
          runQueue[i].push(prog);
        }
      } else {
        //程序运行时间比时间片小
        totalTime = totalTime + prog.runTime;
        console.log(prog.id + "运行完成,目前时间为:" + totalTime);
      }
      break;
    }
  }
} 

运行结果

仓库地址

参考资料

计算机操作系统(第四版) 西安电子科技出版社

ES6中的箭头函数()=>与function的区别

  1. 写法不同
  2. this指向不同
  3. 构造函数
  4. 变量提升

1.写法不同

//function
function x(a, b){
  return a + b;
}
//箭头函数
const x = (a, b)=>{
  return a + b;
}

2.this指向不同

使用function定义的函数,this的指向随着调用环境的变化而变化的,而箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境。

function x(a, b){
  console.log(this);
}
const obj = ()=>{
  test: 7gugu,
}
x(); //Window
obj.test(); //obj { test: 7gugu }

使用function定义的函数中this指向是随着调用环境的变化而变化的

//使用箭头函数定义函数 
var foo = () => { console.log(this) };
var obj = { aa:foo };
foo(); //Window
obj.aa(); //Window

明显使用箭头函数的时候,this的指向是没有发生变化的。

3.构造函数

//使用function方法定义构造函数 
function Person(name, age){     
  this.name = name;   
  this.age = age; 
} 
var lenhart =  new Person(lenhart, 25);
console.log(lenhart); //{name: 'lenhart', age: 25}
//尝试使用箭头函数 
var Person = (name, age) =>{     
  this.name = name;   
  this.age = age; 
}; 
var lenhart = new Person('lenhart', 25); //Uncaught TypeError: Person is not a constructor

function是可以定义构造函数的,而箭头函数是不行的。

4.变量提升

由于js的内存机制,function的级别最高,而用箭头函数定义函数的时候,需要var(let const定义的时候更不必说)关键词,而var所定义的变量不能得到变量提升,故箭头函数一定要定义于调用之前!

foo(); //123
function foo(){     
  console.log('123');
}  
arrowFn(); //Uncaught TypeError: arrowFn is not a function 
var arrowFn = () => {     
  console.log('456'); 
};

转载自:https://blog.csdn.net/github_38851471/article/details/79446722

Vue Production环境中Proxy无效的解决思路&方法

问题

最近上线Vue项目到服务器,上传之后就出现了,代理404的问题。Dev环境中的代理是工作正常的,这点让我很疑惑,但这恰巧是我的一个误解,下面是这次的解决思路。

解决思路

一开始,我以为vue.config.js中的,devServer中的proxy是在路由(Router)层面做的数据转发,所以在这上面花了一些时间进行研究。后续通过查阅官方文档发现,devServer配置的是一个nodejs的测试服务器参数,而不是路由参数后,恍然大悟。(这就是我的误解所在)

解决这个proxy的方向,应该是关注于配置自身的HTTP服务器的代理上面,如果是Nginx,就要配置Nginx的路由转发;我这里用的是Apache作为我的HTTP服务器,所以应该配置的是对应的代理参数。

配置步骤

HTTP服务器部分

1.打开Apache的httpd.conf,开启以下两个proxy拓展,保存

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so 

2.在https.conf中配置监听接口,保存

3.打开httpd-vhost.conf如下图配置即可

4.此时HTTP服务器部分就配置完成了,此时仅需重启Apache服务器即可生效。

Vue部分

因为是使用了History模式的路由,所以为了正确路由到相关的页面,还需要配置相关的PathRewrite才能正确路由。

1.在vue生成生产环境文件的文件夹中,添加.htaccess文件(我使用的是默认参数,所以就在dist文件夹中)

2.配置以下参数即可。(参数参考官方文档进行配置)

3.至此Vue部分配置完成,重新访问404的问题就消除了。

参考文献

  1. vue项目使用history模式打包应该注意的地方
  2. 前端用vue 上传项目,apache服务器 成功中转接口代理
  3. Apache的ProxyPass简单使用
  4. vue项目上线apache反向代理配置跨域
  5. apache proxy作用——ProxyRequests