GithubHelp home page GithubHelp logo

adodo0829 / blog Goto Github PK

View Code? Open in Web Editor NEW
29.0 2.0 4.0 71.45 MB

搭建知识体系

JavaScript 72.37% HTML 25.91% TypeScript 0.98% Vue 0.51% Dockerfile 0.03% CSS 0.03% GLSL 0.09% Shell 0.09%
react vue javascript

blog's Introduction

知识体系 Tree Rebuild

重学前端系列篇... 对日常零碎的知识进行归类总结, 逐步建立自己的认知体系...💪💪💪

前端知识点太多, 有时候需要由点到面的去深入, 有时又需要由面到点去扩展...
每天进步一丢丢, 持续更新中ing...喜欢的话欢迎点个star, 会持续更新

前端基础

浏览器环境

前端框架

前端工程化

跨端开发

PC; H5 Web App; Hybrid App; Native App; Electron; 小程序

性能优化

数据结构与算法

后端基础

运维部署&服务器

计算机基础

技术之外

blog's People

Contributors

adodo0829 avatar

Stargazers

Roman avatar laohuihui avatar  avatar  avatar junble avatar  avatar  avatar Xing Yahao avatar  avatar Crazy avatar zhibo avatar yearm avatar Tomatosouse avatar  avatar  avatar gumiho-bai avatar tangshuo avatar Wynn avatar tflin avatar Junlin-Zhu avatar Durgin avatar  avatar  avatar 你支哥 avatar zhangshuo avatar  avatar Roger avatar  avatar  avatar

Watchers

James Cloos avatar  avatar

blog's Issues

计算机中一些关键术语

计算机中一些关键术语

CPU

**处理器单元: 由控制器和运算器组成

  • cpu 工作流
    计算机的工作流: 输入单元->主存储器->CPU->主存储器->输出单元
    CPU: 从内存中取指令->解码->执行,
    然后再取指->解码->执行下一条指令,重复直至整个程序被执行完成
  • cpu 分类
# 以x86架构64位为例
x86是针对cpu的型号或者说架构的一种统称;
64位指的是cpu一次性能从内存中取出多少64位二进制指令;
cpu具有向下兼容性: 即 64位能运行32位的程序, 32不能运行64位的程序
  • cpu 指令集
# 1.精简指令集(Reduced Instruction Set Computing,RISC)
这种CPU指令集较为精简,每个指令的运行时间都很短,完成的动作也很单纯,指令的执行效能较佳;
但是若要做复杂的事情,就要由多个指令来完成.
常见的RISC微指令集CPU: ARM, 常用的各厂商的手机、PDA、导航系统、网络设备等,几乎都用该架构的CPU。

# 2.复杂指令集(Complex Instruction Set Computer,CISC)
每个小指令可以执行一些较低阶的硬件操作,指令数目多而且复杂,每条指令的长度并不相同。
因此指令执行较为复杂所以每条指令花费的时间较长,但每条个别指令可以处理的工作较为丰富。
常见的CISC微指令集CPU主要有AMD、Intel、VIA等的x86架构的CPU
  • cpu 工作状态
# 1.内核态:运行的程序是操作系统,可以操作计算机硬件
# 2.用户态:运行的程序是应用程序,不能操作计算机硬件

内核态与用户态的转换: 
    应用程序的运行必然涉及到计算机硬件的操作,那就必须有用户态切换到内核态下才能实现,
    所以计算机工作时就是在频繁发生内核态与用户态的转换
  • cpu 多核和多线程
2核4线程:
	2核代表有两个cpu,4线程指的是每个cpu都有两个线程(假4核)

4核8线程:
	4核代表有4个cpu,8线程指的是每个cpu都有两个线程(假8核)

存储器

存储器是计算机第二重要的部分, 计算机的数据都在内存中存储着;

内存:
  RAM: 随机存储(主存)
  ROM: 只读内存
硬盘:
  机械硬盘: 磁盘 
      磁道:一圈数据,对应着一串二进制(1bit代表一个二进制位)
      8bit比特位=1Bytes字节
      1024Bytes=1KB
      1024KB=1MB
      1024MB=1GB
      1024GB=1TB
      1024TB=1PB

      扇区:一个扇区通过为512Bytes
     站在硬盘的解读,一次性读写数据的最小单为为扇区

     操作系统一次性读写的单位是一个block块 -> 8扇区的大小=4096Bytes
  固态硬盘: 闪存, 电力维持

总线

总线连接:把计算机的五大部件连接到一组公共信息传输线上
总线:连接多个部件的信息传输线,是各部件共享的传输介质,实际上由许多传输线或者通路组成,每一条传输线可以一位一位的传输二进制代码
参考资料

操作系统的启动流程

BIOS介绍:
BIOS:Basic Input Output System
BIOS被写入ROM设备

裸机:
cpu
ROM:充当内存,存放BIOS系统
CMOS:充当硬盘

# 操作系统的启动流程
1.计算机加电

2.BIOS开始运行,检测硬件:cpu、内存、硬盘等

3.BIOS读取CMOS存储器中的参数,选择启动设备

4.从启动设备上读取第一个扇区的内容
(MBR主引导记录512字节,前446为引导信息,后64为分区信息,最后两个为标志位)

5.根据分区信息读入bootloader启动装载模块,启动操作系统

6.然后操作系统询问BIOS,以获得配置信息。对于每种设备,系统会检查其设备驱动程序是否存在,
如果没有,系统则会要求用户安装设备驱动程序。
一旦有了全部的设备驱动程序,操作系统就将它们调入内核

# 应用程序的启动流程
1、双击exe快捷方式 -> exe文件的绝对路径
  就是在告诉操作系统说:我有一个应用程序要执行,应用程序的文件路径是(exe文件的绝对路径)

2、操作系统会根据文件路径找到exe程序在硬盘的位置,控制其代码从硬盘加载到内存

3、然后控制cpu从内存中读取刚刚读入内存的应用程序的代码执行,应用程序完成启动

storage API 与 cookie,session

storage API 与 cookie,session

主要是介绍一下本地存储与 cookie,session 之间的区别

cookie

定义: cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上.

在HTTP请求建立连接时,客户端、服务端它们两个之间建立的连接。但是HTTP协议每次建立连接都是独立,是无状态的连接。cookie 可以用来保存状态

  • 背景原因
# 首次建立连接,用户在客户端中登录,服务端验证登录信息,生成Token为以后的请求不需要从新登录

# 第二次建立连接,客户端携带服务端在登录成功时返回的Token,但是这个Token要储存在哪里?一般会存在cookie里; 当然现在 token 一般放在header里了

cookie 创建

当服务器收到HTTP请求时,服务器可以在响应头里面添加一个Set-Cookie选项。浏览器收到响应后通常会保存下Cookie,之后对该服务器每一次请求中都通过Cookie请求头部将Cookie信息发送给服务器。
在创建Cookie是可以设置很多属性,如Expires、Max-Age、Domain、Path、Secure、HttpOnly,因为它会自动携带到服务器端,同时又支持服务器端设置。
所以有很多的方面要注意,比如时效性、作用域、安全性。

  • 时效性
如果在Set-Cookie时不通过Expries、Max-Age两个字段设置Cookie的时效性,
那么这个Cookie是一个简单的会话期Cookie。它在关闭浏览器是会被自动删除。

如果设置了Expries、Max-Age那么这个Cookie在指定时间内都是有效的。
当Cookie的过期时间被设定时,设定的日期和时间只与客户端相关,而不是服务端。
  • 作用域
Domain 和 Path 标识定义了Cookie的作用域: 即Cookie应该发送给哪些URL

Domain 标识指定了哪些主机可以接受Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了Domain,则一般包含子域名。

Path 标识指定了主机下的哪些路径可以接受Cookie(该URL路径必须存在于请求URL中)
  • 安全性
标记为 Secure 的Cookie只应通过被HTTPS协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过Cookie传输,因为Cookie有其固有的不安全性,Secure 标记也无法提供确实的安全保障。

为避免跨域脚本 (XSS) 攻击,通过JavaScript的 Document.cookie API无法访问带有 HttpOnly 标记的Cookie,它们只应该发送给服务端。

# xss 和 防御xss
如果在服务器端Set-Cookie时没有设置HttpOnly=true时,在浏览器端就可以通过document.cookie来读取和修改Cookie中的值,这是十分安全的会造成xss。当Cookie中有关键性信息是要设置HttpOnly=true。

# csrf 和 防御csrf
CSRF: 跨站请求伪造(CSRF)是一种冒充受信任用户,向服务器发送非预期请求的攻击方式
SameSite Cookie允许服务器要求某个cookie在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击.

cookie 特征

  • 应用
1.储存用户信息(用户token)
2.标记用户行为(uuid、埋点)
  • 缺点
Cookie会被附加在每个HTTP请求中,所以无形中增加了流量

Cookie可能被禁用。当用户非常注重个人隐私保护时,他很可能禁用浏览器的Cookie功能;

由于在HTTP请求中的Cookie是明文传递的,潜在的安全风险,Cookie 可能会被篡改

Cookie数量和长度的限制。每个域名(Domain)下的数量限制;
当超过单个域名限制之后,再设置cookie,浏览器就会清除以前设置的cookie。
IE和Opera会清理近期最少使用的cookie,FF会随机清理cookie;

每个Cookie长度不能超过4KB

session

Session是一种记录客户状态的机制,不同于Cookie的是Cookie保存在客户端浏览器中,而Session保存在服务器上。避免了在客户端Cookie中存储敏感数据

session 创建

Session在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同创建Session的方法。 创建Session的同时,服务器会为该Session生成唯一的session id, 这个session id在随后的请求中会被用来重新获得已经创建的Session。

Session被创建之后,就可以调用Session相关的方法往Session中增加内容了, 而这些内容只会保存在服务器中,发到客户端的只有session id.

当客户端再次发送请求的时候,会将这个session id带上, 服务器接受到请求之后就会依据session id找到相应的Session,从而再次使用Session。

session 时效性

Session在用户第一次访问服务器的时候自动创建,Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。 用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session"活跃(active)"了一次。

当有越来越多的用户访问服务器,Session也会越来越多; 为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。 这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。

session 失效

session 依赖于 cookie, 它存在与 cookie 中; 当 cookie 被清除时, sessionid 也会被清理掉.
所以, 在Set-Cookie时设置Expries或Max-Age,其实就是设置Cookie的失效时间。 或者直接把Sessionid储存在本地。

H5 Storage API

sessionStorage 为每一个给定的源(given origin)维持一个独立的存储区域,该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)。

localStorage 同样的功能,但是在浏览器关闭,然后重新打开后数据仍然存在。

相关规则

  • localStorage
允许你访问一个Document 源(origin)的对象 Storage
存储的数据将保存在浏览器会话中
存储在 localStorage 的数据可以长期保留
  • sessionStorage
允许你访问一个 session Storage 对象
存储在 sessionStorage 里面的数据在页面会话结束时会被清除
页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话

区别

从几个方面来说, 大小,时效,安全,作用域

特性 cookie localStorage sessionStorage
生命周期 服务端生成,浏览器也可以设置 一直存在,手动清理 页面关闭,清理
存储大小 4K 5M 5M
服务端通信 每次请求携带在 header 中
作用域 遵循同源策略,当前域名或者父域名下,即二级域名可共享Cookie 在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份localStorage数据 除了协议、主机名、端口外,还要求在同一窗口,可以读取

BOM知识点概览

BOM(浏览器对象模型)

BOM提供了一套 JS浏览器 交互的属性和方法; 存在即必然, window 下挂载很多属性和方法都有用得到的地方, 这个看自己的具体需求...
在BOM对象中,window对象是最顶层对象,在浏览器环境中它是一个Global全局对象,其它对象(如:DOM对象)对是这个对象的属性(子对象)。BOM对象是与内容无关,主要用于管理浏览器窗口及窗口之间的通讯。

window对象

BOM 的核心对象是 window,它表示浏览器的一个实例。在浏览器中,window 对象有双重角色,它既是通过 JavaScript 访问浏览器窗口的一个接口,又是 ECMAScript 规定的 Global 对象。这意味着在网页中定义的任何一个对象、变量和函数,都以 window 作为其 Global 对象。

直接 console.log(window); 我们发现有太多属性和方法了, 经常用到了只是其中一部分(比如, DOM, navigator, history 等...不一一列举了, 没太大意义), 还有很多待发掘的, 包括 WebRTC,SVG,Canvas 等等...

全局作用域

由于 window 对象同时扮演着 ECMAScript 中 Global 对象的角色,因此所有在全局作用域中声明的变量、函数都会变成 window 对象的属性和方法。来看下面的例子。

var age = 29;
function sayAge(){
    console.log(this.age);
}

console.log(window.age);    // 29
sayAge();                   // 29
window.sayAge();            // 29

抛开全局变量会成为 window 对象的属性不谈,定义全局变量与在 window 对象上直接定义属性还是有一点差别:全局变量不能通过 delete 运算符删除,而直接在 window 对象上的定义的属性可以。例如:

var age = 29;
window.color = "red";

// 在 IE < 9 时抛出错误,在其他所有浏览器中都返回 false 
delete window.age;

// 在 IE < 9 时抛出错误,在其他所有浏览器中都返回 true
delete window.color;        // return true

console.log(window.age);    // 29
console.log(window.color);  // undefined

使用 var 语句添加的 window 属性有一个名为 Configurable 的特性,这个特性的值被默认设置为 false,因此这样定义的属性不可以通过 delete 运算符删除。IE8 及更早版本在遇到使用 delete 删除 window 属性的语句时,不管该属性最初是如何创建的,都会抛出错误,以示警告。IE9 及更高版本不会抛出错误。

另外,还要记住一件事:尝试访问未声明的变量会抛出错误,但是通过查询 window 对象,可以知道某个可能未声明的变量是否存在。例如:

// 这里会抛出错误,因为 oldValue 未定义
var newValue = oldValue;

// 这里不会抛出错误,因为这是一次属性查询
// newValue 的值是 undefined
var newValue = window.oldValue;

窗口关系及框架

如果页面中包含框架,则每个框架都拥有自己的 window 对象,并且保存在 frames 集合中。在 frames 集合中,可以通过数值索引(从0开始,从左至右,从上到下)或者框架名称来访问相应的 window 对象。每个 window 对象都有一个 name 属性,其中包含框架的名称。下面是一个包含框架的页面:

<html>
    <head>
        <title>Frameset Example</title>
    </head>
    <frameset rows="160,*">
        <frame src="frame.htm" name="topFrame">
        <frameset cols="50%,50%">
            <frame src="anotherframe.htm" name="leftFrame">
            <frame src="yetanotherframe.htm" name="rightFrame">
        </frameset>
    </frameset>
</html>

对这个例子而言,可以通过 window.frames[0] 或者 window.frames["topFrame"] 来引用上方的框架。不过最好使用 top 而非 window 来引用这些框架(例如 top.frames[0]),因为 top 对象始终指向最高(最外)层的框架,也就是浏览器窗口。使用它可以确保在一个框架中正确地访问另一个框架。因为对于在一个框架中编写的任何代码来说,其中的 window 对象指向的都是那个框架的特定实例,而非最高层的框架。

top 相对的另一个 window 对象是 parent。顾名思义,parent(父)对象始终指向当前框架的直接上层框架。在某些情况下,parent 有可能等于 top;但在没有框架的情况下,parent 一定等于 top(此时它们都等于 window)。

与框架有关的最后一个对象是 self,它始终指向 window;实际上,selfwindow 对象可以互换使用。引入 self 对象的目的只是为了与 topparent 对象对应起来,因此它不格外包含其他值。

所有这些对象都是 window 对象的属性,可以通过 window.parentwindow.top 等形式来访问。同时,这也意味着可以将不同层次的 window 对象连缀起来,例如 window.parent.parent.frames[0]

在使用框架的情况下,浏览器中会存在多个 Global 对象。在每个框架中定义的全局变量会自动成为框架中 window 对象的属性。由于每个 window 对象都包含原生类型的构造函数,因此每个框架都有一套自己的构造函数,这些构造函数一一对应,但并不相等。例如,top.Object 并不等于 top.frames[0].Object。这个问题会影响到对跨框架传递的对象使用 instanceof 运算符。

导航和打开窗口

使用 window.open() 方法既可以导航到一个特定的 URL,也可以打开一个新的浏览器窗口。这个方法可以接收4个参数:要加载的URL、窗口目标、一个特性字符串以及一个表示新页面是否取代浏览器历史记录中当前加载页面的布尔值。通常只须传递第一个参数,最后一个参数只在不打开新窗口的情况下使用。

如果为 window.open() 传递了第二个参数,而且该参数是已有窗口或框架的名称,那么就会在具有该名称的窗口或框架中加载第一个参数指定的 URL。看下面的例子。

// 等同于 <a href="http://baidu.com" target="newWindow"></a>
window.open("http://baidu.com/", "newWindow");

弹出窗口

如果给 window.open() 传递的第二个参数并不是一个已经存在的窗口或框架,那么该方法就会根据在第三个参数位置上传入的字符串创建一个新窗口或新标签页。如果没有传入第三个参数,那么就会打开一个带有全部默认设置(工具栏、地址栏和状态栏等)的新浏览器窗口(或者打开一个新标签页)。在不打开新窗口的情况下,会忽略第三个参数。

第三个参数是一个逗号分隔的设置字符串,表示在新窗口中都显示哪些特性。下表列出了可以出现在这个字符串中的设置选项。

设置 说明
fullscreen yes或no 表示浏览器窗口是否最大化。仅限IE
height 数值 表示新窗口的高度。不能小于100
left 数值 表示新窗口的左坐标。不能是负值
location yes或no 表示是否在浏览器窗口中显示地址栏。不同浏览器的默认值不同。如果设置为no,地址栏可能会隐藏,也可能会被禁用(取决于浏览器)
menubar yes或no 表示是否在浏览器窗口中显示菜单栏。默认值为no
resizable yes或no 表示是否可以通过拖动浏览器窗口的边框改变其大小。默认值为no
scrollbars yes或no 表示如果内容在视口中显示不下,是否允许滚动。默认值为no
status yes或no 表示是否在浏览器窗口中显示状态栏。默认值为no
toolbar yes或no 表示是否在浏览器窗口中显示工具栏。默认值为no
top 数值 表示新窗口的上坐标。不能是负值
width 数值 表示新窗口的宽度。不能小于100

这行代码会打开一个新的可以调整大小的窗口,窗口初始大小为400×400像素,并且距屏幕上沿和左边各10像素。

window.open("http://baidu.com/","newWindow",
    "height=400,width=400,top=10,left=10,resizable=yes");

window.open() 方法会返回一个指向新窗口的引用。引用的对象与其他 window 对象大致相似,但我们可以对其进行更多控制。例如,有些浏览器在默认情况下可能不允许我们针对主浏览器窗口调整大小或移动位置,但却允许我们针对通过window.open()创建的窗口调整大小或移动位置。通过这个返回的对象,可以像操作其他窗口一样操作新打开的窗口,如下所示。

var win = window.open("http://baidu.com/","newWindow",
    "height=400,width=400,top=10,left=10,resizable=yes");

// 调整大小
win.resizeTo(500,500);

// 移动位置
win.moveTo(100,100);

// 关闭窗口
win.close();

但是,close() 方法仅适用于通过 window.open() 打开的弹出窗口。对于浏览器的主窗口,如果没有得到用户的允许是不能关闭它的。

新创建的 window 对象有一个 opener 属性,其中保存着打开它的原始窗口对象。这个属性只在弹出窗口中的最外层 window 对象(top)中有定义,而且指向调用 window.open() 的窗口或框架。例如:

var win = window.open("http://baidu.com/","newWindow",
    "height=400,width=400,top=10,left=10,resizable=yes");

console.log(win.opener === window);   // true

虽然弹出窗口中有一个指针指向打开它的原始窗口,但原始窗口中并没有这样的指针指向弹出窗口。窗口并不跟踪记录它们打开的弹出窗口,因此我们只能在必要的时候自己来手动实现跟踪。

弹出窗口屏蔽程序

曾经有一段时间,广告商在网上使用弹出窗口达到了肆无忌惮的程度。他们经常把弹出窗口打扮成系统对话框的模样,引诱用户去点击其中的广告。由于看起来像是系统对话框,一般用户很难分辨是真是假。为了解决这个问题,大多数浏览器内置有弹出窗口屏蔽程序,将绝大多数用户不想看到弹出窗口屏蔽掉。

于是,在弹出窗口被屏蔽时,就应该考虑两种可能性。如果是浏览器内置的屏蔽程序阻止的弹出窗口,那么 window.open() 很可能会返回 null,如果是浏览器扩展或其他程序阻止的弹出窗口,那么 window.open() 通常会抛出一个错误。因此,要想准确地检测出弹出窗口是否被屏蔽,必须在检测返回值的同时,将对 window.open() 的调用封装在一个 try-catch 块中,如下所示。

var blocked = false;

try {
    var win = window.open("http://baidu.com", "_blank");
    if (win == null){
        blocked = true;
    }
} catch (ex){
    blocked = true;
}
if (blocked){
    console.log("The popup was blocked!");
}

间歇调用和超时调用

JavaScript 是单线程语言,但它允许通过设置超时值和间歇时间值来调度代码在特定的时刻执行。前者是在指定的时间过后执行代码,而后者则是每隔指定的时间就执行一次代码。

超时调用需要使用 window 对象的 setTimeout() 方法,它接受两个参数:要执行的代码和以毫秒表示的时间(即在执行代码前需要等待多少毫秒)。其中,第一个参数可以是一个包含 JavaScript 代码的字符串(就和在 eval() 函数中使用的字符串一样),也可以是一个函数。例如,下面对 setTimeout() 的两次调用都会在一秒钟后显示一个警告框。

// 不建议传递字符串
setTimeout("console.log('Hello world!') ", 1000);

// 推荐的调用方式
setTimeout(function() { 
    console.log("Hello world!"); 
}, 1000);

虽然这两种调用方式都没有问题,但由于传递字符串可能导致性能损失,因此不建议以字符串作为第一个参数。

第二个参数是一个表示等待多长时间的毫秒数,但经过该时间后指定的代码不一定会执行。JavaScript 是一个单线程序的解释器,因此一定时间内只能执行一段代码。为了控制要执行的代码,就有一个 JavaScript 任务队列。这些任务会按照将它们添加到队列的顺序执行。setTimeout() 的第二个参数告诉 JavaScript 再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行。

调用 setTimeout() 之后,该方法会返回一个数值 ID,表示超时调用。这个超时调用 ID 是计划执行代码的唯一标识符,可以通过它来取消超时调用。要取消尚未执行的超时调用计划,可以调用 clearTimeout() 方法并将相应的超时调用 ID 作为参数传递给它,如下所示。

// 设置超时调用
var timeoutId = setTimeout(function() {
    console.log("Hello world!");
}, 1000);

// 注意:把它取消
clearTimeout(timeoutId);

只要是在指定的时间尚未过去之前调用 clearTimeout(),就可以完全取消超时调用。前面的代码在设置超时调用之后马上又调用了 clearTimeout(),结果就跟什么也没有发生一样。

间歇调用与超时调用类似,只不过它会按照指定的时间间隔重复执行代码,直至间歇调用被取消或者页面被卸载。设置间歇调用的方法是 setInterval(),它接受的参数与 setTimeout() 相同:要执行的代码(字符串或函数)和每次执行之前需要等待的毫秒数。下面来看一个例子。

// 不建议传递字符串
setInterval ("console.log('Hello world!') ", 10000);

// 推荐的调用方式
setInterval (function() { 
    console.log("Hello world!"); 
}, 10000);

调用 setInterval() 方法同样也会返回一个间歇调用 ID,该 ID 可用于在将来某个时刻取消间歇调用。要取消尚未执行的间歇调用,可以使用 clearInterval() 方法并传入相应的间歇调用 ID。取消间歇调用的重要性要远远高于取消超时调用,因为在不加干涉的情况下,间歇调用将会一直执行到页面卸载。以下是一个常见的使用间歇调用的例子。

var num = 0;
var max = 10;
var intervalId = null;

function incrementNumber() {
    num++;
    // 如果执行次数达到了max设定的值,则取消后续尚未执行的调用
    if (num == max) {
        clearInterval(intervalId);
        console.log("Done");
    }
}

intervalId = setInterval(incrementNumber, 500);

在这个例子中,变量num每半秒钟递增一次,当递增到最大值时就会取消先前设定的间歇调用。这个模式也可以使用超时调用来实现,如下所示。

var num = 0;
var max = 10;

function incrementNumber() {
    num++;

    // 如果执行次数未达到max设定的值,则设置另一次超时调用
    if (num < max) {
        setTimeout(incrementNumber, 500);
    } else {
        console.log("Done");
    }
}

setTimeout(incrementNumber, 500);

可见,在使用超时调用时,没有必要跟踪超时调用 ID,因为每次执行代码之后,如果不再设置另一次超时调用,调用就会自行停止。一般认为,使用超时调用来模拟间歇调用的是一种最佳模式。在开发环境下,很少使用真正的间歇调用,原因是后一个间歇调用可能会在前一个间歇调用结束之前启动。而像前面示例中那样使用超时调用,则完全可以避免这一点。所以,最好不要使用间歇调用。

系统对话框

浏览器通过 alert()confirm()prompt() 方法可以调用系统对话框向用户显示消息。系统对话框与在浏览器中显示的网页没有关系,也不包含 HTML。它们的外观由操作系统及(或)浏览器设置决定,而不是由 CSS 决定。此外,通过这几个方法打开的对话框都是同步和模态的。也就是说,显示这些对话框的时候代码会停止执行,而关掉这些对话框后代码又会恢复执行。

第一种对话框是调用 alert() 方法生成的。它向用户显示一个系统对话框,其中包含指定的文本和一个 OK(“确定”)按钮。通常使用 alert() 生成的“警告”对话框向用户显示一些他们无法控制的消息,例如错误消息。而用户只能在看完消息后关闭对话框。

第二种对话框是调用 confirm() 方法生成的。从向用户显示消息的方面来看,这种“确认”对话框很像是一个“警告”对话框。但二者的主要区别在于“确认”对话框除了显示OK按钮外,还会显示一个 Cancel(“取消”)按钮,两个按钮可以让用户决定是否执行给定的操作。

为了确定用户是单击了OK还是Cancel,可以检查 confirm() 方法返回的布尔值:true 表示单击了OK,false 表示单击了Cancel或单击了右上角的 X 按钮。确认对话框的典型用法如下。

if (confirm("Are you sure?")) {
    alert("I'm so glad you're sure! ");
} else {
    alert("I'm sorry to hear you're not sure.");
}

最后一种对话框是通过调用 prompt() 方法生成的,这是一个“提示”框,用于提示用户输入一些文本。提示框中除了显示 OK 和 Cancel 按钮之外,还会显示一个文本输入域,以供用户在其中输入内容。prompt() 方法接受两个参数:要显示给用户的文本提示和文本输入域的默认值(可以是一个空字符串)。

如果用户单击了 OK 按钮,则 promp() 返回文本输入域的值;如果用户单击了 Cancel 或没有单击 OK 而是通过其他方式关闭了对话框,则该方法返回 null。下面是一个例子。

var result = prompt("What is your name? ", "");
if (result !== null) {
    alert("Welcome, " + result);
}

综上所述,这些系统对话框很适合向用户显示消息并请用户作出决定。由于不涉及 HTML、CSS 或 JavaScript,因此它们是增强 Web 应用程序的一种便捷方式。

location 对象

location 对象提供了与当前窗口中加载的文档有关的信息,还提供了一些导航功能。事实上,location 对象是很特别的一个对象,因为它既是 window 对象的属性,也是 document 对象的属性;换句话说,window.locationdocument.location 引用的是同一个对象。location 对象的用处不只表现在它保存着当前文档的信息,还表现在它将 URL 解析为独立的片段,让开发人员可以通过不同的属性访问这些片段。下表列出了 location 对象的所有属性。

属性名 例子 说明
hash "#contents" 返回 URL 中的 hash(#号后跟零或多个字符),如果 URL 中不包含散列,则返回空字符串
host "baidu.com:80" 返回服务器名称和端口号(如果有)
hostname "baidu.com" 返回不带端口号的服务器名称
href "http:/baidu.com" 返回当前加载页面的完整URL。而 location 对象的 toString() 方法也返回这个值
pathname "/WileyCDA/" 返回URL中的目录和(或)文件名
port "8080" 返回 URL 中指定的端口号。如果 URL 中不包含端口号,则这个属性返回空字符串
protocol "http:" 返回页面使用的协议。通常是 http: 或 https:
search "?q=javascript" 返回URL的查询字符串。这个字符串以问号开头

查询字符串参数

虽然通过上面的属性可以访问到 location 对象的大多数信息,但其中访问URL包含的查询字符串的属性并不方便。尽管 location.search 返回从问号到 URL 末尾的所有内容,但却没有办法逐个访问其中的每个查询字符串参数。为此,可以像下面这样创建一个函数,用以解析查询字符串,然后返回包含所有参数的一个对象:

/*
 * 这个函数用来解析来自URL的查询串中的name=value参数对
 * 它将name=value对存储在一个对象的属性中,并返回该对象
 * 这样来使用它
 *
 * var args = urlArgs(); // 从URL中解析参数
 * var q = args.q || ""; // 如果参数定义了的话就使用参数;否则使用一个默认值
 * var n = args.n ? parseInt(args.n) : 10;
 */
function urlArgs() {
    var args = {};                                  // 定义一个空对象
    var query = location.search.substring(1);       // 查找到查询串,并去掉'? '
    var pairs = query.split("&");                   // 根据"&"符号将查询字符串分隔开
    for (var i = 0; i < pairs.length; i++) {        // 对于每个片段
        var pos = pairs[i].indexOf('=');            // 查找"name=value"
        if (pos == -1) continue;                    // 如果没有找到的话,就跳过
        var name = pairs[i].substring(0, pos);      // 提取name
        var value = pairs[i].substring(pos + 1);    // 提取value
        value = decodeURIComponent(value);          // 对value进行解码
        args[name] = value;                         // 存储为属性
    }
    return args;                                    // 返回解析后的参数
}

位置操作

使用 location 对象可以通过很多方式来改变浏览器的位置。首先,也是最常用的方式,就是使用 assign()方法并为其传递一个 URL,如下所示。

location.assign("http://baidu.com");

这样,就可以立即打开新URL并在浏览器的历史记录中生成一条记录。如果是将 location.hrefwindow.location 设置为一个URL值,也会以该值调用 assign() 方法。例如,下列两行代码与显式调用 assign() 方法的效果完全一样。

window.location = "http://baidu.com";
location.href = "http://baidu.com";

在这些改变浏览器位置的方法中,最常用的是设置 location.href 属性。

另外,修改 location 对象的其他属性也可以改变当前加载的页面。下面的例子展示了通过将 hashsearchhostnamepathnameport 属性设置为新值来改变 URL。

// 假设初始 URL 为 http://baidu.com/about/
location.href = "http://baidu.com/about/"

// 将 URL 修改为 "http://baidu.com/about/#ds-thread"
location.hash = "#ds-thread";

// 将 URL 修改为 "http://baidu.com/about/?args=123"
location.search = "?args=123";

// 将 URL 修改为 "https://segmentfault.com/"
location.hostname = "segmentfault.com";

// 将 URL 修改为 "http://segmentfault.com/u/stone0090/"
location.pathname = "u/stone0090";

// 将 URL 修改为 "https://segmentfault.com:8080/"
location.port = 8080;

当通过上述任何一种方式修改URL之后,浏览器的历史记录中就会生成一条新记录,因此用户通过单击“后退”按钮都会导航到前一个页面。要禁用这种行为,可以使用 replace() 方法。这个方法只接受一个参数,即要导航到的 URL;结果虽然会导致浏览器位置改变,但不会在历史记录中生成新记录。在调用 replace() 方法之后,用户不能回到前一个页面,来看下面的例子:

<!DOCTYPE html>
<html>
<head>
    <title>You won't be able to get back here</title>
</head>
    <body>
    <p>Enjoy this page for a second, because you won't be coming back here.</p>
    <script type="text/javascript">
        setTimeout(function () {
            location.replace("http://baidu.com/");
        }, 1000);
    </script>
</body>
</html>

如果将这个页面加载到浏览器中,浏览器就会在1秒钟后重新定向到 baidu.com。然后,“后退”按钮将处于禁用状态,如果不重新输入完整的 URL,则无法返回示例页面。

与位置有关的最后一个方法是 reload(),作用是重新加载当前显示的页面。如果调用 reload() 时不传递任何参数,页面就会以最有效的方式重新加载。也就是说,如果页面自上次请求以来并没有改变过,页面就会从浏览器缓存中重新加载。如果要强制从服务器重新加载,则需要像下面这样为该方法传递参数 true

location.reload();        // 重新加载(有可能从缓存中加载)
location.reload(true);    // 重新加载(从服务器重新加载)

位于 reload() 调用之后的代码可能会也可能不会执行,这要取决于网络延迟或系统资源等因素。为此,最好将 reload() 放在代码的最后一行。

总结

BOM(浏览器对象模型)以 window 对象为依托,表示浏览器窗口以及页面可见区域。同时,window 对象还是 ECMAScript 中的 Global 对象,因而所有全局变量和函数都是它的属性,且所有原生的构造函数及其他函数也都存在于它的命名空间下。本章讨论了下列 BOM 的组成部分。

  • 在使用框架时,每个框架都有自己的 window 对象以及所有原生构造函数及其他函数的副本。每个框架都保存在 frames 集合中,可以通过位置或通过名称来访问。
  • 有一些窗口指针,可以用来引用其他框架,包括父框架。
  • top 对象始终指向最外围的框架,也就是整个浏览器窗口。
  • parent 对象表示包含当前框架的框架,而 self 对象则回指 window
  • 使用 location 对象可以通过编程方式来访问浏览器的导航系统。设置相应的属性,可以逐段或整体性地修改浏览器的 URL。
  • 调用 replace() 方法可以导航到一个新 URL,同时该 URL 会替换浏览器历史记录中当前显示的页面。
  • navigator 对象提供了与浏览器有关的信息。到底提供哪些信息,很大程度上取决于用户的浏览器;不过,也有一些公共的属性(如 userAgent)存在于所有浏览器中。

BOM中还有两个对象:screenhistory
screen 对象中保存着与客户端显示器有关的信息,这些信息一般只用于站点分析。
history 对象为访问浏览器的历史记录开了一个小缝隙,开发人员可以据此判断历史记录的数量,也可以在历史记录中向后或向前导航到任意页面。

nginx 配置文件

nginx.conf 文件

nginx.conf 文件是Nginx的总配置文件,我们可以按需进行修改

# 运行用户,默认即是nginx,可以不进行设置
user  nginx;
# nginx进程,一般设置为和CPU核数一样
worker_processes  1;   
# 错误日志存放目录
error_log  /var/log/nginx/error.log warn;
# 进程pid存放位置
pid        /var/run/nginx.pid;


events {
    worker_connections  1024; # 单个后台进程的最大并发数
}


http {
    include       /etc/nginx/mime.types;   #文件扩展名与类型映射表
    default_type  application/octet-stream;  #默认文件类型
    #设置日志模式
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;   #nginx访问日志存放位置

    sendfile        on;   #开启高效传输模式
    #tcp_nopush     on;    #减少网络报文段的数量

    keepalive_timeout  65;  #保持连接的时间,也叫超时时间

    #gzip  on;  #开启gzip压缩

    include /etc/nginx/conf.d/*.conf; #包含的子配置项位置和文件

上述子配置文件

我们创建一个 default.conf 文件

server {
    listen       80;   #配置监听端口
    server_name  localhost;  //配置域名

    #charset koi8-r;     
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;     #服务默认启动目录
        index  index.html index.htm;    #默认访问文件
    }

    #error_page  404              /404.html;   # 配置404页面

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;   #错误状态码的显示页面,配置后需要重启
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

vue 项目部署到服务器

server {
    listen 80;
    server_name xxxx.com;
    error_log /logs/xxxx.com-errorlog.log;
    location / {
        root /www/xxxx/dist; 
        # index index.html; # hash 模式
        try_files $uri $uri/ /index.html; # history 模式
    }
}

作用域和闭包

作用域和闭包

总的来说可以分为以下几块内容区理解:

词法作用域
动态作用域 (动态作用域与this机制挂钩)
全局作用域
函数作用域
块级作用域 (es6+)

前言基础

我们先了解下js引擎(解释器/编译器结构), js引擎可以理解为根据ECMAScript定义的语言标准来动态执行JavaScript字符串...
js 引擎基础
js 执行环境
整个解析过程可分为: 语法检查阶段 -> 运行时阶段

语法检查阶段(一)

语法检查阶段: 分为 词法分析和语法分析

JS解释器先把JavaScript代码(字符串)的字符流按照ECMAScript标准转换为记号流,
分解为词法单元块. 
// 比如 var a = 2, 词法分析后的结果
[
  "var": "keyword",
  "a": "identifier",
  "=": "assignment",
  "2": "integer",
  ";": "eos" (end of statement)
]
// 然后根据解析的词法结构结合标准与法生成 AST
{
  operation: "=",
  left: {
    keyword: "var",
    right: "a"
  }
  right: "2"
}
// 遍历这颗抽象语法树(其中的一些操作还没去深入研究, 反正可以做很多事情, 什么eslint, babel 转化等),
最后直接会转化为机器指令(分配内存空间)

# 当语法检查出现错误时, 会抛出错误...

运行时阶段(二)

运行时阶段: 预解析 和 代码执行

  • 先预解析(一)
# 1.创建执行上下文环境: 先是全局, 然后是函数,会被依次push到执行栈(一块内存来管理上下文)
// 上下文环境包括: 
变量对象VO: 优先级依次是 arguments声明, function声明, var声明
作用域链Scope(词法环境): 由当前变量对象以及上层父级作用域构成的一条链表结构, 便于变量查询
this值:    表示当前上下文对象, 程序进入上下文就会确定下来

# 2.为上下文中的变量对象(VO)赋值
函数形参: undefined
函数声明: 如果变量对象已经包含了相同名字的属性,则会替换它的值, 
         此时函数的标识符在环境中已存在(变量提升的原因)
变量声明: undefined
// 例子就不举了...QAQ
  • 再代码执行(二)
进入执行代码阶段
预解析阶段的初始化属性:
  `变量对象`的undefined值可能会被覆盖 => 变为活动对象(AO)
  `作用域链`可能会改变
  `this值`也可能会改变

作用域

作用域[[scope]]: 可以理解为变量的生命周期,有效范围; 也可以理解为js中用来访问变量的一套规则...

词法作用域

上面为甚么提到 js引擎解析代码的过程, 因为语法检查阶段可以看做是理解词法作用域的基础;
词法作用域就是定义在词法阶段的作用域, JavaScript采用的是词法作用域, 变量,函数的作用域在函数定义的时候就决定

词法作用域只由变量,函数被声明时所处的位置决定

// 第一层
var a = 1
function foo(a) {
  // 第二层
  var b = a * 2;
  function bar(c) {
    // 第三层
    console.log(a, b, c);
  }
  bar(b * 3);
}
foo(2) // 2, 4, 12
分析一下: 这里有个嵌套的作用域
第一层: 全局作用域, 两个声明, foo, a
第二层: foo的函数作用域, 三个声明, a, b, bar
第三层: bar的函数作用域, 一个声明, c

作用域的这种嵌套结构和互相之间的位置关系给 js 引擎提供了的位置信息;
js引擎可以用这些信息来查找标识符声明的位置;

特性: 作用域查找从运行时所处的最内部作用域开始,逐级向上进行,直到遇见第一个匹配的标识符为止

动态作用域

动态作用域主要是js程序在运行时决定的, 与 this 机制相关; 动态作用域并不关心函数和作用域是如何声明以及在任何处声明的,只关心它从何处调用...

函数的作用域是在函数调用的时候才决定, 与上下文挂钩

作用域链

当查找变量标识符的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
作用域链在函数创建运行时是会发生改变的...

函数创建时

函数有一个内部属性 [[scope]],当函数创建的时候, 就会保存所有父变量对象到其中;

function foo() {
    function bar() {
      // ...
    }
}

// scope:
foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];

函数运行时

函数运行时, 进入函数上下文,创建 VO/AO, 就会将活动对象添加到作用链的顶端

Scope = [AO].concat([[Scope]])

总结一下作用域链创建过程(ES3规范下, ES5的规范会在后续ES6的内容中讲到)

var v1 = 'global'
function fn() {
  var v2 = 'local'
  return v2
}
fn()

语法检查阶段

  • 1.函数 fn 被 声明创建,保存作用域链到内部属性[[scope]]
fn.[[scope]] = [
  globalContext.VO
]
  • 2.函数 fn 被执行,创建 fn的函数上下文, 上下文被压入执行栈
ECStack = [
  fnContext,
  globalContext
]
  • 3.创建作用域链Scope...复制函数的 scope 属性
fnContext = {
  Scope: fn.[[scope]]
}
  • 4.添加AO, 即初始化VO(arguments,func声明, 变量声明)
fnContext = {
  AO: {
    arguments: {
      length: 0
    },
    v2: undefined
  },
  Scope: fn.[[scope]]
}
  • 5.AO 添加到 Scope 的顶端
fnContext = {
  AO: {
    arguments: {
      length: 0
    },
    v2: undefined
  },
  Scope: [AO, fn.[[scope]]]
}

运行时阶段

  • 6.准备工作完毕, 开始执行代码
fnContext = {
  AO: {
    arguments: {
      length: 0
    },
    v2: 'local'  // 为 AO 属性赋值
  },
  Scope: [AO, fn.[[scope]]]
}
  • 7.查找 v2 的值并返回, 函数执行完毕, 弹出调用栈
ECStack = [
  globalContext
]

闭包

MDN 闭包解释
ECMAScript中,闭包指的是:
理论角度:
所有的函数都是闭包.
因为它们都在创建的时候就将上层上下文的数据保存起来了.即使是全局变量也是如此,
因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
实践角度:(以下函数才算是闭包)
1.即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
2.在代码中引用了自由变量

闭包实例

var v = "global";
function outer(){
  var v = "local";
  function inner(){
      return v;
  }
  return inner;
}
var foo = outer();
foo();

# 代码执行过程分析:
1.进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
2.全局执行上下文初始化
3.执行outer函数,创建 outer 函数执行上下文,,outer的执行上下文入栈
4.outer 执行上下文初始化,创建变量对象、作用域链、this等
5.outer 函数执行完毕,outer 执行上下文出栈
6.执行 inner 函数,创建 inner 函数执行上下文,inner 执行上下文被压栈
7.inner 执行上下文初始化,创建变量对象、作用域链、this等
8.inner函数执行完毕,inner函数上下文出栈

# 当 inner函数 执行时, outer已经出栈, 如何获取 outer 作用域内的值呢?
答案是: 通过作用域链 Scope

其实 inner 在执行的时候会它的上下文维护这样一个属性
innerContext = {
  Scope: [AO, outerContext.AO, globalContext.VO]
}
所以, 即时 outer 销毁了, 内存中依然会存一份outerContext.AO

闭包解题思路

分析函数调用
分析上下文语义

globalContext = {
  VO: {
    arguments: {}
    变量: '',
  },
  Scope: []
}

funcContext = {
  AO: {
    arguments: {}
    变量: '',
  },
  Scope: [AO, ..., globalContext.VO]
}

总结

  • 作用域: 变量对象所能访问的区域
执行上下文 = {
  变量对象 = {
    arguments = {},
    func 声明,
    var 声明
  },
  Scope: [],
  this
}
词法作用域(声明时)
动态作用域(运行时)
  • 作用域链
Scope: [AO, fnContext.AO, ..., globalContext.VO]
一条能访问上层执行上下文变量对象的链表
  • 闭包
能够访问自由变量(非参数和自己内部的变量)的函数;
理论上: 所有函数
实践上: 引用了上层上下文变量对象的函数

vue nextTick源码理解

vue nextTick源码理解

nextTick 源码地址
vue 在修改data中的数据到视图试图更新的过程: 就是一个“setter -> Dep -> Watcher -> patch -> 视图update”的过程. 如果一个 data 中数据被连续更改 100 次, 是不是触发 100 次的更新呢? 显然不会...
这里 vue 通过 nextTick 实现了DOM批量异步更新...

nextTick 源码逐行解释

  // 回调函数队列
  var callbacks = [];
  // 标志位
  var pending = false;
  // 触发 callbacks 队列中的函数
  function flushCallbacks() {
    pending = false;
    //[].slice(0) 浅拷贝
    var copies = callbacks.slice(0);
    callbacks.length = 0;
    console.log(copies)

    for (var i = 0; i < copies.length; i++) {
      //执行回调函数
      copies[i]();
    }
  }

  // Here we have async deferring wrappers using both microtasks and (macro) tasks. 
  // In < 2.4 we used microtasks everywhere, but there are some scenarios where 
  // microtasks have too high a priority and fire in between supposedly  
  // sequential events (e.g. #4521, #6690) or even between bubbling of the same 
  // event (#6566). However, using (macro) tasks everywhere also has subtle problems 
  // when state is changed right before repaint (e.g. #6813, out-in transitions). 
  // Here we use microtask by default, but expose a way to force (macro) task when  
  // needed (e.g. in event handlers attached by v-on). 
  // 这里,我们默认使用微任务,但是暴露一种方法来强制(宏)任务
  // 在这里,我们使用了微任务和宏任务的异步包装器。
  // 在< 2.4中,我们到处使用微任务,但也有一些场景。
  // 微任务优先级太高,据称介于两者之间。
  // 序贯事件(例如α4521,α6690),甚至在同一气泡之间
  // 事件(α6566)。然而,到处使用(宏)任务也有微妙的问题。
  // 当状态在重新绘制之前被正确改变(例如,α6813,在过渡中出现)。
  // 需要的(例如在事件处理程序中附加的V-on)。

  var microTimerFunc; // 微任务派发功能
  var macroTimerFunc; // 宏任务派发功能
  var useMacroTask = false; // 使用宏任务
  
  // Determine (macro) task defer implementation. 
  // Technically setImmediate should be the ideal choice, but it's only available 
  // in IE. The only polyfill that consistently queues the callback after all DOM 
  // events triggered in the same loop is by using MessageChannel. 
  // 确定(宏)任务延迟实现。
  // 技术上应该是理想的选择,但它是唯一可用的。
  // 在IE.中,唯一的填充在所有DOM之后始终排队回叫。
  // 在同一循环中触发的事件是通过使用消息通道。

  /* istanbul ignore if */
  //判断setImmediate 是否存在,如果存在则判断下是是否是系统内置函数
  if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    //函数表达式赋值给macroTimerFunc
    macroTimerFunc = function () {
      setImmediate(flushCallbacks);
    };
  } else if (typeof MessageChannel !== 'undefined' && (
    isNative(MessageChannel) ||
    // PhantomJS
    MessageChannel.toString() === '[object MessageChannelConstructor]'
  )) {
    //如果有 消息体 内置函数则实例化
    var channel = new MessageChannel();
    //获取端口2
    var port = channel.port2;
    //设置端口1 的接受函数为flushCallbacks
    channel.port1.onmessage = flushCallbacks;
    //端口2推送信息给端口1
    macroTimerFunc = function () {
      port.postMessage(1);
    };
  } else {
    // 异步执行
    macroTimerFunc = function () {
      setTimeout(flushCallbacks, 0);
    };
  }

  // Determine microtask defer implementation.
  // 确定微任务延迟执行。
  if (typeof Promise !== 'undefined' && isNative(Promise)) {
    // 声明一个成功的 Promise
    var p = Promise.resolve();
    //microTimerFunc 一个异步 队列函数
    microTimerFunc = function () {
      p.then(flushCallbacks);
      // in problematic UIWebViews, Promise.then doesn't completely break, but 
      // it can get stuck in a weird state where callbacks are pushed into the 
      // microtask queue but the queue isn't being flushed, until the browser 
      // needs to do some other work, e.g. handle a timer. Therefore we can 
      // "force" the microtask queue to be flushed by adding an empty timer. 
      // 在有问题的UIWebVIEW中,Promise.then并没有完全崩溃,而是
      // 它可能会陷入一种怪异的状态,其中回调被推到
      // 微任务队列,但队列没有刷新,直到浏览器
      // 需要做一些其他的工作,例如处理计时器。因此我们可以
      // [强制]通过添加空计时器来刷新微任务队列。

      // 如果是ios, 派发macro
      if (isIOS) {
        setTimeout(noop);
      }
    };
  } else {
    // 如果无法派发micro,就退而求其次派发为macro
    microTimerFunc = macroTimerFunc;
  }

  /**
   * Wrap a function so that if any code inside triggers state change, 
   * the changes are queued using a (macro) task instead of a microtask. 
   * 包装一个函数,如果内部的任何代码触发状态改变,
   * 使用宏(宏)任务而不是微任务对这些队列进行排队
   */
  function withMacroTask(fn) {
    //宏任务
    return fn._withTask || (fn._withTask = function () {
      useMacroTask = true;
      var res = fn.apply(null, arguments);
      useMacroTask = false;
      return res
    })
  }

  //为callbacks 收集队列cb 函数 并且根据 pending 状态是否要触发callbacks 队列函数
  function nextTick(cb, ctx) {
    //cb 回调函数
    //ctx this的指向
    var _resolve;
    //添加一个回调函数到队列里面去

    callbacks.push(function () {
      if (cb) {
        //如果cb存在 并且是一个函数就执行
        try {
          cb.call(ctx);
        } catch (e) {
          //如果不是函数则报错
          handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {
        //_resolve 如果存在则执行
        _resolve(ctx);
      }
    });

    // 这里会检查上一个异步任务队列(即名为callbacks的任务数组)是否派发和执行完毕了。
    // pending此处相当于一个锁
    if (!pending) {
      pending = true;
      // 确认 pending 锁是开着的(false),就把它设置为锁上(true),
      // 然后对当前 callbacks 数组的任务进行派发(丢进 micro 或 macro 队列)和执行。
      // 设置 pending 锁的意义在于保证状态更新任务的有序进行,避免发生混乱
      if (useMacroTask) {
        macroTimerFunc(); //异步触发 或者 实现观察者触发callbacks 队列中的函数
      } else {
        microTimerFunc(); //异步触发 或者 实现观察者触发callbacks 队列中的函数
      }
    }

    // $flow-disable-line
    if (!cb && typeof Promise !== 'undefined') {
      //如果回调函数不存在 则声明一个Promise 函数
      return new Promise(function (resolve) {
        _resolve = resolve;
      })
    }
  }

源码过程分析

nextTick 函数,传入一个 cb,这个 cb 会被存储到一个队列中,在下一个 tick 时触发队列中的所有 cb 事件.

何时调用的nextTick

data 
=> setter
=> dep.notify() 
=> watcher.update() 
=> queueWatcher(this)  // watcher把自身传进了queueWatcher()

在queueWatcher方法中
=> queue.push(watcher)  // 在push之前会检查queue中是否已有该watcher
=> !waiting && waiting = true && nextTick(() => {
		// ... 执行queue中所有watcher的run
    waiting = false
	})

当触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,
在下一个 tick 的时候将这个队列 queue 全部拿出来 run
( Watcher 对象的一个方法,用来触发 patch 操作) 

about html

html

html: 超文本标记语言, 我们平时浏览的网页的就是由这种标准的标记语言所创建的. 它是由一系列的标签组成的嵌套结构,是有层级的. HTML运行在浏览器平台上,由浏览器来解析其结构,处理相关的逻辑.

具体内容参考标准手册:

HTML结构概述

文档声明

HTML5版本下的声明, 其他可以参考w3c

DOCTYPE来决定浏览器是用怪异模式还是标准模式来处理html文本

文档头部

包含有关页面的常规信息和元数据; 元素下的子元素主要包括、<title>、、、<style>和<script>这六个元素.

meta

meta标签(meta-information)用于提供页面有关的元数据,除了提供文档字符集、使用语言、作者等基本信息外,还涉及对关键词和网页等级的设定。
通过设置不同的属性,元数据可以分为以下几种:

charset: 即将对网页使用的字符集作出声明HTML5
name: 它是一个文档级的元数据,将附着在整个页面上
http-equiv: 它是一个编译指令,即由服务器来指示页面应如何加载
  • charset
charset声明声明当前文档所使用的字符编码,但该声明可以被任何一个元素的lang特性的值覆盖。
文档的编码一定要与文件本身的编码保持一致,否则会出现乱码,推荐使用UTF-8编码
字符编码必须写在<head>元素的最开始,如果位于<title>标签之后,那么<title>标签很可能会乱码
<meta charset="utf-8" />
  • name
# keyword
<meta name="keywords" content="网站主旨..." />
# description
<meta name="description" content="网站描述..." />
# author
<meta name="author" content="huhua">
# copyright
<meta name="copyright" content="版权所有">
# viewport h5页面自适应
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
# IE浏览器渲染
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
安装了GCF(Google Chrome Frame谷歌内嵌浏览器框架GCF),则使用GCF来渲染页面,如果没有安装,则使用最高版本的IE内核进行渲染
# 双核浏览器渲染
<meta name="renderer" content="webkit">
如果是双核浏览器,则使用webkit内核渲染
  • http-equiv
# refresh定: 让网页多少秒刷新,或跳转到其他网页
<meta http-equiv="refresh" content="5">
<meta http-equiv="refresh" content="5;url=http://www.baidu.com">

# Expires: 设定网页的到期时间,过期则必须到服务器上重新调用。
<meta http-equiv="Expires" Content="Sat Nov 28 2016 21:19:15 GMT+0800">
注意必须使用GMT时间格式

# Pragma: 用于设定禁止浏览器从本地机的缓存中调阅页面内容,用户无法脱机浏览
<meta http-equiv="Pragma" Content="No-cach">

# 兼容模式
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=IE8">

title

title元素定义文档的标题,显示在浏览器的标题栏或标签页上

# 作用: 必不可少
1、定义浏览器工具栏中的标题
2、提供页面被添加到收藏夹时显示的标题
3、显示在搜索引擎结果中的页面标题

base

base用于指定文档里所有相对URL地址的基础URL,为页面上所有链接规定默认地址和默认打开方式。
文档中的基础URL可以使用document.baseURI进行查询

<base href="https://baidu.com" target="_blank">
如果指定了多个<base>元素,只会使用第一个href和target值

link

link指定了外部资源与当前文档的关系,具有属性href、rel、media、hreflang、type和sizes。
其中href和rel是常用的,href指定了链接的资源的地址(url),而rel指定了资源的类型

  • ref 的属性值
alternate   指示链接到该文档的另一个版本
author      指示链接到当前文档的作者主页
help        指向一个跟网站或页面相关的帮助文档
icon        引入代表当前文档的图标,新的sized属性与这个属性结合使用,指定链接图片的宽高
license     链接到当前的文档的版权声明
next        指示链接到文档是一组文档中的下一份
pingback    处理当前文档被引用情况的服务器地址
prefetch    指明需要缓存的目标资源
prev        标明了上一个文档
search      链接到可以用于搜索当前页面和相关页面的资源
sidebar     链接到可以作为附属上下文的文档
stylesheet  引入外部样式表
tag         创建应用于当前文档的标签
  • media属性
screen      计算机屏幕
tty         终端
tv          电视
projection  投影仪
handheld    手持设备
print       打印的页面
braille     盲文设备
aural       语音合成器
all         所有 
  • sizes属性; 只有当被链接资源是图标时,才可使用该属性
<link rel="icon" href="demo.gif" type="image/gif" sizes="16x16" />  

style

style元素包含了文档的样式化信息或者文档的一部分,常用于引入内部CSS样式

<style>
  body{background-color: red;}
</style>

script

script元素的作用是在HTML或XHTML文档中嵌入或引用可执行的脚本。没有async或defer属性的脚本和内联脚本会在浏览器继续解析剩余文档前被获取并立刻执行

  • src属性
定义引用外部脚本的URI,这可以用来代替直接在文档中嵌入脚本。有src属性的script元素标签内不应该再有嵌入的脚本
  • type属性
该属性定义script元素包含或src引用的脚本语言。属性的值为MIME类型,支持的MIME类型包括text/javascript, text/ecmascript, application/javascript和application/ecmascript。如果没有定义这个属性,脚本会被视作JavaScript。如果MIME类型不是JavaScript类型(上述支持的类型),则该元素所包含的内容会被当作数据块而不会被浏览器执行

如果type属性为module,代码会被当作JavaScript模块
  • defer 属性
这个布尔属性定义该脚本是否会延迟到文档解析完毕后才执行
  • async 属性
async属性是HTML5新增的属性,IE9-浏览器不支持。该布尔属性指示浏览器是否在允许的情况下异步执行该脚本。该属性对于内联脚本无作用(即没有src属性的脚本)
  • script 中文件的加载
<script src="script.js"></script>

没有 defer 或 async, 当浏览器在解析HTML源文件时如果遇到外部的script,那么解析过程会暂停,并发送网络请求来下载script文件,只有script完全下载并执行后才会继续执行DOM解析

<script async src="script.js"></script>
有 async,网络加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
节省了网络下载文件的时间; 适合无依赖的脚本

<script defer src="myscript.js"></script>
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
注意多个defer脚本按其在HTML页面中的出现顺序依次执行

文档主体

body表示的是HTML文档的主体内容,任何一个HTML文档,只允许存在一个元素
body 的内容都是有标签元素组成的
内容参考上述手册

重点标签

  • a标签
    a元素 (或HTML锚元素, Anchor Element)通常用来表示一个锚点/链接
    可以链接到一个新文件、用id属性指向任何元素
# href 属性表示地址
链接地址       <a href="http://www.baidu.com">百度</a>
下载地址       <a href="test.zip">下载测试</a>
锚点          <a href="#top">返回顶部</a>  
              <a href="http://baidu.com/view/1111.html#2">足球比赛规则</a>
移动端手机号码   <a href="tel:15012345678>15012345678</a>可以唤出手机拨号盘    
href属性一定不要留空,若暂时不需要写地址,则写#或javascript:;。若href留空,会刷新页面

href与src的区别:
href(hypertext reference)指超文本引用,表示当前页面'引用'了外部的内容
src(source)表示来源地址,表示把外部的内容'引入'到当前页面

# target 属性表示链接打开方式
_self     当前窗口(默认)
_blank    新窗口
_parent   父框架集
_top      整个窗口
_framename 指定框架

# download 属性用来设置下载文件的名称
<a href="test.zip" download="下载的文件名">test</a>

# rel 属性表示链接间的关系
next        后一篇文档
prev        前一篇文档
noreferer   访问时链接时不发送referer字段
prefetch    预加载链接指向的页面(对于chrome使用prerender)

<a href="prev.html" rel="prev prefetch prerender">前一页</a>
<a href="next.html" rel="next prefetch prerender">后一页</a>
// 当一篇篇幅很长的文章需要多页显示时,配合next或prev可以实现前后页面导航的预加载
//当然prefetch也可以用于预加载其他类型的资源
<link rel="prefetch prerender" href="test.img">
  • 表单元素
# input
属性: accept、alt、checked、disabled、maxlength、name、readonly、size、src、type、value
h5新增属性: autocomplete、autofocus、form、formaction、formenctype、formmethod、formnovalidate、formtarget、height、list、max、min、multiple、novalidate、pattern、placeholder、required、step、width

// type 属性: 
text      定义单行的输入字段,用户可在其中输入文本
password   定义密码字段。该字段中的字符被掩码
file     定义输入字段和 "浏览"按钮,供文件上传
radio     定义单选按钮
checkbox   定义复选框
hidden    定义隐藏的输入字段
button   定义可点击按钮(多数情况下,用于通过JavaScript启动脚本)
image    定义图像形式的提交按钮
reset    定义重置按钮。重置按钮会清除表单中的所有数据
submit    定义提交按钮。提交按钮会把表单数据发送到服务器

type 新增属性: 
color       定义调色板
tel          定义包含电话号码的输入域
email        定义包含email地址的输入域
url          定义包含URL地址的输入域
search       定义搜索域
number       定义包含数值的输入域
range          定义包含一定范围内数字值的输入域
date        定义选取日、月、年的输入域
month         定义选取月、年的输入域
week       定义选取周、年的输入域
time            定义选取月、年的输入域
datetime        定义选取时间、日 月、年的输入域(UTC时间)
datatime-local    定义选取时间、日 月、年的输入域(本地时间
  • 富文本编辑
有两种编辑富文本的方式,一种是使用iframe元素,另一种是使用contenteditable属性

1.在页面中嵌入一个包含空HTML页面的iframe。通过设置designMode属性,这个空白的HTML页面可以被编辑,而编辑对象则是该页面<body>元素的HTML代码
<iframe name="wysiwyg" src="editor.html" style="height: 100px;width: 100px;"></iframe>    
<script>
window.onload = function(){
    frames['editor'].document.designMode = 'on';
}
</script>

2.把contenteditable属性应用给页面中的任何元素,然后用户立即就可以编辑该元素
富文本编辑器交互的主要方式,就是使用document.execCommand()
// 命令式交互
字体类型      document.execCommand('fontname',false,sFontName)

HTML 语法

  • 标签
HTML 标签分为单标签和双标签
单: <img><br><hr><input>
双: <div></div>
  • 属性
标签可以拥有属性,属性提供了有关HTML元素的更多信息
如 style, id, style, class
  • 元素
HTML元素以开始标签起始,以结束标签终止,元素的内容是开始标签与结束标签之间的内容
  • 注释
注释是在HTML插入的描述性文本,用来解释该代码或提示其他信息
<!-- This is a comment -->
  • 实体
HTML中某些字符是预留的,需要被替换为字符实体
&nbsp; &gt; 等...

结构规范

  • 结构
1、尽量遵循 HTML 标准和语义,但是不要以牺牲实用性为代价。任何时候都要尽量使用最少的标签并保持最小的复杂度

2、结构顺序和视觉顺序基本保持一致,按照从上至下、从左到右的视觉顺序书写HTML结构。有时为了便于搜索引擎抓取,也会将重要内容在HTML结构顺序上提前

3、结构、表现、行为三者分离,避免内联

4、每一个块级元素都另起一行,每一行都使用Tab缩进对齐(head和body的子元素不需要缩进)。删除冗余的行尾空格

5、可以在大的模块之间用空行隔开,使模块更清晰
  • 语义
浏览器会根据标签的语义给定一个默认的样式。判断网页标签语义化是否良好的一个简单方法:去掉样式,看网页结构是否组织良好有序,是否仍然有很好的可读性

1、尽可能少地使用无语义标签span和div

2、在语义不明显,既可以使用p也可以使用div的地方,尽量用p

3、在既可以使用div也可以使用section的地方,尽量用section

4、不要使用纯样式标签,如b、u等,而改用CSS设置

HTML标签嵌套规则

html5出现之前,经常把元素按照block、inline、inline-block来区分。在html5中,元素不再按照display属性来区分,而是按照内容模型来区分,分为元数据型(metadata content)、区块型(sectioning content)、标题型(heading content)、文档流型(flow content)、语句型(phrasing content)、内嵌型(embedded content)、交互型(interactive content)。元素不属于任何一个类别,被称为穿透的;元素可能属于不止一个类别,称为混合的。
img

  • 元数据元素(metadata content)是可以被用于说明其他内容的表现或行为,或者在当前文档和其他文档之间建立联系的元素
base link meta noscript script style template title
  • 流元素(flow content)是在应用程序和文档的主体部分中使用的大部分元素
a abbr address area(如果它是map元素的子元素) article aside audio b bdi bdo blockquote br button canvas cite code data datalist del dfn div dl em embed fieldset figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd keygen label main map mark math meter nav noscript object ol output p pre progress q ruby s samp script section select small span strong sub sup svg table template textarea time u ul var video wbr text
  • 区块型元素(sectioning content)是用于定义标题及页脚范围的元素
article aside nav section
  • 标题型元素(heading content)定义一个区块/章节的标题
h1 h2 h3 h4 h5 h6
  • 语句型元素(phrasing content)是用于标记段落级文本的元素
a abbr area (如果它是map元素的子级) audio b bdi bdo br button canvas cite code data datalist del dfn em embed i iframe img input ins kbd keygen label map mark math meter noscript object output progress q ruby s samp script select small span strong sub sup svg template textarea time u var video wbr text
  • 嵌入型元素(embedded content)是引用或插入到文档中其他资源的元素
audio canvas embed iframe img math object svg video
  • 交互型元素(interactive content)是专门用于与用户交互的元素
a audio(如果设置了controls属性) button embed iframe img(如果设置了usemap属性) input(如果type属性不为hidden) keygen label object(如果设置了usemap属性) select textarea video (如果设置了controls属性)

函数类型(高阶函数)

函数类型(高阶函数)

函数操作函数 => 就是高阶函数; 函数有时候我们理解为一系列规则的集合, 指令集.

# 通常为两种情况
1.函数作为参数
2.函数作为返回值

函数作为参数的场景

  • 回调函数callback
当把函数当作参数传递时, 我们可以抽离出一部分容易变化的业务逻辑(规则), 
把这部分业务逻辑放在函数参数中, 这样一来可以分离业务代码中变化与不变的部分,
做到规则的抽象与分离.

doSomething(cb) {
  // do something...
  // 不变的业务逻辑

  // then
  // 可变的业务逻辑, 可支持自定义
  cb()
}
# 典型的有 ajax 回调, 事件操作等

函数作为返回值的场景

  • 抽离重复功能
当把函数当返回值输出时, 意味着我们可以延续上一步的运算;
这样我们可以抽离出重复的的业务逻辑(规则), 做到简化代码的作用

let isString = function(obj) {
  return Object.prototype.toString.call(obj) === '[object String]';
};
let isArray = function( obj ){
  return Object.prototype.toString.call(obj) === '[object Array]';
};
let isNumber = function( obj ){
  return Object.prototype.toString.call(obj) === '[object Number]';
};
// 这些函数都有相同的部分, 那就是最后都调用Object.prototype.toString.call( obj )
// 我们抽离一下

let isType = function(type){ 
  return function(obj){
    return Object.prototype.toString.call(obj) === '[object '+ type +']';
  }
};
// 之后你可以isType('你想要的判断类型的规则')

高阶函数应用

对于 js 这类语言来说, 我其实比较喜欢且倾向函数式编程, 奈何功底有限,还是得慢慢修炼

AOP(面向切面编程)

把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块

在js中实现AOP,都是指把一个函数“动态织入”到另外一个函数之中

Function.prototype.before = function (beforefn) {
    var _this = this;    // 保存原函数的引用
    return function () {    // 返回包含了原函数和新函数的"代理"函数 
      beforefn.apply(this, arguments);    // 先执行新函数,修正this 
      return _this.apply(this, arguments);    // 再执行原函数
    }
  };
Function.prototype.after = function (afterfn) {
    var _this = this;
    return function () {
      var ret = _this.apply(this, arguments); //先执行原函数
      afterfn.apply(this, arguments); //再执行新函数
      return ret;
    }
  };
var func = function () {
    console.log(2);
  };
func = func.before(function () {
    console.log(1);
  }).after(function () {
    console.log(3);
  });

func(); // 1, 2, 3
# 把负责打印数字1和打印数字3的两个函数通过AOP的方式动态植入func函数

一些工具函数

  • 映射函数:返回的新函数将一个数组映射到另一个使用这个函数的数组上
function mapFunc(fn) {
  return function(arr) {
    return Array.prototype.map.call(arr, fn)
  }
}
let increase = item => item + 1
let increaseMap = mapFunc(increase)
let a = [1,2,3]
increaseMap(a) // [2,3,4]

函数柯里化(currying)

currying又称部分求值函数。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数(单个参数),刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值.

其主要用途在于参数复用。本质上是降低通用性,提高适用性。

  • 计算一周的消费,只需要在周末求和
# 理解: 每天调用该函数,保存累计值,最后一次调用时,求和输出; 我们可以判断参数
let everyWeekCost = (function() {
  // 闭包中用于保存数据的变量,本周每天的费用
  var feeArgs = []
  return function() {
    // 此函数才是最终执行的函数
    if (arguments.length === 0) {
      return feeArgs.reduce((prev, curr) => {
        return prev + curr
      }, 0)
    } else {
      Array.prototype.push.apply(feeArgs, arguments)
    }
  }
})()

everyWeekCost(1)
everyWeekCost(2)
everyWeekCost(3)
...
everyWeekCost(7)
everyWeekCost() // 1 + 2 + 3...+ 7的结果
  • 实现 add(1)(2)(3)() ==> 6
# 只要传了参数,就返回一个函数,并保存参数, 没有参数了就返回和值

function curryAdd() {
  let argsList = []
  // 返回一个闭包
  let closure = function () {
    // 本次调用传入的参数
    let args = [...arguments]
    if (args.length > 0) {
      // 保存参数
      argsList = argsList.concat(args)
      // 再次返回闭包,等待下次调用
      return closure
    }
    // 没有传递参数,执行累加
    return argsList.reduce((prev, cur) => prev + cur)
  }
  return closure
}

let add = curryAdd()
console.log(add(1)(2)(3)())
# 还有一种是利用函数对象的 toString 方法

基础数据类型的扩展

基础数据类型的扩展

总结(摘抄)一下关于基础数据类型的扩展, 即属性和方法...

String 扩展

Unicode表示法: 大括号包含表示Unicode字符(\u{0xXX}或\u{0XXX})
字符串遍历: 可通过for-of遍历字符串
字符串模板: 可单行可多行可插入变量的增强版字符串 ``
标签模板: 函数参数的特殊调用 console.log`123`
String.raw(): 返回把字符串所有变量替换且对斜杠进行转义的结果
String.fromCodePoint(): 返回码点对应字符
codePointAt(): 返回字符对应码点(String.fromCodePoint()的逆操作)
normalize(): 把字符的不同表示方法统一为同样形式,返回新字符串(Unicode正规化)
repeat(): 把字符串重复n次,返回新字符串
matchAll(): 返回正则表达式在字符串的所有匹配
includes(): 是否存在指定字符串
startsWith(): 是否存在字符串头部指定字符串
endsWith(): 是否存在字符串尾部指定字符串
padStart():把指定字符串填充到字符串头部,返回新字符串
padEnd():把指定字符串填充到字符串尾部,返回新字符串

Number 扩展

平时用的也不多...

二进制表示法:0b或0B开头表示二进制(0bXX或0BXX)
八进制表示法:0o或0O开头表示二进制(0oXX或0OXX)
Number.EPSILON:数值最小精度
Number.MIN_SAFE_INTEGER:最小安全数值(-2^53)
Number.MAX_SAFE_INTEGER:最大安全数值(2^53)
Number.parseInt():返回转换值的整数部分
Number.parseFloat():返回转换值的浮点数部分
Number.isFinite():是否为有限数值
Number.isNaN():是否为NaN
Number.isInteger():是否为整数
Number.isSafeInteger():是否在数值安全范围内
Math.trunc():返回数值整数部分
Math.sign():返回数值类型(正数1、负数-1、零0)
Math.cbrt():返回数值立方根
Math.clz32():返回数值的32位无符号整数形式
Math.imul():返回两个数值相乘
Math.fround():返回数值的32位单精度浮点数形式
Math.hypot():返回所有数值平方和的平方根
Math.expm1():返回e^n - 1
Math.log1p():返回1 + n的自然对数(Math.log(1 + n))
Math.log10():返回以10为底的n的对数
Math.log2():返回以2为底的n的对数
Math.sinh():返回n的双曲正弦
Math.cosh():返回n的双曲余弦
Math.tanh():返回n的双曲正切
Math.asinh():返回n的反双曲正弦
Math.acosh():返回n的反双曲余弦
Math.atanh():返回n的反双曲正切

Symbol类型

Symbol 值通过Symbol函数生成。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

Symbol函数前不能使用new命令,否则会报错;
由于 Symbol 值不是对象,所以不能添加属性。本质上,它是一种类似于字符串的数据类型;
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,方便区分
Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的
Symbol 值不能与其他类型的值进行运算,会报错
Symbol 值作为对象属性名时,不能用点运算符, 使用[]
在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中
Symbol 值作为属性名时,该属性还是公开属性,不是私有属性
Symbol 作为属性名时属性不会出现在for...in、for...of循环中,
也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
但是,它也不是私有属性,使用Object.getOwnPropertySymbols方法可以获取指定对象的所有 Symbol 属性名。
  • Symbol.for()
let a1 = Symbol.for('123');
let a2 = Symbol.for('123');

a1 === a2 // true

// Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。
// 它们的区别是,前者会被登记在全局环境中供搜索,后者不会
Symbol.keyFor(a1) // "123"
let c2 = Symbol("f");
Symbol.keyFor(c2) // undefined

vue同构

基于 Vue 的服务端渲染应用框架 Nuxt

  • 什么是服务器端渲染 (SSR: Server Side Rendering)?
    按照官方的说法, 传统的 SPA 应用是在浏览器上输出 Vue 组件,进行生成 DOM 和操作 DOM。服务端渲染应用则是在 node 服务器环境下将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序; 也称为同构,即b/c 端同时构建

  • 优点

SEO
首屏加载变快
  • 缺点
开发环境限制: 浏览器有些 api 在 node 环境下没有, 在 node 中产生副作用代码消耗不必要的内存, 规避单例对象
构建配置和部署: 需要 node 环境下部署
服务器负载增加: 运行 node.js 程序会占用更多的服务器内存, 消耗 CPU 资源

vue 官方自带的 SSR

原理: 通过 new Vue 将我们的模板的转化为 vdom 然后再转为 html字符串返回给浏览器进行解析渲染, 浏览器接管渲染出来的页面,包括事件处理,响应式等; client 和 server 端分开构建, 执行对应的方法和逻辑.
参见链接: https://ssr.vuejs.org/zh/
项目实例: https://github.com/appleguardu/vue-server-render

Nuxt.js

官方文档: https://www.nuxtjs.cn/guide

  • 项目生成
直接是用vue-cli 脚手架
vue init nuxt/starter project-name
  • 目录解释
|-- .nuxt                            // Nuxt自动生成,临时的用于编辑的文件,build
|-- assets                           // 用于组织未编译的静态资源入LESS、SASS 或 JavaScript
|-- components                       // 用于自己编写的Vue组件,比如滚动组件,分页组件
|-- layouts                          // 布局目录,用于组织应用的布局组件,不可更改。
|-- middleware                       // 用于存放中间件
|-- pages                            // 用于存放写的页面,我们主要的工作区域
|-- plugins                          // 用于存放JavaScript插件的地方
|-- static                           // 用于存放静态资源文件,比如图片
|-- store                            // 用于组织应用的Vuex 状态管理。
|-- .editorconfig                    // 开发工具格式配置
|-- .eslintrc.js                     // ESLint的配置文件,用于检查代码格式
|-- .gitignore                       // 配置git不上传的文件
|-- nuxt.config.json                 // 用于组织Nuxt.js应用的个性化配置,已覆盖默认配置
|-- package-lock.json                // npm自动生成,用于帮助package的统一性设置的,yarn也有相同的操作
|-- package-lock.json                // npm自动生成,用于帮助package的统一性设置的,yarn也有相同的操作
|-- package.json                     // npm包管理配置文件
  • 常用配置项
# ip,port, 在 package.json 中增加
"config": {
    "nuxt": {
      "host": "0.0.0.0",
      "port": "8888"
    }
  }
# nuxt.config.js
配置全局的 css
配置 webpack 打包等
  • 路由页面配置
// 页面组件
直接在 pages 目录中创建文件夹, nuxt会把文件夹映射生当前层级的路由
// 页面跳转
<nuxt-link :to="{name:'index'}">home</nuxt-link> 组件
// 路由传参
<nuxt-link :to="{name: 'news', params: {id:1}}">新闻事件</nuxt-link>
// 动态路由: 可以理解为详情页
目录下创建一个_id.vue 文件
// 动态参数校验: 不存在则返回错误页
export default {
  validate({ params }) {
    return /^\d+$/.test(params.id)
  }
}
  • 页面过渡效果
.test-enter-active, .test-leave-active {
  transition: opacity .5s;
}
.test-enter, .test-leave-active {
  opacity: 0;
}
页面组件中的 transition 属性的值设置为 test 即可:
export default {
  transition: 'test'
}
全局的直接设置为 page-enter-active...
  • 默认模板和布局
// 新建一个 app.html 模板
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
  <head {{ HEAD_ATTRS }}>
    {{ HEAD }} 读取的是nuxt.config.js里的信息
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }} 是我们写的pages文件夹下的主体页面
  </body>
</html>

// layouts/default.vue 默认布局
// layouts/blog.vue 自定义布局, 页面组件中通过 layout 属性设置
  • 个性化 meta 设置
<script>
export default {
  validate({ params }) {
    return /^\d+$/.test(params.id)
  },
  data() {
    return {
      title: this.$route.params.title
    }
  },
  // 设置独立的 head
  head() {
    return {
      title: this.title,
      meta: [
        {
          hid: 'description',
          name: 'news',
          content: 'this is content'
        }
      ]
    }
  }
}
</script>
  • 获取异步数据
asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。
export default {
  async asyncData ({ params }) {
    const { data } = await axios.get(`https://my-api/posts/${params.id}`)
    return { title: data.title }
  }
}

js 基础语法

基础语法

介绍

动态: 在运行期间才去做数据类型检查
弱类型: 赋值时可以修改数据类型
解释型语言: 不需要进行编译可以直接运行
运行在浏览器端, 当然现在也可以运行在服务端的 node 环境下

  • 组成
javascript由三部分组成:ECMAScript、DOM和BOM

[1]ECMAScript由ECMA-262定义,提供核心语言功能(ECMA是欧洲计算机制造商协会)
[2]DOM(Document Object Model)文档对象模型,提供访问和操作网页内容的方法和接口
[3]BOM(Browser Object Model)浏览器对象模型,提供与浏览器交互的方法和接口
  • 作用
js 用来增强页面动态效果,实现页面与用户之间的实时、动态交互

基础

标识符&变量

标识符: 就是一个名字,用来对变量、函数、属性、参数进行命名,或者用做某些循环语句中的跳转位置的标记

// 变量
var Identifier = 123;
// 属性
(new Object).Identifier = 'test';
// 函数及参数
function IdentifierName(Identifier1){};
...

变量(variable)是一个用于保存值的占位符,可以通过变量名称来获得对值的引用
推荐命名方式: 变量的命名要使用名词,而函数应该是动词+名称的形式

  • 变量声明
# 声明
使用一个变量之前应当先声明(declare),变量是使用关键字var(variable的缩写)来声明的
var v;

# 赋值
把值存入变量的操作称为赋值(assignment)。一个变量被赋值以后,我们就说该变量包含这个值
给变量第一次赋值的过程,叫初始化
var v = 'hello world'
未在var声明语句中给变量指定初始值时, 其值为 undefined

# 重复声明
使用var语句重复声明变量是合法且无害的,如果重复声明且带有赋值操作,相当于重新赋值
严格模式下使用未声明确赋值了的变量会报错
  • 变量作用域
变量的作用域(scope)是程序源代码中定义这个变量的区域; 分为全局和局部(函数)

全局作用域: 是最外围的一个执行环境,在web浏览器中,全局执行环境被认为是window对象。所有全局变量和函数都是作为window对象的属性和方法创建的。全局变量拥有全局作用域,在javascript代码中的任何地方都是有定义的。全局作用域直到应用程序退出例如关闭网页或浏览器时才会被销毁

局部作用域: 在函数内声明的变量只在函数体内有定义。它们是局部变量,作用域是局部性的。函数参数也是局部变量,它们只在函数体内有定义。函数作用域中的所有代码执行完毕后,该作用域被销毁,保存在其中的所有变量和函数定义也随之销毁
  • 变量声明提升
全局作用域下的变量也会提升到顶部
JS函数里声明的所有变量(不涉及赋值)都被提前到函数体的顶部
var scope = 'global';
function f(){
    console.log(scope);// undefined
    var scope = 'local';
    console.log(scope);// 'local'
}
f()

表达式

js 表达式有操作符合语句组成; 分为原始表达式和复杂表达式

原始表达式

原始表达式是表达式的最小单位, 分为字面量、关键字和变量;详细来说包括this关键字、标识符引用、字面量引用、数组初始化、对象初始化和分组表达式

  • this关键字和标识符
this;// 返回当前对象
i;   // 返回变量i的值
  • 字面量
字面量(literal),又翻译成直接量,就是程序中直接使用的数据值, 包括:
null;
undefined;
true;
false;
1;
'abc';
/pattern/;
  • 数组和对象初始化
数组初始化和对象初始化实际上是一个以字面量的方式描述的初始化的过程。这两个初始化表达式有时称做"对象字面量"和"数组字面量"
[];
[1,2,3];
{};
{a:1};
  • 分组表达式
分组表达式实际上就是括号,用于重写运算符的优先级

复杂表达式

复杂表达式由原始表达式和操作符(operator)组合而成,包括属性访问表达式、对象创建表达式和函数表达式

  • 属性访问表达式
属性访问表达式运算可以得到一个对象属性或一个数组元素的值; 点和方括号.
var o = {x:1,y:{z:3}}; // 对象字面量
var a = [o,4,[5,6]];   // 包含对象的数组字面量
o.x;                   // 表达式o的x属性
o.y.z;                 // 表达式o.y的z属性
o['x'];                // 对象o的x属性
a[1];                  // 表达式a中索引为1的元素
注意: 
在'.'和'['之前的表达式总是会首先计算;
1. 如果计算结果是null或undefined,表达式会抛出一个类型错误异常,因为这两个值都不能包含任意属性
2.如果计算结果不是对象,javascript会将其转换为对象. 
3.如果对象表达式后跟随句点和标识符,则会查找由这个标识符指定的属性值,并将其作为整个表达式的值返回
4.如果对象表达式后跟随一对方括号,则会计算方括号内的表达式的值并将其转换为字符串

不论哪种情况,如果命名的属性不存在,那么整个属性访问表达式的值就是undefined
  • 对象创建表达式
    对象创建表达式创建一个对象并调用一个函数初始化新对象的属性
    new Object()
    Object 构造函数为给定值创建一个对象包装器。如果给定值是 null 或 undefined,将会创建并返回一个空对象,否则,将返回一个与给定值对应类型的对象。

function Person(name) {
this.name = name
}
new Person('huhua') // 返回一个Person实例对象

  • 函数表达式
函数表达式分为函数定义表达式和函数调用表达式

函数定义: 
function square(x){
    return x*x;
}
var square = function(x){return x*x;}
函数调用:
square(1)

严格模式

为了消除js语法的一些不合理、不严谨、不安全问题,减少怪异行为并保证代码运行安全
提高编译器效率,增加运行速度, 因此引入了严格模式

  • 使用
[1]整个脚本启用严格模式,在顶部执行:"use strict";
[2]在指定函数中执行严格模式,在函数体第一行:"use strict"
[3]不支持strict模式的浏览器把"use strict"当做一个字符串语句执行,支持strict模式的浏览器将开启strict模式
[4]支持严格模式的浏览器包括IE10+、Firefox4+、safari12+、opera12+、chrome
  • 相关规则
# 变量
1.不允许意外创建全局变量
"use strict";
a = 'hello world!';

2.不能对变量调用delete操作符
var color = 'red';
delete color;

3.不能为只读属性赋值
Object.defineProperty(person,'name',{
    writable: false
});
person 的 name 属性无法赋值

4.不能为不可配置的属性使用delete操作
5.函数参数必须唯一
6.修改函数的形参不会反映到arguments中
function showValue(value){
    value = "Foo";
    alert(arguments[0]);
    // 非严格模式:"Foo"
    // 严格模式:"Hi"
}
showValue("Hi");

7.不允许this值为null或undefined
全局下 this 为 undefined

8.不允许使用with语句

垃圾回收

垃圾回收机制的原理:找出那些不再继续使用的变量,然后释放其占用的内存,垃圾收集器会按照固定的时间间隔,或代码执行中预定的收集时间,周期性地执行这一操作

局部变量只在函数执行的过程中存在。而在这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。然后在函数中使用这些变量,直到函数执行结束。此时,局部变量就没有存在的必要了。因此可以释放它们的内存以供将来使用。在这种情况下,很容易判断变量是否还有存在的必要;但并非所有情况下都这么容易就能得出结论

垃圾收集器必须跟踪哪个变量有用哪个变量无用,对于不再有用的变量打上标记,以备将来收回其所占用的内存。用于标识无用变量的策略通常有标记清除和引用计数两种

  • 程序内存管理
使用具备垃圾收集机制的javascript的主要问题在于:分配给web浏览器的可用内存数量通常要比分配给桌面应用程序的少,目的是防止运行javascript的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量.

因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式是:为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用,这种做法叫解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性,局部变量会在它们离开执行环境时自动被解除引用

运算符

分类

  • 按操作数
# 一元运算符, 主要包括以下9个:
++ -- - + ~ ! delete typeof void

# 二元运算符, 它们的操作数都是两个,
如 1 + 1, 1 && 2

# 三元运算符, 条件判断运算符 ?:
2 > 1 ? 2 : 1;
  • 按优先级, 从高到低依次是
1  ++ -- - + ~ ! delete typeof void
2  * / %
3  + -
4  << >> >>>
5  < <= > >= instanceof in
6  == != === !==
7  &
8  ^
9  |
10 &&
11 ||
12 ?:
13 = *= /= %= += -= &= ^= |= <<= >>= >>>=
14 ,

一元运算符 > 算术运算符 > 比较运算符 > 逻辑运算符 > 三元运算符 > 赋值运算符 > 逗号运算符

算术运算符

算术运算符包括一元算术运算符和二元算术运算符两种

一元算术运算符

一元算术运算符用于一个单独的操作数,并产生一个新值。在javascript中,一元运算符具有很高的优先级,而且都是右结合(right-associative)

一元算术运算符包括一元加法(+)、一元减法(-)、递增(++)和递减(--)

  • 一元加(+)
在对非数值应用一元加运算符时,会调用Number()转型函数对这个值进行转换
var s1 = '01';
var s2 = '1.1';
var s3 = 'z';
var b = false;
var f = 1.1;
var o = {
    valueOf:function(){
      return -1;
    }
};
s1 = +s1;  // 1
s2 = +s2;  // 1.1
s3 = +s3;  // NaN
b = +b;    // 0
f = +f;    // 1.1
o = +o;    // -1

+new Date() // 1582719041912
在new Date()前面使用一元加符号,可以把日期字符串,转换为日期毫秒数
  • 一元减(-) 一元减运算符主要用于表示负数
一元加和一元减运算符主要用于基本的算术运算,也可以用于转换数据类型
var o = {
    valueOf:function(){
      return -1;
    }
};
o = -o;    // 1
  • 递增(++)
递增++运算符对其操作数进行增量(加1)操作,操作数是一个左值(lvalue)(变量、数组元素或对象属性)。运算符通过Number()转型函数将操作数转换为数字,然后给数字加1,并将加1后的数值重新赋值给变量、数字元素或者对象属性

var i = 1, j = ++i; // i=2 j=2
var i = 1, j = i++; // i=2 j=1
  • 递减(--)
递减--运算符的操作数也是一个左值,它通过Number()转型函数把操作数转换为数字,然后减1,并将计算后的值重新赋值给操作数

运算符的返回值依赖于它相对操作数的位置
var i = 1, j = --i; // i=0 j=0
var i = 1, j = i--; // i=0 j=1

二元算术运算符

  • 加法(+)
    加法运算有大量的特殊行为,不仅可以进行数值加法运算,也可以进行字符串连接
    加法运算遵循如下规则:
1.如果其中一个操作数是对象,则对象会转换为原始值:日期对象通过toString()方法执行转换,其他对象通过valueOf()方法执行转换。由于多数对象valueOf()方法无法返回一个原始值,于是会通过toString()方法来执行转换

注意: 除了单数值数组会转换为数字外,其他原生对象都会通过toString()方法转换为字符串形式

2.在进行了对象到原始值的转换后,如果其中一个操作数是字符串的话,另一个操作数也会转换成字符串,进行字符串连接

3.否则,两个操作数都将转换成数字或NaN,进行加法操作

// 单数值数组和valueOf()返回值为数值的自定义对象会转换为数值
console.log(1 + []); // 1
var o = {
    valueOf: function(){
        return -1;
    }
}
console.log(1 + o); // 0

// 其他原生对象则转换为字符串
console.log(1 + {});//'1[object Object]'
console.log(1 + [1,2]);//'11,2'
console.log(1 + new Date());//'1Thu Jun 16 2016 10:27:13 GMT+0800 (**标准时间)'
console.log(1 + /0/);//'1/0/'

// 如果进行算术加法运算,undefined转换为NaN,null转换为0,false转换为0,true转换为1

// 如果进行字符串连接,undefined转换为'undefined',null转换为'null',false转换为'false',true转换为'true'
  • 减法(-)
    相对于加法,减法就简单的多,只涉及到数字的减法运算。使用Number()转型函数将非数值类型转换为数值或NaN
console.log(1 - {});//NaN
console.log(1 - [1,2]);//NaN
console.log(1 - /0/);//NaN
console.log(1 - []);//1
  • 乘法()
    乘法操作符由一个星号(
    )表示,用于计算两个数值的乘积,会通过Number()转型函数将非数值类型转换为数值或NaN

  • 除法(/)
    除法操作符由一个斜线(/)表示,执行第一个操作数除以第二个操作数的运算,也会通过Number()转型函数将非数值类型转换为数值或NaN

  • 求模(%)
    求模(余数)操作符是由一个百分号(%)表示,是第一个操作数除以第二个操作数的余数

关系运算符

===、!==、==、!=、<、<=、>、>= 8个关系运算符

不恒等运算符下的 两个操作数比较存在隐式转换

以 == 为例, 当两个操作数类型不同时:

1.如果一个值是对象类型,另一值是原始类型,则对象类型会先使用valueOf()转换成原始值,如果结果还不是原始值,则再使用toString()方法转换,再进行比较

2.在对象转换为原始值之后,如果两个操作数都是字符串,则进行字符串的比较

3.在对象转换为原始值之后,如果至少有一个操作数不是字符串,则两个操作数都将通过Number()转型函数转换成数字进行数值比较

逻辑运算符

  • 逻辑非
逻辑非对操作数转为布尔类型的转换类型与Boolean()转型函数相同,只不过最后再将其结果取反。

// 7 个假值
console.log(!!undefined);  //false
console.log(!!null);       //false
console.log(!!0);          //false
console.log(!!-0);         //false
console.log(!!NaN);        //false
console.log(!!'');         //false
console.log(!!false);      //false
  • 逻辑与
逻辑与运算符由两个和号(&&)表示,有两个操作数,只有在两个操作数都为true时,结果才返回true,否则返回false


// 利用短路运算特性
1.可以使用逻辑与运算符来取代if结构
(a == b) && doSomething();

2.逻辑与运算符常常用于回调函数使用中
function fn(a){
    a && a();
}
  • 逻辑或
逻辑或运算符由两个竖线(||)表示,有两个操作数,只有在两个操作数都是false时,结果才返回false,否则返回true

1.逻辑或运算符常用于为变量设置默认值
function fn(p){
    p = p || {};
}

位运算符

  • 按位非(~)
按位非操作符由一个波浪线(~)表示,执行按位非的结果就是返回数值的反码。其本质是操作数的负值减1
var num1 = 25;
var num2 = ~num1;
console.log(num2);//-26

对一个整数两次按位非,可以得到它本身;对一个小数两次按位非,可以得到取整效果
console.log(~~3);//3
console.log(~~3.1);//3
  • 按位与(&) 按位或(|)
按位与操作符由一个和号符号(&)表示,它有两个操作符数。从本质上讲,按位与操作就是将两个数值的每一位对齐,然后根据下表中的规则,对相同位置上的两个数执行AND操作

| 第一个数值的位 | 第二个数值的位 | 结果 |
| :------------- | :------------- | :--- |
| 1              | 1              | 1    |
| 1              | 0              | 0    |
| 0              | 1              | 0    |
| 0              | 0              | 0    |

按位或(|)规则与上相反

25 = 0000 0000 0000 0000 0000 0000 0001 1001
 3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
 & = 0000 0000 0000 0000 0000 0000 0000 0001 // 1
 | = 0000 0000 0000 0000 0000 0000 0001 1011 // 27

一个整数与0按位或运算可以得到它本身,一个小数与0按位或运算可以得到取整效果
console.log(3.1 | 0);//3
console.log(3.9 | 0);//3

其他运算符

逗号运算符

逗号运算符是二元运算符,它的操作数可以是任意类型。它首先计算左操作数,然后计算右操作数,最后返回右操作数的值,用逗号运算符可以在一条语句中执行多个运算

逗号运算符最常用的场景是在for循环中,这个for循环通常具有多个循环变量
//for循环中的第一个逗号是var语句的一部分
//第二个逗号是逗号运算符
//它将两个表达式(i++和j--)放在一条语句中
for(var i=0, j=10;i<j;i++,j--){console.log(i+j);}

逗号运算符还可以用于赋值,在用于赋值时,逗号运算符总是返回表达式中的最后一项
var num = (1,2,3,4,5);
console.log(num);//5

void运算符

void是一元运算符,它出现在操作数之前,操作数可以是任意类型,操作数会照常计算,但忽略计算结果并返回undefined。由于void会忽略操作数的值,因此在操作数具有副作用的时候使用void来让程序更具语义

1.替代undefined
由于undefined并不是一个关键字,其在IE8-浏览器中会被重写,在高版本函数作用域中也会被重写;所以可以用void 0 来替换undefined
function t(){
var undefined = 10;
console.log(undefined);
}
t() // 10

语句

javascript程序无非就是一系列可执行语句的集合,javascript解释器依照语句的编写顺序依次执行。

  • 表达式语句
赋值、delete、函数调用这三类即是表达式,又是语句,所以叫做表达式语句
  • 声明语句
声明语句包括变量声明和函数声明,分别使用var和function关键字

函数声明语句和函数定义表达式包含相同的函数名,但二者有所不同
// 变量声明语句
var f = function(x){return x+1;}
// 声明式语句
function f(x){return x+1;}

函数定义表达式只有变量声明提前了,变量的初始化代码仍然在原来的位置;
而函数声明语句的函数名称和函数体均提前,脚本中的所有函数和函数中所有嵌套的函数都会在当前上下文中其他代码之前声明,也就是说,可以在声明一个javascript函数之前调用它

// 这种只会提升变量, 函数体不会
console.log(f1(0));//Uncaught TypeError: f1 is not a function
var f1 = function(x){return x+1;}

console.log(f2(0));//1
function f2(x){return x+1;}
  • 条件语句
if 语句;

switch (express)
  // case的值不一定是常量,可以是变量或表达式
  case value || express1:
    doSomething
    break;
  default:
  • 循环语句
while(){}, do{}while(), for(){}
  • 跳转语句
break; 跳出循环

continue
for(i = 0; i < 5; i++){
    if(i % 2 === 0)continue;
    console.log(i); // 1, 3
}

return 函数中的返回值, 函数终止执行
  • eval语句
eval()是一个全局函数,javascript通过eval()来解释运行由javascript源代码组成的字符串
eval()只有一个参数
如果传入的参数不是字符串,它直接返回这个参数。
如果参数是字符串,它会把字符串当成javascript代码进行编译。
如果编译失败则抛出一个语法错误(syntaxError)异常。
如果编译成功,则开始执行这段代码,并返回字符串中的最后一个表达式或语句的值,如果最后一个表达式或语句没有值,则最终返回undefined

eval()使用了调用它的变量作用域环境
  • with
定义with语句的目的主要是为了简化多次编写同一对象的工作
with语句将object添加到作用域链的头部,然后执行statement,最后把作用域链恢复到原始状态

with(object){
  statement;
}
在对象嵌套层次很深的时候通常会使用with语句来简化代码编写。而本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作作用域中的标识符来处理,从而创建了一个新的词法作用域
with语句提供了一种读取对象的属性的快捷方式,但它并不能创建对象的属性

浏览器之JS引擎工作机制&事件循环

浏览器之JS引擎工作机制,事件循环

在解析 HTML 构建 DOM 树时, 渲染线程会解析到script标签, 则会将执行权交给 js 线程(引擎)来接管, 此时 js 引擎开始干活了,那么他到底是怎么个干法呢? 这里其实在作用域那一块已经提到了, 那里是以es3 的规范为基础说的, 这里我们按es6的新规范来理解...

js 引擎结构

内存堆heap: 存储引用类型数据
调用栈call stack: 存储基础数据类型, 引用类型地址, 存放执行上下文(运行时)
解释器: 对源代码进行解释,编译,执行等

js引擎(V8)执行代码

  • 1.源码转换为抽象语法树, 并生成执行上下文
# 步骤(不做细讲)
阶段一: 分词, 即词法分析
阶段二: 解析, 语法分析
阶段三: 生成 AST
  • 2.生成字节码
解释器参与工作, 根据 AST 生成字节码
字节码: 介于字节码 和 机器码之间的代码
  • 3.执行代码
解释器会逐行解释和执行字节码; 这里有个优化就是对于多次执行的代码(热点代码),
后台编译器(JIT)将会把这些字节码转换为机器码

执行上下文

当浏览器加载script的时候, js引擎开始工作;
此时会默认直接进入Global(全局上下文),将全局上下文push到引擎的调用栈call Stack;
如果在代码中调用了函数,则会创建Function(函数上下文)并压入调用栈内,变成当前的执行环境上下文;
当执行完该函数,该函数的执行上下文便从调用栈弹出返回到上一个执行上下文.

  • 执行上下文分类
全局: 当js文件加载进浏览器运行的时候,进入的就是全局执行上下文.
     全局变量都是在这个执行上下文中,代码在任何位置都能访问.

函数: 定义在具体某个方法中的上下文,函数调用时就进入函数上下文.
     局部变量就是在这个函数中访问

Eval: 定义在Eval方法中的上下文.

es3 的执行上下文

  • 准备阶段(预解析)
executionContext = {
  // 变量对象
  'variableObject': {
    'arguments声明': {
      length: 0
    },
    'function声明': fn ,
    'var声明': undefined
  },
  // 作用域链
  'scopeChain': [
    '自身的variableObject', ...'所有父级的executionContext' 
  ],
  // this 对象
  'this': {}
}
// 初始化 arguments参数, 变量为 undefined, fn变量指向堆内存
  • 执行阶段
为上述arguments参数, 变量等赋值, 运行代码
  • 示例
var a = 2
function addAll(x,y) {
  var b = 1;
  console.log(add) // ƒ add(x,y) { return x + y }; 如果没有 add 函数声明,则是 undefined
  function add(x,y) {
    return x + y
  }
  var add = function(x,y) {
    return x + y
  }
  console.log(add) // ƒ (x,y) { return x + y }
  var result = add(x, y)
  return a + b + result
}
addAll(100, 100)

es6 的执行上下文

可以看到es3中, js会存在变量提升以及隐式覆盖,for循环的i在全局等问题; 所以在 es6新规范 中引入了块级作用域和 let,const 关键字来规避之前的一些缺陷; 由于 js 需要向下兼容,所以还是会保留变量提升的特性

  • 1,准备阶段(创建执行上下文)
executionContext = {
  // 词法环境
  blockContext: {
    'let,const 声明': 'xxx'
  },
  // 变量环境
  VO: { 
    /* var 等声明 */ 
  },
  // 外部引用(同作用域链)
  outer: [
    // ...
  ]
  // this
  this: {}
}
  • 2.执行阶段
变量查找,赋值,执行
  • 示例
function fn() {
  var a = 1
  // console.log(b) // Cannot access 'b' before initialization
  let b
  // 遇到let,const 声明, b会被提升到当前块级作用域顶部,
  // 从顶部到声明处是暂时性死区, 无法操作b, 会报错
  // 直接在解析词法环境下就报错了
  {
    let b = 3
    var c = 4
    console.log(a)
    console.log(b)
  }
  console.log(b)
  console.log(c)
}
fn() // a,b,c,d => 11, 3, undefined, 4

示例的上下文

变量环境 词法环境
a = 1,c=4 块1: b = 3
块2: b = undefined

异步的JS

通过上面我们知道 JS 通过创建调用栈执行上下文环境来执行一段 js 代码; 由于 js 设计的是单线程(因为JS主要目的用来实现很多交互相关的操作,如DOM相关操作,如果是多线程会造成数据的同步问题),只能从上往下执行单个任务,讲道理遇到耗时任务就会阻塞了.那怎么呢?
js 通过回调函数来处理这种耗时(异步)任务;JS引擎其实并不提供异步的支持,异步支持主要依赖于运行环境(浏览器或Node.js)

浏览器下事件循环(node下的不一样)

当遇到耗时任务(Web APIs的调用)时, 浏览器进程其实会维护一个任务(回调)队列 存放 Web APIs任务的回调函数;
另而JS引擎则会不断监听其主线程调用栈是否为空, 等到执行调用栈空了之后, 就会取出任务队列中的回调函数放到执行栈进行调用; 这种机制就是事件循环...

任务(回调)队列分类

上面任务(回调)队列里面其实存放的任务都成为宏任务, 这类任务会通过回调函数的方式滞后执行;
浏览器中的宏任务包括哪些呢:

渲染事件: DOM 解析, 布局, 绘制...(requestAnimationFrame)
用户交互事件: 点击,缩放,滚动等...click事件 等
script下JS执行事件: js执行
网络请求,文件读取(I/O): ajax请求
定时器: setTimeout....

在宏任务中对时间的控制粒度都比较宽泛, 像页面的渲染事件、各种 IO 的完成事件、执行 JavaScript 脚本的事件、用户交互的事件等都随时有可能被添加到消息队列中,而且添加事件是由系统操作的,JavaScript 代码不能准确掌控任务要添加到队列中的位置,控制不了任务在消息队列中的位置,所以很难控制开始执行任务的时间.

<!DOCTYPE html>
<html>
    <body>
        <div id='demo'>
            <ol>
                <li>test</li>
            </ol>
        </div>
    </body>
    <script type="text/javascript">
        function timerCallback2(){
          console.log(2)
        }
        function timerCallback(){
            console.log(1)
            setTimeout(timerCallback2,0)
        }
        setTimeout(timerCallback,0)
    </script>
</html>
<!-- 
  setTimeout 函数触发的回调函数都是宏任务; 在这期间,可能会插入其他系统任务
  则会影响到第二个定时器的执行时间, 所以不够精确, 实时性太差
-->

为了解决这类问题, 于是引入了微任务;
微任务就是一个需要异步执行的函数, 执行时机是在主函数执行结束之后、当前宏任务结束之前.

当 JavaScript 执行一段脚本的时候,V8 会为其创建一个全局执行上下文,在创建全局执行上下文的同时,V8 引擎也会在内部创建一个微任务队列。顾名思义,这个微任务队列就是用来存放微任务的,因为在当前宏任务执行的过程中,有时候会产生多个微任务,这时候就需要使用这个微任务队列来保存这些微任务了。不过这个微任务队列是给 V8 引擎内部使用的,所以你是无法通过 JavaScript 直接访问的。

  • 在浏览器里面,产生微任务有两种方式
第一种方式是使用 MutationObserver 监控某个 DOM 节点,
然后再通过 JavaScript 来修改这个节点,或者为这个节点添加、删除部分子节点,
当 DOM 节点发生变化时,就会产生 DOM 变化记录的微任务。

第二种方式是使用 Promise,当调用 Promise.resolve() 或者 Promise.reject() 
的时候,也会产生微任务
  • 小结
1.微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列;

2.微任务的执行时长会影响到当前宏任务的时长(多个微任务时长累加);

3.在一个宏任务中,如果创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行。

任务执行顺序

举个例子, 看一下宏任务和微任务的执行顺序...

setTimeout(function() {
	console.log('a')
});

new Promise(function(resolve) {
	console.log('b')
	for (var i = 0; i <1000; i++) {
		i === 1000 && resolve()
	}
}).then(function() {
	console.log('c')
});

console.log('d');

// 1.首先执行script下的宏任务,遇到setTimeout,将其放入宏任务的队列里

// 2.遇到Promise,new Promise直接执行,打印b

// 3.遇到then方法,是微任务,将其放到微任务的队列里

// 4.遇到console.log('d'),直接打印

// 5.本轮宏任务执行完毕,查看微任务,发现then方法里的函数,打印c

// 6.本轮event loop全部完成。

// 7.下一轮循环,先执行宏任务,发现宏任务队列中有一个setTimeout,打印a

对象类型(属性操作)

对象类型(属性操作)

对象作为一种数据类型,也可说一种数据的容器,通常都对应有增删改查的操作;这些属性操作可以分为属性查询、属性设置、属性删除,还包括属性继承等...

属性查询,访问

属性查询一般有两种方法,包括点运算符. 和 方括号运算符[], 当然还有一种 可选链操作?.也可以当 做属性访问的一种方式

  • 点运算符
通过点运算符访问对象的属性时,属性名用一个标识符来表示,标识符要符合`变量命名规则`。

var o = {
  a: 1,
  1: 2
}
o.a // 1
o.1 // 报错, 使用 o['1']
o.b // undefined
  • []运算符
通过方括号运算符[]来访问对象的属性时,属性名通过字符串来表示。
字符串是js的数据类型,在程序运行中可以修改和创建它们

// []通过变量来访问属性
var a = 1;
var o = {
  1: 10
}
o[a]; // 10

// []中的属性名称可以为js无效标识符,如关键字
var o = {
  11: 'ss'
  var: 111,
  this: 222
}
方括号中的值若是非字符串类型会使用String()隐式转换成字符串再输出;
如果是字符串类型,若有引号则原值输出;
否则会被识别为变量,若变量未定义,则报错
o[11] -> o['11']
o[var] var即变量,报错; o['var']

// []可计算属性名, 在方括号运算符内部可以使用表达式
var a = 1;
var person = {
    3: 'abc'
};
person[a + 2]; // 'abc'

// ES6写法
var person = {
    [a + 3]: 'abc'
};
person[4];// 'abc'
  • 属性查询错误
    查询一个不存在的属性不会报错,而是返回undefined
var person = {}
person.a // undefined

通常检查一个全局变量是否被声明,可以之间检测 window.xxx;
所有全局变量都是window对象的属性。window.a的含义就是读取window对象的a属性,
如果该属性不存在,就返回undefined,并不会报错
if (window.a) {...} // 不报错
  • ?.可选链操作符

?.能够去读取一个被连接对象(嵌套对象)的深层次的属性的值而无需明确校验链条上每一个引用的有效性

?.运算符功能类似于.运算符,不同之处在于如果链条上的一个引用是nullish (null 或 undefined),.操作符会引起一个错误,?.操作符取而代之的是会按照短路计算的方式返回一个undefined。当?.操作符用于函数调用时,如果该函数不存在也将会返回undefined; 暂时存在兼容性问题; 存在可选属性的场景的可以考虑使用

let nestedProp = obj.first && obj.first.second
在访问obj.first.second之前,obj.first 的值要被确认非null(且不是undefined)。
目的是为了防止错误发生,如果简单直接的访问obj.first.second而不对obj.first 进行校验有可能产生错误。

可选链操作符 (?.),在访问obj.first.second:之前,
你将不需要明确的校验和短路计算obj.first的状态
let nestedProp = obj.first?.second

const o = { name:'huhua', age: 26, ability: { js: 1, vue: 1, ts: 1 } }
let name = o.name // huhua
let js = o.ability?.js // 1
let react = o.ability?.react // undefined
let other2 = o.otherObj && o.otherObj.other // 通常我们是这样写的
let other1 = o.otherObj?.other // undefined // 访问嵌套对象这样写
等价于 ==>
let temp = o.otherObj; // 创建一个临时变量
let other1 = ((temp === null || temp === undefined) ? undefined : temp.other);
使用?.可以节省一个临时变量的开销
  • ?.与函数调用

当尝试调用一个可能不存在的方法时也可以使用可选链

函数调用时如果被调用的方法不存在,使用可选链可以使表达式
自动返回undefined, 而不是抛出一个异常。
let o = {
  hello() {},
  say() {}
}
o?.sit() // 默认返回 undefined
如果存在一个属性名且不是函数, 使用 ?.还是会报错

场景: 处理可选的callbacks或事件handlers
如果你使用解构赋值来解构的一个对象的callbacks或fetch方法,
你可能得到不能当做函数直接调用的不存在的值,除非你已经校验了他们的存在性。
使用?.的你可以忽略这些额外的校验

function doSomething(onContent, onError) {
  try {
   // ... do something with the data
  }
  catch (err) {
    onError?.(err.message); // 如果onError是undefined也不会有异常
  }
}

属性设置

属性设置又称为属性赋值,与属性查询相同,具有点运算符和方括号运算符这两种方法

当然, 在给对象设置属性之前,一般要先检测对象是否存在
let o = {}
o.num = 1
o.o.num = 2 // 报错
var num = o.o?.num

属性删除

delete运算符可以删除对象属性(包括数组元素), delete操作符的返回值是个布尔值true或false

# delete 对象属性
var o = {
  a : 1
};
console.log(o.a);//1
console.log('a' in o);   // true
console.log(delete o.a); // true
console.log(o.a);        // undefined
console.log('a' in o);   // false

# 使用delete删除数组元素时,不会改变数组长度
var a = [1,2,3];
delete a[2];
2 in a;       // false
a.length;     // 3

delete运算符只能删除自有属性,不能删除继承属性
要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象

var o  = {
    a:1
}
var newO = Object.create(o); // 设置原型为 o, 继承其属性
newO.a = 2;
console.log(newO.a);        // 2
console.log(delete newO.a); // true
console.log(newO.a);        // 1
  • delete的返回值
# 删除对象属性或数组元素删除成功时,返回true
var o = {a:1};
var arr = [1];
console.log(delete o.a);   // true
console.log(delete arr[0]);// true

# 删除不存在的属性或非左值(表达式只能出现在运算符的左侧)时,返回true
var o = {};
console.log(delete o.a);  // true
console.log(delete 1);    // true
console.log(delete {});   // true

# 删除变量时,返回false,严格模式下会抛出ReferenceError错误
var a = 1;
console.log(delete a);    // false
console.log(a);           // 1

'use strict';
var a = 1;
delete a; // Delete of an unqualified identifier in strict mode

# 删除不可配置的属性时,返回false,严格模式下会抛出TypeError错误
var obj = {};
Object.defineProperty(obj, 'a', { configurable: false });
console.log(delete obj.a);  // false

属性继承

每一个javascript对象都和另一个对象相关联。“另一个对象”就是我们熟知的原型,每一个对象都从原型继承属性。所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过Object.prototype获得对原型对象的引用.
Object.prototype的原型对象是null,原型链的终点为 null.

var obj = {};
console.log(obj.__proto__ === Object.prototype);  // true
  • 继承属性
    对象本身具有的属性叫自有属性, 从原型对象继承而来的属性叫继承属性
var o = {a:1};
var obj = Object.create(o);
obj.b = 2;

console.log(obj.a);    // 继承自原型对象 o的属性a
console.log(obj.b);    // 自有的属性 b

对象属性遍历和判断

  • in操作符可以判断属性在不在该对象上,但无法区别自有还是继承属性
var o = {a:1};
var obj = Object.create(o);
obj.b = 2; 

console.log('a' in obj);  // true
console.log('b' in obj);  // true
console.log('b' in o);    // false
  • for-in循环可以遍历出该对象中所有可枚举属性
var o = {a:1};
var obj = Object.create(o);
obj.b = 2;
for(var i in obj){
    console.log(obj[i]); // 2 1 包括了原型的属性
}
  • hasOwnProperty()方法可以确定该属性是自有属性还是继承属性
var o = {a:1};
var obj = Object.create(o);
obj.b = 2;

console.log(obj.hasOwnProperty('a'));  // false
console.log(obj.hasOwnProperty('b'));  // true
  • Object.keys()方法返回所有可枚举的自有属性
var o = { a:1 };
var obj = Object.create(o,{
    c:{value:3,configurable: false}
});
obj.b = 2;
console.log(Object.keys(obj)); // ['b']
  • Object.getOwnPropertyNames()方法返回所有自有属性(包括不可枚举的属性)
var o = {a:1};
var obj = Object.create(o,{
    c:{value:3,configurable: false}
});
obj.b = 2;
console.log(Object.getOwnPropertyNames(obj)); // ['c','b']

性能优化(网络方向)

性能优化(网络方向)

web应用无非是两台主机之间互相传输数据包的一个过程; 如何减少传输过程的耗时就是网络方向优化的重点, 优化出发点从第一篇文章中说起

DNS解析过程的优化

当浏览器从第三方服务跨域请求资源的时候,在浏览器发起请求之前,这个第三方的跨域域名需要被解析为一个IP地址,这个过程就是DNS解析;
DNS缓存可以用来减少这个过程的耗时,DNS解析可能会增加请求的延迟,对于那些需要请求许多第三方的资源的网站而言,DNS解析的耗时延迟可能会大大降低网页加载性能。

  • dns-prefetch
    当站点引用跨域域上的资源时,都应在元素中放置dns-prefetch提示,但是要记住一些注意事项。首先,dns-prefetch仅对跨域域上的DNS查找有效,因此请避免将其用于您当前访问的站点
<link rel="dns-prefetch" href="https://fonts.googleapis.com/">
  • preconnect
    由于dns-prefetch仅执行DNS查找,但preconnect会建立与服务器的连接。如果站点是通过HTTPS服务的,则此过程包括DNS解析,建立TCP连接以及执行TLS握手。将两者结合起来可提供机会,进一步减少跨源请求的感知延迟
<!-- 注意顺序, precontent和dns-prefetch的兼容性 -->
<link rel="preconnect" href="https://fonts.googleapis.com/" crossorigin>
<link rel="dns-prefetch" href="https://fonts.googleapis.com/">

TCP传输阶段优化

这个前端方面好像能做的有限, 我们都知道 http协议 是基于 tcp的;
升级http协议版本可以考虑下, 比如把 http/1.0 -> http/1.1 -> http/2;
这个需要我们在应用服务器上配置(nginx, Apache等), 不做概述了, 另外还需要客户端和服务器都支持哦, 目前还没开发出稳定版本,好多只支持https,不过也不远了...

  • http2 的优势
#  1.多路复用: 同一个tcp连接传输多个资源
这样可以突破统一域名下只允许有限个tcp同时连接, 
这样http1.1所做的减少请求数优化就没有太大必要了
如多张小图合成一张大图(雪碧图),合并js和css文件

# 2.报文头压缩和二进制编码: 减少传输体积
http1 中第一次请求有完整的http报文头部,第二次请求的也是;
http2 中第一次请求有完整的http报文头部,第二次请求只会携带 path 字段;
这样就大大减少了发送的量。这个的实现要求客户端和服务同时维护一个报文头表。

# 3.Server Push
http2可以让服务先把其它很可能客户端会请求的资源(比如图片)先push发给你,
不用等到请求的时候再发送,这样可以提高页面整体的加载速度
但目前支持性不太好...emm...

总的来说, 在 c 端业务下不会太普及, 毕竟需要软件支持才行...

http 请求响应阶段优化

为了让数据包传输的更快, 我们可以从两个方面入手: 请求的数据包大小(服务器), 请求数据包的频率(客户端)

减少请求文件的大小

请求文件对应的是我们项目完成后,打包所指的静态资源文件(会被部署到服务器), 文件越小, 传输的数据包也会相对较小, 讲道理也会更快到达客户端

how to reduce a package size?

目前我们都会使用打包工具了(比如webpack, rollup, glup 等), 如何使用工具来减小包的体积呢? 这边建议您去官网文档呢...当然这里列举一下常用的手段(webpack 的), 但是注意要插件版本更新哦

  • JS文件压缩(
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
 plugins: [
   new UglifyJsPlugin({
     // 允许并发
     parallel: true,
     // 开启缓存
     cache: true,
     compress: {
       // 删除所有的console语句    
       drop_console: true,
       // 把使用多次的静态值自动定义为变量
       reduce_vars: true,
     },
     output: {
       // 不保留注释
       comment: false,
       // 使输出的代码尽可能紧凑
       beautify: false
     }
   })
 ]
}
  • CSS 文件压缩
// optimize-css-assets-webpack-plugin
plugins: [
  new OptimizeCSSAssetsPlugin({
    assetNameRegExp: /\.css$/g,
    cssProcessor: require('cssnano'),
  }),
];
  • html 文件压缩
// html-webpack-plugin
plugins: [
  new HtmlWebpackPlugin({
    template: path.join(__dirname, 'src/index.html'),
    filename: 'index.html',
    chunks: ['index'],
    inject: true,
    minify: {
      html5: true,
      collapseWhitespace: true,
      preserveLineBreaks: false,
      minifyCSS: true,
      minifyJS: true,
      removeComments: false,
    },
  }),
];
  • source map 文件关闭
  • tree shaking
1.代码不会被执行,不可到达,比如 if(false){// 这里边的代码}
2.代码执行的结果不会被用到
3.代码只会影响死变量(只写不读)
4.方法不能有副作用

// 原理相关: 以后在研究
利用 ES6 模块的特点: 
  只能作为模块顶层的语句出现
  import 的模块名只能是字符串常量
  import binding 是 immutable 的
代码擦除: uglify 阶段删除无用代码
  • scope hoisting(作用域提升)
    分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
module.exports = {
  resolve: {
    // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 开启 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ],
};
  • 项目中使用按需加载,懒加载(路由,组件级)
const router = new VueRouter({
  routes: [
    { path: '/foo', component: () => import(/* webpackChunkName: "foo" */ './Foo.vue') }
    { path: '/bar', component: () => import(/* webpackChunkName: "bar" */ './Bar.vue') }
  ]
})
  • 开启 gizp 压缩
有时候启用也会消耗服务器性能, 看情况使用吧

暂时先提这么些吧...后续想到了再加

减少请求频率

因为同一域名下 tcp 连接数的限制导致过多的请求会排队阻塞, 所以我们需要尽量控制请求的数量和频率

常见措施
  • 将静态资源的内联到HTML中
    这样这些资源无需从服务器获取, 但可能影响到渲染进程...
<!-- 1.小图片内联 base64 (url-loader) -->
<!-- 2.css内联 -->
<!-- 3.js内联 -->
<script>
  ${require('raw-loader!babel-loader!./node_modules/lib-flexible/flexible.js')}
</script>
  • 利用各级缓存(下一篇存储方面介绍)
    通常都是在服务端做相关配置, 但你要知道
我们可以利用http缓存(浏览器端)来减少和拦截二次请求, 当然一般都是在服务端设置的;
服务器端也可以设置缓存(redis等), 减少数据查询的时间同样可以缩短整个请求时间
  • 利用本地存储
我们可以将常用不变的信息存在本地(cookie,storage API 等);
判断存在就不去请求相关的接口, 或者定期去请求也是可以的
  • 花钱买 CDN 加速
    CDN 又叫内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。 CDN 其实是通过优化物理链路层传输过程中的网速有限、丢包等问题来提升网速的...
购买 cdn 服务器;
然后把网页的静态资源上传到 CDN 服务上去,
在请求这些静态资源的时候需要通过 CDN 服务提供的 URL 地址去访问;

# 注意, cdn 缓存导致的新版本发布后不生效的问题
所以打包的时候常在文件后面加上 hash 值
然后在 HTML 文件中的资源引入地址也需要换成 CDN 服务提供的地址
/alicdn/xx12dsa311.js

# 利用不同域名的 cdn 去存放资源, (tcp连接限制)
  • webpack 构建时添加 cdn
// 静态资源的导入 URL 需要变成指向 CDN 服务的绝对路径的 URL 而不是相对于 HTML 文件的 URL。
// 静态资源的文件名称需要带上有文件内容算出来的 Hash 值,以防止被缓存。
// 不同类型的资源放到不同域名的 CDN 服务上去,以防止资源的并行加载被阻塞。
module.exports = {
  // 省略 entry 配置...
  output: {
    // 给输出的 JavaScript 文件名称加上 Hash 值
    filename: '[name]_[chunkhash:8].js',
    path: path.resolve(__dirname, './dist'),
    // 指定存放 JavaScript 文件的 CDN 目录 URL
    publicPath: '//js.cdn.com/id/',
  },
  module: {
    rules: [
      {
        // 增加对 CSS 文件的支持
        test: /\.css$/,
        // 提取出 Chunk 中的 CSS 代码到单独的文件中
        use: ExtractTextPlugin.extract({
          // 压缩 CSS 代码
          use: ['css-loader?minimize'],
          // 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL
          publicPath: '//img.cdn.com/id/'
        }),
      },
      {
        // 增加对 PNG 文件的支持
        test: /\.png$/,
        // 给输出的 PNG 文件名称加上 Hash 值
        use: ['file-loader?name=[name]_[hash:8].[ext]'],
      },
      // 省略其它 Loader 配置...
    ]
  },
  plugins: [
    // 使用 WebPlugin 自动生成 HTML
    new WebPlugin({
      // HTML 模版文件所在的文件路径
      template: './template.html',
      // 输出的 HTML 的文件名称
      filename: 'index.html',
      // 指定存放 CSS 文件的 CDN 目录 URL
      stylePublicPath: '//css.cdn.com/id/',
    }),
    new ExtractTextPlugin({
      // 给输出的 CSS 文件名称加上 Hash 值
      filename: `[name]_[contenthash:8].css`,
    }),
    // 省略代码压缩插件配置...
  ],
};
/*
以上代码中最核心的部分是通过 publicPath 参数设置存放静态资源的 CDN 目录 URL,
为了让不同类型的资源输出到不同的 CDN,需要分别在:

output.publicPath 中设置 JavaScript 的地址。
css-loader.publicPath 中设置被 CSS 导入的资源的的地址。
WebPlugin.stylePublicPath 中设置 CSS 文件的地址。
设置好 publicPath 后,WebPlugin 在生成 HTML 文件和 css-loader 转换 CSS 代码时,会考虑到配置中的 publicPath,用对应的线上地址替换原来的相对地址。
*/

参考

DNS MDN]
webpack 文档
深入浅出 Webpack
Scope Hoisting

DOM 知识点概览

DOM(文档对象模型)知识点概览

DOM 定义了 JS 访问和操作 HTML 文档的标准; 所有 HTML 元素(节点)均可被修改,也可以创建或删除节点, 包括属性,样式,结构关系等...

关键知识点

  • DOM 节点树
html > [head,body] > [div] > [p,span,img,a,...tags]
  • 节点分类
分类: 4 大节点; 元素,文本,属性,注释节点
结构: 树形层级结构
  • 节点对象
var ele = getElementById('selector')
ele即dom节点对象; 对应有其属性和方法;
emmm...太多记不住; 用到的时候再去查 :)
{
  属性: 各个节点值(文本,属性,注释), 名称, 类型, 样式等...,
  方法: 查get, 增append, 删remove, 改(重新赋值)
}
  • 节点样式
getComputedStyle()
CSSStyleSheet
js 操作节点样式
  • 节点尺寸(三大家族)
offset
client
scroll
getBoundingClientRect
  • 节点事件(人机,外设,交互的基础)
# 事件机制
  1.事件流: 事件流又称为事件传播,DOM2级事件规定的事件流包括三个阶段:
        事件捕获阶段,处于目标阶段和事件冒泡阶段

       首先发生的是事件捕获,为截获事件提供了机会.
        然后是实际的目标接收到事件,
        最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应.

  2.事件处理程序(回调函数)
    this值等于事件的目标元素
    DOMO事件处理程序: el.onclick = function(){}
                    el.onclick = null
    DOM2级事件处理程序: addEventListener() 和 removeEventListener()
                    el.addEventListener('click', function(){}, false)
    IE事件处理程序: attachEvent()和detachEvent()
  
  3.事件对象 event: 
    属性和方法;
    事件委托代理: click、mousedown、mouseup、keydown、keyup和keypress
    事件阻止: prevent, 阻止冒泡等...

  4.事件模拟
    自定义事件 Event()构造函数
    creatEvent()创建事件

# 事件类型
  鼠标: mouse,
  键盘: key,
  节点变更: DOMNode,
  剪切板: clipboardData
  文本: change,
  浏览器加载: load,unload,error,DOMContentLoaded...
  焦点: focus,
  ...

BOM 之 history 对象

history对象详解

history 对象保存着用户上网的历史记录,从窗口被打开的那一刻算起。因为 historywindow 对象的属性,因此每个浏览器窗口、每个标签页乃至每个框架,都有自己的 history 对象与特定的 window 对象关联。出于安全方面的考虑,开发人员无法得知用户浏览过的 URL。不过,借由用户访问过的页面列表,同样可以在不知道实际 URL 的情况下实现后退和前进。

属性

length

history.length属性保存着历史记录的URL数量。初始时,该值为1。如果当前窗口先后访问了三个网址,history.length属性等于3

history.length // 初始时,该值为1
history.length // 访问三个网址后,该值为3

方法

跳转方法

history对象提供了一系列方法,允许在浏览历史之间移动,包括go()、back()和forward();
当移动的位置超出了访问历史的边界,以上三个方法并不报错,而是静默失败;
使用历史记录时,页面通常从浏览器缓存之中加载,而不是重新要求服务器发送新的网页

  • go()
// 使用go()方法可以在用户的历史记录中任意跳转。
//后退一页
history.go(-1)
//前进一页
history.go(1);
//前进两页
history.go(2);
//刷新当前页面
history.go();
  • back()
back()方法用于模仿浏览器的后退按钮,相当于history.go(-1)
  • forword()
forward()方法用于模仿浏览器的前进按钮,相当于history.go(1)

新增方法(记录增改)

HTML5为history对象添加了两个新方法,history.pushState()和history.replaceState(),用来在浏览历史中添加和修改记录,也是实现vue-router history模式的基础。
state属性用来保存记录对象,而popstate事件用来监听history对象的变化

  • pushState()
    history.pushState()方法向浏览器历史添加了一个状态。
history.pushState(state: object, title: string, url: string);

state: 状态对象是一个由pushState()方法创建的、与历史纪录相关的javascript对象。
        当用户定向到一个新的状态时,会触发popstate事件。
        事件的state属性包含了历史纪录的state对象。如果不需要这个对象,此处可以填null
title: 新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null
url: 这个参数提供了新历史纪录的地址。新URL必须和当前URL在同一个域,
    否则,pushState()将丢出异常。这个参数可选,
    如果它没有被特别标注,会被设置为文档的当前url

注意: pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏的显示地址发生变化;
      pushState的url参数如果设置了一个新的锚点值(即hash),并不会触发hashchange事件
  • replaceState()
    history.replaceState方法的参数与pushState方法一模一样,
    不同之处在于replaceState()方法会修改当前历史记录条目而并非创建新的条目
  • state属性
    history.state属性返回当前页面的state对象
history.pushState({page: 1}, 'title 1', '?page=1');
history.state // { page: 1 }
  • popstate事件
    当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件;
仅仅调用pushState方法或replaceState方法,并不会触发该事件,
只有用户点击浏览器倒退按钮和前进按钮,
或者使用javascript手动调用back()、forward()、go()方法时才会触发。
另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发

缓存加载

默认情况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进”或“后退”按钮时,浏览器就会从缓存中加载页面

浏览器有一个特性叫“往返缓存”(back-forward cache或bfcache),可以在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。这个缓存中不仅保存着页面数据,还保存了DOM和javascript的状态;实际上是将整个页面都保存在了内存里。如果页面位于bfcache中,那么再次打开该页面时就不会触发load事件.

  • pageshow
    pageshow事件在页面加载时触发,包括第一次加载和从缓存加载两种情况。如果要指定页面每次加载(不管是不是从浏览器缓存)时都运行的代码,可以放在这个事件的监听函数.
第一次加载时, 先 load 在 pageshow; 
缓存加载时, 先 pageshow, 不会触发 load
  • pagehide
    该事件会在浏览器卸载页面的时候触发,而且是在unload事件之前触发
window.onpagehide = function(e){
    e = e || event;
    console.log(e.persisted);
}

对象类型(属性和方法)

对象及其属性和方法

对象: 是一种复杂的数据类型,它将许多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值;
对象也可看做是属性的无序集合,每个属性都是一个键值对; 属性键名是字符串,因此我们可以把对象看成是从字符串到值的映射.

对象创建

new构造函数, 对象直接量, Object.create()函数

  • new 方式创建实例对象
var person = new Object(); // 无参可不加括号
person.name = 'huhau';
person.age = 26;
person['sex'] = 'male'; // .访问 优先 []的需计算访问
console.log(person) // {name: "huhau", age: 26, sex: "male"}

// 参数为空,null,undefined: {} 

// 参数为对象; 返回这个对象

// 参数为原始值类型: 返回其包装对象
var n = new Object(1) // { __proto__: Number, [[PrimitiveValue]]: 1 }

// Object(params) 将参数转换为对象
  • 字面量创建实例对象
var person = {
    name: 'huhua',
    age: 26,
    1: true
};
属性名会自动转换成字符串
  • Object.create(p1, p2)创建实例对象
第一个参数就是这个对象的原型
第二个可选参数用以对对象的属性进行进一步描述

var o1 = Object.create({x:1,y:1}); // o1的原型指向这个对象;继承了属性x和y
console.log(o1.x); // 1

var o2 = Object.create({z:3},{
  x:{value:1,writable: false, enumerable:true, configurable:true},
  y:{value:2,writable: false, enumerable:true, configurable:true}
}); 
console.log(o2.x, o2.y, o2.z);// 1 2 3

# 传入 null
# 创建一个没有原型的新对象,但通过这种方式创建的对象不会继承任何东西
var o2 = Object.create(null); // o2不继承任何属性和方法, 是一个纯净的数据容器

对象成员

对象是属性的无序集合,由键名和属性值组成.
键名:

键名都是字符串,所以加不加引号都可以,如果不是字符串也会自动转换成字符串
键名不符合标识符命名规则,则必须加上引号

属性值:

属性值可以是任何类型的表达式,最终表达式的结果就是属性值的结果
属性为函数时,通常称为方法, 也有 name 属性

对象引用

不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量.
如果取消某一个变量对于原对象的引用,不会影响到另一个变量

对象的实例方法

  • valueOf()
# valueOf()方法会返回当前对象
var o = { name: 'huhua' }
console.log(o.valueOf()) // { name: 'huhua' }
  • toString()
# toString()方法返回当前对象对应的字符串形式
var o1 = new Object();
o1.toString() // "[object Object]"

三大包装对象类型

包装类型

number 与 Number

数字Number是javascript中基本的原始数据类型,同时javascript也支持Number对象,它是一个原始数值的包装对象。在需要时,javascript会自动在原始形式和对象形式之间转换。

数字定义

js采用IEEE754格式来表示数字,不区分整数和浮点数,js中的所有数字都用浮点数值表示

由于浮点型数值需要的内存空间是保存整数值的两倍,因此javascript会不失时机地将浮点数值转换成整数值,若小数点后没有跟任何数字或者浮点值本身表示的就是一个整数,这个数值会作为整数值来保存

当一个数字直接出现在javascript程序中时,称之为数字字面量(numeric literal)。而当Number()使用new操作符用做构造函数时,称之为Number对象

数字类型

  • 整数
四种字面量格式: 2(0b01),8(001),10(1),16(0x01)机制
进行算术计算时,所有以二进制、八进制和十六进制表示的数值最终都将被转换成十进制数值
  • 浮点数
浮点数(floating-point number)是指数值中必须包含一个小数点,且小数点后面必须至少有一位数字

注意: 浮点数不是精确值, 在计算时注意取整
  • 科学计数法
对于极大或者极小的数,可以用科学计数法e来表示的浮点数值来表示。科学计数法允许字母e或E的后面,跟着一个整数,表示这个数值的指数部分

// 自动转为科学计数法表示
1.小于1且小数点后面带有6个0以上的浮点数值
0.0000003 // 3e-7
2.整数位数字多于21位
1234567890123456789012 //1.2345678901234568e+21

数字精度

javascript浮点数有64个二进制位

第1位:        符号位,0表示正数,1表示负数
第2位到第12位: 储存指数部分
第13位到第64位:储存小数部分(即有效数字)

javascript提供的有效数字最长为53个二进制位; 精度最长为53个二进制位,意味着绝对值小于2的53次方的整数,即-(253-1)到253-1,都可以精确表示;
==> 换算成十进制,javascript数字最高精度是16位(若整数部分为0,则表示小数点后16位;若整数部分不为0,则表示整体保留16位)

数字范围

64位浮点数的指数部分的长度是11个二进制位,意味着指数部分的最大值是2047(211-1)。分出一半表示负数,则javascript能够表示的数值范围为21024到2-1023.
javascript中的最大值保存在Number.MAX_VALUE中,而最小值保存在Number.MIN_VALUE

console.log(Number.MIN_VALUE,Number.MAX_VALUE)
// 5e-324
// 1.7976931348623157e+308
  • 0.1+0.2!== 0.3的问题
计算机中的数字都是以二进制存储的,如果要计算0.1 + 0.2 的结果,计算机会先把0.1和0.2分别转化成二进制,然后相加,最后再把相加得到的结果转为十进制

把10进制的0.1转换成2进制,表示为0.0 0011 0011...(0011循环):
(0.1).toString(2);// "0.0001100110011001100110011001100110011001100110011001101"
把10进制的0.2转换成2进制:
(0.2).toString(2);//"0.001100110011001100110011001100110011001100110011001101"

计算机只能保存最大53位精度,所以,用科学记数法表示
0.1的二进制为1.1001100110011001100110011001100110011001100110011010e+4(52位小数)

0.2的二进制为1.1001100110011001100110011001100110011001100110011010e+3(52位小数)

两种相加后转为 10 进制后: 0.30000000000000004

说一下 NaN

NaN(not a number)表示非数字,NaN与任何值都不相等,包括NaN本身,且任何涉及NaN的操作都会返回NaN

5 - 'x';           // NaN
Math.acos(2);      // NaN
0 / 0;             // NaN
NaN == NaN;        // false
NaN == Infinity;   // false
Boolean(NaN);      // false
isNaN()来判断这个数字是不是NaN,包含着隐式类型转换Number()

数值转化

Number()可以将任意类型的值转化成数值,而parseInt()和parseFloat()只应用于字符串向数字的转换

  • Number 函数
# Number()函数解析字符串, 会先去空格
1.若字符串只包含十进制或十六进制数字,则转成十进制的数字
2.若字符串为空字符串或空格字符串,则转成0
3.其他情况的字符串,则转成NaN

# Number()函数解析对象
1.调用对象的valueOf()方法,如果返回原始类型的值,则直接对该值使用Number()函数
2.如果valueOf()方法返回的还是对象,则调用对象的toString()方法,如果返回原始类型的值,则对该值使用Number()函数
3.如果toString()方法返回的依然是对象,则结果是NaN
Number([]);        // 0
Number([0]);       // 0
Number([-0]);      // 0
Number([10]);      // 10
Number([1,2]);     // NaN
Number(其他对象);   // NaN
  • parseInt函数
# 字符串转换成整数
1.在转换字符串时,会忽略字符串前面的空格,直到找到第一个非空格字符
2.如果第一个字符不是数字字符或者负号,parseInt()就会返回NaN
3.如果是,则继续解析,直到解析完成或者遇到非数字字符

# 识别出各种进制的数字,输出的是运算后的十进制的数字
console.log(parseInt(011)); // 9
console.log(parseInt('0x11')); // 17
如果是科学计数法表示,会被转成不准确的数字, 不要使用
console.log(parseInt(0.0000008)); // 8
// 等同于
console.log(parseInt('8e-7')); // 8

# 可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。默认情况下,parseInt的第二个参数为10,即默认是十进制转十进制
console.log(parseInt('11',2)); //3
console.log(parseInt('11',8)); //9

如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN。如果第二个参数是0、undefined和null,则直接忽略
console.log(parseInt('10', 37)); // NaN
console.log(parseInt('10', 1)); // NaN
console.log(parseInt('10', 0)); // 10

如果字符串包含对于指定进制无意义的字符,则从最高位开始,只返回可以转换的数值。如果最高位无法转换,则直接返回NaN
console.log(parseInt('1546', 2)); // 1
console.log(parseInt('546', 2)); // NaN

parseInt()是专门用来处理字符串转换数字的,parseInt处理非字符串和数字类型时输出NaN。但是,实际上parseInt()包含着隐式的toString()方法,所以parseInt([数字或字符串])输出对应的数字
  • parseFloat函数
专门用于字符串转换浮点数。同样地,解析时会忽略字符串前面的空格,直到找到第一个非空格字符,然后一直解析到字符串末尾或一个无效的浮点数字字符为止

# parseFloat()可以识别不同进制的数字,但只能解析十进制字符串

其他基本同上

扩展: 实例方法

Number()对象的实例方法总共有6个,分为两类。包括toString()、toLocalString()、valueOf()这3种对象通用方法和toFixed()、toExponential()、toPrecision()这3种改变数值显示形式并转换为字符串的方法

valueOf()方法返回对象的数字字面量
toString()方法将数字转换为字符串,可以接受一个参数,是2到36之间的整数,表示输出的进制
toLocalString()方法将数字转换为本地惯例格式化数字的字符串

console.log(typeof (1).valueOf(),(1).valueOf()); //number 1
console.log(typeof (1).toString(),(1).toString()); //string '1'
console.log(typeof (1).toLocaleString(),(1).toLocaleString()); //string '1'
  • 其他几个方法
toFixed(): 按照指定的小数位返回数值四舍五入后的字符串表示, 参数只接受0~20
toExponential(): 返回数值四舍五入后的指数表示法(e表示法)的字符串表示,参数表示转换后的小数位数
toPrecision()方法接收一个参数(0~21),即表示数值的所有数字的位数(不包括指数部分),自动调用toFixed()或toExponential()

不过,这三个方法在小数位用于四舍五入时都不太可靠,跟浮点数不是精确储存有关
我们可以引入 Math 方法

string 与 String

字符串string是javascript基本数据类型,同时javascript也支持String对象,它是一个原始值的包装对象。在需要时,javascript会自动在原始形式和对象形式之间转换。

定义

字符串String类型是由引号括起来的一组由16位Unicode字符组成的字符序列

字符串类型常被用于表示文本数据,此时字符串中的每个元素都被视为一个代码点。每个元素都被认为占有此序列中的一个位置,用非负数值索引这些位置。首字符从位置0开始,第二个字符在位置1,依次类推

字符串的长度即其中元素的个数。空字符串长度为零,因而不包含任何元素

  • 转义字符
反斜线(\)有着特殊的用途,反斜线符号后加一个字符,就不表示它们的字面含义,用来表示一些特殊字符,称为转义字符
\0 空字节
\n 换行
\t 制表
\b 空格
\r 回车
\f 进纸
\\ 斜杠
\' 单引号
\" 双引号
  • 特性
javascript中的字符串是不可变的。一旦字符串被创建,就永远无法改变它。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量

字符串转化

把一个值转换为字符串有三种方式,toString()和String(), 拼接符+

  • toString()
    每个值都(undefined和null除外)有的toString()方法,这个方法返回相应值的字符串表现
undefined.toString();  // 错误
null.toString();       // 错误
true.toString();       // 'true'
false.toString();      // 'false'
'abc'.toString();      // 'abc'
1.23.toString();       // '1.23'
({}).toString();       // '[object Object]'
[1,2,3,4].toString();  // '1,2,3,4'
(new Date()).toString(); // 'Sun Jun 05 2016 10:04:53 GMT+0800 (**标准时间)'
/ab/i.toString();      // '/ab/i'
  • String()
    在不知道要转换的值是不是undefined或null时,可以使用转型函数String()
转型函数String()遵循下列规则:

如果值是null,则返回'null';如果值是undefined,则返回'undefined'

如果值不是null或undefined,则调用toString()方法并返回原始类型值
  若使用toString()方法返回的是对象,则再调用valueOf()方法返回原始类型值;
  若使用valueOf()方法返回的是对象,会报错;

boolean 与 Boolean

布尔boolean类型表示逻辑实体,它只有两个值,保留字true和false,分别代表真和假这两个状态
Boolean包装类型是与布尔值对应的引用类型

  • 尽量不要混用 boolean 类型和 Boolean 函数
var b1 = true;
var b2 = new Boolean(true);
console.log(b1,typeof b1);// true 'boolean'
console.log(b2,typeof b2);// Boolean{[[PrimitiveValue]]: true}对象  'object'
console.log(b1.valueOf(), typeof b1.valueOf());// true 'boolean'
console.log(b2.valueOf(), typeof b2.valueOf());// true 'boolean'

boolean 值转化

将一个值转为布尔值可使用Boolean()转型函数

  • 转为假值: 7个值包括undefined、null、+0、-0、NaN、false、""(空字符串)
  • 转为真值: 除了以上7个假值外,其他的值转换为布尔值都是true

Boolean 的实例方法

Boolean对象是与布尔值对应的包装类型,继承了Object对象的通用方法toString()、toLocaleString()、valueOf()这三个方法

console.log(true.valueOf()); // true 返回原始值
console.log(true.toString());// 'true'
console.log(true.toLocaleString());// 'true'

console.log((new Boolean(false)).valueOf());//false
console.log((new Boolean(false)).toString());//'false'
console.log((new Boolean(false)).toLocaleString());//'false'

nginx 设置虚拟主机&反向代理

nginx 设置虚拟主机

虚拟主机是指在一台物理主机服务器上划分出多个磁盘空间,每个磁盘空间都是一个虚拟主机,每台虚拟主机都可以对外提供Web服务,并且互不干扰.
这样,我们可以利用虚拟主机把多个不同域名的网站部署在同一台服务器上

基于端口设置

配置 server { ... } 时, 分别监听不同的端口
即Nginx监听多个端口,根据不同的端口号,来区分不同的网站。

// server1
server {
  listen 8000;
  server_name localhost;
  ...
}
// server2
server {
  listen 8001;
  server_name localhost;
  ...
}

基于IP设置

配置不同的server_name 选项

server {
    listen 80;
    server_name 112.74.164.244;
    ...
}

server {
    listen 80;
    server_name 111.111.111.112;
    ...
}

基于域名设置

注意事先我们需要将域名与公网 Ip 绑定
也是配置 server_name 选项

// server1
server {
  listen 8000;
  server_name pc.huhua.com;
  ...
}
// server2
server {
  listen 8001;
  server_name m.huhua.com;
  ...
}

nginx 设置反向代理

web模式: client 端 <=> server 端

代理: 中间接入一层特定功能的服务器, 即代理服务器

正向代理: 
客户端浏览器没有权限访问某些网站服务器, 需要通过有权限的中间服务器来访问这些网站服务器, 
然后转发到客户端(如vpn); 这样代理了客户端

反向代理: 与正向代理相反  
客户端把请求先发给代理服务器, 代理服务器再通过 nginx 把请求(路由 url)
转发到自己设置的服务器上, 设置的服务器将内容返回给客户端浏览器; 这样便代理了服务器

反向代理的作用

  • 安全, 隐藏了真实服务器信息
    使用反向代理客户端用户只能通过外来网来访问代理服务器,并且用户并不知道自己访问的真实服务器是那一台,可以很好的提供安全保护

  • 负载均衡
    反向代理的主要用途是为多个服务器提供负载均衡、缓存等功能。负载均衡就是一个网站的内容被部署在若干服务器上,可以把这些机子看成一个集群,那Nginx可以将接收到的客户端请求“均匀地”分配到这个集群中所有的服务器上,从而实现服务器压力的平均分配,也叫负载均衡。

反向代理指令

# 访问http://xxxx.com 然后反向代理到http://oooo.com这个网站
server{
  listen 80;
  server_name  xxxx.com;
  location / {
    # 也可以是 ip 地址
    proxy_pass http://oooo.com;
  }
}

其他指令

  • proxy_set_header
    在将客户端请求发送给后端服务器之前,更改来自客户端的请求头信息。

  • proxy_connect_timeout
    配置Nginx与后端代理服务器尝试建立连接的超时时间。

  • proxy_read_timeout
    配置Nginx向后端服务器组发出read请求后,等待相应的超时时间。

  • proxy_send_timeout
    配置Nginx向后端服务器组发出write请求后,等待相应的超时时间。

  • proxy_redirect
    用于修改后端服务器返回的响应头中的Location和Refresh

浏览器的主要工作流

浏览器的主要工作流

我们从url输入到页面渲染完成来梳理一下浏览器工作的相关流程...

流程总览

这里涉及几个进程的协作,还有网络相关的知识,前面都已经说过了,这里不再解释; 也不做细讲 :)

浏览器进程: 用户输入url → 处理输入↓  (如果30x则会重定向) ↓ ... 接受网络进程的数据,发送给渲染进程(commit navigation) -> 收到确认信息, 导航结束,让渲染进程开始干活 -> 解析完毕生成展示页面

网络进程:   DNS(先在缓存查找)解析出ip → 发起http/https请求(3次/tsl握手,建立tcp连接) → 读取响应数据并发送 ↑ -> 断开连接

渲染进程:   通过管道接受网络进程的html数据 → 向浏览器进程发送确认信息 -> 开始解析html

总结一下流程:

处理输入 -> 导航 -> 请求资源 -> 确认导航 -> 渲染进程接管... -> 页面呈现

渲染进程工作流

  • 1.DOM Tree 构建
当渲染进程接收到导航的确认信息,开始接受HTML数据时,主线程会解析文本字符串为 DOM;
这里依靠 HTMl 解析器: 
接受字节流 -> 维护 token 栈 -> 生成节点node -> 组成 DOM;

遇到内嵌 script 时, DOM解析工作停止; js引擎介入执行(可能会修改dom结构);
执行完 js 后恢复解析工作, 所以 js 会阻塞 dom 解析.

遇到其他内联资源时(css,img)会通知网络进程去下载, 特别是 css;
js 在操作dom 样式时会依赖cssom,生成 layoutTree也需要 cssom; 
所以 css 又会阻塞 js 的执行

  • 2.样式计算, 构建 cssom
这里会基于 CSS 选择器解析 CSS 获取每一个节点的最终的计算样式值;
对应的就是styleSheets
  • 3.计算布局, 生成layout tree
想要渲染一个完整的页面,除了获知每个节点的具体样式,还需要获知每一个节点在页面上的位置,
布局其实是找到所有元素的几何关系的过程。

这里通过遍历 DOM 及相关元素的计算样式,主线程会构建出包含每个元素的坐标信息及盒子大小的布局树。
布局树和 DOM 树类似,但是其中只包含页面可见的元素,如果一个元素设置了 `display:none` ,
这个元素不会出现在布局树上,伪元素虽然在 DOM 树上不可见,但是在布局树上是可见的。
  • 4.分层,绘制(layer -> paint)
为特定的节点生成专用图层(will-change属性), 生成 图层树;
为图层生成绘制表(记录了绘制指令和顺序), 提交到合成线程
  • 5.分块,光栅化
合成线程将图层分为图块, 通过光栅化生成位图(GPU 进程)
  • 6.合成,显示
图块被光栅化后会生成一个绘制命令, 通过 IPC 提交给浏览器进程去执行,
绘制到内存中然后展示在显示器上

计算机基础概览

计算机基础概览

计算机组成

硬件 <- 操作系统 -> 软件

硬件部分

CPU: 核心部分
  > 控制器: 控制器是计算机的控制系统,指挥控制计算机其他所有组件
  > 运算器: 运算器就是做数学运算和逻辑运算的

存储(寄存器): 数据读写
  内存: 基于电工作,优点:存取速度快;缺点:断电数据丢失,只能临时存储数据
  外存(硬盘): 基于磁工作,缺点:存取速度慢;有点:断电数据不丢失,可以永久保存数据
  手机中分别对应的是 RAM 和 ROM
  寄存器又称I/O设备,不过指的是寄存器既需要读数据又会输出数据的特性

I/O: 交互
  Input: 键盘,鼠标等...
  Output: 显示器,打印机等...

我们编写的程序一定要运行在硬件之上,站在硬件的角度,与运行程序有关的三大核心硬件为CPU、内存、硬盘.
程序最先是存放于硬盘中的,程序的运行是先从硬盘把代码加载到内存中,然后cpu是从内存中读取指令运行

操作系统

操作系统本质是一个软件, 运行在计算机上;
操作系统的功能就是帮我们把复杂的硬件控制封装成简单的接口,对于开发应用程序来说只需要调用这些接口即可.

  • 系统程序
# 控制程序: 用来控制硬件的基本运行,以及把复杂的硬件的操作封装成简单的接口
# 应用程序: 基于控制程序的接口开发包含一系列业务逻辑的程序

软件部分

软件分为两种:应用软件、系统软件(操作系统)

# 应用软件: 例如浏览器, 微信, QQ 等 

# 系统软件: 操作系统应用软件与硬件之间的一个桥梁,
  是协调、管理、控制计算机硬件与应用软件资源的控制程序。

小结

我们编程开发应用程序的目的是控制计算机硬件。但是和我们直接打交道的是操作系统,我们把需求告诉操作系统,再由操作系统控制计算机硬件干活。所以一套完整的计算机系统分为三层, 即:

(软件)应用程序  <- OS -> 硬件设备

nginx适配PC或移动设备

Nginx适配PC或移动设备

$http_user_agent

Nginx通过内置变量$http_user_agent,可以获取到请求客户端的userAgent,就可以用户目前处于移动端还是PC端,进而展示不同的页面给用户。

server {
     listen 80;
     server_name xxoo.com;
     location / {
      root /www/pc;
      if ($http_user_agent ~* '(Android|webOS|iPhone|iPod)') {
         root /www/mobile;
      }
      index index.html;
     }
}

Nginx的Gzip压缩配置

Gzip是网页的一种网页压缩技术,经过gzip压缩后,页面大小可以变为原来的30%甚至更小。更小的网页会让用户浏览的体验更好,速度更快。gzip网页压缩的实现需要浏览器和服务器的支持。

浏览器支持gzip: Accept-Encoding:gzip
服务器响应gzip: Content-Encoding:gzip

# 常规配置
gzip on;
gzip_types text/plain application/javascript text/css

关于this值的理解

关于this值的理解

我发现以前关于 this 值的理解都在于函数调用, 谁调用就指向谁; 然后箭头函数有自己 this 规则,应该是在新的规范里定义的, 现在重新理解一下 this
先了解一下ECMA规范里怎么说的

引用规范类型

js 内部还有个引用规范类型, 用来说明 delete,typeof,赋值运算符这些运算符的行为

这个 Reference 类型的描述

{
  基(base)值: undefined,Object,Boolean,String,Number,environment record(环境数据)中的任意一个,
  引用名称(referenced name): 'string',
  严格引用 (strict reference): false
}
基值是 undefined 表示此引用可以不解决一个绑定
  • 来两个实例理解Reference
var foo = 1;
// 对应的Reference是: 
var fooReference = {
    base: environment record,
    name: 'foo',
    strict: false
};
var foo = {
    bar: function () {
        return this;
    }
};
foo.bar(); // foo

// bar对应的Reference是:
var BarReference = {
    base: foo,
    propertyName: 'bar',
    strict: false
};

再看一下 Reference 的相关组件和操作(可自己去规范里看)

  • 使用以下抽象操作接近引用的组件
GetBase(V): 返回引用值 V 的基值组件
GetReferencedName(V): 返回引用值 V 的引用名称组件
IsStrictReference(V): 返回引用值 V 的严格引用组件
HasPrimitiveBase(V): 如果基值是 Boolean, String, Number,那么返回 true
IsPropertyReference(V): 如果基值是个对象或HasPrimitiveBase(V)是true,那么返回true;否则返回false
IsUnresolvableReference(V): 如果基值是 undefined 那么返回 true,否则返回 false
  • 使用以下抽象操作来操作引用
# GetValue(v):

1.如果 Type(V) 不是引用 , 返回 V。
2.令 base 为调用 GetBase(V) 的返回值。
3.如果 IsUnresolvableReference(V), 抛出一个 ReferenceError 异常。
4.如果 IsPropertyReference(V), 那么
    a. 如果 HasPrimitiveBase(V) 是 false, 那么令 get 为 base 的 [[Get]] 内部方法 , 否则令 get 为下面定义的特殊的 [[Get]] 内部方法。
    b.将 base 作为 this 值,传递 GetReferencedName(V) 为参数,调用 get 内部方法,返回结果。
5.否则 , base 必须是一个 environment record。
6.传递 GetReferencedName(V) 和 IsStrictReference(V) 为参数调用 base 的 GetBindingValue( 见 10.2.1) 具体方法,返回结果。

# 简而言之,用代码表示就是, 这些都是底层操作
var foo = 1;

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};

GetValue(fooReference) // 1; 调用 GetValue后,返回的将是具体的值

由 Reference 到 this

js 在函数调用的时候才会确定 this值的指向
以下来自规范

  • 函数调用时发生的一些事
    1.令 ref 为解释执行 MemberExpression 的结果 .
    2.令 func 为 GetValue(ref).
    3.令 argList 为解释执行 Arguments 的结果 , 产生参数值们的内部列表 (see 11.2.4).
    4.如果 Type(func) is not Object ,抛出一个 TypeError 异常 .
    5.如果 IsCallable(func) is false ,抛出一个 TypeError 异常 .
    6.如果 Type(ref) 为 Reference,那么 如果 IsPropertyReference(ref) 为 true,那么 令 thisValue 为 GetBase(ref). 否则 , ref 的基值是一个环境记录项 令 thisValue 为调用 GetBase(ref) 的 ImplicitThisValue 具体方法的结果
    7.否则 , 假如 Type(ref) 不是 Reference. 令 thisValue 为 undefined.
    8.返回调用 func 的 [[Call]] 内置方法的结果 , 传入 thisValue 作为 this 值和列表 argList 作为参数列表

所以确定 this 我们要先确定 MemberExpression 的值

MemberExpression:
  PrimaryExpression
  FunctionExpression
  MemberExpression [ Expression ]
  MemberExpression . IdentifierName
  new MemberExpression Arguments

// MemberExpression可以理解为左值表达式

function foo() {
    console.log(this)
}
foo(); // MemberExpression 是 foo

function foo() {
    return function() {
        console.log(this)
    }
}
foo()(); // MemberExpression 是 foo()

var foo = {
    bar: function () {
        return this;
    }
}
foo.bar(); // MemberExpression 是 foo.bar

再看 ref 值的类型, 看上面滴 6,7,8 条

如何确定呢?参考这条规范
解释一下 就是:
如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)
如果 ref 不是 Reference,那么 this 的值为 undefined

  • 我们举个例子
var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}

//示例1
console.log(foo.bar());
//示例2
console.log((foo.bar)());
//示例3
console.log((foo.bar = foo.bar)());
//示例4
console.log((false || foo.bar)());
//示例5
console.log((foo.bar, foo.bar)());
  • foo.bar()
先看MemberExpression计算的结果:  foo.bar;
再看值类型是否为Reference:       是
var Reference = {
  base: foo,
  name: 'bar',
  strict: false
};  
再通过IsPropertyReference(ref)判断是否为true: base基值为foo是一个对象, 结果为 true
确定 this值: this = GetBase(ref) 其实就是 基值base: foo
  • (foo.bar)()
重复上述步骤
MemberExpression 进行计算结果 foo.bar
所以 this 还是指向 foo
value 还是 2
  • (foo.bar = foo.bar)()
左边存在赋值操作, 参考http://yanhaijing.com/es5/#211
结果: (foo.bar = foo.bar) 不是一个Reference
所以: this 值为undefined, 非严格模式下会被隐式转为了全局对象
  • (false || foo.bar)()
逻辑或操作符, 同上; 不是一个Reference
所以: this 值为undefined
  • (foo.bar, foo.bar)()
逗号操作符, 同上, 不多说
  • 补充说明
function foo() {
  console.log(this)
}
foo();
MemberExpression计算结果: foo
Reference 类型的值: 
  {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
  }
当base value 正是 Environment Record,会调用 ImplicitThisValue(ref);
其返回值为 undefined

总结

this值的理解:

  • 对象
  • 运行时
  • 函数调用
  • 左值表达式要为 Reference 类型
  • Reference 的 base 值为对象
  • this 就是这个 base 值

前端网络知识概览

前端网络知识概览

web开发中必不可少的一环就是网络,通过各种网络协议(http,https,websocket等...),浏览器端才可以与服务端进行数据交换,互相通信.下面我们便来了解一下网络结构...

计算机网络层级(前瞻)

image
前端开发中接触的协议都位于应用层

  • 链路层
连接网络配件
  • 网络层
IP协议: 定义了不同主机(计算机)之间的寻址方式;
网站访问实际是 一台主机 向 另一台主机请求资源
传输层是非常重要的一层,具有承上启下,向上对应用层提供通信服务,向下将应用层信息封装为网络信息.
传输层连接主机之间的进程,同一主机中不同进程的网络通信通过端口进行区分,所以传输层为主机提供的是端口对端口的服务.
  • 应用层(以http协议为例)
    建议看下图解 HTTP, 这里直接附上别人的总结图解http总结
HTTP 是构建在 TCP/IP 之上的应用层协议,而 HTTPS 是在 HTTP 之下加入 SSL/TLS.
简单介绍下,HTTP 是超文本传输协议的英文缩写,它是 Internet 上最主要的 Web 应用层标准,
HTTP 能够传输任何格式的数据.

# http 请求流程
1.构建请求
2.查找缓存(强缓存和协商缓存304)
3.准备IP地址和端口(可能需要DNS解析)
4.等待 TCP 队列: 同一个域名下最多建立6个tcp连接
5.建立 tcp 连接
6.发送 http 请求: 请求头,请求行,请求体
7.服务器处理 http 请求后返回对应响应信息: 响应行(状态码), 响应头, 响应体
8.断开连接: keep-alive

image

  • http 版本
# HTTP/0.9
  - 只有请求行,没有请求头和请求体
  - 服务器为返回头部信息
  - 内容通过 ASCII 字符流传输

# HTTP/1.0
  - 加入请求体: 支持文件类型,压缩,语言,编码类型
  - 加入状态码
  - 提供了 cache 缓存机制
  - 加入用户代理

# HTTP/1.1
  - 增加持久连接: 一个 tcp 连接上可以发送多个 http 请求
  - 提供虚拟主机: host
  - 支持动态内容 chunk transfer 机制
  - cookie,安全机制
  - 每个域名同时可维护6个tcp持久连接

  - 存在队头阻塞问题(传输过程中出现的单个数据包丢失导致的阻塞) 
  - 带宽利用率不高, tcp 连接竞争固定带宽

# HTTP/2.0
  - 多路复用
  - 设置请求优先级
  - 服务器推送
  - 头部压缩

HTTP 协议

http 协议详解
https 协议详解

Socket

Socket 是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信,也可以实现相同主机内的不同进程之间的通讯。

Socket = IP 地址 + 端口 + 协议
可以看到,Socket 其实是对 TCP/IP 进行了高度封装,屏蔽了很多网络细节。
这样可以使开发者更好地进行网络编程。
其实就是我们写个高度封装内部细节的函数,通过传参来完成指定的行为.

WebSocket

JS websocket API
HTML5 定义的一种新的标准协议,实现了浏览器与服务器的全双工通信。我们可以将 WebSocket 理解为 Web + Socket ,它是一种双工通信.

WebSocket 是建立在 HTTP 之上的,也就意味着你要建立 WebSocket 的话,需要走一次 HTTP ,走完后,你的 WebSocket 就建立起长连接了。然后只要不是主动断开的,就会保持好客户端和服务端之间的连接,不会使其断开。当有数据传输的时候,会直接进行传输,不再发起 HTTP 请求

浏览器安全相关

浏览器安全相关

浏览器安全可以划分为Web页面安全、浏览器网络安全和浏览器系统安全三大方面.

同源策略

如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源.
浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的;
两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略...
同源策略主要表现在 DOM、Web 数据和网络这三个层面

// 两个同源的 url
https://m.feng.com/?page=1
https://m.feng.com/?page=2
  • 1.DOM 层
    同源策略限制了来自不同源的js脚本对当前 DOM 对象读和写的操作
// 如果位于同源下的两个页面可以通过window操作
// 如果位于不同源的两个页面不能相互操纵 DOM
// window.postMessage是HTML5的一个接口,可实现不同窗口不同页面的跨域通讯
  • 2.数据层
    同源策略限制了不同源的站点读取当前站点的Cookie,IndexDB,LocalStorage等数据
  • 3.网络层
同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点

跨域的几种方式

克服上面同源策略的限制, 实现不同域下的交互操作

JSONP

利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以.
但缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。

// client.js
function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) => {
    let script = document.createElement('script')
    window[callback] = function(data) {
      resolve(data)
      document.body.removeChild(script)
    }
    params = { ...params, callback } // wd=b&callback=show
    let arrs = []
    for (let key in params) {
      arrs.push(`${key}=${params[key]}`)
    }
    script.src = `${url}?${arrs.join('&')}`
    document.body.appendChild(script)
  })
}
jsonp({
  url: 'http://localhost:3000/say',
  params: { wd: 'hello world' },
  callback: 'show'
}).then(data => {
  console.log(data)
})

// server.js
let express = require('express')
let app = express()
app.get('/say', function(req, res) {
  let { wd, callback } = req.query
  console.log(wd) // Iloveyou
  console.log(callback) // show
  res.end(`${callback}('哈喽')`)
})
app.listen(3000)

CORS

服务端设置 Access-Control-Allow-Origin 就可以开启 CORS.该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源

postMessage

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递;
它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递

websocket

Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

// client.js
let socket = new WebSocket('ws://localhost:3000');
socket.onopen = function () {
  socket.send('heelo');//向服务器发送数据
}
socket.onmessage = function (e) {
  console.log(e.data);//接收服务器返回的数据
}
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
  ws.on('message', function (data) {
    console.log(data);
    ws.send('world')
  });
})
x

代理 proxy

  • nginx 反向代理
    通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录
// proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;
    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}

攻击的几种方式

XSS

在 html 文件或者 dom 中注入了 js 脚本, 在用户浏览页面时利用 js 脚本获取一些信息, 比如 cookie 信息, 监听用户行为,生成广告等

  • 存储型 XSS
因为一些漏铜,js代码被储存到了服务器,然后这段代码被发送到浏览器,
当用户访问页面时,会被 这段js代码获取一些隐私信息
  • 反射型
当用户给服务器发请求时, js代码被插入到请求中;
服务器返回信息时又把这段代码发回来, 常见的场景就是聊天室.
上次看一次直播的时候还遇到了, 哈哈:(
  • 基于 DOM 的 XSS
通过网络劫持, 在 html文件传输过程中修改了其中的内容
  • XSS 预防
// 前端过滤, 服务端设置头部信息和过滤
- 标签过滤
- 利用CSP
参考https://mp.weixin.qq.com/s/myLY1HKyzNiYV9OXhvAlPw

CSRF

利用用户的登录状态和服务器的漏洞, 伪造请求去进行一些操作

  • csrf 预防
- cookie 的 SameSite属性: strict, lax, none
- reffer 验证
- token 验证

http 劫持

http 在传输数据的过程中都是明文传输,这样可能被劫持篡改

  • http 预防
- 使用 https 加密数据
加密与解密
公钥与私钥
数字证书

数据类型检测

数据类型识别

typeof

  • 可以识别标准类型(将Null识别为'object') 
  • 不能识别具体的对象类型(Function除外)
console.log(typeof "huhua");// "string"
console.log(typeof 12);     // "number"
console.log(typeof true);   // "boolean"
console.log(typeof undefined);// "undefined"

console.log(typeof null);           // "object"
console.log(typeof {name: "huahu"});// "object"
console.log(typeof function(){});   // "function"

# 判断一个值是否为null类型: 直接和null进行恒等比较
xx === null

instanceof

左操作数是一个对象,右操作数是一个构造函数。
如果左侧的对象是右侧构造函数的实例对象,则表达式返回true;否则返回false

  • 可以识别内置对象类型、自定义类型及其父类型
  • 不能识别标准类型,会返回false
  • 不能识别undefined、null,会报错
console.log("huhua" instanceof String);             // false
console.log(new String("huhua") instanceof String)  // true
console.log(12 instanceof Number);                  // false
console.log(true instanceof Boolean);               // false
console.log(undefined instanceof Undefined);        // 报错
console.log(null instanceof Null);                  // 报错
console.log({name: "huhua"} instanceof Object);     // true
console.log(function(){} instanceof Function);      // true
console.log([] instanceof Array);                   // true
console.log(new Date instanceof Date);              // true
console.log(/\d/ instanceof RegExp);                // true
function Person(){};
console.log(new Person instanceof Person);          // true
console.log(new Person instanceof Object);          // true

constructor

实例对象的constructor属性指向其构造函数。如果是内置类型,则输出function 数据类型(){[native code]};如果是自定义类型,则输出function 数据类型(){}

  • 可以识别标准类型、内置对象类型及自定义类型
  • 不能识别undefined、null,会报错,因为它俩没有构造函数
console.log(("huhua").constructor);    // function String(){[native code]}
console.log((12).constructor);         // function Number(){[native code]}
console.log((true).constructor);       // function Boolean(){[native code]}
console.log((undefined).constructor);  // 报错
console.log((null).constructor);       // 报错
console.log(({name: "huhua"}).constructor); // function Object(){[native code]}

console.log((function(){}).constructor);// function Function(){[native code]}
console.log(([]).constructor);          // function Array(){[native code]}
console.log((new Date).constructor);    // function Date(){[native code]}
console.log((/\d/).constructor);        // function RegExp(){[native code]}
function Person(){};
console.log((new Person).constructor);  // function Person(){}

function getType(obj){
    var temp = obj.constructor.toString().toLowerCase();
    return temp.replace(/^function (\w+)\(\).+$/,'$1');
}

Object.prototype.toString

对象的类属性是一个字符串,用以表示对象的类型信息。javascript没有提供设置这个属性的方法,但有一种间接方法可以查询它。Object.prototype.toString()方法返回了如下格式的字符串:[object 数据类型]

  • 可以识别标准类型及内置对象类型
  • 不能识别自定义类型
function getType(obj){
  return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
}

写在最后

类型检测使用的时候看具体场景
通常在函数参数校验时, 一般使用Object.prototype.toString
特定指定了某一类对象时, 就需要使用 constructor 和 instanceof

浏览器环境下的微任务Promise

浏览器环境下的微任务

MutationObserver接口 和 Promise 在浏览器换下都会开启微任务,Promise 在开发中使用的较多,下面我们来了解下;
MutationObserver MDN;Promise MDN

Promise

先来看看MDN promise的使用;

引入 promise 的背景

js 的单线程运行机制决定了 js在处理耗时任务时采用异步回调的方式, 那么在处理异步任务时序的时候就会产生后续的任务依赖上一个任务的结果数据(结果还存在不确定性的问题), 我们通常的解决方式就回调嵌套回调, 这样又会导致我们的编码方式异常难以理解和维护...
为此, 引入了promise 来消除嵌套调用和以及任务的错误处理这些问题.

promise 的使用

function XFetch(request) {
  function executor(resolve, reject) {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', request.url, true)
      xhr.ontimeout = function (e) { reject(e) }
      xhr.onerror = function (e) { reject(e) }
      xhr.onreadystatechange = function () {
          if (this.readyState === 4) {
              if (this.status === 200) {
                  // resolve调用时, 会触发下面 promise.then 的回调 
                  resolve(this.responseText, this)
              } else {
                  let error = {
                      code: this.status,
                      response: this.response
                  }
                  // resolve调用时, 会触发下面 promise.catch 的回调 
                  reject(error, this)
              }
          }
      }
      xhr.send()
  }
  return new Promise(executor)
}

// 写法更线性,优美
// function request: to make a request config object
var x1 = XFetch(request('first url'))

var x2 = x1.then(value1 => {
    console.log(value1)
    return XFetch(request('second url'))
})

var x3 = x2.then(value2 => {
    console.log(value2)
    return XFetch(request('third url'))
})

x3.catch(error => {
    console.log(error)
})

promise 的消除回调嵌套的机理

  • 为什么要使用函数嵌套
一个主要原因是在发起任务请求时会带上回调函数,这样当任务处理结束之后,
下个任务就只能在回调函数中来处理了, 那我们试试将回调函数存起来怎么样?
  • Promise 实现了回调函数的延时绑定
// 业务函数
function executor(resolve, reject){
    setTimeout(() => {
        resolve(100)
    }, 1000)
}
// 回调函数
function onResolve(value){
    console.log(value)
}

// 创建Promise对象p1,并执行executor函数
let p1 = new Promise(executor)
// p1.then设置回调函数onResolve
p1.then(onResolve)
  • Promise 将回调函数 onResolve 的返回值穿透到最外层
    根据 onResolve 函数的传入值来决定创建什么类型的 Promise 任务,
    将创建好的 Promise 对象需要返回到最外层,这样就可以摆脱嵌套循环
// 业务函数
function executor(resolve, reject){
    setTimeout(() => {
        resolve(100)
    }, 1000)
}
let p11

// 回调函数
function onResolve(value){
    console.log(value)
    // 再创建一个 promise 实例
    let p = new Promise((resolve, reject) => {
        resolve(value + 100)
    })
    p11 = p
    console.log('实例inner', p)
    // 返回这个实例
    return p
}
// 创建Promise对象p1,并执行executor函数
let p1 = new Promise(executor)
// p1设置回调函数onResolve
let p2 = p1.then(onResolve)
p2.then(value2 => {
    console.log(value2)
    console.log('实例outer', p2)
})
  • Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到最后被catch
function executor(resolve, reject) {
    let rand = Math.random();
    console.log(1)
    console.log(rand)
    if (rand > 0.5)
        resolve()
    else
        reject()
}
var p0 = new Promise(executor);

var p1 = p0.then((value) => {
    console.log("succeed-1")
    return new Promise(executor)
})

var p3 = p1.then((value) => {
    console.log("succeed-2")
    return new Promise(executor)
})

var p4 = p3.then((value) => {
    console.log("succeed-3")
    return new Promise(executor)
})

p4.catch((error) => {
    console.log("error")
})
console.log(2)
// p0~p4。无论哪个对象里面抛出异常,都可以通过最后一个对象 p4.catch 来捕获异常,
// 通过这种方式可以将所有 Promise 对象的错误合并到一个函数来处理;
// 这样就解决了每个任务都需要单独处理异常的问题

// 怎么实现的呢?
// 判断then中没有第二个参数处理异常,
// 有则包装成promise.reject形式返回
// 没有则继续返回promise.reject的promise直到被处理
/*
 * 1.回调函数延迟执行
*/
function MyPromise(executor) {
    var _resolve = null
    var _reject = null
    // 模拟实现then
    this.then = function (resolve, reject) {
        _resolve = resolve
    };
    function resolve(data) {
        // 延迟执行
        setTimeout(()=>{
            _resolve(data)
        }, 0)
    }
    executor(resolve, null);
}

function func(resolve, reject) {
    resolve(1)
}
let p = new MyPromise(func)
p.then(data => {
    console.log(data)
})

小结

Promise的产生主要是为了解决:

  • 回调地狱问题
  • 从异步编程的代码不连续问题
  • 封装异步代码,让处理流程变的线性
  • 在复杂情况下引出了嵌套调用、任务不确定性两个问题
  • Promise通过实现函数的延时绑定回调函数的返回值穿透到最外面来解决上面问题

about html5

html5

这里主要是记录一些新增规范, 相关应用汇新开文章来写

新增特性

# 视频元素 video

# 音频元素 audio

# 元素拖拽属性 
  <img draggable="true" />

# canvas 图层绘制
  <canvas id="myCanvas" width="200" height="100"></canvas>

# 内联svg
  <svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="190">
    <polygon points="100,10 40,180 190,60 10,60 160,180"
    style="fill:pink;stroke:#444;stroke-width:3;fill-rule:evenodd;" />
  </svg>

# 地理定位 navigator.geolocation
# 本地存储 
  localStorage:没有时间限制的数据存储
  sessionStorage:针对一个 session 的数据存储
# 应用程序缓存 通过创建 cache manifest 文件,可以轻松地创建 web 应用的离线版本
  每个指定了 manifest 的页面在用户对其访问时都会被缓存
  manifest 文件需要配置正确的 MIME-type,即 "text/cache-manifest"。必须在 web 服务器上进行配置
# web worker 可以理解为新开一个线程去做其他事, 不会干扰主线程
# 服务器时间推送 
  EventSource 对象用于接收服务器发送事件通知
  服务器端事件流的语法是非常简单的。
  把 "Content-Type" 报头设置为 "text/event-stream"
  • svg vs canvas
SVG
SVG 是一种使用 XML 描述 2D 图形的语言
SVG 基于 XML,这意味着 SVG DOM 中的每个元素都是可用的,我们可以为某个元素附加 JavaScript 事件处理器
在 SVG 中,每个被绘制的图形均被视为对象,如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。

Canvas
Canvas 通过 JavaScript 来绘制 2D 图形,Canvas 是逐像素进行渲染的。
在 canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象。
Canvas SVG
依赖分辨率 不依赖分辨率
不支持事件处理器 支持事件处理器
弱的文本渲染能力 最适合带有大型渲染区域的应用程序(比如谷歌地图)
能够以.png 或 .jpg 格式保存结果图像 复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)
最适合图像密集型的游戏,其中的许多对象会被频繁重绘 不适合游戏应用

性能优化(存储方面)

性能优化(存储方面)

个人理解, 存储方面的优化其实也是为了减少 http 请求, 节省网络传输带来耗时影响...
把我们需要的文件资源缓存在本地, 或者缓存在代理服务器, 源服务器等. 都是为了优化资源访问的过程...这里我们单纯的从前端能操作的存储来讲, 服务端的手段会介绍下,毕竟全栈也是我们的目标,ememm...

1.浏览器缓存(浏览器)

浏览器缓存机制有几个方面,它们按照获取资源时请求的优先级依次排列如下
Memory Cache/Disk Cache, Service Worker Cache, HTTP Cache, Push Cache(http2).

HTTP Cache(重点)

我们可以根据不同的资源缓存需求去设置的缓存策略, 当然事先将资源进行分类, 然后才去设定对应的缓存策略;
我们可以把http缓存策略按阶段分为缓存存储策略, 缓存过期策略, 缓存协商对比策略三个阶段来理解.
先看下关于缓存头的几个字段, 优先级 http1.1字段高

version header key desc 存储策略 过期策略 协商对比策略
HTTP1.0 Pragma 指定缓存机制: no-cache
HTTP1.1 Cache-Control 指定缓存机制
HTTP1.0 Expires 指定缓存的过期时间
HTTP1.0 res: Last-Modified 资源最后一次的修改时间
HTTP1.0 req: If-Modified-Since 缓存校验字段,值为资源最后一次的修改时间, 即上次收到的Last-Modified值
HTTP1.1 res: ETag 唯一标识请求资源的字符串
HTTP1.1 req: If-None-Match 缓存校验字段, 值为唯一标识请求资源的字符串, 即上次收到的ETag值

缓存存储策略

用来确定 Http 响应内容是否可以被客户端缓存,以及可以被哪些客户端缓存;
这个策略的作用只有一个, 用于决定 Http 响应内容是否可缓存到客户端.

  • cache-control的值设置
public:	资源将被客户端和代理服务器缓存
private:	资源仅被客户端缓存, 代理服务器不缓存
no-store	请求和响应都不缓存
no-cache	相当于max-age:0,must-revalidate即资源被缓存,但是缓存立刻过期, 同时下次访问时强制验证资源有效性
max-age	缓存资源, 但是在指定时间(单位为秒)后缓存过期
s-maxage	同上, 依赖public设置, 覆盖max-age, 且只在代理服务器上有效.
max-stale	指定时间内, 即使缓存过时, 资源依然有效
min-fresh	缓存的资源至少要保持指定时间的新鲜期
must-revalidation / proxy-revalidation	如果缓存失效, 强制重新向服务器(或代理)发起验证(因为max-stale等字段可能改变缓存的失效时间)	
only-if-cached	仅仅返回已经缓存的资源, 不访问网络, 若无缓存则返回504	
no-transform	强制要求代理服务器不要对资源进行转换, 禁止代理服务器对 Content-Encoding, Content-Range, Content-Type字段的修改(因此代理的gzip压缩将不被允许)
  • Pragma
用来兼容 http1.0, 其值为 no-cache

缓存过期策略

客户端用来确认存储在本地的缓存数据是否已过期,进而决定是否要发请求到服务端获取数据;
这个策略的作用也只有一个,那就是决定客户端是否可直接从本地缓存数据中加载数据并展示(否则就发请求到服务端获取)

通过设置:
Expires: Wed, 29 Apr 2020 07:45:47 GMT 缓存到期时间
Cache-Control: max-age=6000 缓存资源, 但是在指定时间6000s(单位为秒)后缓存过期
// Cache-Control优先
假设请求资源于5月1日缓存, 且在5月7日过期(时间是相对于请求的时间)
这期间都会命中强缓存
  • 强缓存
    对于常规请求,只要存在该资源的缓存,且Cache-Control:max-age或者expires没有过期,
    那么就能命中强缓存;浏览器便不会向服务器发送请求, 而是直接读取缓存.
    Chrome下的现象是 200 OK (from disk cache) 或者 200 OK (from memory cache)

缓存协商对比策略

将缓存在客户端的数据标识发往服务端,服务端通过标识来判断客户端 缓存数据是否仍有效,进而决定是否要重发数据;
客户端检测到数据过期或浏览器刷新后,往往会重新发起一个 http 请求到服务器,服务器此时并不急于返回数据,而是看请求头有没有带标识( If-Modified-Since、If-None-Match)过来,如果判断标识仍然有效,则返回304告诉客户端取本地缓存数据来用即可(这里要注意的是你必须要在首次响应时输出相应的头信息(Last-Modified、ETag)到客户端.

协商缓存
缓存过期后, 继续请求该资源, 对于现代浏览器, 拥有如下两种做法:

  • Etag/If-None-Match(优先级高)
根据上次响应中的ETag_value, 自动往request header中添加If-None-Match字段. 
服务器收到请求后, 拿If-None-Match字段的值与资源的ETag值进行比较, 
若相同, 则命中协商缓存, 返回304响应.
  • Last-Modified/If-Modified-Since
根据上次响应中的Last-Modified_value, 自动往request header中添加If-Modified-Since字段. 
服务器收到请求后, 拿If-Modified-Since字段的值与资源的Last-Modified值进行比较, 
若相同, 则命中协商缓存, 返回304响应

# 缓存协商时间计算
// 利用response: Date 和 Last-Modified返回的时间计算出缓存过期日期
缓存过期日期 = (客户端日期new Date) + (Date_value - Last-Modified_value) / 10
# 所以, 当我们更改了本地时间后可能会出现一些问题; http1.1引入Etag解决了

// 然后缓存过期后, 会再次请求服务端,并携带上 Last-Modified 指定的时间去服务器对比
并根据服务端的响应状态决定是否要从本地加载缓存数据.

说了这么多, 怎么设置这些头部呢?

只能说在客户端(浏览器下)能做的有限, 基本都是在服务端(Nginx)设置的...emm; 所以前端必须学server方面的知识啊!!!是不是有点崩溃呢? 快起来我还能学...
单纯通过前端 html 设置, 也需要浏览器支持,基本没啥用

<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>

Memory Cache/Disk Cache

MemoryCache,是指存在内存中的缓存.从优先级上来说,它是浏览器最先尝试去命中的一种缓存.从效率上来说,它是响应速度最快的一种缓存; 浏览器有一套自己的规则, 会将较小的文件 base64图片,css,js 等放进去, 内存的分配是有限的, 如果文件过大或者分配内存不足, 文件会放到 Disk 磁盘中...

Service Worker Cache(PWA的基础)

Service Worker MDN
Service Worker 是一种独立于主线程之外的 Javascript 线程.它脱离于浏览器窗体,因此无法直接访问 DOM.这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能.我们借助 Service worker 实现的离线缓存就称为 Service Worker Cache.

Service Worker 的生命周期包括 install、active、working 三个阶段.一旦 Service Worker 被 install,它将始终存在,只会在 active 与 working 之间切换,除非我们主动终止它.这是它可以用来实现离线存储的重要先决条件.

  • Service Worker实现离线缓存
// mian.js
if ('serviceWorker' in navigator) {
  window.navigator.serviceWorker.register('/demo.js').then(
   function () {
      console.log('注册成功')
    }).catch(err => {
      console.error("注册失败")
    })
} else {
  console.error("The current browser doesn't support service workers")
}
// demo.js: 缓存的文件分别是 test.html,test.css 和 test.js
// Service Worker会监听 install事件,我们在其对应的回调里可以实现初始化的逻辑  
self.addEventListener('install', event => {
  event.waitUntil(
    // 考虑到缓存也需要更新,open内传入的参数为缓存的版本号
    caches.open('test-v1').then(cache => {
      return cache.addAll([
        // 此处传入指定的需缓存的文件名
        '/test.html',
        '/test.css',
        '/test.js'
      ])
    })
  )
})

// Service Worker会监听所有的网络请求,网络请求的产生触发的是fetch事件,
// 我们可以在其对应的监听函数中实现对请求的拦截,进而判断是否有对应到该请求的缓存,
// 实现从Service Worker中取到缓存的目的
// Server Worker 对协议是有要求的,必须以 https 协议为前提
self.addEventListener('fetch', event => {
  event.respondWith(
    // 尝试匹配该请求对应的缓存值
    caches.match(event.request).then(res => {
      // 如果匹配到了,调用Server Worker缓存
      if (res) {
        return res;
      }
      // 如果没匹配到,向服务端发起这个资源请求
      return fetch(event.request).then(response => {
        if (!response || response.status !== 200) {
          return response;
        }
        // 请求成功的话,将请求缓存起来.
        caches.open('test-v1').then(function(cache) {
          cache.put(event.request, response);
        });
        return response.clone();
      });
    })
  );
});

Push Cache

了解不多, 参考此篇文章https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/

  • 一些特性
- Push Cache 是缓存的最后一道防线.
浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache.
- Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放.
- 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache

2.浏览器本地存储

这个就是我们前端可以操作的部分了...

cookie, localStorage. sessionStorage

参考我之前写的

IndexedDB

IndexedDB MDN
这里也不做细讲了, 参考张鑫旭大佬的文章https://www.zhangxinxu.com/wordpress/2017/07/html5-indexeddb-js-example/

参考

http header
Service Worker MDN
http2 push
IndexedDB MDN
web worker
IndexedDB Demo

函数类型(参数)

函数类型(参数:arguments对象)

js 函数不限定传递进来多少个参数, 以及参数的数据类型,甚至可以不传参数

arguments对象

函数参数的一些情况, 实参&形参

形参类型不指定,随意传,所以要在函数内部检测
函数调用不对实参进行类型检查, 个数也不定
function add(x) {
  return x+1;
}
# 不同的传递和调用结果也不同, 违背了单一原则
console.log(add(1));   // 2
console.log(add('1')); // '11'
console.log(add());    // NaN
console.log(add(1,2)); // 2
  • 形参同名情况
在非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参
function add(x,x,x){
    return x ;
}
console.log(add(1,2,3)); // 3

而在严格模式下,出现同名形参会抛出语法错误
  • 参数个数
1.实参 < 形参: 剩下的形参都将设置为undefined值
function add(x,y){
  console.log(x,y); 
}
add(1); // 1 undefined

2.实参 > 形参: 剩下的实参没有办法直接获得,通过arguments对象
函数参数在内部用一个数组表示。
函数接收到的始终都是这个数组,而不关心数组中包含哪些参数。
在函数体内可以通过arguments对象来访问这个参数数组,
从而获取传递给函数的每一个参数。
arguments对象并不是Array的实例,它是一个类数组对象
function add(x){
    console.log(arguments[0],arguments[1],arguments[2]) // 1 2 3
    console.log(arguments.length) // 3
    return x+1;
}
add(1,2,3);  // 2

3. 实参 = 形参: 参数同步,arguments对象的值和对应形参的值保持同步
function test(num1,num2){
    // 形参与 arguments的命名空间是独立的, 但值是同步的
    // 严格模式下都是独立的
    console.log(num1,arguments[0]); // 1 1
    arguments[0] = 2;
    console.log(num1,arguments[0]); // 2 2
    num1 = 10;
    console.log(num1,arguments[0]); // 10 10
}
test(1);

注意: 当形参并没有对应的实参时,arguments对象的值与形参的值并不对应
function test(num1,num2){
    console.log(num1,arguments[0]);// undefined,undefined
    num1 = 10;
    arguments[0] = 5;
    console.log(num1,arguments[0]);// 10, 5
}
test();
  • 对象参数
当行参定义的太多时, 往往很麻烦(顺序不能乱), 可以传一个对象进来
function addAverage(numObj) {
  return (numObj.x + numObj.y) / numObj.keys().length
}

arguments内部属性

callee

arguments对象有一个名为callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数, 严格模式下,访问这个属性会抛出TypeError错误;
一般用不到

// 递归时使用
function factorial(num){
    if(num <=1){
        return 1;
    }else{
        return num* arguments.callee(num-1);
    }
}    
console.log(factorial(5)); //120

caller

  • 函数的 caller属性

函数的caller属性保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值是null

function outer(){
    inner();
}
function inner(){
    console.log(inner.caller); 
}
outer(); // ƒ outer(){ inner();}
inner(); // null
  • arguments对象的caller
    该属性始终是undefined,定义这个属性是为了分清arguments.caller和函数的caller属性

通过参数实现函数重载

同一个函数根据传入的参数不同具有不同的功能.
可以通过检查传入函数中参数的类型和数量并作出不同的反应,来模仿方法的重载

function doAdd(){
    if(arguments.length == 1){
        alert(arguments[0] + 10);
    }else if(arguments.length == 2){
        alert(arguments[0] + arguments[1]);
    }
}
doAdd(10);   // 20
doAdd(30,20);// 50

参数传递(值传递,地址传递)

  • 参数为基础类型
# 值传递
var a = 1
function fn(o) {
    o = 3;
    console.log(o) // 3
}
fn(a);
console.log(a) // 1
  • 参数为引用类型(拷贝地址)
# 对函数形参的赋值,不会影响实参的值
var obj = {x : 1};
function fn(o) {
    o = 100; // 此时相当于一个局部变量
    console.log(o) // 100
}
fn(obj);
console.log(obj.x); // 仍然是1, obj并未被修改为100.

# 修改形参对象的属性值,也会影响到实参的属性值
# 会将外部对象内存中的地址复制给一个局部变量
var obj = {x : 1};
function fn(o) {
    o.x = 3;
}
fn(obj);
console.log(obj.x); // 3, 被修改了!

故调用函数传参时,函数接受对象实参引用的副本(虽是副本,引用的对象是相同的)

引用数据类型的扩展

引用数据类型的扩展

这里总结一些关于对象数据类型的扩展...

Object扩展

  • 规则
简洁表示法: 直接写入变量和函数作为对象的属性和方法({ prop, method() {} })
属性名表达式: 字面量定义对象时使用[]定义键([prop],不能与上同时使用)
方法的name属性: 返回方法函数名
  取值函数(getter)和存值函数(setter): get/set 函数名(属性的描述对象在get和set上)
  bind返回的函数: bound 函数名
  Function构造函数返回的函数实例: anonymous

属性的可枚举性和遍历: 描述对象的enumerable
super关键字: 指向当前对象的原型对象(只能用在对象的简写方法中method() {})
Object.is(): 对比两值是否相等
Object.assign(): 合并对象(浅拷贝),返回原对象
Object.getPrototypeOf(): 返回对象的原型对象
Object.setPrototypeOf(): 设置对象的原型对象
__proto__: 返回或设置对象的原型对象(不常用)
  • 对象的属性
描述: 
  自身、可继承、可枚举、非枚举、Symbol

遍历
  for-in: 遍历对象自身可继承可枚举属性
  Object.keys(): 返回对象自身可枚举属性键组成的数组
  Object.getOwnPropertyNames(): 返回对象自身非Symbol属性键组成的数组
  Object.getOwnPropertySymbols(): 返回对象自身Symbol属性键组成的数组
  Reflect.ownKeys(): 返回对象自身全部属性键组成的数组

规则
  首先遍历所有数值键,按照数值升序排列
  其次遍历所有字符串键,按照加入时间升序排列
  最后遍历所有Symbol键,按照加入时间升序排列

Array 扩展

  • 规则
扩展运算符(...): 转换数组为用逗号分隔的参数序列([...arr],相当于rest/spread参数的逆运算)
Array.from(): 转换具有Iterator接口的数据结构为真正数组,返回新数组
  类数组对象: 包含length的对象、Arguments对象、NodeList对象
  可遍历对象: String、Set结构、Map结构、Generator函数

Array.of(): 转换一组值为真正数组,返回新数组
copyWithin(): 把指定位置的成员复制到其他位置,返回原数组
find(): 返回第一个符合条件的成员
findIndex(): 返回第一个符合条件的成员索引值
fill(): 根据指定值填充整个数组,返回原数组
keys(): 返回以索引值为遍历器的对象
values(): 返回以属性值为遍历器的对象
entries(): 返回以索引值和属性值为遍历器的对象
数组空位: ES6明确将数组空位转为undefined(空位处理规不一,建议避免出现)
flat(): 扁平化数组,如果原数组有空位,flat()方法会跳过空位
  • 应用
克隆数组: const arr = [...arr1]
合并数组: const arr = [...arr1, ...arr2]
拼接数组: arr.push(...arr1)
代替apply: Math.max.apply(null, [x, y]) => Math.max(...[x, y])
转换字符串为数组: [..."hello"]
转换类数组对象为数组: [...Arguments, ...NodeList]
转换可遍历对象为数组: [...String, ...Set, ...Map, ...Generator]
与数组解构赋值结合: const [x, ...rest] = [1, 2, 3]
计算Unicode字符长度: Array.from("hello").length => [..."hello"].length

Function 扩展

默认值参数: 为函数参数指定默认值

形式: function Func(x = 1, y = 2) {}
参数赋值: 惰性求值(函数调用后才求值)
参数位置: 尾参数
参数作用域: 函数作用域
声明方式: 默认声明,不能用const或let再次声明
length: 返回没有指定默认值的参数个数
与解构赋值默认值结合: function Func({ x = 1, y = 2 } = {}) {}
应用: 
  1.指定某个参数不得省略,省略即抛出错误: function Func(x = throwMissing()) {}
  2.将参数默认值设为undefined,表明此参数可省略: Func(undefined, 1)

参数扩展符: 返回函数多余参数

语法: function Func(...rest) {}
形式: 以数组的形式存在,之后不能再有其他参数
作用: 代替Arguments对象
length: 返回没有指定默认值的参数个数但不包括rest参数

name属性: 返回函数的函数名

将匿名函数赋值给变量: 空字符串(ES5)、变量名(ES6)
将具名函数赋值给变量: 函数名(ES5和ES6)
bind返回的函数: bound 函数名(ES5和ES6)
Function构造函数返回的函数实例: anonymous(ES5和ES6)

箭头函数(=>): 函数简写

无参数: () => {}
单个参数: x => {}
多个参数: (x, y) => {}
解构参数: ({x, y}) => {}
嵌套使用: 部署管道机制

this指向固定化:
  并非因为内部有绑定this的机制,而是根本没有自己的this,
  导致内部的this就是外层代码块的this,因为没有this,因此不能用作构造函数

// 定义对象的方法,且该方法内部包括this,
// 因为对象不构成单独的作用域,导致say箭头函数定义时的作用域就是全局作用域
const person = {
  year: 9,
  say: () => {
    this.year--
  }
}

// 需要动态this的时候,也不应使用箭头函数
// 代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this是全局对象
var btn = document.getElementById('btn');
btn.addEventListener('click', () => {
  this.classList.add('on');
});

尾调用

定义: 某个函数的最后一步是调用另一个函数
形式: function f(x) { return g(x); }
尾调用优化: 只保留内层函数的调用帧,删除外层函数的调用帧,减少内存开销

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);
// 调用g之后,函数f就结束了,所以执行到最后一步,
// 完全可以删除f(x)的调用帧,只保留g(3)的调用帧。
// 不过, 只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,
// 否则就无法进行“尾调用优化”。

尾递归

定义: 函数尾调用自身
作用: 只要使用尾递归就不会发生栈溢出,相对节省内存
实现: 把所有用到的内部变量改写成函数的参数并使用参数默认值,
让下一个函数运行时,保证之前的函数销毁,不再占用栈空间

// 这样会保留一个调用记录,复杂度 O(1)
function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}
factorial(5, 1) // 120

function Fibonacci (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) { return ac2 };
  return Fibonacci (n - 1, ac2, ac1 + ac2);
}

工程化(一)之模块化规范

传统模式下的模块

  • 函数即模块
<script>
  function module() {
    // doSomething
  }
</script>
<!-- 利用全局变量, 即模块依赖不清晰 -->
  • 对象即模块
<script>
  var module = {
    id: 0,
    say: function () {}
  }
</script>
<!-- 也是全局变量对象, 且成员属性会被修改 -->
  • 自执行函数(函数作用域实现私有成员)
<script>
  var module = (function() {
    var id = 0; // 私有变量
    var hello = function () {};
    var world = function () {};
    return {
      hello: hello,
      world: world
    }
  })();
</script>
<!-- 全局变量, 但实现了部分成员私有, 其他成员共享 -->
<script>
module.hello()
module.world()
</script>

CommonJS 规范

node 环境下每个文件都有自己的模块作用域, 变量都属于模块

  • 导出模块
// 1.整体内容导出
// module.exports 初始值为一个空对象 {}, 由 Module 系统创建
module.exports = {
  id: 1,
  say: () => {}
}

// 2.exports导出
// exports 也是一个模块下的变量, 指向module.exports的引用, 指向同一块内存
exports.id = 1
exports.say = () => {}

// 模块导出的始终是 module.exports 对象
exports = module.exports = { id: 2, say: () => {} }
  • 导入模块
// node 下全局方法 require
var module1 = require('./module1.js') // 导入本地文件模块, 相对路径查找
var path = require('path')            // Node自带的模块, 得出相对路径后查找
var lodash = require('lodash')        // node_modules目录下的模块, 通过文件名逐级往上查找
  • 模块加载特点
1.所有代码都运行在模块作用域,不会污染全局作用域

2.模块是同步加载的,即只有加载完成,才能执行后面的操作

3.模块在首次执行后就会缓存,再次加载只返回缓存结果,如果想要再次执行,可清除缓存

4.模块加载的是值的拷贝, 同时 require('./moudle.js') 多次都只是一个对象的拷贝;
CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值

AMD 规范(require.js)

异步加载
浏览器环境下使用
定义模块: define(fn())
导入: require(['模块名',...], cb())
存在缓存

ESModule 规范

js 语言环境下使用, 语言层面的规范;
需要 babel工具 进行语法编译

export: 规定模块对外接口

默认导出:export default Person(当导入时可指定模块任意名称,无需知晓内部真实名称)
单独导出:export const name = "huhua"
按需导出:export { age, name, sex }(推荐)
改名导出:export { name as newName }

import:导入模块内部功能

输入的模块变量是不可重新赋值的,它只是个可读引用,可以改写属性

默认导入:import Person from "person"
整体导入:import * as Person from "person" // Module对象
按需导入:import { age, name, sex } from "person"
改名导入:import { name as newName } from "person"
自执导入:import "./person"
复合导入:import Person, { name } from "person"

复合模式

export命令和import命令结合在一起写成一行,变量实质没有被导入当前模块,相当于对外转发接口,导致当前模块无法直接使用其导入变量

默认导入导出:export { default } from "person"
整体导入导出:export * from "person"
按需导入导出:export { age, name, sex } from "person"
改名导入导出:export { name as newName } from "person"
具名改默认导入导出:export { name as default } from "person"
默认改具名导入导出:export { default as name } from "person"

CommonJS与ESModule规范的区别

  • CommonJS模块是运行时加载,ES6 Modules是编译时输出接口
    CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。
    而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成

  • CommonJS输出是值的拷贝; ES6 Modules输出的是值的引用,被输出模块的内部的改变会影响引用的改变

  • CommonJs导入的模块路径可以是一个表达式,因为它使用的是require()方法;而ES6 Modules只能是字符串

  • CommonJS this指向当前模块,ES6 Modules this指向undefined
    ES6 Modules中没有这些顶层变量:arguments、require、module、exports、__filename、__dirname

webpack打包工具

为了让同一套代码在各个运行环境规范下都能适用

  • 模块解析
将各个模块维护到一个list中
/* module code */ 样板代码解析 replace
  • 模块加载
同步加载: 直接加载文件
异步加载: jsonp + promise

null 和 undefined

undefined & null

由于历史原因, js作者设计了 null 和 undefined 来分别表示 "无"这个值; null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN; 当然它俩都是原始类型.

undefined

Undefined类型只有一个值,就是undefined。当声明的变量未初始化时,该变量的默认值是undefined。所以一般地,undefined表示变量没有初始化

  • undefined出现的场景
1.声明为赋值的变量
2.对象不存在的属性
3.无返回值的函数的执行结果
4.函数的参数没有传入
5.void(0)
  • 类型检测
console.log(typeof undefined);//'undefined'
console.log(typeof 'undefined');//'string'
undefined不是一个关键字,其在IE8-浏览器中会被重写,在高版本函数作用域中也会被重写;所以可以用void 0 来替换undefined
  • 类型转换
Boolean(undefined) //  false
Number(undefined)  //  NaN
String(undefined)  // 'undefined'    

null

Null类型只有一个值,就是null。null是javascript语言的关键字,它表示一个特殊值,常用来描述"空值"

逻辑角度看,null值表示一个空对象指针

不同的对象在底层都表示为二进制,在javascript中二进制前三位都为0会被判断为object类型,null的二进制表示是全0,所以执行typeof时返回'object'

null和undefined是不同的,但它们都表示"值的空缺",null表示"空值",undefined表示"未定义"。两者往往可以互换。判断相等运算符==认为两者是相等的

  • null出现场景
1.不存在的 DOM 对象节点
console.log(document.getElementById('node')) // null
2.自定义变量
var timeId = null
  • 类型检测
1.全等符
console.log(null === null);// true
2.万能方法
Object.prototype.toString.call(null).slice(8, -1) // 'Null'
  • 类型转换
Boolean(null) //  false
Number(null)  //  0
String(null)  // 'null'    

变量&解构赋值

1.变量声明

let,const用于声明变量,用来替代老语法的var关键字,与var不同的是,let/const会创建一个块级作用域{}

  • 注意事项
var在全局代码中执行, window 下
const和let只能在代码块中执行, script词法作用域下

const声明常量后必须立马赋值
let声明变量后可立马赋值或使用时赋值

不允许重复声明
未定义就使用会报错:const和let不存在变量提升
暂时性死区: 在代码块内使用const和let声明变量之前,该变量都不可用
  • 变量声明方式
var、const、let、function、class、import

2.解构赋值

// 字符串
const [a, b, c, d, e] = "hello"
// 数值, 先转包装对象; v -> f()
const { valueOf: v } = 123 
// 布尔, 先转包装对象
const { valueOf: v } = false 
// 对象
const { x, y } = { x: 1, y: 2 }
const { x, y = 2 } = { x: 1 } // y 为默认值
const { x, y: z } = { x: 1, y: 2 } // z 为改名变量
// 数组
const [x, y] = [1, 2]
const [x, y = 2] = [1] // 默认 y
// 函数参数
function fn([x = 0, y = 1] = []) {
  console.log(x, y)
} // 数组参数

function fn({ x = 0, y = 1 } = {}) {
  console.log(x, y)
} // 对象参数
  • 应用场景
交换变量值: [x, y] = [y, x]
返回函数多个值: const [x, y, z] = fn()
定义函数参数: fn([1, 2])
提取JSON数据: const { name, version } = packageJson
定义函数参数默认值: function fn({ x = 1, y = 2 } = {}) {}
遍历Map结构: for (let [k, v] of Map) {}
输入模块指定属性和方法: const { readFile, writeFile } = require("fs")
  • 注意事项
匹配模式: 只要等号两边的模式相同,左边的变量就会被赋予对应的值
解构赋值规则: 只要等号右边的值不是对象或数组,就先将其转为对象
解构默认值生效条件: 属性值严格等于undefined
解构遵循匹配模式
解构不成功时变量的值等于undefined
undefined和null无法转为对象,因此无法进行解构

函数类型(一些扩展知识)

函数类型(一些扩展知识)

函数节流和防抖

在一些DOM事件操作场景中, 函数有可能被非常频繁地调用而造成大的性能问题,此时就需要节流和防抖

常见场景

  1. mousemove事件
    如果要实现一个拖拽功能,需要一路监听 mousemove 事件,在回调中获取元素当前位置,然后重置 dom 的位置来进行样式改变。如果不加以控制,每移动一定像素而触发的回调数量非常惊人,回调中又伴随着 DOM 操作,继而引发浏览器的重排与重绘,性能差的浏览器可能就会直接假死。

  2. window.onresize事件
    为window对象绑定了resize事件,当浏览器窗口大小被拖动而改变的时候,这个事件触发的频率非常之高。如果在window.onresize事件函数里有一些跟DOM节点相关的操作,而跟DOM节点相关的操作往往是非常消耗性能的,这时候浏览器可能就会吃不消而造成卡顿现象

  3. mousedown/keydown 事件(触发就请求接口)

4.搜索联想(keyup事件)

  1. 监听滚动事件判断是否到页面底部自动加载更多(scroll事件)

对于这些情况的解决方案就是函数节流(throttle)或函数去抖(debounce, 本质上其实就是限制某一个方法的频繁触发, 限制频率

函数防抖

函数防抖的原理是将即将被执行的函数用setTimeout延迟一段时间执行,不让其连续触发

function debounce(fn, delay = 1000) {
  let self = fn       // 函数引用
  let timeId = null   // 定时器id
  let isFirst = true  // 是否首次调用
  return function() {
    let args = arguments
    let _this = this
    // 是否首次调用
    if (isFirst) {
      self.apply(_this, arguments)
      isFirst = false
      return
    }
    // 是否延迟执行中
    if (timeId) {
      return
    }
    // 延迟执行
    timeId = setTimeout(() => {
      clearTimeout(timeId)
      timeId = null
      self.apply(_this, args)
    }, delay)
  }
}
window.onresize = debounce(function(){ 
  console.log('hello debounce')
});

函数节流

函数节流使得连续的函数执行,变为固定时间段间断地执行,稀释函数执行的频次

触发事件时,设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,
直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器
function throttle(fn, gap=1000) {
  let timeId, args, _this
  return function() {
    _this = this
    args = arguments
    if (!timeId) {
      timeId = setTimeout(() => {
        timeId = null
        fn.apply(_this, args)
      }, gap)
    }
  }
}
window.onresize = throttle(function(){ 
  console.log('hello throttle')
});

惰性函数(即函数重写)

概念: 函数执行的分支只会在函数第一次调用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了

函数重写

在函数内部重新定义函数,函数变量的引用更换

function a() {
  console.log('a1');
  a = function() { // 引用新的函数体
    console.log('a2');
 }
}
a() // a1
a() // a2

使用场景

  • 浏览器能力检测
# 在处理浏览器兼容时,我们通常会判断某些属性来书写不同的函数体
# 想想它有什么弊端? 我们每次调用 addEvent 都会进行 if 判断,
# 如何让它只在第一次调用时判断, 之后的调用不用再判断呢? 重写...
function addEvent(type, element, func) {
    if (element.addEventListener) {
        element.addEventListener(type, func, false);
    }
    else if(element.attachEvent){ // IE
        element.attachEvent('on' + type, func);
    }
    else{
        element['on' + type] = func;
    }
}

// 函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数
function addEvent(type, element, func) {
    if (element.addEventListener) {
        addEvent = function (type, element, func) {
            element.addEventListener(type, func, false);
        }
    }
    else if(element.attachEvent){
        addEvent = function (type, element, func) {
            element.attachEvent('on' + type, func);
        }
    }
    else{
        addEvent = function (type, element, func) {
            element['on' + type] = func;
        }
    }
    return addEvent(type, element, func);
}

// 在声明函数时就指定适当的函数, 这样也只用判断一次
var addEvent = (function () {
    if (document.addEventListener) {
        return function (type, element, func) {
            element.addEventListener(type, func, false);
        }
    }
    else if (document.attachEvent) {
        return function (type, element, func) {
            element.attachEvent('on' + type, func);
        }
    }
    else {
        return function (type, element, func) {
            element['on' + type] = func;
        }
    }
})()

函数式编程

函数式编程指南
一种编程范式; 我觉得挺有意思的,平时也只是在数组,对象操作那一块用的比较多而已,可以学一些其中的**. 到时候单独写一个主题来总结...

  • 函数缓存(将函数上次的计算结果缓存起来)
// 返回f()的带有记忆功能的版本
function memorize(f) {
    var cache = {}; // 将值保存到闭包内
    return function() {
        // 将实参转换为字符串形式,并将其用做缓存的键
        var key = arguments.length + Array.prototype.join.call(arguments, ",");
        if(key in cache){
            return cache[key];
        }else{
            return cache[key] = f.apply(this,arguments);
        }
    }
}
memorize()函数创建一个新的对象,这个对象被当作缓存的宿主,并赋值给一个局部变量,
因此对于返回的函数来说它是私有的。
所返回的函数将它的实参数组转换成字符串,并将字符串用做缓存对象的属性名。
如果在缓存中存在这个值,则直接返回它;
否则,就调用既定的函数对实参进行计算,将计算结果缓存起来并返回
var factorial = memorize(function(n){
    return (n<=1) ? 1 : n*factorial(n-1);
});
factorial(5); // 120
  • 单参函数连续调用(返回自己)
# add(1)(2)(3)()的另一种实现
function add(n){
    return function f(m){  
        if(m === undefined){
            return n;
        }else{
            n += m;
            return f;
        }
    }
}

nginx指令及错误页配置

nginx 常用指令

注意事先配置环境变量 PATH

  • 启动
    nginx

  • 查看是否启动
    ps aux | grep nginx

  • 停止
    nginx -s stop
    nginx -s quit
    killall nginx
    systemctl stop nginx.service

  • 重启
    systemctl restart nginx.service

  • 重载配置文件
    当我们修改了配置文件之后
    nginx -t
    nigin -s reload

  • 查看端口号
    netstat -tlnp

自定义错误页和访问设置

  • 自定义错误页
error_page   500 502 503 504  /50x.html;
error_page指令用于自定义错误页面  
500,502,503,504 这些就是HTTP中最常见的错误代码  
/50.html 用于表示当发生上述指定的任意一个错误的时候,都是用网站根目录下的/50.html文件进行处理。
error_page 404  /404_error.html;
/404_error.html 也可以换成一个地址
  • 访问设置
    服务器只允许特定主机访问
    比如内部OA系统,或者应用的管理后台系统,更或者是某些应用接口
location / {
    deny   禁止访问的IP;
    allow  允许访问的IP;
}
  • 详细的访问权限设置
    通过正则,精确匹配等来设置 location
# 精确匹配 =
location =/img{
    allow all;
}
location =/admin{
    deny all;
}

# 正则表达式
location ~\.php$ {
  deny all;
}

css 的一些基础

css

css 定义了如何去描述 html 元素的展示
引入css样式是为了解决 结构和样式 分离的问题
css 文档教程

基础语法

  • css引入
1.外部引入
<link rel="stylesheet" href="sheet1.css">

2.内部样式 style 元素中书写
<style>
@import url(sheet1.css); // 可以在顶部加载外部样式表
body{
  background-color: red;
}    
</style>

3.行间样式
<body style="background-color: red;"></body>  
  • css选择器
通配, 元素, 类, id, 属性(具体[class="b"]和部分[class ^="b"])
分组, 后代, 相邻(+, ~), 
伪类:hover, 伪元素:before 
  • 特性
三大特性: 继承上层属性, 层叠覆盖, 优先级排序
按权重排
  • 单位
# em 
em表示元素的font-size属性的计算值,如果用于font-size属性本身,相对于父元素的font-size;若用于其他属性,相对于本身元素的font-size的倍数

# rem
rem是相对于根元素html的font-size属性的计算值

# 视口单位
vw:   布局视口宽度的 1/100
vh:   布局视口高度的 1/100
vmin: 布局视口高度和宽度之间的最小值的 1/100
vmax: 布局视口高度和宽度之间的最大值的 1/100
  • calc 计算
它允许使用+、-、*、/这四种运算符,可以混合使用%、px、em、rem等单位进行计算
width: calc(100%/3 - 2*1em - 2*1px);

CSS选择器的解析顺序是从右到左?

如果正向解析,例如「div div p em」,首先要检查当前元素到 html 的整条路径,找到最上层的 div,再往下找,如果遇到不匹配就必须回到最上层那个 div,往下再去匹配选择器中的第一个 div,回溯若干次才能确定匹配与否,效率很低

逆向匹配则不同,如果当前的 DOM 元素是 div,而不是 selector 最后的 em,那只要一步就能排除。只有在匹配时,才会不断向上找父节点进行验证 

正向解析是在试错,而逆向匹配则是在挑选正确的元素。因为匹配的情况远远低于不匹配的情况,所以逆向匹配带来的优势是巨大的

css 变量

// 声明 && 使用
<style>
.box{    
    --base-size:10px;
    width: calc(var(--base-size) * 10);
    height: calc(var(--base-size) * 10);
    padding: calc(var(--base-size) * 2);
    margin: calc(var(--base-size) * 1);
    background-color: red;
}
/* 媒体查询 */
@media screen and (max-width:600px) {
    .box{
        --base-size:5px;    
    }
}
</style>

css规范

// 全局样式重置统一
normalize.css
reset.css
// 命名
BEM: block-element-module-inner
首字母简写
// 声明顺序

布局类属性     position / top / right / bottom / left / float / display / overflow 等
盒模型属性   border / margin / padding / width / height 等
文本类属性     font / line-height / text-align / word-wrap 等
修饰类属性     background / color / transition / list-style 等

布局

盒模型

所有文档元素都生成一个矩形框,这称为元素框(element box),它描述了一个元素在文档布局中所占的空间大小。而且,每个框影响着其他元素框的位置和大小

  • 标准盒模型:
元素所设置的 width 与 height 只会应用到这个元素的内容区。如果这个元素有任何的 border 或 padding ,绘制到屏幕上时的盒子宽度和高度会加上设置的边框和内边距值。

盒子宽高 = content + padding + border + margin; 
元素设置的宽高会随着 pading 和 margin 发生变化
  • 怪异盒模型
不同之处在于IE盒模型的宽高定义的是可见元素框的尺寸,而不是元素框的内容区尺寸
宽高设置多少就是多少; 便于布局; 内容区的宽高会减去 padding 和 margin

标准流

  • block
# 特性
不设置宽度时,宽度为父元素宽度
独占一行
支持宽高

# 具体标签
<address><article><aside><blockquote><body><dd><details><div><dl><dt><fieldset><figcaption><figure><footer><form><h1><header><hgroup><hr><html><legend><menuitem><nav><ol><optgroup><option><p><section><summary><ul>

# 不支持特性
vertical-align
  • inline-block
# 特性
不设置宽度时,内容撑开宽度
非独占一行
支持宽高
代码换行被解析成空格

# 具体标签
<audio><button><canvas><embed><iframe><img><input><keygen><meter><object><progress><select><textarea><video>

  • inline
# 特性
内容撑开宽度
非独占一行
不支持宽高
代码换行被解析成空格

# 具体标签
<a><abbr><area><b><bdi><bdo><br><cite><code><del><dfn><em><i><ins><kbd><label><map><mark><output><pre><q><rp><rt><ruby><s><smap><small><span><strong><sub><sup><time><u><var><wbr>

# 不支持特性
background-position
clear
clip
height | max-height | min-height
width | max-width | min-width
overflow
text-align
text-indent
text-overflow
  • BFC
# 定义
在解释BFC之前,先说一下文档流。我们常说的文档流其实分为定位流、浮动流和普通流三种。而普通流其实就是指BFC中的FC。FC是formatting context的首字母缩写,直译过来是格式化上下文,它是页面中的一块渲染区域,有一套渲染规则,决定了其子元素如何布局,以及和其他元素之间的关系和作用。常见的FC有BFC、IFC,还有GFC和FFC。BFC是block formatting context,也就是块级格式化上下文,是用于布局块级盒子的一块渲染区域

# 触发条件
根元素,即HTML元素
float的值不为none
overflow的值不为visible
display的值为inline-block、table-cell、table-caption
position的值为absolute或fixed

# 作用
BFC是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面元素
可以阻止元素被浮动元素覆盖
可以包含浮动元素
解决 margin 重叠

浮动流

浮动元素脱离普通流,然后按照指定方向,向左或者向右移动,碰到父级边界或者另外一个浮动元素停止

  • 特性
正常流中元素一个接一个排列;浮动元素也构成浮动流;
浮动元素自身会生成一个块级框,而不论这个元素本身是什么,使浮动元素周围的外边距不会合并
浮动元素的包含块是指其最近的块级祖先元素,后代浮动元素不应该超出包含块的上、左、右边界
浮动元素脱离正常流,并破坏了自身的行框属性,使其包含块元素的高度塌陷,使浮动框旁边的行框被缩短,从而给浮动框留出空间,行框围绕浮动框重新排列
  • 浮动清除
解决浮动元素的包含块高度塌陷的问题
.clearfix:after {content:""; display: block; clear: both;}
.clearfix {zoom: 1;}

定位流

偏移
堆叠 z-index
绝对定位
相对定位
固定定位

渲染

  • will-change 属性
GPU是图形处理器,专门处理和绘制图形相关的硬件。GPU是专为执行复杂的数学和几何计算而设计的,使得CPU从图形处理的任务中解放出来,可以执行其他更多的系统任务

所谓硬件加速,就是在计算机中把计算量非常大的工作分配给专门的硬件来处理,减轻CPU的工作量

CSS的动画、变形、渐变并不会自动触发GPU加速,而是使用浏览器稍慢的软件渲染引擎。在transition、transform和animation的世界里,应该卸载进程到GPU以加速速度。只有3D变形会有自己的layer,而2D变形则不会

使用translateZ()或translate3d()方法为元素添加没有变化的3D变形,骗取浏览器触发硬件加速。但是,代价是这种情况通过向它自己的层叠加元素,占用了RAM和GPU的存储空间,且无法确定空间释放时间
  • 使用
可以让父元素hover的时候,声明will-change,这样,移出的时候就会自动remove,触发的范围基本上是有效元素范围

.will-change-parent:hover .will-change {
  will-change: transform;
}
.will-change {
  transition: transform 0.3s;
}
.will-change:hover {
  transform: scale(1.5);
}

# js 操作
var el = document.getElementById('element');
// 当鼠标移动到该元素上时给该元素设置 will-change 属性
el.addEventListener('mouseenter', hintBrowser);
// 当 CSS 动画结束后清除 will-change 属性
el.addEventListener('animationEnd', removeHint);
function hintBrowser() {
  // 填写在CSS动画中发生改变的CSS属性名
  this.style.willChange = 'transform, opacity';
}
function removeHint() {
  this.style.willChange = 'auto';
}
  • 注意事项
1、不要将will-change应用到太多元素上:浏览器已经尽力尝试去优化一切可以优化的东西了。有一些更强力的优化,如果与will-change结合在一起的话,有可能会消耗很多机器资源,如果过度使用的话,可能导致页面响应缓慢或者消耗非常多的资源

  2、有节制地使用:通常,当元素恢复到初始状态时,浏览器会丢弃掉之前做的优化工作。但是如果直接在样式表中显式声明了will-change属性,则表示目标元素可能会经常变化,浏览器会将优化工作保存得比之前更久。所以最佳实践是当元素变化之前和之后通过脚本来切换will-change的值

  3、不要过早应用will-change优化:如果页面在性能方面没什么问题,则不要添加will-change属性来榨取一丁点的速度。will-change的设计初衷是作为最后的优化手段,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。过度使用will-change会导致大量的内存占用,并会导致更复杂的渲染过程,因为浏览器会试图准备可能存在的变化过程。这会导致更严重的性能问题

  4、给它足够的工作时间:这个属性是用来让页面开发者告知浏览器哪些属性可能会变化的。然后浏览器可以选择在变化发生前提前去做一些优化工作。所以给浏览器一点时间去真正做这些优化工作是非常重要的。使用时需要尝试去找到一些方法提前一定时间获知元素可能发生的变化,然后为它加上will-change属性

函数类型(属性和方法)

函数类型(自身属性&方法)

函数是javascript中特殊的对象,可以拥有属性和方法,就像普通的对象拥有属性和方法一样。甚至可以用Function()构造函数来创建新的函数对象。

属性

  • length属性
函数的length属性则表示形参个数

function add(x,y){
    console.log(arguments.length) // 3
    console.log(add.length); // 2
}
add(1,2,3);
  • name属性
函数定义了一个非标准的name属性,通过这个属性可以访问到给定函数指定的名字,
这个属性的值永远等于跟在function关键字后面的标识符,
匿名函数的name属性为空
function fn(){};
console.log(fn.name); // 'fn'
var fn = function(){};
console.log(fn.name); //'' 在chrome浏览器中会显示'fn'

ES6对这个属性的行为做出了一些修改。
如果将一个匿名函数赋值给一个变量,ES5的name属性,会返回空字符串,
而ES6的name属性会返回实际的函数名
var func1 = function () {};
func1.name //ES5:  ""
func1.name //ES6: "func1"

Function构造函数返回的函数实例,name属性的值为“anonymous”
(new Function).name // "anonymous"

bind返回的函数,name属性值会加上“bound ”前缀
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
  • prototype属性
    每一个函数都有一个prototype属性,这个属性指向一个对象的引用,这个对象称做原型对象(prototype object)。
    每一个函数都包含不同的原型对象。将函数用做构造函数时,新创建的对象会从原型对象上继承属性
所以, 我们通常往构造函数的原型挂载一些公共方法

function Fn() {
  // do something
};
var obj = new Fn;
Fn.prototype.a = 1;
console.log(obj.a); // 1

方法

apply()和call()

这两个方法的用途都是在特定的作用域中调用函数, 实际上等于函数体内this对象的值
要想以对象o的方法来调用函数,可以这样使用call()和apply()

  • call()
window.color = "red";
var o = {color: "blue"};
function sayColor(){
    console.log(this.color);
}
sayColor();            // red
sayColor.call(this);   // red
sayColor.call(window); // red
sayColor.call(o);      // blue

sayColor.call(o)的调用方式等价于, 相当于为 o 赋值一个临时方法
o.sayColor = sayColor;
o.sayColor();   // blue
delete o.sayColor;
  • apply()
    apply()方法接收两个参数:一个是在其中运行函数的作用域(或者可以说成是要调用函数的母对象,它是调用上下文,在函数体内通过this来获得对它的引用),另一个是参数数组。其中,第二个参数可以是Array的实例,也可以是arguments对象
function sum(num1, num2){
    return num1 + num2;
}
// 因为运行函数的作用域是全局作用域,所以this代表的是window对象
function callSum1(num1, num2){
    return sum.apply(this, arguments);
}
function callSum2(num1, num2){
    return sum.apply(this, [num1, num2]);
}
console.log(callSum1(10,10)); // 20
console.log(callSum2(10,10)); // 20

注意:
在非严格模式下,使用函数的call()或apply()方法时,null或undefined值会被转换为全局对象。
在严格模式下,函数的this值始终是指定的值

bind()

这个方法的主要作用就是将函数绑定到某个对象
当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数。
以函数调用的方式调用新的函数将会把原始的函数f()当做o的方法来调用,
传入新函数的任何实参都将传入原始函数

function f(y){    // 这个是待绑定的函数
    return this.x + y; 
}
var o = { x: 1 };  // 将要绑定的对象
var g = f.bind(o); // 通过调用g(x)来调用o.f(x)
g(2); // 3

# bind应用: 柯里化 => 对函数参数进行拆分
function getConfig(colors,size,otherOptions){
    console.log(colors,size,otherOptions);
}
var defaultConfig = getConfig.bind(null,'#c00','1024*768');
defaultConfig('123');//'#c00 1024*768 123'
defaultConfig('456');//'#c00 1024*768 456'

toString()

函数的toString()实例方法返回函数代码的字符串,而静态toString()方法返回一个类似'[native code]'的字符串作为函数体

function test(){
    alert(1);
}
test.toString(); 
/*" function test(){
      alert(1);
}"*/

Function.toString(); //"function Function() { [native code] }"

valueOf()

函数的valueOf()方法返回函数本身

性能优化(渲染层面)

性能优化(渲染层面)

这里主要是针对浏览器渲染进程的工作流程做出的一些优化, 有个宏观的概念...
我们来回顾一下渲染进程的工作流

渲染进程工作流

  • 1.DOM Tree 构建
当渲染进程接收到导航的确认信息,开始接受HTML数据时,主线程会解析文本字符串为 DOM;
这里依靠 HTMl 解析器: 
接受字节流 -> 维护 token 栈 -> 生成节点node -> 组成 DOM;

遇到内嵌 script 时, DOM解析工作停止; js引擎介入执行(可能会修改dom结构);
执行完 js 后恢复解析工作, 所以 js 会阻塞 dom 解析.

遇到其他内联资源时(css,img)会通知网络进程去下载, 特别是 css;
js 在操作dom 样式时会依赖cssom,生成 layoutTree也需要 cssom; 
所以 css 又会阻塞 js 的执行
  • 2.样式计算, 构建cssom(css规则树)
这里会基于 CSS 选择器解析 CSS 获取每一个节点的最终的计算样式值; 
与 DOM 树构建并行;
对应的就是styleSheets
  • 3.计算布局, 生成layout tree
想要渲染一个完整的页面,除了获知每个节点的具体样式,还需要获知每一个节点在页面上的位置,
布局其实是找到所有元素的几何关系的过程。

这里通过遍历 DOM 及相关元素的计算样式,主线程会构建出包含每个元素的坐标信息及盒子大小的布局树。
布局树和 DOM 树类似,但是其中只包含页面可见的元素,如果一个元素设置了 `display:none` ,
这个元素不会出现在布局树上,伪元素虽然在 DOM 树上不可见,但是在布局树上是可见的。
  • 4.分层,绘制(layer -> paint)
为特定的节点生成专用图层(will-change属性), 生成 图层树;
为图层生成绘制表(记录了绘制指令和顺序), 提交到合成线程
  • 5.分块,光栅化
合成线程将图层分为图块, 通过光栅化生成位图(GPU 进程)
  • 6.合成,显示
图块被光栅化后会生成一个绘制命令, 通过 IPC 提交给浏览器进程去执行,
绘制到内存中然后展示在显示器上

这里我们针对每一步进行分析, 并尽可能提炼出一些优化手段

1.首屏优化(让浏览器接收到尽可能完整的 HTML 字符串)

目前我们都习惯了使用 vue,react 这些框架,大多数情况下我们都是基于 spa 搭建的单页应用(即纯客户端渲染csp),全靠js运行后输出渲染页面, 这样在首次加载的时候需要等待资源全部加载完毕才会呈现出页面, 增加等待时间, 对于 C端业务(特别是有 SEO 要求的)显然是不可取的; 所以后续又提出来了服务端渲染(ssr同构), 服务端给出完整的 html, 客户端接管后续操作.

优化措施1: 服务端渲染

可以参考 vue nuxt.jsvue 官网的ssr 指南

优化措施2: 预渲染

服务端渲染多服务器开销要求很大, 如果服务器成本预算不足的话, 尽量还是不要用了吧,或者少数页面使用 SSR 就行; 这时不妨考虑下预渲染.
主要是针对一些纯静态页面
prerender-spa-plugin

优化措施3: 增加请求资源过程中的交互体验

比如loading, 转场过渡动画, 骨架屏等

2. DOM解析优化(让 html 的解析流畅)

解析 HTML 字符串时又穿插了 CSS 与 JS, 我们知道 js线程与渲染线程同一时间只有一个在工作, js 的执行会阻塞 DOM 解析;
另外 js 的执行会依赖 cssom(js 可能操作样式), 所以 css 的加载又会阻塞 js;
而且, 布局树最终的合成也依赖于 cssom 和 dom;
因此, js 和 css 的加载顺序也是一个能优化的点.

css 加载

1.css 引入

css一般由head 中的link和style标签引入,
或者内联到标签中,尽量提前放置,尽快加载

2.css 书写(CSS选择符是从右到左进行匹配的)

规则尽量简单,避免层级嵌套;
少用*,标签选择器等;
考虑属性继承

js 加载

1.正常加载

<script src="index.js"></script>
<!-- 浏览器必须等待 index.js 加载和执行完毕才能去做其它事情 -->

1.async 模式加载

<script async src="index.js"></script>
<!-- 加载是异步的,当它加载结束,JS脚本会立即执行(执行还是会阻塞) -->

1.defer 模式加载

<script defer src="index.js"></script>
<!-- 加载是异步的,执行是被推迟的。
等整个文档解析完成、DOMContentLoaded 事件即将被触发时,
被标记了 defer 的 JS 文件才会开始依次执行 
-->

从应用的角度来说:
当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;
当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer

3.DOM 操作优化一

当我们用 JS 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了通信交流, 这样也会消耗性能;
另外,我们对 DOM 的操作都不会局限于访问,而是为了修改它。当我们对 DOM 的修改会引发它外观(样式)上的改变时,就会触发重排重绘。导致布局树的改变...

重排: 当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是重排(也叫回流)。

重绘: 当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的重排环节)。这个过程叫做重绘。

优化措施1: 减少 DOM 操作

类似虚拟 DOM 的手法, js 批量操作需要更新的 dom, 然后一次性更新到 DOM 树中
利用DocumentFragment

优化措施2: 减少重绘重排

  • 减少改变DOM元素的几何属性
当一个DOM元素的几何属性发生变化时,所有和它相关的节点(比如父子节点、兄弟节点等)
的几何属性都需要进行重新计算,它会带来巨大的计算量...
常见的几何属性有 width、height、padding、margin、left、top、border 等等。
此处不一一列举了

我们可以一次性赋值:
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'

// 优化, 切换类名而不是设置值
.basic_style {
  width: 100px;
  height: 200px;
  border: 10px solid red;
  color: red;
}
const container = document.getElementById('container')
container.classList.add('basic_style')
  • 减少位置值的获取
当你要用到像这样的属性:offsetTop、offsetLeft、 offsetWidth、offsetHeight、
scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、
clientLeft、clientWidth、clientHeight 时; 
这些属性值需要通过即时计算得到。因此浏览器为了获取这些值,也会进行重排。

// 我们可以缓存这些值:
// 缓存offsetLeft与offsetTop的值
const el = document.getElementById('el') 
let offLeft = el.offsetLeft, offTop = el.offsetTop

// 在JS层面进行计算
for(let i=0;i<10;i++) {
  offLeft += 10
  offTop  += 10
}

// 一次性将计算结果应用到DOM上
el.style.left = offLeft + "px"
el.style.top = offTop  + "px"
  • 使用 GPU 图层
比如使用心得 css3属性 transform 等, 利用GPU进程去处理

4.DOM 操作优化二(异步更新策略)

利用事件循环(Event Loop)尽可能早的更新DOM;

事件循环下的任务队列

事件循环中的异步队列有两种: macro(宏任务: 浏览器维护)队列和 micro(微任务: js引擎维护)队列。

  • Macro-Task宏任务
setTimeout,setInterval,setImmediate,
script(整体代码), I/O 操作, UI 渲染等
  • Micro-Task
process.nextTick、Promise、MutationObserver等

事件循环运行过程

1.初始状态: 调用栈为空
micro 队列空,macro 队列里有且只有一个 script 脚本(整体的全局代码)

2.全局上下文(script 标签)被推入调用栈,同步代码执行。
在执行的过程中,通过对一些Web API接口的调用,可以产生新的 macro-task 与 micro-task,
它们会分别被推入各自的任务队列里。
同步代码执行完了,script 脚本会被移出 macro 队列,
这个过程本质上是队列的 macro-task 的执行和出队列的过程。

3.前面我们出队列的是一个 macro-task,这一步我们处理的是 micro-task。
但需要注意的是: 当 macro-task 出队时,任务是一个一个执行的;
而 micro-task 出队时,任务是一队一队执行的。(当前出队的宏任务下的微任务队列)
因此,我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。

4.执行渲染操作,更新界面(重点)。

5.检查是否存在 Web worker 任务,如果有,则对其进行处理 。

6.上述过程循环往复,直到两个队列都清空

DOM 异步更新的时刻

假如我想要在异步任务里进行DOM更新,我该把它包装成 micro 还是 macro 呢?

// 假设包装成宏任务
// task是一个用于修改DOM的回调
setTimeout(task, 0)
/*
现在 task 被推入的 macro 队列。但因为 script 脚本本身是一个 macro 任务,
所以本次执行完 script 脚本之后,下一个步骤就要去处理 micro 队列了,
再往下就去执行了一次渲染操作了, 
进入下一轮的事件循环...
我们的 task 此时执行的时机是下一轮的事件循环中
*/

// 假设包装成微任务
Promise.resolve().then(task)
/*
 script 脚本的执行完, 紧接着就去处理 micro-task 队列;
 micro-task 处理完,DOM 修改好了;
 紧接着就可以走渲染流程了...
 所以不需要再消耗多余的一次渲染,不需要再等待一轮事件循环,直接为用户呈现最即时的更新结果。
*/

所以当我们需要在异步任务中实现DOM修改时,要它包装成 micro 任务。这样可以减少实际的渲染次数

vue的批量异步状态更新: nextTick

具体见我之前写的 nextTick 理解, 虽然不全;
至少我们知道了可以利用微任务来优化 DOM 的操作更新

5.懒加载方式

// lazy-loading
// 获取所有的图片标签
const imgs = document.getElementsByTagName('img')
// 获取可视区域的高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight
// num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
let num = 0
function lazyload(){
    for(let i=num; i<imgs.length; i++) {
        // 用可视区域高度减去元素顶部距离可视区域顶部的高度
        let distance = viewHeight - imgs[i].getBoundingClientRect().top
        // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
        if(distance >= 0 ){
            // 给元素写入真实的src,展示图片
            imgs[i].src = imgs[i].getAttribute('data-src')
            // 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
            num = i + 1
        }
    }
}
// 监听Scroll事件
window.addEventListener('scroll', lazyload, false);

6.事件的防抖与节流

像scroll 事件,resize 事件、鼠标事件(比如 mousemove、mouseover 等)、键盘事件(keyup、keydown 等)都可能被用户频繁触发;
频繁触发回调导致的大量计算会引发页面的抖动甚至卡顿。为了规避这种情况,我们需要一些手段来控制事件被触发的频率。这样就出现了throttle(事件节流)和 debounce(事件防抖);
这两个函数都是以闭包的形式存在。
它们通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用 setTimeout 来控制事件的触发频率

throttle

一段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时才调用

// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
  // last为上一次触发回调的时间
  let last = 0
  
  // 将throttle处理结果当作函数返回
  return function () {
      // 保留调用时的this上下文
      let context = this
      // 保留调用时传入的参数
      let args = arguments
      // 记录本次触发回调的时间
      let now = +new Date()
      
      // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
      if (now - last >= interval) {
      // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
          last = now;
          fn.apply(context, args);
      }
    }
}

// 用throttle来包装scroll的回调, 间隔 1s 触发第一次
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)

debounce

一段时间内,不管你触发了多少次回调,我都只认最后一次

// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
function debounce(fn, delay) {
  // 定时器
  let timer = null
  
  // 将debounce处理结果当作函数返回
  return function () {
    // 保留调用时的this上下文
    let context = this
    // 保留调用时传入的参数
    let args = arguments

    // 每次事件被触发时,都去清除之前的旧定时器
    if(timer) {
        clearTimeout(timer)
    }
    // 设立新定时器
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

// 用debounce来包装scroll的回调
// 触发事件后, 1s 内再次触发都不会执行
const better_scroll = debounce(() => console.log('触发了滚动事件'), 1000)

document.addEventListener('scroll', better_scroll)

参考

DocumentFragment MDN

浏览器架构概览

浏览器架构概览

浏览器本质上是运行在计算机操作系统下的一个应用软件

前言知识

多任务

简单地说, 就是操作系统可以同时运行多个任务。
真正的并行执行多任务只能在多核CPU上实现,但在实际应用中,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行,让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

进程

编写完毕的代码,在没有运行的时候,我们称之为程序;正在运行着的代码,就成为进程
进程是系统资源分配的单位,简单来说就是一段运行着的程序;
启动一个进程时,系统会给它分配地址空间(每个进程都有自己独立的地址空间,所以互不影响),建立数据表来维护代码段,堆栈信息,数据段,占用很多资源.
进程之间有自己的通信方式(IPC), IPC参考.

线程

线程就是系统资源调度的单位,是程序执行的最小单位;
可以理解为进程中执行代码的指针,线程存在于进程并执行程序任意部分,由系统的调度算法来决定其如何运行.
线程是共享进程中的数据的,使用相同的地址空间;所以 cpu 切换线程很快,要比操作进程快;
线程间通信快, 因为他们共享同一进程下的全局变量,静态数据等; 当然这样也会互相影响,利弊都有.

浏览器结构组成

avatar
1.用户界面(user interface)
浏览器中菜单栏、地址栏、向前/回退按钮、书签目录等除了页面显示窗口外的所有地方都属于用户界面。

2.浏览器引擎(browser engine)
浏览器引擎是各个组件之间通信的核心,它在用户界面与渲染引擎间传递指令。为渲染引擎提供接口,将用户界面上的给定的网址、用户浏览器操作(如刷新、向前、退后等)信息提供给为渲染引擎;同时,浏览器引擎 为用户界面提供各种如错误提示、资源下载进度等消息。还有,它可以在客户端本地缓存中读写数据。

3.渲染引擎(rendering engine)
渲染引擎负责显示请求的内容。例如,如果请求的内容是HTML文件,则它负责解析文件里的html、css等信息,并将网页内容渲染呈现出来。渲染引擎的内部包括了html解析器、css解析器等。

4.网络模块(networking)
处理网络请求,比如http请求网页、图片资源等。

5.JavaScript解释器(JavaScript interpreter)
网页中经常有JS脚本来动态操作网页,就是靠这个JS引擎来解析执行的。比如Chrome浏览器的V8引擎、Safari浏览器的JavaScriptCore引擎,IE的Chakra

6.界面后端(UI backend)
前面介绍的用户界面是展示给用户看到的,界面后端是浏览器的图形库,用来绘制基本的浏览器窗口内控件,如输入框、按钮、单选按钮、组合框和窗口等,不同浏览器绘制的视觉效果不太想同,但功能基本都是一样的。

7.数据存储(data persistence)
管理用户数据,将与浏览会话相关联的各种数据存储在硬盘上,例如保存书签、cookie、缓存、偏好设置等各种数据,可通过浏览器引擎提供的API进行调用。

浏览器架构组成

浏览器本质上是一个软件(应用程序),采用的是多进程架构,软件在运行时会创建多个进程,协同工作,保证程序正常运行...

  • 进程分类:
浏览器进程(主进程): 管理用户交互(如地址栏,书签...),子进程管理,文件存储等(负责tab外的工作)

渲染进程(沙箱模式): 负责页面渲染,JS脚本的执行,页面事件的处理等,可以同时开启多个渲染进程(tab页);
  > js引擎和 render引擎都运行在这个进程下
  - 渲染线程(引擎)
  - js 线程(引擎)

网络进程: 面向渲染进程和浏览器进程等提供网络下载功能

GPU(图形处理器)进程: 处理gpu任务,负责3D绘制

插件进程: 管理浏览器扩展程序等

其他进程...

shell脚本命令

shell脚本命令

通常前端er在服务器上部署项目时会用上 shell 脚本或者 docker, 用来实现自动化部署, 所以还是有必要了解下的;由于精力有限,这里附上别人的总结链接:
shell语言总结

这里附上一个平时用来同步多台服务器下的静态文件用的脚本

#!/bin/bash
for name in (192.168.188.xxx 192.168.188.xxxx 192.168.188.xxxx); do
/usr/bin/rsync -avP -e 'ssh -p58422' --delete  /www/www.target.com     root@"$name":/www/
done
  • 改进
#!/bin/bash
iplist=(192.168.xxx
	192.168.xxx
	192.168.xxx
	192.168.xxx)
sourcedir="/www/project/"
distdir="/www/project/"
for ip in ${iplist[*]}; do
	# 监听sourcedir目录下更新的文件, 并进行替换
	rsync -avP -e "ssh -p58422" --delete ${sourcedir} ${ip}:${distdir}
done

从浏览器一次网络请求流程中找寻优化手段

浏览器下的一次网络请求流程

以输入 http://github.com/appleguardu为例,一次 web 请求本质上就是一台主机去另一台主机获取对应的资源.

1.进程切换

当导航栏输入 http://github.com/appleguardu/后, 浏览器进程把这个url交给网络进程处理, 给我去这台主机下取资源;

2.解析请求的URL,DNS查询IP,准备端口

  • URL: 统一资源定位符
protocol: 协议头, 譬如有http,ftp等
host: 主机域名或IP地址
port: 端口号
path: 目录路径
query: 即查询参数
fragment: 即#后的hash值,一般用来定位到某个位置
  • DNS(域名与IP的映射系统)
由于不同主机之间都是通过IP地址来确定的,所以首先会进行域名解析,查询; 
浏览器会向域名服务器去查询http://github.com对应的Ip.

//  Ip查询也有一套优化机制: 缓存机制(可以减少网络请求)
浏览器缓存:   浏览器会按照一定的频率缓存 DNS 记录;
操作系统缓存: 如果浏览器缓存中找不到需要的 DNS 记录,那就去操作系统中找;
路由缓存:     路由器也有 DNS 缓存;
ISP(互联网服务提供商)的DNS服务器: ISP 有专门的 DNS 服务器应对 DNS 查询请求;
根服务器: ISP 的 DNS 服务器还找不到的话,它就会向根服务器发出请求,进行递归查询;(DNS 服务器先问根域名服务器.com 域名服务器的 IP 地址,然后再问.github 域名服务器,依次类推)
  • 端口port
    如果不是特别指定,http协议默认的80端口

3.等待 TCP 队列(浏览器对tcp的限制)

chorome浏览器有个机制, 统一域名下同时最多只能建立 6 个 TCP连接(浏览器的限制可能不一样), 如果超过 6 个请求, 那么剩下的会进入排队等待的状态, 等待前面的完事后自己再去建立连接

4.建立 TCP 连接

TCP 协议

TCP运行: 连接创建、数据传送和连接终止

  • 连接创建: 三次握手(保证可靠传输)
1.客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三次握手的一部分;
客户端把这段连接的序号设定为随机数A。

2.服务器端应当为一个合法的SYN回送一个SYN/ACK。
ACK的确认码应为A+1,SYN/ACK包本身又有一个随机产生的序号B。

3.最后,客户端再发送一个ACK。此时包的序号被设定为A+1,而ACK的确认码则为B+1。
当服务端收到这个ACK的时候,就完成了三次握手,并进入了连接创建状态。

client侧: SYN         ACK
server侧:     SYN+ACK
  • 数据传输(一些机制)
使用序号,对收到的TCP报文段进行排序以及检测重复的数据;
使用校验和检测报文段的错误,即无错传输;
使用确认和计时器来检测和纠正丢包或延时;
流量控制(Flow control);拥塞控制(Congestion control);丢失包的重传。
  • 连接终止(四次挥手)
连接终止使用了四路握手过程(或称四次握手,four-way handshake),在这个过程中连接的每一侧都独立地被终止。
当一个端点要停止它这一侧的连接,就向对侧发送FIN,对侧回复ACK表示确认。
因此,拆掉一侧的连接过程需要一对FIN和ACK,分别由两侧端点发出。

client侧: FIN          ACK
server侧:     ACK  FIN

5.发送 HTTP 请求

当 TCP 连接建立之后, 浏览器与服务器之间开始通信,传输数据

构建请求报文

  • 请求行
GET /appleguardu HTTP/1.1
// 请求方式:(Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE
// 资源路径URI: /appleguardu
// 协议版本: HTTP 1.1
  • 请求头(常用的)
Accept: 接收类型,表示浏览器支持的MIME类型(对标服务端返回的Content-Type)
Accept-Encoding: 浏览器支持的压缩类型,如gzip等,超出类型不能接收
Content-Type: 客户端发送出去实体内容的类型
Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache
If-Modified-Since: 对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内,http1.0中
Expires: 缓存控制,在这个时间内不会请求,直接使用缓存,http1.0,而且是服务端时间
Max-age: 代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存,http1.1中
If-None-Match: 对应服务端的ETag,用来匹配文件内容是否改变(非常精确),http1.1中
Cookie: 有cookie并且同域访问时会自动带上
Connection: 当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive
Host: 请求的服务器URL
Origin: 最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私
Referer: 该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址,csrf拦截常用到这个字段)
User-Agent: 用户客户端的一些必要信息,如UA头部等
  • 请求体
post 请求中参数常放在请求体中
如参数的序列化形式(a=1&b=2这种;
或者直接放表单对象(Form Data对象,上传时可以夹杂参数以及文件的等

注意: 如果请求的是缓存资源,则会终止请求

浏览器会对请求的文件资源进行检查, 如果这个请求的资源存在于浏览器缓存当中, 请求就会被拦截, 浏览器直接从本地缓存中获取后发给网络进程, 并结束此处请求过程
关于http缓存介绍

缓存分类

缓存可以简单的划分成两种类型: 强缓存(200 from cache)与协商缓存(服务端返回304);
强缓存: 浏览器如果判断本地缓存未过期,就直接使用,无需发起http请求;
协商缓存304: 当浏览器再次向服务端发起http请求,然后服务端(304)告诉浏览器文件未改变,让浏览器使用本地缓存

  • 如何区分两类缓存? 通过设置 http 头部信息
# 几种头部缓存属性
强缓存(http1.1): Cache-Control/Max-Age 
强缓存(http1.0): Pragma/Expires 

协商缓存(http1.1): If-None-Match/E-tag
协商缓存(http1.0): If-Modified-Since/Last-Modified

<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
HTML页面中也有一个meta标签可以控制缓存方案-Pragma,不过一般都是服务端设置
  • http1.0下的缓存控制
Pragma: 严格来说,它不属于专门的缓存控制头部,但是它设置no-cache时可以让本地强缓存失效(
属于编译控制,来实现特定的指令,主要是因为兼容http1.0,所以以前又被大量应用)

Expires: 服务端配置的,属于强缓存,用来控制在规定的时间之前,浏览器不会发出请求,而是直接使用本地缓存,
注意: Expires一般对应服务器端时间,如Expires: Fri, 30 Oct 1998 14:19:41
如果客户端时间和服务端不同步,可能造成浏览器本地的缓存无用或者一直无法过期

If-Modified-Since/Last-Modified: 这两个是成对出现的,属于协商缓存的内容;
其中浏览器的头部是If-Modified-Since,而服务端的是Last-Modified;
它的作用是,在发起请求时,如果If-Modified-Since和Last-Modified匹配,
那么代表服务器资源并未改变,因此服务端不会返回资源实体,而是只返回头部,通知浏览器可以使用本地缓存...
Last-Modified,顾名思义,指的是文件最后的修改时间,而且只能精确到1s以内
注意: 如果服务端的文件会周期性的改变, 导致缓存失效
  • http1.1下的缓存控制
Cache-Control: 缓存控制头部,有no-cache、max-age等多种取值

Max-Age: 服务端配置的,用来控制强缓存,在规定的时间之内,浏览器无需发出请求,直接使用本地缓存,
注意,Max-Age是Cache-Control头部的值,不是独立的头部,
譬如Cache-Control: max-age=3600,而且它值得是绝对时间,由浏览器自己计算

If-None-Match/E-tag: 这两个是成对出现的,属于协商缓存的内容;
其中浏览器的头部是If-None-Match,而服务端的是E-tag,
同样,发出请求后,如果If-None-Match和E-tag匹配,则代表内容未变,通知浏览器使用本地缓存;
和Last-Modified不同,E-tag更精确,它是类似于指纹一样的东西,所以会优先级也更高...
基于FileEtag INode Mtime Size生成,也就是说,只要文件变,指纹就会变,而且没有1s精确度的限制。

6.服务器处理 http 请求

当服务器接收到浏览器的请求报文后, 会交给对应的处理程序进行处理(此处省略...),然后想服务器返回响应报文

响应报文

  • 响应行
HTTP/1.1 200 OK
// 协议版本 状态码 状态码信息
// 200——表明该请求被成功地完成,所请求的资源发送回客户端
// 304——自从上次请求后,请求的网页未修改过,请客户端使用本地缓存
// 400——客户端请求有错(譬如可以是安全模块拦截)
// 401——请求未经授权
// 403——禁止访问(譬如可以是未登录时禁止)
// 404——资源未找到
// 500——服务器内部错误
// 503——服务不可用
  • 响应头(常用的)
Access-Control-Allow-Headers: 服务器端允许的请求Headers
Access-Control-Allow-Methods: 服务器端允许的请求方法
Access-Control-Allow-Origin: 服务器端允许的请求Origin头部(譬如为*)
Content-Type: 服务端返回的实体内容的类型
Date: 数据从服务器发送的时间
Cache-Control: 告诉浏览器或其他客户,什么环境可以安全的缓存文档
Last-Modified: 请求资源的最后修改时间
Expires: 应该在什么时候认为文档已经过期,从而不再缓存它
Max-age: 客户端的本地资源应该缓存多少秒,开启了Cache-Control后有效
ETag: 请求变量的实体标签的当前值
Set-Cookie: 设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端
Keep-Alive: 如果客户端有keep-alive,服务端也会有响应(如timeout=38)
Server: 服务器的一些相关信息
  • 响应体
响应体一般是服务端需要传给客户端的内容;
如接口请求时: 实体中就是对应的信息的 json格式对象
如页面请求时: 实体中就是对应的 html 字符串

注意

一般来说, 请求头部和响应头部是互相匹配的
如请求头部的Accept要和响应头部的Content-Type匹配,否则会报错;
跨域请求时,请求头部的Origin要匹配响应头部的Access-Control-Allow-Origin,否则会报跨域错误
还有在使用缓存时,
请求头部的If-Modified-Since、If-None-Match分别和
响应头部的Last-Modified、ETag相对应

以上基本都是通过服务端程序来控制的

重定向

当响应状态码是 301 时,会发生重定向, 浏览器进程重新让导航进行工作, 修改 location 字段中的地址,继续上述的请求流程

8.浏览器解析并渲染页面

浏览器进程开始调度, 渲染进程通过管道接受网络进程的html数据,开始解析html工作

渲染进程工作流

  • 1.DOM Tree 构建
当渲染进程接收到导航的确认信息,开始接受HTML数据时,主线程会解析文本字符串为 DOM;
这里依靠 HTMl 解析器: 
接受字节流 -> 维护 token 栈 -> 生成节点node -> 组成 DOM;

遇到内嵌 script 时, DOM解析工作停止; js引擎介入执行(可能会修改dom结构);
执行完 js 后恢复解析工作, 所以 js 会阻塞 dom 解析.

遇到其他内联资源时(css,img)会通知网络进程去下载, 特别是 css;
js 在操作dom 样式时会依赖cssom,生成 layoutTree也需要 cssom; 
所以 css 又会阻塞 js 的执行
  • 2.样式计算, 构建cssom(css规则树)
这里会基于 CSS 选择器解析 CSS 获取每一个节点的最终的计算样式值;
对应的就是styleSheets
  • 3.计算布局, 生成layout tree
想要渲染一个完整的页面,除了获知每个节点的具体样式,还需要获知每一个节点在页面上的位置,
布局其实是找到所有元素的几何关系的过程。

这里通过遍历 DOM 及相关元素的计算样式,主线程会构建出包含每个元素的坐标信息及盒子大小的布局树。
布局树和 DOM 树类似,但是其中只包含页面可见的元素,如果一个元素设置了 `display:none` ,
这个元素不会出现在布局树上,伪元素虽然在 DOM 树上不可见,但是在布局树上是可见的。
  • 4.分层,绘制(layer -> paint)
为特定的节点生成专用图层(will-change属性), 生成 图层树;
为图层生成绘制表(记录了绘制指令和顺序), 提交到合成线程
  • 5.分块,光栅化
合成线程将图层分为图块, 通过光栅化生成位图(GPU 进程)
  • 6.合成,显示
图块被光栅化后会生成一个绘制命令, 通过 IPC 提交给浏览器进程去执行,
绘制到内存中然后展示在显示器上

9.断开TCP连接

当数据传送完毕,需要断开 tcp 连接,此时发起 tcp 四次挥手; 参见 TCP 协议;
通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。
不过如果浏览器或者服务器在其头信息中加入了

Connection: Keep-Alive (http/1.1下默认启用, http/1.0默认close)
// keep-alive不会永远保持,它有一个持续时间,一般在服务器中配置(如apache,
另外长连接需要客户端和服务器都支持时才有效

TCP 连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同一个 TCP 连接 发送请求。
保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。 比如,一个 Web 页面中内嵌的图片就都来自同一个 Web 站点,如果初始化了一个持久连 接,你就可以复用该连接,以请求其他资源,而不需要重新再建立新的 TCP 连接。

参考

TCP 协议百科
HTTP headers

  • http2.0 了解
- 多路复用(即一个tcp/ip连接可以请求多个资源)
- 首部压缩(http头部压缩,减少体积)
- 二进制分帧(在应用层跟传送层之间增加了一个二进制分帧层,改进传输性能,实现低延迟和高吞吐量)
- 服务器端推送(服务端可以对客户端的一个请求发出多个响应,可以主动通知客户端)
- 请求优先级(如果流被赋予了优先级,它就会基于这个优先级来处理,由服务器决定需要多少资源来处理该请求。)

JS原生对象类型系统

JS原生对象类型系统

能够表示并操作的值的类型称做数据类型,javascript拥有强大的类型系统,主要包括原生对象、宿主对象和浏览器拓展对象

js 类型系统

原生对象类型

原生对象分为两类:原始类型(primitive type)和对象类型(object type)。原始类型又分为两类,一类是空值,一类是包装对象;对象类型也可以分为两类:一类是构造器对象,一类是单体内置对象

undefined: 逻辑上一般表示原始类型的空值
null:      逻辑上一般表示对象类型的空值
  • 包装对象
字符串string、数字number、布尔值boolean虽然属于原始类型。
但是,由于其包装对象的性质,可以调用属性和方法; 比如 length 属性, 类型转化
  • 构造器对象
普通的对象是命名值的无序集合,但是通过不同的构造器,javascript定义了功能各异的多种对象,包括对象Object、函数Function、日期Date、数组Array、错误Error、正则RegExp

如果显式地使用new 构造器函数来定义包装对象,那么字符串String、数字number、布尔值boolean也属于构造器对象
  • 单体内置对象
单体内置对象包括Math、JSON、全局对象和arguments这四种。
它们不需声明或者使用构造器构造,直接在相应场景使用即可
  • 新增的BigInt
BigInt类型是 JavaScript 中的一个基础的数值类型,可以用任意精度表示整数。使用 BigInt,您可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。
BigInt是通过在整数末尾附加 n 或调用构造函数来创建的。
  • 新增的 Symbol
Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。
凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

Function 类型(总览)

函数类型

我理解的函数就是一个指令, 由所在的环境来调用或执行;
我们可以封装一系列操作语句在这个指令里面; 当然函数也是一个对象

函数定义

  • 声明式: 关键字 function
function fnName(arg) {
  // do something
}

# 函数名提升问题
声明式函数fnName,函数名称和函数体都提升, 即在当前上下文都可调用

# 函数的重复声明会覆盖前面的声明
# 后面的函数声明会覆盖前面的函数声明
var a;
function a(){
  console.log(1);
}
function a(){
  console.log(2);
}
a() // 2

# 变量赋值的情况
var a = 100;
function a(){
  console.log(1);
}
a() // error, 先提升, 后赋值
  • 表达式: 变量指向函数体
var fnName = function(arg) {
  // do something
}
匿名函数也叫拉姆达函数,是function关键字后面没有标识符的函数
  • Function构造函数
Function构造函数接收任意数量的参数,但最后一个参数始终都被看成是函数体,
而前面的参数则枚举出了新函数的参数

var functionName = new Function(['arg1' [,'arg2' [...,'argn']]],'statement;');
var sum = new Function('num1','num2','return num1 + num2');
//等价于, 不推荐, 多此一举
var sum = function(num1,num2){
    return num1+num2;
}

函数返回值

1.函数中的return语句用来返回函数调用后的返回值
2.return语句只能出现在函数体内
3.没有return语句,则函数调用仅仅依次执行函数体内的每一条语句直到函数结束,
最后返回调用程序.调用结果为 undefined
4.当执行到return语句时,函数终止执行,并返回expression的值给调用程序
5.return语句不会阻止finally子句的执行
function testFinnally(){
    try {
      return 2;
    } catch(error){
      return 1;
    } finally{
      return 0;
    }
}
testFinnally() // 0
6.通过 new 去调用函数,return 的值不是对象的话,则返回 this 对象
它的__proto__指向构造函数 func

7.通过 new 去调用函数,return 的值是对象的话,则返回该对象
它的__proto__指向 Object

函数的调用

函数只有在被调用时,才会执行。调用运算符是跟在任何产生一个函数值的表达式之后的一对圆括号,圆括号内可包含零个或多个用逗号隔开的表达式。每个表达式产生一个参数值,每个参数值被赋予函数声明时定义的形参名

javascript一共有4种调用模式:函数调用模式、方法调用模式、构造器调用模式和间接调用模式.

函数调用模式

当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的。对于普通的函数调用来说,函数的返回值就是调用表达式的值.

function add(x,y){
    return x+y;
}
var sum = add(3,4) // 7
  • this对象
通过函数调用模式调用函数时:
非严格模式下,this被绑定到全局对象;
在严格模式下,this是undefined
function add(){
  console.log(this);
}    
add(); // Window

function add(){
  'use strict'
  console.log(this);
}    
add(); // undefined

# this 绑到全局的弊端; 可能修改全局属性
var a = 0;
function fn(){
  this.a = 1;
}
fn();
console.log(this, this.a, a) // window, 1, 1; a 被重写了

作为方法调用

当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this(突破作用域的限制)被绑定到该对象.

  • this对象
方法可以使用this访问自己所属的对象,所以它能从对象中取值或对对象进行修改。
this到对象的绑定发生在方法被调用的时候。
通过this可取得它们所属对象的上下文的方法称为公共方法.

var o = {
  a: 1,
  m: function(){
    return this;
  },
  n: function(){
    this.a = 2;
  }
};
console.log(o.m()); // {a: ..., m: fn, n:fn} o 自己
o.n();
console.log(o.m().a);// 2
  • 嵌套函数的 this
由于this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。
如果嵌套函数作为方法调用,其this的值指向调用它的对象。
如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下)

var o = {
    m: function(){
       function n(){
           // 'use strict' // undefined
           return this;
       }
       return n();
    }
}
console.log(o.m()); // window

# 如果想访问这个外部函数的this值,需要将this的值保存在一个变量里,
这个变量和内部函数都同在一个作用域内。通常使用变量self或that来保存this

var o = {
  m: function(){
    var self = this;
    console.log(this === o); // true
    function n(){
       // 访问 m 所处环境下 this 对象 
       console.log(this === o); // false
       console.log(self === o); // true
       return self;
    }
    return n();
  }
}
console.log(o.m() === o); // true

构造函数调用模式

如果函数或者方法调用之前带有关键字new,它就构成构造函数调用

function fn(){
  console.log(this) // {}
  this.a = 1;
};
var obj = new fn(); // 上面已经提过,会返回 this 对象
console.log(obj.a); // 1

# 如果构造函数调用在圆括号内包含一组实参列表,先计算这些实参表达式,然后传入函数内
function fn(x){
    this.a = x;
};
var obj = new fn(2);
console.log(obj.a); // 2

# 构造函数即使是一个方法调用,它依然会使用这个新对象作为调用上下文

# 构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回;
# 如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果;
# 如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象

间接调用模式

javascript中函数也是对象,函数对象也可以包含方法。call()和apply()方法可以用来间接地调用函数.修改this指向
这两个方法都允许显式指定调用所需的this值,也就是说:
任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。
call()方法使用它自有的实参列表作为函数的实参
apply()方法则要求以数组的形式传入参数

var obj = {};
function sum(x,y){
    return x+y;
}
console.log(sum.call(obj, 1, 2));  // 3
console.log(sum.apply(obj, [1,2]));// 3

bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被
指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用

fun.bind(thisArg[, arg1[, arg2[, ...]]])

## thisArg 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。
## 当使用 new 操作符调用绑定函数时,该参数无效。
## arg1, arg2, … (可选)当绑定函数被调用时,这些参数加上绑定函数本身的参数
会按照顺序作为原函数运行时的参数。

function fn(a, b, c) {
  return a + b + c;
}

var _fn = fn.bind(null, 10);
var ans = _fn(20, 30, 40); // 60

相当于前几个参数已经 “内定” 了,我们便可以用 bind 返回一个新的函数。
也就是说,bind() 能使一个函数拥有预设的初始参数。
这些参数(如果有的话)作为 bind() 的第二个参数跟在 this 后面,
之后它们会被插入到目标函数的参数列表的开始位置,
传递给绑定函数的参数会跟在它们的后面。

函数的参数

  • 参数为基础类型
# 值传递
var a = 1
function fn(o) {
    o = 3;
    console.log(o) // 3
}
fn(a);
console.log(a) // 1
  • 参数为引用类型
# 对函数形参的赋值,不会影响实参的值
var obj = {x : 1};
function fn(o) {
    o = 100; // 此时相当于一个局部变量
    console.log(o) // 100
}
fn(obj);
console.log(obj.x); // 仍然是1, obj并未被修改为100.

# 修改形参对象的属性值,也会影响到实参的属性值
var obj = {x : 1};
function fn(o) {
    o.x = 3;
}
fn(obj);
console.log(obj.x); // 3, 被修改了!

故调用函数传参时,函数接受对象实参引用的副本(虽是副本,引用的对象是相同的)
(既不是按值传递的对象副本,也不是按引用传递的隐式引用)

错误对象类型

Error对象

error对象是包含错误信息的对象,是javascript的原生对象。当代码解析或运行时发生错误,javascript引擎就会自动产生并抛出一个error对象的实例,然后整个程序就中断在发生错误的地方.

属性

error对象包括两个属性:message和name。

message: 错误信息
name:    错误类型

try {
  xxxx
}
catch(e){
  console.log(e.message); // xxxx is not defined 
  console.log(e.name);    // ReferenceError
}

可以使用error()构造函数来创建错误对象。如果指定message参数,则该error对象将把它用做它的message属性
throw new Error('test'); // Uncaught Error: test

类型

Error (基类)
EvalError(eval错误)
RangeError(范围错误)
ReferenceError(引用错误)
SyntaxError(语法错误)
TypeError(类型错误)
URIError(URI错误)

错误事件 onerror

加载图像失败时会显示一个警告框。发生error事件时,
图像下载过程已经结束,也就是不能再重新下载了

var image = new Image();
image.src = 'smilex.gif';
image.onerror = function(e){
    console.log(e);
}

throw语句

throw语句用于抛出错误。抛出错误时,必须要给throw语句指定一个值,这个值是什么类型,没有要求

抛出错误的过程是阻塞的,后续代码将不会执行

try catch语句与捕获错误

try{
    //通常来讲,这里的代码会从头到尾而不会产生任何问题
    //但有时会抛出一个异常,要么是由throw语句直接抛出,要么通过调用一个方法间接抛出
}catch(e){
    //当且仅当try语句块抛出了异常,才会执行这里的代码
    //这里可以通过局部变量e来获得对Error对象或者抛出的其他值的引用
    //这里的代码块可以基于某种原因处理这个异常,也可以忽略这个异常,还可以通过throw语句重新抛出异常
}finally{
    //不管try语句是否抛出了异常,finally里的逻辑总是会执行,终止try语句块的方式有:
    //1、正常终止,执行完语句块的最后一条语句
    //2、通过break、continue或return语句终止
    //3、抛出一个异常,异常被catch从句捕获
    //4、抛出一个异常,异常未被捕获,继续向上传播
}

谈谈个人的学习方法

谈一下个人学习

上了这么多年的学, 我想每个人都形成了一套自己的学习方式,这里我也总结一下自己的. 当然是关于工作后的学习...

学习一门语言

- 语法,词法
- 语句
- 语句组合
- 应用
每个语法有其对的规则, 我们需要遵守, 不然就要踩坑
编程更多的是学习其**, 对业务逻辑进行抽象提炼, 用语句来实现的一个过程

学习源码

针对性的学习, 不建议从头看到尾;

可以从某个功能入手, 梳理其工作流程,周边依赖

学习一个知识点

遵循下面的规则

是什么: 如何定义, 背景
怎么做: 如何使用,
怎么实现: 内部运行机制
注意点: 需要注意的事项
应用场景: 相信存在即必然, 适用哪些地方

对象的属性描述符

对象的属性描述

对象的属性描述符是用来描述对象的值、是否可配置、是否可修改以及是否可枚举...

数据属性
访问器属性

数据属性

数据属性包含一个数据值的位置, 在这个位置可以读取和写入值。
数据属性有4个特性:

1.Configurable(可配置性): 是否可以使用delete删除属性以及是否可以修改属性描述符的特性,默认值为true
2.Enumerable(可枚举性): 是否出现在对象的属性枚举中,比如是否可用for-in循环返回该属性,默认值为true
3.Writable(可写性): 是否可以修改属性的值,默认值为true
4.Value(属性值): 包含这个属性的数据值,读取属性值的时候,从这个位置读;
                写入属性值的时候,把新值保存在这个位置。默认值为undefined

访问器属性

对象属性是名字、值和一组属性描述符构成的。而属性值可以用一个或两个方法替代,这两个方法就是getter和setter。
而这种属性类型叫访问器属性:

1.Configurable(可配置性): 是否可以使用delete删除属性以及是否可以修改属性描述符的特性,默认值为true
2.Enumerable(可枚举性): 是否出现在对象的属性枚举中,比如是否可用for-in循环返回该属性,默认值为true
3.getter: 在读取属性时调用的函数。默认值为undefined
4.setter: 在写入属性时调用的函数。默认值为undefined

和数据属性不同,访问器属性不具有可写性(Writable)。如果属性同时具有getter和setter方法,那么它是一个读/写属性。
如果它只有getter方法,那么它是一个只读属性。
如果它只有setter方法,那么它是一个只写属性。读取只写属性总是返回undefined

描述符方法(都来自于 Object 构造函数)

Object.getOwnPropertyDescriptor(obj, prop)

用于查询一个属性的描述符,并以对象的形式返回

var obj = {a:1};
Object.getOwnPropertyDescriptor(obj, 'a')
// {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(obj, 'b') // 对象不存在改属性时返回 undefined

Object.defineProperty(obj, prop, config)

用于创建或配置对象的一个属性的描述符,返回配置后的对象
使用该方法创建或配置对象属性的描述符时,如果不针对该属性进行描述符的配置,则该项描述符默认为false

var obj = {}
Object.defineProperty(obj, 'a', {
    value:1,
    writable: true
}) // 返回 { a: 1 }

Object.getOwnPropertyDescriptor(obj,'a') 
// {value: 1, writable: true, enumerable: false, configurable: false}

Object.defineProperties()

用于创建或配置对象的多个属性的描述符,返回配置后的对象

var obj = { a:1 }
Object.defineProperties(obj,{
    a: { writable: false },
    b: { value: 2 }
  })

Object.create()

使用指定的原型和属性来创建一个对象

var o = Object.create(Object.prototype,
  {
    a: { writable: false, value: 1, enumerable: true }
  }
)
o // { a:1 }
Object.getOwnPropertyDescriptor(obj,'a')
// {value: 1, writable: false, enumerable: true, configurable: true}

描述符详解

可配置性(Configurable)

可配置性决定是否可以使用delete删除属性,以及是否可以修改属性描述符的特性,默认值为true

# 设置Configurable:false后,无法使用delete删除属性
var o = { a:1 };
Object.defineProperty(o, 'a', {
    configurable:false
});
delete o.a;      // false
console.log(o.a);// 1

# 严格模式下删除为configurable为false的属性,会提示类型错误

# 使用var命令声明变量时, 变量的configurable为false
var a = 1;
Object.getOwnPropertyDescriptor(this,'a');
// {value: 1, writable: true, enumerable: true, configurable: false}

# 设置Configurable:false后,将无法再使用defineProperty()方法来修改属性描述符
// 设置Configurable:false后,只允许writable的状态从true变为false

可写性(writable)

可写性决定是否可以修改属性的值,默认值为true

# 设置 writable:false后,赋值语句会失效
var o = {a:1};
Object.defineProperty(o, 'a', {
    writable: false
});
o.a; // 1
o.a = 22 // 不起作用

# 严格模式下通过赋值语句为writable为false的属性赋值,会报错

可枚举性(Enumerable)

可枚举性决定属性是否出现在对象的属性枚举中,具体来说,for-in循环、Object.keys方法、JSON.stringify方法是否会取到该属性
用户定义的普通属性默认是可枚举的,而原生继承的属性默认是不可枚举的

var o = {a:1, b: 'bb'};
for(var i in o){
  console.log(o[i]);// 1 bb
}
// 由于原生继承的属性默认不可枚举,所以只取得自定义的属性 a:1, b:'bb'

var o = {a:1};
Object.defineProperty(o,'a',{ enumerable:false });
for(var i in o){
    console.log(o[i]); // undefined
}

# propertyIsEnumerable()方法用于判断对象的属性是否可枚举
var o = { a:1 };
console.log(o.propertyIsEnumerable('a')); // true

get和set

get是一个隐藏函数,在获取属性值时调用。
set也是一个隐藏函数,在设置属性值时调用,它们的默认值都是undefined。
Object.definedProperty()中的get和set对应于对象字面量中get和set方法

getter和setter取代了数据属性中的value和writable属性

# 给只设置get方法,没有设置set方法的对象赋值会静默失败,在严格模式下会报错
var o = {
  get a() {
    return 2;
  }
}    
o.a // 2
o.a = 111 // 没有设置set方法, 设置值无效

Object.defineProperty(o,'a',{
    get: function(){
      return 22;
    }
})
o.a // 22

# 只设置set方法,而不设置get方法,则对象属性值为undefined
var o = {
    set a(val) {
        return 2;
    }
}    
o.a = 1;
console.log(o.a); // undefined

Object.defineProperty(o,'a',{
    set: function(){
        return 2;
    }
})
o.a = 1;
console.log(o.a); // undefined

# set和get方法一起出现时
var o = {
    get a(){
        return this._a;
    },
    set a(val){
        this._a = val + 100;
    }
}
o.a = 1; // 设置值
console.log(o.a); // 访问,获取值 101

Object.defineProperty(o,'a',{
    get: function(){
        return this._a;
    },
    set: function(val){
        this._a = val + 100;
    }
})
o.a = 1;
console.log(o.a); // 101

对象的状态

属性描述符只能用来控制对象中一个属性的状态。而如果要控制对象的状态, 需要用到 Object 构造函数的一些其他方法

  • Object.preventExtensions()

使一个对象无法再添加新的属性,并返回当前对象

  • Object.isExtensible()

检测该对象是否可以扩展

  • Object.seal()

对象封印又叫对象密封,使一个对象不可扩展并且所有属性不可配置,并返回当前对象

  • Object.isSealed()

检测该方对象是否被封印

  • Object.freeze()

Object.freeze()方法使一个对象不可扩展,不可配置,也不可改写,变成一个仅可以枚举的只读常量,并返回当前对象

  • Object.isFrozen()

检测一个对象是否被冻结

正则类型

正则对象 regExp

表达式

var reg = /pattern/g
var reg = new RegExp('pattern','g')

g:表示全局(global)模式
即模式将被应用于所有字符串,而并非在发现第一个匹配项时立即停止

i:表示不区分大小写(case-insensitive)模式
即在确定匹配项时忽略模式与字符串的大小写

m:表示多行(multiline)模式
即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项

匹配模式: pattern

元字符

| 元字符 |名称 |匹配对象|
|:|:|:|:|
| . |点号 |单个任意字符(除回车\r、换行\n、行分隔符\u2028和段分隔符\u2029外)|
| [] |字符组 |列出的单个任意字符|
| [^] |排除型字符组 |未列出的单个任意字符|
| ? |问号 |匹配0次或1次|
| * |星号 |匹配0次或多次|
| + |加号 |匹配1次或多次|
| {min,max} |区间量词 |匹配至少min次,最多max次|
| ^ |脱字符 |行的起始位置|
| $ |美元符 |行的结束位置|
| | |竖线 |分隔两边的任意一个表达式|
| () |括号 |限制多选结构的范围,标注量词作用的元素,为反向引用捕获文本|
| \1,\2... |反向引用 |匹配之前的第一、第二...组括号内的表达式匹配的文本|

转义字符\

转义字符(escape)表示为 \被转译字符 的形式.

1.转译元字符
console.log(/\*/.test('*')); // true

2.特殊符号
\'	单引号
\"	双引号
\&	和号
\\	反斜杠
\n	换行符
\r	回车符
\t	制表符
\b	退格符
\f	换页符

3.双重转译 \/
/name\/age/.test('name/age') true

字符组[]

用方括号表示的一组字符,它匹配若干字符之一, 单个字符

# 范围 -
/[0-9]/.test('1') true
# 排除 [^] 表示在当前位置匹配一个没有列出的字符 
/[^0-9]/.test('1') false
# 简写
\d     数字,等同于[0-9]
\D     非数字,等同于[^0-9]
\s     空白字符,等同于[\f\n\r\t\u000B\u0020\u00A0\u2028\u2029]
\S     非空白字符,等同于[^\f\n\r\t\u000B\u0020\u00A0\u2028\u2029]
\w     字母、数字、下划线,等同于[0-9A-Za-z_](汉字不属于\w)
\W     非字母、数字、下划线,等同于[^0-9A-Za-z_]
# 任意字符 /[\s\S]/ || /[\w\W]/ || /[\d\D]/
console.log(/[\s\S]/.test('\r')) true

量词

{n}       匹配n次
{n,m}     匹配至少n次,最多m次
{n,}      匹配至少n次
?         相当于{0,1}
*         相当于{0,}
+         相当于{1,}

/\d{3}/ 匹配 3 位数字
/https?/  http和https两种协议

# 默认贪婪模式: 匹配到下一个字符不满足匹配规则为止
# 非贪婪模式: 量词后加问号'?'表示,表示尽可能少的匹配,一旦条件满足就再不往下匹配

分组和引用 ()

分组: 括号里的表达式,被称为子表达式
/\w{1,64}@([-a-zA-z0-9]{1,63}.)+[-a-zA-Z0-9]{1,63}/

捕获: 括号不仅可以对元素进行分组,还会保存每个分组匹配的文本,
等到匹配完成后,引用捕获的内容。因为捕获了文本,这种功能叫捕获分组

/(\d{4})-(\d{2})-(\d{2})/  年、月、日
RegExp.$1\RegExp.$2\RegExp.$3……到RegExp.$9分别用于存储第一、第二……第九个匹配的捕获组。
在调用exec()或test()方法时,这些属性会被自动填充
console.log(/(\d{4})-(\d{2})-(\d{2})/.test('2016-06-23'));//true
console.log(RegExp.$1);//'2016'
console.log(RegExp.$2);//'06'
console.log(RegExp.$3);//'23'
console.log(RegExp.$4);//''

捕获分组捕获的文本,不仅可以用于数据提取,也可以用于替换
console.log('2000-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$3-$2-$1'));//'01-01-2000'

反向引用: 允许在正则表达式内部引用之前捕获分组匹配的文本,形式是\num,num表示所引用分组的编号
// 检测重复字母
/([a-z])\1/
console.log(/([a-z])\1/.test('aa'));//true
console.log(/([a-z])\1/.test('ab'));//false

// 检测成对标签
/<([^>]+)>[\s\S]*?<\/\1>/

选择 |

断言 ^ $

1.单词边界 \b
console.log(/\ban\b/.test('an'));//true
console.log(/\ban\b/.test('and'));//false

2.起始位置
^和$,它们分别匹配字符串的开始位置和结束位置
function trim(str){
    return str.replace(/^\s+|\s+$/, '')
}  
console.log(trim('   hello world   '));//'hello world'

环视: (?=n): 表示前面必须是n才匹配
console.log(/a(?=b)/.exec('abc'));//['a']
console.log(/a(?=b)/.exec('ac'));//null
console.log(/a(?!b)/.exec('abc'));//null
console.log(/a(?!b)/.exec('ac'));//['a']

优先级

//从上到下,优先级逐渐降低

\                            转义符
() (?!) (?=) []              括号、字符组、环视
* + ? {n} {n,} {n,m}         量词
^ $                          起始结束位置
|                            选择

实例方法

exec()

找出匹配的所有位置和所有值
var string = 'j1h342jg24g234j 3g24j1';
var pattern = /\d/g;
var valueArray = []; // 值
var indexArray = []; // 位置
var temp;
while((temp=pattern.exec(string)) != null){
    valueArray.push(temp[0]);
    indexArray.push(temp.index);  
}
// valueArray => indexArray
["1", "3", "4", "2", "2", "4", "2", "3", "4", "3", "2", "4", "1"]
[1, 3, 4, 5, 8, 9, 11, 12, 13, 16, 18, 19, 21]

linux 系统常用命令

目录相关命令

  • mkdir:创建目录
-p  当需要创建父目录时创建父目录
-v  每次创建一个目录返回一个信息
  • ls:查看目录
-l  长格式显示具体属性  
-d  只查看目录本身  
-a  显示隐藏文件和目录  
-F  显示结果加上对应结束符  
-r  降序排序  
-t  按修改时间排序  
-h  显示占用的空间大小  
-i  显示索引节点inode节点号  
--time-style  格式化时间(--time-style=long-iso)
  • cd:切换目录
  • pwd:显示当前目录的绝对路径
-L  显示系统变量PWD的值,为逻辑路径,默认使用该参数  
-P  显示物理路径,链接文件会显示其指向的文件路径
  • mv:移动文件或目录到指定位置,移动目录时结尾不要加/
  • tree:以树形图形式显示目录结构
-a  显示目录的所有内容  
-d  只显示目录  
-L  显示目录层级,可以接数字指定  
-f  显示每个文件的完整路径  
-i  不显示横线  
-F  显示结果加上相应结束符

文件相关命令

  • cat:查看文件内容
-n  显示行号,
特别用法:cat  文件1 文件2 > 文件3   将文件内容上下合并并重定向到新文件中
  • touch:创建文件和更新时间戳
-a  只更新访问时间  
-m  只更新修改时间
  • vim:文本编辑器,由vi扩展而来,一般用于开发,有四种工作模式且每种模式有各自的命令集。
  • echo:输出指定字符串
-n 与下一个echo的输出在同一行显示  
-e  支持转义字符(\t,\n等)
  • cp:复制文件或目录
-r  递归复制  
-p  保持文件或目录的属性  
-f  强制复制  
-d  如果源文件是链接文件则拷贝链接文件而不是其指向的目标文件  
-a  相当于-rpd 
  • rm:删除文件或目录
-f  强制删除  
-r  递归删除,用于删除目录
  • grep:过滤结果,Linux三剑客之一
-i  不区分大小写  
-v  排除  
-E  过滤多个关键词  
--color  匹配结果显示颜色  
-n  显示行号  
-o  只显示匹配的内容  
-B  显示匹配行及其之前的n行  
-A  显示匹配行及其之后的n行  
-C  显示匹配行及其前后各n行
  • head:获取文件头部指定多少行的内容
-n  指定需要获取的总行数,可以简写为-2,-3等,默认获取前十行
  • tail:获取文件尾部指定多少行的内容
-n  指定需要获取的总行数,可以简写,默认获取最后十行
  • sed:过滤结果,Linux三剑客之一
-n  取消默认输出  
-i  修改文件  
-e  允许多项编辑  
-r  使用扩展的正则表达式
      功能参数: p  打印(^打印首行,$打印尾行)  
               s  替换  
               g  全局
  • awk:过滤结果,Linux三剑客之一
-F  指定分隔符(指定多个分隔符需要将多个分隔符写在方括号中在方括号后跟上+号
如指定两个分隔符空格和冒号  [ :]+),
内置变量NR  用于显示数据行的编号,可用于过滤文件,统计目录内的文件数等
  • cut:分割数据
-d  指定分隔符  
-f  指定获取分割结果的哪些位置的字段  
-c  指定获取哪些位置的字符
  • stat:查看文件详细属性
-c  以指定的格式输出
(%a:获取文件的权限并以八进制数字显示,%A:获取文件的权限并以可读的方式显示)
  • nl:查看文件时显示行号
  • less:分页显示文件内容
-N  显示行号
  • more:分页显示内容,无法向前翻页
  • chattr:设置文件或目录的属性
-i  设定文件不能被删除、改名、设定链接关系,同时不能写入或新增内容
  • tr:删除,替换,压缩标准输入中的字符
-d  删除指定字符  
-c  一般与-d参数配合使用删除指定字符外的所有字符  
-s  指定字符如果重复出现则只显示一个 
  • rename:文件重命名
  • rev:将字符串的第一个字符和最后一个字符位置互换
  • tac:将文件内容按行上下位置互换
  • split:用于切割文件内容,命令格式split 被切割的文件 新文件的文件名前缀
参数-l   指定文件切割的行数  
-a   指定新文件后缀长度   
-b   指定文件切割的大小(单位K、M。。。)   
-d   使用数字后缀
  • paste:用于将两个文件的内容合并,命令格式paste 文件1 文件2,将文件左右合并
-d   指定文件内容之间的分隔符 
-s   一行内容以一列的形式显示
  • dos2unix:将Windows文件格式转换为unix文件格式
  • diff:比较文件或目录的不同,结果中a为add c为change d为delete

网络相关命令

  • ifconfig:查看服务器网卡信息,参数-a  显示所有网卡信息
  • ifdown:禁用指定网卡
  • ifup:启用指定网卡
  • ip:一般用于查看网卡和路由信息
用法ip  addr查看网卡信息  
   ip  route查看路由信息
  • netstat:查看端口状态
-a  显示所有开启的端口  
-n  以数字形式显示  
-t  只显示TCP连接  
-p  显示进程名  
-l  只显示监听状态的端口
  • ping:检测与指定IP间是否连通
-t  Ping 指定的IP直到中断  
-n  指定ping包个数  
-a  将IP解析为主机名  
-l  指定ping包大小,最大值是65,527字节  
-w  ping的超时时间
  • mtr:路由追踪
-s  指定数据包大小  
-n  不做域名解析  
-a  指定发送数据包的IP(服务器有多个IP时使用)  
-i  指定ping的时间间隔,单位秒 
  • telnet:检测远程主机端口连通性
用法telnet  IP  端口
  • nmap:检测远程主机端口开放情况
用法namp  ip
  • route:查询与设置路由信息
-n  以数字形式显示  
-f  清空路由  
add  添加路由信息  
del  删除路由信息  
-net  目标网络  
-host  目标主机  
netmask  目标网络的子网掩码  
dev  指定路由生效的网卡  
default  默认路由  
gw  设置网关   
reject  屏蔽的路由

用户与用户组相关命令

  • useradd:新建用户账号
-c  添加注释信息  
-U  指定用户组  
-u  指定UID  
-d  指定家目录  
-e  指定账号失效时间  
-g  指定GID  
-G  指定附加组  
-M  不创建家目录  
-s  指定shell环境
  • userdel:删除账号
-r  删除该账号下所有文件和信息
  • usermod:修改账号属性
-l  修改用户名  
-L  锁定账号  
-U  解锁账号
  • chage:修改密码有效期
-l  列出密码有效期信息  
-E  指定密码失效时间  
-I  密码失效后多久锁定账号  
-m  密码最短多久需要修改  
-M  密码最长多久需要修改  
-m  用户家目录不存在就创建
  • passwd:设置修改密码
--stdin  从标准输入获取密码  
-d  清除用户密码  
-l  锁定账号  
-u  解锁账号  
-S  查看账号状态
  • id:查看用户ID
  • su:切换用户
用法su  -  用户名,不加用户名默认切换到root,
-c  以指定用户身份执行单条命令
  • chmod:修改文件或目录的权限设置
参数-R  权限设置对目录下所有文件和子目录生效
754 777...
  • chown:修改文件或目录的属主
  • chgrp:修改文件或目录的属组
  • groupadd:增加用户组,参数-g  指定GID
  • groupdel:删除用户组

打包压缩相关命令

  • tar:打包命令
参数--exclude  打包时排除指定文件  
-c  创建归档文件  
-x  解开归档文件  
-C  指定释放目录  
-f  使用归档文件  
-z  使用gzip压缩和解压  
-j  使用bzip2压缩和解压  
-v  显示打包和解包的过程  
-t  查看归档文件中包含哪些文件

搜索操作相关命令

  • find:查询命令
命令格式find  查找的目标路径  参数  -exec  命令  {}\;
参数-type  按类型查找,f文件,d目录,c字符,l符号链接,sSocket文件,b块设备
-mtime  按修改时间查找+n表示多少天之前修改的,n表示第几天修改的,-n表示多少天内修改的  
-!  取反(需写在取反条件之前,如-! -name 'test.txt')  
-name  按名字查找  
-size  文件大小(+nM|K  大于nM,n  等于nM,-n  小于nM)  
-a  表示左右条件同时满足  
-o  表示左右条件满足一个即可  
-exec  用于对查询结果做进一步处理   
-delete  删除,详细参数说明http://man.linuxde.net/find
  • which:查询扩展命令的绝对路径

命令帮助相关命令

  • man:查询扩展命令的帮助信息
  • help:查询内置命令的帮助信息 

服务器信息相关命令

  • top:查看系统实时运行状态信息
参数-H  显示线程信息  
-p  显示指定进程号的进程信息  
-u  显示指定用户的进程信息  
-i  指定刷新时间间隔,功能选项
-k  杀死指定进程号的进程  
q  退出
  • strace:跟踪显示进程的系统调用信息,参数-p  显示指定进程的系统调用信息
  • uname:显示系统信息
参数-a  显示所有信息  
-m  显示操作系统位数  
-n  显示主机名  
-r  显示内核版本  
-s  显示操作系统名称  
-p  显示处理器位数
  • whoami:查看当前用户用户名
  • who:查看所有登陆用户的登陆信息
  • hostname:查看和临时设置主机名
  • runlevel:查看系统运行级别
  • dmesg:查看系统故障信息
  • env:查看系统环境变量
  • history:显示命令行历史记录,参数-c  清空历史记录  -d  删除指定编号的命令行历史
  • hwclock:查看设置硬件时间,参数-w  与系统时间同步  -r  显示当前硬件时间
  • date:查看设置系统时间,参数-s  设置系统时间  +时间日期格式  以指定格式显示时间,date命令详细说明
  • ulimit:查看和修改系统限制,参数-a  显示系统所有限制信息  -n  查看或设置系统最大可用文件描述符(ulimit -n 65535)  
  • dumpe2fs:查看文件系统信息
  • df:查看磁盘空间以及inode节点使用信息,参数-h  可读的格式显示大小  -i  显示inode信息
  • du:查看目录或文件占用的空间大小,参数-s  只显示指定目录占用空间的大小总和  -h  以可读的方式显示大小
  • lsof:列出被进程调用的文件的信息,一般用于查看某个端口是否已启用,参数-i  列出符号条件的进程(4即ipv4,6即ipv6,协议,:端口,@ip),详细参数说明http://man.linuxde.net/lsof
  • last:显示用户最近登陆信息
  • lastlog:显示所有用户最近一次登陆信息

安全相关命令

  • sudo:使普通用户可以使用超出权限的命令,用法sudo cmd,比如sudo /etc/init.d/network restart,参数-l  查看当前用户被授权执行的命令  -v  追踪最新的时间戳  -u  指定用户身份执行命令  -k  删除时间戳
  • visudo: 配置sudo权限的编辑命令,参数-c  检查配置文件语法是否正确
  • getenforce:查看selinux运行级别
  • setenforce:设置selinux运行级别

进程相关命令

  • ps:查看当前服务器运行中的进程
-e  显示系统所有进程  
-f  以完整格式显示  
-a  显示当前终端下的所有进程  
-u  以用户为主的格式显示  
-x  显示当前用户在所有终端下的进程

其他命令

  • xargs:用于从标准输入获取内容,并将该内容作为后面命令的参数,主要用于将前面命令产生的多行结果合并为一行再让其后的命令进行处理,参数-n  表示分组数,没有该参数默认在一行显示  -i  将标准输入的内容与{}关联起来
  • seq:生成数字序列,命令格式seq 参数 起始数字 步长 结束数字,参数-s  指定分隔符,默认是换行符
  • sort:排序命令,参数-r  降序排序  -n  按数值排序  -k  指定按第几列的数据排序  -u  排除所有重复行  -t  指定每列的分隔符
  • uniq:报告或排除重复数据,参数-c  统计数据行重复次数  -d  只显示重复的数据行  -u  显示只出现一次的数据行
  • alias:查询和设置命令别名,命令格式alias 别名=‘命令’,直接执行alias可查询所有已设置的别名
  • unalias:取消别名,命令格式unalias 别名
  • rz:上传文件,该命令由包lrzsz提供
  • sz:下载文件,该命令由包lrzsz提供
  • init:修改系统运行级别
  • shutdown:关闭与重启系统,参数-h  关机  -r  重启
  • halt:关机
  • reboot:重启
  • chkconfig:查看与设置开机启动服务,参数--list  显示指定服务所有级别下的自启动设置,不加服务名显示所有服务  --add  将指定服务添加到自启动管理  --del  将指定服务从自启动管理中删除  --level  指定服务在哪些运行级别自启动,比如chkconfig --level 35 mysqld on,该命令行不加--level参数表示mysqld服务在所有运行级别自启动
  • source:用于执行shell程序,shell程序中新建和修改的变量会影响当前shell环境,可以用.代替source
  • sh:用于执行shell程序,新建子shell环境并执行shell程序,shell程序中新建和修改的变量不影响当前shell环境
  • export:修改环境变量
  • mount:挂载设备,参数-t  设备类型(iso9660:光盘或镜像,vfat:fat32设备,ntfs:ntfs设备,nfs:unix网络共享,cifs:Windows网络共享)  -o  挂载方式(loop:将文件作为磁盘分区,ro:只读,rw:读写,iocharset:指定访问文件系统所用字符集,例如iocharset=utf8,remount:重新挂载)
  • umount:取消挂载
  • ntpdate:网络时间同步,参数-u  越过防火墙与时间同步服务器进行时间同步(ntp.api.bz,上海的时间同步服务器)
  • basename:获取路径中最后一个路径分隔符后的内容
  • dirname:获取路径中最后一个路径分隔符前的内容
  • file:查看文件类型
  • ln:创建链接文件,参数-s  创建软链接
  • readlink:获取符号链接所指向的源文件文件名
  • md5sum:生成和校验md5值,参数-c  从指定文件获取md5值并校验  --status  校验成功是不输出任何信息  -w  当校验不正确时输出警告信息
  • wc:参数-l   查看行数   -L   查看最长一行的字符数
  • tee:内容输出到屏幕同时重定向到指定文件中,参数-a   追加

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.