JS知识点总结
JS知识点总结
基础知识 #
移除一个对象的某个属性 #
const removeProperty = (propKey, { [propKey]: propValue, ...rest }) => rest;
object = removeProperty('a', object);
$ 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.js库,具有解析能力 引用: https://mathjs.org/docs/expressions/parsing.html#evaluate
math.evaluate('sqrt(3^2 + 4^2)') // 5
math.evaluate('sqrt(-4)') // 2i
math.evaluate('2 inch to cm')
通过function name调用function #
事先在全局环境定义好一个 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)
npm的相对地址和绝对地址 #
在npm的包里,使用 ./template/index.liquid 文件时, 当调用方解析该文件地址,是依照调用方的相对路径来解析的, 那就需要调用方存在该模板文件。 解决方法是,在npm源文件里的调用使用:
path.join(__dirname, './template/service.liquid')
如此,在包里,则会使用包里自带的模板文件。
杂项 #
gitignore重新添加 #
使用下面的命令把已经添加到仓库的缓存文件都删除。
git rm -r --cached .
修改或添加自己的gitignore 文件,然后重新 git add和提交即可。