【js篇】二:常见面试题

235 阅读11分钟

【js篇】常见面试问题

函数的柯里化

总结:思路,每次执行,判断参数是否齐全,如果不齐全,递归调用把原来的 arguments一起上去

      function curry(fn,...args){
        console.log("...args",args) //就是arguments
        // 判断fn需要的参数是否够了
        if(args.length >= fn.length){ //fn.length 是add函数的形参个数
          return fn(...args) // 收集参数齐所有的参数 即是arguments解构
        }
        // 递归调用
        return (...rest)=>{
          console.log('...rest',...rest)// rest就是当前调用的参数 arguments
          return curry(fn,...args,...rest) //递归调用把原来的 arguments一起上去 和当前的arguments一起 解构
        }
      }

      //案例一:
      const add =(x,y,z)=>x+y+z;
      const curryAdd=curry(add);
      let b=curryAdd(1)(2)(3)
      console.log(b) // 6

数组的扁平

image.png

      const arr = [
        {id: 1, name: '部门1', pid: 0},
        {id: 2, name: '部门2', pid: 1},
        {id: 3, name: '部门3', pid: 2},
        {id: 4, name: '部门4', pid: 3},
        {id: 5, name: '部门5', pid: 4},
      ]


      function changeFun(aray,id){
        let arr=[];
        for(let item of aray){
          if(item.pid==id){
            arr.push({...item,children:children(aray,item.id)})
          }
        }
        return aray
      }

数组的降维度

    let arr12=[1,3,4,[5,6,7,[8,9]]]
    console.log('flat',arr12.flat(1)) //[1,3,4,5,6,7,[8,9]]
    console.log('flat',arr12.flat(Infinity))// [1,3,4,5,6,7,8,9]

去重

set方法

    
     let aray=[2,4,1,2,4,6,8,9,1]
        let setArray=new Set(aray)
        aray=[...setArray]
        console.log(aray) // [2, 4, 1, 6, 8, 9]

使用 filter + indexOf

const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = arr.filter((item, index) => arr.indexOf(item) === index);
console.log(uniqueArr); // [1, 2, 3, 4, 5]

使用 reduce

const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = arr.reduce((acc, current) => {
  if (!acc.includes(current)) {
    acc.push(current);
  }
  return acc;
}, []);
console.log(uniqueArr); // [1, 2, 3, 4, 5]

使用 for 循环

function unique(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    if (result.indexOf(arr[i]) === -1) {
      result.push(arr[i]);
    }
  }
  return result;
}

const arr = [1, 2, 2, 3, 4, 4, 5];
console.log(unique(arr)); // [1, 2, 3, 4, 5]

乱序

冒泡排序

      // 数组排序(升和降)sort()
      // let aray=[2,4,1,6,8,9,20,21,18]
      // function riseNumber(a,b){ // 升序
      //   return a-b;
      // }
      // function dropNumber(a,b){
      //   return b-a
      // }
      // console.log(aray.sort(riseNumber))//\[1, 2, 4, 6, 8, 9, 18, 20, 21]

// console.log(aray.sort(dropNumber)) //[21, 20, 18, 9, 8, 6, 4, 2, 1]

简写 const numbers = [10, 2, 1, 20]; numbers.sort((a, b) => a - b); console.log(numbers); // [1, 2, 10, 20]

      // 冒泡排序
      // let aray=[2,4,1,6,8,9,20,21,18]
      // for(var i=0;i<aray.length-1;i++){
      //   let isSort=true;
      //   for(var j=0;j<aray.length-1-i;j++){
      //     if(aray[j]>aray[j+1]){
      //       isSort=false;
      //       let tmp=aray[j]
      //       aray[j]=aray[j+1]
      //       aray[j+1]=tmp
      //     }
      //   }
      //   if(isSort) break;
      // }

面试题一

image.png

image.png

const声明对象

虽然const变量不能修改指针,但是可以修改值,比如我们定义一个对象,我们就可以修改对象里的属性值,但是不可以重写整个对象。

["1","2","3"].map(parseInt)

分解

  1. map():里面是个回调函数,三个参数:分别是当前值(v),下标(i),原始数组(arr)

2: 整写法如下['1', '2', '3'].map((v, i, arr) => parseInt(v, i)) image.png

从这个简单例子来看,new操作符做了两件事:

  1. 创建了一个全新的对象。
  2. 这个对象会被执行[[Prototype]](也就是__proto__)链接。

promise和async区别

  • promise是个对象Es6语法,async是个函数,es7语法
  • promise 处理异步是另一种地狱回调async是测底拉平,更加优雅
  • async的底层是promise

事件捕捉 与冒 泡模型

image.png

image.png

image.png

image.png

如何阻止事件冒泡?

ie:阻止冒泡ev.cancelBubble = true;
非IE ev.stopPropagation();

如何阻止默认事件?

(1)return false;(2) ev.preventDefault();

Javascript的事件流模型都有什么?

“事件捕捉”:是从上往下,window,document,document.documentelment(获取的html) document,body 、……..目标元素

“事件冒泡”:是从下往上:反之

“DOM事件流”:三个阶段:事件捕捉、目标阶段、事件冒泡

image.png

null和undefined的区别?

null是一个表示"无"的对象转为数值时为0undefined是一个表示"无"的原始值转为数值时为NaN
当声明的变量还未被初始化时,变量的默认值为undefined。 null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。

post 和get 区别

image.png

前端性能值有哪些,如何量化

首次内容绘制【 First Contentful Paint, FCP

  • 定义: 浏览器首次渲染DOM内容的时间。
  • 量化: 通过PerformanceObserverAPI监测first-contentful-paint条目。

最大内容绘制【Largest Contentful Paint, LCP

  • 定义: 页面中最大内容元素渲染完成的时间。
  • 量化: 使用PerformanceObserver监测largest-contentful-paint条目。

首次输入延迟【First Input Delay, FID

  • 定义: 用户首次与页面交互到浏览器响应的延迟时间。
  • 量化: 通过PerformanceObserver监测first-input条目。

累计布局偏移【 Cumulative Layout Shift, CLS

  • 定义: 页面生命周期内发生的意外布局偏移总和。
  • 量化: 使用PerformanceObserver监测layout-shift条目,并计算偏移分数。

交互与时间【Time to Interactive, TTI

  • 定义: 页面完全可交互的时间。
  • 量化: 通过Lighthouse等工具测量。

首字节时间【Time to First Byte, TTFB

  • 定义: 浏览器接收到服务器响应的第一个字节的时间。
  • 量化: 使用performance.timingPerformanceObserver监测responseStartrequestStart的差值。

Domcontentloaded 事件

  • 定义: HTML文档完全加载和解析完成的时间。
  • 量化: 通过performance.timing.domContentLoadedEventStart获取。

资源加载时间

  • 定义: 页面中各个资源(如图片、CSS、JS)的加载时间。
  • 量化: 使用浏览器开发者工具的Network面板查看。

页面完全加载时间

  • 定义: 页面所有资源加载完成的时间。
  • 量化: 通过performance.timing.loadEventEnd获取。

重绘和重排次数

  • 定义: 页面渲染过程中发生的重绘和重排次数。
  • 量化: 使用浏览器开发者工具的Performance面板分析。

js 执行时间

  • 定义: JavaScript代码执行的总时间。
  • 量化: 使用浏览器开发者工具的Performance面板分析。

内存使用

  • 定义: 页面运行时的内存占用。
  • 量化: 使用浏览器开发者工具的Memory面板查看。

总阻塞时间

  • 定义: FCP到TTI之间主线程被阻塞的总时间。
  • 量化: 使用Lighthouse等工具测量。

网络请求数量

  • 定义: 页面加载过程中发起的HTTP请求总数。
  • 量化: 使用浏览器开发者工具的Network面板查看。

速度指数

  • 定义: 页面内容视觉填充的速度。
  • 量化: 通过Lighthouse等工具测量。

伪数组和数组的区别

  • 数组的原型是Array,伪数组的是一个普通对象
  • 伪数组:是arguments(函数参数对象)、nodeList(Dom集合)、或者手动({0:"a",1:"b",length:1})
  • 伪数组不能直接使用、map、push、foreach等方法,需要用Array.from转换或者用call/apply

token 能放到cookies上吗

  1. 需结合 Token 校验和加密机制
  2. 推荐将 Token 置于 HTTP 头(如 Authorization: Bearer <token>

XSS攻击

一种通过向网页注入恶意脚本(如JavaScript、HTML、CSS等)实现攻击的网络安全漏洞

防御XSS攻击的关键措施

  1. 输入过滤与转义

    • 对用户输入内容进行严格过滤,使用htmlspecialchars()htmlentities()等函数转义特殊字符(如<>&12
    • 示例:PHP中htmlspecialchars($input, ENT_QUOTES)可转义单双引号。
  2. 输出编码

    • 在将数据输出到页面时,根据上下文(HTML、JavaScript、CSS)选择合适的编码方式5
  3. 设置安全HTTP头部

    • 启用X-XSS-Protection头部强制浏览器启用XSS过滤3
    • 使用Content-Security-Policy (CSP)限制脚本来源5
  4. 使用HttpOnly Cookie

    • 为敏感Cookie设置HttpOnly属性,阻止JavaScript访问,降低信息泄露风险16
  5. 避免内联脚本与动态DOM操作

    • 减少使用eval()innerHTML等高风险API,优先通过文本节点操作DOM6

CSRF攻击是什么?

是一种利用用户在已登录状态下对目标网站的信任,通过诱导用户触发恶意请求的攻击方式。攻击者伪造用户的身份,执行非用户本意的敏感操作(如转账、修改密码等

预防

Token 验证SameSite Cookie 、双重认证 和 请求来源校验 等

与 XSS 攻击的区别

特性XSS 攻击CSRF 攻击
利用对象利用用户对网站的信任利用网站对用户浏览器的信任
攻击目标窃取用户数据或劫持会话伪造用户身份执行敏感操作
防御重点过滤用户输入与输出校验请求来源与身份

三栏布局的实现方案有哪些?

浮动布局、flex布局、绝对定位布局、grid布局grid-template-columns 定义三列,中间自适应、圣杯/双飞翼布局

变量提升

JavaScript 预编译阶段的特性,表现为变量和函数的声明会被隐式提升到当前作用域(全局或函数作用域)的顶端,但赋值操作仍保留在原位置

一个图片 url 访问后直接下载怎样实现?

HTML的download属性

<a>标签中添加download属性,浏览器会直接下载文件:

<a href="图片URL" download="自定义文件名.jpg">下载图片</a>

Blob对象下载

通过Fetch API获取文件流并生成Blob对象:

fetch(url)
  .then(res => res.blob()) 
  .then(blob => {
    const a = document.createElement('a'); 
    a.href  = URL.createObjectURL(blob); 
    a.download  = 'filename.jpg'; 
    a.click(); 
  });
  • 优点:支持大文件异步下载

服务器端配置方案

设置HTTP响应头
在服务器返回图片时添加Content-Disposition头:

BOM和BOM对象

BOM是浏览器对象模型,提供了与浏览器窗口交互的接口。常见对象包括window、location、history、navigator、screen等。

window对象

窗口控制

-   `window.open(url)` :打开新窗口或标签页[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
-   `window.close()` :关闭当前窗口(仅限通过脚本打开的窗口)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
-   `window.resizeTo(width, height)`:调整窗口大小[4](https://blog.csdn.net/weixin_33806509/article/details/93203542)

定时器

-   `setTimeout(func, delay)`:延迟执行一次函数[5](https://blog.csdn.net/lcwben/article/details/59060158)
-   `setInterval(func, interval)`:周期性执行函数[5](https://blog.csdn.net/lcwben/article/details/59060158)
-   `clearTimeout()`/`clearInterval()`:清除定时器[5](https://blog.csdn.net/lcwben/article/details/59060158)

窗口信息

-   `window.innerWidth`/`window.innerHeight` :获取视口尺寸(包含滚动条)[10](https://blog.csdn.net/qq_34771938/article/details/120353624)
-   `window.scrollY` :获取垂直滚动距离[10](https://blog.csdn.net/qq_34771938/article/details/120353624)`

Location 对象(操作 URL 相关)

URL 控制

-   `location.href` :获取或设置完整 URL(可用于跳转页面)[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
-   `location.reload(force)` :刷新页面(参数 `true` 强制从服务器加载)[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[10](https://blog.csdn.net/qq_34771938/article/details/120353624)
-   `location.replace(url)` :替换当前页面(不可后退)[7](https://blog.csdn.net/azhimei1545/article/details/101508707)[10](https://blog.csdn.net/qq_34771938/article/details/120353624)

URL 解析

-   `location.search` :获取 URL 查询参数(`?` 后的内容)[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[3](https://blog.csdn.net/weixin_43900284/article/details/113441680)
-   `location.hash` :获取锚点部分(`#` 后的内容)[3](https://blog.csdn.net/weixin_43900284/article/details/113441680)[7](https://blog.csdn.net/azhimei1545/article/details/101508707)

History 对象*(浏览器历史记录)

页面导航

-   `history.go(n)` :前进/后退指定页数(`1` 前进,`-1` 后退)[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
-   `history.back()` :后退一页[3](https://blog.csdn.net/weixin_43900284/article/details/113441680)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
-   `history.forward()` :前进一页[3](https://blog.csdn.net/weixin_43900284/article/details/113441680)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)

历史栈信息

-   `history.length` :获取历史记录中的页面数量[9](https://blog.csdn.net/cainiaoyihao_/article/details/117883001)

Navigator 对象(浏览器信息)

浏览器识别

-   `navigator.userAgent` :获取浏览器标识(用于检测浏览器类型)[5](https://blog.csdn.net/lcwben/article/details/59060158)[9](https://blog.csdn.net/cainiaoyihao_/article/details/117883001)
-   `navigator.platform` :获取操作系统信息[7](https://blog.csdn.net/azhimei1545/article/details/101508707)[9](https://blog.csdn.net/cainiaoyihao_/article/details/117883001)

功能检测

-   `navigator.cookieEnabled` :检测是否启用 Cookie[2](https://blog.csdn.net/weixin_50077864/article/details/127268410)[6](https://www.nowcoder.com/questionTerminal/e1c38e4b631f4054a663b6531690df7e?toCommentId=2794725)
-   `navigator.onLine` :检测网络连接状态[9](https://blog.csdn.net/cainiaoyihao_/article/details/117883001)

Screen 对象(屏幕信息)

  • screen.width/screen.height :获取屏幕分辨率49
  • screen.availWidth/screen.availHeight :获取可用屏幕尺寸(排除任务栏)910

html5 drag APi

dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发,。

darg:事件主体是被拖放元素,在正在拖放被拖放元素时触发。

dragenter:事件主体是目标元素,在被拖放元素进入某元素时触发。

dragover:事件主体是目标元素,在被拖放在某元素内移动时触发。

dragleave:事件主体是目标元素,在被拖放元素移出目标元素是触发。

drop:事件主体是目标元素,在目标元素完全接受被拖放元素时触发。

dragend:事件主体是被拖放元素,在整个拖放操作结束时触发

iframe 是什么?有什么缺点?

定义:iframe 元素会创建包含另一个文档的内联框架

提示:可以将提示文字放在之间,来提示某些不支持 iframe 的浏览器

缺点:

会阻塞主页面的 onload 事件

搜索引擎无法解读这种页面,不利于 SEO

iframe 和主页面共享连接池,而浏览器对相同区域有限制所以会影响性能。

一句话概括 RESTFUL

就是用 URL 定位资源,用 HTTP 描述操作。

click 在 ios 上有 300ms 延迟,原因及如何解决?

原因:

要是为了区分用户的单击操作和双击缩放操作

解决

1、使用FastClick库

2、 在HTML文档的<head>区域添加以下meta标签来禁用双击缩放功能:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

3、使用CSS属性touch-action: manipulation;。这个属性可以告诉浏览器某些触摸行为(如双击缩放)应该被禁用,从而减少或消除点击延迟。

Math对象

Math.round(x)

返回 `x` 四舍五入后的整数。

```
console.log(Math.round(4.7)); // 5
console.log(Math.round(4.3)); // 4
```

Math.ceil(x)

返回 `x` 向上取整后的整数。


```
console.log(Math.ceil(4.1)); // 5
console.log(Math.ceil(-4.1)); // -4
```

Math.floor(x)

返回 `x` 向下取整后的整数。


```
console.log(Math.floor(4.9)); // 4
console.log(Math.floor(-4.9)); // -5
```
  • Math.trunc(x)
    返回 x 的整数部分(去除小数部分)。

    console.log(Math.trunc(4.9)); // 4
    console.log(Math.trunc(-4.9)); // -4
    

Math.random()

返回一个 01 之间的随机数(包含 0,不包含 1)。

```
console.log(Math.random()); // 0.1234567890123456
```
如果只需要生成 1 到 10 的随机整数,可以简化代码:
const randomNumber = Math.floor(Math.random() * 10) + 1;
console.log(randomNumber); // 1 到 10 之间的随机整数
  • 原理

    • Math.random() * 10 生成 [0, 10) 之间的随机小数。
    • Math.floor() 向下取整,得到 [0, 9] 的整数。
    • 加上 1,将范围调整为 [1, 10]
生成指定范围的整数

使用 Math.random() 和 Math.floor()

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

const randomNumber = getRandomInt(1, 10);
console.log(randomNumber); // 1 到 10 之间的随机整数
  • 原理

    • Math.random() 生成一个 [0, 1) 之间的随机小数。
    • 乘以 (max - min + 1) 将范围扩展到 [0, max - min + 1)
    • 使用 Math.floor() 向下取整,得到 [0, max - min] 的整数。
    • 加上 min,将范围调整为 [min, max]

Math.abs(x)

返回 `x` 的绝对值。

```
console.log(Math.abs(-5)); // 5
```

价格精确度

//精确度 封装方法 相加  
export function addFun(num1, num2) {
  var len1 = 0;
  var len2 = 0;
  var m = 0;
  num1=Number(num1)
  num2=Number(num2)
  try {
    len1 = num1.toString().split(".")[1].length;
  } catch (e) {
    len1 = 0;
  }
  try {
    len2 = num2.toString().split(".")[1].length;
  } catch (e) {
    len2 = 0;
  }
  m = Math.pow(10, Math.max(len1, len2));
  var m1 = num1 * m;
  m1 = new Number(m1.toFixed(0));
  var m2 = num2 * m;
  m2 = new Number(m2.toFixed(0));
  return (m1 + m2) / m;

}

//精确度 封装方法 减
export function subtractFun(num1, num2) {
  var len1 = 0;
  var len2 = 0;
  var m = 0;
  num1=Number(num1)
  num2=Number(num2)
  try {
    len1 = num1.toString().split(".")[1].length;
  } catch (e) {
    len1 = 0;
  }
  try {
    len2 = num2.toString().split(".")[1].length;
  } catch (e) {
    len2 = 0;
  }
  m = Math.pow(10, Math.max(len1, len2));
  var m1 = num1 * m;
  m1 = new Number(m1.toFixed(0));
  var m2 = num2 * m;
  m2 = new Number(m2.toFixed(0));
  return (m1 - m2) / m;
}

//精确度 封装方法剩

export function multiplyFun(num1, num2) {
  var len1 = 0;
  var len2 = 0;
  var m = 0;
  num1=Number(num1)
  num2=Number(num2)
  try {
    len1 = num1.toString().split(".")[1].length;
  } catch (e) {
    len1 = 0;
  }
  try {
    len2 = num2.toString().split(".")[1].length;
  } catch (e) {
    len2 = 0;
  }
  m = Math.pow(10, Math.max(len1, len2) + 1);
  var m1 = num1 * m;
  m1 = new Number(m1.toFixed(0));
  var m2 = num2 * m;
  m2 = new Number(m2.toFixed(0));
  var m3 = m * m;
  var m4 = m1 * m2 / m3;
  return m4;

}

// //精确度 封装方法 除
export function divideFun(num1, num2) {
  var len1 = 0;
  var len2 = 0;
  var m = 0;
  num1=Number(num1)
  num2=Number(num2)
  try {
    len1 = num1.toString().split(".")[1].length;
  } catch (e) {
    len1 = 0;
  }
  try {
    len2 = num2.toString().split(".")[1].length;
  } catch (e) {
    len2 = 0;
  }
  m = Math.pow(10, Math.max(len1, len2) + 1);
  var m1 = num1 * m;
  m1 = new Number(m1.toFixed(0));
  var m2 = num2 * m;
  m2 = new Number(m2.toFixed(0));
  var m3 = m1 / m2;
  return m3;
}

Math.max

回一组数值中的最大值

Math.max(value1, value2, ..., valueN)

把价格转千分位

1. 使用 toLocaleString() 方法

最简单且原生支持的方法,直接调用数字的 toLocaleString() 方法:

const number = 1234567.89;
const formatted = number.toLocaleString(); // 输出:"1,234,567.89"(根据系统区域设置)

说明

  • 自动适配本地化格式(如小数点符号、千分位分隔符可能因系统区域不同而变化)。
  • 兼容性好,推荐优先使用。

2. 自定义正则表达式

手动处理整数部分,用正则表达式插入逗号:

function formatNumber(num) {
  // 处理小数部分(如果有)
  const parts = num.toString().split('.');
  const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d)/g, ',');
  const decimalPart = parts[1] ? '.' + parts[1] : '';
  return integerPart + decimalPart;
}

const number = 1234567.89;
const formatted = formatNumber(number); // 输出:"1,234,567.89"

说明

  • \B(?=(\d{3})+(?!\d)) 正则表达式匹配每三位数字的位置并插入逗号。
  • 完全控制格式,不受区域设置影响。

3. 处理字符串逆序插入逗号

手动从右到左遍历字符串,每三位插入逗号:

function formatNumber(num) {
  const str = num.toString().split('.');
  let integerPart = str[0];
  let result = '';
  let counter = 0;

  // 逆序遍历整数部分
  for (let i = integerPart.length - 1; i >= 0; i--) {
    counter++;
    result = integerPart[i] + result;
    if (counter % 3 === 0 && i !== 0) {
      result = ',' + result;
    }
  }

  // 处理小数部分
  const decimalPart = str[1] ? '.' + str[1] : '';
  return result + decimalPart;
}

const number = 1234567.89;
const formatted = formatNumber(number); // 输出:"1,234,567.89"

说明

  • 不依赖正则表达式,直接操作字符串。
  • 代码稍长,但逻辑清晰。

4. 使用 Intl.NumberFormat

更灵活的国际化 API,可指定区域和格式:

const number = 1234567.89;
const formatter = new Intl.NumberFormat('en-US'); // 指定为美式格式
const formatted = formatter.format(number); // 输出:"1,234,567.89"

说明

  • 支持更多格式选项(如货币符号、小数位数等)。
  • 语法:new Intl.NumberFormat(locales, options)

总结

方法优点缺点
toLocaleString()最简单,原生支持,兼容性好依赖系统区域设置
正则表达式完全控制格式,代码简洁需要理解正则逻辑
手动逆序处理不依赖正则,逻辑透明代码较长
Intl.NumberFormat国际化支持,高度可配置语法稍复杂

推荐使用场景

  • 快速实现:优先用 toLocaleString() 或 Intl.NumberFormat
  • 固定格式需求:选择正则表达式或手动逆序处理。

js常见时间方法

方法返回值说明示例(假设当前时间 2023-09-25 15:30:45
new Date()创建当前时间的 Date 对象2023-09-25T07:30:45.000Z
Date.now()当前时间的时间戳(毫秒,UTC)1695634245000
new Date().getTime()当前时间的时间戳(毫秒,UTC)1695634245000
new Date().valueOf() 或者 +new Date()当前时间的时间戳(毫秒,UTC)1695634245000
Date.parse(dateString)解析时间字符串返回时间戳(UTC)Date.parse("2023-09-25") → 1695600000000
new Date('2023-09-25').getTime()当前时间的时间戳(毫秒,UTC)1695634245000

获取时间各部分

方法返回值范围示例结果
getFullYear()年份(四位数)2023
getMonth()月份(0-11)8(9月)
getDate()日期(1-31)25
getDay()星期(0-6)1(周一)
getHours()小时(0-23)15
getMinutes()分钟(0-59)30
getSeconds()秒(0-59)45
getMilliseconds()毫秒(0-999)0

对象的遍历方式有哪些


总结

方法描述是否遍历继承属性是否遍历 Symbol 属性是否遍历不可枚举属性
for...in遍历对象自身的和继承的可枚举属性(不包括 Symbol 属性)
Object.keys()返回对象自身可枚举属性的键数组
Object.values()返回对象自身可枚举属性的值数组
Object.entries()返回对象自身可枚举属性的键值对数组
Object.getOwnPropertyNames()返回对象自身所有属性的键数组(包括不可枚举属性,不包括 Symbol 属性)
Object.getOwnPropertySymbols()返回对象自身所有 Symbol 属性的数组
Reflect.ownKeys()返回对象自身所有属性的键数组(包括 Symbol 属性和不可枚举属性)
for...of + Object.keys/values/entries结合 Object.keysObject.values 或 Object.entries 使用

根据具体需求选择合适的遍历方式,可以有效操作 JavaScript 对象。

手写瀑布流布局

6. 完整代码示例

以下是完整的 HTML、CSS 和 JavaScript 代码:

HTML
<div class="waterfall-container">
  <div class="waterfall-item">Item 1</div>
  <div class="waterfall-item">Item 2</div>
  <div class="waterfall-item">Item 3</div>
  <div class="waterfall-item">Item 4</div>
  <div class="waterfall-item">Item 5</div>
  <div class="waterfall-item">Item 6</div>
  <div class="waterfall-item">Item 7</div>
  <div class="waterfall-item">Item 8</div>
  <div class="waterfall-item">Item 9</div>
  <div class="waterfall-item">Item 10</div>
</div>
CSS
.waterfall-container {
  display: flex;
  justify-content: space-between;
  padding: 10px;
}

.waterfall-column {
  flex: 1;
  margin: 0 5px;
}

.waterfall-item {
  background-color: #f0f0f0;
  border: 1px solid #ccc;
  margin-bottom: 10px;
  padding: 10px;
  box-sizing: border-box;
}
JavaScript
function waterfallLayout(containerSelector, itemSelector, columns) {
  const container = document.querySelector(containerSelector);
  const items = document.querySelectorAll(itemSelector);

  container.innerHTML = '';

  const columnElements = [];
  for (let i = 0; i < columns; i++) {
    const column = document.createElement('div');
    column.className = 'waterfall-column';
    container.appendChild(column);
    columnElements.push(column);
  }

  items.forEach(item => {
    let minHeightColumn = columnElements[0];
    columnElements.forEach(column => {
      if (column.offsetHeight < minHeightColumn.offsetHeight) {
        minHeightColumn = column;
      }
    });
    minHeightColumn.appendChild(item.cloneNode(true));
  });
}

waterfallLayout('.waterfall-container', '.waterfall-item', 3);

window.addEventListener('scroll', () => {
  const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
  if (scrollTop + clientHeight >= scrollHeight - 10) {
    loadMoreItems();
  }
});

function loadMoreItems() {
  const container = document.querySelector('.waterfall-container');
  const newItems = [
    '<div class="waterfall-item">New Item 1</div>',
    '<div class="waterfall-item">New Item 2</div>',
    '<div class="waterfall-item">New Item 3</div>',
  ];

  newItems.forEach(item => {
    const div = document.createElement('div');
    div.innerHTML = item;
    container.appendChild(div.firstChild);
  });

  waterfallLayout('.waterfall-container', '.waterfall-item', 3);
}

手写poromise

class MyPromise {
  constructor(executor) {
    this.status = 'pending'; // 初始状态
    this.value = undefined; // 成功时的值
    this.reason = undefined; // 失败时的原因
    this.onFulfilledCallbacks = []; // 成功回调队列
    this.onRejectedCallbacks = []; // 失败回调队列

    // 定义 resolve 函数
    const resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'fulfilled'; // 状态改为成功
        this.value = value; // 保存成功值
        // 执行所有成功回调
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    // 定义 reject 函数
    const reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected'; // 状态改为失败
        this.reason = reason; // 保存失败原因
        // 执行所有失败回调
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    // 执行 executor
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error); // 如果 executor 抛出错误,直接 reject
    }
  }

  // 实现 then 方法
  then(onFulfilled, onRejected) {
    // 如果 onFulfilled 不是函数,则创建一个默认函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // 如果 onRejected 不是函数,则创建一个默认函数
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

    // 返回一个新的 Promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 如果当前状态是 fulfilled
      if (this.status === 'fulfilled') {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value); // 执行成功回调
            resolvePromise(promise2, x, resolve, reject); // 处理返回值
          } catch (error) {
            reject(error); // 如果回调抛出错误,直接 reject
          }
        }, 0);
      }

      // 如果当前状态是 rejected
      if (this.status === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason); // 执行失败回调
            resolvePromise(promise2, x, resolve, reject); // 处理返回值
          } catch (error) {
            reject(error); // 如果回调抛出错误,直接 reject
          }
        }, 0);
      }

      // 如果当前状态是 pending
      if (this.status === 'pending') {
        // 将回调函数加入队列
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value); // 执行成功回调
              resolvePromise(promise2, x, resolve, reject); // 处理返回值
            } catch (error) {
              reject(error); // 如果回调抛出错误,直接 reject
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason); // 执行失败回调
              resolvePromise(promise2, x, resolve, reject); // 处理返回值
            } catch (error) {
              reject(error); // 如果回调抛出错误,直接 reject
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
}

// 处理 then 返回值的函数
function resolvePromise(promise2, x, resolve, reject) {
  // 如果 promise2 和 x 是同一个对象,抛出 TypeError
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  // 如果 x 是一个 Promise
  if (x instanceof MyPromise) {
    x.then(resolve, reject);
  } else {
    // 否则直接 resolve
    resolve(x);
  }
}

cavans

getContext

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d'); // 获取 2D 绘图上下文

绘制矩形

  • 填充矩形fillRect(x, y, width, height)
  • 描边矩形strokeRect(x, y, width, height)
  • 清除矩形区域clearRect(x, y, width, height)
ctx.fillStyle = 'blue'; // 设置填充颜色
ctx.fillRect(50, 50, 100, 100); // 填充矩形

ctx.strokeStyle = 'red'; // 设置描边颜色
ctx.strokeRect(200, 50, 100, 100); // 描边矩形

ctx.clearRect(75, 75, 50, 50); // 清除矩形区域

 绘制路径

  • 开始路径beginPath()
  • 移动画笔moveTo(x, y)
  • 绘制线条lineTo(x, y)
  • 闭合路径closePath()
  • 描边路径stroke()
  • 填充路径fill()
// 开始路径
context.beginPath();
context.strokeStyle = 'blue';
context.moveTo(60, 20);
context.lineTo(220, 20);
context.stroke();
// 开始路径 again
context.beginPath();
context.strokeStyle = 'green';
context.moveTo(60, 20);
context.lineTo(160, 120);
context.stroke();

image.png

案例2:

// 开始路径
context.beginPath();
context.strokeStyle = 'blue';
context.moveTo(60, 20);
context.lineTo(220, 20);
context.stroke();

context.strokeStyle = 'green';
context.moveTo(60, 20);
context.lineTo(160, 120);
context.stroke();

image.png

后面只声明了一次beginPath(),结果两条折线全部的颜色都是绿色;而两次beginPath()则是一条蓝色,一条绿色。

绘制文本

  • 填充文本fillText(text, x, y)
  • 描边文本strokeText(text, x, y)
  • 设置字体font
  • 设置文本对齐textAlign 和 textBaseline
ctx.font = '30px Arial'; // 设置字体
ctx.fillStyle = 'black';
ctx.fillText('Hello, Canvas!', 50, 250); // 填充文本

ctx.strokeStyle = 'purple';
ctx.strokeText('Hello, Canvas!', 50, 300); // 描边文本

绘制图像

  • 绘制图像drawImage(image, x, y, width, height)

    • image 可以是 <img><canvas> 或 <video> 元素。
const img = new Image();
img.src = 'image.png';
img.onload = () => {
  ctx.drawImage(img, 300, 50, 100, 100); // 绘制图像
};

 样式和颜色

  • 填充颜色fillStyle
  • 描边颜色strokeStyle
  • 线条宽度lineWidth
  • 线条端点样式lineCapbuttroundsquare
  • 线条连接样式lineJoinmiterroundbevel
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; // 半透明红色
ctx.strokeStyle = '#00ff00'; // 绿色
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';

JavaScript 对象属性键的隐式转换面试题解析

问题代码分析

var a = {}, 
    b = {key: 'b'}, 
    c = {key: 'c'};

a[b] = 123;
a[c] = 456;

console.log(a[b]); // 打印什么?

关键知识点

  1. JavaScript 对象属性键的转换规则

    • 当使用对象作为属性键时,JavaScript 会隐式调用该对象的 toString() 方法
    • 默认的对象 toString() 返回 "[object Object]"
  2. 代码执行过程

    • a[b] = 123 → 实际执行 a["[object Object]"] = 123
    • a[c] = 456 → 实际执行 a["[object Object]"] = 456
    • 两次操作实际上是在修改同一个属性
  3. 最终结果

    • a[b] 访问的是 a["[object Object]"]
    • 该属性的值已被第二次赋值覆盖为 456

逐步验证

console.log(b.toString()); // "[object Object]"
console.log(c.toString()); // "[object Object]"

console.log(a); // { "[object Object]": 456 }

最终答案

console.log(a[b]) 会打印 456

扩展思考

如果想用对象作为唯一的键,可以使用:

  1. Map 数据结构
const map = new Map();
map.set(b, 123);
map.set(c, 456);
console.log(map.get(b)); // 123
  1. Symbol 唯一标识
const a = {};
const b = { key: 'b', id: Symbol() };
const c = { key: 'c', id: Symbol() };
a[b.id] = 123;
a[c.id] = 456;
console.log(a[b.id]); // 123

实现 Promise A、B、C 按顺序执行的方法

在 JavaScript 中,有几种方式可以确保 Promise A、B、C 按顺序执行:

1. 使用 Promise.then() 链式调用

function A() {
  return new Promise(resolve => {
    console.log('A');
    resolve();
  });
}

function B() {
  return new Promise(resolve => {
    console.log('B');
    resolve();
  });
}

function C() {
  return new Promise(resolve => {
    console.log('C');
    resolve();
  });
}

// 顺序执行
A()
  .then(() => B())
  .then(() => C())
  .catch(err => console.error(err));

2. 使用 async/await

async function executeInOrder() {
  try {
    await A();
    await B();
    await C();
  } catch (err) {
    console.error(err);
  }
}

executeInOrder();

3. 处理有返回值的顺序执行

function A() {
  return new Promise(resolve => {
    console.log('A');
    resolve('result from A');
  });
}

function B(input) {
  return new Promise(resolve => {
    console.log('B received:', input);
    resolve('result from B');
  });
}

function C(input) {
  return new Promise(resolve => {
    console.log('C received:', input);
    resolve('final result');
  });
}

// 传递结果的顺序执行
A()
  .then(resultA => B(resultA))
  .then(resultB => C(resultB))
  .then(finalResult => {
    console.log('Final:', finalResult);
  })
  .catch(err => console.error(err));

// 或者使用 async/await
async function executeWithData() {
  try {
    const resA = await A();
    const resB = await B(resA);
    const resC = await C(resB);
    console.log('Final:', resC);
  } catch (err) {
    console.error(err);
  }
}

executeWithData();

4. 动态 Promise 数组顺序执行

const tasks = [A, B, C];

// 使用 reduce 实现顺序执行
tasks.reduce((chain, task) => {
  return chain.then(() => task());
}, Promise.resolve());

// 或者使用 for...of 和 async/await
async function executeTasks() {
  for (const task of tasks) {
    await task();
  }
}
executeTasks();

关键点说明

  1. 顺序保证:以上方法都能确保 A → B → C 的执行顺序
  2. 错误处理:使用 .catch()try/catch 捕获错误
  3. 结果传递:可以通过 then 链或 async/await 传递上一步的结果
  4. 性能考虑
    • 顺序执行会降低并发性能
    • 只有在有依赖关系时才需要顺序执行
    • 无依赖的 Promise 可以用 Promise.all() 并行执行

选择哪种方式取决于具体场景和个人偏好,async/await 通常代码更清晰易读。

Set 和 Map 的详细区别:

1. 基本概念区别

Set - 值集合

// Set:存储唯一值的集合
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // 重复值,不会被添加
set.add('hello');

console.log(set); // Set(3) { 1, 2, 'hello' }

Map - 键值对集合

// Map:存储键值对的集合
const map = new Map();
map.set('name', '张三');
map.set('age', 25);
map.set('age', 26); // 更新已存在的键

console.log(map); // Map(2) { 'name' => '张三', 'age' => 26 }

2. 数据结构对比

特性 Set Map 存储内容 唯一值的集合 键值对的集合 元素形式 单个值 键值对 [key, value] 重复处理 自动去重 键唯一,值可重复 主要用途 去重、成员检查 键值映射、字典

3. API 方法对比

添加元素

// Set - 使用 add()
const set = new Set();
set.add('apple');
set.add('banana');
set.add('apple'); // 重复,不会被添加

// Map - 使用 set()
const map = new Map();
map.set('fruit', 'apple');
map.set('color', 'red');
map.set('fruit', 'orange'); // 更新键 'fruit' 的值

获取元素

// Set - 没有直接的获取方法,只能检查存在性
const set = new Set(['a', 'b', 'c']);
console.log(set.has('a')); // true

// Map - 使用 get() 获取值
const map = new Map([['name', '张三'], ['age', 25]]);
console.log(map.get('name')); // "张三"
console.log(map.get('gender')); // undefined

共同的方法

const set = new Set([1, 2, 3]);
const map = new Map([['a', 1], ['b', 2]]);

// 共同的方法
console.log(set.size); // 3
console.log(map.size); // 2

console.log(set.has(2)); // true
console.log(map.has('a')); // true

set.delete(2);
map.delete('a');

set.clear();
map.clear();

4. 迭代方式对比

Set 的迭代

const set = new Set(['apple', 'banana', 'orange']);

// 直接迭代值
for (const value of set) {
  console.log(value); // 'apple', 'banana', 'orange'
}

// 使用 values()(与直接迭代相同)
for (const value of set.values()) {
  console.log(value);
}

// 使用 forEach
set.forEach(value => {
  console.log(value);
});

// Set 的 keys() 和 values() 返回相同的迭代器
console.log(set.keys() === set.values()); // true

Map 的迭代

const map = new Map([
  ['name', '张三'],
  ['age', 25],
  ['city', '北京']
]);

// 迭代键值对
for (const [key, value] of map) {
  console.log(key, value); // 'name' '张三', 'age' 25, 'city' '北京'
}

// 分别迭代键和值
for (const key of map.keys()) {
  console.log(key); // 'name', 'age', 'city'
}

for (const value of map.values()) {
  console.log(value); // '张三', 25, '北京'
}

// 使用 entries()(与直接迭代相同)
for (const [key, value] of map.entries()) {
  console.log(key, value);
}

// 使用 forEach
map.forEach((value, key) => {
  console.log(key, value);
});

5. 实际应用场景对比

Set 的应用场景

场景1:数组去重
// 数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

// 字符串去重
const str = 'aabbccddee';
const uniqueChars = [...new Set(str)].join('');
console.log(uniqueChars); // 'abcde'
场景2:成员资格检查
// 权限检查
const adminPermissions = new Set(['create', 'update', 'delete', 'read']);

function checkPermission(permission) {
  return adminPermissions.has(permission);
}

console.log(checkPermission('create')); // true
console.log(checkPermission('approve')); // false

// 标签系统
const tags = new Set(['javascript', 'web', 'frontend']);

function addTag(tag) {
  if (tags.has(tag)) {
    console.log(`标签 "${tag}" 已存在`);
    return false;
  }
  tags.add(tag);
  return true;
}
场景3:数学集合操作
class SetOperations {
  // 并集
  static union(setA, setB) {
    return new Set([...setA, ...setB]);
  }

  // 交集
  static intersection(setA, setB) {
    return new Set([...setA].filter(x => setB.has(x)));
  }

  // 差集
  static difference(setA, setB) {
    return new Set([...setA].filter(x => !setB.has(x)));
  }

  // 对称差集
  static symmetricDifference(setA, setB) {
    return new Set([
      ...this.difference(setA, setB),
      ...this.difference(setB, setA)
    ]);
  }

  // 子集检查
  static isSubset(setA, setB) {
    return [...setA].every(x => setB.has(x));
  }
}

// 使用示例
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);

console.log(SetOperations.union(setA, setB)); // Set(4) {1, 2, 3, 4}
console.log(SetOperations.intersection(setA, setB)); // Set(2) {2, 3}
console.log(SetOperations.difference(setA, setB)); // Set(1) {1}

Map 的应用场景

场景1:对象映射
// 用户信息映射
const userMap = new Map();

userMap.set(1, { name: '张三', age: 25 });
userMap.set(2, { name: '李四', age: 30 });
userMap.set(3, { name: '王五', age: 28 });

// 快速查找
function getUserInfo(userId) {
  return userMap.get(userId) || { name: '未知用户', age: 0 };
}

console.log(getUserInfo(2)); // { name: '李四', age: 30 }

// 统计词频
function wordFrequency(text) {
  const frequencyMap = new Map();
  const words = text.toLowerCase().split(/\W+/);
  
  for (const word of words) {
    if (word) {
      frequencyMap.set(word, (frequencyMap.get(word) || 0) + 1);
    }
  }
  
  return frequencyMap;
}

const text = "hello world hello javascript world";
console.log(wordFrequency(text));
// Map(3) { 'hello' => 2, 'world' => 2, 'javascript' => 1 }
场景2:缓存系统
class SimpleCache {
  constructor(maxSize = 100) {
    this.cache = new Map();
    this.maxSize = maxSize;
  }

  set(key, value) {
    // 如果超过最大大小,移除第一个元素(FIFO)
    if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }

  get(key) {
    return this.cache.get(key);
  }

  has(key) {
    return this.cache.has(key);
  }

  delete(key) {
    return this.cache.delete(key);
  }

  clear() {
    this.cache.clear();
  }

  size() {
    return this.cache.size;
  }
}

// 使用示例
const cache = new SimpleCache(3);
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
cache.set('d', 4); // 这会移除 'a'

console.log(cache.get('a')); // undefined
console.log(cache.get('d')); // 4
场景3:配置管理
class ConfigManager {
  constructor() {
    this.configMap = new Map();
    this.defaults = new Map();
  }

  setConfig(key, value, isDefault = false) {
    this.configMap.set(key, value);
    if (isDefault) {
      this.defaults.set(key, value);
    }
  }

  getConfig(key) {
    return this.configMap.get(key) ?? this.defaults.get(key);
  }

  resetToDefault(key) {
    if (this.defaults.has(key)) {
      this.configMap.set(key, this.defaults.get(key));
    }
  }

  getAllConfigs() {
    return Object.fromEntries(this.configMap);
  }

  // 批量设置配置
  setConfigs(configObject) {
    for (const [key, value] of Object.entries(configObject)) {
      this.setConfig(key, value);
    }
  }
}

// 使用示例
const config = new ConfigManager();

// 设置默认值
config.setConfig('theme', 'light', true);
config.setConfig('language', 'zh-CN', true);

// 设置用户配置
config.setConfig('theme', 'dark');
config.setConfig('fontSize', 14);

console.log(config.getConfig('theme')); // 'dark'
console.log(config.getConfig('language')); // 'zh-CN'

config.resetToDefault('theme');
console.log(config.getConfig('theme')); // 'light'

6. 性能特点对比

查找性能

const array = Array.from({ length: 10000 }, (_, i) => i);
const set = new Set(array);
const map = new Map(array.map((x, i) => [i, x]));

const target = 5000;

// Array 查找
console.time('Array find');
array.includes(target);
console.timeEnd('Array find');

// Set 查找
console.time('Set has');
set.has(target);
console.timeEnd('Set has');

// Map 查找
console.time('Map get');
map.get(target);
console.timeEnd('Map get');

内存使用

// Set 和 Map 在存储大量数据时比 Object 更高效
const largeArray = Array.from({ length: 100000 }, (_, i) => i);

const obj = {};
largeArray.forEach(x => obj[x] = x);

const set = new Set(largeArray);
const map = new Map(largeArray.map(x => [x, x]));

// 在实际使用中,Set 和 Map 的内存效率通常更高

7. 转换和互操作

与数组的转换

// Set 转数组
const set = new Set([1, 2, 3]);
const setToArray = [...set]; // [1, 2, 3]

// Map 转数组
const map = new Map([['a', 1], ['b', 2]]);
const mapToArray = [...map]; // [['a', 1], ['b', 2]]

// 数组转 Set
const arrayToSet = new Set([1, 2, 2, 3]); // Set(3) {1, 2, 3}

// 数组转 Map
const arrayToMap = new Map([['x', 10], ['y', 20]]);

Set 和 Map 之间的转换

// Map 的键转为 Set
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
const keysSet = new Set(map.keys()); // Set(3) {'a', 'b', 'c'}

// Set 转为 Map(需要提供值)
const set = new Set(['x', 'y', 'z']);
const setToMap = new Map([...set].map(key => [key, true])); 
// Map(3) {'x' => true, 'y' => true, 'z' => true}

8. 弱引用版本:WeakSet 和 WeakMap

WeakSet

// WeakSet 只能存储对象,且是弱引用
const weakSet = new WeakSet();
const obj1 = { id: 1 };
const obj2 = { id: 2 };

weakSet.add(obj1);
weakSet.add(obj2);

console.log(weakSet.has(obj1)); // true

// 当对象被垃圾回收时,会自动从 WeakSet 中移除

WeakMap

// WeakMap 的键必须是对象,且是弱引用
const weakMap = new WeakMap();
const keyObj = {};

weakMap.set(keyObj, 'private data');

console.log(weakMap.get(keyObj)); // 'private data'

// 常用于存储私有数据

总结

方面 Set Map 存储内容 唯一值 键值对 主要用途 去重、集合运算 映射、字典、缓存 查找方式 has(value) get(key) 添加元素 add(value) set(key, value) 重复处理 值唯一 键唯一 迭代内容 值 键值对 典型场景 标签、权限、去重 配置、缓存、映射关系

选择原则:

· 需要存储唯一值的集合 → 使用 Set · 需要键值映射关系 → 使用 Map · 需要高性能查找 → 两者都适合,比 Array 和 Object 更高效 · 需要弱引用 → 使用 WeakSet 或 WeakMap

处理并发请求 Axios

用promise.all

普通对象使用 for of 遍历

方案1:直接为对象定义迭代器

javascript
 体验AI代码助手
 代码解读
复制代码
const obj = { a: 1, b: 2 };
 
obj[Symbol.iterator] = function* () {
  for (const key of Object.keys(this)) {
    yield [key, this[key]]; // 返回键值对数组 
  }
};
 
// 遍历示例 
for (const [key, value] of obj) {
  console.log(key, value); // 输出:a 1 → b 2 
}

方案2:用生成器封装函数

    javascript
     体验AI代码助手
     代码解读
    复制代码
    function* objectEntries(obj) {
      for (const key of Object.keys(obj)) {
        yield { key, value: obj[key] }; // 返回结构化对象 
      }
    }
     
    const obj = { x: 10, y: 20 };
    for (const entry of objectEntries(obj)) {
      console.log(entry); // 输出:{ key: 'x', value: 10 }, { key: 'y', value: 20 }
    }

箭头函数

let obj1 = {
    name: 'obj1_name',
    print: function() {
        return () => console.log(this.name);
    },
    say: () => {
        return () => console.log(this.name);
    }
};

let obj2 = { name: 'obj2_name' };

1. 分析 obj1.print 和 obj1.say 的区别

  • print:普通函数,所以它内部的 this 取决于调用方式。
  • say:箭头函数,定义时就绑定了外层作用域的 this,在全局环境下定义时 this 通常是 window(浏览器)或 global(Node),严格模式下是 undefined

情况 1:obj1.print()()

  1. obj1.print() 执行 print 函数,此时 print 是普通函数且由 obj1 调用,所以 this = obj1
  2. 它返回一个箭头函数 () => console.log(this.name),其中 this 是定义时所在作用域的 this,即 print 函数执行时的 thisobj1)。
  3. 执行这个返回的箭头函数,this.name 就是 obj1.name
  4. 输出:obj1_name

情况 2:obj1.print().call(obj2)

  1. obj1.print() 返回箭头函数,其中 this 已绑定为 obj1(理由同上)。
  2. 对这个箭头函数使用 .call(obj2) 不会改变其 this(箭头函数的 this 不可更改)。
  3. 所以仍然输出 obj1_name

情况 3:obj1.print.call(obj2)()

  1. obj1.print.call(obj2) 把 print 函数的 this 改为 obj2,然后执行 print
  2. print 执行时 this = obj2,返回的箭头函数捕获此时的 thisobj2)。
  3. 调用这个返回的箭头函数,输出 obj2_name(不是 obj1_name)。

注意:原解析说“同输出结果”,实际上应输出 obj2_name,因为这里改变了 print 的 this,箭头函数捕获了新的 this


情况 4:obj1.say()()

  1. say 是箭头函数,定义时 this 是外层作用域的 this(假设这里是全局,严格模式下是 undefined)。
  2. obj1.say() 执行 say,返回一个新的箭头函数,新箭头函数的 this 也是 say 的 this(即全局 this)。
  3. name 属性在全局对象上可能是 undefined(或空字符串,取决于环境)。
  4. 输出:undefined(在浏览器非严格模式可能输出 '' 或 undefined 看 window.name)。

情况 5:obj1.say().call(obj2)

  1. obj1.say() 返回一个箭头函数,其 this 继承自 say 的 this(全局/undefined)。
  2. 对箭头函数使用 .call(obj2) 无效,this 不变。
  3. 输出与情况 4 相同:undefined

情况 6:obj1.say.call(obj2)()

  1. obj1.say.call(obj2) 试图改变 say 箭头函数的 this,但箭头函数 this 不可更改,所以 say 的 this 还是原来的(全局/undefined)。
  2. say 返回的箭头函数 this 也还是全局/undefined。
  3. 执行这个返回的函数,输出 undefined

总结输出

javascript

obj1.print()()         // obj1_name
obj1.print().call(obj2) // obj1_name
obj1.print.call(obj2)() // obj2_name(原解析错误,应为 obj2_name)

obj1.say()()           // undefined
obj1.say().call(obj2)  // undefined
obj1.say.call(obj2)()  // undefined