阅读 1212

面试这么多家面试题总结

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;
}
复制代码

11.怪异盒模型和标准盒模型区别

css盒模型本质是一个盒子,它由边距、边框、填充和实际内容组成。盒模型能够让我们在其他元素和周边元素边框之间的空间放置元素。

        标准盒与怪异盒的区别在于他们的总宽度的计算公式不一样。标准模式下总宽度=width+margin(左右)+padding(左右)border(左右);怪异模式下总宽度=width+margin(左右)(就是说width已经包含了padding和border值)。标准模式下如果定义的DOCTYPE缺失,则在ie6、ie7、ie8下汇触发怪异模式。当设置为box-sizing:content-box时,将采用标准模式解析计算,也是默认模式;当设置为box-sizing:border-box时,将采用怪异模式解析计算;

image.png 由上图可以看到,width 只为盒子中内容 content 的大小,所以整个盒子的宽度可以计算为 width + padding(左右) + border(左右) + margin(左右)

image.png 由上图可以看到怪异盒子模型中,width 为 content + padding(左右) + border(左右),因此整个盒子的宽度为 width + margin(左右)

2. 什么是BFC, BFC触发的条件以及外边距合并的问题怎么解决的?

1. 什么是BFC(块级格式化上下文): 是web页面可视化渲染css的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域

简单的理解就是具备BFC特性的元素,就像被一个容器所包裹,容器内的元素在布局上不会影响外面的元素。

2. 如何触发BFC?

1. 根元素或包含根元素的元素
2. 浮动元素(元素的 float 不是 none)
3. 绝对定位元素(元素的 position 为 absolute 或 fixed)
4. 行内块元素(元素的 display 为 inline-block)
5. 表格单元格(元素的 display为 table-cell,HTML表格单元格默认为该值)
6. 表格标题(元素的 display 为 table-caption,HTML表格标题默认为该值)
7. 匿名表格单元格元素(元素的 display为 table、table-row、 table-row-group、table-header-group、table-footer-group(分别是HTML table、row、tbody、thead、tfoot的默认属性)或 inline-table)
8. overflow 值不为 visible 的块元素
9. display 值为 flow-root 的元素
10. contain 值为 layout、content或 strict 的元素
11. 弹性元素(display为 flex 或 inline-flex元素的直接子元素)
12. 网格元素(display为 grid 或 inline-grid 元素的直接子元素)
13. 多列容器(元素的 column-count 或 column-width 不为 auto,包括 column-count 为 1)
14. column-span 为 all 的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中(标准变更,
复制代码

解决普通文档流块元素的外边距折叠问题

普通的

 <style>
    * {
        margin: 0;
        padding: 0;
    }
    .demo div {
        width: 40px;
        height: 40px;
    }
    .demo1 {
        margin: 10px 0;
        background: pink;
    }
    .demo2 {
        margin: 20px 0;
        background: blue;
    }
</style>
<div class="demo">
    <div class="demo1"></div>
    <div class="demo2"></div>
</div>

复制代码

解决的

<style>
    * {
        margin: 0;
        padding: 0;
    }
    .demo {
        overflow: hidden;
    }
    .demo div {
        width: 40px;
        height: 40px;
    }
    .demo1 {
        margin: 10px 0;
        background: pink;
    }
    .demo2 {
        margin: 20px 0;
        background: blue;
    }
</style>

<div class="demo">
    <div class="demo1"></div>
</div>
<div class="demo">
    <div class="demo2"></div>
</div>
复制代码

13. 清除浮动有哪些方法:

  • clear清除浮动(添加空div法)在浮动元素下方添加空div,并给该元素写css样式:{clear:both;height:0;overflow:hidden;}
  • 给浮动元素父级设置高度
  • 父级同时浮动(需要给父级同级元素添加浮动)
  • 父级设置成inline-block,其margin: 0 auto居中方式失效
  • 给父级添加overflow:hidden 清除浮动方法
  • 万能清除法 after伪类 清浮动(现在主流方法,推荐使用)。

下面最常用的方法

.clearfix:after {
    content: '';
    display: block;
    both: clear;
    visibility: hidden;
    height: 0;
    overflow: hidden;
}
.clearfix {
    zoom: 1;
}
复制代码

14. 说说你在数组里使用过的十个方法,五个可以改变数据,五个不可以改变的?

不会改变原来数组的有:

concat()---连接两个或更多的数组,并返回结果。

some()---检测数组元素中是否有元素符合指定条件。

filter()---检测数组元素,并返回符合条件所有元素的数组。

join()---把数组的所有元素放入一个字符串。

map()---通过指定函数处理数组的每个元素,并返回处理后的数组。

会改变原来数组的有:

pop()---删除数组的最后一个元素并返回删除的元素。

push()---向数组的末尾添加一个或更多元素,并返回新的长度。

shift()---删除并返回数组的第一个元素。

unshift()---向数组的开头添加一个或更多元素,并返回新的长度。

reverse()---反转数组的元素顺序。

sort()---对数组的元素进行排序。

splice()---用于插入、删除或替换数组的元素。
复制代码

解析

  1. 原数组不变的
// 1. concat 连接两个或更多的数组,并返回结果
  var arr1 = [1, 2, 3],
      arr2 = [4, 5, 6],
      arr3 = [];
  arr3 = arr1.concat(arr2);
  console.log(arr1, arr2, arr3);
  /**
   * 结果: 不改变原数组(arr1, arr2)
   * arr1 -> [1, 2, 3]
   * arr2 -> [4, 5, 6]
   * arr3 -> [1, 2, 3, 4, 5, 6]
  */
  
  // 2 join(): 把所有的元素放入一个字符串,元素通过指定的分隔符进行分隔
    var arr = [1, 2, 3, 4, 5, 6];
    var ret = null;
    ret = arr.join('*');
    console.log(arr, ret);

    /**
     *arr元数组不变 [1, 2, 3, 4, 5, 6]
     * ret:  "1*2*3*4*5*6"
     */
 // 3. slice: 方法可从已有的数组中返回选定的元素
 var arr = [1, 2, 3]

复制代码
  1. 原数组改变的

 // 1 pop: 删除并返回数组的最后一个元素
    var arr = [1, 2, 3, 4, 5, 6],
      ret = null;
    ret = arr.pop();
    console.log(arr, ret);

    /**
     * 结果: 改变了元数组
     * arr -> [1, 2, 3, 4, 5, 6]
     * ret -> 6
     */
 // 2. push: 向数组的末尾添加一个或多个元素,并返回新的长度
    var arr = [1, 2];
    var ret = null;
    ret = arr.push(9);
    console.log(arr, ret)

    /**
     * 结果改变了
     * arr -> [1, 2, 9]
     * ret -> 7
     */
 // 3. reverse: 颠倒数组中元素的顺序
 var arr = [1, 2, 4];
 var ret = [];
 ret = arr.reverse();
 /*
 *结果:改变了原数组
 arr -> [4, 2, 1]
 ret -> [4, 2, 1]
 */
 // 4 shift: 把数组的第一个元素从其中删除,并返回第一个元素的值
 var arr = [1, 2];
 ret = arr.shift();
 console.log(arr, ret);
 /*
 * 结果: 改变了元数组
     arr -> [2]
     ret -> 1
 */
 // sort : 对数组的元素进行排序
 var arr = [6, 5, 4, 3, 2, 1];
 var ret = null;
 ret = arr.sort(sortArr)
 console.log(arr, ret);
 /*
 * 结果: 改变了原数组
 arr -> [1, 2, 3, 4, 5, 6]
 ret -> [1, 2, 3, 4, 5, 6]
 */
 function sortArr (a, b) {
     return a - b;
 }
复制代码

image.png

image.png

image.png

image.png

image.png

image.png

15. ES6的新特性

  1. 不一样的变量声明:const和let
var x = '全局变量';
{
  let x = '局部变量';
  console.log(x); // 局部变量
}
console.log(x); // 全局变量
复制代码

let表示声明变量,而const表示声明常量,两者都为块级作用域;const 声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了

const a = 1
a = 0 //报错
复制代码

如果const的是一个对象,对象所包含的值是可以被修改的。抽象一点儿说,就是对象所指向的地址没有变就行:

const student = { name: 'cc' }

student.name = 'yy';// 不报错
student  = { name: 'yy' };// 报错
复制代码

有几个点需要注意:

  • let 关键词声明的变量不具备变量提升(hoisting)特性
  • let 和 const 声明只在最靠近的一个块中(花括号内)有效
  • 当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
  • const 在声明时必须被赋值
  1. 模板字符串
  • 基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
  • ES6反引号(``)直接搞定;
  1. 箭头函数(Arrow Functions)

ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体;

%% 箭头函数和普通函数的区别

普通函数下的this

    1. 普通函数下的this总是代表它的直接调用者,在默认情况下,this指向的是window
    1. 在严格模式下,没有直接调用者的函数中的this是undefined使用
    1. 绑定call和apply。bind,this指的是绑定的对象

箭头函数中的this

箭头函数没有this绑定,必须通过查找作用域链来决定其值,如果箭头函数被普通函数包含,则this绑定的值一定是最近一层的非箭头函数的this,否则,this的值会被设置为undefined,可以通过以下这种方式使用箭头函数,

var obj = {
    say: function () {
        setTimeout(() => {
            console.log(this); // 指向的是obj
        })
    }
}
复制代码

此时的this继承自obj,指的是定义它的对象obj,而不是windows

  1. 数组和对象的解构
对象
const student = {
    name: 'yanbo',
    age: 22,
    sex: '男'
}
// 数组
const student = ['Sam', 22, '男'];

ES5

const name = student.name

const age = student.age;
const sex = student.sex;

console.log(name + '----' + age + '-----' + sex);

ES6

const {name, age, sex} = student;
console.log(name + '------' + age + '------' + sex);

复制代码

ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。

  1. ES6中的类

ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。

class Student {
    constructor() {
        console.log('I am student');
    }
    study() {
        console.log('study')
    }
    static read () {
        console.log('reading Now');
    }
}

console.log(typeof Student); // function

let stu = new Student();

stu.study();
stu.read();
复制代码

类中的继承和超级

class Phone {
    constructor() {
    console.log('I am a phone')
}
class MI extends Phone {
    constructor() {
        super();
        console.log('I am a phone')
    }
}

let mi8 = new MI()
复制代码

extends 允许一个子类继承父类,需注意的事,子类的constructor函数中需要执行super()函数。 当然,你也可以在子类方法中调用父类的方法

7.一个盒子水平垂直居中有哪些方法?

四种居中方式:

  • 方案1:position 元素已知宽度 父元素设置为:position: relative; 子元素设置为:position: absolute; 距上50%,据左50%,然后减去元素自身宽度的距离就可以实现 示例 2:

image.png

image.png

image.png

image.png

image.png

image.png

image.png

var 和let const 的区别

解答: 

var 是ES5语法,let const 是ES6语法,var有变量提升

var和let是变量,可修改;const 是常量,不可修改

let const 有块级作用域,var没有 ​

// console.log(a); // undefined
  // var a = 200;
  // 等同于下面的代码
  let a;
  console.log(a);
  a = 200;

 for (var i = 0; i < 10; i++) {
    var j = i + 2;
  }
  console.log(i, j); // 10, 11 变量提升

// 块级作用域
 for (var i = 0; i < 10; i++) {
    var j = i + 2;
  }
  console.log(i, j); // 10, 11
复制代码

​​ 块级作用域的时候会报错

typeof返回哪些类型

  1. undefined string number boolean symbol

  2. object (注意:typeof null === 'object')

  3. function typeof Function  // function 函数的意思

列举强制类型转换和隐式类型转换

强制: parseInt  parseFloat  toString等

隐式:if ,逻辑运算,== , +拼接字符串

手写深度比较,模拟lodash isEqual

​​

// 判断是否是对象或数组
function isObject(obj) {
  return typeof obj === 'object' && obj !== null
}
// 全相等(深度)
function isEqual(obj1, obj2) {
  if (!isObject(obj1) || !isObject(obj2)) {
    // 值类型(注意,参与 equal 的一般不会是函数)
    return obj1 === obj2
  }
  if (obj1 === obj2) {
    return true
  }
  // 两个都是对象或数组,而且不相等
  // 1. 先取出 obj1 和 obj2 的 keys ,比较个数
  const obj1Keys = Object.keys(obj1)
  const obj2Keys = Object.keys(obj2)
  if (obj1Keys.length !== obj2Keys.length) {
    return false
  }
  // 2. 以 obj1 为基准,和 obj2 一次递归比较
  for (let key in obj1) {
    // 比较当前 key 的 val —— 递归!!!
    const res = isEqual(obj1[key], obj2[key])
    if (!res) {
      return false
    }
  }
  // 3. 全相等
  return true
}


// 测试
const obj1 = {
  a: 100,
  b: {
    x: 100,
    y: 200
  }
}

const obj2 = {
  a: 100,
  b: {
    x: 100,
    y: 200
  }
}
// console.log(obj1 === obj2);
console.log(isEqual(obj1, obj2));
复制代码

split() 和join()的区别

​​ split 字符串转成数组

join() 数组转成字符串 ​​​

数组slice和splice的区别

  • slice()方法可以从已有数组中返回选定的元素

语法:arrayObject(start, end) 参数:

start: (截取开始位置的索引,包含开始索引)必须,规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。(-1指的是最后一个元素,-2指倒数第二个元素,以此类推)。

end: (截取结束位置的索引,不包含结束索引)可选,规定从何处结束选取。该参数是数组片段结束处的数组下标,如果没有指定该参数,那么切分的数组包含从 start(开始处)到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。

返回值: 返回一个新的数组,包含从 start 到 end(不包含end的这个元素)的数组对象(arrayObject)中的元素。

注意

1、slice 方法并不会修改数组,而是会返回一个子数组。如果想删除一段元素,应该使用方法Array.splice()

2、可以使用负值从尾部选取元素。

3、如果 end未被规定,那么 slice() 方法会选取从 start到数组结尾的所有元素。

使用slice() 方法选取已有数组中的元素

var arr = ["大雪","小雪","霜降","立冬"]
console.log(arr.slice(1,3));
console.log(arr.slice(1));
console.log(arr.slice(-3,-1));
复制代码

image.png

  • splice() 方法可以用于插入、删除或是替换数组的元素。
arrayObject.splice(index,howmany,element1,.....,elementX)
复制代码

参数:

    index,必需,规定从何处添加或是删除元素。,该参数是开始插入和(或)删除的数组元素的下标,必须是数字。

    howmany,必需。规定应该删除多少元素。必须是数字也可以是0,如果没有规定此参数,则删除从index 开始到原数组结尾的所有元素。

    element1,可选,规定有添加到数组的新元素,从index所指的下标处开始插入。

    elementX,可选,可向数组添加若干元素。 返回值:

如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。

注意:

       1、splice() 方法可以删除从 index 处开始的0或多个元素,并且用参数列表声明中声明的一个或是多个值来代替或修改那些被删除的元素。

       2、splice() 方法和 slice() 方法的作用是不同的,splice() 方法会直接对数组进行修改。

实例:

 删除从 index 2  开始的三个元素,并添加一个新元素来替代被删除的元素

var arr=["大雪","小雪","霜降","立冬","寒露"]
arr.splice(2,3,"秋分");
console.log(arr);
复制代码

image.png

var arr = new Array(5)
arr[0] = "大雪"
arr[1] = "小雪"
arr[2] = "霜降"
arr[3] = "立冬"
arr[4] = "寒露"
arr.splice(4,0,"白露");
console.log(arr);
复制代码

image.png

[10, 20, 30].map(parseInt)的结果

const res = [10, 20, 30].map(parseInt)
console.log(res);
相当于
const res = [10, 20, 30].map((num, index) => {
    return parseInt(num, index);
})
复制代码

结果

image.png

parseInt的用法

parseInt() 函数可解析一个字符串,并返回一个整数。

语法: parseInt(string, radix)

image.png

parseInt(string, radix) 当参数radix的值为 0,或没有设置该参数时,parseInt()会根据 string来判断数字的基数。

返回值

返回解析后的数字。

提示和注释

  • 注释:只有字符串中的第一个数字会被返回。
  • 注释:开头和结尾的空格是允许的。
  • 提示:如果字符串的第一个字符不能被转换为数字,那么 parseFloat() 会返回 NaN。
arseInt("10" );   //返回 10 parseInt("19" ,10);  //返回 19 (10+9)parseInt("11" ,2);  //返回 3 (2+1)parseInt("17" ,8); //返回 15 (8+7)parseInt("1f" ,16);  //返回 31 (16+15) parseInt("010" );  //未定:返回 10 或 8

复制代码

get和post的区别

image.png

浏览器回退:GET在浏览器回退时是无害的,而POST会再次提交请求。

收藏书签:GET可以,而POST不能

浏览器缓存:GET请求会被浏览器主动cache,而POST不会,除非手动设置。

请求编码:GET请求只能进行url编码,而POST支持多种编码方式。

浏览历史:GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

参数个数:GET请求在URL中传送的参数是有长度限制的,而POST么有。

参数数据类型:GET只接受ASCII字符,而POST没有限制。

安全:GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

传递方式:GET参数通过URL传递,POST放在Request body中。

请求限制:大多数浏览器通常都会限制url长度在2K个字节,而大多数服务器最多处理64K大小的url。

如果阻止事件冒泡和默认行为

1.阻止事件冒泡,使成为捕获型事件触发机制.

function stopBubble(e) {
//如果提供了事件对象,则这是一个非IE浏览器
if ( e && e.stopPropagation )
    //因此它支持W3C的stopPropagation()方法
    e.stopPropagation();
else
    //否则,我们需要使用IE的方式来取消事件冒泡
    window.event.cancelBubble = true;
}
复制代码

2.当按键后,不希望按键继续传递给如HTML文本框对象时,可以取消返回值.即停止默认事件默认行为.

//阻止浏览器的默认行为
function stopDefault( e ) {
    //阻止默认浏览器动作(W3C)
    if ( e && e.preventDefault )
        e.preventDefault();
    //IE中阻止函数器默认动作的方式
    else
        window.event.returnValue = false;
    return false;
}
复制代码

那么通过下面的一段代码我们来看下函数一的效果:

<!DOCTYPE html>
<html>

<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>效果测试</title>
<script language="javascript" type="text/javascript" src="jquery-1.4.2.js"></script>
<script language="javascript" type="text/javascript">
$(document).ready(function()
{
$('div.c1').click(function(e){alert('单击了div');});
$('div.c2').click(function(e){alert('单击了div');stopBubble(e);});
$(document).click(function(e){alert('单击了document');});
$('#txt1').val('123');
$('#txt1').click(function(e){stopBubble(e);});
$('#txt1').keydown(function(e){stopDefault(e);alert('你按下了键值'+e.keyCode); });
})

function stopBubble(e) {
//如果提供了事件对象,则这是一个非IE浏览器
    if ( e && e.stopPropagation )
    //因此它支持W3C的stopPropagation()方法
    e.stopPropagation();
     else
    //否则,我们需要使用IE的方式来取消事件冒泡
    window.event.cancelBubble = true;
}
//阻止浏览器的默认行为
function stopDefault( e ) {
    //阻止默认浏览器动作(W3C)
    if ( e && e.preventDefault )
        e.preventDefault();
    //IE中阻止函数器默认动作的方式
    else
        window.event.returnValue = false;
    return false;
}
</script>
<style type="text/css">
body{
font-size:14px;
    }
}
.c1{
    font-family:"Arial Unicode MS"
    }
.c2{
    font-family:helvetica,simsun,arial,clean
    }
</style>
</head>

<body>

<div class="c1">测试的文字,这里是样式C1,单击以冒泡的形式触发事件.</div><hr/>

<div class="c2">测试的文字,这里是样式C2,单击以捕获的形式触发事件.</div><hr/>

<div><input id="txt1" name="Text1" type="text" /></div><hr/>

</body>
</html>

复制代码

查找,添加,删除,移动DOM的方法?

一、创建节点、追加节点

1、createElement(标签名)创建一个元素节点(具体的一个元素)。
2、appendChild(节点)追加一个节点。
3、createTextNode(节点文本内容)创建一个文本节点

var oDiv = document.createElement("div");//创建一个div元素,因为是document对象的方法。
var oDivText = document.createTextNode("666");//创建一个文本节点内容是“666”,因为是document对象的方法。
oDiv.appendChild(oDivText);//父级.appendChild(子节点);在div元素中添加“666”
document.body.appendChild(oDiv);//父级.appendChild(子节点);;document.body是指向<body>元素
document.documentElement.appendChild(createNode);//父级.appendChild(子节点);;document.documentElement是指向<html>元素
复制代码

二、插入节点

var oDiv = document.createElement("div");//创建一个div元素,因为是document对象的方法。
var oDiv1 = document.getElementById("div1");
document.body.insertBefore(oDiv,oDiv1);//在oDiv1节点前插入新创建的元素节点
ul.appendChild(ul.firstChild); //把ul的第一个元素节点移到ul子节点的末尾
复制代码

三、删除、移除节点

1、removeChild(节点) 删除一个节点,用于移除删除一个参数(节点)。其返回的被移除的节点,被移除的节点仍在文档中,只是文档中已没有其位置了。

var removeChild = document.body.removeChild(div1);//移除document对象的方法div1

复制代码

四、替换节点

1、replaceChild(插入的节点,被替换的节点) ,用于替换节点,接受两个参数,第一参数是要插入的节点,第二个是要被替换的节点。返回的是被替换的节点。

var replaceChild= document.body.replaceChild(div1,div2); //将div1替换div2
复制代码

五、查找节点 1、childNodes 包含文本节点和元素节点的子节点。

for (var i = 0; i < oList.childNodes.length; i++) {//oList是做的ul的对象。
//nodeType是节点的类型,利用nodeType来判断节点类型,再去控制子节点
//nodeType==1 是元素节点
//nodeType==3 是文本节点
  if (oList.childNodes[i].nodeType == 1) {//查找到oList内的元素节点
    console.log(oList.childNodes[i]);//在控制器日志中显示找到的元素节点
  }
}

复制代码

A、children也可以获取子节点,而且兼容各种浏览器。包括IE6-8

B、parentNode:获取父节点

var oList = document.getElementById('list');//oList是做的ul的对象
var oChild=document.getElementById('child');//oChild是做的ul中的一个li的对象
//通过子节点查找父节点//parentNode:获取父节点
console.log(oChild.parentNode);//在控制器日志中显示父节点
console.log(oList.children);//在控制器日志中显示oList子节点
console.log(children.length)//子节点的个数
复制代码

3、

A、firstChild ; firstElementChild查找第一个子节点。此存在浏览器兼容问题:firstChild是IE兼容,firstElementChild是非IE兼容。

//查找第一个子节点的封装函数
function firstChild(ele) {
  if (ele.firstElementChild) {//如果该条件是true则在该浏览器(IE或非IE)中兼容
    return ele.firstElementChild;
  } else {
    return ele.firstChild;
  }
}
firstChild(oList).style.background = 'red';//将获得的节点的背景变成红色
复制代码

B、lastChild ; lastElementChild查找最后一个子节点。此存在浏览器兼容问题:lastChild 是IE兼容,lastElementChild是非IE兼容。

//查找最后一个子节点的封装函数
function lastChild(ele) {
  if (ele.lastElementChild) {//如果该条件是true则在该浏览器(IE或非IE)中兼容
    return ele.lastElementChild;
  } else {
    return ele.lastChild;
  }
}
lastChild(oList).style.background = 'red';//将获得的节点的背景变成红色
复制代码

C、nextSibling ; nextElementSibling查找下一个兄弟节点。也是存在兼容性问题

//查找下一个兄弟节点的封装函数
function nextSibling(ele) {
  if (ele.nextElementSibling) {
    return ele.nextElementSibling;
  } else {
    return ele.nextSibling;
  }
}
nextSibling(oMid).style.background = 'red';

复制代码

D、previousSibling ; previousElementSibling查找上一个兄弟节点。也是存在兼容性问题

//查找上一个兄弟节点的封装函数
function previousSibling(ele) {
  if (ele.nextElementSibling) {
    return ele.previousElementSibling;
  } else {
    return ele.previousSibling;
  }
}
previousSibling(oMid).style.background = 'red';
复制代码

如何减少DOM操作

  • 减少DOM数量
  • 减少DOM操作
  • 批量处理DOM操作
  • 批量处理样式修改
  • 尽量不要使用tabel布局
  • 尽量不要使用css表达式
  • string用数组join
  • css选择符优化

1.减少DOM数量

  在HTML生成DOM树的时候,DOM数量越少,HTML渲染速度越快

2.减少DOM操作

  每次操作DOM,都会带来repaint和refolw

3.批量处理DOM操作:

  将元素移除DOM Tree,修改完后再放回去,因此只会调用一次repaint或者reflow

4.批量修改样式

  改变classname,或者用css(),原理和批量处理js一样

5.尽量不要使用tabel布局

  tabel中某个元素改变了,整个tabel就会reflow.

  如果非用不可,可以设置tabel-layout:auto或者tabel-layout:fixed,让tabel一行一行的渲染,限制渲染范围

6.尽量不要使用css表达式

  每计算一次就会触发reflow一次

7.string用数组join连接

  在js中使用“+”来拼接字符串效率比较低,因为每次运行都会开辟新的内存并生成新的字符串变量,然后将拼接的字符串赋值给新变量。使用数组的话效率就高一点

8.css选择符优化

  因为css是从右向左解析的,根据这个规则,尽量使右边的样式唯一

解释jsonp的原理,为何他不是真是的ajax

image.png image.png

document load 和ready的区别

image.png

== 和 === 的不同

image.png

函数声明和函数表达式的区别

image.png

// 函数声明
// const res = sum(10, 20);
// console.log(res); // 30
// function sum (x, y) {
//   return x + y;
// }
// 函数表达式
const res = sum(10 + 20);
console.log(res); // 会报错
const sum = (x, y) => {
  return x + y;
}
复制代码

函数表达式的会报错 image.png

new Object()和Object.create()的区别

image.png

const obj1 = {
  a: 10,
  b: 20,
  sum () {
    return this.a + this.b;
  }
}
const obj2 = new Object({
  a: 10,
  b: 20,
  sum () {
    return this.a + this.b;
  }
})

const obj3 = Object.create(null); // {}
const obj4 = new Object() // {}
const obj5 = Object.create({
  a: 10,
  b: 20,
  sum () {
    return this.a + this.b;
  }
})
const obj6 = Object.create(obj1);
const obj7 = new Object(obj1);

复制代码

image.png

image.png

image.png image.png

关于this的场景题

this只有在执行的时候才知道他的指向

const User = {
  count: 1,
  getCount: function () {
    console.log(this) // s执行func()的时候this指向的是window
    return this.count;
  }
}
console.log(User.getCount());
const func = User.getCount;
console.log(func())
复制代码

关于作用域和自由变量的场景题

let i;
for (i = 1; i<=3; i++) {
  setTimeout(function () {
    console.log(i); // 4
  }, 0)
}
复制代码

判断字符串以字母开头,后面数字下划线,长度6-30

image.png

image.png

关于作用域和自由变量的场景题

let a = 100;
function test () {
  alert(a);
  a = 10;
  alert(a);
}
test(); // 100 10
alert(a); // 10
复制代码

手写字符串trim保证浏览器的兼容性

image.png

获取多个数字中的最大值

image.png

image.png

JS如何实现继承

image.png

如何捕获JS程序中的异常

image.png

什么是JSON

image.png

获取当前页面的url参数

以这个url为例子:

 http://localhost:8080/reg?productId=27&productName=奶茶&price=15
复制代码
// 传统方式
function query(name) {
  const search = location.search.substr(1); // 类似于arr.slice()
  console.log(search);
  // search: 'a=10&b=20&c=30'
  // (^|&) 开头要么是开始,要么是&符号
  // ${name} 然后拼接name的值
  // [^&] 中括号一旦匹配了这个^ 表示排异的意思,不要有&符号 * 表示一个或者多个
  // (&|$) & 或者结尾
  const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
  const res = search.match(reg);
  console.log(res);
  if (res === null) {
    return null;
  }
  return res[2];
}
// http://localhost:63342/practice/aaa.html?a=10&b=20&c=30#hash
console.log(query('b'), '打印结果----');

复制代码

image.png\

正好是数组的第三项。没问题res[2] 还有一个简单的方法,但是兼容性不好
image.png

image.png

image.png

flattern拍平

// 第一种方法
function flat(arr) {
  // 验证arr中,还有没有身侧数组[1,2,[3, 4]];
  const isDeep = arr.some(item => item instanceof Array);
  if (!isDeep) {
    return arr // 已经是flatten [1, 2, 3, 4]
  }
  const res = Array.prototype.concat.apply([], arr);
  return flat(res);
}
const res = flat([1, 2, [3, 4, [6,7]]]);
console.log(res);
// 第二种方法

let array = [1, [2], [3, [4, [5]]]]
function flat(arr) {
  return arr.toString().split(',').map(val => +val) // +是为了将字符串转为数字
}
// 结果为 [1,2,3,4,5]

// 第三种方法



复制代码

reduce

reduce() 方法接收一个函数作为累加器`,reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(上一次回调的返回值),当前元素值,当前索引,原数组 

语法:arr.reduce(callback,[initialValue]) callback:函数中包含四个参数

  • previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))

  • currentValue (数组中当前被处理的元素)

  • index (当前元素在数组中的索引)

  • array (调用的数组)

  • initialValue (作为第一次调用 callback 的第一个参数。)

const arr = [1, 2, 3, 4, 5]
const sum = arr.reduce((pre, item) => {
    return pre + item
}, 0)
console.log(sum) // 15
复制代码

递归利用reduce处tree树形

var data = [{
            id: 1,
            name: "办公管理",
            pid: 0,
            children: [{
                    id: 2,
                    name: "请假申请",
                    pid: 1,
                    children: [
                        { id: 4, name: "请假记录", pid: 2 },
                    ],
                },
                { id: 3, name: "出差申请", pid: 1 },
            ]
        },
        {
            id: 5,
            name: "系统设置",
            pid: 0,
            children: [{
                id: 6,
                name: "权限管理",
                pid: 5,
                children: [
                    { id: 7, name: "用户角色", pid: 6 },
                    { id: 8, name: "菜单设置", pid: 6 },
                ]
            }, ]
        },
    ];
    const arr = data.reduce(function(pre,item){
        const callee = arguments.callee //将运行函数赋值给一个变量备用
        pre.push(item)
        if(item.children && item.children.length > 0) item.children.reduce(callee,pre); //判断当前参数中是否存在children,有则递归处理
        return pre;
    },[]).map((item) => {
        item.children = []
        return item
    })
    console.log(arr)
复制代码

数组去重

方法1.利用ES6中的Set方法去重 注:Set为ES6新增的一个对象,允许存储任何类型(原始值或引用值)的唯一值

1      let arr = [1,0,0,2,9,8,3,1];2           function unique(arr) {
3                 return Array.from(new Set(arr))
4           }
5           console.log(unique(arr));   // [1,0,2,9,8,3]  or6      console.log(...new Set(arr)); // [1,0,2,9,8,3]
复制代码

方法2. 利用indexOf
\

 var arr =[1,-5,-4,0,-4,7,7,3];
 function unique(arr){ 
 var arr1 = []; // 新建一个数组来存放arr中的值
 for(var i=0,len=arr.length;i<len;i++){ 
     if(arr1.indexOf(arr[i]) === -1){ 
       arr1.push(arr[i]); 
      }
     } 
        return arr1; 
     } 
11 console.log(unique(arr)); // 1, -5, -4, 0, 7, 3
复制代码

介绍一下, RAF requestAnimateFrame

前端性能如何优化? 一般从哪几个方面考虑

image.png


theme: awesome-green

1. 变量类型和计算

image.png

image.png

  // 值类型
  let a = 100
  let b = a;
  a = 200;
  console.log(a); // 200
  console.log(b); // 100
  
  // 引用类型
  let a = { age: 20};
  let b = a;
  b.age = 21;
  console.log(a.age); // 21
复制代码
文章分类
前端
文章标签