Skip to content

常见进阶面试题

模块化

JS运行的环境全局对象
浏览器window直接支持ES6Module,但是不支持CommonJS
Nodeglobal支持CommonJS,但是不支持ES6Module
WebpackCommon JS&ES6Module都支持,支持相互之间的“混用
Vite基于ES6Module规范,实现模块之间的相互引用

闭包

闭包:作用域的一种特殊应用,内层函数作用域用到了外层函数作用域的变量。由于被内层函数引用,导致外层函数执行完变量不会被释放,就形成了闭包。

闭包案例
js
function func(){
  const val = 10 
  return function(){
    console.log(val)
  }
}
const val = 100
const fn = func()
fn()//10
js
function fn(){
  const data = {}
  return {
    set: function(key,val){
      data[key] = val
    }
    get:function(key){
      return data[key]
    }
  }
}
const handle = fn()
handle.set('name','zhangsan')
handle.get('name')
js
const btns = document.querySelectorAll('.btn') //5个按钮
for(val i = 0;i < btns.length; i++){	//改用 let 形成闭包
	btns[i].onclick = function(){
    console.log(i) //输出结果都是 5
  } 
}
//==>
for(val i = 0;i < btns.length; i++){
(function(){
 btns[i].onclick = function(){
      console.log(i) //输出结果: 01234
  }})(i)
}

应用

  1. 函数柯里化
  2. 变量私有化

弊端

过度使用闭包会造成内存占用过多,容易造成内存泄漏,手动清空可解决问题。

函数柯里化

数组求和
js
let arr = [1,2,3,4]
//方法一:
let sum = arr.reduce((pre,cur) => pre + cur,0)
//方法二:
eval(arr.join('+'))
实现 add(1,2)(3)
js
const add = function(...params){ // params:[1,2]
  return function(...args){ //args:[3]
    return params.concat(args).reduce((pre,cur) => cur+pre)
  }
}
//简化
const add = (...params) => (...args) => params.concat(args).reduce((pre,cur) => pre + cur)
实现 add(1)(2)(3)
js
const add = function(a){
  return function(b){
    return function(c){
      return a + b + c
    }
  }
}
//简化
const add = a => b => c => a + b + c 

//柯里化:curring 函数
const curring = function(){
  let params = []//利用闭包存储实参
  const add = (...args) =>{
    params = params.concat(args)
    return add
  }
  //在对象转数字过程中,进行求和处理
  add[Symbol.toPrimitive] = () => {
    return params.reduce((pre,cur) => pre + cur)
  }
  return add
}
const add = curring()
let sum = add(1)(2)(3)(4)(5)(6)
alert(sum) //21
console.log(+sum)//21

compose

思想:利用闭包预先存储,供其夏季上下文后期使用,可解决函数嵌套导致的可读性差问题。

js
const add1 = x => x + 1
const mul3 = x => x * 3
const div2 = x => x / 2
//不使用柯里化:div2(mul3(add1(10)))

const compose = function(...funcs){
  //考虑不传函数,返回operate传的实参
  let len = funcs.length
  if(len === 0) return x => x
  if(len === 1) return funcs[0]
  return function operate(x){
    return funcs.reduceRight((x,func) => func(x),x)
  }
}
//不传函数
const operate = compose()
operate(10) //10
//传一个
const operate = compose(add1)
operate(10) //11
// 传多个
const operate = compose(div2,mul3,add1,add1)
operate(10) //18

闭包惰性思想

js
function getCss(ele, attr){
  //加载判断样式兼容问题,没有更换刷新浏览器每次都需要做多余的判断
  if(window.getComputedStyle){
      return window.getComputedStyle(ele)[attr]
  }else{
      return window.getCurrentStyle(ele)[attr]
  }
}
//优化后
function getCss(ele, attr){
  //第一次运行,根据兼容情况,重构 getCss
  if(window.getComputedStyle){
    getCss = function(ele, attr){
      return window.getComputedStyle(ele)[attr]
    }
  }else{
    getCss = function(ele, attr){
      return window.getCurrentStyle(ele)[attr]
    }
  }
  return getCss(ele, attr)
}
getCss(body, 'width')
getCss(body, 'padding')

深度优先和广度优先

js
//数据
let tree =  {
    id: '1',
    title: '节点1',
    children: [
      {
        id: '1-1',
        title: '节点1-1'
      },
      {
        id: '1-2',
        title: '节点1-2',
        children: [{
          id: '2',
          title: '节点2',
          children: [
            {
              id: '2-1',
              title: '节点2-1'
            }
          ]
        }]
      }
    ]
  }

深度优先遍历(DFS)

深度优先遍历(DFS)是一种递归或栈的思想来遍历所有节点的算法。在 JavaScript 中,可以使用递归或栈来实现 DFS。具体步骤如下:

  1. 访问根节点
  2. 对根节点的所有子节点,依次执行以下操作:
    • 访问该子节点
    • 对该子节点的所有子节点,依次执行以上两步操作

JavaScript 中 DFS 的时间复杂度为 O(n),其中 n 是节点数。空间复杂度为 O(h),其中 h 是树的高度。

js
//递归方式
let dfs = (node, nodes = []) => {
  if (node) {
    nodes.push(node.id)
    node.children?.forEach(child =>	dfs(child, nodes) )
  }
  return nodes
}
js
//非递归方式
let dfs = (node) => {
  let nodes = []
  let stack = []
  if(node){
    stack.push(node)
    while(stack.length){
      let item = stack.shift()
      let children = item.children
      nodes.push(item.id)
      children?.forEach(child => stack.push(child) )
    }
  }
  return nodes
}
js
// 用非递归方式实现二叉树深度优先遍历
function dfs(node) {
  let nodes = [];
  if (node) {
    nodes.push(node);
    node.children?.forEach(child => nodes = nodes.concat(dfs(child)))
  }
  return nodes;
}

广度优先遍历(BFS)

广度优先遍历(BFS)是一种逐层扫描节点的算法。在 JavaScript 中,可以使用队列来实现 BFS。具体步骤如下:

  1. 将根节点入队
  2. 当队列非空时,执行以下操作:
    • 将队头节点出队,并访问该节点
    • 将该节点的所有子节点入队

JavaScript 中 BFS 的时间复杂度为 O(n),其中 n 是节点数。空间复杂度为 O(w),其中 w 是树的宽度。

js
function bfs(root) {
  let nodes = []
  const queue = [root];
  while (queue.length > 0) {
    const node = queue.shift();
    nodes.push(node.id)
    node.children?.forEach(child => {
      queue.push(child);
    });
  }
  return nodes
}

//简化后
function bfs(root){
  let nodes = []
  let node,queue = [root]
  while(node = queue.shift()){
    nodes.push(node.id)
    node.children && queue.push(...node.children)
  }
  return nodes
}
js
function bfs(node) {
  let nodes = []
  if (node !== null) {
    nodes.push(node)
    let i = 0
    while (node = nodes[i++]) {
      node.children && nodes.push(...node.children)
    }
  }
  return nodes
}

两者的区别

  • 广度优先遍历需要使用队列逐层扫描节点,而深度优先遍历需要使用递归或栈来遍历节点。
  • 广度优先遍历的空间复杂度比深度优先遍历高,因为广度优先遍历需要使用队列来存储节点,而深度优先遍历只需要使用递归或栈。

广度优先遍历的应用

广度优先遍历可以用于许多问题,例如:

  • 查找最短路径:遍历距离起点最近的节点。
  • 查找连通块:可以找到由相邻节点组成的连通块。
  • 查找图的最小生成树

深度优先遍历的应用

  • 查找连通块:可以找到由相邻节点组成的连通块。
  • 查找拓扑排序:可以找到图的拓扑排序,即节点的一个线性序列,使得对于每个有向边 (u,v),都有 u 在序列中排在 v 的前面。
  • 查找强联通分量:通过深度优先遍历,可以找到图的强联通分量,即任意两点之间都存在一条路径的最大子图。

图文详解深度优先,广度优先遍历

loader和plugin区别

Loader

是用来处理单个模块的转换,将各种类型的文件(如 js、css、sass、less、图片等)转换为 Webpack 可以处理的模块;

执行顺序是从后往前依次执行,每个 Loader 都会对文件进行处理,直到最后一个 Loader 将文件转换为模块。

Plugin

是扩展 Webpack 功能的函数,可以在构建过程中执行一系列的任务,遍历所有的 Plugin,并执行它们的 apply 方法。

例如生成 HTML 文件(HtmlWebpackPlugin)、压缩代码(UglifyJsPlugin)、提取公共代码等。

Plugin 在 Loader 执行完成之后执行,可以访问 Webpack 的内部环境,并对其进行修改和优化。

前端模块化规范

前端模块化规范是为了解决前端代码复杂度、可维护性和可扩展性等问题而产生的。目前常用的前端模块化规范有CommonJS、AMD、CMD和ES6 Module等,它们之间的区别如下:

CommonJS规范:主要用于服务器端的JavaScript编程,Node.js采用了该规范。采用同步加载模块的方式,即在需要使用某个模块时,通过require函数同步加载该模块,然后才能执行后续代码。

ES6 Module:是ECMAScript 6标准中新增的模块化规范。 采用静态加载模块的方式,即在编译时就确定模块之间的依赖关系,然后再执行代码。

与其他模块化规范不同的是,ES6 Module不需要使用特定的函数或语法来定义和导入模块,而是使用import和export关键字来实现。

用心去做高质量的内容网站,欢迎 star ⭐ 让更多人发现