前端开发笔记与实践

一、Vue 开发规范与响应式机制

1. 组件命名规范

  • 自定义组件使用大驼峰命名法(如 MyComponent),符合 Vue 官方推荐,便于与原生 HTML 元素区分。

2. Proxy vs defineProperty

特性Proxy(Vue3)Object.defineProperty(Vue2)
拦截范围支持对象增删改查等所有基本操作只能监听已有属性
数组支持自动拦截数组变异方法需手动重写数组方法
性能更高效层级嵌套需递归处理

Vue3 使用 Proxy 实现更全面的响应式拦截,而 Vue2 依赖 defineProperty 实现有限拦截。

3. 响应式核心流程

  1. Observer:将数据转换为响应式对象(通过 definePropertyProxy
  2. Watcher:追踪依赖,记录哪些函数(如 render)用到了哪些数据
  3. Dep:每个属性维护一个依赖列表,当属性变化时通知 Watcher 更新
  4. Scheduler:异步调度更新,避免重复渲染,提高性能

4. 调度器作用

  • 合并多次变更,减少 render 触发次数
  • 异步执行更新(基于 nextTick 和微任务队列)
  • 确保每个 Watcher 只执行一次更新

5. Vue3响应式性能对照

操作Vue2 (defineProperty)Vue3 (Proxy)
初始化1000属性15ms3ms
新增100属性需要Vue.set直接赋值
数组操作特殊处理原生支持
内存占用每个属性1KB整个对象3KB

二、CSS 与 SCSS 进阶

1. CSS 新单位

单位计算依据典型应用场景
vmin视口宽高中较小值移动端全面屏适配(适配小屏幕)
vmax视口宽高中较大值大屏展示元素尺寸控制(适配宽屏)
dvh动态视口高度解决移动浏览器工具栏遮挡问题
svh/lvh小/大视口高度特定视口比例布局
/* 确保元素在任何设备上都可见 */
.modal {
  width: min(90vw, 800px);
  height: max(60vh, 400px);
  /* 移动端避开地址栏 */
  height: calc(100dvh - 60px);
}

2. SCSS 循环与变量

$num: 20;

@for $i from 1 through $num {
    .btn:nth-child(#{$i}) {
        transform: scale(0.1 * $i);
    }
}

三、JavaScript 进阶技巧

1. 判断是否是数组

方法是否可靠说明
Array.isArray()✅ 推荐ES6 标准方法
obj instanceof Array受原型链影响
Object.prototype.toString.call(obj)可被 Symbol.toStringTag 修改

2. 稀疏数组判断

function isSparseArray(arr) {
    if (!Array.isArray(arr)) return false;
    for (let i = 0; i < arr.length; i++) {
        if (!(i in arr)) return true;
    }
    return false;
}

3. 数组去空字符串

const arr = ['', '1'];
const filtered = arr.filter(Boolean); // ['1']

4. 字符串路径转类名

const path = 'view/home/index';
const className = path.split('/').join('-'); // view-home-index

5. 垃圾回收机制

  • 垃圾判定:不可达内存
  • 回收策略
    • 引用计数(易产生循环引用泄漏)
    • 标记清除(主流浏览器采用)
  • 闭包问题:词法环境未释放导致内存膨胀
  • 手动释放:将引用设为 null

四、TypeScript 技巧

1. 函数参数类型约束

const obj = {
    age: 18,
    name: 'zhangsan'
};

function fn(key: keyof typeof obj) {
    const v = obj[key];
}

2. 获取三方库函数参数/返回值类型

import { fn } from "some-lib";

type FnParams = Parameters<typeof fn>[0]; // 获取第一个参数类型
type FnReturn = ReturnType<typeof fn>;   // 获取返回值类型

3. 元组生成联合类型

const obj = {
    a: 1,
    b: 2,
    z: 36
};
type KeysType = keyof typeof obj; // 'a' | 'b' | ... | 'z'

function getValue(key: KeysType) {
    console.log(obj[key]);
}

五、文件上传与下载

1. 文件上传交互方式

  • 多选:<input type="file" multiple>

  • 文件夹选择:<input type="file" webkitdirectory mozdirectory odirectory>

  • 拖拽上传:

    div.ondragover = e => e.preventDefault();
    div.ondrop = e => {
        e.preventDefault();
        for (const item of e.dataTransfer.items) {
            const entry = item.webkitGetAsEntry();
            if (entry.isFile) {
                entry.file(file => console.log(file));
            } else {
                const reader = entry.createReader();
                reader.readEntries(entries => console.log(entries));
            }
        }
    };
    
  • 拖拽上传增强版

    class AdvancedDropzone {
      constructor(selector) {
        this.el = document.querySelector(selector);
        this.setupEvents();
        this.preview = this.createPreview();
      }
    
      setupEvents() {
        this.el.addEventListener('dragover', this.highlight.bind(this));
        this.el.addEventListener('dragleave', this.unhighlight.bind(this));
        this.el.addEventListener('drop', this.handleDrop.bind(this));
      }
    
      async handleDrop(e) {
        e.preventDefault();
        this.unhighlight();
        
        const entries = Array.from(e.dataTransfer.items)
          .map(item => item.webkitGetAsEntry());
        
        const files = [];
        for (const entry of entries) {
          if (entry.isFile) {
            files.push(this.getFile(entry));
          } else {
            files.push(...await this.traverseDirectory(entry));
          }
        }
        
        this.previewFiles(files);
      }
    
      async traverseDirectory(dir) {
        const reader = dir.createReader();
        const entries = await new Promise(resolve => {
          reader.readEntries(resolve);
        });
        
        const files = [];
        for (const entry of entries) {
          if (entry.isFile) {
            files.push(await this.getFile(entry));
          } else {
            files.push(...await this.traverseDirectory(entry));
          }
        }
        return files;
      }
    }
    
  • 大文件分片上传

    class ChunkedUploader {
      constructor(file, options = {}) {
        this.file = file;
        this.chunkSize = options.chunkSize || 5 * 1024 * 1024;
        this.concurrent = options.concurrent || 3;
        this.chunks = Math.ceil(file.size / this.chunkSize);
        this.queue = [];
      }
    
      async upload() {
        const chunks = Array.from({ length: this.chunks }, (_, i) => i);
        const results = await pMap(chunks, this.uploadChunk.bind(this), {
          concurrency: this.concurrent
        });
        return this.finalize();
      }
    
      async uploadChunk(index) {
        const start = index * this.chunkSize;
        const end = Math.min(start + this.chunkSize, this.file.size);
        const chunk = this.file.slice(start, end);
        
        const form = new FormData();
        form.append('chunk', chunk);
        form.append('index', index);
        form.append('total', this.chunks);
    
        await axios.post('/upload', form, {
          onUploadProgress: this.createProgressHandler(index),
          __chunkIndex: index
        });
      }
    }
    

2. 文件上传网络请求

方式支持进度支持取消
XHR / Axios✅ 上传/下载
Fetch✅ 下载✅(AbortController)

3. 文件下载方式

  • <a> 标签下载(同源限制):

    <a href="http://xxx.pdf" download>Download</a>
    
  • Blob + URL.createObjectURL(跨域无 token 限制):

    fetch(url).then(res => res.blob()).then(blob => {
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'file.pdf';
        a.click();
        URL.revokeObjectURL(url);
    });
    

六、性能优化技巧

1. 环境兼容性封装(一次性判断)

const addEvent = (() => {
    if (ele.addEventListener) {
        return (ele, eventName, handler) => ele.addEventListener(eventName, handler);
    } else if (ele.attachEvent) {
        return (ele, eventName, handler) => ele.attachEvent('on' + eventName, handler);
    } else {
        return (ele, eventName, handler) => ele['on' + eventName] = handler;
    }
})();

2. Token 无感刷新方案

  1. 请求失败且为 401 错误
  2. 检查是否为刷新接口本身 → 是则跳过
  3. 若已有刷新 Promise 存在 → 复用
  4. 发起刷新请求 → 替换新 token 重新发起原始请求
  5. 失败 → 清除 token 跳转登录页
let refreshPromise: Promise<any> | null = null;

export async function refreshToken() {
    if (refreshPromise) return refreshPromise;
    refreshPromise = new Promise(async resolve => {
        try {
            const res = await axios.get('/refresh_token', {
                headers: { Authorization: `Bearer ${getRefreshToken()}` },
                __isRefreshToken: true
            });
            if (res.code === 0) resolve(true);
            else resolve(false);
        } catch (e) {
            resolve(false);
        } finally {
            refreshPromise = null;
        }
    });
    return refreshPromise;
}

七、扩展知识

1. 单点登录(SSO)与 JWT 关系

  • 单点登录:用户只需登录一次即可访问多个系统
  • JWT:是一种 Token 生成与验证机制,常用于身份认证
  • 关系:JWT 可作为 SSO 的 Token 实现方式之一,但二者没有必然联系
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值