前言
本文整理了前端面试高频出现的手写js相关的题目,如果对答案有不一样见解的同学欢迎评论区补充讨论,当然有问题,也欢迎在评论区指出。
切记:笔者记录的这些js手写题,做之前,想想这些方法是用来干什么,需要传什么参数,返回的什么,另外还需要注意一些边界条件,这对js基础有很大考验,基础不好的,一定要好好补补js(尤其,原型链、this指向、promise、继承等)
- 这篇文章原型链讲的非常好blog.csdn.net/cc188688768…
- js指向问题juejin.cn/post/684490…
- js继承的六种方式juejin.cn/post/699693…
- promise-----> 后续更新
学习就是这么简单,你听懂了吗?
引用加参考:感谢两位大佬以及全掘金作者帮助,让我收获很多 juejin.cn/post/696871… juejin.cn/post/684490…
1、实现new操作符
new 出来的是一个实例对象,有自己的proto属性,而且实例的proto指向构造函数的prototype
// 传入构造函数和构造函数参数
function myNew(fun,...args){
// 初始化一个对象,分配空间
const obj = {};
// 链接到函数原型
obj.__proto__ = fun.prototype;
//等同Object.setPrototypeOf(obj, fun.prototype)
// 将 obj 绑定到构造函数上,并且传入剩余的参数
const res = fun.apply(obj,args);
// 返回构造好的对象,特别的若res返回一个非空对象,那么返回res
return res instanceof Object ? res : obj
//等同return (typeof res!== null && typeof res === 'object')?res:obj;
}
function Animal(name) {
this.name = name;
}
let animal = myNew(Animal, 'dog');
console.log(animal.name) // dog
2、实现instanceOf
function myInstanceof(left,right){
// 验证如果为基本数据类型,就直接返回 false
const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']
if(baseType.includes(typeof(left))) return false;
const prototype = right.prototype;
let proto = left.__proto__;
while(true) {
if(proto===null) return false;// 找到最顶层
if(proto===prototype) return true;
proto = proto.__proto__;// 没找到继续向上一层原型链查找
}
}
function Func(){
console.log('hzy')
}
let fun = new Func();
myInstanceof(fun,Function);
3、实现浅/深拷贝
- 浅拷贝:拷贝的是对象的指针,修改内容会互相影响
- 深拷贝:将整个对象拷贝到另一个内存中,修改内容互不影响
浅拷贝方法
- concat()和slice(0)和展开符(...)和Object.assign()
let arr = [1, 3, {
username: 'kobe'
}];
let arr2 = arr.concat();
//let arr2 = arr.slice(0);
//let arr2 = [...arr]
//let arr2 = Object.assign([], arr); 合并对象
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]
深拷贝方法
方法1:JSON.parse(JSON.stringify(arr))
let arr = {
a: 1,
b: [ 'e', 'f', 'g' ],
c: { h: { i: 2 } }
};
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'kobe' } ]
//缺点:
//1.他无法实现对函数 、RegExp等特殊对象的克隆
//2.会抛弃对象的constructor,所有的构造函数会指向Object
//3.对象有循环引用,会报错
//如:
const arr = {
a: say,
b: new Array(1),
c: new RegExp('ab+c', 'i'),
d: Messi
};
方法2:基础方式(缺陷同上)
function deepClone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) { //遍历对象要用 。。。in。。。
cloneTarget[key] = deepClone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
const target = {
field1: 1,
field2: undefined,
field3: 'hzy',
field4: {
child: 'child',
},
field5: [2, 4, 8]
};
let arrCopy =deepClone(target);
target['one'] = 5
console.log(target,arrCopy)
4、实现apply、call、bind
apply方法实现
1、实现效果
function fn(age){
this.age = age
}
let target = {name:'hzy'}
fn.myApply(target,[18,3]) // 同fn.apply(target,18,3)
console.log(target) //{ name: 'hzy', age: 18 }
2、分析apply原理
//俩参数,fn构造函数,args参数数组
//myApply 应该挂在 Function.prototype 上
//如果不传入参数,target默认指向为 window,args默认是[]
//指定this到target.fn并传入给定参数执行函数
//删除target里的fn函数
3、代码实现
Function.prototype.myApply = function (target,args){
// this:[Function: fn]
if(typeof this !== 'function'){
throw new TypeError('not a function')
}
//如果不传入参数,target默认指向为 window,args默认是[]
target = target || window;
args = args || [];
target.fn = this; //target:{ name: 'hzy', fn: [Function: fn] }
target.fn(...args) // 执行target里的fn函数
delete target.fn //删除target里的fn函数
return target
}
call方法实现
apply 与 call 的区别: apply 参数要为数组,call参数是以逗号分开
bind方法实现
fn.bind(obj,arg1,arg2) 创建一个新的绑定函数,不会立即执行 fn 函数,而 call, apply 会立即执行
fn绑定的对象是传进的第一个参数,后面的参数作为函数参数传入,如:fn.bind(obj,arg1).bind(arg2,arg3).bind(arg4)
//bind实现要复杂一点 因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)
Function.prototype.myBind = function (target,...args){
if(typeof this !== 'function'){
throw new TypeError('not a function')
}
const self = this; // 存储源函数以及函数参数
// 对返回的函数 secondArgs 二次传参
let fnToBind = function (...secondArgs){
// 判断this是否是fnToBind绑定函数的实例 也就是fnToBind函数返回的值是否通过new调用所得
// new调用的函数this指向当前函数,否则就绑定到传入的target上
let context = this instanceof fnToBind ? this : Object(target);
// 用call调用源函数绑定this的指向并传递参数,返回执行结果
return self.call(context,...args,...secondArgs);
}
if (self.prototype) {
// 复制源函数的prototype给fnToBind 有些函数没有prototype,比如箭头函数
fnToBind.prototype = Object.create(self.prototype);
}
return fnToBind; // 返回绑定的函数
}
let obj = { name: "hzy" }
function test(x,y,z) {
console.log(this.name) // ciel
console.log(x+y+z) // 6
}
let Bound = test.myBind(obj,1,2)
Bound(3) //二次传参3 => 6
// 测试
let obj = { name: "hzy" }
function test(x,y,z) {
console.log(this.name) // ciel
console.log(x+y+z) // 6
}
let Bound = test.myBind(obj,1,2)
Bound(3) //二次传参3 => 6
5、实现Object.create()
new 出来的是一个实例对象,有自己的proto属性,而且实例的proto指向构造函数的prototype
1、实现效果
console.log(myCreate({a:2}).__proto__) //{ a: 2 }
相当于Object.create({a:2})
2、分析Object.create()原理
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
3、代码实现
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
6、实现JSON.parse和JSON.stringify
JSON.parse()
- 可以通过eval()这种方式,但有xss漏洞
eval('(' + target + ')');
- JSON.parse()半小时实现教程:zhuanlan.zhihu.com/p/28049617
JSON.stringify()
function myStringify(target) {
if (typeof target !== 'object' || target === null) {
// 对字符串特殊处理,多双引号
if(typeof target==='string'){
return `"${target}"`
}else{
return String(target);
}
}else{
//处理对象和数组
const json = [];
for(let key in target){
let value = target[key];
if(Array.isArray(target)){
json.push(`${myStringify(value)}`)
}else{
json.push(`"${key}":${myStringify(value)}`)
}
}
//这里需要注意,当数组或对象被转化为字符串时,就没了括号
if (Array.isArray(target)) {
return `[${json}]`
} else {
return `{${json}}`
}
}
}
console.log(myStringify('string'))
// "string"
console.log(myStringify(666))
// 666
console.log(myStringify({name1: {name2: "abc"}}))
// {"name1":{"name2":"abc"}}
console.log(myStringify([1, "false", false]))
// [1,"false",false]
7、版本号排序
将一组版本号['','0.1.1', '0.1.1', '0.1.1.1', '2.2', '4.3.5'],排列成这样[ '', '0.1.1', '0.1.1', '0.1.1.1', '2.2', '4.3.5' ]
let arr = ['','0.1.1', '0.1.1', '0.1.1.1', '2.2', '4.3.5']
arr.sort((a, b) => {
const arr1 = a.split('.');
const arr2 = b.split('.');
let i = 0;
while (true){
let str1 = arr1[i];
let str2 = arr2[i];
i++;
if(str1===undefined||str2===undefined){
return arr1.length-arr2.length
}
if(str1===str2) continue;
return parseInt(str1)-parseInt(str2);
}
});
console.log(arr);//[ '', '0.1.1', '0.1.1', '0.1.1.1', '2.2', '4.3.5' ]
8、LRU(最近最少使用)算法
先带你了解LRU:blog.csdn.net/belongtocod…
// 可以用一个特殊的栈来保存当前正在使用的各个私钥key。
// 当一个新的私钥key加进来,便将该私钥key压入栈顶,其他的私钥key往栈底移,如果内存不够,则将栈底的页面号移除。
// 这样,栈顶始终是最新被访问的私钥key,而栈底则是最近最久未访问的私钥key。
// 实现一个LRU(最近最少使用)缓存机制
// 支持get(key)获取数据,push(key,value)写入数据
class LRU{
constructor(capacity) {
this.capacity = capacity;//设置缓存容量大小
this.secrets = new Map();//存储密钥和密钥值
}
get(secretKey){
if(this.secrets.has(secretKey)){
let secretValue = this.secrets.get(secretKey)
this.secrets.delete(secretKey);//先删掉原来位置上的key,重新加入栈顶
this.secrets.set(secretKey,secretValue);
return secretValue;
}else{
return -1;
}
}
put(secretKey,secretValue){
//先判断栈里有没有这个key,有的话,删了重新加入栈顶
if(this.secrets.get(secretKey)){
this.secrets.delete(secretKey);
this.secrets.set(secretKey,secretValue);
}else if(this.secrets.size < this.capacity){// key不存在,capacity未满
this.secrets.set(secretKey,secretValue);
}else{// key不存在,capacity满了,得移除栈底层的key,在栈顶加入新key
this.secrets.delete([...this.secrets.keys()][0]);
//this.secretKey.delete(this.secretKey.keys().next().value);//同上
this.secrets.set(secretKey,secretValue);
}
}
}
let cache = new LRU(2);
cache.put(1, '1');
cache.put(2, '2');
console.log(cache.get(1))// 返回 '1'
cache.put(3, '3');// 该操作会使得密钥 2 作废
console.log(cache.get(2))// 返回 -1 (未找到)
cache.put(4, '4');// 该操作会使得密钥 1 作废
console.log(cache.get(1))// 返回 -1 (未找到)
console.log(cache.get(3))// 返回 '3'
console.log(cache.get(4))// 返回 '4'
9、将函数柯里化
function curry(fn,args){
const fnLen = fn.length;//整个函数参数长度
args = args || [];
return function (){ //每次返回一个函数
console.log(arguments)//第一个函数的参数对象---类数组
let newArgs = args.concat(Array.prototype.slice.call(arguments))
if(newArgs.length<fnLen){
return curry(fn,...newArgs); //如果长度不够,继续柯里化
}else{
return fn.call(this,...newArgs);//长度够,直接返回柯里化后的函数
}
}
}
function add(a, b, c) {
return a + b + c;
}
let sum = curry(add);
// console.log(sum(2)(3)(4))
// console.log(sum(2,3,4))
// sum(2)(3,4);
console.log(sum(2,3)(4))
10、dom树转json对象以及dom的遍历
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="header">
<div class="content1">111</div>
<div class="content2">222</div>
<div class="content3">333</div>
</div>
<div id="main">
<div class="content4">
<span>内容1</span>
<span>内容2</span>
<span>内容3</span>
</div>
</div>
<div id="footer">
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
</body>
<script>
window.onload = function (){
let root = document.getElementsByTagName('body')[0];
if(root.length) return [];
// dom树转json
// function dom2json(root){
// let obj = {};
// obj.name = root.tagName;
// console.log(root.tagName);
// obj.children = [];
// root.childNodes.forEach(child=>{
// if(child.tagName!==undefined) {
// obj.children.push(dom2json(child))
// }
// })
// return obj
// }
// console.log(dom2json(root))
//层次遍历dom树
let nodes = [];
let queue = [root];
console.log(root)
while (queue.length){
let levelLength = queue.length;
for(let i=0;i<levelLength;i++){
const cur = queue.shift()
nodes.push(cur);
for(let item of cur.children){
queue.push(item);
}
}
}
console.log(nodes)
}
</script>
</html>
11、vdom(json对象)转真实dom树
let vdom = {
tag: 'DIV',
attrs: {
id: 'app'
},
children: [
{
tag: 'SPAN',
children: [
{tag: 'A', children: []}
]
},
{
tag: 'SPAN',
children: [
{tag: 'A', children: []},
{tag: 'A', children: []}
]
}
]
}
把上面虚拟Dom转化成下方真实Dom
<div id="app">
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
function myRender(vdom) {
let dom = document.createElement(vdom.tag);
// 设置标签上的属性
vdom.attrs&&Object.keys(vdom.attrs).forEach(key=>{
dom.setAttribute(key,vdom.attrs[key]);
})
// 递归添加并渲染所有孩子节点
vdom.children&&vdom.children.forEach(child=>{
dom.appendChild(myRender(child));
})
return dom;
}
//let el = document.getElementsByTagName('body')[0].appendChild(myRender(vdom))
console.log(myRender(vdom));
12、按父子关系实现数组转JSON(v-Dom)
let arr = [{
id: 'id1',
parentId: '',
children:[]
}, {
id: 'id2',
parentId: 'id1',
children:[]
}, {
id: 'id3',
parentId: 'id1',
children:[]
}, {
id: 'id4',
parentId: 'id1',
children:[]
}, {
id: 'id5',
parentId: 'id2',
children:[]
}, {
id: 'id6',
parentId: 'id3',
children:[]
}, {
id: 'id7',
parentId: 'id3',
children:[]
}, {
id: 'id8',
parentId: 'id4',
children:[]
}]
结果为:
let res = {
"id": "id1",
"parentId": "",
"children": [
{
"id": "id2",
"parentId": "id1",
"children": [
{
"id": "id5",
"parentId": "id2",
"children": []
}
]
},
{
"id": "id3",
"parentId": "id1",
"children": [
{
"id": "id6",
"parentId": "id3",
"children": []
},
{
"id": "id7",
"parentId": "id3",
"children": []
}
]
},
{
"id": "id4",
"parentId": "id1",
"children": [
{
"id": "id8",
"parentId": "id4",
"children": []
}
]
}
]
}
function array2json(arr) {
let obj = {}
arr.forEach(item => {
if (item.parentId === '') {
obj = item;
}
})
function dfs(obj) {
//找到所有子孩子
let childs = arr.filter(item => {
return item.parentId === obj.id;
})
obj.children.push(...childs)
for (let child of childs) {//遍历所有的孩子节点
dfs(child);//对孩子节点,再重新当作当前节点,进行dfs
}
}
dfs(obj);
return obj;
}
console.log(array2json(arr))
13、按父子关系实现JSON(v-Dom)转数组
function json2array(res){
let arr = []
const dfs = function(obj){
for(let child of obj.children){
dfs(child);
}
obj.children = [];
arr.push(obj);
}
dfs(res);
return arr
}
console.log(json2array(res))
14、实现Object.is
Object.is不会转换被比较的两个值的类型,这点和===更为相似,他们之间也存在一些区别。
- NaN在===中是不相等的,而在Object.is中是相等的
- +0和-0在===中是相等的,而在Object.is中是不相等的
Object.is = function (x, y) {
if (x === y) {
// 当前情况下,只有一种情况是特殊的,即 +0 -0
// 如果 x !== 0,则返回true
// 如果 x === 0,则需要判断+0和-0,则可以直接使用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断
return x !== 0 || 1 / x === 1 / y;
}
// x !== y 的情况下,只需要判断是否为NaN,如果x!==x,则说明x是NaN,同理y也一样
// x和y同时为NaN时,返回true
return x !== x && y !== y;
};
15、实现AJAX请求
function getJSON(url){
return new Promise((resolve,reject)=>{
//创建xhr对象
let xhr = new XMLHttpRequest();
//调用open方法设置基本请求信息
xhr.open('get',url,false)//false表示async
//设置请求头信息
xhr.setRequestHeader("Content-Type", "application/json")
//注册监听函数
xhr.onreadystatechange = function (){
if(xhr.readyState !== 4) return;//未响应完成
if(xhr.status === 200 || xhr.status === 304){
resolve(xhr.responseText) //返回响应结果
}else{
reject(new Error(xhr.responseText))
}
}
xhr.send();
})
}
16、实现大数相加
function add(a,b){
//取两个数的最大长度,补0补齐长度
let maxLen = Math.max(a.length,b.length);
a = a.padStart(maxLen,'0')
b = b.padStart(maxLen,'0');
//定义进位
let flag = 0;
let sum = ""
for(let i=maxLen-1;i>=0;i--){
let temp = parseInt(a[i])+parseInt(b[i])+flag;
flag = Math.floor(temp/10);
sum = temp%10 + sum
}
return sum;
}
// 大数相加
let a = "9007199254740991";
let b = "1234567899999999999";
console.log(add(a,b));
17、数组扁平化
常规方法
function flatten(arr){
if(!Array.isArray(arr)){
throw new TypeError('args must be an array');
}
let res = [];
arr.forEach(item => {
if(Array.isArray(item)){
res.push(...flatten(item));//最小层数组时,需展开,push进res [2,4]--[1,2,2,4]--[1,1,2,2,4]
}else{
res.push(item);
}
})
return res;
}
console.log(flatten([1,[1,2,[2,4]],3,5])); // [1, 1, 2, 2, 4, 3, 5]
reduce方法实现扁平化
let arr = [1, 2, '3js', [4, 5, [6], [7, 8, [9, 10, 11], null, 'abc'], {age: 58}], '[]', null];
function flatten(arr) {
if(!Array.isArray(arr)){
throw new TypeError('not an array');
}
return arr.reduce((prev, cur) => {
return Array.isArray(cur) ? prev.concat(flatten(cur)) : prev.concat(cur)
}, [])
}
console.log(flatten(arr))
18、实现reduce
reduce使用
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(pre, cur, curIndex, arr) {
// pre上次回调返回的值,cur当前索引值,curIndex当前索引
console.log(pre, cur, curIndex);
return pre + cur;
},0) //传递给函数的初始值。默认是0
console.log(sum);
reduce实现
//例子: arr.reduce(cb,initValue)
// 1.函数中有哪些参数cb,initValue
// 2.回调函数参数(pre: 代表累加值,cur: 目前值,curIndex: 第几个,arr 调用 reduce 的数组)
// 3.整体返回 pre 累加值
Array.prototype.myReduce = function (cb,initValue){
//判断arr是不是数组
if(!Array.isArray(this)){
throw new TypeError('not a array')
}
let arr = this;
// 判断有没有初始值,没有就默认是0
initValue = initValue || 0;
let pre = initValue;
// cb 每次执行完都会返回一个新的pre,覆盖之前的
arr.forEach((cur,curIndex)=>{
pre = cb(pre,cur,curIndex,arr)
})
return pre;
}
reduce实现map
//例子: arr.map(cb)
// 1.函数中有哪些参数cb
// 2.回调函数参数(cur: 目前值,curIndex: 第几个,arr:遍历的数组)
// 三个参数刚好和reduce函数接收的回调函数的第2、3、4个参数是对应的
//思路:将每次遍历的元素,作为传入的函数的参数,并将函数执行的结果放入新的数组中
Array.prototype.myMap = function (cb){
//判断arr是不是数组
if(!Array.isArray(this)){
throw new TypeError('not an array')
}
//操作完数组返回最终结果
return this.reduce((pre,cur,index,arr)=>{
pre.push(cb(cur,index,arr));
return pre;
},[])
}
console.log([1,3,4].myMap((item)=>{
return item*2;
}))
reduce实现filter
//例子: arr.filter(cb)
// 1.函数中有哪些参数cb
// 2.回调函数参数(cur: 目前值,curIndex: 第几个,arr:遍历的数组)
// 3.三个参数刚好和reduce函数接收的回调函数的第2、3、4个参数是对应的
//思路:将每次遍历的元素,作为回调函数的参数,并通过函数执行结果true或false判断是否将当前值加入新数组
Array.prototype.myFilter = function (cb){
//判断arr是不是数组
if(!Array.isArray(this)){
throw new TypeError('not an array')
}
//操作完数组返回最终结果
return this.reduce((pre,cur,index,arr)=>{
if(cb(cur,index,arr)){
pre.push(cur)
}
return pre;
},[])
}
console.log([1,3,4].myFilter((item)=>{
return item===3;
}))
reduce实现数组去重
let arr = [1, 2, 3, 1, 1, 2, 3, 3, 4, 3, 4, 5]
let result = arr.reduce((pre, item, index, arr) => {
// !pre.includes(item) && pre.push(item);
if(!pre.includes(item)){
pre.push(item);
}
return pre;
}, [])
console.log(result); //[1, 2, 3, 4, 5]
//其他方式去重(set+解构+展开运算符)
let result = [...new Set(arr)]
reduce实现数组求最大值最小值
let arr = [1, 2, 3, 4, 5, 6, 7]
console.log(arr.reduce((prev, cur)=>{
return Math.max(prev, cur)
})); // 7
console.log(arr.reduce((prev, cur)=>{
return Math.min(prev, cur)
})); // 1
19、对象扁平化
实现效果:
const obj = {
a: {
b: 1,
c: 2,
d: {e: 5}
},
b: [1, 3, {a: 2, b: 3}],
c: 3
}
结果返回如下
// {
// 'a.b': 1,
// 'a.c': 2,
// 'a.d.e': 5,
// 'b[0]': 1,
// 'b[1]': 3,
// 'b[2].a': 2,
// 'b[2].b': 3
// c: 3
// }
function flatten(obj){
let res = {};
const dfs = function (preStr,cur){
// 2.终止条件:当前值cur既不是对象,也不是数组时
if(cur.constructor === Array){
//处理数组时的字符串,递归
cur.forEach((item,index)=>{
dfs(`${preStr}[${index}]`, item);
})
}else if(cur.constructor === Object){
//处理对象字符串,递归
Object.keys(cur).forEach(key=>{
// 这里要判断第一个对象遍历时,没有 . ,即preStr=''时,不要 .
dfs(`${preStr}${preStr?'.':''}${key}`, cur[key])
})
}else{
res[preStr] = cur;
}
}
// 1.递归需要传的参数,preStr:key组成的字符串,obj:当前key对应的值
dfs('',obj);
return res;
}
console.log(flatten(obj))
20、解构和展开运算符结合
// 实现字符串转数组
let str ='abcdefg'
console.log([...str])
console.log(Array.prototype.slice.call(str))
console.log(str.split(''))
//其它小tips
let arr = [1,2,3,4]; //类数组都可
const [a,...arr1] = arr
console.log(a,arr1) //1 [ 2, 3, 4 ]
21、实现setTimeout=>setInterval
//有缺陷
function mySetInterval(cb,delay){
let timeId = null;
const dfs = function (){
cb();// 执行传入的回调函数
setTimeout(()=>{
dfs()
},delay)
}
// 第一个setTimeout,获取timeId
timeId = setTimeout(()=>{
dfs()
},delay)
return timeId;
}
let timeId = mySetInterval(()=>{
console.log('a')
},1000)
console.log(timeId) // 数字
//完整版
let timeMap = {}
let id = 0 // 简单实现id唯一
const mySetInterval = (cb, time) => {
let timeId = id // 将timeId赋予id
id++ // id 自增实现唯一id
let fn = () => {
cb()
timeMap[timeId] = setTimeout(() => {
fn()
}, time)
}
timeMap[timeId] = setTimeout(fn, time)
return timeId // 返回timeId
}
let id2 = mySetInterval(()=>{
console.log('一秒一个亿');
},1000)
// console.log(id) //Number数字
const myClearInterval = (id) => {
clearTimeout(timeMap[id]); // 通过timeMap[id]获取真正的id
delete timeMap[id];
}
setTimeout(()=>{
myClearInterval(id2);
},3000)
小知识:
1、setTimeout回调函数里的this指向window,如果想用外层的this,有两种方式:
- 回调函数用箭头函数,这样this就指向外层
- 在外部设置变量的方式
2、js定时器输出时间不精确问题
setInterval(()=>{
console.log('data:' + new Date());
},1000)
//因为js是单线程的,要执行代码,需要先放入任务队列等待执行
setTimeout() 的第二个参数告诉 JavaScript 再过多长时间把当前任务添加到队列中。
如果队列是空的,那么添加的代码会立即执行;
如果队列不是空的,那么它就要等前面的代码执行完了以后再执行
function test(){
this.name = 'hzy';
setTimeout(function (){
console.log(this) //window
},1000)
}
// 利用箭头函数
function test(){
this.name = 'hzy';
setTimeout(()=>{
console.log(this) //test { name: 'hzy' }
},1000)
}
// 设置变量
function test(){
this.name = 'hzy';
let self = this;
setTimeout(function (){
console.log(self) //test { name: 'hzy' }
},1000)
}
new test()
22、函数防抖(debounce)
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
- 使用echarts改变浏览器宽度时,希望重新渲染(防抖可以替代resize函数)
- 典型案例:输入搜索时,因为通过输入的内容向后端请求数据是异步的,不做处理会导致bug
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>防抖</title>
</head>
<body>
<button id="debounce">点我防抖!</button>
<script>
window.onload = function() {
// 1、获取这个按钮,并绑定事件
var myDebounce = document.getElementById("debounce");
myDebounce.addEventListener("click", debounce(sayDebounce));
}
// 2、防抖功能函数,接受传参
function debounce(fn) {
// 4、创建一个标记用来存放定时器的返回值
let timeout = null;
return function() {//闭包timeout
// 5、每次当用户点击/输入的时候,把前一个定时器清除
clearTimeout(timeout);
// 6、然后创建一个新的 setTimeout,
// 这样就能保证点击按钮后的 interval 间隔内
// 如果用户还点击了的话,就不会执行 fn 函数
timeout = setTimeout(() => {
fn.call(this, arguments);
}, 1000);
};
}
// 3、需要进行防抖的事件处理
function sayDebounce() {
// ... 有些需要防抖的工作,在这里执行
console.log("防抖成功!");
}
</script>
</body>
</html>
23、函数节流(throttle)
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
理解记忆: 函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹
function throttle(fn,delay) {
// 通过闭包保存一个标记
let canRun = true;
return function() {
//在函数开头判断标志是否为 true,不为 true 则中断函数
if(!canRun) {
return;
}
// 将 canRun 设置为 false,防止执行之前再被执行
canRun = false;
setTimeout( () => {
fn.call(this, arguments);
//执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
canRun = true;
}, delay);
};
}
24、利用set实现交、并、差集
// 实现并集、交集、差集
let a =[1,2,2,3,4,2];
let b =[2,3,3,4,5];
let union =new Set([...a, ...b]);
console.log(union);//并集
//集合,map没有filter方法
let intersect =new Set(a.filter(item => new Set(b).has(item)));
console.log(intersect);//交集
//差集=并集-交集
let difference = [...union].filter(item=> !intersect.has(item))
console.log(difference)
25、高级过滤map+filter
// 需求: 年龄大于18的姓名
let arrObj = [{
name: 'aa', age: 13
}, {
name: 'bb', age: 23
}, {
name: 'cc', age: 18
}, {
name: 'dd', age: 11
}, {
name: 'ee', age: 28
}]
let arrObjFilter = arrObj.filter(ele => {
return ele.age > 18;
}).map(ele=>{
return ele.name;
})
console.log(arrObjFilter) // ['bb', 'ee']
26、实现函数组合运算compose
// 用法如下:
function fn1(x) {
return x + 1;
}
function fn2(x) {
return x + 2;
}
function fn3(x) {
return x + 3;
}
function fn4(x) {
return x + 4;
}
const a = compose(fn1,fn2,fn3,fn4);
console.log(a(1));//相当于fn1(fn2(fn3(fn4(1))))
// 1+4+3+2+1=11
function compose(...fns){
if(fns.length===0) {
return function (...args){
return args;
}
}
return fns.reduce((pre,cur)=>{
return function (...args1){
return pre(cur(...args1))
}
})
}
27、url相关操作
匹配参数值
(^|&)这里匹配空开头的或者&开头的字符串,举个例子,如:“ad”,"&ad"都可以([^&]*)这个意思是不能以0个或者多个&开头(&|$)后面是&或者以空结尾的字符串,如"&ad",“ad”。
const url = 'http://www.xxxxxxx.cn/?lx=1&name=JS&from=baidu&name=2#video'
let key = 'name'
const reg = new RegExp("(^|&)"+key+"=([^&]*)(&|$)");
console.log(url.split('?')[1].split('#')[0].match(reg)[2])
存储参数键值
const url = 'http://www.xxxxxxx.cn/?lx=1&name=JS&from=baidu#video'
let params = new Map()
let paramList = url.split('?')[1].split('#')[0].split('&');
paramList.forEach(item => {
let key = item.split('=')[0]
let value = item.split('=')[1];
params.set(key,value);
})
console.log(params)
// Map(3) { 'lx' => '1', 'name' => 'JS', 'from' => 'baidu' }
28、实现range
// 实现效果
const range = new Range(3,8);
// 一次性完成迭代
for (const num of range){
console.log(num);// 3,4,5,6,7
}
//中途退出
for (const num of range){
if(num>5){
break; //自动调用return方法
}
console.log(num);//3,4,5
}
iterator实现range
// 自定义一个仿range的可提前终止的迭代器
class Range{
constructor(start,end) {
this.start = start;
this.end = end;
}
// for..of 默认调用iterator,每次迭代返回一个value
[Symbol.iterator](){
let left = this.start;
let end = this.end;
return {
next(){
if(left<end){
return {done:false,value:left++}
}else{
return {done: true}
}
},
//这里是一个return()方法,不是return关键字
return() {
console.log('提前退出')
return {done:true}
}
}
}
}
generator实现range
function* myRange(start, end) {
while (start < end) {
yield start++;
}
}
const range = myRange(3, 9);
console.log(range[Symbol.iterator]() === range);//true
for (const num of range) {
console.log(num);//3 4 5 6 7 8
}
29、大数据渲染的分片思想
渲染百万条结构简单的大数据时,使用分片思想优化渲染-------递归
//在ul中插入li标签
let ul = document.getElementsByTagName("body")[0];
// 插入十万条数据
let total = 1000;
// 一次插入 20 条
let once = 20;
//每页记录的索引
let index = 0;
function loop(curTotal,curIndex){
if(curTotal<=0){// 所有都渲染完成
return false;
}else if(curTotal<once){// 当剩余待渲染数不足once=20
once = curTotal;
}
for(let i=0;i<once;i++){
let li = document.createElement("li");
li.innerHTML = `第${curIndex+i}条数据`;
ul.appendChild(li);
}
loop(curTotal-once,curIndex+once)
}
loop(total,index)
30、正则表达式相关题
vue模板template变量替换
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
console.log(render(template, data));
// 我是姓名,年龄18,性别undefined
解法一:
function render(template,data){
const reg = /{{(\w+)}}/;
// 判断template还有没有{{}},没有则直接返回渲染完成之后的template
if(reg.test(template)){
// 查找当前模板里第一个模板字符串的字段
const name = template.match(reg)[1];
template = template.replace(reg, data[name]);
//这里必须return,否则第一次渲染完后,直接return template了
return render(template,data);
}
return template;
}
解法二:replace很强大,可以实现多个同时替换
function render(template,data){
const reg = /{{(\w+)}}/g;
return template.replace(reg, function (item){
// console.log(item.slice(2,-2))切割第二个索引和倒数第二个索引之间的字符串
return data[item.slice(2,-2)];
})
}
驼峰
let str = "next-to-meet-you"
console.log(hump(str))
// nextToMeetYou
function hump(str){//驼峰
const reg = /-\w/g;
return str.replace(reg, function (item){
return item.slice(1).toUpperCase()
})
}
常用正则
//日期'2015-12-25'替换成'12/25/2015'
let date = '2015-12-25'
let reg = /(\d{4})-(\d{2})-(\d{2})/g
console.log(date.replace(reg,'$1/$2/$3'))
//电话号码
const reg = /^1[34578]\d{9}$/
//验证邮箱
zyhu_6@stu.xidian.edu
18843186666@163.com
const reg = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/
//省份证号
第一代居民身份证是15位编码
我国二代居民身份证的号码为18位
const reg = /(^\d{15}$)|(^\d{17}[\dxX]$)/
总结
觉得写得好的,对你有帮助的,可以分享给身边人,知识越分享越多,千万不要吝啬呀
后续更新vue底层相关原理讲解,请关注我,整理好,分享给你们,我们一起学前端