js 经常会被问的 面试题

1,598 阅读7分钟

常用函数

深拷贝与浅拷贝

表头 和原数据是否指向同一对象  第一层数据为基本数据类型    原数据中包含子对象  
赋值  改变会使原数据一同改变    改变会使原数据一同改变  
浅拷贝  改变不会使原数据一同改变    改变会使原数据一同改变  
深拷贝  改变不会使原数据一同改变   改变不会使原数据一同改变  

浅拷贝实现

function shallowClone(source) {
  if (!source || typeof source !== "object") {
    throw new Error("error arguments");
  }
  // 判断是数组还是 对象
  var targetObj = source.constructor === Array ? [] : {};
  for (var keys in source) {
    if (source.hasOwnProperty(keys)) {
      targetObj[keys] = source[keys];
    }
  }
  return targetObj;
}

浅拷贝其他方法

var obj = { a: {a: "hello", b: 21} };
var initalObj = Object.assign({}, obj);

initalObj.a.a = "changed";
console.log(obj.a.a); // "changed"

深拷贝实现

如果觉得深拷贝写的太简单的,可以点击查看lodash 的深拷贝实现,比较详细

function deepClone(source) {
  if (!source || typeof source !== "object") {
    throw new Error("error arguments");
  }
  var targetObj = source.constructor === Array ? [] : {};
  for (var keys in source) {
    if (source.hasOwnProperty(keys)) {
      if (source[keys] && typeof source[keys] === "object") {
        // 赋予 初始值
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
        
      } else {
        targetObj[keys] = source[keys];
      }
    }
  }
  return targetObj;
}

深拷贝其他方法

var obj1 = {a:'a'};
var obj2 = JSON.parse(JSON.stringify(obj1));

深拷贝应用场景

当时要把原数据A保存,然后把新数据B遍历更改指定属性,最后A和B对比,将B中属性和A对比判断提交数据C中push A还是B。当时数据A嵌套有三层,最后就用了深拷贝解决的。

防抖节流

防抖(Debounce): 在指定时间A内,连续调用的时间间隔小于A,前面的调用都会被取消,最后一次调用被执行。

  1. 移动端 上拉刷新
  2. 模糊查询搜索框的 ajax请求

节流(throttle): 在指定时间A内,该函数只会被调用一次。等待时间A过了函数可以被再次调用且执行最后一次调用。

  1. ajax请求的时候,规定时间内设置请求次数,可以减少ajax请求。
  2. resize或者鼠标移动事件,防止浏览器频繁响应事件,严重拉低性能。

防抖(Debounce)

利用延时器就可以结局

function debounce(fn, delay) {
  let timer = null;
  return function() {
    let context = this;
    let args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, delay);
  };
} 

节流(throttle)

// 时间戳形式表现
function throttle(fn,delay){
  let preDate = new Date();
  return function(){
    const args = arguments;
    const context = this;
    if(preDate + delay <=  new Date()){
      fn.apply(context,args);
      preDate = new Date();
    }
  }
}

// setTimeout表现
function throttle(fn, delay) {
  let timer = null;
  return function() {
    const args = arguments;
    const context = this;
    if (timer) retun;
      timer = setTimeout(() => {
        fn.apply(context, args);
        clearTimeout(timer); 
        timer = null;
      }, delay);
  };
}

模拟实现 call、apply、bind

call: 在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

apply: 在使用一个指定的 this 值和一个包含多个参数的数组的前提下调用某个函数或方法。

bind: 接收绑定this的对象

call

Function.prototype.call = function(context,...args) {
    var context = context || window;
    context.fn = this;
    const result = context.fn(...args);
    delete context.fn;
    return result
}

apply

Function.prototype.apply = function(context,argsArray) {
    var context = context || window;
    context.fn = this;
    const result = context.fn(...argsArray);
    delete context.fn;
    return result
}

bind

// 这是我自己根据上面改的版本
Function.prototype.bind = function(context, ...args) {
  context.fn = this;
  const bindFn = (...arg) =>{
    const result  = context.fn(...arg);
    delete context.fn;
    return result
  }
  return bindFn;
};

// 这是网上看的版本
Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var context = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function () {};

    var fbound = function () {
        context.apply(this instanceof context ? this : context, args.concat(Array.prototype.slice.call(arguments)));
    }

    fNOP.prototype = this.prototype;
    fbound.prototype = new fNOP();

    return fbound;

}

算法排序

排序经常用在数据处理上,我觉得挺重要的,经常用,都已经耳熟能详了

去重

indexOf

function sort (arr) {
  let result = [];
  arr.forEach((a)=>{
      if(result.indexOf(a) === -1){
        result.push(a)
      }
  })
  return result;
}

sort排序

const  sort = (arr)  => arr.sort((a,b)=>a-b).reduce((target,current)=>{
  if(target[target.length - 1] !== current) {
    target.push(current)
  }
  return target;
},[])

Set

var unique = a => [...new Set(a)];

Map

var unique = arr => {
     const seen = new Map();
     return arr.filter(a => !seen.has(a) && seen.set(a,1))
}

冒泡排序

依次比较相邻的两个数 小的向前放 大的向后放

function bubbleSort(arr) {
  var max = arr.length - 1;
  for (let j = 0; j < max; j++) {
    for (let i = 0; i < max - j; i++) {
      if (arr[i] > arr[i + 1]) {
        var temp = arr[i];
        arr[i] = arr[i + 1];
        arr[i + 1] = temp;
      }
    }
  }
  return arr
}

快速排序

去中间项center,小的放左边left,大的放右边right。递归left、right,返回left,center,right合并项

function quickSort(arr) {
  if (arr.length < 2) {
    return arr;
  }
  var centerNum = Math.floor(arr.length / 2);
  var center = arr.splice(centerNum, 1);

  var left = [];
  var right = [];
  arr.forEach(a => {
    if (a < center) {
      left.push(a);
    } else {
      right.push(a);
    }
  });
  return quickSort(left).concat(center, quickSort(right));
}

插入排序

将一个元素插入到其它已经有序的牌中的适当位置,因此其他所有元素在插入之前都向右移动一位,为新元素腾出空间

function insertSort(array) {
        for (var i = 1; i < array.length; i++) {
            var key = array[i];
            var j = i - 1;
            while (j >= 0 && array[j] > key) {
                array[j + 1] = array[j];
                j--;
            }
            array[j + 1] = key;
        }
        return array;
}

选择排序

找到数组最小的元素,将它和数组中第一个元素交换位置,接下来,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置,往复如此,直到将整个数组排序。基本点就是不断地选择剩余元素之中的最小者。

function select(array){
    var len=array.length;
    for(var i=0;i<len-1;i++){ 
        var minnum=array[i];
        for(var j=i+1;j<len;j++){ // j=i+1是把与自己比较的情况给省略掉
            if(array[j]<minnum){
              var c;
              c=minnum;
              minnum=array[j];    //交换两个值
              array[j]=c;
            }
        }
        array[i]=minnum;      
    }
    return array;

}

二分查找

给定一个有序的数组,查找某个数是否在数组中 如果有则返回index 否则返回-1

function bsearch(array,target,low = 0,high= array.length-1)
{
  if(low > high) {
    return -1
  }
  var mid = Math.floor((low + high)/2) ;
  var midValue = array[mid]
  if(midValue > target){
    return bsearch2(array,target,low,mid-1)
  }else if(midValue < target){
    return bsearch2(array,target,mid+1,high)
  }else {
    return mid;
  } 
}

数据结构

点击进入在线测试

链表

react fiber 里的经典问题

创建单向链表

//节点应用类型
function Node(data){
    this.data=data;
    this.next=null;
}

//链表引用类型
function List(){
    //哨兵节点
    this.head=new Node();
    this.size=0;
}

List.prototype={
    //在链表尾部添加节点
    add: function(data){
        var current=this.head;
        while(current.next!=null){
            current=current.next;
        }
        current.next=new Node(data);

        this.size++;
    },

    //遍历链表,不对链表元素操作都可以调用此方法
    forEach: function(callback){
        for(var current=this.head.next;current!=null;current=current.next){
            callback(current.data);
        }
    },

    //打印链表中所有元素
    print: function(){
        this.forEach(function(item){
            console.log(item);
        })
    },

    //查找链表元素的位置
    indexOf: function(data){
        var pos=0;
        var current=this.head.next;
        while(current!=null){
            if(current.data===data){
                break;
            }
            current=current.next;
            pos++;
        }
        return pos;
    },

   /**
     * 在位置pos处插入节点值为data
     * 若成功则返回插入的值,若失败则返回null
     */
    insert: function(pos,data){
        if(pos<0 || pos>this.size-1){
            return null;
        }

        //插入位置的上一个节点
        var last=this.head;
        for(var i=0;i<pos;i++){
            last=last.next;
        }
        //保存下一个节点的引用
        var ready=last.next;
        last.next=new Node(data);
        last.next.next=ready;

        this.size++;
        return data;
    },

    /**
     * 删除指定位置的元素
     * 若成功则返回删除的值,若失败则返回null
     */
    removeAt: function(index){
        if(index<0 || index>this.size-1){
            return null;
        }

        var current=this.head.next;
        var last=this.head;
        for(var i=0;i<index;i++){
            last=current;
            current=current.next;
        }
        last.next=current.next;

        this.size--;
        return current.data;
    },

    //删除相应元素
    remove: function(data){
        var current=this.head.next;
        var last=this.head;
        while(current!=null){
            if(current.data===data){
                last.next=current.next;
                //已删除节点
                this.size--;
                break;
            }
            last=current;
            current=current.next;
        }
    }
};


var list=new List();
list.add(1);
list.add(2);
list.add(3);
list.insert(1,2);
console.log(list.indexOf(2));   //2
list.remove(3);
list.removeAt(1);
console.log(list.size);   //2
list.print();   //1 2

二叉树

在node处理文件操作,diff算法,动态路由 里常见

深度优先遍历

function deepTraversal(node) {
	var nodes = [];
	if (node != null) {  
        	nodes.push(node);  
        	var children = node.children;  
        	for (var i = 0; i < children.length; i++)  
           		deepTraversal(children[i]);  
    	}  
	return nodes;
}




广度优先遍历

function wideTraversal(node) {
	var nodes = [];
	var i = 0;
	if (!(node == null)) {
		nodes.push(node);
		wideTraversal(node.nextElementSibling);
		node = nodes[i++];
		wideTraversal(node.firstElementChild);
	}
	return nodes;
}

删除二叉树某一个的子节点

//删除最小值
function delMinNode (root){
    if(!root) {
        return false;
    }
    var current = root;
    if (current.left == null) {
        var rightNode = current.right;
        return rightNode;
    }
    current.left = delMinNode(current.left);
    return current.left;
}
//删除最大值
function delMaxNode (root) {
    if(!root) {
        return false;
    }
    var current = root;
    if(current.right == null) {
        var leftNode = current.left;
        return leftNode;
    }
    current.right = delMaxNode(current.right)
    return current.right;
}

吐槽

其实搞不懂为什么要面试写这些代码,真的很烦 !!!
遇到面试要做笔试题的,通常都溜了 !!!
觉得形式主义很烦人!!!

但是迫于淫威 我还是写了 (ಥ_ಥ) , 流下了底层人民的辛酸泪。

欢迎观众老爷们给我提评论,我可以做补充,如果有提议,此博文会一直继续!!