Skip to content

2020

手写继承

javascript
{
  {
    // js 实现继承
    function Animal() {
      this.eat = function () {
        console.log('animal can eat ...')
      }
    }
    function Dog() {
      this.break = function () {
        console.log('dog can break')
      }
    }
    Dog.prototype = new Animal()
    let es5 = new Dog()
    es5.eat() // animal can eat ...
    es5.break() // dog can break
  }
  {
    // class 实现 继承
    class Animal {
      constructor(name) {
        this.name = name
      }
      eat() {
        console.log('animal can wat')
      }
    }
    class Dog extends Animal {
      constructor(name) {
        super(name)
      }
      break() {
        console.log('dog can break')
      }
    }
    let es6 = new Dog('Zekee')
    es6.eat() // animal can wat
    es6.break() // dog can break
  }
}

instanceof 实现原理

javascript
{
  // instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。
  {
    function new_instance_of(leftVaule, rightVaule) {
      let rightProto = rightVaule.prototype
      leftVaule = leftVaule.__proto__
      while (true) {
        if (leftVaule === null) return false
        if (leftVaule === rightProto) return true
        leftVaule = leftVaule.__proto__
      }
    }
  }
  {
    const new_instance_of = function (leftVaule, rightVaule) {
      let proto = Object.getPrototypeOf(leftVaule)
      while (true) {
        if (proto == null) return false
        if (proto === rightVaule.prototype) return true
        proto = Object.getPrototypeOf(proto)
      }
    }
  }
  // 总结:使用 typeof 来判断基本数据类型是 ok 的,不过需要注意当用 typeof 来判断 null 类型时的问题,如果想要判断一个对象的具体类型可以考虑用 instanceof,但是 instanceof 也可能判断不准确,比如一个数组,他可以被 instanceof 判断为 Object。所以我们要想比较准确的判断对象实例的类型时,可以采取 Object.prototype.toString.call() 方法
}

promise 限制并发数

javascript
{
  class LimitPromise {
    constructor(max) {
      // 异步任务“并发”上限
      this._max = max
      // 当前正在执行的任务数量
      this._count = 0
      // 等待执行的任务队列
      this._taskQueue = []
    }

    /**
     * 调用器,将异步任务函数和它的参数传入
     * @param caller 异步任务函数,它必须是async函数或者返回Promise的函数
     * @param args 异步任务函数的参数列表
     * @returns {Promise<unknown>} 返回一个新的Promise
     */
    call(caller, ...args) {
      return new Promise((resolve, reject) => {
        const task = this._createTask(caller, args, resolve, reject)
        if (this._count >= this._max) {
          // console.log('count >= max, push a task to queue')
          this._taskQueue.push(task)
        } else {
          task()
        }
      })
    }

    /**
     * 创建一个任务
     * @param caller 实际执行的函数
     * @param args 执行函数的参数
     * @param resolve
     * @param reject
     * @returns {Function} 返回一个任务函数
     * @private
     */
    _createTask(caller, args, resolve, reject) {
      return () => {
        // 实际上是在这里调用了异步任务,并将异步任务的返回(resolve和reject)抛给了上层
        caller(...args)
          .then(resolve)
          .catch(reject)
          .finally(() => {
            // 任务队列的消费区,利用Promise的finally方法,在异步任务结束后,取出下一个任务执行
            this._count--
            if (this._taskQueue.length) {
              // console.log('a task run over, pop a task to run')
              let task = this._taskQueue.shift()
              task()
            } else {
              // console.log('task count = ', count)
            }
          })
        this._count++
        // console.log('task run , task count = ', count)
      }
    }
  }
  // 调用器:就是把真正的执行函数和参数传入,创建返回一个新的Promise,而这个新Promise的什么时候返回,取决于这个异步任务何时被调度。Promise内部主要就是创建一个任务,判断任务是执行还是入队。
  // 创建任务:实际上就是返回了一个函数,将真正的执行函数放在里面执行。这里利用了Promise的finally方法,在finally中判断是否执行下一个任务,实现任务队列连续消费的地方就是这里。
}

箭头函数跟普通函数的区别

函数扩展

flex 1 全写

css
/* 等价于 */
#app {
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: 0%;
}
/* 分别代表了所定义flex盒子的拉伸因子、收缩规则、基础宽度。 */

vue 双向绑定原理

vue学习记录

https 实现原理(越详细越好)

GraphQL 如何优化请求速度

GraphQL为什么会提升性能

GraphQL能够根据页面展示需求请求所需要的数据,不会有冗余的数据,传输效率上会更高

浏览器渲染页面过程

  • 从耗时的角度,浏览器请求、加载、渲染一个页面,时间花在下面五件事情上:
  • DNS 查询
  • TCP 连接
  • HTTP 请求即响应
  • 服务器响应
  • 客户端渲染

渲染机制

如何性能优化

  • DNS解析时间: domainLookupEnd - domainLookupStart
  • TCP建立连接时间: connectEnd - connectStart
  • 白屏时间: responseStart - navigationStart
  • dom渲染完成时间: domContentLoadedEventEnd - navigationStart
  • 页面onload时间: loadEventEnd - navigationStart

CDN 优化有哪些

缓存有哪些,区别是什么

  • http 缓存 (强缓存和协商缓存)
  • 浏览器缓存 ( Cookie, LocalStorage, SessionStorage )

页面性能类

手写 bind、reduce

javascript
{
  {
    // bind
    // 箭头函数的 this 永远指向它所在的作用域
    // 函数作为构造函数用 new 关键字调用时,不应该改变其 this 指向,因为 new绑定 的优先级高于 显示绑定 和 硬绑定
    {
      var mybind = function (thisArg) {
        if (typeof this !== "function") {
          throw TypeError("绑定必须在函数上调用");
        }
        // 拿到参数,为了传给调用者
        const args = Array.prototype.slice.call(arguments, 1),
        // 保存 this
        var that = this;
        // 构建一个干净的函数,用于保存原函数的原型
        var nop = function () { };
        // 绑定的函数
        var bound = function () {
          // this instanceof nop, 判断是否使用 new 来调用 bound
          // 如果是 new 来调用的话,this的指向就是其实例,
          // 如果不是 new 调用的话,就改变 this 指向到指定的对象 o
          return that.apply(
            this instanceof nop ? this : thisArg,
            args.concat(Array.prototype.slice.call(arguments))
          );
        };

        // 箭头函数处理:由于箭头函数没有 prototype,箭头函数this永远指向它所在的作用域
        if (this.prototype) {
          nop.prototype = this.prototype;
        }
        // 修改绑定函数的原型指向
        bound.prototype = new nop();

        return bound;
      };
    }
    // 测试
    const bar = function () {
      console.log(this.name, arguments);
    };
    bar.prototype.name = "bar";
    const foo = { name: "foo" };
    const bound = bar.mybind(foo, 22, 33, 44);
    new bound(); // bar, [22, 33, 44]
    bound(); // foo, [22, 33, 44]
  }
}

防抖节流

ts
/**
 * @description 函数防抖
 * @param {Function} method 延时调用函数
 * @param {Number} wait 延迟时长
 * @param {Boolean} immediate 立即执行选项
 */
export const Ddebounce = <T extends (...args: any[]) => any>(
  method: T,
  wait: number,
  immediate: boolean
) => {
  if (typeof method !== 'function') {
    throw new TypeError('Expected a function')
  }
  let timeout: ReturnType<typeof setTimeout> | null = null
  // Ddebounce函数为返回值
  // 使用Async/Await处理异步,如果函数异步执行,等待setTimeout执行完,拿到原函数返回值后将其返回
  // args为返回函数调用时传入的参数,传给method
  let Ddebounce = async (...args: Parameters<T>): Promise<ReturnType<T>> => {
    return new Promise((resolve) => {
      // 用于记录原函数执行结果
      let result: ReturnType<T>
      // 将method执行时this的指向设为 debounce 返回的函数被调用时的this指向
      let context = this
      // 如果存在定时器则将其清除
      if (timeout) {
        clearTimeout(timeout)
      }
      // 立即执行需要两个条件,一是immediate为true,二是timeout未被赋值或被置为null
      if (immediate) {
        // 如果定时器不存在,则立即执行,并设置一个定时器,wait毫秒后将定时器置为null
        // 这样确保立即执行后wait毫秒内不会被再次触发
        let callNow = !timeout
        timeout = setTimeout(() => {
          timeout = null
        }, wait)
        // 如果满足上述两个条件,则立即执行并记录其执行结果
        if (callNow) {
          result = method.apply(context, args)
          resolve(result)
        }
      } else {
        // 如果immediate为false,则等待函数执行并记录其执行结果
        // 并将Promise状态置为fullfilled,以使函数继续执行
        timeout = setTimeout(() => {
          // args是一个数组,所以使用fn.apply
          // 也可写作method.call(context, ...args)
          result = method.apply(context, args)
          resolve(result)
        }, wait)
      }
    })
  }

  // 在返回的 Ddebounce 函数上添加取消方法
  // Ddebounce.cancel = function () {
  //   clearTimeout(timeout)
  //   timeout = null
  // }
  return Ddebounce
}
ts
/**
 * 节流
 * @param func 回调函数
 * @param delay 延迟时间
 */
export const Dthrottle = <T extends (...args: any[]) => void>(func: T, delay: number) => {
  let timer: ReturnType<typeof setTimeout> | null = null

  return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
    const context = this

    if (!timer) {
      timer = setTimeout(function () {
        func.apply(context, args)
        timer = null
      }, delay)
    }
  }
}

遍历树,求树的最大层数。求某层最多的节点数

node 跟浏览器的 event loop 区别

JS运行机制

node 进程之间如何通讯

node 开启进程的方法有哪些,区别是什么

Node.js 通过提供 cluster、child_process API 创建子进程的方式来赋予Node.js “多线程”能力。但是这种创建进程的方式会牺牲共享内存,并且数据通信必须通过json进行传输。(有一定的局限性和性能问题) 基于此 Node.js V10.5.0 提供了 worker_threads,它比 child_process 或 cluster更轻量级。 与child_process 或 cluster 不同,worker_threads 可以共享内存,通过传输 ArrayBuffer 实例或共享 SharedArrayBuffer 实例来实现。

node 如何部署的

pm2,docker容器化等

node check 阶段做了什么,触发了什么事件

node 如何处理错误的

大多数异步API发生错误,采用callback方式来处理异常,其中callback的第一个参数就是err,如果第一个参数为null,而非err的话,则正确执行后面指令,反之为error的话,就会处理相应错误。

前端模块化的理解

  • 功能分治,利维护
  • 复用,利开发

隐式转换

javascript
{
  {
    // ToString (注:ToString不是对象的toString方法,而是指其他类型的值转换为字符串类型的操作)
    // null:转为"null"
    // undefined:转为"undefined"
    // 布尔类型:true和false分别被转为"true"和"false"
    // 数字类型:转为数字的字符串形式,如10转为"10", 1e21转为"1e+21"
    // 数组:转为字符串是将所有元素按照","连接起来,相当于调用数组的Array.prototype.join()方法,如[1, 2, 3]转为"1,2,3",空数组[]转为空字符串,数组中的null或undefined,会被当做空字符串处理
    // 普通对象:转为字符串相当于直接使用Object.prototype.toString(),返回"[object Object]"
    String(null) // 'null'
    String(undefined) // 'undefined'
    String(true) // 'true'
    String(10) // '10'
    String(1e21) // '1e+21'
    String([1, 2, 3]) // '1,2,3'
    String([]) // ''
    String([null]) // ''
    String([1, undefined, 3]) // '1,,3'
    String({}) // '[object Objecr]'
  }
  {
    // ToNumber (指其他类型转换为数字类型的操作)
    // null: 转为0
    // undefined:转为NaN
    // 字符串:如果是纯数字形式,则转为对应的数字,空字符转为0, 否则一律按转换失败处理,转为NaN
    // 布尔型:true和false被转为1和0
    // 数组:数组首先会被转为原始类型,也就是ToPrimitive,然后在根据转换后的原始类型按照上面的规则处理,关于ToPrimitive,会在下文中讲到
    // 对象:同数组的处理
    Number(null) // 0
    Number(undefined) // NaN
    Number('10') // 10
    Number('10a') // NaN
    Number('') // 0
    Number(true) // 1
    Number(false) // 0
    Number([]) // 0
    Number(['1']) // 1
    Number({}) // NaN
  }
  {
    // ToBoolean (指其他类型转换为布尔类型的操作)
    // js中的假值只有false、null、undefined、空字符、0和NaN,其它值转为布尔型都为true
    Boolean(null) // false
    Boolean(undefined) // false
    Boolean('') // flase
    Boolean(NaN) // flase
    Boolean(0) // flase
    Boolean([]) // true
    Boolean({}) // true
    Boolean(Infinity) // true
  }
  {
    // ToPrimitive (指对象类型类型(如:对象、数组)转换为原始类型的操作)
    // 注:对于不同类型的对象来说,ToPrimitive的规则有所不同,比如Date对象会先调用toString
    // 当对象类型需要被转为原始类型时,它会先查找对象的valueOf方法,如果valueOf方法返回原始类型的值,则ToPrimitive的结果就是这个值
    // 如果valueOf不存在或者valueOf方法返回的不是原始类型的值,就会尝试调用对象的toString方法,也就是会遵循对象的ToString规则,然后使用toString的返回值作为ToPrimitive的结果
    Number([]) // 0
    Number(['10']) //10

    const obj1 = {
      valueOf() {
        return 100
      },
      toString() {
        return 101
      },
    }
    Number(obj1) // 100

    const obj2 = {
      toString() {
        return 102
      },
    }
    Number(obj2) // 102

    const obj3 = {
      toString() {
        return {}
      },
    }
    Number(obj3) // TypeError

    // 对象类型在ToNumber时会先ToPrimitive,再根据转换后的原始类型ToNumber
    // Number([]), 空数组会先调用valueOf,但返回的是数组本身,不是原始类型,所以会继续调用toString,得到空字符串,相当于Number(''),所以转换后的结果为"0"
    // 同理,Number(['10'])相当于Number('10'),得到结果10
    // obj1的valueOf方法返回原始类型100,所以ToPrimitive的结果为100
    // obj2没有valueOf,但存在toString,并且返回一个原始类型,所以Number(obj2)结果为102
    // obj3的toString方法返回的不是一个原始类型,无法ToPrimitive,所以会抛出错误
  }
  {
    // ==
    // 只要布尔类型参与比较,该布尔类型的值首先会被转换为数字类型
    // 根据布尔类型的ToNumber规则,true转为1,false转为0
    {
      false == 0 // true
      true == 1 // true
      true == 2 // false
    }
    // 数字类型和字符串类型的相等比较
    // 当数字类型和字符串类型做相等比较时,字符串类型会被转换为数字类型
    // 根据字符串的ToNumber规则,如果是纯数字形式的字符串,则转为对应的数字,空字符转为0, 否则一律按转换失败处理,转为NaN(NaN和任何值都不相等,包括本身)
    {
      0 == '' // true
      1 == '1' // true
      1e21 == '1e21' // true
      Infinity == 'Infinity' // true
      true == '1' // true
      false == '0' // true
      false == '' // true
    }
    // 对象类型和原始类型的相等比较
    // 当对象类型和原始类型做相等比较时,对象类型会依照ToPrimitive规则转换为原始类型
    {
      '[object Object]' == {} // true
      '1,2,3' == [1, 2, 3] // true
    }
    // null、undefined和其他类型的比较
    // null和undefined宽松相等的结果为true,其次null和undefined都是假值
    // false转为0,然后呢? 没有然后了,ECMAScript规范中规定null和undefined之间互相宽松相等(==),并且也与其自身相等,但和其他所有的值都不宽松相等(==)。
    {
      null == false // false
      undefined == false // false
      null == undefined // true
    }
    // {
    //   [] == ![] // true
    //   [] == 0 // true
    //   [2] == 2 // true
    //   ['0'] == false // true
    //   '0' == false // true
    //   [] == false // true
    //   [null] == 0 // true
    //   null == 0 // false
    //   [null] == false // true
    //   null == false // false
    //   [undefined] == false // true
    //   undefined == false // false
    // }
    {
      // 定义一个变量a,使得下面的表达式结果为true; a == 1 && a == 2 && a == 3
      {
        const a = {
          // 定义一个属性来做累加
          inx: 1,
          valueOf() {
            return this.inx++
          },
        }
        console.log(a == 1 && a == 2 && a == 3) // true
      }
      {
        const a = {
          // 定义一个属性来做累加
          inx: 1,
          toString() {
            return this.inx++
          },
        }
        console.log(a == 1 && a == 2 && a == 3) // true
      }
    }
  }
}

数字在计算机怎么储存的

webpack 优化

webpack 的 require 是如何查找依赖的

webpack 如何实现动态加载

webpack 插件原理,如何写一个插件

给你一个项目,从头开始你怎么考虑

前言

工作流做了哪些事情

如何提升效率与性能

页面性能类

未来的规划是什么

跨域有哪些

通信类

安全类

安全类

变量提升 let const var 区别

  • let不允许在相同作用域内重复声明同一个变量,即同一个作用域内不允许出现名称相同的变量。
  • const用于声明常量,一旦声明,必须立即赋值,且以后不可更改。
  • const命令两个注意点:
    • 1.const 声明之后必须马上赋值,否则会报错
    • 2.const 简单类型一旦声明就不能再更改,复杂类型(数组、对象等)指针指向的地址不能更改,内部数据可以更改。
    • 解释:对象是引用类型,这里的返回值是对象的指针,指向这个对象存储的这个指针,这个指针是不变的,但是,对象本身是可以变的

链表与数组的区别

链表如何遍历

链表

script 标签中 async 跟 defer 的区别

  • 注:网上说 defer不会阻碍页面渲染,但自己测试iOS与safari会白屏,其效果跟script标签一样。

页面性能类

时针与分针计算 夹角问题(Tencent)

javascript
function angle(h, m) {
  if (h < 24 && m < 60) {
    // 时针一小时30度,一分钟0.5度
    const a = (h % 12) * 30 + m * 0.5
    // 分针一分钟6度
    const b = m * 6
    return Math.abs(a - b)
  }
}

console.log(angle(9, 0)) // 270

2021