前端面试-手写代码部分

333 阅读14分钟

1、 bind/call/apply

1、call

func.call(thisValue, arg1, arg2, ...)

call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。

Function.prototype.myCall = function (context) {
  var context = context || window
  // 给 context 添加一个属性
  // getValue.call(a, 'yck', '24') => a.fn = getValue
  context.fn = this
  // 将 context 后面的参数取出来
  var args = [...arguments].slice(1)
  // getValue.call(a, 'yck', '24') => a.fn('yck', '24')
  var result = context.fn(...args)
  // 删除 fn
  delete context.fn
  return result
}

2、apply

func.apply(thisValue, [arg1, arg2, ...])

它接收一个数组作为函数执行时的参数

由于apply()方法(或者call()方法)不仅绑定函数执行时所在的对象,还会立即执行函数,因此不得不把绑定语句写在一个函数体内

Function.prototype.myApply = function (context) {
  var context = context || window
  context.fn = this

  var result
  // 需要判断是否存储第二个参数
  // 如果存在,就将第二个参数展开
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }

  delete context.fn
  return result
}

3、bind() bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数

实现思路:判断调用对象 是否为函数,即使我们是定义在函数原型上的,但是可能出现使用call等方式调用情况,

Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var _this = this
  var args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

2、class与原形链实现继承

1.原型链继承:将父类的实例作为子类的原型。

2.构造函数继承(使用call):使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类。

3.实例继承:为父类实例添加新特性,作为子类实例返回。

4.拷贝继承:把父类实例对象上的方法拷贝到子类的原型上。

5.组合即成:通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。

6.寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点。

7.使用extends关键字扩展一个类并继承它的行为(es6)。

1.extends关键字扩展一个类并继承它的行为(es6)

class Animal {
  constructor(name,food){
    this.name = name;
    this.food = food;
    this.sleep=()=>console.log(this.name+'正在睡觉。。。。');
  }
  eat(){
    console.log(this.name+'正在吃'+this.food);
    return (this.name+'正在吃'+this.food);
  }
}

class Reptile extends Animal{
  constructor(name,food,habit){
    super(name,food);
    this.habit=habit;
  }
  printHabit(){
    console.log('习惯'+this.habit);
  }
}

// 测试代码
let cat = new Reptile('cat','fish','sleep');
console.log('catttt',cat.sleep())
cat.printHabit();

2.原型链方式

特点:

1.纯粹的继承关系,实例是子类的实例,也是父类的实例 2.父类新增方法属性,子类都能访问到 3.简单易于实现 缺点:

1.可以在Cat构造函数中,为Cat实例增加实例属性。如果要新增原型属性和方法,则必须放在Cat.prototype = new Animal();这样的语句之后执行。 2.无法实现多继承 3.来自原型对象的引用属性是所有实例共享的 4.创建子类的实例时,无法向父类构造函数传参

// 2. 原型链继承

// 定义一个动物类
function Animal(name){
  // 定义属性
  this.name=name||'Animal';
  // 实例方法
  this.sleep=function(){
    console.log(this.name+'正在睡觉。。。。')
  }
}
// 原型方法
Animal.prototype.eat = function(food){
  console.log(this.name+'正在吃'+food);
}

// 将父类的实例作为子类的原型
function Cat(){

}
Cat.prototype=new Animal();
Cat.prototype.name='dog';
let cat=new Cat();
console.log(cat.name);
cat.eat('骨头');
cat.sleep();
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat);// true

3.构造函数继承(使用call) 1.解决了2中,子类实例共享父类引用属性的问题 2.创建子类实例时,可以向父类传递参数 3.可以实现多继承(call多个父类对象) 缺点:

1.实例并不是父类的实例,只是子类的实例 2.只能继承父类的实例属性和方法,不能继承原型属性/方法 3.无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

// 方法3:使用构造函数的方式继承(call)
function Cat2(){
  Animal.call(this);
  this.name=name||'狗子';
}
let cat2  = new Cat2();
console.log('cat',cat2.name)//cat 狗子
cat2.sleep(); // 狗子正在睡觉。。。。
console.log(cat2 instanceof Animal);// false
console.log(cat2 instanceof Cat2)//true

4.实例继承

特点:

1.不限制调用方式,不管是 new 子类() 还是 子类(),返回的对象具有相同的效果 缺点:

1.实例是父类的实例,不是子类的实例 2.不支持多继承

function Cat3(name){
 let instance= new Animal();
 instance.name=name||'汪汪';
 return instance;
}
let dog=new Cat3('花花');
console.log(dog.name)//花花
dog.sleep();//花花正在睡觉。。。。
dog.eat('肉')//花花正在吃肉
console.log(dog instanceof Animal); //true
console.log(dog instanceof Cat3)//false

5.拷贝继承

特点:

支持多继承

缺点:

1.效率较低,占用内存高 2.无法获取父类不可枚举的方法(不可枚举方法,不能使用for-in访问到)

function Cat(name){
  let animal = new Animal();
  for (var p in animal){
    Cat.prototype[p]=animal[p];
  }
  Cat.prototype.name=name||'tom';

}
let dog=new Cat('花花');
console.log(dog.name)//花花
dog.sleep();//花花正在睡觉。。。。
dog.eat('肉')//花花正在吃肉
console.log(dog instanceof Animal);//false
console.log(dog instanceof Cat)//true

6.组合继承

特点:

1.弥补了方式二的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法 2.即是子类的实例,也是父类的实例 3.不存在引用属性共享问题 4.可传参 5.函数可复用 缺点:

1.调用了两次父构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽掉了),消耗内存较少

// 组合继承
function Cat(name){
  Animal.call(this);
  this.name=name||'tom';
}
Cat.prototype=new Animal();
Cat.prototype.constructor =Cat;

var cat = new Cat();
console.log(cat.name);//tom
cat.sleep();//tom正在睡觉。。。。
cat.eat('bread');//tom正在吃bread
console.log(cat instanceof Cat); //true
console.log(cat instanceof Animal); //true

7.寄生组合继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点。

function Cat(name){
  Animal.call(this);
  this.name =name||'tom';
}
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){

  }
  Super.prototype = Animal.prototype;
  // 将实例作为子类的原型
  Cat.prototype = new Super();
})();

Cat.prototype.constructor=Cat;

var cat = new Cat();
console.log(cat.name);
cat.sleep();
cat.eat('bread');
console.log(cat instanceof Cat); //true
console.log(cat instanceof Animal); //true

3、promise的简单版

    const PENDING = 1;
    const FULFILLED = 2;
    const REJECTING = 3;
 
    class JPromise{
        constructor(excutor){
            const me = this;
            me.status = PENDING;
            me.value = null;
            me.error = null;
            me.resolveCallback = [];
            //以数组存储多个then的回调函数
            me.rejectCallback = [];
            //以数组存储多个then的回调函数
 
            function resolve(val){
                if(me.status === PENDING){//检查状态,不可逆操作
                    me.status = FULFILLED;
                    me.value = val;
                    me.resolveCallback.forEach(func => func(val));
                    //状态改变为fulfilled并执行resolve
                }
            }
 
            function reject(val){
                if(me.status === PENDING){//检查状态,不可逆操作
                    me.status = REJECTING;
                    me.error = val;
                    me.rejectCallback.forEach(func => func(val));
                    //状态改变为rejecting并执行reject
                }
            }
 
            try {
                excutor(resolve,reject);
            } catch (error) {
                reject(error);
            }
        }
 
        then(onResolve,onReject){
            const me = this;
            onResolve = typeof onResolve === 'function'
                ? onResolve
                : v => v;
            onReject = typeof onReject === 'function'
                ? onReject
                : e => {
                    throw e;
                }
            if(me.status === PENDING){//状态未改变,存储回调不执行
                me.resolveCallback.push(onResolve);
                me.rejectCallback.push(onReject);
            }else if(me.status === FULFILLED){
                setTimeout(() => {//保证then是异步执行的
                    try {
                        onResolve(me.value);
                    } catch (error) {
                        onReject(error);
                    }
                });
            }else if(me.status === REJECTING){
                setTimeout(() => {//保证then是异步执行的
                    try {
                        onReject(me.error);
                    } catch (error) {
                        onReject(error);
                    }
                });
            }
        }
 
    }

执行测试代码:

 new JPromise((resolve,reject) => {
        console.log('before resolve');
        setTimeout(() => {
            resolve('resolved');
        },1000);
        console.log('after resolve');
    }).then(res =>{
        console.log(res);
    });

参考原文blog.csdn.net/jhzhahuaiyu…

4、深拷贝实现

1>浅拷贝:

let obj={a:1,b:{i:2,j:3}};
let obj1={};
// es5实现方式
for(let key in obj){
	if(!obj.hasOwnProperty(key))break;
    obj1[key]=obj[key];
}
// es6实现方式
obj1={
	...obj
};


2.深拷贝

let obj1=JSON.parse(JSON.stringify(obj)); // 弊端?

1.函数 2.日期格式 3.正则 都在json.stringify 出现问题

采用递归?

function deepClone(obj){
	// =>过滤特殊情况
     if(obj===nullreturn null;
     iftypeof obj!=="object"return obj;
     if(obj instanceof RegExp){
     	return new RegExp(obj);
     }
      if(obj instanceof Date){
     	return new Date(obj);
     }

	// =>不直接创建空对象目的,克隆的结果和之前保持相同的所属类
    let newObj = new obj.constructor;
    forlet key in obj){
	if(obj.hasOwnProperty(key)){
       newObj[key]=deepClone(obj[key]);
    }
  
}
return newObj;

}

再深入一点? 循环引用问题的产生原因可能是对象之间相互引用,也可能是对象引用了其自身,而造成死循环的原因则是我们在进行深拷贝时并没有将这种引用情况考虑进去,因此解决问题的关键也就是可以将这些引用存储起来并在发现引用时返回被引用过的对象,从而结束递归的调用。

const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null; 
const deepClone = (target, map = new Map()) => {

  if(map.get(target))
      return target; 
  
  if (isObject(target)) 
  { map.set(target, true); 
  const cloneTarget = Array.isArray(target) ? []: {};
  for (let prop in target) { 
  if (target.hasOwnProperty(prop)) {
      cloneTarget[prop] = deepClone(target[prop],map); 
  }
  } return cloneTarget;
  } else { 
  return target;
  } 
  }


5、节流与防抖函数

防抖:


<!DOCTYPE html>
<html>
<body>
    <p>如果小时小于18:00,显示“美好的一天!”:</p>
    <p id="demo">晚安</p>
    <div>!!!!!</div>
    <div>!!!!!</div>
    <div>!!!!!</div>
    <div>!!!!!</div>
    <div>此处省略多个div!!!!!</div>

    
    <script>
        // 此方法就实现了,停止滚动1s以后,才会打印出滚动条位置
        function debounce(fn, delay) {
            let timer = null; // 借助闭包
            return function () {
                if (timer) {
                    clearInterval(timer);
                }
                timer = setTimeout(fn, delay);
            }
  
        }

        // 监听浏览器滚动事件,返回当前滚动条与顶部距离
        function showTop() {
            var scrollTop = document.body.scrollTop
            || document.documentElement.scrollTop;
            console.log('滚动位置', scrollTop); 
            // 那么问题来了,浏览器执行这个函数的频率太高了,浏览器性能有限,于是引入防抖
        }
        window.onscroll = debounce(showTop, 1000);
    </script>
</body>
</html>

节流:

<!DOCTYPE html>
<html>

<body>

    <p>如果小时小于18:00,显示“美好的一天!”:</p>

    <p id="demo">晚安</p>
    <div>!!!!!</div>
    <div>!!!!!</div>
    <div>!!!!!</div>
    <div>!!!!!</div>
    <div>此处省略多个div!!!!!</div>
    <script>
        // 监听浏览器滚动事件,返回当前滚动条与顶部距离
        function showTop() {
            var scrollTop = document.body.scrollTop 
            || document.documentElement.scrollTop;
            console.log('滚动位置', scrollTop); 
        }
        function throttle(fn, delay) {
            let valid = true;
            return function () {
                if (!valid) {
                    return false;
                }
                valid = false;
                setTimeout(() => {
                    fn();
                    valid = true;
                }, delay);
            }

        }
        window.onscroll = throttle(showTop, 1000);
    </script>
</body>

</html>

6、函数柯里化

const currying = (x) => {
     return (y) => {return x+y}
}
console.log(currying(1)(2)) 
const curry = ( fn, arr = []) => {
    return (...args) => { 

        return ( a => {   //a是一个数组
            if(a.length === fn.length) {
                return fn(...a)
            }else{
                return curry(fn, a)
            }
        })([...arr, ...args])  //这里把arr和args摊开成一个数组赋值给a

    }
}

或者:const curry = ( fn, arr = []) => (...args) => ( a => a.length === 
fn.length? fn(...a) : curry(fn, a))([...arr, ...args])
let curryPlus = curry((a,b,c,d)=>a+b+c+d)

curryPlus(1,2,3)(4) //返回10
curryPlus(1,2)(4)(3) //返回10
curryPlus(1,2)(3,4) //返回10
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

7、new的原理实现

var Vehicle = function (p) {
  this.price = p;
};

var v = new Vehicle(500);

使用new命令时,它后面的函数依次执行下面的步骤

1、创建一个空对象,作为将要返回的对象实例。

2、将这个空对象的原型,指向构造函数的prototype属性。

3、将这个空对象赋值给函数内部的this关键字。

4、开始执行构造函数内部的代码。

wangdoc.com/javascript/…

8、常用算法排序(快排)


 let arr:any=[1,9,3,4,5,6,7];
 const data=this.handle(arr);
 console.log('!!!!',data);
    handle(arr:any){
        console.log('arr',arr)
         
        if(arr.length <= 1) return arr;
        const pivotIndex = Math.floor(arr.length/2);
        console.log('pivotIndex',pivotIndex)
        var pivot=arr.splice(pivotIndex,1)[0];
        var left:any=[];
        var right:any=[];
       for(let i=0;i<arr.length;i++){
           if(arr[i]<pivot){
               left.push(arr[i]);
           }else{
                right.push(arr[i]);
           }
        }
        console.log(left,right)
        return this.handle(left).concat([pivot],this.handle(right));   
    }

9、常见设计模式

工厂模式:我们只要传递参数进去,里面具体的过程我们不用去关心,最后返回一个对象

单例模式:是保证一个类仅有一个实例,并提供一个访问它的全局访问点。其实这有一点像我们vuex当中的实现,也是一个全局的状态管理,并且提供一个接口访问

适配器模式 代理模式 发布-订阅模式 策略模式 迭代器模式

详情参考:juejin.cn/post/690483…

10、实现水平垂直居中

<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
   .bg1{
     width: 200px;
     height: 200px;
     background: blue;
     display: flex;
     justify-content: center;
     align-items: center;

   }
   .center1{
    width: 50px;
    height:50px;
    background:red;
   }

   .bg2{
    width: 200px;
    height: 200px;
    background: blue;
    position: relative;

   }
   .center2{
    width: 50px;
    height:50px;
    background:red;
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto;
   }
   .bg3{
    width: 200px;
    height: 200px;
    background: blue;
    position: relative;

   }
   .center3{
    width: 50px;
    height:50px;
    background:red;
    position: absolute;
    top:50%;
    left: 50%;
    transform: translate(-50%, -50%);
    
   }

   .bg4{
    width: 200px;
    height: 200px;
    background: blue;
    text-align: center;
   }
   .center4{
    width: 50px;
    height:50px;
    background:red;
    display: inline-block;
    vertical-align: middle;
 
    
   }
   .bg4:after {
  content: "";
  display: inline-block;
  height: 100%;
  width: 0;
  vertical-align: middle;
}

.bg5{
    width: 200px;
    height: 200px;
    background: blue;
    position: relative;

   }
   .center5{
    width: 50px;
    height:50px;
    background:red;
    position: absolute;
    top:50%;
    left: 50%;
    margin-top: -25px;
    margin-left: -25px;
    
   }

   .bg6{
    width: 200px;
    height: 200px;
    background: blue;

		position: relative;
    display: table-cell;	
		text-align: center;	
		vertical-align: middle;

   }
   .center6{
    width: 50px;
    height:50px;
    background:red;
    display: inline-block;
    
   }
    </style>
    </head>
<body>

<h2>垂直水平居中 方法1:利用flex的alignItems:center垂直居中,
justifycontent:center水平居中</h2>

<div class="bg1">
  <div class="center1">垂直水平居中</div>
</div>

<h2>方法2:相对定位下,使用绝对定位将上下左右都设置为0,再设置margin:auto即可实现居中</h2>
<div class="bg2">
  <div class="center2">垂直水平居中</div>
</div>

<h2>方法3:相对定位下,使用绝对定位,利用margin偏移外容器的50%,再利用translate平移回补
自身宽高的50%即可</h2>
<div class="bg3">
  <div class="center3">垂直水平居中</div>
</div>

<h2>方法4:利用textAlign:center实现行内元素的水平居中,再利用verticalAlign:middle实现
行内元素的垂直居中,前提是要先加上伪元素并给设置高度为100%</h2>
<div class="bg4">
  <div class="center4">垂直水平居中</div>
</div>

<h2>方法5:</h2>
<div class="bg5">
  <div class="center5">垂直水平居中</div>
</div>
<h2>方法6:只需要设置父元素即可,text-align: center; 并在竖直方向上令内容居中(middle)
</h2>
<div class="bg6">
  <div class="center6">垂直水平居中</div>
</div>
<script>
</script>

</body>
</html>

11、用promise实现一个promise.all

let p1=new Promise(resolve=>resolve('p1'));
let p2=new Promise(resolve=>resolve('p2'));	
let p3=Promise.reject('p3 promise');

      function promiseAll(promises){
       return new Promise ((resolve,reject)=>{
        let resultCount = 0;
        let results = new Array(promises.length);
        for(let i=0;i<promises.length;i++){
         promises[i].then(item=>{
          resultCount++;
          results[i]=item;
          if(resultCount==promises.length){
          return resolve(results);
          }
         },error=>{
          return reject(error)
         })
        } 
       })
      }

	promiseAll([p1,p2,p3]).then(result=>{
	   console.log('111',result)
	}).catch(error=>{
	   console.log(error)
	})

12、编写一个程序,讲数组扁平化处理,去除重复的部分,最终得到一个升序且不重复的数组


let arr = [[1,2,3],[3,4,5],[6,7,8,9,[11,12,[12,13,[14]]],10]];

        let newArr = Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{return a-b});
        
        //Array.from 转换为数组 new Set 数组去重 sort 数组或对象某个属性排序
        console.log("newArr",newArr);
        
        
function flatten(arr) {
    var res = [];
    arr.map(item => {
        if(Array.isArray(item)) {
            res = res.concat(flatten(item));
        } else {
            res.push(item);
        }
    });
    return res;
}

13、解析url生成query数组?

方法一:
const testURL1 = 'http://www.youzan.com?name=coder&age=20';
//{name: coder, age: 20}
function queryString(testURL1){
  let sting1 = testURL1.split("?")[1];
  let obj={};
  
  let a = sting1.split("&")[0].split("=");
  let b = sting1.split("&")[1].split("=");
  obj[a[0]]=a[1];
  obj[b[0]]=b[1];
  return obj;
}

方法二:
const testURL1 = 'http://www.youzan.com?name=coder&age=20';
//{name: coder, age: 20}
function queryString(testURL1){
  let sting1 = testURL1.split("?")[1].split("&");
  let obj={};
  sting1.map((item)=>{
   let key=item.split("=")[0];
   let value = item.split("=")[1];
    obj[key]=value;
  })
  return obj;
}
const sum = queryString(testURL1)
console.log(sum)

14、实现一个发布订阅模式?

//发布订阅模式
class EventEmiter{
  constructor(){
    //维护一个对象
    this._events={
 
    }
  }
  on(eventName,callback){
    if( this._events[eventName]){
      //如果有就放一个新的
      this._events[eventName].push(callback);
    }else{
      //如果没有就创建一个数组
      this._events[eventName]=[callback]
    }
  }
  emit(eventName,...rest){
    if(this._events[eventName]){ //循环一次执行
      this._events[eventName].forEach((item)=>{
        item.apply(this,rest)
      });
    }
  }
  removeListener(eventName,callback){
    if(this._events[eventName]){
      //当前数组和传递过来的callback相等则移除掉
      this._events[eventName]=
        this._events[eventName].filter(item=>item!==callback);
    }
  }
  once(eventName,callback){
    function one(){
      //在one函数运行原来的函数,只有将one清空
      callback.apply(this,arguments);
      //先绑定 执行后再删除
      this.removeListener(eventName,one);
    }
    this.on(eventName,one);
      //此时emit触发会执行此函数,会给这个函数传递rest参数
  }
}
class Man extends EventEmiter{}
let man=new Man()
function findGirl() {
  console.log('找新的女朋友')
}
function saveMoney() {
  console.log('省钱')
}
man.once('失恋',findGirl);
//man.on('失恋',findGirl) //失恋 ,绑定一个函数方法
man.on('失恋',saveMoney)//失恋 ,绑定一个函数方法
man.removeListener('失恋',saveMoney); //移除一个函数方法
man.emit('失恋');
//绑定一次,触发多次,也只执行一次。触发后一次将数组中的哪一项删除掉下次触发就不会执行

15、对象的属性名下划线 改成驼峰命名?

var const_obj={
    first_name:"chen",
    obj:{
        last_name:"xi"
    },
    ni_hao:"yaya"
}
const changeTuofeng=(value,char="_")=>{
    var arr=value.split("");
    var index=arr.indexOf(char);
    var newValue=value,flag=false;
    if(index!=-1){
        arr.splice(index, 2, arr[index + 1].toUpperCase());
        newValue= arr.join('');
        flag=true;
    }
    return {
        flag,
        newValue
    };
}
const replaceUnderLine=(obj,char="_")=>{
    var arr=Object.keys(obj);
    arr.forEach((item,key)=>{
        var before=Object.prototype.toString.call(obj[item])==="[object Object]"?replaceUnderLine(obj[item],char):obj[item];
        var key=changeTuofeng(item,char);
        obj[key.newValue]=before;
        if(key.flag){
            delete obj[item];
        }
    })
    return obj;
}
const str=replaceUnderLine(const_obj);
console.log(str)

16、 简单实现一个equal?

科普时间到 :对比 浅比较 与 深比较

浅比较:也称引用相等,在javascript中, ===是作浅比较,只检查左右两边是否是同一个对象的引用 举例子:

{a:1} === {a:1} // false

const m = {a:1};
const n = {a:1};
m === n //false

const m = {a:1};
const n = m;
m === n //true

const m = {a:1};
m === {a:1} //false

深比较:深比较也称原值相等,深比较是指检查两个对象的所有属性是否都相等,深比较需要以递归的方式遍历两个对象的所有属性,操作比较耗时,深比较不管这两个对象是不是同一对象的引用。

deepEqual(a,b) {...} //假设这是深比较的具体实现 
const m = {a:1};
const n = {a:1};
deepEqual(m,n) // true
const x = {a:1, b: {c:1}};
const y = {a:1, b: {c:1}};
deepEqual(x,y) //true
只要两个对象的所有属性都相等,深比较就返回true

下面正题时间到: equal深比较

function deepEqual(x, y) {
    if (x === y) {
        return true;
    }
    if (!(typeof x == "object" && x != null) || !(typeof y == "object" && y != null)){
        return false;
    }
    //比较对象内部
    if (Object.keys(x).length != Object.keys(y).length){
        return false;
    }
    for (var prop in x) {
        if (y.hasOwnProperty(prop))
        {  
            if (!deepEqual(x[prop], y[prop])){
                return false;
            }
        }
        else{
            return false;
        }
    }
    return true;
}

equal浅比较

function shallowEqual(objA,objB){
    if(objA === objB){
        return true;
    }
    if(!(typeof objA === 'object' && objA != null) || !(typeof objB === 'object' && objB != null)){
        return false;
    }
    const keysA = Object.keys(objA);
    const keysB = Object.keys(objB);
    if(keysA.length !== keysB.length){
        return false;
    }
    for(let i = 0;i< keysA.length;i++){
        if(objB.hasOwnProperty(keysA[i])){
            if(objA[keysA[i]] !== objB[keysA[i]]){
                return false;
            }
        }
        else{
            return false;
        }
    }
    return true;
}

17、数组去重复?

方法一:
let arr = [1,2,3,1,2,3,4,3,2];
 if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
let newArr=[];
for(let i = 0; i < arr.length;i++){
if(newArr.indexOf(arr[i])==-1){
  newArr.push(arr[i]);
}
}
console.log(newArr)

方法二:
let arr = [1,2,3,1,2,3,4,3,2];
 if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }

for(let i = 0; i<arr.length;i++){
  for(let j = i+1;j<arr.length;j++){
    if(arr[j]==arr[i]){
      arr.splice(j,1);
      j--
    }
  }
}
console.log(arr)

方法三:
let arr = [1,2,3,1,2,3,4,3,2];
let newArr=[];
let sortArr = arr.sort((a,b)=>{return a-b });
for(let i = 0 ;i<sortArr.length;i++){
  if(sortArr[i]!==sortArr[i+1]){
    newArr.push(sortArr[i])
  }
}
console.log(newArr)

18、数组中出现次数?

var firstUniqChar = function(s) {
  const frequency = _.countBy(s);
  console.log(frequency)
    for (const [i, ch] of Array.from(s).entries()) {
        console.log(i,ch)
        if (frequency[ch] === 1) {
            return ch;
        }
    }
    return ' ';

};

19.将一天24小时按每半小划分成48段?

将一天24小时按每半小划分成48段,我们用一个位图表示选中的时间区间,例如110000000000000000000000000000000000000000000000,表示第一个半小时和第二个半小时被选中了,其余时间段都没有被选中,也就是对应00:00~01:00这个时间区间。一个位图中可能有多个不连续的时间区间被选中,例如110010000000000000000000000000000000000000000000,表示00:00-1:00和02:00-02:30这两个时间区间被选中了。

要求:写一个函数timeBitmapToRanges,将上述规则描述的时间位图转换成一个选中时间区间的数组。 示例输入:"110010000000000000000000000000000000000000000000" 示例输出:["00:0001:00", "02:0002:30"]

function timeBitmapToRanges(timeBitmap) {
	// 转化格式
	function format(start, end) {
	    let endHour = (end / 2).toFixed(1);
	    let startHour = (start / 2).toFixed(1);
	    let reg = /(\d+)\.(\d+)/;
	    const endRes = endHour.match(reg);
	    const startRes = startHour.match(reg);
	    return (
	        addZero(startRes[1]) +
	        ':' +
	        addZero(startRes[2]) +
	        '~' +
	        addZero(endRes[1]) +
	        ':' +
	        addZero(endRes[2])
	    );
	}
	function addZero(num) {
	    num = num === '5' ? '30' : num;
	    return num.length > 1 ? num : '0' + num;
	}
    let timeArr = timeBitmap.split('').map(v => +v);
    const res = [];
    let range = {};
    let start = 0;
    for (let i = 0; i <= timeArr.length; i++) {
        if (timeArr[i]) {
            start++;
        }
        if (!timeArr[i] && timeArr[i - 1]) {
            range[i] = start;
            start = 0;
        }
    }
    for (let j in range) {
        res.push(format(parseInt(j - range[j]), parseInt(j)));
    }
    return res;
}
 

有赞2面



/**
 * ## 问题 1
 * 判断两个普通对象是否相等
 * 
 * 举例:
 * const value1 = { a: 1, b: '1', c: [{a: 1}], d: {a: 1}}
 * const value2 = { a: 1, b: 1, c: [{a: 2}], d: {a: 1, b: 1}}
 * 
 * isEqual(value1, value2) === false;
 * isEqual(value1, {...value1}) === true;
 * isEqual(value1, value1) === true;
 */
function isEqual(value1, value2) {
    if(value1===value2){
        return true;
    }
    if(!(typeof value1 == "object" && value1 !=null )||!(typeof value2 == "object" && value2 !=null )){
        return false;
    }
    if(Object.keys(value1).length!=Object.keys(value2).length){
        return false;
    }
    for(var prop in value1){
        if(value2.hasOwnProperty(prop)){
            if(!isEqual(value1[prop],value2[prop])){
                return false;
            }
        }
        return false;
    }
    return true;
   
}

const value1 = { a: 1, b: 1, c: [{a: 1}], d: {a: 1}}
const value2 = { a: 1, b: 1, c: [{a: 1}], d: {a: 1}}
const value3 = { a: 1, b: 1, c: [{a: 2}], d: {a: 1, b: 1}}
console.log('isEqual',isEqual(value1, value3));


/**
 * ## 问题 2
 * 
 * 实现一个发布订阅系统(支持emit、on、off、once等方法)。
 * 
 * 举例:
 * 
 * const event = new EventEmitter();
 * 
 * event.on('msg', (res)=>{
 *  console.log(res);
 * })
 * 
 * event.emit('msg', 'hello!')
 */

class EventEmitter {
    constructor(){
        this.callbacks={}
    }
    on(name,fn){
        (this.callbacks[name] ||(this.callbacks[name]=[])).push(fn)
    }
    emit(name, ...args){
        let cbs = this.callbacks[name]
        if(cbs){
            cbs.forEach(v=>{
                v.call(this,...args)
            })
        }
    }
    off(name){

    }
}


const event = new EventEmitter();
event.on('msg', (arg1, arg2)=>{
 console.log(arg1, arg2);
 })
 
event.emit('msg', 'hello!','world')



/**
 * ## 问题 3
 * 实现 lodash get 方法:根据 object对象的path路径获取值。 如果解析 value 是 undefined 会以 defaultValue 取代
 * @param {Object} object - 要检索的对象
 * @param {String} path - 要获取属性的路径
 * @param {Any} defaultValue - 如果解析值是 undefined ,这值会被返回
 * 
 * 举例:
 * const object = { 'a': [{ 'b': { 'c': 3 } }] };
 * get(object, 'a[0].b.c') === 3;
 * get(object, ['a', '0', 'b', 'c']) === 3;
 * get(object, 'a.b.c', 'default') === 'default';
 */
function get(object, path, defaultValue) {
   let arr = path.split(".");
   let a = arr[0].split("[")[0];
   let num = arr[0].split("[")[1].split("]")[0];
   let b = arr[1];
   let c = arr[2];


    return object[a][num][b][c];
   console.log("111",object[a][num][b][c])
   
}

const object = { 'a': [{ 'b': { 'c': 3 } }] };
console.log(get(object, 'a[0].b.c') === object.a[0].b.c);
//JSRUN引擎2.0,支持多达30种语言在线运行,全仿真在线交互输入输出。

// 实现JS限流调度器,方法add接收一个返回的Promise函数,同事执行的任务数量不能超过两个

class Scheduler {

constructor() {

this.waitTask = [];

this.nowCount = 0;

this.taskMax = 2;

}

add(f) {

return new Promise((resolve, reject) => {

const task = this.action(f, resolve, reject);

if (this.nowCount < this.taskMax) {

task();

} else {

this.waitTask.push(task);

}

})

}



action(f, resolve, reject) {

return () => {

this.nowCount++;

f()

.then(resolve)

.catch(reject)

.finally(() => {

this.nowCount--;

if (this.waitTask.length > 0) {

const _f = this.waitTask.shift();

_f();

}

});

}

}

}

const scheduler = new Scheduler();

const timeout = (time) => {

return new Promise((r) => setTimeout(r, time));

};

const addTask = (time, order) => {

scheduler

.add(() => timeout(time))

.then(() => {

console.log(order);

});

};



addTask(1000, 1)

addTask(500, 2)

addTask(300, 3)

addTask(400, 4)