前端高频问题

129 阅读19分钟

如何限制Promise“并发”的数量

核心函数就两个:

调用器: 就是把真正的执行函数和参数传入,创建返回一个新的Promise,而这个新Promise的什么时候返回,取决于这个异步任务何时被调度。Promise内部主要就是创建一个任务,判断任务是执行还是入队。

创建任务: 实际上就是返回了一个函数,将真正的执行函数放在里面执行。这里利用了Promise的finally方法,在finally中判断是否执行下一个任务,实现任务队列连续消费的地方就是这里。

class LimitPromise {
  constructor (max) {
    // 异步任务“并发”上限
    this._max = max
    // 当前正在执行的任务数量
    this._count = 0
    // 等待执行的任务队列
    this._taskQueue = []
  }

  /**
   * 调用器,将异步任务函数和它的参数传入
   * @param caller 异步任务函数,它必须是async函数或者返回Promise的函数
   * @param args 异步任务函数的参数列表
   * @returns {Promise<unknown>} 返回一个新的Promise
   */
  call (caller, ...args) {
    return new Promise((resolve, reject) => {
      const task = this._createTask(caller, args, resolve, reject)
      if (this._count >= this._max) {
        // console.log('count >= max, push a task to queue')
        this._taskQueue.push(task)
      } else {
        task()
      }
    })
  }

  /**
   * 创建一个任务
   * @param caller 实际执行的函数
   * @param args 执行函数的参数
   * @param resolve
   * @param reject
   * @returns {Function} 返回一个任务函数
   * @private
   */
  _createTask (caller, args, resolve, reject) {
    return () => {
      // 实际上是在这里调用了异步任务,并将异步任务的返回(resolve和reject)抛给了上层
      caller(...args)
        .then(resolve)
        .catch(reject)
        .finally(() => {
          // 任务队列的消费区,利用Promise的finally方法,在异步任务结束后,取出下一个任务执行
          this._count--
          if (this._taskQueue.length) {
            // console.log('a task run over, pop a task to run')
            let task = this._taskQueue.shift()
            task()
          } else {
            // console.log('task count = ', count)
          }
        })
      this._count++
      // console.log('task run , task count = ', count)
    }
  }
}

express.js VS koa.js

nodejs多进程

loader 和 plugin

如何实现 tree-shaking

如何提高 webpack 的构建速度

Vite VS Webpack

webpack 配置多页应用

webpack的打包构建流程

  • 连接: webpack从入口JS开始,递归查找出所有相关的模块, 并【连接】起来形成一个图(网)的结构
  • 编译: 将JS模块中的模块化语法【编译】为浏览器可以直接运行的模块语法(当然其它类型资源也会处理)
  • 合并: 将图中所有编译过的模块【合并】成一个或少量的几个文件, 浏览器真正运行是打包后的文件

React错误监听

ErrorBoundary

概念:

  • 可以监听到所有下级组件报错,不监听事件和异步错误,建议应用到最顶层。监听全局错误,需要打包后可以看到报错UI效果
  • 错误边界涉及到两个生命周期: getDerivedStateFromError, componentDidCatch
  • getDerivedStateFromError是静态方法,需要返回一个对象,该对象会和state对象进行合并用于更改应用程序状态
  • componentDidCatch方法用于记录应用程序错误信息,该方法的参数就是错误对象

缺点:

ErrorBoundary 无法捕捉到事件和异步报错,可使用 window.onerror 来监听,也可 try-catch

总结:

  • ErrorBoundary 监听渲染时报错
  • try-catch 和 window.onerror 捕获其他错误

Promise 监听报错要使用 window.onunhandledrejection

前端拿到错误监听之后,需要传递给服务端,进行错误收集和分析,然后修复 bug

click事件300ms延迟

原因:延迟300ms,是因为double-tap to zoom 双击缩放 (等待检测你的操作是不是双击缩放)

产生的后果:移动端 click 屏幕产生 200-300 ms 的延迟响应,往往会造成按钮点击延迟甚至是点击失效。

解决方案

  • 不使用 click 事件,把 click 事件中要处理的放到 touchstart 或 touchend 中去处理
$btn.addEventListener(
    'touchend',
    () => {
        console.timeEnd('click');
        console.log('提交表单');
    },
    false
);
  • 解决方案二:禁止双击缩放(浏览器厂商的努力)
    • viewport 中禁止缩放
    • touch-action: manipulation;(只允许滚动和持续缩放)
  • 解决方案三:
    • 使用 Fastclick 库
    • 主要针对老版本浏览器
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, minimum-scale=1,maximum-scale=1, user-scalable=no"
    />
    <title>click 事件 300ms 延迟</title>
    <style>
      .btn {
        width: 100%;
        height: 300px;
        font-size: 100px;

        /* touch-action: manipulation; */
      }
    </style>
  </head>
  <body>
    <button id="btn" class="btn">提交</button>
	  <!-- 引入Fastclick 库 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/fastclick/1.0.6/fastclick.min.js"></script>
    <script>
    	// 使用 Fastclick 库
      if ('addEventListener' in document) {
        document.addEventListener(
          'DOMContentLoaded',
          function () {
            FastClick.attach(document.body);
          },
          false
        );
      }

      const $btn = document.getElementById('btn');
      $btn.addEventListener(
        'touchstart',
        () => {
          console.time('click');
        },
        false
      );
      // $btn.addEventListener(
      //   'touchend',
      //   () => {
      //     console.timeEnd('click');
      //     console.log('提交表单');
      //   },
      //   false
      // );
      $btn.addEventListener(
        'click',
        () => {
          console.timeEnd('click');
          console.log('提交表单');
        },
        false
      );
    </script>
  </body>
</html>

Touch事件点击穿透

在发生触摸动作约300ms之后,移动端会模拟产生click动作,如果touch事件隐藏了原来元素 则click总作用到它底下的具有点击特性的元素,触发新元素的click事件和跳转,此现象被称为点击穿透

Touch 事件点击穿透的原因

事件触发的先后顺序是: touchstart -> touched -> click,由于这种 click 事件的滞后性设计为事件穿透(点击穿透)埋下了伏笔。

  • touch 事件结束之后会默认触发该元素的click事件
  • 移动端Touch事件会立即触发,而click事件会延迟一段时间触发

常见的事件穿透场景:

  • 上层元素监听了触摸事件,触摸之后该层元素消失
  • 下层元素具有点击特性(监听了click事件或默认的特性(a标签、input、button标签))
  • 目标元素使用触摸事件跳转至新页面,新页面中对应位置元素触发 click 事件或 a 链接跳转
  • 如标签元素触发触摸事件时隐藏或移除自身,对应位置元素触发 click 事件或 a 链接跳转。
  • a 标签的链接跳转事件属于 click 事件

解决方案

解决事件穿透的方法有很多,大致可以分为两类:第一种是禁止混用 click 和 touch 两种事件;另一种是延迟元素的隐藏或移除

  • 让原本隐藏的元素延迟隐藏
$mask.addEventListener(
    'touchend',
    () => {
        // $mask.style.display = 'none';

        // 延时消失
         setTimeout(() => {
           $mask.style.display = 'none';
         }, 200);

        // 2.2.消失过程中添加动画效果  css添加transition和初始opacity = 1
        $mask.style.opacity = 0;
    },
    false
);

// 过渡结束将元素隐藏
$mask.addEventListener(
    'transitionend',
    () => {
        $mask.style.display = 'none';
    },
    false
);

解决方法二:阻止默认行为

//阻止该默认click行为的触发 
$mask.addEventListener('touchstart', function(e){
    e.preventDefault(); 
    console.log('hello')
    $mask.style.display = 'none';
})

解决方法三

使背后元素不具备click特性,用touchXxxx代替click

banner_img.addEventListener('touchstart',()=>{
    location.href = 'http://www.baidu.com'
})

解决方案四

让背后的元素暂时失去click事件,300毫秒左右再复原

#anode{
  pointer-events: none;
}


btn.addEventListener('touchstart',(event)=>{
    shade.style.display = 'none';
    setTimeout(()=>{
        anode.style.pointerEvents = 'auto'
    },500)
})

前端统计 sdk

数据统计的流程

  • 前端发送数据给服务端
  • 服务端接受,并处理统计数据
  • 查看统计结果

前端统计的范围

  • 访问PV
  • 自定义事件,统计一个按钮被点击了多少次
  • 性能:通过 console.table( performance.timing ) 可以看到网页的各个性能
    • 想要得到全面的性能数据,要在网页加载完成之后( DOMContentLoaded 或 onload )去初始化 埋点统计数据
  • 错误

type和interface的区别

相同点

1、都可以描述一个对象或者函数

2、扩展(extends)与交叉类型(intersection types)

不同点

1. type可以声明 基本类型,联合类型,元组 的别名,interface不行

// 基本类型别名
type Name = string

// 联合类型
interface Dog {
    wong();
}
interface Cat {
    miao();
}

type Pet = Dog | Cat

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
  1. type 支持类型映射,interface不支持
type Keys = "guangdong" | "liangzai"
type DudeType = {
  [key in Keys]: string
}
const test: DudeType = {
  firstname: "guangdong",
  surname: "liangzai"
}

CR 检查什么

  • 代码规范,
  • 重复逻辑抽离,复用
  • 单个函数过长,需要拆分
  • 算法是否可优化
  • 是否有安全漏洞
  • 扩展行如何
  • 是否和现有的功能重复
  • 是否有完善的单元测试
  • 组件设计是否合理

何时 CR

  • 提交 PR(或MR) 时,通过代码 diff 进行 Code review
  • 每周例行一次集体 Code review

持续优化

  • 每次 Code review 的问题要记录下
  • 归纳整理,形成自己的代码规范体系
  • 新加入的成员要提前学习,提前规避

项目负责人

  • 把控需求
  • 技术方案设计
  • 开发
  • 监督代码质量
  • 跟踪代码质量
  • 跟踪进度
  • 稳定安全的运行

做过哪些性能优化

web 性能优化?

降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩, webP,lazyLoad。

加快请求速度:预解析 DNS,减少域名数,并 行加载,CDN 分发。

缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存 localStorage。

渲染:JS/CSS 优化,加载顺序,服务端渲染,pipeline

小程序的首屏加载时间

提前请求:异步数据数据请求不需要等待页面渲染完成

利用缓存:利用 storage API 对异步请求数据进行缓存,二次启动时 先利用缓存数据渲染页面,再进行后台更新

避免白屏:先展示页面骨架和基础内容 及时反馈:及时地对需要用户等待的交互操作给出反馈,避免用户以 为小程序没有响应

性能优化:避免不当使用 setdata 和 onpagescroll

也可以回答webpack等其他自己做过的优化内容,最好回答自己有把握的内容。

es5继承

1.原型链继承

缺点:创建实例时不能传递参数,所有属性都被实例共享

function Parent() {      
     this.name = 'kevin';    
 }    
 Parent.prototype.getName = function () {      
     console.log(this.name);    
 }    
 function Child() { 
 }    
 Child.prototype = new Parent();    
 var child = new Child();    
 child.getName() //kevin

2.借用构造函数

优点:可以传递参数,避免了引用类型共享

缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法。

function Parent() {
  this.name = ['kevin'];
}
function Child() {
  Parent.call(this);
}
Child.prototype = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.name.push("cc");

console.log(child1.name); //["kevin", "cc"]
console.log(child2.name); //["kevin"]

3.组合继承

优点:融合了原型继承和构造函数继承,是JavaScript中常用的设计模式。

缺点:调用两次父构造函数

function Parent() {
  this.name = ['kevin'];
}
Parent.prototype.getName = function () {
  console.log(this.name, this.age);
}
function Child(age) {
  Parent.call(this);
  this.age = age;
}
Child.prototype = new Parent();
var child1 = new Child(19);
var child2 = new Child(20);
child1.name.push("cc");
child1.getName();  //["kevin", "cc"] 19    
child2.getName();  //["kevin"] 20

4.原型式继承

这是es5中的,Object.create的模拟实现。

缺点:创建实例时不能传递参数,所有属性都被实例共享

function createObj(o) {
  function F() { }
  F.prototype = o;
  return new F();
}

5.寄生式继承

function createObj(o) {
  var clone = Object.create(o);
  clone.say = () => { console.log(this) };
  return clone
}
console.log(createObj({}));  //{say: ƒ}

6.寄生组合式继承

优点:开发人员普遍认为寄生组合式继承是最理想的继承范式

function Parent(name) {
  this.name = name;
}
Parent.prototype.getName = function () {
  console.log(this.name, this.age);
}
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
var F = function () { }
F.prototype = Parent.prototype;
Child.prototype = new F();
var child1 = new Child("cc", 20)
console.log(child1) //Child {name: "cc", age: 20}

es6继承

1.class通过extends关键字实现继承

class Parent {
  constructor(x, y) {
    this.x = x;
    this.y = y
  }
}
class Child extends Parent {
  constructor(x, y, name) {
    super(x, y);//调用父类的constructor(x,y)
    this.name = name;
  }
}
var child1 = new Child("x", "y", "ccg");
console.log(child1);  //Child {x: "x", y: "y", name: "ccg"}

2.super关键字

作为函数,super只能在子类的构造函数中。但是如果作为对象时,在普通方法中指向父类的原型对象;

在静态方法中指向父类。(!由于super指向父类的原型对象,所以定义在父类实例的方法无法通过super调用)

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A();
new B();

发布订阅

 class EventEmitter {
      constructor() {
        this.handlers = {};
      }
      on(eventName, cb) {
        if (!this.handlers[eventName]) {
          this.handlers[eventName] = [cb];
        } else {
          this.handlers[eventName].push(cb);
        }
      }
      emit(eventName, data) {
        if (!this.handlers[eventName]) {
          console.log('监听器不存在');
          return;
        }
        const events = this.handlers[eventName].slice();
        events.forEach((cb) => {
          cb(data);
        });
      }
      off(eventName, cb) {
        if (!this.handlers[eventName]) {
          console.log('监听器不存在');
          return;
        }
        const callBacks = this.handlers[eventName];
        const index = callBacks.findIndex((item) => item === cb);
        callBacks.splice(index, 1);
      }
      once(eventName, cb) {
        const wrap = (data) => {
          let fn = this.handlers[eventName];
          cb(data);
          this.off(eventName, fn);
        };
       
        this.on(eventName, wrap);
      }
    }
    let eventBus = new EventEmitter();
    eventBus.once('success', (data) => {
      console.log('data', data);
    });
    eventBus.emit('success', 456);
    eventBus.emit('success', 577);

webpack插件:本地图片进行压缩后上传cdn,返回图片URL

需要安装一些第三方模块,如 imageminimagemin-webpack-plugin

npm install imagemin imagemin-webpack-plugin --save-dev

然后,创建一个名为 upload-to-cdn-plugin.js 的文件,用于写 Webpack 插件:

const ImageminWebpackPlugin = require('imagemin-webpack-plugin').default;
const axios = require('axios');

class UploadToCdnPlugin {
  constructor(options) {
    this.options = options;
  }

  apply(compiler) {
    compiler.hooks.afterEmit.tapAsync('UploadToCdnPlugin', (compilation, callback) => {
      // 获取本地压缩后的图片
      const assets = compilation.assets;
      Object.keys(assets).forEach(async asset => {
        // 判断文件类型是否是图片
        if (/.(jpeg|jpg|png|gif)$/i.test(asset)) {
          // 获取图片内容
          const content = assets[asset].source();
          
          try {
            // 将图片上传到 CDN
            const res = await axios.post(this.options.uploadUrl, content, {
              headers: {
                'Content-Type': 'image/jpeg'
              }
            });
            
            // 获取图片的 URL 链接
            const imageUrl = res.data;
            console.log(`图片 ${asset} 上传到 CDN 成功,URL 链接为 ${imageUrl}`);
          } catch (error) {
            console.error(`图片 ${asset} 上传到 CDN 失败,错误信息:`, error);
          }
        }
      });
      
      callback();
    });
  }
}

module.exports = UploadToCdnPlugin;

然后,您可以在 webpack 配置文件中使用该插件:

const ImageminWebpackPlugin = require('imagemin-webpack-plugin').default;

module.exports = {
  // ...
  plugins: [
    new ImageminWebpackPlugin({
      test: /.(jpeg|jpg|png|gif)$/i,
      pngquant: {
        quality: '95-100'
      }
    })
  ]
};

自己实现ImageminWebpackPlugin插件

使用imagemin库和 Webpack 插件 API

const Imagemin = require("imagemin");
const ImageminMozjpeg = require("imagemin-mozjpeg");
const ImageminPngquant = require("imagemin-pngquant");
const ImageminSvgo = require("imagemin-svgo");
const path = require("path");

class ImageminWebpackPlugin {
  constructor(options) {
    this.options = options || {};
  }

  apply(compiler) {
    compiler.hooks.afterEmit.tapAsync("ImageminWebpackPlugin", (compilation, callback) => {
      const { context, output } = compiler;
      const { include, exclude, plugins } = this.options;

      // Find all files in the output directory
      const files = [];
      for (const file in compilation.assets) {
        if (file.match(/.(jpeg|jpg|png|gif|svg)$/)) {
          files.push(path.join(output.path, file));
        }
      }

      // Optimize the files using Imagemin
      (async () => {
        const imagemin = new Imagemin();
        imagemin.src(files)
          .use(ImageminMozjpeg({ quality: 70 }))
          .use(ImageminPngquant({ quality: [0.6, 0.8] }))
          .use(ImageminSvgo());
        const optimizedFiles = await imagemin.run();

        // Replace the original files with the optimized ones
        optimizedFiles.forEach((file) => {
          const asset = file.history[0];
          const source = file.contents;
          compilation.assets[asset] = {
            source: () => source,
            size: () => source.length,
          };
        });

        callback();
      })();
    });
  }
}

module.exports = ImageminWebpackPlugin;

插件使用 Imagemin 库及其用于 JPEG、PNG 和 SVG 文件的插件优化输出目录中的图像。include您可以使用和选项指定要包含或排除的文件exclude。此外,您可以通过使用该选项指定不同的插件和选项来自定义优化过程plugins

webpack 文件依赖分析插件

const fs = require('fs');
const http = require('http');
const opener = require('opener');
const path = require('path');
const resolve = require("enhanced-resolve");

let myResolve;

function renderViewer(jsonString) {
  return new Promise((resolve) => {
    fs.readFile(path.resolve(__dirname, './dependencies.html'), 'utf-8', (err, data) => {
      if (err) throw err;
      const html = data.replace(/<%=(\w+)%>/g, (match, $1) => jsonString);
      resolve(html);
    });
  });
}

function openBrowser(url, info) {
  try {
    opener(url);
    console.log(info);
  } catch (err) {
    console.error(`Opener failed to open "${url}":\n${err}`);
  }
}

async function startServer(jsonString) {
  const port = 8888;
  const host = '127.0.0.1';
  const isOpenBrowser = true;
  const html = await renderViewer(jsonString);
  http.createServer((req, res) => {
    if (req.method === 'GET' && req.url === '/') {
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end(html);
    } else {
      res.end('blank page');
    }
  }).listen(port, host, () => {
    const url = `http://${host}:${port}`;

    const logInfo = (
      `Webpack Source Code Dependencies Analyzer is started at ${(url)}\n`
      + `Use ${('Ctrl+C')} to close it`
    );

    if (isOpenBrowser) {
      openBrowser(url, logInfo);
    }
  });
}

const transformArrayToTree = (array) => {
  if (!array || array?.length === 0) return null;

  array.forEach(v => {
    (v.children || []).forEach(k => {
      const childDep = array.find(l => l.resource === k.resource);
      childDep && (k.children = childDep.children);
    })
  })
  return array[0];
};

/**
 * 通过source获取真实文件路径
 * @param parser
 * @param source
 */
function getResource(parser, source){
  if(!myResolve){
    myResolve = resolve.create.sync(parser.state.options.resolve);
  }
  return myResolve(parser.state.current.context,source);
}

class WebpackCodeDependenciesAnalysis {
  constructor() {
    this.pluginName = 'WebpackCodeDependenciesAnalysisPlugin';

    //  文件数组
    this.files = [];

    //  当前编译的文件
    this.currentFile = null;
  }

  apply(compiler) {
    compiler.hooks.compilation.tap(this.pluginName, (compilation, { normalModuleFactory }) => {
      const collectFile = (parser) => {
        const { rawRequest, resource } = parser.state.current;
        if (resource !== this.currentFile) {
          this.currentFile = resource;
          this.files.push({
            name: rawRequest,
            resource,
            children: []
          });
        }
      }
      const handler = (parser, options) => {
        parser.hooks.importCall.tap(this.pluginName, (expr) => {

          // 跳过node_modules
          if (parser.state.current.resource.includes('node_modules')) {
            return;
          }

          collectFile(parser);

          let ast = {};
          const isWebpack5 = "webpack" in compiler;
          // webpack@5 has webpack property, webpack@4 don't have the property
          if(isWebpack5){
            // webpack@5
            ast = expr.source;
          } else {
            //webpack@4
            const { arguments: arg } = expr;
            ast = arg[0];
          }
          const { type, value } = ast;
          if (type === 'Literal') {
            const resource = getResource(parser, value);
            this.files[this.files.length - 1].children.push({
              name: value,
              resource
            });
          }
        })
        parser.hooks.import.tap(this.pluginName, (statement, source) => {
          if (parser.state.current.resource.includes('node_modules')
          ) {
            return;
          }
          collectFile(parser);
          this.files[this.files.length - 1].children.push({
            name: source,
            resource: getResource(parser, source)
          });
        });
      }

      normalModuleFactory.hooks.parser
        .for("javascript/auto")
        .tap(this.pluginName, handler);
    });


    compiler.hooks.make.tap(this.pluginName, (compilation) => {
      compilation.hooks.finishModules.tap(this.pluginName, (modules) => {
        // const tree = transformArrayToTree(this.files);
        startServer(JSON.stringify(this.files));
      });
    });
  }
}

module.exports = WebpackCodeDependenciesAnalysis;

commonjs, es6支持tree-shaking方法

co模块内部是如何运行的

co 模块是一个基于 Generator 函数的协程管理器,它的核心代码非常简洁,

co 模块的原理很简单,它会接受一个 Generator 函数作为参数,并通过 next 方法迭代执行该函数中的异步任务,直到执行完毕或出现错误。co 函数返回一个 Promise 对象,用于接收异步任务的结果。

在执行 co 函数时,我们可以传入一个 Generator 函数,也可以传入一个返回 Generator 函数的函数。如果传入的是一个返回 Generator 函数的函数,那么需要在 co 函数调用时传入该函数所需要的参数。

co 函数内部,会将 Generator 函数的执行结果封装为一个 Promise 对象,并通过 Promisethen 方法迭代执行异步任务,直到所有异步任务执行完毕或出现错误。在执行异步任务时,如果异步任务返回的不是 Promise 对象,会将其封装为一个 Promise 对象后再执行。

co 模块的内部实现中,核心代码使用了 PromiseGeneratortry...catch 等语法,实现了异步任务的迭代执行和错误处理等功能,从而简化了异步编程的复杂性

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1)

  // we wrap everything in a promise to avoid promise chaining and handle both promise and non-promise errors
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args)
    if (!gen || typeof gen.next !== 'function') return resolve(gen)

    onFulfilled()

    function onFulfilled(res) {
      var ret
      try {
        ret = gen.next(res)
      } catch (e) {
        return reject(e)
      }
      next(ret)
    }

    function onRejected(err) {
      var ret
      try {
        ret = gen.throw(err)
      } catch (e) {
        return reject(e)
      }
      next(ret)
    }

    function next(ret) {
      if (ret.done) return resolve(ret.value)
      var value = toPromise.call(ctx, ret.value)
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected)
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, but the following object was passed: "' + String(ret.value) + '"'))
    }
  })
}

webpack打包产物是怎样的,到底是为了什么,优化了什么,为什么要合并文件

Webpack是一个现代的JavaScript应用程序静态模块打包器。它将JavaScript和其它静态资源(例如CSS、图片等)作为输入,输出为经过优化、压缩后的单个文件,这个文件可以在浏览器中运行。

Webpack打包产物一般是一个或多个JavaScript文件(可能包含样式、图片等资源),这些文件被组合、优化、压缩后输出到指定的目录。Webpack将应用程序分解为多个小块(称为模块),并分析这些模块之间的依赖关系。通过这种方式,Webpack可以消除重复的代码,并生成更少的文件。

Webpack的打包产物有以下优点:

  1. 支持模块化开发,便于管理项目。
  2. 能够对代码进行压缩和混淆,减小文件体积,提高页面加载速度。
  3. 支持各种类型的资源(如CSS、图片等)的打包,减少了资源的请求次数,提高了页面性能。
  4. 支持代码分割和按需加载,可以减少首屏加载时间。
  5. 支持自动化构建和部署,提高了开发效率。

合并文件是为了减少HTTP请求,因为每个HTTP请求都需要建立和断开连接,这个过程会消耗时间。将多个文件合并成一个文件,可以减少HTTP请求,从而提高页面加载速度。但是,如果合并的文件过大,也会导致页面加载时间变慢,因此需要根据具体情况进行权衡和优化。

await内部的原理

在 JavaScript 中,await 关键字用于等待一个异步操作的完成,并返回其结果。其内部的实现原理与 Promise 有关。

当在 async 函数中使用 await 时,实际上是在暂停当前 async 函数的执行,将控制权交回给调用者,直到该 await 后面的异步操作完成并返回结果,async 函数才会恢复执行。

在实现上,当遇到 await 关键字时,会将该表达式后面的操作转换为 Promise 对象,调用 Promise 对象的 then 方法注册回调函数,同时将控制权交回给调用者。当 Promise 对象状态变为 resolved 时,调用回调函数,async 函数继续执行。

简单来说,await 内部的原理就是利用 Promise 对象实现异步操作的等待和结果获取。当 Promise 对象状态变为 resolved 时,await 关键字会将 Promise 对象的结果返回给 async 函数。

前端怎么实现富文本编辑器

实现富文本编辑器通常需要以下步骤:

  1. 确定富文本编辑器的功能需求,例如字体、字号、加粗、斜体、下划线、颜色等等。
  2. 决定使用何种技术实现,例如原生 JavaScript,jQuery 或者 React 等等。
  3. 确定富文本编辑器的 UI 设计,例如工具栏的按钮布局,字体颜色面板,字体大小下拉框等等。
  4. 使用 contentEditable 属性将 DIV 或者其他 HTML 元素设置为可编辑状态,允许用户输入内容。
  5. 编写代码处理用户输入的内容并将其转化为富文本格式,例如使用 HTML 标记或者其他格式(如 Markdown、BBCode 等等)。
  6. 处理富文本格式的输出和存储,例如将其转化为 HTML、Markdown 或者其他格式。
  7. 处理复制和粘贴的内容,例如从 Word 文档或者其他富文本编辑器中复制粘贴的内容,需要进行格式转化和处理,避免粘贴到编辑器中的内容格式混乱或者出现其他问题。
  8. 对于一些特殊需求,例如插入图片、插入表格等等,需要进行特殊处理。

总之,实现富文本编辑器需要涉及多个方面,包括功能需求、UI 设计、技术选型、输入输出格式处理等等。需要仔细考虑每个步骤并进行细致的实现和测试

HTML5中拖拽事件有哪些API

在 HTML5 中,拖放操作涉及到多个事件和 API。以下是常用的 HTML5 拖放事件和 API:

  1. dragstart 事件:在拖动操作开始时触发,通常用于设置拖动的数据和效果
  2. dragenter 事件:当拖动对象进入目标元素时触发,通常用于设置目标元素的样式或执行一些操作。
  3. dragover 事件:当拖动对象在目标元素上方移动时触发,通常用于防止默认行为或执行一些交互操作。
  4. dragleave 事件:当拖动对象离开目标元素时触发,通常用于恢复目标元素的样式或执行一些操作。
  5. drop 事件:当拖动对象在目标元素上松开鼠标时触发,通常用于获取拖放数据并执行一些操作。

除了上述事件之外,还有一些 HTML5 拖放 API 可以使用:

  1. DataTransfer 对象:在拖动和放置操作中传递数据的对象,可以在 dragstart 事件中设置,然后在 drop 事件中获取。
  2. setDragImage() 方法:设置自定义拖动时的图像,通常用于创建拖动时的半透明效果。
  3. setData() 和 getData() 方法:用于在拖动和放置操作中传递数据,可以在 dragstart 事件中使用 setData() 方法设置数据,在 drop 事件中使用 getData() 方法获取数据。
  4. EffectAllowed 属性:指定拖动时的效果类型,通常用于设置允许的操作类型,比如移动、复制等。
  5. dropEffect 属性:指定放置时的效果类型,通常用于设置放置时的操作类型,比如移动、复制等。

html中的dragenter 和dragover的区别

dragenterdragover 是 HTML5 Drag and Drop API 中与拖放操作相关的两个事件。

dragenter 事件在拖动的对象进入可放置目标时触发,而且只会在对象进入目标时触发一次。这个事件通常用来设置目标元素在拖动对象进入时的样式或者执行某些操作。

dragover 事件则在拖动对象在可放置目标上移动时连续触发,也就是说,只要拖动对象在目标范围内移动,该事件就会持续触发。这个事件通常用来防止默认的拖放操作或者执行一些其他的交互操作,比如改变鼠标指针的样式或者显示一个提示框。

简而言之,dragenter 事件只在拖动对象进入目标范围时触发一次,而 dragover 事件则在拖动对象在目标范围内移动时持续触发。

怎么做JS代码Error统计

在JavaScript中进行错误统计通常有两种方式:

  1. 通过try...catch来捕获错误,然后将错误信息上报到后台。可以使用像Sentry、Bugsnag、Rollbar等第三方服务来自动收集和上报错误信息。
try {
  // 可能会出错的代码
} catch (error) {
  // 将错误信息上报到后台
  console.error(error);
  // 或者将错误信息发送到第三方服务
  Bugsnag.notify(error);
}

2.通过window.onerror事件监听全局的错误信息,然后将错误信息上报到后台或第三方服务

window.onerror = function(message, source, lineno, colno, error) {
  // 将错误信息上报到后台
  console.error(message, source, lineno, colno, error);
  // 或者将错误信息发送到第三方服务
  Bugsnag.notify(error);
};

一张高清图片,怎么保证其在不同移动端设备上的显示效果

要保证一张高清图片在不同移动设备上的显示效果,需要考虑以下几个方面:

  1. 分辨率适配:不同的设备具有不同的屏幕分辨率,要保证图片在各种设备上显示清晰,需要提供不同分辨率的图片,通过CSS中的媒体查询选择对应的分辨率图片进行显示。这可以通过使用srcset和sizes属性来实现。
  2. 图片压缩:对于大的高清图片,可以通过压缩图片来减小图片大小,减小图片加载时间,提高页面加载速度。可以使用工具进行压缩,如TinyPNG。
  3. 图片格式选择:选择合适的图片格式也能够影响到图片的显示效果。对于颜色丰富的图片,可以选择JPEG格式。对于颜色比较简单的图片,可以选择PNG格式。对于需要透明背景的图片,可以选择GIF或PNG格式。近年来,由于WebP格式具有更好的压缩比和图片质量,因此也可以考虑使用WebP格式。
  4. 图片缓存:对于已经下载的图片,可以进行本地缓存,以避免重复下载,提高用户体验和网页性能。可以通过Service Worker和Cache API来实现。
  5. 图片懒加载:对于移动设备来说,网络不稳定,因此可以采用图片懒加载的方式,将图片的加载推迟到用户需要查看的时候,以减少图片加载对页面性能的影响。

要保证一张高清图片在不同移动设备上的显示效果,需要对图片进行适当的压缩,选择合适的图片格式和分辨率,以及使用缓存和懒加载等技术手段来提高图片的加载速度和页面性能

说一下nodejs的原理

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。它是一个开源的、跨平台的、支持高并发的后端 JavaScript 运行环境。

Node.js 的工作原理如下:

  1. Node.js 通过事件驱动和非阻塞 I/O 模型来实现高效的 I/O 操作。它采用了 libuv 库来实现事件循环机制,以达到非阻塞 I/O 的效果。

  2. Node.js 使用单线程模型处理所有的 I/O 操作。这个单线程模型不是指 Node.js 只有一个线程,而是指 Node.js 在主线程上通过事件循环机制来处理所有的 I/O 操作和事件回调,从而避免了线程上下文切换所带来的性能损失。

  3. Node.js 的事件驱动机制采用了观察者模式。它把所有的 I/O 操作和事件回调都封装成事件,并注册到事件循环队列中。事件循环队列会不断地从队列中取出事件并执行它们,直到队列为空或程序被终止。

  4. Node.js 采用了模块化编程的方式。每个文件都被视为一个模块,每个模块都有自己的作用域,且模块之间相互独立。Node.js 采用 CommonJS 规范来实现模块化编程。

  5. Node.js 可以调用 C/C++ 模块。它提供了 C/C++ 扩展 API 来调用本地的 C/C++ 模块,以达到更高的性能和更多的功能扩展。

在懒加载方面,webpack提供了多种懒加载方案,包括使用动态 import 语法、使用 require.ensure() 方法、使用 bundle-loader 等。这些懒加载方案的本质是都是通过异步加载模块来实现懒加载。webpack 会将需要懒加载的模块单独打包成一个或多个 chunks,并在需要时动态加载。这样可以有效地降低页面的初始加载时间,提升用户体验

如何检测浏览器所支持的最小字体大小

在前端中,可以使用JavaScript中的getComputedStyle方法来获取元素的样式,包括字体大小等信息。可以通过设置一个很小的字体大小值,然后获取该元素的计算样式来检测浏览器所支持的最小字体大小。具体步骤如下:

  1. 创建一个元素,并设置一个很小的字体大小值,如1px。

  2. 将该元素添加到文档中。

  3. 获取该元素的计算样式,使用window.getComputedStyle() 方法。

  4. 检查计算样式中的font-size属性值,如果该值小于等于设置的值,则说明浏览器所支持的最小字体大小为该值。

以下是一个使用JavaScript检测浏览器最小字体大小的示例代码

// 创建一个元素并设置字体大小
const el = document.createElement('div');
el.style.fontSize = '1px';

// 将元素添加到文档中
document.body.appendChild(el);

// 获取计算样式并检测最小字体大小
const computedStyle = window.getComputedStyle(el);
const fontSize = parseFloat(computedStyle.fontSize);
if (fontSize <= 1) {
  console.log('浏览器所支持的最小字体大小为1px');
}

// 从文档中删除元素
document.body.removeChild(el);

需要注意的是,该方法并不能完全准确地检测出浏览器所支持的最小字体大小,因为不同的浏览器可能会有不同的实现方式,而且该方法也无法检测用户自定义的字体大小设置

Canvas的性能如何优化

Canvas 的性能优化可以从以下几个方面入手:

  1. 减少绘制区域:只绘制当前屏幕可见区域,可以使用剪裁区域(context.clip() )或使用瓦片绘制。
  2. 减少图形绘制次数:可以合并相邻的绘制,尽量使用 fillRect()strokeRect() 等简单 API,而非绘制复杂的图形。
  3. 减少绘制元素的数量:可以合并相同类型和属性的元素,例如可以将多个文本或图片合并成一张图片进行绘制。
  4. 减少图形层次:尽量减少图形的层次深度,如果图形层次太深,会导致性能下降。
  5. 减少绘制文本:尽量减少绘制文本的数量,可以使用图片代替文本,使用 CSS 控制字体等。
  6. 使用缓存:可以使用缓存技术,将已经绘制好的图形缓存起来,避免重复绘制。
  7. 使用 requestAnimationFrame():在绘制动画时,可以使用 requestAnimationFrame() API,它会自动调整绘制帧率,避免出现卡顿等情况。

在实际应用中,需要根据具体的场景和需求,综合考虑以上因素,并进行相应的优化。

node 中的js和浏览器中的js区别,从区别,相同,性能来分析

Node.js 中的 JavaScript 和浏览器中的 JavaScript 在某些方面存在不同,同时也有许多相同之处。

区别:

  1. 事件循环:Node.js 使用基于 libuv 库的事件循环机制,而浏览器使用 Web API 和 DOM 的事件循环机制。这意味着在 Node.js 中,所有 I/O 操作都是异步的,而在浏览器中,则可以使用诸如 setTimeout、setInterval 等方法实现异步操作。
  2. 模块系统:Node.js 使用 CommonJS 模块系统,而浏览器使用 AMD、ES6 模块等模块系统。Node.js 支持在服务器端直接加载 JavaScript 模块,可以使用 require() 方法来加载模块。而浏览器中,需要使用模块加载器(如 RequireJS)来实现模块化开发。
  3. API 支持:Node.js 提供了许多服务器端相关的 API,如文件 I/O、网络 I/O 等,而浏览器则提供了与 DOM、Web API 相关的 API。

相同:

  1. 语言本身:Node.js 和浏览器都使用 JavaScript 作为开发语言,因此两者的语言特性和语法基本相同。
  2. V8 引擎:Node.js 和 Chrome 浏览器都使用 V8 引擎,因此在 JavaScript 引擎性能上两者都比较接近。

性能:

Node.js 中的 JavaScript 在性能上可能会优于浏览器中的 JavaScript。这是因为,Node.js 不需要关注页面渲染等方面的工作,它的主要任务是处理服务器端请求和数据。此外,Node.js 也提供了一些高效的 API,如异步 I/O 操作、事件驱动等,这些都有助于提高 Node.js 的性能。

分析:

JavaScript 在 Node.js 和浏览器中都得到了广泛应用,它们都有自己的优势和适用场景。在实际开发中,需要根据具体需求和应用场景,选择合适的平台进行开发。同时,还需要注意两者之间的差异和相似之处,以便更好地发挥它们的优势。

目前的小程序和H5的区别,从性能,渲染方式来说

小程序和H5在性能和渲染方式上存在一些区别。

  1. 性能:小程序相对于H5更加高效。这是因为小程序采用了原生应用的方式进行开发,它的运行环境是在微信客户端中,能够利用设备的原生能力,比如网络、存储、摄像头、地理位置等等,以及相对于H5更高效的运行引擎,这使得小程序在性能上比H5更有优势。
  2. 渲染方式:小程序的渲染方式是基于组件化的,它的视图层和逻辑层分离,具有更好的可维护性和可扩展性,使得开发效率更高。而H5的渲染方式是基于DOM树的,相对比较灵活,但也因此存在一些性能问题,比如DOM树的渲染和重排等。

总的来说,小程序和H5的区别主要在于开发方式和运行环境。小程序利用原生能力和高效的运行引擎,具有更高的性能;而H5则具有更灵活的渲染方式,适合开发一些简单的页面。选择哪种技术取决于具体的业务场景和需求。

数据可视化相关

学习数据可视化需要掌握一定的数据分析和可视化技能,以下是一个基础的学习路径和相关的学习资料以及实战项目推荐

  1. 习数据分析和可视化基础知识,例如统计学、图形学、颜色理论、设计原则等等。
  2. 掌握数据分析和可视化的工具,例如Excel、Tableau、PowerBI等等。可以通过官方文档或者视频教程进行学习。
  3. 掌握数据可视化的编程语言和工具,例如JavaScript、D3.js、ECharts等等。可以通过官方文档、教程或者书籍进行学习。
  4. 实战项目实践,例如数据报告、数据仪表盘、交互式数据可视化等等。可以找一些公开的数据集进行练手,也可以参加数据可视化比赛来提高实战能力
  5. 以下是一些学习资料和实战项目推荐:

学习资料:

  • 《JavaScript 数据可视化编程实战》
  • 《D3.js 数据可视化实战手册》
  • 《ECharts 数据可视化实战》
  • Coursera上的数据可视化课程
  1. 实战项目:
  • 用D3.js制作交互式数据地图
  • 制作带有过滤器和动画效果的交互式数据报告
  • 制作交互式数据仪表盘,包含多个指标和图表

最后,要注意不断学习和实践,多看看别人优秀的数据可视化作品,不断提高自己的设计和编程能力

数据分析基础

学习数据可视化需要掌握基础的数据分析知识,包括数据的类型、数据的统计分析方法等。以下是一些基础资料:

数据可视化基础

学习数据可视化需要掌握基础的可视化技术,包括常见的图表类型、图表设计原则等。以下是一些基础资料:

可视化库

掌握可视化库是实现数据可视化的重要手段。以下是一些常用的可视化库:

可以从 D3.js 开始学习,D3.js 是一个功能强大的 JavaScript 数据可视化库,包括多种图表类型、数据处理、交互等功能。以下是一些学习资料:

基础架构建设

基础架构建设(Infrastructure Building)指的是在软件开发项目中搭建和管理底层的技术基础设施,例如服务器、存储、网络、安全等基础设施。这些基础设施是支撑软件应用程序开发、部署和运行的基础,它们必须可靠、高效和安全

基础架构建设是指为软件系统提供基础设施、环境、工具和平台等的过程,以支持软件系统的开发、测试、部署、维护和扩展。基础架构建设通常包括硬件、网络、操作系统、数据库、中间件、开发工具、测试工具、自动化部署工具等方面

下面是一些基础架构建设的具体例子:

  1. 服务器:搭建和管理用于部署应用程序和存储数据的服务器。
  2. 存储:搭建和管理数据库、文件存储、缓存等存储系统。
  3. 网络:搭建和管理网络架构,包括负载均衡、CDN等。
  4. 安全:搭建和管理安全设施,包括防火墙、入侵检测系统等。
  1. 硬件基础架构:包括服务器、存储、网络设备等,用于提供计算、存储、通信等基础设
  2. 操作系统基础架构:包括操作系统、安全管理、系统监控、容器等,用于提供稳定、安全、高效的操作系统环境。
  3. 数据库基础架构:包括关系型数据库、非关系型数据库、数据仓库等,用于管理和存储应用程序的数据。
  4. 中间件基础架构:包括消息队列、缓存、负载均衡、反向代理、API网关等,用于解耦、加速、保护应用程序。
  5. 开发工具基础架构:包括版本控制系统、构建工具、持续集成/持续交付工具等,用于提高软件开发的效率和质量。
  6. 测试工具基础架构:包括自动化测试工具、性能测试工具、安全测试工具等,用于保证软件系统的质量和可靠性

基础架构建设对软件开发项目的作用是:

  1. 提高开发效率:通过搭建可靠的基础架构,开发人员可以更快地部署和测试软件应用程序,从而加速软件开发的整个过程。
  2. 提高应用程序的可用性:可靠的基础架构可以保障应用程序的稳定性和可用性,提高用户的满意度和信任感。
  3. 提高安全性:通过搭建安全的基础架构,可以有效防止黑客攻击、数据泄露等安全问题,保护用户和企业的利益。

基础架构建设的优点包括:

  1. 可靠性:可靠的基础架构可以确保软件应用程序的稳定性和可用性。
  2. 安全性:安全的基础架构可以保护企业和用户的利益,防止黑客攻击、数据泄露等安全问题。
  3. 灵活性:良好的基础架构可以提供灵活的扩展和升级方式,满足不同规模

基础架构建设缺点: 首先,它需要大量的投资和资源来实现。其次,一些特定的基础架构可能无法适应某些特定的应用场景。最后,一旦基础架构出现问题,整个软件系统的运行都将受到影响 开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天 点击查看