前端常见手写面试题

499 阅读10分钟

节流

节流:当持续触发事件时,保证一定时间段内只调用一次事件处理函数

时间戳

特点:第一次就执行

function  throttle(fn,wait){
  //记录上一次函数触发的时间
  var  lastTime = 0
  return function(){
      // 记录当前函数触发的时间
    var  nowTime = Date.now();
    if(nowTime - lastTime > wait){
        fn.call(this)
        lastTime = nowTime;    
    }//闭包是为了避免执行的时候每次都初始化lastTime
}
  
}

定时器非立即执行

当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

特点:delay时间后执行

适用场景:

  • 一段时间内只执行最后一次拖拽
  • 一段时间呢浏览器只缩放一次
  • 一点时间内只执行一次动画
  • 滚动和缩放
  • 鼠标不断点击触发,点击事件在规定时间内只触发一次(单位时间内只触发一次)
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
function throttle_1(fn,wait){
  var flag = true;
  return function(){
    if(flag)
      flag = false
      var timer = setTimeout(() => {
        fn.apply(this,arguments)
        flag = true
      },wait)
    }
  }
}

时间戳+非立即执行

//时间戳+定时器版: 实现第一次触发可以立即响应,结束触发后也能有响应 (该版才是最符合实际工作需求)
//该版主体思路还是时间戳版,定时器的作用仅仅是执行最后一次回调
function throttle(fun, delay = 500) {
     let timer = null;
     let previous = 0;
     return function(args) {
             let now = Date.now();
             let remaining = delay - (now - previous); //距离规定时间,还剩多少时间
             let that = this;
             let _args = args;
             clearTimeout(timer);  //清除之前设置的定时器
              if (remaining <= 0) {
                    fun.apply(that, _args);
                    previous = Date.now();
              } else {
                    timer = setTimeout(function(){
                    fun.apply(that, _args)
            }, remaining); //因为上面添加的clearTimeout.实际这个定时器只有最后一次才会执行
              }
      }
}

定时器立即执行

//节流(立即执行)
function throttle_2(fn,wait){
  var flag = true;
  var timer = null;
  return function(){
    if(flag) {
      fn.apply(this,arguments);
      flag = false;
      timer = setTimeout(() => {
        flag = true
      },wait)
    }
  }
}

立即执行+非立即执行

//节流(合并)
function throttle_merge(fn,wait = 500,isImmediate = false){
  var flag = true;
  var timer = null;
  if(isImmediate){
    return function(){
      if(flag) {
        fn.apply(this,arguments);
        flag = false;
        timer = setTimeout(() => {
          flag = true
        },wait)
      }
    }
  }
  return function(){
    if(flag == true){
      flag = false
      var timer = setTimeout(() => {
        fn.apply(this,arguments)
        flag = true
      },wait)
    }
  }
}

防抖

防抖:你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行,真是任性呐!

非立即执行版本

适用场景:

  • 按钮提交只执行最后一次
  • 输入框只执行输入的最后一次
  • 按钮点击:收藏,点赞,心标等
//防抖(非立即执行)
function debounce_1(fn,wait){
  var timerId = null;
  return function(){
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      fn.apply(this,arguments)
    },wait)
  }
}

立即执行

//防抖(立即执行)
function debounce_2(fn,wait){
  var timerId = null;
  var flag = true;
  return function(){
    clearTimeout(timerId);
    if(flag){
      fn.apply(this,arguments);
      flag = false
      }
    timerId = setTimeout(() => { flag = true},wait)
  }
}

两者结合

//防抖(合并版)
function debounce_merge(fn,wait = 500,isImmediate = false){
  var timerId = null;
  var flag = true;
  if(isImmediate){
    return function(){
      clearTimeout(timerId);
      if(flag){
        fn.apply(this,arguments);
        flag = false
        }
      timerId = setTimeout(() => { flag = true},wait)
    }
  }
  return function(){
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      fn.apply(this,arguments)
    },wait)
  }
}

继承

组合继承

function Parent() {
  this.name = 'parent'
  this.arr = [1,2,3]
}

function Child() {
  Parent.call(this);
  this.type = 'child'
}

Child.prototype = new Parent();
  • 子类构造函数中使用Parent.call(this);的方式可以继承写在父类构造函数中this上绑定的各属性和方法; 使用Child.prototype = new Parent()的方式可以继承挂在在父类原型上的各属性和方法
  • 组合继承最大的缺点是会调用两次父构造函数。
  • 特点:方法公用,属性私有

原型式继承

function createObj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

缺点:属性值始终都会共享相应的值,这点跟原型链继承一样

寄生组合式继承

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
    console.log(this.name)
}
function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}
// 关键的三步
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constrctor = Child;
var child1 = new Child('kevin', '18');
console.log(child1);

封装一个原生的继承方法

/**
 * 继承
 * @param Parent
 * @param Child
 */
function extendsClass(Parent, Child) {
  function F() {}
  F.prototype = Parent.prototype
  Child.prototype = new F()
  Child.prototype.constrctor = Child
  return Child
}

class继承

class Parent {
    constructor(name) {
        this.name = name;
    }
}

class Child extends Parent {
    constructor(name, age) {
        super(name); // 调用父类的 constructor(name)
        this.age = age;
    }
}

var child1 = new Child('kevin', '18');

console.log(child1);
  • super 关键字表示父类的构造函数,相当于 ES5 的 Parent.call(this)。
  • 子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。
  • 也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。

两条继承链

Class 作为构造函数的语法糖,同时有 prototype 属性和 proto 属性,因此同时存在两条继承链。

(1)子类的 proto 属性,表示构造函数的继承,总是指向父类。

  • 父类的静态方法,可以被子类继承
class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod(); // 'hello'

(2)子类 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的 prototype 属性。

class继承原型链

原理是使用的寄生组合式继承以及多一条子类直接父类。

es5继承原型链(寄生组合式)

new实现

function objectFactory() {
    debugger;
    var obj = new Object(),
    Constructor = [].shift.call(arguments);//取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数
    obj.__proto__ = Constructor.prototype;//将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
    Constructor.apply(obj, arguments);//使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
    return obj;
};

call实现

Function.prototype.mycall = function(content){
     var context   = content || window;
     content.fn = this;//将函数设为对象的属性
     var  args = [...arguments].slice(1);
     let result = content.fn(...args);//执行该函数
     delete content.fn;//删除该函数
     return  result;
}

拓展运算符

blog.csdn.net/astonishqft…

apply实现

Function.prototype.myapply = function(content){
     var context   = content || window;
     content.fn = this;//将函数设为对象的属性
     var result;
     if(arguments[1]){
        result = content.fn(...arguments[1]);//执行该函数
     }else{
        result = content.fn();
     }
     delete content.fn;//删除该函数
     return  result;
}

bind实现

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

    var aArgs = Array.prototype.slice.call(arguments,1),
        fToBind = this,
        fNOP = function(){},
        fBound = function(){
            return fToBind.apply(this instanceof fNOP ? this:oThis,aArgs.concat(aArgs,Array.prototype.slice.call(arguments)))
        }
        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();//上面两句等于fBound.prototype = Object.create(this.prototype);

        return fBound;
}

柯里化

函数柯里化

//柯里化:1.调用另一个函数 2。为他传入要柯里化的函数和必要参数
//将被返回函数的参数进行排序
function curry1(fn){
     //var args = Array.prototype.slice.call(arguments,1);//返回第二个人参数开始的所有参数
     var args = [...arguments].slice(1)
     console.log(args)
     return function(){
          //var innerArgs = Array.prototype.slice.call(arguments);
          var innerArgs = [...arguments]
          var finalArgs = args.concat(innerArgs);
          return fn.apply(null,finalArgs)
     }
}

函数柯里化绑定this

function  bindd(fn,context){
     var args = Array.prototype.slice.call(arguments,2);//返回第三个参数开始的所有参数
     return function(){
          var innerArgs = Array.prototype.slice.call(arguments);
          var finalArgs = args.concat(innerArgs);
          return fn.apply(context,finalArgs)
     }
}

柯里化相关实现1

function curry(func) {
    // 存储已传入参数
    let _args = []
    // 做一个闭包
    function _curry(...args) {
        // 把参数合并
        _args = _args.concat(args)
        // 如果参数够了就执行
        if (_args.length >= func.length) {
            //ES6指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数
            const result = func(..._args)
            _args = []
            return result;
        }
        // 继续返回此函数 
        else {
            return _curry
        }
    }
    return _curry
}
function add1(...a, b, c) {
    return a + b + c
}
let testAdd = curry(add1)
console.log(testAdd(1)(2)(3))  //6
console.log(testAdd(1, 2)(3,4))  //6

柯里化相关实现2

function add2() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    //debugger;
    //var _args = Array.prototype.slice.call(arguments);
    var _args = [...arguments]
    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var fn = function() {
        _args.push(...arguments);
        return fn;
    };

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



//add2(1)(2)(3)                // 6
var s = add2(1, 2, 3)(4);console.log(s)            // 10
//add2(1)(2)(3)(4)(5)          // 15
//add2(2, 6)(1)                // 9

函数组合compose

/*
**
compose([a, b, c])('参数')
=>
a( b( c('参数') ) )
***
*/
function compose(funcs) {
    debugger;
  var len = funcs.length;
  var index = len - 1;

  for(let i = 0; i < len; i ++) {
    if(typeof funcs[i] !== 'function') {
      throw new TypeError('Expected function');
    }
  }

  return function (...args) {
    let result =  funcs[index](...args) // 第一次
    while(--index >= 0) {
      result = funcs[index](result)
    }
    return result;
  }
}

function a (str) {
  return `a ${str}`
}
function b (str) {
  return `b ${str}`
}
function c (str) {
  return `c ${str}`
}

const abc = compose([a, b, c])
console.log(abc('guhy'))

数组

reduce实现map

Array.prototype._map = function(fn, callbackThis) {
    // 最终返回的新数组
    let res = [];
    // 定义回调函数的执行环境
    // call第一个参数传入null,则 this指向全局对象,同 map的规则
    let CBThis = callbackThis || null;
    this.reduce((brfore, after, idx, arr) => {
        // 传入map回调函数拥有的参数
        // 把每一项的执行结果push进res中
        res.push(fn.call(CBThis, after, idx, arr));
    }, null);
    return res;
};

数组去重

数组去重的方法有很多种,如果要是手写的话,一般我都会写下面这种。也会顺便说一下ES6的set方法。

function removeDup(arr){
    var result = [];
    var hashMap = {};
    for(var i = 0; i < arr.length; i++){
        var temp = arr[i]
        if(!hashMap[temp]){
            hashMap[temp] = true
            result.push(temp)
        }
    }
    return result;
}
Array.from(new Set(arr))
[...new Set(arr)]
        var arr1 = [123, {a: 1}, {a: {b: 1}}, {a: "1"}, {a: {b: 1}}, "meili"];
        var arr3 = [123, "meili", "123", "mogu", 123];
        var arr2 = [123, [1, 2, 3], [1, "2", 3], [1, 2, 3], "meili"];

        function unique(arr) {
            let b=[]
            let hash={}
            for(let i=0;i<arr.length;i++){
                if(!hash[JSON.stringify(arr[i])]){
                    hash[JSON.stringify(arr[i])]=true
                    b.push(arr[i])
                }
            }
            return b    
        }

        console.log(unique(arr1));
        console.log(unique(arr2));
        console.log(unique(arr3))

数组合并

数组展平

数组flat方法是ES6新增的一个特性,可以将多维数组展平为低维数组。如果不传参默认展平一层,传参可以规定展平的层级。

// 展平一级
function flat(arr){
    var result = [];
    for(var i = 0; i < arr.length; i++){
        if(Array.isArray(arr[i])){
            result = result.concat(flat(arr[i]))
        }else{
            result.push(arr[i]);
        }
    }
    return result;
}
//展平多层
 function flattenByDeep(array,deep){
      var result = [];
      for(var i = 0 ; i < array.length; i++){
          if(Array.isArray(array[i]) && deep > 1){
                result = result.concat(flattenByDeep(array[i],deep -1))
          }else{
                result.push(array[i])
          }
      }
      return result;
  }

参考:www.cnblogs.com/wind-lanyan…

function flatten(arr) {  
    return arr.reduce((result, item)=> {
        return result.concat(Array.isArray(item) ? flatten(item) : item);
    }, []);
}
[].concat(...[1, 2, 3, [4, 5]]);  // [1, 2, 3, 4, 5]

数组排序

字符串

字符串翻转

var str = "smile at life";

//翻转字符串
console.log(str.split(" ").reverse().join(" "))//life at smile  join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。
console.log(str.split("").reverse().join(""))//efil ta elims  这种方法是错误的,所以记住要使用上面一种形式

//替换空格

//方法一: 先转成字符数组,再把数组中的所有字符放入一个字符串
console.log(str.split(" ").join("%20"))

//方法二: 

console.log(str.replace(/\s/g,'%20'))


/*
字符串注意点:
split() 方法用于把一个字符串分割成字符串数组。
两个参数,第一个是以什么元素进行分割,第二个是保留的
如果把空字符串 ("") 用作 separator,那么 stringObject 中的每个字符之间都会被分割。
*/
var str1="How are you doing today?"
document.write(str1.split(" ") + "<br />")//How,are,you,doing,today?
document.write(str1.split("") + "<br />")//H,o,w, ,a,r,e, ,y,o,u, ,d,o,i,n,g, ,t,o,d,a,y,?
document.write(str1.split(" ",3))//How,are,you

//join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。


//替换空格

大数相加

//大数相加
function sumStrings(a,b){
  var res='', c=0;
  a = a.split('');
  b = b.split('');
  while (a.length || b.length || c){
     
      c += ~~a.pop() + ~~b.pop();//对于浮点数,~~value可以代替parseInt(value),而且前者效率更高些
      res = c % 10 + res;
      c = c>9;
  }
  return res.replace(/^0+/,'');
 
}
var sunm1 = sumStrings('9007199254740993', '11')
console.log(sunm1)

获取url参数

function GetQueryString(sUrl,name)
{
     var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
     var left= sUrl.indexOf("?") + 1
     var right= sUrl.lastIndexOf("#")
     var parasString = sUrl.slice(left, right)
     //var r = window.location.search.substr(1).match(reg);
     console.log(parasString)
     var r = parasString.match(reg);
     console.log(r)

     if(r!=null)return  unescape(r[2]); return null;
}


var aaa = GetQueryString( 'https://www.nowcoder.com?key=1&key=2&key=3&test=4#hehe ', 'key')
console.log(aaa)

异步

串行ajax

promise实现

const PENDING = 1;
const FULFILLED = 2;
const REJECTED = 3;

function MyPromise(executor) {
    let self = this;
    this.resolveQueue = [];
    this.rejectQueue = [];
    this.state = PENDING;
    this.val = undefined;
    function resolve(val) {
        if (self.state === PENDING) {
            setTimeout(() => {
                self.state = FULFILLED;
                self.val = val;
                self.resolveQueue.forEach(cb => cb(val));
            });
        }
    }
    function reject(err) {
        if (self.state === PENDING) {
            setTimeout(() => {
                self.state = REJECTED;
                self.val = err;
                self.rejectQueue.forEach(cb => cb(err));
            });
        }
    }
    try {
        // 回调是异步执行 函数是同步执行
        executor(resolve, reject);
    } catch(err) {
        reject(err);
    }
}

MyPromise.prototype.then = function(onResolve, onReject) {
    let self = this;
    // 不传值的话默认是一个返回原值的函数
    onResolve = typeof onResolve === 'function' ? onResolve : (v => v); 
    onReject = typeof onReject === 'function' ? onReject : (e => { throw e });
    if (self.state === FULFILLED) {
        return new MyPromise(function(resolve, reject) {
            setTimeout(() => {
                try {
                    let x = onResolve(self.val);
                    if (x instanceof MyPromise) {
                        x.then(resolve);
                    } else {
                        resolve(x);
                    }
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

    if (self.state === REJECTED) {
        return new MyPromise(function(resolve, reject) {
            setTimeout(() => {
                try {
                    let x = onReject(self.val);
                    if (x instanceof MyPromise) {
                        x.then(resolve);
                    } else {
                        resolve(x);
                    }
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
    
    if (self.state === PENDING) {
        return new MyPromise(function(resolve, reject) {
            self.resolveQueue.push((val) => {
                try {
                    let x = onResolve(val);
                    if (x instanceof MyPromise) {
                        x.then(resolve);
                    } else {
                        resolve(x);
                    }
                } catch(e) {
                    reject(e);
                }
            });
            self.rejectQueue.push((val) => {
                try {
                    let x = onReject(val);
                    if (x instanceof MyPromise) {
                        x.then(resolve);
                    } else {
                        resolve(x);
                    }
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
}

MyPromise.prototype.catch = function(onReject) {
    return this.then(null, onReject);
}

MyPromise.all = function(promises) {
    return new MyPromise(function(resolve, reject) {
        let cnt = 0;
        let result = [];
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(res => {
                result[i] = res;
                if (++cnt === promises.length) resolve(result);
            }, err => {
                reject(err);
            })
        }
    });
}

MyPromise.race = function(promises) {
    return new MyPromise(function(resolve, reject) {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve, reject);
        }
    });
}

MyPromise.resolve = function(val) {
    return new MyPromise(function(resolve, reject) {
        resolve(val);
    });
}

MyPromise.reject = function(err) {
    return new MyPromise(function(resolve, reject) {
        reject(err);
    })
}

简单路由实现(hash,history)

倒计时

www.cnblogs.com/xueyubao/p/…

<script>
    window.onload = function() {
        setInterval(function() {
            var nowTime = new Date();//获取当前时间
            //创建目标日期
            var endTime = new Date("2019-9-1 00:00:00");
            var seconds = parseInt((endTime.getTime() - nowTime.getTime()) / 1000);//两个时间点的时间差(秒)
            var d = parseInt(seconds / 3600 / 24);//得到天数
            var h = parseInt(seconds / 3600 % 24);//小时
            var m = parseInt(seconds / 60 % 60);//分钟
            var s = parseInt(seconds % 60);//秒
            document.getElementById("djs").innerHTML = "距离开学还有" + d +"天" + h + "小时" + m + "分钟" + s + "秒";
        }, 1000);
    }
</script>

实现计算器

blog.csdn.net/Huymi/artic…

实现eventEmitter

观察者模式是我们工作中经常能接触到的一种设计模式。用过 jquery 的应该对这种设计模式都不陌生。eventEmitternode 中的核心,主要方法包括on、emit、off、once

class EventEmitter {
    constructor(){
        this.events = {}
    }
    on(name,cb){
        if(!this.events[name]){
            this.events[name] = [cb];
        }else{
            this.events[name].push(cb)
        }
    }
    emit(name,...arg){
        if(this.events[name]){
            this.events[name].forEach(fn => {
                fn.call(this,...arg)
            })
        }
    }
    off(name,cb){
        if(this.events[name]){
            this.events[name] = this.events[name].filter(fn => {
                return fn != cb
            })
        }
    }
    once(name,fn){
        var onlyOnce = () => {
            fn.apply(this,arguments);
            this.off(name,onlyOnce)
        }
        this.on(name,onlyOnce);
        return this;
    }
}

实现jsonp

jsonp 的作用是跨域。原理是通过动态插入script标签来实现跨域,因为script脚本不受同源策略的限制。它由两部分组成:回调函数和数据。举例:

 function handleResponse(response){
    alert("You’re at IP address " + response.ip + ", which is in " +response.city + ", " + response.region_name);    
    }
    var script = document.createElement("script");
    script.src = "http://freegeoip.net/json/?callback=handleResponse";
    document.body.insertBefore(script,document.body.firstChild);    
}

根据上面的例子,下面来实现一个通用的JSONP函数

function jsonp(obj) {
    const {url,data} = obj;
    if (!url) return
    return new Promise((resolve, reject) => {
        const cbFn = `jsonp_${Date.now()}` 
        data.callback = cbFn
        const head = document.querySelector('head')
        const script = document.createElement('script')
        const src = `${url}?${data2Url(data)}`
        console.log('scr',src)
        script.src = src
        head.appendChild(script)
        
        window[cbFn] = function(res) {
            res ? resolve(res) : reject('error')
            head.removeChild(script)
            window[cbFn] = null 
        }
    })
}

function data2Url(data) {
    return Object.keys(data).reduce((acc, cur) => {
        acc.push(`${cur}=${data[cur]}`)
        return acc
    }, []).join('&')

参考:segmentfault.com/a/119000002…

设计模式

发布订阅者模式

    //把发布—订阅的功能提取出来,放在一个单独的对象内
var event = {
    clientList: [],
    listen: function(key, fn) {
        if (!this.clientList[key]) {
            this.clientList[key] = [];
        }
        this.clientList[key].push(fn);   //订阅的信息添加进缓存
    },
    trigger: function() {
        var key = Array.prototype.shift.call(arguments);
        var fns = this.clientList[key];
        if (!fns || fns.length === 0) {   //如果没有绑定对应的消息
            return false;
        }
        for (var i=0,fn;fn=fns[i++];) {
            fn.apply(this, arguments);
        }
    }
}
//再定义一个installEvent函数,这个函数可以给所有的对象都动态安装发布—订阅者功能:
function installEvent(obj) {
    for (var i in event) {
        obj[i] = event[i];
    }
}
//测试,给售楼处对象salesOffices动态添加发布—订阅功能:
var salesOffices = {};
installEvent(salesOffices);

salesOffices.listen('squareMeter88', function(price) {
    console.log('price=' + price);  
});
salesOffices.listen('squareMeter100', function(price) {
   console.log('price=' + price); 
});
salesOffices.trigger('squareMeter88',200000);
salesOffices.trigger('squareMeter100',300000);

树相关

二叉树

层次遍历

//层次遍历
function printFromTopToBottom(root){
  //debugger;
	let res = [];
	let queue = [];
	if(root == null){
		return res
	}
	queue.push(root)
	while(queue.length){
		let node = queue.shift();
		res.push(node.val);
		if(node.left != null){
			queue.push(node.left);
		}
		if(node.right != null){
			queue.push(node.right);
		}
	}
	return res;
} 

先序遍历

//前序
function preOrderTraversal(root){
  // debugger;
	let res = [];
	let stack = [];
	if(root == null){
		return res
	}
	stack.push(root)
	while(stack.length){
		let node = stack.pop();
		res.push(node.val);
		if(node.right){
			stack.push(node.right);
		}
		if(node.left){
			stack.push(node.left);
		}
	}
	return res
}

中序遍历

//中序
function inOrderTraversal(root){
  let cur = root;
	let res = [];
	let stack = [];
	while(cur != null || stack.length){
		if(cur){
			stack.push(cur);
			cur= cur.left;
		}else{
			cur = stack.pop();
			res.push(cur.val);
			cur = cur.right;
		}
	}
	return res
}

后序遍历

//后序
function postOrderTraversal(root){
  //debugger;
	let res = [];
	let stack = [];
	if(root == null){
		return res
	}
	stack.push(root);
	while(stack.length){
		let top = stack.pop();
		res.unshift(top.val);
		if(top.left){
			stack.push(top.left);
		}
		if(top.right){
			stack.push(top.right);
		}
	}
	return res
}

深拷贝与浅拷贝

深拷贝数组

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}
// 木易杨
function cloneDeep2(source) {
    if (!isObject(source)) return source; // 非对象返回自身
    var target = Array.isArray(source) ? [] : {};
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep2(source[key]); // 注意这里
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
// 木易杨
function isObject(obj) {
	return typeof obj === 'object' && obj != null;
}


循环引用

使用哈希表

// 木易杨
function cloneDeep3(source, hash = new WeakMap()) {

    if (!isObject(source)) return source; 
    if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表
      
    var target = Array.isArray(source) ? [] : {};
    hash.set(source, target); // 新增代码,哈希表设值
    
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep3(source[key], hash); // 新增代码,传入哈希表
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

使用数组

// 木易杨
function cloneDeep3(source, uniqueList) {

    if (!isObject(source)) return source; 
    if (!uniqueList) uniqueList = []; // 新增代码,初始化数组
      
    var target = Array.isArray(source) ? [] : {};
    
    // ============= 新增代码
    // 数据已经存在,返回保存的数据
    var uniqueData = find(uniqueList, source);
    if (uniqueData) {
        return uniqueData.target;
    };
        
    // 数据不存在,保存源数据,以及对应的引用
    uniqueList.push({
        source: source,
        target: target
    });
    // =============

    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep3(source[key], uniqueList); // 新增代码,传入数组
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

// 新增方法,用于查找
function find(arr, item) {
    for(var i = 0; i < arr.length; i++) {
        if (arr[i].source === item) {
            return arr[i];
        }
    }
    return null;
}

Symbol

当然可以,不过 SymbolES6 下才有,我们需要一些方法来检测出 Symble 类型。

方法一:Object.getOwnPropertySymbols(...)

方法二:Reflect.ownKeys(...)

Object.getOwnPropertySymbols(...)

思路就是先查找有没有 Symbol 属性,如果查找到则先遍历处理 Symbol 情况,然后再处理正常情况,多出来的逻辑就是下面的新增代码

// 木易杨
function cloneDeep4(source, hash = new WeakMap()) {

    if (!isObject(source)) return source; 
    if (hash.has(source)) return hash.get(source); 
      
    let target = Array.isArray(source) ? [] : {};
    hash.set(source, target);
    
    // ============= 新增代码
    let symKeys = Object.getOwnPropertySymbols(source); // 查找
    if (symKeys.length) { // 查找成功	
        symKeys.forEach(symKey => {
            if (isObject(source[symKey])) {
                target[symKey] = cloneDeep4(source[symKey], hash); 
            } else {
                target[symKey] = source[symKey];
            }    
        });
    }
    // =============
    
    for(let key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep4(source[key], hash); 
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

Reflect.ownKeys(...)

// 木易杨
function cloneDeep4(source, hash = new WeakMap()) {

    if (!isObject(source)) return source; 
    if (hash.has(source)) return hash.get(source); 
      
    let target = Array.isArray(source) ? [] : {};
    hash.set(source, target);
    
  	Reflect.ownKeys(source).forEach(key => { // 改动
        if (isObject(source[key])) {
            target[key] = cloneDeep4(source[key], hash); 
        } else {
            target[key] = source[key];
        }  
  	});
    return target;
}

// 测试已通过

破解递归爆栈

function cloneDeep5(x) {
    const root = {};

    // 栈
    const loopList = [
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];

    while(loopList.length) {
        // 广度优先
        const node = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = {};
        }

        for(let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循环
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }

    return root;
}

手写dom深度遍历(广度和深度)

segmentfault.com/a/119000001…

相关实现1

 /*
const o = {
  a:{
    b:{
      c:1
    }
  }
}
get(o,'a.b.c')  //1
  
  
  
  */
var obj = {
    x: 1,
    y: {
        a: 2,
        b: {
            c: 3,
            d: 4
        }
    }
};
 
 //str='[{"a.b":1,"a.b.c":2}]'
 
//console.log(JSON.parse(str));
//获取对象所有的key值
function getKeys(obj, path) {
  debugger;
    if(Object.prototype.toString.call(obj) === "[object Object]") {
        var arrKeyValue = {};
        (function getKeysFn(o, char) {
            for(var key in o) {
                // debugger
                //判断对象的属性是否需要拼接".",如果是第一层对象属性不拼接,否则拼接"."
                var newChar = char == "" ? key : char + "." + key;
                if(Object.prototype.toString.call(o[key]) === "[object Object]") {
                    //如果属性对应的属性值仍为可分解的对象,使用递归函数继续分解,直到最里层
                    getKeysFn(o[key],newChar);
                } else {
                    arrKeyValue[newChar] = o[key]
                }
            }
        })(obj,"");
    } else {
        console.log("传入的不是一个真正的对象哦!");
    }
    return arrKeyValue[path]
}
console.log(getKeys(obj,'y.a'));

相关实现2

 function bfs(target, id) {
   debugger;
  const quene = [...target]
 
  do {
    const current = quene.shift()
    if (current.children) {
     
      quene.push(...current.children.map(x => ({ ...x, path: (current.path || current.id) + '-' + x.id })))
    }
    if (current.id == id) {
      return current.path
    }
  } while(quene.length)
  return undefined
}

function dfs(target, id) {
  debugger;
  const stask = [...target]
  do {
    const current = stask.pop()
    if (current.children) {
      stask.push(...current.children.map(x => ({ ...x, path: (current.path || current.id) + '-' + x.id })))
      
    }
    if (current.id == id) {
      return current.path
    }
  } while(stask.length)
  return undefined
}

// 公共的搜索方法,默认bfs
function commonSearch(target, id, mode) {
  //debugger;
  const staskOrQuene = [...target]
  do {
    const current = staskOrQuene[mode === 'dfs' ? 'pop' : 'shift']()
    if (current.children) {
      staskOrQuene.push(...current.children.map(x => ({ ...x, path: (current.path || current.id) + '-' + x.id })))
    }
    if (current.id === id) {
      return current
    }
  } while(staskOrQuene.length)
  return undefined
}


let target = [
  {
    id:'1',
    name:"广东省",
    children:[
      {
        id:'11',
        name:"深圳市",
        children:[
        {
            id:'111',
            name:"苏州市",
            
        },
        {
            id:'112',
            name:"南京市",
            
        }

    ]
  }
]
}
]


console.log(dfs(target, 111))//1-11-111

相关实现3

 /*
const o = {
  a:{
    b:{
      c:1
    }
  }
}
get(o,'a.b.c')  //1
  
  
  
  */
var obj = {
    x: 1,
    y: {
        a: 2,
        b: {
            c: 3,
            d: 4
        }
    }
};
 
 //str='[{"a.b":1,"a.b.c":2}]'
 
//console.log(JSON.parse(str));
//获取对象所有的key值
function getKeys(obj, path) {
  debugger;
    if(Object.prototype.toString.call(obj) === "[object Object]") {
        var arrKeyValue = {};
        (function getKeysFn(o, char) {
            for(var key in o) {
                // debugger
                //判断对象的属性是否需要拼接".",如果是第一层对象属性不拼接,否则拼接"."
                var newChar = char == "" ? key : char + "." + key;
                if(Object.prototype.toString.call(o[key]) === "[object Object]") {
                    //如果属性对应的属性值仍为可分解的对象,使用递归函数继续分解,直到最里层
                    getKeysFn(o[key],newChar);
                } else {
                    arrKeyValue[newChar] = o[key]
                }
            }
        })(obj,"");
    } else {
        console.log("传入的不是一个真正的对象哦!");
    }
    return arrKeyValue[path]
}
console.log(getKeys(obj,'y.a'));//2

相关实现4

var entry = {
  'a.b.c.dd': 'abcdd',
  'a.d.xx': 'adxx',
  'a.e': 'ae'
}

// 要求转换成如下对象
var output = {
  a: {
    b: {
      c: {
        dd: 'abcdd'
      }
    },
    d: {
      xx: 'adxx'
    },
    e: 'ae'
  }
}

实现代码

function map(entry) {
  debugger;
    const obj = Object.create(null);
    for (const key in entry) {
      const keymap = key.split('.');
      set(obj, keymap, entry[key])
    }
    return obj;
  }

  function set(obj, map, val) {
    let tmp;
    if (!obj[map[0]]) obj[map[0]] = Object.create(null);
    tmp = obj[map[0]];
    for (let i = 1; i < map.length; i++) {
      if (!tmp[map[i]]) tmp[map[i]] = map.length - 1 === i ? val : Object.create(null);
      tmp = tmp[map[i]];
    }
  }

相关实现5

var entry = {
  a: {
    b: {
      c: {
        dd: 'abcdd'
      }
    },
    d: {
      xx: 'adxx'
    },
    e: 'ae'
  }
}

// 要求转换成如下对象
var output = {
  'a.b.c.dd': 'abcdd',
  'a.d.xx': 'adxx',
  'a.e': 'ae'
}

实现代码

//展平对象
 function flatObj(obj, parentKey = "", result = {}) {
   debugger;
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      let keyName = `${parentKey}${key}`;
      if (typeof obj[key] === 'object')
        flatObj(obj[key], keyName+".", result)
      else
        result[keyName] = obj[key];
    }
  }
  return result;
}

重复数组转为树形结构

const fn = arr => {
     debugger;
  const res = []

//   const map = arr.reduce(function(res,item){
//     res[item.id] = item
//     return res
//   },{})
//可以简写为
const map = arr.reduce((res, item) => ((res[item.id] = item), res), {})
  for (const item of Object.values(map)) {
    if (!item.pId) {
      res.push(item)
    } else {
      const parent = map[item.pId]
      parent.child = parent.child || []
      parent.child.push(item)
    }
  }
  return res
}
const arr = [{id: 1}, {id:2, pId: 1}, {id: 3, pId: 2}, {id: 4}, {id:3, pId: 2}, {id: 5, pId: 4}]
console.log(fn(arr))
//fn(arr) => [{id: 1, child: [{id: 2, pId: 1, child: [{ id: 3, pId: 2}]}]}, {id: 4, child: [{id: 5, pId: 4}]}]

sleep

blog.csdn.net/Polaris_tl/…

const sleep = time => {
  return new Promise(resolve => setTimeout(resolve,time))
}
sleep(1000).then(()=>{
  console.log(1)
})

function* sleepGenerator(time) {
  yield new Promise(function(resolve,reject){
    setTimeout(resolve,time);
  })
}
sleepGenerator(1000).next().value.then(()=>{console.log(1)})

function sleep(time) {
  return new Promise(resolve => setTimeout(resolve,time))
}
async function output() {
  let out = await sleep(1000);
  console.log(1);
  return out;
}
output();

function sleep(callback,time) {
  if(typeof callback === 'function')
    setTimeout(callback,time)
}

function output(){
  console.log(1);
}
sleep(output,1000);

lazyman

class LazyManClass {
    constructor(name) {
        this.taskList = [];
        this.name = name;
        debugger;
        console.log(`Hi I am ${this.name}`);
        setTimeout(() => {
            this.next();
        }, 0);
    }
    eat (name) {
        var that = this;
        var fn = (function (n) {
            return function () {
                console.log(`I am eating ${n}`)
                that.next();
            }
        })(name);
        this.taskList.push(fn);
        return this;
    }
    sleepFirst (time) {
        var that = this;
        var fn = (function (t) {
            return function () {
                setTimeout(() => {
                    console.log(`等待了${t}秒...`)
                    that.next();
                }, t * 1000);  
            }
        })(time);
        this.taskList.unshift(fn);
        return this;
    }
    sleep (time) {
        var that = this
        var fn = (function (t) {
            return function () {
                setTimeout(() => {
                    console.log(`等待了${t}秒...`)
                    that.next();
                }, t * 1000); 
            }
        })(time);
        this.taskList.push(fn);
        return this;
    }
    next () {
        var fn = this.taskList.shift();
        fn && fn();
    }
}
function LazyMan(name) {
    return new LazyManClass(name);
}
LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(4).eat('junk food');


// Hi I am Tony
// 等待了5秒...
// I am eating lunch
// I am eating dinner
// 等待了4秒...
// I am eating junk food

随机分组

 // 得到一个两数之间的随机整数,包括两个数在内
function getRandomIntInclusive(min, max) {
    debugger;
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值 
}
// 随机生成10个整数数组, 排序, 去重
let initArr = Array.from({ length: 10 }, (v) => { return getRandomIntInclusive(0, 99) });
initArr.sort((a,b) => { return a - b });
initArr = [...(new Set(initArr))];

// 放入hash表
let obj = {};
initArr.map((i) => {
    const intNum = Math.floor(i/10);
    if (!obj[intNum]) obj[intNum] = [];
    obj[intNum].push(i);
})

// 输出结果
const resArr = [];
for(let i in obj) {
    resArr.push(obj[i]);
}
console.log(resArr);

每三个数加一个逗号

function transform(num)
      {  
          debugger;
          var nStr = num.toString();
          var  x = nStr.split('.');
         var x1 = x[0];
         var x2 = x.length > 1 ? '.' + x[1] : '';
         var rgx = /(\d+)(\d{3})/;
         while (rgx.test(x1)) {
         x1 = x1.replace(rgx, '$1' + ',' + '$2');
         }
         return x1 + x2;
1    }
// JS最大准确数为16位,超过自动截取
console.log(transform(45646465734.2358745));

过程中的小收获

将不定长的参数传递给函数呢?有三种办法:eval,apply,ES6的解构语法

eval('obj.fn('+args+'));
obj.fn.apply(obj,args);
obj.fn(...args);