前端知识图谱
计算机基础
产生死锁的必要条件
- 互斥条件:进程对所分配的资源不允许其他进程访问
- 请求和保持条件: 进程获取其他资源,却被其他进程占用,但又对自己保持的资源不放手
- 不可剥夺:进程已经获得资源,除非自己使用完释放,否则不可被剥夺
- 循环等待:发生死锁之后,必然存在一个进程和资源之间的环形链
前端
前端优化方案
网络相关
DNS解析优化
减少DNS查询
- 将资源都放在一个域下面,这样访问整个网站只需要一次DNS查询,但是因为客户端针对每一个域有一定数量的并行度,那么就会出现下载资源时的排队现象。
- 因此建议一个网站中至少使用2个域,但是不多于4个域来提供资源
DNS预解析
浏览页面时,浏览器会在加载网页时对网页里的域名进行解析缓存。这时候在访问时就无需解析,减少了等待时间
<!-- off为关闭,ON为开启 -->
<meta http-equiv="X-dns-prefetch-control" content="on" />
<link rel="dns-prefetch" href="http://renpengpeng.com" />
<!--如果不确定是http还是https连接的话建议如下写法 -->
<link rel="dns-prefetch" href="//renpengpeng.com" />
HTTP
减少HTTP请求次数(CSS精灵、字体图标代替图片、合并脚本):
- CSS sprites
- 即CSS精灵,将多张图片融合到一幅图里,通过CSS布局到网页上,可以减少图片数量,带来速度上的提升。
- 合并后的图片比分离的图片总和要小
- 使用字体图标代替大量图标的图片
- 合并压缩脚本和样式表
使用CDN托管网站静态资源
- CDN(内容发布网络)是一组分布在多个不同地理位置的Web服务器,用于更加有效地向用户发布内容
- 在优化性能时,向特定的游湖发布内容的服务器的选择基于对网络拥堵的测量。
- 例如CDN可能选择网络阶跃数最小的服务器,或者具有最短响应时间的服务器
- 缺点:
- 响应时间可能受到其他网站流量影响。CDN服务提供商在其所有客户之间共享Web服务器组
- 如果CDN服务质量下降,工作质量也将下降
- 无法直接控制组件服务器
选取合适的缓存策略
- 不需要缓存的 no-store
- 频繁变动的 no-cache配合ETag
- 不经常变动的max-age
使用HTTP2.0
- 多路复用提高加载速度
- Header压缩减少请求数据大小
- 服务端推送
服务器端使用gzip压缩内容
- 从HTTP1.1开始,客户端可以发送Accept-Encoding:gzip,deflate请求头来表示对压缩的支持
- 如果Web服务器看到这个请求头,就会使用客户端列出的一种方法来压缩。
- 服务器通过Content-Encoding来通知Web客户端它选择的压缩方式
使用外部的CSS和js
- 当脚本或者样式是从外部引入的文件,浏览器就有可能缓存它们,从而在之后加载的时候直接使用缓存
- HTML文档的大小减小,提高了加载速度
避免空的src和href、避免重定向
预加载 强制浏览器请求想要尽早获取的资源
预渲染 下载的文件先在后台渲染
渲染优化
懒加载 不关键的资源延后加载
懒执行
文件优化
图片优化
- 字体图标
- 移动端取小图放弃加载原图
- base64搞小图
- 雪碧图
- 选取合适的格式
- 能支持WebP的浏览器就用,因为WebP有更好的图像数据压缩算法
- 小图使用PNG SVG base64
- 照片使用JPEG
将CSS放到顶部
- 这并不会降低实际页面加载时间,但是会减少页面首屏加载时间,使页面内容逐步呈现
- 将样式表放在文档底部会阻止浏览器中的内容逐步出现,为了避免当样式变化时重绘页面元素,浏览器会阻塞内容逐步呈现,造成“白屏”
Js放到底部
- js的下载和执行会阻塞DOM树的构建,所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容
- defer并行下载但是HTML解析完毕后顺序执行
- async适用于没有依赖的,加载和渲染后续文档元素的过程与JS的加载和执行并行无序进行
监控前端页面错误
对于代码运行错误,通常的办法是使用 window.onerror
拦截报错。该方法能拦截到大部分的详细报错信息,但是也有例外
- 对于跨域的代码运行错误会显示
Script error.
对于这种情况我们需要给script
标签添加crossorigin
属性 - 对于某些浏览器可能不会显示调用栈信息,这种情况可以通过
arguments.callee.caller
来做栈递归
对于异步代码来说,可以使用 catch
的方式捕获错误。比如 Promise
可以直接使用 catch
函数,async await
可以使用 try catch
但是要注意线上运行的代码都是压缩过的,需要在打包时生成 sourceMap 文件便于 debug。
对于捕获的错误需要上传给服务器,通常可以通过 img
标签的 src
发起一个请求。
其他
避免使用CSS表达式
精简js、使用webpack
-
精简
从代码中移除不必要的字符以减少文件大小
-
混淆
移除注释和空白符,还会改写代码。把函数和变量名转换成更短的字符串
CSS的精简
- 移除空白和注释
- 合并相同的类
- 移除不使用的类
- 使用缩写
W3C
为了解决网络应用中不同平台、技术、开发者带来的不兼容问题,万维网联盟制定了一系列标准来督促开发者遵循。标准的内容包括使用语言的规范、开发中使用的准则和解释引擎的行为。标签都成对出现、属性值都放到引号里。
一般前端页面哪三部分组成
-
结构层
由html或XHTML的标记语言负责创建。
-
表现层
css负责创建表现层,使页面的结构标签更有美感
-
行为层
行为是指页面和用户具有一定的交互,同时页面结构或表现发生变化,主要由JavaScript组成
前端安全性
CSRF(跨站请求伪造):攻击者伪造成用户身份进行操作,服务器识别不了请求的合法性
-
攻击方式:在用户的session尚未到期时诱导用户打开恶意URL,从而伪造成用户的身份向服务器发送合法的请求
-
防御措施
-
验证HTTP的Refer字段
-
在请求地址增加token并验证
在用户登录后放在session中,每次请求时把session的token与请求的token比对
-
在HTTP头增加自定义属性并验证
-
XSS(跨站脚本攻击):恶意攻击者往Web页面中插入恶意脚本,当用户浏览该页时嵌入其中的脚本会被执行,从而达到恶意攻击用户的目的
反射型:经过后端,不经过数据库。
http://www.test.com/message.php?send=Hello,World!
接收者将会接收信息并显示Hello,Word
非正常发送消息:
http://www.test.com/message.php?send=<script>alert(‘foolish!’)</script>!
接收者接收消息显示的时候将会弹出警告窗口
存储型:经过后端和数据库
防御措施:
- 编码 < > / ’ " &
- 表单校验
- 对于重要的cookie设置http only,不允许在js中获取该cookie
- 过滤或移除特殊的html标签
- 过滤js事件的标签 例如 “οnclick=”, “onfocus” 等等
HTML
针对移动端浏览页面,需要全屏单页面,不希望用户放大屏幕
data-xxx
属性的作用是?
cookies、sessionStorage、localStorage
特性 | cookies | sessionStorage | localStorage |
---|---|---|---|
数据的生命周期 | 一般由服务器生成,可以设置生效时间。如果在浏览器端生成,默认浏览器关闭后失效。 | 关闭页面后被清除 | 除非被清除,否则永久保存 |
存放数据大小 | 4k左右 | 一般5M | |
与服务端通信 | 每次携带在HTTP头里 | 仅在浏览器保存 | |
易用性 | 需要程序员自己封装,原生的cookie接口不太友好 | 原生接口可以接受,亦可再次封装来对Object和Array7有更好的支持 | |
使用场景 | 记住密码。服务器在Cookie中存入一段辨识用户的标识码,下次读取这个值就可以判断用户是否登录。 | 页面传值; | 管理电商网站的购物车;保存H5游戏产生的本地数据;存储固定不变的页面信息,这样就不需要每次都重新加载了 |
HTTPONLY
IE6的SP1在cookie里引入了http-only
,告知浏览器该cookie决不能通过js的API访问
CSS
@import与link
link | @import | |
---|---|---|
加载内容 | CSS、RSS 定义文档与外部资源的关系 | 只能加载CSS |
加载时间 | 页面载入时同时加载 | 页面网页完全载入后加载 |
兼容性 | 无 | 低版本浏览器不支持 |
DOM | 支持用js控制DOM改变样式 | 不支持js |
display属性的计算值
- float: left // block
- position: absolute // block
- position: relative // inline
- visibility: hidden // inline
- display: block; display: inline; display: xxx; float:left; // block
- display: flex;(float属性失效) position: fixed // block
- inline-flex // flex
- grid // grid
- inline-grid // grid
**如果一个元素是绝对定位元素(position为fixed或absolute),float的值设置不为none,对于浮动元素或绝对定位元素,计算值由声明值确定;对于根元素,如果声明值为(inline-)table,都会得到计算值table,声明为none时会得到计算值none,其他所有display值都计算为block **
取值 | 特征 | 标签 | 计算值 |
---|---|---|---|
block 块级元素默认值 | 1. 可以设置宽高、内外边距 2. 不支持vertical-align | address、article、aside、form、hr、ul、li等 | |
inline 行内元素默认值 | 1. 内容撑开宽度 2. 不支持宽高、内外边距 3. 代码换行解析成空格 4. 不支持的样式clip、background-position、clear、clip、宽高、overflow、text-align、text-indent、text-overflow | a、br、code、em、i、span等 | block |
inline-block | 1. 不设置宽度时,内容撑开宽度 2. 支持宽高、内外边距 3. 不支持clear? | block | |
list-item | |||
run-in | block | ||
table | table | ||
inline-table | table | ||
table-header-group | block | ||
table-row-group | block | ||
tbale-row | block | ||
table-cell | 垂直对齐 | block | |
table-column | block | ||
table-column-group | block | ||
table-caption | block | ||
flex | |||
inline-flex | flex | ||
grid | |||
inline-grid | grid |
盒模型、边界塌陷、负值作用
-
盒模型
- ie678的怪异模式?使用IE盒模型(box-sizing:border-size)
- Chrome ie9+ ie678(标准模式)使用标准盒模型(box-sizing:content-box)
-
边距塌陷
边距折叠发生在同一BFC的块级元素之间,上下边距是边距较大值而不是边距之和。
会发生边距折叠的三种情况:
-
相邻元素之间
毗邻的两个元素之间的外边距会折叠(除非后一个元素需要清除之前的浮动)。
-
父元素与其第一个或最后一个子元素之间
如果在父元素与其第一个子元素之间不存在边框、内边距、行内内容,也没有创建块格式化上下文、或者清除浮动将两者的
margin-top
分开;或者在父元素与其最后一个子元素之间不存在边框、内边距、行内内容、height
、min-height
、max-height
将两者的margin-bottom
分开,那么这两对外边距之间会产生折叠。此时子元素的外边距会“溢出”到父元素的外面。 -
空的块级元素
如果一个块级元素中不包含任何内容,并且在其
margin-top
与margin-bottom
之间没有边框、内边距、行内内容、height
、min-height
将两者分开,则该元素的上下外边距会折叠。
-
-
负值作用
BFC
- 创建格式化上下文方式
- 根元素或包括根元素的元素
- 浮动元素 float不为none
- 绝对定位 position为fixed或absolute
- 行内块元素 display为inline-block
- 表格单元格 display为table-cell 表格单元格默认
- 表格标题 display为table-caption
- 匿名表格单元格 display为table、table-row等
- overflow不为visible
- display为flow-root
- contain为layout content strict
- 弹性元素 flex inline-flex
- 网格元素 grid inline-grid
- 多列元素
- BFC的作用
- 清除内部浮动:对子元素设置浮动后,父元素的高度塌陷为0.为了解决这个问题,可以把父元素的高度设置为0
- 解决边距重叠:相邻的盒子的外边距会重叠,可以把这两个盒子都变成BFC
- 创建自适应两栏布局:
垂直居中
- 单行文本,height和line-height设置同一高度
- 子元素定高:position:absolute top:50% margin:-高度的一半
- 子元素不定高:position:absolute top:50% translation:(0, -50%)
- display: table-cell; vertial-align: center
- 父元素不设置高度,设置padding
- 弹性布局:display:flex;align-items:center
三角形
#triangle {
width: 0;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid yellow;
}
#round {
height: 0;
width: 0;
border: 100px solid red;
border-radius: 100px;
}
border-radius: 圆角边框
文字截断
单行文本截断 text-overflow
div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
雪碧图
将网页中需要用到的小图标合并在一张图片中,使用background-position来定位每个小图标的位置。
优点:
- 减少HTTP请求数,提高加载速度
- 文件的大小降低(因为单独分割的每一张图片都有自己的色表信息),页面加载时间降低
缺点:
- 影响浏览器的缩放功能(需要防止雪碧图中相邻的图片被显示出来)
- 拼图维护繁琐
- CSS的编写困难提升
字体图标
利用字体来显示网页中的纯色图标,或者特殊字体
优点:
- 灵活性:缩放、改变颜色、产生阴影和透明效果
- 轻量性:体积小。一旦图标字体加载,图标马上就能渲染出来。可以减少HTTP请求,还可以配合H5离线存储做性能优化
- 兼容性好
缺点:
- 只能被渲染成单色或渐变色
- 制作svg比较麻烦
响应式布局
-
允许网页宽度自适应
<meta name="viewport" content="width=device-width,initial-scale=1" /> // viewport是网页默认的宽度和高度,网页宽度默认等于屏幕宽度,原始缩放比例为1.0,网页初始大小占屏幕面积的100%
-
不使用绝对宽度
不指定像素宽度,指定百分比宽度。或者width:auto
-
字体不使用绝对大小px,使用相对大小em
-
流式布局。让各个区块的位置都是浮动的,不是固定不变的。
浮动的好处是,如果宽度太小,放不下两个元素,后面的元素会自动滚动到前面元素的下方,不会再水平方向溢出,避免了水平滚动条的出现。
-
选择加载CSS
自动探测屏幕宽度,然后加载相应的CSS文件
<link rel="stylesheet" type="text/css" media="screen and (max-device-width: 400px)" href="tinyScreen.css" /> @import url("tinyScreen.css") screen and (max-device-width: 400px);
-
CSS的@media规则
同一个CSS文件中,也可以根据不同的屏幕分辨率,选择应用不同的CSS规则。
@media screen and (max-device-width: 400px) { .column { float: none; width:auto; } #sidebar { display:none; } }
上面的代码意思是,如果屏幕宽度小于400像素,则column块取消浮动(float:none)、宽度自动调节(width:auto),sidebar块不显示(display:none)。
-
图片自适应
img { max-width: 100%;}
JS
ES
async和defer
- ``
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。
- ``
有 async,加载后中断HTML解析立即执行。
- ``
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
数据类型
undifined null boolean string number symbol object
js的typeof
undifined boolean number string function object symbol
null和undefined的区别
-
转为Number的取值不同
- null 0
- undefined NaN
-
用法不同
- null表示没有对象,作为函数的参数,表示该函数的参数不是对象;
- 作为对象的原型链的终点 Object.getPrototypeOf(Object.prototype) = null
- undefined表示缺少值,就是此处应该有一个值,但是没有被定义
- 变量被声明但是没有赋值 undefined
- 调用函数时,应该提供的参数没有被提供
- 对象没有赋值的属性
- 函数无返回值,默认返回undefined
var i; i // undefined function f(x){console.log(x)} f() // undefined var o = new Object(); o.p // undefined var x = f(); x // undefined
类型转换
- Boolean
true | false | |
---|---|---|
number | 数字 | -0、NaN、0、+0 |
string | 非空字符串 | ‘’ |
undefined | undefined | |
null | null | |
object(包括数组) | 所有对象 |
- 对象转基本类型时,首先会调用valueOf,然后调用toString。调用优先级最高的是Symbol.toPrimitive
let a = {
valueOf() {
return 0;
},
toString() {
return '1';
},
[Symbol.toPrimitive]() {
return 2;
}
}
1 + a // => 3
'1' + a // => '12'
运算符
[] == false([]转数值为0) 、 但是!![] == true(先转布尔,任何对象转布尔都是true,除了null)
变量提升和函数提升
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
alert(foo);
}
bar();// 10
在代码运行前,函数声明和变量定义(定义提升,赋值不提升)通常会被解释器移动到其所在作用域的最顶部.
在作用域中,不管变量和函数写在什么位置,所有变量会被整体提升到作用域顶部,所有函数也会被整体提升到作用域顶部,但是函数整体在变量整体的后面。
var foo = 1;
function bar() {
var foo;
if (!foo) {
foo = 10;
}
alert(foo);
}
bar();
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
alert(a);
var a;
function b() {
function a() {}
a = 10;
return;
}
a = 1;
b();
alert(a);// 1
// 函数声明
function foo() {
console.log('function declaration');
}
// 匿名函数表达式
var foo = function() {
console.log('anonymous function expression');
};
// 具名函数表达式 这个函数名只能在此函数内部使用
var foo = function bar() {
console.log('named function expression');
};
function hoistFunction() {
foo(); // 2
var foo = function() {
console.log(1);
};
foo(); // 1
function foo() {
console.log(2);
}
foo(); // 1
}
hoistFunction();
function hoistFunction() {
var foo;
function() {
console.log(1);
};
function foo() {
console.log(2);
}
foo(); // 2
foo = function() {
console.log(1);
};
foo(); // 1
foo(); // 1
}
hoistFunction();
var func = function a() {
console.log(typeof a);
}
func(); // function
console.log(typeof a);// undefined
a=3;
a.prop=4; // 赋值不成功
console.log(a+a.prop); // NaN
console.log(typeof a); // number
str = 'a';
str.prop = 'b';
console.log(str+str.prop); // aundefined
console.log(typeof str+str.prop);// stringundefined
var a = {prop: undefined};
var func = function a() {
console.log(typeof a);
}
func();// function
console.log(typeof a); // undefined 具名函数表达式的函数名只能在函数内部使用
a=3;
a.prop=4;// 赋值不成功
console.log(a+a.prop); // 1+undefined = NaN
console.log(typeof a); // number
str;
str = {prop};
str = 'a';
str.prop = 'b'; // 不成功
console.log(str+str.prop); // aundefined
console.log(typeof str+str.prop); // stringundefined
this
详解JavaScript中的this : 梳理了this绑定规则,优先级和实际的项目中的问题
彻底弄懂js中的this指向: 按情况列举this指向
概述
指向调用的上下文
当执行js代码时,会产生三种执行上下文
- 全局执行上下文
- 函数执行上下文
- eval执行上下文
function foo (num) {
console.log("num: ", num);
// 记录foo被调用次数
this.count ++;
}
foo.count = 0;
foo(1); // 全局调用 this指向window
调用方式
对于下面这段代码,在foo中试图调用bar函数,是否成功调用,取决于环境
function foo(){
var a = 2;
this.bar();
}
function bar(){
console.log(this.a);
}
foo();
- window,浏览器环境下,全局声明的函数放在了window下,foo内的函数调用时this指向window。
- nodejs:在node环境下,声明的函数不会放在global全局对象下,因此在foo函数里调用this.bar函数会报错this.bar is not a function
this绑定规则
默认绑定
当函数调用属于独立调用(不带函数引用的调用),无法调用其他的绑定规则,我们称为默认绑定,在非严格模式下绑定到全局对象,严格模式下绑定到undefined
严格模式下调用
'use strict'
function demo(){
console.log(this.a); // TypeError: Cannot read property 'a' of undefined
}
const a = 1;
demo();
非严格模式下,在浏览器window全局对象下会将a绑定到window.a
function demo(){
console.log(this.a); // 1
}
let a = 1;
demo();
非严格模式下,在node环境中,不会将a绑定到global,因此下面输出undefined
function demo(){
console.log(this.a); // undefined
}
let a = 1;
demo();
注意:项目代码中,要么使用严格模式要么使用非严格模式,不要混合使用,也许会给你造成一些意外的bug。
隐式绑定
var obj = {
name: 'zhangsan',
getName: function () {
console.log(this.name);
},
}
obj.getName(); // zhangsan
var getName = obj.getName();
getName(); // 此时又变成了默认绑定,这是隐式绑定的隐患:丢失绑定对象
显式绑定
call、apply、bind
new绑定
优先级
- new绑定
- 显式绑定
- 隐式绑定
- 默认绑定
箭头函数继承自外部函数调用的this绑定
arguments是什么?是数组吗?
function a() {
console.log(arguments); // { '0': 1, '1': 2, '2': 3 }
var args = Array.prototype.slice.call(arguments);
console.log(args); // [ 1, 2, 3 ]
}
a(1,2,3);
剩余参数
- 剩余参数只包含没有对应形参的实参,而arguments对象包含了传给函数的所有形参
- arguments对象不是一个真正的数组,而剩余参数是Array实例
- arguments对象还有一些附加的属性(如callee属性)
闭包
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000*i);
} // 使其输出为0,1,2,3,4 但是输出了5,5,5,5,5
-
把var换成let
-
使用闭包
for (var i = 0; i < 5; i++) { (function(i) { setTimeout(function() { console.log(i); }, i * 1000); })(i); } // 0,1,2,3,4
使用立即执行函数形成闭包,切断外部对i的引用
-
不使用闭包:
for (var i = 0; i < 5; i++) { (function() { setTimeout(function() { console.log(i); }, i * 1000); })(i); } // 5,5,5,5,5
内部没有对i的引用,因此隔段时间输出5
-
这样:
for (var i = 0; i < 5; i++) { setTimeout((function(i) { console.log(i); })(i), i*1000); }
setTimeout的第一个参数只接受单句code或者函数,那么立即执行函数是undefined。因此setTimeout失效了,因此就立即执行函数,输出0-4
-
promise
setTimeout(function() { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(3); }).then(function() { console.log(4); }); console.log(5);
首先碰到一个setTimeout,会设置一个定时,定时结束后放到任务队列里,故而刚开始不会输出1;promise里面的函数是直接执行的,故而输出
2, 3
,then会放到当前tick后,但还是在当前tick里面,故而先输出5,然后输出4,最后一个tick是1,故而最后输出2, 3, 5, 4, 1
-
垃圾清除
instanceof的原理
instanceof 检测一个对象A是不是另一个对象B的实例的原理是:查看对象B的prototype指向的对象是否在对象A的[[prototype]]链上。如果在,则返回true,如果不在则返回false。不过有一个特殊的情况,当对象B的prototype为null将会报错(类似于空指针异常)。
var a=1,b=2,c=3;
function getFunc(a) {
return function (b) {
console.log(a,b,c);
}
}
var func = getFunc(4);
func(5);// 453
function A() {}
function B() {}
function C() {}
A.prototype = B.prototype = {};
C.prototype = Object.create(A.prototype);
var a = new A();
var b = new B();
var c = new C();
console.log(a instanceof B, b instanceof A, c instanceof A, a instanceof C);
// true true true false
// 检测B的原型对象是否在A的原型链上 是 是 是 否
Object.create(proto[, propertiesObject])
// 新创建对象的原型对象,要添加到新对象的可枚举属性(null 或 object)
如何区别{}和[]
- Array.isArray
- typeof a === ‘object’ && length
Object.prototype.toString.call([]) === '[object Array]' Object.prototype.toString.call({}) === '[object Object]'
- instanceof Array
- constructor
call apply bind
-
call和apply就是改变this的值,区别在于传参的方法
// apply以数组传入arguments function apply1(num1, num2) { return sum.apply(this, [num1, num2]); } // call以多参数的传入arguments function call1(num1, num2) { return sum.call(this, num1, num2); }
-
bind后函数不会执行,只是返回一个改变了上下文的函数副本,而call和apply是直接执行函数
var button = document.getElementById('button'), text = document.getElementById('text'); button.onclick = function() { alert(this.text); // alert "text" }.bind(text);
【问题】ie678不支持bind,如何模拟bind?
if (!function() {}.bind) { Function.prototype.bind = function(context) { var self = this, // this指向调用它的对象,而非prototype args = Array.prototype.slice(arguments); return function() { return self.apply(context, args.slice(1)); } } }
原型链、对象、构造函数
-
构造函数用来构造函数时初始化对象
-
原型:
-
__proto__
指向构造函数的prototype,__proto__
连接存在于实例与构造函数的原型对象之间new做的事情
var obj = {}; obj.__proto__=Base.prototype; Base.call(obj);
-
-
关系
- 构造函数》》》prototype》》》原型对象
- 所有原型对象包含一个constructor属性,constructor属性指向构造函数
- 原型对象是构造函数的一个实例
- 原型对象》》》constructor》》》构造函数
- 构造函数》》》new》》》实例对象
- 实例对象》》》
__proto__
》》》构造函数的原型对象》》》原型对象
- 构造函数》》》prototype》》》原型对象
-
原型链
每个对象都有自己的原型对象,原型对象本身也是对象,原型对象也有自己的原型对象,这样就形成了一个链式结构,叫做原型链
例如:访问某个对象的属性,首先在对象本身找,若没有,去原型对象找,一直找到原型链的终点;如果是修改对象的属性,若这个实例化对象中有该属性,就修改,没有该属性,就添加这个属性
继承
原型链继承
每个构造函数都有一个原型对象,原型对象都包括一个指向构造函数的的指针,实例都包含一个指向构造函数的原型对象的指针。让原型对象等于另一个对象的实例
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
}
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubvalue = function () {
return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue());// true
SubType的原型是SuperType的实例,拥有SuperType的实例拥有的全部属性和方法,内部有一个指针,指向了SuperType的原型。则instance指向SubType的原型,SubType的原型指向SuperType的原型。
问题:
- 所有子类实例共享构造函数的原型,也就是一个父类实例,原型上包含引用类型的属性时,一个子类实例修改了原型,会影响其他实例
- 创建子类实例时,不能向父类的构造函数传递参数。(没有办法在不影响所有实例的情况下,给父类的构造函数传递参数)
构造函数继承
function SuperType() {
this.property = true;
}
function SubType() {
// 继承了SuperType,优势是可以传参数
SuperType.call(this);
}
问题:
- 每个子类都调用父类的构造函数,则方法复用无从谈起
- 父类的原型的方法,也对子类不可见
组合继承
使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。既能实现方法的复用,又保证每个实例都有它自己的属性
function SuperType(name) {
this.name = name;
this.colors = ['red'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
}
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayage = function () {
console.log(this.age);
}
var instance = new SubType();
console.log(instance.getSuperValue());// true
class
class B extends A {
constructor() {
// super前使用this,报错
super();
}
}
B.__proto__ = A;
B.prototype.__proto__ = A.prototype;
遍历对象属性和方法
https://www.cnblogs.com/chenyablog/p/6477866.html
Object.keys()
返回一个数组,包括对象自身的(不含继承)的所有可枚举属性(不包含Symbol属性)
for… in
自身的和继承的可枚举属性(不包括Symbol)迭代一个对象的可枚举属性(包括原型链上的可枚举属性,即自定义的属性)
// 遍历object
for (val in obj) {
console.log(val); // name, age, id
console.log(obj[val]); // Li, 22, 14353147
}
// 遍历string
String.prototype.foo = 'you';
var str = 'love';
for (idx in str) {
console.log(str[idx]); // l o v e you
}
// 遍历Array
Array.prototype.foo = 666;
var arr = [1, 2, 3];
for (idx in arr) {
console.log(arr[idx]); // 1 2 3 666
}
for…of(es6)
for (entry of arr) {
console.log(entry);
}
/*
L
O
V
E
*/
Object.getOwnPropertyNames(obj)
返回一个数组,包括对象自身(不含继承)的所有属性(包括不可枚举的属性,不含Symbol属性)
Object.getOwnPropertyNames(obj) // ["name", "age", "id"]
Object.getOwnPropertyNames(arr) // ["0", "1", "2", "3", "length"]
Object.getOwnPropertySymbols(obj)
返回一个数组,包括对象自身(不含继承)的所有Symbol属性
Reflect.ownKeys(obj)
返回一个数组,包含对象自身的所有属性(包括不可枚举的和Symbol属性)
深拷贝和浅拷贝
-
存储方式
基本数据类型 保存在 **栈内存,**形式如下:栈内存中分别存储着变量的标识符以及变量的值。
引用类型 保存在 堆内存 中**,** 栈内存存储的是变量的标识符以及对象在堆内存中的存储地址,当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从对应的堆内存中取得所需的数据。
-
浅拷贝
基本数据类型(undefined null string number boolean)的复制,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上
引用数据类型(Object)将对象的指针存储在为新变量分配的空间中,两个变量指向同一个对象 JS相关复制方法:
-
Array
方法 类别 描述 filter 深拷贝 just一层 创建一个新数组,其包含通过所提供函数实现的测试的所有元素 concat 深拷贝 just一层 合并两个或多个数组 concat() 方法把合并的数组元素浅拷贝到一个新数组对象。且原始数组不会被修改。 pop 改变原数组 删除最后一个元素 push 改变原数组 增加一个元素 reverse 改变原数组 shift 改变原数组 删除第一个元素 unshift 改变原数组 从头插入 slice 深拷贝 just一层 slice() 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象。且原始数组不会被修改。 sort 改变原数组 splice 改变原数组 […arr] 深拷贝 just一层 Array.from(arr) 深拷贝 just一层 var a = [[1,2,3],4,5]; var b = a.splice(); console.log(a === b);// true a[0][0] = 6; console.log(a===b); // true console.log(a[0] === b[0]); // false
- 深拷贝
- JSON.parse(JSON.stringify(obj))
- null、任意的函数、正则表达式、symbol值,在序列化的时候会被忽略(出现在非数组对象的属性值中时)或者会被转换为null。(null可以被拷贝)
- 不可以拷贝原型属性。深拷贝后constructor变为Object或Array
- 无法正确处理循环引用问题
- 递归
function cloneDeep(source){
if(!source && typeof source !== 'object'){
throw new Error('error arguments', 'shallowClone');
}
var targetObj = Arr0ay.isArray(source) ? [] : {};
for(var keys in source){
if(source.hasOwnProperty(keys)){
// 对于引用数据类型,递归操作进行深拷贝
if(source[keys] && typeof source[keys] === 'object'){
targetObj[keys] = cloneDeep(source[keys]);
}else{
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
-
如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用
MessageChannel
function structuralClone(obj) { return new Promise(resolve => { const {port1, port2} = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }); } var obj = {a: 1, b: { c: b }} // 注意该方法是异步的 // 可以处理 undefined 和循环引用对象 const clone = await structuralClone(obj);
数组去重的方法
使用ES6的Set结构,自动处理NaN
const dedupe = function (arr) {
return Array.from(new Set(arr));
}
let arr = [NaN, 0, [0], [], '0', NaN, {}, ['q'], 'q'];
console.log(dedupe(arr));
使用indexOf和filter,需要特殊处理NaN
function dedupeq(array) {
var hasNaN = false;
var res = array.filter(function(item, index, array){
// 由于[NaN].indexOf(NaN) === -1
// 因此对NaN特殊处理
if (!hasNaN && item !== item) {
hasNaN = true;
return true;
}
return array.indexOf(item) === index;
});
return res;
}
var arr = [NaN, 0, [0], [], '0', NaN, {}, ['q'], 'q', null, {}, undefined];
console.log(dedupeq(arr));
// // [NaN, 0, [0], [], "0", {}, ['q'], "q", null, {}, undefined]
setTimeout原理
因为js是单线程的,浏览器遇到setTimeout或者setInterval会先执行完当前的代码块,在指定时间后把回调事件推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码之后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。 所以即使把定时器的时间设置为0还是会先执行当前的一些代码。
js的事件循环
JavaScript执行时是单线程非阻塞的
JavaScript代码执行的时候只有一个主线程来处理所有的任务,而非阻塞则是代码执行异步任务时,主线程会挂起pending这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
执行栈和事件队列
- JavaScript代码执行时会把不同变量放在内存的不同位置,堆存放对象,栈存放基本数据类型和对象指针
- 当调用一个方法时,js会生成一个与这个方法对应的执行环境,又叫做执行上下文。其中存放方法的私有作用域、上层作用域的指向、方法的参数、作用域的变量和this对象。方法执行时执行环境推入执行栈,执行完毕后这个执行环境出栈。
- 异步代码执行后,不会一直等待其返回结果,而是将事件挂起继续执行执行栈的其他任务。异步事件返回结果后,js将事件加入事件队列,被放进事件队列中后不会立即执行其回调,而是等待当前执行栈的任务执行完毕后才检查事件队列的任务,然后取出其中的任务的回调放到当前执行栈里。如此反复。
微任务和宏任务
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
宏任务: setTimeout setInterval setImediate
微任务:new Promise、 new MutationObserver
当前执行栈执行完毕后会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
每个线程都会有自己的事件循环,所以都能独立运行。只要执行栈中没有其他js代码正在执行且每个宏任务执行完,微任务队列会立即执行,微任务执行期间产生了新的微任务,会加到队列尾部,也会被执行。所有微任务都会在下一个宏任务结束之前全部执行完毕。
宏任务按顺序执行,且浏览器在每个宏任务之间渲染页面。
所有微任务都按顺序执行,在每个回调之后且js执行栈为空时,以及每个宏任务结束后,会立即执行所有微任务。
箭头函数
-
更简短的函数
-
this对象是定义时确定的,而不是使用时确定的。箭头函数不创建自己的this,会从作用域链的上一层继承this
-
通过call或apply调用时只能传递参数,不能绑定this
-
不能当构造函数 不能使用new命令
-
不能使用arguments对象(没有自己的this、arguments、super或new.target)
function foo() { var f = (...args) => args[0]; return f(2); } foo(1); // 2
-
没有prototype属性
正则表达式
元字符 | 作用 |
---|---|
. | 匹配任意字符除了换行符和回车符 |
[] | 匹配方括号内任意字符 |
^ | |
{1,2} | |
() | |
| | 匹配|前后任意字符 |
* | 匹配出现0次及以上*前的字符 |
+ | 只匹配出现1次以上+前的字符 |
? | 匹配0次或1次?之前的字符 |
DOM
DOM事件流和IE事件流
DOM:事件捕获、处于目标、事件冒泡
IE:事件从最具体的元素接收,然后逐级向上传播到较为不具体的节点
根元素都是document
target和currentTarget
target触发事件监听的对象
currentTarget绑定事件监听的对象
事件委托
- 减少事件注册、节省内存
- 简化DOM更新时,相应事件的更新(动态增加子节点的情况)
- 不支持冒泡的事件不支持委托
- 理论上委托导致频繁调用回调
事件监听的方法
- 元素属性 onclick
- 事件监听:
- addEventListener(‘click’, func, false);// 冒泡阶段调用事件处理程序 removeEventListener
- attachEvent(‘onclick’, func) // IE只支持在冒泡阶段调用事件处理程序 detachEvent
取消冒泡和取消事件的默认行为
说明 | DOM | IE |
---|---|---|
事件是否冒泡 | bubbles | |
是否可以取消事件的默认行为 | cancelable | |
事件处理程序正在处理事件的那个元素 | currentTarget | |
为true表示已经调用了preventDefault | defaultPrevented | |
detail | ||
eventPhase | ||
取消事件的默认行为 | preventDefault | returnValue = false |
取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用 | stopImmediatePropagation | |
取消事件的进一步捕获或冒泡 | stopPropagation | cancelBubble = true |
事件的目标 | target | srcElement |
被触发的事件类型 | type | |
view | ||
trusted |
Promise
console.log('A');
const promise = new Promise((resolve, reject) => {
console.log('C');
setTimeout(() => {
console.log('D');
resolve();
reject();
resolve();
}, 10);
setTimeout(() => {console.log('H')});
});
promise.then((res) => {
console.log('E');
});
promise.then((res) => {
console.log('F');
});
promise.catch((res) => {
console.log('G');
});
console.log('B');
// 我的答案 ACBDEFG
// Chrome运行结果: ACBHDEF
HTTP
DNS
DNS将域名解析为IP,有两种查询方式
-
递归查询
所谓递归查询就是:如果主机所询问的本机域名服务器不知道被查询的域名的IP地址,那么本机域名服务器就以DNS客户的身份,向其他根域名服务器继续发出查询请求报文(即替主机继续查询),而不是让主机自己进行下一步查询。
发送请求 本地域名服务器 根域名服务器 顶级域名服务器 主域名服务器
-
迭代查询
迭代查询的特点:当根域名服务器收到本地域名服务器发出的迭代查询请求报文时,要么给出所要查询的IP地址,要么告诉本地服务器,“你下一步应当向哪一个域名服务器进行查询”,然后让本地域名服务器继续进行后续的查询
发送请求 本地域名服务器
发送请求 根域名服务器
发送请求 顶级域名服务器
发送请求 主域名服务器
-
DNS缓存
- 浏览器缓存
- 路由缓存
- 根域名服务器缓存
- 顶级域名服务器缓存
- 主域名服务器缓存
- 本地域名服务器缓存
HTTPDNS
域名系统(DNS):作为域名和IP地址相互映射的一个分布式数据库,通过DNS可以将域名解析为IP地址
CDN(Connet Delivery Network):其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输得更快、更稳定
HTTPDNS:使用HTTP请求来模拟DNS请求
DNS循环:当权威DNS发现一个域名映射多个IP时,会使用IP轮询的方式来将IP平均分配给多个DNS请求,从而达到负载均衡的效果
-
概念:使用http协议向DNS服务器的80端口进行请求,代替传统的DNS协议向DNS服务器的53端口进行请求。将服务器返回的IP地址获取后,直接向该IP地址发起对应的API请求,代替使用域名
-
为什么要使用HTTPDNS?
-
LocalDNS劫持
由于HTTPDNS是通过直接请求HTTP获取服务器的IP地址,不存在向本地运营商访问域名的过程,从而避免了劫持
-
平均访问延迟下降
由于IP直接访问省去了一次域名解析的过程,通过智能算法排序后找到最快的节点进行访问
-
用户连接失败率降低
通过算法,降低以往失败率较高的服务器的排序,通过近期历史访问成果记录提高服务器排序,如果IP(a)访问错误,下次将返回IP(b)排序后的结果
-
HTTP2.0和HTTP1.x的区别
多路复用
HTTP2.0复用TCP连接。在一个TCP连接里,客户端和浏览器都可以同时发送多个请求和回应,而且不用按照顺序一一对应,这样就避免了”队头堵塞“。每个请求/响应都使用不同的Stream ID,一个连接上可以有多个请求,每个连接的请求可以随机混杂在一起,接收方可以根据请求的ID将请求再归属到不同的服务端请求里
流优先级
HTTP2.0支持浏览器指定资源的优先级
HEADER压缩传输
使用HPACK算法用做首部压缩
二进制格式
使用二进制传送。
数据流
HTTP2.0可以取消某一次请求(而HTTP1.1的方法就是关闭TCP连接),保持TCP连接打开,被其他请求使用。客户端和服务端都可以发送RST_STREAM帧,来取消这个数据流
服务器推送
服务器把客户端需要的资源一起推送到客户端。例如服务端主动把JS和CSS文件推送给客户端,而不需要客户端解析HTML再发送这些请求。
- 客户端可以设置SETTINGS_ENABLE_PUSH为0来通知服务端禁用推送
- 发现缓存后,客户端和服务端都可以发送信号(RST_STREAM帧),来取消这个数据流
- cache-digest
HTTP状态码
1xx 可续发送请求
2xx 成功
- 200 采用强缓存机制。
- 202 成功
- 204 成功 不返回实体请求
- 206 成功,执行一个范围请求
3xx 重定向
- 301 永久重定向
- 302 临时重定向 禁止post变成get
- 303 临时重定向 使用get请求新的URL
- 304 采用协商缓存机制
- 307 临时重定向
4xx 客户端错误
- 400 客户端语法错误
- 401 未经授权
- 403 服务器拒绝服务
- 404 请求资源不存在
5xx 服务器错误
- 500 不可预期的错误
- 503 此时不能提供服务,稍后恢复正常
HTTP缓存
按缓存位置:Service Worker、Memory Cache、Disk Cache、网络请求
Service Worker
本质上充当了Web应用程序和浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。
接口有Cache、Cache Storage等
Memory Cache
- 浏览器标签关闭后失效
- 保证了一个页面中如果有两个相同的资源,会只请求一次
- 匹配规则有URL、类型、CORS中的域名规则等
Disk Cache(HTTP Cache)
- 存储在硬盘上的缓存,是持久存储的,实际存在于文件系统中的。允许相同的资源在跨会话、甚至在跨站点的情况下使用,例如两个站点都使用了同一张图片
- 严格根据HTTP的头信息来判断哪些资源能否缓存、可用性等,命中缓存后,会从硬盘中读取资源
- 自动清理时,使用算法清理“最老的”或“最可能使用的”。各个浏览器的清理算法各不相同
请求网络
如果一个请求在上述三个位置都没有找到缓存,会发送网络请求去获取内容。之后为了提高缓存命中率,会把这个资源添加到缓存中。具体来说:
- 根据Service Worker中的handler决定是否存入Cache Storage
- 根据HTTP头部的相关字段决定是否存入Disk Cache
- Memory Cache保留一份资源的引用
按失效策略:强缓存与协商缓存
memory cache 是浏览器为了加快读取缓存速度而进行的自身的优化行为,不受开发者控制,也不受 HTTP 协议头的约束,算是一个黑盒。Service Worker 是由开发者编写的额外的脚本,且缓存位置独立,出现也较晚,使用还不算太广泛。所以我们平时最为熟悉的其实是 disk cache,也叫 HTTP cache (因为不像 memory cache,它遵守 HTTP 协议头中的字段)。平时所说的强制缓存,对比缓存,以及 Cache-Control 等,也都归于此类。
浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的
- 为什么要引入ETag?
- 为了解决Last-Modified无法解决的一些问题
- 一些文件也许会周期性的更改,但是内容不改变(仅仅修改改变的时间),这时候我们不希望客户端认为这个文件被修改了从而重新请求
- 某些文件的修改十分频繁,在s以下的时间粒度内修改
- 某些服务器不能精确得到文件的最后修改时间
- 为了解决Last-Modified无法解决的一些问题
cache-control的取值:
private 客户端可以缓存
public 客户端和代理服务器(如CDN)都可以缓存
max-age=xxx 缓存的内容将在xxx秒后失效
no-cache 使用对比缓存来验证数据
no-store 所有内容都不会缓存,强制缓存、对比缓存都不会触发
强缓存
- 强缓存生效后,状态码为200
对比缓存
-
对比缓存生效时,状态码为304,说明资源无最新修改,浏览器使用缓存,状态码为200,说明资源被改动过,响应整片资源内容
-
报文大小和请求时间大大减少。这是因为服务端在进行标识比较后,只返回header部分,通过状态码通知客户端使用缓存,不再需要将报文主体返回给客户端
-
ETag优先级高于Last-Modified
-
请求头:If-None-Match 或 If-Modified-Since
响应头:ETag 或 Last-Modified
浏览器第一次请求:
浏览器再次请求时:
浏览器行为可能改变对资源的读取方式
当浏览器要请求资源时:
- 调用Service Worker的fetch响应
- 查看memory cache
- 查看disk cache
- 如果有强缓存且未失效(max-age),则使用强制缓存,不发送请求,状态码是200
- 如果已经过期,使用对比缓存,发送IF-NONE-MATCH或IF-MODIFIED-SINCE,服务器比较之后发送ETAG或LAST-MODIFIED,状态码为304(只有头部)或200(资源改变,重新返回)
- 发送网络请求,等待响应,约定缓存策略
- 把响应内容存入DISK CACHE(如果HTTP头信息可以存,不为no-store)
- 把响应内容的引用存入MEMORY CACHE
- 把响应内容存入Service Worker的Cache Storage(如果可以)
如果资源已经被缓存,在缓存失效前,再次请求时,默认会先检查是否命中强缓存,如果强缓存命中则直接读取缓存,如果强缓存没有命中则发请求到服务器检查是否命中协商缓存,如果协商缓存命中,则告诉浏览器可以从缓存中读取,否则从服务器返回最新的资源。
ctrl+f5强制刷新,则直接从服务器加载,跳过强缓存和协商缓存不使用缓存
f5刷新页面,会设置max-age=0,会跳过强缓存但是检查协商缓存
地址栏访问,链接跳转,浏览器的刷新按钮是正常用户行为,先从Memory Cache里找,然后触发浏览器缓存机制从Disk Cache里查找
内容协商
一份特定的文件称为一项资源。当客户端获取资源的时候,会使用其对应的URL发送请求,服务器通过URL来选择指向的资源的某一展现形式。
客户端设置特定的HTTP首部
- 服务端驱动型内容协商机制or主动协商机制
- 浏览器or代理发送消息头告知用户选择, 服务器通过算法提供最佳方案
- 每个特性都要对应一个首部,需要新特性就得新建首部,并每次请求都发出。消息体的体积导致性能下降,默许了更多HTTP指纹识别行为,与此相关的隐私问题也将发生
缺点:服务器用算法判断选择时比起代理驱动的协商机制显得武断;客户端提供信息多,首部体积大(2.0首部压缩),存在隐私风险;共享缓存效率降低,服务器端实现变得复杂
优点:时延小
vary首部清单
请求头 | 描述 | 取值 | 响应头 |
---|---|---|---|
Accept | 用户代理希望接收的媒体资源的MIME类型及其优先级 | Content-Type | |
Accept-Charset | 理解何种形式的字符编码 | ISO-8859-1,utf-8;q=0.7,*;q=0.7 | Content-Charset |
Accept-Encoding | 接收的内容编码形式(接收的压缩算法) | br, gzip;q=0.8 | Content-Encoding |
Accept-Language | 期望获得的自然语言的优先顺序 | Content-Language |
服务器返回300multipe choices或406not acceptable
- 代理驱动型机制or响应式协商机制
面临不明确的请求,返回页面,包括了可供选择的资源链接。
预检请求 OPTIONS
不会触发CORS预检请求的简单请求,满足所有条件:
- GET、POST、HEAD
- 对CORS安全的首部字段集合:Accept(-Language)、Content(-Language)、Content-Type…
- Content-Type:text/plain、multiplepart/form-data、application/x-www-form-urlencoded
- …
需要发送预检请求:
使用了PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH
CONNECT(可以开启一个客户端和所请求资源之间的双向沟通的通道)
OPTIONS
TRACE(实现沿通向目标资源的路径的回环测试)
PATCH(对资源进行部分修改)
… 字段、type(text/html)、
HTTP的OPTIONS方法,用于获取目的资源所支持的通信选项。客户端可以对指定的URL使用OPTIONS方法,也可以对*使用该方法
Access-Control-Request-Method:服务器实际请求所使用的HTTP方法
Access-Control-Request-Headers:服务器实际请求所携带的自定义首部字段
// 请求方法 路径 协议
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
// 内容协商字头
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
// 保持连接
Connection: keep-alive
// 源
Origin: http://foo.example
// 获取目标资源所支持的通信选项
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
// 告知资源允许的源、请求方法、头部
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
Browser
跨域
什么是跨域
当协议、域名、端口任何一个不同,都算不同域。
注意:
- 跨域不是不发请求,而是请求可以发送,但是服务端返回的结果被浏览器拦截了。这是因为同源策略的限制。
- 协议和端口造成的跨域问题前端无法解决
- 判断跨域时,不会把IP地址和域名进行映射,仅从字面比较
什么是同源策略及其限制
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。
以下是可能嵌入跨源的资源的一些示例:
- `` 标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。
- `` 标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的
Content-Type
消息头。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。 - img嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG,…
- vedio、audio嵌入多媒体资源。
- object、embed和 `` 的插件。
@font-face
引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。- frame和 `` 载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。
跨域解决方案:
共享cookie
document.domain(二级域名不同情况下)
使用场景:共享cookie
document.domain = 'example.com';
现在,A网页通过脚本设置一个 Cookie。
document.cookie = "test1=hello";
B网页就可以读到这个 Cookie。
var allCookie = document.cookie;
注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。
另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com
。
Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。
共享DOM
iframe+document.domain(二级域名不同情况下)
如果两个窗口一级域名相同,只是二级域名不同,那么设置上一节介绍的document.domain
属性,就可以规避同源政策,拿到DOM。
片段识别符
window.name
浏览器窗口有window.name属性,特点是无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
- 在子窗口载入不同源的网页,写入window.name=data
- 子窗口跳回和主窗口同域的网址
- 主窗口读取子窗口的window.name
优点:window.name容量很大
缺点:必须监听子窗口window.name属性的变化,影响网页性能
跨文档通信API:postMessage
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题: a.) 页面和其打开的新窗口的数据传递 b.) 多窗口之间消息传递 c.) 页面与嵌套的iframe消息传递 d.) 上面三个场景的跨域数据传递
用法:postMessage(data,origin)方法接受两个参数 data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。 origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow
其他窗口的一个引用,比如iframe的contentWindow属性、执行[window.open]返回的窗口对象、或者是命名过或数值索引的[window.frames]
message将要发送到其他 window的数据。它将会被[结构化克隆算法]序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。
targetOrigin
通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口;例如,当用postMessage传送密码时,这个参数就显得尤为重要,必须保证它的值与这条包含密码的信息的预期接受者的origin属性完全一致,来防止密码被恶意的第三方截获。
如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是\*。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。
transfer
是一串和message 同时传递的 [`Transferable`]对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
发送 : otherWindow.postMessage(message, targetOrigin, [transfer]); //调用postMessage方法的window对象是指要接收消息的那一个window对象
监听 : message事件 window.addEventListener( “message”, function(event) {alert(event.data);})
AJAX
JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个``元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
CORS跨域资源共享
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET
请求,CORS允许任何类型的请求。
WebSocket
不同浏览器不同窗口的通信
localStorage
// 本窗口的设值代码
localStorage.setItem("EVENT.PUB", JSON.stringify({username : 'yiifaa', now : Date.now()}))
// 请务必注意,localStorage不能直接传送复合类型的值(对象)
// 其他窗口监听storage事件
$(window).on('storage', function(ev) {
var event = ev.originalEvent,
message = JSON.parse(event.newValue);// 解析出复合的对象值
// 对获取到的值进行处理
console.log(message)
})
- set的值不变时,回调不触发
- set值的页面的storage listeners的回调不触发
postMessage
发送 : otherWindow.postMessage(message, targetOrigin, [transfer]); //调用postMessage方法的window对象是指要接收消息的那一个window对象
监听 : message事件 window.addEventListener( “message”, function(event) {alert(event.data);})
在浏览器输入一个URL,会发生什么?
- DNS解析
- 客户端发送请求
- TCP三次握手建立网络连接
- 浏览器发送http请求
- 网络IP协议查询MAC地址,将TCP分割好的数据包发送给接收方
- 找到MAC地址,数据发送到链路层进行传输
- 服务器响应
- 服务器响应请求,返回http响应报文
- 返回响应文件
- 页面渲染
- 解析HTML,生成DOM树(遇到外链则中断文档解析,下载并执行后,继续文档解析)
- 解析CSS文件
- 生成渲染树(受样式影响,不可见元素和display none都不在树中)
- 绘制渲染树
- 连接结束
重绘和重排
网页的生成过程
网页的生成过程大致可以分成五步:
- HTML转化成DOM
- CSS转化成CSSOM(CSS Object Model)
- 结合DOM树和CSSOM,生成渲染树(包含每个节点的视觉信息)
- 生成布局(layout),即将所有渲染树的所有节点进行平面合成
- 将布局绘制(paint)在屏幕上
其中4-5步是十分耗时的,生成布局(flow)和绘制(paint)这两步,合称为渲染(render)
-
重排:重新生成布局
-
重绘:重新绘制
-
重绘不一定需要重排,重排必然会导致重绘
-
任何改变用来构建渲染树的信息都会导致一次重排或重绘。
- 添加、更新、删除DOM节点
- 通过display:none隐藏一个DOM节点,触发重绘和重排
- 通过visibility:hidden隐藏一个DOM节点,只触发重绘
- 移动或者给页面中的DOM节点增加动画
- 增加一个样式表
- 用户行为 如调整窗口大小、改变字号、或者滚动
-
浏览器的应对
-
浏览器基于脚本创建一个变化的队列,然后分批去展现,通过这种方式许多需要一次重排的变化就会整合起来,最终只有一次重排会被计算渲染
-
有时候代码会组织浏览器优化重排并立即刷新队列,与此同时展示所有批次的变化,这通常发生在请求样式信息的时候。例如:
- offsetTop、offsetLeft、offsetHeigt
- scrollTop
- clientTop
- getComputedStyle currentStyle in IE
为了提高最新的样式值,浏览器必须应用所有队列中的变更,刷新队列然后去实现重排
-
图层
普通文档流看成一个图层,特定的属性生成新图层。不同图层的渲染互不影响。频繁渲染的元素可以单独生成新图层,提高性能。但不能生成过多图层,反而影响性能。
- 3D变换:translate3d translateZ
- will-change
- video iframe
- 通过动画实现的opacity动画转换
- position: fixed
最小化重绘和重排
- translate代替top
- visibility代替display: none 前者只会引起重绘,后者引发回流
- 不要逐个改变样式。对于静态文件来说,可以改类名或直接修改css text
- 频繁进行的动画变成图层
- 离线的批量改变和表现DOM。离线意味着不在当前的DOM树中做修改可以
- 通过documentFragment来保留临时变动
- 复制即将更新的节点,在副本上工作,然后交换节点
- 通过display:none隐藏元素,添加足够多变更后,再改变display展示。这样只有两次重绘重排
- 不要频繁计算样式。减少使用table布局
- position为absolute或fixed的元素,重排的开销会比较小
- 使用虚拟DOM的脚本库,如React
浏览器的标准模式和怪异模式
- set的值不变时,回调不触发
- set值的页面的storage listeners的回调不触发
postMessage
发送 : otherWindow.postMessage(message, targetOrigin, [transfer]); //调用postMessage方法的window对象是指要接收消息的那一个window对象
监听 : message事件 window.addEventListener( “message”, function(event) {alert(event.data);})
在浏览器输入一个URL,会发生什么?
- DNS解析
- 客户端发送请求
- TCP三次握手建立网络连接
- 浏览器发送http请求
- 网络IP协议查询MAC地址,将TCP分割好的数据包发送给接收方
- 找到MAC地址,数据发送到链路层进行传输
- 服务器响应
- 服务器响应请求,返回http响应报文
- 返回响应文件
- 页面渲染
- 解析HTML,生成DOM树(遇到外链则中断文档解析,下载并执行后,继续文档解析)
- 解析CSS文件
- 生成渲染树(受样式影响,不可见元素和display none都不在树中)
- 绘制渲染树
- 连接结束
重绘和重排
网页的生成过程
网页的生成过程大致可以分成五步:
- HTML转化成DOM
- CSS转化成CSSOM(CSS Object Model)
- 结合DOM树和CSSOM,生成渲染树(包含每个节点的视觉信息)
- 生成布局(layout),即将所有渲染树的所有节点进行平面合成
- 将布局绘制(paint)在屏幕上
其中4-5步是十分耗时的,生成布局(flow)和绘制(paint)这两步,合称为渲染(render)
-
重排:重新生成布局
-
重绘:重新绘制
-
重绘不一定需要重排,重排必然会导致重绘
-
任何改变用来构建渲染树的信息都会导致一次重排或重绘。
- 添加、更新、删除DOM节点
- 通过display:none隐藏一个DOM节点,触发重绘和重排
- 通过visibility:hidden隐藏一个DOM节点,只触发重绘
- 移动或者给页面中的DOM节点增加动画
- 增加一个样式表
- 用户行为 如调整窗口大小、改变字号、或者滚动
-
浏览器的应对
-
浏览器基于脚本创建一个变化的队列,然后分批去展现,通过这种方式许多需要一次重排的变化就会整合起来,最终只有一次重排会被计算渲染
-
有时候代码会组织浏览器优化重排并立即刷新队列,与此同时展示所有批次的变化,这通常发生在请求样式信息的时候。例如:
- offsetTop、offsetLeft、offsetHeigt
- scrollTop
- clientTop
- getComputedStyle currentStyle in IE
为了提高最新的样式值,浏览器必须应用所有队列中的变更,刷新队列然后去实现重排
-
图层
普通文档流看成一个图层,特定的属性生成新图层。不同图层的渲染互不影响。频繁渲染的元素可以单独生成新图层,提高性能。但不能生成过多图层,反而影响性能。
- 3D变换:translate3d translateZ
- will-change
- video iframe
- 通过动画实现的opacity动画转换
- position: fixed
最小化重绘和重排
- translate代替top
- visibility代替display: none 前者只会引起重绘,后者引发回流
- 不要逐个改变样式。对于静态文件来说,可以改类名或直接修改css text
- 频繁进行的动画变成图层
- 离线的批量改变和表现DOM。离线意味着不在当前的DOM树中做修改可以
- 通过documentFragment来保留临时变动
- 复制即将更新的节点,在副本上工作,然后交换节点
- 通过display:none隐藏元素,添加足够多变更后,再改变display展示。这样只有两次重绘重排
- 不要频繁计算样式。减少使用table布局
- position为absolute或fixed的元素,重排的开销会比较小
- 使用虚拟DOM的脚本库,如React
浏览器的标准模式和怪异模式
排版引擎有:标准模式、怪异模式、混杂模式