【24秋招冲刺】前端面经

作者碎碎念:

        经历过一场场面试,然后又一场场被挂,小编就算是个铁人也会流泪滴!!!继续受虐虐虐虐,在此记录面试的高频考点,巩固基础,屡败屡战!!!!(SOS什么毒鸡汤???!!!)


 BFC(1次)

定义:

MDN区块格式化上下文(Block Formatting Context,BFC)是 Web 页面的可视 CSS 渲染的一部分,是块级盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。

通俗来讲:BFC是一个独立的布局环境,可以理解为一个容器,在这个容器中按照一定规则进行物品摆放,并且不会影响其它环境中的物品。如果一个元素符合触发BFC的条件,则BFC中的元素布局不受外部影响。

解决布局问题:浮动、外边距重叠

构成BFC方法:

  • HTML根元素
  • float: left | right(除none)
  • position: absolute | fixed
  • display: inline-block | flex | inline-flex | grid | inline-grid | table-cell |table-caption | flow-root(专门用来创建BFC属性)
  • overflow: 除了visible
  • contain: layout | content | paint(现代浏览器支持)

 清除浮动

 浮动会造成父元素高度塌陷(没有设置height)

方法:BFC、clear: both、伪元素(不影响布局复杂度)

像素单位(1次)

px:绝对单位

em:相对单位,相对父元素的字体大小

rem:相对单位,相对根元素(html)的字体大小

  • vw/vh:视口宽高的百分比

CSS选择器以及优先级(2次)

选择器格式优先级权重
id#id100
.class010
伪类    注意区分li:last-child010
属性a[class="refValue"]010
元素div001
伪元素    注意区分 div::after001
相邻兄弟h1+p000
ul>li000
后代li a000
**]000

提示:内联样式的优先级是1000(比id还高) ,!important声明的样式的优先级最高

伪类和伪元素的区别(字节一面)

伪类

将特殊效果添加到特定选择器上,在已有元素上添加类别,不产生新元素 

a:hover {color: #FF00FF}
p:first-child {color: red}
p:nth-child(odd) {color: pink}

伪元素

在内容元素的前后插入额外元素/样式,插入的元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们,因此,称为“伪”元素。

p::before {content:"第一章:";}
p::after {content:"Hot!";}
p::first-line {background:red;}
p::first-letter {font-size:30px;}

H5C3新特性

H5

  • 语义化标签:header footer article section main nav

好处:1)便于搜索引擎爬虫准确抓取页面,提高页面在搜索结果中的排名。2)增加代码易读性,便于代码维护和更新

  • 多媒体:video audio
  • canvas绘图(画布)
  • 本地存储:localStorage sessionStorage cookie

扩展:三者区别

  • 地理位置的API
  • 拖放

CSS3 

  •  弹性布局: display: flex 重要
  • 网格布局: display: grid
  • 渐变、阴影、过渡、动画
  • 媒体查询:依据设备的屏幕尺寸、分辨率 使用不同的样式规则,实现响应式设计
  • 自定义字体:@font-face

如何画一个三角形(基础) 

 把一个元素的宽高设置为0,然后给不同方向的边框设置颜色和宽度,接着想要哪个方向的三角就留下颜色,其余改为透明

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .traingle {
      width: 0;
      height: 0;
      border: 10px solid transparent;
      border-top-color: pink;
    }
  </style>
</head>

<body>
  <!-- 画一个三角形 -->
  <div class="traingle"></div>
</body>
</html>

自适应布局和响应式布局(1次) 

自适应:随着视口变化,元素进行放大缩小 (淘宝无限适配方案:可以动态调整根元素font-size的值 -> rem)

响应式:通过媒体查询来设置特定屏幕尺寸的样式规则(750px 1080px 2000px)

<style>
    /* 小屏幕设备样式 */
   @media (max-width: 767px) {
            body {
                background-color: lightblue;
                font-size: 14px;
            }

            .container {
                padding: 10px;
            }
        }

        /* 大屏幕设备样式 */
        @media (min-width: 768px) {
            body {
                background-color: lightgreen;
                font-size: 16px;
            }

            .container {
                padding: 20px;
                max-width: 1200px;
                margin: 0 auto;
            }
        }
</style>

如何实现垂直居中对齐(5次,高频,唯品会一面,字节一面

<!DOCTYPE html>
<html lang="zh">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    /* 绝对定位 1.*/
    /* #parent {
      position: relative;
      width: 500px;
      height: 500px;
      background-color: aqua;
    }

    #center {
      position: absolute;
      width: 100px;
      height: 100px;
      line-height: 100px;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      text-align: center;
      background-color: brown;
    } */

    /* 绝对定位 2.*/
    /* #parent {
      position: relative;
      width: 500px;
      height: 500px;
      background-color: aqua;
    }

    #center {
      position: absolute;
      width: 100px;
      height: 100px;
      line-height: 100px;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      margin: auto;
      text-align: center;
      background-color: brown;
    } */

    /* flex布局 */
    /* #parent {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 500px;
      height: 500px;
      background-color: aqua;
    }

    #center {
      width: 100px;
      height: 100px;
      line-height: 100px;
      text-align: center;
      background-color: brown;
    } */

    /* flex中用margin布局 */
    /* #parent {
      display: flex;
      place-items: center;
      width: 500px;
      height: 500px;
      background-color: aqua;
    } */

    /* #center {
      width: 100px;
      height: 100px;
      margin:auto;
      line-height: 100px;
      text-align: center;
      background-color: brown;
    } */

    /*grid布局*/
    /* #parent {
      display: grid;
      place-items: center;
      width: 500px;
      height: 500px;
      background-color: aqua;
    } 

    #center {
      width: 100px;
      height: 100px;
      background-color: brown;
    }*/
  </style>
</head>

<body>
  <!-- 实现三栏布局 -->
  <div id="parent">
    <div id="center">center</div>
  </div>
</body>

</html>

效果图如下:

 延伸:说一下flex布局(6次,超高频)

说一下flex怎么布局的,然后说一下常用属性的用法,让面试官觉得你会;

如何实现三栏布局(笔试3次,面试3次,CSS基础)

 flex、绝对定位、浮动、双飞翼、圣杯(后两者布局还没理解到)

<!DOCTYPE html>
<html lang="zh">
 
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    /* flex布局 */
    /* #parent {
      width: 1000px;
      height: 100px;
      display: flex;
    }
 
    #left {
      width: 100px;
      text-align: center;
      background-color: palegoldenrod;
    }
 
    #right {
      width: 200px;
      text-align: center;
      background-color: blue;
    }
 
    #center {
      flex: 1;
      text-align: center;
      background-color: brown;
    } */
 
    /* 绝对定位 */
    #parent {
      position: relative;
      width: 1000px;
      height: 100px;
    }
    #left {
      position: absolute;
      height: 100px;
      width: 100px;
      text-align: center;
      background-color: palegoldenrod;
    }
    #right {
      position: absolute;
      width: 200px;
      height: 100px;
      top: 0;
      right: 0;
      text-align: center;
      background-color: blue;
    }
    #center {
      /* position: absolute; 
      left: 100px;
      right: 200px;*/
      height: 100%;
      margin-left: 100px;
      margin-right: 200px;
      text-align: center;
      background-color: brown;
    }
 
    /* 浮动:注意中间元素center要在末尾 */
    /* #parent {
      width: 1000px;
      height: 100px;
    }
    #left {
      float: left;
      height: 100px;
      width: 100px;
      text-align: center;
      background-color: palegoldenrod;
    }
    #right {
      float: right;
      width: 200px;
      height: 100px;
      text-align: center;
      background-color: blue;
    }
    #center {
      height: 100%;
      margin-left: 100px;
      margin-right: 200px;
      text-align: center;
      background-color: brown;
    } */
  </style>
</head>
<body>
    <!-- 实现三栏布局 -->
    <div id="parent">
      <div id="left">left</div>
      <div id="center">center</div>
      <div id="right">right</div>
    </div>
</body>
 
</html>

效果图都是这样,最好自己动手实操,加深印象~

盒子模型(4次)

width和height属性的范围:

  • box-sizing: content-box,标准盒子模型:content + padding + border + margin
  • box-sizing: border-box,IE(怪异)盒子模型: margin + content (content + padding + border)

面向对象(纪传体)vs面向过程(编年体)

面向过程(ex:C语言):负责完成某个具体任务的代码(可以理解为函数),核心:将要完成的事情拆分成一个个步骤,依次完成 

面向对象(Object Oriented Programming  ex:JAVA):以对象为核心,考虑各个对象有什么性质、能做什么事情;把事务先分解到对象身上,描述各个对象的作用,然后才是它们之间的交互

提取性质定义类创建对象(类是创建对象的模板,对象是类的实例),用对象绑定相关属性(放在类里面的变量)有利于让程序逻辑更加清晰,数据流动更加清晰

用对象绑定对象能实现的方法(放在类里面的函数)

结合方法和属性,能更优雅地处理逻辑

面向对象特性:

封装:写类的人将内部实现细节隐藏起来,使用类的人只通过外部接口(方法)访问和使用

继承:面向对象编程允许创建有层次的类 

多态:同样的接口,因为对象具体类不同而有不同的表现

    new操作符做了什么

    1. 创建了一个空对象
    2. 将空对象的原型指向构造函数的原型
    3. 将空对象作为构造函数的上下文(改变this指向)
    4. 对构造函数有返回值的处理判断
    function Fun(age, name){
        this.age = age;
        this.name = name;
    }
    
    function create(fn, ...args){
        // 1.创建了一个空对象
        let obj = {};
        // 2.将空对象的原型指向构造函数的原型
        Object.setPrototypeOf(obj, fn.prototype);
        // 3.将空对象作为构造函数的上下文(改变this指向)
        let res = fn.apply(obj, args)
        // 4.对构造函数有返回值的处理判断
        return res instanceof Object ? res : obj;
    }
    
    
    console.log(create(Fun, 18, '王五'));

    原型和原型链(2次,重点!)

    原型:每一个函数都有prototype显式属性,称为原型(原型对象),原型有属性和方法与实例对象共享,可以继承

    原型链:对象都有隐式__proto__属性,指向它的原型对象,原型对象也是对象,也有__proto__属性,指向原型对象的原型对象,这样一层一层形成的链式结构称原型链(最顶层为null),原型链是通过对象间的原型链接形成的属性查找路径

    画图加深记忆(理解理解才能吸收!!)

    继承 

    原型链继承:父类的实例赋值给子类的原型

     优:子类可以共享父类的方法

    劣:不能给父类传参、父类的引用数据类型可能被共享

    <script>
      function Person(name) {
        this.name = name
      }
    
      Person.prototype.say = function () {
        console.log(123);
      }
    
      function Son(age) {
        this.age = age
      }
    
      Son.prototype = new Person()
      console.log(new Son())
    </script>

    构造函数继承:在子类构造函数中调用父类构造函数

    优:可以给父类传递属性

    劣:无法共享父类方法

    <script>
      function Person(name) {
        this.name = name
      }
    
      Person.prototype.say = function () {
        console.log(123);
      }
    
      function Son(age) {
        Person.call(this, age)
        this.age = age
      }
    
      console.log(new Son(18)) // {name:18, age:18}
    </script>

    组合式继承:结合原型链继承和构造函数继承的优点

    劣:复杂度增加、增加了父类对象的创建

    <script>
      function Person(name) {
        this.name = name
      }
    
      Person.prototype.say = function () {
        console.log(123);
      }
    
      function Son(age) {
        Person.call(this, age) // 1
        this.age = age
      }
    
      Son.prototype = new Person() // 2
      console.log(new Son(18)) // {name:18, age:18}
    </script>

     class继承:本质基于原型链和构造函数继承

    劣:低版本浏览器不支持

    <script>
      // 父类
      class Person {
        constructor(name) {
          this.name = name
        }
        say() {
          console.log(123)
        }
      }
    
      // 子类
      class Son extends Person {
        constructor(name, age) {
          super(name)
          this.age = age
        }
        write() {
          console.log(456)
        }
      }
      const son = new Son('fu', 'zi')
      console.log(son) // {name: 'fu', age: 'zi'}
      son.say() // 123
      son.write() // 456
    </script>

    ES6新特性(n次,必背必背!!!)

    1、Symbol、BigInt数据类型

    2、模版字符串:`${}`

    3、新增let、const关键字

    var、let、const区别:

    • var没有块级作用域,let、const有(块级作用域 : { } )
    • var存在变量提升(变量只能在声明之后使用,否在会报错),let(暂时性死区)、const没有
    • var可以重复声明,let、const不可以
    • var、let不用设置初始值,const必须设置(初始化)
    • let创建的变量可以重新赋值,const不可以

    4、箭头函数()=>{}

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

    • 箭头函数更简洁
    • 箭头函数不绑定this,会捕获其所在上下文的this,作为自己的this。箭头函数中this的指向在它在定义时已经确定了,不会改变。普通函数的this指向调用者(谁调用就指向谁)。
    • 箭头函数不能作为构造函数使用(如上因为不能绑定this)
    • call()、apply()、bind()等方法不能改变箭头函数中this的指向
    • 箭头函数没有prototype,当然就不存在原型;没有自己的arguments对象
    • 箭头函数不能用作Generator函数,不能使用yeild关键字(作者自己标红,还不懂呜呜呜呜)

    5、扩展运算符

    6、class类

    7、解构赋值

    如何在不添加第三个变量的情况下交换两个变量的值?

    let a = 1;
    let b = 2;
    [a, b] = [b, a]
    
    console.log(a, b)    // 2 1

    8、新增Map、Set数据结构

    Map、Set、json区别:

    • JSON 对象在 JavaScript 中以键值对的形式表示,但键必须是字符串,且整个 JSON 对象必须是一个字符串。
    • Map存储键值对的集合,键和值都能是任意类型,并且键具有唯一性,可迭代(用于 for...of 循环)。
    • Set存储唯一的值的集合,可迭代(用于 for...of 循环)。

    Map和weakMap区别:待续...

    Set和weakSet区别:待续...

    9、默认参数

    即给函数设置默认参数

    function greet(name, message = "Hello") {
      console.log(`${message}, ${name}!`);
    }

    10、Promise:处理异步操作的对象

    11、async/await

    扩展:promise和async/await区别

    async/await基于promise的语法糖(语法糖:简化,语法盐:复杂化)

    12、模块导入导出:import export

    13、??:空值合并运算符

    • 若左侧操作数 null 或者 undefined 时,返回侧操作数
    • 若左侧操作数不为 null 或 undefined,则返回侧操作数

    14、?. :可选链操作符

    const uer = {
        name: 'dili',
        address:{
            street: 'Main Street'
        }
    }
    
    console.log(user.address?.street)
    // 若address属性存在,则继续访问其street属性并输出值
    // 若address属性不存在,则返回undefined避免引用错误

     15、replaceAll()(String的api)

    let str = 'hello world, hello agin'
    
    console.log(str.replaceAll('h', 'H')) // Hello world, Hello agin

    16、顶层await 

    <script type="module">
      // 模拟一个异步操作
      function fetchData() {
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve('Data fetched successfully');
          }, 1000);
        });
      }
    
      // 顶层使用 await
      const data = await fetchData();
      console.log(data);
    </script>

     JS的组成部分

    ECMAScript、DOM(文档对象模型)、BOM(浏览器对象模型)

    async和defer区别

    作用:控制外部脚本的加载和执行顺序

    • async:异步加载脚本,脚本文件会并行下载,不会阻塞页面的解析,下载完就直接执行
    • defer:延迟加载脚本,脚本文件会并行下载,在文档完全解析之后,按顺序执行

     javascript执行过程

      预编译:代码执行前,先进行编译

     JS代码执行过程:

    • 通篇检查代码是否有语法错误
    • 预编译
    • 解释一行执行一行

     变量声明和函数声明

    •  var存在变量提升(声明提升,赋值不会提升)
    • 函数声明的提升是提升整个函数,包括里面的代码

     注意:如果给一个没有进行声明的变量赋值,那么这个变量默认为全局变量

    函数执行期上下文

    活跃对象AO:activation object

    1. 创建AO对象 AO = {}
    2. 找函数的形参和变量声明,并赋值为undefined
    3. 把实参赋值给形参
    4. 找函数声明,并赋值函数体
    5. 执行代码 在预编译阶段执行过的代码,在函数执行期不再执行

    遍历数组的方式有哪些?(1次) 

    map forEach filter reduce some/every find/findIndex flatMap

    操作数组常用的方法有哪些?哪些方法会改变原数组?

     MDN:面向开发者的 Web 技术 | MDN

    不改变原数组,方法会返回一个新的数组或某个结果,不会修改原来数组的内容

    slice、reduce、filter、map

    改变原数组

     push、pop、uhshift(+)、shift(-)、splice、sort、reverse

     深浅拷贝(2次)

    • 浅拷贝:只复制对象/数组的一层属性/元素,如果对象/数组的属性/元素是基本数据类型复制其;但如果属性/元素是引用数据类型,则只会复制其引用,(即新对象/数组和原对象/数组的该属性/元素会指向同一个内存地址)

    实现方式:Object.assign()、展开运算符

    • 深拷贝:递归地复制对象/数组的所有属性,包括嵌套的对象和数组,创建一个完全独立的新对象/数组,新对象/数组和原对象/数组在内存中占用不同的地址,修改新对象/数组不会影响原对象/数组,反之亦然

    实现方式:

    1、JSON的序列化+反序列化(JSON.parse(JSON.stringfy()))

    缺点:

    • 不能识别BigInt类型
    • 不能拷贝undefined、sysmbol、函数类型的值
    • 不能处理循环引用(对象的属性直接或间接地引用了对象本身)

    2、手搓递归

    <script>
      function deepCopy(obj) {
        if (typeof obj !== 'object' || obj === null) {
          return obj
        }
    
        let copy = Array.isArray(obj) ? [] : {}
        for (let key in obj) {
          copy[key] = deepCopy(obj[key])
        }
    
        return copy
      }
    
      let obj = { a: 1, b: { c: 2 } }
      let copy = deepCopy(obj)
      copy.b.c = 4
      console.log(obj, copy)
    // { a: 1, b: { c: 2 } }  { a: 1, b: { c: 4 } }
    </script>

     Promise

    Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,改善了回调地狱

    如何捕获Promise中的错误信息(1次)

    try/catch,Promise.then(,reject), Promise.catch(),Promise.finally()(无论成功失败都会执行)

    Promise实例的三个状态(1次)

    • Pending(进行中)
    • Fulfilled(已完成)
    • Rejected(已拒绝)

    两个过程(pending起手):状态凝固

    • pending -> fulfilled : Resolved(已完成)
    • pending -> rejected:Rejected(已拒绝)

    注意:一旦从进行状态变成为其他状态就永远不能更改状态了。

    处理成功、失败 

     .then() 处理成功的情况 .then(onResolve,onRejected) 接受两个函数,第一个处理成功,第二个处理失败

    .catch() 处理失败的情况         本质:.then(null,onRejected)语法糖

    ***Promise.all()和.race()(1次 美团)

     .all()接受一个数组,返回一个新的promise,只有所有的promise都成功才会成功,如果有一个失败,那么立刻返回失败

    场景题:我要上传多张图片,但是我要等所有图片上传成功后,再去调提交,应该怎么做?

    .race()接受一个数组,返回一个新的promise,只要有一个promsie完成(成功/失败),就立刻决定整个promsie的结果

    ***原理

    1. 初始化promise:创建promise对象,构造函数会立刻调用一个函数,该函数有两个参数resolve(函数,把promise状态从pending -> fullfilled),reject((函数,把promise状态从pending -> rejected))
    2.  执行异步操作:在执行函数的时候,它一般是个异步任务(发送请求),异步任务的结果决定了promise的状态(任务成功则调用resolve函数,失败则调用reject函数,改变promise状态),一旦执行状态不可变

     如何实现Promis功能(要看Promis源码)(1次)

    this/call/apply/bind区别(4次,背背背!!!)

    这三个方法都显式指定调用函数的 this 指向

    • call,第一个参数this绑定的对象,其余参数需要依次列举出来,立即执行
    • apply,第一个参数this绑定的对象,第二个传参数数组,立即执行
    • bind,第一个参数this绑定的对象,仅定义(创建新函数)未执行
    // bind
    
    <script>
      function say(arg, msg) {
        return `${arg}, ${this.name}, ${msg}`
      }
      const obj = { name: 'dili' }
      const newSay = say.bind(obj, 'Hello')
      console.log(newSay('I\'m liuyifei'))
    </script>

    this指向

    全局this --> window       

    普通函数中this  --> 谁调用指向谁(默认window)       

    对象中this --> 函数作为对象的方法被调用,this指向调用该方法的对象

    构造函数this --> 新创建的实例对象

    箭头函数this --> 外层普通函数/外层作用域的this

    定时器中this -> window(定义在window对象下)

    事件循环(必要)

    JS是单线程的(why?设计为多线程如果同时添加删除同一DOM节点会出问题),为了防止阻塞,将代码分为同步and异步

    常见宏任务:script(代码块),setTimeout/setInterval,setImmediate定时器、事件、ajax

    常见微任务:Promise.then()/catch(),ASync/Await,Object.observe,process.nextTick(node)

     执行顺序:执行栈中的同步代码 -> 微任务队列(直到没有微任务) -> 宏任务队列

     说完概念面试官一般会让你做一道关于时间循环的题(穿插seetTimeout、Promise),我的建议是多看多练,就会对事件循环理解更深刻。

    先自己做一下哦~

    setTimeout(() => {
      console.log('setTimeout');
    
      Promise.resolve().then(() => {
        console.log('setPro1');
      }).then(() => {
        console.log('setPro2');
      })
    }, 0);
    
    setTimeout(() => {
      console.log('set');
    }, 0)
    
    Promise.resolve().then(() => {
      console.log('then1');
    }).then(() => {
      console.log('then2');
    }).then(() => {
      console.log('then3');
    })
    

     公布答案啦~

     下面出一个纯Promise的,很绕很绕,直接晕厥

    Promise.resolve().then(() => {
          console.log(0);
          return Promise.resolve(4);
    }).then(res => {
          console.log(res);
    })
    Promise.resolve().then(() => {
          console.log(1);
    }).then(() => {
          console.log(2);
    }).then(() => {
          console.log(3);
    }).then(() => {
          console.log(5);
    })

    闭包(3次)

    MDN:闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合。

    内部函数可以访问外部函数作用域且被外部函数返回就形成闭包,闭包可以使变量私有(不会造成变量污染)

    经典面试题:循环中使用闭包解决 var 定义函数的问题

    for (var i = 1; i <= 5; i++) {
        (function(j) {
            setTimeout(function timer() {
            console.log(j)
        }, j * 1000)
      })(i)
    }

    首先使用了立即执行函数(5次)将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变(j取到的值都是对应循环中的i值),当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的。

    弊端以及注意点:

    • 避免内存泄漏:确保在不需要闭包时,手动释放捕获的变量。

    • 优化性能:避免在嵌套很深的函数中使用闭包,减少作用域链的长度。

    • 保持代码清晰:尽量避免复杂的闭包逻辑,确保代码易于理解和维护。

    注意:内存泄漏可能会延伸垃圾回收机制(V8)

    浏览器的跨域问题

    跨域问题其实就是浏览器的同源策略造成的。同源策略是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:协议域名、端口号必须一致。

    如何解决跨域问题

     跨域问题涉及后端,建议找视频看看,文字内容比较抽象实践一下印象更深;

    (1)CORS(跨域资源共享),CORS需要浏览器和服务器同时支持,整个CORS过程都是浏览器完成的,无需用户参与。因此实现CORS的关键就是服务器,只要服务器实现了CORS请求,就可以跨源通信了。

    浏览器将CORS分为简单请求非简单请求

    简单请求(不会触发预检请求OPTIONS):请求方法:HEAD/GET/POST,请求头信息默认不改变

    浏览器会直接发出CORS请求,它会在请求的头信息中增加一个Origin(协议+域名+端口)字段, 如果Orign指定的域名在许可范围之内,服务器返回的响应头字段中至少有Access-Control-Allow-Origin(和Origin字段存储的数据相等)

    非简单请求:简单请求的要求之外的情况,非简单请求的CORS请求会在正式通信之前进行一次HTTP查询请求,称为预检请求。预检请求使用的请求方法是OPTIONS,他的头信息中的关键字段有OriginAccess-Control-Request-Method,Access-Control-Request-Headers,服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进行判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求,如果没有,就是不同意这个预检请求,就会报错。

    (2)nginx反向代理接口跨域;

    (3)JSONP(已过时):利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET(只能是GET)请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

    XSS攻击(3次)

    定义:跨站脚本注入攻击,攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。

    本质:网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。

    攻击类型:

    • 存储型:攻击者将恶意脚本存储在服务端数据库中,当有请求发送过来,跟随响应数据一起返回,浏览器解析并执行,完成攻击
    • DOM型:通过修改页面DOM节点形成xss
    • 反射型:攻击者诱导用户访问带有恶意代码的URL,服务端处理请求并返回带有恶意代码的数据,浏览器把这段数据当脚本解析并执行,完成攻击

     如何预防?(2次,待更新...)

    1.对用户输入进行严格验证

    明确规定用户输入的格式、长度和允许的字符范围。(脚本通常需要很长的字段)

    2.过滤危险字符

    对于用户输入的内容,过滤掉可能会被用于 XSS 攻击的危险字符,如 <>& 等

    3.设置HTTP头Content-Security-Policy(CSP)

    Content-Security-Policy: default-src'self'; script-src'self' example.com

    4.对敏感的 Cookie 设置 HttpOnly 标志,这样javascript脚本就无法访问(服务端)

    setcookie('session_id', '123456', time() + 3600, '/', '', false, true);

    什么是CSRF(Cross-site request )攻击

    定义:跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求(和被攻击网站同源)。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。

    本质:利用 cookie 会在同源请求中携带发送给服务器的特点(服务器根据域名判断),以此来实现用户的冒充。

    常见攻击类型:

    • GET型:ex: 在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提交。
    • POST型:ex: 构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。
    • 链接型:ex: 在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。

    当输入一个网址按下Enter键会发生什么(美团一面,2次)

     重排一定会触发重绘,重绘不一定触发重排

    布局没有发生变化,跳过layout+layer阶段,直接进行paint(绘制)阶段

    触发回流(重排):

    • 首次渲染页面
    • 添加/删除元素
    • 改变元素大小/位置/内容、字体大小
    • 调整浏览器窗口大小
    • 查询某些属性或调用某些方法(clientWidth/clientLeft/clientHeight..., getcomputerdStyle(),getBoundingClientReact())

    如何避免回流:避免频繁操作样式、DOM(脱离文档流,隐藏修改再显示)、transform/opacity/filters/will-change不会引起回流重绘

    触发重绘:没有出现几何图形的变化的CSS样式

    浏览器缓存机制(百度一面,浏览器原理必需知道,2次)

    我将缓存机制分为四种类型

    浏览器首次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header(响应头),以供下次加载时对比使用;

    • cache-control: max-age;请求数据时,响应头中的cache-control字段,表示有效时间内的重复请求浏览器无需再次访问服务器,直接使用缓存资源;
    • expires:指定过期时间,同样是服务端的响应头字段,在有效的时间点之前,浏览器无需再次访问服务端,直接使用缓存资源;
    • Etag(服务端)/If-None-Match(客户端):首次请求资源时,服务端将缓存结果签名并设置在响应头的Etag字段中发送给客户端,客户端缓存Etag和结果数据,并在下一次请求时将Etag设置在请求头的If-None-Match字段中,服务端接收请求会比较Etag和If-None-Match是否一致, 若一致,返回304告诉客户端资源没有发生变化,客户端接收304状态码直接访问之前缓存的结果数据;
    • Last-Modified(服务端)/If-Modified-Since(客户端):首次请求资源时,服务端将缓存Last-Modified(结果数据最新的更改时间)并设置在响应头的Last-Modified字段中发送给客户端,客户端缓存Last-Modified和结果数据,并在下一次请求时将Last-Modified设置在请求头的If-Modified-Since字段中,服务端接收请求比较Last-Modified和If-Modified-Since,如果Last-Modified < If-Modified-Since(请求资源的时间在结果数据变化之后,资源是最新的),说明结果数据并没有发生变化,返回304告诉客户端资源没有发生变化,客户端接收304状态码直接访问之前缓存的结果数据;

     下一次加载时,强缓存优先级更高,先开始强缓存,判断cahe-control中的max-age(时间点)是否过期(若没有max-age,则判断expirse(时间段)是否过期);

    若没过期,命中强缓存,直接使用本地资源;

    若过期,开始协商缓存,在请求头中携带If-None-Match/If-Modified-Since向服务端发送请求,服务端判断If-None-Match中的etag值(若没有etag,比较If-Modified-Since是否在Last-Modified之后)是否改变;

    如果没有改变(在后面),命中协商缓存,返回304;

    如果改变(在之前),返回200+新的资源文件+新etag(Last-Modified)

    总的来说,就是设置资源过期时间和判断结果数据是否发生变化两种方式,理解记忆哦~ 

     OSI七层模型(字节一面)

    应用层为应用程序提供服务(http发生在这一层)
    表示层数据格式转化、数据加密
    会话层建立、管理和维护会话
    传输层建立、管理和维护端到端的连接(TCP、UDP在这一层,规定了数据包的传输方式)
    网络层IP选址以及路由选择(规定了数据包的传输路线)
    数据链路层提供介质访问和链路管理(传输路线)
    物理层物理层(通过物理介质传输比特流)

     REACT的diff算法(2次,待更新...)

    核心思想:

    REACT的Fiber 

    问题:REACTv15在渲染时会递归对比虚拟DOM,找到需要更新的节点然后同步更新他们,一气呵成。整个过程REACT会占用浏览器资源,,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿

    解决:

    为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。

    所以 React 通过Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:

    • 分批延时对DOM进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;
    • 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。

    核心思想:Fiber也称协程或纤程,它和线程不一样,本身不具有并发或并行的能力(需要线程配合),它是一种控制流程让出机制。让出CPU的执行权,让CPU先执行优先级高的任务(如与用户交互)。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。

     V8的垃圾回收机制——GC算法分代式垃圾回收机制

    V8将内存(堆)分为新生代老生代

    新生代中的对象一般存活时间较短,使用 Scavenge GC 算法。在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了。

    老生代中的对象一般存活时间较长且数量也多,使用了两个算法,分别是标记清除算法标记压缩算法

    对象会出现在老生代空间中的情况:

    • 新生代中的对象是否已经经历过一次 Scavenge 算法,如果经历过的话,会将对象从新生代空间移到老生代空间中。
    • To 空间的对象占比大小超过 25 %。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生代空间中。

    在这个阶段中,会遍历堆中所有的对象,然后标记活的对象,在标记完成后,销毁所有没有被标记的对象。在标记大型对内存时,可能需要几百毫秒才能完成一次标记。这就会导致一些性能上的问题。为了解决这个问题,2011 年,V8 从 stop-the-world 标记切换到增量标志。在增量标记期间,GC 将标记工作分解为更小的模块,可以让 JS 应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况。但在 2018 年,GC 技术又有了一个重大突破,这项技术名为并发标记。该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行。

    清除对象后会造成堆内存出现碎片的情况,当碎片超过一定限制后会启动压缩算法。在压缩过程中,将活的对象向一端移动,直到所有对象都移动完成然后清理掉不需要的内存。

     防抖和节流

    先介绍具体含义和可能使用的场景(一定一定一定不要游戏回城技能起手,典型的B站大学选手)

    •  防抖:确保在指定时间间隔内,仅最后一次触发的事件才会执行函数       

            应用场景:输入框输入、窗口大小调整

    function debounce(func, wait) {
      let timeout;
      return function() {
        const context = this;
        const args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(() => {
          func.apply(context, args);
        }, wait);
      };
    }
    
    // 使用示例
    const inputHandler = debounce(() => {
      console.log('Input event triggered');
    }, 300);
    
    document.getElementById('myInput').addEventListener('input', inputHandler);
    • 节流:防止高频触发,在一段时间内仅触发一次

            应用场景:滚动、鼠标移动事件

    function throttle(func, limit) {
      let inThrottle;
      return function() {
        const args = arguments;
        const context = this;
        if (!inThrottle) {
          func.apply(context, args);
          inThrottle = true;
          setTimeout(() => {
            inThrottle = false;
          }, limit);
        }
      };
    }
    
    // 使用示例
    window.addEventListener('resize', throttle(() => {
      console.log('Window resized');
    }, 1000));

     Webpack热更新原理(唯品会一面,字节一面,待更新...)

    TS篇

    interface和type区别(3次 待更新...)

    如何实现子接口仅继承部分父接口且具有额外属性

    使用typescript的工具类型Pick筛选出子接口想要继承的变量

    Pick属于 TypeScript 内置的工具类型,其用途是从某个类型里选取特定的属性,进而构建出一个新类型

    interface Parent {
      P1: string;
      P2: number;
      P3: boolean;
    }
    
    type PartialParent = Pick<Parent, "P1" | "P2">;
    
    // 添加额外属性1.
    interface Child extends PartialParent {
      C1: string;
    }
    
    let children: Child = {
      P1: "hello",
      P2: 123,
      C1: "world",
    };
    
    console.log(children);
    
    // 添加额外属性2.添加一个字符串索引签名 可自定义添加0-n个属性
    interface Child {
      [propName: string]: any;
    }
    
    let children1: Child = {
      P1: "hello",
      P2: 123,
      C1: "world",
      extra: "extra",
      name:"name"
    }
    
    console.log(children1);

    手写代码篇

    数组去重(3次)

    // 数组去重的方法
     const equal = function (list) {
          // 1.利用Set特性
          // let _list = [...new Set(list)];
    
          
          // 2.数组indexOf、includes方法
          // for (let i = 0; i < list.length; i++) {
          //   // if (_list.indexOf(list[i]) < 0) {
    
          //   if (!_list.includes(list[i])) {
          //     _list.push(list[i]);
          //   }
          // }
    
          // 3.数组filter方法和indexOf组合技
          // let _list = list.filter((item, index, array) => {
          //   return array.indexOf(item) === index;
          // })
    
          // 4.数组reduce和indexOf/includes组合技
          let _list = list.reduce((pre, cur) => {
            if (!pre.includes(cur)) {
              pre.push(cur);
            }
            return pre;
          }, [])
    
    
          return _list;
        }
    
    console.log(equal([1, 2, 3, 4, 5, 23, 1, 2, 3, 4, 5]));
    
    
    //4. 利用Map映射+循环 Map.has()的时间复杂度是O(1)!!!!!!
    //总体时间复杂度为O(n)
    function uniqueArray(arr: number[]) {
      const map = new Map<number, boolean>();
      const uniArr: number[] = [];
      for (let index in arr) {
        if (!map.has(arr[index])) {
          map.set(arr[index], true);
          uniArr.push(arr[index]);
        }
      }
    
      return uniArr;
    }
    
    console.log(uniqueArray([1, 1, 1, 1, 2, 2, 2, 3, 4, 5, 5, 6, 6, 7, 8, 8]));
    

    对象扁平化

    // 处理对象,使得 { a: {b: { c: 1, d:2 } } } 变成 { abc: 1, abd:2 }
    function flatObj(obj: any, prefix: string = "", result: object = {}) {
      for (let key in obj) {
        if (typeof obj[key] === "object" && obj[key] !== null) {
          flatObj(obj[key], prefix + key, result);
        } else {
          result[prefix + key] = obj[key];
        }
      }
    
      return result;
    }
    const obj = { a: { b: { c: 1, d: 2 }, e: 3 } };    // { abc: 1, abd: 2, ae: 3 }
    console.log(flatObj(obj));

    先这样吧,洗洗睡咯...嘻嘻天天都在洗洗睡睡~略略略,就喜欢平平淡淡的生活

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值