面试这么多家面试题总结

  1. 原型链
  2. 闭包 & 闭包有哪些应用
  3. 箭头函数和普通函数的区别
  4. flex布局那三个参数的分别是什么?
  5. vue的自定义指令怎么使用
  6. vue双向绑定原理
  7. 你知道安全问题吗?
  8. http2.0协议
  9. 强缓存和协商缓存
  10. 冒泡排序和快速排序的时间复杂度和空间复杂度,以及稳定还是不稳定。

1. 原型链和闭包

对象着手:

  1. 在谈原型链之前,先了解对象

    • 所有的引用类型(函数,数组,对象)都拥有__prototype__(隐式原型)
    • 所有函数拥有prototype属性(显示原型)(仅限函数)
    • 原型对象: 拥有prototype属性的对象,在定义函数时就被创建
  2. prototype与__proto__两个概念

    • prototype: 此属性只有构造函数才有,他指向的是当前构造函数的原型对象
    • proto: 此属性是任何对象在创建时都会有的一个属性,他指向了产生当前对象的构造函数的原型对象,由于并非标准规定属性,不要随便去更改这个属性的值,以免破坏原型链,但是可以借助这个属性来学习,所谓的原型链就是由__prototype__连接而成的链。

原型链详解

在js代码中,通过对象创建(下面一段简单的代码)详细分析原型链一段简单代码

function foo() {}
foo.prototype.z = 3;
var obj = new foo();
obj.y; // 2
obj.x; // 1
obj.z; // 3
typeof obj.toString; // 'function'
'z' in obj; // true
obj.hasOwnProperty('z');//false

obj.z=5;
obj.z;//5
'z' in obj;//true
obj.hasOwnProperty('z');//true
foo.prototype.z;//3
复制代码

代码简单分析
上面一段代码,声明第一个函数foo的时候,它就会带一个foo.prototype的属性,这个属性是一个对象属性,用new foo();构造器的方式构造一个新的对象obj。这时候这个obj的原型会指向foo的prototype属性。 对于这个foo函数的原型也会指向Object.prototype,这个Object.prototype也是有原型的,它的原型指向null。

代码对象原型链图:

image.png

对象访问属性顺序
对象访问属性的顺序,是采用向上查找,如果当前对象没有,它会一直向上原型链中查找,一直找到null,如果还没有会返回undefind。

对象中值修改说明
代码中修改obj.z的值后,再次输出obj.z的时候是5,foo.prototype.z是3,说明我们在修改或添加对象的属性的时候,只是修改了对象本身obj.prototype.z中的值,而原型链中foo.prototype.z的值并不会修改。

in,hasOwnProperty等方法的出现
首先查看整个原型链,会想这两个方法是怎么来的,在foo的的proto指向上一级Object.prototype的时候,就可以访问Object中的一些函数和属性了,其中就包括这两个方法。

第一次调用

'z' in obj;//true  
obj.hasOwnProperty('z');//false
复制代码

表示的是z并不是obj这个对象上的,而是对象的原型链上的。

'z' in obj;//true
obj.hasOwnProperty('z');//true
foo.prototype.z;//3
复制代码

第二次修改了obj.z的值,z就是obj这个对象上的了,但是也并没有修改原型链中的z的值。

特殊说明
_proto_是每一个对象都有的属性,它的指向会有一个特殊说明,大多数情况下 _proto_指向了产生当前对象的构造函数的原型对象,也就是那个 prototype。但是会有特殊的情况

特殊情况

var a={};
var b=Object.create(a);
复制代码

object.create是创建了一个空对象,空对象的原型指向a,a也是空对象,这其中不存在prototype;Object.create在继承中也常被使用,创建一个空对象指向()内的对象,这这样实现了b继承a,也不会篡改a中的内容,在这里就不具体说明了。

原理图分析

image.png

总结

proto是任何对象都有的属性,在js中会形成一条proto连起来的链条,递归访问proto必须最终到头,并且值是null。

误区

写这篇总结的过程中,发现很多文章都写了“JS中万物皆对象”。难道真的是这样吗? 错,JS世界很大,并不只有对象!推荐大家看我的这篇文章 经常被面试官考的JavaScript数据类型知识你真的懂吗?再来看到底JS中万物皆对象是否正确 ,是不是还有许多其他类型。

闭包&闭包有哪些应用

之所以可能通过这种方式在 JavaScript 种实现公有,私有,特权变量正是因为闭包,闭包是指在 JavaScript 中,内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。

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

举个栗子:

var foo = (function () {
    var secret = 'secret';
// “闭包”内的函数可以访问 secret 变量,而 secret 变量对于外部却是隐藏的
    return {
      get_secret: function () {
// 通过定义的接口来访问 secret
        return secret;
      },
      new_secret: function (new_secret) {
// 通过定义的接口来修改 secret
        secret = new_secret;
      }
    };
  }());

  foo.get_secret(); // 得到 'secret'
  foo.secret; // Type error,访问不能
  foo.new_secret('a new secret'); // 通过函数接口,我们访问并修改了 secret 变量
  foo.get_secret(); // 得到 'a new secret'

复制代码

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覆盖掉。)

  1. flex-grow

用来“瓜分”父项的“剩余空间”。(当父元素的宽度大于所有子元素的宽度的和时(即父元素会有剩余空间),子元素如何索取分配父元素的剩余空间。值越大,索取的越厉害。)

  1. flex-shrink

用来“吸收”超出的空间(父元素的宽度小于所有子元素的宽度的和时(即子元素会超出父元素),子元素如何缩小自己的宽度的。值越大,减小的越厉害。)

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;
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享