下面整理一下最近这段时间面试过程中遇到的一些手撕的代码题,其中有些题目遇到过不止一次,也都是比较基础的题目,大家可以参考一下,ps:如果你没时间刷完大部分的leetcode题,那么我建议优先刷一下leetcode的 热题 HOT 100,确实很容易被问到,而且笔试里面也会遇到,剑指也可以,但是我没刷过,具体的不是很了解。
- 多维数组降维问题
关于这个问题,在ES6中有一个新的方法arr.flat()方法,可以直接返回降维后的数组,flat的参数为降维的维度,默认为一维,可以为数字,Infinity为任意维,但是这个方法在低版本的浏览器中还不支持,所以一般会让你写一个原生的方法,这里介绍几种方法
//方法一:常规方法,递归遍历,判断是否数组,需要用到额外的全局变量
var result=[]
var flat=function (arr) {
for(let i=0;i<arr.length;i++){
if(Array.isArray(arr[i])){
flat(arr[i])
}else{
result.push(arr[i]);
}
}
}
//上述方法优化
var flat=function (arr) {
var result=[]
for(let i=0;i<arr.length;i++){
if(Array.isArray(arr[i])){
result=result.concat(flat(arr[i]))
}else{
result.push(arr[i]);
}
}
return result;
}
//上述方法的简洁写法,利用箭头函数
let flat = arr => arr.reduce((begin,current)=>{
Array.isArray(current)?begin.push(...flat(current)):begin.push(current);
return begin;
},[])
//方法二:使用字符串分割
var flat=function(arr){
let strarr=arr+"";
let str=strarr.split(",");
return str;
}
- 所有子序列问题
//所有顺序子序列(输出所有有序的子序列)
var subSequence=function (arr){
let res=[];
for(let i=0;i<arr.length;i++){
for(let j=i;j<arr.length;j++){
res.push(arr.slice(i,j+1))
}
}
return res;
}
console.log(subSequence([1,2,3]))
//结果为 1 1,2 1,2,3, 2 2,3 3
//所有数组元素排列组合
// var allSubSequence=function(arr){
// let len=arr.length;
// let mark=new Array(len);
// let result=[];
// var recursion=function(arr,mark,n,i){
// if(n==i){
// let temparr=[];
// for(let k=0;k<i;k++){
// if(mark[k]==1){
// temparr.push(arr[k]);
// }
// }
// result.push(temparr);
// return;
// }
// mark[n]=0;
// recursion(arr,mark,n+1,i)
// mark[n]=1;
// recursion(arr,mark,n+1,i)
// }
// recursion(arr,mark,0,len);
// return result;
// }
// console.log(allSubSequence([1,2,3,4]));
//输出结果为
// [],
// [ 4 ],
// [ 3 ],
// [ 3, 4 ],
// [ 2 ],
// [ 2, 4 ],
// [ 2, 3 ],
// [ 2, 3, 4 ],
// [ 1 ],
// [ 1, 4 ],
// [ 1, 3 ],
// [ 1, 3, 4 ],
// [ 1, 2 ],
// [ 1, 2, 4 ],
// [ 1, 2, 3 ],
// [ 1, 2, 3, 4 ]
//原理:
//原理,使用二进制的排列组合来实现
//将a,b,c,d 分别以二进制占位符表示,0即表示不存在,1表示不存在,mark数组即用来存储这个占位符
//如0000则表示空集,1111表示集合{a,b,c,d},0001表示{d},0110表示{b,c}
//本质就是将0000按二进制加到1111,每加1就输出一次其表示的数
//递归的原理图如下,当只有abc三个数时
// 0
// 0
// 1
// 0
// 0
// 1
// 1
// start
// 0
// 0
// 1
// 1
// 0
// 1
// 1
- 多维数组每一维排列组合
var result=[];
var results=[];
var doExchange=function (arr, depth) {
for (let i = 0; i < arr[depth].length; i++) {
result[depth] = arr[depth][i];
if (depth !== arr.length - 1) {
doExchange(arr, depth + 1)
} else {
results.push(result.join(" ").split(" "))
}
}
}
doExchange([[1,2,3],[4,5],[6,7]],0)
for(let i=0;i<results.length;i++){
for(let j in results[i]){
results[i][j]=parseInt(results[i][j])
}
}
console.log(results)
//结果:
// [ 1, 4, 6 ],
// [ 1, 4, 7 ],
// [ 1, 5, 6 ],
// [ 1, 5, 7 ],
// [ 2, 4, 6 ],
// [ 2, 4, 7 ],
// [ 2, 5, 6 ],
// [ 2, 5, 7 ],
// [ 3, 4, 6 ],
// [ 3, 4, 7 ],
// [ 3, 5, 6 ],
// [ 3, 5, 7 ]
- js实现对象属性的get,set方法
这里其实考察的是函数中this的指向问题,这里用到了前文提到过的一个知识点 在js部分-ES6新增-函数相关扩展方法-普通函数中的this的四种调用模式
//方法一:使用函数的方式实现
function Person(val){
this.name="admin";
this.getName=function(){
return this.name;
}
this.setName=function(val){
this.name=val;
}
}
var per=new Person();
console.log(per.name)//admin
console.log(per.getName())//admin
per.setName(111)
console.log(per.getName())//111
//方法二:使用对象的方式实现(这种方式不能new一个实例)
var Person={
name:"admin",
getName(){
return this.name;
},
setName(val){
this.name=val
}
}
console.log(Person.name)//admin
console.log(Person.getName())//admin
Person.setName("111")
console.log(Person.getName())//111
//方法三:使用原型链的按时实现
function Person(){ }
Person.prototype.name="admin";
Person.prototype.getName=function(){
return this.name;
};
Person.prototype.setName=function(val){
this.name=val;
};
var per=new Person();
console.log(per.name)//admin
console.log(per.getName())//admin
per.setName(111)
console.log(per.getName())//111
- a==1&&a==2返回true,这个问题的原理就是js中的隐式类型转换,涉及到两个函数,js在遇到==的时候会先对数据使用valueOf函数将数据转换为原始类型,然后判断值是否相等,如果不等,会对数据使用toString()方法,然后判断两个字符串是否相等。
toString()会把数据类型转换成string类型,也就是说不管原来是什么类型,转换后一律是string类型
valueOf()会把数据类型转换成原始类型,也就是说原来是什么类型,转换后还是什么类型,日期类型除外
let a={
i:1,
valueOf(){
return a.i++;
}
}
console.log(a==1&&a==2) //true
let b={
i:1,
toString(){
return b.i++;
}
}
console.log(b==1&&b==2) //true
- 每隔1秒输出1,2,3,4...
//方法一:ES6 let 块级作用域
//注意:这里如果使用var的话,会每个一秒输出一个5,输出5个5
for(let i = 1; i <5; i++){
setTimeout(function () {
console.log(i);
},1000*i);
}
//方法二:ES5 闭包 匿名函数and函数自动执行
// (function(i){})(i) 其中第一个()返回一个匿名函数,第二()起到立即执行的作用
for (var i = 1; i <= 10; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 1000 * i);
})(i);
}
- js实现防抖和节流
/*
*fn --> 需要防抖的函数;
*delaytime --> 毫秒数,防抖所需期限值;
*/
//防抖
//原理:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,
//就重新开始延时。
function debounce(fn,delaytime){
let timer = null
return function(){
if(timer){
//进入这里说明当前存在一个执行过程,并且同时又执行了一个相同事件,故取消当前的执行过程
clearTimeout(timer)
}
timer = setTimeout(fn,delaytime)
}
}
function show_scrollPosition(){
var scrollPosition = document.body.scrollTop || document.documentElement.scrollTop;
console.log("当前滚动条位置为:",scrollPosition);
}
window.onscroll = debounce(show_scrollPosition,1000)
//节流
//原理:规定一个期限时间,在该时间内,触发事件的回调函数只能执行一次,如果期限时间内回调函数被多次触发,则只有一次能生效。
function throttle(fn, delay) {
let last_time
let timer = null
return function () {
let cur_time = new Date().getTime()
if (last_time && cur_time < last_time + delay) {
//若为真,则表示上次执行过,且在期限值范围内
clearTimeout(timer)
timer = setTimeout(() => {
fn();
last_time = cur_time
}, delay)
} else {
last_time = cur_time;
fn();
}
}
}
function show_scrollPosition() {
var scrollPosition = document.body.scrollTop || document.documentElement.scrollTop;
console.log("当前滚动条位置为:", scrollPosition);
}
window.onscroll = throttle(show_scrollPosition, 1000)
- js实现add(1)(2)(3)=6
function add(val){
var rs = function(oval){
return add(val + oval);
}
rs.toString = function(){
return val;
}
return rs;
}
console.log(add(1)(2)(3) == 6);