Skip to content
  1. import.then和require.ensure原理

    require.ensure: 把一些js模块给独立出一个个js文件,然后需要用到的时候,在创建一个script对象,加入到document.head对象中即可,浏览器会自动帮我们发起请求,去请求这个js文件,写个回调,去定义得到这个js文件后,需要做什么业务逻辑操作;

    import.then: 创建一个 script 标签用于请求脚本,方法执行完返回一个 promise,script 标签被添加到 document.head 后,触发浏览器网络请求。请求成功后,动态的脚本会自动执行,将动态的模块添加到__webpack_require__.m属性中,同时调用 promise 的 resolve 方法改变状态,模块加载完成;

  2. 模块的概念

    实际上把文件的内容(方法、变量等)保存到一个对象上面,由于对象的边界限制,所以可以在不同的moduleId中声明相同的变量,例如:

    javascript
    const export = {
    	moduleId1: {
    		变量1: xxx,
    		fun1: () {}
    	},
    	moduleId2: {
    		变量1: xxx,
    		fun1: () {}
    	}
    }
  3. 低代码:

    组件库设计:单独发布

    表单引擎:通过模块联邦的方式依赖组件库,组件库更新,

     初始化表单,递归解析表单字段配置,并过滤出可以设置默认值的组件
    
    
     创建dag图实例
    
    
     遍历过滤过的字段,提取defaultValue中的(公式、api)依赖项
    
    
     生成图形数据结构(节点、边)
    

    表单设计器:通过模块联邦的方式依赖组件库,组件库更新,动态获取导出的组件以及配置项

    条件联动 :[{condition: ‘条件’, effects: [{componentid: ‘组件id’, effect:’api/assignment/clearValue/defaultValue/show/attribute’ }]}]

    widgetItem组件中间层拦截数据变动;set(value) {},触发change事件;

    生成默认值:遍历所有的节点,采用深度遍历优先的方式,形成拓扑排序,从最开始的变量依次生成默认值

    当值变动时,拓扑排序(采用深度遍历优先),依次生成依赖于这个变量的其他变量值

    javascript
    // 深度遍历优先 先添加b的依赖,再添加b
    topologicalSort(): DagNode[] {
        const visited = new Set<DagNode>();
        const result: DagNode[] = [];
    
        // a -> b -> c
        const visit = (node: DagNode) => {
          if (visited.has(node)) return;
    
          visited.add(node);
    
          for (const dependent of node.getDependents()) {
            visit(dependent);
          }
    
          result.push(node);
        };
    
        for (const [, startNode] of this.dagNodes) {
          visit(startNode);
        }
    
        return result.reverse(); // 返回逆序,即拓扑排序的顺序
      }

    判断是否有环:遍历所有的node节点,将依赖添加到组件节点的数组中,每遍历一个节点前判断是否祖先节点中已经存在当前node,如果存在就是有环

    javascript
    hasCycleInDAG(): boolean {
        const visited = new Set<DagNode>();
        const stack: { node: DagNode; ancestors: Set<DagNode> }[] = [];
    
        for (const [, startNode] of this.dagNodes) {
          stack.push({ node: startNode, ancestors: new Set() });
    
          while (stack.length > 0) {
            const { node, ancestors } = stack.pop() as { node: DagNode; ancestors: Set<DagNode> };
    
            if (ancestors.has(node)) {
              // 如果当前节点已经在祖先节点中,说明存在循环依赖
              return true;
            }
    
            if (visited.has(node)) {
              // 如果当前节点已经被访问过,无需再次处理
              continue;
            }
    
            visited.add(node);
            ancestors.add(node);
    
            for (const dependent of node.getDependents()) {
              stack.push({ node: dependent, ancestors: new Set(ancestors) });
            }
          }
        }
    
        return false;
      }
    javascript
    // 使用拓扑排序的方式验证是否有环
    function hasCycleInDAG(): boolean {
        const inDegree = new Map<DagNode, number>();
        const queue: DagNode[] = [];
    
        // 初始化节点的入度
        for (const [, node] of this.dagNodes) {
            inDegree.set(node, 0);
        }
        for (const [, node] of this.dagNodes) {
            for (const dependent of node.getDependents()) {
                inDegree.set(dependent, (inDegree.get(dependent) || 0) + 1);
            }
        }
    
        // 将所有入度为 0 的节点加入队列
        for (const [node, degree] of inDegree) {
            if (degree === 0) {
                queue.push(node);
            }
        }
    
        let visitedCount = 0;
    
        while (queue.length > 0) {
            const node = queue.shift()!;
            visitedCount++;
    
            for (const dependent of node.getDependents()) {
                inDegree.set(dependent, inDegree.get(dependent)! - 1);
                if (inDegree.get(dependent) === 0) {
                    queue.push(dependent);
                }
            }
        }
    
        // 如果遍历的节点数少于图中的节点数,说明存在循环
        return visitedCount !== this.dagNodes.size;
    }

    为什么离职?

    个人发展空间不大,产品竞争力不足;

    产品分歧:默认值设置可以联动,但是后期修改值还会绑定联动关系,根据图形数据的拓扑关系,条件和默认值联动执行,实际感觉默认值的联动是首次的值,后续再去修改某个值就不需要进行默认值规则的联动,而是根据联动配置的去进行联动操作

    配置条件联动时,再根据值的依赖配置生成新的联动dag图形数据,校验联动配置的关系图中是否存在循环引用;

    为什么使用change事件监听属性变化,因为所有的起源都是来源于用户操作,使用watch

  4. 为什么不用definePrototype监听数组;

    1. 数组的长度不固定,改变数组的方式一个是直接赋值,vue已通过definePrototype实现,还有通过下标实现,this.data[0] = 100,还有数组的7个方法,如果不是改写的话,需要push的时候,还要去遍历数组,数组长度不固定,每次遍历都有性能浪费。

npm @scope

遇到的困难:

权限设置:前端想根据后端返回的数据形成目录结构;但是由于后端的数据格式为目录和资源,每个实体页面都是资源,资源中保存目录的信息,目录组件全部写死的,调整为动态渲染,

  1. 为什么eval会干扰作用域链?

  2. watch 为什么要使用函数

  3. tsconfig的paths和webpack的alias

  4. 闭包常用场景:

    1. 防抖节流函数
    2. 常用hooks封装
  5. layout组件包裹router-view组件,router加载不同页面时通过在加载的组件中调用usePageHook动态加载面包屑、底部操作栏等,替代router中通过meta配置控制layout中布局组件的显隐(router使用多个布局组件时,多次加载usePageHook问题,以及加载顺序)

  6. css区别:auto-fit、auto-fill、auto

  7. console.log打印一个盒子

    javascript
    function box(s) {
      const lines = s.trim().split('\n')
      const width = lines.reduce((a, b) => Math.max(a, b.length), 0)
      const surround = (x) => '║   \x1b[0m' + x.padEnd(width) + '\x1b[31m   ║'
      const bar = '═'.repeat(width)
      const top = '\x1b[31m╔═══' + bar + '═══╗'
      const pad = surround('')
      const bottom = '╚═══' + bar + '═══╝\x1b[0m'
      return [top, pad, ...lines.map(surround), pad, bottom].join('\n')
    }
  8. node_modules中.bin文件中的软链生成过程,根据依赖package.json中的bin属性自动生成,是否会递归解析依赖中的依赖项,并提取到最上层的.bin文件

  9. 实现一个函数,类似于dom异步更新,多次出发后只执行最后的结果

  10. 文件下载请求头格式:

    markdown
    当服务器需要向客户端发送一个文件时,可以使用 `application/octet-stream` MIME 类型,
    以确保浏览器将其作为文件下载而不是尝试在浏览器中打开。
  11. 有趣bug;表格设置row-key属性为表格某一列的xxxkey,表格可以点击某个单元格时变为编辑状态;编辑状态input框失焦时,变为详情状态;当改变xxxkey这列某行数据;只要输入就会触发输入框自动失焦;因为表格的行key为这个数据,key改变,触发行重新渲染,所以输入会触发自动失焦;

  12. watch什么时候使用函数,什么时候使用值

  13. offsetLeft、offsetTop会触发回流吗,offsetWidth呢;

  14. 兄弟div,后面的div设置背景颜色后,为什么会遮盖前一个的box-shadow;

  15. 带有base的域名配置解决刷新后nginx404问题

  16. el-dialog内部嵌套el-input时,使用nextTick自动聚焦问题

    html
    <template>
    	<el-dialog v-model="dialogShow" title="意见" align-center width="40%" destroy-on-close>
    		<el-form ref="formRef" :model="form" label-width="120px" :rules="optionRules">
    			<el-form-item label="意见" prop="opinion">
    				<el-input ref="inputRef" type="textarea" :rows="4" maxlength="2000" clearable v-model.trim="form.opinion" placeholder="请输入" style="width: 80%" />
    			</el-form-item>
    		</el-form>
    		<template #footer>
    			<span class="dialog-footer">
    				<el-button @click="cancelOption">取消</el-button>
    				<el-button :loading="loading" type="primary" @click="handleConfirm">确定</el-button>
    			</span>
    		</template>
    	</el-dialog>
    </template>
    javascript
    使用nextTick自动聚焦问题
    watch(
    	() => props.show,
    	(v) => {
    		if (!v) {
    			form.value.opinion = null;
    		} else {
    			// 方式1:
    			setTimeout(() => {
    				inputRef.value?.focus()
    			});
    			// 方式2:
    			nextTick(() => {
    				nextTick(() => {
    					inputRef.value?.focus()
    				})
    			})
    		}
    	},
    );
  17. axios 获取application/octet-stream数据

    1. responseType字段以及blob,大文件下载使用stream时如何分片
    javascript
    service({
    	url: url,
    	method: 'get',
    	params,
    	headers: {
    		'Content-Type': 'application/x-www-form-urlencoded',
    	},
    	responseType: 'blob',
    })
    
    
    function resolveBlob(res, fileName) {
    	const aLink = document.createElement('a');
    	const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    	// 从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名;
    	// eslint-disable-next-line prefer-regex-literals
    	if (!fileName) {
    		const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*');
    		const contentDisposition = decodeURI(res.headers['content-disposition']);
    		const result = patt.exec(contentDisposition);
    		fileName = result[1];
    	}
    	// fileName = fileName.replace(/\"/g, '')
    	aLink.href = URL.createObjectURL(blob);
    	aLink.setAttribute('download', fileName); // 设置下载文件名称
    	document.body.appendChild(aLink);
    	aLink.click();
    	document.body.appendChild(aLink);
    }
  18. 文件大小计算并添加单位

    javascript
    function formatSizeUnits(kb) {
        let units = ['KB', 'MB', 'GB', 'TB', 'PB'];
        let unitIndex = 0;
    
        while (kb >= 1024 && unitIndex < units.length - 1) {
            kb /= 1024;
            unitIndex++;
        }
    
        return `${kb.toFixed(2)} ${units[unitIndex]}`;
    }
  19. 低代码步骤回溯(undo/redo):

    1. json patch,仅记录json结构改变的规则 使用参考地址;
    2. 游标记录currentIndex,游标位置移动实现undo, redo;
    3. 优化:区分可合并不可合并事件;
      1. 新增组件、配置规则等不可合并
      2. 组件名称配置、规则输入框等change事件可使用最小时间间隔方式,类似防抖事件,连续多次输入并且时间间隔不超过300ms的输入合并为一个记录;
    4. 步骤序列化;
      1. 低代码全部可记录操作
        1. 新增、删除控件
        2. 编辑控件属性
        3. 控件位置拖动
        4. 条件联动配置
        5. 绑定模型、关联字段配置
      2. 序列化分组;
    5. 序列化回显;
    6. 保存时机;
    7. 存储方式;
      1. 使用jsonpatch记录操作历史,存储方式使用indexedDB数据库;
      2. 对比全部json保存历史记录,由于字段不固定,使用localStorage保存;
    8. 兼容性;
    markdown
    /**
       * undo
       * redo
       * 
       * json数据保存;
       * 链表结构
       * currentIndex;
       * reserve
       * 
       * 区分历史记录回溯和属性编辑操作;
       * 属性及组件编辑操作时,currentIndex置为lastIndex;
       * 只能后退,
       * 历史记录后退时,移动游标,可前进后退;
       * 
       * currentJson;
       * 
       * currentIndex: number
       
       * currentJson: json
       * patchs: [];
       * 
       * 优化:
       * 局部更新:操作json(不需要生成json后全部重新渲染);
       * 可合并操作;
       * 区分是否可合并操作:
       * 不可合并:组件列表变动(新增、删除、移动)
       * 可合并(属性变动、条件联动、事件配置)
       * 
       * 
       * 条件联动配置
       * 模型绑定、关联字段配置
       * 
       * 触发历史记录保存;
       * 提取历史记录为单独的sdk;
       * 触发方式webWorker postMessage或者调用抛出api方法,传入序列化参数(json, 前进,后退)
       * 
       * 
       * 物料区 vue-sfc-loader
       * 画板;draggable
       * - 表单引擎
       * 配置区域
       * 联动设置
       * 模型绑定设置(关联字段配置)
       * 历史记录(sdk);
       * combo值设置(设置属性值,根据json解析值)(component)
       * - 类型:
       *  - 基础值
       *  - api获取 (jsonPath提取, api参数选择提取)
       *  - 组件属性值;
       * dag图数据结构(默认值设置后,校验是否有循环引用问题,以及生成默认值拓扑排序api)(sdk)
       * 协作:待完成
       */
  20. 低代码动态加载remote组件;(参考:vue3-sfc-loader)

  21. multer上传文件时传输其他参数;

    1. 期望在req.body中获取fileId参数;但是一直是

      image.png

    javascript
    const chunkUpload = multer({
      storage: multer.diskStorage({
        destination: function (req, file, cb) {
          console.log('req.body', req.body);
          // 从请求中获取fileId,优先使用URL参数,因为在multer处理过程中req.body可能还未被完全解析
          const { fileId } = req.body;
          if (!fileId) {
            return cb(new Error('Missing fileId parameter'));
          }
          const chunkDir = path.resolve(__dirname, '../../uploads/chunks', fileId);
          // 确保分片目录存在
          try {
            require('fs').mkdirSync(chunkDir, { recursive: true });
            cb(null, chunkDir);
          } catch (err) {
            cb(new Error(`Failed to create chunk directory: ${err.message}`));
          }
        },
        filename: function (req, file, cb) {
          // 同样优先使用URL参数获取chunkIndex
          const { chunkIndex } = req.body;
          if (!chunkIndex && chunkIndex !== 0) {
            return cb(new Error('Missing chunkIndex parameter'));
          }
          cb(null, `chunk-${chunkIndex}`);
        },
      }),
      limits: {
        fileSize: 50 * 1024 * 1024, // 分片大小限制增加到50MB
      },
    });

    原因:客户端传输字段顺序限制;

    在multer生成的中间件中,如果先解析到req.body中的对应文件字段的话,会停止挂载后面的数据

    image.png

    调整顺序,解决问题

    javascript
    formData.append('chunkIndex', chunk.index);
    formData.append('fileId', chunk.fileId);
    // 文件字段移到最后
    formData.append('chunk', chunk.chunk);
  22. 项目启动问题;

    1. 移除@vicons-fluent依赖;执行下载,npm -w xxx(包名) install @xxx(依赖)单独安装其他版本的依赖;如果屡次不行,可以重启一下编辑器(vscode)
    2. 前端爆栈问题
      1. window: "buile": set NODE_OPTIONS=--max-old-space-size=8192 && vite build
      2. linux|macos "buile": export NODE_OPTIONS=--max-old-space-size=8192 && vite build流水线中配置:npm install --unsafe-perm=true --allow-root --legacy-peer-deps && cd packages/app && export NODE_OPTIONS=--max-old-space-size=8192 && npm run build:dev
      3. 跨平台解决方案:
        1. 第一步:npm install --save-dev cross-env
        2. 第二部:"build:dev": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build
        3. 注意:****"cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build 中间不需要 && 连接符
  23. vite打包发布后资源问题;

    可以正常访问css等静态资源,访问js静态资源时重定向到index;(调整nginx配置静态资源访问配置)

Released under the MIT License.