Skip to content

Coding

1. Array

去重

javascript
// filter去重
function unique(arr){
  return arr.filter((item,index,array) => {
  //return array.lastIndexOf(item) === index // [3, 1, 2, 4]
    return array.indexOf(item) === index
  })
}
let arr = [1,2,3,1,2,4]
unique(arr) // [1, 2, 3, 4]
javascript
// reduce去重
function Uniq(arr = []) {
  return arr.reduce((prev, next) => prev.includes(next) ? prev : [...prev, next], [])
}
let arr = [1,2,3,1,2,4]
Uniq(arr) // [1, 2, 3, 4]

set 去重

javascript
let unique = arr => Array.from(new Set(arr))
let arr = [1,2,3,1,2,4]
unique(arr) // [1, 2, 3, 4]
javascript
let unique = arr => [...new Set(arr)]
let arr = [1,2,3,1,2,4]
unique(arr) // [1, 2, 3, 4]

找到数组中重复的元素

javascript
function repeat(arr) {
  let res =  arr.filter((item,index,array) =>{
   // return array.indexOf(item) !== index  // [1,2]
    return  array.lastIndexOf(item) !== index
  })
  return res
}
let arr = [1,2,3,1,2,4]
repeat(arr) //[1,2]

数组扁平

javascript
function flatten(arr) {
  return [].concat(...arr.map(item =>{
    return Array.isArray(item) ? flatten(item) : item
    })
  )
}
let arr = [[1, 2], 3, [[[4], 5]]]
flatten(arr) // [1, 2, 3, 4, 5]
javascript
// Array.prototype.flat(depth = 1)
let arr = [[1, 2], 3, [4], 5]
let res =arr.flat() //  [1, 2, 3, 4, 5]
javascript
// reduce
function Flat(arr = []){
  return arr.reduce((prev, next) => prev.concat(Array.isArray(next) ? Flat(next) : next),[] )
}
const arr = [0, 1, [2, 3], [4, 5, [6, 7]], [8, [9, 10, [11, 12]]]]
Flat(arr) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

类数组转换为数组

javascript
let arrayLike = {
  length: 3,
  0: 'a',
  1: 'b',
  2: 'c',
  3: 'd',
  4: 'e',
}
let arr1 = Array.from(arrayLike) // ['a', 'b', 'c']
let arr2 = Array.prototype.slice.call(arrayLike) // ['a', 'b', 'c']

2. 浅拷贝

clone

  • 如果属性是基本类型,拷贝的就是基本类型的值
  • 如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
js
function clone(obj) {
  let target
  for(key in obj){
    target[key] = obj[key]
  }
  return target
}

Object.assign()

Object.assign方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象

js
let obj1 = {person:{name:"Tom",age:20},job:"teacher"}
let obj2 = Object.assign({},obj1)

函数库lodash的_.clone方法

js
let _ = require("lodash")
let obj1 = {person:{name:"Tom",age:20},job:"teacher"}
let obj2 = _.clone(obj1)

展开运算符 ...

javascript
let obj1 = {person:{name:"Tom",age:20},job:"teacher"}
let obj2 = {... obj1}

Array.prototype.concat()

js
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = arr1.concat(arr2)

Array.prototype.slice()

js
let arr1 = [1,2,3]
let arr2 = arr1.slice()

3. 深拷贝

deepClone

  • 深拷贝是将一个对象从内存中完整的拷贝一份出来
  • 从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
js
function deepClone(obj){
  let target
  if(typeof obj !== Object){
    throw new Error("obj must be an Object!")
  }
  if(typeof obj === Object){
    for(key in obj){
      target[key] = deepClone(obj[key])
    }
    return target
  }else{
    return obj
  }
}

JSON.parse(JSON.stringify())

  • 这种方法虽然可以实现数组或对象深拷贝,但不能处理正则和函数
  • 如果使用这种方法正则变为空对象,函数变为null
js
let data1 = [1,{name: "John", age: 20},/\d$/,function(){}]
let data2 = JSON.parse(JSON.stringify(data1))
console.log(data2)
// 0: 1
// 1: {name: 'John', age: 20}
// 2: {} // 正则变为空对象
// 3: null  // 函数变为null
// length: 4

函数库lodash的_.cloneDeep方法

js
let _ = require("lodash")
let obj1 = {person:{name:"Tom",age:20},job:"teacher"}
let obj2 = _.cloneDeep(obj1)

jQuery.extend()方法

js
var $ = require('jquery')
var obj1 = {person:{name:"Tom",age:20},job:"teacher"}
var obj2 = $.extend(true, {}, obj1)

4. 防抖

防抖

防抖(debounce):在事件被触发n秒后再执行回调,如果再这n秒内事件又被触发,则重新计算,最终只执行一次

js
function debounce(fn,delay){
  let timer
  return function(){
    if(timer) && clearTimeout(timer)
    timer =  setTimeout(()=>{
      fn.apply(this,arguments)
    },delay)
  }
}
let onInput = debounce(e=>{
  console.log(e.target.value)
},1000)
document.getElementByTagName('input').addEventListener('input',onInput)

5. 节流

节流

节流(throttle):在规定的时间间隔内不会触发,只有大于时间间隔才会触发,把频繁触发变为少量触发

js
function throttle(fn, delay) {
  let timer
  let lastTime = Date.now()
  return function () {
    let currentTime = Date.now()
    if (timer) clearTimeout(timer)
    if (currentTime - lastTime > delay) {
      fn.apply(this, arguments)
      lastTime = currentTime
    } else {
      timer = setTimeout(() => {
        fn.apply(this, arguments)
      }, delay)
    }
  }
}
let mouseMove = throttle(e => {
  console.log(e.pageX, e.pageY)
}, 1000)
document.querySelector("#div").addEventListener("mousemove", mouseMove)

6. 继承

原型链继承

  • 将子类的原型对象指向父类的实例
    • 继承了父类的模板,又继承了父类的原型对象
    • 如果要给子类的原型上新增属性和方法,就必须放在Student.prototype = new Person()这样的语句后面
    • 创建子类时,无法向父类构造函数传参
js
function Person(name,age) { 
    this.name = name
    this.age = age
}
Person.prototype.run = function() {
    console.log(`${this.name}-${this.age}`)
}
function Student(sex) {
    this.sex = sex
}
Student.prototype = new Person('张三',"20")
Student.prototype.constructor = Student
Student.prototype.run = function() {
    console.log(`学生信息: ${this.name}-${this.age}-${this.sex}`)
}
var s1 = new Student('男')
s1.run() // 学生信息: 张三-20-男

构造函数继承

  • 在子类构造函数内部使用callapply来调用父类构造函数
    • 解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承
    • 创建子类实例时,可以向父类传递参数
    • 构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法
    • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
js
function Person (name,age) {
  this.name = name
  this.age = age
}
Person.prototype.run = function () {
  console.log(`${this.name}-${this.age}`)
}
function Student (sex) {
  this.sex = sex
  Person.call(this,"张三",20)
}
Student.prototype.run = function () {
   console.log(`学生信息: ${this.name}-${this.age}-${this.sex}`)
}
var s1 = new Student('男')
s1.run() // 学生信息: 张三-20-男

组合继承

  • 原型链继承+构造继承
    • 可以继承父类实例属性和方法,也能够继承父类原型属性和方法
    • 弥补了原型链继承中引用属性共享的问题
    • 可传参,可复用
    • 使用组合继承时,父类构造函数会被调用两次
    • 并且生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存
js
function Person (name, age) {
  this.name = name
  this.age = age
}
Person.prototype.run = function(){
    console.log(`${this.name}-${this.age}`)
}
function Student (sex) {
  Person.call(this, '张三',20)
  this.sex = sex
}
Student.prototype = new Person()
Student.prototype.constructor = Student
Student.prototype.run = function(){
   console.log(`学生信息: ${this.name}-${this.age}-${this.sex}`)
}
var s1 = new Student('男')
s1.run()   // 学生信息: 张三-20-男

寄生式继承

  • 寄生组合继承算是ES6之前一种比较完美的继承方式
    • 只调用了一次父类构造函数,只创建了一份父类属性
    • 子类可以用到父类原型链上的属性和方法
js
function Person (name,age) {
  this.name = name
  this.age = age
}
Person.prototype.run = function () {
  console.log(`${this.name}-${this.age}`)
}
function Student (sex) {
  this.sex = sex
  Person.call(this,'张三',20)
}
// 与组合继承的区别
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
Student.prototype.run = function(){
  console.log(`学生信息: ${this.name}-${this.age}-${this.sex}`)
}
var s1 = new Student('男')
s1.run()  // 学生信息: 张三-20-男

ES6继承

  • 主要是依赖extends关键字来实现继承,且继承的效果类似于寄生组合继承**
js
class Person {
  constructor (name,age) {
    this.name = name
    this.age = age
  }
  run () {
     console.log(`学生信息: ${this.name}-${this.age}`)
  }
}
class Student extends Person {
  constructor (name,age,sex) {
    super(name,age)
    this.sex = sex
  }
  run(){
    console.log(`学生信息: ${this.name}-${this.age}-${this.sex}`)
  }
}
var s1 = new Student('张三',20,'男')
s1.run()  //   学生信息: 张三-20-男

7. new

new

  • 创建一个空对象,作为即将要返回的那个对象实例
  • 将这个对象的__proto__指向构造函数的prototype
  • 执行构造函数并将this绑定到新创建的对象上
  • 判断构造函数的返回值类型,
  • 如果是引用类型,就返回这个引用类型的对象,
  • 否则就返回创建的那个对象
js
function myNew(context){
  let obj  = new Object()
  obj.__proto__ = Object.prototype
  let res = context.apply(obj,[...args].slice(1))
  return typeof res === "object" ? res : obj
}

8. 懒加载

懒加载

  • 把图片真正的URL放在另一个属性data-src
  • 然后遍历所有的图片是否到达可视区域
  • 到达可视区域后把data-src值赋给src
js
let imgList = document.querySelectorAll("img")
function lazyLoad() {
  // 获取屏幕可视窗口高度
  let clientHeight = document.documentElement.clientHeight
  // 获取滚动条滚动的距离
  let scrollTop = document.documentElement.scrollTop
  for(let i = 0; i < imgList.length; i++){
    if(imgList[i].offsetTop < clientHeight + scrollTop){
      if(imgList[i].getAttribute("src") == default.jpg){
        imgList[i].src = imgList[i].getAttribute("data-src")
      }
    }
  }
}

通过IntersectionObserver实现

  • IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法
  • 其祖先元素或视口被称为根(root
javascript
const observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    // 如果 intersectionRatio 为 0,则目标在视野外
    if (item.intersectionRatio <= 0) return
    const { target } = item
    // 将元素的 data-src 属性设
    target.src = target.dataset.src
    // 停止观察该元素
    image.onload = () =>{
      observer.unobserve(target)
    }
  })
})
// 监听页面中所有 img 标签  
document.querySelectorAll('img').forEach(img => {  
  observer.observe(img)  
})
  • Vue实现图片懒加载
vue
<script setup lang="ts">
import { ref, reactive, Directive } from 'vue'
// https://cn.vitejs.dev/guide/features.html 
// Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块
const images: Record<string, { default: string }> = import.meta.glob(
  '../assets/images/*.*',
  { eager: true }
)
const arr = Object.values(images).map((v) => v.default)
const vLazy: Directive<HTMLImageElement, string> = async (el, banding) => {
  const url = await import('../assets/1.png')
  el.src = url.default
  const observe = new IntersectionObserver((entries) => {
    entries.forEach((item) => {
      if (item.intersectionRatio > 0) {
        setTimeout(() => {
          el.src = banding.value
        }, 1000)
        observe.unobserve(el)
      }
    })
  })
  observe.observe(el)
}
</script>
<template>
  <div>
    <img v-for="item in arr" v-lazy="item" width="360" height="500" />
  </div>
</template>

9. instanceof

instanceof

  • instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
js
function myInstanceOf(a,b) = {
  if(typeof a !== 'object' || a === "null") return false
  if(typeof b !== "function" ) {
    throw new Error("b must be function")
    let proto = a.__proto__
    let prototype = b.prototype
    while (true) {
      if(proto === null) return false
      if(proto === prototype) return true
      proto = proto.__proto__
    }
  }
}

10. call,apply,bind

call

  • 可以改变函数指向
  • 第一个参数是要改变指向的对象
  • 之后的参数形式是 arg1, arg2... 的形式
js
Function.prototype.myCall = function (context,...args){
  // 为null和undefined的this值会自动指向全局对象
  if(context === undefined || context === null){
    context = windows
  }else{
     // 值为原始值(数字,字符串,布尔值)的this会指向该原始值的实例对象
    context = Object(context)
  }
  // 用 Symbol 是防止跟上下文的原属性冲突
  const fn = Symbol()
  context[fn] = this
  let result = context[fn](...args)
  //把函数删除掉,为了还原外部obj对象,把添加到他上边的属性再删除掉
  delete context[fn]
  return result
}
js
let year = 2021
obj = {
  year: 2022
}
const getDate = function (month,day) {
  return `${this.year}-${month}-${day}`
}
console.log(getDate.call(obj,7,9)) // 2022-7-9
console.log(getDate.myCall(obj,7,9)) // 2022-7-9

apply

  • 可以改变函数指向
  • 第一个参数与call相同,为函数内部this指向
  • 函数的参数,则以数组的形式传递,作为apply第二参数
js
Function.prototype.myApply = function (context,args){
  if(context === undefined || context === null){
    context = windows
  }else{
    context = Object(context)
  }
  const fn = Symbol()
  context[fn] = this
  let result = context[fn](...args)
  delete context[fn]
  return result
}
js
let year = 2021
obj = {
  year: 2022
}
const getDate = function (month,day) {
  return `${this.year}-${month}-${day}`
}
console.log(getDate.apply(obj, [7,9]))    // 2022-7-9
console.log(getDate.myApply(obj, [7,9]))  // 2022-7-9

bind

  • 第一个参数为要绑定的this对象
  • 可以传递多个参数,参数规则类似call
  • 后面的参数传递给要执行函数作为参数
  • bind返回的是一个函数,所以需要在后面加上()去执行返回的函数,此()里面也可以加参数
js
Function.prototype.myBind = function (context,...args1) {
  if(context === undefined || context === null){
    context = windows
  }else{
    context = Object(context)
  }
  return (...args2)=>{
    const fn = Symbol()
    context[fn] = this
    let result = context[fn](...args1,...args2)
    delete context[fn]
    return result
  }
}
js
let year = 2021
obj = {
  year: 2022
}
const getDate = function (month,day) {
  return `${this.year}-${month}-${day}`
}
console.log(getDate.bind(obj)(7,9)) // 2022-7-9
console.log(getDate.myBind(obj)(7,9)) // 2022-7-9

11. promise

promise

  • Promise存在三个状态(statependingfulfilledrejected,
  • pending为初始态,并可以转化为fulfilledrejected,
  • 成功时,不可转为其他状态,且必须有一个不可改变的值(value
  • 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason
  • 若是executor函数报错 直接执行reject

promise

js
// promise
function Promise(executor){
  this.promiseState = 'pending'
  this.promiseResult = 'null'
  const self = this
  function resolve(data){
    if(self.promiseState === 'pending'){
      self.promiseState ='fulfilled'
      self.promiseResult = data
    }
  }
  function reject(reason){
      if(self.promiseState === 'pending'){
      self.promiseState ='rejected'
      self.promiseResult = reason
    }
  }
  try{
    executor(resolve,reject)
  }
  catch(e){
    reject(e) 
  }
}

promise.then

js
// then 第一种:只考虑了同步任务
class Promise{
    ......
  then(onResolved, onRejected) {
    if(this.promiseState === "fulfilled"){
      onResolved(this.promiseResult)
    }
    if(this.promiseState === "rejected"){
      onRejected(this.promiseResult)
    }
  }
}
js
// then 第二种:
//当promise去调用then方法时,当前的promise一直处于pending状态,
//我们需要先将成功和失败的回调分别存放起来,触发resolve或reject,依次调用成功或失败的回调 
class Promise{
  constructor(executor){
    this.promiseResult = "PENDING"
    this.promiseResult = null
    this.reason = null
    this.onResolvedCallbacks = [] // 存放成功的回调
    this.onRejectedCallbacks = [] // 存放失败的回调
    let resolve = data => {
      if(this.promiseState === "PENDING"){
        this.promiseState = "FULFILLED"
        this.promiseResult = data
        this.onResolvedCallbacks.forEach(fn =>{fn()}) // 依次将对应的函数执行
      }
    }
    let reject = reason => {
      if(this.promiseState === "PENDING"){
        this.promiseState = "REJECTED"
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn =>{fn()}) // 依次将对应的函数执行
      }
    }
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
  then(onResolved, onRejected) {
    if(this.promiseState === "fulfilled"){
      onResolved(this.promiseResult)
    }else if(this.promiseState === "rejected"){
      onRejected(this.promiseResult)
    }else{
// 如果promise的状态是 PENDING,需要将 onResolved 和 onRejected函数存放起来
      this.onResolvedCallbacks.push(() => {
        onResolved(this.value)
      })
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason)
      })
    }
  }
}

promise.resolve

js
// resolve方法用来生成一个直接处于FULFILLED状态的Promise
// 对于传入的数据需要判断是不是promise,如果是则正常流程走
// 如果不是表示则返回的是一个成功的promise
Promise.resolve = function(value){
  return new Promise((resolve,reject) => {
    if(value instanceOf Promise){
      value.then((value)=>{
        resolve(value)
      },err=>{
        reject(err)
      })
    }else{
      resolve(value)
    }
  })
}

promise.reject

js
// reject方法用来生成一个直接处于REJECTED状态的Promise
Promise.reject = function(reason){
  return new Promise((resolve,reject) => {
    reject(reason)
  })
}

promise.catch

js
// catch方法用来捕获promise的异常,就相当于一个没有成功的then
Promise.prototype.catch = function(onRejected){
  return this.then(null,onRejected)
}

promise.all

js
// all方法多个异步并发获取最终的结果
// 如果有一个失败则失败,全部成功则成功
Promise.all = function(promiseAll){
  return new Promise((resolve,reject) => {
    let arr = []
    let count = 0
    if(promiseAll.length === 0){
      resolve([])
    }else{
      for(let i = 0; i < promiseAll.length; i++) {
        promiseAll[i].then((value) =>{
          arr[i] = value
          count += 1
          if(count === promiseAll.length){
            resolve(arr)
          }
        })
        .catch(error =>{
          reject(error)
        })
      }
    }
  })
}

promise.finally

js
// 不管是resolve还是reject都会调用finally方法
Promise.prototype.finally = function(callback){
  return this.then((value) => {
    callback()
    return value
  }),
  (err) => {
    callback()
    throw err
  }
}

promise.race

js
//  race用来处理多个请求,采用最快的(谁先完成用谁的)
Promise.race = function(promiseRace){
  return new Promise((resolve, reject) => {
    if(promiseRace.length === 0){
      resolve([])
    }else{
      for(let i = 0; i < promiseRace.length; i++) {
       promiseRace[i].then((value) => {
          resolve(value)
        })
      }.catch((e) => {
        reject(e)
      })
    }
  })
}

promiseLike

javascript
/**
* @description: 判断一个值是否是Promise Like
* @return {*}
*/
function isPromiseLike(value) {
return (
    value !== null &&
    (typeof value === 'object' || typeof value === 'function') &&
    typeof value.then === 'function'
  )
}

12. 可拖拽的div

js实现一个可以拖拽的div

js
document.body.style.margin = 0
document.body.style.padding = 0
let newDiv = document.createElement("div")
newDiv.style.position = "absolute"
newDiv.style.width = "100px"
newDiv.style.height = "100px"
newDiv.style.border = "2px red solid"
document.body.appendChild(newDiv)
let dragging = false
let position = []
newDiv.addEventListener("mousedown", e => {
  dragging = true
  position = [e.pageX, e.pageY]
  // console.log(e.pageX, e.pageY, "===>初始位置")
})
document.addEventListener("mousemove", e => {
  if (dragging) {
    let x = e.pageX
    let y = e.pageY
    // console.log(e.pageX, e.pageY, "===>移动位置")
    let deltaX = x - position[0]
    let deltaY = y - position[1]
    // console.log(deltaX, deltaY, "===>位移距离")
    let left = parseInt(newDiv.style.left || 0)
    let top = parseInt(newDiv.style.top || 0)
    // console.log(left, top, "===>left,top")
    newDiv.style.left = left + deltaX + "px"
    newDiv.style.top = top + deltaY + "px"
    // 判断不能移出屏幕左侧
    if (left < 0) {
      newDiv.style.left = 0
    }
    // 判断不能移出屏幕上方
    if (top < 0) {
      newDiv.style.top = 0
    }
    position = [x, y]
  }
})
document.addEventListener("mouseup", e => {
  dragging = false
})

13. 排序

冒泡排序

  • 比较相邻的元素,如果第一个比第二个大,就交换它们两个
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数
  • 针对所有的元素重复以上的步骤,除了最后一个
  • 重复上述步骤,直到没有任何一堆数字需要比较
js
function bubbleSort(arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
      }
    }
  }
  return arr
}
let arr = [5,3,7,9,6,1,8,4,2]
bubbleSort(arr)  // [1, 2, 3, 4, 5, 6, 7, 8, 9]
bubbleSort 动图演示

pic

选择排序

  • 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  • 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
  • 重复第二步,直到所有元素均排序完毕
js
function selectSort(arr){
  let minIndex
  for(let i = 0; i < arr.length - 1; i++){
    minIndex = i 
    for(let j = i + 1; j < arr.length; j++){
      if(arr[j] < arr[minIndex]){
        minIndex = j 
      }
    }
    [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
  }
  return arr
}
let arr = [5,3,7,9,6,1,8,4,2]
selectSort(arr)  //[1, 2, 3, 4, 5, 6, 7, 8, 9]
selectSort 动图演示

pic

快速排序

  • 从数列中挑出一个元素,称为"基准"(pivot
  • 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
  • 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序
js
function quickSort(arr) {
  let target = arr[0]
  let left = []
  let right = []
  if(arr.length <= 1) return arr
  for(let i = 1; i < arr.length; i++) {
    if(arr[i] < target){
      left.push(arr[i])
    }else{
      right.push(arr[i])
    }
  }
  return quickSort(left).concat([target],quickSort(right))
}
let arr = [5,3,7,9,6,1,8,4,2]
quickSort(arr) //[1, 2, 3, 4, 5, 6, 7, 8, 9]
quickSort 动图演示

pic

插入排序

  • 把待排序的数组分成已排序和未排序两部分,初始的时候把第一个元素认为是已排好序的
  • 从第二个元素开始,在已排好序的子数组中寻找到该元素合适的位置并插入该位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)
  • 重复上述过程直到最后一个元素被插入有序子数组中
js
function insertionSort(arr) {
  var len = arr.length
  var preIndex, current
  for (var i = 1; i < len; i++) {
    preIndex = i - 1
    current = arr[i]
    while(preIndex >= 0 && arr[preIndex] > current) {
        arr[preIndex+1] = arr[preIndex];
        preIndex--
    }
    arr[preIndex+1] = current
  }
  return arr
}
let arr = [5,3,7,9,6,1,8,4,2]
insertionSort(arr) //[1, 2, 3, 4, 5, 6, 7, 8, 9]
insertionSort 动图演示

pic

14. Symbol.iterator

Object.values

javascript
const obj = {a:1,b:2,c:3}
const values = Object.values(obj)
for(let value of values){
  console.log(value) // 1 2 3
}

Object.entries

javascript
const obj = {a:1,b:2,c:3}
const entries = Object.entries(obj)
for(let [key,value] of entries){
  console.log(value) // 1 2 3
}

Symbol.iterator

javascript
const obj={
  a:1,
  b:2,
  c:3,
  [Symbol.iterator](){
    let index = 0
    return{
      next(){
        if(index < Object.values(obj).length){
          return{
            value:Object.values(obj)[index++],
            done:false
          }
        }else{
          return {
            value:undefined,
            done:true
          }
        }
      }
    }
  }
}
for(let value of obj){
  console.log(value) // 1 2 3
}

Example

javascript
// 使左右俩边相等
[a,b] = {a:1,b:2}
Object.prototype[Symbol.iterator] = function(){
  return Object.values(this)[Symbol.iterator]()
}

15. 获取对象的key

for...in 遍历

javascript
const obj = {a:1,b:2,c:3}
for(let key in obj) {
  console.log(key) // a b c
}

Object.keys

javascript
const obj = {a:1,b:2,c:3}
const keys = Object.keys(obj)
console.log(keys) // ['a', 'b', 'c']

Object.getOwnPropertyNames

javascript
const obj = {a:1,b:2,c:3}
const keys = Object.getOwnPropertyNames(obj)
console.log(keys)  // ['a', 'b', 'c']

Object.getOwnPropertySymbols

javascript
let d:symbol= Symbol(1)
const obj = {a:1,b:2,c:3,[d]:1}
const keys = Object.getOwnPropertySymbols(obj)
console.log(keys)  //  [Symbol(1)]

Reflect.ownKeys

javascript
let d:symbol= Symbol(1)
const obj = {a:1,b:2,c:3,[d]:1}
const keys = Reflect.ownKeys(obj)
console.log(keys)  //  ["a", "b", "c", Symbol(1)]

16. forEach,map,filter,reduce

forEach

  1. 针对每一个元素执行提供的函数
  2. forEach方法不会返回执行结果,而是undefined
  3. forEach对原数组进行修改
  4. 不能中止或跳出 forEach 循环(return false或者 break)
javascript
let arr = [1, 2, 3, 4, 5]
arr.forEach((item, index) => {
  return (arr[index] = item * 2) 
})
console.log(arr) // [2, 4, 6, 8, 10]

map

  1. 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数得来
  2. map方法会得到一个新的数组并返回
javascript
let arr = [1, 2, 3, 4, 5]
let doubled = arr.map(item => {
    return item * 2
})
console.log(arr)     // [1, 2, 3, 4, 5]
console.log(doubled) // [2, 4, 6, 8, 10]

filter

  1. filter 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素
  2. filter 不会改变原数组,它返回过滤后的新数组
javascript
let arr = [1, 2, 3, 4, 5]
let filter = arr.filter(item => {
    return item >= 3
})
console.log(arr)     // [1, 2, 3, 4, 5]
console.log(filter)  // [3, 4, 5]

reduce

  1. 对数组中的每个元素执行一个自定义的累计器,将其结果汇总为单个返回值

  2. 形式:Array.prototype.reduce(callback, initialValue)

    • callback:回调函数(必选)
      • 回调函数的参数:
        • previousValue:累计器完成计算的返回值(必选)
        • currentValue:当前元素(必选)
        • currentIndex:当前元素的索引(可选)
        • Array:当前元素所属的数组对象(可选)
    • initValue:初始值(可选)
      • 如果调用 reduce() 时提供了 initialValuepreviousValue 取值则为 initialValuecurrentValue 则取数组中的第一个值
      • 如果没有提供 initialValue,那么 previousValue 取数组中的第一个值,currentValue 取数组中的第二个值
  3. 累加累乘:

    javascript
    const arr = [1, 2, 3, 4, 5]
    const a = arr.reduce((pre, cur) => pre + cur) // 15
    // 等同于
    const b = arr.reduce((pre, cur) => pre + cur, 0) // 15
    javascript
    const Accumulation = (...args) =>{
      return args.reduce((pre,cur) => pre + cur,0)
    }
    Accumulation(1, 2, 3, 4, 5) // 15
    javascript
    const Multiplication = (...args) =>{
      return args.reduce((pre,cur) => pre * cur,1)
    }
    Accumulation(1, 2, 3, 4, 5) // 120
  4. 权重求和:

    javascript
    const scores = [
      { score: 90, subject: "Chinese", weight: 0.5 },
      { score: 95, subject: "Math", weight: 0.3 },
      { score: 85, subject: "English", weight: 0.2 }
    ]
    const res = scores.reduce((pre, cur) => pre + cur.score * cur.weight, 0) // 90.5
  5. 代替reverse:

    javascript
      const reverse = (arr = []) =>{
        return arr.reduceRight((pre,cur) => (pre.push(cur),pre),[])
      }
      reverse([1, 2, 3, 4, 5]) // [5, 4, 3, 2, 1]
  6. 代替 map 和 filter:

    javascript
      const arr = [1, 2, 3, 4, 5]
      // 代替map:[2, 4, 6, 8, 10]
      const a = arr.map(item => item * 2)
      const b = arr.reduce((pre, cur) => [...pre, cur * 2], [])
      // 代替filter:[4, 5]
      const c = arr.filter(item => item > 3)
      const d = arr.reduce((pre, cur) => cur > 3 ? [...pre, cur] : pre, [])
      // 代替map和filter:[4, 6, 8 ,10]
      const e = arr.map(item => item * 2).filter(item => item > 2)
      const f = arr.reduce((pre, cur) => cur * 2 > 2 ? [...pre, cur * 2] : t, [])
  7. 数组最大最小值:

    javascript
    // 最大值
    function Max(arr = []){
      return arr.reduce((pre, cur) => pre > cur ? pre : cur)
    }
    // 最小值
    function Min(arr = []){
      return arr.reduce((pre, cur) => pre < cur ? pre : cur)
    }
    let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    Max(arr) // 9
    Min(arr) // 1
  8. 字符串反转:

    javascript
    // reduceRight
      function ReverseStr(str){
      return str.split("").reduceRight((pre, cur) => pre + cur)
      } 
      ReverseStr("Rng牛逼") // '逼牛gnR'
      function ReverseStr2(str){
        return str.split("").reverse().join("")
      }
      ReverseStr2("Rng牛逼") // '逼牛gnR'

17. 发布订阅

18 代码雨

ts
// 当页面加载完成时执行
onload = () => {
  // 获取 canvas 元素
  let canvas = document.querySelector('canvas') as HTMLCanvasElement
  // 获取 2D 上下文对象
  let ctx = canvas.getContext('2d') as CanvasRenderingContext2D
  // 设置 canvas 的宽度和高度为屏幕可用宽度和高度
  canvas.width = screen.availWidth
  canvas.height = screen.availHeight
  // 定义要显示的字符数组
  let str: string[] = 'HelloWorld0101010'.split('')
  // 计算列数,并创建数组用于存储每列的位置
  let arr = Array(Math.ceil(canvas.width / 10)).fill(0)
  // 定义绘制代码雨的函数
  let rain = () => {
    // 绘制半透明背景,以实现模糊效果
    ctx.fillStyle = 'rgba(0,0,0,.05)'
    ctx.fillRect(0, 0, canvas.width, canvas.height)
    // 设置绘制字符的颜色
    ctx.fillStyle = '#0f0'
    // 遍历每一列
    arr.forEach((item, index) => {
      // 随机选择一个字符,并在当前列的位置绘制
      ctx.fillText(
        str[Math.floor(Math.random() * str.length)],
        index * 10,
        item + 10
      )
      // 更新当前列的位置
      arr[index] =
        item > canvas.height || item > 10000 * Math.random() ? 0 : item + 10
    })
  }
  // 使用 setInterval 方法每隔 40 毫秒调用一次绘制函数,以实现动画效果
  setInterval(rain, 40)
}

Copyright © 2024 Fang He