
https://zhoukekestar.github.io
原文地址:https://yuque.antfin.com/nrdara/asd0u6/rp3na5本文是在原来《再谈“面向未来中后台场景”》 文章上的进一步实践和探索。在我们的看来,工作台的前端开发,核心目标就是降本提效。而这背后需要重塑的是生产方式,而生产方式 = 生产力(劳动力 + 生产资料)和生产关系的特定组合。在工作台场景下,就是 前端 + 外包(劳动力)通过 IDE(或其他生产工具)对物料(或其他生产对象)的生产、组合或再改造。抛去生产关系和生产工具不说,本文重点聊聊前端软件 “可维护性” 这件事。既然我们要聊如何提升工作台效能,那当然要从现有的成本构成开始说起。维护成本占总研发成本的 67% 以上看到这个,是不是有点不太敢相信,其实刚开始,笔者也有些不可思议。但越是深入了解,再结合自身的的体会,这个看似奇怪的结论似乎也有几份道理可讲。从软件的一整个生命周期来看,我们 _参考一份评估模型 [1] _的不同软件时期工作量和时间的分布关系 我们可以看到前期包括设计、开发在内,仅占工作量的 40% 左右,而剩下的更新、升级工作量占比约占 60% 的工作量。在 _另外一份论文__[2] _中,我们更能惊奇地看到,作者在近 30 多年的跨度中,软件的的维护成本更是趋于 90%。这也让我想起,在某些遗弃的角落,哪些各种维护不动的应用。当然,根据不同的场景、统计方式,数据差异也会比较大,所以我们找到 一份报告_ [3]_ 用来说明不同论文的结论,如下所示:AuthorMaintenance as a % of Build CostDaniel D. Galorath75%Stephen R. Schach67%Thomas M. Pigoski>80%Robert L. Glass40% – 80%Jussi Koskinen>90%有些作者得出了最低 40% 的结论,有些作者得出了大于 90% 的结论,所以,通常来看,大家比较认可的一个数据是 67%,来自 Stephen R. Schach [4] 的结论:结合这么多学者的研究,再看看手上各种老应用,是不是有那么一丝丝惊讶了呢?可维护性是技术架构选型的重要依据说到 维护成本,从更大的视角来看,其实是对 “软件质量(Software Quality)” 的控制。软件质量的控制,按照 _CISQ (The Consortium for Information & Software Quality™ ) 的模型 [6] [7] _可分为:MAINTAINABILITYRELIABILITYSECURITYPERFORMANCE EFFICIENCY首先按照 CISQ 的定义来看,在工作台场景下,综合考虑 软件成本 和 软件质量 之后,优先级应该是 MAINTAINABILITY > RELIABILITY > SECURITY > PERFORMANCE EFFICIENCY 的。因为后三者更多的是一次性(或几次性)成本(但对质量来说至关重要),而 MAINTAINABILITY 更多的是和业务强相关的多次、频繁的成本,而且前面也说到了占比 67%。所以说,可维护性是综合软件质量和成本后最重要的考虑因素(其他几个相对次要)。从另外一方面来看,我们在对软件做技术架构的目的是什么?通俗些说,就是又好又快地接业务需求,并长期支撑业务发展。在我看来,好与快之间,“好” 是 “长期” 的 “快” 的基础。而这个 “好” 其实本质上就对软件质量的控制, “快” 则是对软件成本和效率的控制。如此来看来,可维护性就是我们技术架构选型的重要依据了。PS:对所有软件都适用吗?其实不然!对于大促场景下的临时导购页面,考虑到无需过多的人员协同,且页面的生命周期相对较短,快速上线、页面 PERFORMANCE 是更重要的因素,可维护性就显得不那么重要。PPS根据 _IEEE Std 14764-2006 _的定义,维护工作主要有两类:修正型的和增强型的。提高可维护性的几点思路和想法其实这块挺难写的,我尝试通过 SOLID 或其他软件设计原则来拆解,但都不太理想。所以,也只能通过不完全枚举来先简单聊聊了。基础:标准&规范为什么标准很重要?We want to support our products and solutions for as long as we possibly can. We don’t want to have to constantly start from scratch and see our previous work become obsolete.-- https://blog.bosch-si.com/bosch-iot-suite/why-standards-matter-in-the-iot/A standard is a repeatable, harmonised, agreed and documented way of doing something. Standards contain technical specifications or other precise criteria designed to be used consistently as a rule, guideline, or definition. They help to make life simpler and increase the reliability and the effectiveness of many of the goods and services we use.-- https://www.irena.org/inspire/Standards/What-are-Standards简单搜索了一些关于 “why standards matter” 的回答,可以看到标准对于我们软件的长时间的维护、复用、可靠性以及开发成本都有着重要影响。举个反面例子,关于 _视频信号传输 [10] _的(当然也有历史原因和相关取舍):前端运行时规范应该是什么?React 虽然已经是集团内部 “事实上的规范了”,但在工作台场景下,依旧有着许多的担忧。在拉长的时间维度上看,它会不会是另外一种的 “jQuery”?在一篇 The Brutal Lifecycle of JavaScript Frameworks [11] 的文章中,作者通过在 Stack Overflow 上问题数量来分析了前端运行时框架的生命周期,也是挺值得我们深思的。我们能看到,在 2013 年左右,jQuery 无意是最正确的选择,帮我们解决了很多现实的问题,比如最重要的便是兼容性问题。但由于标准的逐渐推行,兼容性得以改善,也便慢慢失去了市场。相应的,也慢慢成为了一种包袱。而现在,必须承认的是 React 是当下最正确的选择,但我们将时间拉长到 5 ~ 10 年,会是这样吗?在我看来 React 解决了我们最大的问题是:组件化(当然也有人认为是 Data 到 UI 的映射、或者渲染性能等等)。抛开数据映射和性能不聊(在我看来,不是当下最痛的点),所谓的组件化方案,也仅仅是 WebComponents 兼容问题的 “过渡方案”。探索中的工作台标准与规范这里的规范包括:运行时框架、表单、搭建协议、工程构建、模块加载等等。我们在这块的实践上,可以说是在 “狂奔” 来形容,技术选型也是比较 “激进”,比如:搭建协议标准化,虽然集团有 _《阿里经济体中后台搭建协议规范》__[9] _,但在长期以及实践过程中,依旧有着不少的困难,所以,全量的 WebComponent 方案 正在探索和考虑中。运行时标准化,抛弃 Render Engine 的思路,让浏览器作为我们唯一核心的渲染引擎。尽管我们的 Render Engine 已经抽象到 400 行代码 都不到了。模块加载标准化,随着 ESM 兼容性的日趋好转,ESM 已经成为我们模型加载的最佳实践。随之带来的天然特性,便是 Bundle-Less,Module-Federation 等等。表单和列表规范,目前得益于 Formily 和 Alist,主要以这两套规范进行落地,也在尝试如何标准化。通过一个 工作台标准化页面的 Demo 示例,大概感受一下应用以上标准的页面,源代码如下:<common-layout style="height: 100vh; display: block"> <krump-app-header slot="header"></krump-app-header> <common-menu slot="menu" krump-app-id="1"></common-menu> <main slot="main" style="padding: 24px"> <sl-card class="card-header"> <div slot="header">我是 Formily 测试表单</div> <schema-form onSubmit="data:text/javascript,await import('https://jspm.1688.com/@ali/ks-b5aed4ed-7d77-4391@daily').then(d => d.onSubmit)" > <formily.field name="name" x-component="Input" x-component-props='data:application/json,{"label": "name"}' ></formily.field> <formily.field name="address" x-component="Input" x-component-props='data:application/json,{"label": "address"}' ></formily.field> <formily.submit></formily.submit> </schema-form> </sl-card> </main> </common-layout> 标准&规范对于软件质量的提升根据 _ISO/IEC 9126 的模型 __[8] _,我们可以简单看一下以上标准对软件质量的影响。_AnalysabilityUI 描述:HTML & WebComponents 对 UI 的描述达到了非常干净的抽离(和 JSX、JSON 相比)逻辑描述:由于 ESM 的引入,Bundle-Less 的代码使人更容易阅读,甚至不用看源码(与之相对的缺点是代码安全性问题)Changeability通过 define 不同的 element 和 slot,对 UI 的描述和实现可以做充分的抽离( JSON 也有此类效果)Stability和框架生命周期相比,规范的生命周期往往是它的数倍,也就意味着,我们的成本也将呈现数倍的下降(仅是猜测,无相关数据佐证)Testability按照组件维度的单测,和 React 相比,无明显的优势、或提升PortabilityAdaptability:WebComponents 可与任何框架进行适配集成Replaceability:定义与实现分离,以及特定的 enhance 能力使得组件的可替换性大大增强,而非 js 的强依赖Installability:页面写完即可运行,无需过多的依赖、打包、部署Co-existence:非排他,可共存。可在 vue、react 中使用,也可以将 vue、react 集成到 WebComponentsReliabilityMaturity:Custom Element V0 的提出到 V1 的大面积推广,光是规范的提出就经历2 ~ 3 年(2014 至 2017),截止目前 2021 已经历了 7 年之久,已经是很多框架的一生了(如 backbone)。Recoverability、Fault tolerance:WebComponents 做了相当多的容错和可修复机制,比如未实现的 element、替换等等。Learnability相对框架的学习,HTML + CSS + JavaScript 的学习曲线显得更为平滑,其知识的适用时间跨度也更为长久。设计原则:随用随弃的重要性你的前端项目代码是不是越来越多,而且只能越来越多。虽然 Data、Service、Controller、View 分层分得好好的,但代码依旧越来越臃肿,越来越不敢删?生怕有些不常用的功能还在使用某个功能。引用即使用得益于 ESM 的特性,我们所有的 JS 模块都采用的 HTTP Import,这就意味着 “引用即使用”(反之 “非引用即弃用”)。当相关模块被替换的时候,引用也就随着消失,项目运行时代码量也就随着发生变动。import Link from 'https://jspm.1688.com/@ali/ks-krump-test1@0.0.1'; // 升级更换其他组件之后 ==> import Link from 'https://jspm.1688.com/@ali/ks-krump-component-link@0.0.1';替换原则从 里氏代换原则(Liskov Substitution Principle LSP) 的角度出发来考虑。随着各种规范 Interface 的确定,我们能替换和随用随弃的能力将得到进一步的释放,项目可维护性将得到进一步的提升。比如:<form> // 定义了 custom element 的 value、change 接口 <input name='date" is="vue-date-picker" /> </form> // 组件实现 <script type="module" src="./vue-date-picker.js"></script> // js升级为其他框架之后 ==> <form> <input name='date" is="angular-date-picker" /> </form> <script type="module" src="./angular-date-picker.js"></script> 人的协同:学习曲线和进场成本工作台场景的外包化是基于成本考量的重要一环,人员的培训、学习,以及代码的质量控制是此场景下需要重点考虑的。学习成本更低的成本:用 HTML 的 UI 描述,降低了学习工作台搭建协议的成本。WebComponents 的强悍集成能力,理论上能让开发者随意地选择自己熟悉的 UI 库,比如 Ant Design、Fusion 等,也能让开发者随意地选择框架,比如:React、Vue 等(同时需要避免泛滥)。更长久的知识储备:在框架快速迭代的时期中,我们本周上更多地是在学习框架的各种接口和使用,如果转而对规范的学习,依托规范的生命周期,我们的知识理论上能得到更持久的应用。进场成本随着 WebIDE 的逐渐成熟,我们现有的工作台开发已经全量线上化,ProCode 也全量 WebIDE 化,这其中所能带来的优势是显而易见的。开发同学不需要关心开发工具的依赖,开箱即用。当然,这对后续的维护也是极为有利。“页面即开发” 思路也正在探索,所有线上页面,只需一键开启 Debug,就能直接在线切换至 WebIDE 资源。并在修改完成后,提交工单即可发布上线。“轻开发”、“轻发布” 这些对于进场成本的降低,或者对于整体软件的可维护性有着重要的影响。否则,你可能还在找仓库源码的阶段的徘徊。小结通过对软件成本的拆解,我们能看到可维护性工作其实占了软件整体生命周期的近 2/3。对比工作台的成本,其实也能得出相似的结论。所以,我们出于对工作台维护成本的思考,来挖掘影响软件质量背后的因素,从而也看到软件可维护性的重要性。在提升可维护性的几点思路中,由于前端还不那么成熟,所以,在现有体系下,标准和规范显得尤为重要,也是当下提升工作台场景下可维护性的重要突破口。在从基础架构出发的同时,我们也看到在应用 “软件设计原则” 对可维护性的重要影响,最后,对场景下工种的特点分析,我们还需要对 “人的协同” 的方面来提升软件的整体可维护性。参考Research on Software Maintenance Cost of Influence Factor Analysis and Estimation MethodWhich Factors Affect Software Projects Maintenance Cost More?Software Maintenance: Understanding and Estimating CostsSchach, R. (1999), _Software Engineering_, Fourth Edition, McGraw-Hill, Boston, MA, pp. 11.IEEE/ISO/IEC 14764-2006 - ISO/IEC/IEEE International Standard for Software Engineering - Software Life Cycle Processes - MaintenanceMEASURING CODE QUALITYhttps://en.wikipedia.org/wiki/Software_qualityhttps://en.wikipedia.org/wiki/ISO/IEC_9126《阿里经济体中后台搭建协议规范》https://en.wikipedia.org/wiki/List_of_video_connectorsThe Brutal Lifecycle of JavaScript Frameworks
记一次奇怪的编码问题。 Meta Element vs Response Header Meta 的作用? 一个细节 细节对编码的影响 Meta Element vs Response Header 一个 GBK 编码页面,使用 meta 指定页面编码和使用 response header 指定页面编码。哪个优先级比较高? Case 1 Header 为 utf-8Meta 为 gbk $ curl -i http://127.0.0.1:3000/utf-1 HTTP/1.1 200 OK X-Powered-By: Express Content-Type: text/html;charset=utf-8 Accept-Ranges: bytes Cache-Control: public, max-age=0 Last-Modified: Sat, 08 Sep 2018 03:53:26 GMT ETag: W/"129-165b7502163" Content-Length: 297 Date: Sat, 08 Sep 2018 03:54:25 GMT Connection: keep-alive <html> <meta charset="gbk"> <p> 中文(使用 encodeURIComponent ): <script> document.write(encodeURIComponent('中文')); </script> </p> <p>页面跳转(使用 a 标签): <a href='/?p=中文'> <script> document.write(document.querySelector('a').href); </script> </a> </p> </html> Case 2 Header 为 gbkMeta 为 utf-8 $ curl -i http://127.0.0.1:3000/utf-2 HTTP/1.1 200 OK X-Powered-By: Express Content-Type: text/html;charset=gbk Accept-Ranges: bytes Cache-Control: public, max-age=0 Last-Modified: Sat, 08 Sep 2018 03:53:44 GMT ETag: W/"12b-165b750666a" Content-Length: 299 Date: Sat, 08 Sep 2018 03:54:31 GMT Connection: keep-alive <html> <meta charset="utf-8"> <p> 中文(使用 encodeURIComponent ): <script> document.write(encodeURIComponent('中文')); </script> </p> <p>页面跳转(使用 a 标签): <a href='/?p=中文'> <script> document.write(document.querySelector('a').href); </script> </a> </p> </html> 结论 Response Header 编码优先级高于 meta 信息 Meta 的作用? 既然 response header 优先级比较高,那要使 meta 生效,需要先设置 content-type 为空,然后再用 meta 指定编码,验证一下 不设置 header,指定 meta 编码为 utf-8 注意 response header 中没有 content-type 了由于 shell 编码是采用 utf-8 的,所有在终端中会显示乱码能看清 meta 信息即可 $ curl -i http://127.0.0.1:3000/gbk-1 HTTP/1.1 200 OK X-Powered-By: Express Content-Type: null Accept-Ranges: bytes Cache-Control: public, max-age=0 Last-Modified: Sat, 08 Sep 2018 04:05:23 GMT ETag: W/"115-165b75b105c" Content-Length: 277 Date: Sat, 08 Sep 2018 04:06:51 GMT Connection: keep-alive <html> <meta charset="utf-8"> <p> </p> </html> 浏览器效果: 不设置 header,指定 meta 编码为 gbk $ curl -i http://127.0.0.1:3000/gbk-2 HTTP/1.1 200 OK X-Powered-By: Express Content-Type: null Accept-Ranges: bytes Cache-Control: public, max-age=0 Last-Modified: Sat, 08 Sep 2018 04:05:27 GMT ETag: W/"113-165b75b22f9" Content-Length: 275 Date: Sat, 08 Sep 2018 04:07:03 GMT Connection: keep-alive <html> <meta charset="gbk"> <p> </p> </html> 效果 结论 在未指定 content-type header 的情况下,可以使用 meta 标签指定页面编码 一个细节 我们把能正常编码的页面放一起看 utf-1 页面 gbk-2 页面 现象 我们发现,不管是 GBK 页面还是 UTF 页面,使用 encodeURIComponent('中文') 后的编码都为 %E4%B8%AD%E6%96%87 。也就是说无论页面编码,encodeURIComponent 都使用 UTF 进行编码。 但使用 a 标签,将中文字符放在 HTML 代码中,或在 js 中直接使用 location.href = '/?p=中文' 进行跳转,该编码格式会与页面编码有关。如上图中,UTF 页面会将中文编码成 %E4%B8%AD%E6%96%87, GBK 页面会将中文编码成 %D6%D0%CE%C4,这里面坑就比较大了。 编码细节的影响 Nodejs 在 nodejs 中,使用 express 框架,分别请求 http://127.0.0.1:3000/?p=%D6%D0%CE%C4 和 http://127.0.0.1:3000/?p=%E4%B8%AD%E6%96%87 ,使用 req.query.xxx 获取 url 参数时,会分别返回 %D6%D0%CE%C4 和 中文,这点也比较好,默认会使用 utf-8 解码,不行的返回原编码。 使用 GBK to UTF 的编码解决即可。 Java Servlet 不像 Nodejs,Java 编码不正确设置,会乱码 @WebServlet("/GBK") public class GBK extends HttpServlet { /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置为 GBK 编码 request.setCharacterEncoding("GBK"); String name =request.getParameter("name"); response.setContentType("text/html; charset=utf-8"); response.getWriter().append("Hello " + name); } } ~ curl -i http://localhost:8080/servlet/GBK\?name\=%D6%D0%CE%C4 HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: text/html;charset=utf-8 Content-Length: 12 Date: Sat, 08 Sep 2018 06:47:52 GMT Hello 中文% ️ 其中需要注意的是,URL 中由于是 GBK 编码,实际处理过程中: processParameters(MessageBytes data, java.lang.String encoding) 中的 encoding ,是org.apache.catalina.connector.Request:parseParameters 方法设置的,相关代码如下: // 从 connector 中拿到配置项 boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI(); if (enc != null) { parameters.setEncoding(enc); // 配置了 bodyencodingforuri 才对 uri 进行制定编码 decode if (useBodyEncodingForURI) { parameters.setQueryStringEncoding(enc); } } else { parameters.setEncoding (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); if (useBodyEncodingForURI) { parameters.setQueryStringEncoding (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); } } // 处理 query 参数 parameters.handleQueryParameters(); 而 useBodyEncodingForURI 需要容器侧设置,如:tomcat 中的 server.xml <Connector useBodyEncodingForURI="true"/> References Headers 所有编码格式 相关源码
JS 加法器模拟,实现 半加器 全加器 波纹进位加法器 全部代码 补码 & 减法 常规位运算 位运算 & 简单的 assert 断言 // 常规位运算 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators const AND = (a, b) => a & b; const OR = (a, b) => a | b; const XOR = (a, b) => a ^ b; const NOT = a => ~a; // Fake Node Assert Lib const assert = { deepEqual: (a, b) => { if (a.toString() === b.toString()) return; throw new Error(`Not Equal: ${a} ${b}`); } } 半加器 电路逻辑 电路简要 /** * 半加器 * 两个 bit 输入,输出数组 [进位,和] * 如: * 1,1 => [1, 0] * @param {bit} a 输入 * @param {bit} b 输入 */ const HalfAdder = (a, b) => [a & b, a ^ b]; // 半加器测试 assert.deepEqual(HalfAdder(0, 0), [0, 0]); assert.deepEqual(HalfAdder(0, 1), [0, 1]); assert.deepEqual(HalfAdder(1, 0), [0, 1]); assert.deepEqual(HalfAdder(1, 1), [1, 0]); 全加器 电路逻辑 电路简要 概要表示 概要表示 /** * 全加器 * 两个 bit 输入,和进位输入,输出数组 [进位,和] * 如: * 0,1,1 => [1, 0] * @param {bit} a 输入 * @param {bit} b 输入 * @param {bit} c 进位输入 */ const FullAdder = (a, b, c) => { var t1 = HalfAdder(a, b); var t2 = HalfAdder(t1[1], c); return [t1[0] | t2[0], t2[1]]; } // 全加器测试 assert.deepEqual(FullAdder(0, 0, 0), [0, 0]); assert.deepEqual(FullAdder(1, 0, 0), [0, 1]); assert.deepEqual(FullAdder(0, 1, 0), [0, 1]); assert.deepEqual(FullAdder(0, 0, 1), [0, 1]); assert.deepEqual(FullAdder(1, 1, 0), [1, 0]); assert.deepEqual(FullAdder(1, 0, 1), [1, 0]); assert.deepEqual(FullAdder(0, 1, 1), [1, 0]); assert.deepEqual(FullAdder(1, 1, 1), [1, 1]); 波纹进位加法器 简要图 简要图2 常见芯片表示 /** * 波纹加法器, 4 位加法器 * 如: * [0, 1, 0, 1],[0, 1, 0, 1] => [1, 0, 1, 0] * @param {Array<Number>} a 4位 bit 输入数组,如:[0, 1, 0, 1] * @param {Array<Number>} b 4位 bit 输入数组,如:[0, 1, 0, 1] * @returns {Array<Number>} */ const RippleCarryAdder = (a, b) => { let carry = 0; let bit = 3; let result = []; while(bit >= 0) { let temp = FullAdder(a[bit], b[bit], carry); carry = temp[0]; result.push(temp[1]); bit--; } return result.reverse(); } /** * 将数字转成 4 位二进制数组 * 如: * 1 => [0, 0, 0, 1] * 3 => [0, 0, 1, 1] * @param {Number} a 数字 * @returns {Array<Number>} */ const to4Bit = a => ( a.toString(2) .split('') .reverse() .concat(Array(4).fill('0')) .slice(0,4) .reverse() .map(i => +i) ); /** * 将二进制字符串转为数字 * 如: * '1010' => 10 * @param {String} a 4 位二进制字符串 * @returns {Number} */ const from4Bit = a => parseInt(a, 2); /** * 加法简写工具 * @param {Number} a 输入 * @param {Number} b 输入 */ const helper = (a, b) => ( from4Bit(RippleCarryAdder( to4Bit(a), to4Bit(b) ).join('')) ) assert.deepEqual(helper(0, 0), 0); assert.deepEqual(helper(1, 1), 2); assert.deepEqual(helper(1, 2), 3); assert.deepEqual(helper(2, 2), 4); assert.deepEqual(helper(3, 5), 8); assert.deepEqual(helper(1, 14), 15); // 9 + 14 为 23,但由于我们写的是 4 位加法器,所以有溢出 // 最终的结果需要 mod 0x10(也就是 16) assert.deepEqual(helper(9, 14), 23 % 0x10); assert.deepEqual(helper(9, 14), 7); 全部代码 Github 地址 关于补码 & 减法 我们以 4 bit 存储数字,并以最高位作为符号位 数字 原码 反码 补码 非溢出的十进制 2 0010 0010 0010 2 -1 1001 1110 1111 15 -3 1011 1100 1101 13 2 - 1 = 2 + (- 1) = 0010(补)+ 1111(补)= 0010(补)= 17 % 16 = 1 2 - 3 = 2 + (- 3) = 0010(补)+ 1101(补)= 1111(补)= 15 % 16 = -1 References 计算机中加法的实现 加法器 更多文章访问:zhoukekestar.github.io/notes
在之前了解 PN 结以及逻辑电路实现之后,终于可以开始尝试实现计算机的加法了。 逻辑门,包括与、或、与非、异或 半加器,半加器电路 全加器,全加器电路 波纹进位加法器 逻辑门 与门 AND 或门 OR 与非门 NAND 异或门 XOR 真值表 A B AND OR NAND XOR 0 0 0 0 1 0 0 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1 0 0 半加器 半加器(half adder)的功能是将两个一位二进制数相加。它具有两个输入和两个输出(分别是和(sum)、进位(carry))。 — Wiki 半加器电路图 A B Carry Sum 0 0 0 0 1 0 0 1 0 1 0 1 1 1 1 0 半加器简化图 全加器 全加器(full adder)将两个一位二进制数相加,并根据接收到的低位进位信号,输出和、进位输出。全加器的三个输入信号为两个加数A、B和低位进位Cin。 全加器逻辑电路 该电路如用简化版半加器表示,可以如下图: 全加器真值表 二进制的全加器,刚可以用两位表示最大值,即 1 + 1 + 1 = 3 = 11(2) A B Cin Cout S 0 0 0 0 0 1 0 0 0 1 0 1 0 0 1 1 1 0 1 0 0 0 1 0 1 1 0 1 1 0 0 1 1 1 0 1 1 1 1 1 全加器简化图 或 波纹进位加法器 如果不需要连接其他进位信号,则最低位的全加器可以用半加器替换。 References 计算机中加法的实现 加法器 更多文章访问:zhoukekestar.github.io/notes
了解完二极管和 PM 结原理后,就可以看懂简单的逻辑电路了 载流子 场效应管 与门 AND 二极管实现 继电器实现 CMOS 实现 NMOS 实现 或门 OR 或非 NOR 与非 NAND 载流子 在物理学中,载流子(charge carrier)简称载子(carrier),指可以自由移动的带有电荷的物质微粒,如电子和离子。在半导体物理学中,电子流失导致共价键上留下的空位(空穴)被视为载流子。 — Wiki 在半导体中,电子和空穴作为载流子。数目较多的载流子称为多数载流子;在N型半导体中多数载流子是电子,而在P型半导体中多数载流子是空穴。数目较少的载流子称为少数载流子;在N型半导体中少数载流子是空穴,而在P型半导体中少数载流子是电子。[1] — Wiki 场效应管 场效应管(英语:field-effect transistor,缩写:FET**)是一种通过电场效应控制电流的电子元件。 它依靠电场去控制导电沟道形状,因此能控制半导体材料中某种类型载流子的沟道的导电性。 — Wiki G 栅极通过控制电压,控制 P 沟道的载流子,从而控制 PN 结的宽度,参考:场效应管及其放大电路 在 Vgs 为 0 时,PN 结最小,导电 N 沟道最宽,导电性能最好 当 Vgs 负电压增大,电子流入 P 沟道,P 沟道载流子减少,N 沟道为平衡 PN 结的内电场,N 沟道的载流子也变小,从而导致 PN 结变宽。 Vds 的原理大致与 Vgs 相同 当 Vds 增强时,也会出现沟道夹断的情况。 此处还待进一步思考 与门 二极管电路实现 vcc:电路供电电压 为10v,假设 3v 以上为高电平,3v 以下为低电平。参考:二极管与门电路原理 ua ub uy 0(正偏)/ 0 0(正偏)/ 0 0.7v / 0 3v (反偏)/ 1 0 (正偏)/ 0 0.7v / 0 0 (正偏)/ 0 3v (反偏)/ 1 0.7v / 0 3v (正偏)/ 1 3v (正偏)/ 1 3.7v / 1 继电器实现 仅 A、B 都为高电平,使继电器将开关闭合,从而使得 Y 导通为高电平。 CMOS与门 PN结指向内的为NMOS管, PN 结指向外部的为 PMOS [4] NMOS的特性,Vgs大于一定的值就会导通,适合用于源极接地时的情况(低端驱动),只要栅极电压达到4V或10V就可以了。 PMOS的特性,Vgs小于一定的值就会导通,适合用于源极接VCC时的情况(高端驱动)。但是,虽然PMOS可以很方便地用作高端驱动,但由于导通电阻大,价格贵,替换种类少等原因,在高端驱动中,通常还是使用NMOS。[5] T1, T2, T5 为 PMOS,0 导通,1 不导通 T3,T4,T6 为 NMOS,1 导通,0 不导通 A,B T1,T2,T3,T4 T5,T6 栅极 T5,T6 Y 0,0 通,通,不通,不通 1 不通,通 0 1,0 不通,通,通,不通 1 不通,通 0 0,1 通,不通,不通,通 1 不通,通 0 1,1 不通,不通,通,通 0 通,不通 1 NMOS 与门 假设与 a 直连的 NMOS 为 T1,与 b 直连的 NMOS 为 T2,与 F 直连的为 T3 仅 a、b 都为 1 时,T3 栅极为低电平,从而 T3 不导通,导致 F 为高电平 其余情况,任意 a、b 为 0 时,与 T3 栅极为高电平并导通,使得 F 接地,为低电平。 或门 A B L 0(正偏)/ 0 0(正偏)/ 0 0v / 0 5v (正偏)/ 1 0 (反偏)/ 0 5v / 1 0 (反偏)/ 0 5v (正偏)/ 1 5v / 1 5v (正偏)/ 1 5v (正偏)/ 1 5v / 1 非门 a 高电平时,F 接地为低电平,反之成立 或非 NOR 或非门具有函数完备性,和与非门一样可以仅用其实现其他所有的逻辑功能。 电路图如下,并假设与 a 直连的 MOS 为 T1,与 b 直连的 MOS 为 T2 当 a、b 中任意一个为高电平,则 F 和 GND 连接为低电平,只有 a、b 都为低电平时,F 才为高电平。 输入A B 输出A NOR B 0 0 1 0 1 0 1 0 0 1 1 0 与非 NAND NMOS CMOS NMOS, 仅 A,B 都为高电平使得 T2,T3 导通,Y 才为低电平 CMOS 如下: A,B T1,T2,T3,T4 Y 0,0 通,通,不通,不通 1 0,1 通,不通,不通,通 1 1,0 不通,通,通,不通 1 1,1 不通,不通,通,通 0 References 二极管与门电路原理 场效应管及其放大电路 逻辑门 如何判断NMOS管和PMOS管? NMOS & PMOS 更多文章访问:zhoukekestar.github.io/notes
PN 结是二极管中最基本的单向导通原理 化学键 离子键 共价键 元素周期表 PN 结 原理 形成 正向偏置 反向偏置 PN 结 化学键 化学键有3种类型 ,即离子键、共价键、金属键(氢键不是化学键,它是分子间力的一种)。 — Baidu 离子键 带相反电荷离子之间的互相作用叫做离子键(Ionic Bond),成键的本质是阴阳离子间的静电作用。 — Baidu 阳离子、阴离子通过静电作用形成的化学键称作离子键。两个原子间的电负性相差极大时,一般是金属与非金属,例如:氯与钠,若他们要结合,电负性大的氯会从电负性小的钠抢走一个电子,以符合八隅体。之后氯会以-1价的方式存在,而钠则以+1价的方式存在,两者再以库仑静电力因正负相吸而结合在一起。 — Wiki Na 在元素周期表中为 11,意味着有 11 个质子,根据外层电子排布,分别为 2, 8, 1, 所以,Na 容易失去电子呈现 +1 ,F 同理容易得到电子,由此得到的 NaF 就形成了离子键。 共价键 共价键(Covalent Bond)是原子间通过共用电子对(电子云重叠)而形成的相互作用 — Baidu 原子间通过共用电子形成的化学键,叫做共价键。它通过两个电负度相近的原子,例如两个氧,互相共用其外围电子以符合八隅体的键结方式结合,因此也有人说这是非金属原子间的结合方式。 — Wiki 金属键 化学键的一种,主要在金属中存在。由自由电子及排列成晶格状的金属离子之间的静电吸引力组合而成。由于电子的自由运动,金属键没有固定的方向,因而是非极性键。 — Baidu 极性键 极性键是在化合物分子中,不同种原子形成的共价键,由于两个原子吸引电子的能力不同,共用电子对必然偏向吸引电子能力较强的原子一方,因而吸引电子能力较弱的原子一方相对的显正电性。这样的共价键叫做极性共价键,简称极性键。 — Baidu 元素周期表 单一元素可以以共价键形式组成,如 H2,O2 NaCl 以离子键(极性键)组成。Na 电子层排布:2-8-1,Cl 电子层排布:2-8-7,一个容易失去电子,一个容易得到电子。 同位素:由于元素周期表是以质子数数量为排序依据,而未考虑中子数,所以,中子数不同,而质子数不同的元素称为同位素。 PM 结 一块半导体晶体一侧掺杂)成P型半导体,另一侧掺杂成N型半导体,中间二者相连的接触面称为PN结(英语:pn junction)。PN结是电子技术中许多元件,例如半导体二极管、双极性晶体管的物质基础。 — Wiki PM 结的基本原理 掺入少量杂质硼元素(或铟元素)的硅晶体(或锗晶体)中,由于半导体原子(如硅原子)被杂质原子取代,硼原子外层的三个外层电子与周围的半导体原子形成共价键的时候,会产生一个“空穴”,这个空穴可能吸引束缚电子来“填充”,使得硼原子成为带负电的离子。这样,这类半导体由于含有较高浓度的“空穴”(“相当于”正电荷),成为能够导电的物质。 Si 电子层排布:2-8-4,纯 Si 在四周和 4 个 Si 元素共用 4 个外层电子,组成 4 个共价键,形成 4 + 4 的 8 电子数稳定结构。在此基础上,加入 B (电子排布:2-3),使其产生一个空穴。 掺入少量杂质磷元素(或锑元素)的硅晶体(或锗晶体)中,由于半导体原子(如硅原子)被杂质原子取代,磷原子外层的五个外层电子的其中四个与周围的半导体原子形成共价键,多出的一个电子几乎不受束缚,较为容易地成为自由电子。于是,N型半导体就成为了含自由电子浓度较高的半导体,其导电性主要是因为自由电子导电。 PN 结的形成 当 PN 级结合的时候,N 极多余电子会往 P 极扩展,形成一个内电场,这个内电场叫做 PN 结,也叫阻挡层、耗尽层、空间电荷区。 电子受到电场力作用会漂移向N级,但N级电子太多,还是会向P级扩散。两种运动形成了动态平衡,当然,不一定会像下面这个动画一样形成稳定的环形电流。 正向偏置 电源正极接P,负极接N,电荷会重新分布 因为载流子多而且PN结窄,所以会形成比较大的电流。 负极电子的流入(外加电场)导致 PN 结变窄,从而使得电子的扩散运动得到增强,并形成正向电流 反向偏置 反向偏置使得 PN 结变宽 因为载流子少而且PN结太宽,所以电流会很小。 QA 同位素 同位素(英语:Isotope)是某种特定化学元素之下的不同种类,同一种元素下的所有同位素都具有相同原子序数,质子数目相同,但中子数目却不同。这些同位素在化学元素周期表中占有同一个位置,因此得名。 例如氢元素中氘和氚,它们原子核中都有1个质子,但是它们的原子核中分别有0个中子、1个中子及2个中子,所以它们互为同位素。 工业制硅 工业上,通常是在电炉中由碳还原二氧化硅而制得: SiO2 + 2C → Si + 2CO 半导体 半导体(英语:Semiconductor)是指一种导电性可受控制,范围可从绝缘体至导体之间的材料。常见的半导体材料有硅、锗、砷化镓等,而硅更是各种半导体材料中,在商业应用上最具有影响力的一种。 二级管 二极管(英语:Diode),是一种具有不对称电导的双电极电子元件[注 1]。理想的二极管在正向导通时两个电极(阳极和阴极)间拥有零电阻,而反向时则有无穷大电阻,即电流只允许由单一方向流过二极管。 References PN 结动画 PN 结工作原理 更多文章访问:zhoukekestar.github.io/notes