前端基础知识总结(二)--JavaScript

271 阅读15分钟

前端基础知识总结(二)--JavaScript

目的:方便自己整体复习前端的基础知识。
资源:在鱼皮整理的前端学习路线的基础上结合自身对相关知识的掌握程度进行的总结。
参考资料:阮一峰的 ES6 入门教程

JavaScript

JavaScript 是一种基于原型、多范式、单线程的动态语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。

(一)数据类型

基本数据类型

存储在栈内存,连续的空间

数字 Number、字符串 String、布尔 Boolean、未定义 Undefined(undefined 表示值的缺失)、空值 Null(null 表示对象的缺失)、Symbol(symbol 的目的是去创建一个唯一属性键)

BigInt 类型

BigInt 是通过将 n 附加到整数末尾或调用 BigInt() 函数来创建的。

const a = 1n;
const b = BigInt(1);
console.log(a === b) // true
console.log(typeof a) // bigInt

引用数据类型

存储在堆内存,非连续空间,对象的变量名存储在栈内存

结构化数据:JSON

  1. JSON.parse()
  2. JSON.stringify()

JSON.parse()解析目标 JSON 字符串对象条件:

  • 对象和数组属性名称必须是双引号括起来的字符串;禁止出现尾随逗号。
  • 数值禁止出现前导零。小数点后必须至少有一位数字。不支持 NaN 和 Infinity。

类型转换

  1. 显式类型转换 (Explicit Coercion)
  • Number(): 将值转换为数字。如果转换失败(undefined、字符串),则返回 NaN。
  • String(): 将值转换为字符串。String() 是唯一一种可以将 Symbol 转换为字符串而不抛出异常的方式。String(Symbol("示例")); // "Symbol(示例)"
  • Boolean(): 将值转换为布尔值。
  1. 隐式类型转换 (Implicit Coercion)
  • 算术运算符: 当使用算术运算符(如+-*/等)时,JavaScript 会尝试将操作数转换为数字。
  • 比较运算符: 当使用比较运算符(如<>=====等)时,JavaScript 也可能会进行类型转换。
  • 逻辑运算符: 如!(逻辑非)也会进行隐式类型转换。
  1. parseInt() 和 parseFloat()
  • parseInt(string, radix): 解析一个字符串参数,并返回一个指定基数的整数(十进制数默认)。parseInt()无法识别小数点.
  • parseFloat(string): 解析一个字符串参数,并返回一个浮点数。如果第一个非空白字符不能被转换为数字,则返回 NaN,parseFloat()无法识别 0x 前缀。
  1. toString() 方法

(二)进阶内容

1、函数

a. 函数调用
1、apply
apply(thisArg)
apply(thisArg, argsArray)
  1. thisArg: 调用 func 时提供的 this 值。如果函数不处于严格模式,则 null 和 undefined 会被替换为全局对象,原始值会被转换为对象。
  2. argsArray(可选): 一个类数组对象,用于指定调用 func 时的参数,或者如果不需要向函数提供参数,则为 null 或 undefined。
  3. 返回值: 使用指定的 this 值和参数调用函数的结果。
2、call
call(thisArg)
call(thisArg, arg1)
call(thisArg, arg1, arg2)
call(thisArg, arg1, arg2, /* …, */ argN)
  1. thisArg: 在调用 func 时要使用的 this 值。如果函数不在严格模式下,null 和 undefined 将被替换为全局对象,并且原始值将被转换为对象。
  2. arg1, …, argN (可选): 函数的参数。
  3. 返回值: 使用指定的 this 值和参数调用函数后的结果。

func.call(this, "eat", "bananas") 与 func.apply(this, ["eat", "bananas"]),函数的参数的形式不同

3、bind
bind(thisArg)
bind(thisArg, arg1)
bind(thisArg, arg1, arg2)
bind(thisArg, arg1, arg2, /* …, */ argN)
  1. thisArg:在调用绑定函数时,作为 this 参数传入目标函数 func 的值。如果函数不在严格模式下,null 和 undefined 会被替换为全局对象,并且原始值会被转换为对象。如果使用 new 运算符构造绑定函数,则忽略该值。
  2. arg1, …, argN (可选):在调用 func 时,插入到传入绑定函数的参数前的参数。
  3. 返回值:使用指定的 this 值和初始参数(如果提供)创建的给定函数的副本。
b. 箭头函数
  1. 箭头函数没有独立的 this、arguments 和 super 绑定,并且不可被用作方法。
  2. 箭头函数不能用作构造函数。使用 new 调用它们会引发 TypeError。它们也无法访问 new.target 关键字。
  3. 箭头函数不能在其主体中使用 yield,也不能作为生成器函数创建。
  4. call()、apply() 和 bind() 方法在箭头函数上调用时不起作用,因为箭头函数是根据箭头函数定义的作用域来建立 this 的,而 this 值不会根据函数的调用方式而改变。
c、闭包

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

d、作用域(作用域链)

作用域是当前的执行上下文,值和表达式在其中“可见”或可被访问,作用域也可以堆叠成层次结构,子作用域可以访问父作用域。

JavaScript 的作用域分以下三种:

  • 全局作用域:脚本模式运行所有代码的默认作用域
  • 模块作用域:模块模式中运行代码的作用域
  • 函数作用域:由函数创建的作用域
  • 块级作用域:用一对花括号(一个代码块)创建出来的作用域,用 let 或 const 声明的变量属于块级作用域

作用域链(Scope Chain)是当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。

2、对象

数组 Array
  1. flat([depth]) 方法创建一个新的数组,并根据指定深度递归地将所有子数组元素拼接到新的数组中。返回值一个新的数组,其中包含拼接后的子数组元素。它不会改变原数组。
  2. Array.from() 静态方法从可迭代或类数组对象创建一个新的浅拷贝的数组实例。返回值一个新的数组实例。
Array.from(arrayLike)
Array.from(arrayLike, mapFn)
Array.from(arrayLike, mapFn, thisArg)
// mapFn 仅接受两个参数(element、index),不接受数组,因为数组仍然在构建中。
  1. keys() 方法返回一个新的数组迭代器对象,其中包含数组中每个索引的键。返回值一个新的可迭代迭代器对象。与 Object.keys() 只包含数组中实际存在的键不同,keys() 迭代器不会忽略缺失属性的键。在非数组对象上调用 keys(), keys() 方法读取 this 的 length 属性,然后生成 0 到 length - 1 之间的所有整数索引。实际并不会访问索引。
  2. Array.of() 静态方法通过可变数量的参数创建一个新的 Array 实例,而不考虑参数的数量或类型。返回值新的 Array 实例。Array.of() 和 Array() 构造函数之间的区别在于对单个参数的处理.
  3. reduce() 方法对数组中的每个元素按序执行一个提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。 查看详细介绍
reduce(callbackFn)
reduce(callbackFn, initialValue)
callbackFn为数组中每个元素执行的函数。其返回值将作为下一次调用 callbackFn 时的 accumulator 参数。
对于最后一次调用,返回值将作为 reduce() 的返回值。该函数被调用时将传入以下参数:
  1. accumulator上一次调用callbackFn 的结果,
     在第一次调用时,如果指定了 initialValue 则为指定的值,否则为 array[0] 的值。
  2. currentValue 当前元素的值。
     在第一次调用时,如果指定了 initialValue,则为 array[0] 的值,否则为 array[1]。
  3. currentIndex: currentValue 在数组中的索引位置。
     在第一次调用时,如果指定了 initialValue 则为 0,否则为 14. array:调用了 reduce() 的数组本身。
initialValue (可选)
  1. with() 方法是使用方括号表示法修改指定索引值的复制方法版本。它会返回一个新数组,其指定索引处的值会被新值替换。它不会改变原数组。
日期 Date

dayjsmoment.js

数学 Math

Math 用于 Number 类型。它不支持 BigInt。

  1. Math.round() 函数返回一个数字四舍五入后最接近的整数。
// 小数舍入
function round(number, precision) {
  return Math.round(+number + "e" + precision) / Math.pow(10, precision);
  //same as:
  //return Number(Math.round(+number + 'e' + precision) + 'e-' + precision);
}

round(1.005, 2); //1.01

3、继承、原型和原型链

每个对象(object)都有一个私有属性指向另一个名为原型(prototype)的对象。原型对象也有一个自己的原型,层层向上直到一个对象的原型为 null。null 没有原型,并作为这个原型链(prototype chain)中的最后一个环节。

4、内存管理

JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。释放的过程称为垃圾回收。

内存生命周期
  1. 分配你所需要的内存,值的初始化、通过函数调用分配内存
  2. 使用分配到的内存(读、写),使用值
  3. 不需要时将其释放\归还,当内存不再需要使用时释放
垃圾回收
  1. 引用:垃圾回收算法主要依赖于引用的概念。在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。例如,一个 Javascript 对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)。
  2. 引用计数垃圾收集:这是最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收,限制:无法处理循环引用的事例。
  3. 标记 - 清除算法:这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。 这个算法假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。限制:那些无法从根对象查询到的对象都将被清除,所有对 JavaScript 垃圾回收算法的改进都是基于标记 - 清除算法的改进

5、事件循环

JavaScript 的事件循环模型与许多其他语言不同的一个非常有趣的特性是,它永不阻塞。 事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。

  1. 常见的 macro-task 比如: setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作、UI 渲染等。
  2. 常见的 micro-task 比如: process.nextTick、Promise、MutationObserver 等。

一个完整的 Event Loop 过程,可以概括为以下阶段:

  1. 初始状态:调用栈空。micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码)。

  2. 全局上下文(script 标签)被推入调用栈,同步代码执行。在执行的过程中,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。同步代码执行完了,script 脚本会被移出 macro 队列,这个过程本质上是队列的 macro-task 的执行和出队的过程。

  3. 上一步我们出队的是一个 macro-task,这一步我们处理的是 micro-task。但需要注意的是:当 macro-task 出队时,任务是一个一个执行的;而 micro-task 出队时,任务是一队一队执行的。因此,我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。

  4. 执行渲染操作,更新界面(敲黑板划重点)。

  5. 检查是否存在 Web worker 任务,如果有,则对其进行处理 。

6、异步编程

Promise

Promise 对象表示异步操作最终的完成(或失败)以及其结果值。

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled):意味着操作成功完成。
  • 已拒绝(rejected):意味着操作失败。

Promise 学习总结Promise 对象

async

Generator 函数的语法糖。async 函数的返回值是 Promise 对象,async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖。

async 函数

7、Ajax

AJAX(Asynchronous JavaScript And XML )是一种在 Web 应用中通过异步发送 HTTP 请求向服务器获取内容,并使用这些新内容更新页面中相关的部分,而无需重新加载整个页面的 Web 开发技术。

应用

1、深拷贝和浅拷贝

浅拷贝:对源或副本的更改可能也会导致其他对象的更改(因为两个对象共享相同的引用)

在 JavaScript 中,标准的内置对象复制操作

展开语法、Array.prototype.concat()、Array.prototype.slice()、Array.from()、Object.assign() 和 Object.create()

深拷贝:是指其属性与其拷贝的源对象的属性不共享相同的引用(指向相同的底层值)的副本

  • 对于可序列化的对象,使用 JSON.stringify() 将该对象转换为 JSON 字符串,然后使用 JSON.parse() 将该字符串转换回(全新的)JavaScript 对象。不能序列化——例如,函数(带有闭包)、Symbol、在 HTML DOM API 中表示 HTML 元素的对象、递归数据。
  • 对于可序列化的对象,使用 structuredClone() 方法,它是浏览器和任何其他实现了 window 这样全局对象的 JavaScript 运行时的一个特性。
function deepClone (target, hash = new WeakMap()) { // 额外开辟一个存储空间WeakMap来存储当前对象
  if (target instanceof Date) return new Date(target) // 处理日期
  if (target instanceof RegExp) return new RegExp(target) // 处理正则
  if (target instanceof HTMLElement) return target // 处理 DOM元素
  if (typeof target !== 'object' || target === null) return target // 处理原始类型和函数 不需要深拷贝,直接返回

  // 是引用类型的话就要进行深拷贝
  if (hash.get(target)) return hash.get(target) // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
  const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
  hash.set(target, cloneTarget) // 如果存储空间中没有就存进 hash 里

  Reflect.ownKeys(target).forEach(key => { // 引入 Reflect.ownKeys,处理 Symbol 作为键名的情况
    cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
  })
  return cloneTarget // 返回克隆的对象
}

2、防抖和节流

节流(throttle):在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应。

// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
  // last为上一次触发回调的时间
  let last = 0;

  // 将throttle处理结果当作函数返回
  return function () {
    // 保留调用时的this上下文
    let context = this;
    // 保留调用时传入的参数
    let args = arguments;
    // 记录本次触发回调的时间
    let now = +new Date();

    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last >= interval) {
      // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
      last = now;
      fn.apply(context, args);
    }
  };
}

防抖(debounce):在某段时间内,不管你触发了多少次回调,我都只认最后一次。

// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
function debounce(fn, delay) {
  // 定时器
  let timer = null;

  // 将debounce处理结果当作函数返回
  return function () {
    // 保留调用时的this上下文
    let context = this;
    // 保留调用时传入的参数
    let args = arguments;

    // 每次事件被触发时,都去清除之前的旧定时器
    if (timer) {
      clearTimeout(timer);
    }
    // 设立新定时器
    timer = setTimeout(function () {
      fn.apply(context, args);
    }, delay);
  };
}

用 Throttle 来优化 Debounce(加强版 throttle):在 delay 时间内,我可以为你重新生成定时器;但只要 delay 的时间到了,我必须要给用户一个响应。

// fn是我们需要包装的事件回调, delay是时间间隔的阈值
function throttle(fn, delay) {
  // last为上一次触发回调的时间, timer是定时器
  let last = 0,
    timer = null;
  // 将throttle处理结果当作函数返回

  return function () {
    // 保留调用时的this上下文
    let context = this;
    // 保留调用时传入的参数
    let args = arguments;
    // 记录本次触发回调的时间
    let now = +new Date();

    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last < delay) {
      // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
      clearTimeout(timer);
      timer = setTimeout(function () {
        last = now;
        fn.apply(context, args);
      }, delay);
    } else {
      // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
      last = now;
      fn.apply(context, args);
    }
  };
}