前端面试题总结(未完待续,持续更新)

236 阅读39分钟

深拷贝

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
}

rootparent

root是根实例,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常见的状态码

blog.csdn.net/qq_35689573…

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 所指向的执行上下文中查找。为了直观理解,你可以看下面这张图:

从图中可以看出,bar 函数和 foo 函数的 outer 都是指向全局上下文的,这也就意味着如果在 bar 函数或者 foo 函数中使用了外部变量,那么 JavaScript 引擎会去全局执行上下文中查找。我们把这个查找的链条就称为作用域链。
现在你知道变量是通过作用域链来查找的了,不过还有一个疑问没有解开,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的内容。

原型链继承