前言:博主在最近面试的过程当中,发现自己不仅前端很多东西没学好,就连本科学过的数据结构什么都忘惹,但是其实很多数据结构的算法对于一个研发人员来说都是基础,因此今天就来总结一下面试中比较爱问的几个算法相关和前端ss的问题吧。
题外话:北京忽冷忽热的我都感冒了我的天。
①数组方法
迭代方法every,some,filter,forEach,map
栈方法和队列方法,pop,push,shift,unshift
排序方法sort
位置方法indexOf,lastIndexOf
操作方法 splice,slice,concat,join
②数组常见题目
数组去重;数组拉平;将类数组转换成数组等。
这个题目比较常见,简单方法有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
数组拉平可以用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
es5中有五种。es6的for-of我真的不想写。(别问,问就是不会)
以上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
类数组,比如arguments,拥有length属性,可以使用下标访问,但是没有数组方法
1、新建空数组,遍历类数组,将类数组对象添加到新数组中
2、通过Array.prototype.slice.call(arraylike)将类数组对象传入,相当于把参数全部截取,返回新数组
3、es6中的Array.from(arraylike)
①字符串方法
大小写转换方法 toUpperCase(),toLowerCase()
合并方法concat
子串方法 substring, slice, substr(前两者是位置,后者是位置加个数)
修剪方法 trim, trimStart, trimEnd
查找方法 indexOf(),lastIndexOf(),includes(), startsWith(), endsWith()
填充方法 padStart(),padEnd(),repeat()
字符串模式匹配方法 split(),search(),match(),replace()
②字符串常见题目
驼峰写法转换;寻找字符串中出现最多的字符及其次数;返回子串在字符串中的出现次数等
将一个普通的以-连接的写法改写成驼峰写法
之前腾讯的笔试题还遇到过是-@_任意连接的,这个用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();
}
//寻找字母出现最多的字母
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);
注意前后两种方法匹配模式上的区别
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
① this的指向问题,闭包
② 箭头函数
③ call,apply,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
① 对象属性器特性
② 创建对象的方式:工厂模式,构造函数,原型模式,组合模式等;es6的class
③ 对象的继承:原型链继承,构造函数继承,组合继承等;es6的extends和super
④ 常见题目:
对象的深浅拷贝;如何实现 a===1&&a===2为true;
这个问题比较宏大,重新再单独写一篇博客。指路:
(https://mp.csdn.net/mdeditor/100175016)[https://mp.csdn.net/mdeditor/100175016#]
如何实现一个私有变量这个问题在红宝书和阮一峰老师的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
指路
https://blog.csdn.net/weixin_36215102/article/details/100146240
https://blog.csdn.net/weixin_36215102/article/details/100146240
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__;
}
}
事件流,事件委托,定时器事件,事件循环等等
首先来看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)
事件委托原理我就不赘述了,举个栗子叭
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)
}
参照我的这篇博客吧https://blog.csdn.net/weixin_36215102/article/details/99981305
wo真的写不动了。
//我写一个简单版的就行了,指路一个复杂版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);
}
}
}
}
几种方法,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();
利用滚动事件什么的,这个我就不写了,晓得原理就行叭。
老规矩,不咋会的就指路
https://www.cnblogs.com/coco1s/p/5499469.html
快速排序的思想:数组中指定一个元素作为标尺,比它大的元素放到后面,比它小的放在前面,如此重复直到全部正序序列.
说白了就是给基准数据找其正确索引位置的过程.
快排分为三步:
(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
其他常见的排序指路
https://blog.csdn.net/weixin_36215102/article/details/100587463
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;
}
/*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;
}
进去都是一样的,出来不一样。
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();
}
略
这篇文章写得比较全
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);
}
/**
* 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
}
};
/* 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;
}