走向IPv6,阿里巴巴IPv6规模化部署实践
作者: IPv6项目组文章:阿里技术01 前言细心的人可能已经发现,我们在首次打开淘宝、天猫、支付宝以及国内主流APP的时候,都能看到“IPv6”的标示,这个“IPv6”意味着我国主流APP正式进入到IPv4和IPv6的双栈时代。本文将从APP及云产品的角度,和大家分享一下我们在这个过程中的经验积累,为进一步推动IPv6规模化部署提供参考。在正式介绍经验之前我们先简单介绍一下IPv6的一些基本信息。1.1 什么是IPv6?IPv6是Internet Protocol Version 6的缩写,其中Internet Protocol译为“互联网协议”。IPv6是IETF(互联网工程任务组,Internet Engineering Task Force)设计的用于替代现行版本IP协议(IPv4)的下一代IP协议,IPv6将IPv4中32位的地址长度扩展到了128位,使用IPv6,可以让全世界的每一粒沙子都能分配到一个IP地址。IPv6的设计初衷是用以解决IPv4地址枯竭问题,同时对IPv4进行大量改进,并最终取代IPv4。然而由于NAT等技术的广泛应用,IPv4在互联网流量中长期占据主要地位,IPv6的使用增长缓慢。1.2 IPv6的国家战略价值IPv6是互联网升级演进的必然趋势、网络技术创新的重要方向、网络强国建设的基础支撑。当前,全球范围内IPv6网络建设与应用创新持续加速,主要发达国家和地区均出台了IPv6发展规划和政策指导,大型网络运营商和应用服务商也在开展IPv6大规模商用部署。同时,随着5G和IoT技术的逐渐成熟,万物互联的时代即将到来,苹果AppStore审核要求全面支持IPv6等,使得市场上对IPv6的呼声不断升高。IPv6对我国数字化、支撑网络强国建设有重要的意义。这方面我国推进IPv6规模部署专家委主任邬贺铨院士对此有比较充分的论述。1.3 IPv6的社会价值在大家的印象中,IPv6可能仍然处在部署的初期,各大运营商还在进行网络改造,全面普及似乎离个人还是一件很遥远的事情。但根据国家IPv6发展监测平台的数据,目前全国IPv6互联网活跃用户总数达到了6.83亿,全国IPv6互联网活跃用户占比达到了66.18%,IPv6已经不知不觉地融入到大家的生活中。随着IPv6的规模化部署,它会给我们带来哪些变化呢?随着5G时代的到来,物联网、工业互联网、云计算、人工智能和大数据等新兴领域的快速发展,移动互联网对于IP地址的需求日益增加,IPv4已经无法满足当前网络环境。IPv6和5G是相辅相成的,它们的目标都是尽可能将更多的设备互联,实现万物互联的最终境界。结合IPv6海量公网地址的技术属性和5G高速率低延迟网络的核心优势,为万物互联网络提供质量保障。普通用户将会逐步进入全面告别内网的时代,目前运营商大部分都使用了NAT技术,这让用户在远程监控、游戏联机、P2P视频、设备公网互联等方面,体验不太稳定,也制约了这些技术的进一步发展。IPv6拥有无穷无尽的公网IP地址,从而实现真正的万物互联互通。未来我们看的优酷视频可能就是自己邻居分享的,我们使用的钉钉视频会议也会因为IPv6变的更快更清晰,我们的家用监控也因支持IPv6而变的丝滑流畅,这一切将会在IPv6全面普及后逐步实现。1.4 IPv6规模化部署过程中遇到的困难与挑战?✪ 1.4.1 超大工程的管理挑战IPv6改造涉及运营商线路和地址申请、网络/计算/存储等IT基础设施新建或升级、应用程序代码逻辑更改等事项,升级难度大、复杂度高,如何保障业务连续性?需要满足业务平滑升级、减少业务中断时间,甚至避免业务中断的核心诉求。国家推进IPv6规模部署行动提速,企业网站系统、互联网APP等需要在较短时间完成IPv6改造工作,如何在保证业务稳定性前提下,快速完成升级是不得不面临的挑战。应用软件需要进行大范围改造,同时也会带来运营商线路费用、IT 基础设施软/硬件新购或升级费用、集成服务费用等综合成本,如何提升改造效率,同时实现持续性成本可控可预期也是非常关键的点。✪ 1.4.2 新场景下的技术难题从IPv4单栈到IPv4和IPv6双栈环境,对应底层表象会增加双倍的工作,如何稳定高效的支撑业务发展,这是首先要解决的难题。双栈网络环境增加了测试的复杂度,如何建立便捷的测试工具,方便日常的测试工作同时能够及时发现异常。随着双栈的部署终端面临的环境更加复杂,如何在复杂网络环境中确保用户体验,这是规模化部署过程中必须要解决的技术难题。✪ 1.4.3 常态化运营体系建立规模化部署不是最终目标,IPv6需要从能用走向好用,需要持续运营下去。互联网技术迭代更新是非常快速的,一到两周就会进行一次版本的迭代发布,如何保证后续的迭代升级中不会影响规模化部署的进程,这需要将这部分工作融入到日常常态化的运营体系中。1.5 阿里巴巴对IPv6的整体思考和改造方案不论从政策导向、市场需求,还是聚焦于技术演进,IPv6都将开始进入规模部署阶段,正是基于此背景,阿里巴巴率先全面进入“IPv6时代”。集团在2017年成立集团IPv6项目组,主流APP均进入到IPv6规模化部署中。2018年启动阶段一:应用接入层改造。2021年启动阶段二:内网应用IPv6升级试点,探索IPv6演进的最佳实践方案。2022年启动阶段三:IPv6 Only试点。本文重点会和大家分享阿里巴巴成熟的应用接入层改造经验,后续会逐步为大家分享内网应用IPv6升级和IPv6 Only试点的改造经验。IPv6规模化部署是一个超级工程项目,更是一个技术创新项目。接下来我们将会以四个维度展开:整体规划;从云、管、端(服务端、客户端)4个层面剖析这个超级复杂工程;为克服新场景困难而打造的三个核心技术;为IPv6常态化运营打造的常态化运营体系。02整体改造方案2.1 战略规划IPv6的整体发展,我们可以将其总结为三个战略阶段:第一个战略阶段是IPv6发展的初期,更多是孤岛式的实验环境。第二个战略阶段是IPv6与IPv4共存阶段,这个阶段又分为IPv4主导和IPv6主导两个阶段,这两个阶段侧重点略有差异。IPv4主导阶段我们重点提升公网IPv6流量占比,由于涉及的面非常大,而且短时间内没有业务价值,因此需要有较强的政策牵引。到达IPv6主导阶段,企业自主驱动力会增强,端到端改造将会是重点。我们现在就处于第2个战略阶段中的IPv4主导向IPv6主导演进的过程中,因此当前比较急迫的是推进IPv6规模化部署,让IPv6流量占比实现较大的提升,进入IPv6主导阶段。第三个战略阶段是IPv6成熟阶段,我们需要逐步关停IPv4,全面进入IPv6 Only时代。复杂的问题总会让人望而生畏,将问题拆解,逐个击破是很好的规划思路。基于这三个战略阶段的划分,我们可以分阶段进行改造,隔离相互间的依赖,既能做到快速的实现公网流量的提升同时确保业务的稳定性连续性,又能分步进行创新探索。1)战略推进:有了长远的战略规划就可以更加清晰进行项目的推进,接下来我们将重点聚焦在IPv6流量提升和规模化部署部分。具体到执行阶段,我们大体上遵循“三步走”的策略,每个阶段都需要经历这三个步骤,这是确保我们业务稳定运行的重要保障。步骤一:应用试点,首先需要通过一个应用,跑通整个改造流程,打通云、管、端的依赖。步骤二:小规模上线,实操起来需要具备精细化的灰度策略,按照省份、运营商、比例的精细度,逐步上量。步骤三:需要形成可复制的解决方案,在集团内部进行大规模的推广部署。2)组织保障:为更好落地整体战略规划,集团成立了一个强有力的执行项目组,根据阿里巴巴主流的APP,成立24个子项目(PM、端、测试、开发的同学组成),再加上云产品的子项目(PM、SA和各个产品的PD开发组成),组建了阿里巴巴IPv6(基石)项目组。3)资金支撑:改造过程会涉及到硬件改造以及硬件和平台费用,同时项目组体量比较大,我们涉及到少量的运营费用,这些都需要提前做好预算规划。涉及到硬件设备及测试手机等设备,需要提前采买好相关的设备2.2 超大工程接下来我们将会进入实际改造的环节,IPv6改造是一个系统性的工程,尤其是对阿里巴巴来说,我们涉及的范围非常大。为了项目的高效推进,我们从全局纬度将其分为“云管端”三大部分,其中端的改造又分为服务端和客户端,我们把这个两个单独拆解开来,最终我们会从云,管,端-服务端、端-客户端这四个部分来进行阐述。✪ 2.2.1 云产品改造为了更好的完成公网流量提升(服务端双栈改造依赖部分不在这里讨论),我们需要全面完成企业互联网域名服务器IPv6改造,支持AAAA记录和IPv6域名解析请求,主要核心业务域名全部配置A及AAAA记录。使用阿里云HTTP DNS服务,支持移动端的IPv4/IPv6双栈及IPv6 Only环境下的域名解析能力。云产品改造会以更好的支撑APP IPv6改造为首要任务,最终达成IPv6用户数和公网流量提升的目标。可以分为三期来进行,首期是在用户访问的公网入口及安全管控处进行了IPv6的支持,包括DNS、http DNS、CDN、SLB、DDoS、WAF、ACL、IP地址库等,这些产品支持了IPv6,就意味着用户可以基于这些产品提供安全、高效的IPv6服务。接下来阿里云还将在内部网络进行双栈的支持,实现端到端的IPv6流量贯通。最后,未来的演进目标是IPv6 Only。⍟ 云解析DNS支持IPv6要使用IPv6服务,首先需要DNS支持IPv6解析。我们都知道,当用户访问互联网时,访问的请求会最先到达DNS,DNS会根据访问域名查询并返回正确的IP地址。IPv6时代,阿里云解析DNS依然作为云计算服务的入口,同时支持IPv4和IPv6双栈(即一个域名解析两个地址,一个IPv4地址,一个IPv6地址)解析,继续提供强大稳定的解析调度入口。⍟ 负载均衡SLB支持IPv6公网入口会优先支持IPv6,快速提升公网流量占比,负载均衡SLB长期以来都是关键业务系统的公网入口,它可以对多台ECS进行流量分发,提高业务系统的可用性和处理能力。负载均衡SLB支持IPv6从产品上看主要有两点:首先,采用了独立的IPv6类型SLB。独立的IPv6类型的SLB实例和IPv4的SLB实例性能和功能没有差异。用户只需要在购买SLB时选择IPv6类型,就可以快速创建一个IPv6的SLB实例。其次,IPv6类型的SLB和后端的ECS通信还采用IPv4私网地址。⍟ CDN服务CDN内容分发网络(Content Delivery Network),解决跨地域跨运营商网络性能问题,提供稳定快速的加速服务。CDN是互联网的流量入口,阿里云CDN团队从2017年起就启动了IPv6的改造,积极持续地在IPv6改造中投入相关资源,从节点部署、节点网络架构改造、IPv6功能支持、IPv6调度能力完善、IPv6全链路监控方案、IPv6质量评测体系建设等方面持续打磨,投入物理资源、人力资源,推动IPv6整体能力不断向IPv4靠近。截止到目前累计完成了近千个节点的IPv6改造升级,IPv6合规率超过90%,超过工信部的验收标准,在产品化上,基础的CDN产品和全站加速产品率先通过由全球IPv6 Forum论坛发布的IPv6 Enabled CDN。阿里云CDN将各地域各运营商已经改造完成的IPv6 CDN节点加入到调度域,用户在阿里云控制台开启IPv6功能,并配置图片域名的灰度比例。可以从流量入口上控制IPv6的灰度总量,确保不会出现IPv6资源不足或者无资源可调度的问题。对于302调度域名,跳转时会有IPv6地址缩写的问题,浏览器访问时会自动用缩写后的IPv6地址跳转,APP内使用libcurl等网络库时,并不会触发自动缩写,以获取到的IPv6地址原样进行请求。需要302节点配置缩写前和缩写后两套IPv6的VIP,确保任何场景下都可以跳转。在HTTPS的VIP证书中,需要加签IPv6的VIP,确保https下也可以正常跳转。对于免流域名,免流调度域需要分配IPv6的VIP组,将需要向运营商报备的免流节点IP都划入进去,并且具备IPv6功能的开启和关闭能力,确保在运营商报备完成前不启用IPv6节点,运营商报务完成后又能及时启用节点,避免出现免流失效或者免流调度域水位过高的问题。⍟ WAF服务当用户以IPv6进行通信时,还有一个关键的云服务即Web应用防火墙,它负责对网站或者APP的业务流量进行恶意特征识别及防护,将正常、安全的流量回源到服务器。当我们运行IPv6协议时,必须升级到对IPv6/IPv4双栈的防护能力,避免网站服务器被恶意入侵,保障业务的核心数据安全,解决因恶意攻击导致的服务器性能异常问题。阿里云WAF已经支持一键升级IPv6功能,在WAF控制台的被防护域名下,直接打开IPv6状态开关即可。⍟ DDoS服务同样重要的还有阿里云DDoS防护服务,它是以阿里云覆盖全球的DDoS防护网络为基础,结合阿里巴巴自研的DDoS攻击检测和智能防护体系,向用户提供可管理的DDoS防护服务。同样需要增加IPv6的防护能力,自动快速地缓解网络攻击对业务造成的延迟增加、访问受限、业务中断等影响,从而减少业务损失,降低潜在DDoS攻击风险。如需对IPv6地址进行DDoS防护,可以开通DDoS防护包中的IPv6协议,防护对象设置为IPv6转换服务的地址。⍟ ACL黑白名单及安全策略确保IPv6安全体系防护的完整性与高效性,对防火墙、入侵检测、行为审计、流量清洗等网络安全设备,进行统一升级以支持IPv6环境下的正常工作。随着IPv6的发展,IPv6的地址数量将远远超过IPv4,现有的ACL黑白名单容量将无法满足,需要提前进行扩容改造。对于IPv6安全防护能力存在风险的节点,应进行网络安全设备升级或替换。从应用业务层面以及安全管理层面进行IPv6安全策略制定与配置,保证IPv6的安全策略包含了所有的IPv4策略。⍟ 其他云产品支持IPv6除了上面提到产品外,还有很多其它产品也已经完成了支持或即将支持,如对象存储OSS、数据库RDS、DTS、API网关等。✪ 2.2.2 管道改造接下来我们将进入另外一个重点改造的场景即“管道改造”,对于阿里来说我们重点需要完成IDC网络和IT网络的全面改造。全面完成集团级数据中心核心网络、互联网出口IPv6网络改造。1)改造步骤首先为了更好的支持云产品改造,实现Internet提供IPv6的域名解析、负载均衡及安全服务服务,核心网、安全清洗、MC、BSW、LSW优先支持,然后是ANAT、XGW的4to6的地址翻译以及LVS、CDN。然后对IDC集群内部的DSW、PSW、ASW、NC进行改造,支持ECS、Docker服务。最后开始进行IPv6 Only尝试,全面进入IPv6时代。2)方案介绍核心网支持双栈,主要有两种技术方向选择:物理机双栈和MPLS 6PE/6vPE。物理机双栈是在现有的IPv4路由器和链路上,使能IPv6和路由协议,IPv6报文和IPv4报文一样,直接封装在链路层上转发;MPLS 6PE/6vPE则是保持现有的MPLS网络不变,在PE上将IPv6报文封装到MPLS中进行转发。这种方式可以在PE设备按需变更支持。阿里核心网已经开启MPLS功能,后续整体演进方向是全面MPLS化,P结点设备成为BGP-free Core以大幅减少对设备的功能要求和降低功耗,这对于节省Capex和Opex成本都有很大帮助。基于阿里核心网的实际情况以及后续整体演进方向,在IPv6的演进方案上,我们选择6PE/6vPE的方案,通过升级PE路由器来支持双栈,不需要对现有核心网P路由器做更改。只需PE设备使能IPv6,P设备不需要使能IPv6,整体实施成本低。IPv6作为MPLS的一个新业务承载,对现有运营维护冲击小,IPv6维护范围只在PE,充分发挥现有的高效运维体系能力。AZ内则主推物理机双栈,两种技术结合实现高效推进。阿里巴巴办公网改造办公网的改造也是一个复杂的过程,不仅涉及到改造的成本,同时也需要投入大量的人力,因此这是一个比较漫长的过程。为了更好的满足业务需求,我们也分为两个步骤来实现。步骤一:两个过渡方案,先通过点状部署,满足大家的测试需求。然后通过改造我们的Vpn客户端和网关,通过拨入Vpn获取IPv6地址,满足开发测试需求。步骤二:在经历一年的测试准备后,2022年初我们在全国21个园区开始逐步进行IPv6改造,预计在2023年3月底完成国内主要园区的双栈改造升级。在全国核心城市部署园区网汇聚POP节点,该汇聚点下联城市各园区核心出口设备,上联阿里云对应region接入点,实现园区网所需资源在云上快速拉起部署,借助云上资源IPv6能力快速组建新一代园区网双栈运维系统,如DNSv6服务、DHCPv6服务、双栈堡垒机等,免去线下自行搭建付出的巨大人力和资金成本。✪ 2.2.3 应用端改造我们组织集团20多款APP同步进行IPv6双栈改造,改造内容更加聚焦,效率也会高很多。下面我们先介绍一下服务端改造工程。⍟ 新建应用改造1)环境准备Web容器环境:选用支持IPv6协议的最新版本Tengine,安装toa模块,支持透传IPv6头信息至应用服务。开发环境及OS系统:应用的开发编译选用支持IPv6协议的操作系统,如Windows server 2003以上版本、MAC OS 10以后,以及Linux系统的CentOS 7或者Alios 7U等。测试环境:办公网环境与测试机房通过IPv6专线打通,办公网提供IPv6的无线/有线接入点,同时保留了原来的IPv4接入点。开发同学可以接入IPv6接入点进行日常开发和办公事务处理,需要访问不支持IPv6的公网服务时,可以切换到IPv4网络环境。也可以通过Vpn拨入IPv6,进行IPv6测试。2)业务应用改造场景1-IP地址库:使用IP地址库服务对用户归属地进行定位判断处理的,需要升级到最新版本的IP地址库数据服务,并具备定期升级能力。确保IPv6地域归属判断的准确性。场景2-IP地址格式统一:因为IPv6地址可以略写的原因,导致直接按字符串进行判断的话,会把有略写和无略写的IPv6判断成不是一个IP地址,从而导致业务处理出现偏差。同时,部分浏览器请求会自动略写IPv6地址,JAVA网络包、CURL等,不会主动略写IPv6地址,这会增长服务端处理的复杂性,所以需要前置一个公共处理,将所有的IPv6地址都经过公共处理进行标准化,统一业务处理逻辑,减少业务间不一致问题。场景3-IP地址保存:一般服务端日志落库,用户信息落库等常规处理中,会把用户IP保存到数据库等存储中,对于严格限制数据类型和长度的数据库,需要依据存储型号进行定义,原来的IPv4只需要32位字符串或者长整型数据就可以保存,而IPv6需要扩大到128位,同时长整型也存不下,需要高64位和低64位拆分存储等方式进行处理。场景4-接口传递:某些使用http get方式将多个用户IP通过参数形式传递时,需要注意get的1024B的上限,原来传递IPv4时10个用户一起传递没有问题,但现在改用IPv6时10个用户的IP长度就会超过get上限,需要改用post或者调低上限。场景5-存在IP地址的hardcopy:不能把上下游调用接口的IP地址直接写在代码中或者配置文件中,因为上下游完成IPv6改造的时间并不完全一致,上线时间也不一致,会导致出现线上故障。需要全部改为域名方式调用。场景6-客户端IP出口地址的取得方式:在双栈环境中,同一请求,只能从请求头部中获取到IPv4/IPv6地址中的一个,不可能两个都获取。如果希望同时获取IPv4或者IPv6地址,那么只能选择重复请求,或者是通过参数将客户端地址传上来。需要扩展请求字段,将IPv4/IPv6分成两个字段提交,同时服务端也需要做接收改造处理。场景7-日志分析逻辑:众所周知,为了方便日志分析和拆解,所有的业务日志都会定义一个统一的格式,日志输出的各个字段之间统一使用“||”等分隔符分隔或者按字段长度固定。使用分隔符的,需要考虑到不能再使用了,因为IPv6中带了这个符号。使用固定长度分隔的,也需要考虑到对IP地址不能再固定32位长度了,要调整到128位,同时要向下兼容IPv4,IPv4也要补齐到128位。3)依赖改造第三方库的更新:选用支持IPv6协议的第三方SDK版本,如果三方库不再更新支持IPv6,那就需要寻找置换方案。安全部署环境:接入层安全控件、限流插件、ACL白名单插件等应用安全服务应支持IPv6协议。4)降级能力构建:需要从业务逻辑上原生考虑IPv6网络不可用时,如何降级至IPv4来继续提供服务。⍟ 存量应用改造1)环境改造接入层改造:制定IPv6升级计划,申请IPv6的VIP,将负载均衡的RS指向新的IPv6接入层,确保IPv4与IPv6的流量隔离。升级开发环境及OS系统:应用的开发编译选用支持IPv6协议的操作系统,如Windows server 2003以上版本、MAC OS 10以后,以及Linux系统的CentOS 7或者Alios 7U等。替换Web容器环境:升级最新版本Tengine,升级最新toa模块,支持透传IPv6头信息至应用服务。基础镜像升级:原业务使用过旧,版本过低的OS镜像打包时,需要升级到最新支持IPv6的基础镜像,并用最新的OS进行编辑打包。2)业务应用、依赖改造及降级能力构建参考上述新建应用的改造、依赖改造及降级能力构建即可,对存量业务逻辑进行排查,存在上述场景的需要进行业务代码的重构,业务逻辑的修改,使存量业务满足IPv6环境下运行需求。⍟ 回归测试存量应用一般都经过严格的IPv4环境下测试,但不能保证在IPv6下一定是没有问题。所以需要在IPv6的测试环境下,对存量应用以及新建应用进行回归测试。包括对所有有改动功能点的全量测试,以及没有改动但属于核心功能点的覆盖性测试。⍟ 验收环节进行线上灰度验收,按照国家网站/应用IPv6升级改造验收督查指标相关要求进行测试验证,在域名IPv6支持度、页面IPv6可达性、业务IPv6支持度、应用服务质量、IPv6安全防护等方面开展测试。✪ 2.2.4 客户端改造接下来我们将会进入客户端功能改造部分,客户端有分为手机、电脑等场景,全部需要支持IPv4/IPv6双栈及IPv6-only环境下的网络通信能力。IPv6双栈环境测试IPv6流量占比不低于80%,并逐步引导IPv6-only模式。⍟ Windows/mac端IKU应用接入HTTPDNS服务,在客户端集成HTTPDNS服务SDK包,IKU应用具备手机移动客户端相同的IPv6引流灰度能力。替换IKU应用端中的网络包,具备IPv6的网络通信能力。开发在IPv6弱网条件下的降级能力,在用户可以接受的延迟时间内,切换到IPv4通信,保障用户体验无损。逐步关闭低OS版本的使用许可,关闭低版本应用的上线使用,推动用户端进行OS或者设备的升级,提高IPv6使用率。⍟ 安卓/iPhone/iPad 手机客户端这块占比最大,同时用户设备类型也是最丰富的,需要通过不断的版本更新,来降低旧版本的占有率,达到提升IPv6使用率的目标。终端IPv6支持度评估:将安装优酷APP的终端按照机型,OS版本,性能, IPv6下通信能力分类。对新上市机型进行逐台验证,分别进行IPv6支持度评估。客户端APP基础套件升级:基础网络包NetworkSDK等集团二方包进行升级,实现支持IPv6的协议栈解析以及基础降级能力。使用第三方网络库的,例如:libcurl,需要升级到最新版本,同时通过业务逻辑弥补上缺失的自动降级能力。升级IP地址:部分APP中集成有小型的IP地址库,因为数据包大小的问题,基本上小型IP库都没有包含IPv6的数据。需要重新评估IP库数据包大小与APP整体包大小的关系,如果集成支持IPv6 IP库数据包过大的话,那需要通过服务端判断的方式来替代本地的IP库。端侧埋点服务的改造:埋点的正常上报,是整体评估IPv6下业务可用性和用户体验一致性保障的前提。特别是在弱网、断网、降级回落的情况下,数据可以正常上报。IPv6 下埋点是否正确检测出网络环境的变化,网络切换导致的RT变化等,需要根据业务逻辑和用户操作场景,进行埋点的改造。2.3 创新技术通过前面的介绍,相信大家已经对四个板块(云,管道,服务端,客户端)的庞大改造工程有所了解。如前面所说,作为一个创新技术项目,接下来重点介绍三个关键的创新技术方向,在应用的双栈改造过程中对我们流量的提升和服务质量保障起到了至关重要的作用。首先我们会介绍一下核心的全链路双栈技术,这里重点从核心网、硬件和系统三个大的技术方向中选出AliBGP、双栈智能网卡及Linux内核三个点做一个简单的介绍,然后会介绍保障测试效率的TMQ-Monkey技术,最后会介绍提升用户体验的EQM及T-happyeyeballs技术。✪ 2.3.1 关键双栈技术⍟ 负载均衡网关支持IPv4、IPv6双栈平滑演进双栈支持,在业务向IPv6迁移升级过程中,做到无缝切换,有效降低用户使用网络的复杂度。通过DNAT vxlan封装或者FNAT携带TCP option,后端内核模块获取访问SLB的IPv6客户端源地址。 负载均衡网关支持IPv4、IPv6双栈后端平滑演进的技术IPv6的改造难度大、周期长,一般涉及从原有IPv4服务平滑过渡到IPv6。阿里云负载均衡网关通过支持前后端IPv4/IPv6双栈session技术提供NAT64和NAT66 session以及IPv4/IPv6后端混挂能力,满足用户平滑演进的需求:初期可通过负载均衡NAT64能力的过渡方案,IPv4后端在不改造或微改造的情况下可通过IPv6负载均衡快速对外提供IPv6服务;后期通过支持挂载IPv4、IPv6混布能力的IPv6 SLB,逐步切换到IPv6后端服务,再逐步减少IPv4后端比例,增加IPv6后端比例,最终切换到纯IPv6。获取客户端IPv6源地址在原有IPv4场景中,后端服务一般有需要获取到客户端IPv4源地址需求。IPv4到IPv6的改造过程中,获取客户端IPv6源地址的需求也是显而易见的。在NAT64场景下,由于源地址被转换为IPv4地址,原始IPv6源地址信息只能考虑通过其他方式携带给后端,阿里云SLB通过自定义TCP option方式中携带IPv6地址信息的方式携带源地址给后端,后端通过特定内核模块解析tcp option并保存在sock中,用户调用getpeername来获取IPv6源地址。⍟ AliBGP大规模IPv6路由控制方案IPv6网络构建是IPv6商业部署的基础设施。而IPv6网络构建中的重要一环是IPv6路由系统的搭建,其主要包括路由管控和路由快速收敛两部分。在IPv6网络中,发布什么路由,学习什么路由,以及在网络抖动时快速收敛,是IPv6网络稳定可用的重要课题。在IPv4网络中,C段的大小是8bit,即256个IP地址;而在IPv6网络中,C段的大小是64bit,最大IP地址数可大2的64次方。在网络中,除了正常转发数据用到的路由,还需要发布防攻击的黑洞路由,容灾用到的不同长度不同优先级的网段和明细路由。为此,单设备中学习和发布的IPv6路由条目数可达10K甚至100K级别,再叠加原先IPv4的32K路由,整网路由收敛能力需要有数倍增加。同时需要对IPv6路由进行更细致的管理,以避免因误发布或者误学习路由造成网络数据包的丢失。传统IPv6网络实际部署业务规模不大,网络路由量也不大,传统的商用交换机的路由学习和收敛能力较低,性能大致为1.4K条目每秒,不能适应阿里业务网络路由量的需求。AliBGP广泛应用于高性能转发网关、自研交换机、网络业务服务器平台之上。高性能优化,路由收敛性能达到10K条目每秒。实现了大规模快速收敛、故障范围控制、快速倒换等方面的能力。可靠性方面,支持添加路由发布和学习总条目数控制、指定设备路由平滑隔离和恢复、IPv6路由热重启、远程控制API等功能,改善了IPv6路由精细化管理能力。高性能:ALIBGP采用开源的FRR路由软件,并在上面做了大量的功能开发和性能提升开发。实现了用户态IPv6 BGP协议,并引入了IPv4 BGP协议中的Dynamic Update Peer-Groups和Next-Hop tracking两大特性功能,实现以Group方式的发布和学习路由条目,在路由震荡时跟踪next-hop状态刷新路由下一跳;同时特别优化了IPv6 ECMP路由与内核的交互过程。这些优化极大提升了IPv6路由学习和发布效率,以及收敛速度。高可靠:强大的路由控制能力,IPv6的route-map的配置渲染能力,能根据用户规则,精细控制每个路由条目的学习和发布。高性能IPv4/IPv6双栈智能网卡双栈支持,在业务向IPv6迁移升级过程中,做到无缝切换,有效降低用户使用网络的复杂度。后端支持IPv4、IPv6混合部署IPv6的改造是一个复杂的系统工程,既要保障用户现有IT投资,又要紧跟网络发展趋势,从实际来看,IPv4/IPv6双栈将长期存在。短期来看,IPv6转换服务会是一个经济适用的过渡方案;但长期来看,无缝支持IPv4/IPv6双栈将是终极解决方案。基于此,我们在AVS转发阶段即考虑完全兼容IPv4/IPv6双栈,从底层有效解决IPv4、IPv6混布的问题。IPv4/IPv6双栈全面支持TCP、UDP、GRE、ICMP等各种报文类型,通过自动识别报文版本号、报文类型,提取报文五元组等关键信息实现报文自动转发。FPGA硬件加速,极大提升网络性能通过使用自研FPGA板卡进行AVS硬件加速,将AVS转发路径下沉硬件,自主定制开发IPv4/IPv6转发引擎,实现双栈无差别加速,极大提升IPv4/IPv6转发速率。单机性能从软转发的5Mpps(4core),提升至30Mpps;端到端单向延时从60us,降低至25us,达到业界领先水平。既节省了主机CPU,又提高了报文处理的效率,有效降低了IPv6改造过程中的巨大经济成本。自定义会话压缩技术,支持千万级别会话流表通过使用外挂ddr,将流表转发信息转移到外部存储。同时,针对IPv6转发信息位宽大的特点,采用自定义数据压缩技术,在有限空间内,无缝存储IPv4/IPv6转发信息,极大提高了流的会话数量。session数从内存模式的几兆提升到几十兆,将单机会话数量提高了一个数量级。为防止外部存储数据错误,自行开发了一套接口信号检错、纠错机制,有效保证整张卡的接口稳定性。⍟ Linux内核优化1)IPv6内核协议栈bugfixIPv6 路由cache优化当核心网和物理机网卡已经高效支持IPv6后,接下来我们还剩下一个非常关键的优化点,那就是操作系统。我们早期在试点IPv6应用的过程中发现IPv6流量会引起的大规模CPU软中断飙高以及宕机现象,所有的IPv6请求受到了影响,而且会有宕机风险,这也会对同一台机器上的IPv4服务产生影响。因此,Linux内核的优化就是摆在我们内核工程师面前重点方向。通过分析发现主要原因是gc拥堵,造成了同一个TCP连接会在tcp_v6_rcv函数里的socket spinlock那里spin,而不同连接则会在fib6_run_gc函数里的gc spinlock那里spin 。为什么IPv4没有问题而IPv6就会有问题呢?IPv4地址大概有43亿个,去掉广播、组播、私有地址等,系统需要路由处理的地址完全是在哈希、树这种传统数据结构的处理能力之内。但IPv6最大的特点就是地址数多了很多,这意味着处理逻辑会变的更加复杂。经过我们工程师的测试验证,最终选择关闭IPv6的路由cache,看起来只是通过修改配置就能解决,实际上由于IPv6的所有路由项都在同一棵树里,所以拆分起来还是有难度的。IPv6 代码优化 IPv6 sysctl的数据指针是一对一设置的,不利于维护,尤其是使用 kconfig。我们通过补订实现了简化处理,达到了和IPv4一样的效果。IPv6协议栈传输机制优化IPv6 mtu优化,经过长达一年多的大范围持续验证,最终拟定了IPv6 MTU1450,在阿里CDN的场景下,最大化的兼顾IPv6传输性能和业务可用性。拥塞控制和丢包恢复算法的优化。拥塞控制是协议栈心脏,控制着协议栈的功能启停;丢包恢复系统是免疫系统,负责异常修复,CDN设计了CUPLUS和PBR两大核心算法保障IPv6协议栈的内容分发效率。IPv6协议栈数据体系建设在IPv6上量规模化过程中,为了建立对IPv6网络的基础认知、优化IPv6传输机制和监控IPv6服务的稳定性,我们构建了IPv6协议栈的网络数据中心,数据中心设计了两个大板块的功能:① 网络链路质量监控:监控IPv6的网络链路质量与IPv4的差异性,实现了重传率(字节和报文个数两个维度统计)、TCP连接建连时间、响应时间(服务器视角)、平滑RTT、最小RTT、RTT方差和IPv6总传输流量等维度统计;② 业务质量监控:监控IPv6协议栈提供的业务质量与IPv4的差异性,实现了请求失败率千分比、平均请求大小、下载速度三个维度统计。产品化能力支持了TCP连接粒度和域名维度的数据监控能力。✪ 2.3.2 TMQ-Monkey技术Android系统原生支持Monkey能力,通过adb命令即可触发monkey操作,从而对指定应用进行随机的点击操作。为了更好的提升集团APP的IPv6浓度占比,我们采用Android原生Monkey能力进行测试,经过使用后,发现原生Monkey能力存在一些痛点,无法满足我们建设自动化测试平台的需求:点击效率低:由于原生Monkey完全基于屏幕大小生成随机行为,对页面内容和元素不感知,仅通过快速生成随机坐标进行点击,较多的点击行为是无效的。容易陷入相同页面:原生Monkey对页面内容和路径没有记录,完全幂等的行为,导致在相同业务如果退出的出口较少情况下,无法退出业务页面,长期位于同一页面,效率低下。策略较少:原生Monkey完全依赖随机策略,不支持针对情况调整页面策略,对于部分场景支持度不高。不支持自定义能力:对于业务需求的页面不支持重点的定制化。以上痛点导致IPv6自动化项目中,原生Monkey能力跑出数据量不足,流量不达标且跑到的页面量不足,IPv6流量测试效果不佳。为了解决这部分问题,与淘宝TMQ团队,通过结合openatx的UI自动化和优化过的Android Monkey能力来支持IPv6自动化测试能力。在保留Android底层直接驱动的快速效果的同时,也能利用UI自动化能力支持自定义能力。TMQ-Monkey提供了独立的Python SDK,封装依赖的Monkey驱动能力和自动化调度能力,支持一键部署和运行,实现IPv6项目低成本的接入和维护。⍟ TMQ-Monkey系统技术结构TMQ-Monkey基于Python封装各类依赖和管理Monkey运行任务,主要实现能力如下:生命周期管理:对于每次执行的Monkey任务,及时在服务端创建相关信息用于任务记录,并管理Monkey运行期间的设备运行、设备状态、插件运行等相关行为,统筹整个运行期间的设备行为。自动化设备接口:基于openatx提供Android和iOS的设备接口,用于处理monkey运行前的前期准备和运行期间的自定义行为。依赖管理:处理Monkey驱动层相关的依赖下发和配置。配置管理:通过配置管理中心,对各类运行参数、插件信息、运行数据等内容进行管理。Monkey驱动层:主要用于基于Android Monkey的底层驱动能力。事件管理机制:Monkey运行期间的整个驱动层的事件管理,通过事件生成器生成的随机事件,对设备进行运行,并处理各类异常情况。事件生成器:通过策略中心、配置中心、异常识别结合生成最终Monkey事件,从而驱动Monkey运行。策略中心:按照用户输入的策略选择,依据策略生成相应的事件生成方案。配置中心:处理用户定义的黑白名单和下发的各类场景处理的配置,用于优化Monkey执行效果。异常识别能力:有效对运行期间各类异常状态:Crash、假死、跳出等场景进行识别,并处理。插件中心:执行运行期间的其他运行能力。数据收集器:对Monkey运行期间的页面信息进行收集,上报服务端,用于数据统计和后续策略优化。自动跳转插件:集团部分支持自动跳转能力的应用,可以通过用户下发常见页面,支持直接跳转后运行Monkey,有效提高Monkey的效果。运行数据监控:有效对运行期间的SDK本身的运行情况进行监控,将异常和错误及时记录并上报。✪ 2.3.3 EQM与T-happyeyeballs另一个影响规模化部署的因素就是用户体验保障技术,面临着IPv6基础生态发展的不平衡性带来复杂性挑战,以及复杂生产环境中IPv6的大规模应用挑战。双栈覆盖(不同运营商、不同省份)发展不平衡公网IPv6质量参差不齐移动设备及网络环境的复杂性⍟ 故障自动发现体系建设从IP调度的角度,任何一个IP对某一个端侧设备是好还是坏,主要看以下两个核心指标:连接成功率越高越好请求耗时越低越好通过这两个指标,结合以下多维度观测,理论上我们可以形成一个智能的调度平台,该平台可以自动发现问题并及时自动最优化调度:图:故障监控维度覆盖事实上,我们的“反馈监控—>故障发现—>自动调度”这个过程演进,经过了两个阶段:第一阶段是故障自动发现告警,人工及时干预调度;第二阶段是故障自动发现并实时自动调度。在第一阶段,因为人工调度无法关注粒度太细的维度,我们主要关注以下三个维度的指标:地理位置(精确到地市)、运营商、平台类型,一旦实时统计发现某个IP在上述三个维度上有明显“偏差”(即从成功率与耗时两个指标上,与IPv4相比劣化明显),立即给运维人员发送警告短信和邮件,运营人员及时在发现故障的维度上把该IP给摘除,控制故障扩散,收敛故障影响。在第二阶段,因机器可实现高频细粒度调度,我们通过基于拨测记录与业务结果反馈形成的数据,通过智能学习,在发现某个IP在某个维度上有明显“偏差”后,自动实现在故障的维度上把该IP给摘除,控制故障扩散,收敛故障影响。同时,相比人工告警的方式的,自动调度情况下系统关注维度的粒度与广度要高得多。图:大规模智能调度示意图前置网络质量探测当前业界并无可供直接参考的无线IPv6质量数据,在无参考数据的情况下,应用侧直接灰度放量,样本质量波动大、噪点数据多,放量决策价值不高。在业务正式上线前,必须对整个网络有端到端的全景式观测,为业务放量、调度做到有效可靠支撑。针对现有基础网络IPv6发展不平衡的问题,设计了主动式大规模网络质量拨测系统。通过调度大规模端侧设备对指定IPv6地址提前探测,实现对基础网络IPv6质量的提前测量;通过在大规模移动端实现ping6、traceroute6等拨测指令,满足对网络的细粒度全景测量。在该体系基础上,通过对不同省份、不同运营商、不同设备类型、不同无线环境多维度分析,实现基于用户侧的无线端到端大数据全景测量。其中,拨测指令包含以下内容:指令作用反馈数据PING6探测IPv6地址可达性;探测路径MTUHOP, RTT, MTUTRACE6发现并优化整个服务链路上的路由问题每一跳IP与耗时CONNECT探测TCP连接连通性成功与耗时/错误码GET探测业务可服务性HTTP 状态码与耗时/错误码构建全球首个大规模无线IPv6监控体系,同时为国内运营商提供了可靠的问题发现与度量系统,进一步推动了国内IPv6演进速度。在应用侧为业务安全定向灰度放量提供可靠决策依据。移动端业务EQM(端到端质量检测)前置探测并不能代表实际业务质量,因规模大、影响面广,业务运行期实时监控也是必不可少的环节。且实际运维中,故障的发现与原因排查,后续网络部署的优化与演进,均需要有效数据支撑。PC端业务EQM(端到端质量检测)当前PC端的性能监控方案主要是基于js采集浏览器performance.timing接口数据上报汇总计算实现的,因为浏览器并没有对js暴露当前是基于v4还是v6建立的连接,所以单纯通过js无法实现区分v4和v6的性能监控。我们制定了一个通过后端应用tengine配合增加一个不影响页面展示v6访问特征,js通过判断特征实现区分v4和v6访问,并且为v6设置单独的采样率,最终生成v6独立的性能报表。⍟ 大规模精准化实时IP调度应用针对现有公网DNS在IPv6流量调度风险不可控问题,设计了私域大规模精细化实时IP调度体系。通过对单个设备直接下发IP来满足风险控制需求;通过直接对端侧IP识别实现精准调度需求。在该体系基础上,通过单个设备的多维度信息识别,可在地域、运营商、终端类型等多个维度实现细粒度精准调度。并在发现IPv6业务故障后,云上大数据分析服务,通过端侧实时反馈,快速感知问题区域,实现流量的智能调度与恢复。我们通过高频业务的旁路指令,调度指令下发由拉变推,实现基于“探针模式”的快速调度。应用侧从原来的基于Local-DNS的间接调度升级为基于端侧设备直接调度,调度精度与时效大幅上升。产业侧为IPv6在应用上大规模落地扫清了障碍,极大加快了整个IPv6的发展进程。T-happyeyeballs基于移动应用的多IP&多协议无损切换技术的应用针对移动网络环境的复杂性及低可靠性,开发了移动应用的多IP&多协议无损切换技术。通过多IP并行建连技术,满足端侧在IPv6下快速Fall-back需求。基于该技术,端侧具备快速的黑洞逃逸能力,能够在不依赖调度体系情况下快速的自愈。同时根据拨测与业务反馈上报到控制中心调整判断参数(比如某些型号、版本、某网络差的城市),依据综合指标进行不同滑落惩罚的时长。在手机淘宝应用中,IPv6失败率在iOS/Android双端分别只有0.4%+/0.6%+。支撑集团IPv6活跃设备从0至全量过程中0故障。在我们内部实验过程中,还进一步使用了多网卡切换技术。通过在Wi-Fi网络下强制数据连接到蜂窝网的能力,实现了在局部固网IPv6故障下用户新的无感迁移数据通道能力。⍟ 应用体验保障实践效果(IPv6体验指标体系建立)经过多年沉淀,我们面向海量移动终端与云的IPv6商用部署与规模化应用,应对移动网络环境复杂性时不断增强IPv6 inside的终端网络架构及技术能力,支撑了IPv6端到端规模化创新应用,持续沉淀领域数据、引领IPv6体验&质量标准。通过打造终端网络诊断工具、体系化数据平台,实现了IPv6的高质量体验。线上应用最新数据表明,淘宝的IPv6应用,在基础网络指标上已经全面优于IPv4。当然,用户面临的网络环境愈加复杂,例如家庭AP设备驳杂,地域/小运营商/城域网v6接入配置标准和启用程度参差不齐,会不断产生新的未知体验问题,这些都将是影响IPv6体验的不确定因素,我们将持续创新实践,确保IPv6体验质量不断破障。2.4 运营体系讲完了庞大的改造工程以及创新的技术亮点,我们技术分享又步入了另外一个重点的领域。IPv6改造是一个长期的事情,需要有一套完善的机制确保其一直有效的运营下去。所以我们还需要重点介绍一下我们的流程体系,我们需要建立一套常态化的IPv6运营方案,IPv6的技术已经纳入到阿里巴巴的技术文化中,不断的自我演进和迭代。✪ 2.4.1 新增云产品及APP url管控体系建设项目组在推进IPv6改造的过程中,业务也在快速的发展,会不停的有新增的url和云产品上线,上线之后改造的成本也会增加很多,为了更加高效的解决这个问题,我们会对新增云产品进行IPv6评审,为新增url上线时默认启动双栈。✪ 2.4.2 版本发布测试体系阿里的APP发版涉及到众多团队,原来已经完成改造的url,可能会因为一次发布,而导致不支持IPv6的情况发生。为了更好的做到IPv6流量占比稳步提升,我们将IPv6流量占比测试纳入到日常发布测试流程体系中,这样在新版本上线之前就可以通过测试及时发现异常,然后更好的修复。✪ 2.4.3 监控测试体系主要分为两个大的方向,即全局监控和体验监控。全局监控重点检测IPv6占比监控和异常告警、降级率监控和异常告警,然后对异常情况进行及时的分析和解决。体验监控重点关注IPv6耗时监控和告警、长尾数据分析以及用户舆情监控。✪ 故障管理体系我们把IPv6流量占比异常下跌纳入到整体故障管理体系中,从故障前的故障等级定义、监控体系,到故障中的应急响应,再到故障后的复盘、演练。每次异常下跌都会明确原因,责任到人,持续改进,最终实现稳步提升。✪ 奖励体系我们通过建立阿里巴巴IPv6“基石”奖,鼓励各APP及云产品进行规模化部署,同时鼓励项目组成员在IPv6技术创新及大规模应用上面做出贡献。03未来计划今年我们同步启动了IPv6 Only的试点应用,为第三阶段IPv6成熟阶段做好技术储备。接下来我们还将尝试打造在用户上云的过程中同步实现双栈改造,这样会对企业的IPv6改造效率实现极大的提升。面向未来的持续创新P2P的大规模推广,目前受制于WiFi终端IPv6支持度不高,在P2P的视频会议、通话及长短视频应用领域,IPv6均无法很好的施展开来,随着规模化部署的推进,最终会迎来这一技术的大量普及和创新。HTTP3与IPv6的强强联合,rfc9114使用QUIC协议作为传输层协议,可以有效地降低网络卡顿率和首包延迟。QUIC是基于UDP协议实现网络通信,为了实现对QUIC协议的支持, SLB上需要支持DNAT转发模式(注:在UDP通信中SLB的FNAT转发模式无法携带客户端IP信息)。DNAT转发模式中,IPv6的流量只能做6 to 6的地址转换,不支持6 to 4的地址转换。Aserver进行ipv6双栈升级后,完成了在IPv6网络链路中DNAT转发模式的后端支持,从而实现了整个ipv6网络链路上完整地支持HTTP3协议。手淘客户端发起IPv6的HTTP3请求,源目的IP分别是(CIP,SIP),SLB通过DNAT转换将请求的源目的IP转换成(CIP,RIP),转发给Aserver;Aserver获取HTTP响应数据后,封装源目的IP(RIP,CIP),将HTTP3响应发送到SLB上,SLB将源目的IP转换成(SIP,CIP)后,将HTTP3响应数据发送给手淘客户端。总之,IPv6的发展是一次难得的机会,当然整个过程非常艰难,但如果我们克服这些困难,会为未来在互联网领域的持续创新积累下丰富的经验,打下扎实的基础,创造良好的环境。参考文献[01] 关于加快推进互联网协议第六版(IPv6)规模部署和应用工作的通知http://www.gov.cn/zhengce/zhengceku/2021-07/23/content_5626963.htm[02] 邬贺铨:加快IPv6规模部署支撑网络强国建设http://www.cac.gov.cn/2022-03/30/c_1648522019215113.htm[03] Linux 3.10内核锁瓶颈描述以及解决-IPv6路由cache的性能缺陷https://blog.csdn.net/dog250/article/details/91046131[04] Department of State Internet Protocol Version 6 (IPv6) Policy Statement - United States Department of Statehttps://www.state.gov/department-of-state-internet-protocol-version-6-ipv6-policy-statement/[05] ipv6: Use math to point per net sysctls into the appropriate struct net https://patchwork.ozlabs.org/project/netdev/patch/20200303065434.81842-1-cambda@linux.alibaba.com/[06] https://nvd.nist.gov/vuln/detail/CVE-2022-1678[07] IPv6详解 卷一:核心协议实现【M】,北京:人民邮电出版社,2009.[08] 一种流量控制方法及装置,CN106603256B[09] 一种流量隔离方法及装置,CN106528362A[10] 服务限流系统、方法、装置及电子设备,CN111367651A[11] 速率控制方法、装置及电子设备,CN110278160A[12] 一种流量控制方法、装置及设备,CN111385214A[13] 压测过程中的流量调度方法、调度平台和调度系统,CN107872397A
iOS上架我不允许你还不会
自己的经验总结,有错的话请留言,第一时间更改。先大概说一下IOSAPP上架的几个步骤(详细步骤见下图):创建证书请求文件登录苹果开发者中心生成发布者证书(下载下来要双击一下)设置APPID(要用到Bundle ID,建议从XCode中复制这样的话就不用更改XCode项目里的Bundl ID里,不然还得改)生成配置文件(要用到APPID和证书,退出XCode再双击配置文件,打开项目)在开发者中心里的ITunes Connect里的APP新建APP项目并配置相应的信息(需要各个版本的屏幕截图,运行模拟器,调到最大(command+1)用command+S截图(如果你的键盘没有更改command键的话),还有就是一个1024*1024的图标,不能有圆角)设置XCode的一些属性(主要是选择配置文件和证书账号之类的)打包并上传你的程序详情如下:创建证书请求文件接着还有证书请求文件,有的人叫他CSR文件,证书一会要用到这个文件。首先打开应用程序-》实用工具-》钥匙串访问(KEY CHAIN),左上角钥匙串访问-》证书助理中,选择"从证书颁发机构求证书",如下图:在下图所示的界面,你的电子邮件地址:填你申请开发者账号的名称,也就是你登录苹果开发中心的用户名,常用名称,随便起但是要能找到,建议和APP的素材放到一起,但是这个可以单独用一个文件夹来存放证书,CA空,选择存存储到磁盘,点击"继续",然后完成。如图:有了证书请求文件就可以创建证书了找到苹果开发者中心的Member Center(会员中心)https://developer.apple.com/如图:输入公司或个人的开发者账号和密码,如图:创建证书然后登陆,就会出现以下界面:点击Certificates, Identifiers & Profiles接着看到下图,按照下图继续点击,接着就可以制作证书文件,就选就像之前创建APPID一样创建证书文件,需要用到刚才的那个文件,如图:然后一直continue到这个界面,需要你选择刚才下载的证书请求文件,不要找错,如图:下载证书然后Done,下载好的证书需要双击一下导入到钥匙串里面。接着就是到APPID里设置应用程序的名字(这个随便啦记住就行啦一会会用到的)和Bundle ID(建议从XCode中直接复制过来)接着创建APPID如图:然后continue,Submit,Done。然后就是创建配置文件然后continue,然后Done,退出XCOde双击配置文件,XCode会启动,打开项目即可。接着登录ITunes Connect,新建APP左上角,点+,新建APP接着还有就是配置一些应用程序的信息,只要是没说可不填的都尽量填上如图:每个版本最好都要有几张图片,图片的来源就是运行所有的模拟器,用command+1调到最大,然后command+s就可以了接着就是XCOde的配置了,HEHEHEHE是配置文件的名字这个就解释到这吧,剩下的就是打包上传了把模拟器的设备选一下如图:然后就是在Product->Archive进行编译接着就可以上传到APPStore了还有最后一步就是登录到开发者中心找到iTunes Connect的APP找到准备提交,内建版本找到刚才上传的那个APP就可以了,如图:然后点击提交以供审核就可以了。重点来了在下面这个地方我是死了N多次如图:最终的最终还是解决了,原因是因为有个证书过期了删了再下个就好了:打开钥匙串显示所有已过期的证书3,在‘登陆’和‘系统’中删除已过期的Apple Worldwide Developer Relations Certification Authority证书(‘系统’需要解除权限才能删除)4,下载新的Apple Worldwide Developer Relations Certification Authority证书,双击安装即可。
阿里云概述及建站(2)|学习笔记
开发者学堂课程【阿里云新手上云实战演练:阿里云概述及建站】学习笔记,与课程紧密联系,让用户快速学习知识。 课程地址:https://developer.aliyun.com/learning/course/655/detail/10851阿里云概述及建站(2)公网IP分配一个公网IP两兆。就可以按使用流量,也可以按固定带宽,一般推荐大家按固定带宽,网站刚开始上线的时候,不管是机器还是在负载均衡,选网络就选按量,等到网站稳定一段时间,大概能评估出每天的量是多少之后,就可以选带宽了,现在选一个两兆。 安全组先默认,可以选端口,比如现在命名是一个自动S,就不需要看3389了,先开22,等登录上去之后改完相关的配置,把IOS改了之后,就把22取消。看到有一个公网IP,还有一个弹性网卡,阿里云的ecs对于公网是有两种模式的,一种是直接购买机器的,然后选择分配公网地址,公网IP相当于与机器是绑定的,一旦机器释放了,公网IP也就释放了,这是一种。还有一种是弹性网卡,弹性网卡是什么,看弹性就很好理解了,想用的时候用,不用的时候就删了。弹性网卡可以购买机器的时候不购买它,另外购买一个弹性网卡,然后把弹性门槛与这个机器做绑定,然后这个机器就能上公网了。然后要是现在这个机器不需要上公网了,弹性网卡再绑定别的机器,别的机器就能上了,相当于生命周期是脱离的。然后还有ipv6需要提交公测资格申请,什么时候需要用ipv6,苹果官方要求是线上应用或者应用审核就是上苹果商店的时候域名必须支持https和ipv6,如果不支持,苹果官方审核是不通过的,就上不了APP了,审核就不通过了,这个时候就需要ipv6了。可以在阿里云上申请一个ipv6的地址,然后配置一下,具体怎么配置,大家如果有需求可以查一下。给大家一个建议,如果是遇到这个情况,可以使用阿里云来做ipv6,但是ipv6是需要付费的。有个好办法是一般审核就是一个月左右,审核之前配ipv6,就是相当于给这个域名配一个ipv6的解析,审核通过之后就把 ipv6地址删掉,这样就节省费用,因为平时用不到,应用商店审核的时候。之前阿里云没有 ipv6的时候,访问一个国外的网站,在国外的网站上相当于申请一个ipv6,配置起来特别复杂,后来有了阿里云有了ipv6之后,每次审核都是申请一个地址,直接在类似于按配置配置上一个 ipv6的解析。4、系统设置首先root密码可以选择密钥,一般为了方便都是选择自定义密码,假如要配不能用密码登录,也都是登到机器上之后再做修改的,或者可以创建以后设置。最好别创建之后设置,就是改密码必须重启比较麻烦,所以一开始就自定义配置,购买一台机器,然后这个实例的名字无所谓,使用的时候最好给机器一个标识,比如说这台机器就是做网站的,做一个water price的网站,这个网站是做论坛的,就给它起名字wp。如果有多个可以命名wp1234,主机名可以设置也可以不用设,然后这些都是可以设置也可以不用设置。还有任务这部分,可以到时候单独设置,因为需要单独创建一个ram角色,简单讲一下ram是干嘛的,就是阿里云是有一个主账号的,但是这个主账号相当于是一个特别高的管理员权限,如果需要把某些权限开放给个别人怎么办,就需要创建一个RAM角色,就可以简单理解成是阿里云的子账号,子账号可以设置不同的权限,比如只有 ecs的只读权限,或者是只有财务的权限等等。5、分组分组是干嘛的,分组是后期对阿里云资源的一个管理,一个是权限,需要让某个人对某个资源组下面的,不管是ecs还是rds或者等等资源有权限,然后不许的时候就可以给子账号受资源组下面的权限。第二个是财务。财务需要做成本分割,可能阿里云账号下面有好多的资源,这些资源不只是一个业务线在用,如果多个业务线用的时候,就需要做成本的一个分割,这个时候把不同业务线的资源放到不同的资源组下面,财务就可以很好的做分割了,要不得自己查,要看资源用到,然后资源花了多少钱,就比较麻烦,如果放到资源组,阿里云可以自动统计。6、确认订单确认订单就看一下有没有问题,一个是保存为启动模板,假如要买机器,可能以后都买一样配置的,就可以把它保存成一个启动模板,保存完之后下次买,就直接用模板买就行了,刚才点的过程都省了。更好的一个是生成open,API最佳实践脚本,买机器了买一些配置什么等等的,直接生成代码了。现在支持两种,一个是Java的,一个是iPhone的购买机器,购买的是张家口的,然后购买的配置是C6large,后付费方式的,后付费的意思就是按量的都是后付费,包年包月的都是预付费,选的倾向ID等等,可以直接把代码粘出来,放脚本里面,然后下次执行就买完一台机器。 比如要扩阿里云的机器的时候,会根据一些逻辑,现在为了双11做准备,双11的时候是不是量上来了,多打几个wp,word press。假如双11论坛也有活动,发论坛发博客都可以奖励红包然后大家都上来发,这个时候可能论坛就支持不住,可以将论坛直接做成一个绩效,做成一个自定义镜像,然后把镜像ID替换,就可以直接用代码,在当天创建多个,或者写成计划任务,直接把脚本放到计划任务里面,到了时间自动执行,然后创建完之后,再调用别的脚本,加到负载均衡里面,就可以对外访问了,或者要做QS肯定会有扩容的需求,然后什么时候扩容逻辑是自己想的,比如要扩容处罚条件,这个逻辑是自己想,但是扩容的步骤不需要想了,已经帮写好了,直接粘过去就行。简单看一下就是一个类,就是执行这条命令,执行阿里云instance example,先购买机器了,这一段直接可以复用,或者要对Java比较了解,可以用Java,这就是特别强大的一个地方。然后还可以设置自动释放时间。整个购买的过程就讲完了,然后点创建实例,就能创建成功了。六、域名服务1、使用阿里云镜像安装 docker-ce启动是需要一个过程,然后启动之后就可以连上去了。登上去之后安全可能会把端口改了,禁止入库登录或者禁止密码登录,只能用K登录这种的,都可以再配置。直接装一个docker,docker-ce是docker的社区版。使用阿里云镜像安装docker-ce。环境先准备着,现在继续往下,构建环境和构建域名是同步的,因为备案需要时间,如果要想网站上线,备案是需要时间。怎么购买域名,左边导航栏没有加星可以在这搜索,然后现在加星了,直接到域名的控制台,左边导航栏是一些比如域名列表已经买的域名的一些信息,是已经买的一个域名。 2、信息模板对于信息模板,域名需要实名认证分两种,一种是个人的,一种是企业的。假如是企业,单位名称联系人然后所属区域,把单位的一些信息填上,个人就填个人的信息。再有一个就是批量操作,比如要对域名批量做更改等等的都可以批量操作。比如域名注册、续费转入DNS修改,然后禁止转移禁止更新等等。3、域名转入域名转入是什么,分两种情况,一种就是域名之前不在阿里云托管,在别的运营商托管,然后现在要把域名转到阿里这边,就需要做一个域名的转入,还有邮箱验证,操作记录不需要管。假如有一个域名是JD.com,要卖域名,就可以在我是卖家里面发布一下,就会有人找到阿里作为中间商来帮两方协调然,如果要买别人的域名,觉得这个域名特别好,就可以先查一下,查到这个域名是托管到阿里了,就可以在阿里上发一个我要买域名,或者是如果能直接联系到卖家,可以跟卖家直接联系。没有中间商了。4、域名预定域名预订就是要买什么域名,这个就是新构域名,别人没有注册的时候可以预定。然后还有域名回购。先来买一个域名,要抢购。直接在下面就有域名注册,域名转入购买二手域名,然后直接域名注册,先查要买什么域名,查一下有没有被别人注册了。一般可以选后缀,选一些不太常用的后缀,就不会被买了。举个例子,估计预算然后挑选域名,买域名就特别简单,点加入清单,然后要买这个点立即结算,一般都是满一年,域名持有者就是要对域名做实名认证,选个人还是体验就用到模板了。比如要选企业,现在没有企业的模板,就需要去创建信息模板,然后我在是个人,之前创建过一个信息模板,已经做实名认证了,所以直接选它立即购买,这个域名就购买成功了。我现在不买这个域名了,买了短时间也没法用,好像得等一个多小时。就是买完之后,提交了实名认证了,阿里云要做审核,大概要等一个多小时左右,然后就用之前购买的域名。5、实战买完域名之后需要计算资源,已经在准备过程实现了,已经买了一机器了,然后构建环境,docker环境已经装好了。不管是怎么装软件的,最好都设置一个开机自启。能看到信息就代表docker装成功了。现在下载一个wordpress的镜像。原来搭个word press很复杂,要先搭一个lnmp,打好lnp之后,还得再配置,过程中发现怎么连不上数据库了。然后要用docker就直接就起来了。一般要用装my circle,一般大家可能都推荐用源码装,就二进制源码这种的。然后先看一下备案,也可以同时操作的。备案不在产品列表里面,直接就在他这儿。到备案的控制台上就是再点一下控制台备案。开始备案或者是直接点。七、备案流程1、备案申请备案下面就有一个备案服务号申请,要备案先需要申请一个备案服务号,简单的看一下流程,实际备案的就没法做,因为自2018年起就是买的服务器,不管买的轻量应用服务器,还是买的VPS,还或者是ecsslb之类的,必须在三个月以上,才能申请备案号,申请了备案号才能做备案。所以大家真的要做一个网站的时候,得把时间预留好,先买了机器三个月以后,然后才能做备案。可能现在变了,原来是需要有一个备案服务号才能备案。2、备案流程简单看一下备案的流程。(1)进入备案系统(2)填写信息核验资料备案要填一些信息,企业主要是上传营业执照等等这些的,法人的身份证,网站负责人的身份证,然后在职证明。如果要备案多个公司,比如说有好多子公司,还得工作证明上必须盖好几个公司的章。(3)阿里云初审与管局审核要按照要求提交这些信息,提交之后,阿里云会做一个初审,初审的时候会有阿里云的工作人员打电话,说哪不合格需要改,然后我们会邮件发送,然后改了之后重新提交,然后工作人员审核基本上通过之后会提交到管局,管局审核需要20个工作日,所以一般备案1个域名,就预留22个工作日。要开始做一个网站,打算要先上一个网站或者先上一个新域名,只要是你换了域名的一个东西,可能你还是一个网站,只不过你不用这个域名了那一个,然后你就需要预留22天。 一开始在做规划的时候,就先做备案这块,或者新买了域名打算后续用,可以先备案,防止用的时候备案不成功,等管局审核,阿里云会审核你的这些信息,审核过了之后,管局这边一般都不会有什么问题,就等待20个工作日就备案成功了。还有一个是在备案的时候,只有国内需要备案,国外是不需要备案的,要买一个香港的服务器就不需要备案,直接可以使用的,或者是买个美国的机器。但是现在在阿里云上买个美国的机器,问题挺多,动不动机器就登不上去了。以前买了一个美国的机器,打算做有一些着急的网站但是没有备案,实际通过负载均衡跳回来,这样绕一下,或者是还有一些什么情况,有时候会发现 get up上下载东西特别慢了就有时候翻墙也不太方便,就可以在美国买一台或者香港买台机器,然后下载下来拷回来,然后再把机器释放了。一般都这么干,那么翻墙太麻烦了,就可以在美国买一台或者香港买一台机器,然后下载下来拷回来,然后再把那机器释放了。这是应对临时的问题,长期肯定还是要备案。而且要国外会涉及一些网络慢的问题,买域名的时候要注意,就是要确定这个域名是能在国内做备案的。按照提交备案的信息,然后提交信息就行了,提交不合格工作人员会打电话。八、云服务1、首先将网站环境搭建起来现在已经把word price镜像下载了,可以直接绕起来。要把这个镜像直接启动成一个服务。可以看到服务已经启动来了,日志启动了。通过IP访问一下做解析,在访问之前,需要先把阿里云的安全组打开。用了阿里云是在系统上,确定没问题,通过iptables这种方式,把防火墙打开。用了阿里云之后可能就不需要做以上步骤了,所有的安全都放在阿里云了。需要开哪些端口,在安全组里面配一下就行。就直接点击实例点进去之后会看到左边导航栏,有一个本实例的安全组,点进来,然后配置规则。Netstat-anlptulgrep 80。启动80的服务,将80端口打开即可。2、安全组规则添加安全组规则添加安全组规则是分为规则方向、网卡类型等。出方向一般就不用设置,默认出方向是所有的端口都打开,主要就需要改入方向的。一般授权策略就是允许和拒绝,拒绝会比允许的权限高。协议类型,比如常见的一些HTTP80,可以直接写80,如果不在这里面的,就要开一个1022,把ssh端口设成1022,需要写定义。自定义怎么写,就是一个端口的范围,以斜杠分隔。比如要开80就是把80~89这一段端口都开了,假如要开某一个,比如80~80就是只开80这个端口。授权对象是分两种,一种是ipv4地址访问,一种是安全组访问。安全组访问是假如ECS不在同一个安全组,比如A机器在A安全组,B机器在B安全组,但是某个端口需要互相访问。比如A安全组里面有一个nfs。然后B安全组里面有一台服务器,这个服务器要访问,要把这个nfs做挂载,要挂载就需要能访问nfs端口。就可以指定安全组访问,选择哪个安全组就可以。而且除了本账号还可以跨账号,不在一个阿里云账号下面的,然后输入账号ID就可以做安全组访问。一般用得更多的是ipv4地址访问。就是需要哪种,只让访问那种IP。现在公网IP就是222.129.47.248。就不需要对外访问的时候配置自己的公网IP就行。如果是对外访问,就配制成0.0.0.00。现在添加这个端口了,可以访问一下试试。能打开,选简体中文。然后以上就是word present配制过程。3、域名解析(1)整个环境做好了,而且备案也做好了。现在需要添加域名解析。回到域名控制台,然后点击域名,比如现在要用这个域名,其中域名的管理控制台,就是针对某个域名的管理控制台,域名持有者过户信息修改都可以,平时做的最多的就是域名解析。还有一个DNS修改,一般都改成托管到阿里云或者华为云。就改成阿里云DNS或者华为云DNS。(2)现在做一个域名解析,打开终端。生效了可以通过域名来访问。被封就是会出现下面情况:配置数据库先不配,等备案好了之后,改一下解析就可以了。以上就完成了整个一个网站了。可以通过 yanyan.live 来访问论坛了。现在这个网站是 HTTP 的,报的是不安全。现在配一个 https,需要买证书。第二个实验就是 nginx 实现网站安全认证,购买完证书就在网站前面加一层负载均衡这个词。把证书配到 nginx 里面,或者配到 word price 也行。购买证书可以选择 SSL 证书。
前端面试+学习笔记(HTML+CSS+JavaScript+ES6+Vue+NodeJs)
前端面试+学习笔记(HTML+CSS+JavaScript+ES6+Vue+NodeJs)一. HTML1. 盒子模型是什么:每个元素被表示为一个矩形的盒子,有四个部分组成:内容(content)、内边距(padding)、边框(border)、外边距(margin)。它在页面中所占的实际大小(宽高)是content+padding+border+margin之和。盒模型有两种:标准盒模型(W3C盒模型)、IE盒模型。两种盒模型的区别:标准盒模型内容大小就是content大小、而IE盒模型内容大小则是content+padding+border总的大小。**怎么设置两种盒模型:**通过设置box-sizing属性为content-box(默认值:标准盒模型)、border-box(IE盒模型)。JS怎么获取和设置box的宽高。box-sizing使用场景:若设置子元素的margin或border时可能会撑破父元素的尺寸,就需要使用box-sizing:border-box来将border包含进元素的尺寸中。2.页面导入样式时,使用link和@import有什么区别**link属于XHTML标签,**除了加载CSS外,还能用于定义RSS(简易信息聚合,是一种基于XML标准,在互联网上被广泛采用的内容包装和投递协议)rel连接属性等作用;@import是CSS提供的;只能用于加载CSS页面被加载时,link会同时被加载;而@import引用的CSS会等到页面被加载完成后再加载link是XHTML标签,没有兼容问题;而@import只有在IE5以上才能被识别link支持使用JavaScript控制DOM修改样式;而@import不支持。4.行内元素有哪些?块级元素有哪些?空元素(void)有哪些?**行内元素:**a,b,span,img,input,strong,label,button,select,textarea,em**块级元素:**div,ul(无序列表),ol,li,dl(自定义列表),dt(自定义列表项),dd(自定义列表项的定义),p,h1-h6,blockquote(块引用)空元素(void):即没有内容的HTML元素。br(换行),hr(水平分割线),meta,link,input,img5. src 和 herf 的区别href是指向网络资源所在位置,建立和当前(锚点)或当前文档(链接)之间的连接,用于超链接。src执行外部资源的位置,指向的内容会嵌入到文档中当前标签所在位置,在请求src资源时会将其指向的资源下载并应用到文档中。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕。(性能优化)6. 为什么CSS样式放在头部,JS脚本放在底部浏览器为了更好的用户体验,渲染引擎会尝试尽快在屏幕上显示内容,不会等到所有的HTML元素解析之后在构建和布局DOM树,所以部分内容将被解析并显示。前端一般主要关心首屏的渲染速度,这也是为什么要提倡“图片懒加载”的原因。**其实外部的js和CSS文件时并行下载的。**随着JS技术的发展,JS也开始承担起页面的渲染工作了。如果JS加载需要很长时间,会影响用户体验。所以需要将JS区分为承担页面渲染工作的JS和承担事件处理的JS。渲染页面的JS放在前面,事务处理的JS放在后面。7.常用浏览器,内核Trident内核:(国产的绝大部分浏览器)IE,360,搜狗**Gecko内核:**Firefox,NetScape6及以上**Presto内核:**Opera7及以上Webkit内核:(国产大部分双核浏览器其中一核)Safari(苹果),Chrome浏览器内核:主要分成两部分:渲染引擎和JS引擎。最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎**渲染引擎:**负责取得网页内容(HTML,XML,图像等)、整理讯息(加入CSS等),以及计算网页的显示方式,后会输出至显示器或打印机。JS引擎:解析和执行JavaScript来实现网页的动态效果。8. DOCTYPE作用?严格模式与混杂模式 , 标准模式和怪异模式声明位于HTML文档中的第一行,处于标签之前,告知浏览器的解析器用什么文档标准解析这个文档。严格模式下,排版和JS以浏览器支持的最高标准运行;**混杂模式下,**页面以宽松向后兼容的方式显示**如何触发混杂模式:**DOCTYPE不存在或格式不正确,会导致文档以混合模式呈现**标准模式(standards mode)**是指浏览器按照W3C标准解析执行代码;**怪异模式(quirks mode)**则是使用浏览器自己的方式解析执行代码。浏览器解析时到底使用何种模式,与网页中的DTD声明(文档类型定义,DOCTYPE相关)有关,忽略DTD声明,将使网页进入怪异模式。9.优雅降级和渐进增强渐进增强(progressive enhancement):针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。优雅降级(graceful degradation):一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。区别:优雅降级是**从复杂的现状开始,并试图减少用户体验的供给;渐进增强则是从一个非常基础的,**能够起作用的版本开始,并不断扩充,以适应未来环境的需要。渐进增强观点认为应该关注于内容本身,这使得渐进增强成为一种更为合理的设计范例;优雅降级观点认为应该针对那些最高级、最完善的浏览器来设计网站。10. 对HTML语义化的理解用正确的标签做正确的事情HTML语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析即使在没有样式CSS情况下也以一种文档格式显示,并且是易于阅读的搜索引擎的爬虫也依赖于HTML标记来确定上下文和各个关键字的权重,利于SEO使阅读源代码的人更容易将网站分块,便于阅读维护理解二. CSS1.CSS选择符,优先级**选择符:**id(ID);class(类);element(标签);element element(后代);element>element(子);element,element(群组);element+element(相邻同胞);伪类(:link,:visited,:active,:hover,:focus:first-child,:lang(language));伪元素(:first-letter,:first-line,:before,:after);属性选择器**可继承的选择符:**主要是文本方面的可继承,盒模型相关的属性基本没有继承特性。font-size,font-family,color,ul**不可继承的选择符:**border,padding,margin,width,height**优先级:**同权重下样式定义最近者高。!important>内联样式 即定义在HTML标签内的样式,(1000)>id(100)>class/伪类/属性(10)>伪元素/element(1)**CSS引入伪类和伪元素的原因:**用来修饰DOM树以外的部分。伪类用于当已有元素处于某个状态时,为其添加对应的样式,这个状态根据用户行为而动态变化。伪类的操作对象是DOM树中已有的元素。伪元素用于创建一些不在DOM树中的元素,并为其添加样式伪类和伪元素的区别在于有没有创建一个DOM树之外的元素2. 外边距重叠(collapsing margins)/margin坍塌**是什么:**相邻的两个或多个普通流中的块元素,如果它们设置了外边距,那么在垂直方向上,外边距会发生重叠,以绝对值大的那个为最终结果显示在页面上,即最终的外边距等于发生层叠的外边距中绝对值较大者。**最终外边距:**margin全为正(取最大值)、margin全为负(取绝对值最大的负数)、margin有正有负(分别取正数最大值a,负数的最大绝对值b,a-b)**外边距重叠的应用:**几个段落一起布局,第一个段落的上外边距正常显示,下外边距与第二个段落的上外边距重叠。防止外边距重叠:创建BFC元素。不会发生外边距重叠的情况:行内元素、浮动元素、绝对定位元素之间的外边距都不会叠加。3. BFC(Block Formatting Context,块级格式化上下文)**是什么:**决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。简言之,就是一个特殊的块,内部的元素和外部的元素不会相互影响。BFC内的盒子会在垂直方向上一个接一个地放置,垂直方向上也会发生外边距重叠。**应用场景:**自适应布局(BFC不与float box重叠)、清除浮动(计算BFC的高度时,内部的浮动元素也被计算在内)、防止外边距重叠如何触发BFC:float属性(不为none)、overflow属性(不为visible)、position属性(absolute,fixed)、display属性(inline-block,table-cell,table-caption,flex,inline-flex)。4.元素的position属性**定义:**规定元素的定位类型。**正常文档流:**指的是没有用CSS样式去控制的HTML文档结构,代码的顺序就是网页展示的顺序。**脱离文档流:**指的是元素所显示的位置和文档代码不一致。**static:**默认值。没有定位,元素出现在正常的文档流中。**relative:**生成相对定位的元素,相对于其在正常文档流中的位置进行定位(不脱离文档流)。**absolute:**生成绝对定位的元素,相对于static定位以外的最近父级元素进行定位,即相对于其直接父级元素(脱离文档流)。absolute较少直接单独使用在正常的文档流中,主要运行于进行了相对定位的元素框架层里面,相对该层的左上点进行偏移。**fixed:**生成固定定位元素,相对于浏览器窗口进行定位。**inherit:**从父元素继承position属性的值。**z-index属性:**使用了relative、absolute、fixed三种定位后,都会使正常的文档流发生一定程度的改变,造成元素出现重叠的情形。为了能让重叠的元素有序的显示出来,需要在定位的相关元素加上z-index属性。其值是一个整数值,默认为0,数值越大表示拥有的优先级越高,该属性只对使用了定位的元素有效。5.元素的display属性**定义:**规定元素应该生成的框的类型常用属性值:**inline:**默认值。元素会被显示为内联元素。**none:**元素不会被显示。**block:**元素将显示为块级元素。**inline-block:**行内块元素,即元素像行内元素一样显示,内容像块元素一样显示。**list-item:**元素像块元素一样显示,并添加样式列表标记。**table:**元素会作为块级表格来显示。**table-caption:**元素会作为一个表格标题显示。**inherit:**从父元素继承display属性。display属性值inline和block的区别:block元素会独占一行,默认情况下,block元素宽度自动填满父级元素的宽度;block元素可以设置width、height属性,即使设置了宽度,仍然是独占一行;block元素可以设置margin和padding属性;inline元素不会独占一行,多个相邻的行内元素会排列在同一行里面,其宽度随元素的内容而变化;inline元素设置width、height无效;inline元素的margin和padding属性在水平方向上能产生边距效果,垂直方向不会产生边距效果。display:inline-block元素显示间隙inline-block水平呈现的元素之间,HTML元素标签换行显示或标签之间有空格的情况下会有间距消除办法:移除标签之间的空格;使用margin-left或margin-right取负值;对父元素设置font-size为0,然后对元素的font-size初始化;对父元素设置letter-spacing(字符间距)为负值,然后设置元素的letter-spacing为0;对父元素设置word-spacing(单词间距)为负值,然后设置元素的word-spacing为0。7.overflow属性**定义:**规定当内容溢出元素框时发生的事情**visible:**默认值。内容不会被修剪,会呈现在元素框之外**hidden:**内容会被修剪,并且其余内容不可见**scroll:**内容被修剪,但浏览器会显示滚动条以便查看其余内容**auto:**如果内容被修剪,则浏览器会显示滚动条以便查看其余内容inherit:从父元素继承overflow属性的值8. 初始化CSS样式**为什么要初始化CSS样式:**因为浏览器的兼容问题,不同浏览器对有些标签的默认值时不同的,如果没有对CSS初始化往往会出现浏览器之间页面显示差异。最简单的方法:*{margin:0;padding:0;}**初始化CSS的缺点:**对SEO(搜索引擎优化)有一定的影响。SEO:Search Engine Optimization,搜索引擎的优化。SEO具体是指通过网站结构调整、网站内容建设、网站代码优化以及站外优化,使网站满足搜索引擎的收录排名需求,提高网站在搜索引擎中关键字的排名,从而吸引精准用户进入网站,获得免费流量,产生直接销售或品牌推广。**什么是CSS Hack:**一般来说针对不同的浏览器写不同的CSS,就是CSS Hack。9.CSS属性cursor?cursor属性规定要显示的鼠标的光标类型。常用取值:pointer(手),crosshair(十字线),default(箭头),auto(浏览器设置的光标)HTML5一. HTML5 新特性1. HTML5新特性:主要是关于图像、位置、存储、多任务等功能的增加。包括:绘画canvas(通过脚本实现绘画)用于媒介回放的video和audio元素本地离线存储localStorage、sessionStorage语义化更好的内容元素:article、footer、header、nav、section表单元素:datalist(规定输入域的选项列表)、output(用于不同元素的输出)、keygen(提供一种验证用户的可靠方法)input类型:color、date、month、week、number、email(检测是否为一个email格式的地址)、range(滑动条)、search、url、tel(输入电话号码,-time选择时间)2. HTML5新标签的浏览器兼容问题:当在页面中使用HTML5新标签时,可能会得到三种不同的结果:新标签被当做错误处理并被忽略,在DOM构建时会当做这个标签不存在新标签被当做错误处理,在DOM构建时,这个新标签会被构造成行内元素新标签被识别成HTML5标签,然后用DOM节点对齐进行替换3.解决兼容性问题:**实现标签被识别。**通过document.createElement(tagName)即可让浏览器识别新标签,浏览器支持新标签后,还可以为其添加CSS样式JavaScript解决方案:使用html5shim。在中调用以下代码(也可下载到本地后调用):<!--[if It IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->使用kill IE6<!--[if It IE 6]>
<script src="http://letskillie6.googlecode.com/svn/trunk/letskillie6.an_CN.pack.js"></script>
<![endif]-->4. 如何区分HTML和HTML5:DOCTYPE声明新增的元素5. HTML5移除的元素:**纯表现的元素:**big,center,font,strike(删除线),u(下划线),s(删除线)**对可用性产生负面影响的元素:**frame,frameset,noframes6.iframe的缺点会阻塞主页面的onload事件搜索引擎的检索程序无法解读这种页面,不利于SEO二. cookie和localStorage的区别1.共同点:cookie、sessionStorage和localStorage都是由浏览器存储在本地的数据。区别:cookie是网站为了标识用户身份而存储在用户本地终端上的数据(通常经过加密),数据始终在同源的http请求中携带,即在浏览器和服务器之间来回传递;localStorage不会自动把数据发给服务器,尽在本地保存cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下,存储大小也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据(如会话标识);localStorage也有存储大小的限制,但比cookie大很多,可以达到5M或更大。cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭;localStorage始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;sessionStorage在当前浏览器窗口关闭之后自动删除localStorage支持**事件通知机制,**可以将数据更新的通知发送给监听者,API接口使用更方便;cookie的原生接口不友好,需要程序员自己封装**localStorage如何删除数据:**localStorage对象可以将数据长期保存在客户端,除非人为清除,提供了以下几个方法:**存储:**localStorage.setItem(key,value) 如果key存在,更新value**获取:**localStorage.getItem(key) 如果key不存在,则返回null**删除:**localStorage.removeItem(key) 一旦删除,key对应的数据将会全部删除全部清除:localStorage.clear()使用removeItem逐个删除太麻烦,可以使用**clear,**执行的结果是清除所有的localStorage对象保存的数据localStorage存储的数据是不能跨浏览器共用的,一个浏览器只能读取各自浏览器的数据。CSS3一. CSS3新特性1. 新特性边框:border-radius(圆角)、box-shadow(阴影)、border-image(边框图片)背景:background-size(背景图片的尺寸)、background-origin(背景图片的定位区域)文本效果:text-shadow(文本阴影)、word-wrap(文本换行)转换和变形:transform(包括2D,3D转换,rotate(angle),translate(x,y),scale(x,y))过渡:transition动画:animation多列:column-count(元素被分隔的列数)、column-gap(列之间的间隔)、column-rule(洌之间的宽度,样式,颜色规则)用户界面:resize(规定是否可由用户调整元素尺寸)、box-sizing(以确切的方式适应某个区域的具体内容)、outline-offset(对轮廓进行偏移)2. 新增伪类:element:before(在元素之前添加内容) element:after(在元素之后添加内容)element:first-of-type、element:last-of-type、element:only-of-type、element:only-child、element:nth-child(n)(第n个):checked、:disabled、:enabled3. 四个锚点伪类的设置问题:**问题描述:**超链接访问后hover样式就不出现了,被点击访问过的超链接样式不再具有hover和active**解决办法:**爱恨原则LoVe/HAte。改变CSS属性的排列顺序,L-V-H-A 即a:link{} a:visited() a:hover{} a:active{}4.transition、transform和animation的区别transform是指转换,可以将元素移动、旋转、倾斜、拉伸。没有变化的过程。而transition和animation都加上了时间属性,所以能产生动画效果transition是指过渡,一般由行为(hover等)触发;而animation则是自动触发transition只能设置头尾,所有样式属性一起变化;animation可以设定每一帧的样式和时间,且可以循环播放。5.rgba和opacity的区别?rgba和opacity都能实现透明效果,但最大的不同在于opacity作用于元素本身以及元素内的所有内容,而rgba只作用于元素本身,子元素不会继承透明效果。**rgba是CSS3的属性,**用法说明:rgba(R,G,B,A),参数说明R(红色值。正整数|百分数),G(绿色值。正整数|百分数),B(蓝色值。正整数|百分比),A(Alpha透明度。0(透明)~1)。IE6-8不支持rgba模式,可以使用IE滤镜处理:filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr=#AARRGGBBAA,endColorstr=#AARRGGBBAA);其中AA,RR,GG,BB分别表示Alpha,R,G,B,取值为00-FF。opacity也是CSS3的属性,用法说明:opacity:value 其中value取值0(透明)~1。对于IE6-8,可以用IE滤镜处理:filter:alpha(opacity=50); /对应于opacity:0.5;浮动一. 清除浮动父级div定义height结尾处加空div标签,样式clear:both父级div定义伪类:after和zoom通过CSS伪元素在容器的内部元素最后添加了一个看不见的空格"020"或点".",并且赋予clear属性来清除浮动。需要注意的是为了IE6和IE7浏览器,要给clearfix这个class添加一条zoom:1;触发haslayout。父级div定义overflow:hidden(同时还要定义width或zoom:1,不能定义height)父级div定义overflow:auto(同时还要定义width或zoom:1,不能定义height)父级也浮动,需要定义width(不推荐)父级div定义display:table(不推荐)结尾处加br标签,样式clear:both(父元素div定义zoom:1,不推荐)二. 属性clear取值1. 定义:规定元素的那一侧不允许其他浮动元素2. 取值none:(默认值)。允许浮动元素left:在左侧不允许浮动元素right:在右侧不允许浮动元素both:在左右侧均不允许浮动元素inherit:从父元素继承clear属性三. 属性zoom取值1. 定义:设置或检索对象的缩放比例2.取值normal:(默认值),使用对象的实际尺寸:用浮点数来定义缩放比例,不允许负值:用百分比来定义缩放比例,不允许负值新的注意点:一. 重排(reflow) 与重绘(repaint)浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排重绘是一个元素外观的改变所触发的浏览器行为(例如改变visibility,outline,background等属性),浏览器会根据元素的新属性重新绘制,是元素呈现新的外观。**重排时更明显的一种改变,可以理解为渲染树需要重新计算。**常见的触发重排的操作:DOM元素的几何属性变化DOM树的结构变化(例如节点的增减、移动)获取某些属性(例如offsetTop,offsetLeft,offsetHeight,offsetWidth,clientWidth,clientHeight等)改变元素的一些样式(例如调整浏览器窗口大小)重绘不会带来重新布局,并不一定伴随着重排。在实践中,应该尽量减少重排次数和缩小重排的影响范围。有以下几种方法:将多次改变样式属性的操作合并成一次操作将需要多次重排的元素,position属性设为absolute或fixed,使其脱离文档流,这样它的变化就不会影响到其他元素在内存中多次操作节点,完成后再添加到文档中去如果要对一个元素进行复杂的操作,可以将其display属性设置为none使其隐藏,待操作完成后再显示在需要经常获取那些引起浏览器重排的属性值时,要缓存到变量如何在网页中添加空格? 在HTML代码中输入&nbsp;二. 如何在网页中显示代码?对于单行代码,使用标签<code>代码</code>对于多行代码,使用标签<pre></pre> (被包围在pre元素中的文本通常会保留空格和换行符)26.使用mailto在网页中链接Email地址?(1)a标签有一个作用是可以链接Email地址,使用mailto能让访问者便捷想网站管理者发送电子邮件(2)如果mailto后面同时又多个参数的话,第一个参数必须以?开头,后面的参数每一个都以&分隔三. form表单当前页面无刷新提交?使用target属性取值为iframe元素的name属性值。具体如下:在当前页面建一个iframe并隐藏(display:none)给这个iframe取名(name=“id_iframe”)设置form表单的target属性(target=“id_iframe”)提交表单,就是无刷新iframe标签一. 优点iframe能够把嵌入的页面展示出来,如果有多个网页引用iframe,只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用重载页面时不需要重载整个页面,只需要重载页面中的一个框架页,减少了数据的传输,增加了网页的下载速度方便制作导航栏二. 缺点会产生很多页面,不利于管理浏览器的前进/后退按钮无效无法被一些搜索引擎索引到,现在搜索引擎爬虫还不能很好的处理iframe中的内容,所以不利于SEO多数小型的移动设备无法显示框架,兼容性差多框架的页面会增加服务器的http请求,对于大型网站是不可取的综上,目前框架中的所有优点完全可以使用Ajax实现,因此不推荐使用框架ES6一、问:ES6是什么,为什么要学习它,不学习ES6会怎么样?答: ES6是新一代的JS语言标准,对分JS语言核心内容做了升级优化,规范了JS使用标准,新增了JS原生方法,使得JS使用更加规范,更加优雅,更适合大型应用的开发。学习ES6是成为专业前端正规军的必经之路。不学习ES6也可以写代码打鬼子,但是最多只能当个游击队长。二、问:ES5、ES6和ES2015有什么区别?答: ES2015特指在2015年发布的新一代JS语言标准,ES6泛指下一代JS语言标准,包含ES2015、ES2016、ES2017、ES2018等。现阶段在绝大部分场景下,ES2015默认等同ES6。ES5泛指上一代语言标准。ES2015可以理解为ES5和ES6的时间分界线。三、问:babel是什么,有什么作用?答:babel是一个 ES6 转码器,可以将 ES6 代码转为 ES5 代码,以便兼容那些还没支持ES6的平台。四、问:let有什么用,有了var为什么还要用let?答: 在ES6之前,声明变量只能用var,var方式声明变量其实是很不合理的,准确的说,是因为ES5里面没有块级作用域是很不合理的,甚至可以说是一个语言层面的bug(这也是很多c++、java开发人员看不懂,也瞧不起JS语言的劣势之一)。没有块级作用域回来带很多难以理解的问题,比如for循环var变量泄露,变量覆盖等问题。let 声明的变量拥有自己的块级作用域,且修复了var声明变量带来的变量提升问题。五、问:举一些ES6对String字符串类型做的常用升级优化?答:1、优化部分:ES6新增了字符串模板,在拼接大段字符串时,用反斜杠(`)取代以往的字符串相加的形式,能保留所有空格和换行,使得字符串拼接看起来更加直观,更加优雅。2、升级部分:ES6在String原型上新增了includes()方法,用于取代传统的只能用indexOf查找包含字符的方法(indexOf返回-1表示没查到不如includes方法返回false更明确,语义更清晰), 此外还新增了startsWith(), endsWith(), padStart(),padEnd(),repeat()等方法,可方便的用于查找,补全字符串。六、问:举一些ES6对Array数组类型做的常用升级优化?答:1、优化部分:a. 数组解构赋值。ES6可以直接以let [a,b,c] = [1,2,3]形式进行变量赋值,在声明较多变量时,不用再写很多let(var),且映射关系清晰,且支持赋默认值。b. 扩展运算符。ES6新增的扩展运算符(…)(重要),可以轻松的实现数组和松散序列的相互转化,可以取代arguments对象和apply方法,轻松获取未知参数个数情况下的参数集合。(尤其是在ES5中,arguments并不是一个真正的数组,而是一个类数组的对象,但是扩展运算符的逆运算却可以返回一个真正的数组)。扩展运算符还可以轻松方便的实现数组的复制和解构赋值(let a = [2,3,4]; let b = [...a])。2、升级部分:ES6在Array原型上新增了find()方法,用于取代传统的只能用indexOf查找包含数组项目的方法,且修复了indexOf查找不到NaN的bug([NaN].indexOf(NaN) === -1).此外还新增了copyWithin(), includes(), fill(),flat()等方法,可方便的用于字符串的查找,补全,转换等。七、问:举一些ES6对Number数字类型做的常用升级优化?答:1、优化部分:ES6在Number原型上新增了isFinite(), isNaN()方法,用来取代传统的全局isFinite(), isNaN()方法检测数值是否有限、是否是NaN。ES5的isFinite(), isNaN()方法都会先将非数值类型的参数转化为Number类型再做判断,这其实是不合理的,最造成isNaN('NaN') === true的奇怪行为–'NaN’是一个字符串,但是isNaN却说这就是NaN。而Number.isFinite()和Number.isNaN()则不会有此类问题(Number.isNaN('NaN') === false)。(isFinite()同上)2、升级部分:ES6在Math对象上新增了Math.cbrt(),trunc(),hypot()等等较多的科学计数法运算方法,可以更加全面的进行立方根、求和立方根等等科学计算。八、问:举一些ES6对Object类型做的常用升级优化?(重要)答:1、优化部分:a. 对象属性变量式声明。ES6可以直接以变量形式声明对象属性或者方法,。比传统的键值对形式声明更加简洁,更加方便,语义更加清晰。let [apple, orange] = ['red appe', 'yellow orange'];
let myFruits = {apple, orange}; // let myFruits = {apple: 'red appe', orange: 'yellow orange'};
复制代码尤其在对象解构赋值(见优化部分b.)或者模块输出变量时,这种写法的好处体现的最为明显:let {keys, values, entries} = Object;
let MyOwnMethods = {keys, values, entries}; // let MyOwnMethods = {keys: keys, values: values, entries: entries}
复制代码可以看到属性变量式声明属性看起来更加简洁明了。方法也可以采用简洁写法:let es5Fun = {
method: function(){}
};
let es6Fun = {
method(){}
}
复制代码b. 对象的解构赋值。 ES6对象也可以像数组解构赋值那样,进行变量的解构赋值:let {apple, orange} = {apple: 'red appe', orange: 'yellow orange'};
复制代码c. 对象的扩展运算符(…)。 ES6对象的扩展运算符和数组扩展运算符用法本质上差别不大,毕竟数组也就是特殊的对象。对象的扩展运算符一个最常用也最好用的用处就在于可以轻松的取出一个目标对象内部全部或者部分的可遍历属性,从而进行对象的合并和分解。let {apple, orange, ...otherFruits} = {apple: 'red apple', orange: 'yellow orange', grape: 'purple grape', peach: 'sweet peach'};
// otherFruits {grape: 'purple grape', peach: 'sweet peach'}
// 注意: 对象的扩展运算符用在解构赋值时,扩展运算符只能用在最有一个参数(otherFruits后面不能再跟其他参数)
let moreFruits = {watermelon: 'nice watermelon'};
let allFruits = {apple, orange, ...otherFruits, ...moreFruits};
复制代码d. super 关键字。ES6在Class类里新增了类似this的关键字super。同this总是指向当前函数所在的对象不同,super关键字总是指向当前函数所在对象的原型对象。2、升级部分:a. ES6在Object原型上新增了is()方法,做两个目标对象的相等比较,用来完善’=‘方法。’='方法中NaN === NaN //false其实是不合理的,Object.is修复了这个小bug。(Object.is(NaN, NaN) // true)b. ES6在Object原型上新增了assign()方法,用于对象新增属性或者多个对象合并。const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
复制代码注意: assign合并的对象target只能合并source1、source2中的自身属性,并不会合并source1、source2中的继承属性,也不会合并不可枚举的属性,且无法正确复制get和set属性(会直接执行get/set函数,取return的值)。c. ES6在Object原型上新增了getOwnPropertyDescriptors()方法,此方法增强了ES5中getOwnPropertyDescriptor()方法,可以获取指定对象所有自身属性的描述对象。结合defineProperties()方法,可以完美复制对象,包括复制get和set属性。d. ES6在Object原型上新增了getPrototypeOf()和setPrototypeOf()方法,用来获取或设置当前对象的prototype对象。这个方法存在的意义在于,ES5中获取设置prototype对像是通过__proto__属性来实现的,然而__proto__属性并不是ES规范中的明文规定的属性,只是浏览器各大产商“私自”加上去的属性,只不过因为适用范围广而被默认使用了,再非浏览器环境中并不一定就可以使用,所以为了稳妥起见,获取或设置当前对象的prototype对象时,都应该采用ES6新增的标准用法。d. ES6在Object原型上还新增了Object.keys(),Object.values(),Object.entries()方法,用来获取对象的所有键、所有值和所有键值对数组。九、问:举一些ES6对Function函数类型做的常用升级优化?(重要)答:1、优化部分:a. 箭头函数**(核心)**。箭头函数是ES6核心的升级项之一,箭头函数里没有自己的this,这改变了以往JS函数中最让人难以理解的this运行机制。主要优化点:Ⅰ. 箭头函数内的this指向的是函数定义时所在的对象,而不是函数执行时所在的对象。ES5函数里的this总是指向函数执行时所在的对象,这使得在很多情况下this的指向变得很难理解,尤其是非严格模式情况下,this有时候会指向全局对象,这甚至也可以归结为语言层面的bug之一。ES6的箭头函数优化了这一点,它的内部没有自己的this,这也就导致了this总是指向上一层的this,如果上一层还是箭头函数,则继续向上指,直到指向到有自己this的函数为止,并作为自己的this。Ⅱ. 箭头函数不能用作构造函数,因为它没有自己的this,无法实例化。Ⅲ. 也是因为箭头函数没有自己的this,所以箭头函数 内也不存在arguments对象。(可以用扩展运算符代替)b. 函数默认赋值。ES6之前,函数的形参是无法给默认值得,只能在函数内部通过变通方法实现。ES6以更简洁更明确的方式进行函数默认赋值。function es6Fuc (x, y = 'default') {
console.log(x, y);
}
es6Fuc(4) // 4, default
复制代码2、升级部分:ES6新增了双冒号运算符,用来取代以往的bind,call,和apply。(浏览器暂不支持,Babel已经支持转码)foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
复制代码十、问:Symbol是什么,有什么作用?答: Symbol是ES6引入的第七种原始数据类型(说法不准确,应该是第七种数据类型,Object不是原始数据类型之一,已更正),所有Symbol()生成的值都是独一无二的,可以从根本上解决对象属性太多导致属性名冲突覆盖的问题。对象中Symbol()属性不能被for…in遍历,但是也不是私有属性。十一、问:Set是什么,有什么作用?答: Set是ES6引入的一种类似Array的新的数据结构,Set实例的成员类似于数组item成员,区别是Set实例的成员都是唯一,不重复的。这个特性可以轻松地实现数组去重。十二、问:Map是什么,有什么作用?答: Map是ES6引入的一种类似Object的新的数据结构,Map可以理解为是Object的超集,打破了以传统键值对形式定义对象,对象的key不再局限于字符串,也可以是Object。可以更加全面的描述对象的属性。十三、问:Proxy是什么,有什么作用?答: Proxy是ES6新增的一个构造函数,可以理解为JS语言的一个代理,用来改变JS默认的一些语言行为,包括拦截默认的get/set等底层方法,使得JS的使用自由度更高,可以最大限度的满足开发者的需求。比如通过拦截对象的get/set方法,可以轻松地定制自己想要的key或者value。下面的例子可以看到,随便定义一个myOwnObj的key,都可以变成自己想要的函数。function createMyOwnObj() {
//想把所有的key都变成函数,或者Promise,或者anything
return new Proxy({}, {
get(target, propKey, receiver) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomBoolean = Math.random() > 0.5;
let Message;
if (randomBoolean) {
Message = `你的${propKey}运气不错,成功了`;
resolve(Message);
} else {
Message = `你的${propKey}运气不行,失败了`;
reject(Message);
}
}, 1000);
});
}
});
}
let myOwnObj = createMyOwnObj();
myOwnObj.hahaha.then(result => {
console.log(result) //你的hahaha运气不错,成功了
}).catch(error => {
console.log(error) //你的hahaha运气不行,失败了
})
myOwnObj.wuwuwu.then(result => {
console.log(result) //你的wuwuwu运气不错,成功了
}).catch(error => {
console.log(error) //你的wuwuwu运气不行,失败了
})
复制代码十四、问:Reflect是什么,有什么作用?答: Reflect是ES6引入的一个新的对象,他的主要作用有两点,一是将原生的一些零散分布在Object、Function或者全局函数里的方法(如apply、delete、get、set等等),统一整合到Reflect上,这样可以更加方便更加统一的管理一些原生API。其次就是因为Proxy可以改写默认的原生API,如果一旦原生API别改写可能就找不到了,所以Reflect也可以起到备份原生API的作用,使得即使原生API被改写了之后,也可以在被改写之后的API用上默认的API。十五、问:Promise是什么,有什么作用?答: Promise是ES6引入的一个新的对象,他的主要作用是用来解决JS异步机制里,回调机制产生的“回调地狱”。它并不是什么突破性的API,只是封装了异步回调形式,使得异步回调可以写的更加优雅,可读性更高,而且可以链式调用。十六、问:Iterator是什么,有什么作用?(重要)答: Iterator是ES6中一个很重要概念,它并不是对象,也不是任何一种数据类型。因为ES6新增了Set、Map类型,他们和Array、Object类型很像,Array、Object都是可以遍历的,但是Set、Map都不能用for循环遍历,解决这个问题有两种方案,一种是为Set、Map单独新增一个用来遍历的API,另一种是为Set、Map、Array、Object新增一个统一的遍历API,显然,第二种更好,ES6也就顺其自然的需要一种设计标准,来统一所有可遍历类型的遍历方式。Iterator正是这样一种标准。或者说是一种规范理念。就好像JavaScript是ECMAScript标准的一种具体实现一样,Iterator标准的具体实现是Iterator遍历器。Iterator标准规定,所有部署了key值为[Symbol.iterator],且[Symbol.iterator]的value是标准的Iterator接口函数(标准的Iterator接口函数: 该函数必须返回一个对象,且对象中包含next方法,且执行next()能返回包含value/done属性的Iterator对象)的对象,都称之为可遍历对象,next()后返回的Iterator对象也就是Iterator遍历器。//obj就是可遍历的,因为它遵循了Iterator标准,且包含[Symbol.iterator]方法,方法函数也符合标准的Iterator接口规范。
//obj.[Symbol.iterator]() 就是Iterator遍历器
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};
复制代码ES6给Set、Map、Array、String都加上了[Symbol.iterator]方法,且[Symbol.iterator]方法函数也符合标准的Iterator接口规范,所以Set、Map、Array、String默认都是可以遍历的。//Array
let array = ['red', 'green', 'blue'];
array[Symbol.iterator]() //Iterator遍历器
array[Symbol.iterator]().next() //{value: "red", done: false}
//String
let string = '1122334455';
string[Symbol.iterator]() //Iterator遍历器
string[Symbol.iterator]().next() //{value: "1", done: false}
//set
let set = new Set(['red', 'green', 'blue']);
set[Symbol.iterator]() //Iterator遍历器
set[Symbol.iterator]().next() //{value: "red", done: false}
//Map
let map = new Map();
let obj= {map: 'map'};
map.set(obj, 'mapValue');
map[Symbol.iterator]().next() {value: Array(2), done: false}
复制代码十七、问:for…in 和for…of有什么区别?答: 如果看到问题十六,那么就很好回答。问题十六提到了ES6统一了遍历标准,制定了可遍历对象,那么用什么方法去遍历呢?答案就是用for…of。ES6规定,有所部署了载了Iterator接口的对象(可遍历对象)都可以通过for…of去遍历,而for…in仅仅可以遍历对象。这也就意味着,数组也可以用for…of遍历,这极大地方便了数组的取值,且避免了很多程序用for…in去遍历数组的恶习。上面提到的扩展运算符本质上也就是for…of循环的一种实现。十八、Generator函数是什么,有什么作用?答: 如果说JavaScript是ECMAScript标准的一种具体实现、Iterator遍历器是Iterator的具体实现,那么Generator函数可以说是Iterator接口的具体实现方式。执行Generator函数会返回一个遍历器对象,每一次Generator函数里面的yield都相当一次遍历器对象的next()方法,并且可以通过next(value)方法传入自定义的value,来改变Generator函数的行为。Generator函数可以通过配合Thunk 函数更轻松更优雅的实现异步编程和控制流管理。十九、async函数是什么,有什么作用?答: async函数可以理解为内置自动执行器的Generator函数语法糖,它配合ES6的Promise近乎完美的实现了异步编程解决方案。二十、Class、extends是什么,有什么作用?答: ES6 的class可以看作只是一个ES5生成实例对象的构造函数的语法糖。它参考了java语言,定义了一个类的概念,让对象原型写法更加清晰,对象实例化更像是一种面向对象编程。Class类可以通过extends实现继承。它和ES5构造函数的不同点:a. 类的内部定义的所有方法,都是不可枚举的。///ES5
function ES5Fun (x, y) {
this.x = x;
this.y = y;
}
ES5Fun.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
}
var p = new ES5Fun(1, 3);
p.toString();
Object.keys(ES5Fun.prototype); //['toString']
//ES6
class ES6Fun {
constructor (x, y) {
this.x = x;
this.y = y;
}
toString () {
return '(' + this.x + ', ' + this.y + ')';
}
}
Object.keys(ES6Fun.prototype); //[]
复制代码b.ES6的class类必须用new命令操作,而ES5的构造函数不用new也可以执行。c.ES6的class类不存在变量提升,必须先定义class之后才能实例化,不像ES5中可以将构造函数写在实例化之后。d.ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。二十一、module、export、import是什么,有什么作用?答: module、export、import是ES6用来统一前端模块化方案的设计思路和实现方案。export、import的出现统一了前端模块化的实现方案,整合规范了浏览器/服务端的模块化方法,用来取代传统的AMD/CMD、requireJS、seaJS、commondJS等等一系列前端模块不同的实现方案,使前端模块化更加统一规范,JS也能更加能实现大型的应用程序开发。import引入的模块是静态加载(编译阶段加载)而不是动态加载(运行时加载)。import引入export导出的接口值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。二十二、日常前端代码开发中,有哪些值得用ES6去改进的编程优化或者规范?答:1、常用箭头函数来取代var self = this;的做法。2、常用let取代var命令。3、常用数组/对象的结构赋值来命名变量,结构更清晰,语义更明确,可读性更好。4、在长字符串多变量组合场合,用模板字符串来取代字符串累加,能取得更好地效果和阅读体验。5、用Class类取代传统的构造函数,来生成实例化对象。6、在大型应用开发中,要保持module模块化开发思维,分清模块之间的关系,常用import、export方法。VUE一、对于MVVM的理解?MVVM 是 Model-View-ViewModel 的缩写。Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。View 代表UI 组件,它负责将数据模型转化成UI 展现出来。ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。二、Vue的生命周期beforeCreate(创建前) 在数据观测和初始化事件还未开始created(创建后) 完成数据观测,属性和方法的运算,初始化事件,el属性还没有显示出来 **beforeMount**(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。 **mounted**(载入后) 在el 被新创建的 vm.el属性还没有显示出来∗∗beforeMount∗∗(载入前)在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。∗∗mounted∗∗(载入后)在el被新创建的vm.el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。1.什么是vue生命周期?答: Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。2.vue生命周期的作用是什么?答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。3.vue生命周期总共有几个阶段?答:它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。4.第一次页面加载会触发哪几个钩子?答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。5.DOM 渲染在 哪个周期中就已经完成?答:DOM 渲染在 mounted 中就已经完成了。三、 Vue实现数据双向绑定的原理:Object.defineProperty()vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过**Object.defineProperty()**来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。js实现简单的双向绑定<body>
<div id="app">
<input type="text" id="txt">
<p id="show"></p>
</div>
</body>
<script type="text/javascript">
var obj = {}
Object.defineProperty(obj, 'txt', {
get: function () {
return obj
},
set: function (newValue) {
document.getElementById('txt').value = newValue
document.getElementById('show').innerHTML = newValue
}
})
document.addEventListener('keyup', function (e) {
obj.txt = e.target.value
})
</script>四、Vue组件间的参数传递1.父组件与子组件传值父组件传给子组件:子组件通过props方法接受数据;子组件传给父组件:$emit方法传递参数2.非父子组件间的数据传递,兄弟组件传值eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。(虽然也有不少人推荐直接用VUEX,具体来说看需求咯。技术只是手段,目的达到才是王道。)五、Vue的路由实现:hash模式 和 history模式**hash模式:**在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。**history模式:**history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”六、Vue与Angular以及React的区别?(版本在不断更新,以下的区别有可能不是很正确。我工作中只用到vue,对angular和react不怎么熟)1.与AngularJS的区别相同点:都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。不同点:AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。2.与React的区别相同点:React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。不同点:React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。七、vue路由的钩子函数首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。beforeEach主要有3个参数to,from,next:to:route即将进入的目标路由对象,from:route当前导航正要离开的路由next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。八、vuex是什么?怎么使用?哪种功能场景使用它?只用来读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。在main.js引入store,注入。新建了一个目录store,…… export 。场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-biCZCx5s-1611887935896)(C:\Users\zjx\Desktop\桌面文件\web面试\Vuepng.png)]stateVuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。mutationsmutations定义的方法动态修改Vuex 的 store 中的状态或数据。getters类似vue的计算属性,主要用来过滤一些数据。actionactions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。const store = new Vuex.Store({ //store实例
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})modules项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
})九、vue-cli如何新增自定义指令?1.创建局部指令var app = new Vue({
el: '#app',
data: {
},
// 创建指令(可以多个)
directives: {
// 指令名称
dir1: {
inserted(el) {
// 指令中第一个参数是当前使用指令的DOM
console.log(el);
console.log(arguments);
// 对DOM进行操作
el.style.width = '200px';
el.style.height = '200px';
el.style.background = '#000';
}
}
}
})2.全局指令Vue.directive('dir2', {
inserted(el) {
console.log(el);
}
})3.指令的使用<div id="app">
<div v-dir1></div>
<div v-dir2></div>
</div>十、vue如何自定义一个过滤器?html代码:<div id="app">
<input type="text" v-model="msg" />
{{msg| capitalize }}
</div>JS代码:var vm=new Vue({
el:"#app",
data:{
msg:''
},
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})全局定义过滤器Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})过滤器接收表达式的值 (msg) 作为第一个参数。capitalize 过滤器将会收到 msg的值作为第一个参数。十一、对keep-alive 的了解?keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。使用方法<keep-alive include='include_components' exclude='exclude_components'>
<component>
<!-- 该组件是否缓存取决于include和exclude属性 -->
</component>
</keep-alive>参数解释include - 字符串或正则表达式,只有名称匹配的组件会被缓存exclude - 字符串或正则表达式,任何名称匹配的组件都不会被缓存include 和 exclude 的属性允许组件有条件地缓存。二者都可以用“,”分隔字符串、正则表达式、数组。当使用正则或者是数组时,要记得使用v-bind 。使用示例<!-- 逗号分隔字符串,只有组件a与b被缓存。 -->
<keep-alive include="a,b">
<component></component>
</keep-alive>
<!-- 正则表达式 (需要使用 v-bind,符合匹配规则的都会被缓存) -->
<keep-alive :include="/a|b/">
<component></component>
</keep-alive>
<!-- Array (需要使用 v-bind,被包含的都会被缓存) -->
<keep-alive :include="['a', 'b']">
<component></component>
</keep-alive>十二、一句话就能回答的面试题1.css只在当前组件起作用答:在style标签中写入scoped即可 例如:2.v-if 和 v-show 区别答:v-if按照条件是否渲染,v-show是display的block或none;3.route和route和router的区别答:$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。4.vue.js的两个核心是什么?答:数据驱动、组件系统5.vue几种常用的指令答:v-for 、 v-if 、v-bind、v-on、v-show、v-else6.vue常用的修饰符?答:.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;.capture: 事件侦听,事件发生的时候会调用7.v-on 可以绑定多个方法吗?答:可以8.vue中 key 值的作用?答:当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM。9.什么是vue的计算属性?答:在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。好处:①使得数据处理结构清晰;②依赖于数据,数据更新,处理结果自动更新;③计算属性内部this指向vm实例;④在template调用时,直接写计算属性名即可;⑤常用的是getter方法,获取数据,也可以使用set方法改变数据;⑥相较于methods,不管依赖的数据变不变,methods都会重新计算,但是依赖数据不变的时候computed从缓存中获取,不会重新计算。10.vue等单页面应用及其优缺点答:优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。11.怎么定义 vue-router 的动态路由? 怎么获取传过来的值答:在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 router 对象的 params.id 获取。算法面试1. 字符串回文判断思路:回文是指把相同的词汇或句子,在下文中调换位置或颠倒过来,产生首尾回环的情景,叫做回文,也叫回环。将一个字符串首尾倒序排列,如果与原字符串相等,则这个字符串回文。<script type="text/javascript">
var str1 = 'abcdefgh';
var str2 = 'abcdcba';
function plalindrome(str){
return str == str.split('').reverse().join('');
}
console.log(plalindrome(str1));//false
console.log(plalindrome(str2));//true
</script>2. 数组去重思路:利用indexOf()a方法,在遍历原数组,若里面的元素第一次出现,则放在数组arr1中,遍历完之后,arr1中存放的是无重复的新数组<script type="text/javascript">
var arr = [2,4,2,2,5,6,7,8,9,9,9];
function unique(arr){
var arr1 = [];
for (var i = 0;i < arr.length;i ++){
if(arr1.indexOf(arr[i]) == -1){
arr1.push(arr[i]);
}
}
return arr1;
}
console.log(unique(arr));//[2, 4, 5, 6, 7, 8, 9]
</script>3. 统计一个字符串中出现最多的字母思路:在另外一个数组存放原数组每个元素出现的位置次数,且次数跟存放不重复数组的下标对应,然后取出最多的次数,对应的下标就是不重复数组里面那个出现次数最多的元素的下标<script type="text/javascript">
var str1 = "jhadfgskjfajhdewqe";
var arr1 = str1.split('');
console.log(arr1);
function MostUnit(){
var arrA = [];
var arrB = [];
for(var i = 0 ;i <arr1.length; i ++){
if(arrA.indexOf(arr1[i])==-1){
arrA.push(arr1[i]);
arrB.push(1);
}else {
arrB[arrA.indexOf(arr1[i])] ++;
}
}
console.log(arrB)
console.log(arrA[arrB.indexOf(Math.max.apply(Math,arrB))]);
}
MostUnit();//j
</script>4. 冒泡排序:比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较<script type="text/javascript">
var arr1 = [2,3,45,64,321,3,21,321,31,999];
function bubbleSort(arr) {
for(var i = 0 ;i < arr1.length-1 ;i ++){
for(var j = 0; j < arr1.length - i - 1 ;j ++){
if(arr[j]>arr[j+1]) {
let tem = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tem;
}
}
}
return arr;
}
console.log(bubbleSort(arr1));//[2, 3, 3, 21, 31, 45, 64, 321, 321, 999]
</script>5. 快速排序:思路:算法参考某个元素值,将小于它的值,放到左数组中,大于它的值的元素就放到右数组中,然后递归进行上一次左右数组的操作,返回合并的数组就是已经排好顺序的数组了。<script type="text/javascript">
var arr1 = [1,4,765,86,53,87,53,32,6,64,2,3,767,34,1,4,35,6];
function quickSort(arr){
if(arr.length <= 1){
return arr;
}
var leftArr = [];
var rightArr = [];
var q = arr[0];
for(var i = 1;i < arr.length; i++) {
if(arr[i]>q) {
rightArr.push(arr[i]);
}else{
leftArr.push(arr[i]);
}
}
return [].concat(quickSort(leftArr),[q],quickSort(rightArr));
}
console.log(quickSort(arr1));//[1,4,765,86,53,87,53,32,6,64,2,3,767,34,1,4,35,6]
</script>6. 不利用第三方变量的情况下交换两个变量的值思路:利用两个元素的差值进行计算<script type="text/javascript">
var a = 10;
var b = 12;
function swap (a,b) {
b = b - a;
a = a + b;
b = a - b;
return [a,b]
}
console.log(swap(a,b));
</script>7. 求一个数组中最大数和最小数的差值<script type="text/javascript">
var arr1 = [2,44,3,-12,43,5,8,67,54,32,-211];
var max = Math.max.apply(Math,arr1);
var min = Math.min.apply(Math,arr1);
console.log(max-min);//278
</script>8. 生成指定长度的随机字符串思路:charAt()方法,获取元素下标<script type="text/javascript">
function randomString(n){
var str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz9876543210"
var str2 = "";
for (var i = 0; i < n ; i ++){
str2 += str1.charAt(Math.floor(Math.random()*str1.length));
}
return str2;
}
console.log(randomString(5));
</script>9. 获取一个DOM节点下面包含某个class名的所有节点<div id="text">
<div class="cs"></div>
<div class="as"></div>
<p class="cs"></p>
</div>
<script type="text/javascript">
function getClass(node,classname) {
if(node.getElementsByClassName) {
return node.getElementsByClassName(classname);
//如果存在该标签 就返回
} else {
var elems = node.getElementsByTagName(node),
defualt = [];
for (var i = 0; i < elems.length; i++) {
//遍历所有标签
if(elems[i].className.indexOf(classname) != -1) {
//查找相应类名的标签
defualt[defualt.length] = elems[i];
}
}
return defualt;
}
}
var text = document.getElementById('text'),
cs = getClass(text,'cs');
console.log(cs);//[div.cs, p.cs]
</script>10. 二叉树一说到二叉树我们肯定会问,什么是二叉树,二叉树是个啥子东东,拿来有啥子用嘛,我们为啥子要学习它嘛? 如果当初你在学习二叉树的时候你没有问过自己这些问题,那么你对它的了解也仅仅也只是了解。那我们现在来说说什么是二叉树,二叉树就是一种数据结构, 它的组织关系就像是自然界中的树一样。官方语言的定义是:是一个有限元素的集合,该集合或者为空、或者由一个称为根的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成。至于为啥子要学习它,妈妈总是说,孩子,等你长大了就明白了。11. 二叉树的性质性质1:二叉树第i层上的节点数目最多为2i-1(i≥1);性质2:深度为k的二叉树至多有2k-1个结点(k≥1)。性质3: 在任意-棵二叉树中,若叶子结点(即度为0的结点)的个数为n0,度为1的结点数为n1,度为2的结点数为n2,则no=n2+1。12. 二叉树的存储结构与构建二叉树的存储方式有两种,一种顺序存储,比如:var binaryTree = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘h’, ‘i’]; 这就是一颗二叉树,假设binaryTree[i]是二叉树的一个节点,那么它的左孩子节点 leftChild = binaryTree[i2+1]那 么相应的右孩子节点 rightChild = binaryTree[i2+2]; 一般情况下顺序存储的这种结构用的较少,另外一种存储方式就是链式存储,下面我会用代码来详细描述二叉树式 结构的构建与存储方式,构建二叉树也有两种方式一种是递归方式构建,这种很简单,另一种是非递归方法构建,这种呢相对于前一种复杂一点点,不过也不用担心,我在 代码中加上详细的注释,一步一步的走下去。我们现在就以26个英文字母来构建二叉树var charecters = [‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’];在构建二叉树之前我们会用到一个节点对象,节点对象如下:(注意:关于javascript的面向对象,原型,语法特点我会放在javascript语言知识点这个系列)/*
*二叉树的节点对象
*/
function Node() {
this.text = ''; //节点的文本
this.leftChild = null; //节点的左孩子引用
this.rightChild = null; //节点右孩子引用
}13. 递归构建二叉树在构建好二叉树节点之后我们紧接着用递归来构建二叉树var charecters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
function buildTree(node, i) {
var leftIndex = 2*i+1, //左孩子节点的索引
rightIndex = 2*i+2; //右孩子节点的索引
if(leftIndex < charecters.length) { //判断索引的长度是否超过了charecters数组的大小
var childNode = new Node(); //创建一个新的节点对象
childNode.text = charecters[leftIndex]; //给节点赋值
node.leftChild = childNode; //给当前节点node加入左孩子节点
buildTree(childNode, leftIndex); //递归创建左孩子
}
if(rightIndex < charecters.length) { //下面注释参照上面的构建左孩子的节点
var childNode = new Node();
childNode.text = charecters[rightIndex];
node.rightChild = childNode;
buildTree(childNode, rightIndex);
}
}
//下面构造二叉树
var node = new Node();
node.text = charecters[0];
buildTree(node, 0); //索引i是从0开始构建14. 非递归构建二叉树下面是以非递归方式构建二叉树:var root;
function createBinaryTree() {
var len = charecters.length, //数组的长度
index = 0, //索引从0开始
nodes = new Array(); //创建一个临时数组,用于存放二叉树节点
//循环创建二叉树节点存放到数组中
for (var i = 0 ; i < charecters.length ; i++) {
var node = new Node();
node.text = charecters[i];
nodes.push(node);
}
//循环建立二叉树子节点的引用
while(index < len) {
var leftIndex = 2*index+1, //当前节点左孩子索引
rightIndex = 2*index+2; //当前节点右孩子索引
//给当前节点添加左孩子
nodes[index].leftChild = nodes[leftIndex];
//给当前节点添加右孩子
nodes[index].rightChild = nodes[rightIndex];
index++;
}
root = nodes[0];
}15.二叉树的三种遍历好了,现在我们已经成功构建了二叉树的链式结构,在构建了二叉树的链式结构后我们进入二叉树的最基本的遍历了,遍历有三种最基本的遍历,我不说想必大家都知道,先序遍历,中序遍历和后续遍历。虽然这三种遍历递归方式都比较简单,但非递归方式就不是那么容易了,当时我在实现的时候都卡了半天,真的是说起来容易做起来难啊,在实现遍历前我们首先要来实现的是栈,因为在非递归遍历的时候会用到栈,那到底什么是栈呢,这里我就简单介绍下吧,有兴趣的朋友可以去维基百科有权威的定义,栈和队列也是一种数据结构,栈存放数据的时候是先进先出,而队列是先进后出。16.实现栈的对象下面用javascript来实现栈的对象function Stack() {
var stack = new Array(); //存放栈的数组
//压栈
this.push = function(o) {
stack.push(o);
};
//出栈
this.pop = function() {
var o = stack[stack.length-1];
stack.splice(stack.length-1, 1);
return o;
};
//检查栈是否为空
this.isEmpty = function() {
if(stack.length <= 0) {
return true;
}
else {
return false;
}
};
}
//使用方式如下
var stack = new Stack();
stack.push(1); //现在栈中有一个元素
stack.isEmpty(); //false , 栈不为空
alert(stack.pop()); //出栈, 打印1
stack.isEmpty(); //true, 此时栈为空,因为在调用了stack.pop()之后元素出栈了,所以为空17. 先序遍历在实现了栈对象以后我们首先来进行先序遍历的递归方式function firstIteration(node) {
if(node.leftChild) { //判断当前节点是否有左孩子
firstIteration(node.leftChild); //递归左孩子
}
if(node.rightChild) { //判断当前节点是否有右孩子
firstIteration(node.rightChild); //递归右孩子
}
}
//递归遍历二叉树
firstIteration(root);18. 先序遍历的非递归方式上面的代码大家可以在firstIteration()方法中加个alert()函数来验证是否正确。那么下面就要说说先序遍历的非递归方式,遍历思想是这样的:先访问根节点在访问左节 点, 最后访问右节点。从根节点一直往下访问找左孩子节点,直到最后一个左孩子节点(将这条路径保存到栈中),然后再访问最后一个左孩子的兄弟节点(右孩子节点),之后回溯到上一层(将栈中的元素取出 就是出栈),又开始从该节点(回溯到上一层的节点)一直往下访问找左孩子节点… 直到栈中的元素为空,循环结束。function notFirstIteration(node) {
var stack = new Stack(), //开辟一个新的栈对象
resultText = ''; //存放非递归遍历之后的字母顺序
stack.push(root); //这个root在上面非递归方式构建二叉树的时候已经构建好的
var node = root;
resultText += node.text;
while(!stack.isEmpty()) {
while(node.leftChild) { //判断当前节点是否有左孩子节点
node = node.leftChild; //取当前节点的左孩子节点
resultText += node.text; //访问当前节点
stack.push(node); //将当前节点压入栈中
}
stack.pop(); //出栈
node = stack.pop().rightChild; //访问当前节点的兄弟节点(右孩子节点)
if(node) { //当前节点的兄弟节点不为空
resultText += node.text; //访问当前节点
stack.push(node); //将当前节点压入栈中
}
else { //当前节点的兄弟节点为空
node = stack.pop(); //在回溯到上一层
}
}
}
//非递归先序遍历
notFirstIteration(root);19.中序遍历只要把思路理清楚了现实起来其实还是挺容易的,只要我们熟悉了一种二叉树的非递归遍历方式,其他几种非递归方式就容易多了,照着葫芦画瓢,下面是中序遍历的递归 方式,中序遍历的思想是:先访问左孩子节点,在访问根节点,最后访问右节点var strText = "";
function secondIteration(node) {
//访问左节点
if(node.leftChild) {
if(node.leftChild.leftChild) {
secondIteration(node.leftChild);
}
else {
strText += node.leftChild.text;
}
}
//访问根节点
strText += node.text;
//访问右节点
if(node.rightChild) {
if(node.rightChild.leftChild) {
secondIteration(node.rightChild);
}
else {
strText += node.rightChild.text;
}
}
}
secondIteration(root);
alert(strText);20. 中序遍历的非递归方式思想是:1. 从根节点一直往下找左孩子节点,直到找到最后一个左孩子节点(用栈将此路径保存,但不访问)2.访问最后一个左孩子节点,然后再 访问根节点(要先弹出栈,就是在栈中取上一层节点)3.在访问当前节点(最后一个左孩子节点)的兄弟节点(右孩子节点),这里要注意如果兄弟节点是一个叶节点就直 接访问,否则是兄弟节点是一颗子树的话不能马上访问,要先来重复 1, 2,3步骤, 直到栈为空,循环结束function notSecondIteration() {
var resultText = '',
stack = new Stack(),
node = root;
stack.push(node);
while(!stack.isEmpty()) {
//从根节点一直往下找左孩子节点直到最后一个左孩子节点,然后保存在栈中
while(node.leftChild) {
node = node.leftChild;
stack.push(node);
}
//弹出栈
var tempNode = stack.pop();
//访问临时节点
resultText += tempNode.text;
if(tempNode.rightChild) {
node = tempNode.rightChild;
stack.push(node);
}
}
alert(resultText);
}21. 后续遍历最后就还剩下一种遍历方式,二叉树的后续遍历,后续遍历的思想是:先访问左孩子节点,然后在访问右孩子节点,最后访问根节点22. 后续遍历的递归方式var strText = '';
function lastIteration(node) {
//首先访问左孩子节点
if(node.leftChild) {
if(node.leftChild.leftChild) {
lastIteration(node.leftChild);
}
else {
strText += node.leftChild.text;
}
}
//然后再访问右孩子节点
if(node.rightChild) {
if(node.rightChild.rightChild) {
lastIteration(node.rightChild);
}
else {
strText += node.rightChild.text;
}
}
//最后访问根节点
strText += node.text;
}
//中序递归遍历
lastIteration(root);
alert(strText);23.后续非递归遍历后续非递归遍历的思想是:1.从根节点一直往下找左孩子节点,直到最后一个左孩子节点(将路径保存到栈中,但不访问)2.弹出栈访问最后一个左孩子节点 3.进入最后一 个左孩子节点的兄弟节点,如果兄弟节点是叶节点就访问它,否则将该节点重复 1, 2步骤, 直到栈中的元素为空,循环结束。3.访问根节点function notLastIteration() {
var strText = '',
stack = new Stack();
nodo = root;
stack.push(node);
while(!stack.isEmpty()) {
while(node.leftChild) {
node = node.leftChild;
stack.push(node);
}
//弹出栈
var tempNode = stack.pop();
//访问左孩子节点
strText += tempNode.text;
//访问右孩子节点
if(tempNode.rightChild) {
if(tempNode.rightChild.leftChild || tempNode.rightChild.rightChild) { //判断最后一个左孩子节点的兄弟节点是否为页节点
stack.push(tempNode.rightChild);
}
else {
strText += tempNode.rightChild.text;
}
}
}
alert(strText);
}24. 已知前序和中序构建二叉树<script>
let qianxu = [ 1,2,4,7,3,5,6,8 ];
let zhongxu = [ 4,7,2,1,5,3,8,6 ];
function TreeNode( val ){
this.val = val;
this.left = null;
this.right = null;
}
function rebuildTree(qianxu,zhongxu){
if (qianxu[0]){
// 1. 根据找到的根节点( 前序序列的第一个元素一定是根节点 )
let rootVal = qianxu[0];
// 2. 找到根节点和中序序列,找到树的左子树和右子树
// 根节点在中序序列中的位置
let index = zhongxu.indexOf( rootVal );
// 前序序列:左子树 qianxu(1,index),右子树qianxu(index+1,最后)
// 中序序列:左子树 zhongxu(0,index-1),右子树 zhongxu(index+1,最后)
let leftTree = rebuildTree(qianxu.slice(1,index+1),zhongxu.slice(0,index));
let rightTree = rebuildTree(qianxu.slice(index+1),zhongxu.slice(index+1));
let root = new TreeNode(rootVal);
root.right = rightTree;
root.left = leftTree;
return root;
}
}
console.log(rebuildTree(qianxu,zhongxu));
</script>25. 栈转化成队列<script>
let stack1 = [];
let stack2 = [];
function push(node){
stack1.push(node);
}
function pop (){
while(stack1.length){
stack2.push(stack1.pop());
}
let popVal = stack2.pop();
while(stack2.length){
stack1.push(stack2.pop());
}
return popVal;
}
/**
* 1,2,3,4,5 入队
* 出队操作 1
* 入队 6
* 出队操作2
* 出队操作3
*/
push(1);
push(2);
push(3);
push(4);
push(5);
console.log(pop());
</script>26.跳台阶(典型递归)一只青蛙一次可以跳上一级台阶,也可以跳上两级求该青蛙上一个n级的台阶总共有多少中跳法(向后次序不同算不同结果)<script>
function jumpFloor(n){
if(n==1) return 1;
else if(n==2) return 2;
return jumpFloor(n-1)+jumpFloor(n-2);
}
console.log(jumpFloor(3));
</script>算法优化用空间换时间;(加缓存)(记忆化递归)<script>
// 记忆化递归
let cache = [,1,2];
function jumpFloor2(n){
if(cache[n] !==undefined) return cache[n];
return cache[n] = jumpFloor(n-1)+jumpFloor(n-2);
}
console.log(jumpFloor2(3));
</script>一只青蛙一次可以跳上一级台阶,也可以跳上两级…它也可以跳上n级;求该青蛙上一个n级的台阶总共有多少中跳法(向后次序不同算不同结果)f(n)=f(n-1)+f(n-2)+…+f(2)+f(1)+1<script>
let cache = [,1,2];
function jumpFloor(n){
if(cache[n] !== undefined) return cache[n];
cache[n] = 1;
for(let i = n-1;i >=1;i--){
cache[n] += jumpFloor(i);
}
return cache[n];
}
console.log(jumpFloor(10));
</script>27. 反转链表写一个函数,输入一个链表,反转练表后,输出新链表的表头。<script>
function Node(val){
this.val = val;
this.next = null;
}
function creatList(arr){
let head = new Node(arr[0]);
let tail = head;
for(let i =1;i<=arr.length-1;i++){
tail.next = new Node(arr[i]);
tail = tail.next;
}
return head;
}
let list = creatList([1,2,3,4,5]);
// console.log(list);
function reverseList (head){
let arr = [];
let p = head;
while(p){
arr.push(p.val);
p = p.next;
}
p = head;
while(p){
p.val = arr.pop(p.val);
p = p.next;
}
return head;
}
console.log(reverseList(list));
</script>28. 字典树需求:10 创建一个字典树,在字典树中查找是否包含某个单词1112 单词序列:13 and14 about15 as16 boy17 by18 because19 as2021 查找:22 close false23 an false24 as true25 boy true2627 字典树是什么28 字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。30 字典树的作用31 统计,排序和保存大量的字符串33 字典树的特点34 1、字典树的节点存储的是单词的字符(字母)35 2、为了表示一个单词是否出现,我们可以给单词的最后的字符加上标记36 3、字典树中表示一个单词用的是一条链37 4、字典树的根节点没有什么意义39 字典树的操作40 1、把单词插入到字典树里面去41 2、在字典树中查找单词45 1、把单词插入到字典树里面去46 算法步骤:47 去跟节点下面去找这个单词的第一个字符是否出现,48 如果没出现,就创建,然后走这条路,49 如果出现了,就直接走这条路50 (在这个过程里面,单词的第一个字符就被消耗掉了)52 算法:53 递归56 2、在字典树中查找单词57 算法:58 递归60 算法步骤:61 查找单词的第一个字符是否在根节点的子节点中,如果出现了,就接着往下找62 如果没出现,直接return false63 在单词找完后,如果标记大于1,表示单词出现过,就return true,64 否则return falseNodeJS一:Node 好处: 处理高并发 事件驱动 轻量 要用于搭建高性能的web服务器,1. 它是一个Javascript运行环境2. 依赖于Chrome V8引擎进行代码解释3. 事件驱动4. 非阻塞I/O5. 轻量、可伸缩,适于实时数据交互应用6. 单进程,单线程二:Express 和 koa的区别?异步 摆脱回调地域对response 和request进行了封装 contentExpress主要基于Connect中间件框架,功能丰富,随取随用,并且框架自身封装了大量便利的功能,比如路由、视图处理等等。而koa主要基于co中间件框架,框架自身并没集成太多功能,大部分功能需要用户自行require中间件去解决,但是由于其基于ES6 generator特性的中间件机制,解决了长期诟病的“callback hell”和麻烦的错误处理的问题,大受开发者欢迎。三:事件驱动模型和事件循环:事件驱动模型:当服务端收到请求时,就把它关闭 然后处理下一个请求 当第一个请求处理完毕后 就放回处理队列 当达到队列开头 将结果返回给用户 好处:高效 扩展性强 因为服务端一直接受请求 不等待任何读写操作事件循环:查看队列里面是否有队列里面有待处理的 如果有 交给主线程执行四:Redis:使用场景:支持string、list、set、zset和hash类型数据。配合关系型数据库做高速缓存缓存高频次访问的数据,降低数据库io分布式架构,做session共享可以持久化特定数据。利用zset类型可以存储排行榜利用list的自然时间排序存储最新n个数据五:mysql 和mongodb的区别mysql 关系型数据库 mongodb是非关系数据库(主要)六:MySQL索引七:闭包应该注意的地方八:进程和线程进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位进程是线程的容器十:mysql存储引擎 和区别InnoDB存储引擎:事务型数据库首选,支持事务安全表(ACID),支持行锁定和外键 是mysql 5.5之后的默认引擎MyISAM 存储引擎:不支持事务和外键,访问速度较快,是mysql5.5 之前的默认引擎MEMORY: 保存在内存中的数据表 ,每个memory表对应一个磁盘文件。格式是.frm 访问速度很快 缺点是:mysql服务关闭,数据丢失,另外对数据表大小有限制。十一:如何判断一个字符串是另一个字符串的子串indexof es6:include startWith endWith十二:单点登录十三:oauth2.0十四:type of 和instance of 区别十五:pm2 restart 和reload的区别(配置文件的重载 重启)十六:MySQL 读写分离十七:pm2如何查看指定三个项目的日志十八:深拷贝 浅拷贝十九:路由机制二十:MySQL 批量更新二十一:登录流程二十二:cookie 和session二十三:基本数据类型 引用数据类型 区别二十四:防止sql 注入1.使用escape() 对传入参数进行编码2.使用connection.query ()的查询参数占位符3.使用escapeId()编码SQL查询标识符4.使用mysql.format()转义参数:二十五:require()模块加载机制先判断是否存在文件缓存区中,存在直接导入,没有的话,在判断是否是原生模块,如果是原生模块,再看是否在原生模块缓存区中,如果有直接导入,没有的话加载原生模块,缓存原生模块,在导入如果不是原生模块,先查找文件模块,根据扩展名载入文件模块,缓存文件模块,在导入[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rujkgYx7-1611887935898)(C:\Users\zjx\Desktop\桌面文件\web面试\1.jpg)]第1题, 什么是nodejs?我们在哪里使用它?Nodejs是服务器端的一门技术。它是基于Google V8 JavaScript引擎而开发的。用来开发可扩展的服务端程序。第2题,为什么要使用node js?nodejs会让我们的编程工作变得简单,它主要包含如下几点几个好处:执行快速。永远不会阻滞。JavaScript是通用的编程语言。异步处理机制。避免并行所带来的问题。第3题,nodejs有哪些特点?是单线程的,但是有很高的可扩展性,使用JavaScript作为主流编程语言。使用的是异步处理机制和事件驱动。处理高效。第4题, Set immediate和set time out 区别在哪里?Set immediate就是马上执行的意思。Set time out, 时间参数传为0,也想获得同样的功能。只不过前者要快一些。第5题,如何更新nodejs的版本?npm install npm -g第6题,为什么nodejs是单线程的?Nodejs使用的是单线程没错,但是通过异步处理的方式,可以处理大量的数据吞吐量,从而有更好的性能和扩可扩展性。第7题,什么是回调函数?回调函数是指用一个函数作为参数传入另一个函数,这个函数会被在某个时机调用。第8题, 什么叫做回调地狱?回调地狱是由嵌套的回调函数导致的。这样的机制会导致有些函数无法到达,并且很难维护。第9题,如何阻止回调地狱?有三种方法, 对每个错误都要处理到, 保证代码的贯通, 程序代码模块化。第10题,解释一下repl的作用?Read evaluate print loop, 用于测试,调试和实验用。第11题,API函数的类型有哪些?有两种,一种是阻滞型函数。阻滞型函数会等待操作完成以后再进行下一步。另外一种是非阻滞型函数。这种函数使用回调函数来处理当前函数获取的结果。第12题,回调函数的第1个参数是什么?通常是错误对象。如果这个参数为空,表示没有错误。第13题,NPM的作用是什么?Node package manager, 主要有两个功能。它是一个网端模块的存储介质。它的另一个作用是安装程序依赖和版本管理。第14题,nodejs和ajax的区别是什么?Nodejs和ajax也就是asynchronous JavaScript and xml,都是通过JavaScript来表现的,但是他们的目的截然不同。Ajax是设计用来动态的更新页面的某个区域,从而不需要更新整个页面。Nodejs是用来开发客户服务器类型应用的。第15题,解释一下nodejs中chaining.Chaining是指从一个数据流到另一个数据流的链接,从而实现多个流操作。第16题,什么是streams?解释一下有哪些类型?流的概念是不间断的,它可以不间断的从某个地方读取数据,或者向某个地方写入数据。有4种类型的流数据。可读,可写。既可读,又可写,转化。第17题,退出代码是什么?有哪些退出代码?退出代码是指中断nodejs运行时返回的代码。有这么几种unused, uncaught fatal exception, fatal error, non function internal exception handler, internal exception handler run time failure,internal JavaScript evaluation failure.第18题, 什么是globals?有三个global的关键字。Global代表的是最上层的命名空间,用来管理所有其他的全局对象。Process 是一个全局对象,可以把异步函数转化成异步回调, 它可以在任何地方被访问,它主要是用来返回系统的应用信息和环境信息.Buffer, 是用来处理二进制数据的类.第19题, Angular js和node js的区别是什么?Angular js是网络应用开发框架,而nodejs是一个实时系统。第20题, 为什么统一的风格儿非常重要,有什么工具可以保证这一点?统一的风格可以让所有的组成员按照一种规矩来写代码。工具有Standard和eslint.第21题, 用什么方法来处理没有被处理的异常?在应用和node js之间使用domain来处理这样的异常。第22题, Node js是如何支持多处理器平台的?Cluster模块是用来支持这方面的。它可以允许多个nodejs工作进程运行在相同的端口上。第23题, 如何配置开发模式和生产模式的环境?首先有一个配置文件,然后通过环境变量参数来获取对应的配置内容。第24题, nodejs中跟时间相关的函数有哪些?Set time out, clear time out.Set interval, clear interval.Set immediate, clear immediate.Process.nextTick.第25题, 解释一下什么是reactor pattern。Reactor pattern主要是非阻滞的i/o操作。提供一个回调函数来关联io操作。io请求完成以后会不会提交给demultiplexer, 这是一个通知接口用来处理并发性的非阻滞的io操作,这个功能是通过查询一个event loop来实现的.第26题,lts版本是什么意思?也就是long term support版本。至少会被支持18个月。使用的是偶数来标识。这种版本有稳定性和安全性的保证。第27题,你为什么需要把express APP和server分开?分开以后方便维护以及测试,在测试某个模块的时候,尤其是APP模块的时候,你不需要去对网络方面的连接配置做工作。第28题,next tick和setImmediate的区别是什么?Next tick会等待当前的event执行完成或者下一轮儿事件循环到达再执行。Set immediate, 会在下一轮的事件循环中,执行回调并且返回当前的循环来做读写操作.第29题,apply, call和bind有什么区别?参考答案:三者都可以把一个函数应用到其他对象上,注意不是自身对象.apply,call是直接执行函数调用,bind是绑定,执行需要再次调用.apply和call的区别是apply接受数组作为参数,而call是接受逗号分隔的无限多个参数列表,代码演示function Person() {
}
Person.prototype.sayName() { alert(this.name); }
var obj = {name: 'michaelqin'}; // 注意这是一个普通对象,它不是Person的实例
1) apply
Person.prototype.sayName.apply(obj, [param1, param2, param3]);
2) call
Person.prototype.sayName.call(obj, param1, param2, param3);
3) bind
var sn = Person.prototype.sayName.bind(obj);
sn([param1, param2, param3]); // bind需要先绑定,再执行
sn(param1, param2, param3); // bind需要先绑定,再执行二)、node全局对象3. node有哪些核心模块?参考答案: EventEmitter, Stream, FS, Net和全局对象1. node有哪些全局对象?参考答案: process, console, Buffer2. process有哪些常用方法?参考答案: process.stdin, process.stdout, process.stderr, process.on, process.env, process.argv, process.arch, process.platform, process.exit3. console有哪些常用方法?参考答案: console.log/console.info, console.error/console.warning, console.time/console.timeEnd, console.trace, console.table4. node有哪些定时功能?参考答案: setTimeout/clearTimeout, setInterval/clearInterval, setImmediate/clearImmediate, process.nextTick5. node中的事件循环是什么样子的?总体上执行顺序是:process.nextTick >> setImmidate >> setTimeout/SetInterval 看官网吧:[https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/)6. node中的Buffer如何应用?参考答案: Buffer是用来处理二进制数据的,比如图片,mp3,数据库文件等.Buffer支持各种编码解码,二进制字符串互转.三)、EventEmitter1. 什么是EventEmitter?参考答案: EventEmitter是node中一个实现观察者模式的类,主要功能是监听和发射消息,用于处理多模块交互问题.2. 如何实现一个EventEmitter?参考答案: 主要分三步:定义一个子类,调用构造函数,继承EventEmitter代码演示var util = require('util');
var EventEmitter = require('events').EventEmitter;
function MyEmitter() {
EventEmitter.call(this);
} // 构造函数
util.inherits(MyEmitter, EventEmitter); // 继承
var em = new MyEmitter();
em.on('hello', function(data) {
console.log('收到事件hello的数据:', data);
}); // 接收事件,并打印到控制台
em.emit('hello', 'EventEmitter传递消息真方便!');3. EventEmitter有哪些典型应用?参考答案:模块间传递消息回调函数内外传递消息处理流数据,因为流是在EventEmitter基础上实现的.观察者模式发射触发机制相关应用4. 怎么捕获EventEmitter的错误事件?参考答案: 监听error事件即可.如果有多个EventEmitter,也可以用domain来统一处理错误事件.代码演示var domain = require('domain');
var myDomain = domain.create();
myDomain.on('error', function(err){
console.log('domain接收到的错误事件:', err);
}); // 接收事件并打印
myDomain.run(function(){
var emitter1 = new MyEmitter();
emitter1.emit('error', '错误事件来自emitter1');
emitter2 = new MyEmitter();
emitter2.emit('error', '错误事件来自emitter2');
});5. EventEmitter中的newListenser事件有什么用处?参考答案: newListener可以用来做事件机制的反射,特殊应用,事件管理等.当任何on事件添加到EventEmitter时,就会触发newListener事件,基于这种模式,我们可以做很多自定义处理.代码演示var emitter3 = new MyEmitter();
emitter3.on('newListener', function(name, listener) {
console.log("新事件的名字:", name);
console.log("新事件的代码:", listener);
setTimeout(function(){ console.log("我是自定义延时处理机制"); }, 1000);
});
emitter3.on('hello', function(){
console.log('hello node');
});四)、Stream1. 什么是Stream?参考答案: stream是基于事件EventEmitter的数据管理模式.由各种不同的抽象接口组成,主要包括可写,可读,可读写,可转换等几种类型.2. Stream有什么好处?参考答案: 非阻塞式数据处理提升效率,片断处理节省内存,管道处理方便可扩展等.3. Stream有哪些典型应用?参考答案: 文件,网络,数据转换,音频视频等.4. 怎么捕获Stream的错误事件?参考答案: 监听error事件,方法同EventEmitter.5. 有哪些常用Stream,分别什么时候使用?参考答案: Readable为可被读流,在作为输入数据源时使用;Writable为可被写流,在作为输出源时使用;Duplex为可读写流,它作为输出源接受被写入,同时又作为输入源被后面的流读出.Transform机制和Duplex一样,都是双向流,区别时Transfrom只需要实现一个函数_transfrom(chunk, encoding, callback);而Duplex需要分别实现_read(size)函数和_write(chunk, encoding, callback)函数.6. 实现一个Writable Stream?参考答案: 三步走:1)构造函数call Writable 2) 继承Writable 3) 实现_write(chunk, encoding, callback)函数代码演示var Writable = require('stream').Writable;
var util = require('util');
function MyWritable(options) {
Writable.call(this, options);
} // 构造函数
util.inherits(MyWritable, Writable); // 继承自Writable
MyWritable.prototype._write = function(chunk, encoding, callback) {
console.log("被写入的数据是:", chunk.toString()); // 此处可对写入的数据进行处理
callback();
};
process.stdin.pipe(new MyWritable()); // stdin作为输入源,MyWritable作为输出源五)、文件系统1. 内置的fs模块架构是什么样子的?参考答案: fs模块主要由下面几部分组成: 1) POSIX文件Wrapper,对应于操作系统的原生文件操作 2) 文件流 fs.createReadStream和fs.createWriteStream 3) 同步文件读写,fs.readFileSync和fs.writeFileSync 4) 异步文件读写, fs.readFile和fs.writeFile2. 读写一个文件有多少种方法?参考答案: 总体来说有四种: 1) POSIX式低层读写 2) 流式读写 3) 同步文件读写 4) 异步文件读写3. 怎么读取json配置文件?参考答案: 主要有两种方式,第一种是利用node内置的require(‘data.json’)机制,直接得到js对象; 第二种是读入文件入内容,然后用JSON.parse(content)转换成js对象.二者的区别是require机制情况下,如果多个模块都加载了同一个json文件,那么其中一个改变了js对象,其它跟着改变,这是由node模块的缓存机制造成的,只有一个js模块对象; 第二种方式则可以随意改变加载后的js变量,而且各模块互不影响,因为他们都是独立的,是多个js对象.4. fs.watch和fs.watchFile有什么区别,怎么应用?参考答案: 二者主要用来监听文件变动.fs.watch利用操作系统原生机制来监听,可能不适用网络文件系统; fs.watchFile则是定期检查文件状态变更,适用于网络文件系统,但是相比fs.watch有些慢,因为不是实时机制.六)、网络1. node的网络模块架构是什么样子的?参考答案: node全面支持各种网络服务器和客户端,包括tcp, http/https, tcp, udp, dns, tls/ssl等.2. node是怎样支持https,tls的?参考答案: 主要实现以下几个步骤即可: 1) openssl生成公钥私钥 2) 服务器或客户端使用https替代http 3) 服务器或客户端加载公钥私钥证书3. 实现一个简单的http服务器?参考答案: 经典又很没毛意义的一个题目.思路是加载http模块,创建服务器,监听端口.代码演示var http = require('http'); // 加载http模块
http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'}); // 200代表状态成功, 文档类型是给浏览器识别用的
res.write('<meta charset="UTF-8"> <h1>我是标题啊!</h1> <font color="red">这么原生,初级的服务器,下辈子能用着吗?!</font>'); // 返回给客户端的html数据
res.end(); // 结束输出流
}).listen(3000); // 绑定3ooo, 查看效果请访问 http://localhost:3000七)、child-process1. 为什么需要child-process?参考答案: node是异步非阻塞的,这对高并发非常有效.可是我们还有其它一些常用需求,比如和操作系统shell命令交互,调用可执行文件,创建子进程进行阻塞式访问或高CPU计算等,child-process就是为满足这些需求而生的.child-process顾名思义,就是把node阻塞的工作交给子进程去做.2. exec,execFile,spawn和fork都是做什么用的?参考答案: exec可以用操作系统原生的方式执行各种命令,如管道 cat ab.txt | grep hello; execFile是执行一个文件; spawn是流式和操作系统进行交互; fork是两个node程序(javascript)之间时行交互.3. 实现一个简单的命令行交互程序?参考答案: 那就用spawn吧.代码演示var cp = require('child_process');
var child = cp.spawn('echo', ['你好', "钩子"]); // 执行命令
child.stdout.pipe(process.stdout); // child.stdout是输入流,process.stdout是输出流
// 这句的意思是将子进程的输出作为当前程序的输入流,然后重定向到当前程序的标准输出,即控制台4. 两个node程序之间怎样交互?参考答案: 用fork嘛,上面讲过了.原理是子程序用process.on, process.send,父程序里用child.on,child.send进行交互.代码演示1) fork-parent.js
var cp = require('child_process');
var child = cp.fork('./fork-child.js');
child.on('message', function(msg){
console.log('老爸从儿子接受到数据:', msg);
});
child.send('我是你爸爸,送关怀来了!');
2) fork-child.js
process.on('message', function(msg){
console.log("儿子从老爸接收到的数据:", msg);
process.send("我不要关怀,我要银民币!");
});5. 怎样让一个js文件变得像linux命令一样可执行?参考答案: 1) 在myCommand.js文件头部加入 #!/usr/bin/env node 2) chmod命令把js文件改为可执行即可 3) 进入文件目录,命令行输入myComand就是相当于node myComand.js了6. child-process和process的stdin,stdout,stderror是一样的吗?参考答案: 概念都是一样的,输入,输出,错误,都是流.区别是在父程序眼里,子程序的stdout是输入流,stdin是输出流.九、node高级话题(异步,部署,性能调优,异常调试等)1. node中的异步和同步怎么理解参考答案: node是单线程的,异步是通过一次次的循环事件队列来实现的.同步则是说阻塞式的IO,这在高并发环境会是一个很大的性能问题,所以同步一般只在基础框架的启动时使用,用来加载配置文件,初始化程序什么的.2. 有哪些方法可以进行异步流程的控制?参考答案: 1) 多层嵌套回调 2) 为每一个回调写单独的函数,函数里边再回调 3) 用第三方框架比方async, q, promise等3. 怎样绑定node程序到80端口?参考答案: 多种方式 1) sudo 2) apache/nginx代理 3) 用操作系统的firewall iptables进行端口重定向4. 有哪些方法可以让node程序遇到错误后自动重启?参考答案: 1) runit 2) forever 3) nohup npm start &5. 怎样充分利用多个CPU?参考答案: 一个CPU运行一个node实例6. 怎样调节node执行单元的内存大小?参考答案: 用–max-old-space-size 和 --max-new-space-size 来设置 v8 使用内存的上限7. 程序总是崩溃,怎样找出问题在哪里?参考答案: 1) node --prof 查看哪些函数调用次数多 2) memwatch和heapdump获得内存快照进行对比,查找内存溢出8. 有哪些常用方法可以防止程序崩溃?参考答案: 1) try-catch-finally 2) EventEmitter/Stream error事件处理 3) domain统一控制 4) jshint静态检查 5) jasmine/mocha进行单元测试9. 怎样调试node程序?参考答案: node --debug app.js 和node-inspector10. 如何捕获NodeJS中的错误,有几种方法? 参考答案: 1) 监听错误事件req.on(‘error’, function(){}), 适用EventEmitter存在的情况; 2) Promise.then.catch(error),适用Promise存在的情况 3) try-catch,适用async-await和js运行时异常,比如undefined object十、 常用知名第三方类库(Async, Express等)1. async都有哪些常用方法,分别是怎么用?参考答案: async是一个js类库,它的目的是解决js中异常流程难以控制的问题.async不仅适用在node.js里,浏览器中也可以使用.1). async.parallel并行执行完多个函数后,调用结束函数async.parallel([
function(){ ... },
function(){ ... }
], callback);2). async.series串行执行完多个函数后,调用结束函数async.series([
function(){ ... },
function(){ ... }
]);3). async.waterfall依次执行多个函数,后一个函数以前面函数的结果作为输入参数async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});4). async.map异步执行多个数组,返回结果数组async.map(['file1','file2','file3'], fs.stat, function(err, results){
// results is now an array of stats for each file
});5). async.filter异步过滤多个数组,返回结果数组async.filter(['file1','file2','file3'], fs.exists, function(results){
// results now equals an array of the existing files
});2. express项目的目录大致是什么样子的参考答案: app.js, package.json, bin/www, public, routes, views.3. express常用函数参考答案: express.Router路由组件,app.get路由定向,app.configure配置,app.set设定参数,app.use使用中间件4. express中如何获取路由的参数参考答案: /users/:name使用req.params.name来获取; req.body.username则是获得表单传入参数username; express路由支持常用通配符 ?, +, *, and ()5. express response有哪些常用方法参考答案: res.download() 弹出文件下载res.end() 结束responseres.json() 返回jsonres.jsonp() 返回jsonpres.redirect() 重定向请求res.render() 渲染模板res.send() 返回多种形式数据res.sendFile 返回文件res.sendStatus() 返回状态十一、其它相关后端常用技术(MongoDB, Redis, Apache, Nginx等)1. mongodb有哪些常用优化措施参考答案: 类似传统数据库,索引和分区.2. mongoose是什么?有支持哪些特性?参考答案: mongoose是mongodb的文档映射模型.主要由Schema, Model和Instance三个方面组成.Schema就是定义数据类型,Model就是把Schema和js类绑定到一起,Instance就是一个对象实例.常见mongoose操作有,save, update, find. findOne, findById, static方法等.3. redis支持哪些功能参考答案: set/get, mset/hset/hmset/hmget/hgetall/hkeys, sadd/smembers, publish/subscribe, expire4. redis最简单的应用参考答案:var redis = require("redis"),
client = redis.createClient();
client.set("foo_rand000000000000", "some fantastic value");
client.get("foo_rand000000000000", function (err, reply) {
console.log(reply.toString());
});
client.end();5. apache,nginx有什么区别?参考答案: 二者都是代理服务器,功能类似.apache应用简单,相当广泛.nginx在分布式,静态转发方面比较有优势.HTTP状态码200 - 请求成功301 - 资源(网页等)被永久转移到其它URL404 - 请求的资源(网页等)不存在500 - 内部服务器错误
利用FreeNas创建WebDAV共享并实现ssl加密
WebDAV(Web-based Distributed Authoring and Versioning)一种基于 HTTP 1.1协议的通信协议。它扩展了HTTP 1.1,在GET、POST、HEAD等几个HTTP标准方法以外添加了一些新的方法,使应用程序可对Web Server直接读写,并支持写文件锁定(Locking)及解锁(Unlock),还可以支持文件的版本控制。FreeNAS从9.3版本开始提供WebDAV服务支持。通过WebDAV共享,使得我们可以直接在浏览器中,通过身份验证之后访问特定的卷或数据集中存储的数据。任务1 创建用户和WebDAV共享数据集添加1块硬盘,用作创建数据集在存储这里创建存储池点击左侧共享,WebDAV共享任务2 配置WebDAV服务点击左侧 服务 找到 WebDAV,点击开启和打开开机启动单击配置,即可设置WebDAV密码,这里不做设置默认用户名为 webdav 密码为 davtest任务3 使用网页浏览器访问WebDAV共享浏览器打开访问 ,请注意,需要注意大小写http://192.168.100.10:8080/WebDAVshare/任务4 新建Word文档存入WebDAV如果无法保存,可以用文件管理器打开,再将word文档复制进去网页中单击文件即可下载任务5 自建ssl证书实现WebDAV加密访问首先打开https://freessl.cn/免费申请证书,按照网站教程进行申请然后准备证书的 证书和证书链 还有 私钥随后进入 System > General 设置页,修改一些通用选项并启用 https:Protocol: HTTP+HTTPSCertificate: freenas-certWebGUI IPv4 Address: 192.168.100.10WebGUI HTTP Port: 8080防止浏览器默认访问80端口,将8080留做备用端口。WebGUI HTTPS Port: 443WebGUI HTTP -> HTTPS Redirect: 取消勾选关闭自动重定向到HTTPS。浏览器访问任务6 浅析WebDAV如此重要,为何国内网盘不提供?主要原因为若国内网盘都支持WebDAV,部分用户就可以不用特地去载网盘的客户端,即可在其他的App中使用到云盘的服务。比如扫描后将资料上传至网盘,批注后将文档上传至网盘。对于网盘服务商来说,相应的,App关注度、客户端下载量、用户活跃度、广告展现和推送等都会减少,公司的运营势必会受到一定影响。这个分析确实有理,并且这与国内缺乏“生态”的行业现状有直接关联。很多云盘产品团队会认为,支持webDAV会影响自身的活跃用户数,粘性,用户数据,无法精准的对接到用户需求,这对于需要融资的厂商来说更会影响资本市场的融资情况(用户数据被“削弱”)WebDAV对开发的要求并不低WebDAV协议是标准协议,很多产品都在用,年限也较长了,包括微软的Office和自家Sharepoint服务器通信,苹果的iWork套件也是基于WebDAV。年限长了,功能繁多的协议里面细节部分也多,需要开发考虑的点就增多。比如权限如何管理,如何支持文件锁定等都是难点,需要认真的去一遍又一遍的读协议,还得把自己希望实现的点和协议里的要求漂亮的吻合起来。因此,如何完美的支持WebDAV是个苦活,不是每间公司都愿意去做的。2. 自主控制权不管是哪个领域的生态圈,我们都知道某些“潜规则”都需要以业内老大说的算,或者老大来创立“明规则”。如果是我自己做的“接口“(API),那所有接入这个接口的合作方,都在某种程度上需要“听我的”,我是这个API的主导。阿里和顺丰的事件就是阿里希望在数据接口上有主导权。所以大厂愿意做自己的API而不是支持WebDAV,是希望其保持主导权,避免和同行或者相关行业的合作伙伴有利益的冲突,可以自主控制。而WebDAV是一个标准协议,是统一的,这么一来控制权自然就不再大厂手上了,重要数据也可能需要“共享”。而且,区别于自己的API协议,由于无法分辨调用WebDAV协议的到底是哪个应用,很多大厂也不愿意选择支持WebDAV。3. 公司运营出于对自家团队的KPI影响的考虑。比如,如果团队的API是活跃用户数,那么如果支持WebDAV势必会对此有一些影响。坚果云的情况是,确实有些用户就习惯用Readdle家的Documents来访问坚果云的文件。如此一来团队的KPI是比较难定夺的。此外,重新造轮子是工程师的天性,开发自己独有的API,这样有业绩也有成就感,唯一的问题加重了生态圈App的负担
友盟+高吞吐、极速高并发智能推送服务,赋能值得买科技的精准化用户运营
经过多年的发展,我国消费电商总体上处于商品溢出、内容溢出的状态。如何提升C端(消费者)和B端(品牌与商家)的连接效率,成为消费电商企业亟需解决的问题。北京值得买科技股份有限公司(以下简称“值得买科技”)作为一家专注于消费产业的科技集团,在主营业务“什么值得买”的基础上,致力于通过构建覆盖站内外优质内容的全域流量体系,提高电商、品牌商与用户之间的连接效率。面对消费电商市场日益激烈的市场竞争,如何更好的触达消费者,如何将优质内容、商品更精准的推送给目标人群,提升用户粘性和满意度的同时,提升产品销量和GMV,是值得买科技必须要回答的问题。值得买科技业务体系消息推送作为提升用户粘性和活跃度的重要手段,在“什么值得买”的用户运营的方式之一。然而,在进行消息推送厂商选型过程中,值得买科技发现市场上的大量消息推送工具都无法很好的满足其需求,且存在诸多问题。例如:消息推送服务器吞吐量不足,尤其是当大促时,推送堆积问题严重,自身优惠活动消息无法触达用户;推送延迟,消息不能及时送达客户,错过最佳时机,活动效果大打折扣;从技术角度,推送SDK接入复杂,需要多次调试,易用性差,学习门槛高;无法判断用户是否活跃,浪费厂商通道配额;推送链路监测不完整,不能有效分析推送效果数据,难以优化推送策略等。作为一家技术驱动型的消费电商企业,值得买科技希望将平台的优质消费内容精准推送给有需要的用户,避免对用户造成无谓的打扰,不仅对消息推送吞吐量、延迟、易用性、手机厂商覆盖、推送链路监测等性能指标有严格的要求,还对消息推送准确度有很高的要求。经过严格的评估程序,值得买科技最终选择了友盟+,并持续了长达5年的紧密合作。什么值得买公司借助友盟+的U-Push平台,构建智能消息推送方案,成功提升“什么值得买”APP的活跃度和用户粘性。具体来看,友盟+为值得买科技打造的智能消息推送方案涵盖两部分:多层次技术优化,实现高吞吐、极速高并发消息推送服务什么值得买APP每天消息推送吞吐量数千万,并且对消息延迟要求严格,为了保证绝大部分用户都能在几秒内收到推送,这对消息推送供应商的技术能力提出了很高的要求。在长期服务客户的实践中,友盟+的U-Push研发团队结合阿里巴巴数据技术能力总结出一套高性能应用实践方案,为高吞吐量、低延时消息推送奠定了坚实的技术基础。友盟+消息推送应用架构在这个高性能技术方案中,友盟+在应用接入层、服务层、存储层、数据通道等多个层面都进行了技术优化,具体如下:接入层-Internet接入层友盟+消息推送服务对外网暴露的业务接口统一经过阿里巴巴安全接入层,接入层主要实现为lvs+OpenResty,结合阿里巴巴集团安全部能力通过插件的方式支持防刷量、防垃圾、防攻击、弹性扩展,安全和稳定性水位与阿里巴巴集团一致。接入层-API部分友盟+消息推送支持通过HTTP/HTTPS协议调用OpenAPI接口,实现消息发送、状态查询、任务取消、统计数据查询、设备标签设置和查询、分通道数据查询,OpenAPI采用友盟+开发者平台统一颁发masterSecretKey做sign鉴权。同时友盟+消息推送服务支持web页面发送,可直接由运营人员通过友盟+开发者控制台(portal)完成消息发送、应用管理、任务控制、设备信息管理、测试设备信息管理和多维业务报表查询。服务层友盟+消息推送使用阿里巴巴内部RPC框架HSF构建的微服务体系,服务依据业务权重实现逻辑和物理双隔离。所有服务采用多机房、多环境部署,服务发布严格遵守阿里巴巴安全生产管理规范,保障生产、运行时服务稳定。其中Android长链服务采用阿里巴巴基于SPDY协议的低延迟自研解决方案,可支持150w/QPS消息下行,iOS发送集群采用阿里巴巴自研的高延迟环境下境内外双机房海量数据同步解决方案,可支撑50w/QPS消息下行,送达速率保持行业领先。存储层友盟+消息推送为不同的业务场景设计了针对性的存储解决方案,包括MySQL(元信息和少量配置场景)、Lindorm(海量数据KV场景)、Lindorm Wide Column(多维度海量数据场景)、AnalyticDB MySQL版(OLAP场景)、Tair企业版(海量数据高性能场景)、LevelDB(高性能持久化场景),其中除LevelDB使用开源版其它所有存储组件均使用阿里巴巴集团统一内部服务版,具备专职垂直领域研发团队支撑和稳定性保障,友盟+消息推送团队和阿里巴巴内部相关存储开发团队长期保持深度技术合作。数据通道层友盟+自建长连通道和阿里系App使用相同基础设施和技术,基于阿里系基础设施团队提供的全双工、低延时、高安全的通道服务,系统稳定性高,保障通道带宽buffer充足,业务高峰期保证服务稳定、送达率高,送达速度快。友盟+同时支持国内主流Android厂商通道,在自建长连通道不在线时,通过厂商通道保障消息送达率,包括华为、小米、荣耀、oppo、vivo、魅族。iOS通道友盟+完全兼容苹果官方APNs通道,通过在美东部署专有独立发送集群和跨国专线来保障消息送达速度和质量,减少延时。U-Push产品具备手机设备在线状态实时接口功能,能判断用户设备的在线状态:对于在线设备,通过友盟+在线通道投递;对于离线设备,则通过厂商通道投递。通过差异化消息推送,最大化在线通道投递量,节约厂商通道推送配额和费用。除了在不同环节进行技术优化,为了满足值得买科技严格的消息推送技术要求,友盟+提供的解决方案,还有诸多技术优势,主要表现在:针对苹果手机用户的高性能消息处理苹果在中国手机市场占有相当份额,但苹果手机的消息发送依赖苹果自身的Apns服务,而Apns服务在中国境内没有服务器,这会带来消息延迟问题,即便使用Proxy在访问Apns环节仍然有150ms的延迟。在极端情况下,Apns只支持单播(一次发送一个设备)。为了更好支撑值得买科技千万量级的消息吞吐需求,友盟+针对苹果手机用户做了诸多高性能优化设计方案,具体包括:异地部署,消息推送服务将iOS消息发送的最后一环“消息发送服务”部署在了Apns服务所在地,在物理层面降低RT;HTTP2+Netty,Apns服务支持HTTP2协议,基于Netty的高性能事件驱动设计iOS发送服务可以轻松支持数万QPS。连接复用和故障保护,开发一套连接复用机制,同时对疑似异常连接增加了故障保护机制,在证书失效、证书过期、网络抖动的情况下保护进程。单播批量出境,针对Apns跨境高延迟的问题,推送服务采用内存队列的方式实现单播的批量出境,使用drainTo代替pop平摊跨境的高RT。针对上、下行链路特征设置差异化解决方案,优化链路设计消息推送根据业务场景分为上行链路和下行链路,这两种链路在业务需求上存在明显的差异。上下行链路解析为了更好满足值得买科技的需求,友盟+针对这两种场景,分别设计了针对性的解决方案。实时流的削峰处理,应对峰值流量挑战在定时消息、广播消息、模版消息等业务场景中,需要面临的一大挑战是对峰值流量的处理。例如,将定时消息设置在整点,广播消息会将一次请求分裂为数千万甚至数亿次,此时消息推送需要解决的削峰问题更加严重。为了解决这个问题,友盟+的推送服务使用内存队列和消息中间件的方式实现削峰:内存队列,在实时流写入消息队列前在内存阶段将消息切分为毫秒级的微批,相当于用内存IO换消息队列IO;消息队列,将不同职能的服务彻底解耦,实现不同职能服务的按需扩展,如入口服务要承载大量HTTP链接,需要部署大量机器,筛选服务业务量相对较小,只需要少量机器支撑。多点优化,提升C端用户接收推送体验为了进一步提升C端用户的推送体验,U-Push进行了多项优化:历经多年双十一等高并发、超大流量锤炼,推送稳定高效,实测消息下行并发速度超过200万/秒;全面支持P8/P12证书,大幅降低C端用户收不到消息概率,有效降低开发人员维护成本,提升iOS平台推送稳定性及到达率;根据不同网络环境,调整心跳频率,将参数调整到最优状态,降低消息推送的电量/流量消耗,提升C端用户体验;推送错误消息后,可直接实现一键撤回,降低由于推送事故导致的不良后果。正是基于以上技术能力,U-Push很好的满足了值得买科技消息推送的业务需求,每天吞吐值得买科技超过千万的用户推送量,其中90%的用户在10秒内就能收到消息。尤其是在电商大促期间,U-Push很好解决了以往由于大量消息推送导致的用户接收速度慢的问题。U-Push以稳定推送、高效吞吐、极速高并发的推送服务,有效支撑了值得买科技的用户运营和业务增长,并多次助力值得买科技的双十一和618大促活动,保障了大促期间消息推送的快速推送、高并发性和高稳定性。友盟+全域数据+达摩院AI算法,构建智能推送方案在消息推送层面,值得买科技的需求除了要保证推送的稳定性、高效吞吐、高并发这些基础能力外,越来越看重消息推送的转化效果和用户体验。尤其是在用户隐私保护、垃圾消息免打扰意识越来越强的情况下,以往通过群发消息来进行“信息轰炸”的方式,越来越难以为继。值得买科技希望能够推送用户真正需要的信息,切实帮助用户并提升用户满意度。在长期的客户服务过程中,友盟+总结出了一套智能推送方法论,即“智能推送=精准的用户x合适的时机x恰当的频次x合理的渠道x优质的内容”。在这个公式中有三个关键要素,分别是友盟+的全域用户洞察、阿里达摩院的智能推送算法和值得买科技的推送文案。友盟+全域数据友盟+作为国内领先的第三方全域数据智能服务商,已累计为250万移动应用、970万家网站提供十余年的专业数据服务。在长期的数据服务过程中,通过算法预测并刻画出了立体化的用户群体画像。阿里达摩院AI算法有了精准的用户群体画像,下一步就是要基于对用户群体的了解,来精准的匹配文案内容,这还需要用到精准匹配的AI算法。阿里巴巴达摩院自主研发的智能推送算法技术,采用MindOpt优化求解器中的在线优化算法,并结合了流计算等技术,构建在线学习与优化(简称OLAD)方案,该算法已经稳定服务了阿里内部的多个业务场景,经受住了实践的考验。友盟+与达摩院强强合作,推出国内首个智能推送功能,对推送人群与推送文案进行精准匹配,通过对不同用户场景感知和各种约束配额下的最佳分配,将无用推送信息降权显示,降低对用户的干扰,最大化用户点击率,应用效果明显。以某电商APP客户为例,通过应用达摩院智能推送算法,客户推送消息的点击率提升了35.87%。客户案例-某电商App应用智能推送算法提升近36%的点击率优质文案友盟+智能推送方案,支持一次下发多种推送文案,实现推送文案和人群的智能匹配。同一用户只被一种文案触达,避免对用户的重复打扰。U-Push平台支持可视化展示不同文案点击量差异,并基于历史推送点击率推荐优秀文案,优质文案的直接复用效率高。智能消息文案推送后数据分析案例综上,U-Push平台的智能推送方案,以高效吞吐、极速高并发、精准匹配的智能推送服务,帮助值得买科技实现智能化用户运营,很好的解决了以往推送文案存在的用户兴趣度低、点击量低的问题,大幅度提升了消息点击率。同时避免频繁推送造成对用户的打扰,用户的满意度、粘性都得到了明显的改善。流量见顶的移动互联网,亟需智能推送来实现精细化用户运营随着中国互联网流量红利见顶,移动应用的重心逐渐从增量用户转向存量用户的精细化运营。某种程度上,能否留住用户并提升用户价值和活跃度,将决定了一款APP的生死。从各种数据维度来看,我国移动互联网企业的生存环境都不乐观。2月25日,中国互联网络信息中心(CNNIC)发布了第49次《中国互联网络发展状况统计报告》(以下简称:《报告》)。《报告》显示,截至2021年12月,我国网民规模达10.32亿,较上年只提升2.6个百分点。网民规模和互联网普及率 数据来源:中国互联网络发展状况统计调查截至2021年12月,我国手机网民规模为10.29亿,网民中使用手机上网的比例为99.7%,手机用户规模见顶。可以说,手机用户大规模增长的时代已经一去不复返了,因而中国移动互联网将进入惨烈的存量搏杀阶段。手机网民规模及其占网民比例 数据来源:中国互联网络发展状况统计调查从细分领域来看,大部分互联网细分市场的增长率都已经小于10%。各类互联网应用用户规模和网民使用率移动互联网的寒意,正在席卷整个行业。目前,移动APP的生存已经变得日益艰难。截至2021年12月,我国国内市场上监测到的APP数量为252万款,相比2020年下降超过93万款。事实上,移动应用APP总量从2018年就开始在不断下降了。中国移动APP的数量,从2018年巅峰时期的452万,降低到2021年的252万。两年时间,有200万移动APP都消失不见了,市场的惨烈程度可见一斑。中国移动APP数量 来源:工业和信息化部与此同时,用户的上网时长也见顶了。截至2021年12月,我国网民的人均每周上网时长为28.5个小时,较2020年12月只提升2.3个小时。可以预见,移动应用对用户的争夺将日趋激烈。网民人均每周上网时长 数据来源:中国互联网络发展状况统计调查在这种情况下,高吞吐量、极速高并发的智能推送服务,就成为移动应用APP激活用户、提升用户粘性的重要法宝。找一个靠谱的智能推送合作伙伴,对移动互联网企业的用户运营至关重要。显然,作为领先的全域数据智能服务商,友盟+就是一个不错的合作伙伴。友盟+能够为客户提供稳定、快速、高并发的推送服务,多项性能指标居于业界领先地位:消息吞吐量大,具备百万级qps的消息吞吐能力,日消息发送量100亿+;延迟低,10分钟内成功推送99.9%的在线用户,相对友商竞品可以实现提前2-5分钟送达;送达率高,U-Push整体送达率72%,Android送达率(基于发出)超过90%。友盟+在消息推送领域深耕长达7年时间,服务了大量的客户,日建立长链接应用数超过50,000+。可以预见,像友盟+这样的数据智能服务商,将是移动互联网企业的重要合作伙伴。友盟+的智能推送服务,将显著提升移动互联网企业的用户运营效率,进而帮助其打赢这场“用户保卫战”。文:月满西楼 / 数据猿
网站建设指南之网站HTTPS化与证书
通常我们说的HTTPS就是HTTP+SSL证书,简单的说就是HTTP的安全版。HTTP网站的数据传输都是以明文形式,比如用户密码等信息都没有加密,很容易造成信息泄露。绑定SSL证书后,可以实现网站HTTPS化,加密用户与网站之间的交互访问。
为什么网站需要HTTPS?
运营商等会利用HTTP的信息传送不加密对网站内容进行劫持修改,比如我们打开APP或某些网站看到的的浮窗广告,大多都是运营商劫持添加。
主流的浏览器会对不支持HTTPS的网站标注为“不安全”,会带给网站访客不好体验。
搜索引擎认为支持HTTPS的网站更安全,提高排名权重,未来可能会逐步弱化或取消对非HTTPS站点的收录。
证书的分类:
DV证书 D表示域名 Domain Validation CertificateDV证书适合个人网站使用,申请证书时只验证域名信息,即whois信息中的管理员邮箱验证或在解析当中添加txt验证信息。阿里云官网有免费DV证书,如果域名是开通在同一阿里云ID下,补充完信息后可以自动验证,通常1个小时内就可以核发。
OV证书 O表示组织 Organization Validation CertificateOV证书是在DV证书的基础上,增加认证公司的信息,会要求提供公司营业执照扫描件等。
EV证书 E表示扩展 Extended Validation CertificateEV证书的认证最为严格,一般会要求提供纸质材料。通常来说是要求提供公司营业执照及银行开户许可证扫描件,并由认证机构通过电话联系到申请人核实信息并确认申请人为公司雇员后,通常几个工作日内核发。
DV和OV在浏览器当中都会显示灰色的锁头,而EV会显示绿色的锁或认证的公司名称,因此EV证书对提升公司形象有明显作用,如果你注重自己的品牌宣传,对公司或组织的网络身份和整体形象都有高标准,想与各大巨头网站拥有同等的网络身份和安全等级,EV证书再适合不过了。
阿里云自营建站服务——自助建站类产品云·速成美站及定制类的云·企业官网均已支持一键在网站后台开通HTTPS,用户只需要把自己申请的证书文件(zip包)直接上传到后台即可开启,非常简单。云·企业官网旗舰版赠送了一个价值4250元的EV证书,提供最低128位至256位加密来保障您的用户数据和在线购物的安全,自动激活浏览器绿色地址栏并在地址栏中显示网站经营者的企业名称,让用户更容易识别真正网站经营者真实身份并充分信赖您的网站且给网站带来更多的订单。
几个EV证书的案例:
NIKE https://www.nike.com/cn/zh_cn/
ADIDAS https://www.adidas.com.cn/
无印良品 https://www.muji.com.cn/
苹果 https://www.apple.com/cn/
iOS开发HTTPS实现之信任SSL证书和自签名证书
首先来分析一下什么是HTTPS以及了解HTTPS对于iOS开发者的意义
HTTPS 以及SSL/TSL
什么是SSL?
SSL(Secure Sockets Layer, 安全套接字层),因为原先互联网上使用的 HTTP 协议是明文的,存在很多缺点,比如传输内容会被偷窥(嗅探)和篡改。 SSL 协议的作用就是在传输层对网络连接进行加密。
何为TLS?
到了1999年,SSL 因为应用广泛,已经成为互联网上的事实标准。IETF 就在那年把 SSL 标准化。标准化之后的名称改为 TLS(Transport Layer Security,传输层安全协议)。SSL与TLS可以视作同一个东西的不同阶段
HTTPS
简单来说,HTTPS = HTTP + SSL/TLS, 也就是 HTTP over SSL 或 HTTP over TLS,这是后面加 S 的由来 。
HTTPS和HTTP异同:HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。
在WWDC 2016开发者大会上,苹果宣布了一个最后期限:到2017年1月1日 App Store中的所有应用都必须启用 App Transport Security安全功能。App Transport Security(ATS)是苹果在iOS 9中引入的一项隐私保护功能,屏蔽明文HTTP资源加载,连接必须经过更安全的HTTPS。苹果目前允许开发者暂时关闭ATS,可以继续使用HTTP连接,但到年底所有官方商店的应用都必须强制性使用ATS。
所以对于iOS开发者来说,需要尽早解决HTTPS请求的问题。
发送HTTPS请求信任SSL证书和自签名证书,分为三种情况
1.如果你的app服务端安装的是SLL颁发的CA,可以使用系统方法直接实现信任SSL证书,关于Apple对SSL证书的要求请参考:苹果官方文档CertKeyTrustProgGuide
这种方式不需要在Bundle中引入CA文件,可以交给系统去判断服务器端的证书是不是SSL证书,验证过程也不需要我们去具体实现。
示例代码:
NSURL *URL = [NSURL URLWithString:URLString]; NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10]; //创建同步连接
NSError *error = nil; NSData *receivedData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; NSString *receivedInfo = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
当然,如果你需要同时信任SSL证书和自签名证书的话还是需要在代码中实现CA的验证,这种情况在后面会提到。
2.基于AFNetWorking的SSL特定服务器证书信任处理,重写AFNetWorking的customSecurityPolicy方法,这里我创建了一个HttpRequest类,分别对GET和POST方法进行了封装,以GET方法为例:
+ (void)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure { // 1.获得请求管理者
AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager]; // 2.申明返回的结果是text/html类型
mgr.responseSerializer = [AFHTTPResponseSerializer serializer]; // 3.设置超时时间为10s
mgr.requestSerializer.timeoutInterval = 10; // 加上这行代码,https ssl 验证。
if(openHttpsSSL) {
[mgr setSecurityPolicy:[self customSecurityPolicy]];
} // 4.发送GET请求
[mgr GET:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObj){ if (success) {
success(responseObj);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { if (error) {
failure(error);
}
}];
}
+ (AFSecurityPolicy*)customSecurityPolicy { // /先导入证书
NSString *cerPath = [[NSBundle mainBundle] pathForResource:certificate ofType:@"cer"];//证书的路径
NSData *certData = [NSData dataWithContentsOfFile:cerPath]; // AFSSLPinningModeCertificate 使用证书验证模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; // allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
// 如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES; //validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = NO;
securityPolicy.pinnedCertificates = @[certData]; return securityPolicy;
}
其中的cerPath就是app bundle中证书路径,certificate为证书名称的宏,仅支持cer格式,securityPolicy的相关配置尤为重要,请仔细阅读customSecurityPolicy方法并根据实际情况设置其属性。
这样,就能够在AFNetWorking的基础上使用HTTPS协议访问特定服务器,但是不能信任根证书的CA文件,因此这种方式存在风险,读取pinnedCertificates中的证书数组的时候有可能失败,如果证书不符合,certData就会为nil。
3.更改系统方法,发送异步NSURLConnection请求。
- (void)getDataWithURLRequest { //connection
NSString *urlStr = @"https://developer.apple.com/cn/"; NSURL *url = [NSURL URLWithString:urlStr]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10]; NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
[connection start];
}
重点在于处理NSURLConnection的didReceiveAuthenticationChallenge代理方法,对CA文件进行验证,并建立信任连接。
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { /*
//直接验证服务器是否被认证(serverTrust),这种方式直接忽略证书验证,直接建立连接,但不能过滤其它URL连接,可以理解为一种折衷的处理方式,实际上并不安全,因此不推荐。
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
forAuthenticationChallenge: challenge];
*/
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) { do
{
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust]; NSCAssert(serverTrust != nil, @"serverTrust is nil"); if(nil == serverTrust) break; /* failed */
/**
* 导入多张CA证书(Certification Authority,支持SSL证书以及自签名的CA)
*/
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cloudwin" ofType:@"cer"];//自签名证书
NSData* caCert = [NSData dataWithContentsOfFile:cerPath]; NSString *cerPath2 = [[NSBundle mainBundle] pathForResource:@"apple" ofType:@"cer"];//SSL证书
NSData * caCert2 = [NSData dataWithContentsOfFile:cerPath2]; NSCAssert(caCert != nil, @"caCert is nil"); if(nil == caCert) break; /* failed */
NSCAssert(caCert2 != nil, @"caCert2 is nil"); if (nil == caCert2) { break;
}
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert); NSCAssert(caRef != nil, @"caRef is nil"); if(nil == caRef) break; /* failed */
SecCertificateRef caRef2 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert2); NSCAssert(caRef2 != nil, @"caRef2 is nil"); if(nil == caRef2) break; /* failed */
NSArray *caArray = @[(__bridge id)(caRef),(__bridge id)(caRef2)]; NSCAssert(caArray != nil, @"caArray is nil"); if(nil == caArray) break; /* failed */
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray); NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed"); if(!(errSecSuccess == status)) break; /* failed */
SecTrustResultType result = -1;
status = SecTrustEvaluate(serverTrust, &result); if(!(errSecSuccess == status)) break; /* failed */
NSLog(@"stutas:%d",(int)status); NSLog(@"Result: %d", result); BOOL allowConnect = (result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed); if (allowConnect) { NSLog(@"success");
}else { NSLog(@"error");
} /* https://developer.apple.com/library/ios/technotes/tn2232/_index.html */
/* https://developer.apple.com/library/mac/qa/qa1360/_index.html */
/* kSecTrustResultUnspecified and kSecTrustResultProceed are success */
if(! allowConnect)
{ break; /* failed */
}#if 0
/* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */
/* since the user will likely tap-through to see the dancing bunnies */
if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError) break; /* failed to trust cert (good in this case) */#endif
// The only good exit point
return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
forAuthenticationChallenge: challenge];
} while(0);
} // Bad dog
return [[challenge sender] cancelAuthenticationChallenge: challenge];
}
这里的关键在于result参数的值,根据官方文档的说明,判断(result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed)的值,若为1,则该网站的CA被app信任成功,可以建立数据连接,这意味着所有由该CA签发的各个服务器证书都被信任,而访问其它没有被信任的任何网站都会连接失败。该CA文件既可以是SLL也可以是自签名。
NSURLConnection的其它代理方法实现
#pragma mark -- connect的异步代理方法-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSLog(@"请求被响应");
_mData = [[NSMutableData alloc]init];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data { NSLog(@"开始返回数据片段");
[_mData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection { NSLog(@"链接完成"); //可以在此解析数据
NSString *receiveInfo = [NSJSONSerialization JSONObjectWithData:self.mData options:NSJSONReadingAllowFragments error:nil]; NSLog(@"received data:\\\\n%@",self.mData); NSLog(@"received info:\\\\n%@",receiveInfo);
}//链接出错-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { NSLog(@"error - %@",error);
}
至此,HTTPS信任证书的问题得以解决,这不仅是为了响应Apple强制性使用ATS的要求,也是为了实际生产环境安全性的考虑,HTTPS是未来的趋势,建议尽早支持。
如需参考Demo请移步本人在Github上的开源项目
文/无忌不悔(简书作者)
原文链接:http://www.jianshu.com/p/6b9c8bd5005a#
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
本文转自ljianbing51CTO博客,原文链接:http://blog.51cto.com/ljianbing/1874196 ,如需转载请自行联系原作者
前端面试+学习笔记(HTML+CSS+JavaScript+ES6+Vue+NodeJs)
前端面试+学习笔记(HTML+CSS+JavaScript+ES6+Vue+NodeJs)一. HTML1. 盒子模型是什么:每个元素被表示为一个矩形的盒子,有四个部分组成:内容(content)、内边距(padding)、边框(border)、外边距(margin)。它在页面中所占的实际大小(宽高)是content+padding+border+margin之和。盒模型有两种:标准盒模型(W3C盒模型)、IE盒模型。两种盒模型的区别:标准盒模型内容大小就是content大小、而IE盒模型内容大小则是content+padding+border总的大小。**怎么设置两种盒模型:**通过设置box-sizing属性为content-box(默认值:标准盒模型)、border-box(IE盒模型)。JS怎么获取和设置box的宽高。box-sizing使用场景:若设置子元素的margin或border时可能会撑破父元素的尺寸,就需要使用box-sizing:border-box来将border包含进元素的尺寸中。2.页面导入样式时,使用link和@import有什么区别**link属于XHTML标签,**除了加载CSS外,还能用于定义RSS(简易信息聚合,是一种基于XML标准,在互联网上被广泛采用的内容包装和投递协议)rel连接属性等作用;@import是CSS提供的;只能用于加载CSS页面被加载时,link会同时被加载;而@import引用的CSS会等到页面被加载完成后再加载link是XHTML标签,没有兼容问题;而@import只有在IE5以上才能被识别link支持使用JavaScript控制DOM修改样式;而@import不支持。4.行内元素有哪些?块级元素有哪些?空元素(void)有哪些?**行内元素:**a,b,span,img,input,strong,label,button,select,textarea,em**块级元素:**div,ul(无序列表),ol,li,dl(自定义列表),dt(自定义列表项),dd(自定义列表项的定义),p,h1-h6,blockquote(块引用)空元素(void):即没有内容的HTML元素。br(换行),hr(水平分割线),meta,link,input,img5. src 和 herf 的区别href是指向网络资源所在位置,建立和当前(锚点)或当前文档(链接)之间的连接,用于超链接。src执行外部资源的位置,指向的内容会嵌入到文档中当前标签所在位置,在请求src资源时会将其指向的资源下载并应用到文档中。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕。(性能优化)6. 为什么CSS样式放在头部,JS脚本放在底部浏览器为了更好的用户体验,渲染引擎会尝试尽快在屏幕上显示内容,不会等到所有的HTML元素解析之后在构建和布局DOM树,所以部分内容将被解析并显示。前端一般主要关心首屏的渲染速度,这也是为什么要提倡“图片懒加载”的原因。**其实外部的js和CSS文件时并行下载的。**随着JS技术的发展,JS也开始承担起页面的渲染工作了。如果JS加载需要很长时间,会影响用户体验。所以需要将JS区分为承担页面渲染工作的JS和承担事件处理的JS。渲染页面的JS放在前面,事务处理的JS放在后面。7.常用浏览器,内核Trident内核:(国产的绝大部分浏览器)IE,360,搜狗**Gecko内核:**Firefox,NetScape6及以上**Presto内核:**Opera7及以上Webkit内核:(国产大部分双核浏览器其中一核)Safari(苹果),Chrome浏览器内核:主要分成两部分:渲染引擎和JS引擎。最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎**渲染引擎:**负责取得网页内容(HTML,XML,图像等)、整理讯息(加入CSS等),以及计算网页的显示方式,后会输出至显示器或打印机。JS引擎:解析和执行JavaScript来实现网页的动态效果。8. DOCTYPE作用?严格模式与混杂模式 , 标准模式和怪异模式声明位于HTML文档中的第一行,处于标签之前,告知浏览器的解析器用什么文档标准解析这个文档。严格模式下,排版和JS以浏览器支持的最高标准运行;**混杂模式下,**页面以宽松向后兼容的方式显示**如何触发混杂模式:**DOCTYPE不存在或格式不正确,会导致文档以混合模式呈现**标准模式(standards mode)**是指浏览器按照W3C标准解析执行代码;**怪异模式(quirks mode)**则是使用浏览器自己的方式解析执行代码。浏览器解析时到底使用何种模式,与网页中的DTD声明(文档类型定义,DOCTYPE相关)有关,忽略DTD声明,将使网页进入怪异模式。9.优雅降级和渐进增强渐进增强(progressive enhancement):针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。优雅降级(graceful degradation):一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。区别:优雅降级是**从复杂的现状开始,并试图减少用户体验的供给;渐进增强则是从一个非常基础的,**能够起作用的版本开始,并不断扩充,以适应未来环境的需要。渐进增强观点认为应该关注于内容本身,这使得渐进增强成为一种更为合理的设计范例;优雅降级观点认为应该针对那些最高级、最完善的浏览器来设计网站。10. 对HTML语义化的理解用正确的标签做正确的事情HTML语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析即使在没有样式CSS情况下也以一种文档格式显示,并且是易于阅读的搜索引擎的爬虫也依赖于HTML标记来确定上下文和各个关键字的权重,利于SEO使阅读源代码的人更容易将网站分块,便于阅读维护理解二. CSS1.CSS选择符,优先级**选择符:**id(ID);class(类);element(标签);element element(后代);element>element(子);element,element(群组);element+element(相邻同胞);伪类(:link,:visited,:active,:hover,:focus:first-child,:lang(language));伪元素(:first-letter,:first-line,:before,:after);属性选择器**可继承的选择符:**主要是文本方面的可继承,盒模型相关的属性基本没有继承特性。font-size,font-family,color,ul**不可继承的选择符:**border,padding,margin,width,height**优先级:**同权重下样式定义最近者高。!important>内联样式 即定义在HTML标签内的样式,(1000)>id(100)>class/伪类/属性(10)>伪元素/element(1)**CSS引入伪类和伪元素的原因:**用来修饰DOM树以外的部分。伪类用于当已有元素处于某个状态时,为其添加对应的样式,这个状态根据用户行为而动态变化。伪类的操作对象是DOM树中已有的元素。伪元素用于创建一些不在DOM树中的元素,并为其添加样式伪类和伪元素的区别在于有没有创建一个DOM树之外的元素2. 外边距重叠(collapsing margins)/margin坍塌**是什么:**相邻的两个或多个普通流中的块元素,如果它们设置了外边距,那么在垂直方向上,外边距会发生重叠,以绝对值大的那个为最终结果显示在页面上,即最终的外边距等于发生层叠的外边距中绝对值较大者。**最终外边距:**margin全为正(取最大值)、margin全为负(取绝对值最大的负数)、margin有正有负(分别取正数最大值a,负数的最大绝对值b,a-b)**外边距重叠的应用:**几个段落一起布局,第一个段落的上外边距正常显示,下外边距与第二个段落的上外边距重叠。防止外边距重叠:创建BFC元素。不会发生外边距重叠的情况:行内元素、浮动元素、绝对定位元素之间的外边距都不会叠加。3. BFC(Block Formatting Context,块级格式化上下文)**是什么:**决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。简言之,就是一个特殊的块,内部的元素和外部的元素不会相互影响。BFC内的盒子会在垂直方向上一个接一个地放置,垂直方向上也会发生外边距重叠。**应用场景:**自适应布局(BFC不与float box重叠)、清除浮动(计算BFC的高度时,内部的浮动元素也被计算在内)、防止外边距重叠如何触发BFC:float属性(不为none)、overflow属性(不为visible)、position属性(absolute,fixed)、display属性(inline-block,table-cell,table-caption,flex,inline-flex)。4.元素的position属性**定义:**规定元素的定位类型。**正常文档流:**指的是没有用CSS样式去控制的HTML文档结构,代码的顺序就是网页展示的顺序。**脱离文档流:**指的是元素所显示的位置和文档代码不一致。**static:**默认值。没有定位,元素出现在正常的文档流中。**relative:**生成相对定位的元素,相对于其在正常文档流中的位置进行定位(不脱离文档流)。**absolute:**生成绝对定位的元素,相对于static定位以外的最近父级元素进行定位,即相对于其直接父级元素(脱离文档流)。absolute较少直接单独使用在正常的文档流中,主要运行于进行了相对定位的元素框架层里面,相对该层的左上点进行偏移。**fixed:**生成固定定位元素,相对于浏览器窗口进行定位。**inherit:**从父元素继承position属性的值。**z-index属性:**使用了relative、absolute、fixed三种定位后,都会使正常的文档流发生一定程度的改变,造成元素出现重叠的情形。为了能让重叠的元素有序的显示出来,需要在定位的相关元素加上z-index属性。其值是一个整数值,默认为0,数值越大表示拥有的优先级越高,该属性只对使用了定位的元素有效。5.元素的display属性**定义:**规定元素应该生成的框的类型常用属性值:**inline:**默认值。元素会被显示为内联元素。**none:**元素不会被显示。**block:**元素将显示为块级元素。**inline-block:**行内块元素,即元素像行内元素一样显示,内容像块元素一样显示。**list-item:**元素像块元素一样显示,并添加样式列表标记。**table:**元素会作为块级表格来显示。**table-caption:**元素会作为一个表格标题显示。**inherit:**从父元素继承display属性。display属性值inline和block的区别:block元素会独占一行,默认情况下,block元素宽度自动填满父级元素的宽度;block元素可以设置width、height属性,即使设置了宽度,仍然是独占一行;block元素可以设置margin和padding属性;inline元素不会独占一行,多个相邻的行内元素会排列在同一行里面,其宽度随元素的内容而变化;inline元素设置width、height无效;inline元素的margin和padding属性在水平方向上能产生边距效果,垂直方向不会产生边距效果。display:inline-block元素显示间隙inline-block水平呈现的元素之间,HTML元素标签换行显示或标签之间有空格的情况下会有间距消除办法:移除标签之间的空格;使用margin-left或margin-right取负值;对父元素设置font-size为0,然后对元素的font-size初始化;对父元素设置letter-spacing(字符间距)为负值,然后设置元素的letter-spacing为0;对父元素设置word-spacing(单词间距)为负值,然后设置元素的word-spacing为0。7.overflow属性**定义:**规定当内容溢出元素框时发生的事情**visible:**默认值。内容不会被修剪,会呈现在元素框之外**hidden:**内容会被修剪,并且其余内容不可见**scroll:**内容被修剪,但浏览器会显示滚动条以便查看其余内容**auto:**如果内容被修剪,则浏览器会显示滚动条以便查看其余内容inherit:从父元素继承overflow属性的值8. 初始化CSS样式**为什么要初始化CSS样式:**因为浏览器的兼容问题,不同浏览器对有些标签的默认值时不同的,如果没有对CSS初始化往往会出现浏览器之间页面显示差异。最简单的方法:*{margin:0;padding:0;}**初始化CSS的缺点:**对SEO(搜索引擎优化)有一定的影响。SEO:Search Engine Optimization,搜索引擎的优化。SEO具体是指通过网站结构调整、网站内容建设、网站代码优化以及站外优化,使网站满足搜索引擎的收录排名需求,提高网站在搜索引擎中关键字的排名,从而吸引精准用户进入网站,获得免费流量,产生直接销售或品牌推广。**什么是CSS Hack:**一般来说针对不同的浏览器写不同的CSS,就是CSS Hack。9.CSS属性cursor?cursor属性规定要显示的鼠标的光标类型。常用取值:pointer(手),crosshair(十字线),default(箭头),auto(浏览器设置的光标)HTML5一. HTML5 新特性1. HTML5新特性:主要是关于图像、位置、存储、多任务等功能的增加。包括:绘画canvas(通过脚本实现绘画)用于媒介回放的video和audio元素本地离线存储localStorage、sessionStorage语义化更好的内容元素:article、footer、header、nav、section表单元素:datalist(规定输入域的选项列表)、output(用于不同元素的输出)、keygen(提供一种验证用户的可靠方法)input类型:color、date、month、week、number、email(检测是否为一个email格式的地址)、range(滑动条)、search、url、tel(输入电话号码,-time选择时间)2. HTML5新标签的浏览器兼容问题:当在页面中使用HTML5新标签时,可能会得到三种不同的结果:新标签被当做错误处理并被忽略,在DOM构建时会当做这个标签不存在新标签被当做错误处理,在DOM构建时,这个新标签会被构造成行内元素新标签被识别成HTML5标签,然后用DOM节点对齐进行替换3.解决兼容性问题:**实现标签被识别。**通过document.createElement(tagName)即可让浏览器识别新标签,浏览器支持新标签后,还可以为其添加CSS样式JavaScript解决方案:使用html5shim。在中调用以下代码(也可下载到本地后调用):<!--[if It IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->使用kill IE6<!--[if It IE 6]>
<script src="http://letskillie6.googlecode.com/svn/trunk/letskillie6.an_CN.pack.js"></script>
<![endif]-->4. 如何区分HTML和HTML5:DOCTYPE声明新增的元素5. HTML5移除的元素:**纯表现的元素:**big,center,font,strike(删除线),u(下划线),s(删除线)**对可用性产生负面影响的元素:**frame,frameset,noframes6.iframe的缺点会阻塞主页面的onload事件搜索引擎的检索程序无法解读这种页面,不利于SEO二. cookie和localStorage的区别1.共同点:cookie、sessionStorage和localStorage都是由浏览器存储在本地的数据。区别:cookie是网站为了标识用户身份而存储在用户本地终端上的数据(通常经过加密),数据始终在同源的http请求中携带,即在浏览器和服务器之间来回传递;localStorage不会自动把数据发给服务器,尽在本地保存cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下,存储大小也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据(如会话标识);localStorage也有存储大小的限制,但比cookie大很多,可以达到5M或更大。cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭;localStorage始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;sessionStorage在当前浏览器窗口关闭之后自动删除localStorage支持**事件通知机制,**可以将数据更新的通知发送给监听者,API接口使用更方便;cookie的原生接口不友好,需要程序员自己封装**localStorage如何删除数据:**localStorage对象可以将数据长期保存在客户端,除非人为清除,提供了以下几个方法:**存储:**localStorage.setItem(key,value) 如果key存在,更新value**获取:**localStorage.getItem(key) 如果key不存在,则返回null**删除:**localStorage.removeItem(key) 一旦删除,key对应的数据将会全部删除全部清除:localStorage.clear()使用removeItem逐个删除太麻烦,可以使用**clear,**执行的结果是清除所有的localStorage对象保存的数据localStorage存储的数据是不能跨浏览器共用的,一个浏览器只能读取各自浏览器的数据。CSS3一. CSS3新特性1. 新特性边框:border-radius(圆角)、box-shadow(阴影)、border-image(边框图片)背景:background-size(背景图片的尺寸)、background-origin(背景图片的定位区域)文本效果:text-shadow(文本阴影)、word-wrap(文本换行)转换和变形:transform(包括2D,3D转换,rotate(angle),translate(x,y),scale(x,y))过渡:transition动画:animation多列:column-count(元素被分隔的列数)、column-gap(列之间的间隔)、column-rule(洌之间的宽度,样式,颜色规则)用户界面:resize(规定是否可由用户调整元素尺寸)、box-sizing(以确切的方式适应某个区域的具体内容)、outline-offset(对轮廓进行偏移)2. 新增伪类:element:before(在元素之前添加内容) element:after(在元素之后添加内容)element:first-of-type、element:last-of-type、element:only-of-type、element:only-child、element:nth-child(n)(第n个):checked、:disabled、:enabled3. 四个锚点伪类的设置问题:**问题描述:**超链接访问后hover样式就不出现了,被点击访问过的超链接样式不再具有hover和active**解决办法:**爱恨原则LoVe/HAte。改变CSS属性的排列顺序,L-V-H-A 即a:link{} a:visited() a:hover{} a:active{}4.transition、transform和animation的区别transform是指转换,可以将元素移动、旋转、倾斜、拉伸。没有变化的过程。而transition和animation都加上了时间属性,所以能产生动画效果transition是指过渡,一般由行为(hover等)触发;而animation则是自动触发transition只能设置头尾,所有样式属性一起变化;animation可以设定每一帧的样式和时间,且可以循环播放。5.rgba和opacity的区别?rgba和opacity都能实现透明效果,但最大的不同在于opacity作用于元素本身以及元素内的所有内容,而rgba只作用于元素本身,子元素不会继承透明效果。**rgba是CSS3的属性,**用法说明:rgba(R,G,B,A),参数说明R(红色值。正整数|百分数),G(绿色值。正整数|百分数),B(蓝色值。正整数|百分比),A(Alpha透明度。0(透明)~1)。IE6-8不支持rgba模式,可以使用IE滤镜处理:filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr=#AARRGGBBAA,endColorstr=#AARRGGBBAA);其中AA,RR,GG,BB分别表示Alpha,R,G,B,取值为00-FF。opacity也是CSS3的属性,用法说明:opacity:value 其中value取值0(透明)~1。对于IE6-8,可以用IE滤镜处理:filter:alpha(opacity=50); /对应于opacity:0.5;浮动一. 清除浮动父级div定义height结尾处加空div标签,样式clear:both父级div定义伪类:after和zoom通过CSS伪元素在容器的内部元素最后添加了一个看不见的空格"020"或点".",并且赋予clear属性来清除浮动。需要注意的是为了IE6和IE7浏览器,要给clearfix这个class添加一条zoom:1;触发haslayout。父级div定义overflow:hidden(同时还要定义width或zoom:1,不能定义height)父级div定义overflow:auto(同时还要定义width或zoom:1,不能定义height)父级也浮动,需要定义width(不推荐)父级div定义display:table(不推荐)结尾处加br标签,样式clear:both(父元素div定义zoom:1,不推荐)二. 属性clear取值1. 定义:规定元素的那一侧不允许其他浮动元素2. 取值none:(默认值)。允许浮动元素left:在左侧不允许浮动元素right:在右侧不允许浮动元素both:在左右侧均不允许浮动元素inherit:从父元素继承clear属性三. 属性zoom取值1. 定义:设置或检索对象的缩放比例2.取值normal:(默认值),使用对象的实际尺寸:用浮点数来定义缩放比例,不允许负值:用百分比来定义缩放比例,不允许负值新的注意点:一. 重排(reflow) 与重绘(repaint)浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排重绘是一个元素外观的改变所触发的浏览器行为(例如改变visibility,outline,background等属性),浏览器会根据元素的新属性重新绘制,是元素呈现新的外观。**重排时更明显的一种改变,可以理解为渲染树需要重新计算。**常见的触发重排的操作:DOM元素的几何属性变化DOM树的结构变化(例如节点的增减、移动)获取某些属性(例如offsetTop,offsetLeft,offsetHeight,offsetWidth,clientWidth,clientHeight等)改变元素的一些样式(例如调整浏览器窗口大小)重绘不会带来重新布局,并不一定伴随着重排。在实践中,应该尽量减少重排次数和缩小重排的影响范围。有以下几种方法:将多次改变样式属性的操作合并成一次操作将需要多次重排的元素,position属性设为absolute或fixed,使其脱离文档流,这样它的变化就不会影响到其他元素在内存中多次操作节点,完成后再添加到文档中去如果要对一个元素进行复杂的操作,可以将其display属性设置为none使其隐藏,待操作完成后再显示在需要经常获取那些引起浏览器重排的属性值时,要缓存到变量如何在网页中添加空格? 在HTML代码中输入&nbsp;二. 如何在网页中显示代码?对于单行代码,使用标签<code>代码</code>对于多行代码,使用标签<pre></pre> (被包围在pre元素中的文本通常会保留空格和换行符)26.使用mailto在网页中链接Email地址?(1)a标签有一个作用是可以链接Email地址,使用mailto能让访问者便捷想网站管理者发送电子邮件(2)如果mailto后面同时又多个参数的话,第一个参数必须以?开头,后面的参数每一个都以&分隔三. form表单当前页面无刷新提交?使用target属性取值为iframe元素的name属性值。具体如下:在当前页面建一个iframe并隐藏(display:none)给这个iframe取名(name=“id_iframe”)设置form表单的target属性(target=“id_iframe”)提交表单,就是无刷新iframe标签一. 优点iframe能够把嵌入的页面展示出来,如果有多个网页引用iframe,只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用重载页面时不需要重载整个页面,只需要重载页面中的一个框架页,减少了数据的传输,增加了网页的下载速度方便制作导航栏二. 缺点会产生很多页面,不利于管理浏览器的前进/后退按钮无效无法被一些搜索引擎索引到,现在搜索引擎爬虫还不能很好的处理iframe中的内容,所以不利于SEO多数小型的移动设备无法显示框架,兼容性差多框架的页面会增加服务器的http请求,对于大型网站是不可取的综上,目前框架中的所有优点完全可以使用Ajax实现,因此不推荐使用框架ES6一、问:ES6是什么,为什么要学习它,不学习ES6会怎么样?答: ES6是新一代的JS语言标准,对分JS语言核心内容做了升级优化,规范了JS使用标准,新增了JS原生方法,使得JS使用更加规范,更加优雅,更适合大型应用的开发。学习ES6是成为专业前端正规军的必经之路。不学习ES6也可以写代码打鬼子,但是最多只能当个游击队长。二、问:ES5、ES6和ES2015有什么区别?答: ES2015特指在2015年发布的新一代JS语言标准,ES6泛指下一代JS语言标准,包含ES2015、ES2016、ES2017、ES2018等。现阶段在绝大部分场景下,ES2015默认等同ES6。ES5泛指上一代语言标准。ES2015可以理解为ES5和ES6的时间分界线。三、问:babel是什么,有什么作用?答:babel是一个 ES6 转码器,可以将 ES6 代码转为 ES5 代码,以便兼容那些还没支持ES6的平台。四、问:let有什么用,有了var为什么还要用let?答: 在ES6之前,声明变量只能用var,var方式声明变量其实是很不合理的,准确的说,是因为ES5里面没有块级作用域是很不合理的,甚至可以说是一个语言层面的bug(这也是很多c++、java开发人员看不懂,也瞧不起JS语言的劣势之一)。没有块级作用域回来带很多难以理解的问题,比如for循环var变量泄露,变量覆盖等问题。let 声明的变量拥有自己的块级作用域,且修复了var声明变量带来的变量提升问题。五、问:举一些ES6对String字符串类型做的常用升级优化?答:1、优化部分:ES6新增了字符串模板,在拼接大段字符串时,用反斜杠(`)取代以往的字符串相加的形式,能保留所有空格和换行,使得字符串拼接看起来更加直观,更加优雅。2、升级部分:ES6在String原型上新增了includes()方法,用于取代传统的只能用indexOf查找包含字符的方法(indexOf返回-1表示没查到不如includes方法返回false更明确,语义更清晰), 此外还新增了startsWith(), endsWith(), padStart(),padEnd(),repeat()等方法,可方便的用于查找,补全字符串。六、问:举一些ES6对Array数组类型做的常用升级优化?答:1、优化部分:a. 数组解构赋值。ES6可以直接以let [a,b,c] = [1,2,3]形式进行变量赋值,在声明较多变量时,不用再写很多let(var),且映射关系清晰,且支持赋默认值。b. 扩展运算符。ES6新增的扩展运算符(…)(重要),可以轻松的实现数组和松散序列的相互转化,可以取代arguments对象和apply方法,轻松获取未知参数个数情况下的参数集合。(尤其是在ES5中,arguments并不是一个真正的数组,而是一个类数组的对象,但是扩展运算符的逆运算却可以返回一个真正的数组)。扩展运算符还可以轻松方便的实现数组的复制和解构赋值(let a = [2,3,4]; let b = [...a])。2、升级部分:ES6在Array原型上新增了find()方法,用于取代传统的只能用indexOf查找包含数组项目的方法,且修复了indexOf查找不到NaN的bug([NaN].indexOf(NaN) === -1).此外还新增了copyWithin(), includes(), fill(),flat()等方法,可方便的用于字符串的查找,补全,转换等。七、问:举一些ES6对Number数字类型做的常用升级优化?答:1、优化部分:ES6在Number原型上新增了isFinite(), isNaN()方法,用来取代传统的全局isFinite(), isNaN()方法检测数值是否有限、是否是NaN。ES5的isFinite(), isNaN()方法都会先将非数值类型的参数转化为Number类型再做判断,这其实是不合理的,最造成isNaN('NaN') === true的奇怪行为–'NaN’是一个字符串,但是isNaN却说这就是NaN。而Number.isFinite()和Number.isNaN()则不会有此类问题(Number.isNaN('NaN') === false)。(isFinite()同上)2、升级部分:ES6在Math对象上新增了Math.cbrt(),trunc(),hypot()等等较多的科学计数法运算方法,可以更加全面的进行立方根、求和立方根等等科学计算。八、问:举一些ES6对Object类型做的常用升级优化?(重要)答:1、优化部分:a. 对象属性变量式声明。ES6可以直接以变量形式声明对象属性或者方法,。比传统的键值对形式声明更加简洁,更加方便,语义更加清晰。let [apple, orange] = ['red appe', 'yellow orange'];
let myFruits = {apple, orange}; // let myFruits = {apple: 'red appe', orange: 'yellow orange'};
复制代码尤其在对象解构赋值(见优化部分b.)或者模块输出变量时,这种写法的好处体现的最为明显:let {keys, values, entries} = Object;
let MyOwnMethods = {keys, values, entries}; // let MyOwnMethods = {keys: keys, values: values, entries: entries}
复制代码可以看到属性变量式声明属性看起来更加简洁明了。方法也可以采用简洁写法:let es5Fun = {
method: function(){}
};
let es6Fun = {
method(){}
}
复制代码b. 对象的解构赋值。 ES6对象也可以像数组解构赋值那样,进行变量的解构赋值:let {apple, orange} = {apple: 'red appe', orange: 'yellow orange'};
复制代码c. 对象的扩展运算符(…)。 ES6对象的扩展运算符和数组扩展运算符用法本质上差别不大,毕竟数组也就是特殊的对象。对象的扩展运算符一个最常用也最好用的用处就在于可以轻松的取出一个目标对象内部全部或者部分的可遍历属性,从而进行对象的合并和分解。let {apple, orange, ...otherFruits} = {apple: 'red apple', orange: 'yellow orange', grape: 'purple grape', peach: 'sweet peach'};
// otherFruits {grape: 'purple grape', peach: 'sweet peach'}
// 注意: 对象的扩展运算符用在解构赋值时,扩展运算符只能用在最有一个参数(otherFruits后面不能再跟其他参数)
let moreFruits = {watermelon: 'nice watermelon'};
let allFruits = {apple, orange, ...otherFruits, ...moreFruits};
复制代码d. super 关键字。ES6在Class类里新增了类似this的关键字super。同this总是指向当前函数所在的对象不同,super关键字总是指向当前函数所在对象的原型对象。2、升级部分:a. ES6在Object原型上新增了is()方法,做两个目标对象的相等比较,用来完善’=‘方法。’='方法中NaN === NaN //false其实是不合理的,Object.is修复了这个小bug。(Object.is(NaN, NaN) // true)b. ES6在Object原型上新增了assign()方法,用于对象新增属性或者多个对象合并。const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
复制代码注意: assign合并的对象target只能合并source1、source2中的自身属性,并不会合并source1、source2中的继承属性,也不会合并不可枚举的属性,且无法正确复制get和set属性(会直接执行get/set函数,取return的值)。c. ES6在Object原型上新增了getOwnPropertyDescriptors()方法,此方法增强了ES5中getOwnPropertyDescriptor()方法,可以获取指定对象所有自身属性的描述对象。结合defineProperties()方法,可以完美复制对象,包括复制get和set属性。d. ES6在Object原型上新增了getPrototypeOf()和setPrototypeOf()方法,用来获取或设置当前对象的prototype对象。这个方法存在的意义在于,ES5中获取设置prototype对像是通过__proto__属性来实现的,然而__proto__属性并不是ES规范中的明文规定的属性,只是浏览器各大产商“私自”加上去的属性,只不过因为适用范围广而被默认使用了,再非浏览器环境中并不一定就可以使用,所以为了稳妥起见,获取或设置当前对象的prototype对象时,都应该采用ES6新增的标准用法。d. ES6在Object原型上还新增了Object.keys(),Object.values(),Object.entries()方法,用来获取对象的所有键、所有值和所有键值对数组。九、问:举一些ES6对Function函数类型做的常用升级优化?(重要)答:1、优化部分:a. 箭头函数**(核心)**。箭头函数是ES6核心的升级项之一,箭头函数里没有自己的this,这改变了以往JS函数中最让人难以理解的this运行机制。主要优化点:Ⅰ. 箭头函数内的this指向的是函数定义时所在的对象,而不是函数执行时所在的对象。ES5函数里的this总是指向函数执行时所在的对象,这使得在很多情况下this的指向变得很难理解,尤其是非严格模式情况下,this有时候会指向全局对象,这甚至也可以归结为语言层面的bug之一。ES6的箭头函数优化了这一点,它的内部没有自己的this,这也就导致了this总是指向上一层的this,如果上一层还是箭头函数,则继续向上指,直到指向到有自己this的函数为止,并作为自己的this。Ⅱ. 箭头函数不能用作构造函数,因为它没有自己的this,无法实例化。Ⅲ. 也是因为箭头函数没有自己的this,所以箭头函数 内也不存在arguments对象。(可以用扩展运算符代替)b. 函数默认赋值。ES6之前,函数的形参是无法给默认值得,只能在函数内部通过变通方法实现。ES6以更简洁更明确的方式进行函数默认赋值。function es6Fuc (x, y = 'default') {
console.log(x, y);
}
es6Fuc(4) // 4, default
复制代码2、升级部分:ES6新增了双冒号运算符,用来取代以往的bind,call,和apply。(浏览器暂不支持,Babel已经支持转码)foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
复制代码十、问:Symbol是什么,有什么作用?答: Symbol是ES6引入的第七种原始数据类型(说法不准确,应该是第七种数据类型,Object不是原始数据类型之一,已更正),所有Symbol()生成的值都是独一无二的,可以从根本上解决对象属性太多导致属性名冲突覆盖的问题。对象中Symbol()属性不能被for…in遍历,但是也不是私有属性。十一、问:Set是什么,有什么作用?答: Set是ES6引入的一种类似Array的新的数据结构,Set实例的成员类似于数组item成员,区别是Set实例的成员都是唯一,不重复的。这个特性可以轻松地实现数组去重。十二、问:Map是什么,有什么作用?答: Map是ES6引入的一种类似Object的新的数据结构,Map可以理解为是Object的超集,打破了以传统键值对形式定义对象,对象的key不再局限于字符串,也可以是Object。可以更加全面的描述对象的属性。十三、问:Proxy是什么,有什么作用?答: Proxy是ES6新增的一个构造函数,可以理解为JS语言的一个代理,用来改变JS默认的一些语言行为,包括拦截默认的get/set等底层方法,使得JS的使用自由度更高,可以最大限度的满足开发者的需求。比如通过拦截对象的get/set方法,可以轻松地定制自己想要的key或者value。下面的例子可以看到,随便定义一个myOwnObj的key,都可以变成自己想要的函数。function createMyOwnObj() {
//想把所有的key都变成函数,或者Promise,或者anything
return new Proxy({}, {
get(target, propKey, receiver) {
return new Promise((resolve, reject) => {
setTimeout(() => {
let randomBoolean = Math.random() > 0.5;
let Message;
if (randomBoolean) {
Message = `你的${propKey}运气不错,成功了`;
resolve(Message);
} else {
Message = `你的${propKey}运气不行,失败了`;
reject(Message);
}
}, 1000);
});
}
});
}
let myOwnObj = createMyOwnObj();
myOwnObj.hahaha.then(result => {
console.log(result) //你的hahaha运气不错,成功了
}).catch(error => {
console.log(error) //你的hahaha运气不行,失败了
})
myOwnObj.wuwuwu.then(result => {
console.log(result) //你的wuwuwu运气不错,成功了
}).catch(error => {
console.log(error) //你的wuwuwu运气不行,失败了
})
复制代码十四、问:Reflect是什么,有什么作用?答: Reflect是ES6引入的一个新的对象,他的主要作用有两点,一是将原生的一些零散分布在Object、Function或者全局函数里的方法(如apply、delete、get、set等等),统一整合到Reflect上,这样可以更加方便更加统一的管理一些原生API。其次就是因为Proxy可以改写默认的原生API,如果一旦原生API别改写可能就找不到了,所以Reflect也可以起到备份原生API的作用,使得即使原生API被改写了之后,也可以在被改写之后的API用上默认的API。十五、问:Promise是什么,有什么作用?答: Promise是ES6引入的一个新的对象,他的主要作用是用来解决JS异步机制里,回调机制产生的“回调地狱”。它并不是什么突破性的API,只是封装了异步回调形式,使得异步回调可以写的更加优雅,可读性更高,而且可以链式调用。十六、问:Iterator是什么,有什么作用?(重要)答: Iterator是ES6中一个很重要概念,它并不是对象,也不是任何一种数据类型。因为ES6新增了Set、Map类型,他们和Array、Object类型很像,Array、Object都是可以遍历的,但是Set、Map都不能用for循环遍历,解决这个问题有两种方案,一种是为Set、Map单独新增一个用来遍历的API,另一种是为Set、Map、Array、Object新增一个统一的遍历API,显然,第二种更好,ES6也就顺其自然的需要一种设计标准,来统一所有可遍历类型的遍历方式。Iterator正是这样一种标准。或者说是一种规范理念。就好像JavaScript是ECMAScript标准的一种具体实现一样,Iterator标准的具体实现是Iterator遍历器。Iterator标准规定,所有部署了key值为[Symbol.iterator],且[Symbol.iterator]的value是标准的Iterator接口函数(标准的Iterator接口函数: 该函数必须返回一个对象,且对象中包含next方法,且执行next()能返回包含value/done属性的Iterator对象)的对象,都称之为可遍历对象,next()后返回的Iterator对象也就是Iterator遍历器。//obj就是可遍历的,因为它遵循了Iterator标准,且包含[Symbol.iterator]方法,方法函数也符合标准的Iterator接口规范。
//obj.[Symbol.iterator]() 就是Iterator遍历器
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};
复制代码ES6给Set、Map、Array、String都加上了[Symbol.iterator]方法,且[Symbol.iterator]方法函数也符合标准的Iterator接口规范,所以Set、Map、Array、String默认都是可以遍历的。//Array
let array = ['red', 'green', 'blue'];
array[Symbol.iterator]() //Iterator遍历器
array[Symbol.iterator]().next() //{value: "red", done: false}
//String
let string = '1122334455';
string[Symbol.iterator]() //Iterator遍历器
string[Symbol.iterator]().next() //{value: "1", done: false}
//set
let set = new Set(['red', 'green', 'blue']);
set[Symbol.iterator]() //Iterator遍历器
set[Symbol.iterator]().next() //{value: "red", done: false}
//Map
let map = new Map();
let obj= {map: 'map'};
map.set(obj, 'mapValue');
map[Symbol.iterator]().next() {value: Array(2), done: false}
复制代码十七、问:for…in 和for…of有什么区别?答: 如果看到问题十六,那么就很好回答。问题十六提到了ES6统一了遍历标准,制定了可遍历对象,那么用什么方法去遍历呢?答案就是用for…of。ES6规定,有所部署了载了Iterator接口的对象(可遍历对象)都可以通过for…of去遍历,而for…in仅仅可以遍历对象。这也就意味着,数组也可以用for…of遍历,这极大地方便了数组的取值,且避免了很多程序用for…in去遍历数组的恶习。上面提到的扩展运算符本质上也就是for…of循环的一种实现。十八、Generator函数是什么,有什么作用?答: 如果说JavaScript是ECMAScript标准的一种具体实现、Iterator遍历器是Iterator的具体实现,那么Generator函数可以说是Iterator接口的具体实现方式。执行Generator函数会返回一个遍历器对象,每一次Generator函数里面的yield都相当一次遍历器对象的next()方法,并且可以通过next(value)方法传入自定义的value,来改变Generator函数的行为。Generator函数可以通过配合Thunk 函数更轻松更优雅的实现异步编程和控制流管理。十九、async函数是什么,有什么作用?答: async函数可以理解为内置自动执行器的Generator函数语法糖,它配合ES6的Promise近乎完美的实现了异步编程解决方案。二十、Class、extends是什么,有什么作用?答: ES6 的class可以看作只是一个ES5生成实例对象的构造函数的语法糖。它参考了java语言,定义了一个类的概念,让对象原型写法更加清晰,对象实例化更像是一种面向对象编程。Class类可以通过extends实现继承。它和ES5构造函数的不同点:a. 类的内部定义的所有方法,都是不可枚举的。///ES5
function ES5Fun (x, y) {
this.x = x;
this.y = y;
}
ES5Fun.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
}
var p = new ES5Fun(1, 3);
p.toString();
Object.keys(ES5Fun.prototype); //['toString']
//ES6
class ES6Fun {
constructor (x, y) {
this.x = x;
this.y = y;
}
toString () {
return '(' + this.x + ', ' + this.y + ')';
}
}
Object.keys(ES6Fun.prototype); //[]
复制代码b.ES6的class类必须用new命令操作,而ES5的构造函数不用new也可以执行。c.ES6的class类不存在变量提升,必须先定义class之后才能实例化,不像ES5中可以将构造函数写在实例化之后。d.ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。二十一、module、export、import是什么,有什么作用?答: module、export、import是ES6用来统一前端模块化方案的设计思路和实现方案。export、import的出现统一了前端模块化的实现方案,整合规范了浏览器/服务端的模块化方法,用来取代传统的AMD/CMD、requireJS、seaJS、commondJS等等一系列前端模块不同的实现方案,使前端模块化更加统一规范,JS也能更加能实现大型的应用程序开发。import引入的模块是静态加载(编译阶段加载)而不是动态加载(运行时加载)。import引入export导出的接口值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。二十二、日常前端代码开发中,有哪些值得用ES6去改进的编程优化或者规范?答:1、常用箭头函数来取代var self = this;的做法。2、常用let取代var命令。3、常用数组/对象的结构赋值来命名变量,结构更清晰,语义更明确,可读性更好。4、在长字符串多变量组合场合,用模板字符串来取代字符串累加,能取得更好地效果和阅读体验。5、用Class类取代传统的构造函数,来生成实例化对象。6、在大型应用开发中,要保持module模块化开发思维,分清模块之间的关系,常用import、export方法。VUE一、对于MVVM的理解?MVVM 是 Model-View-ViewModel 的缩写。Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。View 代表UI 组件,它负责将数据模型转化成UI 展现出来。ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。二、Vue的生命周期beforeCreate(创建前) 在数据观测和初始化事件还未开始created(创建后) 完成数据观测,属性和方法的运算,初始化事件,el属性还没有显示出来 **beforeMount**(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。 **mounted**(载入后) 在el 被新创建的 vm.el属性还没有显示出来∗∗beforeMount∗∗(载入前)在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。∗∗mounted∗∗(载入后)在el被新创建的vm.el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。1.什么是vue生命周期?答: Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。2.vue生命周期的作用是什么?答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。3.vue生命周期总共有几个阶段?答:它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。4.第一次页面加载会触发哪几个钩子?答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。5.DOM 渲染在 哪个周期中就已经完成?答:DOM 渲染在 mounted 中就已经完成了。三、 Vue实现数据双向绑定的原理:Object.defineProperty()vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过**Object.defineProperty()**来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。js实现简单的双向绑定<body>
<div id="app">
<input type="text" id="txt">
<p id="show"></p>
</div>
</body>
<script type="text/javascript">
var obj = {}
Object.defineProperty(obj, 'txt', {
get: function () {
return obj
},
set: function (newValue) {
document.getElementById('txt').value = newValue
document.getElementById('show').innerHTML = newValue
}
})
document.addEventListener('keyup', function (e) {
obj.txt = e.target.value
})
</script>四、Vue组件间的参数传递1.父组件与子组件传值父组件传给子组件:子组件通过props方法接受数据;子组件传给父组件:$emit方法传递参数2.非父子组件间的数据传递,兄弟组件传值eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。(虽然也有不少人推荐直接用VUEX,具体来说看需求咯。技术只是手段,目的达到才是王道。)五、Vue的路由实现:hash模式 和 history模式**hash模式:**在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。**history模式:**history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”六、Vue与Angular以及React的区别?(版本在不断更新,以下的区别有可能不是很正确。我工作中只用到vue,对angular和react不怎么熟)1.与AngularJS的区别相同点:都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。不同点:AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。2.与React的区别相同点:React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。不同点:React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。七、vue路由的钩子函数首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。beforeEach主要有3个参数to,from,next:to:route即将进入的目标路由对象,from:route当前导航正要离开的路由next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。八、vuex是什么?怎么使用?哪种功能场景使用它?只用来读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。在main.js引入store,注入。新建了一个目录store,…… export 。场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-biCZCx5s-1611887935896)(C:\Users\zjx\Desktop\桌面文件\web面试\Vuepng.png)]stateVuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。mutationsmutations定义的方法动态修改Vuex 的 store 中的状态或数据。getters类似vue的计算属性,主要用来过滤一些数据。actionactions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。const store = new Vuex.Store({ //store实例
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})modules项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
})九、vue-cli如何新增自定义指令?1.创建局部指令var app = new Vue({
el: '#app',
data: {
},
// 创建指令(可以多个)
directives: {
// 指令名称
dir1: {
inserted(el) {
// 指令中第一个参数是当前使用指令的DOM
console.log(el);
console.log(arguments);
// 对DOM进行操作
el.style.width = '200px';
el.style.height = '200px';
el.style.background = '#000';
}
}
}
})2.全局指令Vue.directive('dir2', {
inserted(el) {
console.log(el);
}
})3.指令的使用<div id="app">
<div v-dir1></div>
<div v-dir2></div>
</div>十、vue如何自定义一个过滤器?html代码:<div id="app">
<input type="text" v-model="msg" />
{{msg| capitalize }}
</div>JS代码:var vm=new Vue({
el:"#app",
data:{
msg:''
},
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})全局定义过滤器Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})过滤器接收表达式的值 (msg) 作为第一个参数。capitalize 过滤器将会收到 msg的值作为第一个参数。十一、对keep-alive 的了解?keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。使用方法<keep-alive include='include_components' exclude='exclude_components'>
<component>
<!-- 该组件是否缓存取决于include和exclude属性 -->
</component>
</keep-alive>参数解释include - 字符串或正则表达式,只有名称匹配的组件会被缓存exclude - 字符串或正则表达式,任何名称匹配的组件都不会被缓存include 和 exclude 的属性允许组件有条件地缓存。二者都可以用“,”分隔字符串、正则表达式、数组。当使用正则或者是数组时,要记得使用v-bind 。使用示例<!-- 逗号分隔字符串,只有组件a与b被缓存。 -->
<keep-alive include="a,b">
<component></component>
</keep-alive>
<!-- 正则表达式 (需要使用 v-bind,符合匹配规则的都会被缓存) -->
<keep-alive :include="/a|b/">
<component></component>
</keep-alive>
<!-- Array (需要使用 v-bind,被包含的都会被缓存) -->
<keep-alive :include="['a', 'b']">
<component></component>
</keep-alive>十二、一句话就能回答的面试题1.css只在当前组件起作用答:在style标签中写入scoped即可 例如:2.v-if 和 v-show 区别答:v-if按照条件是否渲染,v-show是display的block或none;3.route和route和router的区别答:$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。4.vue.js的两个核心是什么?答:数据驱动、组件系统5.vue几种常用的指令答:v-for 、 v-if 、v-bind、v-on、v-show、v-else6.vue常用的修饰符?答:.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;.capture: 事件侦听,事件发生的时候会调用7.v-on 可以绑定多个方法吗?答:可以8.vue中 key 值的作用?答:当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM。9.什么是vue的计算属性?答:在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。好处:①使得数据处理结构清晰;②依赖于数据,数据更新,处理结果自动更新;③计算属性内部this指向vm实例;④在template调用时,直接写计算属性名即可;⑤常用的是getter方法,获取数据,也可以使用set方法改变数据;⑥相较于methods,不管依赖的数据变不变,methods都会重新计算,但是依赖数据不变的时候computed从缓存中获取,不会重新计算。10.vue等单页面应用及其优缺点答:优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。11.怎么定义 vue-router 的动态路由? 怎么获取传过来的值答:在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 router 对象的 params.id 获取。算法面试1. 字符串回文判断思路:回文是指把相同的词汇或句子,在下文中调换位置或颠倒过来,产生首尾回环的情景,叫做回文,也叫回环。将一个字符串首尾倒序排列,如果与原字符串相等,则这个字符串回文。<script type="text/javascript">
var str1 = 'abcdefgh';
var str2 = 'abcdcba';
function plalindrome(str){
return str == str.split('').reverse().join('');
}
console.log(plalindrome(str1));//false
console.log(plalindrome(str2));//true
</script>2. 数组去重思路:利用indexOf()a方法,在遍历原数组,若里面的元素第一次出现,则放在数组arr1中,遍历完之后,arr1中存放的是无重复的新数组<script type="text/javascript">
var arr = [2,4,2,2,5,6,7,8,9,9,9];
function unique(arr){
var arr1 = [];
for (var i = 0;i < arr.length;i ++){
if(arr1.indexOf(arr[i]) == -1){
arr1.push(arr[i]);
}
}
return arr1;
}
console.log(unique(arr));//[2, 4, 5, 6, 7, 8, 9]
</script>3. 统计一个字符串中出现最多的字母思路:在另外一个数组存放原数组每个元素出现的位置次数,且次数跟存放不重复数组的下标对应,然后取出最多的次数,对应的下标就是不重复数组里面那个出现次数最多的元素的下标<script type="text/javascript">
var str1 = "jhadfgskjfajhdewqe";
var arr1 = str1.split('');
console.log(arr1);
function MostUnit(){
var arrA = [];
var arrB = [];
for(var i = 0 ;i <arr1.length; i ++){
if(arrA.indexOf(arr1[i])==-1){
arrA.push(arr1[i]);
arrB.push(1);
}else {
arrB[arrA.indexOf(arr1[i])] ++;
}
}
console.log(arrB)
console.log(arrA[arrB.indexOf(Math.max.apply(Math,arrB))]);
}
MostUnit();//j
</script>4. 冒泡排序:比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较<script type="text/javascript">
var arr1 = [2,3,45,64,321,3,21,321,31,999];
function bubbleSort(arr) {
for(var i = 0 ;i < arr1.length-1 ;i ++){
for(var j = 0; j < arr1.length - i - 1 ;j ++){
if(arr[j]>arr[j+1]) {
let tem = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tem;
}
}
}
return arr;
}
console.log(bubbleSort(arr1));//[2, 3, 3, 21, 31, 45, 64, 321, 321, 999]
</script>5. 快速排序:思路:算法参考某个元素值,将小于它的值,放到左数组中,大于它的值的元素就放到右数组中,然后递归进行上一次左右数组的操作,返回合并的数组就是已经排好顺序的数组了。<script type="text/javascript">
var arr1 = [1,4,765,86,53,87,53,32,6,64,2,3,767,34,1,4,35,6];
function quickSort(arr){
if(arr.length <= 1){
return arr;
}
var leftArr = [];
var rightArr = [];
var q = arr[0];
for(var i = 1;i < arr.length; i++) {
if(arr[i]>q) {
rightArr.push(arr[i]);
}else{
leftArr.push(arr[i]);
}
}
return [].concat(quickSort(leftArr),[q],quickSort(rightArr));
}
console.log(quickSort(arr1));//[1,4,765,86,53,87,53,32,6,64,2,3,767,34,1,4,35,6]
</script>6. 不利用第三方变量的情况下交换两个变量的值思路:利用两个元素的差值进行计算<script type="text/javascript">
var a = 10;
var b = 12;
function swap (a,b) {
b = b - a;
a = a + b;
b = a - b;
return [a,b]
}
console.log(swap(a,b));
</script>7. 求一个数组中最大数和最小数的差值<script type="text/javascript">
var arr1 = [2,44,3,-12,43,5,8,67,54,32,-211];
var max = Math.max.apply(Math,arr1);
var min = Math.min.apply(Math,arr1);
console.log(max-min);//278
</script>8. 生成指定长度的随机字符串思路:charAt()方法,获取元素下标<script type="text/javascript">
function randomString(n){
var str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz9876543210"
var str2 = "";
for (var i = 0; i < n ; i ++){
str2 += str1.charAt(Math.floor(Math.random()*str1.length));
}
return str2;
}
console.log(randomString(5));
</script>9. 获取一个DOM节点下面包含某个class名的所有节点<div id="text">
<div class="cs"></div>
<div class="as"></div>
<p class="cs"></p>
</div>
<script type="text/javascript">
function getClass(node,classname) {
if(node.getElementsByClassName) {
return node.getElementsByClassName(classname);
//如果存在该标签 就返回
} else {
var elems = node.getElementsByTagName(node),
defualt = [];
for (var i = 0; i < elems.length; i++) {
//遍历所有标签
if(elems[i].className.indexOf(classname) != -1) {
//查找相应类名的标签
defualt[defualt.length] = elems[i];
}
}
return defualt;
}
}
var text = document.getElementById('text'),
cs = getClass(text,'cs');
console.log(cs);//[div.cs, p.cs]
</script>10. 二叉树一说到二叉树我们肯定会问,什么是二叉树,二叉树是个啥子东东,拿来有啥子用嘛,我们为啥子要学习它嘛? 如果当初你在学习二叉树的时候你没有问过自己这些问题,那么你对它的了解也仅仅也只是了解。那我们现在来说说什么是二叉树,二叉树就是一种数据结构, 它的组织关系就像是自然界中的树一样。官方语言的定义是:是一个有限元素的集合,该集合或者为空、或者由一个称为根的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成。至于为啥子要学习它,妈妈总是说,孩子,等你长大了就明白了。11. 二叉树的性质性质1:二叉树第i层上的节点数目最多为2i-1(i≥1);性质2:深度为k的二叉树至多有2k-1个结点(k≥1)。性质3: 在任意-棵二叉树中,若叶子结点(即度为0的结点)的个数为n0,度为1的结点数为n1,度为2的结点数为n2,则no=n2+1。12. 二叉树的存储结构与构建二叉树的存储方式有两种,一种顺序存储,比如:var binaryTree = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘h’, ‘i’]; 这就是一颗二叉树,假设binaryTree[i]是二叉树的一个节点,那么它的左孩子节点 leftChild = binaryTree[i2+1]那 么相应的右孩子节点 rightChild = binaryTree[i2+2]; 一般情况下顺序存储的这种结构用的较少,另外一种存储方式就是链式存储,下面我会用代码来详细描述二叉树式 结构的构建与存储方式,构建二叉树也有两种方式一种是递归方式构建,这种很简单,另一种是非递归方法构建,这种呢相对于前一种复杂一点点,不过也不用担心,我在 代码中加上详细的注释,一步一步的走下去。我们现在就以26个英文字母来构建二叉树var charecters = [‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’];在构建二叉树之前我们会用到一个节点对象,节点对象如下:(注意:关于javascript的面向对象,原型,语法特点我会放在javascript语言知识点这个系列)/*
*二叉树的节点对象
*/
function Node() {
this.text = ''; //节点的文本
this.leftChild = null; //节点的左孩子引用
this.rightChild = null; //节点右孩子引用
}13. 递归构建二叉树在构建好二叉树节点之后我们紧接着用递归来构建二叉树var charecters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
function buildTree(node, i) {
var leftIndex = 2*i+1, //左孩子节点的索引
rightIndex = 2*i+2; //右孩子节点的索引
if(leftIndex < charecters.length) { //判断索引的长度是否超过了charecters数组的大小
var childNode = new Node(); //创建一个新的节点对象
childNode.text = charecters[leftIndex]; //给节点赋值
node.leftChild = childNode; //给当前节点node加入左孩子节点
buildTree(childNode, leftIndex); //递归创建左孩子
}
if(rightIndex < charecters.length) { //下面注释参照上面的构建左孩子的节点
var childNode = new Node();
childNode.text = charecters[rightIndex];
node.rightChild = childNode;
buildTree(childNode, rightIndex);
}
}
//下面构造二叉树
var node = new Node();
node.text = charecters[0];
buildTree(node, 0); //索引i是从0开始构建14. 非递归构建二叉树下面是以非递归方式构建二叉树:var root;
function createBinaryTree() {
var len = charecters.length, //数组的长度
index = 0, //索引从0开始
nodes = new Array(); //创建一个临时数组,用于存放二叉树节点
//循环创建二叉树节点存放到数组中
for (var i = 0 ; i < charecters.length ; i++) {
var node = new Node();
node.text = charecters[i];
nodes.push(node);
}
//循环建立二叉树子节点的引用
while(index < len) {
var leftIndex = 2*index+1, //当前节点左孩子索引
rightIndex = 2*index+2; //当前节点右孩子索引
//给当前节点添加左孩子
nodes[index].leftChild = nodes[leftIndex];
//给当前节点添加右孩子
nodes[index].rightChild = nodes[rightIndex];
index++;
}
root = nodes[0];
}15.二叉树的三种遍历好了,现在我们已经成功构建了二叉树的链式结构,在构建了二叉树的链式结构后我们进入二叉树的最基本的遍历了,遍历有三种最基本的遍历,我不说想必大家都知道,先序遍历,中序遍历和后续遍历。虽然这三种遍历递归方式都比较简单,但非递归方式就不是那么容易了,当时我在实现的时候都卡了半天,真的是说起来容易做起来难啊,在实现遍历前我们首先要来实现的是栈,因为在非递归遍历的时候会用到栈,那到底什么是栈呢,这里我就简单介绍下吧,有兴趣的朋友可以去维基百科有权威的定义,栈和队列也是一种数据结构,栈存放数据的时候是先进先出,而队列是先进后出。16.实现栈的对象下面用javascript来实现栈的对象function Stack() {
var stack = new Array(); //存放栈的数组
//压栈
this.push = function(o) {
stack.push(o);
};
//出栈
this.pop = function() {
var o = stack[stack.length-1];
stack.splice(stack.length-1, 1);
return o;
};
//检查栈是否为空
this.isEmpty = function() {
if(stack.length <= 0) {
return true;
}
else {
return false;
}
};
}
//使用方式如下
var stack = new Stack();
stack.push(1); //现在栈中有一个元素
stack.isEmpty(); //false , 栈不为空
alert(stack.pop()); //出栈, 打印1
stack.isEmpty(); //true, 此时栈为空,因为在调用了stack.pop()之后元素出栈了,所以为空17. 先序遍历在实现了栈对象以后我们首先来进行先序遍历的递归方式function firstIteration(node) {
if(node.leftChild) { //判断当前节点是否有左孩子
firstIteration(node.leftChild); //递归左孩子
}
if(node.rightChild) { //判断当前节点是否有右孩子
firstIteration(node.rightChild); //递归右孩子
}
}
//递归遍历二叉树
firstIteration(root);18. 先序遍历的非递归方式上面的代码大家可以在firstIteration()方法中加个alert()函数来验证是否正确。那么下面就要说说先序遍历的非递归方式,遍历思想是这样的:先访问根节点在访问左节 点, 最后访问右节点。从根节点一直往下访问找左孩子节点,直到最后一个左孩子节点(将这条路径保存到栈中),然后再访问最后一个左孩子的兄弟节点(右孩子节点),之后回溯到上一层(将栈中的元素取出 就是出栈),又开始从该节点(回溯到上一层的节点)一直往下访问找左孩子节点… 直到栈中的元素为空,循环结束。function notFirstIteration(node) {
var stack = new Stack(), //开辟一个新的栈对象
resultText = ''; //存放非递归遍历之后的字母顺序
stack.push(root); //这个root在上面非递归方式构建二叉树的时候已经构建好的
var node = root;
resultText += node.text;
while(!stack.isEmpty()) {
while(node.leftChild) { //判断当前节点是否有左孩子节点
node = node.leftChild; //取当前节点的左孩子节点
resultText += node.text; //访问当前节点
stack.push(node); //将当前节点压入栈中
}
stack.pop(); //出栈
node = stack.pop().rightChild; //访问当前节点的兄弟节点(右孩子节点)
if(node) { //当前节点的兄弟节点不为空
resultText += node.text; //访问当前节点
stack.push(node); //将当前节点压入栈中
}
else { //当前节点的兄弟节点为空
node = stack.pop(); //在回溯到上一层
}
}
}
//非递归先序遍历
notFirstIteration(root);19.中序遍历只要把思路理清楚了现实起来其实还是挺容易的,只要我们熟悉了一种二叉树的非递归遍历方式,其他几种非递归方式就容易多了,照着葫芦画瓢,下面是中序遍历的递归 方式,中序遍历的思想是:先访问左孩子节点,在访问根节点,最后访问右节点var strText = "";
function secondIteration(node) {
//访问左节点
if(node.leftChild) {
if(node.leftChild.leftChild) {
secondIteration(node.leftChild);
}
else {
strText += node.leftChild.text;
}
}
//访问根节点
strText += node.text;
//访问右节点
if(node.rightChild) {
if(node.rightChild.leftChild) {
secondIteration(node.rightChild);
}
else {
strText += node.rightChild.text;
}
}
}
secondIteration(root);
alert(strText);20. 中序遍历的非递归方式思想是:1. 从根节点一直往下找左孩子节点,直到找到最后一个左孩子节点(用栈将此路径保存,但不访问)2.访问最后一个左孩子节点,然后再 访问根节点(要先弹出栈,就是在栈中取上一层节点)3.在访问当前节点(最后一个左孩子节点)的兄弟节点(右孩子节点),这里要注意如果兄弟节点是一个叶节点就直 接访问,否则是兄弟节点是一颗子树的话不能马上访问,要先来重复 1, 2,3步骤, 直到栈为空,循环结束function notSecondIteration() {
var resultText = '',
stack = new Stack(),
node = root;
stack.push(node);
while(!stack.isEmpty()) {
//从根节点一直往下找左孩子节点直到最后一个左孩子节点,然后保存在栈中
while(node.leftChild) {
node = node.leftChild;
stack.push(node);
}
//弹出栈
var tempNode = stack.pop();
//访问临时节点
resultText += tempNode.text;
if(tempNode.rightChild) {
node = tempNode.rightChild;
stack.push(node);
}
}
alert(resultText);
}21. 后续遍历最后就还剩下一种遍历方式,二叉树的后续遍历,后续遍历的思想是:先访问左孩子节点,然后在访问右孩子节点,最后访问根节点22. 后续遍历的递归方式var strText = '';
function lastIteration(node) {
//首先访问左孩子节点
if(node.leftChild) {
if(node.leftChild.leftChild) {
lastIteration(node.leftChild);
}
else {
strText += node.leftChild.text;
}
}
//然后再访问右孩子节点
if(node.rightChild) {
if(node.rightChild.rightChild) {
lastIteration(node.rightChild);
}
else {
strText += node.rightChild.text;
}
}
//最后访问根节点
strText += node.text;
}
//中序递归遍历
lastIteration(root);
alert(strText);23.后续非递归遍历后续非递归遍历的思想是:1.从根节点一直往下找左孩子节点,直到最后一个左孩子节点(将路径保存到栈中,但不访问)2.弹出栈访问最后一个左孩子节点 3.进入最后一 个左孩子节点的兄弟节点,如果兄弟节点是叶节点就访问它,否则将该节点重复 1, 2步骤, 直到栈中的元素为空,循环结束。3.访问根节点function notLastIteration() {
var strText = '',
stack = new Stack();
nodo = root;
stack.push(node);
while(!stack.isEmpty()) {
while(node.leftChild) {
node = node.leftChild;
stack.push(node);
}
//弹出栈
var tempNode = stack.pop();
//访问左孩子节点
strText += tempNode.text;
//访问右孩子节点
if(tempNode.rightChild) {
if(tempNode.rightChild.leftChild || tempNode.rightChild.rightChild) { //判断最后一个左孩子节点的兄弟节点是否为页节点
stack.push(tempNode.rightChild);
}
else {
strText += tempNode.rightChild.text;
}
}
}
alert(strText);
}24. 已知前序和中序构建二叉树<script>
let qianxu = [ 1,2,4,7,3,5,6,8 ];
let zhongxu = [ 4,7,2,1,5,3,8,6 ];
function TreeNode( val ){
this.val = val;
this.left = null;
this.right = null;
}
function rebuildTree(qianxu,zhongxu){
if (qianxu[0]){
// 1. 根据找到的根节点( 前序序列的第一个元素一定是根节点 )
let rootVal = qianxu[0];
// 2. 找到根节点和中序序列,找到树的左子树和右子树
// 根节点在中序序列中的位置
let index = zhongxu.indexOf( rootVal );
// 前序序列:左子树 qianxu(1,index),右子树qianxu(index+1,最后)
// 中序序列:左子树 zhongxu(0,index-1),右子树 zhongxu(index+1,最后)
let leftTree = rebuildTree(qianxu.slice(1,index+1),zhongxu.slice(0,index));
let rightTree = rebuildTree(qianxu.slice(index+1),zhongxu.slice(index+1));
let root = new TreeNode(rootVal);
root.right = rightTree;
root.left = leftTree;
return root;
}
}
console.log(rebuildTree(qianxu,zhongxu));
</script>25. 栈转化成队列<script>
let stack1 = [];
let stack2 = [];
function push(node){
stack1.push(node);
}
function pop (){
while(stack1.length){
stack2.push(stack1.pop());
}
let popVal = stack2.pop();
while(stack2.length){
stack1.push(stack2.pop());
}
return popVal;
}
/**
* 1,2,3,4,5 入队
* 出队操作 1
* 入队 6
* 出队操作2
* 出队操作3
*/
push(1);
push(2);
push(3);
push(4);
push(5);
console.log(pop());
</script>26.跳台阶(典型递归)一只青蛙一次可以跳上一级台阶,也可以跳上两级求该青蛙上一个n级的台阶总共有多少中跳法(向后次序不同算不同结果)<script>
function jumpFloor(n){
if(n==1) return 1;
else if(n==2) return 2;
return jumpFloor(n-1)+jumpFloor(n-2);
}
console.log(jumpFloor(3));
</script>算法优化用空间换时间;(加缓存)(记忆化递归)<script>
// 记忆化递归
let cache = [,1,2];
function jumpFloor2(n){
if(cache[n] !==undefined) return cache[n];
return cache[n] = jumpFloor(n-1)+jumpFloor(n-2);
}
console.log(jumpFloor2(3));
</script>一只青蛙一次可以跳上一级台阶,也可以跳上两级…它也可以跳上n级;求该青蛙上一个n级的台阶总共有多少中跳法(向后次序不同算不同结果)f(n)=f(n-1)+f(n-2)+…+f(2)+f(1)+1<script>
let cache = [,1,2];
function jumpFloor(n){
if(cache[n] !== undefined) return cache[n];
cache[n] = 1;
for(let i = n-1;i >=1;i--){
cache[n] += jumpFloor(i);
}
return cache[n];
}
console.log(jumpFloor(10));
</script>27. 反转链表写一个函数,输入一个链表,反转练表后,输出新链表的表头。<script>
function Node(val){
this.val = val;
this.next = null;
}
function creatList(arr){
let head = new Node(arr[0]);
let tail = head;
for(let i =1;i<=arr.length-1;i++){
tail.next = new Node(arr[i]);
tail = tail.next;
}
return head;
}
let list = creatList([1,2,3,4,5]);
// console.log(list);
function reverseList (head){
let arr = [];
let p = head;
while(p){
arr.push(p.val);
p = p.next;
}
p = head;
while(p){
p.val = arr.pop(p.val);
p = p.next;
}
return head;
}
console.log(reverseList(list));
</script>28. 字典树需求:10 创建一个字典树,在字典树中查找是否包含某个单词1112 单词序列:13 and14 about15 as16 boy17 by18 because19 as2021 查找:22 close false23 an false24 as true25 boy true2627 字典树是什么28 字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。30 字典树的作用31 统计,排序和保存大量的字符串33 字典树的特点34 1、字典树的节点存储的是单词的字符(字母)35 2、为了表示一个单词是否出现,我们可以给单词的最后的字符加上标记36 3、字典树中表示一个单词用的是一条链37 4、字典树的根节点没有什么意义39 字典树的操作40 1、把单词插入到字典树里面去41 2、在字典树中查找单词45 1、把单词插入到字典树里面去46 算法步骤:47 去跟节点下面去找这个单词的第一个字符是否出现,48 如果没出现,就创建,然后走这条路,49 如果出现了,就直接走这条路50 (在这个过程里面,单词的第一个字符就被消耗掉了)52 算法:53 递归56 2、在字典树中查找单词57 算法:58 递归60 算法步骤:61 查找单词的第一个字符是否在根节点的子节点中,如果出现了,就接着往下找62 如果没出现,直接return false63 在单词找完后,如果标记大于1,表示单词出现过,就return true,64 否则return falseNodeJS一:Node 好处: 处理高并发 事件驱动 轻量 要用于搭建高性能的web服务器,1. 它是一个Javascript运行环境2. 依赖于Chrome V8引擎进行代码解释3. 事件驱动4. 非阻塞I/O5. 轻量、可伸缩,适于实时数据交互应用6. 单进程,单线程二:Express 和 koa的区别?异步 摆脱回调地域对response 和request进行了封装 contentExpress主要基于Connect中间件框架,功能丰富,随取随用,并且框架自身封装了大量便利的功能,比如路由、视图处理等等。而koa主要基于co中间件框架,框架自身并没集成太多功能,大部分功能需要用户自行require中间件去解决,但是由于其基于ES6 generator特性的中间件机制,解决了长期诟病的“callback hell”和麻烦的错误处理的问题,大受开发者欢迎。三:事件驱动模型和事件循环:事件驱动模型:当服务端收到请求时,就把它关闭 然后处理下一个请求 当第一个请求处理完毕后 就放回处理队列 当达到队列开头 将结果返回给用户 好处:高效 扩展性强 因为服务端一直接受请求 不等待任何读写操作事件循环:查看队列里面是否有队列里面有待处理的 如果有 交给主线程执行四:Redis:使用场景:支持string、list、set、zset和hash类型数据。配合关系型数据库做高速缓存缓存高频次访问的数据,降低数据库io分布式架构,做session共享可以持久化特定数据。利用zset类型可以存储排行榜利用list的自然时间排序存储最新n个数据五:mysql 和mongodb的区别mysql 关系型数据库 mongodb是非关系数据库(主要)六:MySQL索引七:闭包应该注意的地方八:进程和线程进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位进程是线程的容器十:mysql存储引擎 和区别InnoDB存储引擎:事务型数据库首选,支持事务安全表(ACID),支持行锁定和外键 是mysql 5.5之后的默认引擎MyISAM 存储引擎:不支持事务和外键,访问速度较快,是mysql5.5 之前的默认引擎MEMORY: 保存在内存中的数据表 ,每个memory表对应一个磁盘文件。格式是.frm 访问速度很快 缺点是:mysql服务关闭,数据丢失,另外对数据表大小有限制。十一:如何判断一个字符串是另一个字符串的子串indexof es6:include startWith endWith十二:单点登录十三:oauth2.0十四:type of 和instance of 区别十五:pm2 restart 和reload的区别(配置文件的重载 重启)十六:MySQL 读写分离十七:pm2如何查看指定三个项目的日志十八:深拷贝 浅拷贝十九:路由机制二十:MySQL 批量更新二十一:登录流程二十二:cookie 和session二十三:基本数据类型 引用数据类型 区别二十四:防止sql 注入1.使用escape() 对传入参数进行编码2.使用connection.query ()的查询参数占位符3.使用escapeId()编码SQL查询标识符4.使用mysql.format()转义参数:二十五:require()模块加载机制先判断是否存在文件缓存区中,存在直接导入,没有的话,在判断是否是原生模块,如果是原生模块,再看是否在原生模块缓存区中,如果有直接导入,没有的话加载原生模块,缓存原生模块,在导入如果不是原生模块,先查找文件模块,根据扩展名载入文件模块,缓存文件模块,在导入[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rujkgYx7-1611887935898)(C:\Users\zjx\Desktop\桌面文件\web面试\1.jpg)]第1题, 什么是nodejs?我们在哪里使用它?Nodejs是服务器端的一门技术。它是基于Google V8 JavaScript引擎而开发的。用来开发可扩展的服务端程序。第2题,为什么要使用node js?nodejs会让我们的编程工作变得简单,它主要包含如下几点几个好处:执行快速。永远不会阻滞。JavaScript是通用的编程语言。异步处理机制。避免并行所带来的问题。第3题,nodejs有哪些特点?是单线程的,但是有很高的可扩展性,使用JavaScript作为主流编程语言。使用的是异步处理机制和事件驱动。处理高效。第4题, Set immediate和set time out 区别在哪里?Set immediate就是马上执行的意思。Set time out, 时间参数传为0,也想获得同样的功能。只不过前者要快一些。第5题,如何更新nodejs的版本?npm install npm -g第6题,为什么nodejs是单线程的?Nodejs使用的是单线程没错,但是通过异步处理的方式,可以处理大量的数据吞吐量,从而有更好的性能和扩可扩展性。第7题,什么是回调函数?回调函数是指用一个函数作为参数传入另一个函数,这个函数会被在某个时机调用。第8题, 什么叫做回调地狱?回调地狱是由嵌套的回调函数导致的。这样的机制会导致有些函数无法到达,并且很难维护。第9题,如何阻止回调地狱?有三种方法, 对每个错误都要处理到, 保证代码的贯通, 程序代码模块化。第10题,解释一下repl的作用?Read evaluate print loop, 用于测试,调试和实验用。第11题,API函数的类型有哪些?有两种,一种是阻滞型函数。阻滞型函数会等待操作完成以后再进行下一步。另外一种是非阻滞型函数。这种函数使用回调函数来处理当前函数获取的结果。第12题,回调函数的第1个参数是什么?通常是错误对象。如果这个参数为空,表示没有错误。第13题,NPM的作用是什么?Node package manager, 主要有两个功能。它是一个网端模块的存储介质。它的另一个作用是安装程序依赖和版本管理。第14题,nodejs和ajax的区别是什么?Nodejs和ajax也就是asynchronous JavaScript and xml,都是通过JavaScript来表现的,但是他们的目的截然不同。Ajax是设计用来动态的更新页面的某个区域,从而不需要更新整个页面。Nodejs是用来开发客户服务器类型应用的。第15题,解释一下nodejs中chaining.Chaining是指从一个数据流到另一个数据流的链接,从而实现多个流操作。第16题,什么是streams?解释一下有哪些类型?流的概念是不间断的,它可以不间断的从某个地方读取数据,或者向某个地方写入数据。有4种类型的流数据。可读,可写。既可读,又可写,转化。第17题,退出代码是什么?有哪些退出代码?退出代码是指中断nodejs运行时返回的代码。有这么几种unused, uncaught fatal exception, fatal error, non function internal exception handler, internal exception handler run time failure,internal JavaScript evaluation failure.第18题, 什么是globals?有三个global的关键字。Global代表的是最上层的命名空间,用来管理所有其他的全局对象。Process 是一个全局对象,可以把异步函数转化成异步回调, 它可以在任何地方被访问,它主要是用来返回系统的应用信息和环境信息.Buffer, 是用来处理二进制数据的类.第19题, Angular js和node js的区别是什么?Angular js是网络应用开发框架,而nodejs是一个实时系统。第20题, 为什么统一的风格儿非常重要,有什么工具可以保证这一点?统一的风格可以让所有的组成员按照一种规矩来写代码。工具有Standard和eslint.第21题, 用什么方法来处理没有被处理的异常?在应用和node js之间使用domain来处理这样的异常。第22题, Node js是如何支持多处理器平台的?Cluster模块是用来支持这方面的。它可以允许多个nodejs工作进程运行在相同的端口上。第23题, 如何配置开发模式和生产模式的环境?首先有一个配置文件,然后通过环境变量参数来获取对应的配置内容。第24题, nodejs中跟时间相关的函数有哪些?Set time out, clear time out.Set interval, clear interval.Set immediate, clear immediate.Process.nextTick.第25题, 解释一下什么是reactor pattern。Reactor pattern主要是非阻滞的i/o操作。提供一个回调函数来关联io操作。io请求完成以后会不会提交给demultiplexer, 这是一个通知接口用来处理并发性的非阻滞的io操作,这个功能是通过查询一个event loop来实现的.第26题,lts版本是什么意思?也就是long term support版本。至少会被支持18个月。使用的是偶数来标识。这种版本有稳定性和安全性的保证。第27题,你为什么需要把express APP和server分开?分开以后方便维护以及测试,在测试某个模块的时候,尤其是APP模块的时候,你不需要去对网络方面的连接配置做工作。第28题,next tick和setImmediate的区别是什么?Next tick会等待当前的event执行完成或者下一轮儿事件循环到达再执行。Set immediate, 会在下一轮的事件循环中,执行回调并且返回当前的循环来做读写操作.第29题,apply, call和bind有什么区别?参考答案:三者都可以把一个函数应用到其他对象上,注意不是自身对象.apply,call是直接执行函数调用,bind是绑定,执行需要再次调用.apply和call的区别是apply接受数组作为参数,而call是接受逗号分隔的无限多个参数列表,代码演示function Person() {
}
Person.prototype.sayName() { alert(this.name); }
var obj = {name: 'michaelqin'}; // 注意这是一个普通对象,它不是Person的实例
1) apply
Person.prototype.sayName.apply(obj, [param1, param2, param3]);
2) call
Person.prototype.sayName.call(obj, param1, param2, param3);
3) bind
var sn = Person.prototype.sayName.bind(obj);
sn([param1, param2, param3]); // bind需要先绑定,再执行
sn(param1, param2, param3); // bind需要先绑定,再执行二)、node全局对象3. node有哪些核心模块?参考答案: EventEmitter, Stream, FS, Net和全局对象1. node有哪些全局对象?参考答案: process, console, Buffer2. process有哪些常用方法?参考答案: process.stdin, process.stdout, process.stderr, process.on, process.env, process.argv, process.arch, process.platform, process.exit3. console有哪些常用方法?参考答案: console.log/console.info, console.error/console.warning, console.time/console.timeEnd, console.trace, console.table4. node有哪些定时功能?参考答案: setTimeout/clearTimeout, setInterval/clearInterval, setImmediate/clearImmediate, process.nextTick5. node中的事件循环是什么样子的?总体上执行顺序是:process.nextTick >> setImmidate >> setTimeout/SetInterval 看官网吧:[https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/)6. node中的Buffer如何应用?参考答案: Buffer是用来处理二进制数据的,比如图片,mp3,数据库文件等.Buffer支持各种编码解码,二进制字符串互转.三)、EventEmitter1. 什么是EventEmitter?参考答案: EventEmitter是node中一个实现观察者模式的类,主要功能是监听和发射消息,用于处理多模块交互问题.2. 如何实现一个EventEmitter?参考答案: 主要分三步:定义一个子类,调用构造函数,继承EventEmitter代码演示var util = require('util');
var EventEmitter = require('events').EventEmitter;
function MyEmitter() {
EventEmitter.call(this);
} // 构造函数
util.inherits(MyEmitter, EventEmitter); // 继承
var em = new MyEmitter();
em.on('hello', function(data) {
console.log('收到事件hello的数据:', data);
}); // 接收事件,并打印到控制台
em.emit('hello', 'EventEmitter传递消息真方便!');3. EventEmitter有哪些典型应用?参考答案:模块间传递消息回调函数内外传递消息处理流数据,因为流是在EventEmitter基础上实现的.观察者模式发射触发机制相关应用4. 怎么捕获EventEmitter的错误事件?参考答案: 监听error事件即可.如果有多个EventEmitter,也可以用domain来统一处理错误事件.代码演示var domain = require('domain');
var myDomain = domain.create();
myDomain.on('error', function(err){
console.log('domain接收到的错误事件:', err);
}); // 接收事件并打印
myDomain.run(function(){
var emitter1 = new MyEmitter();
emitter1.emit('error', '错误事件来自emitter1');
emitter2 = new MyEmitter();
emitter2.emit('error', '错误事件来自emitter2');
});5. EventEmitter中的newListenser事件有什么用处?参考答案: newListener可以用来做事件机制的反射,特殊应用,事件管理等.当任何on事件添加到EventEmitter时,就会触发newListener事件,基于这种模式,我们可以做很多自定义处理.代码演示var emitter3 = new MyEmitter();
emitter3.on('newListener', function(name, listener) {
console.log("新事件的名字:", name);
console.log("新事件的代码:", listener);
setTimeout(function(){ console.log("我是自定义延时处理机制"); }, 1000);
});
emitter3.on('hello', function(){
console.log('hello node');
});四)、Stream1. 什么是Stream?参考答案: stream是基于事件EventEmitter的数据管理模式.由各种不同的抽象接口组成,主要包括可写,可读,可读写,可转换等几种类型.2. Stream有什么好处?参考答案: 非阻塞式数据处理提升效率,片断处理节省内存,管道处理方便可扩展等.3. Stream有哪些典型应用?参考答案: 文件,网络,数据转换,音频视频等.4. 怎么捕获Stream的错误事件?参考答案: 监听error事件,方法同EventEmitter.5. 有哪些常用Stream,分别什么时候使用?参考答案: Readable为可被读流,在作为输入数据源时使用;Writable为可被写流,在作为输出源时使用;Duplex为可读写流,它作为输出源接受被写入,同时又作为输入源被后面的流读出.Transform机制和Duplex一样,都是双向流,区别时Transfrom只需要实现一个函数_transfrom(chunk, encoding, callback);而Duplex需要分别实现_read(size)函数和_write(chunk, encoding, callback)函数.6. 实现一个Writable Stream?参考答案: 三步走:1)构造函数call Writable 2) 继承Writable 3) 实现_write(chunk, encoding, callback)函数代码演示var Writable = require('stream').Writable;
var util = require('util');
function MyWritable(options) {
Writable.call(this, options);
} // 构造函数
util.inherits(MyWritable, Writable); // 继承自Writable
MyWritable.prototype._write = function(chunk, encoding, callback) {
console.log("被写入的数据是:", chunk.toString()); // 此处可对写入的数据进行处理
callback();
};
process.stdin.pipe(new MyWritable()); // stdin作为输入源,MyWritable作为输出源五)、文件系统1. 内置的fs模块架构是什么样子的?参考答案: fs模块主要由下面几部分组成: 1) POSIX文件Wrapper,对应于操作系统的原生文件操作 2) 文件流 fs.createReadStream和fs.createWriteStream 3) 同步文件读写,fs.readFileSync和fs.writeFileSync 4) 异步文件读写, fs.readFile和fs.writeFile2. 读写一个文件有多少种方法?参考答案: 总体来说有四种: 1) POSIX式低层读写 2) 流式读写 3) 同步文件读写 4) 异步文件读写3. 怎么读取json配置文件?参考答案: 主要有两种方式,第一种是利用node内置的require(‘data.json’)机制,直接得到js对象; 第二种是读入文件入内容,然后用JSON.parse(content)转换成js对象.二者的区别是require机制情况下,如果多个模块都加载了同一个json文件,那么其中一个改变了js对象,其它跟着改变,这是由node模块的缓存机制造成的,只有一个js模块对象; 第二种方式则可以随意改变加载后的js变量,而且各模块互不影响,因为他们都是独立的,是多个js对象.4. fs.watch和fs.watchFile有什么区别,怎么应用?参考答案: 二者主要用来监听文件变动.fs.watch利用操作系统原生机制来监听,可能不适用网络文件系统; fs.watchFile则是定期检查文件状态变更,适用于网络文件系统,但是相比fs.watch有些慢,因为不是实时机制.六)、网络1. node的网络模块架构是什么样子的?参考答案: node全面支持各种网络服务器和客户端,包括tcp, http/https, tcp, udp, dns, tls/ssl等.2. node是怎样支持https,tls的?参考答案: 主要实现以下几个步骤即可: 1) openssl生成公钥私钥 2) 服务器或客户端使用https替代http 3) 服务器或客户端加载公钥私钥证书3. 实现一个简单的http服务器?参考答案: 经典又很没毛意义的一个题目.思路是加载http模块,创建服务器,监听端口.代码演示var http = require('http'); // 加载http模块
http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'}); // 200代表状态成功, 文档类型是给浏览器识别用的
res.write('<meta charset="UTF-8"> <h1>我是标题啊!</h1> <font color="red">这么原生,初级的服务器,下辈子能用着吗?!</font>'); // 返回给客户端的html数据
res.end(); // 结束输出流
}).listen(3000); // 绑定3ooo, 查看效果请访问 http://localhost:3000七)、child-process1. 为什么需要child-process?参考答案: node是异步非阻塞的,这对高并发非常有效.可是我们还有其它一些常用需求,比如和操作系统shell命令交互,调用可执行文件,创建子进程进行阻塞式访问或高CPU计算等,child-process就是为满足这些需求而生的.child-process顾名思义,就是把node阻塞的工作交给子进程去做.2. exec,execFile,spawn和fork都是做什么用的?参考答案: exec可以用操作系统原生的方式执行各种命令,如管道 cat ab.txt | grep hello; execFile是执行一个文件; spawn是流式和操作系统进行交互; fork是两个node程序(javascript)之间时行交互.3. 实现一个简单的命令行交互程序?参考答案: 那就用spawn吧.代码演示var cp = require('child_process');
var child = cp.spawn('echo', ['你好', "钩子"]); // 执行命令
child.stdout.pipe(process.stdout); // child.stdout是输入流,process.stdout是输出流
// 这句的意思是将子进程的输出作为当前程序的输入流,然后重定向到当前程序的标准输出,即控制台4. 两个node程序之间怎样交互?参考答案: 用fork嘛,上面讲过了.原理是子程序用process.on, process.send,父程序里用child.on,child.send进行交互.代码演示1) fork-parent.js
var cp = require('child_process');
var child = cp.fork('./fork-child.js');
child.on('message', function(msg){
console.log('老爸从儿子接受到数据:', msg);
});
child.send('我是你爸爸,送关怀来了!');
2) fork-child.js
process.on('message', function(msg){
console.log("儿子从老爸接收到的数据:", msg);
process.send("我不要关怀,我要银民币!");
});5. 怎样让一个js文件变得像linux命令一样可执行?参考答案: 1) 在myCommand.js文件头部加入 #!/usr/bin/env node 2) chmod命令把js文件改为可执行即可 3) 进入文件目录,命令行输入myComand就是相当于node myComand.js了6. child-process和process的stdin,stdout,stderror是一样的吗?参考答案: 概念都是一样的,输入,输出,错误,都是流.区别是在父程序眼里,子程序的stdout是输入流,stdin是输出流.九、node高级话题(异步,部署,性能调优,异常调试等)1. node中的异步和同步怎么理解参考答案: node是单线程的,异步是通过一次次的循环事件队列来实现的.同步则是说阻塞式的IO,这在高并发环境会是一个很大的性能问题,所以同步一般只在基础框架的启动时使用,用来加载配置文件,初始化程序什么的.2. 有哪些方法可以进行异步流程的控制?参考答案: 1) 多层嵌套回调 2) 为每一个回调写单独的函数,函数里边再回调 3) 用第三方框架比方async, q, promise等3. 怎样绑定node程序到80端口?参考答案: 多种方式 1) sudo 2) apache/nginx代理 3) 用操作系统的firewall iptables进行端口重定向4. 有哪些方法可以让node程序遇到错误后自动重启?参考答案: 1) runit 2) forever 3) nohup npm start &5. 怎样充分利用多个CPU?参考答案: 一个CPU运行一个node实例6. 怎样调节node执行单元的内存大小?参考答案: 用–max-old-space-size 和 --max-new-space-size 来设置 v8 使用内存的上限7. 程序总是崩溃,怎样找出问题在哪里?参考答案: 1) node --prof 查看哪些函数调用次数多 2) memwatch和heapdump获得内存快照进行对比,查找内存溢出8. 有哪些常用方法可以防止程序崩溃?参考答案: 1) try-catch-finally 2) EventEmitter/Stream error事件处理 3) domain统一控制 4) jshint静态检查 5) jasmine/mocha进行单元测试9. 怎样调试node程序?参考答案: node --debug app.js 和node-inspector10. 如何捕获NodeJS中的错误,有几种方法? 参考答案: 1) 监听错误事件req.on(‘error’, function(){}), 适用EventEmitter存在的情况; 2) Promise.then.catch(error),适用Promise存在的情况 3) try-catch,适用async-await和js运行时异常,比如undefined object十、 常用知名第三方类库(Async, Express等)1. async都有哪些常用方法,分别是怎么用?参考答案: async是一个js类库,它的目的是解决js中异常流程难以控制的问题.async不仅适用在node.js里,浏览器中也可以使用.1). async.parallel并行执行完多个函数后,调用结束函数async.parallel([
function(){ ... },
function(){ ... }
], callback);2). async.series串行执行完多个函数后,调用结束函数async.series([
function(){ ... },
function(){ ... }
]);3). async.waterfall依次执行多个函数,后一个函数以前面函数的结果作为输入参数async.waterfall([
function(callback) {
callback(null, 'one', 'two');
},
function(arg1, arg2, callback) {
// arg1 now equals 'one' and arg2 now equals 'two'
callback(null, 'three');
},
function(arg1, callback) {
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});4). async.map异步执行多个数组,返回结果数组async.map(['file1','file2','file3'], fs.stat, function(err, results){
// results is now an array of stats for each file
});5). async.filter异步过滤多个数组,返回结果数组async.filter(['file1','file2','file3'], fs.exists, function(results){
// results now equals an array of the existing files
});2. express项目的目录大致是什么样子的参考答案: app.js, package.json, bin/www, public, routes, views.3. express常用函数参考答案: express.Router路由组件,app.get路由定向,app.configure配置,app.set设定参数,app.use使用中间件4. express中如何获取路由的参数参考答案: /users/:name使用req.params.name来获取; req.body.username则是获得表单传入参数username; express路由支持常用通配符 ?, +, *, and ()5. express response有哪些常用方法参考答案: res.download() 弹出文件下载res.end() 结束responseres.json() 返回jsonres.jsonp() 返回jsonpres.redirect() 重定向请求res.render() 渲染模板res.send() 返回多种形式数据res.sendFile 返回文件res.sendStatus() 返回状态十一、其它相关后端常用技术(MongoDB, Redis, Apache, Nginx等)1. mongodb有哪些常用优化措施参考答案: 类似传统数据库,索引和分区.2. mongoose是什么?有支持哪些特性?参考答案: mongoose是mongodb的文档映射模型.主要由Schema, Model和Instance三个方面组成.Schema就是定义数据类型,Model就是把Schema和js类绑定到一起,Instance就是一个对象实例.常见mongoose操作有,save, update, find. findOne, findById, static方法等.3. redis支持哪些功能参考答案: set/get, mset/hset/hmset/hmget/hgetall/hkeys, sadd/smembers, publish/subscribe, expire4. redis最简单的应用参考答案:var redis = require("redis"),
client = redis.createClient();
client.set("foo_rand000000000000", "some fantastic value");
client.get("foo_rand000000000000", function (err, reply) {
console.log(reply.toString());
});
client.end();5. apache,nginx有什么区别?参考答案: 二者都是代理服务器,功能类似.apache应用简单,相当广泛.nginx在分布式,静态转发方面比较有优势.HTTP状态码200 - 请求成功301 - 资源(网页等)被永久转移到其它URL404 - 请求的资源(网页等)不存在500 - 内部服务器错误
【钉钉】自建应用个别客户端无法成功跳转到https页面
公司OA与钉钉做了对接。之前一直稳定使用,最近几天启用了HTTPS。启用HTTPS之后,小部分用户的手机无法访问https应用。
提示网络不给力,有两个按钮 “刷新”和“网络诊断”均无法显示。截图如下:
公司购买的公网证书应该没到期,手机时间时区设置都是网络同步。
公司主要用它推送消息和OA网页端。推送的消息,一小部分用户打开后也是提示“网络不给力”,点右上角的更多,选择自带的浏览器safari打开,就能正常显示。
这一小部分异常的用户打开自建应用OA网页端,也是相同的提示,网络不给力。选择用自带的safari浏览器打开,显示为钉钉网址开头的空白页面。
这一小部分异常的用户,目前发现都是苹果手机用户,有ios9.3,ios11 钉钉版本均为最新。
盼大神能提一下宝贵意见。