秋夜已凉 02

博客年久失修还是来点音乐吧~~ 去 http://music.xiaoyu.work 都是你爱听的🎵~


  • 首页

  • 归档

前端部署发展史

发表于 2021-01-17

带着两个问题去思考:

1 缓存: 前端中的http的response header是由谁来配置?

2 跨域: /api的代理配置由谁来配置?在开发环境前端可以开个小服务,启用webpack-dev-server配置跨域,那在生产环境呢?

几个概念

跳板机:跳板机属于内控堡垒机范畴,是一种用于单点登陆的主机应用系统。2000年左右,高端行业用户为了对运维人员的远程登录进行集中管理,会在机房里部署跳板机。跳板机就是一台服务器,维护人员在维护过程中,首先要统一登录到这台服务器上,然后从这台服务器再登录到目标设备进行维护。但跳板机并没有实现对运维人员操作行为的控制和审计,使用跳板机过程中还是会有误操作、违规操作导致的操作事故,一旦出现操作事故很难快速定位原因和责任人。此外,跳板机存在严重的安全风险,一旦跳板机系统被攻入,则将后端资源风险完全暴露无遗。同时,对于个别资源(如telnet)可以通过跳板机来完成一定的内控,但是对于更多更特殊的资源(ftp、rdp等)来讲就显得力不从心了。

运维堡垒机: 人们逐渐认识到跳板机的不足,需要更新、更好的安全技术理念来实现运维操作管理,需要一种能满足角色管理与授权审批、信息资源访问控制、操作记录和审计、系统变更和维护控制要求,并生成一些统计报表配合管理规范来不断提升IT内控的合规性的产品。在这些理念的指导下,2005年前后,运维堡垒机开始以一个独立的产品形态被广泛部署,有效地降低了运维操作风险,使得运维操作管理变得更简单、更安全。

现在堡垒机产品已成熟,各家产品功能基本一致。早几年这还是个比较有前景的行业,但后来越来越多的人看到了这个机会,很多优秀的工程师也出来创业做类似的堡垒机产品。但主流的总是那么几家,如:思福迪、帕拉迪、尚思卓越、科友、齐治(微医线上用的就是齐治)、极地等,这些都是目前行业里做的专业且受到企业用户好评的厂商,但每家厂商的产品所关注的侧重又有所差别。各企业在选购的时候,除了仔细研究产品技术指标是否可以满足自己的需求外,还应该着重考虑产品的交互性、易用性、性价比、维护成本低、产品自身安全性等等。堡垒机作为单点故障点,自身安全性很重要。对于大数据量的企业,还应该考虑产品可扩展性,毕竟大数据中心的信息系统会越来越复杂。

server {
    listen 80;
    server_name xiaoyu.work;
}
# 避免非root路径404
location / {
    try_files $uri $uri/ /index.html;
}
# 解决跨域
location /api {
    proxy_pass http://api.xiaoyu.work;
}
# 为带有hash值的文件配置永久缓存(js/css静态资源)
location ~* \.(?:css|js)$ {
    try_files $uri = 404;
    expires 1y;
    add_header Cache-Control "public";
}

location ~ ^.+\..+$ {
    try_files &uri =404;
}

问题: 脚本经常跑不起来 运维抱怨着前端的部署脚本没有标好 node 版本,前端嚷嚷着测试环境没问题。 but why? 为什么会跑不起来?

使用docker构建镜像(引入docker)

https://juejin.im/book/5b7ba116e51d4556f30b476c/section/5b83817ae51d4538b406d852#heading-5

docker知识待整理…

dockerfile就是部署脚本,部署脚本就是dockfile 很大程度上缓解了前端运维的摩擦,至少部署脚本没问题了( 为什么?)

这时候,前端不再是提供静态资源了,而是提供一个http服务。 (服务?)

此时: 缓存开始交由前端控制(但是镜像中的http-server不太适合做这件事情)

跨域: 仍然在运维nginx中配置

CI/CD 与gitlab

解放运维的ci/cd

ci: Continuous Integration: 持续集成

cd: Continuous Delivery: 持续交付

重要的不是ci/cd是什么,重要的是现在运维不用跟着业务上线走了,不需要一直盯着前端部署了,这些都是ci/cd的事情了。

.gitlab-ci.yml是gitlab的ci配置文件。

deploy: 
    stage: deploy
    only:
        - master
    script:
        - docker-compose up --build -d

    tags: 
        -shell

ci/cd不仅仅更加解放了业务项目的部署,也在交付之前大大加强了业务代码的质量,它可以用来lint test package 安全检查,甚至

多特性多环境部署。

如果你有一台个人服务器的话,如果希望结合github做ci cd 可以试一试github + github action,或者drone.ci

使用k8s

k8s 知识待整理…

随着业务越来越大,镜像越来越多,docker-compose已经不太能应付,kubernetes应时而出,此时一台服务器从一台变成了多台,多台

服务器就会有多台服务器的分布式问题。

一个重要的角色

SRE: site reliability engineer 网站可靠性工程师,是软件工程师和系统管理员的结合,一个sre工程师基本上需要掌握很多知识

算法、数据结构、编程能力、网络编程、分布式系统、可扩展架构、故障排除。

SRE起源于国外大型互联网公司,直接掌管着互联网公司的机器和服务,保证网站不宕机是他们的使命。SRE基本是从软件研发工程师转型,有很强

的编程算法能力,同时具备系统管理员的技能,熟悉网络架构等,是一个要求非常高的职业。 [1]

大部分人理解SRE等于传统运维工程师(OP)或者系统管理员(SA),实则不然,这两类角色离一名合格的SRE还有太大的差距,完全无法匹配得上这

个称号。

在国内,只有少数几家顶尖互联网公司才会出现真正的SRE。

nginx: 作为http服务器接受来自internet的请求,并将请求按配置的规则转发给对应的端口

nodejs: 在云主机上提供js的运行环境

pm2: node应用进程管理器

git: 将git仓库的代码拉取到云主机上

发布

前后端分离中的一个要点就是发布分离,如果你的前端发布还在依赖后端发布,那就没法聊了。

先让它跑起来

  • npm install 安装依赖
  • npm run build 编译、打包、生成静态资源
  • 服务化静态资源

(需要学习docker)

每次ci部署的流程

  • 在构建服务器构建镜像
  • 把镜像推至镜像仓库服务器
  • 在生产服务器拉取镜像,启动容器

显而易见,镜像体积过大造成传输效率低下,增加了每次部署的延时。

堡垒机为访问集群限定一个入口,方便了权限的控制和监控

在传统的it环境中,安全边界是非常明确的,我们可以利用传统的堡垒机、防火墙来对服务等应用系统进行严格的

访问控制,在业务迁入云环境之后,传统堡垒机已经不再适用。

在分布式部署中使用容器技术是一个方向,Docker是这个方向上跑得最快也最完美的运动员。

函数式编程理念📝

发表于 2021-01-10

函数式编程

写React几个月了,接触到的,听到的较多的一个词“functional programming”函数式编程。在工作中也遇到了不少之前的大佬写的函数式代码,总结记录一些有关函数式的东西。

函数式?函数式编程?

1、函数式是一个数学概念:

f(x) = y;

函数式本身是很纯粹的一个概念,表达的就是函数定义域到值域之间一种特定的映射关系。(定义域中的一个值经过计算有且仅有一个值与之对应,也即只能得到一个确定的结果)。

f(x) = y;

这个数学函数式表达了以下几层意思:

  • 函数总是接受一个参数—x
  • 函数总是返回一个值—y
  • 函数应该根据接受的参数而不是外部环境运行
  • 对于一个给定的x只会输出一个唯一的y

2、函数式编程技术主要就是基于数学函数和它的思想。

比如有一个简单的计税函数如下:

let percentValue = 5;
let calculateTax = (value) => {
    return value/100 * (100 + percentValue)
}            

这个函数准确的实现了我们的想法,但是从数学的定义上分析,就会发现calculateTax依赖了全局变量percentValue,因此在数学上它不能被称之为一个真正的函数。修复方法很简单,我们只需要移动percentValue,把它作为函数的参数。

let calculateTax = (value, percentValue) => {
return value/100 * (100 + percentValue)
}        

现在calculateTax函数可以被称为一个真正的函数了。

移除一个函数内部对全局变量的访问会使得该函数的测试更加容易

函数式编程的特性

1、引用透明性

定义:所有的函数对于相同的输入都将返回相同的值,函数的这一特性被称为引用透明性

🌰例:

let identity = (i) = {return i}

定义一个简单的函数identity,无论传入什么作为输入,该函数都会把它返回,在函数内部没有全局引用,我们的函数只根据传入的参数i进行操作,该函数满足引用透明性条件。

sum(4,5) + identity(1)

根据引用透明性的定义,可以把上面的语句转换为:

sum(4,5) + 1; // 直接用结果替换函数的计算过程

该过程称为替换模型,因为我们可以直接替换函数的结果(因为函数的逻辑不依赖其他全局变量)而正是这一特性使得代码和缓存成为可能!

2、 并发?

引用透明性使得我们可以轻松地采用多线程来运行代码,根本不需要同步。为什么呢?因为同步的问题在于线程在并发运行的的时候不应该依赖全局变量。而遵循引用透明性的函数只依赖来自参数的输入,不会对全局产生任何依赖,这里没有任何“锁”🔒的概念,所以线程可以自由的运行(虽然js引擎是单线程…)

3、 缓存?

由于函数会为给定饿输入返回相同的值,实际上我们就可以缓存它。假设函数factorial用来计算给定数组的阶乘,其接受输入作为参数以计算其阶乘,5的阶乘是120,如果用户第二次用5来调用factorial,如果factorial遵循引用透明性,我们就可以直接返回缓存的值120而不用再重新计算一遍。

函数式编程对比命令式编程主张移除“如何”做的部分,使用一个函数来处理“如何”做的部分,将关注点放在手头的工作上即”做什么“。

为什么函数式好?

slice和splice两个函数在做数据处理的时候很类似,但是实现的方式却大不相同。

let xs = [1,2,3,4,5];

xs.slice(0,3); // 返回 [1,2,3];
xs.slice(0,3); // 返回 [1,2,3];
xs.slice(0,3); // 返回 [1,2,3];
.
.
. // 无论调用多少次它都能保证相同的输出。

而splice却会嚼烂调用它的那个数组,然后再吐出来🤮,这就产生了副作用,即这个数组永久的改变了🤷‍♂️

函数式编程讨厌这种会改变数据的函数,追求的是那种更加”可靠“的每次都能返回同样结果的函数。

就像上面的那个计税函数中,非函数式(即纯函数)的版本中,其计算结果依赖了全局变量percentValue,这是令人沮丧的🤦‍♂️,因为引入外部变量,增加了我们对函数的认知负荷和心理负担。程序中到处充斥着的这些不纯的函数,往往是产生BUG的罪魁祸首!

另一方面,使用纯函数的形式,函数自己就能做到自给自足,并且随时都可以放心大胆将其“打包带走”📦

函数式编程让我想到了之前在网上看到的两种关于线缆艺术的图片:

非函数式编程就行下图:

每个函数从哪里来到哪里去,因为有依赖全局变量的可能因素存在,同时函数本身也可能会改变其他函数所依赖的外部变量,所以让人看上去就比较乱,不敢轻举妄动。

函数式编程就好很多:

函数式编程因为做到了“字给自足”我需要的东西都在我自己内部,不依赖外部变量,同时你放心我很单纯我也不会去改变外面的什么,所以给人的感觉就是看上去比较清爽,数据清晰的感觉。

本来下面接着的内容该是柯里化、compose函数组合、管道啥的,但是对于柯里化看了一段时间感觉还是没有一个让自己“豁然开朗”的点,先留着,转而说说React

函数式编程与React…

关于react有一个很形象的公式 UI = F(state) 可以说是总结的非常到位了!

React强调数据的不可变性,把每一确定时刻的UI状态当作是电影🎬中的某一个确定的帧,长什么样子是确定的是由确定的state状态确定的,要让我渲染UI的另外一种状态,麻烦用另一个state来驱动,这给我一种“死板且靠谱”的感受~

Vue信奉数据可变哲学🙆,React则选择数据不可变哲学🙅‍♂️

作用? 副作用?

作用:可以理解为一切除了计算结果之外发生的事情。

副作用:是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的任何可观察的交互。

具体到前端开发中,副作用包括但不限于:

  • 更改文件系统
  • 往数据库插入记录
  • 发送一个 http 请求
  • 可变数据
  • 打印/log
  • 获取用户输入
  • DOM 查询
  • 访问系统状态
  • …

函数式编程的哲学里认为副作用是造成问题的主要原因。这一假定不无道理,在只有我和你的世界里,我不会出问题,然而问题产生了,那么肯定是你出了问题…
React主张的即是函数式的编程,但是我们做业务开发不是写静态页面,我们是用页面来承载我们的业务。从这个角度看,我有一种“业务都是通过副作用完成的”感觉~

那React是怎么平衡纯函数与副作用的呢?

effect? hooks?

React16.8引入了hooks API.对其中的useEffect感触比较多.在类组件中render函数就是个纯函数,负责实现UI = F(state)而业务操作多放在componentDidMount、updated、willUnmounted…等生命周期钩子中,其实单单就从组件生命周期的心智模型来理解,这样就挺好的。

1、但是生命周期是咋来的?

2、how do you think about componentDidMount?

3、useEffect(() => {doSomething}, []) === componentDidMount really???

在看React的hooks介绍文档的时候,在介绍到effect的执行时机的时候看到有这么一句话:

与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。

what??😺 就是说componentDidMount会阻塞浏览器更新屏幕咯?
答案: 是的。componentDidMount指的是组件在内存上中已经完成“挂载”一切已成定数,只等浏览器的GUI线程对页面进行渲染了,只有浏览器的GUI线程对页面进行了绘制操作,我们的肉眼才能看到屏幕上的内容。所以如果一个组件定义如下:

class Demo extends Compponent({
    componentDidMount() {
        console.log('now you see me!')
    }
    render() {
        return (
            <div>You can see me with your naked eye</div>
        )
    }
})

我们会先在控制台看到log的打印输出,然后才看到屏幕上渲染的内容。所以说componentDidMount会阻塞浏览器更新屏幕,指的就是componentDidMount中的内容属于当前Tick任务周期内的工作,浏览器会等到V8将这一工作做完,才让GUI线程执行绘制操作。

useEffect与componentDidMount的不同点在与useEffect中的effect操作会等到浏览器render完成之后(是真正意义上的render,肉眼看见的那种,不是内存中的挂载。)才会执行副作用操作,所以其不会阻塞浏览器的渲染。真正跟componentDidMount一致的是useLayoutEffect。它会排在浏览器GUI线程对页面进行渲染之前执行。
React建议一开始先使用useEffect,只有当它出问题时候再尝试使用useLayoutEffect。

另外:关于“用了hooks的函数式组件每次渲染都有属于每次自己的、单独的effect,每次的effect都是全新的,而不是上次的。。。
”此类的说法。理解: effect是传给useEffect()这个API的回调函数,而不是useEffect这个api(听起来好像是废话~)

const effect = () => {
    console.log('i am effect')
}

useEffect(effect, [])

useEffect是一个API,react内部会“hold住它~” 而传递进去的函数才是每次都不一样的effect.

生命周期函数也叫生命周期钩子函数 life cycle hooks function,其中也有一个单词’hook’,回过头来看,似乎又有一种“大一统”的感觉。

其实都是业务模型的抽象,我们在写componentDidMount或者其他任何生命周期钩子函数的时候并不是在写componentDidMount或者什么,而是在写“我希望在这个时候做点什么事情。。。“的业务需求模型

hooks用函数式的方式优化更新了这种业务模型,使之看起来似乎更加合理,编写起来更加清爽方便。

关于每秒60帧

发表于 2020-12-31

关于每秒60帧

看了一些介绍FPS和浏览器刷新率的科普文章,直接抛结论:

目前大部分电脑的屏幕刷新率是60Hz即每秒钟页面会刷新60次,也即是60帧的画面。这是显示器硬件层面的,浏览器的刷新频率一般与屏幕的刷新率保持一致或者略低于屏幕的刷新率,基本也是60Hz,所以平均每一帧的耗时是1000/60 = 16.67ms. (浏览器的fps高于显示器的fps是无意义的性能浪费,因为即使你绘制的贼快,屏幕不刷新依旧看不出来变化~)

不同帧率给人的直观感受:

  • 帧率能够达到 50 ~ 60 FPS 的动画将会相当流畅,让人倍感舒适;
  • 帧率在 30 ~ 50 FPS 之间的动画,因各人敏感程度不同,舒适度因人而异;
  • 帧率在 30 FPS 以下的动画,让人感觉到明显的卡顿和不适感;
  • 帧率波动很大的动画,亦会使人感觉到卡顿

要想页面给用户丝滑顺畅的感受,就要努力完成每秒60帧的目标💪,具体到每一帧就要控制在16.67ms之内,因为浏览器内部通常还会有不同的线程之间调度的工作,所以通常留给我们的V8的时间只有10ms,我们应该尽量保证在10ms的时间内完成js的执行。。。

用像素管道的方式来看前端优化问题

像素管道

浏览器将像素绘制到页面一般要经过以下几个过程:

JS / CSS -> style -> layout -> paint -> composite

1、 js: 一般我们使用js来实现一个视觉变化的效果。

2、 style: 样式计算,根据匹配选择器计算出哪些元素应用哪些css规则的过程

3、 layout: 布局,在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置

4、 paint: 绘制,有时候也叫栅格化是一个fill像素点的过程

5、 composite: 合成,用于处理不同的层级之间的关系,比如谁盖在谁的上面

一个帧内要做这么多事情。。。。如果js执行时间过长超过16.67ms,就会block住,那么就会丢掉一帧的绘制。

那么如何着手优化它呢?

JS,Style 和 Composite 是不可避免的,因为需要 JS 来引发样式的改变,Style 来计算更改后最终的样式,Composite 来合成各个层最终进行显示。Layout 和 Paint 这两个步骤不一定会被触发,所以在优化的过程中,如果是需要频繁触发的改变,我们应该尽可能避免 Layout 和 Paint。

1、最耗费性能之重排!

管道运行方式
JS/CSS —> Style —> Layout —> Paint —> Composite


此过程就是我们常说的浏览器重排,也就是改变了元素的几何属性(例如宽度、高度、左侧或顶部位置等),那么浏览器将必须检查所有其他元素,然后“自动重排”页面。任何受影响的部分都需要重新绘制,而且最终绘制的元素需进行合成,重排进行了管道的每一步,性能受到较大影响。

2、稍好之重绘
管道运行方式
JS/CSS —> Style —> Paint —> Composite


重绘,也就是修改“paint only”属性(例如背景图片、文字颜色或阴影等),即不会影响页面布局的属性,则浏览器会跳过布局,但仍将执行绘制。

3、性能最好之不重拍也不重绘
管道运行方式
JS/CSS —> Style —> Composite


此过程不会重排重绘,仅仅是进行合成,也就是修改 transform 和 opacity 属性更改来实现动画,性能得到较大提升,最适合于应用生命周期中的高压力点,例如动画或滚动。

尽量使用 transform 和 opacity 属性更改来实现动画

性能最佳的像素管道版本会避免 Layout 和 Paint

为了实现此目标,需要坚持更改可以由合成器单独处理的属性。常用的两个属性符合条件:transform 和 opacity。

另外:想知道每种 CSS 属性的更改是否会触发 Layout,Paint,Composite,可以通过 csstriggers.com 查看。

有一种能有效减小 Layout 和 Paint 的方法是将元素提升,像 Photoshop 中层的概念一样,样式也有层的概念,不同的层根据不同顺序叠加起来,通过 Composite 最终显

示出来。在每个层中对这个层进行 Layout 或者 Paint 是不会影响其他层的,一般会根据整个页面的语义将页面分为几个层。

提升元素还有一个好处就是会将动画从 CPU 转移到 GPU 来完成,来实现硬件加速。

尽量避免 Layout

让 DOM 脱离文档流再对其进行操作,所有操作完成后添加进文档流,这样可以将重排及重绘的次数降低到一次或两次(脱离文档流及回归文档流的时

候),以下方法可以让元素脱离文档流:

  • 隐藏元素 —— display: none;(事实上 display:none 不会让元素出现在 layout tree 中)。
  • 使用 DocumentFragment(推荐,只有一次 re-flow)。
  • 将原始元素拷贝到一个脱离文档的节点中,修改这个副本,完成后再替换掉原始元素。

事件委托

利用事件冒泡的机制来处理子元素事件的绑定,将子元素的 DOM 事件,交由它们的父元素来进行处理,可以有效降低页面的开销 —— 由于事件的冒泡

特性,只需要给父组件添加一个监听事件,就能够捕获到冒泡自子元素的事件,再通过 e.target 来获取真正被操作的子元素。

使用 requestAnimationFrame

在某个单个帧中,有可能发生这种情况,在某一帧中会被多次触发某个事件(比如 scroll),这个事件又会频繁的触发样式的修改,导致可能需要多次

Layout 或者 Paint,这是一种浪费,过于频繁的 Layout 和 Paint 会造成卡顿,而且实际上一帧中并不需要重复 Layout 或者 Paint 那么多次。

这个时候就可以用到 rAF 了,先放上一段 MDN 上对 rAF 的解释:

window.requestAnimationFrame() 方法告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调

函数作为参数,这个回调函数会在浏览器重绘之前调用。

在下一次重绘之前调用,理解: 重点不在于“重绘之前” 而是“下一次” 是下一次!不是本次!

简单来说,rAF的作用就是将传给rAF的回调函数,安排在下一帧的一开始执行。这样就能保证这个回调函数最先执行,并且因为

绝大多数浏览器都是60fps,所以rAF自带截流效果。

在浏览器的一轮事件循环中,会有 task -> microtask -> UI render,这么的一个循序,rAF 将回调函数放在下一帧的

开头,就是已经让其所在的那一轮的 UI 先 render,然后再在下一帧的最开始去执行.

理解: 开头,就是已经让其所在的那一轮的 UI 先 render,然后再在下一帧的最开始去执行。
在调用 rAF 时,有一点切记:不要在 rAF 的回调函数中先修改样式,再查询样式,这样就失去了 rAF 的作用。可以将对样式的查询提前到回调函数中或者 rAF 中尽量靠前的位置。

参考链接🔗:

30帧、60帧、120帧对画面影响有多大?
useEffect的执行时机
how to write fast memory-officient js

React hooks、Redux

发表于 2020-12-28

由hooks API 想到的Redux 的设计

“React不是一套完整的前端解决方案,它只提供了view层面的支持。。。。组件通信和数据管理它没有给出解决方案。。。“这是网上一些介绍 why redux 的文章中经常会看到的一话(也有很多声音说redux是过度设计..)那么redux做了什么为什么用了它数据就能‘整整齐齐’不易出错了呢?

我的理解就是“开源节流”的一个想法,没有用redux时,任何组件都可以随便任何时set任何State,这样就造成数据很乱,没有一个数据的“交通规则”,用了redux之后大家统一通过dispatchAction的方式来提交对数据改变的“申请”,dispatch就是更改数据的通道—-“开源”
提交的各种action,到了reducer这里会做统一的处理,每种type的action,能做的改动都是被reducer严格定义好的,只有reducer中有注册的类型才能被执行,这样就严格的限制了可对数据造成更改的范围—-“节流”. 一些文章中介绍说,redux做的就是当你想改变全局的state的时候“大张旗鼓”的告诉我。。。。大张旗鼓用的好,我的觉得Hook其实有点类似的味道,hooks只能在函数式组件中用,函数式组件本身就很纯,你给我什么我就渲染什么仅此而起,而当你想要做一些其他的小动作的时候也要大张旗鼓的告诉我,怎么个大张旗鼓呢,useEffect这个Api的名字,就很说明问题了—-“是的,我就是在这里在做会带来副作用的小动作啊~🤷‍♂️”。 这样就能比价清晰的将纯粹渲染的部分和带有副作用的部分划分开来,既能减少产生问题的可能,也方便对问题进行追查。

关于EventLoop

发表于 2020-12-20

关于EventLoop

不知道有没有人跟我我一样产生过这样的疑问

1、浏览器的一次事件循环♻️的时间是多久?是16.67ms吗?

2、我当前的这行代码执行的时候处于当前循环的什么位置? 是循环的开始?还是中间位置,还是循环马上要结束了?

我大概知道产生这样疑问,说明对对浏览器内部event loop的心智模型是不对的,但是很长一段时间以来,无论是在看vue中的nextTick还是一些文章中介绍的“下次dom更新。。“这样的字眼时心中总是感觉有一层不甚明确的概念,What exactly is a event loop tick?

现在理解如下:

当浏览器解析到一个script开始标签时,整个这个标签整体可以看作是对应一次Tick

<script>
    // Tick开始
    console.log('同步代码')
    ...
    setTimeout(() => {
        console.log('宏任务队列代码')
    })
    ...
    Promise.then(() => {
        console.log('微任务队列代码')
    })
    requestAnimationFrame(() => {
        console.log('本次微任务之后下次宏任务之前的代码。。')
    })
    ...
    // Tick结束
</script>

浏览器在执行这段代码的时候,浏览器会为其开辟一块儿内存空间作为程序的执行栈,整块代码会作为一个main调用最先进入执行栈开始执行,可以认为此时开启了一个宏任务,浏览器会依次执行该栈中的函数,遇到同步代码就执行,遇到setTimeout这样的Webapi调用,会将其压入宏任务队列中下次Tick再执行,遇到Promise.then这样的代码,它属于是本次Tick运行中产生的微任务代码,会将其放入微任务队列,在本次Tick结束之前(即所有的同步代码都执行完毕)会对其进行调用。也即是浏览器的每一个Tick会执行一个宏任务以及在该宏任务执行期间产生微任务。(自己产生的微任务,自己执行掉,不会留到下一个Tick去执行)

而requestAnimationFrame是个比较特殊的调用,它根本不在event loop的生命周期里,这个api也并不属于JS运行时,而是浏览器宿主环境提供的,它属于另一个主题——渲染。

浏览器作为一个复杂的应用是多线程工作的,除了运行JS的线程外,还有渲染线程,定时器触发线程,HTTP 请求线程等等。JS线程可以读取并且修改DOM而渲染线程也需要读取DOM,这是一个典型的多线程竞争临界资源的问题。 所以浏览器就把这两个线程设计成互斥的,即同时只能有一个线程在执行。

渲染原本就不应该出现在event loop相关的知识体系里。因为event loop显然是在讨论JS如何运行的问题,而渲染则是浏览器另外一个线程的工作。但是requestAnimationFrame的出现却把这两件事情给关联起来不在V8环境里,只是浏览器又开放的一个在渲染之前发生的新的hook。所以它对应的animation callback queue的处理方式也是不一样的。
通过调用requestAnimationFrame我们可以在下次渲染之前执行回调函数。那下次渲染具体是哪个时间点呢?渲染和event loop有什么关系呢?简单来说,就是在每一次event loop的末尾,判断当前页面是否处于渲染时机,是就重新渲染。而渲染时机又是个啥,查阅资料发现有点“玄学”~ 有屏幕的硬件限制,跟机器性能有关联,跟js执行是否超时有关联,总之要不要渲染是一个多方面因素条件综合下来浏览器判断的结果。有点React中shouldComponentUpdate内味儿~但无论如何requestAnimationFrame保证的就是在浏览器下次渲染之前一定会被调用。

关于执行上下文

函数调用栈,其实就是执行上下文栈,每当调用一个函数

时就会产生一个新的执行上下文,同时新产生的这个执行

上下文就会被压入执行上下文栈中。

全局上下文最先入栈,并且在离开页面时才会出栈,js引擎

不断的执行上下文栈中栈定的那个执行上下文,在它执行完毕后

将它出栈,直到整个执行栈为空。

关于执行栈有5点注意:

  • 单线程
  • 同步执行
  • 只有一个全局上下文
  • 可以有无数个函数上下文(理论是函数上下文没有限制,但是太多了会爆栈)
  • 每个函数调用都会创建一个新的执行上下文

明确一个概念: 函数上下文执行栈是与js引擎相关的概念,

而异步/回调是与运行环境相关的概念。

如果执行栈与异步机制完全无关,我们写了无数遍的点击触发回调是如何做到的呢?是运行环境(浏览器/Node)来完成的, 在浏览器中,异步机制是借助 event loop 来实现的,

event loop 是异步的一种实现机制。JavaScript 引擎只是“傻傻”的一直执行栈顶的函数,而运行环境负责管理在什么时候压入执行上下文栈什么函数来让引擎执行

JavaScript 引擎本身并没有时间的概念,只是一个按需执行 JavaScript 任意代码片段的环境。“事件”( JavaScript 代码执行)调度总是由包含它的环境进行。

另外,从一个侧面可以反应出执行上下文栈与异步无关的 —— 执行上下文栈是写在 ECMA-262 的规范中,需要遵守它的是浏览器的 JavaScript 引擎,比如 V8、Quantum 等。

event loop 的是写在 HTML 的规范中,需要遵守它的是各个浏览器,比如 Chrome、Firefox 等。

浏览器和浏览器的js引擎是两码事。

一个页面有一个event loop ,但是一个event loop 可以有多个

task queues.

每个来自相同 task source 并由相同 event loop(比如,Document 的计时器产生的回调函数,Document 的鼠标移动产生的事件,Document 的解析器产生的 tasks) 管理

的 task 都必须加入到同一个 task queue 中,可是来自不同 task sources 的 tasks 可能会被排入到不同的 task queues 中。

来自相同的 task source 的 task 将会被排入相同的 task queue,但是规范说来自不同 task sources 的 tasks 可能会被排入到不同的 task queues 中,也就是说一个 task queue 中

可能排列着来自不同 task sources 的 tasks,但是具体什么 task source 对应什么 task queue,规范并没有具体说明。

一般我们看个各个文章中对于 task queue 的描述都是只有一个,不论是网络,用户时间内还是计时器都会被 Web APIs 排入到用一个 task queue 中,但事实上规范中明确表示了是

有多个 task queues,并举例说明了这样设计的意义:

举例来说,一个用户代理可以有一个处理键盘鼠标事件的 task queue(来自 user interaction task source),还有一个 task queue 来处理所有其他的。用户代理可以以 75% 的几率

先处理鼠标和键盘的事件,这样既不会彻底不执行其他 task queues 的前提下保证用户界面的响应, 而且不会让来自同一个 task source 的事件顺序错乱

当用户代理将要排入任务时,必须将任务排入相关的event loop 的task queues;

这句话很关键,是用户代理来控制任务的调度.

参考链接🔗:

理解event loop的n重境界

Reacteffect理解

发表于 2020-11-19

加入依赖是在做什么

useEffect本身是组件每次渲染都会执行的,加入effect是为了减少其执行的次数

加入空数组代表只执行一次

加入其他变量,表示在其改变的时候执行

当你调用 useEffect时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。

useEffect是在dom更新完成后做的事情。

一般来说,函数退出后,变量会销毁,而state中的变量会被react保留

const [count, setCount] = useState(0);

我们声明了一个叫count的state变量,然后把它设置为0。React会在重新渲染时记住它当前的值,并提供最新的值给我们的函数。

之所以叫做useState而不是createState,就是我们并不是每次都create这个state变量,而是只在组件第一次渲染的时候创建。

当用户点击后,我们传递一个新的值给setCount。React会重新渲染Example组件,并把最新的count传给它。

如果我们想使用多个state变量,它允许我们给不同的state变量取不同的名称。

我们可以单独更新每一个state。(理解: 我们单独更新一个state, react会重新渲染函数组件,并用每个state的最新值去渲染,我们更新的那个state状态,得到更新,而那些没有setState的变量,之前的值就是最新的)

当我们的state变量是一个复杂的对象或者数组时,不同于class的this.setState,更新state变量总是替换而不是合并。(理解: clss组件中的setState是合并式的更新,因此我们得以单独的跟新state中的某一个值)

副作用

数据获取、设置订阅以及手动更改react组件中的dom都属于副作用。

1、无需清除的副作用

有时候,我们只想在react更新dom之后运行一些额外的代码。比如发送网络请求

,手动更新dom,记录日志等。

在class组件中,render函数是不应该有任何副作用的。

我们基本都希望在react更新dom之后才执行我们的操作。

这就是为什么我们把副作用操作放在componentDidMount 和

componentDidUpdate函数中。

useEffect(() => {
document.title = you clicked ${count} times
})

通过这个hook,你可以告诉react组件需要在渲染后执行某些操作。

React会保存你传递的函数(我们将它称之为effect,重点理解:effect是我们

传递的函数!!!而不是useEffect这个hooks api),并且在执行dom更新

之后调用它。(理解:在useEffect中我们给它传递的是一个回调函数,将我们

需要的操作放在回调函数中,react会在每次更改dom后执行我们的回调函数)

理解: 因为我们传递的是一个函数,而函数是一个引用数据类型的数组,

所以传递给useEffect的函数在每次渲染中都会不一样,每次渲染都会有自己的

回调函数(内存地址不同,不是同一个函数对象),这是刻意为之。这正是

我们可以在effect中获取最新的count的值,而不用担心其过期的原因。

每次我们重新渲染,都会生成新的effect,替换掉之前的(effect是我们写在useEffect中的回调函数!!)

某种意义上讲effect更像是渲染结果的一部分——每个effect 属于一次特定的渲染。

不理解的地方:提示
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。

是什么意思?

解惑: useLayoutEffect 的函数签名与useEffect相同,但是它会在所有的DOM变更之后同步调用effect。可以使用它来读取DOM布局并同步触发重渲染。在浏览器执行绘制之前,

useLayoutEffect内部的更新计划将被同步刷新。尽可能的使用标准的useEffect以避免阻塞视觉更新。

useLayoutEffect与componentDidMount componentDidUpdate 的调用阶段是一样的。

理解: 就是说类似vue中的nextTick componentDidMount componentDidUpdate useLayoutEffect, 会在dom更新完毕,浏览器执行绘制(肉眼看到新的页面)之前执行,如果在这里面做复杂的计算就会阻塞页面的渲染。而useEffect会在页面渲染完毕之后执行,所以不会阻塞页面的绘制渲染。

http://www.soolco.com/post/73226_1_1.html。 // 这篇文章说的很清楚

2、需要清除的effect

如果你的effect返回一个函数,react将会在执行清除操作时调用它(理解:写在这里的回调函数会在react卸载的时候执行,你可以在这块空间做任何自己的操作,同样我们也是return一个函数定义给react,react会在销毁的时机去调用它)

忘记正确地处理componentDidUpdate是React应用中常见的bug来源。

useEffect默认就会处理,它会在调用一个新的effect之前对前一个effect进行清理。

如果某些特定值在两次重新渲染之间没有发生变化,可以通知react跳过对effect的调用,只要传递数组作为useEffect的第二个参数即可。理解:所以我们传递第二个参数是为了减少effecta执行的次数。

自定义hook是一个函数,函数内部可以调用其他的hook
与React组件不同的是,自定义hook不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回的是什么(理解: 自定义hook不必须返回 一个值和一个set值的函数,换句话说,不非要成对出现,可以自由定义返回的东西,根据需求来)

在多个hook之间传递信息:

由于hook本身是函数,因此我们可以在他们之间传递信息。就像下面这样:

const [recipentID, setRecipentID] = useState(1);

const isRecipentOnline = useFriendStatus(recipientID);

当我们通过select调用setRecipentID ,改变了recipentID后,useFriendStatus也会用最新的recipientID来执行。

惰性初始state

initialState参数只会在组件的初始化渲染中起作用,后续渲染时会被忽略。如果初始state需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染的时候被调用:

const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState
})

调用state hook的更新函数并传入当前的state时,react会跳过子组件的渲染以及effect的执行,react使用Object.is比较算法来比较state.

useCallback

const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]),

返回一个memoized的回调函数。

把内联回调函数及依赖项数组作为参数传入useCallback,

它将返回该回调函数的memoized版本,该回调函数仅仅在

某个依赖项改变时才会更新。

当把回调函数传递给经过优化的并使用引用相等性去避免非必要

渲染的子组件时,它将非常有用。比如(shouldComponentUpdate)

useCallback(fn, deps)相当于useMemo(() => fn, deps).

useMemo

返回一个memoized值。

把创建函数和依赖项数组作为参数传入useMemo,它仅会在某个依赖改

变时才会重新计算memoized值。这种优化有助于避免每次渲染时

都进行高开销的计算。

传入useMemo的函数会在渲染期间执行。不要在这个函数中执行与

渲染无关的操作。

可以把useMemo作为性能优化的手段,但不要把它作为语义上的保证。

应该先编写在没有useMemo的情况下也可以执行的代码—之后再在代码中

添加useMemo,以达到性能优化的目的。

一些函数式编程的思考🤔

发表于 2020-10-10

let’s talk about functional programming

原本我以为自己对于函数式编程是有一定的认知的,或者说自己对于这个概念的掌控是足够的,然而前段时间做的一个需求中遇到了大量的前辈大佬的“真函数式编程代码”还是被教育重新做人了🙄️于是特地从家中翻出从二手市场上淘来的《javaScriptES6函数式编程入门经典》细细研究起来,有所收获记录于此📝

函数的第一条原则是要小,函数的第二条原则是要更小 –鲁迅

什么是函数式编程,为何它重要

首先看一个数学中的函数

f(x) = y;

该语句可以解读为: ‘一个函数f,以x作为参数,并返回输出y’

该定义包含了以下几个关键点:

  • 函数总是接受一个参数
  • 函数总是返回一个值
  • 函数应该根据接受到的参数而不是外部环境运行
  • 对于一个给定的x只会输出一个唯一的y

函数式编程技术主要就是基于数学函数和它的思想

来看一个简单的计税函数

let percentValue = 5;
let calculateTax = (value) => {
    return value/100 * (100 + percentValue)
}                    

这个函数准确的实现了我们的想法,但是从数学的定义上分析,就会发现calculateTax依赖了全局变量percentValue,因此在数学上它不能被称之为一个真正的函数。修复方法很简单,我们只需要移动percentValue,把它作为函数的参数。

let calculateTax = (value, percentValue) => {
    return value/100 * (100 + percentValue)
}

现在calculateTax函数可以被称为一个真正的函数了。

移除一个函数内部对全局变量的访问会使得该函数的测试更加容易

函数式编程是一种范式,我们能够以此创建仅依赖输入就可以完成自身逻辑的函数。这保证了当函数被多次调用时仍然返回相同的结果。函数不会改变任何外部环境的变量,这将产生可缓存的、可测试的代码库。

引用透明性

所有的函数对于相同的输入都将返回相同的值,函数的这一特性被称为引用透明性

看一个例子

let identity = (i) => {return i}

定义一个简单的函数identity,无论传入什么作为输入,该函数都会把它返回,在函数内部没有全局引用,我们的函数只根据传入的参数i进行操作,该函数满足引用透明性条件。

sum(4,5) + identity(1)

根据引用透明性的定义,可以把上面的语句转换为:

sum(4,5) + 1

该过程称为替换模型因为我们可以直接替换函数的结果(因为函数的逻辑不依赖其他全局变量)这一特性使得并发代码和缓存成为可能。

并发

引用透明性使得我们可以轻松地采用多线程来运行代码,根本不需要同步。为什么呢?因为同步的问题在于线程在并发运行的的时候不应该依赖全局变量。而遵循引用透明性的函数只依赖来自参数的输入,不会对全局产生任何依赖,这里没有任何“锁”🔒的概念,所以线程可以自由的运行(虽然js引擎是单线程…)

缓存

由于函数会为给定饿输入返回相同的值,实际上我们就可以缓存它。假设函数factorial用来计算给定数组的阶乘,其接受输入作为参数以计算其阶乘,5的阶乘是120,如果用户第二次用5来调用factorial,如果factorial遵循引用透明性,我们就可以直接返回缓存的值120而不用再重新计算一遍。

命令式、函数式、抽象

函数式编程主张声明式编程和编写抽象的代码

以遍历一个数组为例子:命令式编程方法遍历如下:

let array = [1,2,3]
for (i=0;i<array.length;i++){
    console.log(array[i])
}

采用声明式编程方法遍历:

let array = [1,2,3]
array.forEach((element) => console.log(element))

理解体会:移除“如何”做的部分,使用一个函数来处理“如何”做的部分,将关注点放在手头的问题(做什么上)

javascript函数式编程

发表于 2020-09-17

什么是函数式编程?

数学中的函数
f(x) = y
  • 函数必须总是接受一个参数
  • 函数必须总是返回一个值
  • 函数应该依据接收到的参数而不是外部环境运行
  • 对于一个给定的x,只会输出唯一的一个y
var percentValue = 5;
var calculateTex = (value) => {
    return value/100 * (100 + percentValue ))
}

上面的计税函数不是一个纯函数在于它依赖了全局变量percentValue ,修复方法很简单,只需要移动全局变量percentValue ,把它作为函数的参数改写如下:

var calculateTex = (value, percentValue ) => {
return value/100 * (100 + percentValue )
}

现在calculateTex 可以被称为一个真正的纯函数了。
将函数对全局变量的依赖移到函数的参数中是最常见的一种“纯函数化”的方式,移除函数对全局变量的访问会使得函数的测试更加容易。

引用透明性 (Referential Transparency)

函数对于相同的输入都将返回相同的值,这一属性被称为引用透明性。依据函数的这一属性可以直接用函数的结果替换掉函数的调用,这个过程被称为替换模型,这些特性使得并发代码和缓存称为可能。多线程和同步的问题在于对全局数据的依赖上,而遵循引用透明性的函数只依赖参数的输入而不依赖全局数据,所以线程可以自由的运行且没有锁的机制。
引用透明性是一种分析哲学

函数式编程与命令式编程的对比

命令式编程遍历数组
let array = [1,2,3]
for (let i = 0; i< array.length; i++) {
console.log(array[i])
}
函数式编程
let array = [1,2,3]
array.forEach(item => console.log(item))

在函数式编程中我们移除了“如何做”的部分将他抽象到一个函数中(高阶函数),如此可以让开发者只需要关心手头的问题

纯函数

  • 纯函数不应该依赖任何外部变量,同时也不应该改变任何外部变量。
  • 纯函数应该像Math.max(3,4,5,6)一样,不需要看max的内部实现就能得到结果。
  • 纯函数允许我们并发的执行代码,因为纯函数不会改变它的环境,意味着我们不需要关心同步的问题。
  • 纯函数使缓存函数的输出避免下次重新计算成为可能
  • 纯函数应该被设计成只做一件事(单一职责)就像UNIX哲学那样,我们可以通过组合或者管道来完成复杂的任务

Get or Post

发表于 2020-08-27

http只是个行为准则,而tcp才是get和post怎么实现的基本。

不同的浏览器(发起http请求)和服务器(接受http请求)就是不同的运输公司。 虽然理论上,你可以在车顶上无限的堆货物(url中无限

加参数)。但是运输公司可不傻,装货和卸货也是有很大成本的,他们会限制单次运输量来控制风险,数据量太大对浏览器和服务器都是很

大负担。业界不成文的规定是,(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url。超过的

部分,恕不处理。如果你用GET服务,在request body偷偷藏了数据,不同服务器的处理方式也是不同的,有些服务器会帮你卸货,读出数

据,有些服务器直接忽略,所以,虽然GET可以带request body,也不能保证一定能被接收到哦。

还要看服务器对请求的实现的支持啊。

GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。

get和post还有一个重大的区别,简单的说:get产生一个tcp数据包,post产生两个tcp数据包。

不简单的说:对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

也就是说,GET只需要汽车跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开

门迎接我”,然后再回头把货送过去。

因为POST需要两步,时间上消耗的要多一点,看起来GET比POST更有效。因此Yahoo团队有推荐用GET替换POST来优化网站性能。但

这是一个坑!跳入需谨慎。为什么?

  • GET与POST都有自己的语义,不能随便混用。

  • 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证

数据包完整性上,有非常大的优点。

  • 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

Get/post

如果尝试重新执行post请求,浏览器也会弹一个框提示这个刷新可能会有副作用,询问要不要继续。

确认重新提交表单?

把get实现为有副作用是个很可怕的事情。

其实并不是GET只能用url,而是浏览器直接发出的GET只能由一个url触发。所以没办法,GET上要在url之外带一些参数就只能依靠url上附带querystring。但是HTTP协议本身并没有这个限制。

浏览器的POST请求都来自表单提交。每次提交,表单的数据被浏览器用编码到HTTP请求的body里。浏览器发出的POST请求的body主要有有两种格式,一种是application/x-www-form-urlencoded用来传输简单的数据,大概就是”key1=value1&key2=value2”这样的格式。另外一种是传文件,会采用multipart/form-data格式。采用后者是因为application/x-www-form-urlencoded的编码方式对于文件这种二进制的数据非常低效。

浏览器在POST一个表单时,url上也可以带参数,只要

里的url带querystring就行。只不过表单里面的那些用”input”等标签经过用户操作产生的数据都在会在body里。

因此我们一般会泛泛的说“GET请求没有body,只有url,请求数据放在url的querystring中;POST请求的数据在body中“。但这种情况仅限于浏览器发请求的场景。

接口中的GET和POST

Ajax 中

太自由也带来了另一种麻烦,开发人员不得不每次讨论确定参数是放url的path里,querystring里,body里,header里这种问题,太低效了。于是就有了一些列接口规范/风格。其中名气最大的当属REST

REST充分运用GET、POST、PUT和DELETE,约定了这4个接口分别获取、创建、替换和删除“资源”,REST最佳实践还推荐在请求体使用json格式。这样仅仅通过看HTTP的method就可以明白接口是什么意思,并且解析格式也得到了统一。

babel-node 包含在 babel-cli 库,其实就是在 node 上包了一层, 在原来的 node 执行前拦截代码并转换成合适的格式

最后:

  • GET在浏览器回退时是无害的,而POST会再次提交请求。



  • GET产生的URL地址可以被Bookmark,而POST不可以。



  • GET请求会被浏览器主动cache,而POST不会,除非手动设置。



  • GET请求只能进行url编码,而POST支持多种编码方式。



  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。



  • GET请求在URL中传送的参数是有长度限制的,而POST么有。



  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。



  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。



  • GET参数通过URL传递,POST放在Request body中。

什么是shell

发表于 2020-08-23

bash

一说到命令行,我们真正指的是shell.shell就是一个程序,它接受从键盘输入的命令,然后把命令传递给操作系统去执行。几乎所有的Linux发行版都提供一个名为bash的来自GNU项目的shell程序。“bash”是“Bourne Again SHell“的缩写,所指的是这样一个事实,bash是最初Unix上由pSteve Bourne 史蒂夫伯恩写成shell程序的sh的增强版。

所以shell就是一个从键盘接受命令,然后把命令传递给操作系统去执行的程序,bash是一种shell程序。

终端仿真器

当使用图形用户界面时,我们需要另一个和shell交互的叫做终端仿真器的程序,在菜单中它们可能被简单称为“terminal”基本上它们都完成相同的事情,让我们能访问shell.

启动终端

wangxiaoyudeMacBook-Pro:~ wangxiaoyu$

这叫做shell提示符,无论何时当shell准备好了去接受输入时,它就会出现。不同的shell可能会以各种各样的面孔显示,它通常包括你的用户名@主机,紧接着当前的工作目录和一个美元符号💲
如果提示符的最后一个字符是”#”而不是“$”那么这个终端会话就有超级用户权限,这意味着,你要么是以root用户的身份登录,要么是选择的终端仿真器提供了超级用户权限。许多Linux发行版默认保存最后输入的500个命令,按下上箭头就会出现上次的命令。

复制和粘贴

虽然shell是和键盘打交道的,但是你可以在终端仿真器里使用鼠标。X窗口系统(是GUI工作的底层引擎)内建了一种机制,支持快速拷贝和站忒技巧。按下鼠标左键,沿着文本拖动鼠标高亮一些文本,那么这些文本就被拷贝到一个由X管理的缓冲区里面,然后按下鼠标中键,这些文本就被粘贴到光标所在的位置。

注意:不要在一个终端窗口里使用Ctrl-c和Ctrl-v快捷键来执行拷贝和粘贴。它们不起作用,对于shell来说这两个控制代码有着不同的含义,它们早于Microsoft Windows定义复制粘贴的的含义,许多年之前就赋予了不同的意义。

几个简单的基础命令

date显示系统当前时间和日期

wangxiaoyudeMacBook-Pro:~ wangxiaoyu$ date
2020年 1月31日 星期五 10时16分53秒 CST

cal显示当前月份的日历

wangxiaoyudeMacBook-Pro:~ wangxiaoyu$ cal
      一月 2020
日 一 二 三 四 五 六
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

df查看磁盘剩余空间的数量

wangxiaoyudeMacBook-Pro:~ wangxiaoyu$ df
Filesystem    512-blocks      Used Available Capacity iused      ifree %iused  Mounted on
/dev/disk1s5   489620264  21466584 155803688    13%  484313 2447617007    0%   /
devfs                684       684         0   100%    1187          0  100%   /dev
/dev/disk1s1   489620264 308791640 155803688    67% 2740319 2445361001    0%   /System/Volumes/Data
/dev/disk1s4   489620264   2099240 155803688     2%       2 2448101318    0%   /private/var/vm
map auto_home          0         0         0   100%       0          0  100%   /System/Volumes/Data/home

exit可以通过关闭终端仿真器窗口,或者是在shell提示符下输入exit命令来终止一个终端会话。

12…7

王晓宇

67 日志
© 2021 王晓宇
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4