[技术积累] 基础知识

1、静态属性、静态方法、实例方法可以被继承,但是私有属性不行

2、typescript的泛型是用T指代任何一种类型,以方便提高代码的灵活度,<T> 定义T为封装,如果没有它T会被编辑器认为是某个具体的类型:

function foo<T>(tag: T): T {
    if (typeof tag === 'number') {
        return (tag as number).toString(16) as T;
    }
    return tag;
}

3、立即执行函数(IIFE):

let a = 1;
(function(){
let a = 2;
function foo(){
 console.log(this.a);
}
foo();
})()

打印的结果是undefined,这是因为外部的a变量是储存在“全局作用域”的,iife中this指向的是“全局变量”window,所以结果是undefined,如果代码修改为:

var a = 1;

(function () {
  let a = 10;
  function b() {
    console.log(window.a);
  }
  b();
})();

那么输出是:1,因为var是直接将a保存到了window上的,这是var与let的重要区别之一。

4、事件循环模型

事件循环模型中包含两种任务:

  • 宏任务:ui渲染、i/o操作、setTimeout、setInterval、web请求等
    • setTimeout、setInterval、web请求 是异步操作,但是有所不同:setTimeout是将一个函数或代码块推迟到将来执行,而Web请求是在浏览器中发起的网络通信操作,涉及到与服务器的交互。简单来说前者只是推迟执行,后者回归到事件循环时,结果已经产生了。
    • ui渲染和i/o操做是不在后台进行的
  • 微任务:包括Promise.then()MutationObserverprocess.nextTick等。它们会在当前宏任务执行完毕后,但在下一个宏任务开始前执行。
    • mutationObeserver的作用是监听节点的变化,包括:
      • 子节点的变化:包括添加、删除或替换子节点。
      • 属性的变化:包括添加、删除或修改属性。
      • 文本内容的变化:包括修改文本节点的内容。

5、闭包

闭包是”在一个函数内部定义的函数“,闭包允许函数执行完毕后,仍然和可以访问函数内部的变量和参数。闭包的作用:

  • 模拟私有变量
  • 延长函数生命周期
  • 柯里化函数
  • 实现模块模式

6、虚拟滚动

  • 这个是保留骨架的方式,本质上使用了骨架容器代替了item容器占位,这种方法可以减少5~10倍的容器数,取决于一个骨架包含的item数量,适用于简单的列表展示进行性能优化
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>视口分页加载</title>
    <style>
      .outter-container {
        width: 500px;
        height: 500px;
        overflow-y: scroll;
        border: 1px solid #000;
      }
      .inner-container {
        width: 100%;
        height: 500px;
        border: 10px solid red;
        box-sizing: border-box;
        .box-container {
          width: 100%;
          height: 100px;
          border: 1px solid #000;
          box-sizing: border-box;
        }
        .hidden {
          display: none;
        }
      }

      /* 示例:显示当前inner-container及其周围两个 */
      .visible,
      .visible + .inner-container,
      .visible ~ .inner-container:nth-of-type(-n + 2) {
        display: block;
      }
    </style>
  </head>
  <body>
    <div class="outter-container"></div>
  </body>

  <script>
    const list = [];

    function createBox(innerBox, index, initVisibleNums) {
      if (index > initVisibleNums) {
        return;
      }
      for (let i = 0; i < 5; i++) {
        const box = document.createElement("div");
        box.className = `box-container`;
        box.innerHTML = `${index}_${i}`;
        innerBox.appendChild(box);
      }
    }

    function createInnerBox(index, initVisibleNums = 3) {
      const innerBox = document.createElement("div");
      innerBox.className = "inner-container";
      innerBox.id = `inner-container${index}`;
      //   createBox(innerBox, index, initVisibleNums);
      document.querySelector(".outter-container").appendChild(innerBox);
    }

    // 初始加载时创建inner-container,但只显示前三个
    let innerBoxIndex = 0;
    for (let i = 0; i < 1000; i++) {
      list.push(i);
      if (i % 10 === 9) {
        createInnerBox(innerBoxIndex++, 3);
      }
    }

    //判断哪些inner-container在outter-container中显示,使用IntersectionObserver
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            entry.target.classList.add("visible");
            console.log("############visible:", entry.target.id);
            // 加载下一页
            const nextInnerBox = document.querySelector(
              `.inner-container:nth-of-type(${
                parseInt(entry.target.id.split("inner-container")[1]) + 1
              })`
            );
            if (nextInnerBox) {
              createBox(
                nextInnerBox,
                parseInt(nextInnerBox.id.split("inner-container")[1]),
                Infinity
              );
            }
          } else {
            entry.target.classList.remove("visible");
            console.log("############hidden:", entry.target.id);
            // 卸载当前inner-container
            const currentInnerBox = entry.target;
            if (currentInnerBox) {
              currentInnerBox.innerHTML = "";
            }
            // // 卸载当前inner-container
            // const currentInnerBox = document.querySelector(
            //   `.inner-container:nth-of-type(${parseInt(
            //     entry.target.id.split("inner-container")[1]
            //   )})`
            // );
            // if (currentInnerBox) {
            //   console.log("$$$$$$$$$", currentInnerBox.id);
            //   currentInnerBox.innerHTML = "";
            //   //   currentInnerBox.removeChild(
            //   //   currentInnerBox.querySelector(".box-container"));
            // }
          }
        });
      },
      {
        threshold: 0.1,
      }
    );
    const innerContainers = document.querySelectorAll(".inner-container");
    innerContainers.forEach((container) => observer.observe(container));
  </script>
</html>
  • 使用动态渲染+节流进行优化:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>虚拟滚动</title>
    <style>
      .scroll {
        position: relative;
        width: 500px;
        height: 500px;
        overflow-y: auto;
        border: 1px solid #000;
        box-sizing: border-box;
        .content {
          position: relative;
          width: 100%;
        }
      }

      .item {
        position: absolute;
        left: 0px;
        width: 100%;
        height: 100px;
        border-bottom: 1px solid #ff0000;
        box-sizing: border-box;
      }
    </style>
  </head>
  <body>
    <div class="scroll">
      <div class="content"></div>
    </div>
  </body>

  <script>
    let data = [];
    const item_height = 100;
    const scrollHeight = 500;
    const visibleCount = Math.ceil(scrollHeight / item_height);
    const buffer = 15;
    function initData() {
      for (let i = 0; i < 10000; i++) {
        data.push(i);
      }
    }

    function initContent() {
      const contentDiv = document.querySelector(".content");
      contentDiv.style.height = item_height * data.length + "px";
    }

    function render() {
      const contentDiv = document.querySelector(".content");
      const scrollTop = document.querySelector(".scroll").scrollTop;
      const startIndex = Math.max(
        0,
        Math.floor(scrollTop / item_height) - buffer
      );
      const endIndex = Math.min(
        startIndex + visibleCount + buffer,
        data.length
      );

      console.log("起始索引", startIndex, "结束索引", endIndex);

      for (let i = startIndex; i < endIndex; i++) {
        if (!contentDiv.querySelector(`#item_${i}`)) {
          const item = document.createElement("div");
          item.className = "item";
          item.innerText = data[i];
          item.style.top = i * item_height + "px";
          item.id = `item_${i}`;
          contentDiv.appendChild(item);
        }
      }

      //删除多余的元素
      for (let i = 0; i < data.length; i++) {
        if (i < startIndex || i > endIndex) {
          if (contentDiv.querySelector(`#item_${i}`)) {
            contentDiv.removeChild(contentDiv.querySelector(`#item_${i}`));
          }
        }
      }
    }

    //节流
    function throttle(fn, delay) {
      let timer = null;
      return function () {
        if (!timer) {
          timer = setTimeout(() => {
            fn.apply(this, arguments);
            timer = null;
          }, delay);
        }
      };
    }

    const packageThrottle = throttle(render, 50);
    function initScrollEvent() {
      const scrollDiv = document.querySelector(".scroll");
      scrollDiv.addEventListener("scroll", function (e) {
        //加入防抖
        packageThrottle();
      });
    }

    //初始化数据
    initData();
    //初始化内容
    initContent();
    //初始化滚动事件
    initScrollEvent();
    //初始化渲染
    render();
  </script>
</html>
  1. 初始化数据
    • 使用initData函数创建一个包含大量数据(在这个例子中是10000个元素)的数组data
  2. 初始化内容容器的高度
    • 使用initContent函数设置.content元素的高度,这个高度等于所有可能显示的项目(data数组中的元素)的总高度(item_height * data.length)。
  3. 渲染函数(render
    • 这个函数负责在滚动时更新可见的项目。
    • 首先,它确定哪些项目当前是可见的,基于滚动容器的scrollTop值、每个项目的高度(item_height)以及可见的项目数量(visibleCount)和缓冲区大小(buffer)。
    • 然后,它遍历这些可见的项目索引,并检查每个项目是否已经在DOM中。如果不在,就创建一个新的项目元素并将其添加到.content容器中。
    • 最后,它遍历所有不在当前可见范围内的项目索引,并从DOM中删除它们。
  4. 节流(Throttle)
    • 由于滚动事件可能会非常频繁地触发(尤其是在快速滚动时),直接绑定滚动事件到render函数可能会导致性能问题。
    • 为了解决这个问题,代码中使用了节流(Throttle)函数throttle,它确保render函数不会在短时间内被频繁调用。在这里,节流函数的延迟被设置为50毫秒,意味着render函数最多每秒会被调用20次。
  5. 初始化滚动事件
    • 使用initScrollEvent函数将节流后的render函数(packageThrottle)绑定到.scroll元素的滚动事件上。这样,每当用户滚动容器时,都会触发render函数(但受到节流函数的限制)。
  6. 初始渲染
    • 在所有初始化函数执行完毕后,调用render函数以首次渲染可见的项目。

这个虚拟滚动系统的主要优点是它只渲染当前可见的项目,而不是所有项目,从而大大减少了DOM元素的数量,提高了滚动性能和响应速度。此外,使用节流函数来限制render函数的调用频率,也有助于进一步提高性能。

演示地址:http://alaya.zone:50001/demo/virtualScrolling.html

7、内部属性 [[Class]] 是什么?

这是一个用于标识参数类型的方式,是一种内禀的标识符,他不能直接访问但可以使用Object.prototype.toString.call()来查看:

  • Object.prototype.toString.call(123); // [object Number]
  • Object.prototype.toString.call(true); // [object Boolean]
  • Object.prototype.toString.call(null); // [object Null]
  • Object.prototype.toString.call(undefined); // [object Undefined]
  • Object.prototype.toString.call({}); // [object Object]
  • Object.prototype.toString.call(new Date()); // [object Date]
  • Object.prototype.toString.call(new Function()); // [object Function]
  • Object.prototype.toString.call(Symbol(‘test’)); // [object Symbol]
此属性的原理是:class FOO{  get [Symbol.toStringTag](){      return "FOO";}}

或者
      let o = {
        get [Symbol.toStringTag]() {
          return "O";
        },
      };

      console.log(Object.prototype.toString.call(o)); // [object O]

8、Symbol.iterator

      let myObject = {
        data: [1, 2, 3],
        [Symbol.iterator]() {
          let index = 0;
          return {
            next: () => {
              if (index < this.data.length) {
                return { value: this.data[index++], done: false };
              } else {
                return { done: true };
              }
            },
          };
        },
      };

      for (let num of myObject) {
        console.log(num);
      }

      for (let num of myObject) {
        console.log(num);
      }

可以自定义迭代器
还有其他字段:
Symbol.asyncIterator(实验性):返回对象默认的异步迭代器的方法,供 for await of 使用。

Symbol.match:用于对字符串进行匹配的方法,也用于确定一个对象是否可作为正则表达式使用,被 String.prototype.match() 使用。

Symbol.replace:替换匹配字符串的子串的方法,被 String.prototype.replace() 使用。

Symbol.search:返回一个字符串中与正则表达式相匹配的索引的方法,被 String.prototype.search() 使用。

Symbol.split:在匹配正则表达式的索引处拆分一个字符串的方法,被 String.prototype.split() 使用。

Symbol.hasInstance:确定一个构造器对象识别的对象是否为它的实例的方法,被 instanceof 使用。

Symbol.isConcatSpreadable:一个布尔值,表明一个对象是否应该被展开为它的数组元素,被 Array.prototype.concat() 使用。

Symbol.unscopables:拥有和继承属性名的一个对象的值被排除在与环境绑定的相关对象外。

Symbol.species:用于创建派生对象的构造器函数。

9、原型链相关题目

function Foo() {}
function Bar() {}

Foo.prototype.a = 1;
Bar.prototype.b = 2;

const foo = new Foo();
const bar = new Bar();
bar.__proto__ = Foo.prototype;

console.log(foo.a); // 1
console.log(bar.a); // 1
console.log(bar.b); // undefined
console.log(foo.b); // undefined

function A() {}
function B() {}

A.prototype.value = 42;
B.prototype = new A();

const b1 = new B();
const b2 = new B();

b1.value = 100;

console.log(b1.value); // 100
console.log(b2.value); // 42

delete b1.value;

console.log(b1.value); // 42

function Person() {}
Person.prototype = {
  constructor: Person,
  name: 'John'
};

function Employee() {}
Employee.prototype = Person.prototype;

const emp = new Employee();

console.log(emp.name); // John

Person.prototype.name = 'Doe';
console.log(emp.name); // Doe

function Grandparent() {}
Grandparent.prototype.greet = function() {
  return "Hello from Grandparent";
};

function Parent() {}
Parent.prototype = Object.create(Grandparent.prototype);
Parent.prototype.constructor = Parent;

function Child() {}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.greet = function() {
  return "Hello from Child";
};

const child = new Child();

console.log(child.greet()); // "Hello from Child"
delete Child.prototype.greet;
console.log(child.greet()); // “Hello from Grandparent”
delete Parent.prototype.greet;
console.log(child.greet()); // "Hello from Grandparent"
delete Grandparent.prototype.greet;
console.log(child.greet()); // error

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部