手撕
js 特性
7.自增运算符
// 1
let a = 1;
// 2 2
const b = ++a;
// 3 2 2
const c = a++;
console.log(a);
console.log(b);
console.log(c);
8.隐式转化 I
console.log(Boolean("false")); // true
console.log(Boolean(false)); // false
console.log("3" + 1); // "31" 字符串类型,打印出来还是数字
console.log("3" - 1); // 2
console.log("3" - " 02 "); // 1
console.log("3" * " 02 "); // 6
console.log(Number("1")); // 1
console.log(Number("number")); //NaN
console.log(Number(null)); // 0
console.log(Number(false)); // 0
js 实现
2.带占位符的柯里化
curriedJoin(_, 2)(1, 3); // '1_2_3'
function curry(func) {
return function curried(...args) {
const complete =
args.length >= func.length &&
!args.slice(0, func.length).includes(curry.placeholder);
if (complete) return func.call(this, ...args);
return function (...newArgs) {
// replace placeholders in args with values from newArgs
// _,_,_ + _,a -> _,_,_ + a -> a,_,_
const res = args.map((arg) =>
arg === curry.placeholder && newArgs.length ? newArgs.shift() : arg
);
return curried(...res, ...newArgs);
};
};
}
curry.placeholder = Symbol();
补充
3.实现箭头函数
babel 就是这样转译的
es6->es5
// ES6
const obj = {
getArrow() {
return () => {
console.log(this === obj);
};
},
};
// ES5,由 Babel 转译
var obj = {
getArrow: function getArrow() {
// 继承上一层作用域的this
// 如果!getArrow在使用时,作用域被推到window上,因此上一层作用域是window
var _this = this;
return function () {
console.log(_this === obj);
};
},
};
注意是
() => {
console.log(this === obj);
};
----
var _this = this;
function () {
console.log(_this === obj);
};
4.实现私有属性
class E {
constructor() {
this.map = new Map();
this.map.set(this, { a: 1 });
}
getA() {
return this.map.get(this)["a"];
}
}
let e = new E();
console.log(e.getA());
var Person = (function () {
var privateData = new WeakMap();
function Person(name) {
privateData.set(this, { name: name });
}
Person.prototype.getName = function () {
return privateData.get(this).name;
};
return Person;
})();
用 this 做 map 上的 Key
这样在外界无法直接找到 map 上存有数据的那个键
利用 map 的键可以是对象
5.洋葱模型中间件+compose
// function middleWare({ getState, dispatch }) {
// return next => action => {
// ...
// next(action);
// }
// }
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args),
};
// 向chain中每个高阶函数注入API
const chain = middlewares.map((middleware) => middleware(middlewareAPI));
// 接下来执行的是compose聚合函数,把chain中的中间件聚合成一个新的dispatch函数:
//第一个中间件是store.dispatch
const dispatch = compose(...chain)(store.dispatch);
return { ...store, dispatch };
function compose(...functions) {
if (functions.length === 0) {
return (...args) => args;
} else if (functions.length === 1) {
return functions[0];
} else {
return functions.reduce(
(pre, current) =>
(...args) =>
pre(current(args))
);
// [a,b,c]
// (...args)=>a(b(args))
// (...args)=>a(b(c))
}
}
6.实现 new
!!!prototype 不允许直接修改 不可直接被外界访问和修改
const _new = function (constructor, ...args) {
// new关键字做了4件事
// 1. 创建一个新对象
const obj = {};
// 2. 为新对象添加属性__proto__,将 该属性链接至构造函数的原型对象
obj.__proto__ = Object.create(constructor.prototype);
// 3. 执行构造函数,this被绑定在新对象上
const res = constructor.apply(obj, args);
// 4. 确保返回一个对象
return res instanceof Object ? res : obj;
};
7.Function.prototype.call/apply
Function.prototype._call = function (target = window) {
// fn可以随便起个名字,只要最后删除就行
// 注意 this 是 fn
target["fn"] = this;
//参数中去掉target
let arg = [...arguments].shift();
const result = target["fn"](...arg);
delete target["fn"];
return result;
};
Function.prototype._apply = function (target = window) {
target["fn"] = this;
let arg = [...arguments].shift();
// 其实和call是一样的,因为都先转为数组再展开
const result = target["fn"](...arg);
delete target["fn"];
return result;
};
手写类型判断
function getType(value) {
// 判断数据是 null 的情况
if (value === null) {
return value + "";
}
// 判断数据是引用类型的情况
if (typeof value === "object") {
let valueClass = Object.prototype.toString.call(value),
type = valueClass.split(" ")[1].split("");
type.pop();
return type.join("").toLowerCase();
} else {
// 判断数据是基本数据类型的情况和函数的情况
return typeof value;
}
}
promise 实现每隔 1 秒输出 1,2,3
运用 reduce 的吃豆人思想!
let arr = [1, 2, 3];
// 利用循环.then
arr.reduce((p, x) => {
return p.then(() => {
return new Promise((resolve) => {
setTimeout(() => resolve(console.log(x)), 1000);
});
});
//* 初始值是Promise.resolve()
}, Promise.resolve());
_map
// this是数组实例
Array.prototype._map = function (fn,outerThis=undefined){
// 'function'是字符串
if(typeof fn!=='function') return
let newArr = []
for(let i = 0;i<this.length;i++){
// map(array[index],index,array)
// 绑定外层传进来的this
newArr[i] = fn.call(outerThis,this[i],i,this)
}
return newArr
}
filter
Array.prototype._filter = function (fn) {
if (typeof fn !== "function") return this;
let res = [];
this.forEach((item) => {
if (fn(item)) {
res.push(item);
}
});
return res;
};
浅拷贝
const _shallowClone = (target) => {
// 补全代码
// 基本数据类型
if (typeof target !== "object" || target === null) return target;
let ret = Array.isArray(target) ? [] : {};
// Object.assign(ret, target);
let str = Object.prototype.toString.call(target);
// 函数,日期正则,直接返回
if (
str == "[object Date]" ||
str == "[object RegExp]" ||
str == "[object Function]"
)
return target;
Object.keys(target).forEach((key) => {
ret[key] = target[key];
});
return ret;
};
插入排序
function Insertion(arr) {
let len = arr.length;
let preIndex, current;
for (let i = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
// [2,3,5,7] 4, i=4,preIndex=3,current=4,
while (preIndex >= 0 && current < arr[preIndex]) {
// 7往右移,第二次5往右移
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
proxy 实现响应式
// 创建响应式
function reactive(target = {}) {
if (typeof target !== "object" || target == null) {
// 不是对象或数组,则返回
return target;
}
// proxy的代理配置,单独拿出来写
const proxyConf = {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target);
if (ownKeys.includes(key)) {
console.log("get", key); // 监听
}
const result = Reflect.get(target, key, receiver);
// 深度监听,因为是触发了get,才会进行递归处理,所以性能会更好些
return reactive(result);
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true;
}
const ownKeys = Reflect.ownKeys(target);
if (ownKeys.includes(key)) {
console.log("已有的 key", key);
} else {
console.log("新增的 key", key);
}
const result = Reflect.set(target, key, val, receiver);
console.log("set", key, val);
return result; // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
console.log("delete property", key);
return result; // 是否删除成功
},
};
// 生成代理对象
const observed = new Proxy(target, proxyConf);
return observed;
}
// 测试数据
const data = {
name: "xiaoming",
age: {
young: 18,
old: 26,
},
};
const proxyData = reactive(data);
打印质数
for (let i = 2; i <= num; i++) {
for (let j = 1; j * j < i; j++) {
if (i % j === 0 && j !== 1) {
res.push(i);
break;
}
}
}
Object.defineProperty
const obj = { name: "xxx" };
// 添加具有数据描述符的属性,相当于 obj.age = 18
Object.defineProperty(obj, "age", {
configurable: true, // 可删除
enumerable: true, // 可枚举
value: 18,
writable: true, // 可重写
});
// 添加具有存取描述符的属性
let age = 18;
Object.defineProperty(obj, "age", {
configurable: true,
enumerable: true,
get() {
return age;
},
set(newVal) {
age = newVal;
},
});
封装带有超时(重试)机制的异步请求工具函数
const defaultOptions = {
timeout: 5000,
retry: 3,
};
const timeout = (timeout = defaultOptions.timeout) =>
new Promise((_, reject) =>
// timeout的时间
setTimeout(() => reject(new Error("timeout")), timeout)
);
const request = async (url, fetchOption, options = defaultOptions) => {
let times = 0;
// 新promise,race
const _request = () =>
Promise.race([fetch(url, fetchOption), timeout(options.timeout)]);
while (times < (options.retry || defaultOptions.retry)) {
try {
return await _request();
} catch (err) {
++times;
continue;
}
}
// 这里抛出错误了,所以可以被catch到
throw new Error("fetch failed");
};
react
8.useDebounce()
function App() {
const [value, setValue] = useState(...)
// this value changes frequently,
const debouncedValue = useDebounce(value, 1000)
// now it is debounced
}
import { useState, useEffect } from "react";
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
9.useEffectOnce()
import { useRef, useEffect, EffectCallback } from "react";
// 因为依赖数组必须useEffect第一个参数里的包括所有状态,否则会报错,所以用useRef可以解决这个
export function useEffectOnce(effect: EffectCallback) {
const ref = useRef(effect);
useEffect(() => ref.current(), []);
}
15.useClickOutside()
使用的时候给固定的标签绑定导出的 ref
function Component() {
// 定义回调函数
const ref = useClickOutside(() => {
alert("clicked outside");
});
// 把ref和标签绑定
return <div ref={ref}>..</div>;
}
import React, { useRef, useEffect } from 'react'
export function useClickOutside<T extends HTMLElement>(callback: () => void): React.RefObject<T> {
const ref = useRef<T>(null)
useEffect(() => {
const click = ({ target }: Event): void => {
// !ref.current.contains(target),说明点击了外部组件
if (target && ref.current && !ref.current.contains(target as Node)) {
callback()
}
}
// 在整个document上绑定
document.addEventListener('mousedown', click)
// 记得清除
return () => {
document.removeEventListener('mousedown', click)
}
}, [])
return ref
}
16.useUpdateEffect()
useRef + useEffect
第一次渲染时不执行副作用 Implement useUpdateEffect() that it works the same as useEffect() except that it skips running the callback on first render.
import { EffectCallback, DependencyList } from "react";
// 来自react的类型
export function useUpdateEffect(effect: EffectCallback, deps?: DependencyList) {
// your code here
const ref = useRef(false);
useEffect(() => {
if (!ref.current) {
ref.current = true;
return;
}
// effect参数的返回结果是清理函数,拿到清理函数
// 手动调用
const cleanup = effect();
return () => {
cleanup && cleanup();
};
}, deps);
}
17.useHover
function App() {
const [ref, isHovered] = useHover();
return <div ref={ref}>{isHovered ? "hovered" : "not hovered"}</div>;
}
import { Ref, useRef, useState, useEffect } from "react";
// 第一个参数如果有一定要是dom
export function useHover<T extends HTMLElement>(): [
Ref<T | undefined>,
boolean
] {
const ref = useRef<T>();
const [isHovering, setHovering] = useState(false);
useEffect(() => {
// false by default if ref.current changes
setHovering(false);
const element = ref.current;
if (!element) return;
const setYes = () => setHovering(true);
const setNo = () => setHovering(false);
element.addEventListener("mouseenter", setYes);
element.addEventListener("mouseleave", setNo);
return () => {
element.removeEventListener("mouseenter", setYes);
element.removeEventListener("mouseleave", setNo);
};
}, [ref.current]); // 如果ref.current变了,也要重新执行
return [ref, isHovering];
}
useParams
利用 window.location.href
function parseParam(url) {
const [href, params] = url.split("?");
const result = {};
params &&
params.split("&").map((item) => {
let [key, value = true] = item.split("=");
value = value === true ? value : decodeURIComponent(value); // 转译中文
if (!result[key]) {
result[key] = value;
} else {
// 如果不是数组,则只有一个数,要加上value的话则concat生成个数组
result[key] =
result[key] instanceof Array
? [].concat(...result[key], value)
: [].concat(result[key], value);
}
});
return result;
}
// 执行
parseParam(
"http://www.getui.com?user=superman&id=345&id=678&city=%E6%9D%AD%E5%B7%9E&enabled"
);
// {user: "superman", id:["345", "678"], city: "杭州", enabled: true}