一、数组去重的五种方法
let arr = [1, 0, 2, 3, 4, 5, 2, 3, 4];
//indexOf去重
function removeRepeat(arr) {
let res = [];
for (let i of arr) {
if (res.indexOf(i) == -1) {
res.push(i);
}
}
return res;
}
// set 去重
function removeRepeat(arr) {
let res = new Set(arr);
return Array.from(res);
}
// for循环去重
function removeRepeat(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
// filter 去重
function removeRepeat(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) == index;
});
}
// includes 去重
function removeRepeat(arr) {
let res = [];
for (let i of arr) {
if (!res.includes(i)) {
res.push(i);
}
}
return res;
}
let res = removeRepeat(arr);
console.log(res);
二、深拷贝和浅拷贝
区别:
深拷贝:拷贝对象中的所有数据都进行重新赋值给新对象 对于引用类型 开辟一块新的空间 进行指向 和之前的对象没有关联
浅拷贝:只拷贝第一层对象中的数据 里面的引用不会进行拷贝 对于浅拷贝 引用类型的数值指向同一块内存空间
无论是深拷贝还是浅拷贝 :基本数据类型的值都是相互独立的,深拷贝其中的引用类型值独立、浅拷贝 其中的引用类型指向同一块空间
- 深拷贝
1、for循环
let obj = {
name: "coderqian",
hobby: {
outdoor: "basketball",
indoor: "watch mv",
},
};
function deepClone(obj) {
let newobj = {};
for (let i in obj) {
if (typeof obj[i] == "Object") {
// 如果是object类型 递归调用
newobj[i] = deepClone(obj[i]);
} else {
// 基本类型 直接赋值
newobj[i] = obj[i];
}
}
return newobj;
}
let newobj = deepClone(obj);
console.log(newobj);
2、Json方法
let obj = {
name: "coderqian",
hobby: {
outdoor: "basketball",
indoor: "watch mv",
},
};
// JSON.parse 把json转化为js对象 JSON.stringify 把js对象转化为json对象
let newobj = JSON.parse(JSON.stringify(obj));
- 浅拷贝
1、for循环遍历一层
let obj = {
name: "coderqian",
hobby: {
outdoor: "basketball",
indoor: "watch mv",
},
};
function shallowclone(obj) {
let newobj = {};
for (let i in obj) {
newobj[i] = obj[i];
}
return newobj;
}
// 此时第一层非引用类型的值是相互独立的 但是hobby对象中的引用类型指向同一块内存空间
let newobj = shallowclone(obj);
2、Object.assign
let obj = {
name: "coderqian",
hobby: {
outdoor: "basketball",
indoor: "watch mv",
},
};
let newobj = {};
Object.assign(newobj, obj);
3、直接=赋值
三、手写instanceof
function myInstanceof(instance, target) {
// 定义一个指针指向实例
let pointer = instance;
while (pointer) {
// 一个实例的隐式原型 等于构造函数的显示原型
if (pointer == target.prototype) {
return true;
} else {
pointer = pointer.__proto__;
}
}
return false;
}
let obj = {};
let res = myInstanceof(obj, Object);
console.log(res); // true
四、手写new
function mynew(Classname) {
// 1、创建新对象
let newobj = {};
// 2、将新对象的隐式原型指向构造函数的显式原型 即部署在原型链上
newobj.__proto__ = Classname.prototype;
// 把参数转化成数组 且只取第二个开始的参数
let args = Array.from(arguments).slice(1);
// 3、改变构造函数中的this指向新对象 即给新对象赋值
let res = Classname.call(newobj, ...args);
// 4、返回新对象 (若构造函数有返回对象 则返回那个对象 否则返回newobj)
return typeof res == "object" ? res : newobj;
}
function Person(name, age) {
this.name = name;
this.age = age;
}
let person = mynew(Person, "coderqian", 23);
console.log(person); //Person { name: 'coderqian', age: 23 }
五、手写call、apply、bind
这里解释一下这三者的区别 这三个方法都是为了改变this指向
其中call和apply第一参数就是传入this的值 区别在于:
call 后面的参数 可以分开传递
apply后面的参数 放在一个数组中传递
bind和call 传参类似 只是bind 返回的是一个函数 不会立即执行 需要再次调用 可以看下发具体实现领悟~
一、call
Function.prototype.mycall = function (thisArg) {
// 如果没传参数 就以window
let context = thisArg || window;
// arguments是伪数组 先转化为数组 之后 从第二个元素(下标为0)开始拷贝剩余的作为参数
let argus = Array.from(arguments).slice(1);
// 为这个对象创建一个属性 属性为play函数 这里的this就指向 mycall的调用者 即play函数
context["fn"] = this;
context["fn"](...argus); // 执行play函数 此时这个函数的调用者 是context 即 传入的 obj 达到改变this指向的效果
delete context["fn"]; // 删除刚才添加的属性
};
function play(name, age) {
console.log(name + age); // coderqian22
console.log(this.name); //obj-coderqian this指向obj
}
let obj = {
name: "obj-coderqian",
};
play.mycall(obj, "coderqian", 22);
二、apply
// apply的实现和call思路一样 只是需要区别有参数和没参数的情况
Function.prototype.myapply = function (thisArg) {
let context = thisArg || window;
context["fn"] = this;
if (arguments[1]) {
// 传入参数
let argus = Array.from(arguments)[1];
context["fn"](...argus);
} else {
// 没参数
context["fn"]();
}
delete context["fn"];
};
function play(name, age) {
console.log(name + age); //coderqian22
console.log(this.name); //obj-coderqian
}
let obj = {
name: "obj-coderqian",
};
play.myapply(obj, ["coderqian", 22]);
三、bind
Function.prototype.mybind = function (thisArg) {
// 保存this变量 因为闭包 不好找到this (也就是mybind的调用者 play)
let self = this;
// 第一个括号的参数
let argus = Array.from(arguments).slice(1);
return function F() {
// 第二个括号的参数
let argus1 = Array.from(arguments);
// 参数进行拼接
let argssum = argus.concat(argus1);
// 判断是不是new出来的 因为new里面this会丢失
if (this instanceof F) {
// 是new出来的话 就让当前的这个实例作为this
self.apply(this, argssum);
} else {
// 不是new的话 就直接是传入的这个对象作为this
self.apply(thisArg, argssum);
}
};
};
function play(name, age, sex) {
console.log(name + age + sex);
}
let obj = {
name: "coderqian111",
};
let res = play.mybind(obj, "coderqian", 22);
res("男");
六、防抖 、节流
一、防抖
// 防抖 就是在没触发的一段时间后执行 若是在这一段时间内触发了 则会重新计时 不会执行该函数
// 防抖:在停止操作的一段时间后执行
// func 是要处理的函数 delay是延迟时间
function debounce(func, delay) {
let timer;
return function () {
if (timer) clearTimeout(timer); // 如果上次的计时器还在 就清空计数器 重新计时
timer = setTimeout(() => {
func.apply(this, arguments);
}, delay);
};
}
function paymoney() {
console.log("付钱防抖!");
}
let button = document.getElementById("debounce");
button.onclick = debounce(paymoney, 1000);
二、节流
// 节流:每隔一个固定的时间就执行
// 方法一
function throttle(func, delay) {
let timer;
return function () {
// 如果timer存在 说明 上一个计时器还没结束 也就是 在这段时间内 不会执行函数
if (timer) {
return;
}
timer = setTimeout(() => {
func();
timer = null; // delay 时间过完了 timer置为null 表示后面的出发可以再次执行
}, delay);
};
}
// 方法二 通过时间戳 这个方法节流的第一次会立马执行
function throttle(func, delay) {
let prev = 0;
return function () {
let now = new Date();
// 如果当前的时间减去上一次执行的时间大于延迟时间 就会可以执行
// 并将这次执行的时间作为下一次的开始时间
if (now - prev > delay) {
func();
prev = now;
}
};
}
function paymoney() {
console.log("付钱节流!");
}
let button1 = document.getElementById("throttle");
button1.onclick = throttle(paymoney, 1000);
七、原生ajax
function sendajax() {
// 1、 初始化xhr对象
const xhr = new XMLHttpRequest();
// 2、 建立连接 设置请求方法和url
xhr.open("get", "./data.json");
// 3、发送请求
xhr.send();
// 4、状态改变时 进行回调
xhr.onreadystatechange = function () {
// readyState 有0-4 五个值
// 0 代表 未初始化 1 代表 初始化成功 2 代表发送请求
// 3 代表返回了部分数据 4 代表返回了全部数据
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 进行成功的操作
console.log(xhr.responseText);
}
}
};
}
sendajax();
八、数组扁平化
//传入参数 决定扁平化的阶数
Array.prototype._flat = function (n) {
let result = [];
let num = n;
for (let item of this) {
// 如果是数组
if (Array.isArray(item)) {
n--;
// 没有扁平化的空间 直接推入
if (n < 0) {
result.push(item);
}
// 继续扁平化 并将n传入 决定item这一个数组中的扁平化
else {
result.push(...item._flat(n));
}
}
// 不是数组直接推入
else {
result.push(item);
}
// 每次循环 重置n 为传入的参数 因为每一项都需要扁平化 需要进行判断
n = num;
}
return result;
};
let arr = [1, 2, [3, 4], [5, 6, [7, 8]]];
let res = arr._flat(1);
console.log(res); // [ 1, 2, 3, 4, 5, 6, [ 7, 8 ] ]