常见进阶面试题
模块化
JS运行的环境 | 全局对象 | |
---|---|---|
浏览器 | window | 直接支持ES6Module,但是不支持CommonJS |
Node | global | 支持CommonJS,但是不支持ES6Module |
Webpack | Common JS&ES6Module都支持,支持相互之间的“混用 | |
Vite | 基于ES6Module规范,实现模块之间的相互引用 |
闭包
闭包:作用域的一种特殊应用,内层函数作用域用到了外层函数作用域的变量。由于被内层函数引用,导致外层函数执行完变量不会被释放,就形成了闭包。
函数不在定义的作用域执行,在外部执行能拿到内部作用于的值,就形成了闭包。
闭包案例
function func() {
const val = 10
return function () {
console.log(val)
}
}
const val = 100
const fn = func()
fn()// 10
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')
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)
}
:::
应用
- 函数柯里化
- 变量私有化
// 调用 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
弊端
过度使用闭包会造成内存占用过多,容易造成内存泄漏,手动清空可解决问题。
函数柯里化
数组求和
const arr = [1, 2, 3, 4]
// 方法一:
const sum = arr.reduce((pre, cur) => pre + cur, 0)
// 方法二:
// eval(arr.join('+'))
实现 add(1,2)(3)
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)
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
思想:利用闭包预先存储,供其夏季上下文后期使用,可解决函数嵌套导致的可读性差问题。
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
闭包惰性思想
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关键字来实现。