含泪整理前端相关算法(共48题)

81 阅读14分钟

防抖和节流

防抖

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

//防抖函数:抖动停止后的时间超过设定的时间时执行一次函数--某固定时间内最多执行一次
function debounce(func, delay) {
    var timeout;
    //返回的函数在一个抖动结束后的delay毫秒内执行func函数
    return function() {
        var context = this, args = arguments; //保存函数调用时的上下文和参数
        clearTimeout(timeout); //触发func前清除定时器
        timeout = setTimeout(function() {
            func.apply(context, args);  //如果不这么做,this指向为window,参数为undefined
        }, delay); //用户停止某个连续动作delay毫秒后执行func
    };
}
function realfun(){
    console.log(111)
}
window.addEventListener("resize",debounce(realfun,1000))

防抖优化

我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。因此加个 immediate 参数判断是否是立刻执行。

// 防抖优化
function debounce(func, delay, immediate) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, delay)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, delay);
        }
    }
}

window.addEventListener("resize", debounce(realFunc, 500));
window.addEventListener("resize", debounce(realFunc, 500true));

节流

每隔一段时间执行一次,目的是降低频率

使用时间戳

使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

// 使用时间戳
function throttle(func, wait) {
    var context, args;
    var previous = 0;

    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

使用定时器

// 使用定时器
function throttle(func, wait) {
    var timeout;

    return function() {
        context = this;
        args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(context, args)
            }, wait)
        }
    }
}
function realfun(){
    console.log(111)
}
window.addEventListener("resize", throttle(realFunc, 500));

比较两个方法: 1. 使用时间戳会立刻执行,使用定时器会在 n 秒后第一次执行 2. 使用时间戳停止触发后没有办法再执行事件,使用定时器停止触发后依然会再执行一次事件

双剑合璧

我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!

// 双剑合璧版
function throttle(func, wait) {
    var timeout, context, args, result;
    var previous = 0;

    var later = function() {
        previous = +new Date();
        timeout = null;
        func.apply(context, args)
    };

    var throttled = function() {
        var now = +new Date();
        //下次触发 func 剩余的时间
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
         // 如果没有剩余的时间了或者你改了系统时间
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
        } else if (!timeout) {
            timeout = setTimeout(later, remaining);
        }
    };
    return throttled;
}
//简写版
function throttle(func, delay, mustRun) {
    var timeout;
    var starttime = new Date(); //起始的时间
    return function() {
        var context = this, args = arguments;
        var curtime = new Date(); //当前时间
        clearTimeout(timeout);
        //如果达到规定的触发时间间隔,触发func
        if(curtime - starttime >= mustRun) {
            func.apply(context, args);
            starttime = curtime;
        }
        //没有达到触发时间,重新设定计时器
        else {
            timeout = setTimeout(func, delay);
        }
    }
}

数组去重

对于

var array = [1, 1, '1', '1', null, null, undefined, undefined, new String('1'), new String('1'), /a/, /a/, NaN, NaN];

image.png

//优化后的键值对方法
//但是JSON.stringify 任何一个正则表达式的结果都是 {},所以这个方法并不适用于处理正则表达式去重
//因此先处理正则
var array = [1,1,'1','1',NaN,NaN,undefined,undefined,null,null,/a/,/a/,/b/,{value: '1'},{value: 1}, {value: 1}, {name: 2},{value: 2}];
function unique(array) {
    //提取正则到arr
    let arr=[]
    for(let a of array){
        if(a instanceof RegExp){
            arr.push(a)
        }
    }
    //去重正则
    var o={}
    arr=arr.filter(function(item, index, array){
        return o.hasOwnProperty(typeof item + item) ? false : (o[typeof item + item] = true)
    })
    //去重除正则外的(去重后会含第一个正则)
    var obj = {};
    array=array.filter(function(item, index, array){   
        //return obj.hasOwnProperty(item) ? false : (obj[item] = true)    //此做法无法区分1和‘1’
        //return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)  // 无法区分{value: 1} 和 {value: 2},因为 typeof item + item 的结果都会是 object[object Object]       
        return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
    })
    //拼接(去除第一个已有的正则)
    return array.concat(arr.slice(1))
}

console.log(unique(array)); // [1,,'1',NaN,undefined,null,/a/,{value: '1'},{value: 1},{name: 2},{value: 2},/b/];

事件委托

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>
      <ul>
            <li>.</li>
            <li>.</li>
            <li>.</li>
        </ul>

        <script type="text/javascript">
            // 补全代码
            //此处onclick不能写成onClick
            document.querySelector('ul').onclick=function(e){
                //兼容
                e=e||window.e  //注意是||不是|
                if(e.target.nodeName.toLowerCase()==='li'){  //必须有.toLowerCase()
                    e.target.innerHTML+='.'
                }
            }
            
        </script>
    </body>
</html>

比较版本号

var compareVersion = function(version1, version2) {
    const v1=version1.split('.')
    const v2=version2.split('.')
    for(let i=0;i<v1.length || i<v2.length;i++){
        //需要另外设置x y来保存未定义时令其为0
        let x = 0, y = 0;
        if (i < v1.length) {
            x = parseInt(v1[i]);
        }
        if (i < v2.length) {
            y = parseInt(v2[i]);
        }
        if (x > y) {
            return 1;
        }
        if (x < y) {
            return -1;
        }
    }
    return 0
};
//另一种写法
var compare = (version1,version2) =>{
    const newversion1=`${version1}`.split('.').length>=3?`${version1}`:`${version1}`.concat('.0')
    const newversion2=`${version2}`.split('.').length>=3?`${version2}`:`${version2}`.concat('.0')
    var res1=newversion1.split('.')
    var res2=newversion2.split('.')
    for(let i=0;i<res1.length;i++){
    //+号是为了字符串转数字
        if(+res1[i]<+res2[i]){
            return -1
        }else if(+res1[i]>+res2[i]){
            return 1
        }
        if(i===res1.length-1 && +res1[i]===+res2[i]){
            return 0
        }
    }
}

console.log(compare('0.1', '1.1.1'))  //-1
console.log(compare('13.37', '1.2 '))  //1
console.log(compare('1.1', '1.1.0'))  //0
function compareVersion(version1, version2) {
    const newVersion1 = `${version1}`.split('.').length < 3 ? `${version1}`.concat('.0') : `${version1}`;
    const newVersion2 = `${version2}`.split('.').length < 3 ? `${version2}`.concat('.0') : `${version2}`;
    //计算版本号大小,转化大小
    function toNum(a){
        const c = a.toString().split('.');  //[0,1,0]
        const num_place = ["", "0", "00", "000", "0000"],//设x=4
            r = num_place.reverse();
        for (let i = 0; i < c.length; i++){
            const len=c[i].length;
            c[i]=r[len]+c[i]; //拼串
        }
        return c.join(''); //0000 0001 0000
    }

    //检测版本号是否需要更新
    function checkPlugin(a, b) {
        const numA = toNum(a);
        const numB = toNum(b);
        return numA > numB ? 1 : numA < numB ? -1 : 0;
    }
    return checkPlugin(newVersion1 ,newVersion2);
}
compareVersion('0.1', '1.1.1'); // -1   000000010000 000100010001
compareVersion('13.37', '1.2 '); // 1
compareVersion('1.1', '1.1.0'); // 0

生成随机字符串

var chars = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];

function generateMixed(n) {
     var res = "";
     for(var i = 0; i < n ; i ++) {
         var id = Math.ceil(Math.random()*35);
         res += chars[id];
     }
     return res;
}

or

<script language="javascript">
function randomString(len) {
  len = len || 32;
  var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';    /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
  var maxPos = $chars.length;
  var pwd = '';
  for (i = 0; i < len; i++) {
    pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
  }
  return pwd;
}
document.write(randomString(32));
</script>

下划线与驼峰的转换

// 下划线转换驼峰
function toHump(name) {
    return name.replace(/_(\w)/g, function(all, letter){  //letter为()里的内容
        return letter.toUpperCase();
    });
}
// 驼峰转换下划线
function toLine(name) {
    return name.replace(/([A-Z])/g,"_$1").toLowerCase();  //$1指以()为组,每一个()对应一个$
}


// 测试
let a = 'a_b2_345_c2345';
console.log(toHump(a)); //aB2345C2345

let b = 'aBdaNf';
console.log(toLine(b)); //a_bda_nf

金钱格式化

/*
 *Array.prototype.reverse() Array→String
 *Array.prototype.join() Array→String
 *String.prototype.split() String→Array
 *String.prototype.match() String→Array
*/

function transferToMoney(money) {
    if(money && money != null) {
        money = String(money);
        var left = money.split('.')[0], right = money.split('.')[1];
        right = right ? (right.length >= 2 ? '.' + right.substr(0, 2) : '.' + right + '0') : '.00';
        var tmp = left.split('').reverse().join('').match(/\d{1,3}/g);
        left = tmp.join(',').split('').reverse().join('');
        return (Number(money) < 0 ? '-' : '') + left + right;
    }
    else if(money === 0) {
        return '0.00';
    }
    else {
        return '';
    }
}

合法的url

//仅验证是否是http(s)

const _isUrl = url => {
    // 补全代码
    //正则 /^(http:\/\/|https:\/\/)?([\w-]+.)+[\w-]+(/[\w- ./?%&=]*)?/
    return /^((http|https)://)?(([A-Za-z0-9]+-[A-Za-z0-9]+|[A-Za-z0-9]+).)+([A-Za-z]+)(:\d+)?(/.*)?(?.*)?(#.*)?$/.test(url)
                                                            
}
//完整版
function isURL(str_url) {// 验证url
    var strRegex = "^((https|http|ftp|rtsp|mms)?://)"
                       + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" // ftp    的user@
                       + "(([0-9]{1,3}.){3}[0-9]{1,3}" // IP形式的URL- 199.194.52.184
                       + "|" // 允许IP和DOMAIN(域名)
                       + "([0-9a-z_!~*'()-]+.)*" // 域名- www.
                       + "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]." // 二级域名
                       + "[a-z]{2,6})" // first level domain- .com or .museum
                       + "(:[0-9]{1,4})?" // 端口- :80
                       + "((/?)|" // a slash isn't required if there is no file name
                       + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$";
    var re = new RegExp(strRegex);
    return re.test(str_url);
}

数组的扁平化

reduce

利用reduce需要递归,有一定的性能消耗

//利用reduce实现
var array=[1,2,[3,4,{name:'doing'}],[5,[6,7]],{name:'jack',age:[12,34,23]}]

function flatten(arr) {
    return arr.reduce(function (prev, next) {
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}
console.log(flatten(array)) //[1,2,3,4,{name:'doing'},5,6,7,{name:'jack',age:[12,34,23]}]

扩展运算符

通过扩展运算符确实可以避免递归,但是却要使用到循环,如果数组层级过高,循环的消耗也不小

var array=[1,2,[3,4,{name:'doing'}],[5,[6,7]],{name:'jack',age:[12,34,23]}]
//利用扩展运算符
function flatten(arr) {
    var arr;
    while (arr.some(v => Array.isArray(v))) {
        arr = [].concat(...arr);   //一次... 数组降一维
        console.log(arr)
    }
    return arr;
}

console.log(flatten(array)) //[1,2,3,4,{name:'doing'},5,6,7,{name:'jack',age:[12,34,23]}]

正则

正则处理不了 对象里又含数组的情况

var array=[1,2,[3,4,{name:'doing'}],[5,[6,7,[8]]],{age:23}]

function flatten(arr) {
    let str = JSON.stringify(arr).replace(/[|]/g, ''); 
    return JSON.parse(Array.of('[' + str + ']')[0]); 
    //return JSON.parse(`[${JSON.stringify(arr).replace(/[|]/g,'')}]`);

//JSON.stringify使数组序列化为字符串,否则对象会是object object
//JSON.parse将数据转换为 JavaScript 对象
//Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型
}

console.log(flatten(array)) //[1,2,3,4,{name:'doing'},5,6,7,{age:23}]

flat

兼容性不好

var array=[1,2,[3,4,{name:'doing'}],[5,[6,7,[8]]],{name:'jack',age:[12,34,23]}]
console.log(array.flat(Infinity)) //[1,2,3,4,{name:'doing'},5,6,7,{name:'jack',age:[12,34,23]}]

generator

var array=[1,2,[3,4,{name:'doing'}],[5,[6,7,[8]]],{name:'jack',age:[12,34,23]}]

function* flatten(arr) {
    if (!Array.isArray(arr)) yield arr;
    else for (let el of arr) yield* flatten(el);
}

let flattened = [...flatten(array)]
console.log(flattened) //[1,2,3,4,{name:'doing'},5,6,7,{name:'jack',age:[12,34,23]}]

函数柯里化

柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。

// 第一版
var curry = function (fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        var newArgs = args.concat([].slice.call(arguments));
        return fn.apply(this, newArgs);
    };
};
// 第二版
function sub_curry(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        return fn.apply(this, args.concat([].slice.call(arguments)));
    };
}

function curry(fn, length) {

    //如果传入的参数不够,就继续执行 curry 函数接收参数,如果参数达到个数,就执行柯里化了的函数。
    length = length || fn.length;

    var slice = Array.prototype.slice;

    return function() {
        if (arguments.length < length) {
            var combined = [fn].concat(slice.call(arguments));
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            return fn.apply(this, arguments);
        }
    };
}

偏函数

偏函数则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。

如经常需要实现1+n的功能,则可以把1固定,以后只传n

// 第二版
var _ = {};

function partial(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        var position = 0, len = args.length;
        for(var i = 0; i < len; i++) {
            args[i] = args[i] === _ ? arguments[position++] : args[i]
        }
        while(position < arguments.length) args.push(arguments[position++]);
        return fn.apply(this, args);
    };
};

惰性函数

我们现在需要写一个 foo 函数,这个函数返回首次调用时的 Date 对象,注意是首次。

接下来的使用方式都不会发生改变的时候,想想是否可以考虑使用惰性函数。

//利用闭包可以解决变量污染全局的问题,但是解决不了每次调用都要进行一次判断
var foo = (function() {
    var t;
    return function() {
        if (t) return t;
        t = new Date();
        return t;
    }
})();
//惰性函数就是解决每次都要进行判断的这个问题,就是重写函数
var foo = function() {
    var t = new Date();
    foo = function() {
        return t;
    };
    return foo();
};

应用

// 为了兼容现代浏览器和 IE 浏览器,
function addEvent (type, el, fn) {
    if (window.addEventListener) {
        el.addEventListener(type, fn, false);
    }
    else if(window.attachEvent){
        el.attachEvent('on' + type, fn);
    }
}
//不想要每次都判断,而是只判断一次,可使用惰性函数
function addEvent (type, el, fn) {
    if (window.addEventListener) {
        addEvent = function (type, el, fn) {
            el.addEventListener(type, fn, false);
        }
    }
    else if(window.attachEvent){
        addEvent = function (type, el, fn) {
            el.attachEvent('on' + type, fn);
        }
    }
    addEvent(type, el, fn) 
}
//或者闭包
var addEvent = (function(){
    if (window.addEventListener) {
        return function (type, el, fn) {
            el.addEventListener(type, fn, false);
        }
    }
    else if(window.attachEvent){
        return function (type, el, fn) {
            el.attachEvent('on' + type, fn);
        }
    }
})();

函数组合

function compose() {
    var args = arguments;
    var start = args.length - 1;
    return function() {
        var i = start;
        var result = args[start].apply(this, arguments);
        //从右到左
        while (i--) result = args[i].call(this, result);
        return result;
    };
};

函数记忆

// 第一版 (来自《JavaScript权威指南》)
function memoize(f) {
    var cache = {};
    return function(){
        var key = arguments.length + Array.prototype.join.call(arguments, ",");
        if (key in cache) {
            return cache[key]
        }
        else {
            return cache[key] = f.apply(this, arguments)
        }
    }
}
// 第二版 (来自 underscore 的实现)
var memoize = function(func, hasher) {
    var memoize = function(key) {
        var cache = memoize.cache;
        var address = '' + (hasher ? hasher.apply(this, arguments) : key);
        if (!cache[address]) {
            cache[address] = func.apply(this, arguments);
        }
        return cache[address];
    };
    memoize.cache = {};
    return memoize;
};

应用

斐波那契,斐波那契的调用次数会大大减少

尾递归

采用与不采用尾递归的区别:

不采用:执行上下文栈会有很多上下文

采用:上一个执行上下文先pop,下一个才会push

function fibonacci(n){
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(5)) // 1 1 2 3 5
function factorial(n, res) {
    if (n == 1) return res;
    return factorial(n - 1, n * res)
}

console.log(factorial(4, 1)) // 24

数组乱序

遍历数组元素,然后将当前元素与以后随机位置的元素进行交换

function shuffle(a) {
    for (let i = a.length; i; i--) {
    //Math.random()生成的随机数,是可以包括0,但是不包括1!
        let j = Math.floor(Math.random() * i);
        [a[i - 1], a[j]] = [a[j], a[i - 1]];
    }
    return a;
}
var a=[1,2,3]
shuffle(a)
//demo
var times = 100000;
var res = {};

for (var i = 0; i < times; i++) {
    var arr = shuffle([1, 2, 3]);

    var key = JSON.stringify(arr);
    res[key] ? res[key]++ :  res[key] = 1;
}

// 为了方便展示,转换成百分比
for (var key in res) {
    res[key] = res[key] / times * 100 + '%'
}

console.log(res)

数组实现栈

function Stack(){
  this.items=[]
  Stack.prototype.push=function(val){
    this.items.push(val)
  }
  Stack.prototype.pop=function(){
    return this.items.pop()
  }
  Stack.prototype.size=function(){
    return this.items.length
  }
  Stack.prototype.peek=function(){
    return this.items[this.items.length-1]
  }
  Stack.prototype.isEmpty=function(){
    return this.items.length==0
  }
  // 将栈结构的内容以字符形式返回toString()
  Stack.prototype.toString = function(){
      var str = '';
      for(var i =0;i<this.items.length;i++){
          str += this.items[i] + ' ';
      }
      return str;
  }
  
}

实现两栏布局

左边固定,右边自适应

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            display: flex;
           
        }
        .left{
            width: 300px;
            height: 100%;
            position: fixed;
            background-color: red;
        }
        .main{
            position: absolute;
            right:0;
            height:1800px;
        }
    </style>
</head>
<body>
    <div class="box">
        <div class="left">left</div>
        <div class="main">main</div>
    </div>

</body>
</html>

实现三栏布局

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            display: flex;
            min-height: 100vh;
        }
        .left{
            width: 300px;
            background-color: red;
        }
        .right{
            width: 300px;
            background-color: yellow;
        }
        .main{
            flex:1;
            //overflow:hidden;
        }
    </style>
</head>
<body>
    <div class="box">
        <div class="left">left</div>
        <div class="main">main</div>
        <div class="right">right</div>
    </div>

</body>
</html>

图片懒加载

方案一:clientHeight、scrollTop 和 offsetTop

image.png

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    img {
      display: block;
      margin-bottom: 50px;
      width: 400px;
      height: 400px;
    }
  </style>
</head>
<body>
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <script>
    let num = document.getElementsByTagName('img').length;
    let img = document.getElementsByTagName("img");
    let n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历

    lazyload(); //页面载入完毕加载可是区域内的图片

    window.onscroll = lazyload;

    function lazyload() { //监听页面滚动事件
      let seeHeight = document.documentElement.clientHeight; //可见区域高度
      let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
      for (let i = n; i < num; i++) {
        if (img[i].offsetTop < seeHeight + scrollTop) {
          if (img[i].getAttribute("src") == "Go.png") {
            img[i].src = img[i].getAttribute("data-src");
          }
          n = i + 1;
        }
      }
    }
  </script>

</body>

</html>

方案二:getBoundingClientRect

图片上边距离到可视区上边的距离(可为负数) < 浏览器可视高度: element.getBoundingClientRect().top < clientHeight

image.png

现在我们用另外一种方式来判断图片是否出现在了当前视口, 即 DOM 元素的 getBoundingClientRect API。

上述的 lazyload 函数改成下面这样:
function lazyload() {
  for(let i = count; i <num; i++) {
    // 元素现在已经出现在视口中
    if(img[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
      if(img[i].getAttribute("src") !== "default.jpg") continue;
      img[i].src = img[i].getAttribute("data-src");
      count ++;
    }
  }
}

方案三: IntersectionObserver

目标元素的可见比例,即 intersectionRect 占 boundingClientRect 的比例,完全可见时为 1 ,完全不可见时小于等于 0

这是浏览器内置的一个API,实现了监听window的scroll事件、判断是否在视口中以及节流三大功能。
我们来具体试一把:

let img = document.getElementsByTagName("img");

const observer = new IntersectionObserver(changes => {
  //changes 是被观察的元素集合
  for(let i = 0, len = changes.length; i < len; i++) {
    let change = changes[i];
    // 通过这个属性判断是否在视口中
    if(change.isIntersecting) {
      const imgElement = change.target;
      imgElement.src = imgElement.getAttribute("data-src");
      observer.unobserve(imgElement);
    }
  }
})
Array.from(img).forEach(item => observer.observe(item));

优化

通过以下css可以提高性能

  # 之所以使用visibility而不是display是因为
  # visibility不会触发重绘(repaint)和重排(reflow)
  img {
    visibility: hidden;
  }
  
  img[src] {
    visibility: visible;
  }

因为scroll事件的触发频率很高,频繁操作dom结点会造成很大的性能问题,所以需要做节流和防抖设计,减少scroll事件的触发频率

浅拷贝

自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。

  • Object.assign()
const _shallowClone = target => {
    // 补全代码
    //Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
    return Object.assign({},target);   
}
注意点:

- 它不会拷贝对象的继承属性;
- 它不会拷贝对象的不可枚举的属性;
- 可以拷贝 Symbol 类型的属性。
  • 扩展运算符

    和使用Object.assign功能相同,其注意事项也相同,两者在使用上基本是可以相互转换

const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
  • Array.prototype.concat()
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
  • Array.prototype.slice()
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
  • 手动
//手动
const _shallowClone = target => {
    if(typeof target === 'object' && target !== null) {
        const constructor = target.constructor
        if(/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) return target
        const cloneTarget = Array.isArray(target) ? [] : {}
        for(prop in target) {
        //for...in会遍历到原型链,所以此处用hasOwnProperty判断一下是不是自身拥有
            if(target.hasOwnProperty(prop)) {
                cloneTarget[prop] = target[prop]
            }
        }
        return cloneTarget
    } else {
     // 基础类型 直接返回
        return target
    }
}

深拷贝

  • lodash.cloneDeep()
const _ = require('lodash');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
  • jQuery.extend()
const $ = require('jquery');
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
  • JSON.parse()
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse( JSON.stringify(arr) );
console.log(new_arr);
注意:

  但是这种方式存在弊端,会忽略`undefined``symbol``函数`
  • 手动
//简易版
//1. 参数对象和参数对象的每个数据项的数据类型范围仅在数组、普通对象({})、基本数据类型中]  
//2. 无需考虑循环引用问题
const _sampleDeepClone = target => {
    if(typeof target === 'object' && target !== null) {
        const cloneTarget = Array.isArray(target) ? [] : {}
        for(prop in target) {
            if(target.hasOwnProperty(prop)) {
                cloneTarget[prop] = _sampleDeepClone(target[prop])
            }
        }
        return cloneTarget
    } else {
        return target
    }
}
//完整版
//1. 需要考虑函数、正则、日期、ES6新对象  
//2. 需要考虑循环引用问题
const _completeDeepClone = (target, map = new Map()) => {
    // 补全代码
    //参数如果为空
    if(target===null) return target;
    //参数如果不是对象类型,而是基本数据类型
    if(typeof target!=='object') return target;
    //参数为其他数据类型
    const cons = target.constructor;
    if(/^(Function|RegExp|Date|Map|Set)$/i.test(cons)) return new cons(target);
    const cloneTarget = Array.isArray(target)? []:{};
    //如果存在循环引用,直接返回当前循环引用的值,否则,将其加入map,
    if(map.get(target)) return map.get(target);
    map.set(target,cloneTarget);
    for(let key in target){
        cloneTarget[key] = _completeDeepClone(target[key],map)
    }
    return cloneTarget;
}

判断两个数相等

var toString = Object.prototype.toString;

function isFunction(obj) {
    return toString.call(obj) === '[object Function]'
}

function eq(a, b, aStack, bStack) {

    // === 结果为 true 的区别出 +0 和 -0
    if (a === b) return a !== 0 || 1 / a === 1 / b;

    // typeof null 的结果为 object ,这里做判断,是为了让有 null 的情况尽早退出函数
    if (a == null || b == null) return false;

    // 判断 NaN
    if (a !== a) return b !== b;

    // 判断参数 a 类型,如果是基本类型,在这里可以直接返回 false
    var type = typeof a;
    if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;

    // 更复杂的对象使用 deepEq 函数进行深度比较
    return deepEq(a, b, aStack, bStack);
};

function deepEq(a, b, aStack, bStack) {

    // a 和 b 的内部属性 [[class]] 相同时 返回 true
    var className = toString.call(a);
    if (className !== toString.call(b)) return false;

    switch (className) {
        case '[object RegExp]':
        case '[object String]':
            return '' + a === '' + b;
        case '[object Number]':
            if (+a !== +a) return +b !== +b;
            return +a === 0 ? 1 / +a === 1 / b : +a === +b;
        case '[object Date]':
        case '[object Boolean]':
            return +a === +b;
    }

    var areArrays = className === '[object Array]';
    // 不是数组
    if (!areArrays) {
        // 过滤掉两个函数的情况
        if (typeof a != 'object' || typeof b != 'object') return false;

        var aCtor = a.constructor,
            bCtor = b.constructor;
        // aCtor 和 bCtor 必须都存在并且都不是 Object 构造函数的情况下,aCtor 不等于 bCtor, 那这两个对象就真的不相等啦
        if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {
            return false;
        }
    }


    aStack = aStack || [];
    bStack = bStack || [];
    var length = aStack.length;

    // 检查是否有循环引用的部分
    while (length--) {
        if (aStack[length] === a) {
            return bStack[length] === b;
        }
    }

    aStack.push(a);
    bStack.push(b);

    // 数组判断
    if (areArrays) {

        length = a.length;
        if (length !== b.length) return false;

        while (length--) {
            if (!eq(a[length], b[length], aStack, bStack)) return false;
        }
    }
    // 对象判断
    else {

        var keys = Object.keys(a),
            key;
        length = keys.length;

        if (Object.keys(b).length !== length) return false;
        while (length--) {

            key = keys[length];
            if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;
        }
    }

    aStack.pop();
    bStack.pop();
    return true;

}

console.log(eq(0, 0)) // true
console.log(eq(0, -0)) // false

console.log(eq(NaN, NaN)); // true
console.log(eq(Number(NaN), Number(NaN))); // true

console.log(eq('Curly', new String('Curly'))); // true

console.log(eq([1], [1])); // true
console.log(eq({ value: 1 }, { value: 1 })); // true

var a, b;

a = { foo: { b: { foo: { c: { foo: null } } } } };
b = { foo: { b: { foo: { c: { foo: null } } } } };
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b;

console.log(eq(a, b)) // true

call

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>
        <script type="text/javascript">
            // 补全代码
            Function.prototype._call=function(obj,...args){
                //obj可能为null
                obj= obj|| window
                obj.fun=this
                const res = obj['fun'](...args); //obj.fun不行
                delete obj['fun']
                return res
            }
        </script>
    </body>
</html>
Function.prototype.call2 = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    //eval会把字符串当成函数执行
    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}

apply

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

bind

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>
        <script type="text/javascript">
            // 补全代码
            Function.prototype._bind = function(obj,...args){
                obj=obj || window
                let self=this
                return function(...args1){
                    return self.call(obj,...args,...args1)
                }
            }
        </script>
    </body>
</html>
Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("error");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

new

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>
      
        <script type="text/javascript">
           const _new = function(constructor) {
                // 补全代码\n      
                let obj = {};
                obj.__proto__ = constructor.prototype;
                const ret=constructor.call(obj)
                return typeof ret === 'object' ? ret : obj;
              }
           //or
           const _new = function (constructor, ...args) {
                // new关键字做了4件事
                // 1. 创建一个新对象
                const obj = {};
                // 2. 为新对象添加属性__proto__,将该属性链接至构造函数的原型对象
                obj.__proto__ = constructor.prototype;
                // 3. 执行构造函数,this被绑定在新对象上
                const ret=constructor.apply(obj, args);
                // 4. 返回对象
                return typeof ret === 'object' ? ret : obj;
            };

        </script>
    </body>
</html>
// 第二版的代码
function objectFactory() {

    var obj = new Object(),
    //删除并拿到arguments的第一项
    Constructor = [].shift.call(arguments);

    obj.__proto__ = Constructor.prototype;

    var ret = Constructor.apply(obj, arguments);

    return typeof ret === 'object' ? ret : obj;

};

instanceof

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>
      
        <script type="text/javascript">
            const _instanceof = (target, Fn) => {
                let proto = target.__proto__
                let prototype = Fn.prototype
                while(true) {
                    if(proto === Fn.prototype) return true
                    if(proto === null) return false
                    proto = proto.__proto__
                }
            }

        </script>
    </body>
</html>
<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>
      
        <script type="text/javascript">
            const _instanceof = (target, Fn) => {
                return target instanceof Fn
            }

        </script>
    </body>
</html>

map

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>

        <script type="text/javascript">
            // 补全代码
            Array.prototype._map = function(Fn) {  //不要用箭头函数
                if (typeof Fn !== 'function') return
                const array = this
                const newArray = new Array(array.length)
                for (let i=0; i<array.length; i++) {
                    let result = Fn.call(arguments[1], array[i], i, array)
                    newArray[i] = result
                }
                return newArray
            }
            
            //or
            Array.prototype._map = function (fn){
                if(typeof fn !== 'function') return;
                let newArr = [];
                for(let i = 0;i<this.length ;i++){  
                  newArr[i] = fn(this[i])
                }
                return newArr;
            }

        </script>
    </body>
</html>

filter

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>

        <script type="text/javascript">
            // 补全代码
            Array.prototype._filter = function(Fn){
                if(typeof Fn!=='function') return
                const res=[]
                for(let i=0;i<this.length;i++){
                    if(Fn(this[i])){
                        res.push(this[i])
                    }
                }
                return res
            }
        </script>
    </body>
</html>

reduce

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>
      
        <script type="text/javascript">
            // 补全代码
            Array.prototype._reduce = function(Fn,init){
                if(typeof Fn!=='function') return
                for(let i=0;i<this.length;i++){
                    if(init===undefined){
                        init=Fn(this[i],this[i+1])
                        ++i
                    }else{
                        init=Fn(init,this[i])
                    }
                    
                }
                return init
            }
            //or
            Array.prototype._reduce = function(Fn){
                if(typeof Fn!=='function') return
                var total=0
                for(let i=0;i<this.length;i++)
                    total = Fn(total,this[i]);
                return total;
            }
        </script>
    </body>
</html>

findindex

Array.prototype.findIndex=function(predicate) {
    for (var i = 0; i < this.length; i++) {
        if (predicate.call(this, this[i], i, this)) return i;
    }
    return -1;
}

console.log([1, 2, 3, 4].findIndex(function(item, i, array){
    if (item == 3) return true;
})) // 2

findlastindex

Array.prototype.findLastIndex=function(predicate) {
    for (var i = this.length-1; i>=0; i++) {
        if (predicate.call(this, this[i], i, this)) return i;
    }
    return -1;
}

console.log([1, 2, 3, 4].findLastIndex(function(item, i, array){
    if (item == 3) return true;
})) // 2

合并findindex和findlastindex

function createIndexFinder(dir) {
    return function(predicate) {

        var length = this.length;
        var index = dir > 0 ? 0 : length - 1;

        for (; index >= 0 && index < length; index += dir) {
            if (predicate.call(this, this[index], index, this)) return index;
        }

        return -1;
    }
}

Array.prototype.findIndex = createIndexFinder(1);
Array.prototype.findLastIndex = createIndexFinder(-1);
console.log([1, 2, 3, 4].findLastIndex(function(item, i, array){
    if (item == 3) return true;
})) // 2

合并indexof和lastindexof

function createIndexOfFinder(dir) {
    return function(item){
        var length = this.length;
        var index = dir > 0 ? 0 : length - 1;
        for (; index >= 0 && index < length; index += dir) {
            if (this[index] === item) return index;
        }
        return -1;
    }
}

Array.prototype.indexof= createIndexOfFinder(1);
Array.prototype.lastindexof = createIndexOfFinder(-1);

var result = [1, 2, 3, 4, 5].indexOf(2);

console.log(result) // 1
// 进阶版:支持从某个索引开始找和NaN的查找
function createIndexOfFinder(dir, predicate, sortedIndex) {

    return function(array, item, idx){
        var length = array.length;
        var i = 0;

        if (typeof idx == "number") {
            if (dir > 0) {
                i = idx >= 0 ? idx : Math.max(length + idx, 0);
            }
            else {
                length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
            }
        }
        else if (sortedIndex && idx && length) {
            idx = sortedIndex(array, item);
            // 如果该插入的位置的值正好等于元素的值,说明是第一个符合要求的值
            return array[idx] === item ? idx : -1;
        }

        // 判断是否是 NaN
        if (item !== item) {
            idx = predicate(array.slice(i, length), isNaN)
            return idx >= 0 ? idx + i: -1;
        }

        for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
            if (array[idx] === item) return idx;
        }
        return -1;
    }
}

var indexOf = createIndexOfFinder(1, findIndex, sortedIndex);
var lastIndexOf = createIndexOfFinder(-1, findLastIndex);

Set

class mySet {
    constructor() {
        this.items={};//js对象不允许一个键指向两个不同的属性→保证集合里元素都是唯一的
    }
    //判断集合中是否存在val元素
    has(val) {
        return this.items.hasOwnProperty(val);
    }
    //向集合中添加元素
    add(val) {
        if(!this.has(val)) {
            this.items[val] = val;
            return true;
        }
        else {
            return false;
        }
    }
    //删除集合中的指定元素
    remove(val) {
        if(this.has(val)) {
           delete this.items[val];
        }
    }
    //清空集合中的元素
    clear() {
        this.items={};
    }
    //集合的大小
    size() {
        /*Object.keys()返回给定对象所有可枚举属性的字符串数组*/
        return Object.keys(this.items).length;
    }
    //获取集合中的所有元素
    values(){
        let res=[];
        Object.keys(this.items).forEach(item=>{
            res.push(this.items[item]);
        })
        return res;
    }
}

var set = new mySet();
set.add(3);
set.add(1);
set.add(7);
set.add(0);
console.log(set.size()); //输出4
set.remove(3);
console.log(set.values()); //输出[ 0, 1, 7 ]
console.log(set.has(5)); //输出false
set.clear();
console.log(set.size()); //输出0

promise相关

class Promise{
    //构造方法
    constructor(executor){
        //添加属性
        this.PromiseState = 'pending';
        this.PromiseResult = null;
        //声明属性
        this.callbacks = [];
        //保存实例对象的 this 的值
        const self = this;// self _this that
        //resolve 函数
        function resolve(data){
            //判断状态
            if(self.PromiseState !== 'pending') return;
            //1. 修改对象的状态 (promiseState)
            self.PromiseState = 'fulfilled';// resolved
            //2. 设置对象结果值 (promiseResult)
            self.PromiseResult = data;
            //调用成功的回调函数
            setTimeout(() => {
                self.callbacks.forEach(item => {
                    item.onResolved(data);
                });
            });
        }
        //reject 函数
        function reject(data){
            //判断状态
            if(self.PromiseState !== 'pending') return;
            //1. 修改对象的状态 (promiseState)
            self.PromiseState = 'rejected';// 
            //2. 设置对象结果值 (promiseResult)
            self.PromiseResult = data;
            //执行失败的回调
            setTimeout(() => {
                self.callbacks.forEach(item => {
                    item.onRejected(data);
                });
            });
        }
        try{
            //同步调用『执行器函数』
            executor(resolve, reject);
        }catch(e){
            //修改 promise 对象状态为『失败』
            reject(e);
        }
    }

    //then 方法封装
    then(onResolved,onRejected){
        const self = this;
        //判断回调函数参数
        if(typeof onRejected !== 'function'){
            onRejected = reason => {
                throw reason;
            }
        }
        if(typeof onResolved !== 'function'){
            onResolved = value => value;
            //value => { return value};
        }
        return new Promise((resolve, reject) => {
            //封装函数
            function callback(type){
                try{
                    //获取回调函数的执行结果
                    let result = type(self.PromiseResult);
                    //判断
                    if(result instanceof Promise){
                        //如果是 Promise 类型的对象
                        result.then(v => {
                            resolve(v);
                        }, r=>{
                            reject(r);
                        })
                    }else{
                        //结果的对象状态为『成功』
                        resolve(result);
                    }
                }catch(e){
                    reject(e);
                }
            }
            //调用回调函数  PromiseState
            if(this.PromiseState === 'fulfilled'){
                setTimeout(() => {
                    callback(onResolved);
                });
            }
            if(this.PromiseState === 'rejected'){
                setTimeout(() => {
                    callback(onRejected);
                });
            }
            //判断 pending 状态
            if(this.PromiseState === 'pending'){
                //保存回调函数
                this.callbacks.push({
                    onResolved: function(){
                        callback(onResolved);
                    },
                    onRejected: function(){
                        callback(onRejected);
                    }
                });
            }
        })
    }

    //catch 方法
    catch(onRejected){
        return this.then(undefined, onRejected);
    }

    //添加 resolve 方法
    static resolve(value){
        //返回promise对象
        return new Promise((resolve, reject) => {
            if(value instanceof Promise){
                value.then(v=>{
                    resolve(v);
                }, r=>{
                    reject(r);
                })
            }else{
                //状态设置为成功
                resolve(value);
            }
        });
    }

    //添加 reject 方法
    static reject(reason){
        return new Promise((resolve, reject)=>{
            reject(reason);
        });
    }

    //添加 all 方法
    static all(promises){
        //返回结果为promise对象
        return new Promise((resolve, reject) => {
            //声明变量
            let count = 0;
            let arr = [];
            //遍历
            for(let i=0;i<promises.length;i++){
                //
                promises[i].then(v => {
                    //得知对象的状态是成功
                    //每个promise对象 都成功
                    count++;
                    //将当前promise对象成功的结果 存入到数组中
                    arr[i] = v;
                    //判断
                    if(count === promises.length){
                        //修改状态
                        resolve(arr);
                    }
                }, r => {
                    reject(r);
                });
            }
        });
    }

    //添加 race 方法
    static race (promises){
        return new Promise((resolve, reject) => {
            for(let i=0;i<promises.length;i++){
                promises[i].then(v => {
                    //修改返回对象的状态为 『成功』
                    resolve(v);
                },r=>{
                    //修改返回对象的状态为 『失败』
                    reject(r);
                })
            }
        });
    }
} 

promise.all限制并发数

Promise.asyncStep = function (promises) {
  return new Promise((resolve, reject) => {
    let index = 0
    let stepCount = 10 // 并行多个
    let result = []
    let count = promise.length
    if (promise.length === 0) {
        resolve(result)
    } else {
        function runPromise () {
          if (index === promise.length) {
              resolve(result);
          } else {
              stepCount = Math.min(count, stepCount)
              for (let i = 0; i < stepCount; i++) {
                    --count;
                    Promise.resolve(promise[index]).then(data => {
                          result[index] = data;
                          ++index;
                    }, err => {
                      reject(err)
                    });
              }
              runPromise();
           }
        }
        runPromise()
      }
    })
}

冒泡排序

image.png

时间复杂度:O(N^2);空间复杂度:O(1)

function BubbleSort(arr) {
    if(arr == null || arr.length <= 0){
        return [];
    }
    var len = arr.length;
    for(var end = len - 1; end > 0; end--){
        for(var i = 0; i < end; i++) {
            if(arr[i] > arr[i + 1]){
                swap(arr, i, i + 1);
            }
        }
    }
    return arr;
}
//这里必须传arr,否则是传值
function swap(arr, i, j){
    // var temp = arr[i];
    // arr[i] = arr[j];
    // arr[j] = temp;
    //交换也可以用异或运算符
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

插入排序

插入排序思路:将一个新的数,插入已排好序数组中;时间复杂度:O(N^2);空间复杂度:O(1)

//利用哨兵
var InsertSort=function(array){
            if(array.length===0) return []
            let len=array.length
            for(let i=1;i<len;i++){
                if(array[i]<array[i-1]){
                    var tmp=array[i]
                    for(var j=i-1;tmp<array[j];j--){
                        array[j+1]=array[j]
                    }
                    array[j+1]=tmp
                }
            }
            return array
        }
function insertSort(arr) {
    if(arr == null  || arr.length <= 0){
        return [];
    }
    var len = arr.length;
    for(var i = 1; i < len; i++) {
        for(var j = i - 1; arr[j] > arr[j + 1]; j--) {
            swap(arr, j, j + 1);
        }
    }
    return arr;
}
 
function swap(arr, i, j){
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

选择排序

选择排序的实现思路:遍历数组,选择最小的数加入已排好序数组;时间复杂度:O(N^2);空间复杂度:O(1)

function SelectionSort(arr) {
    if(arr == null || arr.length < 0) {
        return [];
    }
    for(var i = 0; i < arr.length - 1; i++) {
        var minIndex = i;
        for(var j = i + 1; j < arr.length; j++) {
            minIndex = arr[j] < arr[minIndex] ? j : minIndex;
        }
        swap(arr, i, minIndex);
    }
    return arr;
}
 
function swap(arr, i, j) {
  if (i === j) {
        return;
    }
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

归并排序

归并排序的思路:

1.先左侧部分排好序

2.再右侧部分排好序

3.准备一个辅助数组,用外排的方式,小的开始填,直到有个结束,将另一个数组剩余部分拷贝到末尾

4.再将辅助数组拷贝回原数组

// 递归实现
 
function mergeSort(arr){
    if(arr == null  || arr.length <= 0){
        return [];
    }
    sortProcess(arr, 0, arr.length - 1);
    return arr;
}
 
function sortProcess(arr, L, R){
    //递归的终止条件,就是左右边界索引一样
    if(L == R){
        return;
    }
    var middle = Math.floor((L+R)/2);//找出中间值
    sortProcess(arr, L, middle);//对左侧部分进行递归
    sortProcess(arr, middle + 1, R);//对右侧部分进行递归
    merge(arr, L, middle, R);//然后利用外排方式进行结合
}
 
function merge(arr, L, middle, R){
    var help = [];
    var l = L;
    var r = middle + 1;
    //利用外排方式进行
    while(l <= middle && r <= R){
        help.push(arr[l] < arr[r] ? arr[l++] : arr[r++])
    }
    while(l <= middle){
        help.push(arr[l++]);
    }
    while(r <= R){
        help.push(arr[r++]);
    }
 
    //for(var i = 0; i < help.length; i++) {
        //arr[L + i] = help[i];
    //}
    //arr=[...help] 这样不行
    arr.splice(L, help.length, ...help);//这个利用了ES6的语法
}

快速排序

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>
      
        <script type="text/javascript">
            const _quickSort = array => {
                if(array.length <= 1) return array
                var pivotIndex = Math.floor(array.length / 2)
                var pivot = array.splice(pivotIndex, 1)[0]
                var left = []
                var right = []
                for (var i=0 ; i<array.length ; i++){
                    if (array[i] < pivot) {
                        left.push(array[i])
                    } else {
                        right.push(array[i])
                    }
                }
                return _quickSort(left).concat([pivot], _quickSort(right))
            }

        </script>
    </body>
</html>

堆排序

堆排序思路:

1.让数组变成大根堆

2.把最后一个位置和堆顶做交换

3.则最大值在最后,则剩下部分做heapify,则重新调整为大根堆,则堆顶位置和该部分最后位置做交换

4.重复进行,直到减完,则这样最后就调整完毕,整个数组排完序(为一个升序)

时间复杂度:O(N * logN) 空间复杂度:O(1)

function heapSort(arr) {
    if(arr == null || arr.length <= 0) {
        return [];
    }
 
    //首先是建立大顶堆的过程
    for(var i = 0; i < arr.length; i++) {
        heapInsert(arr, i);
    }
    var size = arr.length;//这个值用来指定多少个数组成堆,当得到一个排序的值后这个值减一
    //将堆顶和最后一个位置交换
    /**
     * 当大顶堆建立完成后,然后不断将最后一个位置和堆顶交换;
     * 这样最大值就到了最后,则剩下部分做heapify,重新调整为大根堆,则堆顶位置和倒数第二个位置交换,重复进行,直到全部排序完毕*/
    //由于前面已经是大顶堆,所以直接交换
    swap(arr, 0, --size);
    while(size > 0) {
        //重新变成大顶堆
        heapify(arr, 0, size);
        //进行交换
        swap(arr, 0, --size);
    }
}
 
//加堆过程中
function heapInsert(arr, index) {
    //比较当前位置和其父位置,若大于其父位置,则进行交换,并将索引移动到其父位置进行循环,否则跳过
    //结束条件是比父位置小或者到达根节点处
    while(arr[index] > arr[parseInt((index - 1) / 2)]){
        //进行交换
        swap(arr, index, parseInt((index - 1) / 2));
        index = parseInt((index - 1) / 2);
    }
}
//减堆过程
/**
 * size指的是这个数组前多少个数构成一个堆
 * 如果你想把堆顶弹出,则把堆顶和最后一个数交换,把size减1,然后从0位置经历一次heapify,调整一下,剩余部分变成大顶堆*/
function heapify(arr, index, size) {
    var left = 2 * index + 1;
    while(left < size) {
        var largest = (left + 1 < size && arr[left] < arr[left + 1]) ? left + 1 : left;
        largest = arr[index] > arr[largest] ? index : largest;
 
        //如果最大值索引和传进来索引一样,则该值到达指定位置,直接结束循环
        if(index == largest) {
            break;
        }
 
        //进行交换,并改变索引和其左子节点
        swap(arr, index, largest);
        index = largest;
        left = 2 * index + 1;
    }
}
 
function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

桶排序

桶排序会经历三次遍历:准备一个数组、遍历一遍数组、重构一遍数组,是非基于比较的排序,下面以一个问题来阐述其思路。

**问题:**给定一个数组,求如果排序之后,相邻两个数的最大差值,要求时间复杂度O(N),且要求不能用基于比较的排序

思路:

1.准备桶:数组中有N个数就准备N+1个桶

2.遍历一遍数组,找到最大值max和最小值min 。若min = max,则差值=0;若min≠max,则最小值放在0号桶,最大值放在N号桶,剩下的数属于哪个范围就进哪个桶

3.根据鸽笼原理,则肯定有一个桶为空桶,设计该桶的目的是为了否定最大值在一个桶中,则最大差值的两个数一定来自于两个桶,但空桶两侧并不一定是最大值

4.所以只记录所有进入该桶的最小值min和最大值max和一个布尔值表示该桶有没有值

5.然后遍历这个数组,如果桶是空的,则跳到下一个数,如果桶非空,则找前一个非空桶,则最大差值=当前桶min - 上一个非空桶max,用全局变量更新最大值

时间复杂度:O(N)空间复杂度:O(N)

function maxGap(arr) {
    if(arr == null || arr.length <= 0) {
        return 0;
    }
    var len = arr.length;
    var max = -Infinity, min = Infinity;
    //遍历一遍数组,找到最大值max和最小值min
    for(var i = 0; i < len; i++) {
        max = max > arr[i] ? max : arr[i];
        min = min > arr[i] ? arr[i] : min;
    }
 
    //若min = max,则差值为0;
    if(min == max) {
        return 0;
    }
 
    var hasNum = new Array(len + 1);
    var mins = new Array(len + 1);
    var maxs = new Array(len + 1);
 
    var bid = 0;//指定桶的编号
 
    for(var i = 0; i < len; i++) {
        bid = bucket(arr[i], min, max, len);//获得该值是在哪个桶//由于有N+1个桶,所以间隔就是N个,所以此处除以的是len,然后通过这个函数得到应该放到哪个桶里
        maxs[bid] = hasNum[bid] ? Math.max(arr[i], maxs[bid]) : arr[i];
        mins[bid] = hasNum[bid] ? Math.min(arr[i], mins[bid]) : arr[i];
        hasNum[bid] = true;
    }
 
    var res = 0;
    var lastMax = maxs[0];
 
    for(var i = 0; i < len + 1; i++) {
        if(hasNum[i]) {
            res = Math.max(mins[i] - lastMax, res);
            lastMax = maxs[i];
        }
    }
    return res;
}
 
//获得桶号
//这个函数用于判断在哪个桶中,参数分别为值、最小值、最大值、桶间隔
function bucket(value, min, max, len) {
    return parseInt((value - min) / ((max - min) / len));
}

全排列

‘abc’的全排列等于 ('a'拼接上'bc'的全排列数组中的每一项) + ('b'拼接上'ac'的全排列数组的每一项) + ('c'拼接上'ab'的全排列数组的每一项)

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
    </head>
    <body>
      
        <script type="text/javascript">
            // 补全代码
            const _permute = string => {
                if(string.length === 1) {
                    return [string]
                }
                const results = []
                for(let s of string){
                    const arr = string.split('').filter(str =>str !== s)
                    _permute(arr.join('')).forEach(item => {
                        results.push(s + item)
                    })
                }
                return results
            }

        </script>
    </body>
</html>

寄生组合式继承

function Human(name) {
    this.name = name
    this.kingdom = 'animal'
    this.color = ['yellow', 'white', 'brown', 'black']
}
Human.prototype.getName=function(){
    return this.name
}

function Chinese(name,age) {
    Human.call(this,name)
    this.age=age
    this.color = 'yellow'
}
Chinese.prototype=new Human()
Chinese.prototype.constructor=Chinese
Chinese.prototype.getAge=function(){
    return this.age
}
function inherit(parent,child){
  function F(){}
  F.prototype=parent.prototype
  child.prototype=new F()
  //这么写也行 child.prototype=Object.creat(parent.prototype)
  child.prototype.constructor=child
}
function Parent(name){
  this.name="doing"
  this.age="18"
}
Parent.prototype.getName=function(){
  console.log(this.name)
}
function Child(name,age){
  Parent.call(this,name)
  this.age="5"
  this.friend=["jack"]
}
Child.prototype.getFriend=function(){
  console.log(this.friend)
}
inherit(parent,child)
let child=new Child()

发布订阅模式

class EventEmitter {
    // 补全代码
    constructor(){
        //记录当前被订阅的事件
        this.event = {};
    }
    //订阅事件
    on(e,fn){
        //是新事件
        if(!this.event[e]){
            this.event[e] = [fn];
        }else{
            //旧事件添加新方法
            this.event[e].push(fn);
        }
    }
    //触发事件
    emit(e){
        if(this.event[e]){
            this.event[e].forEach( fun =>fun());

        }
    }
}

闭包实现单例模式

var SingleTon = function(){
    var instance;
    class CreateSingleTon {
        constructor (name) {
            if(instance) return instance;
            this.name = name;
            this.getName();
            return instance = this;
        }

        getName() {
            return this.name;
        }
    }
    return CreateSingleTon;
}();

var a = new SingleTon('instance1');
console.log(a.getName()); //输出instance1
var b = new SingleTon('instance2');
console.log(b.getName()); //输出instance1
console.log(a === b); //输出true