前端面试高频手写题

197 阅读11分钟

防抖

  1. 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
  2. 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
  3. 文本编辑器实时保存,当无任何更改操作一秒后进行保存
const debounce = (fn, delay) => {
  let timer = null
  return function (...args) {
    clearTimeout(timer)
    let that = this
    timer = setTimeout(() => {
      fn.call(that, ...args)
    }, delay)
  }
}

节流

  1. scroll 事件,每隔一秒计算一次位置信息等
  2. 浏览器播放事件,每隔一秒计算一次进度信息等
  3. input 框实时搜索并发送请求展示下拉列表,没隔一秒发送一次请求 (也可做防抖)
const throttle = (fn, delay) => {
  let flag = true
  return function () {
    if (!flag) return
    flag = false
    setTimeout(() => {
      fn()
      flag = true
    }, delay)
  }
}

数组平铺

方法一

function flatten(arr) {
  let res = []
  for (const item of arr) {
    if (Array.isArray(item)) {
      res = res.concat(flatten(item))
    } else {
      res.push(item)
    }
  }
  return res
}

方法二

function flatten(arr) {
  while (arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr)
  }
  return arr
}

方法三

function flatten(arr) {
  return arr.flat(Infinity)
}

拓展:指定deep层级

函数的柯里化

function add(...arg1) {
  let x = arg1.reduce((a, b) => a + b, 0)
  return function (...arg2) {
    if (arg2.length === 0) return x
    let y = arg2.reduce((a, b) => a + b, 0)
    return add(x + y)
  }
}

console.log(add(1)(2)(3, 4, 5)()) // 15

函数的柯里化II

var curry = function (f) {
  var len = f.length // 函数的参数个数
  return function t() {
    var innerLength = arguments.length
    var args = Array.from(arguments)
    if (innerLength >= len) {
      // 递归出口,f.length
      return f(...args)
    } else {
      return function () {
        var innerArgs = Array.from(arguments)
        var allArgs = args.concat(innerArgs)
        return t.apply(undefined, allArgs)
      }
    }
  }
}

// 测试一下
function add(num1, num2) {
  return num1 + num2
}
var curriedAdd = curry(add)
console.log(curriedAdd(2)(3)) //5

深拷贝

function deepCopy(obj) {
  if (typeof obj === 'object') {
    const res = obj instanceof Array ? [] : {}
    for (let key in obj) {
      if (typeof obj[key] === 'object') {
        res[key] = deepCopy(obj[key])
      } else {
        res[key] = obj[key]
      }
    }
    return res
  } else {
    const res = obj
    return res
  }
}


// 测试
const obj = {
  name: 'yogln',
  age: 18,
  friends: {
    name: 'John'
  }
}
const newObj = deepCopy(obj)
newObj.friends.name = 'Kobe'
console.log(obj.friends)

手写new

new是一个关键字,我们不能重写,我们可以通过它内部的原理进行模拟

function _new(fn, ...args) {
  const obj = Object.create(fn.prototype)
  const newObj = fn.call(obj, ...args)
  return newObj instanceof Object ? newObj : obj
}

new的内部实现其实很简单,创建一个对象和fn.prototype关联起来,将fnobj里调用一次,判断返回的值是否是对象类型,如果是就返回新的对象,否则就返回创建的对象。

// 测试
function Person(name) {
  this.name = name
}
Person.prototype.sayName = function () {
  console.log(`My name is ${this.name}`)
}
const me = _new(Person, 'Jack')
me.sayName()
console.log(me)

实现instanceof

根据原型链进行查找判断 leftValue 是否为rightValue 的实例,思想是在 leftValue的原型链上,即leftValue.__proto__上寻找是否存在 rightValue.prototype

function newInstanceOf(left, right) {
  let proto = left.__proto__
  let prototype = right.prototype

  while (true) {
    if (proto === prototype) return true
    if (proto === null) return false
    proto = proto.__proto__
  }
}

测试

// 测试
class Parent {}
class Child extends Parent {}
const child = new Child()
console.log(
  newInstanceOf(child, Parent),
  newInstanceOf(child, Child),
  newInstanceOf(child, Array)
)

apply

apply方法后面是单个参数

Function.prototype.myApply = function (ctx) {
  ctx = ctx === undefined || ctx === null ? globalThis : ctx
  ctx.fn = this
  let args = Array.from(arguments).slice(1)
  ctx.fn(...args)
  delete ctx.fn
}

// 测试
let obj = { name: 'yogln' }
function sayName() {
  console.log(...arguments)
  console.log('my name is ' + this.name)
}
sayName.myApply(obj, 123)

call

call方法后面是多个参数

Function.prototype.myCall = function (ctx) {
  ctx = ctx === undefined || ctx === null ? globalThis : ctx
  ctx.fn = this
  const args = Array.from(arguments).slice(1)
  ctx.fn(...args)
  delete ctx.fn
}
// 测试
const me = { name: 'Jack' }
function say() {
  console.log(...arguments)
  console.log(`My name is ${this.name || 'default'}`)
}
say.myCall(me, 123, 456)

bind

bind方法内部不要调用call或者apply方法

Function.prototype.myBind = function (ctx) {
  let that = this
  let args = Array.from(arguments).slice(1)
  function fBind() {
    that.apply(
      this instanceof fBind ? this : ctx,
      args.concat(Array.from(arguments).slice(1))
    )
  }
  fBind.prototype = Object.create(this.prototype)
  return fBind
}

// 测试
const me = { name: 'yogln' }
function sayName() {
  console.log('my name is ' + this.name)
}
sayName.myBind(me)()

实现promise

const PENDING = Symbol()
const FULLFILLED = Symbol()
const REJECTED = Symbol()

function MyPromise(fn) {
  this.state = PENDING
  this.value = ''

  const resolve = value => {
    this.state = FULLFILLED
    this.value = value
  }

  const reject = reason => {
    this.state = REJECTED
    this.value = reason
  }

  this.then = (resolve, reject) => {
    if (this.state === FULLFILLED) {
      resolve(this.value)
    } else {
      reject(this.value)
    }
  }

  try {
    fn(resolve, reject)
  } catch (error) {
    reject(error)
  }
}

// 测试
const p = new MyPromise((resolve, reject) => {
  resolve('hello')
})
p.then(res => console.log(res))

实现promiseAll

function myPromiseAll(arr) {
  return new Promise(function (resolve, reject) {
    let res = []
    for (let i = 0; i < arr.length; i++) {
      arr[i]
        .then(value => {
          res[i] = value
          if (res.length === arr.length) resolve(res)
        })
        .catch(err => reject(err))
    }
  })
}

// 测试
let p1 = Promise.resolve(1),
  p2 = Promise.resolve(2),
  p3 = Promise.resolve(3)

myPromiseAll([p1, p2, p3]).then(
  res => {
    console.log(res, 'res')
  },
  err => {
    console.log(err, 'err')
  }
)

实现promisify

promisify是将一个函数转成promise实现

function promisify(fn) {
  return function (...args) {
    return new Promise(function (resolve, reject) {
      args.push((err, ...values) => {
        if (err) return reject(err)
        resolve(...values)
      })
      fn.call(this, ...args)
    })
  }
}

function func1(a, b, c, callback) {
  callback(null, a + b + c)
}

const func2 = promisify(func1)
func2(1, 2, 3).then(res => console.log(res))

发布订阅者模式

class EventEmitter {
  constructor() {
    this.cache = {}
  }

  on(name, fn) {
    if (this.cache[name]) {
      this.cache[name].push(fn)
    } else {
      this.cache[name] = [fn]
    }
  }

  emit(name, once = false) {
    if (this.cache[name]) {
      this.cache[name].forEach(fn => fn())
    }
    if (once) {
      delete this.cache[name]
    }
  }

  off(name, fn) {
    if (this.cache[name]) {
      let tasks = this.cache[name]
      const index = tasks.findIndex(f => f === fn)
      if (index !== -1) {
        tasks.splice(index, 1)
      }
    }
  }
}

// 测试
const e = new EventEmitter()
const task1 = () => console.log('task1')
const task2 = () => console.log('task2')
e.on('task', task1)
e.on('task', task2)
e.off('task', task1)
e.emit('task', true)
console.log(e.cache)

lazyman

LazyMan('jack').eat('lunch').sleep(2).eat('dinner').sleepFirst(2)

2s后输出jack,lunch,再过2s后dinner

class _LazyMan {
  constructor(name) {
    this.name = name
    this.list = []
    this.sayName(name)
    setTimeout(async () => {
      for (const fn of this.list) {
        await fn()
      }
    })
  }

  sayName(name) {
    this.list.push(() => {
      console.log(`i am ${name}`)
    })
    return this
  }

  eat(what) {
    this.list.push(() => {
      console.log(`eat ${what}`)
    })
    return this
  }

  _hold(time) {
    return function () {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log(`${time}s 后`)
          resolve()
        }, time * 1000)
      })
    }
  }

  sleep(time) {
    this.list.push(this._hold(time))
    return this
  }

  sleepFirst(time) {
    this.list.unshift(this._hold(time))
    return this
  }
}


//测试
const LazyMan = name => new _LazyMan(name)
LazyMan('jack').eat('lunch').sleep(2).eat('dinner').sleepFirst(2)

reduce实现map

Array.prototype._map = function (fn, thisArg) {
    const res = [];
    this.reduce((pre, cur, index, arr) => {
        res[index] = fn.call(thisArg, arr[index], index);
    }, 0);
    return res;
};

// 测试
const arr1 = [1, 2, 3, 4, 5];
const obj = { multiplier: 2 };
const res = arr1._map(function (item, index) {
    return item * this.multiplier + index;
}, obj);

console.log(res);

并发限制

class Scheduler {
  constructor(num) {
    this.list = []
    this.count = 0
    this.maxNum = num
  }

  add(time, order) {
    const promiseCreator = () => {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(order)
        }, time)
      })
    }
    this.list.push(promiseCreator)
  }

  start() {
    for (let i = 0; i < this.list.length; i++) {
      this.next()
    }
  }

  next() {
    if (this.list.length && this.count < this.maxNum) {
      this.count++
      this.list
        .shift()()
        .then(res => {
          console.log(res)
          this.count--
          this.next()
        })
    }
  }
}

const scheduler = new Scheduler(2)
const addTask = (time, order) => {
  scheduler.add(time, order)
}

addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')
scheduler.start()

寄生组合继承

function Animal(name) {
  this.name = name
}
Animal.prototype.sayName = function () {
  console.log(`i am ${this.name}`)
}

function Dog(name) {
  Animal.call(this, name)
}

Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog

let dog = new Dog('旺财')
dog.sayName()

原型链继承

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

function Dog() {}
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog

const dog = new Dog()
console.log(dog.getName())

封装继承方法实现继承

function inherit(target, origin) {
  function Fn() {}
  Fn.prototype = origin.prototype
  const fn = new Fn()
  target.prototype = fn
  fn.constructor = target
}

function Animal() {
  this.name = 'animal'
}
Animal.prototype.say = function () {
  console.log(this.name)
}

function Dog() {
  Animal.call(this)
}
inherit(Dog, Animal)
const dog = new Dog()
dog.say()

实现compose函数的实现

// 用法如下:
function fn1(x) {
  return x + 1
}
function fn2(x) {
  return x + 2
}
function fn3(x) {
  return x + 3
}
function fn4(x) {
  return x + 4
}
const a = compose(fn1, fn2, fn3, fn4)
console.log(a(1)) // 1+4+3+2+1=11

递归实现

function compose(...funcs) {
  let count = funcs.length - 1
  let res = 0
  return function fn(x) {
    if (count < 0) return res
    else {
      res = funcs[count--](x)
      return fn(res)
    }
  }
}

迭代实现

function compose(...funcs) {
  function callback(f, g) {
    return function (x) {
      return f(g(x))
    }
  }

数组的reduce方法实现

function compose(...funcs) {
  if (funcs.length === 0) return x => x
  if (funcs.length === 1) return funcs[0]
  return funcs.reduce(
    (a, b) =>
      (...args) =>
        a(b(...args))
  )
}

冒泡排序

注意冒泡排序最有的时间复杂度为On,也就是说给一个有序的数组只需要遍历一次就可以,所以有一个flag用来检测数组是否已经有序

function bubbleSort(arr) {
  let flag
  for (let i = 0; i < arr.length; i++) {
    flag = false
    for (let j = 0; j < arr.length - i; j++) {
      if (arr[j] > arr[j + 1]) {
        flag = true
        ;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
      }
    }
    if (!flag) break
  }
  return arr
}

选择排序

function selectionSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    let minIndex = i
    for (let j = minIndex; j < arr.length; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j
      }
    }
    ;[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
  }
  return arr
}

插入排序

function insertSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    let j = i
    let target = arr[i]
    while (j >= 0 && arr[j - 1] > target) {
      arr[j] = arr[j - 1]
      j--
    }
    arr[j] = target
  }
  return arr
}

归并排序

function merge(left, right) {
  let [i, j] = [0, 0]
  const res = []
  while (i < left.length && j < right.length) {
    if (left[i] < right[j]) {
      res.push(left[i])
      i++
    } else {
      res.push(right[j])
      j++
    }
  }

  if (i < left.length) res.push(...left.slice(i))
  else res.push(...right.slice(j))
  return res
}

function mergeSort(arr) {
  if (arr.length < 2) return arr
  let mid = Math.floor(arr.length / 2)
  let left = mergeSort(arr.slice(0, mid))
  let right = mergeSort(arr.slice(mid))
  return merge(left, right)
}

快速排序递归版

function quicksort(arr) {
  if (arr.length < 2) return arr
  let pivot = arr.splice(0, 1)[0]
  let left = []
  let right = []
  for (const item of arr) {
    if (item <= pivot) left.push(item)
    else right.push(item)
  }
  return quicksort(left).concat(pivot, quicksort(right))
}

快速排序非递归版

function quickSort(arr) {
  let list = [[0, arr.length - 1]]
  while (list.length) {
    let now = list.pop()
    let [L, R] = [now[0], now[1]]
    if (L >= R) continue
    let pivot = arr[L]
    while (L < R) {
      while (L < R && arr[R] >= pivot) R--
      arr[L] = arr[R]
      while (L < R && arr[L] < pivot) L++
      arr[R] = arr[L]
    }
    arr[L] = pivot
    list.push([now[0], L - 1])
    list.push([L + 1, now[1]])
  }
  return arr
}

setTimeout实现setInterval

setInterval 被推入任务队列时,如果在它前面有很多任务或者某个任务等待时间较长比如网络请求等,那么这个定时器的执行时间和我们预定它执行的时间可能并不一致

虑极端情况,假如定时器里面的代码需要进行大量的计算(耗费时间较长),或者是 DOM 操作。这样一来,花的时间就比较长,有可能前一 次代码还没有执行完,后一次代码就被添加到队列了。也会到时定时器变得不准确,甚至出现同一时间执行两次的情况。

setInterval 有两个缺点:

  • 使用 setInterval 时,某些间隔会被跳过;
  • 可能多个定时器会连续执行;

综上所述,在某些情况下,setInterval 缺点是很明显的,为了解决这些弊端,可以使用 setTimeout() 代替。

  • 在前一个定时器执行完前,不会向队列插入新的定时器(解决缺点一)
  • 保证定时器间隔(解决缺点二)
function myInterval(fn, time) {
  let timer
  function interval() {
    timer = setTimeout(() => {
      fn()
      interval()
    }, time)
  }
  interval()
  return function () {
    clearTimeout(timer)
  }
}

const cancel = myInterval(() => console.log('hello'), 1000)
// cancel() // 取消执行

setInterval实现setTimeout

function mySetTimeout(fn, delay) {
  let timer = setInterval(() => {
    fn()
    clearInterval(timer)
  }, delay)
}

mySetTimeout(() => console.log('hello'), 2000)

链表

实现一个链表,可以添加、插入、删除、获取某个值

class Node {
  constructor(element) {
    this.element = element
    this.next = null
  }
}

class LinkedList {
  constructor() {
    this.size = 0
    this.head = null
  }

  append(element) {
    let node = new Node(element)
    if (!this.head) {
      this.head = node
    } else {
      let current = this.getNode(this.size - 1)
      current.next = node
    }
    this.size++
  }

  insert(pos, element) {
    if (pos < 0 || pos > this.size) throw new RangeError('out range')
    let node = new Node(element)
    if (pos === 0) {
      node.next = this.head
      this.head = node
    } else {
      let preNode = this.getNode(pos - 1)
      node.next = preNode.next
      preNode.next = node
    }
    this.size++
  }

  remove(pos) {
    if (pos < 0 || pos >= this.size) throw new RangeError('out range')
    let current = this.head
    if (pos === 0) {
      this.head = current.next
    } else {
      let preNode = this.getNode(pos - 1)
      preNode.next = preNode.next.next
    }
    this.size--
  }

  indexOf(element) {
    let current = this.head
    for (let i = 0; i < this.size; i++) {
      if (current.element === element) return i
      else current = current.next
    }
    return -1
  }

  getNode(index) {
    if (index < 0 || index >= this.size) throw new RangeError('out range')
    let current = this.head
    for (let i = 0; i < index; i++) {
      current = current.next
    }
    return current
  }
}

let ll = new LinkedList()
ll.append(1)
ll.append(2)
// ll.append(3)
ll.insert(2, 3)
ll.insert(0, 0)
ll.remove(1)

console.log(ll.indexOf(1))
// console.dir(ll, {
//   depth: 10
// })

const Stack = (function () {
  let _items = new WeakMap()

  return class {
    constructor() {
      _items.set(this, [])
    }

    push(elem) {
      _items.get(this).push(elem)
    }

    pop() {
      return _items.get(this).pop()
    }

    peek() {
      return _items.get(this)[_items.get(this).length - 1]
    }

    size() {
      return _items.get(this).length
    }
  }
})()

let stack = new Stack()
stack.push(1)
stack.push(2)
stack.push(3)
console.log(stack.peek())
console.log(stack.pop())
console.log(stack.size())

实现一个get请求

function get(url) {
    return new Promise(function (resolve, reject) {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', url, true)
      xhr.send()
      xhr.onload = function () {
        resolve(xhr.responseText)
      }
    })
  }

圣杯布局

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      body {
        min-width: 600px;
      }
      .header,
      .footer {
        height: 50px;
        width: 100%;
        background-color: #ccc;
      }
      .middle,
      .left,
      .right {
        float: left;
      }
      .content {
        padding: 0 200px;
        overflow: hidden;
      }
      .left,
      .right {
        width: 200px;
        height: 200px;
        background-color: rgb(202, 50, 113);
      }
      .middle {
        width: 100%;
        height: 200px;
        background-color: rgb(28, 134, 60);
      }
      .left {
        margin-left: -100%;
        position: relative;
        left: -200px;
      }
      .right {
        margin-left: -200px;
        position: relative;
        left: 200px;
      }
    </style>
  </head>
  <body>
    <div class="wrap">
      <div class="header"></div>
      <div class="content">
        <div class="middle"></div>
        <div class="left"></div>
        <div class="right"></div>
      </div>
      <div class="footer"></div>
    </div>
  </body>
</html>

双飞翼布局

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .header,
      .footer {
        width: 100%;
        height: 50px;
        background-color: #ccc;
      }
      .left,
      .right {
        width: 200px;
        height: 200px;
        background-color: rgb(48, 170, 58);
      }
      .middle {
        background-color: pink;
        height: 200px;
        width: 100%;
        float: left;
      }
      .content {
        overflow: hidden;
      }
      .left {
        float: left;
        margin-left: -100%;
      }
      .right {
        float: left;
        margin-left: -200px;
      }
      .middle-inner {
        margin: 0 200px;
      }
    </style>
  </head>
  <body>
    <div class="header"></div>
    <div class="content">
      <div class="middle">
        <div class="middle-inner"></div>
      </div>
      <div class="left"></div>
      <div class="right"></div>
    </div>
    <div class="footer"></div>
  </body>
</html>

宽度是父元素一般的正方形

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      .outer {
        width: 600px;
        height: 400px;
        background-color: rgb(53, 61, 134);
      }
      /* padding 的百分比数值是相对父元素宽度的宽度计算的。
			所以只需将元素垂直方向的一个 padding 值设定为与 width 相同的百分比就可以制作出自适应正方形了: */
      /* 方式一 */
      .inner {
        background-color: #ccc;
        width: 50%;
        padding-top: 50%;
      }
    </style>
  </head>
  <body>
    <div class="outer">
      <div class="inner"></div>
    </div>
  </body>
</html>

三角形

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    .box {
      width: 0;
      border-top: 40px solid transparent;
      border-left: 40px solid transparent;
      border-right: 40px solid transparent;
      border-bottom: 40px solid #ff0000;
			
      /* 方式二 */
      /* width: 0;
      border: 40px solid transparent;
      border-top-color: aqua; */
    }
  </style>
  <body>
    <div class="box"></div>
  </body>
</html>

0.5px的下划线

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <style>
        /* 方法一 */
        /* .box {
        width: 200px;
        height: 200px;
        background-color: red;
        margin: 0 auto;
        border-bottom: 1px solid transparent;
        border-image: linear-gradient(to bottom, transparent 50%, Green 50%) 0 0
        100% 0;
        } */

        /* 方法二 */
        /* .box {
        width: 200px;
        height: 200px;
        position: relative;
        margin: 0 auto;
        background-color: red;
        }
        .box::before {
        content: '';
        position: absolute;
        left: 0;
        bottom: 0;
        width: 100%;
        height: 1px;
        background-image: linear-gradient(to bottom, transparent 50%, Green 50%);
        } */

        /* 方法三 */
        .box {
            width: 200px;
            height: 0px;
            position: relative;
            margin: 0 auto;
            background-color: red;
        }
        .box::after {
            content: '';
            position: absolute;
            left: 0;
            top: 0;
            background-color: rgb(30, 32, 146);
            width: 100%;
            height: 1px;
            transform: scaleY(0.5);
        }
    </style>
    <body>
        <div class="box"></div>
    </body>
</html>

0.5px的正方形

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    .box {
      width: 200px;
      height: 200px;
      /* background-color: red; */
      position: relative;
    }
    /* .box::after {
      content: '';
      height: 200%;
      width: 200%;
      position: absolute;
      left: 0;
      top: 0;
      border: 1px solid blue;
      transform: scale(0.5);
      transform-origin: 0 0;
    } */
    .box::after {
      content: '';
      position: absolute;
      left: 0;
      top: 0;
      border: 1px solid blue;
      width: 200%;
      height: 200%;
      transform: scale(0.5);
      transform-origin: 0 0;
    }
  </style>
  <body>
    <div class="box"></div>
  </body>
</html>

图片懒加载

<img data-src="./images/img_1.JPG" />
<img data-src="./images/img_2.JPG" />
<img data-src="./images/img_3.JPG" />
<img data-src="./images/img_4.JPG" />

JS部分使用IntersectionObserver

// const observer = new IntersectionObserver(callback);
// observer.observe(DOM)
// observer.unobserve(DOM);
const callback = (entries) => {
  console.log('entries', entries);
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      console.log('触发');
      const image = entry.target;
      const data_src = image.getAttribute('data-src');
      image.setAttribute('src', data_src);
      observer.unobserve(image);
    }
  });
};

const observer = new IntersectionObserver(callback);
const images = document.querySelectorAll('img');
images.forEach((image) => {
  observer.observe(image);
});

手写ajax

ajax就是记住4中API

  • 1个构造函数:XMLHttpRequest;用于创建
  • 1个时间:readystatechange;用于判断状态
  • 2个方法:open,send。用于开始
  • 3个属性:readState,status,responseText。用于结束

顺序

#做什么伪代码readyState
1XMLHttpRequest()创建const xhr = new XMLHttpRequest();xhr.readyState = 0;
2open()准备xhr.open(method, url);xhr.readyState = 1;
3onReadyStateChange事件处理- readyState返回
  • status 结果状态码。 | xhr.onreadystatechange- xhr.status状态码
  • xhr.responseText值 | xhr.readyState = 4; | | 4 | send()发送 | xhr.send();无参数 | |

代码:

const ajax = {
  get: function (url, fn) {
    const xhr = new XMLHttpRequest();
    xhr.open('get', url);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if ((xhr.status > 200 && xhr.status < 300) || xhr.status === 304) {
          fn(xhr.responseText);
        }
      }
    };
    xhr.send();
  },
};

// ajax.get('http://127.0.0.1:3000/', console.log);

手写async await

// 1. 定义一个promise
const p = new Promise((resolve, reject) => {
  setTimeout(() => resolve('p'), 500);
});

const myAsync = (generator) => {
  const iterator = generator();

  const handle = (iteratorResult) => {
    if (iteratorResult.done) {
      return;
    }

    const iteratorValue = iteratorResult.value;
    if (iteratorValue instanceof Promise) {
      iteratorValue.then((result) => handle(iterator.next(result))).catch((err) => iterator.throw(err));
    }
  };

  handle(iterator.next());
};

myAsync(function* generaFun() {
  const data = yield p;
  console.log(data);
});

判断是否是promise

怎么判断是一个promise
定义:promiseA+闺房中 promise是一个object或者是一个函数并且有then方法
原因:因为历史原因,类promise对象都实现了then接口
注意:不用instanceof 和 Object.prototype.toString()

function isPromiseList(value) {
  return !!value && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function';
}

const p = new Promise(() => {});

console.log(isPromiseList(p));
console.log(isPromiseList(null));

请求超时返回失败

思路:利用promise.race等待指定时间,超过n秒返回请求失败:

代码:

function overTime(request, time) {
  function failTask() {
    return new Promise((_, reject) => {
      setTimeout(() => {
        reject(new Error('请求失败'));
      }, time);
    });
  }
  return Promise.race([request(), failTask()]);
}

测试:

const getData1 = () => {
    return new Promise((resolve) => [
        setTimeout(() => {
            resolve('success');
        }, 1000),
    ]);
};

const getData2 = () => {
    return new Promise((resolve) => [
        setTimeout(() => {
            resolve('success');
        }, 4000),
    ]);
};

console.log(new Date());
overTime(getData1, 3000)
    .then((value) => {
        console.log(value);
    })
    .catch((err) => {
        console.log('err', err);
    });

overTime(getData2, 3000)
    .then((value) => {
        console.log(value);
    })
    .catch((err) => {
        console.log('err', err);
    });

红绿灯(经典问题)

描述:每隔3,2,1秒输出红绿黄灯并且循环

function red() {
    console.log('red', new Date());
}

function green() {
    console.log('green', new Date());
}

function yellow() {
    console.log('yellow', new Date());
}

function task(time, light) {
    return new Promise((resolve) => {
        setTimeout(() => {
            if (light === 'red') {
                red();
            } else if (light === 'green') {
                green();
            } else {
                yellow();
            }
            resolve();
        }, time);
    });
}

function promiseRunTask() {
    task(3000, 'red')
        .then(() => task(2000, 'green'))
        .then(() => task(1000, 'yellow'))
        .then(() => promiseRunTask());
}

promiseRunTask();

async function asyncRunTask() {
    await task(3000, 'red');
    await task(2000, 'green');
    await task(1000, 'yellow');
}

asyncRunTask();