myDocs
首页
  • JavaScript小记
  • HTML小记
  • CSS小记
  • 计算机网络
  • React小记
  • Vue小记
  • 手写js
  • 前端工程化
  • 前端性能优化
  • 实际项目开发
  • Typescript面试题
  • Nodejs面试题
  • 小程序
  • 排序
  • 算法题
  • Git小记
  • NodeJs小记
  • TypeScript小记
  • 正则表达式入门
  • Linux基本命令
  • PixiJS的基本使用
  • PixiJS实现一镜到底
  • Canvas入门
  • SVG入门
  • Echarts基本使用
  • antv G6的基础入门及树图的实际应用
  • Three.js
  • 《CSS揭秘》
  • 《Python编程:从入门到实践》
  • 低代码数据可视化平台开发记录
  • 中后台管理系统模板记录
  • 多页签开发记录
  • 浙政钉、浙里办、浙江政务服务网应用上架指南
Github
首页
  • JavaScript小记
  • HTML小记
  • CSS小记
  • 计算机网络
  • React小记
  • Vue小记
  • 手写js
  • 前端工程化
  • 前端性能优化
  • 实际项目开发
  • Typescript面试题
  • Nodejs面试题
  • 小程序
  • 排序
  • 算法题
  • Git小记
  • NodeJs小记
  • TypeScript小记
  • 正则表达式入门
  • Linux基本命令
  • PixiJS的基本使用
  • PixiJS实现一镜到底
  • Canvas入门
  • SVG入门
  • Echarts基本使用
  • antv G6的基础入门及树图的实际应用
  • Three.js
  • 《CSS揭秘》
  • 《Python编程:从入门到实践》
  • 低代码数据可视化平台开发记录
  • 中后台管理系统模板记录
  • 多页签开发记录
  • 浙政钉、浙里办、浙江政务服务网应用上架指南
Github

手写js代码

1、数据类型判断

function typeOf(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
}

2、简单实现instanceof

function instance_of(left, right) {
  if (typeof left !== 'object' || left === 'null') return false // 基础类型一律为 false
  let proto = Object.getPrototypeOf(left) // 获取对象的原型
  while(true) {
    if (proto === null) return false
    if (proto === right.prototype) return true
    proto = Object.getPrototypeOf(proto)
  }
}

3、实现Object.create

function objCreate(obj) {
  function F() {}
  F.prototype = obj
  F.prototype.constructor = F
  return new F()
}

4、实现 new 关键字

new 一个对象的过程

1、创建一个新对象,如:var person = {};

2、新对象的_ proto _属性指向构造函数的原型对象。

3、将构造函数的作用域赋值给新对象。(this对象指向新对象)

4、执行构造函数内部的代码,将属性和方法添加给person中的this对象。

5、返回新对象person。

function myNew(fn, ...args) {
  if (typeof fn !== 'function') {
    throw new TypeError(`${fn} is not a constructor`)
  }
  let obj = Object.create(fn.prototype)
  // 效果一样
  // let obj = new Object()
  // obj.__proto__ = fn.prototype
  let res = fn.apply(obj, args)
  return (typeof res === 'object' && res !== null) ? res : obj
}

5、手写call

Function.prototype.myCall = function(thisArg, ...args) {
  if(typeof this !== 'function') {
      throw new TypeError('caller must be a function')
  }
  const fn = Symbol('fn')        // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
  thisArg = thisArg || window    // 若没有传入this, 默认绑定window对象
  thisArg[fn] = this              // this指向调用call的对象,即我们要改变this指向的函数
  const result = thisArg[fn](...args)  // 执行当前函数
  delete thisArg[fn]              // 删除我们声明的fn属性
  return result                  // 返回函数执行结果
}

6、手写bind

Function.prototype.myBind = function(thisArg, ...args) {
  let fn = this
  return function(...newArgs) {
    return fn.call(thisArg, ...args, ...newArgs)
  }
}

7、函数科里化⭐

将使用多个参数的函数转换成一系列使用一个参数的函数

function currying(fn, ...args) {
  if (fn.length > args.length) {
    return function(...newArgs) {
      return currying(fn, ...args, ...newArgs)
    }
  } else {
    return fn(...args)
  }
}

function add(a, b, c, d) {
  return a + b + c + d
}

var curry = currying(add)

curry(1)(2, 3)(4)
curry(1, 2)(3)(4)

8、偏函数⭐

将一个 n 参的函数转换成固定 x 参的函数,剩余参数(n - x)将在下次调用全部传入

function partial(fn, ...args) {
  return (...newArgs) => {
    return fn(...args, ...newArgs)
  }
}

function add(a, b, c) {
  return a + b + c
}
let partialAdd = partial(add, 1)
partialAdd(2, 3)

9、数组去重⭐

var unique = arr => [...new Set(arr)]

----------------------------------------------------------------------

function unique(arr) {
  var res = arr.filter((item, index) => {
    return arr.indexOf(item) === index
  })
  return res
}

----------------------------------------------------------------------

function unique(arr) {
  let res = []
  arr.forEach((item, index) => {
    if (arr.indexOf(item) == index) {
      res.push(item)
    }
  })
  return res
}

----------------------------------------------------------------------

function unique(arr){            
  for(var i = 0; i < arr.length; i++){
    for(var j = i+1; j < arr.length; j++){
      if(arr[i] == arr[j]){         
        arr.splice(j, 1);
        j--;
      }
    }
  }
  return arr;
}

10、数组扁平化⭐

// 数组方法flat(),默认打平一层,Infinity打平任意层
var flatArr = arr => arr.flat(Infinity)

// 递归+concat
function flatArr(arr) {
  let result = []
  arr.forEach(item => {
    if (item instanceof Array) {
      result = result.concat(flatArr(item))
    } else {
      result.push(item)
    }
  })
  return result
}


// reduce(与上一种的递归类似)
function flatArr(arr) {
  return arr.reduce((total, item) => {
    return total.concat(Array.isArray(item) ? flatArr(item) : item)
  }, [])
}

// 扩展运算符...
function flatArr(arr) {
  while(arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr)
  }
  return arr
}

// join()
function flatArr(arr) {
  return arr.join(',').split(',').map(Number)
}

// toString()
function flatArr(arr) {
  return arr.toString().split(',').map(Number)
}

11、数组排序⭐

冒泡排序

时间复杂度 O(n*n) 稳定

原理:从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较到最后一个元素,那么此时最后一个元素就是该数组中最大的数。下一轮重复以上操作,但是此时最后一个元素也就是最大数了,所以不需要再比较最后一个元素,只需要比较到length-1的位置。

function bubbleSort(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    for (let j = 0; j < i; j++) {
      if (arr[j] > arr[j+1]) {
        [arr[j], arr[j+1]] = [arr[j+1], arr[j]] // ES6解构赋值
      }
    }
  }
  return arr
}

选择排序

时间复杂度 O(n*n) 不稳定

原理:遍历数组,设置最小值的索引为0,如果取出的值比当前最小值小,就替换最小值索引,遍历完成后,将第一个元素和最小值索引上的值交换。如上操作后,第一个元素就是数组中的最小值,下次遍历就可以从索引1开始重复上述操作。

function selectSort(arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] > arr[j]) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }
  return arr
}

插入排序

时间复杂度 O(n*n) 稳定

原理:第一个元素默认是已排序元素,取出下一个元素和当前元素比较,如果当前元素大就交换位置。那么此时第一个元素就是当前的最小数,所以下次取出操作从第三个元素开始,向前对比,重复之前的操作。

即:将元素插入到已排序好的数组中

function insertSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j > 0; j--) {
      if (arr[j] < arr[j-1]) {
        [arr[j], arr[j - 1]] = [arr[j - 1], arr[j]]
      } else {
        break
      }
    }
  }
  return arr
}


var arr = [1,3,2,7,5,4]
insertSort(arr)

快速排序

时间复杂度 O(nlogn) 不稳定

原理:选择基准值 mid,循环原数组,小于基准值放左边数组,大于放右边数组,然后 concat 组合,最后依靠递归完成排序。

function quickSort(arr) {
  let left = [], right = [], mid = arr.splice(0, 1)
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] > mid) {
      right.push(arr[i])
    } else {
      left.push(arr[i])
    }
  }
  return quickSort(left).concat(mid, quickSort(right))
}

12、深浅拷贝⭐

普通数组深拷贝

var arr = [1,2,3,4,5]
var objArr = [{name: 'ccc'}, {age: 1}]

// 1.直接遍历
function copyArr(arr) {
  let newArr = []
  arr.forEach(item => {
    newArr.push(item)
  })
  return newArr
}

// 2.slice()
var arr1 = arr.slice()
var objArr1 = objArr.slice()

// 3.concat()
var arr1 = arr.concat()
var objArr1 = objArr.concat()

// 4.扩展运算符
var arr1 = [...arr]
var objArr1 = [...objArr]

对象拷贝

var obj = {
  name: 'cc',
  age: 9,
  family: ['mother', 'father']
}

// 单层深拷贝,多层浅拷贝
function shallowCopy(obj) {
  if (typeof obj !== 'object') return
  let newObj = obj instanceof Array ? [] : {}
  for(let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key]
    }
  }
  return newObj
}
// Object.assign() 类似
var newObj = Object.assign({}, obj)


// 深拷贝 (限制于对象和数组。不考虑时间,函数等)
function deepClone(obj) {
  if (typeof obj !== 'object') return
  let newObj = obj instanceof Array ? [] : {}
  for(let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
    }
  }
  return newObj
}

// 深拷贝升级版(还可以拷贝函数,时间等)
function deepClone(obj, newObj) {
  for (var key in obj) {
    var val = obj[key]
    if (val instanceof Object) {
      var tempObj = new val.constructor()
      deepClone(val, tempObj)
      newObj[key] = tempObj
    } else {
      newObj[key] = val
    }
  }
}

var obj = {
  name: 'cc',
  age: 9,
  family: ['mother', 'father'],
  date: new Date(),
  say() {
    console.log('hello');
  }
}

// 序列化反序列化法
var newObj = JSON.parse(JSON.stringify(obj))

实现 deepAssign

// 实现 deepAssign({a: {b: 1, c: 2}}, {a: {c: 3}});
// => {a: {b: 1, c: 3}}

// 两个对象
function deepAssign(obj1, obj2) {
  for (let key in obj2) {
    obj1[key] = typeof obj2[key] === 'object' ? deepAssign(obj1[key], obj2[key]) : obj2[key]
  }
  return obj1
}

// 不限参数数量
function deepAssign() {
  var args = [...arguments]
  return args.reduce(deepClone, args[0])
  function deepClone(total, current) {
    if (!total) {
      total = Array.isArray(current) ? [] : {}
    }
    for (key in current) {
      total[key] = typeof current[key] === 'object' ? deepClone(total[key], current[key]) : current[key]
    }
    return total
  }
}

13、数组随机排序

function randomArr(arr) {
  arr.sort(() => {
    return Math.random() > 0.5 ? -1 : 1    // Math.random() - 0.5 
  })
  return arr
}

var arr = [1, 2, 3, 4, 5, 6]
randomArr(arr)

14、防抖⭐

防抖是指在事件被触发后延迟一段时间后再执行回调,如果在这段延迟时间内事件又被触发,则重新计算延迟时间。

function debounce(fn, wait = 100) {
  let timer = 0;
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, wait)
  }
}

15、节流⭐

节流是指在一段时间内,不管事件触发了多少次,只执行一次回调。

function throttle(fn, duration) {
  var begin = new Date()
  return function(...args) {
    var current = new Date()
    if (current - begin >= duration) {
      fn.apply(this, args)
      begin = current
    }
  }
}

function throttle(fn, delay) {
  let flag = true
  return (...args) => {
    if (!flag) return
    flag = false
    setTimeout(() => {
      fn.call(this, ...args)
      flag = true
    }, delay)
  }
}

16、随机数

function randomNum(min, max) {
  let choice = max - min + 1
  return Math.floor(Math.random() * choice + min)
}

17、随机字符串

toString(36)将数值转换成36进制,包含了0-9和a-z的所有字符

function randomStr(len) {
  let str = ''
  while(str.length < len) {
    str = str + Math.random().toString(36).slice(2)
  }
  return str.slice(0, len)
}

18、斐波那契数列

function fibonacci(n) {
  if (n == 0 || n == 1) return n
  return fibonacci(n-1) + fibonacci(n-2)
}


function fibonacci(n) {
  let arr = [0, 1]
  for (let i = 2; i < n; i++) {
    arr[i] = arr[i-1] + arr[i-2]
  }
  return arr[n]
}

19、继承

原型链继承

function Person(name) {
  this.name = name
}
Person.prototype.getName = function() {
  return this.name
}

function Teacher(name, job) {
  this.name = name
  this.job = job
}
Teacher.prototype = new Person()         // Teacher的原型指向Person的实例,实现继承
Teacher.prototype.constructor = Teacher  // 解决原型链继承后对象类型改变(变成了Person)的问题,Teacher原型的构造函数指向Teacher
Teacher.prototype.getJob = function() {
  return this.job
}

var teacher = new Teacher('cc', 'teacher');
console.log(teacher.getName());
console.log(teacher.getJob());

借用构造函数继承

(解决包含引用类型值的原型属性会被所有实例共享的问题),但是自身也有问题

function Person(name) {
  this.name = name
  this.family = ['mother', 'father']
}
Person.prototype.getName = function() {
  return this.name
}
Person.prototype.getFamily = function() {
  return this.family
}

function Teacher(name, job) {
  this.name = name
  this.job = job
  Person.call(this, name)
}
Teacher.prototype.getJob = function() {
  return this.job
}

var teacher1 = new Teacher('cc', 'teacher')
var teacher2 = new Teacher('ss', 'alsoTeacher')
teacher1.family.push('son')
console.log(teacher1.family);       // ["mother", "father", "son"]
console.log(teacher2.family);       // ["mother", "father"]  (如果是原型链继承的话 值和teacher1.family相同)
console.log(teacher1.getFamily());  // 报错:teacher1.getFamily is not a function  
console.log(teacher2.getFamily());  // 没用到原型,只能继承父类的实例属性和方法,不能继承原型属性/方法

组合继承

(对原型链继承和构造函数继承进行组合)(常用)

问题:调用了两次父类构造函数(耗内存)

function Person(name) {
  this.name = name
  this.family = ['mother', 'father']
}
Person.prototype.getName = function() {
  return this.name
}
Person.prototype.getFamily = function() {
  return this.family
}

function Teacher(name, job) {
  this.name = name
  this.job = job
  Person.call(this, name)               // 继承属性
}
Teacher.prototype = new Person()        // 继承方法
Teacher.prototype.constructor = Teacher
Teacher.prototype.getJob = function() {
  return this.job
}
var teacher1 = new Teacher('cc', 'teacher')
var teacher2 = new Teacher('ss', 'alsoTeacher')
teacher1.family.push('son')
console.log(teacher1.family);         // ["mother", "father", "son"]
console.log(teacher2.family);         // ["mother", "father"]
console.log(teacher1.getFamily());    // ["mother", "father", "son"]
console.log(teacher2.getFamily());    // ["mother", "father"]

原型式继承

(用函数包装,原型链继承实例对象,返回函数调用)

这个函数就变成了一个可以随意增添属性的实例或对象。Object.create()就是这个原理。

function createObj(obj) {
  function Fn() {}
  Fn.prototype = obj
  Fn.prototype.constructor = Fn
  return new Fn()
}
var person = {
  name: 'cc',
  family: ['mother', 'father'],
  getName() {
    return this.name
  },
  getFamily() {
    return this.family
  }
}

var teacher1 = createObj(person)
var teacher2 = createObj(person)
teacher2.name = 'ss'
teacher1.family.push('son')

console.log(teacher1.getName());    // cc
console.log(teacher2.getName());    // ss
console.log(teacher1.getFamily());  // ["mother", "father", "son"]
console.log(teacher2.getFamily());  // ["mother", "father", "son"]  和原型链继承有一样的问题,引用类型的原型属性被共享了

寄生式继承

在原型式继承的基础上,通过调用函数创建一个新对象

可以给这个对象新增属性和方法(增强对象)

function createObj(obj) {
  function Fn() {}
  Fn.prototype = obj
  Fn.prototype.constructor = Fn
  return new Fn()
}

function createPerson(person) {
  var newPerson = createObj(person)       // 通过调用函数创建一个新对象
  newPerson.getName = function() {        // 可以给这个对象新增属性和方法(增强对象)
    return this.name
  }
  newPerson.getFamily = function() {
    return this.family
  }
  return newPerson
}
var person = {
  name: 'cc',
  family: ['mother', 'father']
}

var teacher1 = createPerson(person)
var teacher2 = createPerson(person)
teacher2.name = 'ss'
teacher1.family.push('son')

console.log(teacher1.getName());    // cc
console.log(teacher2.getName());    // ss
console.log(teacher1.getFamily());  // ["mother", "father", "son"]
console.log(teacher2.getFamily());  // ["mother", "father", "son"]  和原型链继承有一样的问题,引用类型的原型属性被共享了

寄生组合式继承

(解决组合继承中调用两次超类型构造函数的问题)(常用)

核心:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

function Person(name) {
  this.name = name
}
Person.prototype.getName = function() {
  return this.name
}

function Teacher(name, job) {
  this.name = name
  this.job = job
  Person.call(this, name)               // 继承属性
}

function createObj(obj) {
  function Fn() {}
  Fn.prototype = obj
  Fn.prototype.constructor = Fn
  return new Fn()
}
// 继承方法
function inheritPrototype(parent, child) {
  child.prototype = createObj(parent.prototype)   // 复制父类原型
  child.prototype.constructor = child             // 构造函数指回Teacher
}

// 复制父类原型,用该函数替换组合继承中为子类型原型赋值的语句
inheritPrototype(Person, Teacher);
// Teacher.prototype = new Person();
// Teacher.prototype.constructor = Teacher;
Teacher.prototype.getJob = function() {
  return this.job
}

var teacher = new Teacher('cc', 'teacher')
console.log(teacher.getName());
console.log(teacher.getJob());

class继承

class Person {
  constructor(name) {
    this.name = name
  }
  getName() {
    return this.name
  }
}

class Teacher extends Person {
  constructor(name, job) {
    super(name)
    this.job = job
  }
  getJob() {
    return this.job
  }
}

var teacher = new Teacher('cc', 'teacher')
console.log(teacher.getName());
console.log(teacher.getJob());

20、事件总线(发布订阅模式)

实现思路

1、创建一个对象

2、在该对象上创建一个缓存列表(调度中心)

3、on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)

4、emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)

5、off 方法可以根据 event 值取消订阅(取消订阅)

6、once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)

class EventEmitter {
  constructor() {
    this.cache = {}
  }
  // 订阅
  on(name, fn) {
    if (this.cache[name]) {
      this.cache[name].push(fn)
    } else {
      this.cache[name] = [fn]
    }
  }
  // 取消订阅
  off(name, fn) {
    let tasks = this.cache[name]
    if (tasks) {
      const index = tasks.findIndex(f => f === fn || f.callback === fn)
      if (index >= 0) {
        tasks.splice(index, 1)
      }
    }
  }
  // 发布(once默认为false,为true时表示只订阅一次)
  emit(name, once = false, ...args) {
    if (this.cache[name]) {
      // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
      let tasks = this.cache[name].slice()
      for(let fn of tasks) {
        fn(...args)
      }
      if (once) {
        delete this.cache[name]
      }
    }
  }
}

// 测试
let eventBus = new EventEmitter()
let fn1 = function(name) {
  console.log('name', name)
}
let fn2 = function(age) {
  console.log('age', age)
}
let fn3 = function(age) {
  console.log('my age is', age)
}
eventBus.on('event1', fn1)
eventBus.on('event2', fn2)
eventBus.on('event2', fn3)
eventBus.emit('event1', false, 'ccc')	// name ccc
eventBus.emit('event2', false, 24)		// age 24  my age is 24

21、字符串模板

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; // 如果模板没有模板字符串直接返回
}

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
    name: 'ccc',
    age: 12
}
render(template, person); // 我是ccc,年龄12,性别undefined

22、Promise⭐

promise的使用

function loadImg(src) {
  const promise = new Promise(function(resolve, reject) {
      var img = document.createElement('img')
      img.onload = function() {
          resolve(img)
      }
      img.onerror = function() {
          reject()
      }
      img.src = src
  })
  return promise
}

var src = 'xxx.png'
var result = loadImg(src)
result.then(function(img) {
  console.log(img.width)
}, function() {
  console.log('failed')
})

promise的实现

Promise 实现梳理:

链式调用

错误捕获(冒泡)

const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失败

class Promise {
  constructor(exector) {
    // 初始化状态
    this.status = PENDING;
    // 将成功、失败结果放在this上,便于then、catch访问
    this.value = undefined;
    this.reason = undefined;
    // 成功态回调函数队列
    this.onFulfilledCallbacks = [];
    // 失败态回调函数队列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 成功态函数依次执行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }
    const reject = reason => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 失败态函数依次执行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      // 立即执行executor
      // 把内部的resolve和reject传入executor,用户可调用resolve和reject
      exector(resolve, reject);
    } catch(e) {
      // executor执行出错,将错误内容reject抛出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function'? onRejected:
      reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
    // 保存this
    const self = this;
    return new Promise((resolve, reject) => {
      if (self.status === PENDING) {
        self.onFulfilledCallbacks.push(() => {
          // try捕获错误
          try {
            // 模拟微任务
            setTimeout(() => {
              const result = onFulfilled(self.value);
              // 分两种情况:
              // 1. 回调函数返回值是Promise,执行then操作
              // 2. 如果不是Promise,调用新Promise的resolve函数
              result instanceof Promise ? result.then(resolve, reject) :
              resolve(result)
            })
          } catch(e) {
            reject(e);
          }
        });
        self.onRejectedCallbacks.push(() => {
          // 以下同理
          try {
            setTimeout(() => {
              const result = onRejected(self.reason);
              // 不同点:此时是reject
              result instanceof Promise ? result.then(resolve, reject) : 
              reject(result)
            })
          } catch(e) {
            reject(e);
          }
        })
      } else if (self.status === FULFILLED) {
        try {
          setTimeout(() => {
            const result = onFulfilled(self.value)
            result instanceof Promise ? result.then(resolve, reject) : resolve(result)
          });
        } catch(e) {
          reject(e);
        }
      } else if (self.status === REJECTED){
        try {
          setTimeout(() => {
            const result = onRejected(self.reason);
            result instanceof Promise ? result.then(resolve, reject) : reject(result)
          })
        } catch(e) {
          reject(e)
        }
      }
    });
  }
  catch(onRejected) {
    return this.then(null, onRejected);
  }
  static resolve(value) {
    if (value instanceof Promise) {
      // 如果是Promise实例,直接返回
      return value;
    } else {
      // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
      return new Promise((resolve, reject) => resolve(value));
    }
  }
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    })
  }
}

Promise.prototype.finally = function(callback) {
  this.then(value => {
    return Promise.resolve(callback()).then(() => {
      return value
    })
  }, error => {
    return Promise.resolve(callback()).then(() => {
      throw error
    })
  })
}

实现Promise.resolve

静态方法梳理:

1、传参为一个 Promise,则直接返回它。

2、传参为一个 thenable 对象,返回的 Promise 会跟随这个对象,采用它的最终状态作为自己的状态。

3、其他情况,直接返回以该值为成功状态的 promise 对象。

Promise.resolve = (param) => {
  if (param instanceof Promise) return param // 符合 1
  return new Promise((resolve, reject) => {
    if (param && param.then && typeof param.then === 'function') { // 符合 2
      // param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
      param.then(resolve, reject)
    } else { // 符合 3
      resolve(param)
    }
  })
}

实现Promise.reject

冒泡捕获

Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
      reject(reason)
  })
}

实现promise.all

function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    let resultCount = 0
    let results = new Array(promises.length)

    for (let i = 0; i < promises.length; i++) {
      promises[i].then(value => {
        resultCount++
        results[i] = value
        if (resultCount === promises.length) {
          return resolve(results)
        }
      }, error => {
        reject(error)
      })
    }
  })
}

let p1 = new Promise(resolve => resolve('p1'))
let p2 = new Promise(resolve => resolve('p2'))
let p3 = Promise.reject('p3 error')

promiseAll([p1, p2]).then(results => {
  console.log(results)    // ['p1', 'p2']
}).catch(error => {
  console.log(error)
})

promiseAll([p1, p2, p3]).then(results => {
  console.log(results)
}).catch(error => {
  console.log(error)      // 'p3 error'
})

实现promise.race

function PromiseRace(promises) {
  return new Promise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
          promises[i].then(value => {
              return resolve(value)
          }, error => {
              reject(error)
          })
      }
  })
}

let p1 = new Promise(resolve => resolve('p1'))
let p2 = new Promise(resolve => resolve('p2'))
let p3 = Promise.reject('p3 error')

promiseAll([p1, p2]).then(results => {
  console.log(results)    
}).catch(error => {
  console.log(error)
})
// p1

promiseAll([p1, p2, p3]).then(results => {
  console.log(results)
}).catch(error => {
  console.log(error)      
})
// p1

23、根据数组中对象的某一个属性值进行排序

var arr = [
  { name: 'baby', age: 0 },
  { name: 'aaa', age: 18 },
  { name: 'bbb', age: 8 }
];

// 方法一
function compare(property){
  return function(a,b){
      var value1 = a[property];
      var value2 = b[property];
      return value1 - value2;
  }
}
arr.sort(compare('age'))

// 方法二
arr.sort((a,b) => a.age - b.age)

24、ajax

let xhr = new XMLHttpRequest()      // 创建Ajax对象
xhr.open('get', '/xx', true)        // xhr发送请求    
// true 异步 false 同步 true是在等待服务器响应时执行其他脚本,当响应就绪后对响应进行处理;false是等待服务器响应再执行
xhr.onreadystatechange = function() { 
  if (xhr.readyState == 4) {
    if (xhr.status == 200) {
      console.log(xhr.responseText);
    }
  }
}
xhr.send()    // 如果是post  send(data)

25、封装一个jsonp⭐

function jsonp({url, params, callbackName}) {
  function generateUrl() {
    let dataUrl = ''
    for (let key in params) {
      dataUrl += `${key}=${params[key]}&`
    }
    return `${url}?${dataUrl}callback=${callbackName}`
  }

  return new Promise((resolve, reject) => {
    let scriptEle = document.createElement('script')
    scriptEle.src = generateUrl()
    document.appendChild(scriptEle)
  
    window[callbackName] = data => {
      resolve(data)
      document.removeChild(scriptEle)
    }
  })
}


jsonp({
  url: 'http://xxx', 
  params: {
    name: 'ccc',
    age: 22
  }, 
  callbackName: 'callback1'
}).then((data) => {
  console.log(data);
})

26、利用 async await 实现 sleep 效果

function sleep(fn, delay) {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      resolve(fn)
    }, delay)
  })
}

function sayHello(name) {
  console.log('hello, ' + name);
}

async function test() {
  await sleep(sayHello('ccc'), 1000)
  await sleep(sayHello('sss'), 1000)
  await sleep(sayHello('mmm'), 1000)
}

test()

27、图片懒加载

首先,给所有的图片一个占位资源:

<img src="default.jpg" data-src="https://www.xxx.com/target-1.jpg" />
<img src="default.jpg" data-src="https://www.xxx.com/target-2.jpg" />
......
<img src="default.jpg" data-src="https://www.xxx.com/target-39.jpg" />

1、clientHeight、scrollTop 和 offsetTop

let imgs = document.getElementsByTagName('img'),
    count = 0

function lazyLoad() {
  let clientHeight = document.documentElement.clientHeight
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
  for (let i = count; i < imgs.length; i++) {
    if (clientHeight + scrollTop > imgs[i].offsetHeight) {
      if (imgs[i].getAttribute('src') !== 'default.jpg') continue
      imgs[i].src = imgs[i].getAttribute('data-src')
      count++
    }
  }
}
// 首次加载
lazyLoad()
// 通过监听 scroll 事件来判断图片是否到达视口,别忘了防抖节流
window.addEventListener('scroll', throttle(lazyLoad, 160))

2、getBoundingClientRect

dom 元素的 getBoundingClientRect().top 属性可以直接判断图片是否出现在了当前视口。

只修改一下 lazyLoad 函数

function lazyLoad() {
  for (let i = count; i < imgs.length; i++) {
    if (imgs[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
      if (imgs[i].getAttribute('src') !== 'default.jpg') continue
      imgs[i].src = imgs[i].getAttribute('data-src')
      count++
    }
  }
}

3、IntersectionObserver

IntersectionObserver 浏览器内置的 API,实现了监听 window 的 scroll 事件、判断是否在视口中 以及 节流 三大功能。该 API 需要 polyfill。

let imgs = document.getElementsByTagName('img')

const observe = new IntersectionObserver(() => {
  for(let i = 0; i < imgs.length; i++) {
    // 通过这个属性判断是否在视口中,返回 boolean 值
    if (imgs[i].isIntersecting) {
      const imgElement = img.target
      imgElement.src = imgElement.getAttribute('data-src')
      observe.unobserve(imgElement)   // 解除观察
    }
  }
})

Array.from(imgs).forEach(item => observe.observe(item))

28、事件委托应用

给li绑定点击事件

<ul>
    <li>知否知否,点我应有弹框在手!</li>
    <li>知否知否,点我应有弹框在手!</li>
    <li>知否知否,点我应有弹框在手!</li>
    <li>知否知否,点我应有弹框在手!</li>
    <li>知否知否,点我应有弹框在手!</li>
</ul>
let ul = document.querySelector('ul')
ul.addEventListener('click', function() {
  if (e.target.tagName.toLowerCase() == 'li') {
    e.target.style.backgroundColor = 'blue'
  }
})

通用的事件绑定函数

function bindEvent(elem, type, selector, fn) {
  if (fn = null) {
    fn = selector;
    selector = null;
  }
  elem.addEventListener(type, function(e){
    var target;
    if (selector) {
      target = e.target;
      // matches 用来判断当前DOM节点能否完全匹配对应的CSS选择器规则;
      // 如果匹配成功,返回true,反之则返回false。
      if (target.matches(selector)) {
        fn.call(target, e);
      }
    } else {
      fn(e);
    }
  })
}

// 使用代理
var div1 = document.getElementById('div1');
bindEvent(div1, 'click', 'a', function(e){
  console.log(this.innerHtml);
})
// 不使用代理
var a = document.getElementById('a1');
bindEvent(a, 'click', function(e){
  console.log(a.innerHtml);
})

29、用正则实现 trim() 去掉首位多余的空格

// 去掉首位多余的空格
function trim(str) {
  return str.replace(/^\s+|\s+$/g, '')
}

30、数字转字符串千分位(暂时没有掌握)

方法一:利用字符串提供的toLocaleString()方法处理,此方法最简单

var num = 1450068;
console.log(num.toLocaleString()) // 1,450,068

方法二:截取末尾三个字符的功能可以通过字符串类型的slice、substr或substring方法做到

function toThousandsNum(num) {
  var num = (num || 0).toString(),
        result = '';

   while (num.length > 3) {
       //此处用数组的slice方法,如果是负数,那么它规定从数组尾部开始算起的位置
       result = ',' + num.slice(-3) + result;
       num = num.slice(0, num.length - 3);
   }
   // 如果数字的开头为0,不需要逗号
   if (num){
     result = num + result
   }
   return result;
}

console.log(toThousandsNum(000123456789123)) // 123,456,789,123

方法三:

把数字通过toString,转换成字符串后,打散为数组,再从末尾开始,逐个把数组中的元素插入到新数组(result)的开头,每插入一个元素,counter就计一次数(加1),当counter为3的倍数时,利用取余的方式,就插入一个逗号,但是要注意开头(i为0时)不需要逗号。最后通过调用新数组的join方法得出结果

function toThousands(num) {
  var result = [],
  counter = 0;
  num = (num || 0).toString().split('');
  for (var i = num.length - 1; i >= 0; i--) {
      counter++;
      result.unshift(num[i]);
      if (!(counter % 3) && i != 0) {
         result.unshift(',');
      }
  }
  return result.join('');
}
console.log(toThousands(236471283572983412)); // 236,471,283,572,983,420

方法四:不把字符串打散为数组,始终对字符串操作,下面的这种操作字符串的方式是对上面的改良

function toThousands(num) {
  var result = '',
  counter = 0;
  num = (num || 0).toString();
  for (var i = num.length - 1; i >= 0; i--) {
      counter++;
      result = num.charAt(i) + result;
      if (!(counter % 3) && i != 0) {
        result = ',' + result;
          
      }
  }
  return result;
}
console.log(toThousands(42371582378423))  // 42,371,582,378,423

方法五:正则表达式,此方法个人觉得比较难以理解,网上大牛写的

function toThousands(num) {
  var numStr = (num || 0).toString();
   return numStr.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
}


function thousandth(str) {
  return str.replace(/\d(?=(?:\d{3})+(?:\.\d+|$))/g, '$&,');
}

31、文件大小单位转换

function formatSizeUnits(kb) {
  let units = ['KB', 'MB', 'GB', 'TB', 'PB'];
  let unitIndex = 0;

  while (kb >= 1024 && unitIndex < units.length - 1) {
    kb /= 1024;
    unitIndex++;
  }

  return `${kb.toFixed(2)} ${units[unitIndex]}`;
}

31、求两个数组的并集、交集和差集

const arr1 = [1, 3, 4, 6, 7]
const arr2 = [2, 5, 3, 6, 1]

// 并集
function getUnion(arr1, arr2) {
  const res = new Set(arr1)
  for (let item of arr2) {
    res.add(item) // 利用 Set 自动去重的特性
  }
  return Array.from(res)
}

// 交集
function getIntersection(arr1, arr2) {
  const res = new Set()
  const set2 = new Set(arr2)
  for (let item of arr1) {
    if (set2.has(item)) {
      // 注意,这里要用 Set has 方法,比数组的 includes 快很多
      res.add(item)
    }
  }
  return Array.from(res)
}

// 差集
function getDifference(arr1, arr2) {
  const res = new Set()
  const set2 = new Set(arr2)
  for (let item of arr1) {
    if (!set2.has(item)) {
      res.add(item)
    }
  }
  return Array.from(res)
}

32、数组转树⭐

通常我们有一个包含父子关系的数组,目标是将其转化为树形结构。

示例数据:

const arr = [
  { id: 1, parentId: null, name: 'Root' },
  { id: 2, parentId: 1, name: 'Child 1' },
  { id: 3, parentId: 1, name: 'Child 2' },
  { id: 4, parentId: 2, name: 'Grandchild 1' },
]

目标生成:

const tree = [
  {
    id: 1,
    name: 'Root',
    children: [
      {
        id: 2,
        name: 'Child 1',
        children: [{ id: 4, name: 'Grandchild 1', children: [] }],
      },
      {
        id: 3,
        name: 'Child 2',
        children: [],
      },
    ],
  },
]

参考答案:

实现思路:

  1. 遍历数组,将每个元素存储到一个以 id 为键的 Map 中。
  2. 再次遍历数组,根据 parentId 将子节点挂载到父节点的 children 属性上。
  3. 提取 parentId 为 null 的顶层节点作为树的根。

代码实现

function arrayToTree(arr) {
  const idMap = new Map()
  const result = []

  // 初始化 Map
  arr.forEach((item) => {
    idMap.set(item.id, { ...item, children: [] })
  })

  // 构建树
  arr.forEach((item) => {
    const parent = idMap.get(item.parentId)
    if (parent) {
      parent.children.push(idMap.get(item.id))
    } else {
      result.push(idMap.get(item.id))
    }
  })

  return result
}

console.log(JSON.stringify(arrayToTree(arr), null, 2))

注意点:

  • 确保 parentId 为 null 的节点是根节点。
  • 避免循环依赖:输入数据需要合法,否则会导致死循环。

33、树转数组⭐

将树形结构扁平化为数组,保留原有的层级关系。

示例数据:

const tree = [
  {
    id: 1,
    name: 'Root',
    children: [
      {
        id: 2,
        name: 'Child 1',
        children: [{ id: 4, name: 'Grandchild 1', children: [] }],
      },
      {
        id: 3,
        name: 'Child 2',
        children: [],
      },
    ],
  },
]

目标生成:

const arr = [
  { id: 1, name: 'Root', parentId: null },
  { id: 2, name: 'Child 1', parentId: 1 },
  { id: 3, name: 'Child 2', parentId: 1 },
  { id: 4, name: 'Grandchild 1', parentId: 2 },
]

参考答案:

实现思路:

  1. 使用递归遍历树。
  2. 在每次递归中记录当前节点的 parentId。
  3. 将节点及其子节点逐一添加到结果数组中。

代码实现:

function treeToArray(tree, parentId = null) {
  const result = []

  tree.forEach((node) => {
    const { id, name, children } = node
    result.push({ id, name, parentId })
    if (children && children.length > 0) {
      result.push(...treeToArray(children, id))
    }
  })

  return result
}

console.log(JSON.stringify(treeToArray(tree), null, 2))

注意点:

  • 递归中需避免重复引用。
  • 树节点的 children 属性需要有效(可以为空数组但不能为 undefined)。
最近更新: 2025/7/19 07:56
Contributors: csmSimona, chenshimeng