公告

👇微信扫码添加好友👇

Skip to content

每日一题 - 202407

7-19

VUEX 和单纯的全局对象有什么区别?

Vuex 状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会更新。

程序不能直接改变 store 中的状态,而是进行显式的提交(commit),这样就可以跟踪每一个状态的变化。

7-18

Vue3.0 为什么要用 proxy?

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象,通过 getter 和 setter 实现。 所以检测不到对象属性的添加和删除,而且它需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

Proxy 直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的,它的很多拦截方法是 Object.defineProperty 不具备的。

在 Vue3.0 中,使用了 Proxy 取代了 defineProperty,主要是因为 Proxy 的强大以及 defineProperty 自身的缺陷。

7-17

多线程的优势

降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 提高响应速度: 当任务到达时,可以不需要等到线程创建就能立即执行。 提高线程的可管理性: 统一管理线程,避免系统创建大量同类线程而导致消耗完内存。

7-16

如何优化接口性能?

  1. 优化数据库索引
  2. 优化 SQL 语句
  3. 避免在事务中进行大量操作
  4. 异步处理,剥离主逻辑和副逻辑
  5. 多线程场景下降低锁粒度
  6. 加缓存,Redis 等
  7. 分库分表
  8. 避免循环查询数据库

7-15

JAVA 实例化的顺序

  1. 静态属性,静态代码块
  2. 普通属性,普通代码块
  3. 构造方法

7-14

String s = new String("mufeng"); 创建了几个对象?

在执行 String s = new String("mufeng"); 时,创建了两个对象。

  1. 字面量对象: Java 字符串字面量 "mufeng" 被存储在字符串池中,如果该字面量之前没有在字符串池中出现过,会在字符串池中创建一个新的字符串对象。
  2. 堆中的字符串对象: new String("mufeng") 会在堆中创建一个新的字符串对象,其内容为 "mufeng",即使字符串池中已经有 "mufeng" 对象,也会在堆中再创建一个新的对象。

7-13

什么是字符串常量池?

字符串常量池(String Constant Pool)是 Java 中一种特殊的内存区域,用于存储字符串字面量。这种机制的主要目的是为了节省内存空间并提高性能。它位于 Java 堆内存(Heap Memory)中的一个特定区域。

当使用双引号创建字符串(如 "mufeng")时,编译器会检查字符串常量池,如果该字符串已经存在,则直接引用池中的对象;如果不存在,则在池中创建一个新的字符串对象。

字符串常量池可以提高内存效率和性能:

  • 通过避免重复创建相同内容的字符串,可以显著减少内存开销。
  • 字符串比较操作(使用 ==)在常量池中的字符串上会更快,因为它们比较的是内存地址而不是内容。
java
public class StringPoolExample {
    public static void main(String[] args) {
        String str1 = "mufeng"; // 字符串字面量,存储在常量池中
        String str2 = "mufeng"; // 引用常量池中的同一个对象
        String str3 = new String("mufeng"); // 在堆中创建新的字符串对象
        String str4 = str3.intern(); // 将 str3 引用的字符串添加到常量池中,或获取常量池中的相同内容的字符串引用

        System.out.println(str1 == str2); // true
        System.out.println(str1 == str3); // false
        System.out.println(str1 == str4); // true
    }
}

在这个示例中,str1str2 都引用常量池中的同一个字符串对象,而 str3 引用的是堆中的新对象。通过调用 str3.intern()str4 引用了常量池中的字符串对象。

7-12

什么是 StringJoiner ?

StringJoiner 是 JAVA8 新增的 API,基于 StringBuilder 实现,用于实现字符串之间通过分隔符拼接的场景。

常用的 Collectors.joining(","),底层就是通过 StringJoiner 实现的。

java
String[] strings = {"白鹿原", "平凡的世界", "穆斯林的葬礼", "呼兰河传"};
StringJoiner stringJoiner = new StringJoiner("》,《", "《", "》");
for (String string : strings) {
    stringJoiner.add(string);
}

stringJoiner:

text
《白鹿原》,《平凡的世界》,《穆斯林的葬礼》,《呼兰河传》

7-11

为何 JDK9 要将 String 的底层实现由 char[] 改成 byte[]?

主要是为了节约内存

在大部分 Java 程序的堆内存中,String 占用的空间最大,并且绝大多数 String 只有 Latin-1 字符,这些 Latin-1 字符只需要1个字节就够了。

而在 JDK9 之前,JVM 因为 String 使用 char 数组存储,每个 char 占2个字节,所以即使字符串只需要1字节,它也要按照2字节进行分配,浪费了一半的内存空间。

到了 JDK9 之后,对于每个字符串,会先判断它是不是只有 Latin-1 字符,如果是,就按照1字节的规格进行分配内存,如果不是,就按照2字节的规格进行分配, 这样便提高了内存使用率,同时GC次数也会减少,提升效率。

不过 Latin-1 编码集支持的字符有限,比如不支持中文字符,因此对于中文字符串,用的是 UTF16 编码(两个字节),所以用 byte[]char[] 实现没什么区别。

7-10

什么是自动拆装箱?

由于 JAVA 面向对象的特性,很多地方都需要引用数据类型而非基础数据类型,比如在集合类中,要求放入元素为 对象,所以无法放入基础数据类型 int 等。

为了让基础数据类型也拥有引用数据类型的特性,就出现了包装类型,将引用数据类型用对象包装,让其拥有对象的特性,为其添加属性和方法,丰富操作。

原始类型和包装类型对应关系:

原始类型包装类型
booleanBoolean
byteByte
charCharacter
floatFloat
intInteger
longLong
shortShort
doubleDouble

当基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱:

  • 赋值操作(装箱或拆箱)
  • 进行加减乘除混合运算 (拆箱)
  • 进行>,<,==比较运算(拆箱)
  • 调用equals进行比较(装箱)
  • ArrayList、HashMap 等集合类添加基础类型数据时(装箱)

7-9

怎么实现图片懒加载?

懒加载是一种网页性能优化的方式,它能极大的提升用户体验。就比如说图片,图片一直是影响网页性能的主要元凶,现在一张图片超过几兆已经是很经常的事了。如果每次进入页面就请求所有的图片资源,那么可能等图片加载出来用户也早就走了。所以,我们需要懒加载,进入页面的时候,只请求可视区域的图片资源。

1、html实现

最简单的实现方式是给 img 标签加上 loading="lazy",比如

javascript
<img src="./example.jpg" loading="lazy">

2、JS实现原理

我们通过js监听页面的滚动也能实现。

使用js实现的原理主要是判断当前图片是否到了可视区域:

  • 拿到所有的图片 dom 。
  • 遍历每个图片判断当前图片是否到了可视区范围内。
  • 如果到了就设置图片的 src 属性。
  • 绑定 window 的 scroll 事件,对其进行事件监听。

在页面初始化的时候,图片的src实际上是放在data-src属性上的,当元素处于可视范围内的时候,就把data-src赋值给src属性,完成图片加载。

javascript
// 在一开始加载的时候
<img data-src="http://xx.com/xx.png" src="" />

// 在进入可视范围内时
<img data-src="http://xx.com/xx.png" src="http://xx.com/xx.png" />
使用背景图来实现,原理也是一样的,把图片链接存放在 `data-src` 中,在可视范围时,就把data-src赋值给 `background-image` 属性,完成图片加载。
javascript
// 在一开始加载的时候
<div
  data-src="http://xx.com/xx.png"
  style="background-image: none;background-size: cover;"
></div>

// 在进入可视范围内时
<div
  data-src="http://xx.com/xx.png"
  style="background-image: url(http://xx.com/xx.png);background-size: cover;"
></div>

示例

html
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Lazyload</title>
    <style>
      img {
        display: block;
        margin-bottom: 50px;
        height: 200px;
        width: 400px;
      }
    </style>
  </head>
  <body>
    <img src="./img/default.png" data-src="./img/1.jpg" />
    <img src="./img/default.png" data-src="./img/2.jpg" />
    <img src="./img/default.png" data-src="./img/3.jpg" />
    <img src="./img/default.png" data-src="./img/4.jpg" />
    <img src="./img/default.png" data-src="./img/5.jpg" />
    <img src="./img/default.png" data-src="./img/6.jpg" />
    <img src="./img/default.png" data-src="./img/7.jpg" />
    <img src="./img/default.png" data-src="./img/8.jpg" />
    <img src="./img/default.png" data-src="./img/9.jpg" />
    <img src="./img/default.png" data-src="./img/10.jpg" />
  </body>
</html>

先获取所有图片的 dom,通过 window.innerHeight || document.documentElement.clientHeight|| document.body.clientHeight 获取可视区高度,再使用 element.getBoundingClientRect() API 直接得到元素相对浏览的 top 值, 遍历每个图片判断当前图片是否到了可视区范围内。最后给 window 绑定 onscroll 事件。

javascript
function lazyload() {
  let viewHeight = window.innerHeight || document.documentElement.clientHeight|| document.body.clientHeight //获取可视区高度,兼容不同浏览器
  let imgs = document.querySelectorAll('img[data-src]')
  imgs.forEach((item, index) => {
    if (item.dataset.src === '') return

    // 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
    let rect = item.getBoundingClientRect()
    if (rect.bottom >= 0 && rect.top < viewHeight) {
      item.src = item.dataset.src
      item.removeAttribute('data-src')
    }
  })
}
window.addEventListener('scroll', lazyload)

这样存在较大的性能问题,因为 scroll 事件会在很短的时间内触发很多次,严重影响页面性能,为了提高网页性能,我们需要一个节流函数来控制函数的多次触发,在一段时间内(如 200ms)只执行一次回调。

优化(节流)

javascript
function throttle(fn, delay) {
  let timer
  let prevTime
  return function (...args) {
    const currTime = Date.now()
    const context = this
    if (!prevTime) prevTime = currTime
    clearTimeout(timer)

    if (currTime - prevTime > delay) {
      prevTime = currTime
      fn.apply(context, args)
      clearTimeout(timer)
      return
    }

    timer = setTimeout(function () {
      prevTime = Date.now()
      timer = null
      fn.apply(context, args)
    }, delay)
  }
}
window.addEventListener('scroll', throttle(lazyload, 200))

7-8

什么是事件代理,以及它的应用场景有哪些?

一、事件代理是什么

事件代理,俗地来讲,就是把一个元素响应事件(clickkeydown......)的函数委托到另一个元素

前面讲到,事件流的都会经过三个阶段: 捕获阶段 -> 目标阶段 -> 冒泡阶段,而事件委托就是在冒泡阶段完成

事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素

当事件响应到目标元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数

二、应用场景

如果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件

javascript
<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>

如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的

javascript
// 获取目标元素
const lis = document.getElementsByTagName("li")
// 循环遍历绑定事件
for (let i = 0; i < lis.length; i++) {
    lis[i].onclick = function(e){
        console.log(e.target.innerHTML)
    }
}

这时候就可以事件委托,把点击事件绑定在父级元素ul上面,然后执行事件的时候再去匹配目标元素

javascript
// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
    // 兼容性处理
    var event = e || window.event;
    var target = event.target || event.srcElement;
    // 判断是否匹配目标元素
    if (target.nodeName.toLocaleLowerCase === 'li') {
       console.log('the content is: ', target.innerHTML);
   }
});

还有一种场景是上述列表项并不多,我们给每个列表项都绑定了事件

但是如果用户能够随时动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件

如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的

三、总结

适合事件委托的事件有:clickmousedownmouseupkeydownkeyupkeypress

从上面应用场景中,我们就可以看到使用事件委托存在两大优点:

  • 减少整个页面所需的内存,提升整体性能
  • 动态绑定,减少重复工作

但是使用事件委托也是存在局限性:

  • focusblur 这些事件没有事件冒泡机制,所以无法进行委托绑定事件
  • mousemovemouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的

如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件

7-7

JavaScript 中内存泄漏有哪几种情况?

一、内存泄漏是什么

内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存

并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费

程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存

对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃

二、垃圾回收机制

Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

通常情况下有两种实现方式:

  • 标记清除
  • 引用计数

标记清除

JavaScript最常用的垃圾收回机制

当变量进入执行环境是,就标记这个变量为“进入环境“。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为“离开环境“

垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉

在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了

随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存

javascript
var m = 0,n = 19 // 把 m,n,add() 标记为进入环境。
add(m, n) // 把 a, b, c标记为进入环境。
console.log(n) // a,b,c标记为离开环境,等待垃圾回收。
function add(a, b) {
  a++
  var c = a + b
  return c
}

引用计数

语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放

如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏

三、常见内存泄漏情况

意外的全局变量

javascript
function foo(arg) {
    bar = "this is a hidden global variable";
}

另一种意外的全局变量可能由 this 创建:

javascript
function foo() {
    this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();

上述使用严格模式,可以避免意外的全局变量

定时器也常会造成内存泄露

javascript
var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
   if(node) {
        // 处理 node 和 someResource
       node.innerHTML = JSON.stringify(someResource);
    }
}, 1000);

如果id为Node的元素从DOM中移除,该定时器仍会存在,同时,因为回调函数中包含对someResource的引用,定时器外面的someResource也不会被释放

包括我们之前所说的闭包,维持函数内局部变量,使其得不到释放

javascript
function bindEvent() {
  var obj = document.createElement('XXX');
  var unused = function () {
    console.log(obj, '闭包内引用obj obj不会被释放');
  };
  obj = null; // 解决方法
}

没有清理对DOM元素的引用同样造成内存泄露

javascript
const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引用

包括使用事件监听addEventListener监听的时候,在不监听的情况下使用removeEventListener取消对事件监听

7-6

RESTful 接口规范是什么?

RESTful 接口规范是一种设计 Web 服务接口的风格和规范,遵循 REST(Representational State Transfer)架构。它的设计原则包括以下几点:

  1. 资源(Resources):将系统中的所有事物视为资源,每个资源都有一个唯一的标识符(通常是 URL),用于对其进行操作。
  2. 统一接口(Uniform Interface):接口设计应该简单一致,包括以下几个方面:
    • 使用标准的 HTTP 方法(GET、POST、PUT、DELETE 等)来对资源进行操作。
    • 使用标准的 HTTP 状态码(如 200、404、500)来表示请求结果。
    • 使用资源的 URL 来唯一标识资源。
    • 使用适当的 MIME 类型(如 JSON、XML)来传输数据。
  3. 状态无关(Stateless):每个请求都应该包含足够的信息,服务器不需要保存客户端的状态。这样可以使系统更加简单、可伸缩性更好。
  4. 客户端 - 服务器分离(Client-Server Separation):客户端和服务器之间的交互应该通过标准化的接口进行,使得客户端和服务器可以独立地进行演化。
  5. 可缓存性(Cacheability):对于经常不变的数据,应该使用合适的缓存机制,提高系统的性能和可伸缩性。
  6. 按需代码(Code on Demand)(可选):服务器可以向客户端传输可执行代码,以提供更丰富的功能。

遵循 RESTful 接口规范能够使得系统具有良好的可维护性、可伸缩性和性能,并且更容易与其他系统进行集成。

7-5

如何让 var [a, b] = {a: 1, b: 2} 解构赋值成功?

如果直接运行:

javascript
const obj={a:'1',b:'2'}
const [a,b]=obj

运行结果: 报错!Uncaught TypeError: obj is not iterable at .....

obj这个对象是不可迭代的

1、可迭代协议

可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object)。

要成为可迭代对象,该对象必须实现 @@iterator 方法,这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性:

[Symbol.iterator]

一个无参数的函数,其返回值为一个符合迭代器协议的对象。

当一个对象需要被迭代的时候(比如被置入一个 for...of 循环时),首先,会不带参数调用它的 @@iterator 方法,然后使用此方法返回的迭代器获得要迭代的值。

简而言之就是让obj成为一个可迭代的对象需要它实现 @@iterator 方法,具体表现为对象身上要有一个名为[Symbol.iterator] 的方法。而数组和Map则是一开始就有这个方法,所以它们是可迭代的。

数组解构的本质
javascript
const array = [1,2,3]
var [a,b,c] = array
// 本质上是
const iterator = array[Symbol.iterator]()
var a = iterator.next().value
var b = iterator.next().value
var c = iterator.next().value

2、解决方案

javascript
 const obj = {
    a: '1',
    b: '2',
    [Symbol.iterator]() {
        let index = 0
        const keys = Object.keys(this)
        return {
            next() {
                if (index < keys.length) {
                    return {
                        done: false,
                        value: obj[keys[index++]]
                    }
                }
                return {done:true,value:undefined}
            }
        }
    }
}

const [a, b] = obj

7-4

Proxy 能够监听到对象中的对象的引用吗?

Proxy可以监听到对象中的对象的引用。

当使用Proxy包装一个对象时,可以为该对象的任何属性创建一个拦截器,包括属性值为对象的情况。

javascript
const obj = {
  nestedObj: { foo: 'bar' }
}

const handler = {
  get(target, prop, receiver) {
    const value = Reflect.get(target, prop, receiver)
    if (typeof value === 'object' && value !== null) {
      return new Proxy(value, handler)
    }
    console.log('get', prop, target[prop])
    return value
  },
  set(target, property, value) {
    target[property] = value
    console.log(`Setting property '${property}' to '${value}'`)
    return true
  }
}

const proxyObj = new Proxy(obj, handler)
proxyObj.nestedObj.foo = 'baz'  // 输出: Setting property 'foo' to 'baz'

7-3

MessageChannel 是什么,有什么使用场景?

MessageChannel 是一个 JavaScript API,用于在两个独立的执行环境(如 Web Workers 或者不同的 browsing contexts)之间建立双向通信的通道。

MessageChannel 提供了两个通信端点(port1port2),可以在两个不同的执行环境之间传递消息,并通过事件监听的方式来处理这些消息。

使用场景包括但不限于:

  1. Web Workers 通信:在 Web 开发中,MessageChannel 通常用于在主线程和 Web Worker 之间建立通信通道,以便主线程与 Worker 之间传递消息和数据。
  2. 不同浏览上下文(browsing context)之间的通信:在现代浏览器中,多个标签页、iframe 或者其他类型的 browsing context 可以通过 MessageChannel 实现通信。
  3. SharedWorker 通信MessageChannel 可以用于在主线程和 Shared Worker 之间建立通信通道。
  4. 服务端和客户端之间的通信MessageChannel 可以用于客户端(如浏览器)与服务端(如 WebSocket 服务器)之间的通信,特别是在与 WebSocket 或其他类似技术结合使用时。
  5. 异步任务处理:在某些场景中,使用 MessageChannel 可以更方便地处理异步任务,因为它提供了独立于主线程的通信通道。
javascript
// 创建 MessageChannel
const channel = new MessageChannel();
const port1 = channel.port1;
const port2 = channel.port2;

// 在主线程中
const worker = new Worker('worker.js');
worker.postMessage({ port: port2 }, [port2]);

port1.onmessage = function(event) {
  console.log('Received message from worker:', event.data);
};

// 发送消息给 worker
port1.postMessage('Hello, Worker!');

7-2

mouseEnter 和 mouseOver 有什么区别?

mouseentermouseover 是两个用于处理鼠标进入元素时的事件,但它们在一些关键点上有所不同:

  1. 事件冒泡
    • mouseenter:这个事件在鼠标指针首次进入特定元素(或其子元素)时触发。当鼠标进入元素时,会触发该元素的 mouseenter 事件,但不会在元素的子元素上冒泡。因此,该事件通常用于检测鼠标首次进入元素时的动作。
    • mouseover:这个事件在鼠标指针移动到某个元素上时触发,不论它是直接在这个元素上触发还是在其子元素上触发。当鼠标进入一个元素时,它会在该元素上触发 mouseover 事件,然后冒泡到父元素。
  2. 事件触发范围
    • mouseenter:当鼠标进入元素自身时触发,只在目标元素上触发,不会因为鼠标移动到其子元素上而再次触发。
    • mouseover:不仅在目标元素上触发,也在其子元素上触发。所以,如果鼠标从一个子元素移动到另一个子元素,这些元素的父元素会触发多个 mouseover 事件。
  3. 事件对象的属性
    • mouseenter:事件对象通常会有 relatedTarget 属性,它指向鼠标移动前的那个元素。如果 relatedTarget 指向目标元素或为 null,那么事件就不会触发。
    • mouseover:事件对象也会有 relatedTarget 属性,通常指向从中离开的那个元素。

使用场景

  • mouseenter 更适合用来检测鼠标首次进入某个元素时的行为。
  • mouseover 更适合用来检测鼠标在元素或其子元素之间移动时的行为,因为它冒泡。

在实际使用时,如果你只想在鼠标首次进入元素时触发某些行为(比如显示一个提示),可以使用 mouseenter;如果你希望在鼠标移动到某个元素或其子元素上时都触发某些行为(比如动态改变样式),可以使用 mouseover

7-1

Js的基础类型,typeof和instanceof的区别?

基础类型有:boolean、string、number、bigint、undefined、symbol、null

typeof能识别所有的值类型,识别函数,能区分是否是引用类型。

instanceof用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

javascript
const a = "str";
console.log("typeof a :>> ", typeof a); // typeof a :>>  string

const b = 999;
console.log("typeof b :>> ", typeof b); // typeof b :>>  number

const c = BigInt(9007199254740991);
console.log("typeof c :>> ", typeof c); // typeof c :>>  bigint

const d = false;
console.log("typeof d :>> ", typeof d); // typeof d :>>  boolean

const e = undefined;
console.log("typeof e :>> ", typeof e); // typeof e :>>  undefined

const f = Symbol("f");
console.log("typeof f :>> ", typeof f); // typeof f :>>  symbol

const g = null;
console.log("typeof g :>> ", typeof g); // typeof g :>>  object

const h = () => {};
console.log("typeof h :>> ", typeof h); // typeof h :>>  function

const i = [];
console.log("typeof i :>> ", typeof i); // typeof i :>>  object