2024-08-15

在JavaScript中,实现深度复制有多种方法。以下是四种常见的方法:

  1. 使用JSON.parseJSON.stringify



function deepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
  1. 递归复制:



function deepClone(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
 
    if (obj instanceof Date) {
        return new Date(obj.getTime());
    }
 
    if (obj instanceof Array) {
        return obj.reduce((arr, item, i) => {
            arr[i] = deepClone(item);
            return arr;
        }, []);
    }
 
    if (obj instanceof Object) {
        return Object.keys(obj).reduce((newObj, key) => {
            newObj[key] = deepClone(obj[key]);
            return newObj;
        }, {});
    }
}
  1. 使用lodashcloneDeep方法:



const _ = require('lodash');
const clonedObj = _.cloneDeep(obj);
  1. 使用MessageChannel(适合大型数据结构):



function deepClone(obj) {
    const { port1, port2 } = new MessageChannel();
    port2.onmessage = (e) => {
        port1.postMessage(e.data);
    };
    port1.postMessage(obj);
    return port2.getMessage();
}

这四种方法各有优缺点,选择哪种方法取决于具体场景和需求。

2024-08-15

以下是一个简化的示例,展示了如何在Vue 3和Node.js中使用WebSocket和flv.js来实现监控RTSP流的功能。

前端 (Vue 3)

  1. 安装flv.js:



npm install flv.js
  1. 在Vue组件中使用flv.js播放FLV格式的视频流:



<template>
  <div>
    <video ref="videoElement" controls autoplay></video>
  </div>
</template>
 
<script>
import flvjs from 'flv.js';
 
export default {
  name: 'VideoPlayer',
  mounted() {
    if (flvjs.isSupported()) {
      const videoElement = this.$refs.videoElement;
      const flvPlayer = flvjs.createPlayer({
        type: 'flv',
        url: 'ws://your-node-server/ws/stream' // Node.js服务器WebSocket地址
      });
      flvPlayer.attachMediaElement(videoElement);
      flvPlayer.load();
      flvPlayer.play();
    }
  },
  beforeUnmount() {
    if (flvjs.isSupported()) {
      flvPlayer.pause();
      flvPlayer.unload();
      flvPlayer.detachMediaElement();
      flvPlayer.destroy();
    }
  }
};
</script>

后端 (Node.js with WebSocket)

  1. 安装必要的库:



npm install ws ffmpeg
  1. 使用WebSocket和ffmpeg处理RTSP流:



const WebSocket = require('ws');
const { spawn } = require('child_process');
 
const wsServer = new WebSocket.Server({ port: 8080 });
 
wsServer.on('connection', (ws) => {
  const ffmpeg = spawn('ffmpeg', [
    '-i', 'rtsp://your-rtsp-stream', // 替换为你的RTSP流地址
    '-c:v', 'copy',
    '-an',
    '-f', 'flv',
    'pipe:1'
  ]);
 
  ffmpeg.stdout.on('data', (data) => {
    ws.send(data);
  });
 
  ffmpeg.on('error', (error) => {
    console.error(error);
  });
 
  ws.on('close', () => {
    ffmpeg.kill('SIGKILL');
  });
});

确保替换rtsp://your-rtsp-stream为实际的RTSP流地址,并且在Node.js服务器端口8080上运行WebSocket服务。

以上代码实现了一个简单的示例,展示了如何使用flv.js在前端播放来自Node.js后端通过WebSocket和ffmpeg转换的RTSP流视频。这个示例假设你已经有了一个运行中的RTSP流,并且需要将其转换为FLV格式以便于Web浏览器的播放。

2024-08-15



// rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import { terser } from 'rollup-plugin-terser';
 
export default {
  input: 'src/index.js',
  output: [
    {
      file: 'dist/bundle.cjs.js',
      format: 'cjs',
      plugins: [terser()]
    },
    {
      file: 'dist/bundle.esm.js',
      format: 'esm',
      plugins: [terser()]
    }
  ],
  plugins: [
    resolve(),
    babel({
      exclude: 'node_modules/**', // 排除转换node_modules内的代码
      presets: [
        [
          '@babel/preset-env',
          {
            modules: false, // 指示babel不将ES6模块转换为其他模块系统
            targets: '> 0.25%, not dead' // 指定浏览器兼容性目标
          }
        ]
      ]
    })
  ]
};

这个配置文件定义了如何使用Rollup和Babel来转换和打包项目代码。它设置了源代码的输入文件和输出文件的路径,并指定了Babel预设来转换代码。通过配置文件,开发者可以更轻松地管理项目的构建过程,并且得到更小、更优化的代码输出。

2024-08-15



import * as THREE from 'three';
 
// 创建一个全局的三维场景,用来放置所有的物体
const scene = new THREE.Scene();
 
// 创建一个相机对象
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
 
// 创建一个渲染器并将它的DOM元素加到HTML文档中
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
 
// 创建一个立方体的几何对象
const geometry = new THREE.BoxGeometry(1, 1, 1);
 
// 定义一个玻璃材质
const glassMaterial = new THREE.MeshPhysicalMaterial({
  color: 0xffffff, // 玻璃颜色
  clearcoat: 1.0, // 清洁层强度
  clearcoatRoughness: 0.0, // 清洁层粗糙度
  reflectivity: 0.5, // 镜面反射强度
  ior: 1.5, // 表面光学参数,玻璃的值通常是1.5
  transmission: 1.0, // 透明度
  roughness: 0.0, // 粗糙度
  metalness: 0.0, // 金属度
  specularIntensity: 0.5, // 镜光强度
  specularColor: new THREE.Color(1.0, 1.0, 1.0), // 镜光颜色
});
 
// 使用定义好的玻璃材质创建一个物体
const cube = new THREE.Mesh(geometry, glassMaterial);
 
// 将立方体添加到场景中
scene.add(cube);
 
// 设置相机位置并指向场景
camera.position.z = 5;
 
// 渲染场景
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
 
animate();

这段代码创建了一个简单的三维场景,包含了一个使用玻璃材质定义的立方体。通过调整玻璃材质的属性,可以模拟出不同的玻璃表面效果。

2024-08-15



// 引入CanvasRenderingContext2D对象
import CanvasRenderingContext2D from 'canvas/lib/canvas-rendering-context-2d';
 
export default {
    data: {
        // 圆的半径
        radius: 150,
        // 圆心坐标
        centerX: 300,
        centerY: 300,
        // 起始角度
        startAngle: 0,
        // 结束角度
        endAngle: 2 * Math.PI,
        // 是否在绘制过程中
        isDrawing: false,
        // 当前角度
        currentAngle: 0,
        // 是否停止
        stop: false,
        // 是否重置
        reset: false,
        // 是否开始
        start: false,
        // 中奖索引
        winningIndex: -1,
        // 中奖名单
        winningList: [],
        // 公共颜色
        commonColor: '#ffde33',
        // 中奖颜色
        winningColor: '#ff5858',
        // 文本颜色
        textColor: '#000000',
        // 文本大小
        fontSize: 20,
        // 文本内容
        textContent: '开始',
        // 画笔宽度
        lineWidth: 10,
        // 圆环数量
        circleCount: 8,
        // 圆环颜色数组
        circleColors: ['#ffde33', '#ffb236', '#ff993e', '#ff7745', '#ff5858'],
        // 圆环线条宽度数组
        circleLineWidths: [5, 10, 15, 20, 25],
        // 圆环内半径
        innerRadius: 100,
        // 圆环外半径
        outerRadius: 150,
        // 圆环间隔角度
        angleInterval: 2 * Math.PI / 8,
        // 是否显示中奖信息
        showWinningInfo: false,
        // 中奖信息颜色
        winningInfoColor: '#ff5858',
        // 中奖信息字体大小
        winningInfoFontSize: 30,
        // 中奖信息内容
        winningInfoContent: '恭喜中奖!',
        // 中奖信息x坐标
        winningInfoX: 150,
        // 中奖信息y坐标
        winningInfoY: 100,
        // 是否显示重新开始按钮
        showRestart: false,
        // 重新开始按钮颜色
        restartColor: '#ff5858',
        // 重新开始按钮字体大小
        restartFontSize: 20,
        // 重新开始按钮内容
        restartContent: '重新开始',
        // 重新开始按钮x坐标
        restartX: 150,
        // 重新开始按钮y坐标
        restartY: 150,
    },
    // 绘制方法
    draw() {
        // 获取Canvas上下文
        const ctx = this.$refs.canvas.getContext('2d');
        // 清除画布
        ctx.clearRect(0, 0, 600, 600);
        // 如果没有开始或者停止,则继续绘制
        if (!this.start || this.stop) {
            return;
        }
        // 如果没有重置,则
2024-08-15

在JavaScript中,数组是一种常用的数据结构,它提供了许多内置方法来处理数据。以下是一些经典且常用的数组方法:

  1. push() - 在数组末尾添加一个或多个元素,并返回新的长度。
  2. pop() - 删除数组的最后一个元素,并返回那个元素。
  3. shift() - 删除数组的第一个元素,并返回那个元素。
  4. unshift() - 在数组的开始添加一个或多个元素,并返回新的长度。
  5. slice(start, end) - 返回从 startend(不包括 end)之间的元素的新数组。
  6. splice(start, deleteCount, ...items) - 从 start 位置开始,删除 deleteCount 个元素,并可以在该位置添加 items
  7. concat(array1, array2, ...) - 返回一个新数组,是将原数组与 array1, array2, ... 连接后的结果。
  8. join(separator) - 返回一个字符串,是通过 separator 连接数组每个元素后生成的。
  9. map(function(item, index, array) { ... }) - 返回一个新数组,其每个元素都是通过调用 function 后返回的结果。
  10. filter(function(item, index, array) { ... }) - 返回一个新数组,包含通过 function 测试的所有元素。
  11. reduce(function(accumulator, item, index, array) { ... }, initialValue) - 对数组中的每个元素执行一个由您提供的 reducer 函数,将其结果汇总为单个返回值。
  12. sort() - 对数组的元素进行排序。
  13. reverse() - 颠倒数组中元素的顺序。

以下是这些方法的简单示例代码:




// 创建一个数组
let numbers = [1, 2, 3, 4, 5];
 
// 使用push方法添加元素
numbers.push(6);
 
// 使用pop方法删除元素
let lastNumber = numbers.pop();
 
// 使用shift方法删除元素
let firstNumber = numbers.shift();
 
// 使用unshift方法添加元素
numbers.unshift(0);
 
// 使用slice方法获取子数组
let sliceNumbers = numbers.slice(1, 4);
 
// 使用splice方法替换元素
numbers.splice(1, 2, 'a', 'b');
 
// 使用concat连接数组
let concatenatedNumbers = numbers.concat([10, 11, 12]);
 
// 使用join生成字符串
let joinedNumbers = numbers.join(' - ');
 
// 使用map进行映射
let doubledNumbers = numbers.map(number => number * 2);
 
// 使用filter进行过滤
let evenNumbers = numbers.filter(number => number % 2 === 0);
 
// 使用reduce进行累加
let sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
 
// 使用sort进行排序
numbers.sort((a, b) => a - b);
 
// 使用reverse颠倒顺序
numbers.reverse();

这些方法是JavaScript数组操作的基础,每个方法都有其特定的用途和行为,熟悉它们有助于提高代码操作数组的效率和质量。

2024-08-15

在JavaScript中实现本地图片上传后的预览和删除功能,可以通过HTML5的FileReader API来实现。以下是一个简单的实现示例:

HTML部分:




<input type="file" id="image-upload" accept="image/*" />
<img id="image-preview" src="" alt="Image preview..." style="display: none;" />
<button id="delete-image">删除图片</button>

JavaScript部分:




document.getElementById('image-upload').addEventListener('change', function(e) {
    var file = e.target.files[0];
    var reader = new FileReader();
    reader.onload = function(e) {
        document.getElementById('image-preview').src = e.target.result;
        document.getElementById('image-preview').style.display = 'inline';
    };
    reader.readAsDataURL(file);
});
 
document.getElementById('delete-image').addEventListener('click', function() {
    document.getElementById('image-upload').value = null;
    document.getElementById('image-preview').src = '';
    document.getElementById('image-preview').style.display = 'none';
});

这段代码实现了以下功能:

  1. 用户通过<input>选择图片文件后,使用FileReader读取文件并将其转换为DataURL。
  2. 将读取到的DataURL设置为<img>src属性,从而实现图片的预览。
  3. 点击删除按钮后,清空文件输入的值,并隐藏图片预览,实现图片的删除。
2024-08-15



import { useState } from 'react';
 
// 定义泛型类型,用于表示数组中元素的类型
type UseArrayReturnType<T> = [T[], (newItem: T) => void, () => void];
 
// 自定义Hook,用于管理数组状态
function useArray<T>(): UseArrayReturnType<T> {
  const [items, setItems] = useState<T[]>([]);
 
  // 添加新元素到数组
  const addItem = (newItem: T) => {
    setItems((prevItems) => [...prevItems, newItem]);
  };
 
  // 清空数组
  const clearItems = () => {
    setItems([]);
  };
 
  // 返回状态和操作方法
  return [items, addItem, clearItems];
}
 
// 使用自定义Hook
export default function App() {
  // 使用泛型指定数组元素类型为字符串
  const [items, addItem, clearItems] = useArray<string>();
 
  // 示例:添加和清空数组
  return (
    <div>
      <button onClick={() => addItem('新项目')}>添加项目</button>
      <button onClick={clearItems}>清空列表</button>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

这段代码定义了一个名为useArray的自定义Hook,它使用TypeScript的泛型来提供一个灵活的数组状态管理解决方案。该Hook返回一个数组,其中包含当前状态、添加新项目的函数和清空数组的函数。在App组件中,我们演示了如何使用这个Hook来管理字符串类型的数组。

2024-08-15

JavaScript的执行机制基于事件循环(Event Loop)。事件循环主要有几个阶段:

  1. 宏任务(Macro Task):一般指执行整体的任务,如script全部代码,setTimeout,setInterval。
  2. 微任务(Micro Task):执行的任务较小,如Promise。

当JavaScript运行时,会有一个执行栈和一个任务队列。执行栈是JavaScript执行代码时的工作空间,任务队列是存放异步任务的空间。

事件循环的步骤如下:

  1. 检查执行栈是否为空,如果为空,则执行微任务队列中的任务。
  2. 处理完微任务后,再处理宏任务队列中的任务。
  3. 重复步骤1和步骤2,直到所有任务都处理完毕。

例子代码:




console.log('script start');
 
setTimeout(function() {
  console.log('setTimeout');
}, 0);
 
Promise.resolve().then(function() {
  console.log('promise');
});
 
console.log('script end');
 
// 输出顺序为: script start, script end, promise, setTimeout

在这个例子中,首先执行同步代码,其中包括记录"script start"和"script end"。然后执行setTimeout中的代码,它被放入宏任务队列中。接着是Promise,它的.then函数被放入微任务队列中。当同步代码执行完毕后,JavaScript运行时开始检查微任务队列,记录"promise",然后处理宏任务队列中的setTimeout任务,记录"setTimeout"。

2024-08-15

在ECharts中,调整图表的大小通常涉及到两个方面:容器大小和ECharts实例的自适应。

  1. 容器大小:图表会占据其父容器的大小。确保你的HTML元素(例如div)有正确的宽度和高度。



<div id="main" style="width: 600px;height:400px;"></div>
  1. 自适应:如果需要图表在容器大小变化时自动调整大小,可以在容器大小改变后调用ECharts实例的 resize 方法。



// 假设你已经初始化了一个ECharts实例
var myChart = echarts.init(document.getElementById('main'));
 
// ... 设置图表的选项和数据等
 
// 当浏览器窗口大小变化时,或者容器大小变化时,调整图表大小
window.onresize = function() {
    myChart.resize();
};
  1. 使用响应式布局:如果你使用CSS进行布局,可以利用响应式设计来调整图表大小。



/* 响应式布局 */
@media (max-width: 600px) {
  #main {
    width: 100%;
    height: 300px;
  }
}
 
@media (min-width: 601px) {
  #main {
    width: 600px;
    height: 400px;
  }
}

确保你的图表容器(例如#main)遵循上述的布局规则,ECharts图表将自动适应其大小。