第4期 - 互联网大会

封面图来源于2020年RTE互联网大会,作为主办方开发者带着自己的作品参展还是很有成就感的。

自己写的demo

技术分享-js相关

闭包

是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

闭包的应用

节流

function throttle(fun, delay) {
  let last = 0;
  return function(...args) {
    const cur = +new Date();
    if (cur - last >= delay) {
      fun.apply(this, args);
      last = now;
    }
  }
}

防抖

debounce(fun, delay) {
  let timer = null;
  return function() {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fun.apply(this, arguments)
    }, delay)
  }
}

Promise

实现原理

通过状态的变化来表示异步操作的结果

当异步操作完成时,可以调用 resolve 来将 Promise 的状态标记为 fulfilled,或者调用 reject 将其状态标记为 rejected。 通过链式调用 .then() 方法来注册回调函数,用于在 Promise 状态变为 fulfilled 时执行,或者通过 .catch() 方法来注册回调函数,用于在状态变为 rejected 时执行

异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值。

回调地狱

回调地狱是指在处理多个嵌套的异步操作时,代码变得难以理解、维护和扩展的情况。

处理回调地狱

Promise:可以形成一个线性的链式调用,避免了嵌套的回调。catch 捕获错误

async/await:使用 async 关键字声明函数,然后在内部使用 await 关键字等待异步操作的结果

async/await

将异步操作的执行流程暂停,直到 Promise 完成,然后再继续执行。避免回调地狱

async 声明一个使用 async 关键字的函数,它会返回一个 Promise 对象。 await 阻塞函数的执行,直到所等待的 Promise 被 resolved(或 rejected)。

当 await 关键字在 async 函数内部使用时,它会阻塞函数的执行,直到所等待的 Promise 被 resolved(或 rejected)。在等待期间,await 表达式的值将是 Promise 的 resolved 值

Promise API

Promise.all() // 所有都成功执行回调 Promise.race() // 返回第一个敲定(无论是兑现还是拒绝) Promise.any() // 忽略所有被拒绝的 Promise,直到第一个被兑现的 Promise。

封装未支持 Promise 的 方法

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('foo');
  }, 300);
});
promise1.then((value) => {
  console.log(value);
  // Expected output: "foo"
});
console.log(promise1);
// Expected output: [object Promise]

状态

待定(pending)已兑现(fulfilled)已拒绝(rejected)

手写 Promise.all()

在所有输入的 Promise 都变为 resolved 状态时被 resolved,在其中一个 Promise 变为 rejected 状态时被 rejected。

function customPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Input is not an array'));
    }
    
    let resolvedCount = 0;
    const results = new Array(promises.length);
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(result => {
          results[index] = result;
          resolvedCount++;
          
          if (resolvedCount === promises.length) {
            resolve(results);
          }
        })
        .catch(error => {
          reject(error);
        });
    });
  });
}

// 使用示例
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve(2), 1000);
});
const promise3 = Promise.resolve(3);

customPromiseAll([promise1, promise2, promise3])
  .then(results => {
    console.log('Resolved values:', results); // [1, 2, 3]
  })
  .catch(error => {
    console.error('Error:', error);
  });

拷贝

深拷贝

对象的深拷贝是指其属性与其拷贝的源对象的属性不共享相同的引用, 当你更改原对象或拷贝对象时, 可以确保不会导致其他对象也发生更改

知识点补充:

WeakMap

创建弱引用的键值对集合,当你需要存储键值对,并且希望在没有其他引用时允许垃圾回收器回收这些键值对 通常情况下,一个对象如果被其他对象引用,那么它就不会被垃圾回收。WeakMap 使用弱引用,这意味着当一个对象作为键被存储在 WeakMap 中时,如果没有其他引用指向该键,它将被垃圾回收,不再阻止其被释放。 当原始对象不再被引用时,与其关联的数据也会被垃圾回收

const wm1 = new WeakMap()
const o1 = {}
wm1.set(o1, "azerty");
wm1.get(o1); 
// "azerty"

内存泄漏

在计算机程序中,内存被分配出去但未能正确释放,导致系统中的可用内存逐渐减少,最终可能会耗尽系统的可用内存资源。内存泄漏可能会导致程序性能下降,甚至崩溃。

内存泄漏是指应用程序中的内存不再使用,但没有被垃圾回收机制释放。 常见的内存泄漏原因包括循环引用、未释放的事件处理程序、不断增长的数据结构等。 需要定期检查和解决潜在的内存泄漏问题,以保持应用程序的性能和稳定性。

场景:循环引用、未正确清除定时器和事件监听器、闭包、长期存在的全局变量 闭包场景,一个函数引用了它外部作用域中的变量,如果这些变量不再需要却被闭包引用,就会导致内存泄漏。

手写深拷贝-递归

const deepClone = (obj) => {
    if(typeof obj === null || typeof obj !== 'object') return obj

    if(Array.isArray(obj)) {
        const arr = []
        for(let i = 0; i < obj.length; i++) {
            arr[i] = deepClone(item)
        }
        return arr
    }

    const deepObj = {}
    for(let key in obj) {
        // for...in 循环会遍历对象的所有可枚举属性,包括继承自原型链的属性
        if(obj.hasOwnProperty(key)) {
            deepObj[key] = deepClone(obj[key])
        }
    }
    return deepObj
};

可能存在, 使用 visited 来记录已经被拷贝的对象,避免重复拷贝以及循环引用导致的无限递归。

const deepClone = (obj, visit = new WeakMap()) => {
    if(typeof obj === null || typeof obj !== 'object') return obj

    if(visit.has(obj)) {
        return visit.get(obj)
    }

    if(Array.isArray(obj)) {
        const arr = []
        visit.set(obj, arr)
        for(let i = 0; i < obj.length; i++) {
            arr[i] = deepClone(item)
        }
        return arr
    }

    const deepObj = {}
    visit.set(obj, deepObj)
    for(let key in obj) {
        // for...in 循环会遍历对象的所有可枚举属性,包括继承自原型链的属性
        if(obj.hasOwnProperty(key)) {
            deepObj[key] = deepClone(obj[key])
        }
    }
    return deepObj
};

循环引用

const obj1 = {};
const obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
// 形成循环引用

序列化实现深拷贝

如果一个 JavaScript 对象可以被序列化,则存在一种创建深拷贝的方式:使用 JSON.stringify() 将该对象转换为 JSON 字符串,然后使用 JSON.parse() 将该字符串转换回(全新的)JavaScript 对象:

let ingredients_list = ["noodles", { list: ["eggs", "flour", "water"] }];
let ingredients_list_deepcopy = JSON.parse(JSON.stringify(ingredients_list));

ingredients_list_deepcopy[1].list = ["rice flour", "water"];

console.log(ingredients_list[1].list);
// Array(3) [ "eggs", "flour", "water" ]

浅拷贝

将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。 当更改原对象/拷贝对象时,可能导致其他对象也发生更改

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, ...obj1 };

console.log(obj2); // { c: 3, a: 1, b: 2 }

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combinedArray = [...arr1, ...arr2];

console.log(combinedArray); // [1, 2, 3, 4, 5, 6]

js事件

js 的执行原理(event loop)事件循环&微任务宏任务

事件循环用于处理异步操作机制,执行非阻塞的I/O操作,定时器、Promise等。事件循环分为宏任务队列和微任务队列,通过事件循环来处理宏任务和微任务,以确保按照特定顺序执行异步操作

微任务会在宏任务之前执行。微任务是在当前事件循环中执行的任务,如Promise回调、MutationObserver回调。宏任务是在下一个事件循环中执行的任务,如setTimeout、setInterval、I/O操作等

https://github.com/shellGrace/shellGrace.github.io/blob/master/_posts/2022-07-26-js%E7%BA%BF%E7%A8%8B.md

事件捕获和事件冒泡机制

事件捕获是一种事件传播机制,事件从根元素向目标元素传播,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件

事件冒泡是另一种事件传播机制,事件从目标元素向根元素传播,触发冒泡阶段的事件处理程序。事件冒泡顺序是由内到外进行事件传播,直到根节点

事件委托 当我们为最外层的节点添加点击事件,那么里面的ul、li、a的点击事件都会冒泡到最外层节点上,委托它代为执行事件。减少dom操作,节省内存,不需要给子节点注销事件

js作用域

全局作用域和局部作用域,变量在不同作用域中的可见性不同。

词法作用域(静态作用域):词法作用域的工作方式,根据变量在代码中的位置决定了它的可见性和作用域。它首先查找自己的作用域(内部函数作用域),如果找不到会向外部函数作用域查找,然后继续向全局作用域查找。

js原型链 & js继承

https://github.com/shellGrace/shellGrace.github.io/blob/master/_posts/2021-07-08-js%E5%8E%9F%E5%9E%8B%E9%93%BE.md