前端之手撕代码

前言:博主在最近面试的过程当中,发现自己不仅前端很多东西没学好,就连本科学过的数据结构什么都忘惹,但是其实很多数据结构的算法对于一个研发人员来说都是基础,因此今天就来总结一下面试中比较爱问的几个算法相关和前端ss的问题吧。
题外话:北京忽冷忽热的我都感冒了我的天。

1、前端ss

1.1 数组相关

①数组方法
迭代方法every,some,filter,forEach,map
栈方法和队列方法,pop,push,shift,unshift
排序方法sort
位置方法indexOf,lastIndexOf
操作方法 splice,slice,concat,join
②数组常见题目
数组去重;数组拉平;将类数组转换成数组等。

1.1.1 数组去重

这个题目比较常见,简单方法有set,麻烦一点就是循环去重,还可以先排序后去重,我就随便写两个

function arrRemove(arr){
	return [...new Set(arr)]; 
}
//或者Array.from
function arrRemove(arr){
	var temp=new Set(arr);
	return Array.from(temp); 
}
function arrRemove(arr){
	var newArr=[];
	for(let i=0;i
1.1.2 数组拉平

数组拉平可以用es6的flat,也可以自己用递归实现,也可以用join和split等实现
(1) es6的flat

 [1, [2, [3]]].flat(Infinity);  // [1, 2, 3]

(2) arr.toString().split(’,’)

var arr= [11, [22, 33], [44, 55], 66];
var newArr=arr.toString().split(',');  //["11", "22", "33", "44", "55", "66"]

(3) arr.join().split(’,’)

const arr = [11, [22, 33], [44, 55], 66];
const flatArr = arr.join().split(','); // ["11", "22", "33", "44", "55", "66"]

(4) 递归拉平

function flatArr(arr){
	var newArr=[];
	for(let i=0;i
1.1.3 数组的几种迭代方法,举例说一下区别

es5中有五种。es6的for-of我真的不想写。(别问,问就是不会)

  • every() 对数组中的每一项执行函数,如果每一项都返回true那么该方法返回true
  • some() 对数组中的每一项执行函数,只要有一项返回true,那么数组方法返回true
  • filter() 对数组中的每一项执行函数,把里面返回true的项组成一个新的数组返回
  • map() 对数组中的每一项执行函数,返回处理后的每一项
  • forEach() 对数组中的每一项指向函数,没有返回值,类似于for循环

以上5种方法,都不会改变数组本身。

举栗子说明吧

var arr=[1,2,3,4,5];
//1 every
var everyEx= arr.every(function(item,index,arr){
	return item>2
})
console.log(everyEx);  //false

//2 some
var someEx= arr.some(function(item,index,arr){
	return item>2
})
console.log(someEx);  //true

//3 filter
var filterRes=arr.filter(function(item,index,arr){
	return item>2;
})
console.log(filterRes);  // [3, 4, 5]

//4 map
var mapRes=arr.map(function(item,index,arr){
	return item>2;
})
console.log(mapRes);  //[false, false, true, true, true]

//5 forEach
var res=arr.forEach(function(item,index,arr){
	return item>2;
})
console.log(res);  //undefined

1.1.4 将类数组转换成数组

类数组,比如arguments,拥有length属性,可以使用下标访问,但是没有数组方法
1、新建空数组,遍历类数组,将类数组对象添加到新数组中
2、通过Array.prototype.slice.call(arraylike)将类数组对象传入,相当于把参数全部截取,返回新数组
3、es6中的Array.from(arraylike)

1.2 字符串相关

①字符串方法
大小写转换方法 toUpperCase(),toLowerCase()
合并方法concat
子串方法 substring, slice, substr(前两者是位置,后者是位置加个数)
修剪方法 trim, trimStart, trimEnd
查找方法 indexOf(),lastIndexOf(),includes(), startsWith(), endsWith()
填充方法 padStart(),padEnd(),repeat()
字符串模式匹配方法 split(),search(),match(),replace()
②字符串常见题目
驼峰写法转换;寻找字符串中出现最多的字符及其次数;返回子串在字符串中的出现次数等

1.2.1 驼峰写法

将一个普通的以-连接的写法改写成驼峰写法
之前腾讯的笔试题还遇到过是-@_任意连接的,这个用split分割的时候注意用正则就可以

function Camel(str){
	var arr=str.split('-');
	var newStr=arr[0];
	for(let i=1;i

正则

// 下划线转换驼峰	
function toHump(name) {
    return name.replace(/\_(\w)/g, function(all, letter){
        return letter.toUpperCase();
    });
}
// 驼峰转换下划线
function toLine(name) {
  return name.replace(/([A-Z])/g,"_$1").toLowerCase();
}
1.2.2 寻找字符串中出现最多的字符及其次数
//寻找字母出现最多的字母
	function countCh(s){
		var obj={};
		var len=s.length;
		for(let i=0;imax){
           	   max=obj[props];
               maxCh=props;
           }
		}
		console.log(max+'--'+maxCh);
	}
	var s="hjqwertyuuuiiiooooouuu";
	countCh(s);
1.2.3 子串在字符串中的出现次数

注意前后两种方法匹配模式上的区别

function countStr(str,sub){
	var reg=new RegExp(sub,'g');
	return str.match(reg).length;
}
var str="abcabcabca";
var sub="abca";
countStr(str,sub); //2 

function countStr2(str,sub){
	var count=0;
	var reg=new RegExp(sub);
	var len=str.length-sub.length;
	var newStr=str.slice(0, sub.length);
	for(var i=0;i

1.3 函数相关

① this的指向问题,闭包
② 箭头函数
③ call,apply,bind

1.3.1 如何实现一个bind函数
Function.prototype.mybind = function () {
	var self = this; // 保存原函数
	context = [].shift.call(arguments), // 保存需要绑定的this上下文
	outArgs = [].slice.call(arguments); // 剩余的参数转为数组
	return function () { // 返回一个新函数
	    let inArgs=[].slice.call(arguments);
		return self.apply(context,outArgs.concat(inArgs));
	}
}

简单版(默认绑定的时候只传入对象,调用的时候在传参数,实际上可以在绑定的时候传参)

Function.prototype.mybind=function(context){
	var self=this;
	return function(){
		return self.apply(context,arguments);
	}
}

验证

var a={color:'red'}
var color="blue";
function say(){console.log(this.color)};
say(); //bule
var asay=say.mybind(a);
asay();   //red
function add(a,b){
	return a+b+this.c
}
var c=100;
var obj={c:1};
add(1,2);  //103;
var bindAdd=add.mybind(obj);
bindAdd(1,2);  //4
1.3.2 闭包问题的解决

1.4 对象相关

① 对象属性器特性
② 创建对象的方式:工厂模式,构造函数,原型模式,组合模式等;es6的class
③ 对象的继承:原型链继承,构造函数继承,组合继承等;es6的extends和super
④ 常见题目:
对象的深浅拷贝;如何实现 a===1&&a===2为true;

1.4.1 关于类、继承和原型链

这个问题比较宏大,重新再单独写一篇博客。指路:
(https://mp.csdn.net/mdeditor/100175016)[https://mp.csdn.net/mdeditor/100175016#]

1.4.2 如何实现一个私有成员变量

如何实现一个私有变量这个问题在红宝书和阮一峰老师的es6中也有提及对私有变量的提案
(1)首先es6 class中的私有属性提案:
目前,有一个提案,为class加了私有属性。方法是在属性名之前,使用#表示。

class IncreasingCounter {
  #count = 0;
  get value() {
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}

上面代码中,#count就是私有属性,只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。

const counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错

(2)es5利用任何在函数中定义的变量都可以认为是私有变量,因为不能在函数外部进行访问。
利用私有变量

function Person(){
	var name='nike';
	this.getName=function(){
		return name;
	}
}
var p=new Person();
console.log(p.getName()); //nike
console.log(p.name); //undefined

利用特权方法

function Person(name){
	this.getName=function(){
		return name;
	}
}
var p=new Person("nike");
console.log(p.getName()); //nike
console.log(p.name); //undefined
1.4.3 如何实现 a===1&&a===2为true

指路
https://blog.csdn.net/weixin_36215102/article/details/100146240

1.4.4 深拷贝和浅拷贝

https://blog.csdn.net/weixin_36215102/article/details/100146240

1.14.5 手动实现instanceof

function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 
    var O = R.prototype;   // 取 R 的显示原型 
    L = L.__proto__;  // 取 L 的隐式原型
    while (true) {    
        if (L === null)      
             return false;   
        if (O === L)  // 当 O 显式原型 严格等于  L隐式原型 时,返回true
             return true;   
        L = L.__proto__;  
    }
}

1.5 事件及其他

事件流,事件委托,定时器事件,事件循环等等

1.5.1 用超时调用实现间歇调用

首先来看setInterval的缺陷,使用setInterval()创建的定时器确保了定时器代码规则地插入队列中。这个问题在于:如果定时器代码在代码再次添加到队列之前还没完成执行,结果就会导致定时器代码连续运行好几次。而之间没有间隔。不过幸运的是:javascript引擎足够聪明,能够避免这个问题。当且仅当没有该定时器的如何代码实例时,才会将定时器代码添加到队列中。这确保了定时器代码加入队列中最小的时间间隔为指定时间。
这种重复定时器的规则有两个问题:1.某些间隔会被跳过 2.多个定时器的代码执行时间可能会比预期小

假定题目为实现一个第一秒打印1 ,第二秒打印2,…第五秒打印5

//超时调用封装成函数
function printNum(){
	num++;
	console.log(num);
	if(num
//超时调用
for(let i=0;i<5;i++){
		setTimeout(function(){
			console.log(i);
		}, 1000*i)
	}
//间歇调用
let num=0;
let max=5;
var timer=setTimeout(function(){
	console.log(++num);
	if(num==max){
		clearInterval(timer);
		console.log("打印结束");
	}
},1000)
1.5.2 js事件委托

事件委托原理我就不赘述了,举个栗子叭
html代码

hahahahaha

js代码

    window.onload=function(){
		var box=document.getElementById("box");
		box.addEventListener('click', function(event){
			switch(event.target.id){
				case 'box':
					console.log("点击了box");
					break;
				case 'bbox':
					console.log("点击了bbox");
					break;
				case 'bbbox':
					console.log("点击了bbbox");
					break;
				default:
				}
			}, false)
		}

1.6 es6 promise等

1.6.1 如何实现一个promise

参照我的这篇博客吧https://blog.csdn.net/weixin_36215102/article/details/99981305
wo真的写不动了。

1.6.2 将ajax封装成promise

//我写一个简单版的就行了,指路一个复杂版https://www.cnblogs.com/Abner5/p/5839214.html

	function ajaxpro(path){
		return new Promise(resolve,reject){
			var xhr=open('get',path);
			xhr.send();
			xhr.onreadystatechange=function(){
				if(xhr.state==4){
					if(xhr.readyState==200){
						resolve(xhr.responseText)
					}
				}else{
					reject("出错了"+xhr.status);
				}
			}
		}
	}
1.6.3 如何解决地狱回调

几种方法,promise、Generator、async/await
以异步读取文件为例,比如要求依次读取3个文件

//回调函数实现, 层层嵌套产生回调地狱
const fs=require('fs');
fs.readFile("./1.txt",'UTF-8',(err,dataStr)=>{
	console.log(dataStr);
	fs.readFile('./2.txt','UTF-8',(err,dataStr)=>{
		console.log(dataStr);
		fs.readFile('./3.txt','UTF-8',(err,dataStr)=>{
			console.log(dataStr)
		})
	})
})

(1)promise改善,缺点就是全是.then的链式调用

const fs=require('fs');
function readFiles(path){
	return new Promise((resolve,reject)=>{
		fs.readFile(path,'UTF-8',(err,dataStr)=>{
			if(err) return reject(err);
			resolve(dataStr);
		})
	})
}

readFiles('./1.txt').then((data)=>{
	console.log(data);
	return readFile('files/2.txt');
},(err)=>{
	console.log(err);
}).then((data)=>{
	console.log(data);
	return readFile('files/3.txt');
},(err)=>{
	console.log(err);
}).then((data)=>{
	console.log(data);
},(err)=>{
	console.log(err);
}).catch(function(err){
// catch的作用,如果前面有任何的promise执行失败,则立即终止所有promise的执行,捕获异常
	console.log(err.message+'失败了')
})

(2)Generator,Generator函数的缺点就是要手动书写run函数执行器,不然需要引入co模块使其自动执行

const fs=require('fs');
//封装读取模块
function readFile(path){
   return new Promise((resolve,reject)=>{
   	fs.readFile(path,'UTF-8',(err,data)=>{
   		if(err) reject(err);
   		resolve(data);
   	})
   })
}
//gen函数
function* gen(){
   try{
   	var f1=yield readFile('./files/1.txt');
   	console.log(f1.toString());
   	var f2=yield readFile('./files/2.txt');
   	console.log(f2.toString());
   	var f3=yield readFile('./files/3.txt');
   	 console.log(f3.toString());
   }catch(e){
   	//handle err
   	console.log("出错啦");
   }
}
//手动书写自动执行器
function run(gen){
   var g=gen();
   function next(data){
   	var result=g.next(data);
   	if(result.done) return result.value;
   	result.value.then(function(data){
   		next(data);
   	})
   }
   next();
}
run(gen);

(3)async/await

const fs=require('fs');
//封装读取模块
function readFile(path){
   return new Promise((resolve,reject)=>{
   	fs.readFile(path,'UTF-8',(err,data)=>{
   		if(err) reject(err);
   		resolve(data);
   	})
   })
}

async function asRead(){
   try{
   	var f1=await readFile('./files/1.txt');
   	console.log(f1.toString());
   	var f2=await readFile('./files/2.txt');
   	console.log(f2.toString());
   	var f3=await readFile('./files/3.txt');
   	console.log(f3.toString());
   }catch(e){
   	console.log('error');
   }
}
asRead(); 

1.7 实现图片懒加载

利用滚动事件什么的,这个我就不写了,晓得原理就行叭。

1.8 节流与防抖

老规矩,不咋会的就指路
https://www.cnblogs.com/coco1s/p/5499469.html

2、基础算法

2.1 快速排序

快速排序的思想:数组中指定一个元素作为标尺,比它大的元素放到后面,比它小的放在前面,如此重复直到全部正序序列.
说白了就是给基准数据找其正确索引位置的过程.
快排分为三步:
(1)选基准:在数组中选一个元素作为基准
(2)划分区:参照基准元素值的大小,划分无序区,所有小于基准元素的数据放入一个区间,所有大于基准元素的数据放入另一区间,分区操作结束后,基准元素所处的位置就是最终排序后它应该所处的位置
(3)递归:对初次划分出来的两个无序区间,递归调用第 1步和第 2步的算法,直到所有无序区间都只剩下一个元素为止。

详细说明
写一个简单的递归js版的,不考虑空间的消耗。(不然复杂的我真的记不住啊)

    function quickSort(arr){
        if(arr.length<1){
            return arr;
        }
        var pivotIndex=Math.floor(arr.length/2);//找到那个基准数
        var pivot=arr.splice(pivotIndex,1)[0]; //取出基准数,并去除,splice返回值为数组。
        var left=[]; 
        var right=[];
        for(var i=0;i

复杂一点的我贴上来吧我太难了啊

function quicksort(arr,low,high){
	var key=arr[low];//选中基准
	var start=low;
	var end=high;
	while(start=key)
			end--;
		if(arr[end]key){
			var temp=arr[start];
			arr[start]=arr[end];
			arr[end]=temp;
		}
	}
	if(start>low) quicksort(arr,low,start-1);
	if(end

2.2 其他排序

其他常见的排序指路
https://blog.csdn.net/weixin_36215102/article/details/100587463

2.3 链表翻转

public static ListNode reverseList(ListNode head) {
    if (head==null){
        return null;
    }

    // 创建一个临时结点,当作头插法的逻辑头结点
    ListNode root=new ListNode();
    root.next=null;

    //要处理的下一个节点
    ListNode temp=null;

    while(head!=null){

        //先记录要处理的下一个节点
        temp=head.next;

        //再把当前节点头插到root节点之后
         head.next=root.next;
         root.next=head;

         //再将head变为当前要处理的节点
         head=temp;
    }
    // 逻辑头结点的下一个结点就是返回后的头结点
    return root.next;
}

js版:从尾部到头部打印每个节点的值

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function printNode(head){
  var res=[];
  var pNode=head;
  while(pNode!=null){
    res.unshift(pNode.val);
    pNode=pNode.next;
  }
  return res;
}

2.4 打印链表倒数第k个节点

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function FindKthToTail(head, k)
{
    // pNode2为快指针,PNode1为慢指针
    if(head===null||k<=0) return null;
    let pNode1=head;
    let pNode2=head;
    while(--k){
        if(pNode2.next!=null){
            pNode2=pNode2.next;
        }else{
            return null;
        }
    }
    while(pNode2.next!=null){
        pNode1=pNode1.next;
        pNode2=pNode2.next;
    }
    return pNode1;
}

2.5 两个链表的公共节点

2.6 用两个栈来实现队列

进去都是一样的,出来不一样。

const outStack = [],
  inStack = [];
  
function push(node) {
  // write code here
  inStack.push(node);
}
function pop(){
  if(!outStack.length){
      while(inStack.length){
         outStack.push(inStack.pop())
      }
  }
  return outStack,pop();
}

2.7 斐波那契相关(跳台阶、矩形覆盖)

2.8 遍历二叉树

这篇文章写得比较全
jianshu.com/p/5e9ea25a1aae
有三种遍历算法,1:中序遍历,2:先序遍历,3:后序遍历。

//先序遍历
function DLR(tree){
    console.log(tree.value);
    if(tree.left){
        DLR(tree.left);
    }
    if(tree.right){
        DLR(tree.right);
    }
//中序遍历

function LDR(tree){
    if(tree.left){
        LDR(tree.left);
    }
    console.log(tree.value);
    if(tree.right){
        LDR(tree.right);
    }
//后序遍历
function LRD(tree){
    if(tree.left){
        LRD(tree.left);
    }
    if(tree.right){
        LRD(tree.right);
    }
    console.log(tree.value);
}

2.9 二叉树的深度

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if (root === null) { //注意等号
        return 0;
    } else {
        var leftDepth = maxDepth(root.left),
            rightDepth = maxDepth(root.right);

        var childDepth = leftDepth > rightDepth ? leftDepth : rightDepth;

        return childDepth + 1;//根节点不为空高度至少为1
    }
};

2.10 镜像二叉树

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
function Mirror(root)
{
    // write code here
    if(root === null)
        return null
    var t = root.left;
    root.left = root.right;
    root.right = t;
    if(root.left){
        Mirror(root.left);
    }
    if(root.right){
        Mirror(root.right);
    }
}

//简单一些的代码
/* function TreeNode(x) {
 this.val = x;
 this.left = null;
 this.right = null;
 } */
function Mirror(root) {
  if (root === null) return;
  Mirror(root.left);
  Mirror(root.right);
  [root.left, root.right] = [root.right, root.left];
  return root;
}

你可能感兴趣的:(前端之手撕代码)