面试这么多家面试题总结

1,836 阅读28分钟

1. 原型链

自己总结的文章

juejin.cn/post/698057…

闭包&闭包有哪些应用

闭包是指在 JavaScript 中,内部函数总是可以访问其所在的外部函数中声明的参数和变量。

需要注意的一点时,内部函数访问的是被创建的内部变量本身,而不是它的拷贝。所以在闭包函数内加入 loop 时要格外注意。另外当然的是,闭包特性也可以用于创建私有函数或方法。

应用:

  1. 防抖和节流

函数防抖:在事件被触发n秒后再执行回调,如果再这n秒内又被触发,则重新计算

如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件

  • 实现方式:每次触发事件时设置一个延迟调用方法,并且取消之前的延时调用方法

  • 缺点: 如果事件在规定的时间间隔内被不断的触发,则调用方法会被不断的延迟

// 防抖
function debounce(fn, delay = 500) {
    // timer 是闭包中的
    let timer = null

    return function () {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

input1.addEventListener('keyup', debounce(function (e) {
    console.log(e.target)
    console.log(input1.value)
}, 600))

函数节流(throttle): 当持续触发事件时,保证一定时间段内只调用一次事件处理函数

实现方式: 每次触发事件时,如果当前有等待执行的延时函数,则直接return

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<style>
  #div1 {
    width: 100px;
    height: 100px;
    border: 1px solid #eee;
  }
</style>
<body>
<div id="div1" draggable="true">

</div>
</body>
<script>
  function throttle (fn, delay = 100) {
    let timer = null;
    return function () {
      if (timer) return;
      timer = setTimeout(() => {
        fn.apply(this, arguments);
        timer = null
      }, delay)
    }
  }
  const div1 = document.getElementById('div1');
  div1.addEventListener('drag', throttle((e) => {
    console.log(e.offsetX, e.offsetY);
  }, 400))
</script>
</html>

总结

函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。

区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

3. 箭头函数和普通函数的区别?

  1. 箭头函数是匿名函数,不能作为构造函数,不能使用new
  2. 箭头函数不能绑定arguments,取而代之用rest参数...解决
function A(a){
  console.log(arguments);
}
A(1,2,3,4,5,8);
// [1, 2, 3, 4, 5, 8, callee: ƒ, Symbol(Symbol.iterator): ƒ]
let C = (...c) => {
  console.log(c);
}
C(3,82,32,11323);
// [3, 82, 32, 11323]

3 箭头函数没有原型属性

var a = ()=>{
  return 1;
}

function b(){
  return 2;
}

console.log(a.prototype);  // undefined
console.log(b.prototype);   // {constructor: ƒ}
  1. 箭头函数的this永远指向其上下文的this,没有办改变其指向,普通函数的this指向调用它的对象
  2. 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
var obj = {
  a: 10,
  b: () => {
    console.log(this.a); // undefined
    console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
  },
  c: function() {
    console.log(this.a); // 10
    console.log(this); // {a: 10, b: ƒ, c: ƒ}
  }
}
obj.b(); 
obj.c();

4. flex三个参数分别是什么?

1. flex-basis

flex-basis 用于设置子项的占用空间。(该属性用来设置元素的宽度,其实,width也可以设置宽度。如果元素上同时设置了width和flex-basis,那么width 的值就会被flex-basis覆盖掉。)

<style type="text/css" media="screen">
        .box{
            display: flex;
            margin:100px auto;
            width:400px;
            height:200px;
        }
        .inner{
            width:200px;
            height:100px;
            flex-basis:300px;
            background:pink;
        }
    </style>
</head>
<body>
<div class="box">
    <div class="inner">
    </div>
</div>
</body>

见下图: 小编我把宽度设置为width: 200px;flex-basis: 300px;结果显示表明子元素.inner应用了flex-basis属性

image.png

image.png

2. flex-grow

该属性用来设置当父元素的宽度大于所有子元素的宽度的和时(即父元素会有剩余空间),子元素如何分配父元素的剩余空间。 flex-grow的默认值为0,意思是该元素不索取父元素的剩余空间,如果值大于0,表示索取。值越大,索取的越厉害。

举个例子: 父元素宽度为400px,有两个子元素A和B,A宽度为100px;B宽度为200px,剩余空间为400-(100+200) = 100px; 如果A,B都不索取剩余空间,则有100px的剩余空间。

 .box {
      display: flex;
      flex-direction: row;
      margin: 100px auto;
      width: 400px;
      height: 200px;
      border: 1px solid black;
    }

    .inner {
      flex-basis: 100px;
      height: 100px;
      background-color: green;
    }
    .inner1 {
      flex-basis: 200px;
      height: 100px;
      background-color: blue;
    }

<body>
<div class="box">
  <div class="inner"></div>
  <div class="inner1"></div>
</div>
</body>

image.png

.inner {
      flex-basis: 100px;
      height: 100px;
      background-color: green;
      flex-grow: 1;
    }
    .inner1 {
      flex-basis: 200px;
      height: 100px;
      background-color: blue;
    }

image.png

<div class="box">
  <div class="inner"></div>
  <div class="inner1"></div>
</div>

.box {
      display: flex;
      flex-direction: row;
      margin: 100px auto;
      width: 400px;
      height: 200px;
      border: 1px solid black;
    }

    .inner {
      flex-basis: 100px;
      height: 100px;
      background-color: green;
      flex-grow: 1;
    }
    .inner1 {
      flex-basis: 200px;
      height: 100px;
      background-color: blue;
      flex-grow: 2;
    }

如果A,B都设索取剩余空间,A设置flex-grow为1,B设置flex-grow为2。则最终A的大小为 自身宽度(100px)+ A获得的剩余空间的宽度(100px (1/(1+2))),最终B的大小为 自身宽度(200px)+ B获得的剩余空间的宽度(100px (2/(1+2)))(这里呢小编只给了公式,小伙伴们可以自己去算一下)

image.png

3. flex-shrink

该属性用来设置,当父元素的宽度小于所有子元素的宽度的和时(即子元素会超出父元素),子元素如何缩小自己的宽度的。 flex-shrink的默认值为1,当父元素的宽度小于所有子元素的宽度的和时,子元素的宽度会减小。值越大,减小的越厉害。如果值为0,表示不减小。

举个例子: 父元素宽400px,有两子元素:A和B。A宽为200px,B宽为300px。 则A,B总共超出父元素的宽度为(200+300)- 400 = 100px。 如果A,B都不减小宽度,即都设置flex-shrink为0,则会有100px的宽度超出父元素。


    .box {
      display: flex;
      flex-direction: row;
      margin: 100px auto;
      width: 400px;
      height: 200px;
      border: 1px solid black;
    }

    .inner {
      flex-basis: 200px;
      height: 100px;
      background-color: green;
      flex-shrink: 0;
    }
    .inner1 {
      flex-basis: 300px;
      height: 100px;
      background-color: blue;
      flex-shrink: 0;
    }

image.png

如果A不减小宽度:设置flex-shrink为0,B减小。则最终B的大小为 自身宽度(300px)- 总共超出父元素的宽度(100px)= 200px

 .inner {
      flex-basis: 200px;
      height: 100px;
      background-color: green;
      flex-shrink: 0;
    }
    .inner1 {
      flex-basis: 300px;
      height: 100px;
      background-color: blue;
      flex-shrink: 1;
    }

image.png

如果A,B都减小宽度,A设置flex-shirk为3,B设置flex-shirk为2。则最终A的大小为 自身宽度(200px)- A减小的宽度(100px * (200px * 3/(200 * 3 + 300 * 2))) = 150px,最终B的大小为 自身宽度(300px)- B减小的宽度(100px * (300px * 2/(200 * 3 + 300 * 2))) = 250px

        .inner{
            flex-basis:200px;
            height:100px;
            background:black;
             flex-shrink:3;
        }
        .inner1{
            flex-basis:300px;
            height:100px;
            background:blue;
            flex-shrink:2;
 
        }

image.png

image.png

这里小编明确一点,flex是flex-grow,flex-shrink, flex-basis (注意小编写的顺序)缩写形式,大家可以记一下下面的缩写规则:

如:flex 取值为 none,则计算值为 0 0 auto,如下是等同的:

.item {flex: none;}
.item {
    flex-grow: 0;
    flex-shrink: 0;
    flex-basis: auto;
}

当 flex取值为 auto,则计算值为 1 1 auto,如下是等同的:

.item {flex: auto;}
.item {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: auto;
}

当 flex 取值为一个非负数字,则该数字为 flex-grow 值,flex-shrink 取 1,flex-basis 取 0%,如下是等同的:

.item {flex: 1;}

.item {flex-grow: 1; flex-shrink: 1; flex-basis: 0%;}

当 flex 取值为一个长度或百分比,则视为 flex-basis 值,flex-grow 取 1,flex-shrink取 1,有如下等同情况(注意 0% 是一个百分比而不是一个非负数字):

.item-1 {flex: 0%;}
.item-1 { flex-grow: 1; flex-shrink: 1; flex-basis: 0%;} 

当 flex取值为两个非负数字,则分别视为 flex-grow和 flex-shrink的值,flex-basis取 0%,如下是等同的:

.item {flex: 2 3;} 
.item { flex-grow: 2; flex-shrink: 3; flex-basis: 0%;} 

zz当 flex取值为一个非负数字和一个长度或百分比,则分别视为 flex-grow和 flex-basis的值,flex-shrink取 1,如下是等同的:

.item {flex: 2333 3222px;} 
.item { flex-grow: 2333; flex-shrink: 1; flex-basis: 3222px;}

5. vue的自定义指令的使用方法?

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。举个聚焦输入框的例子,如下:(咳咳,借官网的用一用)

  1. 定义全局的自定义变量
Vue.directive('color',{
  inserted(el){
//  各单位注意,这里的el获取的是标签元素,说白了就是可以直接操作DOM
    console.log(el)
    el.style.color = "red"
  }
})

app.vue 这里直接写v-color就可以

<div >前端伪大叔</div>
<div v-color>前端伪大叔</div>

image.png 2. 自定义指令中,添加属性;

Vue.directive('color',{
  bind(el,binding){
    switch(binding.value){
      case 'red':
        el.style.color = 'red' 
        break;
      case 'blue':
        el.style.color = 'blue'
        break;
    }
  }
})

App.vue

<div >前端伪大叔</div>
<div v-color="'red'">前端伪大叔</div>
<div v-color="'blue'">前端伪大叔</div>

可以通过给自定义的属性,添加属性的方式来修改颜色;当然不仅仅只能修改颜色这么简单,因为el直接获得了DOM,所以你懂得!

image.png

3、组件内指令-只有自己组件可以使用

注意:directives是一个对象,里面定义的自定义指令也是对象!

//  template
<div >前端伪大叔</div>
<div v-color>前端伪大叔</div>

//  script
//  这里是对象
 directives:{ 
//  每个指令都是一个对象
   color:{  
     inserted(el){
       el.style.color = 'cyan'
     }
   }
 }

image.png 4、组件内的自定义指令,增加属性

//  template
<div v-color="'red'">前端伪大叔</div>
<div v-color="'blue'">前端伪大叔</div>
//  script
//  这里是对象
 directives:{
//  每个指令都是一个对象
   color:{
     bind(el,binding){
       if(binding.value == 'red'){
         el.style.color = 'red'
       }else if (binding.value = 'blue'){
         el.style.color = 'blue'
       }
     }
   }
 }

image.png

6 vue双向绑定原理

vue双向绑定原理分析

当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理。

1.vue双向绑定原理

vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。我们先来看Object.defineProperty()这个方法:

var obj  = {};
Object.defineProperty(obj, 'name', {
        get: function() {
            console.log('我被获取了')
            return val;
        },
        set: function (newVal) {
            console.log('我被设置了')
        }
})
obj.name = 'fei';//在给obj设置name属性的时候,触发了set这个方法
var val = obj.name;//在得到obj的name属性,会触发get方法

已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,那么在设置或者获取的时候我们就可以在get或者set方法里假如其他的触发函数,达到监听数据变动的目的,无疑这个方法是本文中最重要、最基础的内容之一。

2.实现最简单的双向绑定 我们知道通过Object.defineProperty()可以实现数据劫持,是的属性在赋值的时候触发set方法,

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="demo"></div>
    <input type="text" id="inp">
    <script>
        var obj  = {};
        var demo = document.querySelector('#demo')
        var inp = document.querySelector('#inp')
        Object.defineProperty(obj, 'name', {
            get: function() {
                return val;
            },
            set: function (newVal) {//当该属性被赋值的时候触发
                inp.value = newVal;
                demo.innerHTML = newVal;
            }
        })
        inp.addEventListener('input', function(e) {
            // 给obj的name属性赋值,进而触发该属性的set方法
            obj.name = e.target.value;
        });
        obj.name = 'fei';//在给obj设置name属性的时候,触发了set这个方法
    </script>
</body>
</html>

当然要是这么粗暴,肯定不行,性能会出很多的问题。

3.讲解vue如何实现

image.png 3.1 observer用来实现对每个vue中的data中定义的属性循环用Object.defineProperty()实现数据劫持,以便利用其中的setter和getter,然后通知订阅者,订阅者会触发它的update方法,对视图进行更新。

3.2 我们介绍为什么要订阅者,在vue中v-model,v-name,{{}}等都可以对数据进行显示,也就是说假如一个属性都通过这三个指令了,那么每当这个属性改变的时候,相应的这个三个指令的html视图也必须改变,于是vue中就是每当有这样的可能用到双向绑定的指令,就在一个Dep中增加一个订阅者,其订阅者只是更新自己的指令对应的数据,也就是v-model='name'和{{name}}有两个对应的订阅者,各自管理自己的地方。每当属性的set方法触发,就循环更新Dep中的订阅者。

4.vue代码实现 4.1 observer实现,主要是给每个vue的属性用Object.defineProperty(),代码如下:

function definereactive (obj, key, val) {
    var dep = new Dep();
        Object.defineProperty(obj, key, {
             get: function() {
                    //添加订阅者watcher到主题对象Dep
                    if(Dep.target) {
                        // js的浏览器单线程特性,保证这个全局变量在同一时间内,只会有同一个监听器使用
                        dep.addSub(Dep.target);
                    }
                    return val;
             },
             set: function (newVal) {
                    if(newVal === val) return;
                    val = newVal;
                    console.log(val);
                    // 作为发布者发出通知
                    dep.notify();//通知后dep会循环调用各自的update方法更新视图
             }
       })
}
        function observe(obj, vm) {
            Object.keys(obj).forEach(function(key) {
                definereactive(vm, key, obj[key]);
            })
        }
4.2实现compile: compile的目的就是解析各种指令称真正的html。

function Compile(node, vm) {
    if(node) {
        this.$frag = this.nodeToFragment(node, vm);
        return this.$frag;
    }
}
Compile.prototype = {
    nodeToFragment: function(node, vm) {
        var self = this;
        var frag = document.createDocumentFragment();
        var child;
        while(child = node.firstChild) {
            console.log([child])
            self.compileElement(child, vm);
            frag.append(child); // 将所有子节点添加到fragment中
        }
        return frag;
    },
    compileElement: function(node, vm) {
        var reg = /\{\{(.*)\}\}/;
        //节点类型为元素(input元素这里)
        if(node.nodeType === 1) {
            var attr = node.attributes;
            // 解析属性
            for(var i = 0; i < attr.length; i++ ) {
                if(attr[i].nodeName == 'v-model') {//遍历属性节点找到v-model的属性
                    var name = attr[i].nodeValue; // 获取v-model绑定的属性名
                    node.addEventListener('input', function(e) {
                        // 给相应的data属性赋值,进而触发该属性的set方法
                        vm[name]= e.target.value;
                    });
                    new Watcher(vm, node, name, 'value');//创建新的watcher,会触发函数向对应属性的dep数组中添加订阅者,
                }
            };
        }
        //节点类型为text
        if(node.nodeType === 3) {
            if(reg.test(node.nodeValue)) {
                var name = RegExp.$1; // 获取匹配到的字符串
                name = name.trim();
                new Watcher(vm, node, name, 'nodeValue');
            }
        }
    }
}
4.3 watcher实现

function Watcher(vm, node, name, type) {
    Dep.target = this;
    this.name = name;
    this.node = node;
    this.vm = vm;
    this.type = type;
    this.update();
    Dep.target = null;
}

Watcher.prototype = {
    update: function() {
        this.get();
        this.node[this.type] = this.value; // 订阅者执行相应操作
    },
    // 获取data的属性值
    get: function() {
        console.log(1)
        this.value = this.vm[this.name]; //触发相应属性的get
    }
}
4.4 实现Dep来为每个属性添加订阅者

function Dep() {
    this.subs = [];
}
Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },
    notify: function() {
        this.subs.forEach(function(sub) {
        sub.update();
        })
    }
}

这样一来整个数据的双向绑定就完成了。

5.梳理

首先我们为每个vue属性用Object.defineProperty()实现数据劫持,为每个属性分配一个订阅者集合的管理数组dep;然后在编译的时候在该属性的数组dep中添加订阅者,v-model会添加一个订阅者,{{}}也会,v-bind也会,只要用到该属性的指令理论上都会,接着为input会添加监听事件,修改值就会为该属性赋值,触发该属性的set方法,在set方法内通知订阅者数组dep,订阅者数组循环调用各订阅者的update方法更新视图。

7.你知道安全问题吗?

1、基于DOM的跨站点脚本编制(XSS) ①XSS(Cross Site Script):跨站点脚本编制,指的是攻击者向合法的web页面插入恶意的脚本代码(通常是是HTML代码和JS代码),然后提交给服务器,随即服务器响应页面(被植入的恶意脚本代码),攻击者可以利用这些恶意脚本代码进行会话挟持登攻击。例如:攻击者在论坛中放一个看似安全得连接,骗取用户点击之后,盗取cookies得用户隐私信息。 ②XSS通常被分为:反射型和持久型

反射型:恶意代码请求的数据在服务器中呈现为未编码和未过滤

持久性:恶意代码请求的数据被保存在服务器中,每次用户访问这个页面时,恶意代码都会被执行。

第三类基于DOM的跨站点脚本编制不依赖服务器端的内容,比如HTML页面使用了document.location、document.URL、或者document.referer等DOM元素的属性,攻击者可以利用这些属性植入恶意脚本。

③XSS防范方法

代码里对用户输入的地方需要仔细检查长度和对“<”“>”“,”“'”等字符串做过滤; 任何内容写到页面之前都必须加以encode,避免不小心把html tag弄出来; 避免直接在cookie中泄露用户隐私,例如email、密码等 如果网站不需要在浏览器对cookie进行操作,可以在set-cookie末尾加上HttpOnly来防止js代码直接获取cookie 尽量采用post而不是get提交表单

2、CSRF (cross-site- request forgery)跨站点请求伪造

xss是获取信息,不需要提前知道其他用户页面的代码和数据包

csrf是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包

①crsf攻击原理

用户C打开浏览器,访问受信任网站A,输入用户名和密码登录网站A 在用户信息通过验证之后,网站A产生cookie信息并返回给浏览器,这时用户登录网站A成功,可以正常发送求请求到网站A 用户未退出网站A之前,在同一浏览器中,打开一个页面访问网站B 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问网站A 用户在接收到B返回的攻击性代码后,在不知情的情况下携带cookie信息,向网站A发送请求。而网站A不知道这个请求其实是B发的(误认为是用户发的),会根据用户C的cookie的信息以C的权限处理该请求,导致网站B的恶意代码被执行 ②crsf攻击实例(转)

受害者 Bob 在银行有一笔存款,通过对银行的网站发送请求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可以使 Bob 把 1000000 的存款转到 bob2 的账号下。通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的 session,并且该 session 的用户 Bob 已经成功登陆。
    黑客 Mallory 自己在该银行也有账户,他知道上文中的 URL 可以把钱进行转帐操作。Mallory 可以自己发送一个请求给银行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。但是这个请求来自 Mallory 而非 Bob,他不能通过安全认证,因此该请求不会起作用。

这时,Mallory 想到使用 CSRF 的攻击方式,他先自己做一个网站,在网站中放入如下代码: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”,并且通过广告等诱使 Bob 来访问他的网站。当 Bob 访问该网站时,上述 url 就会从 Bob 的浏览器发向银行,而这个请求会附带 Bob 浏览器中的 cookie 一起发向银行服务器。大多数情况下,该请求会失败,因为他要求 Bob 的认证信息。但是,如果 Bob 当时恰巧刚访问他的银行后不久,他的浏览器与银行网站之间的 session 尚未过期,浏览器的 cookie 之中含有 Bob 的认证信息。这时,悲剧发生了,这个 url 请求就会得到响应,钱将从 Bob 的账号转移到 Mallory 的账号,而 Bob 当时毫不知情。等以后 Bob 发现账户钱少了,即使他去银行查询日志,他也只能发现确实有一个来自于他本人的合法请求转移了资金,没有任何被攻击的痕迹。而 Mallory 则可以拿到钱后逍遥法外。

③防御csrf攻击

三种策略:验证HTTP Referer字段,在请求地址中添加token并验证,在http头中自定义属性并验证

验证HTTP Referer字段

   在http头部的referer字段中,记录了该http请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如需要访问 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用户必须先登陆 bank.example,然后通过点击页面上的按钮来触发转账事件。这时,该转帐请求的 Referer 值就会是转账按钮所在的页面的 URL,通常是以 bank.example 域名开头的地址。而如果黑客要对银行网站实施 CSRF 攻击,他只能在他自己的网站构造请求,当用户通过黑客的网站发送请求到银行时,该请求的 Referer 是指向黑客自己的网站。因此,要防御 CSRF 攻击,银行网站只需要对于每一个转账请求验证其 Referer 值,如果是以 bank.example 开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。

缺点:

referer的值是由浏览器提供的,我们并不能保障浏览器自身没有漏洞,而且目前已经有一些方法可以篡改referer值 用户可以设置浏览器发送时不提供referer,当他们正常访问银行网站时,网站会因为请求没有referer而认为是csrf攻击,拒绝合法用户的请求 在请求地址中添加token并验证

CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上 ,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token。

缺点:

难以保证 token 本身的安全。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址。由于系统也会在这个地址后面加上 token,黑客可以在自己的网站上得到这个 token,并马上就可以发动 CSRF 攻击。为了避免这一点,系统可以在添加 token 的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加 token,如果是通向外网则不加。不过,即使这个 csrftoken 不以参数的形式附加在请求之中,黑客的网站也同样可以通过 Referer 来得到这个 token 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因。

在http头中自定义属性并验证

这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。

缺点:

XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。 对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。 3、基于URL的重定向 指的是web页面会采用HTTP参数来保存URL,且web页面的脚本会将请求重定向到该保存的URL上,攻击者可以将HTTP中保存的URL改为恶意站点

4、客户端JS Cookie引用 cookie由服务器创建,并存储在客户端浏览器,保存用户的身份识别、session信息、甚至授权信息等。 客户端js可以操作cookie数据 如果在客户端使用JS创建或者修改站点的cookie,那么攻击者就可以查看这些代码,然后根据逻辑修改cookie。一旦cookie中边包含重要的信息,攻击者很容易利用这些漏洞进行特权升级等 5、JS劫持 许多的应用程序利用JSON作为AJAX的数据传输机制,这通常会收到JS挟持攻击。 JSON实际就是一段JS代码,通常是数组格式 攻击者在其恶意站点的网页中通过

8. http2.0协议

9. 强缓存和协商缓存

web缓存描述 : Web 缓存是可以自动保存常见文档副本的 HTTP 设备。当 Web 请求抵达缓存时, 如果本地有“已缓存的”副本,就可以从本地存储设备而不是原始服务器中提取这 个文档。(此结论来自http权威指南) 缓存的优缺点: 优点:

缓存减少了冗余的数据传输,节省了你的网络费用。 缓存缓解了网络瓶颈的问题。不需要更多的带宽就能够更快地加载页面。 缓存降低了对原始服务器的要求。服务器可以更快地响应,避免过载的出现。 缓存降低了距离时延,因为从较远的地方加载页面会更慢一些。

缺点:

缓存中的数据可能与服务器的数据不一致; 消耗内存;

缓存验证概述: 缓存可分为强缓存和协商缓存。

1,浏览器进行资源请求时,会判断response headers是否命中强缓存,如果命中,直接从本地读取缓存,不会向服务器发送请求,

2,当强缓存没有命中时,会发送请求到服务端,判断协商缓存是否命中,如果命中,服务器将请求返回,不会返回资源,告诉浏览器从本地读取缓存。如何不命中,服务器直接返回资源
区别: 强缓存命中,不会请求服务器,直接请求缓存;协商缓存命中,会请求服务器,不会返回内容,然后读取缓存;

image.png

缓存的处理流程

from memory cache 和 from disk cache的区别 from memory cache:字面理解是从内存中,其实也是字面的含义,这个资源是直接从内存中拿到的,不会请求服务器一般已经加载过该资源且缓存在了内存当中,当关闭该页面时,此资源就被内存释放掉了,再次重新打开相同页面时不会出现from memory cache的情况

from disk cache:同上类似,此资源是从磁盘当中取出的,也是在已经在之前的某个时间加载过该资源,不会请求服务器但是此资源不会随着该页面的关闭而释放掉,因为是存在硬盘当中的,下次打开仍会from disk cache (来自:blog.csdn.net/garrettzxd/… ) 以下是缓存实现的四种方式 强缓存 强缓存又分为Expires 和 Cache-Control

Expires,该值是一个GMT时间格式个字符串,浏览器进行第一次请求时,服务器会在返回头部加上Expires,下次请求,如果在这个时间之前则命中缓存,

image.png


app.get('/', (req, res) => {
    const cssContent = path.join(__dirname, './html/index.html');
    fs.readFile(cssContent, function(err, data) {
          res.setHeader("Expires", new Date(Date.now() + 2592000000).toUTCString());
        res.end(data);
    })
});


Cache-Control ,该值是利用max-age判断缓存的生命周期,是以秒为单位,如何在生命周期时间内,则命中缓存

image.png

命中缓存

image.png

协商缓存

协商缓存利用Last-Modified , If-Modified-Since 和 ETag , If-None-Match来实现 Last-Modified , If-Modified-Since Last-Modified: 表示为为实体头部部分,response返回,表示为资源的最后更新时间

If-Modified-Since:通过比较两次的时间判断,资源在请求期间是否有修改,假如没有修改,则命中协商缓存,浏览器从缓存中读取资源,如果没有命中,资源有过修改,返回新的Last-Modified时间和服务器资源

    app.get('/', (req, res) => {
    const cssContent = path.join(__dirname, './html/index.html')
    fs.stat(cssContent, (err, start) => {
        if (req.headers['if-modified-since'] === start.mtime.toUTCString()) {
            res.writeHead(304, 'Not Modified');
            res.end();
        } else {
            fs.readFile(cssContent, function (err, data) {
                let lastModified = start.mtime.toUTCString();
                res.setHeader('Last-Modified', lastModified);
                res.writeHead(200, 'OK');
                res.end(data);
            })
        }
    })

});


ETag , If-None-Match

有些情况下仅判断最后修改日期来验证资源是否有改动是不够的: 1,存在周期性重写某些资源,但资源实际包含的内容并无变化; 2,被修改的信息并不重要,如注释等; 3,Last-Modified无法精确到毫秒,但有些资源更新频率有时会小于一秒。

ETag:为相应头部字段,表示资源内容的唯一标识,随服务器response返回;

If-None-Match: 服务器比较请求头中的If-None-Match和当前资源中的etag是否一致,来判断资源是否修改过,如果没有修改,则命中缓存,浏览器从缓存中读取资源,如果修改过,服务器会返回新的etag,并返回资源;

app.get('/home', (req, res) => {
    const cssContent = path.join(__dirname, './html/index.html')
    fs.stat(cssContent, (err, start) => {
        let etag = md5(cssContent);
        if (req.headers['if-none-match'] === etag) {
            res.writeHead(304, 'Not Modified');
            res.end();
        } else {
            fs.readFile(cssContent, function (err, data) {
                res.setHeader('Etag', etag);
                res.writeHead(200, 'OK');
                res.end(data);
            })
        }    })
});

不推荐使用 Expires 首部,它指定的是实际的过期日期而不是秒数。HTTP 设计者 后来认为,由于很多服务器的时钟都不同步,或者不正确,所以最好还是用剩余秒 数,而不是绝对时间来表示过期时间。 ETag解决了Last-Modified使用时可能出现的资源的时间戳变了但内容没变及如果再一秒钟以内资源变化但Last-Modified没变的问题,感觉ETag更加稳妥。 补充:根据浏览器缓存策略,Expire和Cache-Control用回车、后退、F5刷新会跳过本地缓存,每次都会从服务器中获数据。

10. js实现冒泡排序,快速排序【解析时间空间复杂度】

冒泡排序(Bubble Sort)

时间复杂度

最好的情况:数组本身是顺序的,外层循环遍历一次就完成O(n)

最坏的情况:,O(n2)数组本身是逆序的,内外层遍历O(n2)

空间复杂度

开辟一个空间交换顺序O(1)

稳定性

稳定,因为if判断不成立,就不会交换顺序,不会交换相同元素

冒泡排序它在所有排序算法中最简单。然而, 从运行时间的角度来看,冒泡排序是最差的一个,它的复杂度是O(n2)。

冒泡排序比较任何两个相邻的项,如果第一个比第二个大,则交换它们。元素项向上移动至正确的顺序,就好像气泡升至表面一样,冒泡排序因此得名。 交换时,我们用一个中间值来存储某一交换项的值。其他排序法也会用到这个方法,因此我 们声明一个方法放置这段交换代码以便重用。使用ES6(ECMAScript 2015)**增强的对象属性——对象数组的解构赋值语法,**这个函数可以写成下面 这样:

    [array[index1], array[index2]] = [array[index2], array[index1]];

具体实现:

function bubbleSort(arr) {
  for (let i = 0; i < arr.length; i++) {//外循环(行{2})会从数组的第一位迭代 至最后一位,它控制了在数组中经过多少轮排序
    for (let j = 0; j < arr.length - i; j++) {//内循环将从第一位迭代至length - i位,因为后i位已经是排好序的,不用重新迭代
      if (arr[j] > arr[j + 1]) {//如果前一位大于后一位
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];//交换位置
      }
    }
  }
  return arr;
}

快速排序

时间复杂度

最好的情况:每一次base值都刚好平分整个数组,O(nlogn)

最坏的情况:每一次base值都是数组中的最大/最小值,O(n2)

空间复杂度

快速排序是递归的,需要借助栈来保存每一层递归的调用信息,所以空间复杂度和递归树的深度一致

最好的情况:每一次base值都刚好平分整个数组,递归树的深度O(logn)

最坏的情况:每一次base值都是数组中的最大/最小值,递归树的深度O(n)

稳定性

快速排序是不稳定的,因为可能会交换相同的关键字。

快速排序是递归的,

特殊情况:left>right,直接退出。

步骤:

  • (1) 首先,从数组中选择中间一项作为主元base,一般取第一个值。
  • (2) 创建两个指针,左边一个指向数组第一个项,右边一个指向数组最后一个项。移动右指针直到找到一个比主元小的元素,接着,移动左指 针直到我们找到一个比主元大的元素,然后交 换它们,重复这个过程,直到左指针遇见了右指针。这个过程将使得比主元小的值都排在主元之前,而比主元大的值都排在主元之后。这一步叫作划分操作。
  • (3)然后交换主元和指针停下来的位置的元素(等于说是把这个元素归位,这个元素左边的都比他小,右边的都比他大,这个位置就是他最终的位置)
  • (4) 接着,算法对划分后的小数组(较主元小的值组成的子数组,以及较主元大的值组成的 子数组)重复之前的两个步骤(递归方法),
  • 递归的出口为left/right=i,也就是:
left>i-1 / i+1>right

此时,子数组数组已排序完成。

具体实现

function quicksort(arr, left, right) {
  if (left > right) {
    return;
  }
  var i = left,
    j = right,
    base = arr[left]; //基准总是取序列开头的元素
  //   var [base, i, j] = [arr[left], left, right]; //以left指针元素为base
  while (i != j) {
    //i=j,两个指针相遇时,一次排序完成,跳出循环
    // 因为每次大循环里面的操作都会改变i和j的值,所以每次循环/操作前都要判断是否满足i
    while (i < j && arr[j] >= base) {
      //寻找小于base的右指针元素a,跳出循环,否则左移一位
      j--;
    }
    while (i < j && arr[i] <= base) {
      //寻找大于base的左指针元素b,跳出循环,否则右移一位
      i++;
    }
    if (i < j) {
      [arr[i], arr[j]] = [arr[j], arr[i]]; //交换a和b
    }
  }
  [arr[left], arr[j]] = [arr[j], arr[left]]; //交换相遇位置元素和base,base归位
  //   let k = i;
  quicksort(arr, left, i - 1); //对base左边的元素递归排序
  quicksort(arr, i + 1, right); //对base右边的元素递归排序
  return arr;
}