二叉树档次遍历

// 二叉树档次遍历class Node {  constructor(element, parent) {    this.parent = parent // 父节点     this.element = element // 以后存储内容    this.left = null // 左子树    this.right = null // 右子树  }}class BST {  constructor(compare) {    this.root = null // 树根    this.size = 0 // 树中的节点个数    this.compare = compare || this.compare  }  compare(a,b) {    return a - b  }  add(element) {    if(this.root === null) {      this.root = new Node(element, null)      this.size++      return    }    // 获取根节点 用以后增加的进行判断 放右边还是放左边    let currentNode = this.root     let compare    let parent = null     while (currentNode) {      compare = this.compare(element, currentNode.element)      parent = currentNode // 先将父亲保存起来      // currentNode要不停的变动      if(compare > 0) {        currentNode = currentNode.right      } else if(compare < 0) {        currentNode = currentNode.left      } else {        currentNode.element = element // 相等时 先笼罩后续解决      }    }    let newNode = new Node(element, parent)    if(compare > 0) {      parent.right = newNode    } else if(compare < 0) {      parent.left = newNode    }    this.size++  }  // 档次遍历 队列  levelOrderTraversal(visitor) {    if(this.root == null) {      return    }    let stack = [this.root]    let index = 0 // 指针 指向0    let currentNode     while (currentNode = stack[index++]) {      // 反转二叉树      let tmp = currentNode.left      currentNode.left = currentNode.right      currentNode.right = tmp      visitor.visit(currentNode.element)      if(currentNode.left) {        stack.push(currentNode.left)      }      if(currentNode.right) {        stack.push(currentNode.right)      }    }  }}
// 测试var bst = new BST((a,b)=>a.age-b.age) // 模仿sort办法// ![](http://img-repo.poetries.top/images/20210522203619.png)// ![](http://img-repo.poetries.top/images/20210522211809.png)bst.add({age: 10})bst.add({age: 8})bst.add({age:19})bst.add({age:6})bst.add({age: 15})bst.add({age: 22})bst.add({age: 20})// 应用访问者模式class Visitor {  constructor() {    this.visit = function (elem) {      elem.age = elem.age*2    }  }}// ![](http://img-repo.poetries.top/images/20210523095515.png)console.log(bst.levelOrderTraversal(new Visitor()))

实现类的继承

类的继承在几年前是重点内容,有n种继承形式各有优劣,es6遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。

function Parent(name) {    this.parent = name}Parent.prototype.say = function() {    console.log(`${this.parent}: 你打篮球的样子像kunkun`)}function Child(name, parent) {    // 将父类的构造函数绑定在子类上    Parent.call(this, parent)    this.child = name}/**  1. 这一步不必Child.prototype =Parent.prototype的起因是怕共享内存,批改父类原型对象就会影响子类 2. 不必Child.prototype = new Parent()的起因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性3. Object.create是创立了父类原型的正本,与父类原型齐全隔离*/Child.prototype = Object.create(Parent.prototype);Child.prototype.say = function() {    console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);}// 留神记得把子类的结构指向子类自身Child.prototype.constructor = Child;var parent = new Parent('father');parent.say() // father: 你打篮球的样子像kunkunvar child = new Child('cxk', 'father');child.say() // father好,我是练习时长两年半的cxk

实现apply办法

apply原理与call很类似,不多赘述

// 模仿 applyFunction.prototype.myapply = 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;};

实现双向数据绑定

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

参考:前端手写面试题具体解答

实现非负大整数相加

JavaScript对数值有范畴的限度,限度如下:

Number.MAX_VALUE // 1.7976931348623157e+308Number.MAX_SAFE_INTEGER // 9007199254740991Number.MIN_VALUE // 5e-324Number.MIN_SAFE_INTEGER // -9007199254740991

如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER)进行加法运算,然而又想输入个别模式,那么应用 + 是无奈达到的,一旦数字超过 Number.MAX_SAFE_INTEGER 数字会被立刻转换为迷信计数法,并且数字精度相比以前将会有误差。

实现一个算法进行大数的相加:

function sumBigNumber(a, b) {  let res = '';  let temp = 0;  a = a.split('');  b = b.split('');  while (a.length || b.length || temp) {    temp += ~~a.pop() + ~~b.pop();    res = (temp % 10) + res;    temp  = temp > 9  }  return res.replace(/^0+/, '');}

其次要的思路如下:

  • 首先用字符串的形式来保留大数,这样数字在数学示意上就不会发生变化
  • 初始化res,temp来保留两头的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
  • 将两个数组的对应的位进行相加,两个数相加的后果可能大于10,所以可能要仅为,对10进行取余操作,将后果保留在以后位
  • 判断以后位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会主动隐式转化为1,以便于下一次相加
  • 反复上述操作,直至计算完结

查找字符串中呈现最多的字符和个数

例: abbcccddddd -> 字符最多的是d,呈现了5次

let str = "abcabcabcbbccccc";let num = 0;let char = ''; // 使其依照肯定的秩序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => {    if(num < $0.length){        num = $0.length;        char = $1;            }});console.log(`字符最多的是${char},呈现了${num}次`);

小孩报数问题

有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 而后下一个小孩 从新报数 1、2、3,问最初剩下的那个小孩儿的编号是多少?

function childNum(num, count){    let allplayer = [];        for(let i = 0; i < num; i++){        allplayer[i] = i + 1;    }    let exitCount = 0;    // 来到人数    let counter = 0;      // 记录报数    let curIndex = 0;     // 以后下标    while(exitCount < num - 1){        if(allplayer[curIndex] !== 0) counter++;            if(counter == count){            allplayer[curIndex] = 0;                             counter = 0;            exitCount++;          }        curIndex++;        if(curIndex == num){            curIndex = 0                       };               }        for(i = 0; i < num; i++){        if(allplayer[i] !== 0){            return allplayer[i]        }          }}childNum(30, 3)

实现数组的flat办法

function _flat(arr, depth) {  if(!Array.isArray(arr) || depth <= 0) {    return arr;  }  return arr.reduce((prev, cur) => {    if (Array.isArray(cur)) {      return prev.concat(_flat(cur, depth - 1))    } else {      return prev.concat(cur);    }  }, []);}

实现公布-订阅模式

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

Object.is

Object.is解决的次要是这两个问题:

+0 === -0  // trueNaN === NaN // false
const is= (x, y) => {  if (x === y) {    // +0和-0应该不相等    return x !== 0 || y !== 0 || 1/x === 1/y;  } else {    return x !== x && y !== y;  }}

实现类的继承

实现类的继承-简版

类的继承在几年前是重点内容,有n种继承形式各有优劣,es6遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。
// 寄生组合继承function Parent(name) {  this.name = name}Parent.prototype.say = function() {  console.log(this.name + ` say`);}Parent.prototype.play = function() {  console.log(this.name + ` play`);}function Child(name, parent) {  // 将父类的构造函数绑定在子类上  Parent.call(this, parent)  this.name = name}/**  1. 这一步不必Child.prototype = Parent.prototype的起因是怕共享内存,批改父类原型对象就会影响子类 2. 不必Child.prototype = new Parent()的起因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性3. Object.create是创立了父类原型的正本,与父类原型齐全隔离*/Child.prototype = Object.create(Parent.prototype);Child.prototype.say = function() {  console.log(this.name + ` say`);}// 留神记得把子类的结构指向子类自身Child.prototype.constructor = Child;
// 测试var parent = new Parent('parent');parent.say() var child = new Child('child');child.say() child.play(); // 继承父类的办法

ES5实现继承-具体

第一种形式是借助call实现继承

function Parent1(){    this.name = 'parent1';}function Child1(){    Parent1.call(this);    this.type = 'child1'    }console.log(new Child1);
这样写的时候子类尽管可能拿到父类的属性值,然而问题是父类中一旦存在办法那么子类无奈继承。那么引出上面的办法

第二种形式借助原型链实现继承:

function Parent2() {    this.name = 'parent2';    this.play = [1, 2, 3]  }  function Child2() {    this.type = 'child2';  }  Child2.prototype = new Parent2();  console.log(new Child2());

看似没有问题,父类的办法和属性都可能拜访,但实际上有一个潜在的有余。举个例子:

var s1 = new Child2();  var s2 = new Child2();  s1.play.push(4);  console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]

明明我只扭转了s1的play属性,为什么s2也跟着变了呢?很简略,因为两个实例应用的是同一个原型对象

第三种形式:将前两种组合:

function Parent3 () {    this.name = 'parent3';    this.play = [1, 2, 3];  }  function Child3() {    Parent3.call(this);    this.type = 'child3';  }  Child3.prototype = new Parent3();  var s3 = new Child3();  var s4 = new Child3();  s3.play.push(4);  console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]
之前的问题都得以解决。然而这里又徒增了一个新问题,那就是Parent3的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是咱们不愿看到的。那么如何解决这个问题?

第四种形式: 组合继承的优化1

function Parent4 () {    this.name = 'parent4';    this.play = [1, 2, 3];  }  function Child4() {    Parent4.call(this);    this.type = 'child4';  }  Child4.prototype = Parent4.prototype;
这里让将父类原型对象间接给到子类,父类构造函数只执行一次,而且父类属性和办法均能拜访,然而咱们来测试一下
var s3 = new Child4();  var s4 = new Child4();  console.log(s3)
子类实例的构造函数是Parent4,显然这是不对的,应该是Child4。

第五种形式(最举荐应用):优化2

function Parent5 () {    this.name = 'parent5';    this.play = [1, 2, 3];  }  function Child5() {    Parent5.call(this);    this.type = 'child5';  }  Child5.prototype = Object.create(Parent5.prototype);  Child5.prototype.constructor = Child5;
这是最举荐的一种形式,靠近完满的继承。

实现redux-thunk

redux-thunk 能够利用 redux 中间件让 redux 反对异步的 action
// 如果 action 是个函数,就调用这个函数// 如果 action 不是函数,就传给下一个中间件// 发现 action 是函数就调用const thunk = ({ dispatch, getState }) => (next) => (action) => {  if (typeof action === 'function') {    return action(dispatch, getState);  }  return next(action);};export default thunk

循环打印红黄绿

上面来看一道比拟典型的问题,通过这个问题来比照几种异步编程办法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯一直交替反复亮灯?

三个亮灯函数:

function red() {    console.log('red');}function green() {    console.log('green');}function yellow() {    console.log('yellow');}

这道题简单的中央在于须要“交替反复”亮灯,而不是“亮完一次”就完结了。

(1)用 callback 实现

const task = (timer, light, callback) => {    setTimeout(() => {        if (light === 'red') {            red()        }        else if (light === 'green') {            green()        }        else if (light === 'yellow') {            yellow()        }        callback()    }, timer)}task(3000, 'red', () => {    task(2000, 'green', () => {        task(1000, 'yellow', Function.prototype)    })})

这里存在一个 bug:代码只是实现了一次流程,执行后红黄绿灯别离只亮一次。该如何让它交替反复进行呢?

下面提到过递归,能够递归亮灯的一个周期:

const step = () => {    task(3000, 'red', () => {        task(2000, 'green', () => {            task(1000, 'yellow', step)        })    })}step()

留神看黄灯亮的回调里又再次调用了 step 办法 以实现循环亮灯。

(2)用 promise 实现

const task = (timer, light) =>     new Promise((resolve, reject) => {        setTimeout(() => {            if (light === 'red') {                red()            }            else if (light === 'green') {                green()            }            else if (light === 'yellow') {                yellow()            }            resolve()        }, timer)    })const step = () => {    task(3000, 'red')        .then(() => task(2000, 'green'))        .then(() => task(2100, 'yellow'))        .then(step)}step()

这里将回调移除,在一次亮灯完结后,resolve 以后 promise,并仍然应用递归进行。

(3)用 async/await 实现

const taskRunner =  async () => {    await task(3000, 'red')    await task(2000, 'green')    await task(2100, 'yellow')    taskRunner()}taskRunner()

Function.prototype.apply()

第一个参数是绑定的this,默认为window,第二个参数是数组或类数组

Function.prototype.apply = function(context = window, args) {  if (typeof this !== 'function') {    throw new TypeError('Type Error');  }  const fn = Symbol('fn');  context[fn] = this;  const res = context[fn](...args);  delete context[fn];  return res;}

实现数组的filter办法

Array.prototype._filter = function(fn) {    if (typeof fn !== "function") {        throw Error('参数必须是一个函数');    }    const res = [];    for (let i = 0, len = this.length; i < len; i++) {        fn(this[i]) && res.push(this[i]);    }    return res;}

实现观察者模式

观察者模式(基于公布订阅模式) 有观察者,也有被观察者

观察者须要放到被观察者中,被观察者的状态变动须要告诉观察者 我变动了 外部也是基于公布订阅模式,收集观察者,状态变动后要被动告诉观察者

class Subject { // 被观察者 学生  constructor(name) {    this.state = 'happy'    this.observers = []; // 存储所有的观察者  }  // 收集所有的观察者  attach(o){ // Subject. prototype. attch    this.observers.push(o)  }  // 更新被观察者 状态的办法  setState(newState) {    this.state = newState; // 更新状态    // this 指被观察者 学生    this.observers.forEach(o => o.update(this)) // 告诉观察者 更新它们的状态  }}class Observer{ // 观察者 父母和老师  constructor(name) {    this.name = name  }  update(student) {    console.log('以后' + this.name + '被告诉了', '以后学生的状态是' + student.state)  }}let student = new Subject('学生'); let parent = new Observer('父母'); let teacher = new Observer('老师'); // 被观察者存储观察者的前提,须要先接收观察者student. attach(parent); student. attach(teacher); student. setState('被欺侮了');

实现一个迭代器生成函数

ES6对迭代器的实现

JS原生的汇合类型数据结构,只有Array(数组)和Object(对象);而ES6中,又新增了MapSet。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以ES6在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator)。

ES6约定,任何数据结构只有具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被for...of...循环和迭代器的next办法遍历。 事实上,for...of...的背地正是对next办法的重复调用。

在ES6中,针对ArrayMapSetStringTypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都能够通过for...of...进行遍历。原理都是一样的,此处咱们拿最简略的数组进行举例,当咱们用for...of...遍历数组时:

const arr = [1, 2, 3]const len = arr.lengthfor(item of arr) {   console.log(`以后元素是${item}`)}
之所以可能按程序一次一次地拿到数组里的每一个成员,是因为咱们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过重复调用迭代器对象的next办法拜访了数组成员,像这样:
const arr = [1, 2, 3]// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 对迭代器对象执行next,就能一一拜访汇合的成员iterator.next()iterator.next()iterator.next()

丢进控制台,咱们能够看到next每次会按程序帮咱们拜访一个汇合成员:

for...of...做的事件,根本等价于上面这通操作:
// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 初始化一个迭代后果let now = { done: false }// 循环往外迭代成员while(!now.done) {    now = iterator.next()    if(!now.done) {        console.log(`当初遍历到了${now.value}`)    }}
能够看出,for...of...其实就是iterator循环调用换了种写法。在ES6中咱们之所以可能开心地用for...of...遍历各种各种的汇合,全靠迭代器模式在背地给力。

ps:此处举荐浏览迭代协定 (opens new window),置信大家读过后会对迭代器在ES6中的实现有更深的了解。

实现深拷贝

简洁版本

简略版:

const newObj = JSON.parse(JSON.stringify(oldObj));

局限性:

  • 他无奈实现对函数 、RegExp等非凡对象的克隆
  • 会摈弃对象的constructor,所有的构造函数会指向Object
  • 对象有循环援用,会报错

面试简版

function deepClone(obj) {    // 如果是 值类型 或 null,则间接return    if(typeof obj !== 'object' || obj === null) {      return obj    }    // 定义后果对象    let copy = {}    // 如果对象是数组,则定义后果数组    if(obj.constructor === Array) {      copy = []    }    // 遍历对象的key    for(let key in obj) {        // 如果key是对象的自有属性        if(obj.hasOwnProperty(key)) {          // 递归调用深拷贝办法          copy[key] = deepClone(obj[key])        }    }    return copy} 
调用深拷贝办法,若属性为值类型,则间接返回;若属性为援用类型,则递归遍历。这就是咱们在解这一类题时的外围的办法。

进阶版

  • 解决拷贝循环援用问题
  • 解决拷贝对应原型问题
// 递归拷贝 (类型判断)function deepClone(value,hash = new WeakMap){ // 弱援用,不必map,weakMap更适合一点  // null 和 undefiend 是不须要拷贝的  if(value == null){ return value;}  if(value instanceof RegExp) { return new RegExp(value) }  if(value instanceof Date) { return new Date(value) }  // 函数是不须要拷贝  if(typeof value != 'object') return value;  let obj = new value.constructor(); // [] {}  // 阐明是一个对象类型  if(hash.get(value)){    return hash.get(value)  }  hash.set(value,obj);  for(let key in value){ // in 会遍历以后对象上的属性 和 __proto__指代的属性    // 补拷贝 对象的__proto__上的属性    if(value.hasOwnProperty(key)){      // 如果值还有可能是对象 就持续拷贝      obj[key] = deepClone(value[key],hash);    }  }  return obj  // 辨别对象和数组 Object.prototype.toString.call}
// testvar o = {};o.x = o;var o1 = deepClone(o); // 如果这个对象拷贝过了 就返回那个拷贝的后果就能够了console.log(o1);

实现残缺的深拷贝

1. 简易版及问题

JSON.parse(JSON.stringify());
预计这个api能笼罩大多数的利用场景,没错,谈到深拷贝,我第一个想到的也是它。然而实际上,对于某些严格的场景来说,这个办法是有微小的坑的。问题如下:
  1. 无奈解决循环援用的问题。举个例子:
const a = {val:2};a.target = a;
拷贝a会呈现零碎栈溢出,因为呈现了有限递归的状况。
  1. 无奈拷贝一些非凡的对象,诸如 RegExp, Date, Set, Map
  2. 无奈拷贝函数(划重点)。

因而这个api先pass掉,咱们从新写一个深拷贝,简易版如下:

const deepClone = (target) => {  if (typeof target === 'object' && target !== null) {    const cloneTarget = Array.isArray(target) ? []: {};    for (let prop in target) {      if (target.hasOwnProperty(prop)) {          cloneTarget[prop] = deepClone(target[prop]);      }    }    return cloneTarget;  } else {    return target;  }}

当初,咱们以刚刚发现的三个问题为导向,一步步来欠缺、优化咱们的深拷贝代码。

2. 解决循环援用

当初问题如下:

let obj = {val : 100};obj.target = obj;deepClone(obj);//报错: RangeError: Maximum call stack size exceeded

这就是循环援用。咱们怎么来解决这个问题呢?

创立一个Map。记录下曾经拷贝过的对象,如果说曾经拷贝过,那间接返回它行了。
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;const deepClone = (target, map = new Map()) => {   if(map.get(target))      return target;   if (isObject(target)) {     map.set(target, true);     const cloneTarget = Array.isArray(target) ? []: {};     for (let prop in target) {       if (target.hasOwnProperty(prop)) {           cloneTarget[prop] = deepClone(target[prop],map);       }     }     return cloneTarget;   } else {     return target;   } }

当初来试一试:

const a = {val:2};a.target = a;let newA = deepClone(a);console.log(newA)//{ val: 2, target: { val: 2, target: [Circular] } }
如同是没有问题了, 拷贝也实现了。但还是有一个潜在的坑, 就是map 上的 key 和 map 形成了强援用关系,这是相当危险的。我给你解释一下与之绝对的弱援用的概念你就明确了

在计算机程序设计中,弱援用与强援用绝对,

被弱援用的对象能够在任何时候被回收,而对于强援用来说,只有这个强援用还在,那么对象无奈被回收。拿下面的例子说,map 和 a始终是强援用的关系, 在程序完结之前,a 所占的内存空间始终不会被开释。

怎么解决这个问题?

很简略,让 map 的 key 和 map 形成弱援用即可。ES6给咱们提供了这样的数据结构,它的名字叫WeakMap,它是一种非凡的Map, 其中的键是弱援用的。其键必须是对象,而值能够是任意的

略微革新一下即可:

const deepClone = (target, map = new WeakMap()) => {  //...}

3. 拷贝非凡对象

可持续遍历

对于非凡的对象,咱们应用以下形式来甄别:

Object.prototype.toString.call(obj);

梳理一下对于可遍历对象会有什么后果:

["object Map"]["object Set"]["object Array"]["object Object"]["object Arguments"]

以这些不同的字符串为根据,咱们就能够胜利地甄别这些对象。

const getType = Object.prototype.toString.call(obj);const canTraverse = {  '[object Map]': true,  '[object Set]': true,  '[object Array]': true,  '[object Object]': true,  '[object Arguments]': true,};const deepClone = (target, map = new Map()) => {  if(!isObject(target))     return target;  let type = getType(target);  let cloneTarget;  if(!canTraverse[type]) {    // 解决不能遍历的对象    return;  }else {    // 这波操作相当要害,能够保障对象的原型不失落!    let ctor = target.prototype;    cloneTarget = new ctor();  }  if(map.get(target))     return target;  map.put(target, true);  if(type === mapTag) {    //解决Map    target.forEach((item, key) => {      cloneTarget.set(deepClone(key), deepClone(item));    })  }  if(type === setTag) {    //解决Set    target.forEach(item => {      target.add(deepClone(item));    })  }  // 解决数组和对象  for (let prop in target) {    if (target.hasOwnProperty(prop)) {        cloneTarget[prop] = deepClone(target[prop]);    }  }  return cloneTarget;}

不可遍历的对象

const boolTag = '[object Boolean]';const numberTag = '[object Number]';const stringTag = '[object String]';const dateTag = '[object Date]';const errorTag = '[object Error]';const regexpTag = '[object RegExp]';const funcTag = '[object Function]';

对于不可遍历的对象,不同的对象有不同的解决。

const handleRegExp = (target) => {  const { source, flags } = target;  return new target.constructor(source, flags);}const handleFunc = (target) => {  // 待会的重点局部}const handleNotTraverse = (target, tag) => {  const Ctor = targe.constructor;  switch(tag) {    case boolTag:    case numberTag:    case stringTag:    case errorTag:     case dateTag:      return new Ctor(target);    case regexpTag:      return handleRegExp(target);    case funcTag:      return handleFunc(target);    default:      return new Ctor(target);  }}

4. 拷贝函数

  • 尽管函数也是对象,然而它过于非凡,咱们独自把它拿进去拆解。
  • 提到函数,在JS种有两种函数,一种是一般函数,另一种是箭头函数。每个一般函数都是
  • Function的实例,而箭头函数不是任何类的实例,每次调用都是不一样的援用。那咱们只须要
  • 解决一般函数的状况,箭头函数间接返回它自身就好了。

那么如何来辨别两者呢?

答案是: 利用原型。箭头函数是不存在原型的。
const handleFunc = (func) => {  // 箭头函数间接返回本身  if(!func.prototype) return func;  const bodyReg = /(?<={)(.|\n)+(?=})/m;  const paramReg = /(?<=\().+(?=\)\s+{)/;  const funcString = func.toString();  // 别离匹配 函数参数 和 函数体  const param = paramReg.exec(funcString);  const body = bodyReg.exec(funcString);  if(!body) return null;  if (param) {    const paramArr = param[0].split(',');    return new Function(...paramArr, body[0]);  } else {    return new Function(body[0]);  }}

5. 残缺代码展现

const getType = obj => Object.prototype.toString.call(obj);const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;const canTraverse = {  '[object Map]': true,  '[object Set]': true,  '[object Array]': true,  '[object Object]': true,  '[object Arguments]': true,};const mapTag = '[object Map]';const setTag = '[object Set]';const boolTag = '[object Boolean]';const numberTag = '[object Number]';const stringTag = '[object String]';const symbolTag = '[object Symbol]';const dateTag = '[object Date]';const errorTag = '[object Error]';const regexpTag = '[object RegExp]';const funcTag = '[object Function]';const handleRegExp = (target) => {  const { source, flags } = target;  return new target.constructor(source, flags);}const handleFunc = (func) => {  // 箭头函数间接返回本身  if(!func.prototype) return func;  const bodyReg = /(?<={)(.|\n)+(?=})/m;  const paramReg = /(?<=\().+(?=\)\s+{)/;  const funcString = func.toString();  // 别离匹配 函数参数 和 函数体  const param = paramReg.exec(funcString);  const body = bodyReg.exec(funcString);  if(!body) return null;  if (param) {    const paramArr = param[0].split(',');    return new Function(...paramArr, body[0]);  } else {    return new Function(body[0]);  }}const handleNotTraverse = (target, tag) => {  const Ctor = target.constructor;  switch(tag) {    case boolTag:      return new Object(Boolean.prototype.valueOf.call(target));    case numberTag:      return new Object(Number.prototype.valueOf.call(target));    case stringTag:      return new Object(String.prototype.valueOf.call(target));    case symbolTag:      return new Object(Symbol.prototype.valueOf.call(target));    case errorTag:     case dateTag:      return new Ctor(target);    case regexpTag:      return handleRegExp(target);    case funcTag:      return handleFunc(target);    default:      return new Ctor(target);  }}const deepClone = (target, map = new WeakMap()) => {  if(!isObject(target))     return target;  let type = getType(target);  let cloneTarget;  if(!canTraverse[type]) {    // 解决不能遍历的对象    return handleNotTraverse(target, type);  }else {    // 这波操作相当要害,能够保障对象的原型不失落!    let ctor = target.constructor;    cloneTarget = new ctor();  }  if(map.get(target))     return target;  map.set(target, true);  if(type === mapTag) {    //解决Map    target.forEach((item, key) => {      cloneTarget.set(deepClone(key, map), deepClone(item, map));    })  }  if(type === setTag) {    //解决Set    target.forEach(item => {      cloneTarget.add(deepClone(item, map));    })  }  // 解决数组和对象  for (let prop in target) {    if (target.hasOwnProperty(prop)) {        cloneTarget[prop] = deepClone(target[prop], map);    }  }  return cloneTarget;}

实现数组的push办法

let arr = [];Array.prototype.push = function() {    for( let i = 0 ; i < arguments.length ; i++){        this[this.length] = arguments[i] ;    }    return this.length;}

实现Object.freeze

Object.freeze解冻一个对象,让其不能再增加/删除属性,也不能批改该对象已有属性的可枚举性、可配置可写性,也不能批改已有属性的值和它的原型属性,最初返回一个和传入参数雷同的对象
function myFreeze(obj){  // 判断参数是否为Object类型,如果是就关闭对象,循环遍历对象。去掉原型属性,将其writable个性设置为false  if(obj instanceof Object){    Object.seal(obj);  // 关闭对象    for(let key in obj){      if(obj.hasOwnProperty(key)){        Object.defineProperty(obj,key,{          writable:false   // 设置只读        })        // 如果属性值仍然为对象,要通过递归来进行进一步的解冻        myFreeze(obj[key]);        }    }  }}