css画扇形
/* 画一个圆 */
.circle {
width: 100px;
height: 100px;
border-radius: 100px;
background-color: red;
}
/* 用两个透明的块来遮挡,两个快都是原始块的一半 */
.sector1 {
/* 改变旋转中心 */
transform-origin: 50px 50px;
transform: rotate(90deg);
position: absolute;
top: 0px;
left: 0px;
width: 100px;
height: 50px;
background-color: white;
}
.sector2 {
transform-origin: 50px 50px;
transform: rotate(120deg);
position: absolute;
top: 0px;
left: 0px;
width: 100px;
height: 50px;
background-color: white;
}
图片懒加载
//DOMContentLoaded比load快,它仅仅是把dom树加载好了
document.addEventListener("DOMContentLoaded", function() {
let lazyImages = [...document.querySelectorAll('img')];
//Intersection Observer API提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。
if ("IntersectionObserver" in window) {
// 创建一个观察函数,以便待会调用
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src; // 替换 src URL
lazyImageObserver.unobserve(lazyImage); // 解除观察
}
});
});
// 对所有需要懒加载的图片进行 “暗中观察”
lazyImageObserver.observe(lazyImages);
}else{
alert('您的浏览器不支持 IntersectionObserver');
}
});
手写鼠标拖拽事件
let img = document.querySelector('.img');
img.addEventListener('mousedown', function(event) {
let subWidth = event.clientX - img.offsetLeft;
let subHeight = event.clientY - img.offsetTop;
console.log(subWidth, subHeight);
let update = function(event) {
console.log(event);
let moveLeft = event.clientX - subWidth;
let moveTop = event.clientY - subHeight;
console.log();
if (moveLeft < 0) moveLeft = 0;
else if (moveLeft > window.innerWidth - img.offsetWidth) moveLeft = window.innerWidth - img.offsetWidth;
if (moveTop < 0) {
moveTop = 0;
} else if (moveTop > window.innerHeight - img.offsetHeight) {
moveTop = window.innerHeight - img.offsetHeight;
}
img.style.left = moveLeft + 'px';
img.style.top = moveTop + 'px'
}
let moveMethod = document.addEventListener('mousemove', update)
let upMethod = document.addEventListener('mouseup', function(event) {
document.removeEventListener(moveMethod, update);
})
})
浮点数加法
function accMul(arg1, arg2) {
var m = 0,
s1 = arg1.toString(),
s2 = arg2.toString();
try {
//这个length是为了知道把这个小数放大了多少倍
m += s1.split(".")[1].length;
console.log(m);
} catch (e) {}
try {
m += s2.split(".")[1].length;
console.log(m);
} catch (e) {}
//直接从String类型化为Number
return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
}
// 给Number类型增加一个mul方法,调用起来更加方便。
Number.prototype.mul = function(arg) {
return accMul(arg, this);
};
console.log(accMul(0.1, 0.3));
手写高并发
const arr = [];
for (let i = 0; i < 100; i++) {
arr.push(`这是第${i+1}条数据`)
}
const axiosGet = function(index) {
return new Promise((resolve, reject) => {
setTimeout(resolve(arr[index]), 10000 * Math.random())
})
}
async function asyncProcess(max = 10) {
//保存并发的数组
const task = [];
//保存所有完成了的任务
const ans = [];
for (let i = 0; i < arr.length; i++) {
//axiosGet直接执行,p有值
const p = axiosGet(i).then(value => {
console.log(value, task.length);
ans.push(value);
task.splice(task.indexOf(p), 1);
})
//将p加入队列
task.push(p);
//任务量达到最大的时候
if (task.length === max) {
await Promise.race(task);
}
}
await Promise.allSettled(task);
return ans
}
asyncProcess().then(res => {
console.log(res);
})
设计模式
适配器模式
适配器模式的作用是将两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。适配器模式主要是用来解决已有的对象接口之间的不匹配问题,它不需要改变现有的接口,就能使它们协同工作。
const baiduMap = {
display: function () {
console.log('开始渲染百度地图')
}
}
const baiduAdapter = {
show: function () {
return baiduMap.display()
}
}
renderMap(baiduAdapter)
观察者模式
发布-订阅模式:又称为观察者模式,它定义对象间的依赖关系,当对象的状态改变时,所有依赖于它的对象都将得到通知。发布订阅模式主要的好处就是对象之间的解耦,帮助我们写出更松耦合的代码。缺点是订阅者的创建是消耗一定的时间和内存的。使用过多也是难以维护的。
发布订阅模式实现的是一种多对多的关系,在发布者与订阅者之间需要有一个中介者,发布者发布事件名和参数到中间者,中间者向事件集中的订阅者发送参数。 而观察者是一种一对多的关系,所有的在同一被观察者身上绑定的观察者只能接受同一个被观察者的消息。
单例模式
构造函数创建出来的实例对象都相同。
- 需要使用return。使用new的时候如果没有手动设置return,那么会默认返回this。但是,我们这里要使得每次返回的实例相同,也就是需要手动控制创建的对象,因此这里需要使用return。
- 我们需要每次return的是同一个对象。也就是说实际上在第一次实例的时候,需要把这个实例保存起来。再下一个实例的时候,直接return这个保存的实例。因此,这里需要用到闭包了。
const Person = (function(){
let instance = null;
return class{
constructor(){
if(!instance){
//第一次创建实例,那么需要把实例保存
instance = this;
}else{
return instance;
}
}
}
})()
装饰器模式
装饰器模式:为对象添加新功能,不改变其原有的结构和功能。装饰器模式是原有的还能用,但是需要新增一些东西来完善这个功能。
代理模式
代理模式:使用者无权访问目标对象,中间加代理,通过代理做授权和控制。
ES6中的Proxy
发红包
function redPocket(totalSum, totalCount) {
var count = 0;
var res = [];
while (count !== totalCount) {
count++;
var num = totalSum + 1;
if (count === totalCount)
num = Number(totalSum).toFixed(1);
while (num > totalSum) {
num = Number(Math.random() * totalSum).toFixed(1);
}
totalSum -= num;
res.push(num);
}
return res;
}
归并排序
var sortArray = function(nums) {
//因为后面是索引赋值所以需要设初值,为什么一定要另外搞一个
//因为数组插入值比较麻烦,所以用新数组赋值更方便。
let result = new Array(nums.length).fill(0);
mergeSort(nums, 0, nums.length - 1, result);
return result;
};
var mergeSort = function(nums, left, right, temp) {
//相当于只有一个元素或者说两个元素的时候就可以merge了
if (left === right) return;
let middle = left + Math.floor((right - left) / 2);
mergeSort(nums, left, middle, temp);
mergeSort(nums, middle + 1, right, temp);
merge(nums, left, middle, right, temp);
}
var merge = function(nums, left, middle, right, temp) {
let i = left,
j = middle + 1;
let k = left;
while (i < middle + 1 || j < right + 1) {
if (i < middle + 1) temp[k++] = j < (right + 1) && nums[j] < nums[i] ? nums[j++] : nums[i++];
if (j < right + 1) temp[k++] = i < (middle + 1) && nums[i] <= nums[j] ? nums[i++] : nums[j++];
}
//这一步一定要做,因为一直在用nums,但是保存改变后的数据的是temp;
//不然后面合并时会发现还是原来的nums
for (let i = left; i <= right; i++) {
nums[i] = temp[i];
}
}
快排
var quickSort = function(arr) {
ranSort(arr, 0, arr.length - 1);
return arr;
}
var ranSort = function(arr, i, j) {
if (i >= j) return;
var ran = Math.floor(Math.random() * (j - i) + i);
var temp = arr[ran];
arr[ran] = arr[i];
arr[i] = temp;
var partitionNum = partition(arr, i, j);
ranSort(arr, i, partitionNum - 1);
ranSort(arr, partitionNum + 1, j);
}
var partition = function(arr, i, j) {
var pivot = arr[i];
while (i < j) {
while (i < j && arr[j] > pivot) j--;
if (i < j) arr[i++] = arr[j];
while (i < j && arr[i] <= pivot) i++;
if (i < j) arr[j--] = arr[i];
}
arr[i] = pivot;
return i;
}
堆排序
// 堆排序
function buildHeap(heap) {
for (let i = Math.floor(heap.length / 2 - 1); i >= 0; i--) {
heapSort(heap,i, heap.length);
}
return heap
}
//注意这里的length
function heapSort(heap, length) {
let j=2*i+1;
while (j < length) {
if (j + 1 < length) {
j += heap[j] > heap[j + 1];
}
if (heap[j] < heap[i]){
swap(heap, i, j);
i=j;
j=2*i+1
}else break;
}
}
function swap(heap, i, j) {
if (i == j)
return;
[heap[i],heap[j]]= [heap[j],heap[i]]
}
function main(heap) {
heap = buildHeap(heap);
for (var i = heap.length - 1; i >= 1; i--) {
swap(heap, 0, i);
heapSort(heap, 0,i)
}
return heap;
}
寻找第k大
var quickSortK = function(arr, k) {
if (arr.length < k) return -1;
randomPartition(arr, 0, arr.length - 1, k)
return arr[arr.length - k]
}
var randomPartition = function(arr, left, right, k) {
if (left < right) {
let ranNum = Math.floor(left + Math.random() * (right - left));
[arr[left], arr[ranNum]] = [arr[ranNum], arr[left]];
let partition = getPartition(arr, left, right);
if (partition === arr.length - k) return;
else if (partition < arr.length - k) randomPartition(arr, partition + 1, right, k);
else randomPartition(arr, left, partition - 1, k);
}
return;
}
var getPartition = function(arr, left, right) {
let pivot = arr[left];
while (left < right) {
while (left < right && arr[right] > pivot) right--;
if (left < right) arr[left++] = arr[right]
while (left < right && arr[left] <= pivot) left++;
if (left < right) arr[right--] = arr[left]
}
arr[left] = pivot
return left;
}
实现Set
function mySet() {
var items = {};
mySet.prototype.has = function(value) {
return items.hasOwnProperty(value);
}
mySet.prototype.add = function(value) {
if (!this.has(value)) {
items[value] = value;
}
return this;
}
mySet.prototype.remove = function(remove) {
if (this.has(value)) {
delete items[value];
}
}
mySet.prototype.size = function() {
return Object.keys(items).length
}
}
let set = new mySet();
set.add(1).add(2);
console.log(set.size());
实现异步Queue
function Queue() {}
//要用构造函数访问只能这样写
Queue.data = []
Queue.push = function(fn) {
[].push.call(this.data, fn);
return this
}
Queue.run = async function() {
for (let i = 0; i < this.data.length; i++) {
await this.data[i]()
}
}
Queue.push(function() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('1')
resolve()
},
1000)
})
}).push(function() {
console.log('2');
}).push(function() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('3')
resolve()
}, 500)
})
}).run()
数组转成树结构
const data = [
{ id: 0, name: "name0", pid: -1 },
{ id: 1, name: "name1", pid: 0 },
{ id: 2, name: "name2", pid: 0 },
{ id: 3, name: "name3", pid: 0 },
{ id: 4, name: "name4", pid: 1 },
{ id: 5, name: "name5", pid: 2 },
{ id: 6, name: "name6", pid: 3 },
{ id: 7, name: "name7", pid: 4 },
{ id: 8, name: "name8", pid: 5 },
{ id: 9, name: "name9", pid: 6 },
{ id: 10, name: "name10", pid: 7 },
];
function TreeNode(id, name, pid, children) {
this.id = id;
this.name = name;
this.pid = pid;
this.children = children || [];
}
var convert = function(arr) {
if (!arr) return;
//先把所有结点都化为树节点
let treeNodes = [];
arr.forEach(element => {
let { id, name, pid, children } = element;
let node = new TreeNode(id, name, pid, children);
treeNodes.push(node);
});
//找到根结点
let root = treeNodes.find(element => element.pid === -1);
let que = [root];
while (que.length !== 0) {
//最开始队列中只有一个元素,一定是root所以继续往后
let node = que.shift();
let chidrenNodes = treeNodes.filter(element => element.pid === node.id);
for (let i = 0; i < chidrenNodes.length; i++) {
node.children.push(chidrenNodes[i]);
que.push(chidrenNodes[i]);
}
}
return root;
}
手写遍历DOM树的tagName
function consoAllTagName(root) {
if (!root) return;
const queue = [root];
while (queue.length) {
const currentNode = queue.shift();
//解构得到元素和标签名
const {
childNodes,
tagName
} = currentNode;
//root有标签名输出
if (tagName) {
console.log(currentNode.tagName);
}
//childNodes没有filter,需要使用数组的方法
Array.prototype.filter.call(childNodes, item => item.tagName).forEach(itemNode => {
queue.push(itemNode)
})
}
}
深度遍历dom结点
function deepOrder(root) {
let res = []
dfs(res, root, []);
console.log(res);
}
function dfs(res, root, path) {
if (!root) return;
path.push(root.tagName)
console.log(root);
console.log(root.children.length);
if (root.children.length === 0) {
res.push(path.slice());
return
}
let arr = Array.prototype.forEach.call(root.children, element => {
dfs(res, element, path);
path.pop()
})
}
解析innerText
var solution = function(str) {
let i = 0;
let result = [];
let stack = []
while (i < str.length) {
if (str[i] === '<')
stack.push(stack[i]);
else if (str[i] === '>')
stack.pop();
else {
if (stack.length === 0) {
let arr = [str[i++]];
while (str[i] !== '<' && i < str.length) {
arr.push(str[i++])
}
if (str[i] === '<') stack.push(str[i]);
result.push(arr.join(''))
}
}
i++;
}
return result
}
判断两个对象
function diff(obj1, obj2) {
var o1 = obj1 instanceof Object;
var o2 = obj2 instanceof Object;
// 判断是不是对象
if (!o1 || !o2) {
return obj1 === obj2;
}
//Object.keys() 返回一个由对象的自身可枚举属性(key值)组成的数组,
//例如:数组返回下表:let arr = ["a", "b", "c"];console.log(Object.keys(arr))->0,1,2;
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return false;
}
for (var o in obj1) { // 遍历对象 fon in 循环 o 为 对象的属性名
var t1 = obj1[o] instanceof Object;
var t2 = obj2[o] instanceof Object;
if (t1 && t2) {
return diff(obj1[o], obj2[o]);
} else if (obj1[o] !== obj2[o]) {
console.log('false')
return false;
}
}
return true;
}
比较数组
//在原型上写
Array.prototype.equals = function (array) {
// if the other array is a falsy value, return
if (!array)
return false;
// 比较长度
if (this.length != array.length)
return false;
for (var i = 0, l = this.length; i < l; i++) {
// 检查是否有数组的嵌套
if (this[i] instanceof Array && array[i] instanceof Array) {
//递归
if (!this[i].equals(array[i]))
return false;
}
else if (this[i] != array[i]) {
// 如果不能返回false,等的话继续比较
return false;
}
}
return true;
}
Object.defineProperty(Array.prototype, "equals", {enumerable: false});
#### 继承
##### 原型链继承
```js
function Box(name){
this.name = name
}
Box.prototype.run = function (){
console.log(this.name + '正在运行...')
}
function Desk(){
this.name='ker'
}
Desk.prototype = new Box() // 原型链
重点:让新实例的原型等于父类的实例。
特点:1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
缺点:1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
构造函数继承
function Box(name){
this.name = name
}
Box.prototype.age = 18
function Desk(name){
Box.call(this, name) // 对象冒充,对象冒充只能继承构造里的信息
this.age=age;
}
var desk = new Desk('ccc')
console.log(desk.name) // --> ccc
console.log(desk.age) // --> undefined
重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点1、2、3。
3、可以继承多个构造函数属性(call多个)。
4、在子实例中可向父实例传参。
缺点:1、只能继承父类构造函数的属性。
2、无法实现构造函数的复用。(每次用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。
组合继承
使用原型链的方式来实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。组合继承最大的问题就是无论什么情况下,都会调用过两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。(用寄生组合式继承来解决)
function Box(name){
this.name = name
}
Box.prototype.run = function (){
console.log(this.name + '正在运行...')
}
function Desk(name){
Box.call(this, name) // 对象冒充
}
Desk.prototype = new Box() // 原型链
var desk = new Desk('ccc')
console.log(desk.name) // --> ccc
desk.run() // --> ccc正在运行...
重点:结合了两种模式的优点,传参和复用
特点:1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。
缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
原型式继承
function object(o) { //传递一个字面量函数
function F(){} //创建一个构造函数
F.prototype = o; //把字面量函数赋值给构造函数的原型
return new F() //最终返回出实例化的构造函数
}
var box = {
name: 'ccc',
age: 18,
family: ['哥哥','姐姐']
}
var box1 = obj(box)
console.log(box1.name) // --> ccc
box1.family.push('妹妹')
console.log(box1.family) // --> ["哥哥", "姐姐", "妹妹"]
var box2 = obj(box)
console.log(box2.family) // --> ["哥哥", "姐姐", "妹妹"]
借助原型可以基于已有的对象创建新对象。box1和box2会共享box的内容
寄生式继承
function obj(o) {
function F (){}
F.prototype = o;
return new F()
}
//上面是原型式继承
function create(o){
var clone = obj(o) // 通过调用函数创建一个新对象
clone.sayName = function(){ // 以某种方式来增强这个对象
console.log('hi')
}
return clone // 返回这个对象
}
var person = {
name: 'ccc',
friends: ['aa','bb']
}
var anotherPerson = create(person)
anotherPerson.sayName() // --> hi
这个例子中的代码基于person返回一个新对象————anotherPerson。新对象不仅具有person的所有属性和方法,而且还有自己的sayHi()方法。在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。
寄生组合式继承
可以看到很好的继承了父类的方法和属性以及自己添加属性和方法,并且只调用了1次父类构造函数,同时保证了原型链的完整,是一种理想的继承方法。
// 设置父类
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
SuperType.prototype.sayName = function () {
console.log(this.name)
}
}
// 设置子类
function SubType(name, age) {
//构造函数式继承--子类构造函数中执行父类构造函数
SuperType.call(this, name);
this.age = age;
}
// 实现继承的核心函数
function inheritPrototype(subType,superType) {
function F() {};
//F()的原型指向的是superType
F.prototype = superType.prototype;
//subType的原型指向的是F()
subType.prototype = new F();
// 重新将构造函数指向自己,修正构造函数
subType.prototype.constructor = subType;
}
// 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
inheritPrototype(SubType, SuperType)
// 添加子类私有方法
SubType.prototype.sayAge = function () {
console.log(this.age);
}
var instance = new SubType("Taec",18)
console.dir(instance)
extends
function _extends(A,B){
A.prototype.__proto__= B.prototype;
A.prototype.constructor = A;
Object.setPrototypeOf(A,B);
};
手写typeof
function typeOf(obj) {
return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
}
变种版跳台阶
var getCount = function(n) {
if (n === 0 || n === 1) return 0;
if (n === 2 || n === 3) return 1;
let dp = [];
//[0,0]代表无论用2还是3都是0
dp.push([0, 0]);
dp.push([0, 0]);
dp.push([1, 0]);
dp.push([0, 1]);
for (let i = 4; i <= n; i++) {
//dp[i][0]表示我是由2走过来的,dp[i][1]表示我是由3走过来的
//这是js,不写这一句他是不知道dp[i]的状况的
dp[i] = new Array(2);
dp[i][0] = dp[i - 2][0] + dp[i - 2][1];
dp[i][1] = dp[i - 3][0] + dp[i - 3][1];
}
return dp[n][0] + dp[n][1];
}
console.log(getCount(5));
手写instanceof
var myInstance = function(obj, objPro) {
var item = obj.__proto__;
while (true) {
if (item === null) return false;
if (item === objPro.prototype) return true;
item = item.__proto__
}
}
Array.isArray
Object.prototype.toString.call(array) === '[object Array]'
手写防抖
function debounce(fn,wait) {
var timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
if(timeout) clearTimeout(timeout); //清除定时器
//创建新的 setTimeout
timeout = setTimeout(fn(), wait);
};
}
// 处理函数
function handle() {
console.log(document.getElementById("kw").value);
}
//输入框事件
document.getElementById("kw").addEventListener('input', debounce(handle,5000));
手写节流
//fn是需要被节流的函数 delay是时间
function throttle(fn, delay) {
let flag= true
return function () {
let args=arguments;
if (!flag) {
return // 如果开关关闭了,那就直接不执行下边的代码
}
flag= false // 持续触发的话,run一直是false,就会停在上边的判断那里
setTimeout(() => {
fn.apply(this, args)
flag= true // 定时器到时间之后,会把开关打开,我们的函数就会被执行
}, delay)
}
}
手写浅拷贝
function shallowCopy(obj){
var data = {};
for (var key in obj){
if(obj.hasOwnProperty(key)){ // for in 循环,也会循环原型链上的属性,所以这里需要判断一下
//hasOwnProperty的相关知识点,查看下面的:相关知识点补充
data[key] = obj[key]
}
}
return data
}
手写深拷贝
function deepClone(obj){
let data = Array.isArray(obj)?[]:{};
if(obj && typeof obj==="object"){
for(key in obj){
if(obj.hasOwnProperty(key)){
//判断ojb子元素是否为对象,如果是,递归复制
if(obj[key]&&typeof obj[key] ==="object"){
data[key] = deepClone(obj[key]);
}else{
//如果不是,简单复制
data[key] = obj[key];
}
}
}
}
return data;
}
//针对循环引用问题的优化版
//使用Map函数
function deepCopy(obj, map = new Map()) {
if (!(obj instanceof Object)) return obj
const newObj = Array.isArray(obj) ? [] : {}
if (map.get(obj)) {
return map.get(obj);
}
map.set(obj, newObj);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] instanceof Object) {
newObj[key] = deepCopy(obj[key], map);
} else {
newObj[key] = obj[key];
}
}
}
return newObj;
}
new
function _new(fn,...args){ // ...args为ES6展开符,也可以使用arguments
//先用Object创建一个空的对象,
const obj = Object.create(fn.prototype) //fn.prototype代表 用当前对象的原型去创建
//现在obj就代表Dog了,但是参数和this指向没有修改
const rel = fn.apply(obj,args)
//正常规定,如何fn返回的是null或undefined(也就是不返回内容),我们返回的是obj,否则返回rel
//也是这里为啥不用typeof的原因,因为null返回object
return rel instanceof Object ? rel : obj
}
数组去重
let arr = [12,43,23,43,68,12];
//利用...把可以iterable的变为数组
let item = [...new Set(arr)];
console.log(item);//[12, 43, 23, 68]
//利用filter和indexOf找到该元素第一次出现的位置如果是当前位置返回
//如果和当前位置索引相同说明当前元素是第一次出现
array.filter((value, index, arr) => arr.indexOf(value) === index)
//只用indexOf而不用filter,用splice删除重复的元素
setTimeout实现setInterval
function mySetInterval(fn, delay, times) {
let timer = setTimeout(function a() {
fn()
times--
timer = setTimeout(a, delay)
if (times <= 0) {
clearTimeout(timer)
}
}, delay)
}
apply
Function.prototype.apply = function(obj, arr) {
//将方法挂载到传入的上下文对象上
obj = obj || window;
obj.fn = this;
//调用函数
if (!arr) {
obj.fn();
} else {
if (!Array.isArray(arr)) {
throw new Error('必须传入数组!');
}
var result = eval('obj.fn(' + arr + ')');
}
delete obj.fn;
return result;
}
call
Function.prototype.myCall = function(obj, ...args) {
obj = obj || window;
obj.fn = this;
let result = obj.fn(args)
delete obj.fn;
return result;
}
bind
Function.prototype.myBind = function(obj, ...args) {
obj = obj || window;
obj.fn = this;
console.log(args);
return function() {
args.concat(Array.prototype.slice.call(arguments));
let result = obj.fn.apply(obj, args);
delete obj.fn;
return result;
}
}
jsonp
function jsonp(url, data, callback) {
var ch = url.indexOf("?") == -1 ? "?" : "&";
var funcName = 'jsonp_' + Date.now() + Math.random().toString().substring(2)
//如果存在其他传入参数,需要进行拼接
if (typeof data === 'object') {
var tempArr = []
for (var key in data) {
var value = data[key]
tempArr.push(key + '=' + value)
}
data = tempArr.join('&')
}
var script = document.createElement('script')
script.src = url + ch + data + '&callback= ' + funcName
document.body.appendChild(script)
window[funcName] = function(data) {
callback(data)
//清除全局函数和script标签
delete window[funcName]
document.body.removeChild(script)
}
}
jsonp('https://www.baidu.com', {}, function(res) {
console.log(res)
})
手写reduce
Array.prototype.myReduce = function(fn, origin) {
let prev = undefined;
let arr = this,
i = 0;
if (origin) {
prev = origin;
} else {
prev = arr[0];
i++;
}
for (; i < arr.length; i++) {
prev = fn(prev, arr[i], i, arr);
}
return prev;
}
解析url
var getObj = function(string) {
if (string.indexOf('?') === -1)
return {};
var obj = {}
var str = string.substr(string.indexOf('?') + 1);
var array = str.split('&');
for (const item of array) {
var couple = item.split('=');
obj[couple[0]] = couple[1];
}
return obj;
}
var input = "http://www.baidu.com?age=2&name=3"
console.log(getObj(input));
promise的几个
promise
const PENDING = 'pending'
const RESOLVED = 'fulfilled'
const REJECTED = 'reject'
class tPromise {
constructor(executor) {
this.state = PENDING
this.data = undefined
//存储then的函数参数,现有resolv执行,后有then
this.callbacks = []
//在new实例对象时,这个this就变成实例,而resolve方法在类里面
try {
executor(this.resolve.bind(this), this.reject.bind(this))
//这一步是因为promise执行失败会提示错误而不会执行弹出错误
} catch (error) {
this.reject(error);
}
}
resolve = value => {
setTimeout(() => {
if (this.state === PENDING) {
this.state = RESOLVED
this.data = value
if (this.callbacks.length > 0) {
this.callbacks.forEach(callbacksObj => {
callbacksObj(value)
})
}
}
})
}
reject = err => {
if (this.state === PENDING) {
this.state = REJECTED
this.data = err
if (this.callbacks.length > 0) {
this.callbacks.forEach(callbacksObj => {
callbacksObj(err.message)
})
}
}
}
then = (onResolved, onRejected) => {
return new Promise((resolve, reject) => {
//then后面的两个参数必须是函数
onResolved = typeof onResolved === 'function' ? onResolved : () => {};
onRejected = typeof onRejected === 'function' ? onRejected : () => {};
//如果值判断是成功还是失败,两者都不是就要等执行后菜执行
if (this.state === PENDING) {
this.callbacks.push(onResolved)
this.callbacks.push(onRejected)
}
if (this.state === RESOLVED) {
onResolved(this.data)
}
if (this.state === REJECTED) {
onRejected(this.data)
}
})
}
}
promise.all
从第一个执行到最后一个,失败直接返回,否则执行完输出数组
Promise.prototype.all = (iterator) => {
let count = 0
let len = iterator.length
//输出的内容是数组,利用数组存放结果
let res = []
return new Promise((resolve, reject) => {
//这里用in而不用of是因为then是异步的,不一定按顺序,
//那么在加入数组中的时候顺序可能不一致
for (const item in iterator) {
//先转化为promise对象
Promise.resolve(iterator[item])
.then(value => {
res[item] = value;
if (++count == len)
resolve(res)
}, err => { reject(err) })
}
})
}
promise.any
从第一个开始执行直到碰到成功的直接返回,否则计数直到执行完
Promise.prototype.any = function(parr) {
let res = [],
count = 0,
len = parr.length;
return new Promise((resolve, reject) => {
for (let p in parr) {
Promise.resolve(p).then(value => {
resolve(res);//只要有一个成功,就走resolve
}, err => {
res[p] = err;//遇到错误先不管,继续检查
if (++count == len) {//直到遇到成功的或检查完
reject(res);
}
})
}
})
}
promise.race
返回第一个执行完的状态,无论是成功还是失败
Promise.prototype.race = (iterator) => {
return new Promise((resolve, reject) => {
for (const item of iterator) {
Promise.resolve(item)
.then(value => {
resolve(value)
}, err => { reject(err) })
}
})
}
promise.finally
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
promise.allSettled
Promise.myAllSettled = function(parr) {
let len = parr.length;
let result = new Array(len);
let count = 0;
return new Promise((resolve, reject) => {
for (const item in parr) {
Promise.resolve(item).then(res => { //成功:加入装状态列表
result[item] = { //记录当前promise信息
status: "fullfilled",
result: res
};
if (++count == len) { //遍历完,走resolve
resolve(result);
}
}, err => { //失败:加入状态列表
result[item] = { //记录当前promise状态信息
status: "rejected",
result: err
}
if (++count == len) { //遍历完依然走resolve
resolve(result);
}
})
}
})
}
拍扁数组
var array = [5, 4, [2, 1, [4, 6]]];
var flat= function(input) {
var heap = input;
var res = [];
//heap-步一步把heap结构出来
while (heap.length!== 0) {
//首先取出第一个元素,
var target = heap.shift();
//判断它是否为数组,如果是数组,解构,并把值传进去,等系又可以扫描到他
if(Array.isArray(target)) {
heap.unshift(...target);
}else{
//否则直接加入res
res.unshift(target);
}
}
return res;
}
console. log(flat(array));
var arr=[1,2,[3, 4, [5,6,[7]]]];
//方法二
function flatten(arr, deep){
return deep > 1 ? arr.reduce((prev, next) => prev.concat(
Array.isArray(next) ? flatten(next, deep - 1) : next), []
) : arr.slice()
}
flatten(arr, Infinity);
//方法三
var flat = function(arr) {
arr.forEach(element => {
arr = Array.isArray(element) ? [...arr] : arr
});
return arr;
}
手写lodash.get
function deepGet(object,path,defaultValue){
//是数组就直接搜,不是数组先转换成字符数组,给o初值是object对象,key就是path的第一个元素
//undefined或null和false做或操作,结果都是false
return (!Array.isArray(path)?path.replace(/\[/g,'.').replace(/\]/g,''):path).reduce((o,key)=>(o||{})[key],object)||defaultValue;
}
path解析obj对象
// 注意象棋棋盘是7 * 7,然后象棋每次跳日
// let count = 0
function dfs(x0, y0, xn, yn, n) {
// 递归出口
if (n == 0) {
if (x0 == xn && y0 == yn) {
return 1
}
return 0
}
//(x0,y0)结点能走的范围
let arr = getNextSteps(x0, y0)
let sum = 0
console.log(arr);
//一个for循环对从每个位置走的做处理
for (let i = 0; i < arr.length; i++) {
sum += dfs(arr[i][0], arr[i][1], xn, yn, n - 1)
}
return sum
}
let c = dfs(0, 0, 5, 6, 5)
// console.log(count); console.log(c);
// 用于判断它的下一步跳的情况,在最中间时一共有8种跳跃情况
function getNextSteps(x, y) {
let arr = []
// 往第一象限跳
if (x <= 4 && y <= 5) arr.push([x + 2, y + 1])
if (x <= 5 && y <= 4) arr.push([x + 1, y + 2])
// 往第二象限跳
if (x >= 2 && y <= 5) arr.push([x - 2, y + 1])
if (x >= 1 && y <= 4) arr.push([x - 1, y + 2])
// 往第三象限跳
if (x >= 2 && y >= 1) arr.push([x - 2, y - 1])
if (x >= 1 && y >= 2) arr.push([x - 1, y - 2])
// 往第四象限跳
if (x <= 4 && y >= 1) arr.push([x + 2, y - 1])
if (x <= 5 && y >= 2) arr.push([x + 1, y - 2])
return arr
}
diff算法
ajax
function Ajax(url){
return new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest();
xhr.open('GET', url, true)
xhr.onreadystatechange=()=>{
if (xhr.readyState == 4) {
// 200-300 之间表示请求成功,304资源未变,取缓存
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
resolve(xhr.responseText)
} else {
reject(new Error("error"))
}
}
}
xhr.send(null)
})
}
Ajax('./test.json').then(value=>{
console.log(value);
}).catch(err=>{
console.log(err);
})
手写axios
//创建Axios类
function Axios(config) {
this.defaults = config; //添加defaults默认属性
this.interceptors = { //添加拦截器属性
request:new InterceptorManager(), //添加管理存储拦截器中方法的实例
response:new InterceptorManager()
}
}
//拦截器管理类
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function (fullfilled, rejected) {
this.handlers.push({
fullfilled,
rejected
})
}
//添加实例方法
Axios.prototype.request = function (config) { //创建统一请求函数
//1、发送前合并defaults和传入的配置参数,此处省略
//使用promise来处理配置参数,封装为promise类型
let promise = Promise.resolve(config);
//设置.then的两个回调函数
let chains = [dispatchRequest, undefined]; //undefined占位,避免原型链调用出错
//处理拦截器,因为创建实例时已经将this指向了axios实例,所以能够获取到axios实例上存储的拦截器方法
//请求拦截器
this.interceptors.request.handlers.forEach((item) => {
chains.unshift(item.rejected); //注意压入顺序,先压入失败回调,再压入成功,使得shift()先取出成功回调
chains.unshift(item.fullfilled);
})
//响应拦截器
this.interceptors.response.handlers.forEach((item) => {
chains.push(item.fullfilled);
chains.push(item.rejected);
})
while (chains.length) { //遍历存储的方法,执行请求拦截器->发送请求方法dispatchRequest->响应拦截器
//请求拦截器中的方法会根据返回参数,使用promise成功回调,将每次修改后的配置传递到dispatchRequest中
//响应拦截器会根据dispatchRequest返回的请求数据,将数据传递给之后的响应拦截器中
//若中途产生了失败回调,会异常穿透到最后一个promise中
//最终返回带有结果的promise
promise = promise.then(chains.shift(), chains.shift()); //shift弹出数组第一个元素,并改变数组结构
}
//返回值对应实例返回的Promise对象即最终的数据结果
return promise;
}
//dispatchRequest函数搭建
function dispatchRequest(config) {
//调用适配器发送请求
return xhrAdaptor(config).then(res => { //根据适配器函数的Promise执行器结果,设置.then的Promise的返回值
return res;
}, err => {
throw err;
})
}
//adaptor适配器函数搭建,这里只实现xhr(前台)环境的情况
function xhrAdaptor(config) {
return new Promise((resolve,reject) => {
//进行xhr请求,将结果使用执行器执行,则dispatchRequest返回的Promise内容就是执行器执行的内容,request中收到的Promise内容被dispatchRequest返回的Promise内容决定
let xhr = new XMLHttpRequest();
xhr.open(config.method, config.url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200&&xhr.status<300) {
resolve({
config,
data: xhr.response,
header:xhr.getAllResponseHeaders(), //这里是字符串,axios内部对其进行了格式化变成了对象
status: xhr.status,
request:xhr, //xhr请求对象
statusText:xhr.statusText
})
} else {
reject('请求失败'+xhr.status)
}
}
//取消请求
if (config.cancelToken) { //如果配置了cancelToken属性
config.cancelToken.promise.then((cancelInfo) => { //当调用了cancelToken实例上的Promise的执行器时,才会执行.then的方法参数,在其中进行取消xhr请求
xhr.abort();
reject('请求取消');
})
}
}
})
}
//创建方法
Axios.prototype.get = function (config) {
return this.request({method:"GET"});
}
Axios.prototype.post = function (config) {
return this.request({method:"POST"});
}
const methodsArr = ['get','post','put','delete','head','options','patch','head'];
methodsArr.forEach(method => {
Axios.prototype[method] = function(){
return this.request({
method: method,
...arguments[0]
})
}
})
//创建取消axios请求类
Axios.prototype.cancelToken = function (executor) {
let resolvePromise; //声明该属性上的promise执行器
//为当前cancelToken类添加属性
this.promise = new Promise((resolve) => {
resolvePromise = resolve; //赋值执行器
})
executor(function () {//将执行器传递给cancelToken实例的方法,当方法调用执行器后,会改变cancelToken实例上的Promise的状态,在xhr发送请求时判断axios实例上的cancelToken实例上的Promise状态,从而来取消请求
resolvePromise();
});
}
//创建axios实例
function createInstance(config) {
let context = new Axios(config);
//创建请求函数,目的是能够直接x()发送请求,而返回的实例做不到
let instance = Axios.prototype.request.bind(context); //若实例调用,则将方法中的this改变为实例
//添加其他请求方法到instance函数上
Object.keys(Axios.prototype).forEach(key => {
instance[key] = Axios.prototype[key].bind(context); //将this指向为实例
})
//为请求函数添加实例上的属性和方法
Object.keys(context).forEach(key => {
instance[key] = context[key];
})
return instance;
}
let axios = createInstance({ method: 'GET' });
//拦截器调用
axios.interceptors.request.use(function (config) {
console.log('req1')
return config
}, function (err) {
return Promise.reject(err);
})
axios.interceptors.request.use(function (config) {
console.log('req2')
return config
}, function (err) {
console.log('err');
return Promise.reject(err);
})
axios.interceptors.response.use(function (data) {
console.log('resp1')
return data;
}, function (err) {
return Promise.reject(err);
})
axios.interceptors.response.use(function (data) {
console.log('resp2')
return data;
}, function (err) {
return Promise.reject(err);
})
console.dir(axios);
let token;
axios({ //相当于调用Axios.prototype.request方法
method: "GET",
url: " http://localhost:3001/posts",
cancelToken: new axios.cancelToken(function (c) {
token = c;
})
}).then(res => {
console.log(res);
})
token();
reduce实现map
Array.prototype.rMap = function(fn) {
if (typeof fn === 'function') {
return this.reduce((prev, cur) => {
prev.push(fn(cur));
return prev
}, []);
}
}
var array = [1, 2, 3, 4, 5];
array = array.rMap((x) => x * 5);
console.log(array.toString());
use fetch
发布/订阅
function EventHub() {
let cache = {}
this.on = function(eventName, fn) {
cache[eventName] = cache[eventName] || [];
cache[eventName].push(fn)
}
this.emit = function(eventName) {
if (cache.hasOwnProperty(eventName) && cache[eventName].length !== 0)
cache[eventName].forEach(fn => fn());
}
this.off = function(eventName, fn) {
if (!cache.hasOwnProperty(eventName)) return;
const index = cache[eventName].indexOf(fn);
if (index !== -1) {
cache[eventName].splice(index, 1)
} else {
delete cache[eventName]
}
}
}
函数柯里化
var cur = function() {
let args = Array.prototype.slice.call(arguments);
var add = function() {
args = args.concat(...arguments);
return add;
}
add.toString = function() {
return args.reduce((prev, cur) => prev + cur);
}
return add;
}
console.log(cur(1, 2, 3)(2)(3).toString());
下象棋
// 注意象棋棋盘是7 * 7,然后象棋每次跳日
// let count = 0
function dfs(x0, y0, xn, yn, n) {
// 递归出口
if (n == 0) {
if (x0 == xn && y0 == yn) {
return 1
}
return 0
}
//(x0,y0)结点能走的范围
let arr = getNextSteps(x0, y0)
let sum = 0
console.log(arr);
//一个for循环对从每个位置走的做处理
for (let i = 0; i < arr.length; i++) {
sum += dfs(arr[i][0], arr[i][1], xn, yn, n - 1)
}
return sum
}
let c = dfs(0, 0, 5, 6, 5)
// console.log(count); console.log(c);
// 用于判断它的下一步跳的情况,在最中间时一共有8种跳跃情况
function getNextSteps(x, y) {
let arr = []
// 往第一象限跳
if (x <= 4 && y <= 5) arr.push([x + 2, y + 1])
if (x <= 5 && y <= 4) arr.push([x + 1, y + 2])
// 往第二象限跳
if (x >= 2 && y <= 5) arr.push([x - 2, y + 1])
if (x >= 1 && y <= 4) arr.push([x - 1, y + 2])
// 往第三象限跳
if (x >= 2 && y >= 1) arr.push([x - 2, y - 1])
if (x >= 1 && y >= 2) arr.push([x - 1, y - 2])
// 往第四象限跳
if (x <= 4 && y >= 1) arr.push([x + 2, y - 1])
if (x <= 5 && y >= 2) arr.push([x + 1, y - 2])
return arr
}