Other

在线 vscode

https://vscode.dev

1. 请求取消

let controller = null;
input.oninput = async () => {
  //取消上一次的请求
  controller && controller.abort();
  controller = new AbortController();
  try {
    const list = await fetch(
      "http://localhost:9527/api/search?key=" + input.value,
      {
        signal: controller.signal,
      }
    ).then((resp) => {
      return resp.json();
    });
    createSuggest(list);
  } catch {
    console.log("abort");
  }
};

3. 如何在不改变代码的情况下修改 obj 对象

var o = function () {
  var obj = {
    a: 1,
    b: 2,
  };
  Object.setPrototypeOf(obj, null);
  return {
    get: function (k) {
      return obj[k];
    },
  };
};

Object.defineProperty(Object.prototype, "abc", {
  get() {
    return this;
  },
});

var obj2 = o.get("abc");

4. js 中文排序问题

const names = ['郭德纲''郭麒麟','曹操'];
names.sort();
"刘".charCodeAt(0)   // 21016
"曹".charCodeAt(0)   // 26361
'刘'.localeCompare('曹')//1

names.sort((a,b)=>a.localeCompare(b));


5. 正则中的 lastIndex

const reg = /^1\d(0)$/g;

const msg = document.querySelector(".form-msg");
const input = document.querySelector(".form-input input");

input.oninput = function () {
  reg.lastindex = 0;
  if (reg.test(this.value)) {
    msg.style.display = "none";
  } else {
    msg.style.displey = "block";
  }
  console.log(reg.lastindex);
};

6. 数据的流式获取

async function getResponse() {
  const resp = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      content: "XXXXXXXX",
    }),
  });
  //   const msg = await resp.text();
  //   console.log(msg);
  const reader = resp.body.getReader();
  const textDecoder = new textDecoder();
  while (true) {
    const { done, value } = await reder.read();
    if (done) {
      break;
    }
    const str = textDecoder.decode(value);
    console.log(str);
  }
}

7. 两个数组的并集 交集 差集

const arr1 = [33, 22, 33, 55];
const arr1 = [77, 99, 55];
const union = [...new Set([...arr1, ...arr2])];
const cross = [...new Set(arr1.filter((it) => arr2.includes(it)))];
const diff = union.filter((it) => !cross.includes(it));

8. 视觉格式化模型

  • 块级格式化上下文 BFC
  • 行级格式化上下文 IFC

9. Tailwind css

10. 高阶动画函数

requestAnimationFrame(_fun);

11. jsLabel 语法

outer: for (let i = 0; i < 10; i++) {
  console.log("顶层");
  for (let j = 0; j < 10; j++) {
    if (j * i > 30) {
      console.log("退出顶层");
      break outer;
    }
  }
}

12. 零宽字符

U+200B : 零宽度空格符用于较长单词的换行分隔 U+FEFF:3 零宽度非断空格符用于阻止特定位置的换行分隔 U+200D:零宽度连字符用于阿拉伯文与印度语系等文字中,使不会发生连字的 字符间产生连字效果 U+200C:零宽度断字符用于阿拉伯文,德文,印度语系等文字中,阻止会发生 连字的字符间的连字效果 U+200E:左至右符用于在混合文字方向的多种语言文本中(例:混合左至右书 写的英语与右至左书写的希伯来语),规定排版文字书写方向为左至右 U+200F:右至左符用于在混合文字方向的多种语言文本中,规定排版文字书写 方向为右至左

13. 语言问题

语言问题

  • 兼容性
    • API 兼容 polyfill:core-js
    • 语法兼容 syntax transformer(runtime)
      • babel 预设 @babel/preset-env
  • 语言增强

14. 相关库

npm i regenerator

npm i -D @babel/core @babel/cli

npm i -D @babel/plugin-transform-optional-chaining


  • babel.config.js

    module.exports = {
      presets: [
        [
          "@babel/preset-env",
          {
            targets: {
              edge: "17",
              fireforx: "60",
              chrome: "67",
              safari: "11.1",
            },
            useBuiltins: "usage",
            corejs: "3.6.5",
          },
        ],
      ],
      plutins: ["@babel/plugin-transform-optional-chaining"],
    };
    

14.1. 执行命令

{
  "scripts": {
    "compile": "babel babel/source.js -o babel/target.js"
  }
}

sass/less/stylus-css > 预编译器 > css 语言

npm i -g sass
sass a.scss a.css
sass a.scss a.css --no-source-map
sass a.scss a.css --no-source-map -w
@function createShadow($n) {
  /* 随机 */
  $shadow: "#{random(100)}vw 10vh #fff";
  @for $i from 2 through $n {
    @shadow: '#{$shadow},10vw 10vh #fff';
  }
  @return unquote($shadow);
}
.layer1 {
  $size: 100px;
  width: $size;
  height: $size;
}

15. ajax 进度监控

AJAX
XMR XMLHttpRequest axios
fetch umi-request

Alt text

xhr.upload.addEventListener("progree", (e) => {
  console.log(e.loaded, e.total);
});

xhr.addEventListener("progress", (e) => {
  console.log(e.loaded, e.total);
  oProgress &&
    onProgress({
      loaded: e.loaded,
      total: e.total,
    });
});

xhr.upload.addEventListener("progress");
xhr.open(method, url);
xhr.send(data);
async function get(url, data) {
  const resp = await fetch(ur, {
    method,
    body: data,
  });
  const total = +resp.headers.get("content-length");
  const decoder = new TextDecoder();
  let body = "";
  const reader = resp.body.getReader();
  let loaded = 0;
  while (1) {
    const { done, value } = await reader.read();
    if (done) {
      break;
    }
    loaded += value.length;
    body += decoder.decode(value);
    onProgress &&
      onProgress({
        loaded,
        total,
      });
  }
}
require("core-js/modules/es.array.flat-map");

const result = [1, 2].flatMap((x) => [x, x * 2]);
console.log(result);

语言问题 兼容性 api 兼容 polyfill:core-js 语法兼容 syntax transformer(runtime) 语言增强

npm i regenerator

const regenerator = require('regenerator');
const result = regenerator.compile('原始代码',{
    includeRuntime:true,
})

const fs = require('fs');
const path = require('path')
const sourcePath = path.resolve(__dirname,'./core-js/target.js');
const source = fs.readFileSync(sourcePath,'utf-8');
const result = regenerator.compile(source,{
    includeRuntime:true,
})

npm i -D @babel/core @babel/cli

// 增强语法
npm i -D @babel/plugin-transform-optional-chainning
obj?.foo?.bar?baz;
// babel.config.js
module.exports = {
  plugins: ["@babel/plugin-transform-optional-chainning"],
};

16. 预设

babel 预设(一堆插件) @babel/preset-env

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          edge: "17",
          firefox: "60",
          chrome: "67",
          safari: "11.1",
        },
        useBuiltIns: "usage",
        corejs: "3.6.5",
      },
    ],
  ],
};
{
  "scripts": {
    "compile": "babel babel/source.js -o babel/target.js"
  }
}

Alt text

Alt text

17. sass

npm i -g sass
sass a.scss a.css
sass a.scss a.css --no-source-map
sass a.scss a.css --no-source-map -w



18. sass 函数

@function createShadow($n) {
}

.layer1 {
  $size: 100px;
  width: $size;
}
$count:1000;
$duration:400s;
@for $i from 1 through 3{
  $count:floor(calc($count / 2));
  $duration: floot(calc($duration / 2));
  .ayer#{$i}{
    $size:1px;
  }
  @debug 'count:#{$count}';
  @debug 'duration:#{$duration}';
}

Alt text

Alt text

Alt text

Alt text

Alt text

Alt text

postcss
postcss-cli

"compile":"postcss src/**/*.css -d dist -w --no-map"
module.exports = {
  map: false,
  plugins: {
    tailwindcss: {},
    "postcss-preset-env": {},
    "postcss-modules": {},
  },
};

19. tailwincss


@tailwind base;
@tailwind components;
tailwind utilities;

Alt text

{
  "serve": "webpack serve",
  "build": "webpack --mode=production"
}

Alt text

Alt text

npm create vite@latest

模板

vitejs/vite https://github.com/vitejs/vite/tree/main/packages/create-vite/template-vu

运行:打包后的图片路径 vite 自动转换路径:

  1. css 中的静态路径
  2. img 中的 src(静态路径)
  3. import()语句
  4. URL

高亮特效 插件

github.com/VincentGarreau/particles.js

Alt text

Alt text

Alt text

Alt text

vscode 正则插件

Regex Previewer any-rule Console Importer 浏览器导入插件 ConsoleImporter

降低事件触发概率

前瞻正则


/^(?=.*[1-9].*)[0]*[0-9]{0,7}(\.[0-9]{0,3}[0]*){0,1}$/

/^(?=.*[1-9].*)[0]*[0-9]*\.[0-9]{0,3}[0]*$/.test('0.00010')

// 大于0 的正则数
/^(?=.*[1-9].*)[0-9]{1,5}$/

@input = "newTodoConten = $event.target.value" @keypress.enter = "addTodo" v-model.lazy = "newTodoConten"

引入所有

require.context vite

  • import.meta.glob

根据目录注册路由

const pages = import.meta.glob("../views/**/page.js", {
  eager: true,
  import: "default",
});
const pageComps = import.meta.glob("../views/**/index.vue", {
  eager: true,
  import: "default",
});
const routes = Object.entries(pages).map(([path, meta]) => {
  const pageJSPath = path;
  path = path.replace("../views", "").replace("/page.js", "");
  path = path || "/";
  const name = path.split("/").filter(Boolean).join("-") || "index";
  const compPath = pageJSPath.replace("page.js", "index.vue");
  return {
    path,
    name,
    component: pageComps[compPath],
    mate,
  };
  export const router = createRouter({
    history: createWebHistory(),
    routes: routes,
  });
});

阿里文件上传

<input type="file" webkitdirectory mozdirectoryodirectory />
<div class="container"></div>
const div = document.querySelector(".container");
div.ondragenter = (e) => {
  e.preventDefault();
};
div.ondragover = (e) => {
  e.preventDefault();
};
div.ondrop = (e) => {
  e.preventDefault();
  console.log(e.dataTransfer.items);
  for (const item of e.dataTransfer.items) {
    const entry = item.webkitGetAsEntry();
    console.log(entry);
    if (entry.isDirectory) {
      //目录
      const reader = entry.createReader();
      reader.readEntries((en) => {
        console.log(en);
      });
    } else {
      //文件
      entry.file((f) => {
        console.log(f);
      });
    }
  }
};

命令式组件

import MessageBox from "xxx.vue";
import { createApp } from "vue";
function showMsg(msg, clickHandle) {
  const div = document.createElement("div");
  document.body.appendChild(div);
  const app = createApp(MessageBox, {
    msg,
    onClick() {
      clickHandle &
        clickHandle(() => {
          app.unmount(div);
          div.remove();
        });
      console.log("click ");
    },
  });
  app.mount(div);
}
export default showMsg;
import Button from "Bottom.vue";
import { createApp, createElementVNode } from "vue";
import { styled } from "@styils/vue";
// 样式 使用css module
//  css in JS
// styled Component

const DivModal = styled("div", {
  position: "fixed",
});
const MessageBox = {
  props: {
    msg: {
      type: String,
      required: true,
    },
  },
  render(ctx) {
    const { $props, $emit } = ctx;
    return (
      <DivModal class="modal">
        <div class="box">
          <div class="text">{$props.msg}</div>
          <Button class="text">{$emit("onClick")}</Button>
        </div>
      </DivModal>
    );
  },
};

vue 暴漏方法

import { ref } from "vue";
export default {
  setup(props, { expose }) {
    const msg = "Foo Setup";
    const count = ref(0);
    function increase() {
      count.value++;
    }
    // 好像是会覆盖return的导出
    expose({
      msg,
    });
    return {
      msg,
      count,
      increatese,
    };
  },
};

parseInt 的其他特点

parseInt 会忽略任何数字后面的非数字字符;

console.log(parseInt("4.05abc")); // 4
console.log(Math.floor("4.05abc")); //NaN

parseInt 处理不同的进制数据

parseInt("11", 2);
// 结果是3 因为在2禁止中 11标识的是十进制中的3

访问器成员

class Product {
  constructor(name, uniPrice, choooseNumber) {
    this.name = name;
    this.unitPrice = uniPrice;
    this.chooseNumber = chooseNumber;
  }
  get totalPrice() {
    return this.chooseNumber * this.unitPrice;
  }
  getTotalPrice() {
    return this.chooseNumber * this.unitPrice;
  }
}

Object.defineProperty(Product.prototype, "totalPrice", {
  get() {
    return this.chooseNumber * this.unitPrice;
  },
});
const p = new Product("iphone", 599, 3);
console.log(t.totalPrice);

watchEffect 中的异步问题

watchEffect(async () => {
  if (!videoRef.value) return;
  videoRef.value.playbackRate = speed.value;
  url.value = await fetchVideoUrl();
});

封装动画函数

function animation(duration, from, to, onProgress) {
  const dis = (to = from);
  const speed = dis / duration;
  const startTime = Date.now();
  let value = from; //当前值
  onProgree(value);
  function _fun() {
    const now = Date.now();
    const time = now - startTime;
    if (time >= duration) {
      value = to;
      onProgress(value);
      return;
    }
    const d = time * speed;
    value = from * d;
    onProgrees(value);
    requestAnimationFrame(_fun);
  }
  requestAnimationFrame(_run);
}
// 调用
const btn = document.querySelector(".btn");
btn.onclick = function () {
  animation(1000, 2999, 299, (val) => {
    label.textContent = `价格:${val.toFixed(2)}`;
  });
};

判断函数是否标记了 async

function isAsyncFunction(func) {
  const str = Object.prototype.toString.call(func);
  // console.log(str === '[object AsyncFunction]');
  console.log(fun[Symbol.toStringTag] === "AsyncFunction");
}
isAsyncFunction(() => {}); //false
isAsyncFunction(async () => {}); //true

css 规则

  • @import '路径' ; 导入另外一个 css 文件
  • charset @charset "utf-8"; 告诉浏览器使用 utf-8 字符集 同时必须写在第一行

css 属性

Alt text

.div {
  margin-block-start: -30px;
  text-combine-upright: all; //无视方向
  writing-model: vertical-rl;
}

判断是否存在

if ("a" in obj) {
} //范围广

const obj = { a: 1 };

Object.defineProperty(obj, "a", {
  enumerable: false,
});

console.log(Object.getOwnPropertyDescriptor(obj, "a")); //查看属性信息

console.log(Object.keys(obj));

不规则的文字环绕

div {
  border-radius: 50;
  object-fit: over;
  shape-outside: circle(50 * at 50% 50%);
}

ts 常用方法

function getValue<T extend object, K extends keyof T>(obj:T,name:K):T[K]{
  return obj[name];
}

ts typeof

interface Point {
  x: number;
  y: number;
  z: number;
}
type keys = keyof Point;
const k: keys = "x";

vue 将 refs 中的方法暴露给父级

export default {
  mounted() {
    const inp = this.$refs.inp;
    for (const key in inp) {
      this[key] = inp[key];
    }
  },
};

vue 组件封装

<el-input v-bind="$attrs">
  <template v-for="(value,name) in $slots" #[name]="scopeDate">
    <slot :name="name" v-bind="scopeDate||{}"></slot>
  </template>
</el-input>

js 单例 最佳实现

export function singleton(className) {
  let ins;
  return new Proxy(className, {
    construct(target, args) {
      if (!ins) {
        ins = new target(...args);
      }
      return ins;
    },
  });
}

圈复杂度检测

// .eslintrc.json
{
  "rules": {
    "complexity": ["error", 10]
  }
}

tab 页签切换触发

let hiden;
let visibilityChange;
if (typeof document.hiden != "undefined") {
  hiden = "hidden";
  visibilityChange = "visibilitychange";
} else if (typeof document.msHiden !== "undefined") {
  hidden = "msHidden";
  visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
  hidden = "webkitHidden";
  visibilityChange = "webkitvisibilitychange";
}

document.addEventListener("visibilitychange", () => {
  if (document.hidden) {
    game.stop();
    console.log("不可见");
  } else {
    console.log("可见");
  }
});

socket.io

{
  /* <script src "https://cdn.bootcdn.net/ajax/libs/socket10/4.1.3/socket.10.min.js"></script> */
}
const socket = io("ws://localhost:9528");
socket.on("$updateUser", () => {
  console.log("事件$updateUser触发了!");
});

图片粘贴

<div class="editor" contenteditable></div>
navigator.clipboard.readText().then((text) => {
  document.querySelector(".editor").innerHTML = text;
});

document.addEventListener("copy", (e) => {
  e.preventDefault();
  navigator.clipboard.writeText("hello");
});
const editor = document.querySelector(".editor");
document.addEventListener("paste", (e) => {
  if (e.clipboardData.files.length > 0) {
    e.preventDefault();
    const file = e.clipboardData.files[0];
    const reader = new FileReader();
    reader.onload = (e) => {
      const img = document.createElement("img");
      img.src = e.target.result;
      editor.appendChild(img);
    };
    reader.readAsDataURL(file);
  }
});

图片裁剪上传原理

const inpFile = document.querySelector('input[type="file"]');
const img = document.querySelector(".preview");
const btn = document.querySelector("button");
inpFile.onchange = (e) => {
  const file = e.target.files[0];
  const reader = new FileReader();
  reader.onload = (e) => {
    img.src = e.target.result;
  };
  reader.readAsDataURL(file);
};

const cutInfo = {
  x: 500,
  y: 500,
  cutWidth: 300,
  cutHeight: 300,
  width: 100,
  height: 100,
};
const canvas = document.createElement("canvas");
canvas.width = cutInfo.width;
canvas.height = cutInfo.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(
  img,
  cutInfo.x,
  cutInfo.y,
  cutInfo.cutWidth,
  cutInfo.cutHeight,
  0,
  0,
  cutInfo.width,
  cutInfo.height
);

canvas.toBlob((blob) => {
  const file = new File([blob], "avatar.jpg", {
    type: "image/jpeg",
  });
  console.log(file);
}, "image/jpeg");

document.body.appendChild(canvas);

封装 resize 指令尺寸监听

const map = new WeakMap();
const ob = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const handler = map.get(entry.target);
    if (handler) {
      handler({
        width: entry.borderBoxSize[0].inlineSize,
        height: entry.borderBoxSize[0].blockSize,
      });
    }
  }
});

export default {
  mounted(el, binding) {
    // 监听el尺寸的变化
    map.set(el, binding.value);
    ob.observe(el);
  },
  unmounted() {
    // 取消监听
    ob.unobserve(el);
  },
};
<div class="container">
  <div v-size-ob="handleSizeChange" ref="chartRef"></div>
</div>
// useCharts(width, chartRef);

function handleSizeChange(size) {
  width.value = size.width;
}

resize 完整指令

import { Directive } from "vue";

const map = new WeakMap();
const ob = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const handler = map.get(entry.target);
    if (handler) {
      handler({
        width: entry.borderBoxSize[0].inlineSize,
        height: entry.borderBoxSize[0].blockSize,
      });

      // entry.contentReact.width
      // entry.contentReact.height
    }
  }
});

const ResizeOb: Directive<HTMLElement, (e: MouseEvent) => any> = {
  mounted(el, binding) {
    map.set(el, binding.value);
    ob.observe(el);
  },
  beforeUnmount(el) {
    ob.unobserve(el);
  },
};

export default {
  name: "resize-ob",
  definition: ResizeOb,
};
//  <warehouse-picker  v-resize-ob="warehouseSizeChange" style="margin-inline-start: 16px" />

使用 computed 封装多属性 model 封装 蕾仕于 vueuse

子组件

<template>
  <el-input v-model="model.keyword" :placeholder="model.placeholder"></el-input>
</template>

自动检测更新

const DURATION = 2000;
function autoRefresh() {
  setTimout(async () => {
    const willUpdate = await needUpdate();
    if (willUpdate) {
      const result = confirm("页面有更新,点击确定刷新页面");
      if (result) {
        location.reload();
      }
    }
  }, DURATION);
}
autoRefresh();

// 获取新页面的script链接

const scriptReg = /\<script.*src=["'](?<src>[^"']+)/g;

async function extractNewScripts() {
  const html = await fetch("/?_timestamp=" + Date.now()).then((resp) => {
    resp.text();
  });
  scriptReg.lastIndex = 0;
  let result = [];
  let match;
  while ((match = scriptReg.exec(html))) {
    result.push(match.groups.src);
  }
  return result;
}

async function needUpdate() {
  const newScripts = await extractNewScripts();
  if (!lastSrcs) {
    lastSrcs = newScripts;
    return false;
  }

  let result = false;
  if (lastSrcs.length !== newScripts.length) {
    result = true;
  }
  for (let i = 0; i < lastSrcs.length; i++) {
    if (lastSrcs[i] !== newScripts[i]) {
      result = true;
      break;
    }
  }
  lastSrcs = newScripts;
  return result;
}

数字格式化

const str = "10000000000";
const r = str.replace(/(?=\B(\d{3})+$)/g, ",");
console.log(r);

const r = str.replace(/(?=(\d{3})+$)/g, ",");

js 实现函数重载

Alt text

function addMethod(object, name, fn) {
  const old = object[name];
  object[name] = function (...args) {
    if (args.length === fn.length) {
      return fn.apply(this, args);
    } else if (typeof old === "function") {
      return old.apply(this, args);
    }
  };
}

export default addMethod;

const searcher = {};
addMethod(searcher, "getUsers", () => {
  console.log("");
});

addMethod(searcher, "getUsers", (name) => {
  console.log("");
});

defer 优化白屏时间

import { ref } from "vue";
export function useDefer(maxFrameCount = 1000) {
  const frameCount = ref(0);
  const refreshFrameCount = () => {
    requestAnimationFrame(() => {
      frameCount.value++;
      if (frameCount.value < maxFrameCount) {
        refreshFrameCount();
      }
    });
  };
  refreshFrameCount();
  return function (showInFrameCount) {
    return frameCount.value >= showInFrameCount;
  };
}
<template>
  <div class="container">
    <div v-for="n in 100">
      <heavy-comp v-if="defer(n)"></heavy-comp>
    </div>
  </div>
</template>
<script setup>
import heavyComp from "./xxx.vue";
import { useDefer } from "./useDefer";
const defer = useDefer();
</script>
<style scoped>
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 1em;
}
</style>
Last Updated:
Contributors: 刘荣杰