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

:::

应用

  1. 函数柯里化
  2. 变量私有化
js
// 调用 times 次后 让回调执行
function after(times, callback) {
  // AO 执行对象
  /**
   * AO = {
   *  times: 3
   * }
   */
  return function () {
    if (--times === 0) {
      callback()
    }
  }
}
const fn = after(3, () => {
  console.log('ready')
})
fn()
fn()
fn()
// ready

弊端

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

函数柯里化

数组求和
js
const arr = [1, 2, 3, 4]
// 方法一:
const 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传的实参
  const 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') */

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关键字来实现。

如何判断链表是否有环