前端中的那些 This vs That,你知道吗?

2024年 6月 3日 61.4k 0

前端知识中有很多相近的概念或 API,相信不少人在开发中有注意到这些相近的概念或 API,但是有时不会深入去了解异同,只要某个 API 能满足开发需求即可。

本文将介绍一些相近的概念和 API,让你能更清晰地了解它们的异同,在使用时更游刃有余。

1. cookie vs localStorage vs sessionStorage

前端开发中,这三个本地存储方案可以说是很常见的,用一张图说明下它们的区别:

前端中的那些 This vs That,你知道吗?-1comparison-table

图片来源:local-storage-vs-session-storage-vs-cookies[1]

图中从存储大小、是否自动过期、服务端是否可以获取、是否支持 HTTP 请求传输和数据持久性方面进行对比。除了图中几个部分,在作用域方面,cookie 由域名和路径决定,localStorage 和 sessionStorge 都是遵守同源策略。

最后再提几个关于在使用 sessionStorage 的时偶尔会陌生的知识点:

  • sessionStorage 数据在各个直接打开的浏览器页签中是不会同步的,这意味着你打开了两个同域名的网站,在其中一个设置了 sessionStorage 数据,另一个页面是不会同步这个数据的(而 localStorage 会),也就是说 sessionStorage 除了关闭浏览器时不会保留数据,各个页签数据的同步也和 localStorage 不一样。
  • 如果你在当前页设置了一些 sessionStorage 数据,然后通过 window.open 或  标签打开,新页签会同步一份当前页副本,随后两个页签的 sessionStorage 又会是独立的,不过要注意打开新页签的 rel 属性(用于指定当前文档与被链接文档的关系)要设置为 opener。
  • 前端中的那些 This vs That,你知道吗?-2图片

    2. querySelectorAll vs getElementsByTagName

    querySelectorAll 可以根据传入的 CSS 选择器查找 HTML 元素,使用上比 getElementsByTagName 更灵活。

    它们之间的不同点在于:querySelectorAll 返回的是一个静态的 NodeList,而 getElementsByTagName 返回的是动态的。

    来看下面这个示例:

    • 1
    • 2
    • 3

    接下来使用两个方法获取 li 元素类数组,然后再动态插入一个 li,最后查看两个类数组的长度。

    const listItems = document.querySelectorAll('li');
    const listItems2 = document.getElementsByTagName('li');
    console.log(listItems.length, listItems2.length);  // 3,3
    
    const list = document.querySelector('ul');
    const li = document.createElement('li');
    li.innerHTML = '4';
    list.appendChild(li);
    
    console.log(listItems.length, listItems2.length);  // 3, 4

    可以看到 querySelectorAll 方法获取的类数组长度在动态添加 li 后还是 3,而 getElementsByTagName 的为 4。

    常用的获取元素方法中getElementsByClassName 方法、element.childNodes 和 element.children 返回的也是动态 NodeList。

    3. children vs childNodes

    children 和 childNodes 都可以用来获取元素的子节点,不同的是 children 只会获取 HTML 元素节点,而 childNodes 会获取到非 HTML 元素节点,包括文本、注释节点等。

    • A
    • B
    • C
    const parent = document.querySelector('ul');
    // 输出 HTMLCollection(3) [li, li, li]
    console.log(parent.children)
    // 输出 NodeList(10) [text, comment, text, text, li, text, li, text, li, text]
    console.log(parernt.childNodes)

    4. microtasks vs macrotasks

    宏任务和微任务概念也经常在前端中出现,与之相关的就是事件循环机制。事件循环机制是必须掌握的,宏任务和微任务也可以了解下,实际开发中碰到相关问题能反应过来是宏任务和微任务的不同即可。

    宏任务包括:

    • setTimeout and  setInterval 的回调
    • DOM 操作
    • I/O 操作 (Node 中读写文件)
    • requestAnimationFrame

    微任务包括:

    • Promises 的 resolve 和 reject
    • MutationObserver 回调
    • Node 中的 process.nextTick

    事件循环机制如下图:

    前端中的那些 This vs That,你知道吗?-3图片

    宏任务微任务执行顺序如下图:

    前端中的那些 This vs That,你知道吗?-4图片

    最后配合一个例子看下效果:

    console.log('Script start')
    
    setTimeout(function () {
      console.log('setTimeout')
    }, 0)
    
    new Promise((resolve) => {
      console.log('Promise')
    }).then(function () {
      console.log('Promise then')
    })
    
    console.log('Script end')
    
    // 输出顺序为: Script start、Promise、Script end、Promise then、setTimeout

    一个更清晰的图(源[2]):

    前端中的那些 This vs That,你知道吗?-5图片

    5. setTimeout(0) vs requestAnimationFrame

    setTimeout(0) 和 requestAnimationFrame 都能把代码延迟到下一个动画帧运行,它们的不同在于:

    • setTimeout(0) 将代码推到事件循环的任务队列中,如果任务队列中有大量任务,setTimeout(0) 就不会立即执行。
    • requestAnimationFrame 会在下一次渲染前执行,而不是在事件循环中执行,它能自动与显示器刷新率同步。不过,它只有在浏览器准备好渲染新帧时才会执行,如果标签页处于非激活状态,它就不会运行。

    处理动画时,requestAnimationFrame 更合适, 如果你要延迟执行代码的话,可以直接使用 setTimeout(0)。

    补充一个小点:setTimeout 的语法是  setTimeout(functionRef, delay, param1, param2, /* … ,*/ paramN),除了回调函数和延迟时间,后续参数都会作为回调函数的参数。

    // 1 秒后输出 delay 1s
    setTimeout(console.log, 1000, 'delay 1s')

    6. naturalWidth vs width

    naturalWidth 是元素的自然宽度,它永远不会改变。例如,一张 100px 宽的图片的 naturalWidth 始终是 100px,即使通过 CSS 或 JavaScript 调整图片大小后也不变。

    而 width 是可以改变的,可以通过 CSS 或 JavaScript 设置。

    前端中的那些 This vs That,你知道吗?-6图片

    7. stopImmediatePropagation vs stopPropagation

    stopImmediatePropagation() 方法与 stopPropagation() 方法一样,可阻止事件冒泡。但是,stopImmediatePropagation() 方法会阻止元素同一事件的其他监听器。

    button.addEventListener('click', function () {
      console.log('foo')
    })
    
    button.addEventListener('click', function (e) {
      console.log('bar')
      e.stopImmediatePropagation()
    })
    
    button.addEventListener('click', function () {
      console.log('baz')
    })

    上面代码中按钮点击后只会输出 foo and bar,baz 的事件监听函数不会触发。

    8. HTML 字符实体 vs Unicode 字符

    HTML 实体是特殊字符序列,用来表示可能被误认为是 HTML 代码的字符,如小于号 (=16.9.0",
    "react-dom": ">=16.9.0"
    },

    12. isNaN vs Number.isNaN

    isNaN 是一个全局函数,用于判断参数是否为 NaN,不过,在判断参数是否为 NaN 之前,它会尝试先将参数转换为数字。

    isNaN('hello');     // true
    isNaN(undefined);   // true
    isNaN({});          // true
    isNaN([]);          // false  +[] === 0
    isNaN(42);          // false

    在 ES6 中引入了 Number.isNaN 函数,与 isNaN 不同的是,在判断前 Number.isNaN 不会转换参数。

    Number.isNaN('hello');      // false
    Number.isNaN(undefined);    // false
    Number.isNaN({});           // false
    Number.isNaN([]);           // false
    Number.isNaN(42);           // false
    Number.isNaN(NaN);          // true

    一般来说,使用 Number.isNaN 比 isNaN 更准确。

    13. 默认参数 vs 或操作符

    JavaScript 提供了两种为函数参数设置默认值的方法:使用默认参数或 OR (||) 操作符,两者在最终效果上会有一些不同。

    先来看默认参数:

    const sayHello = (name = 'World') => {
      console.log(`Hello, ${name}!`);
    };
    
    sayHello();                 // `Hello, World!`
    sayHello(undefined);        // `Hello, World!`
    sayHello(null);    // `Hello, null!`
    sayHello('');    // `Hello, !`
    sayHello("Phuoc Nguyen");   // `Hello, Phuoc Nguyen!`

    可以看到默认参数只有为 undefined 的时候,默认参数才会生效。不传和传 undefined 效果一致。

    再来看或操作符:

    const sayHello2 = (name) => {
        const withDefaultName = name || 'World';
        console.log(`Hello, ${withDefaultName}!`);
    };
    
    sayHello2();                 // `Hello, World!`
    sayHello2(undefined);        // `Hello, World!`
    sayHello2(null);    // `Hello, World!`
    sayHello2('');     // `Hello, World!`
    sayHello2("Phuoc Nguyen");   // `Hello, Phuoc Nguyen!`

    可以看到参数只要是 falsy 值(undefined、null、NaN、0、""和 false),都会使用代码中默认参数,这个就是和 ES6 默认参数不同的地方。

    14. null vs undefined

    null 和 undefined 的不同点如下:

  • undefined 表示变量已经被声明,但未被赋值;null 用来表示变量没有值。
  • let foo;
    console.log(foo); // undefined
    
    let foo = null;
    console.log(foo); // null
  • undefined 和 null 代表的类型不同。
  • console.log(typeof undefined); // 'undefined'
    console.log(typeof null); // 'object'

    除了以上两点不同之外,还有两点值得关注的:

  • undefined 和 null 进行比较的结果。
  • null == undefined; // true
    null === undefined; // false
  • JSON.stringify 会忽略 undefined, 但是会保留 null。
  • JSON.stringify({
        name: 'John',
        address: null,
        age: undefined,
    });
    
    // {"name":"John","address":null}

    小结

    前端中还有很多相近的概念和 API,在业务开发时可能没时间去了解,但是有空的时候还是可以花点时间去掌握其中的异同,扎实自己的前端基础。

    参考资料

    [1]local-storage-vs-session-storage-vs-cookies: https://www.loginradius.com/blog/engineering/guest-post/local-storage-vs-session-storage-vs-cookies/

    [2]图片源: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23

    相关文章

    塑造我成为 CTO 之路的“秘诀”
    “人工智能教母”的公司估值达 10 亿美金
    教授吐槽:985 高校成高级蓝翔!研究生基本废了,只为房子、票子……
    Windows 蓝屏中断提醒开发者:Rust 比 C/C++ 更好
    Claude 3.5 Sonnet 在伽利略幻觉指数中名列前茅
    上海新增 11 款已完成登记生成式 AI 服务

    发布评论