JavaScript

简短介绍

JavaScript 是一门用来与网页交互的脚本语言,包含以下三个组成部分。
1、ECMAScript:由 ECMA-262 定义并提供核心功能。
2、文档对象模型(DOM):提供与网页内容交互的方法和接口。
3、浏览器对象模型(BOM):提供与浏览器交互的方法和接口。
JavaScript 的这三个部分得到了五大 Web 浏览器(IE、Firefox、Chrome、Safari 和 Opera)不同程度
的支持。所有浏览器基本上对 ES5(ECMAScript 5)提供了完善的支持,而对 ES6(ECMAScript 6)
ES7(ECMAScript 7)的支持度也在不断提升。这些浏览器对 DOM 的支持各不相同,但对 Level 3 的支
持日益趋于规范。HTML5 中收录的 BOM 会因浏览器而异,不过开发者仍然可以假定存在很大一部分
公共特性。
学习是一项长期的任务,让我来一起成长吧!


基础

JavaScript的数据类型有哪些?引用类型有哪些?

JavaScript 中的数据类型可以分为两类:基本类型(原始类型)引用类型

  • 原始类型:Number, String, Boolean, null,undefined, Symbol, BigInt。

  • 引用类型:Object, Array, Function, Date, RegExp, Map, Set


    如何判断JavaScript的数据类型

  1. 使用 typeof 运算符
       console.log(typeof BigInt(123));   // "bigint"
       console.log(typeof {});            // "object"
       console.log(typeof []);            // "object" (数组也是对象)
  2. 使用 instanceof 运算符
       console.log([] instanceof Array);  // true
       console.log({} instanceof Object); // true
       console.log(function(){} instanceof Function); // true
  3. 使用 Object.prototype.toString.call() 方法
       console.log(Object.prototype.toString.call([])); // "[object Array]"
       console.log(Object.prototype.toString.call({})); // "[object Object]"
       console.log(Object.prototype.toString.call(function(){})); // "[object Function]"
  4. 使用 Array.isArray() 方法
       console.log(Array.isArray([]));        // true
       console.log(Array.isArray({}));        // false
  • 原始类型可以使用 typeof 进行判断,但对于 null 和数组的判断不够准确。
  • 引用类型可以使用 instanceof 和 Array.isArray() 进行判断,或者通过 Object.prototype.toString.call() 来进行更准确的判断。
  • 结合不同的方法,可以实现更灵活和准确的数据类型判断。

怎么判断两个对象相等?如何判断空对象?

  1. 比较对象的属性

    • 要判断两个对象的属性是否相等,可以遍历它们的属性,逐一比较值。

      
      function isEqual(obj1, obj2) {
        // 如果两个对象的引用相同,则直接返回 true
        if (obj1 === obj2) return true;
      
        // 如果两个对象其中一个不是对象,或者它们的类型不同,返回 false
        if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 === null || obj2 === null) {
          return false;
        }
      
        // 获取两个对象的属性名数组
        const keys1 = Object.keys(obj1);
        const keys2 = Object.keys(obj2);
      
        // 如果属性数量不同,返回 false
        if (keys1.length !== keys2.length) {
          return false;
        }
      
        // 遍历属性并递归比较
        for (let key of keys1) {
          // 如果 obj2 中没有 obj1 的某个属性,或者属性值不相等,返回 false
          if (!keys2.includes(key) || !isEqual(obj1[key], obj2[key])) {
            return false;
          }
        }
      
        return true;
      }
      
      // 示例
      const objA = { a: 1, b: { c: 2 } };
      const objB = { a: 1, b: { c: 2 } };
      
      console.log(isEqual(objA, objB));  // true
      

2. `判断空对象`
- 要判断一个对象是否为空对象(即没有任何可枚举的属性),可以使用 `Object.keys()` 或 `Object.getOwnPropertyNames()` 来获取对象的属性,然后判断属性数组是否为空。
```javascript
  // 遍历keys
  function isEmptyObject(obj) {
    // 判断是否为对象,且是否为空
    return obj && Object.keys(obj).length === 0 && obj.constructor === Object;
  }

  // 示例
  console.log(isEmptyObject({}));      // true
  console.log(isEmptyObject({ a: 1 })); // false

0.1+0.2为什么不等于0.3?

原理解释

  • 在二进制表示中,像 0.1 和 0.2 这样的十进制小数不能被精确表示。它们在二进制中是无限循环的数值,因此被截断为近似值。这种近似值在计算时会引发精度误差。

举个例子:

  • 0.1 在二进制中的表示是:0.0001100110011001100110011001100110011001100110011001101(无限循环)
  • 0.2 在二进制中的表示是:0.001100110011001100110011001100110011001100110011001101(无限循环)
    当你进行 0.1 + 0.2 的计算时,JavaScript 将近似值相加,结果是 0.30000000000000004,这和你期望的 0.3 不完全相等。
    console.log(0.1 + 0.2);   // 输出 0.30000000000000004
    console.log(0.1 + 0.2 === 0.3); // 输出 false

如何解决
为了避免这种精度问题,通常会使用四舍五入的方法来修正计算结果。例如,可以使用 toFixed() 方法或 Number.EPSILON 来比较两个浮点数:

    let sum = (0.1 + 0.2).toFixed(1); // 四舍五入保留 1 位小数
    console.log(sum === '0.3');       // 输出 true

    // 使用 Number.EPSILON (允许小误差)
    function areEqual(a, b) {
      return Math.abs(a - b) < Number.EPSILON;
    }
    console.log(areEqual(0.1 + 0.2, 0.3)); // 输出 true

如何判断一个对象是否为空对象?

方法一:使用for...in循环

  // 遍历keys
  function isEmptyObject(obj) {
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {

    }
    function isEqual(a, b) {
      return Math.abs(a - b) < Number.EPSILON;
    }

    console.log(isEqual(0.1 + 0.2, 0.3));  // 输出 true

强制类型转换、隐式类型转换分别是什么?场景说明

  1. 强制类型转换,也称为显式类型转换,是通过开发者手动调用函数或运算符来将一个值转换为所需的类型。常见的类型转换包括将值转换为字符串、数字或布尔值。

常见的强制类型转换方法:

  • 转换为字符串:使用 String() 函数或调用 toString() 方法
  • 转换为数字:使用 Number()、parseInt()、parseFloat() 函数
  • 转换为布尔值:使用 Boolean() 函数
    // 将数字转换为字符串 (互转)
    let num = 123;
    let str = String(num);  // str = "123"
    console.log(typeof str); // 输出 "string"
    // 将值转换为布尔值
    let value = 0;
    let isTrue = Boolean(value); // isTrue = false, 因为 0 转换为 false
    console.log(isTrue);  // 输出 false

2.隐式类型转换,也称为自动类型转换,是 JavaScript 在遇到不同类型的值时自动进行的类型转换。它通常发生在算术运算、比较操作等场景下。

常见的隐式类型转换:

  • 数字与字符串混合运算时,JavaScript 会将数字转换为字符串进行拼接
  • 使用双等号 == 进行比较时,JavaScript 会自动进行类型转换以比较值
  • 在条件语句中,非布尔类型会被自动转换为布尔类型
  // 字符串与数字相加
  let num = 10;
  let str = "5";
  let result = num + str;  // "10" + "5" -> "105" (字符串拼接)
  console.log(result);  // 输出 "105"


  // 双等号比较
  let num = 1;
  let bool = true;
  console.log(num == bool);  // 输出 true,因为 `true` 被转换为 1

  // 条件语句中的隐式转换
  let value = "";
  if (value) {
    console.log("True");
  } else {
    console.log("False");  // 输出 "False",因为空字符串被转换为 `false`
  }

总结:

  • 强制类型转换:开发者主动调用函数将数据类型显式转换。
  • 隐式类型转换:JavaScript 自动转换类型,可能导致意外的结果。

===和==的区别

  • ===:严格相等,不会进行类型转换,只有当值和类型都相等时才返回 true。

  • ==:宽松相等,允许类型转换,会将两个值的类型转换为相同类型后再进行比较。

      1 === 1;         // true
      1 === '1';       // false (不同类型,一个是数字,一个是字符串)
      true === 1;      // false (布尔值与数字不同类型)
      null === undefined; // false (不同类型)
    
      // == 
      0 == false;      // true (0 被转换为 false)
      0 === false;     // false (不同类型)
      [] == false;     // true (空数组被转换为 false)
      [] === false;    // false (不同类型)
  • 总结:=== 更严格、更安全,而 == 允许隐式类型转换,但可能导致意外行为。


元素拖动如何实现,实现原理

实现步骤

  1. 监听 mousedown 事件:当用户按下鼠标时,记录鼠标当前位置,并开始监听鼠标移动事件。
  2. 监听 mousemove 事件:在鼠标移动过程中,计算鼠标的位移并更新元素的位置,使其跟随鼠标移动。
  3. 监听 mouseup 事件:当鼠标松开时,停止移动操作,移除鼠标移动的监听器。

实现原理

  1. 获取初始位置:
    当用户按下鼠标(mousedown)时,获取当前鼠标相对于页面的坐标,记录元素的初始位置。

  2. 计算位移并更新位置:

在鼠标移动时(mousemove),不断获取新的鼠标位置,计算与初始按下时的差值,更新元素的 left 和 top 样式,使元素随着鼠标移动。

  1. 结束拖动:
    当用户松开鼠标(mouseup),停止监听 mousemove 事件。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        #draggable {
            width: 100px;
            height: 100px;
            background-color: skyblue;
            position: absolute;
            cursor: grab;
        }
    </style>
    <title>Draggable Element</title>
</head>
<body>
    <div id="draggable">Drag me</div>

    <script>
        const draggable = document.getElementById('draggable');
        let offsetX, offsetY, isDragging = false;

        // 当鼠标按下时
        draggable.addEventListener('mousedown', (event) => {
            isDragging = true;
            offsetX = event.clientX - draggable.offsetLeft;
            offsetY = event.clientY - draggable.offsetTop;
            draggable.style.cursor = 'grabbing';

            // 防止文本选择
            event.preventDefault();
        });

        // 当鼠标移动时
        document.addEventListener('mousemove', (event) => {
            if (isDragging) {
                draggable.style.left = (event.clientX - offsetX) + 'px';
                draggable.style.top = (event.clientY - offsetY) + 'px';
            }
        });

        // 当鼠标松开时
        document.addEventListener('mouseup', () => {
            isDragging = false;
            draggable.style.cursor = 'grab';
        });
    </script>
</body>
</html>

节流和防抖

  1. 节流: 降低用户事件响应的频率(节流是指在规定的时间间隔内,只执行一次操作)
    function throttle(func, delay) {
     let lastTime = 0;
     return function(...args) {
         const now = Date.now();
         if (now - lastTime >= delay) {
             lastTime = now;
             func.apply(this, args);
         }
     };
    }
    

// 使用
window.addEventListener(‘scroll’, throttle(() => {
console.log(‘Throttled scroll event’);
}, 1000));


2. **防抖: 只响应最后一个用户事件(只有在规定的时间内没有再次触发)**
```javascript
function debounce(func, delay) {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// 使用
const input = document.getElementById('searchInput');
input.addEventListener('input', debounce(() => {
    console.log('Debounced input event');
}, 500));

结合场景选择

  • 节流:适合持续触发的场景,例如滚动、拖拽等事件,避免处理函数的频繁调用而影响性能。
  • 防抖:适合非持续触发的场景,例如搜索框输入、表单校验等,只有在用户停止输入后才执行操作。

promise 和 async/await的关系

  • Promise 是异步处理的基础,它通过 .then().catch() 来处理异步操作及其结果。
  • async/await 是基于 Promise语法糖,使得异步代码看起来像同步代码,减少嵌套和复杂的链式调用,提高了代码的可读性和维护性。

async function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Data fetched');
        }, 1000);
    });
}

async function getData() {
    try {
        const result = await fetchData();
        console.log(result);  // 'Data fetched'
    } catch (error) {
        console.error(error);
    }
}

getData();

webworker

  1. 什么是 Web Worker?
    Web Worker 是一个独立的 JavaScript`` 线程,与主线程并行执行代码。它不能直接操作DOM,但可以通过消息传递机制与主线程通信。Web Worker的主要用途是在后台运行长时间运行的JavaScript` 任务,例如数据处理、文件解析等,避免在主线程上执行这些操作时出现界面卡顿的问题。

  2. Web Worker 的特性

  • 独立运行Web Worker 运行在浏览器的单独线程中,不影响主线程(也就是 UI 线程)的执行。主线程负责处理用户交互DOM 操作,而 Web Worker 运行复杂计算、数据处理等。

  • 无法访问 DOMWeb Worker 不能直接访问和操作 DOM 元素,因此它不适合处理与用户界面直接相关的操作。

  • 消息传递:主线程和 Web Worker 之间通过 postMessageonmessage 进行通信。主线程可以向 Worker 发送消息,Worker 可以返回结果。

  • 异步执行:因为 Worker 运行在独立的线程中,执行是异步的,主线程不会被阻塞。

3.1 创建 Worker 文件

// worker.js
self.onmessage = function(event) {
    // 获取主线程发送过来的数据
    const data = event.data;

    // 执行复杂的任务
    let result = 0;
    for (let i = 0; i < data; i++) {
        result += i;
    }

    // 将结果发送回主线程
    self.postMessage(result);
};

3.2 在主线程中使用 Worker

// main.js
const worker = new Worker('worker.js');

// 向 Worker 发送消息
worker.postMessage(1000000000);

// 接收来自 Worker 的结果
worker.onmessage = function(event) {
    console.log('Worker 计算结果:', event.data);
};

// 处理 Worker 错误
worker.onerror = function(error) {
    console.error('Worker 出错:', error.message);
};
  • Web Worker 允许在主线程之外运行 JavaScript 代码,用于处理后台的复杂计算任务,避免阻塞用户界面。
  • 通信机制:主线程和 Worker 通过 postMessage 传递消息,不能直接访问 DOM。
  • 适用场景:适合处理长时间的计算、数据处理任务,提高页面性能和用户体验

最后,如果项目和教程对你有所帮助或者你看见了还算比较喜欢,欢迎给我star,谢谢您!

持续更新中…,如果遇到问题欢迎联系我,在文章最后评论区【留言和讨论】,当然,欢迎点击文章最后的打赏按键,请博主一杯冰阔乐,笑~


  转载请注明: 秦东旭的博客 JavaScript

  目录