你会忽略的前端N个小Tips知识点

6,013 阅读48分钟

近日,每天早上六点起床学习;有很多小伙伴问我有没有知识点的总结或者刷到了什么经典题吗?我决定在这里每天记下方便大家一起学习前端知识;你能接住几题呢?

2021.10.29

1.flex:1 代表了什么意思

flex: 1的组成:

  • flex-grow(放大比例,默认为0

  • flex-shrink(缩小比例,默认为1

  • flex-basic(flex 元素的内容盒(content-box)的尺寸,默认auto)的缩写;

flex的第三项指的是flex-basis这个属性,这个属性的“0”和“auto”在大多数情况下都会有所不同:

  • 数值被解释为特定宽度,因此0将与指定“width:0”相同;
  • 0%就相当于容器内所有项目原本的width不起作用,然后平分宽度;
  • auto就相当于容器内所有项目原本的width起作用,伸缩空间减去这些width,剩余空间再平分。所以有的情况下auto和0%是相同的

2种,flex-basis为0%,覆盖width,实际占用0, flex-basis为auto,width权限更高,占用width的值

所以在下面的情况下


     .content {
        display: flex;
      }
     .content1{
        flex:1
      }
      
    <div class="content">
        <div class="content1" style="background-color: blue;">1</div>
        <div class="content1" style="background-color: blueviolet;">2</div>
        <div class="content1" style="background-color: chartreuse;">3</div>
    </div>
    

flex: 1 等价于:

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

2.单行文本溢出省略号和一个向下的三角形的css怎么写

单行文本溢出省略号:

{
    overflow: hidden;            // 溢出隐藏 
    text-overflow: ellipsis;      // 溢出用省略号显示 
    white-space: nowrap;         // 规定段落中的文本不进行换行 
}

多行文本溢出省略号:

{
    overflow: hidden;            // 溢出隐藏 
    text-overflow: ellipsis;     // 溢出用省略号显示 
    display:-webkit-box;         // 作为弹性伸缩盒子模型显示。 
    -webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列 
    -webkit-line-clamp:3;        // 显示的行数 
}

向下三角形:

div {
    width: 0;
    height: 0;
    border-top: 50px solid red;
    border-right: 50px solid transparent;
    border-left: 50px solid transparent;
}
// 或者
div {
    width: 0;
    height: 0;
    border: 50px solid;
    border-color: red transparent transparent transparent;
}
// 或者
div {
  width: 0;
  height: 0;
  border: 50px solid transparent;
  border-top-color: red;
}

3.flex布局有哪些常用属性?

以下6个属性设置在容器上:

  • flex-direction属性决定主轴的方向(即项目的排列方向)。
  • flex-wrap属性定义,如果一条轴线排不下,如何换行。
  • flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
  • justify-content属性定义了项目在主轴上的对齐方式。
  • align-items属性定义项目在交叉轴上如何对齐。
  • align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

以下6个属性设置在子元素上:

  • order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
  • flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  • flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
  • flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
  • flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。
  • align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

4.css如何开启硬件加速

  • 动画实现过程中利用transform: translateZ(0),欺骗浏览器开启GPU加速
  • will-change开启gpu加速,就是性能不太好

5.如何在浏览器画出一个正方形并保证这个正方形是浏览器可视区最大的?

(第1种答案)

body {
  margin:0;
  padding:0;
}
.box{
  width: 100vmin;
  height:100vmin;
  background-color: #ccc;
  margin:0 auto
}

换一种方式,比如使用padding-top,我们想一下 padding-top的百分比是相对什么来的?--- 宽度

(第2种答案)

<body>
    <div class="container">

    </div>
    
</body>
<style>

.container {
    background-color: aqua;
    width: 100vmin;
    padding-top:  100vmin;
}

</style>
<body>
    <div class="container">

    </div>
</body>
<style>

.container {
    background-color: aqua;
    width: 100%;
    padding-top:  100vmin;
}

</style>

上面那个写法对吗?

显然是不对的,因为container是用了body的width去做的padding,导致存在滑动效果 所以我们可以在外面再包一层

(第3种答案)

<body>
    <div class="container">
        <div class="child">

        </div>

    </div>
    
</body>
<style>

.container {
    background-color: aqua;
    width: 100vmin;
}
.child{
    width: 100%;
    padding-top: 100%;
}

</style>

(第4种答案)

div::before {
  content:'',
  padding-top:100%;
  display:block;
  width:100vmin;
}

当然你也可以改成after的写法 image.png

2021.11.1

6.数据检测的方法有哪些

typeof

// 只能检测简单数据类型还有function;不能判断数组和对象
typeof(11) === 'number'
typeof(function(){}) === 'function'

instanceof

用法:判断实例 instanceof 构造函数

// 原理:判断在其原型链中能否找到该类型的原型

// 简单数据类型检测不出来,会false
console.log(2 instanceof Number); // false 
console.log(true instanceof Boolean); // false

// 可以用来判断复杂数据类型
console.log([] instanceof Array); // true 
console.log([] instanceof Object) // true 
console.log({} instanceof Object); // true 
console.log(function(){} instanceof Function); // true

constructor

// 虽然简单和复杂数据类型都能判断,但是! 构造函数可以被手动改变,所以也不是百分之百准确
console.log((2).constructor === Number); // true 
console.log((true).constructor === Boolean); // true 
console.log(('str').constructor === String); // true 
console.log(([]).constructor === Array); // true 
console.log((function() {}).constructor === Function); // true console.log(({}).constructor === Object); // true

Object.prototype.toString.bind()

var testMethods = Object.prototype.toString; 
console.log(testMethods.call(2)); // [object Number] 
console.log(testMethods.call(true)); // [object Boolean] 
console.log(testMethods.call('str')); // [object String]

// 实现原理:
// 先判断参数是否是null或者undefined,是的话直接返回结果;
// 否则将参数转为对象,取得该对象的 [Symbol.toStringTag] 属性值(可能会遍历原型链)取得 tag, 然后返回 "[object " + tag + "]" 形式的字符串。

7.如何实现bind

// 模拟bind 自己手写bind函数,可以在原型上增加这个方法
Function.prototype.myBind = function (context) {
    // 如果调用bind方法的对象不是函数的话返回出去
    if (typeof(this) !== 'function') {
        throw new TypeError('类型错误')
    }
    // 把对象转成数组,且把参数截出来存入args中
    var args = [...arguments].slice(1)
    var self = this  // 把调用bind的函数存起来
    return function Fn(){
        //  new的话this指向新创建的实例this;普通调用的this指向新bind的第一个参数context
        return self.apply(this instanceof Fn ? this : context, args.concat(...arguments))
    }
}

// 使用
function fn1 (a, b, c){
    console.log('this', this, a, b, c) // this {x: 100} 10 20 30
    return 'this is fn1'
}

const fn2 = fn1.myBind({x:100},10,20,30) 
const res = fn2()
console.log('fn2', fn2, res) // fn2返回一个函数

8.mouseover和mouseenter的区别

二者的本质区别:mouseenter不会冒泡,简单的说,它不会被它本身的子元素的状态影响到.但是mouseover就会被它的子元素影响到,在触发子元素的时候,mouseover会冒泡触发它的父元素.(想要阻止mouseover的冒泡事件就用mouseenter)

共同点:当二者都没有子元素时,二者的行为是一致的,但是二者内部都包含子元素时,行为就不同了.

9.JS垃圾回收机制

垃圾回收机制:Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存

浏览器通常使用的垃圾回收方法有两种:标记清除,引用计数

标记清除: 当变量进入执行环境时,就标记这个变量“进入环境”, 变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。

引用计数: 跟踪记录每个值被引用的次数。当一个新的引用指向对象时,引用计数器就递增+1,当去掉一个引用时,引用计数就递减-1。当引用计数到0时,该对象就将释放占有的资源。

详解可移步至juejin.cn/post/698158…

2021.11.2

10.组合继承的缺点,寄生继承的缺点

组合继承:由于我们是以父级的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。导致原型对象被父类实例属性污染,出现属性冗余问题

function Father(name){
  this.name = name
}
Father.prototype.say = function(){
  console.log(`hello大家好,我是${this.name}今年${this.age}岁了`)
}
function Son(name ,age){
  this.age = age
  Father.call(this, name)
}
Son.prototype = new Father // 将父子方法建立关系,为了复用prototype属性

var xrr = new Son('小熔熔', '12')
xrr.say()

寄生继承:没有盗用父类的构造函数,而只能够继承原型中方法,所以没有办法实现函数的复用。

function People(obj){
  function F(){}
  F.prototype = obj
  return new F()
}

function Student(obj){
  var clone = People(obj) // 通过调用函数创建一个新对象
  clone.__proto__.long = '12cm'
  clone.__proto__.getAA =  function () {
        console.log('aaaa')
  }
  clone.say = function(){
    console.log(`hello大家好,我是${this.name}今年${this.age}岁了`)
  }
  return clone
}

var obj = {
  name:'小熔熔',
  age:'12'
}
var xrr = Student(obj)
xrr.say()

解决方案:使用组合寄生继承

11.function构造函数和Class的差别

class事实上是一种特殊的funcion,就像可以定义funcion表达式和funcion声明一样,class语法也有2种形式:class表达式和class声明

  1. Function构造函数:可以实例化函数对象,function声明会变量提升。

  2. Class:ES6中的构造语法——Class,在语法上更贴合面向对象的写法。实现继承更加易读、易理解。更易于写java等后端语言的使用。本质是语法糖,使用prototyp。不会变量提升,需要先声明再使用

    class的body部分包含在花括号{}中
    constructorconstructor中可以通过super关键字,调用父类的constructor方法
    通过static关键字为一个class创建静态方法
    extends关键字用于在class声明或表达式中,创建另一个class的子类。
    
    

12.怎么计算元素ele到浏览器视口顶部的距离

let sum = 0
let ele = xxx; // 要求距离的元素
while(ele.offsetParent) {
	sum += ele.offsetTop; // 统计当前到其有定位元素的距离
	ele = ele.offsetParent; // 将父设为当前继续求距离
}
// 输出到视口顶部距离
console.log(sum)

13.说说prop和attribute的差别

  • property是js原生对象的直接属性,是 dom 元素在 js 中作为对象拥有的属性。
  • attribute:是 dom 元素在文档中作为 html 标签拥有的性,它的值只能够是字符串,attributes是属于property的一个子集

对于 html 的标准属性来说,attribute 和 property 是同步的,是会自动更新的, 但是对于自定义的属性来说,他们是不同步的。

14.事件冒泡和捕获的区别?

捕获阶段 -> 目标阶段 -> 冒泡阶段

比如点击了某个input框,从window 到 body 是捕获阶段(捕获从外向里),到input是目标阶段(到达目标阶段),再到body到window是冒泡阶段(冒泡从里向外);ie规定先让他冒泡,addEventListener 第三个参数就代表的是冒泡还是捕获,默认是false代表默认冒泡

2021.11.3

15. Cookie有哪些属性?

(1)cookie内包含

  1. name:名称

  2. value:值

  3. domain:域名

        有特定场景下会使用到domain,比如顶级域名和子级域名之间的cookie共享和相互修改、删除,会用到domain
    
  4. path:访问此cookie的页面路径

  5. expires/Max-Age:cookie超时时间

  6. size:大小

  7. http:cookie的httpOnly属性

  8. secure

  9. same-site等等

       cookie的httpOnly是安全相关,cors要求cookie传输的时候httpOnly不能为none
    

(2)发送请求的时候会携带cookie吗

axios/ajax默认是发送请求的时候不会带上cookie的,需要在请求头上设置withCredentials: true来解决;

同时后端要配合设置:

  • header 信息 Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin不可以为 '*',因为会和 Access-Control-Allow-Credentials: true 冲突,需配置指定的地址
  • 设置httponly,一般值都是httponly,要设置为same-site

(3)关掉页面对cookie有影响吗?

cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。

没有设置cookie生命周期的话,cookie 的生命周期是累計的,从创建开始计时,20mins后cookie的生命周期結束,cookie就無效了; 一般保存在内存里,如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。

16. 说明EventTarget.addEventListener() 的第三个参数

第三个参数true为捕获,默认false 是冒泡;

新语法:第三个参数可为 Boolean,也可为 Object;

  • options (可选)

    1. capture 表示listener会在该类型的事件捕获阶段传播到该EventTarget 时触发。
    2. once 表示listener在添加以后最多只调用一次。若是是 true,listener会在其被调用以后自动移除。
    3. passive 表示listener永远不会调用preventDefault()。若是listener仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
target.addEventListener(type, listener, true/false)
target.addEventListener(type, listener, options)

17. Object.freeze() 是做什么用的,有哪些应用场景

1.Object.freeze()是ES5新增的特性,可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。防止对象被修改,但不能防止被修改引用。如果你有一个巨大的数组或Object,并且确信数据不会修改,使用Object.freeze()可以让性能大幅提升,多用于展示。

    // 为了不让obj中的键对应的值被修改可以使用;Object.freeze(obj)
    const obj = { a: '1', b: '2' }

2. freeze可以用来实现const; 3. 只是浅冻结:复合类型的对象不能freeze比如 const obj = { arg: [1, 2, 3] },这个arg是冻不了的; 4. 就是要冻它,只能遍历逐层freeze 5. object.freeze这个可以不让vue去添加响应

    // 深层冻结ultil:
    function deepFreeze(obj = {}) {
      Object.freeze(obj);
      (Object.keys(obj) || []).forEach(key => {
        if (typeof obj[key] === 'object') {
          deepFreeze(obj[key])
        }
      })
    }

6.reduce可以实现深层冻结一行搞定

18. 点击移动端浏览器的前进按钮或后退按钮,往返页面不刷新,可以如何解决

往返缓存指浏览器为了在页面间执行前进后退操作时能拥有更流畅体验的一种策略。

该策略具体表现为:当用户前往新页面前将旧页面的DOM状态保存在缓存里,当用户返回旧页面前将旧页面的DOM状态从缓存里取出并加载。大部分移动端浏览器都会部署缓存,可大大节省接口请求的时间和带宽。

解决方案:

1. onunload

// 在新页面监听页面销毁事件
window.addEventListener("onunload", () => {
    // 执行旧页面代码
});

2. beforeRouteEnter 若在Vue中使用了keep-alive,可将接口请求放到beforeRouteEnter()里。

3. 监听pageshow事件 pageshow事件在每次页面加载时都会触发,无论是首次加载还是再次加载都会触发,这就是它与load事件的区别。 pageshow事件暴露的persisted可判断页面是否从缓存里取出。

window.addEventListener("pageshow", e => e.persisted && location.reload());

如果e.persisted依然是false,可以观察performance对象

window.addEventListener('pageshow', () => { 
if (e.persisted || (window.performance && window.performance.navigation.type == 2)) { 
    location.reload() 
} }, false)
performance.navigation.type是一个无符号短整型

TYPE_NAVIGATE (0): 
当前页面是通过点击链接,书签和表单提交,或者脚本操作,或者在url中直接输入地址,type值为0

TYPE_RELOAD (1) 
点击刷新页面按钮或者通过Location.reload()方法显示的页面,type值为1

TYPE_BACK_FORWARD (2) 
页面通过历史记录和前进后退访问时。type值为2

TYPE_RESERVED (255) 
任何其他方式,type值为255

4. 禁用缓存

5. history,通过路由就可以前进后退不刷新,history路由变化就是pushstate 和popstate,通过popState事件还能监听到history,用来在点击按钮后进行操作加强。

2021.11.4

19. new做了什么

(1)创建了一个新的空对象

(2)设置原型:将对象的__proto__设置为函数的 prototype 对象。

(3)执行构造函数的代码,让函数的 this 指向这个新对象(为这个新对象添加属性方法)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

function objectFactory(){
    // 把数组的第一个元素从其中删除,并返回第一个元素的值=>第一个为构造函数后面的为参数
    let constructor = Array.prototype.shift.call(arguments)
    // 限定第一个参数必须为构造函数
    if (typeof constructor !== "function") {
        console.error("第一个参数应为函数")
        return
    }
    // 创建一个空对象
    let newObject = null
    
    // 将构造函数的prototype赋给新对象的__proto__
    newObject = Object.create(constructor.prototype)
    
    // 相当于newObject.constructor(arguments)
    let result = null // 将this指向新对象,执行构造函数,将属性方法赋值给添加到这个新对象里
    result = constructor.apply(newObject, arguments)
    
    // 判断返回的类型-对象或者函数
    let flag = result && (typeof result === "object" || typeof result === "function")
    
    // 如果是函数或者对象则返回结果,否则返回构造函数
    return flag ? result : newObject
}

// 使用方法
objectFactory(构造函数, 初始化参数);
let a = objectFactory(Array, '111', '2222')
console.log(a, 'aa') //  ["111", "2222"] "aa"

20.通过原型实现的继承和类继承有什么区别

  • 原型继承:以原型链的方式来实现继承,缺点:在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱;还有就是在创建子类型的时候不能父级传递参。

    function Father(){
          this.FatherName = "father's 构造函数";
     }
    Father.prototype.age = 12;
    function Son(){
          this.SonNname = "Son's 构造函数";
    }
    //Son的原型继承Father创建出来的对象,相当于继承了Father的全部内容,同时全部都存在Son__proto__属性里
    Son.prototype = new Father();
    
    var xrr = new Son();
    console.log(xrr.age);
    
  • 类式继承(构造函数):JS中其实是没有类的概念的,所谓的类也是模拟出来的。通过在子类型的函数中调用父级的构造函数来实现,这一种方法解决了不能向父级传递参数的缺点;缺点:无法实现函数方法的复用(只能复用father里 this.name = name的这种,无法复用father.prototype.a ),并且父级原型定义的方法子类型也没有办法访问到(都没有用到原型继承当然就访问不到原型定义的方法)

    function Father(name){
            this.name = name
    }
    function Son(name, age){
      this.age = age
            Father.call(this, name)
    }
    
    var xrr = new Son('小熔熔', '12')
    console.log(xrr.age, xrr.name)
    
  • class继承:class以及extends其实是寄生组合继承的语法糖,新的class写法只是让对象原型的写法更加清晰,更加面向对象编程的语法而已。子类在构造函数中必须调用super方法,这个super指的是父类的构造函数,从而获取到父类实例,在组合继承里父类需要将this指向子类的实例需要用parent.call(this, ...arguments)。

    // 父类
    class People {
        constructor(name){
            this.name = name
        }
        eat(){
            console.log(`${this.name}是一个妹子`)
        }
    }
    // 子类 继承的话要记得在constructor内super
    class Student extends People {
        constructor(name,number){
            super(name)
            this.number = number
        }
        sayHi(){
            console.log(`姓名${this.name}妹子还对你招了手${this.number}`)
        }
    }
    
    // 再写一个继承People的子类
    class Teacher extends People {
        constructor(name,major) {
            super(name)
            this.major = major
        }
        teacher(){
            console.log(`${this.name}教授${this.major}`)
        }
    }
    
    // 实例
    const xrr = new Student('小熔熔', 100)
    console.log(xrr.name) //小熔熔
    console.log(xrr.Siholll)//undefined
    xrr.sayHi() 
    xrr.eat()
    xrr instanceof Student // true
    xrr instanceof People // true
    
    // 了解this指向后补充一下
    xrr._proto_.sayHi()  // 姓名undfined妹子还对你招了手undfined
    // 这里undefined其实是因为xrr._proto_调用了sayHi()方法,this指向的是xrr._proto_
    
    // xrr.sayHi()  其实相当于xrr._proto_.sayHi.call(xrr) ,但内部不是这样执行的
    

21. window.onload和domcontentloaded的区别

window.addEventListener('load',function(){.......}):页面全部资源加载完才会执行

ducument.addEventListener('DOMContentLoaded',function(){.......}):DOM渲染完可执行;通常用这个

2021.11.5

22.什么是BFC,如何形成BFC

BFC: 块级格式化上下文.通俗的说就是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。

如何形成BFC:(float不是none,position是absolute和fixed,overflow不是visible)

  • overflow: hidden
  • display: inline-block
  • position: absolute
  • position: fixed
  • display: table-cell
  • display: flex

解决了什么问题:

  1. 使用Float脱离文档流,高度塌陷
  2. Margin边距重叠
  3. 两栏布局,Float脱离文档后文字环绕问题

23.你知道的浅拷贝有哪些?

  1. object.assign
  2. 扩展运算符方式(...)
  3. concat 拷贝数组
  4. slice/concat 拷贝数组
  5. lodash函数库
  6. for单层循环

24.说说Object.assign和扩展运算符的区别

  • Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。

  • 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。

25.你知道的深拷贝有哪些?(让你实现,你会怎么设计)

  1. JSON.parse(JSON.stringify())
缺陷:
    会忽略 undefined
    会忽略 symbol
    不能序列化函数
    无法拷贝不可枚举的属性
    无法拷贝对象的原型链
    拷贝 RegExp 引用类型会变成空对象
    拷贝 Date 引用类型会变成字符串
    对象中含有 NaNInfinity 以及 -InfinityJSON 序列化的结果会变成 null
    不能解决循环引用的对象,即对象成环 (obj[key] = obj)。
    
  1. 自己设计深拷贝函数
自己设计思路:
【基础版】递归实现,通过 for in 遍历传入参数的属性值,如果值是引用类型则再次递归调用该函数,如果是基础数据类型就直接复制。
【考虑的点】
1.递归 (存在问题,不够健壮)
2.需考虑数组 (问题是只考虑了普通的object,没有考虑数组的话会有问题)
3.循环引用(如果递归进入死循环会导致栈内存溢出了)
4.性能优化(历数组和对象都使用了for in这种方式,实际上for in在遍历时效率是非常低的)
5.其他数据类型(只考虑了普通的object和array两种数据类型,实际上所有的引用类型不止这两个,就需要精确判断引用类型)
【存在问题】:
1.简单的递归实现深拷贝的函数并不能复制不可枚举的属性以及 Symbol 类型;
2.这种方法只是针对普通的引用类型的值做递归复制,而对于 ArrayDateRegExpErrorFunction 这样的引用类型并不能正确地拷贝;
3.对象的属性里面成环,即循环引用没有解决。

26.Promise执行过程中可以中断吗?若想中断,怎么对其进行中断。

Promise执行是不可以中断的。但实际需求中会出现一种场景,就是在合适的时候,把pending状态的promise给reject掉。例如把网络请求设置超时时间,一旦超时就中断。
这里用定时器模拟一个网络请求,
function timeoutWrapper(p,timeout =2000){
     const wait = new Prkmise(ersolve,reject){
        setTimeout(()=>{

          reject('请求超时')
          
       },timeout)
    }
    return Promise.race([p,wait])
}

2021.11.8

27.event loop 执行过程

事件循环从宏任务队列开始,一开始宏任务队列中只有一个script(整体代码)任务,遇到任务源时,分发到相应的任务队列中。异步任务可分为task 和 microtask 两类(requestAnimationFrame 既不属于 macrotask, 也不属于 microtask),不同的API注册的异步任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。执行栈在执行完同步任务后,检查执行栈是否为空,如果为空,检查微任务队列是否为空,如果微任务队列不为空,则一次性执行完所有的微任务。如果微任务为空,则去执行下一个宏任务。每次单个的宏任务执行完之后,都会检查微任务队列是否为空,如果不为空,则按照先进先出的方式执行完所有的微任务,然后执行下一个宏任务,以此循环。每次执行宏任务 产生的微任务队列都是新创建的 宏任务队列只有一个。

image.png

image.png

28.数组去重的方式

  1. Set,

  2. filter,

  3. 双层for循环然后splice去重,

  4. indexof去重,

  5. sort,

  6. includes,

  7. hasOwnProperty(利用hasOwnProperty 判断是否存在对象属性)

  8. map,

  9. […new Set(arr)]

  10. Array.from(new Set(arr))

    function unique(arr) {
        var obj = {};
        return arr.filter(function(item, index, arr){
            return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
        })
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
    //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]   //所有的都去重了
    
    

29.set和map的区别

set不允许元素重复

属性和方法:

  • size-获取元素数量;
  • add(value)-添加元素,返回set实例本身;
  • delete(value)-删除元素返回一个布尔值;
  • has(value)-返回布尔值,表示该值是否是set实例的元素;
  • clear()-清除所有元素,无返回值。

map的key可以是任何数据类型,Map中的键值是有序的,Map的键值对个数可以从 size 属性获取。

属性和方法:

  • set:设置成员key和value;
  • get :获取成员属性值;
  • has:判断是否值存在;
  • delete:删除;
  • clear:清除所有

set存储单个的值,map存储键值对

30.介绍一下es6的symbol

symbol是es6新增的一种数据类型,symbol可以用来创建唯一值,可以用作对象的属性。

  1. 作为属性名时不能被for...in, for...of遍历,不能被Object.keys()或者object.getOwnPropertyNames()返回
  2. 不能使用new命令
  3. 相同参数的symbol()返回的值是不相等的:例如let a = Symbol(‘a’) let b = Symbol(’a’)a===b 返回false
  4. Symbol 值作为属性名时,该属性是公有属性不是私有属性
  5. Symbol 作为对象的属性名,可以保证属性不重名,可以在类的外部访问。
  6. Symbol 作为对象属性名时不能用.运算符,要用方括号[]

2021.11.9

31.常见的DOM操作有哪些

  • 增:createElement(),appendChild()

  • 删:removeChild()

  • 改: insertBefore(),appendChild()

  • 查:

    getElementById // 按照 id 查询
    getElementsByTagName // 按照标签名查询
    getElementsByClassName // 按照类名查询
    querySelectorAll // 按照 css 选择器查询
    

32.数组有哪些原生方法?

(具体使用可自己查)

slice,splice,pop,push,unshift,shift

toString,reverse,sort,concat,indexOf,lastIndexOf,every,some,filter,forEach,reduce,reduceRight

find,findIndex, includes, fill,join,flat flatmap entries reduceRight

33.重绘和回流

重绘:当页面中某些元素的样式发生变化比如颜色等,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘,不会导致浏览器重新渲染页面,不影响性能

重流(回流):渲染树中部分或者全部元素的大小尺寸、位置,结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程,会影响性能

34.协商缓存和强缓存的区别

强制缓存: 优先级较高,先比较当前时间与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期;强制缓存不用走请求,使用强制缓存可以提升性能。

协商缓存: 白话说就是和服务端协商能不能用缓存,需耗时。向服务器发送带有If-None-MatchIf-Modified-Since的请求优先根据Etag的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200; 如果服务器收到的请求没有 Etag 值,则将If-Modified-Since和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回304;不一致则返回新的 last-modified 和文件并返回 200;

image.png

35.浏览器的渲染过程

  1. 生成DOM 树:首先解析收到的文档(html),根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的网页结构框架。
  2. 生成CSSOM 规则树:然后对 CSS 进行解析,生成 CSSOM 规则树,它决定了网页的样式。
  3. 构建成渲染树:将 DOM树 和 CSSOM规则树 构建成 渲染树(Render Tree)。
  4. 布局阶段:浏览器开始计算布局(元素位置信息,大小等等),计算层级,position z-index会导致产生新的层,渲染还会对区域进行分块。接下来图层绘制(画像素,媒体文件解码),最后图层合并,GPU绘制。
  5. 绘制阶段:布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的paint方法,将它们的内容显示在屏幕上,绘制使用 UI 基础组件,遇到script标签则暂停渲染,优先加载并执行JS代码,执行完后再继续渲染(js线程和dom渲染线程是共用一个线程的;因为js有可能会改变dom结构有可能改变Render Tree结构),所以 我们要把js放在末尾避免它堵塞加载渲染页面;Css如果没有解析完 rendertree会构建不了,样式放在底部可能会导致重绘,所以css 要放前面尽早加载出来。
  6. 直至把Render Tree渲染显示完成

2021.11.10

36.伪元素和伪类的区别和作用?

伪元素:在一个元素前后加一个元素或者样式,但是在dom节点上不存在这个元素,代表某个元素的子元素,特征看起来有 ::

::before 
::after 
::placeholder 
::selection 
::first-letter 
::first-line

伪类:在css中只有一个:形成,不会产生新的元素,可以在元素选择器上改变元素的状态,特征看起来有 :

:hover 
:link 
:active 
:focus 
:first-child 
:last-child

区别:是否创建了新的元素

37.this 的指向

  1. 箭头函数:往上找到的最近的词法作用域的this指向
  2. 定时器:windows
  3. 对象/函数调用:this指向调用方,
  4. 全局直接调用函数:window(非严格模式),undefined(严格模式)
  5. 构造函数:this指向实例
  6. 显示绑定apply\call\bind:指向第一个参数

38.https 加密过程

https是由http通讯,用SSL/TLS(完全传输层协议)来加密数据包的,https主要是提供对网站服务器的身份认证,保护交换数据的隐私和完整性。

传输加密过程:

(1)客户端先向服务端发起https请求,客户端生成一个随机数发给服务端,传输的时候会带上客户端支持的加密方式套件,还带上协议版本号(随机数 + 加密方式套件 + 协议版本号)

(2)服务端收到请求后存放随机数,确认加密方法,也生成一个随机数伴随着公钥(数字证书)一起传给客户端(加密方法+随机数+公钥)

(3)客户端确认服务器证书后对证书进行递归验证并获取公钥,把收到的公钥生成一个预主密钥,再生成一个用公钥加密后的随机数一起发给服务端,还会发一个用hash加密过的内容(预主密钥+随机数)

(4)服务端拿到预主秘钥后,用私钥解密信息拿到预主密钥

(5)客户端和服务端同时对这三个随机数用同一套加密方式算出一个主密钥,所以这个主密钥只有客户端和服务端知道,中间件是破解不了的

(6)客户端finished,发送共享秘钥key加密过的“finished”信号

(7)服务端finished,发送共享秘钥key加密过的“finished”信号

(8)达成安全通信

image.png

39.柯里化的定义和实现

柯里化是把接受多个参数的函数变成接受单一参数的函数,并返回接受剩下参数返回结果的技术, 本质就是要实现减少代码冗余同时增加可读性,用途之一:参数复用

  • 输入是一个函数,并且这个函数拥有n个参数
  • 输出也是一个函数,并且可以使用fn()()()这种方式调用
  • 参数被柯里化过程中的函数被拆分
let add = this.curry(this.addf)
console.log(add(1)(2)(3)(4)(5, 3), '111')
curry(fn, ...args) {
      console.log(args, 'args,fn一直没有改变')
      return args.length < fn.length ? (...innerArgs) => { console.log(innerArgs, 'innerArgs'); return this.curry(fn, ...args, ...innerArgs) } : fn(...args)
},
addf(a, b, c, d, e) {
      return a + b + c + d + e
},
    

40.什么是 JavaScript 生成器?如何使用生成器?

generator生成器是一种返回迭代器的函数,通过functuion关键字后的星号(*)来表示,函数中会用到新的关键字yield,星号可以紧挨着function关键字,也可以在中间添加一个空格.

特性

  1. 每当执行完一条yield语句后函数就会自动停止执行,直到再次调用next();
  2. yield -- 关键字只可在生成器内部使用,yeild是相当于return的功能,在其他地方使用会导致程序抛出错误;
  3. 可以通过函数表达式来创建生成器,但是不能使用箭头函数
  4. generator并不执行但会返回一个遍历器对象,当执行next()的时候才依次执行内部的函数
  5. 用函数名之前加上*的方式去创建,具有暂停、执行、结束三种状态
  6. Generator.prototype.throw() -- 向生成器抛出错误
  7. Generator.prototype.return()-- 返回给定的值并结束生成器
  8. Generator.prototype.next()-- 返回一个由yeild表达式生成的值
  9. yield* -- Generator函数内部调用另一个Generator函数

2021.11.11

41.你知道哪些攻击类型?

  1. XSS攻击:跨站脚本攻击,是一种代码注入攻击。攻击者把可执行的恶意脚本注入搭配页面中,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。

    解决方案:1. 对需要插入到 HTML 中的代码做好充分的转义;2.使用 CSP,建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击

  2. CSRF攻击:跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求,如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。比如攻击者可以通过在输入留言框内输入可发送请求的代码,留言放到网站上,这样每个人进入这个网站的时候都会执行这行代码并发送携带自己的cookie账号密码等关键信息的请求,这样攻击者就能拿到用户的信息伪造拿去登录了

    解决方案:1.同源检测,2.使用 CSRF Token 进行验证,3.限制 cookie 不能作为被第三方使用

42.实现一个函数 countFn,接收一个函数作为参数,返回一个新函数,具备调用计数功能

`1.Proxy中函数调用操作的捕捉器是apply,因此我们只需要有一个计数的属性count,然后重写apply中内容,在这里面做count的增量就行了。

这样每次调用 testCount(),都会执行到 apply 里的内容。

至于 count,不论是放在 handler 这个对象中还是放到 countFn 这个函数里面都可以:

function countFn (fn) {
    var count = 0
    let handler = {
        // count: 0,
        apply: function(target, that, args) {
            // console.log(target, that, args);
            // console.log(++this.count);
            console.log(++count);
            target.apply(that, args);
        }
    }
    return new Proxy(fn, handler);
}
function test () {
    console.log('test');
    console.log(this);
}
function test2 () {
    console.log('test2');
}
const testCount = countFn(test);
const test2Count = countFn(test2);
testCount(); // 1 'test'
testCount(); // 2 'test'
testCount(); // 3 'test'

test2Count(); // 1 'test2'
test2Count(); // 2 'test2'
test2Count(); // 3 'test2'

2.闭包

function countFn(fn){
    var count=0
    return function(){
        count++
        fn.call(this,...arguments)
        console.log(count)
    }
}
function fn1(test){
    console.log('test1',test)
}
function fn2(test){
    console.log('test2',test)
}

var test1 =countFn(fn1)
var test2 =countFn(fn2)

test1('111')
test2('111')
test1('222')
test2('222')
test2('333')
test1('333')
//test1 111 1
//test2 111 1
//test1 222 2
//test2 222 2
//test2 333 3
//test1 333 3

3.放在prototype上

Function.prototype.countFn=function(){
    var count=0;
    var fn=this
    return function(){
        count++
        fn.call(this,...arguments)
        console.log(count)
    }
}
var test1= fn1.countFn()
var test2= fn2.countFn()
test1('111')
test2('111')
test1('222')
test2('222')
test2('333')
test1('333')
//test1 111 1
//test2 111 1
//test1 222 2
//test2 222 2
//test2 333 3
//test1 333 3

`

43.Object.create(null) 和直接用字面量{}创建空对象有什么区别和好处吗

Object.create()有两个参数,第一个参数是指定的原型对象,第二个参数是可选参数,给新对象自身添加新属性以及描述器

直接创建{ }的话内部会有很多对象内置写好的原型,里面存放了对象的属性和方法,而Object.create(null)的话是以null为原型创建对象,这样创建出来的对象相对比较干净,没有那么多属性和方法,不会担心自己写的方法会和原型链上的方法名重复,可以节省hasOwnProperty带来的性能损失;如果我们是想自己在一个干净的对象里写属性方法不被原生的原型上的属性和方法影响的话,可以采取Object.create(null)这种方式

44.手写Promise.then如何保证后一个then里的方法在前一个then结束之后再执行?

我们可以将传给 then 的函数和新 promiseresolve 一起 push 到前一个 promisecallbacks 数组中,达到承前启后的效果:

  • 承前:当前一个 promise 完成后,调用其 resolve 变更状态,在这个 resolve 里会依次调用 callbacks 里的回调,这样就执行了 then 里的方法了
  • 启后:上一步中,当 then 里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 promiseresolve,让其状态变更,这又会依次调用新 promisecallbacks 数组里的方法,循环往复。。如果返回的结果是个 promise,则需要等它完成之后再触发新 promiseresolve,所以可以在其结果的 then 里调用新 promiseresolve
then(onFulfilled, onReject){
    // 保存前一个promise的this
    const self = this; 
    return new MyPromise((resolve, reject) => {
      // 封装前一个promise成功时执行的函数
      let fulfilled = () => {
        try{
          const result = onFulfilled(self.value); // 承前
          return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后
        }catch(err){
          reject(err)
        }
      }
      // 封装前一个promise失败时执行的函数
      let rejected = () => {
        try{
          const result = onReject(self.reason);
          return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
        }catch(err){
          reject(err)
        }
      }
      switch(self.status){
        case PENDING: 
          self.onFulfilledCallbacks.push(fulfilled);
          self.onRejectedCallbacks.push(rejected);
          break;
        case FULFILLED:
          fulfilled();
          break;
        case REJECT:
          rejected();
          break;
      }
    })
   }

注意:

  • 连续多个 then 里的回调方法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考上面的例子和图)
  • 注册完成后开始执行构造函数中的异步事件,异步完成之后依次调用 callbacks 数组中提前注册的回调

2021.11.12

45.事件循环代码输出题


async function async1 () { 
    console.log('1'); 
    await async2(); 
    console.log('2'); 
}
async function async2 () { 
    console.log('3'); 
}
console.log('4')
setTimeout(function () {
    console.log('5');
    Promise.resolve().then(function () { 
        console.log('6'); 
    });
}, 0);
setTimeout(function () {
    console.log('7');
    Promise.resolve().then(function () { 
    console.log('8'); 
    });
}, 0);
async1();
new Promise(function (resolve) {
    console.log('9'); 
    resolve();
}).then(function () { 
    console.log('10'); 
});
console.log('11');

答案:4->1->3->9->11->2->10->5->6->7->8(注意:微任务要在当前本轮宏任务执行完成之后再次执行)

46.类型变换代码输出题

var home = {
    address'shanghai'
}
var xrr = Object.create(home);
delete xrr.address
console.log(xrr.address);

答案:shanghai(注意:delete不能删除原型上的属性)

47.作用域相关代码输出题

var x = 1;
if (function f () { }) {
    x += typeof f;
}
console.log(x)

答案:1undefined (注意:typeof运算结果为字符串,函数声明写在运算符中,其为 true,但 放在运算符中的函数声明在执行阶段时找不到的

48.闭包代码输出题

function xrr (n, o) {
    console.log(o); 
    return {
        XRR: function (m) {
            return xrr(m, n);
        }
    }
}
const a = xrr(0); a.XRR(1); a.XRR(2); a.XRR(3);
const b = xrr(0).XRR(1).XRR(2).XRR(3);
const c = xrr(0).XRR(1); c.XRR(2); c.XRR(3);

答案:

undefined  0 0 0 
undefined  0 1 2 
undefined  0 1 1

解析:

1. a 执行过程:const a = xrr(0); a.XRR(1); a.XRR(2); a.XRR(3);

1> const a = xrr(0); 当把0传进去的时候,调用的是外层的函数xrr传入参数0,n为0,但是o没有值,打印为undefined,返回一个未执行的函数赋值给a,其中a的XRR的n为0
2> a.XRR(1) ;执行内层的函数XRR并传入1进去返回执行xrr的结果,此时xrr传入10两个参数,打印第二个参数为0,所以输出为0
3> a.XRR(2) ;执行内层的函数XRR并传入1进去返回执行xrr的结果,此时xrr传入20两个参数,打印第二个参数为0,所以输出为0
4> a.XRR(3) ;执行内层的函数XRR并传入1进去返回执行xrr的结果,此时xrr传入30两个参数,打印第二个参数为0,所以输出为0

所以结果是 undefined 0 0 0

2. b 执行过程:const b = xrr(0).XRR(1).XRR(2).XRR(3);

1> 第一次调用第一层xrr(0) 时,o 为 undefined
2> 第二次调用 .XRR(1) 时 m 为 1,此时 xrr 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层的xrr(1,0);所以 o 为 03> 第三次调用 .XRR(2) 时 m 为 2,此时当前的 xrr 函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次第一层 xrr(1,0) 时,n=1,o=0,返回时闭包了第二次的 n,所以在第三次调用第三层的 XRR 函数时,m=2,n=1,即调用第一层 xrr(21) 函数,所以 o 为 14> 第四次调用 .XRR(3) 时 m=3,闭包了第三次的 n ,同理,最终调用第一层 xrr(3,2);所以 o 为 2

所以结果为: undefined 0 1 2

3. c 执行过程:const c = xrr(0).XRR(1); c.XRR(2); c.XRR(3);

1> const c = xrr(0):调用第一层xrr(0) 时,o 为 undefined
2> 第二次调用 .XRR(1) 时 m 为 1,此时 XRR 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层的xrr(1,0);所以 o 为 0
3> 第三次调用 .XRR(2) 时 m 为 2,此时xrr闭包的是第二次执行的返回的函数XRR(内层的xrr(m=2(本次传入的2),n=1(私有值1)),所以外层的xrr(n=2,o=1))。所以打印 o 输出为 14> 第四次 .XRR(3) 时同理,但依然时调用第二次的返回值,所以最终调用第一层的 XRR(3,1),所以 o 为 1

所以结果是 undefined 0 1 1

2021.11.15

49.TCP是如何保持可靠的?

  1. 连接管理机制:tcp是面向连接的协议,也就是说收发前必须和对方建立可靠的链接,一个tcp必须经过三四才能建立起来,大大的提高了数据可靠性,使我们在数据收发前就有了交互,为正式传输打下了基础;
  2. 序列号机制,保证数据乱序处理:tcp为了保证报文传输可靠,给每个包一个序号,同时序号也保证了传输到接收端实体的包的安顺序接收,
  3. 确认重传机制:然后接受端实体对已成功收到的字节发回一个相应的确认ack,如果发送端实体在合理往返时延内未收到确认,那么对应的丢失数据将被重传.
  4. 当网络拥塞时,tcp能够减少向网络注入数据的速率和数量,缓解拥塞
  5. 流量控制:处理能力
  6. 拥塞控制:网络环境

50.后端一次性给出几万条数据该怎么渲染 ?

  1. 优先使用虚拟列表(思路就是动态控制显示区域的内容,类似于antd滚动加载无限长列表)
  2. 使用分页
  3. js缓冲器分片处理
  4. web worker(懒加载->页面会崩溃了)
  5. 尽量使用flexbox布局
  6. 使用骨架组件减少布局移动
  7. Windowing提高性能
  8. 时间分片: 1.定时器(setTimeout) 有白屏现象 ---> requestAnimationFrame -> DocumentFragment---->.....

51.前端的一次请求会经过哪些缓存?

强缓,协商缓存,启发缓存,缓存失败,四个阶段

52.promise和async await的区别

promise解决回调地狱的问题,但会有一些逻辑混乱的问题,而async await是在promise的基础上优化了的,可以解决逻辑混乱的问题,以同步的视角去看异步

53.算法题:不同路径(动态规划)

leetcode-cn.com/problems/un… image.png

2021.11.16

54.说一下 js 严格模式下有哪些不同

  1. 严格模式下无法再意外创建全局变量。
  2. 严格模式会使引起静默失败的赋值操作抛出异常
  3. 在严格模式下, 试图删除不可删除的属性时会抛出异常
  4. 严格模式要求函数的参数名唯一
  5. 严格模式禁止八进制数字语法
  6. 严格模式禁用with
  7. 严格模式下eval不再为上层范围引入新变量
  8. 严格模式禁止删除声明变量
  9. 名称eval和arguements不能通过程序语法被绑定或赋值
  10. 严格模式下,参数的值不会随argurments对象的值的改变而变化
  11. 不再支持arguments.callee
  12. 在严格模式下通过this传递给一个函数的值不会被强制转换为一个对象。
  13. 在严格模式中再也不能通过广泛实现的ecmascript扩展“游走于”javascript的栈中
  14. 严格模式下的arguements不会再提供访问与调用这个函数相关的变量的途径
  15. 在严格模式中一部分字符变成了保留的关键字。
  16. 严格模式禁止了不在脚本或者函数层面上的函数声明。

55.说一下递归和迭代的区别是什么,各有什么优缺点?

(一)定义:

递归: 递归常被用来描述以自相似方法重复事物的过程,在数学和开发中,指的是在函数定义中使用函数自身的方法;递归实际上不断地深层调用函数,直到函数有返回才会逐层的返回,递归是用栈机制实现的,每深入一层,都要占去一块栈数据区域,因此,递归涉及到运行时的堆栈开销(参数必须压入堆栈保存,直到该层函数调用返回为止),所以有可能导致堆栈溢出的错误;但是递归编程所体现的思想正是人们追求简洁、将问题交给计算机,以及将大问题分解为相同小问题从而解决大问题的动机。递归,还有个尾调用优化,尾调用优化就是如果本次调用的返回值,是子调用的返回值的话,本次调用就可以直接出栈了,不需要进行嵌套。就可以实现栈深为1的递归调用。递归从字面可以其理解为重复“递推”和“回归”的过程(递推:层层推进,分解问题;回归:层层回归,返回较大问题的解)

迭代: 是重复反馈过程的活动,其目的通常是为了接近并到达所需的目标或结果。每一次对过程的重复被称为一次“迭代”,而每一次迭代得到的结果会被用来作为下一次迭代的初始值。迭代是顺序的,不涉及调用栈操作,前面的代码不会被后面代码影响,递归是涉及到调用栈的,遵循先入栈后结束后入栈先结束原则,前面的函数调用会阻塞,要在后面的调用返回值后才能继续执行,所以迭代的好处就是栈深小,但是代码逻辑不够清晰,递归则是嵌套调用多,栈深比较大,容易爆栈,但代码结构会比较简洁,速度的话还是迭代快。迭代大部分时候需要人为的对问题进行剖析,分析问题的规律所在,将问题转变为一次次的迭代来逼近答案。迭代不像递归那样对堆栈有一定的要求,另外一旦问题剖析完毕,就可以很容易的通过循环加以实现。迭代的效率高,但却不太容易理解,当遇到数据结构的设计时,比如图表、二叉树、网格等问题时,使用就比较困难,而是用递归就能省掉人工思考解法的过程,只需要不断的将问题分解直到返回就可以了。

(二)同异点:

  1. 相同点:递归和迭代都是循环的一种。

  2. 不同点:

    (1)程序结构不同:递归是重复调用函数自身实现循环。迭代是函数内某段代码实现循环。 其中,迭代与普通循环的区别是:迭代时,循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值。

    (2)算法结束方式不同:递归循环中,遇到满足终止条件的情况时逐层返回来结束。迭代则使用计数器结束循环。 当然很多情况都是多种循环混合采用,这要根据具体需求。

    (3)效率不同:在循环的次数较大的时候,迭代的效率明显高于递归

    (4)运行过程不同,如果是循环迭代的话,这个整个就在主函数的或者在调用函数的栈空间里面,如果是递归的话,它会不断的申请函数调用的栈空间,在计算的过程中,计算一个结果,退一层栈,递归过程,在调用的时候有可能会出现栈的溢出。

    (5)理论上递归和迭代时间复杂度方面是一样的,但实际应用中(函数调用和函数调用堆栈的开销)递归比迭代效率要低。

(三)优缺点

  • 递归的

    优点: 大问题转化为小问题,可以减少代码量,同时代码精简,可读性好。

    缺点: 递归调用浪费了空间,而且递归太深容易造成堆栈的溢出。

  • 迭代的

    优点: 就是代码运行效率好,因为时间只因循环次数增加而增加,而且没有额外的空间开销。 缺点: 就是代码不如递归简洁

56.详细说一下你对 cookie、session、token 的理解

  • cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求同网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。
  • session 从字面上讲,就是会话。这个就类似于你和一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。session 也是类似的道理,服务器要知道当前发请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个“身份标识”,可以有很多种方式,对于浏览器客户端,一般都默认采用cookie的方式。服务器使用 session 把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。
  • 在web领域基于token的身份验证随处可见。在大多数使用web api的互联网公司中,token是多用户下处理认证的最佳方式。基于token的身份验证是无状态的,我们不将用户信息存在服务器或session中。这种概念解决了在服务端存储信息时的许多问题。

57.说一下 在 map 中和 for 中调用异步函数的区别

区别:

  • map会先把执行同步操作执行完,就返回,之后再一次一次的执行异步任务
  • for是等待异步返回结果后再进入下一次循环

map函数的原理:

  • 循环数组,把数组每一项的值,传给回调函数
  • 将回调函数处理后的结果push到一个新的数组
  • 返回新数组
  • map函数函数是同步执行的,循环每一项时,到给新数组值都是同步操作。

58.实现个函数柯里化

// function curry(func) {
//   //此处补全
// }
// function sum(...args) {
//   return args.reduce((a, b) => a + b)
// }

// let curriedSum = curry(sum);

// alert(curriedSum(1, 2, 3)()); // 6
// alert(curriedSum(1)(2, 3, 4)()); // 10
// alert(curriedSum(1)(2)()); // 3

答案:

function sum (...args) {
  return args.reduce((a, b) => a + b)
}
function currying (fn) {
  let args = []
  return function temp (...newArgs) {
      if (newArgs.length) {
          args = [
              ...args,
              ...newArgs
          ]
          return temp
      } else {
          let val = fn.apply(this, args)
          args = [] 
          return val
      }
  }
}

let curriedSum = currying(sum)

2021.11.17

59.object、map和weakMap的区别

  • object有默认的一些键,键只能是string。
  • map的键则可以是各种类型包括引用类型,map提供了很多api对map中的键值对进行操作比如get,set,delete,has,clear,相比较object更方便,针对频繁删改键值的操作,性能更好,
  • weakmap则类似map,但weakmap是弱引用,不增加引用计数器的值,只要其他地方没有再引用weakmap中的引用类型值,那么垃圾回收就会回收掉那个空间。

60.async原理

async 是generator的语法糖,aync封装了generator 让yeild执行变为手动执行,async会将函数返回值为promise.

61.你对于堆与栈的理解

(一)这些数据可以分为原始数据类型和引用数据类型:

栈:原始数据类型(UndefinedNullBooleanNumberString)
堆:引用数据类型(对象、数组和函数)

(二)两种类型的区别在于存储位置的不同:

  • 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;栈存的是基本类型和引用类型的指针,存在栈中存的数据会被频繁操作提高性能;栈是由操作系统自动分配释放的,存档函数的参数值和局部变量的值等。操作方式就类似数据结构的栈。堆由人工分配释放,或者运行结束os回收,类似于链表

  • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

(三)堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:

  • 在数据结构中,栈中数据的存取方式为先进后出。堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
  • 在操作系统中,内存被分为栈区和堆区:栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

62.sort的底层原理

  • V8 引擎 sort 函数只给出了两种排序分别是:根据传入的数组大小判断使用 数组长度小于等于 10 的用插入排序,比10大的数组则使用快速排序
  • 如果还要按引擎区分的话 v8就是上面说的这个,ff用的归并排序,webkit用的C++库的排序

63.前端路由模式及其原理

路由是为了需要路径被改变而页面不会重新请求刷新,路由模式有hash和history还有repleaceState,hash就是利用hashchange不会重新请求这个原理,window.addEventListener('hashchange',function() {//改变视图}),history同理,只不过history模式下路径不会有#

  • push是往history去添加一条记录
  • repleaceState是替换路径
  • replace直接替换,不会添加新的;push和replace区别就是push能返回上一页replace不能