你可能会遇到的手写js代码

408 阅读8分钟

js 判断两个json是否一样

需要判断json中的数据类型,拆解对象的嵌套情况。

        function isEqual() {
            var arg = getArg(arguments);
            var o1 = arg["object"][0] || [];
            var o2 = arg["object"][1] || [];
            if (!arg["object"].length) {
                return (o1 == o2);
            }
            var isEqualForInner = function (obj1, obj2) {
                var o1 = obj1 instanceof Object;
                var o2 = obj2 instanceof Object;
                if (!o1 || !o2) {
                    return obj1 === obj2;
                }
                if (Object.keys(obj1).length !== Object.keys(obj2).length) {
                    return false;
                }
                for (var attr in obj1) {
                    var t1 = obj1[attr] instanceof Object;
                    var t2 = obj2[attr] instanceof Object;
                    if (t1 && t2) {
                        return isEqualForInner(obj1[attr], obj2[attr]);
                    } else if (obj1[attr] !== obj2[attr]) {
                        return false;
                    }
                }
                return true;
            }
            function getArg(arg) {
                var ret = {
                    'number': [],
                    'string': [],
                    'boolean': [],
                    'object': [],
                    'Object': [],
                    'Array': [],
                    'function': [],
                    'Date': [],
                    'null': []
                }
                for (var i = 0, len = arg.length; i < len; i++) {
                    var ar = arg[i];
                    if (typeof (ar) == 'number') {
                        ret['number'].push(ar);
                    } else if (typeof (ar) == 'string') {
                        ret['string'].push(ar);
                    } else if (typeof (ar) == 'boolean') {
                        ret['boolean'].push(ar);
                    } else if (typeof (ar) == 'function') {
                        ret['function'].push(ar);
                    } else if (typeof (ar) == 'object') {
                        if (ar) {
                            if (ar.constructor == Object) {
                                ret['object'].push(ar);
                                ret['Object'].push(ar);
                            } else if (ar.constructor == Array) {
                                ret['object'].push(ar);
                                ret['Array'].push(ar);
                            } else if (ar.constructor == Date) {
                                ret['Date'].push(ar);
                            }
                        } else {
                            ret['null'].push(ar);
                        }
                    }
                }
                return ret;
            }
            return isEqualForInner(o1, o2);
        }

实现bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

        Function.prototype.my_bind = function () {
            var self = this, // 保存原函数
                context = Array.prototype.shift.call(arguments), // 保存需要绑定的this上下文
                // 上一行等价于 context = [].shift.call(arguments);
                args = Array.prototype.slice.call(arguments); // 剩余的参数转为数组
            return function () { // 返回一个新函数
                self.apply(context, Array.prototype.concat.call(args, Array.prototype.slice.call(arguments)));
            }
        }

实现call

// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.mycall = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('not funciton')
  }
  context = context || window
  context.fn = this
  let arg = [...arguments].slice(1)
  let result = context.fn(...arg)
  delete context.fn
  return result
}

实现apply

// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.myapply = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('not funciton')
  }
  context = context || window
  context.fn = this
  let result
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

实现new方法

/*
* 1.创建一个空对象
* 2.链接到原型
* 3.绑定this值
* 4.返回新对象
*/
// 第一种实现
function createNew() {
    let obj = {}  // 1.创建一个空对象

    let constructor = [].shift.call(arguments)  
    // let [constructor,...args] = [...arguments]  

    obj.__proto__ = constructor.prototype  // 2.链接到原型

    let result = constructor.apply(obj, arguments)  // 3.绑定this值
    // let result = constructor.apply(obj, args)   

    return typeof result === 'object' ? result : obj  // 4.返回新对象
}

function People(name,age) {
    this.name = name
    this.age = age
}

let peo = createNew(People,'Bob',22)
console.log(peo.name)
console.log(peo.age)

浅拷贝、深拷贝的实现

浅拷贝

// 1. ...实现
let copy1 = {...{x:1}}

// 2. Object.assign实现
let copy2 = Object.assign({}, {x:1})

深拷贝

// 1. JOSN.stringify()/JSON.parse()  
// 缺点:拷贝对象包含 正则表达式,函数,或者undefined等值会失败
let obj = {a: 1, b: {x: 3}}
JSON.parse(JSON.stringify(obj))

// 2. 递归拷贝
function deepClone(obj) {
  let copy = obj instanceof Array ? [] : {}
  for (let i in obj) {
    if (obj.hasOwnProperty(i)) {
      copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
    }
  }
  return copy
}

实现instanceof

// 思路:右边变量的原型存在于左边变量的原型链上
function instanceOf(left, right) {
  let leftValue = left.__proto__
  let rightValue = right.prototype
  while (true) {
    if (leftValue === null) {
      return false
    }
    if (leftValue === rightValue) {
      return true
    }
    leftValue = leftValue.__proto__
  }
}

函数柯里化

柯里化函数的定义:将多参数的函数转换成单参数的形式。 柯里化函数实现的原理:利用闭包原理在执行可以形成一个不销毁的作用域,然后把需要预先处理的内容都储存在这个不销毁的作用域中,并且返回一个最少参数函数。

第一种:固定传入参数,参数够了才执行

/**
 * 实现要点:柯里化函数接收到足够参数后,就会执行原函数,那么我们如何去确定何时达到足够的参数呢?
 * 柯里化函数需要记住你已经给过他的参数,如果没给的话,则默认为一个空数组。
 * 接下来每次调用的时候,需要检查参数是否给够,如果够了,则执行fn,没有的话则返回一个新的 curry 函数,将现有的参数塞给他。
 * 
 */
// 待柯里化处理的函数
let sum = (a, b, c, d) => {
  return a + b + c + d
}

// 柯里化函数,返回一个被处理过的函数 
let curry = (fn, ...arr) => {  // arr 记录已有参数
  return (...args) => {   // args 接收新参数
    if (fn.length <= (...arr,...args)) {  // 参数够时,触发执行
      return fn(...arr, ...args)
    } else {  // 继续添加参数
      return curry(fn, [...arr, ...args])
    }
  }
}

var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)
sumPlus(1, 2)(3)(4)
sumPlus(1, 2, 3)(4)

第二种:不固定传入参数,随时执行

/**
 * 当然了,柯里化函数的主要作用还是延迟执行,执行的触发条件不一定是参数个数相等,也可以是其他的条件。
 * 例如参数个为0的情况,那么我们需要对上面curry函数稍微做修改
 */
// 待柯里化处理的函数
let sum = arr => {
  return arr.reduce((a, b) => {
    return a + b
  })
}

let curry = (fn, ...arr) => {  // arr 记录已有参数
  return (...args) => {  // args 接收新参数
    if (args.length === 0) {  // 参数为空时,触发执行
      return fn(...arr, ...args)
    } else {  // 继续添加参数
      return curry(fn, ...arr, ...args)
    }
  }
}

var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)()
sumPlus(1, 2)(3)(4)()
sumPlus(1, 2, 3)(4)()

ES6写法

const curry = (fn, arr = []) => (...args) => (
 arg => arg.length === fn.length
 ? fn(...arg)
 : curry(fn, arg)
)([...arr, ...args])
 
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10

实现Event bus

// 组件通信,一个触发与监听的过程
class EventEmitter {
  constructor () {
    // 存储事件
    this.events = this.events || new Map()
  }
  // 监听事件
  addListener (type, fn) {
    if (!this.events.get(type)) {
      this.events.set(type, fn)
    }
  }
  // 触发事件
  emit (type) {
    let handle = this.events.get(type)
    handle.apply(this, [...arguments].slice(1))
  }
}

// 测试
let emitter = new EventEmitter()
// 监听事件
emitter.addListener('ages', age => {
  console.log(age)
})
// 触发事件
emitter.emit('ages', 18)  // 18

实现一个双向数据绑定

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
})

实现Promise

// 未添加异步处理等其他边界情况
// ①自动执行函数,②三个状态,③then
class Promise {
  constructor (fn) {
    // 三个状态
    this.state = 'pending'
    this.value = undefined
    this.reason = undefined
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
      }
    }
    let reject = value => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = value
      }
    }
    // 自动执行函数
    try {
      fn(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  // then
  then(onFulfilled, onRejected) {
    switch (this.state) {
      case 'fulfilled':
        onFulfilled(this.value)
        break
      case 'rejected':
        onRejected(this.value)
        break
      default:
    }
  }
}

实现一个简单的路由

// 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]()
  }
}

实现懒加载

<ul>
  <li><img src="./imgs/default.png" data="./imgs/1.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/2.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/3.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/4.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/5.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/6.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/7.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/8.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/9.png" alt=""></li>
  <li><img src="./imgs/default.png" data="./imgs/10.png" alt=""></li>
</ul>
let imgs =  document.querySelectorAll('img')
// 可视区高度
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
function lazyLoad () {
  // 滚动卷去的高度
  let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
  for (let i = 0; i < imgs.length; i ++) {
    // 图片在可视区冒出的高度
    let x = clientHeight + scrollTop - imgs[i].offsetTop
    // 图片在可视区内
    if (x > 0 && x < clientHeight+imgs[i].height) {
      imgs[i].src = imgs[i].getAttribute('data')
    }
  }
}
// addEventListener('scroll', lazyLoad) or setInterval(lazyLoad, 1000)

用正则和非正则,实现一个字符串转为驼峰

非正则

let str = "get-element-by-id";
    function fun1(str) {
      var strArr = str.split("-");
      for (var i = 1; i < strArr.length; i++) {
        var start = strArr[i].charAt(0).toUpperCase();
        var end = strArr[i].slice(1);
        strArr[i] = start + end;
      }
      return strArr.join("");
    }
    console.log(fun1(str));

正则1

 let str = "get-element-by-id";
    function fun1(str) {
      //\w元字符用来匹配单词字符,它等价于"[a-zA-Z0-9]"。
      var reg = /-(\w)/g; //子项()表示子项
      return str.replace(reg, function($0, $1) {
        //$0代表正则整体,replace()方法中的第二个参数若是回调函数,那么这个回调函数中的参数就是匹配成功后的结果
        //若回调函数中有多个参数时,第一个参数代表整个正则匹配结果,第二个参数代表第一个子项
        console.log($0); //-b
        console.log($1); //b
        return $1.toUpperCase();
      });
    }
    console.log(fun1(str));

正则2

let str = "get-element-by-id";
    var reg = /-\w/g;
    function fun1(str) {
      //\w元字符用来匹配单词字符,它等价于"[a-zA-Z0-9]"return str.replace(reg, function($) {
        //console.log($); //-e -b -i
        return $.slice(1).toUpperCase();
      });
    }
    console.log(fun1(str));

判断一个字符串中出现次数最多的字符,统计这个次数

var str = "asdfssaaasasasasaa";
    var obj = {};
    for (var i = 0; i < str.length; i++) {
      if (!obj[str.charAt(i)]) {
        obj[str.charAt(i)] = 1;
      } else {
        obj[str.charAt(i)]++;
      }
    }
    var count = 0;
    var word = "";
    for (var i in obj) {
      if (obj[i] > count) {
        count = obj[i];
        word = i;
      }
    }
    console.log("出现次数最多的是:" + word + "出现" + count + "次");

实现函数节流

对于持续触发的事件,规定一个间隔时间(n秒),每隔一段只能执行一次。

function _throttle(fn, time) { 
 
  let _self = fn, 
      timer,  
      firstTime = true //记录是否是第一次执行的flag
 
  return function() { 
    let args = arguments, //解决闭包传参问题
        _me = this //解决上下文丢失问题
        
    if(firstTime) { //若是第一次,则直接执行
      _self.apply(_me, args)
      return firstTime = false
    }
    if(timer) { //定时器存在,说明有事件监听器在执行,直接返回
      return false
    }
 
    timer = setTimeout(function() { 
      clearTimeout(timer)
      timer = null
      _self.apply(_me, args)
    }, time || 500)
  }
}
 
function _log(){
    console.log(1)
}
window.onscroll = _throttle(_log,500)

实现函数防抖

当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间

function _debounce(fn,wait,time){
    var previous = null; //记录上一次运行的时间
    var timer = null;
 
    return function(){
        var now = +new Date();
 
        if(!previous) previous = now;
        //当上一次执行的时间与当前的时间差大于设置的执行间隔时长的话,就主动执行一次
        if(now - previous > time){
            clearTimeout(timer);
            fn();
            previous = now;// 执行函数后,马上记录当前时间
        }else{
            clearTimeout(timer);
            timer = setTimeout(function(){
                fn();
            },wait);
        }
    }
}
function _log(){
    console.log(1)
}
window.onscroll = _debounce(_log,500,2000)

计算两个数组的差集

function difference(arr1, arr2) {
        var res = [];
        for (var i = 0, len = arr1.length; i < len; i++) {
            if (arr2.indexOf(arr1[i]) === -1) {
                res.push(arr1[i]);
            }
        }
        for (var j = 0, len = arr2.length; j < len; j++) {
            if (arr1.indexOf(arr2[j]) === -1) {
                res.push(arr2[j]);
            }
        }
        return res;
    }