JS知识点总结

JS知识点总结

基础知识 #

移除一个对象的某个属性 #

 const removeProperty = (propKey, { [propKey]: propValue, ...rest }) => rest;
 object = removeProperty('a', object);

https://stackoverflow.com/questions/208105/how-do-i-remove-a-property-from-a-javascript-object/52301527#52301527

$ npm install -g cnpm --registry=https://registry.npmmirror.com
npm install -g nrm
nrm ls
nrm use taobao
// 注意npm publish 到 npm官方站点后记得切换到官方源,否则看不到最新的版本

// 旧地址2022年(今年)5月份停止解析
npm config set registry http://registry.npmmirror.com

ts项目不识别@符号的相对路径设置 #

在tsconfig.json 或者jsconfig.json中,配置 compilerOptions 的路径,加入:

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

直接运行js字符串代码 #

引用: https://stackoverflow.com/questions/939326/execute-javascript-code-stored-as-a-string

  • 方法一:使用 Function,注意:如何为Function添加参数?
//Executes immediately
// 注意使用return, 不然没有返回值。
function stringToFunctionAndExecute(str) {
    let func = new Function(str);
    return (func()); // <--- note the parenteces
}

//Executes when called
function stringToFunctionOnly(str) {
    let func = new Function(str);
    return func;
}

// 有参数的Function
let func = new Function ([arg1[, arg2[, ...argN]],] functionBody);
let foo = new Function("name", "console.log(name)");
  • 方法二: 添加js脚本头
function executeScript(source) {
    var script = document.createElement("script");
    script.onload = script.onerror = function(){ this.remove(); };
    script.src = "data:text/plain;base64," + btoa(source);
    document.body.appendChild(script);
}

executeScript("alert('Hello, World!');");
  • 方法三: 提前定义好函数,传递 函数名字符串进入系统运行:
function runMe(x,y,z){
    console.log(x);
    console.log(y);
    console.log(z);
}

// function name and parameters to pass
var fnstring = "runMe";
var fnparams = [1, 2, 3];//<--parameters

// find object
var fn = window[fnstring];

// is object a function?
if (typeof fn === "function") fn.apply(null, fnparams);//<--apply parameter
  • 方法四: 使用setTimeout方法
 function ExecStr(cmd, InterVal) {
    try {
        setTimeout(function () {
            var F = new Function(cmd);
            return (F());
        }, InterVal);
    } catch (e) { }
}
//sample
ExecStr("alert(20)",500);
math.evaluate('sqrt(3^2 + 4^2)')        // 5
math.evaluate('sqrt(-4)')               // 2i
math.evaluate('2 inch to cm')  

通过function name调用function #

https://stackoverflow.com/questions/359788/how-to-execute-a-javascript-function-when-i-have-its-name-as-a-string

事先在全局环境定义好一个 function,然后在仅知道该function 名称的情况下调用它。

// context在浏览器内则指代 window 对象
function executeFunctionByName(functionName, context /*, args */) {
    var args = Array.prototype.slice.call(arguments, 2);
    var namespaces = functionName.split(".");
    var func = namespaces.pop();
    for (var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
    }
    return context[func].apply(context, args);
}
/**
 * Converts a string containing a function or object method name to a function pointer.
 * @param  string   func
 * @return function
 */
function getFuncFromString(func) {
    // if already a function, return
    if (typeof func === 'function') return func;

    // if string, try to find function or method of object (of "obj.func" format)
    if (typeof func === 'string') {
        if (!func.length) return null;
        var target = window;
        var func = func.split('.');
        while (func.length) {
            var ns = func.shift();
            if (typeof target[ns] === 'undefined') return null;
            target = target[ns];
        }
        if (typeof target === 'function') return target;
    }

    // return null if could not parse
    return null;
}
// https://juejin.cn/post/7078532391832125448
// 
function getPercentWithPrecision(valueList, precision) {
  // 根据保留的小数位做对应的放大
  const digits = Math.pow(10, precision)
  const sum = valueList.reduce((total, cur) => total + cur, 0)
  
  // 计算每项占比,并做放大,保证整数部分就是当前获得的席位,小数部分就是余额
  const votesPerQuota = valueList.map((val) => {
      return val / sum * 100 * digits
  })
  // 整数部分就是每项首次分配的席位
  const seats = votesPerQuota.map((val) => {
    return Math.floor(val);
  });
  // 计算各项的余额
  const remainder = votesPerQuota.map((val) => {
    return val - Math.floor(val)
  })
    
  // 总席位
  const totalSeats = 100 * digits
  // 当前已经分配出去的席位总数
  let currentSeats = votesPerQuota.reduce((total, cur) => total + Math.floor(cur), 0)
    
  // 按最大余额法分配
  while(totalSeats - currentSeats > 0) {
    let maxIdx = -1 // 余数最大的 id
    let maxValue = Number.NEGATIVE_INFINITY // 最大余额, 初始重置为无穷小

    // 选出这组余额数据中最大值
    for(var i = 0; i < remainder.length; i++) {
      if (maxValue < remainder[i]) {
        maxValue = remainder[i]
        maxIdx = i
      }
    }
        
    // 对应的项席位加 1,余额清零,当前分配席位加 1
    seats[maxIdx]++
    remainder[maxIdx] = 0
    currentSeats++
  }
    
  return seats.map((val) => `${val / totalSeats * 100}%`)
}

// 限制异步并发这个功能也很常见,如果同时发出一堆请求,网络可能就被这一堆请求占用了,其他请求就会排在后边,所以我们想限制下请求的并发数。
async function asyncPool(poolLimit, iterable, iteratorFn) {
  // 用于保存所有异步请求
  const ret = [];
  // 用户保存正在进行的请求
  const executing = new Set();
  for (const item of iterable) {
    // 构造出请求 Promise
    const p = Promise.resolve().then(() => iteratorFn(item, iterable));
    ret.push(p);
    executing.add(p);
    // 请求执行结束后从正在进行的数组中移除
    const clean = () => executing.delete(p);
    p.then(clean).catch(clean);
    // 如果正在执行的请求数大于并发数,就使用 Promise.race 等待一个最快执行完的请求
    if (executing.size >= poolLimit) {
      await Promise.race(executing);
    }
  }
  // 返回所有结果
  return Promise.all(ret);
}

// 使用方法
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
asyncPool(2, [1000, 5000, 3000, 2000], timeout).then(results => {
  console.log(results)
})

SVG基础知识 #

path #

元素用于定义一个路径。 下面的命令可用于路径数据:

  • M = moveto
  • L = lineto
  • H = horizontal lineto
  • V = vertical lineto
  • C = curveto
  • S = smooth curveto
  • Q = quadratic Bézier curve
  • T = smooth quadratic Bézier curveto
  • A = elliptical Arc
  • Z = closepath 注意:以上所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位。 如何绘制一个线条并在其上写字?
 <svg viewBox="0 0 500 500">
      <path id="curve" d="M 0 150 h 200" stroke="#0ff" stroke-width="1px" fill="none" />
      <text dy="15">
        <textPath startOffset="50%" text-anchor="middle" href="#curve" side="left">13'</textPath>
      </text>
    </svg>

其中的dy能控制文本的位置, text-anchor 是指文字水平位置。 先居中,然后根据文字长度左移一半的长度。

getBBox() 获取对象的矩阵对象 #

getBBox方法返回一个包含svg元素的最小矩形的坐标对象。坐标的位置相对于svg元素的原点,且不受任何transform变换的影响。对于测量text文本元素的宽度、高度很有用。

const svgText = document.querySelector('...')
const rect = svgText.getBBox()
// 如下
{
    x: 50,
    y: 50,
    width: 50,
    height: 50,
    // __proto__: SVGRect
}

DvaJS #

基础知识 #

state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件。其中connect 是一个函数,绑定 State 到 View,实现了为组件传递props。

注意这个state参数的数据来源, 它是 整个项目注册的modelTypde的字典集合,key就是model实例的namespace

import { connect } from 'dva';

// 注意这个state参数的来源, 它是 整个项目注册的modelTypde的字典集合,key就是model实例的namespace。
// 它会注入全部的models,你需要返回一个新的对象构成链接组件的props。
function mapStateToProps(state) {
  return { todos: state.todos };
}
// 把todos作为App组件的props,传递给App组件。
const wrappedApp = connect(mapStateToProps)(App);
// wrappedApp被称为 容器组件, 因为它是原始 UI 组件的容器,即在外面包了一层 State而已。

// 被 connect 的 容器组件 会自动在 props 中拥有 dispatch 方法,可在组件里使用: 
dispatch({
  type: 'click-submit-button',
  payload: this.form.data
})

模型解释 #

import { Effect, Reducer } from 'umi'
import { ActivitiesType, CurrentUser, NoticeType, RadarDataType } from './data.d'
import { fakeChartData, queryActivities, queryCurrent, queryProjectNotice } from './service'

// 模型状态定义
export interface ModalState {
  currentUser?: CurrentUser
  projectNotice: NoticeType[]
  activities: ActivitiesType[]
  radarData: RadarDataType[]
}

// Model模型定义
export interface ModelType {
  namespace: string
  state: ModalState
  reducers: {
    save: Reducer<ModalState>
    clear: Reducer<ModalState>
  }
  effects: {
    init: Effect
    fetchUserCurrent: Effect
    fetchProjectNotice: Effect
    fetchActivitiesList: Effect
    fetchChart: Effect
  }
}

// 创建一个新的 Model,umi会自动注册? 对,plugin-dva会自动遍历: model.ts
// 约定的目录: https://umijs.org/zh-CN/plugins/plugin-dva
// 各种plugin的配置在 config/config.ts 中,
// dva: {
//    immer: true, // 是否启用 immer 以方便修改 reducer
//    hmr: false, // 是否启用 dva model 的热更新
//  },
const Model: ModelType = {
  namespace: 'dashboardAndworkplace',
  state: {
    currentUser: undefined,
    projectNotice: [],
    activities: [],
    radarData: [],
  },
  effects: {
     // 计算以外的操作都属于 Effect,典型的就是 I/O 操作、数据库读写
     // Effect 是一个 Generator 函数,内部使用 yield 关键字,标识每一步的操作(不管是异步或同步)
     // call:执行异步函数
     // put:发出一个 Action,类似于 dispatch
      // select: 从 state 里获取数据。
      // _ 是action,因为没用到, 变成了占位符。
    *init(_, { put }) {
      yield put({ type: 'fetchUserCurrent' })
      yield put({ type: 'fetchProjectNotice' })
      yield put({ type: 'fetchActivitiesList' })
      yield put({ type: 'fetchChart' })
    },
    *fetchUserCurrent(_, { call, put }) {
      const response = yield call(queryCurrent)
      yield put({
        type: 'save',
        payload: {
          currentUser: response,
        },
      })
    },
    *fetchProjectNotice(_, { call, put }) {
      const response = yield call(queryProjectNotice)
      yield put({
        type: 'save',
        payload: {
          projectNotice: Array.isArray(response) ? response : [],
        },
      })
    },
    *fetchActivitiesList(_, { call, put }) {
      const response = yield call(queryActivities)
      yield put({
        type: 'save',
        payload: {
          activities: Array.isArray(response) ? response : [],
        },
      })
    },
    *fetchChart(_, { call, put }) {
      const { radarData } = yield call(fakeChartData)
      yield put({
        type: 'save',
        payload: {
          radarData,
        },
      })
    },
  },
  reducers: {
    save(state, { payload }) {
      return {
        ...state,
        ...payload,
      }
    // 启用 immer 之后
    // save(state, action) {
    //   state.name = action.payload;
    // },
    },
    clear() {
      return {
        currentUser: undefined,
        projectNotice: [],
        activities: [],
        radarData: [],
      }
    },
  },
}

export default Model

React #

useContext #

父组件传递给孙子组件的props或state,需要层层传递,非常麻烦,如果是公用的数据,如何做到一步到位?即孙组件可以直接访问当顶层的props。

// 首先在组件外部声明一个Context,等待引用。
import React from 'react';
const ThemeContext = React.createContext(0);
export default ThemeContext;

// 然后在父组件中引用一个已声明的Context,并包含了一些值。CC1组件里还有孙子组件。
<ThemeContext.Provider value={count}>
   <ContextComponent1 />
</ThemeContext.Provider>

// 最后:孙组件可以使用useContext直接获取到value值。
import ThemeContext from './ThemeContext';
function ContextComponent () {
  const value = useContext(ThemeContext);
  return (
    <div>useContext:{value}</div>
  );
}

注意: 使用 useContext的组件,即使memo了,只要context的value发生变化,组件的重新渲染总是发生(useState, useReducer等也是如此)。那该怎么消除某些时候不必要的渲染呢?

  • 推荐:将Context分割成不同用途的小Context,分配的原则是看 哪些数据是经常变化的,哪些是不常变化的。
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {}
};

// 分割成不同context,根据变化频度分割。
const ThemeContext = React.createContext(themes.light);
const AppContext = React.createContext(user);

let appContextValue = useContext(AppContext);
let themeContextValue = useContext(ThemeContext);

// 对于button组件来说,只用到theme的值, 这个值不是经常变化的,所以button组件很少重新渲染。
function Button() {
  let theme = useContext(ThemeContext);
  // The rest of your rendering logic
  return <ExpensiveTree className={theme} />;
}

function ExpensiveTree() {
  let theme = useContext(ThemeContext);
  // The rest of your rendering logic
  return <div className={theme} />;
}
  • 把一个组件分成两部分,内组件和外部组件。外部组件总是渲染,但是渲染开销比较小。内部组件不渲染,尽管它的渲染开销大。避免了开销大的渲染。
// AppContext的变化会引起Button的重新渲染,但是它的渲染开销小,因为内部组件的props可能并没有变化,memo后就无需重新渲染了。
// 这是个外部组件
function Button() {
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"
  return <ThemedButton theme={theme} />
}

// 这是个渲染开销大的内部组件,但是用memo后,与Context没有任何关联,Context的变化不会引起它的重新渲染,它只关心具体的theme值是否变化。
// 这是个内部组件
const ThemedButton = memo(({ theme }) => {
  /// 这个方法的不方便之处在于:内部组件依然需要定义复杂的props,
  // 如果是个普通的组件函数,则可以直接使用 theme
  return <ExpensiveTree className={theme} />;
});
  • 使用useMemo,返回一个包装的内部组件,本质和上面的方法一样,不过更简单。
function Button() {
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"
 // 整个组件依然会被重新渲染,但是由于使用了useMemo,会查看它的依赖 theme 值是否发生了变化,如没有,则会立刻返回。
  return useMemo(() => {
    // The rest of your rendering logic
    // 这个方法的不方便之处在于:内部组件依然需要定义复杂的props,
    // 如果是个普通的组件函数,则可以直接使用 theme
    return <ExpensiveTree className={theme} />;
  }, [theme])
}

避免重新渲染的技巧 #

如上所述,当一个组件里的 context、state、reducer等发生变化时,组件一定会触发重新渲染。但有些渲染是无意义的,因为不涉及组件props的变化。原则上只有组件的props的具体值(不仅仅是其props引用)发生变化再去渲染才是有意义的。而不是预期的组件内部或外部的动作引起的状态变化,重新渲染一般是无意义的。避免重新渲染的方法有:

  • 使用useMemo,返回一个包装的内部组件。
function Button() {
  // 若以下语句总是引起渲染
  let appContextValue = useContext(AppContext);
  let theme = appContextValue.theme; // Your "selector"
 // 整个组件依然会被重新渲染,但是由于使用了useMemo,会查看它的依赖 theme 值是否发生了变化,如没有,则会立刻返回。
  return useMemo(() => {
    // The rest of your rendering logic
    return <ExpensiveTree className={theme} />;
  }, [theme])
}

一个通用的HOC(高阶组件)方法:

const withContext = (
  context = createContext(),
  mapState,
  mapDispatchers
) => WrapperComponent => {
  function EnhancedComponent(props) {
    const targetContext = useContext(context);
    const { ...statePointers } = mapState(targetContext);
    const { ...dispatchPointers } = mapDispatchers(targetContext);
    return useMemo(
      () => (
        <WrapperComponent {...props} {...statePointers} {...dispatchPointers} />
      ),
      [
        ...Object.values(statePointers),
        ...Object.values(props),
        ...Object.values(dispatchPointers)
      ]
    );
  }
  return EnhancedComponent;
};

// 具体的使用方法:
const mapActions = state => {
  return {};
};

const mapState = state => {
  return {
    theme: (state && state.theme) || ""
  };
};
// 导出了包装后的高阶组件。
export default connectContext(ThemeContext, mapState, mapActions)(Button);
  • 将内部组件作为props传入组件中。
// 该组件总是渲染时, 内部的Logger也需要重新创建一次,故每次渲染都是一个新的实例。
function Counter(props){
    return (	
    <Logger label="counter"/>
)}
// 该组件总是被渲染,但是因为可能传递的是同一个logger, 所以logger本身并不会重新创建,使用的是同一个实例。
function Counter({logger}){return (	{logger})}

React.memo的用途 #

const MyComponent = React.memo(function MyComponent(props) {  /* 使用 props 渲染 */});

相同 props 的情况下渲染相同的结果,则不会再次渲染,提高性能。但是React.memo 仅检查 props 变更,且是浅层对比,如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 context (app state)发生变化时,它仍会重新渲染。

以下演示使用第二个函数来判断是否重新渲染。

// 以下演示使用第二个函数来判断是否重新渲染。
function MyComponent(props) {  
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*  如果把 nextProps 传入 render 方法的返回结果与  将 prevProps 传入 render 方法的返回结果一致则返回 true,  否则返回 false  */
}
export default React.memo(MyComponent, areEqual);

React.memo 与 useMemo的区别 #

React.memo 相当于 类组件的 PureComponent, 它通过比较组件的props是否已改变,来决定是否需要重新调用 render方法或者 函数组件自身。

const ThemedButton = memo(({ props }) => {
  // props对象尽可能是简单对象, 这样可以充分利用memo自带的浅比较机制  
  // 如果props的对象有复杂的引用对象, 则引用相同不会重绘。  		 return <ExpensiveTree {...props} />;
});

useMemo 只能用在函数组件的内部,可以将子组件包裹起来。 函数组件本身总是被渲染的,这与react.memo是不同的。 只是 use Memo包裹的子组件根据依赖,来决定是否重新渲染其子组件

一句话总结: react.memo后的组件,可以减少自身被父组件重复渲染;useMemo包裹后的子组件,可以减少子组件的渲染,但自身可能总是被渲染。

function Button() {  // 若以下语句总是引起渲染  let appContextValue = useContext(AppContext);  let theme = appContextValue.theme; // Your "selector" // 整个组件依然会被重新渲染,但是由于使用了useMemo,会查看它的依赖 theme 值是否发生了变化,如没有,则会立刻返回。  return useMemo(() => {    // The rest of your rendering logic    return <ExpensiveTree className={theme} />;  }, [theme])}

触发组件重新渲染的技巧 #

组件的props是个复杂深层次的对象,对象的引用本身不变,则对象属性改变不会触发更新。

const props = {  user:{  	name: 'Tom',  	age: 18  }}

props.user.name = ‘Bob’ 并不会触发组件更新。

nodeJs #

发布npm命令 #

npm publish

npm install -g nrm
nrm ls
nrm use taobao
// 注意npm publish 到 npm官方站点后记得切换到官方源,否则看不到最新的版本

保证npm的name和version不能都一样。(记得编辑version的版本和登录 npm login)

https://www.npmjs.com/package/swagger-code-gen-liquid

npm的相对地址和绝对地址 #

在npm的包里,使用 ./template/index.liquid 文件时, 当调用方解析该文件地址,是依照调用方的相对路径来解析的, 那就需要调用方存在该模板文件。 解决方法是,在npm源文件里的调用使用:

path.join(__dirname, './template/service.liquid')

如此,在包里,则会使用包里自带的模板文件。

杂项 #

gitignore重新添加 #

使用下面的命令把已经添加到仓库的缓存文件都删除。

 git rm -r --cached .

修改或添加自己的gitignore 文件,然后重新 git add和提交即可。