深拷贝:考虑数组 对象 正则 Date
前提知识:typeof ,==,值类型和引用类型,instanceof,for...in遍历,hasOwnProperty
typeof 1
'number'
typeof 1.2
'number'
typeof 'ss'
'string'
typeof undefined
'undefined'
typeof true
'boolean'
typeof Symbol
'function'
typeof class student{}
'function'
typeof null
'object'
typeof []
'object'
typeof {}
'object'
typeof /^\w+$/
'object'
typeof new Date()
'object'
null == undefined
true
[1,2,3] instanceof Array
true
[1,2,3].__proto__ === Array.prototype
true
值类型:在栈空间中:key是变量名,value是变量值。
引用类型:在栈空间中:key是变量名,value是一个地址。在堆空间中,key是地址,value是引用类型的内容本身。即引用类型变量本身是一个对数据内容的引用。
所以要使用深拷贝。
for...in会遍历出原型对象上新增的属性或方法,所以需要使用hasOwnproperty判断某属性是否是数组本身具有的属性。
// 手写deepClone
// 深拷贝:考虑数组 对象 正则 Date
function deepClone(obj){
if(typeof obj !=='object' || obj==null)
return obj ;
if(obj instanceof Date){
const res=new Date();
res.setTime(obj.getTime());
return res;
}
if(obj instanceof RegExp){
const Constructor =obj.constructor;
return new Constructor(obj);
}
if(obj instanceof Array || obj instanceof Object){
const res=Array.isArray(obj)?[]:{};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
res[key]=deepClone1(obj[key]);
}
}
return res;
}
}
结合Object.assign()实现的深拷贝(Object.assign()并不是深拷贝,只能复制属性和基本类型的属性值,当属性值是引用类型时,复制的是地址。所以要手动写深拷贝)
deepClone = (obj) => {
if (obj == null) return;
if (typeof obj !== "object") return obj;
let resultObj = {};
for (let item in obj) {
let temp = {};
if (typeof obj[item] === "object" && obj[item] !== null) {
if (!Array.isArray(obj[item])) {
temp[item] = this.deepClone(obj[item]);
} else {
temp[item] = [];
for (let i in obj[item]) {
temp[item][i] = this.deepClone(obj[item][i]);
}
}
} else {
temp[item] = obj[item];
}
Object.assign(resultObj, temp);
}
return resultObj;
};
JSON.parse(JSON.stringify(obj))也可以实现深拷贝,但是前提要求是,被序列化和反序列化的数据必须是符合JSON格式需求的,否则会直接被忽略掉。
浅拷贝可以用:
obj2={...obj1}或者obj2=Object.assign({},obj1)实现。
Object.assign(target,...source)用于将一个或多个源对象复制到目标对象,并返回目标对象。
instanceof函数:(大致思路)
// a instanceof b
function instanceof1(){
while(a!==null){
if(a.__proto__ === b.prototype)
return true;
a=a.__proto__;
}
return false;
}
bind函数:
fn.bind(obj,a,b),返回一个新的函数,当新函数被调用时,指定fn的调用者为obj,并执行fn函数。
前提知识:原型和原型链,this指向函数的调用者
Function.prototype.bind1=function(){
// 这样直接指定slice的调用者为arguments是不可以的
// 必须通过call或apply指定slice的调用者为arguments
// console.log(arguments.slice());
const args=Array.prototype.slice.call(arguments);
// 指定fn的调用者对象
const obj=args.shift() || window;
// 拿到fn
const self=this;
return function(){
// 指定fn的调用者对象,并执行fn
self.apply(obj,args);
}
}
call函数:
fn.call(obj,a,b),指定fn函数的调用者为obj,参数为非数组形式,并执行函数
前提知识:数组解构赋值
Function.prototype.call1=function(){
const args=Array.prototype.slice.call(arguments);
const obj=args.shift() || window;
obj.fn=this;
if(args[0])
obj.fn(...args);
else
obj.fn();
}
apply函数:
fn.apply(obj,[a,b]) ,指定fn函数的调用者为obj,参数为数组形式,并执行函数
Function.prototype.apply1=function(){
const args=Array.prototype.slice.call(arguments);
const obj=args.shift() || window;
obj.fn=this;
if(args[0])
obj.fn(...args[0]);
else
obj.fn();
}
防抖debounce函数
前提知识:定时器
// 封装debounce
function debounce(fn, delay = 500) {
// timer是在闭包中的,不能被外界修改
let timer = null;
// 函数作为返回值,使用闭包
return function () {
if (timer)
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this,arguments)
// 清空定时器,到时清空
timer = null;
}, delay)
}
}
节流throttle函数
// 节流封装
function throttle(fn,delay=500){
let timer=null;
return function(){
if(timer)
return;
timer=setTimeout(()=>{
// arguments是[返回的函数]的参数,并不是给fn的,所以fn.()直接调用并没有事件对象e
fn.apply(this,arguments);
timer=null;
},delay)
}
}
手写Promise加载一张图片:
function loadImg(src){
// pending
return new Promise((resolve,reject)=>{
let img=document.createElement('img');
// onload是加载完之后执行回调函数,也是异步
img.onload=function(){
resolve(img);//resolved
}
img.onerror=function(){
reject(new Error(`图片加载失败${src}`));//rejected
}
img.src=src;
document.body.appendChild(img);
})
}
通用的事件监听函数:
前提知识:事件代理
// 事件监听函数
function bindEvent(elem, type, fn, selector) {
elem.addEventListener(type, event => {
const target = event.target;
// 有选择器,说明存在事件代理
if (selector) {
// 再判断target和选择器selector是否匹配
if (target.matches(selector)) {
fn.call(target, event);
}
} else
fn.call(target, event);
})
}
手写ajax,结合promise
function ajax(url){
const p=new Promise((resolve,reject)=>{
const xhr=new XMLHttpRequest();
xhr.open('get',url,true);
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if(xhr.status===200){
resolve(JSON.parse(xhr.responseText));
}else if(xhr.status===404){
reject(new Error('404 not found'));
}
}
}
xhr.send(null);
// post请求
// xhr.open('post',url,true);
// xhr.send(JSON.stringify({name:'tom'}));
})
}
深度比较:判断两个对象的具体内容是否相等。(因为地址不同,===肯定是false)
前提知识:
typeof:
值类型:number string boolean undefined symbol
引用类型object:[] {} (typeof null也是object)
function:type of class也是function
值类型判断相等用===
// 手写深度比较
const obj1 = {
a: 100, b: {
x: 200,
y: 300,
},
d:[1,2,3]
}
const obj2 = {
a: 100, b: {
x: 200,
y: 300,
},
d:[1,2,3]
}
console.log(obj1===obj2);//false
// 判断是否为引用类型:[]或{}
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}
// typeof:值类型[number string boolean undefined symbol],引用类型[object],function
// 值类型判断相等直接用===,引用类型才用isEqual,函数一般不用isEqual
function isEqual(obj1, obj2) {
// 只要有一个不是[]或{},就用===判断
if (!isObject(obj1) || !isObject(obj2))
// 值类型,注意:参与equal的一般不会是函数
return obj1 === obj2;
// 传的参数是同一个
if(obj1===obj2)
return true;
// 下面保证了都是引用类型:[]或{}
// 先获取到两个对象的keys构成的数组,比较长度
const obj1keys=Object.keys(obj1);
const obj2keys=Object.keys(obj2);
if(obj1keys.length!==obj2keys.length)
return false;
// 保证key的个数一致,再比较具体内容
for (let item in obj1) {
if (obj2.hasOwnProperty(item)) {
const res= isEqual(obj1[item], obj2[item]);
if(!res)
return false;
}
else
return false;
}
return true;
}
console.log(isEqual(obj1,obj2));//true
手写字符串的trim()方法:
前提知识:原型,this,正则表达式,replace()
原型:trim()方法不是字符串的方法,而是其原型对象上的方法。
this:trim()方法中的this,就是其调用者对象字符串。
正则表达式:/^/匹配开头,/$/匹配结尾,\s匹配空格,+表示一个或多个
replace():匹配字符串的子串,并进行替换,返回新串
即用正则表达式匹配空格,用空字符串进行替换。
String.prototype.trim1=function(){
return this.replace(/^\s+/,'').replace(/\s+$/,'');
}
求几个数的最大值:
// 求几个数的最大值
// 用api
const arr=[10,50,60,42,30,74,54,65];
console.log(Math.max(...arr));
// 手写一个函数
function max(){
const args=Array.prototype.slice.call(arguments);
// foreach遍历
// let max=args[0];
// args.forEach(element => {
// if(element>max)
// max=element;
// });
// return max;
// 或者用reduce
return args.reduce((prev,current)=>{
return current>prev?current:prev;
})
}
console.log(max(...arr));
获取url中的参数值:
// 获取url中的参数值,给定key,求value
function query(name){
// location.search获取到所有参数,'?a=10&b=20&c=30',substring(1),截取?后面的,类似slice
const search=location.search.substring(1);
console.log(search);
// 方法一:split
// const arr=search.split('&');
// console.log(arr);
// for (const item of arr) {
// const sub=item.split('=');
// if(sub[0]===name)
// return sub[1];
// }
// return null;
// 方法二:正则表达式
// 'a=10&b=20&c=30'
// (^|&)匹配key=value对的前一个字符,要么是开头,要么是&
// ([^&]*)匹配value,[^&]表示匹配不是&的字符,即遇到&就停止匹配,*表示0个或多个字符
// (&|$)匹配key=value对的后一个字符,要么是&,要么是结尾
const reg=new RegExp(`(^|&)${name}=([^&]*)(&|$)`,'i');
const res=search.match(reg);
// res为数组,第一个元素是整个表达式匹配到的内容,后面三个元素是三个括号分别匹配到的内容
if(res==null)
return null;
console.log(res);
return res[2];
}
// res:['&b=20&', '&', '20', '&', index: 4, input: 'a=10&b=20&c=50', groups: undefined]
console.log(query('b'))
function query1(name){
const search=location.search
const p=new URLSearchParams(search);
return p.get(name);
}
实现异步函数的重试机制RunWithRetry:调用fn函数直至成功,或达到重试次数上限
// fn返回成功或失败的Promise对象,调用runWithRetry直至fn成功(返回成功的Promise对象),或者达到重试次数上限(返回失败的Promise对象)
function runWithRetry(fn,retryTimes){
let i=1;
return new Promise(async (resolve,reject)=>{
while(i<=retryTimes){
try{
const res=await fn();
resolve(res);
break;
}catch(ex){
if(i===retryTimes)
reject(ex);
}
i++;
}
})
}