实现公布-订阅模式

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

手写 new 操作符

在调用 new 的过程中会产生以上四件事件:

(1)首先创立了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)

(4)判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。

function objectFactory() {  let newObject = null;  let constructor = Array.prototype.shift.call(arguments);  let result = null;  // 判断参数是否是一个函数  if (typeof constructor !== "function") {    console.error("type error");    return;  }  // 新建一个空对象,对象的原型为构造函数的 prototype 对象  newObject = Object.create(constructor.prototype);  // 将 this 指向新建对象,并执行函数  result = constructor.apply(newObject, arguments);  // 判断返回对象  let flag = result && (typeof result === "object" || typeof result === "function");  // 判断返回后果  return flag ? result : newObject;}// 应用办法objectFactory(构造函数, 初始化参数);

实现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;};

debounce(防抖)

触发高频工夫后n秒内函数只会执行一次,如果n秒内高频工夫再次触发,则从新计算工夫。

const debounce = (fn, time) => {  let timeout = null;  return function() {    clearTimeout(timeout)    timeout = setTimeout(() => {      fn.apply(this, arguments);    }, time);  }};

防抖常利用于用户进行搜寻输出节约申请资源,window触发resize事件时进行防抖只触发一次。

判断对象是否存在循环援用

循环援用对象原本没有什么问题,然而序列化的时候就会产生问题,比方调用JSON.stringify()对该类对象进行序列化,就会报错: Converting circular structure to JSON.

上面办法能够用来判断一个对象中是否已存在循环援用:

const isCycleObject = (obj,parent) => {    const parentArr = parent || [obj];    for(let i in obj) {        if(typeof obj[i] === 'object') {            let flag = false;            parentArr.forEach((pObj) => {                if(pObj === obj[i]){                    flag = true;                }            })            if(flag) return true;            flag = isCycleObject(obj[i],[...parentArr,obj[i]]);            if(flag) return true;        }    }    return false;}const a = 1;const b = {a};const c = {b};const o = {d:{a:3},c}o.c.b.aa = a;console.log(isCycleObject(o)

查找有序二维数组的目标值:

var findNumberIn2DArray = function(matrix, target) {    if (matrix == null || matrix.length == 0) {        return false;    }    let row = 0;    let column = matrix[0].length - 1;    while (row < matrix.length && column >= 0) {        if (matrix[row][column] == target) {            return true;        } else if (matrix[row][column] > target) {            column--;        } else {            row++;        }    }    return false;};

二维数组斜向打印:

function printMatrix(arr){  let m = arr.length, n = arr[0].length    let res = []  // 左上角,从0 到 n - 1 列进行打印  for (let k = 0; k < n; k++) {    for (let i = 0, j = k; i < m && j >= 0; i++, j--) {      res.push(arr[i][j]);    }  }  // 右下角,从1 到 n - 1 行进行打印  for (let k = 1; k < m; k++) {    for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {      res.push(arr[i][j]);    }  }  return res}

实现节流函数(throttle)

防抖函数原理:规定在一个单位工夫内,只能触发一次函数。如果这个单位工夫内触发屡次函数,只有一次失效。

// 手写简化版

// 节流函数const throttle = (fn, delay = 500) => {  let flag = true;  return (...args) => {    if (!flag) return;    flag = false;    setTimeout(() => {      fn.apply(this, args);      flag = true;    }, delay);  };};

实用场景:

  • 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
  • 缩放场景:监控浏览器resize
  • 动画场景:防止短时间内屡次触发动画引起性能问题

参考 前端进阶面试题具体解答

实现日期格式化函数

输出:

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{    var day = dateInput.getDate()     var month = dateInput.getMonth() + 1      var year = dateInput.getFullYear()       format = format.replace(/yyyy/, year)    format = format.replace(/MM/,month)    format = format.replace(/dd/,day)    return format}

实现AJAX申请

AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新以后网页的对应局部,而不必刷新整个网页。

创立AJAX申请的步骤:

  • 创立一个 XMLHttpRequest 对象。
  • 在这个对象上应用 open 办法创立一个 HTTP 申请,open 办法所须要的参数是申请的办法、申请的地址、是否异步和用户的认证信息。
  • 在发动申请前,能够为这个对象增加一些信息和监听函数。比如说能够通过 setRequestHeader 办法来为申请增加头信息。还能够为这个对象增加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变动时会触发onreadystatechange 事件,能够通过设置监听函数,来解决申请胜利后的后果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接管实现,这个时候能够通过判断申请的状态,如果状态是 2xx 或者 304 的话则代表返回失常。这个时候就能够通过 response 中的数据来对页面进行更新了。
  • 当对象的属性和监听函数设置实现后,最初调用 sent 办法来向服务器发动申请,能够传入参数作为发送的数据体。
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);

验证是否是身份证

function isCardNo(number) {    var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;    return regx.test(number);}

数组去重

const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];// => [1, '1', 17, true, false, 'true', 'a', {}, {}]
办法一:利用Set
const res1 = Array.from(new Set(arr));
办法二:两层for循环+splice
const unique1 = arr => {  let len = arr.length;  for (let i = 0; i < len; i++) {    for (let j = i + 1; j < len; j++) {      if (arr[i] === arr[j]) {        arr.splice(j, 1);        // 每删除一个树,j--保障j的值通过自加后不变。同时,len--,缩小循环次数晋升性能        len--;        j--;      }    }  }  return arr;}
办法三:利用indexOf
const unique2 = arr => {  const res = [];  for (let i = 0; i < arr.length; i++) {    if (res.indexOf(arr[i]) === -1) res.push(arr[i]);  }  return res;}

当然也能够用include、filter,思路大同小异。

办法四:利用include
const unique3 = arr => {  const res = [];  for (let i = 0; i < arr.length; i++) {    if (!res.includes(arr[i])) res.push(arr[i]);  }  return res;}
办法五:利用filter
const unique4 = arr => {  return arr.filter((item, index) => {    return arr.indexOf(item) === index;  });}
办法六:利用Map
const unique5 = arr => {  const map = new Map();  const res = [];  for (let i = 0; i < arr.length; i++) {    if (!map.has(arr[i])) {      map.set(arr[i], true)      res.push(arr[i]);    }  }  return res;}

验证是否是邮箱

function isEmail(email) {    var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;    return regx.test(email);}

手写 Promise.all

1) 外围思路

  1. 接管一个 Promise 实例的数组或具备 Iterator 接口的对象作为参数
  2. 这个办法返回一个新的 promise 对象,
  3. 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
  4. 参数所有回调胜利才是胜利,返回值数组与参数程序统一
  5. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。

2)实现代码

一般来说,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)      })    }  })}// testlet 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]})

实现数组的push办法

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

模板引擎实现

let template = '我是{{name}},年龄{{age}},性别{{sex}}';let data = {  name: '姓名',  age: 18}render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则  if (reg.test(template)) { // 判断模板里是否有模板字符串    const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染    return render(template, data); // 递归的渲染并返回渲染后的构造  }  return template; // 如果模板没有模板字符串间接返回}

实现字符串的repeat办法

输出字符串s,以及其反复的次数,输入反复的后果,例如输出abc,2,输入abcabc。

function repeat(s, n) {    return (new Array(n + 1)).join(s);}

递归:

function repeat(s, n) {    return (n > 0) ? s.concat(repeat(s, --n)) : "";}

实现公布订阅模式

简介:

公布订阅者模式,一种对象间一对多的依赖关系,但一个对象的状态产生扭转时,所依赖它的对象都将失去状态扭转的告诉。

次要的作用(长处):

  1. 广泛应用于异步编程中(代替了传递回调函数)
  2. 对象之间涣散耦合的编写代码

毛病:

  • 创立订阅者自身要耗费肯定的工夫和内存
  • 多个发布者和订阅者嵌套一起的时候,程序难以跟踪保护

实现的思路:

  • 创立一个对象(缓存列表)
  • on办法用来把回调函数fn都加到缓存列表中
  • emit 依据key值去执行对应缓存列表中的函数
  • off办法能够依据key值勾销订阅
class EventEmiter {  constructor() {    // 事件对象,寄存订阅的名字和事件    this._events = {}  }  // 订阅事件的办法  on(eventName,callback) {    if(!this._events) {      this._events = {}    }    // 合并之前订阅的cb    this._events[eventName] = [...(this._events[eventName] || []),callback]  }  // 触发事件的办法  emit(eventName, ...args) {    if(!this._events[eventName]) {      return    }    // 遍历执行所有订阅的事件    this._events[eventName].forEach(fn=>fn(...args))  }  off(eventName,cb) {    if(!this._events[eventName]) {      return    }    // 删除订阅的事件    this._events[eventName] = this._events[eventName].filter(fn=>fn != cb && fn.l != cb)  }  // 绑定一次 触发后将绑定的移除掉 再次触发掉  once(eventName,callback) {    const one = (...args)=>{      // 等callback执行结束在删除      callback(args)      this.off(eventName,one)    }    one.l = callback // 自定义属性    this.on(eventName,one)  }}

测试用例

let event = new EventEmiter()let login1 = function(...args) {  console.log('login success1', args)}let login2 = function(...args) {  console.log('login success2', args)}// event.on('login',login1)event.once('login',login2)event.off('login',login1) // 解除订阅event.emit('login', 1,2,3,4,5)event.emit('login', 6,7,8,9)event.emit('login', 10,11,12)  

公布订阅者模式和观察者模式的区别?

  • 公布/订阅模式是观察者模式的一种变形,两者区别在于,公布/订阅模式在观察者模式的根底上,在指标和观察者之间减少一个调度核心。
  • 观察者模式是由具体指标调度,比方当事件触发,Subject 就会去调用观察者的办法,所以观察者模式的订阅者与发布者之间是存在依赖的。
  • 公布/订阅模式由对立调度核心调用,因而发布者和订阅者不须要晓得对方的存在。

应用ES5和ES6求函数参数的和

ES5:

function sum() {    let sum = 0    Array.prototype.forEach.call(arguments, function(item) {        sum += item * 1    })    return sum}

ES6:

function sum(...nums) {    let sum = 0    nums.forEach(function(item) {        sum += item * 1    })    return sum}

实现斐波那契数列

// 递归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;}

手写深度比拟isEqual

思路:深度比拟两个对象,就是要深度比拟对象的每一个元素。=> 递归
  • 递归退出条件:

    • 被比拟的是两个值类型变量,间接用“===”判断
    • 被比拟的两个变量之一为null,直接判断另一个元素是否也为null
  • 提前结束递推:

    • 两个变量keys数量不同
    • 传入的两个参数是同一个变量
  • 递推工作:深度比拟每一个key
function isEqual(obj1, obj2){    //其中一个为值类型或null    if(!isObject(obj1) || !isObject(obj2)){        return obj1 === obj2;    }    //判断是否两个参数是同一个变量    if(obj1 === obj2){        return true;    }    //判断keys数是否相等    const obj1Keys = Object.keys(obj1);    const obj2Keys = Object.keys(obj2);    if(obj1Keys.length !== obj2Keys.length){        return false;    }    //深度比拟每一个key    for(let key in obj1){        if(!isEqual(obj1[key], obj2[key])){            return false;        }    }    return true;}

树形构造转成列表(解决菜单)

[    {        id: 1,        text: '节点1',        parentId: 0,        children: [            {                id:2,                text: '节点1_1',                parentId:1            }        ]    }]转成[    {        id: 1,        text: '节点1',        parentId: 0 //这里用0示意为顶级节点    },    {        id: 2,        text: '节点1_1',        parentId: 1 //通过这个字段来确定子父级    }    ...]

实现代码如下:

function treeToList(data) {  let res = [];  const dfs = (tree) => {    tree.forEach((item) => {      if (item.children) {        dfs(item.children);        delete item.children;      }      res.push(item);    });  };  dfs(data);  return res;}