Git Product home page Git Product logo

til's Introduction

til's People

Contributors

vivipure avatar

Watchers

 avatar  avatar

til's Issues

JavaScript Generators

迭代器

JS 中迭代器是指包含 next 方法的对象, next 方法返回一个包含 value 和 done 键值的对象。

a.next() // {value: 1, done: false}
a.next() // {value: 2, done: false}
a.next() // {value: undefined, done: true}

生成器

生成器 是 一种类型的迭代器, 生成器函数返回一个生成器对象,这个对象是可迭代的

function * genFunction() {
     yield 1;
}
const g = genFunction()
g.next() // value: 1, done: false
g.next() // value: undefined , done: true

自定义迭代器

const customIterator = {
    [Symbol.iterator]: function * () {
        let i = 0;
        while(i++ < 10) {
             yield i
       }
    }
}

利用 [Symbol.iterator] 我们可以实现直接的迭代对象,该对象可以通过 for ... in... 进行遍历

yield 传值

当我们在生成器对象next 方法传入参数时,当前的 yield 会变成我们传入的参数

function * listener() {
  while(true) {
     let msg = yield
     console.log(msg)
  }
}
let l = listener()
l.next(1) // 1
l.next(2) // 2

纤程

纤程 是 更小的线程,在JS中,可以通过 generator 实现纤程的效果

TODO:demo

惰性求值

利用yield 结合 无限循环, 我们可以通过其他函数调用生成器函数,生产我们想要的值。实际上函数内部使用了 while(true) 的语法,但是执行线程不会进入死循环,十分的有趣。

Canvas 绘制跨域问题

最近在做浏览器合成PSD的功能,需要利用 canvas 生成 图层。

canvas 在绘制图片时,需要图片必须同源。 其实线上环境是ok的,主要是开发时,本地localhost和图片就产生跨域了。

直觉的方法就是本地配置一个反向代理,但是我还是嫌麻烦。查阅资料后,发现解决办法很简单。利用 fetch 即可

function imgUrlToCanvas(url: string, width: number, height: number) {
    return new Promise((resolve, reject) => {
        fetch(url)
            .then(res => res.blob())
            .then(res => {
                const url = URL.createObjectURL(res)
                const img = new Image()
                img.onload = () => {
                    const canvas = document.createElement('canvas')
                    canvas.width = width
                    canvas.height = height
                    canvas.getContext('2d')?.drawImage(img, 0, 0, width, height)
                    resolve(canvas)
                    URL.revokeObjectURL(url)
                }
                img.src = url
                img.onerror = reject
            })
            .catch(reject)
    })
}

代码逻辑很简单, 利用fetch 获取 blob ,然后 blob 转化为 url, 然后再绘制。这样就不会跨域了,十分的nice

关于HTTP

HTTP 版本

HTTP 1.0

无状态,无连接的应用层协议,每次请求都需要和服务器建立一个TCP连接

HTTP 1.1

1.0 的升级版,支持长连接,通过 Connection 字段设置 Keep-Alive 保持HTTP连接,避免每次连接都重复连接和断开TCP, 提高来连接效率

支持断点续传,请求头中新增 Range字段

支持 pipe 传输

支持长连接

HTTP 2

  1. 二进制分帧
  2. 基于流进行传输,多路复用
  3. 服务器推送(Server push)
  4. header压缩

HTTP 3

1.基于UDP的QUIC协议

一个新的提案对React 的影响

注意到React 一条新的PR, 是关于 rfc。

RFC: First class support for promises and async/await

链接地址: reactjs/rfcs#229

rfc文档: https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md

看了大致的内容,主要概括为两点

  1. 服务端时,函数组件可以直接使用 async await 获取数据后在返回 Node
  2. 客户端时,提供use函数,该函数支持异步,且解决了 hook 必须在不能在条件语句中使用的问题

这个对React 本身的影响还是挺大的。进行客户端开发,极大的方便了我们撰写逻辑,解除了hooks的限制。

对于React 的生态,例如Nextjs, Remix等,都有很大的影响。 以前必须要写的那些获取数据的额外函数,都不在需要约定了,可以直接写在组件内部。

[Vue] Vite 打包Vue 3组件

 build: {
    lib: {
      entry: "packages/components/index.ts",
      name: "funui",
      formats: ["umd", "iife"],
      fileName: "funui.js",
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        }
      }
    }
  },

在vite.config.ts中配置build.lib属性, 其中format为打包的格式,提供给html直接使用可以选择iife

需要注意的的是,打包Vue组件时不应该把Vue的相关依赖打包进去,否则会运行失败,所以在rollup中声明vue为外置依赖

还有组件中的css,不能设置为scoped

Web 性能优化

首页白屏优化

原因

页面在进行渲染时,同步加载的JS和CSS都会阻塞页面渲染。SPA 应用将所有逻辑打包成JS,导致浏览器加载js 时间过长,就会出现白屏情况

解决办法

  1. 首页加载不执行的js,可以使用异步加载,例如Vue 路由懒加载,异步组件
  2. 将通用库从 bundler中抽离,然后放在cdn
  3. gzip 压缩
  4. SSR
  5. 使用 prefetch ,preload 设置样式的优先级
  6. 骨架屏,loading
  7. 优化图片格式,使用webP
  8. 减少文件请求
  9. 使用缓存

5备注:
prefetch 加载浏览器页面可能需要的资源,浏览器不一定加载这些资源,在Vue 生成的App 中我们可以看到 路由的资源基本使用prefetch

preload 告诉浏览器页面必需这些资源,浏览器也一定会加载

preload 将加载和执行分离,不阻塞渲染。

perload/prefetch 不仅可以用于js,也可以加载样式图片。defer只能异步加载js

TypeScript tricks

Utility Types

Omit

Omit 可以忽略对象类型中的某些属性

type User = {
  name: string
  age: number
  id: string
}

type SimpleUser = Omit<User, "name" >

等同于

type SimpleUser = {
     age: number;
     id: string;
}

自身实现,利用Exclude 剔除属性

type OwnOmit<T , P extends keyof T> = {
  [K in Exclude<keyof T, P>]: T[K]
}

Pick

Pick 的功能和 Omit 相反,Pick 筛选出 对象类型中需要的属性

type SimpleUser = Pick<User, "name" >

等同于

type SimpleUser = {
     name: string;
}

本身的实现也很简单

type OwnPick<T , P extends keyof T> = {
  [K in P]: T[K]
}

Exclude

Omit的实现有用到 Exclude, 它的作用就是将第一个类型中,符合第二个类型中条件的类型剔除

对于 对象类型等,第二个类型是第一个类型的子集也可以达到效果

type TextExclude = Exclude<'number' | 'age', 'age'> // 'number'

自身实现

type MyExclude<T, U> = T extends U ? never : T

Extract

Extract的功能和 Exclude 相反, 它的作用就是将第一个类型中,符合第二个类型筛选的类型保留

type TextExclude = Exclude<'number' | 'age', 'age'> //  'age'

自身实现

type MyExclude<T, U> = T extends U ? T:  never

Partial

Partial可以让对象类型的每个属性变为可选

type UserPartial = Partial<User>

等同于

type UserPartial = {
    name?: string | undefined;
    age?: number | undefined;
    id?: string | undefined;
}

自身实现

type OwnPartial<T> = {
  [K in keyof T]?: T[K]
}

Required

Required可以让对象类型的的每个属性都变为必须

type UserRequired = Required<UserPartial>

等同于

type UserRequired = {
    name: string;
    age: number;
    id: string;
}

Required 的实现 使用了 -? ,语法奇怪但是很好理解

type OwnRequired<T> = {
  [K in keyof T]-?: T[K]
}

Record

Record 可以定义对象类型 键和值的类型

type A = Record<string, {name: string}>

自身实现

type OwnRecord<Key extends number | string | symbol , Value> = {
  [key in Key]: Value
}

ReturnType

ReturnType 可以返回函数类型的返回值类型

function test(username: string, password: string) {
  return { username, password }
}

type TestReturnType = ReturnType<typeof test>

等同于

type TestReturnType = {
    username: string;
    password: string;
}

自身实现

type OwnReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R: unknown

Parameters

Parameters 可以返回 函数类型的参数类型

function test(username: string, password: string) {
  return { username, password }
}

type TestParameterType = Parameters<typeof test>

等同于

type TestParameterType = [username: string, password: string, age: number]

它的实现也是通过 infer 来进行实现

type OwnParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : unknown

Awaited

对于 async 函数或者返回一个 promise值时, 我们可以通过 Awaited 得到未被Promise包裹的类型

async function testUser() {
  return 2
}  


type TestUserResult1 = ReturnType<typeof testUser> // Promise<number>

type TestUserResult = Awaited<ReturnType<typeof testUser>> // number

Edge Function

Edge Function

在油管上看到了 Vercel 关于 Nextjs 的Edge Function 的视频,这里做下总结。

视频地址: https://www.youtube.com/watch?v=WlP2TB2ORL4&ab_channel=Vercel

文章地址: https://www.netlify.com/blog/edge-functions-explained/

example : https://github.com/netlify/edge-functions-examples

在 Nextjs 中,我们可以使用SSR或者SSG模式。 在服务器返回静态内容时,可以被用户缓存,减少客户端渲染时间,提高访问速度。

Edge Function 是 Nextjs 提供的中间件能力,在我们访问路由时,可以在服务端自定义一些行为:

  1. 路由重定向【不同地区访问,返回不同内容,客户端路由不变】
  2. 自定义Header
  3. auth限制
  4. 针对不同地区用户返回特殊内容

从功能上来说,Edge Function 对静态内容进行了增强。可以看成动态化的静态【哈哈,有点奇怪】。在合适的业务场景,Edge Function 可以提供强大的能力。

Nextjs 中提供配置文件,可以针对不同的路由进行不同的 Edge Function .从开发者的角度来说,可以看成访问不同路由返回不同的静态内容,在返回之前会执行一些其他的逻辑。

实现图片懒加载的几种方式

getBoundingClientRect

使用 getBoundingClientRect 方法可以获取元素的尺寸和位置。在页面滚动时,可以计算所有的图片元素,获取元素的距离视图的高度,和视图的高度。如果视图高度大于距离高度,则将懒加载的地址放到src 中

img.getBoundingClientRect().top < window.innerHeigh

IntersectionObserver

IntersectionObserver 当元素进入到可视视图时就会触发回调

const imgIntersectionObserver = new IntersectionObserver((entries) => {
     for (let img of entries) {
          if(img.isIntersecting) {
            img.src = img.dataset.src
             imgIntersectionObserver .unobserve(img)
         }
      }
})

for (let img of imgList) {
     imgIntersectionObserver .observe(img)
}

原生支持

<img loading="lazy" src="" />

[Krpano] 全景问题

krpano内部在进行字符串比较相等时,有些字符不能进行比较。例如 scene-12 和scene-12 不相等,因为 - 在krpano中解析是有问题的

krpano 中的名称不能以数字开头,例如场景的name

Astro 使用体验

介绍

Astro 是 snowpack 的作者开发的前端框架,支持多种组件化框架。提供SSG,SSR等能力。

开始

npm create astro@latest

添加模块

solid 可以通过命令直接添加模块,例如引入 solid-js

npm astro add solid

当然也可以手动引入 solid-js@astro/solid-js,然后在 astro.config.mjs中引入

import { defineConfig } from 'astro/config';
import solid from '@astrojs/solid-js'

export default defineConfig({
  integrations: [ solid()]
});

使用组件

现代的开发框架都支持组件开发,astro自身支持 astro组件,也支持.tsx,.vue,.svelte等组件

甚至组件和组件之前可以各种嵌套。

---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.vue';
import CodeEditor from '../components/CodeEditor'
---
<Layout>
   <main>
         <Card />
         <CodeEditor />
   <main>
</Layout>

package.json 详解

package.json 作为 Node.js 包管理的配置文件,平时使用的频率太高了。但是对于其中有些字段的含义一直没有进行系统的学习。

因此特意记录下 各个字段的意义和功能,参考文档:https://docs.npmjs.com/cli/v8/configuring-npm/package-json

name

应用名,包名。可以在前面使用 @xx/xx, 标识 scope

version

版本号,可以参考 #10 (comment)

description

描述

keywords

关键词数组,用于 搜索

homepage

包首页,文档或者github

bugs

一般可以 issue 地址, npm bug 可以打开该地址

license

许可协议

author

作者信息

files

包所包含的文件

bin

声明后,用户下载时在bin目录会增加一个 对应的 可执行shell,对应的js 需要加上 shebang

 "bin": {
    "cli": "./cli.js"
  }

// cli.js

#!/usr/bin/env node

scripts

可以用来定义命令,用来 npm run 进行使用。除了用户自定义,也包含生命周期。

我在使用 husky 时就会使用 prepare ,在每次下载时,执行 prepare 对应的脚本

总共有 : prepare, prepublish, prepublishOnly, prepack, postpack

dependencies

  • dependenices 安装时需要的依赖,
  • devDependencies 开发时需要的依赖,例如babel, webpack
  • peerDependencies 要使用我这个包时,必须安装这个里面声明的包,一般库会使用。例如 Vue 的组件,需要在这个字段声明Vue

os

支持的系统

cpu

支持的CPU

workspace

monorepo 设置

type

type: 'module`

标识是 esm还是 commonjs

main

main 是 npm package 的入口文件,当我们使用 CommonJS 导入包时,实际上导入的是 main 所指向的文件

// package.json 
{
    "name": "dep",
    "main": "./dist/index.js"
}

const dep = require('dep')
// 等同于
const dep = require('dep/dist/index.js')

module

moduele 是ESM 导入时的寻找字段,若没有则引入 main 字段。所以一般库 CommonJS 放在 main, ESM 放在 module 字段

main: './dist/index.js',
module: './dist/index.mjs',

exports

exports 描述了子目录的访问路径,如果定义了 exports ,那么不在 exports 的模块,用路径也无法访问。

{
  name: 'midash',
  main: './index.js',
  exports: {
    '.': './dist/index.js',
    'es': './dist/es/index.js'
  }
}

engines

engines 指定了当前项目所需要的环境,例如声明 node 最小版本,如果本地环境与版本不匹配,则会进行报错

"engines": {
    "node": ">=14.0.0"
}

browser

在浏览器使用时,标识使用哪个文件

less 深入

less

https://lesscss.org/

作为常用的 CSS 预处理器之一,less 的使用频率是比较高的。相较于 Sass , less 的功能会相对简单。

作为开发者,使用嵌套写法只是基础的功能,还有写很多实用功能等着我们去探索。

下载

npm i less -D

webpack 使用

npm i less-loader -D

浏览器的进程和线程

浏览器中的进程

  1. 应用主进程
    浏览器应用进程
  2. 渲染进程
    每一个Tab
  3. GPU 进程
    GPU 渲染进程,CSS属性可以使用GPU渲染进行加速
  4. 网络进程
    网路请求
  5. 插件进程
  6. 音频进程

浏览器中线程

  1. GUI渲染线程
    页面渲染
  2. JS执行线程
    JS执行线程,与GUI线程互斥
  3. 事件线程
    事件监听线程,addEventListener
  4. 定时器线程
    setTimeout, setInterval, 定时将回调放在 宏任务队列中 让JS执行线程执行
  5. 异步网络线程
    异步网络请求

ES2022 的新语法

at 新的索引方法🤔

新增了了索引函数 at

const arr = [1,2,3]

arr.at(0) // 1
arr.at(-1) // 3

对数组,字符串,类型数组都起作用

Object.hasOwn 🙍‍♂️

新增hasOwn 方法判断自身属性,和 hasOwnProperty 没啥区别

Error Cause 🙍‍♂️

实例化 Error 时可以穿入 cause

const error = new Error('test error', {cause: ' error reason '})
error.cause

Top-level await 👌

允许await 在 async 函数之外使用,可以在顶级作用域使用

await Promise.resolve()

Class field 🤮

垃圾的私有变量语法还是通过了

class Sample {
   a = 2; // public

   #b = 4 // private

   static #c(){} // private

   static {}  // 类声明时调用 ???
}

除了这恶心的 标识符, 还引入了判断方法,来判断私有属性。 可以在静态方法中 判断 私有属性是否在对象中

class Sample {
    static is(obj) {
        return #b in obj && #method in obj
     }

}

常用的一些前端配置

别名配置

JS配置

新建 jsconfig.json, 方便编辑器代码跳转,对构建无意义

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "exclude": ["node_modules"],
  "include": ["src/**/*"]
}

TS配置

tsconfig.json 中配置 paths

 "compilerOptions": {
    "paths": {
      "@/*":["./src/*"]
    }
  }

Webpack配置

webpack.config.js 配置 resolve

  resolve: {
        alias: {
            '@': path.resolve('src'),
        },
  },

Vite 配置

vite.config.js中配置 resolve

resolve: {
    alias: {
      "@": path.resolve(__dirname, 'src')
    }
  }

Babel配置(RN使用时)

下载插件babel-plugin-module-resolver,在 babel.config.js中配置

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    [
      'module-resolver',
      {
        alias: {
          '@': './src',
        },
      },
    ],
  ],
}

npm 疑难杂症

Cannot read properties of null (reading 'pickAlgorithm')

清除缓存:

npm cache clear --force

husky: command not found

原因: 环境变量解析问题

解决: 在 pre-commit 中添加shell

PATH="/usr/local/bin:$PATH"

cannot set property closed of # which has only a getter

迁移电脑时,重新运行前端项目,都会报这个错。发现是node版本问题。nodejs 18.* 版本文件处理的方式有变化,导致webpack打包失败。

解决:nodejs 版本降级,使用16.18.1 版本即可

Linux 知识点

硬链接(hard link) 是指向文件本身

ln {source} {link}

软链接(soft link) 是指向文件名即路径

ln -s {source-filename} {symbolic-filename}

经典算法

fibonacci

F(0) = 0, F(1) = 1

when n > 1

F(n) = F(n-1) + F(n-2)

function fibonacci(n) {
    if(n < 2) return n
    return fibonacci(n - 1) + fibonacci(n - 2)
}

递归模式在参数较大时,速度较慢,可以增加缓存机制

const cacheMap = new Map()
function fibonacci(n) {
   if(cacheMap.has(n)) {
       return cacheMap.get(n)
   }
    if(n < 2) { 
       cacheMap.set(n, n)
       return n
    }
    const result =  fibonacci(n - 1) + fibonacci(n - 2)
    cacheMap.set(n, result)
    return result
}

Vue 2.7 升级之旅

前言

公司呆久了,总会有很多项目会被重构,重构的过程和结果都能让人很有收获。最近我又作为主程,开始了一个H5编辑器项目的重构。

重构过程中不仅涉及到UI的更改,还加了很多的新的功能。之前的数据结构也设计的不太合理,导致开发难度还是挺大的。

原技术栈: Vue 2 + VueX

升级后: Vue 2.7 + TS + Pinia

使用Vue 2.7 的原因

不用Vue3 是因为还有很多公用组件是Vue2 的,Vue3 升级成本较大。Vue 2.7 支持 setup 语法,加上 TS,开发体验俱佳。

升级TS 原因

编辑器项目,数据结构较多。各种数据处理和转化,有了TS才能得心应手

Pinia

pinia 代替 Vuex ,一是为了适配 composition api 的写法。二是对 store 进行抽离。Vuex 的设计导致 store 中的数据太过臃肿,且 mutation 确实没多大用。

过程

首先是升级 Vue

npm i [email protected] 
npm i pinia

加上 TS 支持

npm i ts-loader  -D
npm i typescript -D

类型声明文件

// setup 宏支持
/// <reference types="vue/macros-global" />

// vue 组件定义
declare module '*.vue' {
    import type { DefineComponent } from 'vue'
    const component: DefineComponent<{}, {}, any>
    export default component
}

// 扩展window 属性
interface Window {
    LOAD_STATUS: boolean
}
// 环境变量定义
declare var HTTP_ENV: 'prod' | 'test'

本来想把构建工具换成 Vite 的,但是项目依赖的 Module Federation 组件, vite 的插件还是存在一些问题。因此还是使用 webpack 5进行构建。后续会考虑增加对 Vite 的支持

结果

总的来说,Vue 2.7 + TS + Pinia 这一套是真的爽。composition api 还好,主要是 TS, 使用 TS 重写了数据结构之后,各种开发十分方便,类型提示几乎杜绝了以前经常出现的解析和转化bug.

Event Loop 一次搞定

Event Loop

JavaScript 处理事件执行顺序的机制

JavaScript 是单线程语言,执行代码时即有同步代码也有异步代码。同步代码的执行顺序就是按顺序执行,而对于异步代码,就需要我们对Event Loop 有足够的了解。

Event Loop 负责执行代码、收集和处理事件以及执行队列中的子任务

通常我们在执行代码时,会有执行堆栈。同步代码都在堆栈中执行。当堆栈中的代码执行完成后,就会从队列中取出任务进行执行。

任务队列主要是 定时器,Promise 等操作的回调函数

宏任务队列

定时器定义的回调,在时间达到后,会有 定时器线程将回调推入到 宏任务队列。

举例:setTimeout, setInterval, requestAnimationFrame,

举例

微任务队列

定义的Promise 回调在执行时,就会推入到 微任务队列。

举例:MutationObserver, Promise,

队列优先级

同步代码执行完成后,会将微任务队列中的任务执行完成,再执行宏任务队列中的任务,每执行一个后又会检查微任务队列,将微任务队列执行清空,重复此过程知道所有代码执行完成。

【Vue】 Vue.extend 存在的问题

在Vue2中,Vue.extend将组件转化为构造函数,通过实例化生成组件实例。因此我们可以通过函数调用的方式生成业务所需要的一些弹窗性质的组件。

通过平时的业务积累,我总结了些使用这种方法存在的一些不足支持。

生命周期问题

在new 构造函数的时候生成了组件的实例,这个时候就已经触发了created hook。 当我们通过函数将参数传递给实例后再挂载到dom时,发生在created中需要props值进行的相关操作都无法获得正常的props值。

如果有需要在生命周期调用函数使用props值的业务场景,我们需要将事件都放在mounted中,才能正常运行。


后面又发现了这个propsData属性,可以当作参数传入构造函数中,这样初始化时的prop值就是正确的值了。

this问题

之前遇到过 函数方式生成的组件中,this获取不到自定义挂载到prototype上的属性。

原因也很简单,入口文件使用的Vue和Vue.extend使用的Vue不是同一个Vue.所以在原型上找不到挂载的数据。

可以通过只引入一次,然后分别提供给入口和Vue.extend使用,就能解决这个问题

我在使用webpack5的Module Federation时并未遇到这种情况,可能是由于在模块导出时直接声明了Vue为外部依赖,导致使用了同一个Vue, 所以才没出现这个问题。

Router和Store

由于Router和Store是主应用在实例化时应用的,然后将$store属性和$router属性只有主应用的Vue下的组件vm才拥有该属性,使用Vue.extend生成的组件由于不再主应用的tree,因此获取不到对应属性。

function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }

目前暂时没有解决办法。。。

Web 安全扫盲

XSS

https://owasp.org/www-community/attacks/xss/

XSS (cross site scripting) 跨站脚本攻击,可以理解为网站会执行用户提交的内容。

类型:

  1. 反射型
    恶意输入包含到链接中,通过服务端生成包含恶意代码的HTML, 用户访问链接后执行恶意代码
  2. 存储型
    用户输入存储到服务器中,其他用户可以看到这个输入。如果这个输入直接渲染,可能会执行恶意代码。
  3. DOM型
    和反射性类似,一般是通过链接输入获取参数然后写入到页面中,区别是服务端返回HTML是正常的,客户端后续执行生成了恶意代码。例如innerHTML,eval 等操作。

出现原因:未对用户输入进行转义,直接展示用户的输入,导致执行用户输入的代码。常见于评论区,个人信息,私信等功能。可能造成其他用户的用户信息泄漏,危害极大。

如何防御:

  1. 对用户输入进行转义,过滤
  2. 使用 CSP ,进行脚本执行限制
  3. cookie 设置HttpOnly, 禁止 JS 获取
  4. 避免直接拼接HTML的操作,或者直接设置 innerHTML

XSS 攻击本质上可以说是 HTML注入,执行用户输入的内容

[Git] 常用命令

git reset <commit_id> 重置某个提交,会将对应更改进行回撤,提交一个新的提交

git reset --hard <commit_id> 重置到某个提交,该提交之后的所有更改都会丢失,不会产生新提交

git reset --soft <commit_id> 重置到某个提交,该提交后的所有更改会回到暂存区,不会产生新提交

git commit --amend 可以对已经提交的内容进行追加

git reset --hard origin/master 丢弃本地提交,重置为远端最新提交

[Vue] Vue 开发中的各种异常情况

报错: Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.

场景: 多个挂载到body下的弹窗在进行切换显示时报错

<Modal v-if="visible1">
<Modal1 v-if="visible2">
<Modal2 v-if="visible3">

原因:从报错中可知,Vue在DOM更新时,插入节点insertBefore方法报错。通过调试窗口得知 调用时实际传参为
image

visible1变为false,visible2变为true时, Modal组件已经销毁了,可是这里还是执行了插入方法,由于父节点不包含已经销毁的元素,所以会产生插入失败的报错.

解决方法:

<Modal v-if="visible1">
<Modal1 v-else-if="visible2">
<Modal2 v-else-if="visible3">

JavaScript 模块化

CommonJS

定义

CommonJS 规范定义了 exports 导出模块,require引入模块。Node.js 实现了 CommonJS 规范

module.exports = {
   name: "aa",
   age: 12,
}
//   等同于
exports.name = 'aa'
exports.age = 12
// ❌
exports = {
   name: "aa",
   age: 12,
}

Node.js 中每个模块都可以看作一个 module 对象,表示当前模块的信息,通过 module.exports 进行导出。

Node.js 使用 require 进行导入模块

const  a = require('./a'.js)
a.name
a.age

特点

  1. 导出的值是当前模块导出值的浅拷贝
  2. 运行时加载,所以可以在函数内动态加载
  3. 同步加载,有缓存

补充

Node.js 的寻包机制: TODO

Chrome Devtools 指南

Debug

在平时开发时,使用 Chrome Devtools 可以快速调试我们的程序,定位问题。

一. 断点

1. 1 代码断点

代码断点标识了代码暂停的位置,代码运行时,遇到断点就会停止执行。

如何进入

  1. 在 console 中打印对应函数名,点击就可以进入到函数的内部,添加断点就可以进行Debugging.
  2. 使用log, 通过log 堆栈,进入对应的代码
  3. 代码debugger, 在打开 devtools 时执行代码会自动暂停
  4. 在 Ctrl Shfit F中搜索关键字,找到相关逻辑并进入

断点除了直接断点,也可以添加条件断点,当代码符合某个逻辑才暂停。可以根据具体场景进行使用

1.2 DOM 断点

当JS操作DOM时,会更改DOM的属性或者结构,Devtools 提供 DOM断点,在DOM变化时进入断点逻辑

如何使用
选中元素,右键选择 break on, 选择对应场景。

subtree modifications :后代变化

attribute modifications : 属性变化

node removal : 节点删除

1.3 XHR 断点

我们可以通过主动添加 XHR断点, 根据接口地址添加断点,当浏览器发送该请求时,就能进入当前逻辑内。

除此之外我们也可以通过 Network -> 请求 -> Initiator 中查看代码堆栈进入对应逻辑

1.4 事件断点

在Elements 中选中具体元素,然后找到属性 EventListers ,找到对应事件,进入到代码逻辑中添加断点。

也可以在 Sources 中直接选中对应事件,然后勾选断点。当对应事件触发时,即触发断点

二. 调试

设置断点后就是调试代码,了解代码实际运行时的逻辑。

快捷键

  1. F10 按行步进
  2. F11 进入到当前函数内部
  3. F12 跳出当前函数

2.1 Watch

Watch 中可以监听变量的值,在处理一些循环时,我们可以讲关注的变量添加到 Watch 中方便我们查看变量变化

2.2 Scopes

当前代码执行处所在的作用域,可以查看当前可以访问到的变量值

2.3 Call Stack

执行堆栈,可以通过执行堆栈精确定位到代码触发的位置

三. 改动代码

有些调试场景并不在本地,而是在网路上。当我们想直接改动代码来调试逻辑时,我们可以通过以下步骤实现代码的改动

  1. Sources -> Overrides 选择本地文件夹
  2. 在代码中进行改动,然后保存,代码会保存到 Overrides 指定文件夹中
  3. 刷新,代码断点就会走本地JS逻辑,我们也可以在本地JS中进行修改

总结

通过 Devtools 进行Debug 不仅方便我们快速定位问题。熟练掌握能力后,对于常见的前端逆向推导,也十分有益处。

调试时注意函数入口和出口,观察变量变化。通过二分法快速断点,可以快速定位异常位置。

Transition 组件使用

介绍

Transition 组件是 Vue 的内置组件,用于组件内部的发生变化时提供切换的动效。
场景:

  1. v-if/v-show
  2. 动态组件
  3. 组件根节点

Transition 组件在动画变化时,提供 className 的变化,也提供JS hook。

class

  • v-enter-active
    • v-enter
    • v-enter-to
  • v-leave-active
    • v-leave
    • v-leave-to

v是 Transition 组件的name, 在使用CSS transition 时,我们可以定义 v-enter,v-leave-to 的状态,来配置动画

.slide-fade-enter-active {
  transition: all .3s ease;
}
.slide-fade-leave-active {
  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active below version 2.1.8 */ {
  transform: translateX(10px);
  opacity: 0;
}

在使用 CSS animation 时,可以定义 v-enter-active, v-leave-active 的动画

.bounce-enter-active
 {
  animation: bounce-in .5s;
}.
bounce-leave-active { 
 animation: bounce-in .5s reverse;
}
@keyframes bounce-in  {  
0% {    transform: scale(0);  }  
50% {    transform: scale(1.5);  }  
100% {    transform: scale(1);  }
}

Transtion 也提供自定义className 的属性

  1. enter-class
  2. enter-active-class
  3. enter-to-class (2.1.8+)
  4. leave-class
  5. leave-active-class
  6. leave-to-class (2.1.8+)

注意点:

  1. Transition 组件通过监听 transitionend 或者 animationend 时间来执行结束hook.当两种动画同时执行时,需要手动指定type属性,告诉 Transition 组件使用哪种事件监听
  2. 除了在 css 中定义动画事件,也可以使用 duration 属性定义动画的执行时间

JavaScript

Macbook 设置

问题: AppStore 无法正常登陆

解决: 设置网络 DNS, 114.114.114.114 , 8.8.8.8

RN 开发问题汇总

常见报错

1. java.io.IOException: Cannot run program “node”: error=2, No such file or directory

https://www.jianshu.com/p/2d180087e376

2. 打开 ios 项目构建时报错

使用 xcworkspace 文件打开项目,不要使用 xcodeproj 打开项目

3. 构建报错

查看报错原因,优先排查是否新增依赖。有则 npm i 然后 cd iospod install

4. cocopads 下载失败

不使用 ruby gem 进行下载,使用 homebrew 进行下载

5. 第一次初始化 ios 项目报错,pod install失败

遭遇 boost 文件无法通过http正常进行下载,可以找到镜像网站下载好对应依赖后,在依赖文件中从 文件中引用

6. 安卓真机预览时,无法热更新

使用adb 进行端口映射 adb reverse tcp:8081 tcp:8081

7. 引入SDK后报错

引入SDK 时注意 SDK 限制的架构,如果SDK 对架构有限制,记得在 ndk 中声明

8. NativeEmitter 问题

iOS 端需要先JS端监听事件,再原生 sendEvent, 否则会报错listener not regist, android 则不会有这个问题。因此处理时,可以将监听前置。

9. iOS 真机预览

需要设置开发者账号,如果真机预览时 iOS 版本过高无法正常预览,可以将对应的依赖下载好后放到/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport

下载地址: https://github.com/filsv/iOSDeviceSupport

10. pod install za

TypeError: null is not an object (evaluating 'RNGestureHandlerModule.flushOperations')

android 更新gradle ,ios重新pod install

11. pod install 下载 glog 报错

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer/

12. gradlew 执行错误

在个人文件夹( (C:\Users\username.gradle 或 ~.gradle))下创建gradle.properties文件,添加

org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m

13. sdk 无法自动link . undefined symbol arm64

到 Build Setting / Architectures / Excluded Architectures

添加 arm64

14. m1 执行 pod install 失败

sduo arch -x86_64 gem install ffi
arch -x86_64 pod install

15. m1 debug模式无法启动

xcode 以rosetta 模式运行

16. 'value' is unavailable: introduced in iOS 12.0

podfile

  post_install do |installer|
    react_native_post_install(
      installer,
    )
    # NOTE: Change IPHONEOS_DEPLOYMENT_TARGET to 12.4.
    installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4'
      end
    end
    __apply_Xcode_12_5_M1_post_install_workaround(installer)
  end

[JS] 高级但开发时不常用的知识点

函数柯里化

函数柯里化(curry)是指将一个多参数的函数转化为多个单参数的函数。

  • 最简实现
function curry(fn) {
   const  judge = (...args) =>
          args.length >= fn.length
              ? fn(...args)
              : (...arg) => judge(...args, ...arg)
    return judge
}

原理是这样的,JS中可以通过函数的length获取函数的参数个数,curry函数将fn包裹,返回一个函数。函数内部比较参数的长度,如果大于等于就执行fn,否则就返回通过递归和闭包继续执行

CSS 深入

display , visibility , opacity 区别

在平时开发时,隐藏元素有几种方式。在不同场景可以根据相关特性选择隐藏的方式。

例如最近遇到元素设置为 display: none 时,swiper初始化异常,原因应该是计算滚动区域的问题。又遇到 overflow 结合 visibility 时在 iOS上渲染异常的问题【渲染穿透,渲染了应该隐藏的元素】。

由于CSS的复杂性和在不同平台的兼容性问题,开发时我们应该了解特性后做出合适的样式。

说了很多额外的,下面步入正题:

display visibility opacity
占据页面空间
是否影响子元素设置该属性
触发事件
影响遮挡元素事件
transition
回流 reflow
重绘 repaint (不一定)链接

文件上传那些事

文件上传

上传 header 为 Content-Type: multipart/form-data, 前端上传时需要使用 FormData 进行上传

const formData = new FormData()
formData.append('file', file)
axios.post(formData)

大文件上传

在文件大小超大时,需要对上传环境有要求,长时间的上传有失败的机率。因此大文件上传一般可以做切片上传,然后后端进行合并。

前端

将文件进行切片,分割成 小的chunk 进行上传, chunk的名称可以用文件的名称加索引,方便后续合并

 file.slice(cur, cur + size)

每个切片上传完成后,调用合并接口,通知后端进行合并文件

后端

上传接口接收切片,将切片下载到对应的文件夹。

接受到前端的合并请求后,将文件夹的文件使用流进行合并。完成大文件的上传

断点续传

断点续传需要建立在 切片上传的基础上, 使用 md5 计算库 在webworker 中计算整个文件的hash , 每个切片以 hash + 索引的方式进行命名

前端方式

记录已经上传的文件的 hash 和对应索引, 跳过上传

后端方式

上传前后端返回已经上传过的切片,前端再进行跳过

后端的方式比较好,没有浏览器的限制

文件秒传

上传前生成文件的hash ,验证是否上传过,如果上传过则跳过上传,实现秒传的效果

百度网盘就是类似的机制

暂停上传

上传切片时可以存储对应的请求实例, 使用 AbortController 可以取消对应请求。暂停时,将对应实例全部取消请求即可。

恢复上传

和断点续传类似

多文件上传

有些业务场景可能需要上传几百张图片,如果有请求失败的话,可能对业务有影响。有两种方式进行解决

重试机制

单个请求失败后,可以进行重试。

  static retry(fn: () => MyPromise, times: number, delay: number) {
    return new this((resolve, reject) => {
      function attemp() {
        fn()
          .then(resolve)
          .catch((err) => {
            if (times > 0) {
              times--;
              setTimeout(() => {
                attemp();
              }, delay);
            } else {
              reject(err);
            }
          });
      }
      attemp();
    });
  }

压缩上传

前端通过 JSzip 将图片压缩成压缩包,后端再进行解压。这样只会有一个文件上传请求。

注意: 可能在一些配置较低的电脑上存在内存溢出的情况

相关文章

  1. https://juejin.cn/post/6844904046436843527#heading-0

PostCSS 学习

介绍

postcss是 css 的 babel, 用来处理css的转换

npm i postcss

配置文件

postcss.config.js

module.exports = {
	plugins: [
		require('...')
	]
}

插件

插件搜索: https://www.postcss.parts/

1. cssnano

yarn add cssnano

功能: 压缩css

2. autoprefixer

yarn add autoprefixer

功能: 自动添加浏览器兼容前缀
需要配置package.json 中的 browserslist,可以根据需求配置比例

"browserslist": {
	"cover 99.5%"
}

3. postcss-preset-env

https://preset-env.cssdb.org/

yarn add postcss-preset-env

功能: 支持新的语法,向下兼容

// postcss.config.js
...
	require('postcss-preset-env')({
		stage: 0,
	})
...

4. postcss-pxtorem

yarn add postcss-pxtorem

功能: 自动转化px为 rem

// postcss.config.js
...
	require('postcss-pxtorem')({
		 rootValue: 16,
	    unitPrecision: 5,
	    propList: ['font', 'font-size', 'line-height', 'letter-spacing'],
	    selectorBlackList: [],
	    replace: true,
	    mediaQuery: false,
	    minPixelValue: 0,
	    exclude: /node_modules/i
	})
...

Vue 知识点

data 声明的变量 不能以 _ 或者 $ 开头,Vue 不会将这些变量转化为响应式变量,且无法在实例上访问到该变量

// core/instance/state.ts
...
else if (!isReserved(key)) {
    proxy(vm, `_data`, key)
}
...

// core/util/lang.ts
export function isReserved(str: string): boolean {
  const c = (str + '').charCodeAt(0)
  return c === 0x24 || c === 0x5f
}

Vue3 使用心得

Teleport

在 Vue2 中,开发弹窗组件时,如果需要将DOM挂载到body下时,需要我们手动进行处理。

mounted() {
    document.body.appendChild(this.$el)
}

在Vue3 中,使用setup语法时是无法使用this的,而使用ref获取dom的方式也比较麻烦。

不过,Vue3提供了 Teleport组件,方便讲组件挂在到指定的dom下。类似于 ReactDOM.createPortal

 <Teleport to="body">
  ...
  </Teleport>

定时器那些事

开发中我们会用到 setTimeout,setInterval作为定时器,在做JS动画或者Canvas游戏时也会用到 requestAnimationFrame 这个 API, 接下来就好好讲一下 JS 中的定时器。

setTimeout 与 setInterval

第二个参数作为延时时间,到达时间后会执行回调。定时器时间并不准确,有执行时间的损耗。在定时器线程负责时间,时间到达后会放入 宏任务队列等待执行,setTimeout 只执行一次,setInterval 执行多次

可以用 clearTimeout ,clearInterval 取消回调

requestAnimationFrame

requestAnimationFrame 会在 微任务执行后, repaint 之前执行。如果执行过程中产生了新的微任务,则会在当前代码执行后,继续执行微任务.

requestAnimationFrame 的回调不属于 宏任务,也不是微任务。根据浏览器的渲染机制来决定的,当浏览器判断需要更新屏幕时,则会开始重新绘制,触发 requestAnimationFrame 的回调。

可以用 cancelAnimationFrame 取消回调

requestIdleCallback

requestIdleCallback 会在 repaint 之后,有空闲时间时进行执行,如果带有timeout参数,则会在超时后的下一帧强制执行。

CSS 技巧

关于Link

类型

  1. 加载css
  2. 加载favicon

加载问题

  1. link加载css,不影响DOM解析,影响DOM渲染
  2. 阻塞其之后的<script>标签的执行
  3. script标签既阻塞DOM解析,又阻塞DOM渲染

实现换肤效果

HTML 通过 link 加载 CSS, 当 link 标签的 rel 属性为 alternative ,且 title 属性存在时,该样式会加载但不渲染。

当我们控制js将该标签的 disabled 设为 false 时,该 link 标签引用的 CSS 就会进行渲染,可以通过这个特性实现换肤效果。

优点:

  1. 兼容性好
  2. 语义好
  3. 性能好,秒渲染

参考资料:

  1. https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types
  2. 张鑫旭的视频(https://www.bilibili.com/video/BV1kU4y1X7a8?spm_id_from=333.851.dynamic.content.click)

Swiper 开发汇总

  1. 当DOM处于 display : none 时,初始化的swiper由于无法获取高度,会导致swiper无法正常滑动。当DOM处于可显示状态时,使用swiper.update() 可以恢复正常。 隐藏DOM可以用 visibility

关于跨域

跨域

由于浏览器同源策略问题,当页面和请求的协议,主机或者端口不同时,则会判定为不同的域。即跨域请求

原因

同源策略

判定

协议,主机,port

解决办法

CORS

后端返回响应头加上 Access-Control-Allow-*

跨域时不同的请求的处理方式是不同的,简单分为两类,简单和需预检请求

简单请求:GET,POST,HEAD 。 此类请求不会发送预检请求

其他请求会发送 OPTIONS 请求,来判定 Access-Control-Allow-*是否被允许

  1. Access-Control-Allow-Origin 表示允许的来源
  2. Access-Control-Allow-Methods 表示允许的请求方法
  3. Access-Control-Allow-Headers 表示允许的请求头
  4. Access-Control-Allow-Credentials 表示允许携带认证信息

反向代理

可以后端创建一个同源的服务,用来做代理,利用服务端不受限制的特性来做接口转发处理

JSONP

以前很流行,现在很少用,只支持 GET请求

就是利用加载JS不跨域的特性,使用script src发送get请求,然后服务端返回将数据包裹在回调函数中进行返回。当请求返回回调函数时,该函数就会执行,实现跨域请求数据的需求。

原理相同的还有 ImgP

其他

  1. postMessage
  2. window.name 结合iframe 拿去跨域页面数据
  3. docment.domain 没用过

扩展

  1. localStorage 也存在跨域问题
  2. 针对跨域脚本,一般无法捕获具体错误。可以加上 crossorigin="anonymouse"加上JS返回允许跨域,即可显示详细堆栈
  3. canvas 绘制图片元素时也存在跨域限制,可以使用上述同种方法进行解决

curl 速记

下载文件

curl -o <fileName> <download_url>

浏览器存储

localStorage

持续存储,最大存储5M.

sessionStorage

页面浏览器用的临时存储,关闭页面就消失

cookie

React hooks

useId

useIdReact 18 新增的hooks, 可以生成独一无二的id。

当我们的组件html 里面包含 id 时,多个组件同时渲染,会表现为多个元素拥有同一个id,导致语法上不合理。虽然我们可以用随机数或者时间戳的方式生成id, 但是在函数组件中每次更新都会更改id,所以官方出了 useId.

官方的文档也说明了 useId 出现的作用

  1. 当第三方库需要独一无二的 id时
  2. 处理服务端渲染后,和客户端 hydrate 不匹配的问题

具体使用:

function Checkbox() {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>Do you like React?</label>
      <input id={id} type="checkbox" name="react"/>
    </>
  );
};

这里比较有意思的是,生成的 id 并不是合法的 selector id, 也就是说无法通过 DOM 匹配都该元素,毕竟 React 推荐使用 ref 来操作操作 DOM

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.