0%

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

说明:

  • 必须在原数组上操作,不能拷贝额外的数组。

  • 尽量减少操作次数。

实现

优化版:

本例实现使用了sort排序,sort排序如果传入的是自定义排序函数的话,其内部实现其实就是冒泡排序:
每次对比相邻的两个元素a,b,如果a>b则将a和b交换,重复这一步骤直到排序完成。
冒泡排序示意
例,将[1,4,0,2]这四个数按从小到大排序,内部是这样实现的:
image
扩展到上面format函数的实现,要实现将target移动到数组末尾的话,只需要将每个元素与target比较,如果元素与target相等,则把这个元素与target交换,这样,target最终就会被交换到数组末尾,而其他的元素位置也不会改变。

视差滚动,其实原理源自生活中,举个例子:
我们坐在车上,车以60km的速度往前开,观察路旁的树,感觉景物倒退的速度很快,如果我们看远处的高楼,发现倒退的比较慢,虽然相对于路边的树和远处的高楼,同样我们都是以60km的速度离开,但是眼睛给我们的感觉,像是远处的景物离开的慢一点,其实这就是视差效果。
生活中这样的例子,举一反三到网页开发中来,如果我们能给两个物体设置离我们眼睛不同的距离,那么是否也可以出现视差效果呢?
先介绍perspective属性,定义3D元素离观察点的位置。使用这个属性,就能模拟出视差效果:
scroll

上半部分滚动的快一点,下半部分慢一点,就产生了视差效果,具体代码:

关键在于:
perspective: 1px;定义了container容器距观察点的距离是1px,再将容器内的子元素inner使用transform属性设置为距离容器-1px:transform: translateZ(-1px)
空间位置示意:
image

这样就实现了视差效果。

一致性hash算法,是集群存储的一种实现方式。

一个经典问题:3万张图片,需要相对均匀的存储到3台服务器中去,使每台服务器存储大约1万张图片,如何实现?
image

  1. 将30000张图片按文件名按照一定算法将其转换成数字。
    js实现: 每张图片文件名,都能够通过hash方法转换成数字,比如转换之前是a.png,b.png,c.png,转换为:9746112110103,9846112110103,9946112110103。
    现在怎么把这三张图片,存储到3台服务器中去呢?
    可以对这3张图片的hash分别对服务器数量取余,比如a.png:9746112110103 % 3 -> 0,b.png: 9846112110103 % 3 -> 1, c.png: 9946112110103 % 3 -> 2
    再将三张图片,依次存入三台服务器中:
    image
    这样,每次访问图片的时候,就可以根据图片的文件名hash,来计算出这张图片存在哪一台服务器中,io读取的时候,就可以直接读取目标服务器的文件,而不必去遍历所有服务器,节约了开销。这样就简单的实现了分布存储
    但是问题来了,如果3台并不能满足我们的需求时那么应该怎么做?肯定是增加几台服务器就可以了,假设我们增加1台服务器,服务器的数量由3变成了4,此时仍然用上述方法对同一张图片进行缓存,那么这张图片所在的服务器的编号必定是与原来的3台服务器所在的 编号是不同的,因为除数3变成了4,被除数不变的情况下,余数肯定不同,这情况带来的结果就是当服务器数量变动时,所有和缓存的位置都要发生改变,也就是说缓存服务器数量发生改变时,所有缓存数据在一定时间是失效的,当应用无法从缓存中和获取数据时,则会向后端服务器请求数据,同理,如果3台缓存服务器中突然有一台出现了故障,,无法进行缓存数据,那么需要移除故障机器,但是如果移除了一台缓存服务器后,数量从3变成了2,如果想访问有一张图片,这张图片缓存为位置必定发生改变,以前缓存的图片也会失去缓存的作用和意义,由于大量缓存在同一时间失效,造成了缓存的雪崩(血崩),后端服务器将会承担所有巨大压力,会导致整个系统可能会被压垮,所以为了避免这类情况的发生,一致性hash算法诞生了!

其实一致性hash算法也是取模运算,只是,上面描述的取模算法是对服务器数量进行取模,而一致性hash是对2^32取模.

首先把2^32个数字组成一个圆:
image
顺时针排列。然后再将3台服务器的ip地址的hash对2^32取余,余数是0-2^32之间的任意数,将其映射到圆上,会在圆上得到三个点与之对应:
image
然后,我们将图片也取余2^32,得到0-2^32之间的任意数,也对应着hash环上的一点。
人为规定,从图片出发,沿着hash环顺时针寻找,遇到的第一台服务器,就将图片存在这台服务器中:

image

这样做的好处是,由于被缓存对象与服务器hash后的值都是固定的,所以服务器不变的情况下,一张图片必定会被缓存到固定的服务器上,那么,当下次访问这张图片时,只要再次使用相同的算法进行计算,即可算出这张图片被缓存在那个服务器上,直接去对应的服务器上查找即可。

那么一致性hash环怎么抵抗雪崩呢?

假如现在有3张图片缓存情况如下:
image
可见,图片1缓存在server2中,图片2,3缓存在server3中,现在假设,server2崩溃了,显然,server2中的图片1就不存在了,但是图片2,3仍然存在server3中,不会因为某一节点的崩溃而存储位置发生变化,这就是一致性hash的优势,可以最小程度的减少集群节点服务器崩溃带来的灾难。

js在es6之前是没有模块化这一概念的
es6之前,js模块化有几种规范:CommonJS、AMD。

CommonJS
CommonJS有一个全局方法require(),nodejs就是采用的CommonJS规范来实现的模块化:

上面的代码可以看出来,其实这两行代码是同步的,第二行代码,必须要等第一行代码执行完毕才能执行,不然就会报错。
这就是CommonJS的特点,同步执行。
缺点也是同步导致的,如果在浏览器中使用CommonJS规范,因为浏览器加载资源会通过网络请求,所以如果客户端的网络很卡,那同步的模块加载就卡住了,很影响用户体验。所以浏览器中需要用异步模块规范。
AMD
AMD采用异步方式加载模块,模块的加载不影响它后面语句的运行,加载模块的时候,会定义一个回调函数,等待模块加载完毕后,会去调用这个函数,实现异步加载。

es6以后,js语言本身提供了模块化的实现,可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案:

上面代码其实是一个import命令,import命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行,本质是从path模块加载一个path方法,其他方法不加载,这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。
但是缺点来了,import命令是在编译阶段执行的,所以他能与js语法嵌套使用,比如:

这样会报错。于是,就有了import()方法提案:

这样,是不是跟AMD规范很像,其实import()函数就是js语言本身模块化的异步实现,并且返回一个Promise,来实现回调。

首先解释什么是加密

  • 加密就是把一段明文,通过一个密钥,并通过某一种算法,计算出对应的一段密文,这个过程就叫加密。可以理解为(明文+密钥)*算法 = 密文。反之可以通过密文和密钥来解析出明文。

我们知道http本质上是没有任何安全措施的,https的本质是为了解决,在http传输中,数据未加密产生的安全问题,所以我们想,能不能通过一个密钥来加密我们的数据,就像上面的公式:(明文+密钥)*算法 = 密文,然后再通过http传输这个密文,这样就能解决安全问题了。

但是问题来了,客户端(client)很容易就可以实现(明文+密钥)*算法 = 密文这个步骤,但是将密文发送到服务端(server)后,服务器是没有加密密钥的,那如何根据密文解析出明文呢?

要解决这个问题其实很简单,把密钥给服务器,服务器不就能通过密钥和密文来计算出明文了吗。看似很简单,但紧接着问题来了,如何将密钥给服务器呢?每个client的密钥肯定是不一样的,如何安全的将密钥给服务端,就成了最大的问题。

其实https就是为了解决这一痛点而产生的,https做的事情很简单,就是安全的将密钥从client发送到server,让client和server都拥有这个密钥,这样就能通过密钥来加解密数据了。如何实现呢?如图:
image

首先,server中有一个公私钥对公钥进行加密的数据,能通过私钥解密出来,也就是非对称加密。这个公私钥对,就是用来安全传递加密密钥的。

  1. client先向server请求公钥。
  2. client拿到server的公钥后,将密钥用公钥进行加密,再将加密后的密钥,传递给server。
  3. server拿到client通过公钥加密后的密钥,通过自己的私钥进行解密,这样就得到了真正的密钥。
  4. client和server都拿到了密钥,现在就可以使用这个密钥来进行数据传递的加密了。

这样,https的技术实现就完成了,也就是SSL握手环节的实现。但是问题又来了,服务器上的公私钥对,如何保证是安全的呢?比如某一台服务器的公私钥对,是从别人的服务器上盗取来的呢?

所以,需要有一个权威机构来颁布https公私钥对,也就是https证书,向权威机构申请,权威机构就会下发一个数字证书,包括了一对公私钥。这个证书是跟域名和ip地址绑定的,盗用别人的也没用。

这样,通过https,安全的数据传输就实现了

防抖:若事件在10s内连续被触发,则只响应最后一次事件。比如人为在页面快速点击,则只触发最后一次点击。

节流:若事件在10s内被连续触发,则每2s(人为设置)触发一次事件。比如人为在页面快速点击,则有5次事件被触发。

他们的作用都是降低回调执行频率,节省计算资源的。

节流实现:

分析:传入定义的wait时间小于函数两次调用的时间差时,函数不会被执行,只有两次调用的时间差大于wait值,函数才会被执行,所以函数会每隔一定时间被执行一次,实现了节流。

防抖实现:

分析:每当函数被调用的时候,就会设置一个延迟,当下一次调用时,延迟会被重置,所以如果一直连续调用的话,延迟一直被重置,这样函数一直都不会被调用,只有等到函数一段时间没有被调用(时间大于最后一次延迟)时,函数才会被调用。这样,函数就只会在持续调用时间段内的最后一次被调用,实现了防抖。

在浏览器环境中,setTimeout属于macrotasks queue(宏任务队列),Promise、Async/Await属于microtasks queue(微任务队列)。
js事件循环,会先执行微任务队列,再执行宏任务队列。
如在浏览器中:

会先执行promise微任务队列,再执行setTimeout的宏任务队列。
另外,我们可以手动把一个任务加入微任务执行队列中:

执行顺序为3-》4-》queueMicrotask-》1

观察者模式

观察者模式有几个要素:

  1. 需要将需要观察的对象存放起来
  2. 在需要的地方触发该观察者
  3. 第三观察者需要分类,同一类的观察者可监听同一个事件,监听被触发时,所有的观察者都需要被通知到。

具体实现一个观察者模式如下:

上面代码里使用了es6 的Map对象来存储观察者。注册观察者的时候,每收到一个观察者注册,就按类型将他们分类存放,同一类的观察者存储在一个数组中。触发观察者的代码中,收到触发的命令后,找出触发的类型,调用所有已注册的同类的观察者。删除观察者,就将注册的所有观察者删除,return this方便链式调用

调用示例如下:

至此,就实现了一个简单的观察者模式

javascript单线程就是在一个javascript运行环境中(如:浏览器,nodejs)只能同时运行一个javascript线程,那么单线程的语言的异步机制是如何实现的?
javascript是一个事件驱动的语言,他的异步机制与事件机制相关。

在javascript中,有一个主线程和一个异步队列池,并且异步队列池中的事件,需要等到主线程执行完毕才执行。

  • 主线程,就是执行所有js同步代码的线程。
  • 当js遇到异步操作时,会将异步操作推送到一个异步队列中,这个异步操作具体要做的事情,由js的宿主去执行,怎么理解呢?如浏览器发送一个ajax,js只需要将发送请求提交给浏览器,然后注册一个回调事件,浏览器会用多线程去发送真正的http请求,等到请求完成,再通知js异步队列中的指定回调,js再去通知代码中的回调,这样一个完整的异步操作就完成了,再如nodejs异步读取文件,js只需要发送一个读取文件的请求,然后nodejs会将读取文件的请求提交给他的宿主,nodejs中依赖libuv去执行I/O,然后js继续执行主线程的代码,等到I/O完成,则通知监听的代码。

所以,js异步的实现其实很简单,js本身的功能并不强大,他只是借助了他的宿主环境去执行他的指令,宿主根本上也是多线程去执行的,只是对于js语言的机制来说,他是单线程。
上面说到,异步队列池中事件的执行,只有等主线程执行完毕以后才会执行,举个例子,setTimeout是异步操作。

上面代码定义了一个异步事件,将在1s后打印一个log。但是假如我们主线程中有代码需要执行超过1s,那log会不会按时打印呢?

执行上面的代码,可以看到,log在5s后才被打印,也就是setTimeout并不一定准时执行,只是我们在一般的项目中,不会遇到主线程被阻塞很长时间的情况,感觉不到setTimeout的延迟。这就是上面说到的,异步队列池中的事件,需要等到主线程执行完毕才执行,也就是同步任务会阻塞接下来的代码执行。所以我们平时在编码的过程中,遇到大数据量的处理,或者遇到需要很长时间才能执行完毕的操作,尽量使用异步操作。