装饰器模式

定义 装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向现有对象动态添加新功能,同时又不改变其结构。装饰器通过创建一个包装对象来包裹真实对象,实现在不修改原有对象的基础上扩展功能。 装饰器模式的核心思想是:使用组合而非继承来扩展对象的功能,遵循"对扩展开放,对修改关闭"的原则。 为什么需要装饰器模式 装饰器模式要解决的核心问题是:在不修改原有对象的情况下,动态地给对象添加新功能。 问题场景 假设我们正在开发一个UI组件库,需要为UILabel添加各种视觉效果:圆角、阴影、边框、渐变背景等。而且这些效果需要能够自由组合。 方案一:使用继承(不推荐) // 基础标签 class BasicLabel: UILabel { init(text: String) { super.init(frame: .zero) self.text = text } } // 圆角标签 class RoundedLabel: BasicLabel { override init(text: String) { super.init(text: text) layer.cornerRadius = 8 clipsToBounds = true } } // 阴影标签 class ShadowLabel: BasicLabel { override init(text: String) { super.init(text: text) layer.shadowColor = UIColor.black.cgColor layer.shadowOffset = CGSize(width: 0, height: 2) layer.shadowOpacity = 0.3 } } // 边框标签 class BorderLabel: BasicLabel { override init(text: String) { super.init(text: text) layer.borderWidth = 1 layer.borderColor = UIColor.gray.cgColor } } // 如果需要圆角+阴影呢? class RoundedShadowLabel: BasicLabel { override init(text: String) { super.init(text: text) // 重复圆角代码 layer.cornerRadius = 8 clipsToBounds = true // 重复阴影代码 layer.shadowColor = UIColor.black.cgColor layer.shadowOffset = CGSize(width: 0, height: 2) layer.shadowOpacity = 0.3 } } // 如果需要圆角+边框呢? class RoundedBorderLabel: BasicLabel { /* 又要重复代码 */ } // 如果需要阴影+边框呢? class ShadowBorderLabel: BasicLabel { /* 继续重复代码 */ } // 如果需要圆角+阴影+边框呢? class RoundedShadowBorderLabel: BasicLabel { /* 所有代码都重复一遍 */ } 这种方式的问题: ...

May 2, 2026

持续集成/持续部署(CI/CD)

传统应用发布模式 开发人员:在开发环境完成代码编写,单元测试,测试通过后提交到代码仓库 运维人员:把项目部署到测试环境,供QA团队测试,测试通过后,部署生成环境 测试人员:进行测试,测试完成后通知运维部署生产环境 缺点 项目在早期就存在错误,但到最后集成的时候才发现 需要手动操作,易错率高 开发与运维需要及时沟通 有了以上缺点,那么就有了CI/CD CI/CD 持续集成(CI): 合并开发人员正在编写的所有代码 一天内进行多次合并和提交代码 从存储库或生产环境中进行构建和自动化测试,确保没有集成问题并及早发现任何问题 持续交付(CD): 可以通过将更改自动推送到发布系统来随时将软件发布到生产环境中 持续部署,并自动将更改推送到生产中 GitLab内置CI/CD 运行流水线任务 Job 在文件中可以定义一个或多个作业,每个作业具有唯一的名称,每个作业是独立执行的,每个作业至少包含一个script stages: 用于定义作业可以使用的阶段,并且是全局定义,同一个阶段的作业并行运行,不同阶段按顺序运行 only: 用分支策略来限制jobs构建 script: 项目中package中的脚本 environment: 定义此作业完成部署的环境名称 下面是每个jobs的详细变量名 Keyword Required Description script yes Runner执行的命令或脚本 image no 所使用的docker镜像,查阅使用docker镜像 services no 所使用的docker服务,查阅使用docker镜像 stage no 定义job stage(默认:test) type no stage的别名(已弃用) variables no 定义job级别的变量 only no 定义一列git分支,并为其创建job except no 定义一列git分支,不创建job tags no 定义一列tags,用来指定选择哪个Runner(同时Runner也要设置tags) allow_failure no 允许job失败。失败的job不影响commit状态 when no 定义何时开始job。可以是on_success,on_failure,always或者manual dependencies no 定义job依赖关系,这样他们就可以互相传递artifacts cache no 定义应在后续运行之间缓存的文件列表 before_script no 重写一组在作业前执行的命令 after_script no 重写一组在作业后执行的命令 environment no 定义此作业完成部署的环境名称 coverage no 定义给定作业的代码覆盖率设置 配置.gitlab-ci.yml stages: - deploy_dev - deploy_test - deploy_production deploy_dev: stage: deploy_dev environment: name: dev only: - dev script: - rsync -av . ${WORKSPACE_PATH} && cd ${WORKSPACE_PATH} - npm run build:dev - ansible-playbook ansible-deploy.yml --extra-vars "hosts=cloud_ui_test projectDir=${WORKSPACE_DIST_PATH} projectName=${PROJECT_NAME} webRootPath=/opt/devroot/" deploy_test: stage: deploy_test environment: name: test only: - master script: - rsync -av . ${WORKSPACE_PATH} && cd ${WORKSPACE_PATH} - npm run build:stage - ansible-playbook ansible-deploy.yml --extra-vars "hosts=cloud_ui_test projectDir=${WORKSPACE_DIST_PATH} projectName=${PROJECT_NAME} webRootPath=${WEB_ROOT_PATH}" deploy_production: stage: deploy_production only: - production when: manual script: - npm install - npm run build:prod - ansible-playbook ansible-deploy.yml --extra-vars "hosts=dvs-front-prod projectDir=${PROJECT_PATH} projectName=xianglin-cloud-ui/${PROJECT_NAME} webRootPath=/opt/" 上面这个.yml文件,我们首先定义了三个阶段,deploy_dev部署到dev环境,并且拉去的是dev分支代码; deploy_test部署到test环境,拉去的是master分支代码,deploy_production拉取production分支代码,执行完后需要手动操作 ...

August 7, 2025

正向代理和反向代理

1. 概念 正向代理是一个位于客户端和目标服务器之间的代理服务器(中间服务器)。为了从原始服务器取得内容,客户端向代理服务器发送一个请求,并且指定目标服务器,之后代理向目标服务器转交并且将获得的内容返回给客户端。正向代理的情况下客户端必须要进行一些特别的设置才能使用。 反向代理正好相反。对于客户端来说,反向代理就好像目标服务器。并且客户端不需要进行任何设置。客户端向反向代理发送请求,接着反向代理判断请求走向何处,并将请求转交给客户端,使得这些内容就好似他自己一样,一次客户端并不会感知到反向代理后面的服务,也因此不需要客户端做任何设置,只需要把反向代理服务器当成真正的服务器就好了。 2. 区别 正向代理需要你主动设置代理服务器ip或者域名进行访问,由设置的服务器ip或者域名去获取访问内容并返回;而反向代理不需要你做任何设置,直接访问服务器真实ip或者域名,但是服务器内部会自动根据访问内容进行跳转及内容返回,你不知道它最终访问的是哪些机器。 正向代理是代理客户端,为客户端收发请求,使真实客户端对服务器不可见;而反向代理是代理服务器端,为服务器收发请求,使真实服务器对客户端不可见。 从上面的描述也能看得出来正向代理和反向代理最关键的两点区别: 是否指定目标服务器 客户端是否要做设置 下面用一张图来表示两者的差异: 正向代理与反向代理 正向代理中,proxy和client同属一个LAN,对server透明; 反向代理中,proxy和server同属一个LAN,对client透明。 实际上proxy在两种代理中做的事都是代为收发请求和响应,不过从结构上来看正好左右互换了下,所以把前者那种代理方式叫做正向代理,后者叫做反向代理。 从用途上来区分: 正向代理:正向代理用途是为了在防火墙内的局域网提供访问internet的途径。另外还可以使用缓冲特性减少网络使用率 反向代理:反向代理的用途是将防火墙后面的服务器提供给internet用户访问。同时还可以完成诸如负载均衡等功能 从安全性来讲: 正向代理:正向代理允许客户端通过它访问任意网站并且隐蔽客户端自身,因此你必须采取安全措施来确保仅为经过授权的客户端提供服务 反向代理:对外是透明的,访问者并不知道自己访问的是代理。对访问者而言,他以为访问的就是原始服务器 3. 使用场景 正向代理的典型用途是为在防火墙内的局域网客户端提供访问Internet的途径。正向代理还可以使用缓冲特性减少网络使用率。反向代理的典型用途是将 防火墙后面的服务器提供给Internet用户访问。反向代理还可以为后端的多台服务器提供负载平衡,或为后端较慢的服务器提供缓冲服务。 3.1 正向代理 从上面的介绍也就可以猜出来正向代理的至少一个功能(俗称翻墙),也即: 用户A无法访问facebook,但是能访问服务器B,而服务器B可以访问facebook。于是用户A访问服务器B,通过服务器B去访问facebook,,服务器B收到请求后,去访问facebook,facebook把响应信息返回给服务器B,服务器B再把响应信息返回给A。这样,通过代理服务器B,就实现了翻墙。 3.2 反向代理 从上面的介绍也可以猜出来反向代理的至少一个功能(比如负载均衡),也即: 假设用户A访问 **www.somesite.com/something.html**,但 www.somesite.com 上并不存在something.html页面,于是接收用户请求的该服务器就偷偷从另外一台服务器上取回来,然后返回给用户,而用户并不知道something.html页面究竟位于哪台机器上。 反向代理的作用就比较多了,这里简单列举一下: 保护和隐藏原始资源服务器 加密和SSL加速 负载均衡 缓存静态内容 压缩 减速上传 安全 外网发布 下面做两个简单介绍 保护和隐藏原始资源服务器 代理作用 用户A始终认为它访问的是原始服务器B而不是代理服务器Z,但实用际上反向代理服务器接受用户A的应答,从原始资源服务器B中取得用户A的需求资源,然后发送给用户A。由于防火墙的作用,只允许代理服务器Z访问原始资源服务器B。尽管在这个虚拟的环境下,防火墙和反向代理的共同作用保护了原始资源服务器B,但用户A并不知情。 负载均衡 负载均衡 当反向代理服务器不止一个的时候,我们甚至可以把它们做成集群,当更多的用户访问资源服务器B的时候,让不同的代理服务器Z(x)去应答不同的用户,然后发送不同用户需要的资源。 3.3 透明代理 透明代理比较类似正向代理的功能,差别在于客户端根本不知道代理的存在,它改编你的request,并会传送真实IP(使用场景就是公司限制网络的访问)。 比如为了工作效率或者安全,A公司屏蔽了QQ软件的使用。A公司的员工接上了网络,但发现无法使用qq。这就是透明代理捣的鬼。公司在内网和外网的中间插入一个透明代理,这个代理会根据规则抓取请求内容,遇到qq的请求我就把这个请求给屏蔽掉,这样就完成了透明屏蔽。当然了,如果你明白原理,就可以自己搞个正向代理来绕过公司的屏蔽。 Nginx作为时下最流行的HTTP服务器之一,同时它是一个反向代理服务器,提到反向代理服务器,有同学可能觉得这个概念很模糊,如果说到代理,他可能明白,但是再引出一个正向代理,估计懵了,笔者将尝试用浅显易懂的比方把这两个概念解释清楚。 正向代理 A同学在大众创业、万众创新的大时代背景下开启他的创业之路,目前他遇到的最大的一个问题就是启动资金,于是他决定去找马云爸爸借钱,可想而知,最后碰一鼻子灰回来了,情急之下,他想到一个办法,找关系开后门,经过一番消息打探,原来A同学的大学老师王老师是马云的同学,于是A同学找到王老师,托王老师帮忙去马云那借500万过来,当然最后事成了。不过马云并不知道这钱是A同学借的,马云是借给王老师的,最后由王老师转交给A同学。这里的王老师在这个过程中扮演了一个非常关键的角色,就是代理,也可以说是正向代理,王老师代替A同学办这件事,这个过程中,真正借钱的人是谁,马云是不知道的,这点非常关键。 我们常说的代理也就是只正向代理,正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求,知名的科学上网工具shadowsocks 扮演的就是典型的正向代理角色。在天朝用浏览器访问 www.google.com 时,被残忍的拒绝了,于是你可以在国外搭建一台代理服务器,让代理帮我去请求google.com,代理把请求返回的相应结构再返回给我。 _ 反向代理 大家都有过这样的经历,拨打10086客服电话,可能一个地区的10086客服有几个或者几十个,你永远都不需要关心在电话那头的是哪一个,叫什么,男的,还是女的,漂亮的还是帅气的,你都不关心,你关心的是你的问题能不能得到专业的解答,你只需要拨通了10086的总机号码,电话那头总会有人会回答你,只是有时慢有时快而已。那么这里的10086总机号码就是我们说的反向代理。客户不知道真正提供服务人的是谁。 反向代理隐藏了真实的服务端,当我们请求 www.baidu.com 的时候,就像拨打10086一样,背后可能有成千上万台服务器为我们服务,但具体是哪一台,你不知道,也不需要知道,你只需要知道反向代理服务器是谁就好了,www.baidu.com 就是我们的反向代理服务器,反向代理服务器会帮我们把请求转发到真实的服务器那里去。Nginx就是性能非常好的反向代理服务器,用来做负载均衡。 ...

July 28, 2025

react 事件机制

一 前言 我们来一起探讨一下React事件原理,这篇文章,我尽量用通俗简洁的方式,把React事件系统讲的明明白白。 我们讲的react版本是16.13.1 , v17之后react对于事件系统会有相关的改版,文章后半部分会提及。 老规矩,在正式讲解react之前,我们先想想这几个问题(如果我是面试官,你会怎么回答?): 我们写的事件是绑定在dom上么,如果不是绑定在哪里? 为什么我们的事件不能绑定给组件? 为什么我们的事件手动绑定this(不是箭头函数的情况) 为什么不能用 return false 来阻止事件的默认行为? react怎么通过dom元素,找到与之对应的 fiber对象的? onClick是在冒泡阶段绑定的? 那么onClickCapture就是在事件捕获阶段绑定的吗? 必要的知识概念 在弄清楚react事件之前,有几个概念我们必须弄清楚,因为只有弄明白这几个概念,在事件触发阶段,我们才能更好的理解react处理事件本质。 我们写在JSX事件终将变成什么? 我们先写一段含有点击事件的react JSX语法,看一下它最终会变成什么样子? class Index extends React.Component{ handerClick= (value) => console.log(value) render(){ return <div> <button onClick={ this.handerClick } > 按钮点击 </button> </div> } } 经过babel转换成React.createElement形式,如下: 最终转成fiber对象形式如下: fiber对象上的memoizedProps 和 pendingProps保存了我们的事件。 什么是合成事件? 通过上一步我们看到了,我们声明事件保存的位置。但是事件有没有被真正的注册呢?我们接下来看一下: 我们看一下当前这个元素<button>上有没有绑定这个事件监听器呢? button上绑定的事件 我们可以看到 ,button上绑定了两个事件,一个是document上的事件监听器,另外一个是button,但是事件处理函数handle,并不是我们的handerClick事件,而是noop。 noop是什么呢?我们接着来看。 原来noop就指向一个空函数。 然后我们看document绑定的事件 可以看到click事件被绑定在document上了。 接下来我们再搞搞事情😂😂😂,在demo项目中加上一个input输入框,并绑定一个onChange事件。睁大眼睛看看接下来会发生什么? class Index extends React.Component{ componentDidMount(){ console.log(this) } handerClick= (value) => console.log(value) handerChange=(value) => console.log(value) render(){ return <div style={{ marginTop:'50px' }} > <button onClick={ this.handerClick } > 按钮点击 </button> <input placeholder="请输入内容" onChange={ this.handerChange } /> </div> } } 我们先看一下input dom元素上绑定的事件 ...

December 17, 2024

中间人攻击

一句话总结:中间人攻击的关键在于获取公钥。 一、我和小美传纸条 我和隔壁班的小美互相喜欢,经常互相传纸条。但我懒得动,一般让老王帮我顺路带过去,我信任老王,所以就是简单把纸对折一下就交给老王了。 二、带锁的盒子 有一天我发现,老王居然偷看我的纸条! 我很生气,于是买了一个带锁的盒子,并配了两把钥匙🔑,我留一把,另一把让老王交给小美。我心想这下子老王没钥匙,看不了我们的纸条了。 三、一个需要两把钥匙的盒子 又过了几天,我发现老王还是会偷看,为什么呢?原来这老小子当初传递钥匙的时候,自己去复刻了一把! 于是我又想了一个办法,换了一个更高级的盒子,这个盒子必须用一对钥匙才能使用,使用公钥上锁,必须使用私钥才能打开。我委托老王把我的公钥交给小美,把小美的公钥拿给我。 以后我们都用对方的公钥来进行加密,对方用自己的私钥就能解密。而且就算老王把公钥拿去复刻一把也没关系,没有私钥他就打不开箱子,完美! 四、班主任 过了几天,我发现老王还是能偷看我们的纸条,为什么呢?????? 我想了好几天终于想明白了!原来老王当初传递钥匙的时候,他把我俩的公钥自己留下了,这老小子把自己的公钥给了我们俩。他现在有手里有四把钥匙,想看什么看什么! 这次我们终于意识到问题的关键,不管什么方案,让老王传钥匙就得坏事!于是我和小美商量好,这次不通过老王,而是通过我的班主任传递公钥,终于可以愉快的聊天了! 五、网络中的加密知识 对称加密 对称加密使用同一个密钥进行加密和解密。它的优点是速度快,它的问题是密钥传播过程中一旦泄露,就功亏一篑了。 非对称加密 非对称加密使用一对密钥:公钥和私钥。公钥用于加密,私钥用于解密。它的优点是密钥分发问题得到解决,公钥可以公开传播。缺点是速度较慢,通常用于加密小数据量或密钥交换。常见做法是通过非对称加密建立链接,交换对称加密密钥,然后使用对称加密的方式进行消息传递。 证书 数字证书是由认证机构(CA)签发的,包含公钥及其持有者身份信息的电子文档。它通过 CA 的数字签名验证其真实性,确保通信双方的身份和公钥的可信性。(证书就是上面例子中的班主任,是一个可信任的获取对方公钥的机构。) 六、中间人攻击 中间人攻击的关键在于攻击者成功获取并提供可信公钥,欺骗用户使其认为其公钥是合法服务器的公钥。这可以通过伪造证书或利用受损的 CA 来实现。(用上面例子来说,就是老王把自己的公钥伪装成小美的公钥给了我) 作为普通用户防御措施很简单,当访问一个网站时,如果浏览器弹出“证书不可信”告警的时候,不要点“我信任”。 常见考点 1. 中间人攻击的基本概念 问题: 什么是中间人攻击?请用通俗的语言解释。 中间人攻击有哪些典型的攻击方式? 被动攻击(如窃听)。 主动攻击(如数据篡改)。 中间人攻击的危害有哪些? 关键点: 中间人攻击是一种攻击者拦截并篡改通信内容的攻击。 危害包括:窃取敏感数据(如密码)、篡改通信内容、伪装身份等。 2. 中间人攻击的原理 问题: 中间人攻击是如何实现的?请简要描述其工作流程。 在 HTTP 中,为什么容易发生中间人攻击? HTTPS 是否完全能防止中间人攻击?为什么? DNS 劫持和 ARP 欺骗是如何帮助实现中间人攻击的? 关键点: 工作流程: 攻击者充当通信双方之间的“中间人”。 拦截通信数据,可能进行监听或篡改。 常见技术: ARP 欺骗:伪装网关。 DNS 劫持:将域名解析到攻击者控制的服务器。 Wi-Fi 劫持:在公共网络中拦截通信数据。 3. 防御中间人攻击的技术措施 问题: HTTPS 如何防御中间人攻击?它的安全机制是什么? 什么是证书信任链?如何防止伪造的证书? 什么是 HSTS(HTTP Strict Transport Security)?它如何防御中间人攻击? 什么是双向 SSL/TLS?它如何进一步提高安全性? 如何防止公共 Wi-Fi 中的中间人攻击? 关键点: ...

December 4, 2024

布局技巧

1、普通流(常规流) 文档流(流式布局)分为 普通流、定位流、浮动流,不同流内的块级元素和行内元素的布局方式不同,布局方式用 FC(格式化上下文,含有 块级格式化上下文、内联格式化上下文、层叠格式化上下文、灵活格式化上下文等)来命名; 普通流中的布局规则默认为块级盒子从上到下,垂直排列;内联盒子从左到右,水平排列等;(书写模式(writing-mode)会对流布局产生影响; 2、position 属性有: static:默认值,元素依然按照 普通流 的定位规则定位; relative absolute sticky fixed 使用 position 会脱离常规流,成为定位流,并有其自己的定位规则; 3、float 属性有: left right none inherit 使用 float 会脱离常规流,成为浮动流,并有其自己的定位规则; 4、table 使用 HTML 标签来布局; <table> <tr> <th></th> <th></th> </tr> <tr> <td></td> <td></td> </tr> </table> 5、flex 2009 年 W3C 提供的方案,注意只有 IE 10+ 支持 flex; 重要的概念:flex 容器,flex 项目,主轴,交叉轴; 设置某个元素的属性 display: flex;,此元素就成了 flex 容器,flex 容器内的元素是 flex 项目; flex 容器的属性: flex-direction flex-wrap flex-flow justify-content align-items align-content flex 项目的属性 flex (flex-grow、flex-shrink、flex-basis) order align-self 6、grid 二维布局方式,相对于使用 HTML 结构实现的 table 布局,grid 布局都是在 CSS 中完成的; ...

October 10, 2024

插槽

一、插槽的几种使用方式 1. 普通插槽 举例来说,这里有一个 <FancyButton> 组件,可以像这样使用: <FancyButton> Click me! <!-- 插槽内容 --> </FancyButton> 而 <FancyButton> 的模板是这样的: <button class="fancy-btn"> <slot></slot> <!-- 插槽出口 --> </button> <slot> 元素是一个 插槽出口 (slot outlet),标示了父元素提供的 插槽内容 (slot content) 将在哪里被渲染。 最终渲染出的 DOM 是这样: <button class="fancy-btn">Click me!</button> 2. 具名插槽 有时在一个组件中包含多个插槽出口是很有用的。举例来说,在一个 <BaseLayout> 组件中,有如下模板: <div class="container"> <header> <!-- 标题内容放这里 --> </header> <main> <!-- 主要内容放这里 --> </main> <footer> <!-- 底部内容放这里 --> </footer> </div> 对于这种场景, <slot> 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容: ...

October 4, 2024

Generator

聊聊 for of 说起 for of 相信每个写过 JavaScript 的人都用过 for of ,平时我们用它做什么呢?大多数情况应该就是遍历数组了,当然,更多时候,我们也会用 map() 或者 filer() 来遍历一个数组。 但是就像我们标题里面说的,它跟 Generator 能扯上什么关系呢? 首先我们想一个问题,为什么使用 for of 或者 map()/filer() 方法就可以遍历一个数组 (或者类数组对象: Strings , Maps , Sets , arguments ) 呢? 为什么不能用他们来遍历一个对象呢? 你能学到什么 对 for of 更深入的理解 iterator 到底是何方神圣? 数组也是对象,为什么不能用 for of 来遍历对象呢? 如何实现对象的 for of? Generator 又是何方神圣?他有什么用呢? 类数组对象的玄机 在真正揭开谜底之前,站在 for of 的角度想一下,现在让你去遍历一个数组,你需要知道什么信息呢? 对应下标的值 是否遍历结束的标志 带着这样的思考,我们打印一个数组来看看这里面的玄机: const numbersArray = [1, 2, 3]; console.dir(numbersArray); 数组 (或者类数组对象: Strings , Maps , Sets , arguments ) 的原型中都实现了一个方法 Symbol.iterator,问题来了,那么这个 Symbol.iterator 又有什么用呢? 拿出来试一下就知道了: ...

October 2, 2024

函数与函数式编程

纵观JavaScript中所有必须需要掌握的重点知识中,函数是我们在初学的时候最容易忽视的一个知识点。在学习的过程中,可能会有很多人、很多文章告诉你面向对象很重要,原型很重要,可是却很少有人告诉你,面向对象中所有的重点难点,几乎都与函数息息相关。 包括之前几篇文章介绍的执行上下文,变量对象,闭包,this等,都是围绕函数的细节来展开。 很多人在学习中,很急切的希望自己快一点开始学习面向对象,学习模块,学习流行框架,然后迅速成为高手。但是我可以很负责的告诉你,关于函数的这些基础东西没理解到一定程度,那么你的学习进展一定是举步维艰的。 所以,大家一定要重视函数! 当然,关于函数的重点,难点在前面几篇文章都已经说得差不多了,这篇文章主要总结一下函数的基础知识,并初步学习函数式编程的思维。 一、函数声明、函数表达式、匿名函数与自执行函数 关于函数在实际开发中的应用,大体可以总结为函数声明、函数表达式、匿名函数、自执行函数。 函数声明 我们知道,JavaScript中,有两种声明方式,一个是使用var的变量声明,另一个是使用function的函数声明。 在前端基础进阶(三):变量对象详解中我有提到过,变量对象的创建过程中,函数声明比变量声明具有更为优先的执行顺序,即我们常常提到的函数声明提前。因此我们在执行上下文中,无论在什么位置声明了函数,我们都可以在同一个执行上下文中直接使用该函数。 fn(); // function function fn() { console.log('function'); } 函数表达式 与函数声明不同,函数表达式使用了var进行声明,那么我们在确认他是否可以正确使用的时候就必须依照var的规则进行判断,即变量声明。我们知道使用var进行变量声明,其实是进行了两步操作。 // 变量声明 var a = 20; // 实际执行顺序 var a = undefined; // 变量声明,初始值undefined,变量提升,提升顺序次于function声明 a = 20; // 变量赋值,该操作不会提升 同样的道理,当我们使用变量声明的方式来声明函数时,就是我们常常说的函数表达式。函数表达的提升方式与变量声明一致。 fn(); // 报错 var fn = function() { console.log('function'); } 上例子的执行顺序为: var fn = undefined; // 变量声明提升 fn(); // 执行报错 fn = function() { // 赋值操作,此时将后边函数的引用赋值给fn console.log('function'); } 因此,由于声明方式的不同,导致了函数声明与函数表达式在使用上的一些差异需要我们注意,除此之外,这两种形式的函数在使用上并无不同。 关于上面例子中,函数表达式中的赋值操作,在其他一些地方也会被经常使用,我们清楚其中的关系即可。 在构造函数中添加方法 function Person(name) { this.name = name; this.age = age; // 在构造函数内部中添加方法 this.getAge = function() { return this.age; } this. } // 给原型添加方法 Person.prototype.getName = function() { return this.name; } // 在对象中添加方法 var a = { m: 20, getM: function() { return this.m; } } 匿名函数 ...

October 1, 2024

多媒体技术

对于大多数前端工程师来说,音视频技术是一个比较少涉足的领域, 本知识点主要介绍流媒体技术中的文本、图形、图像、音频和视频多种理论知识,涉及到播放器、web媒体技术、主流框架等内容。 一、音视频基础 1.1 视频 1.1.1 基础概念 码率 单位时间内取样率越大,精度就越高,处理出来的文件就越接近原始文件。 帧率 对视频来说,帧率对应这观看卡顿。帧率越高,流畅度越高,低帧率会造成视觉卡顿。 压缩率 经过压缩后文件的大小 / 原始文件的大小 * 100% = 压缩率。编码压缩越小越好,但压得越小,解压时间 分辨率 用于度量图像内数据量多少的一个参数,和视频清晰度息息相关。 1.1.2 视频容器格式 容器格式相信大家经常见到: MP4、AVI、FLV、TS/M3U8、WebM、OGV、MOV… 1.1.2 视频编码格式 H.264 目前最流行的编码格式。 H.265 新型的编码格式,高效的视频编码。用来以替代H.264/AVC编码标准。 VP9 VP9是WebM Project开发的下一代视频编码格式 。VP9支持从低比特率压缩到高质量超高清的所有Web和移动用例,并额外支持10/12位编码和HDR AV1 AOM(Alliance for Open Media,开放媒体联盟)制定的一个开源、免版权费的视频编码格式。AV1是google制定的VP9标准的继任者,也是H265强有力的竞争者。 1.2 音频 1.2.1 基础概念 采样率 音频采样率是指录音设备在一秒钟内对声音信号的采样次数,采样频率越高声音的还原就越真实越自然。 采样大小 一秒钟所采的样本数为比特率,每个样本中信息的比特数就是位深,即采样精度,单位为Bit。 比特率 指每秒传送的比特(bit)数,又称数据信号速率。单位为比特/秒、千比特/秒或兆比特/秒。比特率越高,表示单位时间传送的数据就越多。 压缩率 原始音频数据与通过PCM等压缩编码技术压缩后的数据大小的比率 1.2.2 音频容器格式 音频格式也比较常见:WAV、AIFF、AMR、MP3、Ogg… 1.2.3 音频编码格式 PCM 脉冲编码调制(Pulse Code Modulation,PCM),PCM是数字通信的编码方式之一。 AAC-LC(MPEG AAC Low Complexity) 低复杂度编码解码器(AAC-LC — 低复杂度高级音频编码)是低比特率、优质音频 的高性能音频编码解码器。 AAC-LD (又名AAC低延迟或MPEG-4低延迟音频编码器),为电话会议和OTT服务量身打造的低延迟音频编解码器 LAC(Free Lossless Audio Codec) 免费无损音频编解码器。是一套著名的自由音频压缩编码,其特点是无损压缩。 2012年以来它已被很多软件及硬件音频产品(如CD等)所支持。 1.3 常见标签 1.3.1 <audio> 元素 1.3.1.1 基本用途 <audio> 元素用于在网页中嵌入音频内容。它可以播放音乐、播客、音效等。 ...

September 27, 2024