深拷贝
function deepCopy (obj) {
let copyResult = Array.isArray(obj) ? [] : {}
if(obj && typeof obj === "object"){
for(key in obj){
if(obj.hasOwnProperty(key)){
if(obj[key] && typeof obj[key] == 'object'){
copyResult[key] = deepCopy(obj[key])
}else {
copyResult[key] = obj[key]
}
}
}
}
return copyResult
}
两列布局,左面固定,右面自适应
- 左面用浮动或者绝对定位(浮动的话清楚浮动) 有面用margin-left:负的左面宽度
- 用flex布局,有面flex:1
Http与Https的区别
- 1、https协议需要到CA (Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。(原来网易官网是http,而网易邮箱是https。)
- 2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
- 3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- 4、http的连接很简单,是无状态的。Https协议是由SSL+Http协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
new的操作符干了啥?实现一下new
- 创建一个对象。
- 将构造函数中的this指向了这个对象(作用域赋给这个对象)。
- 执行构造函数中的代码,为这个对象添加属性或方法。
- 返回这个对象。
function _new () { // 这里面将第一个参数当作构造函数,后面参数当作传入构造函数的参数
let constructor = arguments[0]
let params = Array.prototype.slice.call(arguments,1)
let obj = new Object()
let result = constructor.apply(obj,params)
return typeof result === 'object' ? result : obj
}
// 验证一下
function Person (name,age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
}
let p1 = _new(Person,'zsm',18)
console.log(p1.name)
console.log(p1.age)
p1.sayName()
说说flex布局都有哪些属性,都代表什么
flex布局属性
容器属性
- flex-direction:代表主轴方向(即项目的排列方向),默认从左到右,row (从左到右)主轴为水平方向,起点在左端。row-reverse(从右到左)主轴为水平方向,起点在右端。column(从上到下)主轴为垂直方向,起点在上沿。column-reverse(从下到上)主轴为垂直方向,起点在下沿。
- flex-wrap:属性定义,如果一条轴线排不下,如何换行,默认不换行,nowrap(不换行),wrap(换行,第一行在上方),wrap-reverse(换行,第一行在下方。)
- flex-flow:前两个属性的简便写法,默认,row,nowrap
- justify-content:属性定义了项目在主轴上的对齐方式,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。flex-start 默认值(左对齐),flex-end (右对齐),center(居中),space-between(两端对齐,项目之间的间隔都相等),space-around(每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍)
- align-items:交叉轴上如何对齐,默认stretch(如果项目未设置高度或设为auto,将占满整个容器的高度),flex-start(交叉轴的起点对齐),flex-end(交叉轴的终点对齐)center:(交叉轴的中点对齐),flex-baseline(项目的第一行文字的基线对齐)
- align-content:属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。flex-start:与交叉轴的起点对齐。 flex-end:与交叉轴的终点对齐。 center:与交叉轴的中点对齐。 space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。 space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。 stretch(默认值):轴线占满整个交叉轴。
子元素属性
- order:属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
- flex-grow 属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。 如果有剩余空间的话,如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
- flex-shrink:属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。
- flex-basic :它的默认值为auto,即项目的本来大小。它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。
- flex flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1。auto (1 1 auto) 和 none (0 0 auto auto)。后两个属性可选
- align-self:属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。该属性可能取6个值,除了auto,其他都与align-items属性完全一致。
处理url中的参数值
function getQuery () {
let result = {}
if(window.location.href.indexOf('?')>-1){
let src = window.location.search.substr(1)
let strArr = src.split('&')
for (let i = 0; i < strArr.length; i++) {
let arrIndex = strArr[i].split('=')
result[arrIndex[0]] = decodeURIComponent(arrIndex[1])
}
}
return result
}
parent
parent是父实例
处理并发请求
promise.all
跨域,jsonp实现原理
jsonp
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个script标签元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
- 首先,网页动态插入script标签元素,由它向跨源网址发出请求。
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
上面代码通过动态添加script标签元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
服务器返回
foo({
"ip": "8.8.8.8"
});
由于script元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
cros
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息(就是在头信息之中,增加一个Origin字段),有时还会多出一次附加的请求,但用户不会有感觉。
实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
两种请求
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。 只要同时满足以下两大条件,就属于简单请求。
-
(1) 请求方法是以下三种方法之一:
HEAD, GET, POST
-
(2)HTTP的头信息不超出以下几种字段:
1. Accept 2. Accept-Language 3. Content-Language 4. Last-Event-ID Content-Type:只限于三个值 application/x-www-form-urlencoded、 multipart/form-data、 text/plain
凡是不同时满足上面两个条件,就属于非简单请求。 浏览器对这两种请求的处理,是不一样的。
简单请求
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。 下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。 如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。 下面是一段浏览器的JavaScript脚本。
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header。
浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
预检请求的回应
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://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。
如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
服务器回应的其他CORS相关字段如下。
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
(1)Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
(2)Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
(3)Access-Control-Allow-Credentials
它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
(4)Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
下面是"预检"请求之后,浏览器的正常CORS请求。
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
上面头信息的Origin字段是浏览器自动添加的。
下面是服务器正常的回应。
Access-Control-Allow-Origin: api.bob.com Content-Type: text/html; charset=utf-8 上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。
ajax实现原理
Ajax 技术的核心是 XMLHttpRequest 对象(简称:XHR),可以在不刷新页面页面也能取得新的数据。
XMLHttpRequest 的用法
使用 XMLHttpRequest 三步骤:
- 要用 XMLHttpRequest 构造一个对象
- 调用 open() 方法
- 调用 send() 方法
open() 方法接收三个参数:请求类型,请求 url,是否使用异步;
send() 方法接受一个参数:请求主体发送的数据。
这个请求是同步的,浏览器会等到服务器响应之后继续执行,响应之后的相关属性:
-
responseText:响应主体返回的文本
-
status:响应的 HTTP 状态
-
statusText:响应的 HTTP 状态说明
大多数情况下,我们使用的是异步请求,才能 JS 继续执行,不必等待响应,此时应该检查readyState,这个属性有5种取值:*值 状态 描述 0 UNSENT(未打开) open()方法还未被调用 1 OPENED(未发送) send()方法还未被调用 2 HEADERS_RECEIVED(以获取响应头) send()方法已经被调用,响应头和响应状态已经返回 3 LOADING(正在下载响应体) 响应体下载中;responseText中已经获取部分数据 4 DONE(请求完成) 整个请求过程已完毕
只要readyState属性值一变化,就会触发一次readystatechange事件,可以利用这个事件来检测每次状态变化后的readystate的属性值,通常我们只对readystate值为4进行检测。
let request = new XMLHttpRequest()
request.onreadystatechange = function(e){
if(request.readyState === 4){
if(request.status >= 200 && request.status <= 300){
console.log(request.responseText)
}else if(request.status >=400){
console.log("错误信息:" + request.status)
}
}
}
request.open('POST','http://jack.com:8889/xxx')
request.send()
响应返回的requestText永远是字符串,早期使用的符合 XML 格式的字符串,现在使用的是符合 JSON 语法的字符串,前端拿到后可以用window.JSON.parse()来解析
每次走1个台阶或两个台阶,n个台阶有多少种方法
此题利用斐波那契数列
function fbnq (n) {
if(n <= 0) return 0
if(n===1) return 1 // 1节台阶1步就一种
if(n===2) return 2 // 两节台阶两种 1.走两步 2.一步一步走
return fbnq(n-1)+fbnq(n-2) // n节台阶, 拿3当例子,可以从第2节走上去 走一步,也可以从第1节走上去,一次走两步
}
flex实现一个平局分布局
设置justify-content: space-between;将容器内部前面加空元素,后面加空元素
<div class="flexClass">
<div class="flexItem">1</div>
<div class="flexItem">2</div>
<div class="flexItem">3</div>
<div class="flexItem">4</div>
</div>
<style>
.flexClass{
display: flex;
height: 100px;
justify-content: space-between;
}
.flexItem{
background: aqua;
flex-basis: 300px;
border: 1px solid red;
}
.flexClass::before{
content: '';
}
.flexClass::after{
content: '';
}
</style>
直接父元素设置 justify-content: space-evenly;
描述webpack4中关于分包的内容优化
webpack4移除了commonChunksPlugin,改用splitChunksPlugin blog.csdn.net/weixin_3381…88022452
let暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
【拓展】
var的创建和初始化被提升,赋值不会被提升。
let的创建被提升,初始化和赋值不会被提升。
function的创建、初始化和赋值均会被提升。
http常见的状态码
1、200 OK:请求已正常处理。
2、204 No Content:请求处理成功,但没有任何资源可以返回给客户端,一般在只需要从客户端往服务器发送信息,而对客户端不需要发送新信息内容的情况下使用。
3、206 Partial Content:是对资源某一部分的请求,该状态码表示客户端进行了范围请求,而服务器成功执行了这部分的GET请求。响应报文中包含由Content-Range指定范围的实体内容。
3XX——表明浏览器需要执行某些特殊的处理以正确处理请求
4、301 Moved Permanently:资源的uri已更新,你也更新下你的书签引用吧。永久性重定向,请求的资源已经被分配了新的URI,以后应使用资源现在所指的URI。
5、302 Found:资源的URI已临时定位到其他位置了,姑且算你已经知道了这个情况了。临时性重定向。和301相似,但302代表的资源不是永久性移动,只是临时性性质的。换句话说,已移动的资源对应的URI将来还有可能发生改变。
6、303 See Other:资源的URI已更新,你是否能临时按新的URI访问。该状态码表示由于请求对应的资源存在着另一个URL,应使用GET方法定向获取请求的资源。303状态码和302状态码有着相同的功能,但303状态码明确表示客户端应当采用GET方法获取资源,这点与302状态码有区别。
当301,302,303响应状态码返回时,几乎所有的浏览器都会把POST改成GET,并删除请求报文内的主体,之后请求会自动再次发送。
7、304 Not Modified:资源已找到,但未符合条件请求。该状态码表示客户端发送附带条件的请求时(采用GET方法的请求报文中包含If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since中任一首部)服务端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304.。
8、307 Temporary Redirect:临时重定向。与302有相同的含义。
4XX——表明客户端是发生错误的原因所在。
9、400 Bad Request:服务器端无法理解客户端发送的请求,请求报文中可能存在语法错误。
10、401 Unauthorized:该状态码表示发送的请求需要有通过HTTP认证(BASIC认证,DIGEST认证)的认证信息。
11、403 Forbidden:不允许访问那个资源。该状态码表明对请求资源的访问被服务器拒绝了。(权限,未授权IP等)
12、404 Not Found:服务器上没有请求的资源。路径错误等。
5XX——服务器本身发生错误
13、500 Internal Server Error:貌似内部资源出故障了。该状态码表明服务器端在执行请求时发生了错误。也有可能是web应用存在bug或某些临时故障。
14、503 Service Unavailable:抱歉,我现在正在忙着。该状态码表明服务器暂时处于超负载或正在停机维护,现在无法处理请求。
原生浅拷贝
1.Object.assign() 2.slice 3.concat
数组去重
Array.from(new Set(arr))
本地存储localStorage,sessionStorage,cookie 区别
cookie
生命周期:默认保存在内存中,随浏览器关闭失效(如果设置过期时间,在到过期时间后失效)
存储容量:4KB
存储位置:保存在客户端,每次请求时都会带上
localStorage
生命周期:理论上永久有效的,除非主动清除。
存储容量:4.98MB(不同浏览器情况不同,safari 2.49M)
存储位置:保存在客户端,不与服务端交互。节省网络流量
sessionStorage
生命周期:仅在当前网页会话下有效,关闭页面或浏览器后会被清除。
存储容量:4.98MB(部分浏览器没有限制)
存储位置:同上
① cookie在浏览器与服务器之间来回传递。
sessionStorage和localStorage不会把数据发给服务器,仅在本地保存
②数据有效期不同:
cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
sessionStorage:仅在当前浏览器窗口关闭前有效。
localStorage 始终有效,长期保存。
③cookie数据还有路径的概念,可以限制cookie只属于某个路径下。
存储大小也不同,cookie数据不能超过4k,sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
④ 作用域不用
sessionStorage不在不同的浏览器窗口中共享;
localStorage在所有同源窗口中都是共享的;
cookie也是在所有同源窗口中都是共享的;
对于模块化的理解,Common.js和AMD的区别
前端优化方面,首屏加载慢,白屏优化
路由懒加载 图片懒加载 gzip压缩 css js html压缩 处理模块去除node_modules cdn
字符串,对象,数组方法
数组方法
改变原数组 push,unshift,reverse,splice,pop,shift,sort
不改变原数组 concat, slice ,join,map,filter,some,every
字符串方法
- charAt() 方法 可返回指定位置的字符。
- concat() 方法用于连接两个或多个字符串。
- indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
- slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。slice(start,end),包括start,不包括end
- substr() 方法可在字符串中抽取从 start 下标开始的指定数目的字符。
- substring() 方法用于提取字符串中介于两个指定下标之间的字符。
- split() 方法用于把一个字符串分割成字符串数组。
清除浮动
方式1: 给浮动元素的父辈容器添加高度 方式2: 在浮动元素下加
方式3: 给浮动元素父级加{zoom:1;} :after{content:""; display:block;clear:both;}方式4:给浮动元素父级加overflow:auto;
js继承
- 原型链
- 借用构造函数
- 组合继承
- 原型继承
垂直水平居中
1.定位 +left: 50%; top: 50%; margin-top (负的高度的一半) + margin-left(负的宽度的一半)
.box-container{
position: relative;
width: 300px;
height: 300px;
}
.box-container .box {
width: 200px;
height: 100px;
position: absolute;
left: 50%;
top: 50%;
margin-top: -50px; /* 高度的一半 */
margin-left: -100px; /* 宽度的一半 */
}
2、定位 + margin
.box-container{
position: relative;
}
.box {
width: 100px;
height: 100px;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
同样是使用绝对定位,但四个方向的偏移量全都为0,之后设置 margin:auto 分配剩余空间,令元素的均匀拖拽至父元素的中心位置。
3.transform 方案: 存在兼容问题:
.box {
position: relative;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
在子元素上设置,transform: translate(-50%, -50%); 用于平面的2D转换,后面的百分比以自身的宽高为参考,定位后将元素的左上角置于父级中央,之后再用 transform 进行偏移,相当于上面设置的 margin-top 和 margin-left。
4.flexbox 方案: 存在兼容问题
.box-container {
display: flex;
justify-content: center;
align-items: center;
}
5. display: table-cell 无兼容性问题
.box {
position: relative;
width: 300px;
height: 300px;
border: 1px solid red;
display: table-cell;
text-align: center;
vertical-align: middle;
}
发布订阅
观察者模式由两类对象组成:主体和观察者。主体负责发布事件,同时观察者通过订阅这些事件来 观察该主体。该模式的一个关键概念是主体并不知道观察者的任何事情,也就是说它可以独自存在并正 常运作即使观察者不存在。从另一方面来说,观察者知道主体并能注册事件的回调函数(事件处理程序)。
下面写一下主题的代码:
function EventTarget () {
this.handles = {}
}
EventTarget.prototype = {
constructor: EventTarget ,
addHandler: function(type, handler){
if (typeof this.handles[type] == 'undefined') {
this.handles[type] = []
}
this.handles[type].push(handler)
},
fire: function (event) {
if(!event.target){
event.target = this
}
if(this.handles[event.type] instanceof Array){
var handlers = this.handles[event.type]
for (var i = 0; i < handlers.length; i++) {
handlers[i](event)
}
}
},
removeHandler: function (type, handler) {
if(this.handles[type] instanceof Array){
var handlers = this.handles[type]
for (var i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
break
}
}
handlers.splice(i,1)
}
}
}
EventTarget 类型有一个单独的属性
handlers,用于储存事件处理程序。
还有三个方法:
- addHandler(),用于注册给定类型事件的事件处理程序;
- fire(),用于触发一个事件;
- removeHandler(),用于注销某个事件类型的事件处理程序。
addHandler()方法接受两个参数:事件类型和用于处理该事件的函数。当调用该方法时,会进行 一次检查,看看 handlers 属性中是否已经存在一个针对该事件类型的数组;如果没有,则创建一个新 的。然后使用 push()将该处理程序添加到数组的末尾。
如果要触发一个事件,要调用 fire()函数。该方法接受一个单独的参数,是一个至少包含 type 属性的对象。fire()方法先给 event 对象设置一个 target 属性,如果它尚未被指定的话。然后它就 查找对应该事件类型的一组处理程序,调用各个函数,并给出 event 对象。因为这些都是自定义事件, 所以 event 对象上还需要的额外信息由你自己决定。
removeHandler()方法是 addHandler()的辅助,它们接受的参数一样:事件的类型和事件处理 程序。这个方法搜索事件处理程序的数组找到要删除的处理程序的位置。如果找到了,则使用 break 操作符退出 for 循环。然后使用 splice()方法将该项目从数组中删除。
观察者和观察者订阅事件的代码:(使用 EventTarget 类型的自定义事件)
function handleMessage (event) {
console.log('Message received: '+ event.message)
}
var target = new EventTarget()
target.addHandler('message',handleMessage)
target.fire({type: 'message', message: 'hello World'})
target.removeHandler('message', handleMessage)
target.fire({type: 'message', message: 'hello World'})
在这段代码中,定义了 handleMessage()函数用于处理 message 事件。它接受 event 对象并输 出 message 属性。调用 target 对象的 addHandler()方法并传给"message"以及 handleMessage() 函数。在接下来的一行上,调用了 fire()函数,并传递了包含 2 个属性,即 type 和 message 的对象 直接量。它会调用 message 事件的事件处理程序,这样就会显示一个警告框(来自 handleMessage())。 然后删除了事件处理程序,这样即使事件再次触发,也不会显示任何警告框。
判断数据类型
typeof instanceof Object.prototype.toString.call 判断数组:Array.isArray()
constructor
闭包
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
function foo() {
var myName = "时间"
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1)
return myName
},
setName:function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName("金钱")
bar.getName()
console.log(bar.getName())
从上面的代码可以看出,innerBar 是一个对象,包含了 getName 和 setName 的两个方法(通常我们把对象内部的函数称为方法)。你可以看到,这两个方法都是在 foo 函数内部定义的,并且这两个方法内部都使用了 myName 和 test1 两个变量。根据词法作用域的规则,内部函数 getName 和 setName 总是可以访问它们的外部函数 foo 中的变量,所以当 innerBar 对象返回给全局变量 bar 时,虽然 foo 函数已经执行结束,但是 getName 和 setName 函数依然可以使用 foo 函数中的变量 myName 和 test1。
foo 函数执行完成之后,其执行上下文从栈顶弹出了,但是由于返回的 setName 和 getName 方法中使用了 foo 函数内部的变量 myName 和 test1,所以这两个变量依然保存在内存中。这像极了 setName 和 getName 方法背的一个专属背包,无论在哪里调用了 setName 和 getName 方法,它们都会背着这个 foo 函数的专属背包。之所以是专属背包,是因为除了 setName 和 getName 函数之外,其他任何地方都是无法访问该背包的,我们就可以把这个背包称为 foo 函数的闭包。
链表和顺序表比较
顺序表存储是将数据元素放到一块连续的内存存储空间,相邻数据元素的存放地址也相邻。(和数组类似)
链表存储是在程序运行时候动态的分配空间,只要存储器还有空间,就不会发生内存溢出问题,相邻数据元素可以随意存放, 但所占内存空间分两部分,一部分存放结点值,另一部分存放表示 结点关系的指针。
顺序表的优点:
(1)空间利用率高(局部性原理,连续存放,利用率高) (2)存取速度高效,通过下标直接存储
顺序表的缺点:
(1)插入和删除比较慢,插入删除一个元素,整个表需要遍历移动元素来重新排一次。 (2)不可以增长长度,有空间限制。当需要存储的元素个数可能多于顺序表的 元素个数时 会出现溢出问题,当元素的个数远小于预分配的空间时,空间浪费巨大。 时间性能 :查找 O(1) ,插入和删除O(n)。
链表的优点:
(1)插入和删除速度快,保留原有的物理顺序。比如插入和删除一个元素时候,只需要改变指针指向 (2)没有空间限制,存储元素个数无上限,基本只与内存空间大小有关。
链表的缺点:
(1)占用额外的空间以存储指针 (2)查找速度慢,因为查找时,需要循环链表访问,需要从开始结点一个一个结点去查找元素 (3)存取某个元素速度慢 查找 O(n) ,插入和删除O(1)
作用域链
我们来看下面这段代码
function bar() {
console.log(myName)
}
function foo() {
var myName = "极客邦"
bar()
}
var myName = "极客时间"
foo()
通过执行上下文来分析代码的执行流程。当这段代码执行到 bar 函数内部时,其调用栈的状态图如下所示:
作用域链
关于作用域链,很多人会感觉费解,但如果你理解了调用栈、执行上下文、词法环境、变量环境等概念,那么你理解起来作用域链也会很容易。
其实在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。当一段代码使用了一个变量时,JavaScript引擎首先会在“当前的执行上下文”中查找该变量,
比如上面那段代码在查找 myName 变量时,如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找。为了直观理解,你可以看下面这张图:
现在你知道变量是通过作用域链来查找的了,不过还有一个疑问没有解开,foo 函数调用的 bar 函数,那为什么 bar 函数的外部引用是全局执行上下文,而不是 foo 函数的执行上下文?要回答这个问题,你还需要知道什么是词法作用域。这是因为在 JavaScript 执行过程中,其作用域链是由词法作用域决定的。
词法作用域
词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
这么讲可能不太好理解,你可以看下面这张图:
从图中可以看出,词法作用域就是根据代码的位置来决定的,其中 main 函数包含了 bar 函数,bar 函数中包含了 foo 函数,因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:foo 函数作用域—>bar 函数作用域—>main 函数作用域—> 全局作用域。
了解了词法作用域以及 JavaScript 中的作用域链,我们再回过头来看看上面的那个问题:在开头那段代码中,foo 函数调用了 bar 函数,那为什么 bar 函数的外部引用是全局执行上下文,而不是 foo 函数的执行上下文?
这是因为根据词法作用域,foo 和 bar 的上级作用域都是全局作用域,所以如果 foo 或者 bar 函数使用了一个它们没有定义的变量,那么它们会到全局作用域去查找。也就是说,词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系。
块级作用域中的变量查找
前面我们通过全局作用域和函数级作用域来分析了作用域链,那接下来我们再来看看块级作用域中变量是如何查找的?在编写代码的时候,如果你使用了一个在当前作用域中不存在的变量,这时 JavaScript 引擎就需要按照作用域链在其他作用域中查找该变量,如果你不了解该过程,那就会有很大概率写出不稳定的代码。
我们还是先看下面这段代码:
function bar() {
var myName = "极客世界"
let test1 = 100
if (1) {
let myName = "Chrome浏览器"
console.log(test)
}
}
function foo() {
var myName = "极客邦"
let test = 2
{
let test = 3
bar()
}
}
var myName = "极客时间"
let myAge = 10
let test = 1
foo()
ES6 是支持块级作用域的,当执行到代码块时,如果代码块中有 let 或者 const 声明的变量,那么变量就会存放到该函数的词法环境中。对于上面这段代码,当执行到 bar 函数内部的 if 语句块时,其调用栈的情况如下图所示:
现在是执行到 bar 函数的 if 语块之内,需要打印出来变量 test,那么就需要查找到 test 变量的值,其查找过程我已经在上图中使用序号 1、2、3、4、5 标记出来了。下面我就来解释下这个过程。首先是在 bar 函数的执行上下文中查找,但因为 bar 函数的执行上下文中没有定义 test 变量,所以根据词法作用域的规则,下一步就在 bar 函数的外部作用域中查找,也就是全局作用域。
至于单个执行上下文中如何查找变量
cdn
- 1.多域名加载资源。一般情况下,浏览器都会对单个域名下的并发请求数(文件加载)进行限制,通常最多有4个,那么第5个加载项将会被阻塞,直到前面的某一个文件加载完毕。 cdn文件是存放在不同区域的,可以同时加载页面所需的文件,可以提高加载速度
- 2.多文件可能被加载并保存有缓存
- 3.好效率,一个好的cdns,会提高更高的效率,更低的网略延迟。
- 4.分布式数据中心 ,cdn会让用户从离的近的结点加载所需文件,提升加载速度
- 5.有效防止网站被攻击。 运营商也会提高网站安全服务
- 6.使用情况分析,运营商回提高数据统计功能。
重绘,重排以及怎么解决
浏览器下载完页面所有的资源后,就要开始构建DOM树,于此同时还会构建渲染树(Render Tree)。(其实在构建渲染树之前,和DOM树同期会构建Style Tree。DOM树与Style Tree合并为渲染树)
DOM树 表示页面的结构 渲染树 表示页面的节点如何显示
一旦渲染树构建完成,就要开始绘制(paint)页面元素了。当DOM的变化引发了元素几何属性的变化,比如改变元素的宽高,元素的位置,导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”。简单的说,重排负责元素的几何属性更新,重绘负责元素的样式更新。而且,重排必然带来重绘,但是重绘未必带来重排。比如,改变某个元素的背景,这个就不涉及元素的几何属性,所以只发生重绘。
重排触发机制
上面已经提到了,重排发生的根本原理就是元素的几何属性发生了改变,那么我们就从能够改变元素几何属性的角度入手
- 添加或删除可见的DOM元素
- 元素位置改变
- 元素本身的尺寸发生改变
- 内容改变
- 页面渲染器初始化
- 浏览器窗口大小发生改变
重绘和重排的开销是非常昂贵的,如果我们不停的在改变页面的布局,就会造成浏览器耗费大量的开销在进行页面的计算,这样的话,我们页面在用户使用起来,就会出现明显的卡顿。现在的浏览器其实已经对重排进行了优化,比如如下代码:
var div = document.querySelector('.div');
div.style.width = '200px';
div.style.background = 'red';
div.style.height = '300px';
比较久远的浏览器,这段代码会触发页面2次重排,在分别设置宽高的时候,触发2次,当代的浏览器对此进行了优化,这种思路类似于现在流行的MVVM框架使用的虚拟DOM,对改变的DOM节点进行依赖收集,确认没有改变的节点,就进行一次更新。但是浏览器针对重排的优化虽然思路和虚拟DOM接近,但是还是有本质的区别。大多数浏览器通过队列化修改并批量执行来优化重排过程。也就是说上面那段代码其实在现在的浏览器优化下,只构成一次重排。
但是还是有一些特殊的元素几何属性会造成这种优化失效。比如:
- offsetTop, offsetLeft,...
- scrollTop, scrollLeft, ...
- clientTop, clientLeft, ...
- getComputedStyle() (currentStyle in IE)
仔细看这些属性,都是需要实时回馈给用户的几何属性或者是布局属性,当然不能再依靠浏览器的优化,因此浏览器不得不立即执行渲染队列中的“待处理变化”,并随之触发重排返回正确的值。
xss csrf
xss
XSS,即 Cross Site Script,中译是跨站脚本攻击;
XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。
攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML 和 Flash。有很多种方式进行 XSS 攻击,但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。
XSS攻击的核心原理是:不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等)。
最后导致的结果可能是:
盗用Cookie破坏页面的正常结构,插入广告等恶意内容D-doss攻击
XSS的攻击方式
1、反射型
发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,所以叫反射型XSS。
2、存储型存
储型XSS和反射型XSS的差别在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求时目标页面时不用再提交XSS代码。
XSS的防范措施(encode + 过滤)
XSS的防范措施主要有:
1、编码:
对用户输入的数据进行
2、过滤:
移除用户输入的和事件相关的属性。如onerror可以自动触发攻击,还有onclick等。(总而言是,过滤掉一些不安全的内容)移除用户输入的Style节点、Script节点、Iframe节点。(尤其是Script节点,它可是支持跨域的呀,一定要移除)。
CSRF
CSRF(Cross-site request forgery):跨站请求伪造。 用户是网站A的注册用户,且登录进去,于是网站A就给用户下发cookie。从上图可以看出,要完成一次CSRF攻击,受害者必须满足两个必要的条件:
(1)登录受信任网站A,并在本地生成Cookie。(如果用户没有登录网站A,那么网站B在诱导的时候,请求网站A的api接口时,会提示你登录)
(2)在不登出A的情况下,访问危险网站B(其实是利用了网站A的漏洞)。
我们在讲CSRF时,一定要把上面的两点说清楚。
温馨提示一下,cookie保证了用户可以处于登录状态,但网站B其实拿不到 cookie。
3、CSRF如何防御 方法一、Token 验证:(用的最多)
(1)服务器发送给客户端一个token;
(2)客户端提交的表单中带着这个token。
(3)如果这个 token 不合法,那么服务器拒绝这个请求。
方法二:隐藏令牌:
把 token 隐藏在 http 的 head头中。
方法二和方法一有点像,本质上没有太大区别,只是使用方式上有区别。
方法三、Referer 验证/验证码:
Referer 指的是页面请求来源。意思是,只接受本站的请求,服务器才做响应;如果不是,就拦截。
CSRF 和 XSS 的区别
区别一:
CSRF:需要用户先登录网站A,获取 cookie。XSS:不需要登录。
区别二:(原理的区别)
CSRF:是利用网站A本身的漏洞,去请求网站A的api。XSS:是向网站 A 注入 JS代码,然后执行 JS 里的代码,篡改网站A的内容。