面试题系列

面试题系列

css、html篇---------

flex原理

父元素设置 flex 子元素:

order : 子元素值越高越往后排列,越小越往前。

flex-grow : 当子盒子在父盒子内部时 ,获取到父盒子的剩余空间,增大子盒子的占比(自己的宽度加剩余空间占比)。

Flex-shrink:当子盒子一起的大小超过父盒子的大小后,超出部分进行缩小的取值比例(真实比例计算:盒子1宽度shrink**😗*盒子2宽度shrink)

flex-basis : 用来设置盒子默认基准宽度,basis和width同时存在basis会把width干掉。(flex-basis会根据内部元素和flex-grow获取的宽度,与width作比较 ,三者比较情况不同不一样)

理解 1:flex:1的逻辑*就是用flex-basis把width干掉,然后再用flex-grow和flex-shrink增大的增大缩小的缩小,达成最终的效果。

理解 2 : 子元素 flex 不写,默认 0 ,1,auto。 flex1 : 实际 flex:1 1 0% ;

Align-self :

元素居中方式

margin : auto

position(居中元素宽高固定)

Postion 配合 transfrom

flex

BFC:块级格式化上下文。

作用:处理浮动脱离盒子问题 。(bfc就是页面上的一个独立容器,容器里面的子元素不会影响外面元素,也不会影响旁边元素)

解决方案:

.bfc {
//两种方式
//不推荐方式
overflow: hidden;
//推荐方式
display: flow-root;
}

实例:

<style>
.box {
border: 1px solid red;
}
.float {
width: 100px;
height: 100px;
background-color: red;
float: left;
}
.bfc {
/* overflow: hidden; */
display: flow-root;
}
</style>
<body>
<div class="box bfc">
<div class="float"></div>
<p class="bfc">我是一段问题</p>
</div>
</body>

grid布局

script标签 defer async

  • scriptHTML暂停解析,下载JS,执行JS,在继续解析HTML

  • deferHTML继续解析,并行下载JSHTML解析完在执行JS(不用把script放到body后面,我们在head<script defer>js脚本并行加载会好点)

  • async`:`HTML`继续解析,并行下载`JS`,执行`JS`(`加载完毕后立即执行`),在继续解析`HTML ,加载完毕后立即执行,这导致async属性下的脚本是乱序的,对于 script 有先后依赖关系的情况,并不适用
    

link标签preload和prefetch

  • preload 资源在当前页面使用,会优先加载
  • prefetch 资源在未来页面使用,空闲时加载
<head>
  <!-- 当前页面使用 -->
  <link rel="preload" href="style.css" as="style" />
  <link rel="preload" href="main.js" as="script" />
</head>

js篇-----------

instanceof与typeof

总体 :instanceof判断引用类型更加精确(instanceof是判断对象是否有(Array ,Function ,Object等类的构造函数是否与对象相等), typeof判断非object类型 ,返回类型字符串

  • typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值
  • instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
  • typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的判断不清楚,比如 typeof [] 等于 object
  • 使用Object.prototype.toString判断类型更加精确
Object.prototype.toString({})       // "[object Object]"
Object.prototype.toString.call({})  // 同上结果,加上call也ok
Object.prototype.toString.call(1)    // "[object Number]"
Object.prototype.toString.call('1')  // "[object String]"
Object.prototype.toString.call(true)  // "[object Boolean]"
Object.prototype.toString.call(function(){})  // "[object Function]"
Object.prototype.toString.call(null)   //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g)    //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([])       //"[object Array]"
Object.prototype.toString.call(document)  //"[object HTMLDocument]"
Object.prototype.toString.call(window)   //"[object Window]"
  • 手写完整的类型判断
function getType(obj){
  //可以显示用typeof 判断null之外的基本数据类型
  let type  = typeof obj;
  if (type !== "object") {    // 先进行typeof判断,如果是基础数据类型,直接返回
    return type;
  }
  // object 类型应为typeof判断不清楚 ,可以使用Object.prototype.toString.call()判断
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); 
}

Promise相关

解决了之前函数回调地狱的问题

  • 使用then来链式调用,代码可读性增强
  • 三种状态 :fulfilled(成功) ,reject(失败) , pending (进行中)
    • 特点
      • 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
      • 一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果

async/await单独理解

  1. 同步语法
  2. 执行async函数,返回的是promise
  3. await相当于promisethen
  4. try catch可捕获异常(捕获 rejected 状态),代替了promisecatch
  5. await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 fulfilled ,才获取结果并继续执行,await 后续跟非 Promise 对象:会直接返回

promiseGeneratorasync/await进行比较:

  • promiseasync/await是专门用于处理异步操作的
  • Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口…)
  • promise编写代码相比Generatorasync更为复杂化,且可读性也稍差
  • Generatorasync需要与promise对象搭配处理异步情况
  • async实质是Generator的语法糖,相当于会自动执行Generator函数
  • async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案

Generator

概要:Generator 函数是 ES6 提供的一种异步编程解决方案

说明:执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

3个特征:

  • function关键字与函数名之间有一个星号

  • 函数体内部使用yield表达式,定义不同的内部状态

  • 通过next方法才会遍历到下一个内部状态

//通过yield关键字可以暂停generator函数返回的遍历器对象的状态
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloWorldGenerator();
//上述存在三个状态:hello、world、return

This问题

this 特点: 1 严格模式和非严格模式有所不同(严格模式为undefined) 2 大多数情况函数调用决定了this的值 3 this关键字是函数运行时自动生成的对象,只能在函数内部使用,总是指向调用它的对象 4 this在函数执行过程中 , 一旦确认不可修改

绑定规则

  • 默认绑定 : 非严格模式下 , 默认绑定到window全局对象 。
function func(){
  //this 指向window
}
func()
  • 隐式绑定 :函数内this 永远指向调用它的对象 ,没有就指向window或undfined
  • 显示绑定 :通过call ,apply ,bind
  • new绑定 : 指向创建的对象 ,如果有返回对象(只有返回的数据是对象才行) ,指向返回的对象
function Person(){
  return {a:1}
}
const p = new Person()
// this 指向 {a:1} 

箭头函数

  • 在编译的时候确定了this指向

优先级:

  • new绑定优先级 > 显示绑定优先级(call , bind , apply) > 隐式绑定优先级(obj.say()) > 默认绑定优先级

箭头函数

  • 箭头函数不绑定 arguments,可以使用 ...args 代替
  • 箭头函数没有 prototype 属性,不能进行 new 实例化
  • 箭头函数不能通过 callapply 等绑定 this,因为箭头函数底层是使用bind永久绑定this了,bind绑定过的this不能修改
  • 箭头函数的this指向创建时父级的this
  • 箭头函数不能使用yield关键字,不能作为Generator函数

总结:不适用箭头函数的场景

  • 场景1:对象方法(this问题)
  • 场景2:对象原型(this问题)
  • 场景3:构造函数(不能使用new)
  • 场景4:vue的生命周期和method(this问题)

js事件循环机制

文章:理解事件循环https://blog.csdn.net/weixin_45730243/article/details/125673846

文章:理解事件循环https://zhuanlan.zhihu.com/p/618003341

  • 浏览器提供给js的线程是单线程的,在同步任务执行的同时,js线程并不会(阻塞),而是在等待异步任务准备的同时,JS 引擎去执行其他同步任务,等到异步任务准备好了,再去执行回调。
  • 事件循环是由一个队列(微任务,宏任务)组成的,异步任务的回调遵循先进先出,在 JS 引擎空闲时会一轮一轮地被取出,所以被叫做循环。

微任务宏任务

宏任务微任务
是否重新渲染页面不会
是否需要其他异步线程的支持需要不需要
宏任务与微任务发起者宿主(node、浏览器)js引擎
具体事件script、setTimeout/setInterval、postMessage、MessageChannel、UI rending/UI事件、setImmediate与I/O(Node.js环境)Promise.then()、await后面的代码、 MutaionObserver(html5新特性,监视 DOM 变动。DOM 的任何变动) 、proxy、process.nextTick(Node.js)
  • 同步任务 > 微任务 > 宏任务 :微任务和宏任务执行会放到消息队列,等待同步任务执行完毕之后执行。
  • 微任务::process.nextTick(node 独有)、Promise.then()、Object.observe、MutationObserver
  • 宏任务:script(整体代码、setTimout、setInterval、setImmediate(node 独有)、requestAnimationFrame(浏览器独有)、IO、UI render(浏览器独有)
setTimeout(() => {
    console.log("1")
    new Promise((resolve) => {
        console.log("2")
        resolve()
    }).then(res => {
        console.log("3")
    })
})
new Promise((resolve) => {
    setTimeout(() => {
        console.log("4")
    })
    resolve()
}).then(res => {
    console.log("5")
    setTimeout(() => {
        console.log("6")
    })
})
setTimeout(() => {
    console.log("7")
})

//5 1 2 3 4 7 6

1-1 1-3 1-5
1-4
1-2

2-1 2-3 2-5
2-4
2-2
setTimeout(() => {
    console.log(1);
}, 0);
async function test1() {
    console.log(2)
}
async function test2() {
//await后面一般跟一个Promise对象,如果后面不是Promise对象就会被转成Promise对象,await会暂停当前async //function的执行,等待await表达式后面的Promise处理完成后才会继续执行
    await test1() // 后面执行代码 ,相当于.then 
    console.log(3)
}
test2()

new Promise((resolve) => {
    console.log(4);
    resolve(5);
    new Promise((resolve) => {
        console.log(6);
        resolve(7);
    }).then((res) => {
        console.log(res);
    });
}).then((res) => {
console.log(res);
});
//2463751
  • 宏任务执行包含宏任务 :先把一个宏任务内容(包括微任务)执行完成 , 再执行下一个宏任务
  • 微任务包含宏任务:先统一把所有的微任务执行完成再执行宏任务队列
  • 如果有多个
setTimeout(() => {
        console.log("1-1")
        new Promise((resolve) => {
            console.log("1-2")
            resolve()
        }).then(res => {
            console.log("1-3")
        })
    })
    setTimeout(() => {
        console.log("1-4")
        new Promise((resolve) => {
            console.log("1-5")
            resolve()
        }).then(res => {
            console.log("1-6")
        })
    })
//1-1 1-2 1-3 1-4 1-5 1-6
    new Promise((resolve) => {
        resolve()
    }).then(res => {
        console.log("2-1")
        setTimeout(() => {
            console.log("2-2")
        })
    })
    new Promise((resolve) => {
        resolve()
    }).then(res => {
        console.log("2-3")
        setTimeout(() => {
            console.log("2-4")
        })
    })
//2-1 2-3 2-2 2-4

原型链理解

1、原型链可以理解为原型和链。

2、首先说一下原型:

  • 每一个函数都拥有一个显示原型 ,以属性的形式存在 ,prototype(一个对象)。
  • 每一个对象都拥有隐式原型 ,proto(一个对象)
  • 在js里面函数也是对象 ,所以函数也有一个隐式原型

3、在说下链:

  • 一个对象有他自己本身的属性 ,和原型属性 , 当对象.属性的时候,优先获得本身属性,如果没有那就会往原型链去查找 ,而每一个原型又是一个对象 , 对象又有原型 ,所以会持续往下层寻找,直到找到为止!这个就是原型链。

4、如果使用一个构造函数 或者说类 ,实例一个对象那么

  • 构造函数的显示原型 === 对象的隐试原型

5、原型链的终点是Object类或者说构造函数, Object的显示原型的隐式原型是null ,原型链的终点

Object.prototype.__proto__ =  null 

Object这个构造函数作为一个对象来说,他是由一个顶层类(或者说构造函数创建的)Function创建的。

  • 一切对象都继承Object这个顶层对象
  • 一切函数对象都是继承直Function构造函数
  • Object直接继承至Function

作用域链

概念:子级可以向父级查找变量,逐级查找,找到为止。

  • 全局作用域 : 全局作用域页面打开时创建 ,销毁时关闭
    • 直接编写在 script 标签之中的JS代码,都是全局作用域;
    • 引用单独js文件
    • 有一个全局对象window(
      • 前端所有的全局作用域变量作为window的对象属性保存
      • 前端所有的全局作用域方法作为window的对象方法保存
  • 局部作用域(函数作用域)
    • 函数调用的时候创建, 只在函数作用域有效 ,每调用一次创建一个新作用域
  • 作用域链:内层作用域->外层作用域->…->全局作用域

function类继承

  • 原型链继承 : 存在问题?所有属性使用原型链继承,创建多个对象 , 修改原型上面的属性 ,都会修改!!!
function Person(){
  this.name = "test"
}
 function Person1(){
  this.arr = [1,2]
}
 Person.prototype = new Person1()
 const p1 = new Person()
 const p2 = new Person()
 //存在问题
 p1.arr.push(3)
 console.log(p1 ,p2)
  • 构造函数继承(借助 call): 存在问题 ? 属性可以私有 , 但是无法继承父类原型链属性。
function Person(){
  this.name = "test"
 }
 //父类的原型链属性 ,无法被继承
 Person.prototype.say=function(){
  console.log("我是"+this.name)
 }
 function Person1(){
  Person.call(this)
  this.age = 20
 }
 const p1 = new Person1()
 p1.say() // 报错
  • 组合继承 :存在问题?方式一和方式二的问题都解决了,但是从上面代码我们也可以看到父构造函数执行了两次,造成了多构造一次的性能开销。
function Person(){
      this.name = "test"
 }
 //父类的原型链属性 ,无法被继承
 Person.prototype.say=function(){
  console.log("我是"+this.name)
 }
 function Person1(){
  Person.call(this) // 调用一次
  this.age = 20
 }
 Person1.prototype = new Person() //调用2次
 Person1.prototype.constructor = Person1 //手动挂载自己的构造器 ,因为上面把自己的构造器覆盖了
 const p1 = new Person1()
 p1.say() // 报错
 console.log(Person1.prototype )
  • 原型式继承 :主要借助Object.create方法实现普通对象的继承 存在问题?Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能
    • Object.create使用现有的对象来提供新创建的对象的__proto __,let obj = Object.create(ob) , 那么obj.proto ==ob
// 现有的对象来提供新创建的对象的__proto__
const Obj = {
  name:"test",
  arr:[1,2]
}
const o1 = Object.create(Obj)
const o2 = Object.create(Obj)
o1.name ="haha"
o1.arr.push(3) //都修改了 ,和原型链继承类似
console.log(o1,o2)
  • **寄生式继承 ** 缺点同上!!
let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};
function clone(original) {
    //添加原型属性
    let clone = Object.create(original);
  	//额外添加属性
    clone.getFriends = function() {
        return this.friends;
    };
    return clone;
}
let person5 = clone(parent5);

console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]
  • 寄生组合式继承
// 父构造函数
function Parent() {
    this.name = 'parent';
    this.play = [1, 2, 3];
}
// 父构造函数原型
Parent.prototype.getName = function () {
    return this.name;
}
//子构造函数
function Child() {
    //调用子构造函数
    Parent.call(this);
    this.friends = 'child';
}
// 处理原型属性
function clone (Parent, Child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    Child.prototype = Object.create(Parent.prototype); //相当于组合式继承少调用了一次构造函数
  	///手动挂载自己的构造器 ,应为上面把自己的构造器覆盖了
    Child.prototype.constructor = Child;
}
clone(Parent, Child);
let person = new Child();
console.log(person); 
  • extends :实际采用寄生组合式继承 ,使用babel进行传化 ,无缺点

防抖节流函数

  • 防抖(debounce) : 重复执行 , 执行最后次 (防抖=回城)
    • 实例:防止重复请求 ,抽奖系统 ,搜索输入框输入值(提交按钮 , 手机号验证 ,邮箱验证)
function debounce(fn, delay = 2000) {
      //保存上一次定时器
      let timer = null
      //真正的执行函数
      const _debounce = (...args) => {
          //取消上一次定时器
          if (timer) clearTimeout(timer)
          //重新定义,延迟执行
          timer = setTimeout(() => {
              //调用回调事件
              fn.apply(this,args)
          }, delay)
      }
      return _debounce
  }
  //使用
  document.getElementById("root").onclick = debounce(() => {
      console.log("执行了")
  })
  • 节流(throttle)函数 :固定频率去执行函数**(节流=技能)**
    • 实例 :监听页面滚动 ,鼠标移动事件 ,用户频繁点击 ,
function throttle(fn, delay) {
  //第一次执行时间
  let previous = 0;
  // 使用闭包返回一个函数并且用到闭包函数外面的变量previous
  return function () {
      let args = arguments;
      let now = new Date();
      // 当前时间 - 上一次执行时间 大于 间隔时间
      if (now - previous > delay) {
          fn.apply(this, args);
          //保存这一次执行时间 , 下一次执行判断使用
          previous = now;
      }
  }
}
//使用
document.getElementById("root").onclick = throttle(() => { console.log("执行了") }, 3000);

手写深拷贝

  • 浅拷贝 : 拷贝对象第一层 , 若第一层属性值为引用类型无法拷贝

    • Object.assign({},obj)
    • Array.prototype.slice(), Array.prototype.concat()
    • 拓展运算符{…obj}
  • 深拷贝 :深层拷贝

    • _.cloneDeep() : const _ = require(‘lodash’); (漏得洗)
    • jQuery.extend()
    • JSON.stringify() : 会忽略undefinedsymbol函数、和引用类型数据
    • 手写循环递归
    function deepClone(obj, hash = new WeakMap()) {
      if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
      if (obj instanceof Date) return new Date(obj);
      if (obj instanceof RegExp) return new RegExp(obj);
      // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
      if (typeof obj !== "object") return obj;
      // 是对象的话就要进行深拷贝
      if (hash.get(obj)) return hash.get(obj);
      let cloneObj = new obj.constructor();
      // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
      hash.set(obj, cloneObj);
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          // 实现一个递归拷贝
          cloneObj[key] = deepClone(obj[key], hash);
        }
      }
      return cloneObj;
    }
    

call apply bind

  • 三者都可以改变函数的this对象指向
  • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefinednull,则默认指向全局window
  • 三者都可以传参,但是apply是数组,而call是参数列表,且applycall是一次性传入参数,而bind可以分为多次传入
  • bind是返回绑定this之后的函数,applycall 则是立即执行

造成内存泄露的方式

  • 意外全局变量
function func(){
  a = 1 //意外的全局变量 , 不会销毁
}
  • 闭包
  • 定时器或者监听函数未取消(定时器 , window)
  • 没有清理的dom元素引用(元素被删除,由于一直保留了对这个元素的引用)

为什么需要堆栈

在 JavaScript 中,引擎需要用栈来维护程序执行时的上下文状态(即执行上下文),如果栈空间大了的话,所有数据存放在栈空间中,会影响到上下文切换的效率,从而影响整个程序的执行效率,所以占内存大的数据会放在堆空间中,引用它的地址来表示这个变量。

重绘(重排)与回流

总结: 回流必定会引起重绘,重绘一定不会引起回流。 回流会导致页面重排,影响性能。

回流:dom节点增删 ,元素位置变化 , 尺寸变化 ,窗口大小。(至少一次回流 , 初次渲染)

重绘:影响外观风格改变是重绘 。颜色 ,样式改变。

js线程相关

注意:JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript 引擎有多个线程,单个脚本只能在一个线程上运行(称为主线程),其他线程都是在后台配合。为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。

闭包

概念:闭包就是能够读取其他函数内部变量的函数。(一个函数中包含另一个函数)

文章:https://www.jianshu.com/p/9de6f1e8a7fe

闭包形式:

  • 1 函数返回值为函数
function a() {
	var num = 0;
	function b() {
		console.log(++num);
	};
	return b;
};
var c = a();
c() // 1 
c() //2 //下次再调用c()的时候num的值在内存中保存,所以每次都是在原有的基础上加1
// 全局变量c获取使用到了局部作用域a的内部变量,这是最常见的形式
#####
function two() {
	var a = 1;
	return function() {
		a++;
		console.log(a);
	};
};

two()() //2
two()() //2 //每次都是调用two()都会重新执行一次,返回一个新的函数 , 再调用

  • 2 内部函数赋给外部变量
let num;
function foo() {
  const _num = 18;
  function bar() {
    return _num;
  }
  num = bar;
}
foo();
console.log(num()); // 18
  • 3 通过立即执行函数行成独立作用域,保存变量(es6之后使用let,const替代)
for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i);
  }, i * 1000);
}
// 上述代码的预期输出结果是每隔一秒按顺序输出12345。
// 根据前面讨论过的事件循环机制,定时器任务会在同步任务执行完毕后再执行。因此此时的i已经变成6 会直接输出5个6(为什么是6?因为:循环流程是先定义 -> 加加 -> 判断是否符合 -> ok在执行循环内部)
// 解决这一问题的关键就是每次循环形成一个独立作用域,这样定时器中的操作执行时会访问对应作用域的变量。
#闭包解决
for (var i = 1; i <= 5; i++) {
//  包一层立即执行函数  并传入i  由于内部操作用到了i 因此会形成闭包
    (function (i) {
      setTimeout(function timer() {
        console.log(i);
      }, i * 1000);
    })(i)
  • 一般setTimeout的第一个参数是个函数,但是不能传值。如果想传值进去,可以调用一个函数返回一个内部函数的调用,将内部函数的调用传给setTimeout。

闭包的优缺点

  • 外部可以访问函数内部变量。

  • 让函数内部变量一直保留在内存中

  • 形成独立作用域。

  • 闭包的应用—柯里化函数

高阶函数

  • 接收参数为函数

  • 返回值为函数

柯里化(一种特殊的高阶函数)

  • 柯里化函数的优势就是参数复用
function sum(a){ 
  return (b)=>{
    return c=>{
      return a+b+c
    }
  }
}
调用
sun(1)(2)(3)

使用场景

  • 表单数据校验
//未使用柯里化
function check(targetString, reg) {
    return reg.test(targetString);
}
check(/^1[34578]\d{9}$/, '14900000088');
check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com');
//使用柯里化
function curring(reg) {
  return (str) => {
    return reg.test(str);
  };
}
var checkPhone = curring(/^1[34578]\d{9}$/);
var checkEmail = curring(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
console.log(checkPhone("183888888")); // false
console.log(checkPhone("17654239819")); // true
  • 请求工具函数封装(api封装)
  • 请求状态码处理
  • 函数柯里化求和
function total(...args) {
    let sum = args.reduce((total, item) => total += item, 0)
    const func = (sum) => total(...[sum, ...args])
    func.toString = () => sum.toString()
    return func
}
console.log(total(1)(2)(3) + "") // 6
console.log(total(1)(2)(3) + 1) //61

Js垃圾回收机制

概念词:

  • GC : 垃圾回收机制的简写。可以找到内存中的垃圾,并释放和回收垃圾。

  • 可达对象 : 可以访问到的对象就是可达对象(可以通过引用、作用域链查找到)

  • 垃圾回收方式 : ⑴引用计数 , ⑵标记清除法 ,⑶标记清理法

  • 概念 :在JS中,我们创建变量的时候,JS引擎会自动给对象分配对应的内存空间,不需要我们手动分配,当代码执行完毕的时候,JS引擎也会自动地将你的程序,所占用的内存清理掉。

  • 引用计数法 : 内部通过对数据的引用计数 , 来判断该数据的引用数是否为 0 ,决定是否为一个垃圾数据 , 如果为 0 ,GC开始工作 ,讲其内存空间进行释放再使用!

    • 优点:
      • 1 发现垃圾及时回收
      • 2 最大程度减少程序暂停(内存爆满的时候会去找那些引用数值为0的对象释放其内存)
    • 缺点:
      • 1 无法回收循环引用的对象
      • 2 维护引用(计数)开销大
  • **标记清除法:**将整个垃圾回收操作分为三个阶段:1 遍历所有的对象找到活动对象 ,进行标记操作 2 遍历所有的对象,找到那些没有标记的对象进行清除 3 回收相应空间

    • 优点:
      • 可以回收循环引用的对象空间
    • 缺点:
      • 容易产生碎片化空间(清除的空间不是连续的 , 需要整理合并),浪费空间
      • 不能立即回收垃圾对象(回收慢)。
  • 标记整理 : 和标记清除一样,在V8中也会被频繁使用:1,标记整理可以看做是标记清除的增强; 2,标记阶段的操作和标记清除一致;3,清除阶段之前会先执行整理,移动对象位置,使得他们在地址上是一个连续的空间

    • 优点:
      • 减少标记清除碎片空间
    • 缺点
      • 不会立即回收对象
  • V8对GC的优化 :分代式回收

    • 新生代垃圾回收 : 临时分配的内存,存活时间短 (并行回收)
    • 老生代垃圾回收 :回收是常驻内存,存活时间长 (增量标记与惰性回收)

注意:闭包不是内存泄露,闭包的数据是不可以被回收的

内存泄露场景

  • dom监听事件 , 组件销毁未移除
  • 组件定时器设置 , 组件销毁未移除

扩展:weakmapweakset 都是弱引用,不会阻止垃圾回收机制回收对象。

const map = new Map() 
function fn1() {
  const obj = { x: 100 }
  map.set('a', obj) // fn1执行完 map还引用着obj
}
fn1()
const wMap = new WeaMap() // 弱引用
function fn1() {
  const obj = { x: 100 }
  // fn1执行完 obj会被清理掉
  wMap.set(obj, 100) // weakMap 的 key 只能是引用类型,字符串数字都不行
}
fn1()

const let var

①重复声明 ②变量提升 ③暂时性死区 ④块级作用域 ⑤window对象的属性和方法(全局作用域中)

  • var允许重复声明,let、const不允许
  • var有变量提升 , let 、 const 没有
  • var没有暂时性死区 , let const 有
let a = 1
let b = 2 
function func(){
  console.log(b)
  console.log(a)
  let a = 3
}
func()
//1 和 Cannot access 'a' before initialization
  • 全局作用域中,var声明的变量和function声明的函数,会自动变为window对象属性或方法,但const和let不会
let a = 1
console.log(a === window.a)
const b = 1
console.log(b === window.b)
var c = 1
console.log(c === window.c)
  • var没有块级作用域,let和const有块级作用域
//let
for(let i = 1 ;i<4;i++){
    console.log(i)
}
//1,2,3 
console.log(i) //i 没有定义
//var
for(var i = 1 ;i<4;i++){
    console.log(i)
}
//1,2,3
console.log(i) //4

模块化

  • 解决命名冲突
  • 提供复用性
  • 提高代码可维护性

模块化进化路线

  • 1 - 立即执行函数 (解决了命名冲突、污染全局作用域的问题)
  • 2 - AMD 和CMD
// AMD
define(['./a', './b'], function(a, b) {
  // 加载模块完毕可以使用
  a.do()
  b.do()
})
// CMD
define(function(require, exports, module) {
  // 加载模块
  // 可以把 require 写在函数体的任意地方实现延迟加载
  var a = require('./a')
  a.doSomething()
})
  • CommonJS (最早在node环境使用 ,目前使用广泛 ,webpack(工程化)中能见到他)
// a.js文件
module.exports = {
    a: 1
}
//或
exports.a = 1

// b.js文件
var module = require('./a.js')
module.a // -> log 1
 
  • ES Module (会编译成 require/exports来执行的)

Vue篇-------

为什么Vue3 比vue2快

  • proxy响应式:深度监听,性能更好(获取到哪一层才触发响应式get,不是一次性递归)
  • PatchFlag 动态节点标记 (只比较动态节点)
  • 将静态节点,提升定义到父作用域,缓存起来。[空间换时间]
  • CacheHandler 事件缓存(把节点的时间缓存到一个数组)
  • Tree-shaking 根据模板的内容动态import不同的内容,不需要就不import

深入Vue(VM与VC)

理解vm与vc

  1. Vue.extend({}),创建一个组件,组件的本质是一个VueComponent构造函数。

  2. cosnt s = Vue.extend({options})可以简写为const s = {options}。简写也调用了Vue.extend。

  3. 写组件标签时,就会创建school组件实例对象,相当于new VueComponent({options})。(解析标签是调用)

  4. 每次调用Vue.extend,返回的都是一个新的VueComponent。内部sub变量接收VueComponent构造函数,返回sub。

    //Vue.extend源码
    Vue.extend = function (extendOptions: Object): Function {
     // 定义子构造函数 Sub
        const Sub = function VueComponent (options) {
          this._init(options)
        }
      //...........
      return Sub
    }
    
  5. vc是可复用的vm。el是vm特有的。

  6. VueComponent.prototype.proto === Vue.prototype所以vc能够访问的Vue原型上的方法。

代码理解

<body>
    <div id="root"></div>
    <script>
        // 可以省略 Vue.extend() , 应为当模版解析vue标签的时候,会自动调用Vue.extend生成组件的构造函数并创建组件对象VC
        var Btn = {
            template: '<p>{{name}}</p>',
            data: function () {
                return {
                    name: "我是btn组件"
                }
            }
        }
        // 创建一个构造函数 ,用该构造函数创建VC(组件对象)
        var App = Vue.extend({
            template: '<div><h1>{{title}}</h1><p @click="handle">{{num}}</p><btn/></div>',
            components: { btn: Btn },
            data: function () {
                return {
                    title: "app容器组件",
                    num: 10
                }
            },
            methods: {
                handle() {
                    this.num++
                }
            }
        })
        //创建vm实例对象
        const vm = new Vue({
            // 写法一
            el: "#root",
            // data: {
            //     age: 999,
            //     name: "李四"
            // },
            // template: '<div><btn/></div>',
            // methods: {
            //     handle() {
            //         this.age++
            //     }
            // },
            // components: {
            //     Btn: Btn
            // }
            render: h => h(App)
        })
        //写法二
        vm.$mount("#root")
    </script>
</body>

react篇-----

fiber

  • 组件树转为链表,可分段渲染
  • 渲染时可以暂停,去执行其他高优先级任务,空闲时在继续渲染(JS是单线程的,JS执行的时候没法去DOM渲染)
  • 如何判断空闲?requestIdleCallback

react setState优化

function handleClick() {
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
}
//与
function handleClick() {
  setAge(a => a + 1); // setAge(42 => 43)
  setAge(a => a + 1); // setAge(43 => 44)
  setAge(a => a + 1); // setAge(44 => 45)
}

a => a + 1 是你的更新函数。它接收一个待改变的 state,然后在函数体计算返回下一个 state。

React 把更新函数放入一个队列中。然后在下一次 render 时,更新函数将会以相同的顺序被调用。

理解 useRef、useMemo、useCallback

useRef 可用来存储一个引用值(不会受 re-render 影响),也可用来获取 dom 节点。

useMemo 用来缓存一个值。当依赖项为空数组时,缓存值永远不会变。当有依赖性时,每次 re-render 如果依赖改变,那么将重新执行函数,将新的函数返回值作为缓存的数据。

useCallback 是 useMemo 的语法糖,相当于返回一个函数。

  const fn1 = useCallback(() => {
    console.log(123);
  }, [])

  const fn2 = useMemo(() => () => {
    console.log(123);
  }, [])

使用memo注意

  • 传递给子组件的函数 和 数据 分别使用useCallback 和useMemo包裹一下 , 应为会重新定义 , 地址改变 , 子组件也会重新刷新。

业务篇-------

代码codeView

代码标准

  • 注释类
    • 公共函数、样式加上注释
    • 敏感注释处理
    • 没用到的方式和生命周期, 删掉
    • 测试代码,旧代码
    • 核心业务描述等
  • 风格类
    • 如果特殊情况. 避免使用行类样式.
    • 遵循基本标签嵌套规则
    • css使用sass
  • js代码
    • 方法使用 ,比如用map必须要返回值
    • 必要时使用模块化. 独立组件, 减少单文件代码量
  • git hooks – pre commit
    • 新项目统一使用ESLint作为代码规范检测工具,每次Git commit会判断代码是否符合规范,只有符合规范的才能被提交

切片上传+断点续传

  1. 获取上传文件
  2. 文件切片后存入数组 fileChunkList.push({ file: file.slice(cur, cur + size) });
  3. [断点续传] 生成文件hash [“文件hash值+当前切片索引值"] + 数据库判断已经上传的切片
  4. 根据文件切片列表生成请求列表
  5. 并发请求
  6. 待全部请求完成后发送合并请求

项目优化方案

  1. 路由懒加载:原理ES6的动态模块加载 ,通过路由懒加载 ,项目首页资源大幅度压缩 , 打开效率更高。

  2. 组件懒加载: 比如弹窗组件。react的和路由懒加载一样 ,使用组件时需要使用Suspense包裹。

  3. 异步组件

  4. 合理使用treeShaking : 原理 基于ES6的模块化特性 ,分析导入import ,只对export导出的变量生效, 无法通过静态分析判断一个变量是否被使用。

  5. 骨架屏:缩短白屏时间 。

  6. 长列表虚拟滚动。

  7. WebWork优化长js任务 ,或者处理耗时计算任务

  8. js的加载方式

    • async(js资源与Dom没有依赖关系 ,如埋点)
    • defer(js资源有依赖关系 ,加载完执行也会有先后顺序)
    • Type = module, Vite就是利用浏览器支持原生es module ,开发时可以跳过打包 ,直接启动服务,编译代码
    • link preload :渲染前加载 ,不会阻塞渲染。
    • Link prefetch : 空闲是加载并缓存,将来使用。
  9. 图片的优化:

    • 图片动态裁剪 :使用阿里云或七牛云 ,图片url后面添加参数
    • 图片懒加载:1 、 先使用data-xxx属性绑定图片链接 2 、判断图片在屏幕可视区域内在加载
    • 使用svg效果好 , 可以修改颜色样式等
    • 使用小图使用base64 (图片转base64会变大 ,与浏览器请求次数来说,更有优势),写入html 减少http请求
  10. 打包开启gzip

  11. 打包代码分割(Code Splitting) + 配和异步组件实现

    • 1、项目包含第三方依赖库以及自己写的代码,打包出的文件会比较大,在用户访问系统的时候,由于请求的资源比较大,所以会响应的比较慢,造成页面渲染缓慢,影响用户体验。

      代码分割以后,chunk会相应的变小,用户访问时,只需返回此页面相关chunk(块的意思),再加上浏览器的并行请求策略,从而加快系统响应速度,优化用户体验。

      2、由于将第三方依赖库和自己写的代码打包到了一起,一旦我们修改了其中的一部分代码,整个chunk(包括第三方依赖库,即使它没有发生任何变化)都会重新打包,生成带有新的hash值的chunk。原本在浏览器已经缓存了的chunk,会因为hash的变更,不会再使用之前缓存的chunk,而重新请求新的chunk。简言之,一处代码变更,要重新请求所有资源,缓存效果差。

      代码分割以后,就可以将比较大的第三方依赖库分离到单独的chunk中,以后即使用户修改业务代码,只要不改变此库的版本或库代码,在浏览器端的此chunk的缓存会一直有效。剩余的比较小的第三方依赖库打包到同一个chunk中,将依赖库与业务代码彻底分离。由于更改依赖库的版本的概率比较小,所以可以有效利用缓存,提升响应速度。

    • 使用插件 : SplitChunksPlugin

  12. 缓存组件:vue keepalive 、 react使用import {KeepAlive} from ‘react-activation’

vdom和diff算法

  • vdom的核心概念很重要:hvnodepatchdiffkey

  • vdom存在的价值更重要,数据驱动视图,控制dom操作

  • diff算法:新旧 vnode 对比,得出最小的更新范围,最后更新DOM

浏览器篇—

浏览器存储

cookie : 一般由服务器生成 , 可以设置有效时间 ,大小4kb ,header协大 , 参与请求,不建议用来作为存储

localStorage : 不清理一直存在 , 大小5M

sessionStorage :页面关闭清理 , 大小5M

indexDB :

  • 浏览器提供的本地数据库
  • 属于非关系型数据库(键值对形式)
  • 存储空间大(无限)
  • 同源限制:每一个数据库对应创建它的域名。
  • 异步(不会锁死浏览器)
  • 支持事务(一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况)

cookie的安全性

属性作用
value如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识
http-only不能通过 JS 访问 Cookie,减少 XSS 攻击
secure只能在协议为 HTTPS 的请求中携带
same-site规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击

Service Worker 也可以实现缓存

W3C 组织早在 2014 年 5 月就提出过 Service Worker 这样的一个 HTML5 API ,主要用来做持久的离线缓存。service worker是浏览器的一个高级特性,本质是一个web worker,是独立于网页运行的脚本。 web worker这个api被造出来时,就是为了解放主线程。因为,浏览器中的JavaScript都是运行在单一个线程上,随着web业务变得越来越复杂,js中耗时间、耗资源的运算过程则会导致各种程度的性能问题。 而web worker由于独立于主线程,则可以将一些复杂的逻辑交由它来去做,完成后再通过postMessage的方法告诉主线程。 service worker则是web worker的升级版本,相较于后者,前者拥有了持久离线缓存的能力。

Service Worker的特点

  • 浏览器背后的独立线程、在后台运行的脚本
  • 被install后就永远存在,除非被手动卸载
  • 必须是https的协议才能使用。不过在本地调试时,在http://localhosthttp://127.0.0.1 下也是可以跑起来的。
  • 不能直接操纵dom:因为sw是个独立于网页运行的脚本。
  • 可拦截请求和返回,缓存文件。sw可以通过fetch这个api,来拦截网络和处理网络请求,再配合cacheStorage来实现web页面的缓存管理以及与前端postMessage通信。

浏览器缓存机制

url:https://blog.csdn.net/qq_46658751/article/details/123433335

  1. Service Worker : 当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据
  2. Memory Cache : 内存缓存 :主要包含的是当前页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等,打开页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存。
  3. Disk Cache : 磁盘缓存
  4. Push Cache (推送缓存) :Push CacheHTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。
  5. 网络请求 :如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。
    • 强缓存 :不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。
      • 设置header :
      • Expires ( HTTP/1) :缓存过期时间(是一个时间,存在客户端与服务器时间备不一致情况) , Last-modified结合使用。(判断该时间内 , 无需要发起请求)
      • Cache-Control(HTTP/1.1 ,优先级高)
      • public:表示响应可以被客户端和代理服务器缓存。
      • private: 表示响应只可以被客户端缓存。
      • max-age=xxx:缓存xxx秒就过期,需要重新请求(是一个时间段 , 比Expires好)。
    • 协商缓存

网络请求缓存判断流程:

发送一次网络请求

  1. 判断不存在该缓存结果和缓存标识,则无缓存,直接向服务器发起一次请求 ,并缓存结果数据和标识(last-Modified或eTag)
  2. 存在该缓存结果和缓存标识,且该结果没有还没有失效,强制缓存生效,直接返回缓存结果 。
  3. 存在该缓存结果和缓存标识,但是结果已经失效,强制缓存失效则使用协商缓存
  4. 强制缓存失效后开启协商缓存 , 浏览器携带缓存标识向服务器发起请求 ,服务器根据标识判断客户端的缓存当前是否还可以用!
  5. 如果还可以用 ,协商缓存生效,返回 304 状态码 和 not Modified , 浏览器依旧获取浏览器缓存结果
  6. 如果不可以用 , 协商缓存失效,返回请求结果 和 缓存规则及标识 , 浏览器直接获取请求结果 , 并缓存结果和标识。
  7. 怎么判断协商缓存失效 , 两种方式
    • Last-Modified(第一次请求,服务端会给浏览器端缓存): 下次请求浏览器检测到有 Last-Modified 字段 , 把这个字段放在if-Modified-Since ,去服务端请求 , 服务端判断请求数据是否还生效。(有弊端 :没有对文件进行修改,可能还是会造成 Last-Modified 被修改)
    • **Etag **: 服务端生成的hash值判断 , 下次请求把这个hash值放到 If-None-Match字段里 , 服务端判断当前hash是否一致 ,然后确定协商缓存是否失效 , 这种方式更加准确,但是性能要差点(因为取hash).

实际场景应用缓存策略

  • 频繁变动的资源::Cache-Control:no-cache 每次都请求 。
  • **不长变动的资源( jquery-3.3.1.min.js, lodash.min.js ,图片,css):**Cache-Control: max-age=31536000(一年)

Http1/http1.1/http2.0+HTTPS

目前主要:HTTP1.1 是当前使用最为广泛的HTTP协议

http1

  • 只有GET, POST 和 HEAD
  • 缓存处理 :if-Modified-since + expires缓存判断标准
  • 网络优化:HTTP1.0中,浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,存在一些浪费带宽的现象,(每次发送完整请求数据)不支持断点续传 。

http1.1

  • 新增OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT
  • 缓存处理 :Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略(多个缓存策略,最常用Etag)
  • 网络优化:请求头引入了range头域,它允许只请求资源的某个部分
  • 错误状态码新增。
  • 长连接 :HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理。一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建新连接的缺点。通过设置http的请求头和响应头,保证本次数据请求结束之后,下一次请求仍可以重用这一通道,避免重新握手。

http2.0

  • 采用二进制格式解析 :HTTP1.x的解析是基于文本(超文本传输协议)。基于文本协议的格式解析存在天然缺陷。
  • 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行请求。
  • 使用报头(header)压缩,通讯双方各自缓存一份header fields表,既避免了重复header的传输,又减小了需要传输的大小
  • 服务器推送 (一个通道持续向客户端发送数据资料)(实际中使用websocket

https

  • 在传输之前客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。

  • Http运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS(使用了非对称加密,对称加密以及HASH算法)之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的(防劫持)。

  • 端口号不同:http 80 , https 443

三次握手

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层(http是应用层)通信协议,采用三次握手建立连接,四次挥手终止连接。

HTTP (超文本传输协议)WWW标准 ,建立在TCP协议上的一种应用。

主要是浏览器与服务器建立连接 ,相互之间发送报文通信的过程 ,主要目的就是确认相互间能否接受到手机并确认对方接受的数据。
重复总结

  • HTTP1.0
    • 最基础的HTTP协议
    • 支持基本的GETPOST方法
  • HTTP1.1
    • 缓存策略 cache-control E-tag
    • 支持长链接 Connection:keep-alive 一次TCP连接多次请求
    • 断点续传,状态码206
    • 支持新的方法 PUT DELETE等,可用于Restful API写法
  • HTTP2.0
    • 可压缩header,减少体积
    • 多路复用,一次TCP连接中可以多个HTTP并行请求
    • 服务端推送(实际中使用websocket

连环问:HTTP协议和UDP协议有什么区别

  • HTTP是应用层,TCPUDP是传输层
  • TCP有连接(三次握手),有断开(四次挥手),传输稳定
  • UDP无连接,无断开不稳定传输,但效率高。如视频会议、语音通话

浏览器原理(加载一个页面流程)

资料相关

blibli

1、思学堂:https://space.bilibili.com/695517853

CSDN

1、前端啊

https://zhuanlan.zhihu.com/p/574913236 知乎

https://blog.csdn.net/weixin_61029090/article/details/130187720 面试题总结2 还可以

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,我可以为您介绍一下MySQL面试题系列。MySQL是一种常用的关系型数据库管理系统,广泛应用于Web应用程序的后台数据存储。以下是一些常见的MySQL面试题及其答案: 1. 什么是数据库事务? 数据库事务是指一组数据库操作,它们被视为一个单独的工作单元,要么全部执行成功,要么全部回滚到初始状态。 2. 什么是ACID属性? ACID是指数据库事务应具备的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。 3. 什么是索引?为什么使用索引? 索引是一种数据结构,用于加快数据库中数据的检索速度。它可以提高查询效率,减少数据扫描的次数。 4. 什么是主键和外键? 主键是用于唯一标识表中每一行数据的列,它必须具备唯一性和非空性。外键是表中的一个列,它与另一个表的主键形成关联,用于建立表与表之间的关系。 5. 什么是SQL注入?如何防止SQL注入攻击? SQL注入是指通过在用户输入的数据中插入恶意的SQL代码,从而对数据库进行非法操作的攻击方式。为了防止SQL注入攻击,可以使用参数化查询或预编译语句,对用户输入的数据进行过滤和转义。 6. 什么是数据库连接池?为什么使用数据库连接池? 数据库连接池是一种管理数据库连接的技术,它可以在应用程序启动时创建一定数量的数据库连接,并将这些连接保存在连接池中,供应用程序使用。使用数据库连接池可以减少数据库连接的创建和销毁开销,提高数据库访问的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值