前端面试自用总结--代码手写题

118 阅读14分钟
模拟Object.create
function create(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}
手写instanceof方法
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
实现步骤:
首先获取类型的原型
然后获得对象的原型
然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left), //获取对象的原型
      prototype = right.prototype; // 获取构造函数的 prototype 对象

  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
}
手写类型判断
function getType(value) {
  // 判断数据是 null 的情况
  if (value === null) {
    return value + "";
  }
  // 判断数据是引用类型的情况
  if (typeof value === "object") {
    let valueClass = Object.prototype.toString.call(value),
      type = valueClass.split(" ")[1].split("");
    type.pop();
    return type.join("").toLowerCase();
  } else {
    // 判断数据是基本数据类型的情况和函数的情况
    return typeof value;
  }
}
手写new操作符
function MyNew() {\
    // 获取到形参列表,拿到构造函数以及对应参数\
    let [ArgFun, ...arg] = [...arguments]\
    // 定义一个空的对象\
    let obj = {};\
    // 原型指向\
    obj.__proto__ = ArgFun.prototype;\
    // 设置this指向\
    let tartgetObj = ArgFun.apply(obj, arg)\
    // 通过apply调用构造函数(改变this指向到obj)\
    return tartgetObj instanceof Object == true ? tartgetObj : obj\
  }
手写防抖
 // 函数防抖的实现
 function debounce(fn, wait) {    //(传入一个函数,停止多久再次触发)
   let timer = null;    //利用闭包,维护了私有变量
   return function() {      //要返回一个函数
     let context = this,
         args = arguments;    //传递参数
     // 如果此时存在定时器的话,则取消之前的定时器重新记时
     if (timer) {
       clearTimeout(timer);
       timer = null;
     }
     // 设置定时器,使事件间隔指定事件后执行
     timer = setTimeout(() => {     //timer记录这次触发所生成的定时器
       fn.apply(context, args);
     }, wait);
   };
 }
 ​
 //简化版
 function debounce(fn, wait) {    //(传入一个函数,停止多久再次触发)
   let timer = null;    //利用闭包
   return function(...args) {      //要返回一个函数
       clearTimeout(timer);
     timer = setTimeout(() => fn(...args), wait);
   };
 }
手写节流
// 函数节流的实现;
function throttle(fn, delay) {
  let curTime = Date.now(); //利用闭包,维护一个私有变量,记录前一次触发的时间
  return function() {        //返回一个函数
    let context = this,
        args = arguments,
    let nowTime = Date.now();    //记录本次触发时间
    // 如果两次时间间隔超过了指定时间,则执行函数。
    if (nowTime - curTime >= delay) {    //两次触发的时间差,如果大于间隔时间,就再次调用回调函数
      curTime = Date.now();     //更新触发时间
      return fn.apply(context, args);    //fn(...args);
    }
  };
}
手写bind、apply、call函数
call 函数的实现步骤:
// call函数实现
Function.prototype.myCall = function(thisArg, ...args) { 
    //thisArg fn 被调用时 this 指向的对象(this 要绑定的对象),...args 传递给 fn 的参数列表,这里使用了 ES6 的 rest 参数特性
    //将myCall方法挂载到Function.prototype,如此,所有的函数都可直接使用.语法找到 myCall 方法
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  const fnName = Symbol();  //将表示独一无二的值赋值给 thisArg 的 fnName
  thisArg[fnName] = this;   //这里的 this 将指向调用 myCall 方法的函数 
  const res = thisArg[fnName](...args);  //此时函数在执行时的 this 因为隐式绑定的规则便指向了 thisArg ,即实现了显示绑定
  delete thisArg[fnName];  //清除为 thisArg 对象带来的副作用
  return res;
};
apply 函数的实现步骤:
使用 apply ,仅支持两个参数,第一个为 thisArg,第二个为 一个包含多个参数的数组(或者类数组对对象)
使用 call,不限制参数个数,第一个为 thisArg,其余为 参数列表
即 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组(或者类数组对对象)
// apply 函数实现
Function.prototype.myApply = function(thisArg, args) {	// 仅传递参数不同
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  const fnName = Symbol();
  thisArg[fnName] = this;
  const res = thisArg[fnName](...args);
  delete thisArg[fnName];
  return res;
};
bind 函数的实现步骤:
一般情况下,可以认为 bind 方法与 call 方法几乎一致,只是 function.call(thisArg, ...) 会立即执行 function 函数,而 function.bind(thisArg, ...) 并不会立即执行,而是返回一个新的绑定函数。

// bind 函数实现
Function.prototype.myBind = function(thisArg, ...args1) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  const fn = this;  //使用闭包,在返回的新函数中获取到 function
  return function(...args2) {  //返回一个绑定函数
    const fnName = Symbol();
    thisArg[fnName] = fn;
    // 1. args1:调用 `bind` 方法传递的参数 , 2. args2:执行返回的绑定函数时传递的参数 
    const res = thisArg[fnName](...args1, ...args2);
    delete thisArg[fnName];
    return res;
  }
};

//利用 call 方法快速实现 bind 方法
Function.prototype.myBindPerfect = function(thisArg, ...args1) {
  const fn = this;
    //当返回的绑定函数作为构造函数时忽略 thisArg
    return function BindedFn (...args2) {
    if (new.target === BindedFn) {
      return fn(...args1, ...args2);
    };  
    //利用call方法实现this指向的绑定以及参数的传递
      return fn.call(thisArg, ...args1, ...args2)
  };
};
实现AJAX请求
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
  if (this.readyState !== 4) return;
  // 当请求成功时
  if (this.status === 200) {
    handle(this.response);
  } else {
    console.error(this.statusText);
  }
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
  console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);
使用promise封装AJAX请求
// promise 封装实现:
function getJSON(url) {
  // 创建一个 promise 对象
  let promise = new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest();
    // 新建一个 http 请求
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;
      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    // 设置错误监听函数
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 请求
    xhr.send(null);
  });
  return promise;
}
实现深拷贝
1._.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

2.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

3.JSON.stringify()
const obj2=JSON.parse(JSON.stringify(obj1));
但是这种方式存在弊端,会忽略undefined、symbol和函数
const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

4循环递归
function deepClone(obj={}, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);// 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;// 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
    
  let result;
  if(obj instanceof Array){    //定义result的类型
      result=[];
  }else{
      result={};
  }
  for(let key in obj){
      result[key]=deepClone(obj[key]);//进行递归,进行深层的深拷贝(属性内部的属性)
  }
  return result;
}
const newObj=deepClone(oldObj);
实现浅拷贝
1.Object.assign
const obj = {
  name: 'lin'
}
const newObj = Object.assign({}, obj)
obj.name = 'xxx' // 改变原来的对象
console.log(newObj) // { name: 'lin' } 新对象不变
console.log(obj == newObj) // false 两者指向不同地址

2.数组的 slice 和 concat 方法
const arr = ['lin', 'is', 'handsome']
const newArr = arr.slice(0)
arr[2] = 'rich' // 改变原来的数组
console.log(newArr) // ['lin', 'is', 'handsome']
console.log(arr == newArr) // false 两者指向不同地址

const arr = ['lin', 'is', 'handsome']
const newArr = [].concat(arr)
arr[2] = 'rich' // 改变原来的数组
console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变
console.log(arr == newArr) // false 两者指向不同地址

3.数组静态方法 Array.from
const arr = ['lin', 'is', 'handsome']
const newArr = Array.from(arr)
arr[2] = 'rich' // 改变原来的数组
console.log(newArr) // ['lin', 'is', 'handsome']
console.log(arr == newArr) // false 两者指向不同地址

4.扩展运算符
const arr = ['lin', 'is', 'handsome']
const newArr = [...arr]
arr[2] = 'rich' // 改变原来的数组
console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变
console.log(arr == newArr) // false 两者指向不同地址

const obj = {
  name: 'lin'
}
const newObj = { ...obj }
obj.name = 'xxx' // 改变原来的对象
console.log(newObj) // { name: 'lin' } // 新对象不变
console.log(obj == newObj) // false 两者指向不同地址
柯里化
只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
function curry(func) {
  return function curried(...args) {
    // 关键知识点:function.length 用来获取函数的形参个数
    // 补充:arguments.length 获取的是实参个数
    if (args.length >= func.length) {
      return func.apply(this, args)
    }
    return function (...args2) {
      return curried.apply(this, args.concat(args2))
    }
  }
}

// 测试
function sum (a, b, c) {
  return a + b + c
}
const curriedSum = curry(sum)
console.log(curriedSum(1, 2, 3))
console.log(curriedSum(1)(2,3))
console.log(curriedSum(1)(2)(3))

函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
function curry(fn, args) {
  // 获取函数需要的参数长度
  let length = fn.length;
  args = args || [];
  return function() {
    let subArgs = args.slice(0);
    // 拼接得到现有的所有参数
    for (let i = 0; i < arguments.length; i++) {
      subArgs.push(arguments[i]);
    }
    // 判断参数的长度是否已经满足函数所需参数的长度
    if (subArgs.length >= length) {
      // 如果满足,执行函数
      return fn.apply(this, subArgs);
    } else {
      // 如果不满足,递归返回科里化的函数,等待参数的传入
      return curry.call(this, fn, subArgs);
    }
  };
}

// es6 实现
function curry(fn, ...args) {
  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
手写promise
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保存初始化状态
  var self = this;
  // 初始化状态
  this.state = PENDING;
  // 用于保存 resolve 或者 rejected 传入的值
  this.value = null;
  // 用于保存 resolve 的回调函数
  this.resolvedCallbacks = [];
  // 用于保存 reject 的回调函数
  this.rejectedCallbacks = [];
  // 状态转变为 resolved 方法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变,
      if (self.state === PENDING) {
        // 修改状态
        self.state = RESOLVED;
        // 设置传入的值
        self.value = value;
        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }
  // 状态转变为 rejected 方法
  function reject(value) {
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变
      if (self.state === PENDING) {
        // 修改状态
        self.state = REJECTED;
        // 设置传入的值
        self.value = value;
        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }
  // 将两个方法传入函数执行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到错误时,捕获错误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {
          return value;
        };
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {
          throw error;
        };
  // 如果是等待状态,则将函数加入对应列表中
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }
  // 如果状态已经凝固,则直接执行对应状态的函数
  if (this.state === RESOLVED) {
    onResolved(this.value);
  }
  if (this.state === REJECTED) {
    onRejected(this.value);
  }
};
手写promise.all
接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
这个方法返回一个新的 promise 对象,
遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
参数所有回调成功才是成功,返回值数组与参数顺序一致
参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。

function promiseAll(promises) {
  return new Promise(function(resolve, reject) {
    if(!Array.isArray(promises)){
        throw new TypeError(`argument must be a array`)
    }
    var resolvedCounter = 0;
    var promiseNum = promises.length;
    var resolvedResult = [];
    for (let i = 0; i < promiseNum; i++) {
      Promise.resolve(promises[i]).then(value=>{
        resolvedCounter++;
        resolvedResult[i] = value;
        if (resolvedCounter == promiseNum) {
            return resolve(resolvedResult)
          }
      },error=>{
        return reject(error)
      })
    }
  })
}
// test
let p1 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(1)
    }, 1000)
})
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(2)
    }, 2000)
})
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(3)
    }, 3000)
})
promiseAll([p3, p1, p2]).then(res => {
    console.log(res) // [3, 1, 2]
})
手写promise.race
Promise.race = function (args) {
  return new Promise((resolve, reject) => {
    for (let i = 0, len = args.length; i < len; i++) {
      args[i].then(resolve, reject)
    }
  })
}
手写promise.then
 then方法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,我们使用一个 callbacks 数组先把传给then的函数暂存起来,等状态改变时再调用。
 那么,怎么保证后一个 then里的方法在前一个 then(可能是异步)结束之后再执行呢?
 我们可以将传给 then 的函数和新 promise 的 resolve 一起 push 到前一个 promise 的 callbacks 数组中,达到承前启后的效果
 then(onFulfilled, onReject){
     // 保存前一个promise的this
     const self = this; 
     return new MyPromise((resolve, reject) => {
       // 封装前一个promise成功时执行的函数
       let fulfilled = () => {
         try{
           const result = onFulfilled(self.value); // 承前
           return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后
         }catch(err){
           reject(err)
         }
       }
       // 封装前一个promise失败时执行的函数
       let rejected = () => {
         try{
           const result = onReject(self.reason);
           return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
         }catch(err){
           reject(err)
         }
       }
       switch(self.status){
         case PENDING: 
           self.onFulfilledCallbacks.push(fulfilled);
           self.onRejectedCallbacks.push(rejected);
           break;
         case FULFILLED:
           fulfilled();
           break;
         case REJECT:
           rejected();
           break;
       }
     })
    }
用Promise实现图片的异步加载
 let imageAsync=(url)=>{
             return new Promise((resolve,reject)=>{
                 let img = new Image();
                 img.src = url;
                 img.οnlοad=()=>{
                     console.log(`图片请求成功,此处进行通用操作`);
                     resolve(image);
                 }
                 img.οnerrοr=(err)=>{
                     console.log(`失败,此处进行失败的通用操作`);
                     reject(err);
                 }
             })
         }
         
 imageAsync("url").then(()=>{
     console.log("加载成功");
 }).catch((error)=>{
     console.log("加载失败");
 })
实现发布-订阅模式
 class EventCenter{
   // 1. 定义事件容器,用来装事件数组
     let handlers = {}
   // 2. 添加事件方法,参数:事件名 事件方法
   addEventListener(type, handler) {
     // 创建新数组容器
     if (!this.handlers[type]) {
       this.handlers[type] = []
     }
     // 存入事件
     this.handlers[type].push(handler)
   }
   // 3. 触发事件,参数:事件名 事件参数
   dispatchEvent(type, params) {
     // 若没有注册该事件则抛出错误
     if (!this.handlers[type]) {
       return new Error('该事件未注册')
     }
     // 触发事件
     this.handlers[type].forEach(handler => {
       handler(...params)
     })
   }
   // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布
   removeEventListener(type, handler) {
     if (!this.handlers[type]) {
       return new Error('事件无效')
     }
     if (!handler) {
       // 移除事件
       delete this.handlers[type]
     } else {
       const index = this.handlers[type].findIndex(el => el === handler)
       if (index === -1) {
         return new Error('无该绑定事件')
       }
       // 移除事件
       this.handlers[type].splice(index, 1)
       if (this.handlers[type].length === 0) {
         delete this.handlers[type]
       }
     }
   }
 }
实现prototype(原型链)继承
//父方法
function SupperFunction(flag1){
    this.flag1 = flag1;
}
//子方法
function SubFunction(flag2){
    this.flag2 = flag2;
}
//父实例
var superInstance = new SupperFunction(true);
//子继承父
SubFunction.prototype = superInstance;
//子实例
var subInstance = new SubFunction(false);
//子调用自己和父的属性
subInstance.flag1;   // true
subInstance.flag2;   // false
实现双向数据绑定
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
  configurable: true,
  enumerable: true,
  get() {
    console.log('获取数据了')
  },
  set(newVal) {
    console.log('数据更新了')
    input.value = newVal
    span.innerHTML = newVal
  }
})
// 输入监听
input.addEventListener('keyup', function(e) {
  obj.text = e.target.value
})
实现简单路由
 // hash路由
 class Route{
   constructor(){
     // 路由存储对象
     this.routes = {}
     // 当前hash
     this.currentHash = ''
     // 绑定this,避免监听时this指向改变
     this.freshRoute = this.freshRoute.bind(this)
     // 监听
     window.addEventListener('load', this.freshRoute, false)
     window.addEventListener('hashchange', this.freshRoute, false)
   }
   // 存储
   storeRoute (path, cb) {
     this.routes[path] = cb || function () {}
   }
   // 更新
   freshRoute () {
     this.currentHash = location.hash.slice(1) || '/'
     this.routes[this.currentHash]()
   }
 }
实现斐波那契数列
 // 递归
 function fn (n){
     if(n==0) return 0
     if(n==1) return 1
     return fn(n-2)+fn(n-1)
 }
 // 优化
 function fibonacci2(n) {
     const arr = [1, 1, 2];
     const arrLen = arr.length;
     if (n <= arrLen) {
         return arr[n];
     }
     for (let i = arrLen; i < n; i++) {
         arr.push(arr[i - 1] + arr[ i - 2]);
     }
     return arr[arr.length - 1];
 }
 // 非递归
 function fn(n) {
     let pre1 = 1;
     let pre2 = 1;
     let current = 2;
     if (n <= 2) {
         return current;
     }
     for (let i = 2; i < n; i++) {
         pre1 = pre2;
         pre2 = current;
         current = pre1 + pre2;
     }
     return current;
 }
图片懒加载
 // <img src="default.png" data-src="https://xxxx/real.png">
 function isVisible(el) {
   const position = el.getBoundingClientRect()
   const windowHeight = document.documentElement.clientHeight
   // 顶部边缘可见
   const topVisible = position.top > 0 && position.top < windowHeight;
   // 底部边缘可见
   const bottomVisible = position.bottom < windowHeight && position.bottom > 0;
   return topVisible || bottomVisible;
 }
 function imageLazyLoad() {
   const images = document.querySelectorAll('img')
   for (let img of images) {
     const realSrc = img.dataset.src
     if (!realSrc) continue
     if (isVisible(img)) {
       img.src = realSrc
       img.dataset.src = ''
     }
   }
 }
 // 测试
 window.addEventListener('load', imageLazyLoad)
 window.addEventListener('scroll', imageLazyLoad)
 // or
 window.addEventListener('scroll', throttle(imageLazyLoad, 1000))
实现数组去重
 //ES6方法(使用数据结构集合):
 const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
 Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
 ​
 //ES5方法:使用map存储不重复的数字
 const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
 uniqueArray(array); // [1, 2, 3, 5, 9, 8]
 function uniqueArray(array) {
   let map = {};
   let res = [];
   for(var i = 0; i < array.length; i++) {
     if(!map.hasOwnProperty([array[i]])) {
       map[array[i]] = 1;
       res.push(array[i]);
     }
   }
   return res;
 }
 ​
 //使用filter
 const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
 const arr=array=>{
     return array.filter((item,index)=>{
         return array.indexOf(item)===index;
     })
 }
实现数组扁平化
 (1)递归实现
 普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:
 let arr = [1, [2, [3, 4, 5]]];
 function flatten(arr) {
   let result = [];
   for(let i = 0; i < arr.length; i++) {
     if(Array.isArray(arr[i])) {
       result = result.concat(flatten(arr[i]));
     } else {
       result.push(arr[i]);
     }
   }
   return result;
 }
 flatten(arr);  //  [1, 2, 3, 4,5]
 (2)reduce 函数迭代
 从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce 来实现数组的拼接,从而简化第一种方法的代码,改造后的代码如下所示:
 let arr = [1, [2, [3, 4]]];
 function flatten(arr) {
     return arr.reduce(function(prev, next){
         return prev.concat(Array.isArray(next) ? flatten(next) : next)
     }, [])
 }
 console.log(flatten(arr));//  [1, 2, 3, 4,5]
 (3)扩展运算符实现
 这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的:
 let arr = [1, [2, [3, 4]]];
 function flatten(arr) {
     while (arr.some(item => Array.isArray(item))) {
         arr = [].concat(...arr);
     }
     return arr;
 }
 console.log(flatten(arr)); //  [1, 2, 3, 4,5]
 (4)split 和 toString
 可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示:
 let arr = [1, [2, [3, 4]]];
 function flatten(arr) {
     return arr.toString().split(',');
 }
 console.log(flatten(arr)); //  [1, 2, 3, 4,5]
 通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。
 (5)ES6 中的 flat
 我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])
 其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:
 let arr = [1, [2, [3, 4]]];
 function flatten(arr) {
   return arr.flat(Infinity);
 }
 console.log(flatten(arr)); //  [1, 2, 3, 4,5]
 可以看出,一个嵌套了两层的数组,通过将 flat 方法的参数设置为 Infinity,达到了我们预期的效果。其实同样也可以设置成 2,也能实现这样的效果。在编程过程中,如果数组的嵌套层数不确定,最好直接使用 Infinity,可以达到扁平化。
 (6)正则和 JSON 方法
 在第4种方法中已经使用 toString 方法,其中仍然采用了将 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组:
 let arr = [1, [2, [3, [4, 5]]], 6];
 function flatten(arr) {
   let str = JSON.stringify(arr);
   str = str.replace(/([|])/g, '');
   str = '[' + str + ']';
   return JSON.parse(str); 
 }
 console.log(flatten(arr)); //  [1, 2, 3, 4,5]
实现数组乱序输出
 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行交换。第二次取出数据数组第二个元素,随机产生一个除了索引为1的之外的索引值,并将第二个元素与该索引值对应的元素进行交换。按照上面的规律执行,直到遍历完成
 var arr = [1,2,3,4,5,6,7,8,9,10];
 for (var i = 0; i < arr.length; i++) {
   const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
   [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
 }
 console.log(arr)
 ​
 倒序遍历:
 var arr = [1,2,3,4,5,6,7,8,9,10];
 let length = arr.length,
     randomIndex,
     temp;
   while (length) {
     randomIndex = Math.floor(Math.random() * length--);
     temp = arr[length];
     arr[length] = arr[randomIndex];
     arr[randomIndex] = temp;
   }
 console.log(arr)
实现类数组转化为数组
 通过 call 调用数组的 slice 方法来实现转换
 Array.prototype.slice.call(arrayLike);
 ​
 通过 call 调用数组的 splice 方法来实现转换
 Array.prototype.splice.call(arrayLike, 0);
 ​
 通过 apply 调用数组的 concat 方法来实现转换
 Array.prototype.concat.apply([], arrayLike);
 ​
 通过 Array.from 方法来实现转换
 Array.from(arrayLike);
实现字符串翻转
 String.prototype._reverse = function(a){
     return a.split("").reverse().join("");
 }
 var obj = new String();
 var res = obj._reverse ('hello');
 console.log(res);    // olleh
将js对象化为树形结构
 function jsonToTree(data) {
   // 初始化结果数组,并判断输入数据的格式
   let result = []
   if(!Array.isArray(data)) {
     return result
   }
   // 使用map,将当前对象的id与当前对象对应存储起来
   let map = {};
   data.forEach(item => {
     map[item.id] = item;
   });
   // 
   data.forEach(item => {
     let parent = map[item.pid];
     if(parent) {
       (parent.children || (parent.children = [])).push(item);
     } else {
       result.push(item);
     }
   });
   return result;
 }
排序算法
 //冒泡排序
 const arr = [5, 2, 7, 8, 34, 7, 39, 12, 56, 9, 1]
   function bubbleSort(arr) {
     const len = arr.length
     // 外层循环i控制比较的轮数
     for (let i = 0; i < len; i++) {
       // 里层循环控制每一轮比较的次数j,arr[i] 只用跟其余的len - i个元素比较
       for (let j = 1; j < len - i; j++) {
         // 若前一个元素"大于"后一个元素,则两者交换位置
         if (arr[j - 1] > arr[j]) {
           [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
         }
       }
     }
     return arr
   }
   console.log(bubbleSort(arr))  // [1, 2,  5,  7,  7, 8, 9, 12, 34, 39, 56]
   
 //改进的冒泡排序法:
 function bubbleSort(arr) {
     let temp = null, flag = 1
     const len = arr.length
     for (let i = 0; i <= len - 1 && flag === 1; i++) {
       flag = 0
       for (let j = 0; j < len - i; j++) {
         if (arr[j] > arr[j + 1]) {
           temp = arr[j + 1]
           arr[j + 1] = arr[j]
           arr[j] = temp
           flag = 1// 发生数据交换flag置为1
         }
       }
     }
     return arr
   }
//插入排序
const arr = [5, 2, 7, 8, 34, 7, 39, 12, 56, 9, 1]

  function insertSort(arr) {
    const handle = [arr[0]], len = arr.length
    for (let i = 1; i <= len - 1; i++) {
      const current = arr[i]
      for (var j = handle.length - 1; j >= 0; j--) {
        if (current > handle[j]) {
          handle.splice(j + 1, 0, current)
          break
        }
        if (j === 0) {
          handle.unshift(current)
        }
      }
    }
    return handle
  }
  console.log(insertSort(arr))	// [1, 2,  5,  7,  7, 8, 9, 12, 34, 39, 56]

//优化版:将arr[]按升序排列,插入排序法
  function insertSort(arr) {
    for (let i = 1; i < arr.length; i++) {
      //将arr[i]插入到arr[i-1],arr[i-2],arr[i-3]……之中
      for (let j = i; j > 0; j--) {
        if (arr[j] < arr[j - 1]) {
          [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
        }
      }
    }
    return arr
  }
//快速排序
 function quickSort(arr) {
  if(arr.length < 2) {
    return arr;
  } else {
    const pivot = arr[0]; // 基准值
    const pivotArr = []; // 一样大的放中间
    const lowArr= []; // 小的放左边
    const hightArr = []; // 大的放右边
    arr.forEach(current => {
      if(current === pivot) pivotArr.push(current);
      else if(current > pivot) hightArr.push(current);
      else lowArr.push(current);
    })
    return quickSort(lowArr).concat(pivotArr).concat(quickSort(hightArr));
  }
}

\