npm 离不开 package.json
,要了解 npm 的运行机制,我们先看看 package.json
文件是做什么的。首先 package.json
文件是每个前端项目存在于根目录的文件,包含了项目的一些基本信息(例如项目名称,版本号,描述,一些脚本,依赖等等),大致的结构如下:
{ "name": "whyknown", "version": "1.0.0", "description": "whyknown,前端开发者必备网站", "main": "src/index.js", "scripts": { "start": "node index.js", "lint": "eslint **/*.js" }, "dependencies": { "express": "^4.16.4", "compression": "~1.7.4" }, "devDependencies": { "eslint": "^5.16.0", "nodemon": "^1.18.11" }, "repository": { "type": "git", "url": "https://github.com/xxx.git" }, "author": "whyknown", "contributors": [ { "name": "whyknown", "email": "example@example.com", "url": "https://www.whyknown.com" } ], "keywords": ["server", "osiolabs", "express", "compression"] }
我们这里重点关注 script
的配置,该配置罗列了一些可以运行的脚本,当我们在根目录执行 npm run
或者 yarn run
的时候,就会去调用这里的命令。我们拿上面的 start 命令来讲解。
compose
compose 是函数式编程中一个非常重要的函数,compose 的函数作用就是组合函数的,将函数串联起来执行。将多个函数组合起来,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,就会像多米诺骨牌一样推导执行了。
2.1 简单例子
比如有这样的需求,输入一个名字
yideng
,然后全部变成大写,打印输出HELLO YIDENG
,我们可以考虑用函数组合的方法来解决这个问题,需要两个函数greeting、toUpper
。const greeting = (name) => `hello ${name}`; const toUpper = (str) => str.toUpperCase(); const fn = compose(toUpper, greeting); console.log(fn("yideng")); // HELLO YIDENG
这就是 compose 的大致使用,主要有以下几点:
- compose 参数是函数,返回也是一个函数
- 除了第一个函数接受参数,其它函数接受的都是上一个函数的返回值,所以初始函数的参数是多元的,而其它函数的接受值是一元的。
- compose 函数可以接受任意的参数,所有的参数都是函数,且执行方向是自右向左的,初始函数一定放到参数的最右面。
参考实现
- 方式一:
border-radius
<style> #circle { background: red; width: 100px; height: 100px; border-radius: 50%; } </style> <div id="circle"></div> <script> document.querySelector("#circle").onclick = function () { alert("ok"); }; </script>
方式二:map + area
- map 标签用来定义一个客户端图像映射(带有可点击区域的一副图像);
- area 标签用来定义图像映射中的区域,area 元素永远嵌套在 map 元素内部;
<img src="xxxxx.png" usemap="#Map" /> <map name="Map" id="Map"> <area shape="circle" coords="100,100,50" href="https://www.yidengxuetang.com/" rel="external nofollow" target="_blank" /> </map>
area 属性
- shape:表示热点区域的形状,支持 rect(矩形),circle(圆形),poly(多边形)
- coords:表示热点区域的坐标,(0,0)表示图片左上角。rect 四个值分别表示左上角坐标和右下角坐标。circle 三个值分别表示圆心坐标和半径。poly 有若干个值,每两个表示一个坐标点。
- href:表示链接到某个地址,和
<a>
标签差不多- alt:对该区域描述,类似于
<img>
的 alt
一、http 协议的队首阻塞
队首阻塞,队首的事情没有处理完的时候,后面的都要等着。
1.1 HTTP1.0 的队首阻塞
对于同一个 tcp 连接,所有的 http1.0 请求放入队列中,只有前一个请求的响应收到了,然后才能发送下一个请求。http1.0 的队首组塞发生在客户端。
1.2 HTTP1.1 的队首阻塞
HTTP1.1 版本上使用了一种 Pipelining 管道技术来并行发送和处理多个请求。让客户端能够并行发送多个请求,服务器端也可以并行处理多个来自客户端的请求。在一个 TCP 连接中,发送多个 HTTP 请求,不需要等待服务器端对前一个请求的响应之后,再发送下一个请求。但是使用了管道技术的 HTTP/1.1,根据 HTTP/1.1 的规则,服务器端在响应时,要严格按照接收请求的顺序发送,即先接收到的请求,需要先发送其响应,客户端浏览器也是如此,接收响应的顺序要按照自己发送请求的顺序来。这样造成的问题是,如果 最先收到的请求的处理时间长的话,响应生成也慢,就会阻塞已经生成了的响应的发送。也会造成队首阻塞。
总的来说,管道技术允许客户端和服务器端并行发送多个请求和响应,但是客户端接收响应的顺序要和自己发送请求的顺序对应,服务器端发送响应的顺序要和自己接收到的请求的顺序对应,这样做似乎没什么问题,看起来是不是“FIFO”先来先服务的方式,如果前面收到的一个请求,在服务器端处理的时间很长,生成响应需要很多时间,那么对于后面的已经处理完生成响应的请求来说,它们只能阻塞等待,等待前面的响应发送完后,自己才能被发送出去(即使该请求的响应已经生成),造成了“队首阻塞”问题。可见队首阻塞发生在服务器端,虽然服务器端并行接收了多个请求,也并行处理生成多个响应,但由于要遵守 HTTP/1.1 的规则,先接收到的请求需要先发送响应,造成了阻塞问题。
另外需要注意的是,虽然 HTTP/1.1 规范中规定了 Pipelining 管道技术来并行发送和处理多个请求,但是这个功能在浏览器中默认是关闭的。
看一下 Pipelining 是什么,RFC 2616 中的规定 A client that supports persistent connections MAY "pipeline" its requests (i.e., send multiple requests without waiting for each response). A server MUST send its responses to those requests in the same order that the requests were received. 一个支持持久连接的客户端可以在一个连接中发送多个请求(不需要等待任意请求的响应)。收到请求的服务器必须按照请求收到的顺序发送响应。 至于标准为什么这么设定,我们可以大概推测一个原因:由于 HTTP/1.1 是个文本协议,同时返回的内容也并不能区分对应于哪个发送的请求,所以顺序必须维持一致。比如你向服务器发送了两个请求 GET /query?q=A 和 GET /query?q=B,服务器返回了两个结果,浏览器是没有办法根据响应结果来判断响应对应于哪一个请求的。
Pipelining 这种设想看起来比较美好,但是在实践中会出现许多问题:
- 一些代理服务器不能正确的处理 HTTP Pipelining。
- 正确的流水线实现是复杂的
- Head-of-line Blocking 连接头阻塞:在建立起一个 TCP 连接之后,假设客户端在这个连接连续向服务器发送了几个请求。按照标准,服务器应该按照收到请求的顺序返回结果,假设服务器在处理首个请求时花费了大量时间,那么后面所有的请求都需要等着首个请求结束才能响应。
所以现代浏览器默认是不开启 HTTP Pipelining 的。
let
块级作用域 重复声明
function outputNum(count){ //块级作用域 (function(){ for(var i = 0; i < count; i ++){ console.log(i) } })() console.log(i) } outputNum(5)
const
function _const(key, value) { window[key] = value Object.defineProperty(window, key, { enumerable: false, configurable: false, get: function () { return value }, set: function(newValue) { if (newValue !== value) { throw TypeError('这是只读变量,不可修改') } else { return value } } }) }