Skip to content

JavaScript

数据类型

  • 字符串 (String):表示文本。
  • 数字 (Number):表示整数或浮点数。
  • 布尔值 (Boolean):表示真或假。
  • null:表示一个空值。
  • undefined:表示一个未定义的值。
  • 对象 (Object):表示一组键值对。
  • 数组 (Array):表示一组有序的值。
  • 函数 (Function):表示一段可执行的代码。
  • 符号 (Symbol):表示一个唯一的值。
  • 大整数 (BigInt):表示一个超过安全整数范围的整数。

垃圾回收机制

全局变量在页面关闭时被销毁,局部变量在函数执行完毕被销毁,由于闭包形式内部函数不算执行完毕,因为内部函数还保持着对其外部函数变量的引用,所以这个变量会一直保存在内存之中。

深拷贝和浅拷贝

浅拷贝

浅拷贝只复制对象的第一层属性,而不会复制嵌套的对象或数组。这意味着如果嵌套的对象或数组发生改变,浅拷贝的对象也会受到影响。

javascript
const obj1 = {
  name: "John",
  address: {
    street: "123 Main Street",
    city: "Anytown",
    state: "CA",
    zip: "12345",
  },
};

const obj2 = Object.assign({}, obj1);

obj2.address.street = "456 Elm Street";

console.log(obj1); // { name: 'John', address: { street: '456 Elm Street', city: 'Anytown', state: 'CA', zip: '12345' } }
console.log(obj2); // { name: 'John', address: { street: '456 Elm Street', city: 'Anytown', state: 'CA', zip: '12345' } }

深拷贝

深拷贝会递归地复制对象的所有属性,包括嵌套的对象或数组。这意味着如果嵌套的对象或数组发生改变,深拷贝的对象不会受到影响。

javascript
const obj1 = {
  name: "John",
  address: {
    street: "123 Main Street",
    city: "Anytown",
    state: "CA",
    zip: "12345",
  },
};

const obj2 = JSON.parse(JSON.stringify(obj1));

obj2.address.street = "456 Elm Street";

console.log(obj1); // { name: 'John', address: { street: '123 Main Street', city: 'Anytown', state: 'CA', zip: '12345' } }
console.log(obj2); // { name: 'John', address: { street: '456 Elm Street', city: 'Anytown', state: 'CA', zip: '12345' } }

何时使用浅拷贝和深拷贝

  • 浅拷贝通常用于复制简单的数据结构,如字符串、数字和布尔值。
  • 深拷贝通常用于复制复杂的数据结构,如对象和数组。

注意:

  • 浅拷贝和深拷贝都是复制对象,但它们复制的方式不同。
  • 浅拷贝只复制对象的第一层属性,而深拷贝会递归地复制对象的所有属性。
  • 浅拷贝通常比深拷贝更快,因为浅拷贝只需要复制对象的第一层属性,而深拷贝需要递归地复制对象的所有属性。

宿主对象和原生对象的区别

  • 宿主对象:由宿主环境(如浏览器)提供的对象。例如,在浏览器中,宿主对象包括 window、document、navigator 等。
  • 原生对象:由 JavaScript 语言本身提供的对象。例如,Array、Object、Function 等。

数组去重

方法一:使用 Set

javascript
const arr = [1, 2, 3, 4, 4, 5, 5, 6, 6, 6];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3, 4, 5, 6]

方法二:使用 filter

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

方法三:使用双层循环

javascript
const arr = [1, 2, 3, 4, 5, 1, 2, 3];

const uniqueArr = [];
for (let i = 0; i < arr.length; i++) {
  let isUnique = true;
  for (let j = 0; j < uniqueArr.length; j++) {
    if (arr[i] === uniqueArr[j]) {
      isUnique = false;
      break;
    }
  }
  if (isUnique) {
    uniqueArr.push(arr[i]);
  }
}

console.log(uniqueArr); // [1, 2, 3, 4, 5]

防抖和节流

防抖:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。

节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次,只有一次生效。

原型与原型链

原型

每个函数都有一个原型对象,这个原型对象包含了该函数的所有属性和方法。 当创建一个新对象时,该对象会继承其构造函数的原型对象。 因此,所有对象都能够访问其构造函数的原型对象上的属性和方法。

原型链

原型链是一个对象到其原型对象的链接列表。 当一个对象访问一个不存在于自身上的属性或方法时,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法。 如果在原型链上找不到该属性或方法,则会返回 undefined。

如何理解闭包

闭包是指在 JavaScript 中,函数可以访问其外部作用域的变量,即使该函数已经离开其作用域。

执行上下文

在 JavaScript 中,执行上下文(Execution Context)是一个非常重要的概念,它是当前 JavaScript 代码被评估和执行的环境。每当 JavaScript 代码运行时,它都是在执行上下文中运行的。

一个执行上下文的生命周期可以分为两个阶段:

  1. 创建阶段:在这个阶段,JavaScript 引擎首先会创建一个变量对象(Variable Object),其中包含了函数的所有参数(arguments)、内部的所有函数声明(Function Declarations)以及变量声明(Variable Declarations)。然后确定 this 的指向。

  2. 执行阶段:在这个阶段,JavaScript 引擎开始执行代码,解释/运行代码,这时候会完成剩余的函数定义,进行变量赋值等操作。

根据创建方式和执行环境的不同,执行上下文大致可以分为三种类型:

  1. 全局执行上下文:这是默认的、最基本的执行上下文。不在任何函数内部的代码都在全局执行上下文中。它做两件事:创建一个全局对象,在浏览器中这个全局对象是window,并且设置this为这个全局对象。

  2. 函数执行上下文:每当一个函数被调用时,都会为该函数创建一个新的函数执行上下文。每个函数都有自己的执行上下文,但是只有当函数被调用时才会被创建。

  3. Eval 函数执行上下文:执行在eval函数内部的代码也有自己的执行上下文。

值得注意的是,这些执行上下文会形成一个执行栈(Execution Stack),也称为调用栈(Call Stack)。栈底始终是全局执行上下文,而栈顶则是当前(活动的)执行上下文。

函数柯里化

函数柯里化是指将一个函数分解成一系列更小的函数,每个函数都接受一个参数并返回一个新的函数,直到最后一个函数接受最后一个参数并返回最终结果。

在 JavaScript 中,我们可以利用闭包的特性来实现函数柯里化。闭包是一种可以访问其自身作用域、外部函数作用域和全局作用域的函数。这使得闭包可以“记住”环境中的值,即使在其父函数已经完成执行之后。

下面是一个简单的柯里化函数的例子:

javascript
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function (...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

// 使用方式
function sum(a, b, c) {
  return a + b + c;
}

let curriedSum = curry(sum);

console.log(curriedSum(1)(2)(3)); // 6,完全柯里化
console.log(curriedSum(1, 2)(3)); // 6,部分柯里化

在上述代码中,curry 函数接受一个函数 fn 作为参数。然后,它返回一个新的函数 curriedcurried 函数会检查传入的参数数量是否足够。如果足够,它就会立即执行 fn 函数。如果参数不足,它就会返回一个新的函数,等待接收更多的参数。

apply,call,bind 的异同

apply, call, 和 bind 都是 JavaScript 中的函数方法,它们都可以用来改变函数的 this 上下文。但是,这三个方法在使用和结果上有一些不同:

  1. apply:该方法接受两个参数,第一个是函数应该使用的 this 值,第二个是一个数组或类数组对象,其中的数组元素将作为单独的参数传给函数。
javascript
func.apply(thisArg, [argsArray]);
  1. call:该方法的工作方式与 apply 类似,但是它接受一个参数列表,而不是一个参数数组。
javascript
func.call(thisArg, arg1, arg2, ...)
  1. bind:该方法创建一个新的函数,当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
javascript
newFunc = func.bind(thisArg[, arg1[, arg2[, ...]]])

总结一下,applycall 都是立即调用函数,而 bind 是返回一个新的函数,等待调用。applycall 的区别在于参数的处理方式,apply 接受一个参数数组,而 call 接受一系列的参数列表。

获取 dom 元素的几种方式

  1. getElementById:通过元素的 id 获取,如果存在则返回这个元素,不存在则返回 null。特点是如果文档中存在多个相同的 id,则获取第一个,在一些低版本浏览器会把元素的 name 当做 id 获取,上下文只能是 document。
  2. getElementsByClassName:通过元素的类名获取,存在则返回由目标元素组成的 HTMLCollectio,不存在则返回一个空的 HTMLCollectio。特点是获取的结果是一个类数组,上下文可以是任意元素(document 或其它 dom 元素),在一些低版本浏览器不支持。
  3. querySelector:通过选择器获取一个元素,上下文可以是任意元素(document 或其它 dom 元素),低版本浏览器不兼容,返回值是类数组。
  4. querySelectorAll:通过选择器获取一组元素,同 querySelector。
  5. getElementsByTagName:通过标签名获取,上下文可以是任意元素(document 或其它 dom 元素),返回值是类数组,参数是标签名,不区分大小写。
  6. getElementsByName:通过元素的 name 属性获取,上下文只能是 document,返回值是类数组,在 IE 浏览器中只能获取到表单元素,当然我们一般也只用它获取表单元素,从 ie10 开始可以不只是表单元素。
  7. document.body:获取 body 这个元素。
  8. document.documentElement:获取 html 这个元素。
  9. getElementsByTagNameNS:返回带有指定名称和命名空间的所有元素的 NodeList。

总结:

  • 上下文只能是 document 的方法:getElementById、getElementsByName。
  • 上下文为任意元素的方法:getElementsByClassName、getElementById、querySelectorAll、getElementsByTagName、getElementsByTagNameNS。
  • 查询效率最高的是 getElementById。
  • 只有 getElementById 与 getElementById 返回对象本身,其余查询方法均返回一个类数组 HTMLCollectio。

类数组与 arguments

类数组

  • 类数组对象是具有数字键的 JavaScript 对象。
  • 它们不是真正的数组,但它们的行为非常相似。
  • 它们可以存储任何类型的值,并且可以像数组一样访问和操作它们。
  • 类数组对象的一个常见示例是 arguments 对象。

arguments

  • arguments 对象是类数组对象,它包含传递给函数的所有参数。
  • 它是一个伪数组,这意味着它没有内置的数组方法,如 push()pop()
  • 但是,你可以使用 Array.prototype 方法来操作它,例如 Array.prototype.slice()Array.prototype.forEach()

类数组与 arguments 的比较

特征类数组arguments
类型JavaScript 对象类数组对象
数字键数字键
任何类型的值传递给函数的所有参数
方法没有内置的数组方法可以使用 Array.prototype 方法
用例存储任何类型的值,并像数组一样访问和操作它们访问和操作传递给函数的所有参数

Symbol

Symbol 的主要特点

  • Symbol 值是不可变的,并且不能被转换成其他数据类型。
  • Symbol 值是唯一的,并且不能被其他 Symbol 值相等。
  • Symbol 值可以作为对象的属性名,也可以作为函数的参数或返回值。
  • Symbol 值非常适合用于创建私有属性或方法。
  • Symbol 值还可以用于创建枚举类型。

Symbol 的常见用法

  • 创建私有属性或方法。
  • 创建枚举类型。
  • 创建迭代器、生成器和代理对象。
  • 作为函数的参数或返回值。

ajax/axios/fetch 区别

Ajax、JQuery Ajax、Axios 和 Fetch 都是在 JavaScript 中处理异步 HTTP 请求的不同方法。

Ajax 是原始的 JavaScript 技术,核心使用 XMLHttpRequest 对象。如果多个请求之间有先后关系,可能会出现"回调地狱"。这是因为 Ajax 本身是针对 MVC(Model-View-Controller)的编程,这并不符合现在前端 MVVM(Model-View-ViewModel)的趋势。此外,XMLHttpRequest 的架构不清晰。

JQuery Ajax 是对原生 XMLHttpRequest 的封装。但其缺点在于,如果你只想使用 Ajax 功能,引入整个 JQuery 库显得不太合理。此外,它的配置和调用方式可能会显得有些混乱,而且基于事件的异步模型可能不够友好。

Axios 也是对原生 XMLHttpRequest 的封装,但它实现了 Promise,符合最新的 ES 规范。它支持从浏览器中创建 XMLHttpRequest,支持 Promise API,提供了一些并发请求的接口,并且可以从 Node.js 创建 HTTP 请求。但是,Axios 也有其自身的问题,例如错误处理可能会比较复杂。

Fetch 是一种新的用于网络请求的 API,是 XMLHttpRequest 的替代方案。Fetch API 的优点在于其语法简洁,更加语义化,基于标准 Promise 实现,支持 async/await,提供更丰富的 API。但是,Fetch 是一个较低级别的 API,可能需要进行一些额外的封装才能更好地使用。而且,Fetch API 不会默认接收或发送 cookies,如果需要,需要手动设置。

require/import 之间的区别

  • require 是 CommonJS 语法,import 是 ES6 语法
  • require 只在后端服务器支持,import 在高版本浏览器及 Node 中都可以支持
  • require 引入的是原始导出值的复制,import 则是导出值的引用
  • require 是运行时动态加载,import 是静态编译
  • require 调用时默认不是严格模式,import 则默认调用严格模式

map 与 forEach 的区别

  • forEach 方法,是最基本的方法,就是遍历与循环,默认有 3 个传参:分别是遍历的数组内容 item、数组索引 index、和当前遍历数组 Array
  • map 方法,基本用法与 forEach 一致,但是不同的,它会返回一个新的数组,所以在 callback 需要有 return 值,如果没有,会返回 undefined

for in 和 for of 区别

for...infor...of是 JavaScript 中的两种不同的循环结构,它们的主要区别在于它们迭代的内容。

  1. for...in循环: for...in用于遍历对象的可枚举属性,包括其原型链上的属性。

  2. for...of循环: for...of用于遍历可迭代对象(如 Array, Map, Set, String, TypedArray, arguments 对象等)的元素。

这是一个简单的示例来说明两者的区别:

javascript
let array = [3, 5, 7];
array.foo = "hello";

for (let i in array) {
  console.log(i); // 输出 "0", "1", "2", "foo"
}

for (let i of array) {
  console.log(i); // 输出 "3", "5", "7"
}

在这个例子中,for...in遍历了数组的所有可枚举属性,包括索引和添加的foo属性。而for...of只遍历了数组的元素。

forEach怎么跳出循环

1. 使用 return

js
const arr = [1, 2, 3, 4, 5];

arr.forEach((item) => {
  if (item === 3) {
    return; // 跳出循环
  }
  console.log(item);
});

2. 使用 break

js
const arr = [1, 2, 3, 4, 5];

arr.forEach((item) => {
  if (item === 3) {
    break; // 跳出循环
  }
  console.log(item);
});

3. 使用 throw

js
const arr = [1, 2, 3, 4, 5];

arr.forEach((item) => {
  if (item === 3) {
    throw new Error('Custom error'); // 跳出循环并抛出错误
  }
  console.log(item);
});

4. 使用 Array.prototype.some

js
const arr = [1, 2, 3, 4, 5];

arr.some((item) => {
  if (item === 3) {
    return true; // 跳出循环
  }
  console.log(item);
});

5. 使用 Array.prototype.find

js
const arr = [1, 2, 3, 4, 5];

const foundItem = arr.find((item) => {
  if (item === 3) {
    return true; // 跳出循环
  }
});

console.log(foundItem);

6. 使用 Array.prototype.filter

js
const arr = [1, 2, 3, 4, 5];

const filteredArr = arr.filter((item) => {
  return item !== 3; // 过滤掉值为 3 的元素
});

console.log(filteredArr);

document load 和 document ready 的区别

页面加载完成有两种事件:

  • load 是当页面所有资源全部加载完成后(包括 DOM 文档树,css 文件,js 文件,图片资源等),执行一个函数 问题:如果图片资源较多,加载时间较长,onload 后等待执行的函数需要等待较长时间,所以一些效果可能受到影响

  • $(document).ready()是当 DOM 文档树加载完成后执行一个函数 (不包含图片,css 等)所以会比 load 较快执行 在原生的 js 中不包括 ready()这个方法,只有 load 方法也就是 onload 事件

白屏时间

白屏时间是指浏览器从输入网址,到浏览器开始显示内容的时间。 Performance 接口可以获取到当前页面中与性能相关的信息,该类型的对象可以通过调用只读属性 Window.performance 来获得
performance.timing.navigationStart 是一个返回代表一个时刻的 unsigned long long 型只读属性,为紧接着在相同的浏览环境下卸载前一个文档结束之时的 Unix 毫秒时间戳。如果没有上一个文档,则它的值相当于 PerformanceTiming.fetchStart
所以将以下脚本放在 </head> 前面就能获取白屏时间

JavaScript
<script>
	new Date() - performance.timing.navigationStart
</script>

前端性能优化方法

  1. 使用 CDN 和缓存技术: 把静态资源部署到 CDN 服务器和使用缓存可以显著提高网页加载速度,减少对服务器的请求。

  2. 优化静态资源: 通过合并多个 CSS 和 JavaScript 文件,使用 Gzip 压缩文本文件和其他压缩技术,可以减少静态资源的体积和 HTTP 请求次数,从而提高加载速度。

  3. 异步加载和预加载: 对于不需要立即加载的静态资源使用异步加载,对于需要立即加载的静态资源使用预加载,可以有效地减少网页的加载时间。

  4. 优化 CSS 和 JavaScript: 通过缩小 CSS 和 JavaScript 文件,使用 CSS 预处理器和 JavaScript 构建工具,以及避免使用过多的 CSS 和 JavaScript 库,可以提高代码的执行效率。

  5. 优化图像: 选择正确的图像格式,压缩图像,以及使用 CDN 来缓存图像,可以减少图像加载的时间。

  6. 使用浏览器缓存: 利用 HTTP 缓存头来控制浏览器缓存,以及使用 Service Worker 来缓存离线资源,可以减少服务器的请求次数。

  7. 优化页面结构: 避免使用过多的嵌套元素,使用语义化 HTML,以及避免使用过多的 JavaScript,可以提高页面的渲染速度。

  8. 使用性能工具: 利用浏览器的性能工具和第三方工具来分析页面性能,可以帮助我们找到性能瓶颈并进行优化。

  9. 持续优化: 通过定期检查页面性能和根据需要调整优化策略,可以确保网站始终保持最佳状态。

iframe 跨页面传递消息

window.postMessage,允许来自不同源的窗口之间进行安全的通信。

父页面(parent.html):

html
<!DOCTYPE html>
<html>
  <body>
    <iframe id="iframe" src="child.html" style="display:none;"></iframe>
    <button onclick="sendMessage()">Send Message</button>
    <script>
      function sendMessage() {
        var iframe = document.getElementById("iframe").contentWindow;
        iframe.postMessage("Hello from parent", "*");
      }
      window.addEventListener(
        "message",
        function (event) {
          console.log("Received message:", event.data);
        },
        false
      );
    </script>
  </body>
</html>

子页面(child.html):

html
<!DOCTYPE html>
<html>
  <body>
    <script>
      window.addEventListener(
        "message",
        function (event) {
          console.log("Received message:", event.data);
          event.source.postMessage("Hello from child", "*");
        },
        false
      );
    </script>
  </body>
</html>

说一下 web worker

Web worker 是 JavaScript 的一个 API,它使网页能够在后台执行任务,而不会阻塞页面的渲染。它通常用于执行一些耗时且不依赖于 UI 的任务,例如视频或音频解码、数据处理、图像处理等。

Web worker 的主要优点是它可以提高网页的性能和用户体验。当一个耗时任务在后台执行时,页面不会被阻塞,用户可以继续与页面交互。这对于一些需要实时交互的网页来说非常重要,例如游戏、视频播放器等。

Web worker 的工作流程如下:

  1. 主页面的 JavaScript 代码创建一个 worker 对象,并指定 worker 运行的 JavaScript 或者 TypeScript 文件的 URL。
  2. 浏览器创建一个新的 worker 线程,并加载指定的 JavaScript 或 TypeScript 代码。
  3. worker 线程中的代码被执行,并且可以与主页面进行消息通信。
  4. 主页面可以向 worker 发送消息,并且可以接收来自 worker 的消息。
  5. worker 线程中的代码可以执行任何 JavaScript 或 TypeScript 代码,但它不能直接访问 DOM 或其他与页面相关的对象。
  6. worker 线程中的代码可以创建自己的子 worker 线程,并且可以与子 worker 线程进行消息通信。
  7. worker 线程可以终止自己,或者可以被主页面终止。

Web worker 的使用非常简单,下面是一个简单的例子:

javascript
// 创建一个 worker 对象
const worker = new Worker("worker.js");

// 接收来自 worker 的消息
worker.addEventListener("message", (event) => {
  console.log(event.data);
});

// 向 worker 发送消息
worker.postMessage("Hello from the main page");

在上面的例子中,我们创建了一个 worker 对象,并指定了 worker 运行的 JavaScript 代码的 URL。然后,我们添加了一个事件侦听器,以便在 worker 发送消息时能够接收并处理这些消息。最后,我们向 worker 发送了一条消息。

Web worker 是一个非常强大的 API,它可以用来提高网页的性能和用户体验。如果你需要在你的网页中执行一些耗时且不依赖于 UI 的任务,那么 web worker 是一个非常好的选择。

可选链操作符和空值合并运算符

  • 可选链操作符?.
  • 空值合并运算符??

什么是 "use strict"

"use strict" 是 JavaScript 中的一种指令,用于启用严格模式。严格模式是一种可选的设置,它可以帮助防止某些常见的 JavaScript 错误,并使代码更加安全和可靠。

启用严格模式

要启用严格模式,只需在脚本或模块的顶部添加 "use strict" 指令,如下所示:

js
"use strict";

严格模式的影响

启用严格模式后,以下更改将生效:

  • 禁止使用未声明的变量: 严格模式下,必须在使用变量之前对其进行声明,否则会抛出错误。
  • 禁止重新声明变量: 变量在严格模式下只能声明一次,重复声明会抛出错误。
  • 禁止删除变量: 严格模式下无法使用 delete 运算符删除变量。
  • 禁止使用保留字作为变量名: 严格模式下,不能使用 JavaScript 保留字(例如 letconstclass)作为变量名。
  • 禁止使用八进制和十六进制字面量: 严格模式下,只能使用十进制字面量,八进制和十六进制字面量会抛出错误。
  • 禁止使用 with 语句: 严格模式下,with 语句被禁用,因为它容易导致范围问题。
  • 禁止使用 eval 严格模式下,eval 函数被禁用,因为它存在安全风险。
  • 禁止使用 arguments.calleearguments.caller 严格模式下,arguments.calleearguments.caller 属性被禁用,因为它们容易导致循环引用。

优点

使用严格模式有以下优点:

  • 提高代码质量: 严格模式有助于防止错误,使代码更加健壮和可靠。
  • 提高安全性: 严格模式禁用某些不安全的特性,例如 eval,从而提高代码的安全性。
  • 提高性能: 严格模式可以提高某些操作的性能,例如变量查找。
  • 与未来 JavaScript 版本兼容: 严格模式是 JavaScript 未来版本中推荐的编程模式。

缺点

使用严格模式也有一些缺点:

  • 可能破坏现有代码: 如果现有代码没有遵循严格模式规则,启用严格模式可能会导致错误。
  • 需要额外的编码: 严格模式要求对代码进行更严格的编码,例如显式声明变量。

document.write 和 innerHTML 有何区别

  • document.write 会重绘整个页面,如果不指定元素的话,它会覆盖掉整个页面内容。
  • innerHTML 只会重绘页面的一部分。

JS 隐式转换

JS 隐式转换是指在某些情况下,JS 会自动将一种数据类型转换为另一种数据类型,而无需显式转换。

  • 布尔值到数字: true 转换为 1,false 转换为 0。
  • 数字到布尔值: 0 转换为 false,非零数字转换为 true
  • 字符串到数字: 如果字符串可以解析为数字,则转换为数字,否则转换为 NaN
  • 字符串到布尔值: 空字符串转换为 false,非空字符串转换为 true
  • null 和 undefined 到布尔值: nullundefined 转换为 false
  • 对象到布尔值: 对象转换为 true

数据类型转换

数据类型转换分为显示转换和隐式转换。

显示转换

  • 使用内置函数:Boolean(), Number(), String() 等。

隐式转换

  • 四则运算(加减乘除)
  • == 比较运算符
  • 判断语句(if

1. String

1.1 String 转换为 Number

  • parseInt(string, 10):解析字符串为整数。
  • parseFloat(string):解析字符串为浮点数。
  • Number(string):强制类型转换为数字。
  • 加号运算:+ string

1.2 String 转换为 Object

  • JSON.parse(string):将 JSON 字符串解析为对象。

1.3 String 转换为 Array

  • 使用方括号:[string1, string2, ...]

2. Number

2.1 Number 转换为 String

  • n.toString():转换为字符串。
  • 加号运算:+ ""

2.2 Number 转换为 Boolean

  • 0 和 NaN 转换为 false,其他转换为 true

3. Boolean

  • 除了以下值,其他都转换为 true
    • false
    • 0
    • null
    • undefined
    • 空字符串
    • NaN

4. Object

4.1 Object 转换为 String

  • Object.prototype.toString.call(object):返回对象的类型。
  • JSON.stringify(object):将对象转换为 JSON 字符串。

4.2 Object 转换为 Array

  • Object.values(object):返回对象值的数组。
  • Object.keys(object):返回对象键的数组。
  • Object.entries(object):返回键值对数组。
  • Array.from(object):将类数组对象转换为数组。

4.3 Date Object 转换为 Number

  • Number(date):转换为时间戳。
  • date.getTime():获取时间戳。

4.4 Array Object 转换为 String

  • array.join():连接数组元素为字符串。
  • array.toString():转换为字符串。

5. Undefined 和 Null

5.1 Undefined 和 Null 转换为 Number

  • undefined 无法转换为数字。
  • null 转换为 0。

5.2 总结

  • 不要将变量显式赋值为 undefined
  • 使用 null 表示空值。
  • == 比较中,undefinednull 可能相等。

6. Symbol

  • ES6 中引入的新数据类型。
  • 表示独一无二的值。
  • 使用 Symbol() 创建。

7. 拓展

7.1 类型判断方法

  • typeof:区分基本类型。
  • Object.prototype.toString.call(object):区分复杂数据类型。

7.2 原型链

  • 对象继承自 Object
  • 对象的原型可以通过 .prototype 访问。
  • 当调用对象上的方法时,会沿着原型链查找方法。

目前 JS 解决异步的方案有哪些

  • 回调函数
  • 事件监听
  • 发布-订阅
  • Promise
  • Generator
  • Async/Await

JS 宏任务和微任务

宏任务

  • 在事件循环的下一个循环中执行。
  • 包括:setTimeout、setInterval、UI 渲染、HTTP 请求。

微任务

  • 在当前事件循环中,在所有同步代码执行完后执行。
  • 包括:Promise.then、MutationObserver、process.nextTick。

执行顺序

事件循环的执行顺序如下:

  1. 同步代码
  2. 微任务
  3. 宏任务

示例

javascript
// 宏任务(setTimeout)
setTimeout(() => {
  console.log("宏任务");
}, 0);

// 微任务(Promise.then)
Promise.resolve().then(() => {
  console.log("微任务");
});

console.log("同步代码");

输出:

同步代码
微任务
宏任务

关键区别

特征宏任务微任务
执行时机下一个事件循环当前事件循环
优先级
阻塞会阻塞事件循环不会阻塞事件循环
用例延迟执行、动画响应用户交互、更新 UI

JavaScript 中分号的细节

分号的作用

  • 作为语句的断言(EOS),用于结束一个程序语句。
  • 在 C-Style 语言中,明确结束一行语句,降低编译器开发成本。
  • JavaScript 中分号可选,有自动分号插入机制 (ASI)。

JavaScript 自动分号插入机制 (ASI)

  • 当遇到换行符、}、受限操作或输入流结束时,会在违规 token 前自动插入分号。
  • 不会插入空语句或 for 语句头部两个分号之一。

分号自动插入的情况

  • 新行并入当前行将构成非法语句时。
  • 新行以 } 开头时。
  • 以 return、break、throw、continue、yield 语句结束时。
  • ++、-- 后缀表达式作为新行的开始时。
  • 源代码文件末尾。

不能省略分号的情况

  • 语句以 (、[、/、+、-、` 开头时。
  • return 语句后跟非对象字面量。

总结

ASI 机制提供了两种选择:加或不加分号。代码风格因人而异,但需要准确地添加分号以避免错误。

get 请求传参长度的误区

误区:

  • GET 请求的参数大小存在限制,而 POST 请求的参数大小无限制。

实际情况:

  • HTTP 协议未规定 GET/POST 请求的参数长度限制。
  • 对 GET 请求参数长度的限制源自浏览器或 Web 服务器,它们限制了 URL 的长度。

澄清:

  1. HTTP 协议未规定 GET 和 POST 请求的长度限制。
  2. GET 请求的最大长度限制是由于浏览器和 Web 服务器限制了 URI 的长度。
  3. 不同的浏览器和 Web 服务器对最大长度的限制不同。
  4. 为了支持 IE,最大长度为 2083 字节;如果仅支持 Chrome,则最大长度为 8182 字节。

三种事件模型

DOM0 级事件模型

  • 不会传播事件,没有事件流的概念。
  • 可以直接在 HTML 中定义事件处理函数,或使用 on 属性指定事件处理函数。
  • 兼容所有浏览器。

IE 事件模型

  • 事件处理过程分为两个阶段:事件处理阶段和事件冒泡阶段。
  • 事件处理阶段:首先执行目标元素绑定的事件监听函数。
  • 事件冒泡阶段:事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
  • 使用 attachEvent 添加事件监听函数,可以添加多个监听函数,按顺序依次执行。

DOM2 级事件模型

  • 事件处理过程分为三个阶段:事件捕获阶段、事件处理阶段和事件冒泡阶段。
  • 事件捕获阶段:事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
  • 事件处理阶段:与 IE 事件模型中的事件处理阶段相同。
  • 事件冒泡阶段:与 IE 事件模型中的事件冒泡阶段相同。
  • 使用 addEventListener 添加事件监听函数,第三个参数可以指定事件是否在捕获阶段执行。