Skip to content

javaScript

本文使用的浏览器

Chrome 版本 124.0.6367.208(正式版本) (arm64)

🔸 数据类型

难度:★☆☆☆☆

值类型(基本数据类型)

String、Boolean、Number、Null、Undefined、Symbol、BigInt。

  • BigInt 类型在 JavaScript 中是一个数字的原始值,它可以表示任意大小的整数。使用 BigInt,你可以安全地存储和操作巨大的整数,甚至超过 Number 的安全整数限制(Number.MAX_SAFE_INTEGER)。
  • Symbol 是唯一并且不可变的原始值并且可以用来作为对象属性的键。

引用数据类型(对象类型)

instanceof Object。

🔸 数据类型内存管理

难度:★☆☆☆☆

存储内容

  • 栈内存: 存储 String、Boolean、Number、Null、Undefined、Symbol、BigInt 基本数据类型。
  • 堆内存: 存储 引用数据类型

特性

栈内存

  • 先进后出,后进先出原则
  • 存储空间小,效率高

类似 push 方式向内存写入,类似 pop 方式从内存中移除。

ts
const name = '张三'
const age = 18

栈赋值

ts
let name = '张三'
const name2 = name
name = '李四'

堆内存

  • 动态分配与回收
  • 存储空间大,分配灵活,但效率相对较低
ts
const name = '张三'
const age = 18
const obj = {
  name: '张三',
  age: 18,
}

堆赋值

ts
const obj = {
  name: '张三',
  age: 18,
}
const obj2 = obj
obj.age = 20
console.log(obj2.age)
// 20

修改 obj.age 会影响 obj2.age

🔸 浅拷贝&深拷贝

难度:★★☆☆☆

浅拷贝

创建一个新对象,这个新对象的属性值是原对象属性值的引用。

浅拷贝方法

  • Object.assign()
  • Object.create()
  • Spread Operator (...拓展运算符)
  • Array.prototype.slice(); Array.prototype.concat(); Array.from();
  • 自定义函数
Object.assign()
ts
const obj = {
  name: '张三',
  age: 18,
  childArr: [1, 2, 3],
  childObj: {
    childName: '张三2',
  },
  childArr2: [1],
  childObj2: {
    childName: '张三3',
  },
}
const obj2 = Object.assign({}, obj)
obj2.name = '李四'
obj2.age = 20
obj2.childArr = [4, 5, 6]
obj2.childObj = {
  childName: '李四2',
}
obj2.childArr2[0] = 4
obj2.childObj2.childName = '李四3'
console.log(JSON.stringify(obj, null, 2))
// {
//   "name": "张三",
//   "age": 18,
//   "childArr": [
//     1,
//     2,
//     3
//   ],
//   "childObj": {
//     "childName": "张三2"
//   },
//   "childArr2": [ change
//     4
//   ],
//   "childObj2": { change
//     "childName": "李四3"
//   }
// }

Object.create()

ts
const obj = {}
const obj2 = Object.create(obj)

Spread Operator

ts
const obj = {}
const obj2 = {
  ...obj,
}

Array

ts
const list = [1, 2, 3]
const list2 = list.slice()
const list3 = [].concat(list)
const list4 = [...list]
const list5 = Array.from(list)

自定义函数

ts
function clone(value) {
  if (value === null) {
    return null
  }
  if (typeof value !== 'object') {
    return value
  }
  // TODO Date、RegExp、Symbol、Buffer、ArrayBuffer、DataView、TypedArray
  const result = new value.constructor()
  for (const prop in value) {
    if (Object.prototype.hasOwnProperty.call(value, prop)) {
      result[prop] = value[prop]
    }
  }
  return result
}

深拷贝

深拷贝方法

  • JSON.stringify()
  • lodash => _.cloneDeep()
  • jQuery => jQuery.extend()
  • 自定义函数
JSON.stringify()
ts
const _ = require('lodash')
const obj = {
  name: '张三',
  age: 18,
  childArr: [1],
  childObj: {
    childName: '张三3',
  },
}
const obj2 = JSON.parse(JSON.stringify(obj))
obj2.childArr[0] = 2
obj2.childObj.childName = '李四3'
console.log(JSON.stringify(obj, null, 2))
// {
//   "name": "张三",
//   "age": 18,
//   "childArr": [
//     1
//   ],
//   "childObj": {
//     "childName": "张三3"
//   }
// }

部分类型无法拷贝

  • undefinedSymbolFunction 忽略
  • Function 如果在数组中会转为 null, [function(){}] => [null]
  • BigInt TypeError: Do not know how to serialize a BigInt
  • Date 转换为一个 ISO 8601 格式的字符串
  • RegExp 转为 {}
  • InfinityNaN 转为 null
  • MapSetWeakMapWeakSet 转为 {}
lodash => _.cloneDeep()
ts
const _ = require('lodash')
const obj = {}
const obj2 = _.cloneDeep(obj)
jQuery => jQuery.extend()
ts
const $ = require('jquery')
const obj = {}
const obj2 = $.extend(true, {}, obj)
自定义函数
ts
function cloneDeep(value, hash = new WeakMap()) {
  if (value === null) {
    return null
  }
  if (typeof value !== 'object') {
    return value
  }
  // TODO Date、RegExp、Symbol、Buffer、ArrayBuffer、DataView、TypedArray
  if (hash.get(value)) {
    return hash.get(value)
  }
  const result = new value.constructor()
  hash.set(value, result)
  for (const key in value) {
    if (Object.prototype.hasOwnProperty.call(value, key)) {
      result[key] = cloneDeep(value[key], hash)
    }
  }
  return result
}

🔸 类型判断

难度:★☆☆☆☆ typeof 能够检测出 string、number、boolean、function、symbol、bigint。

ts
typeof 1 // 'number'
typeof '1' // 'string'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'
typeof new Date() // 'object'
typeof new Date() // 'string'
typeof Symbol('') // 'symbol'
typeof 1n === 'bigint' // true
typeof BigInt('1') === 'bigint' // true
  • instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
  • 构造函数通过 new 可以实例对象,instanceof 能判断这个对象是否是之前那个构造函数生成的对象。
ts
function myInstanceof(left, right) {
  // 这里先用typeof来判断基础数据类型,如果是,直接返回false
  if (typeof left !== 'object' || left === null) {
    return false
  }
  // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
  let proto = Object.getPrototypeOf(left)
  while (true) {
    if (proto === null) {
      return false
    }
    if (proto === right.prototype) {
      return true // 找到相同原型对象,返回true
    }
    proto = Object.getPrototypeof(proto)
  }
}

准确的判断类型

ts
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(() => {}) // "[object Function]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(/123/g) // "[object RegExp]"
Object.prototype.toString.call(new Date()) // "[object Date]"
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call(document) // "[object HTMLDocument]"
Object.prototype.toString.call(window) // "[object Window]"
Object.prototype.toString.call(1n) // "[object BigInt]"
Object.prototype.toString.call(BigInt('1')) // "[object BigInt]"
Object.prototype.toString.call(Symbol('')) // "[object Symbol]"

🔸 类型转换

难度:★★★☆☆

显式转换

  • Number()
  • parseInt()
  • parseFloat()
  • String()
  • Boolean()

隐式转换

  • toPrimitive(input: any, preferedType?: 'string' | 'number')
  • preferedType=number 调用顺序 1,2,3,4
  • preferedType=string 调用顺序 1,3,2,4
  1. 基础类型不处理
  2. valueOf
  3. toString
  4. TypeError
对象valueOf()toString()默认 preferedType
Object原值"[object Object]"Number
Function原值"function xyz() {...}"Number
Array原值"x,y,z"Number
Date数字"Sat May 22 2021..."String
  • 数组的toString()可以等效为join(","),遇到 null、undefined 都被忽略,遇到symbol直接报错,遇到无法ToPrimitive的对象也报错。
  • 使用模板字符串或者使用String(...)包装时,preferedType=string,即优先调用 .toString()。
  • 使用减法或者Number(...)包装时,preferedType=number,即优先调用.valueOf()。

总结

对象都需要先 ToPrimitive 转成基本类型,除非是宽松相等(==)时两个对象做对比。

  • + 运算,preferedType 是默认值(见表格),没有字符串就全转数字。
  • - 运算,preferedType 是 Number 全转数字。
  • == 同类型不转,数字优先,布尔全转数字,null、undefined、symbol 不转
  • < > 数字优先,除非两边都是字符串

参考资料:

🔸 字符串API

难度:★☆☆☆☆

  • substring substring(start, end(不包含))
  • substr(已弃用)substr(start, length)

substring

  • substringslice 使用方法相似,slice 支持负数,substring 不支持。
ts
const string = 'Hello World'
string.substring(5, -1) // => string.substring(5, 0) => string.substring(0, 5)
// 'Hello'

🔸 数组API

难度:★★☆☆☆ 改变数组的方法(8种)

  • push 方法将指定的元素添加到数组的末尾,并返回新的数组长度。
  • pop 方法从数组中删除最后一个元素,并返回删除元素的值。
  • unshift 方法将指定元素添加到数组的开头,并返回新的数组长度。
  • shift 方法从数组中删除第一个元素,并返回删除元素的值。
  • splice 方法就地移除或者替换已存在的元素和/或添加新的元素。splice(start, deleteCount, item1, item2, /* …, */ itemN)
  • sort 方法就地对数组的元素进行排序,并返回对相同数组的引用。默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。
  • reverse 方法就地反转数组中的元素,并返回同一数组的引用。数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。换句话说,数组中的元素顺序将被翻转,变为与之前相反的方向。
  • copyWithin 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。copyWithin(target, start, end(不包括)) 相关方法
  • slice 方法返回一个新的数组对象,这一对象是一个由 start 和 end 决定的原数组的浅拷贝 slice(start, end(不包括)),返回新数组。
  • toReversed reverse的复制版本,返回新数组。
  • toSorted sort的复制版本,返回新数组。
  • toSpliced splice的复制版本,返回新数组。

参考资料:

🔸 原型链 prototype

难度:★★★☆☆

JavaScript 是基于原型的语言。当我们访问一个对象的属性时,如果对象没有该属性,JavaScript 解释器就会从对象的原型对象上去找该属性,如果原型上也没有该属性,那就去找原型的原型,直到最后返回null为止,null没有原型。这种属性查找的方式被称为原型链(prototype chain)。

  • prototype: 每一个函数都有一个特殊的属性,叫做原型 (prototype)。
  • constructor: 相比于普通对象的属性,prototype 属性本身会有一个属性 constructor,该属性的值为 prototype 所在的函数。
  • __proto__: 每一个对象都有一个 __proto__ 属性(不同对象之间的桥梁),该属性指向对象(实例)所属构造函数(类)的原型 prototype。应该为 [[Prototype]],主流浏览器实现为 __proto__。

  • 一切对象都是继承自 Object 对象,Object 对象直接继承根源对象 null。
  • 一切的函数对象(包括 Object 对象),都是继承自 Function 对象。
  • Object 对象直接继承自 Function 对象。
  • Function 对象的 __proto__ 会指向自己的原型对象,最终还是继承自 Object 对象。

🔸 创建对象的方式

难度:★★☆☆☆

对象字面量

ts
console.time('函数字面量运行时间')
// 嵌套函数字面量
const Person = {
  name: '张三',
}
Person.getName = function () {
  console.log(this.name)
}
// 调用方法
Person.getName() // 张三
console.timeEnd('函数字面量运行时间') // 0.376953125 ms

new

  1. 创建一个新的对象
  2. obj将对象与构建函数通过原型链连接起来
  3. 将构建函数中的 this 绑定到新建的对象 obj 上
  4. 根据构建函数返回类型作判断,如果 引用类型,返回 构造函数原型对象,否则返回 新对象
ts
// 构造函数
function Person(name) {
  this.name = name
}
// 原型添加方法
Person.prototype.getName = function () {
  console.log(this.name)
}

console.time('构造函数运行时间')
// 生成实例
const person = new Person('张三')
// 调用方法
person.getName() // 张三
console.timeEnd('构造函数运行时间') // 构造函数运行时间: 0.489013671875 ms

实现一个 myNew

ts
function myNew(constructor, ...args) {
  // 1. 创建一个新对象
  const obj = {}
  // 2. 新对象原型指向构造函数原型对象
  Object.setPrototypeOf(obj, constructor.prototype) // obj.__proto__ = constructor.prototype
  // 3. 将构建函数的 this 指向新对象
  const result = constructor.apply(obj, args)
  // 4. 根据返回值判断
  return result instanceof Object ? result : obj
}
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.say = function () {
  console.log(this.name, this.age)
}

const p = myNew(Person, '张三', 18)
p.say() // 张三 18

TIP

这里 result instanceof Object 不能使用 typeof,因为 typeof null === 'object'

Object.create

Object.create 方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__。

ts
console.time('create运行时间')
// 嵌套函数字面量
const Person = Object.create({
  name: '张三',
})
Person.getName = function () {
  console.log(this.name)
}
// 调用方法
Person.getName() // 张三
console.timeEnd('create运行时间') // 0.429931640625 ms
ts
Object.create(null) // 创建一个纯净的对象
const obj = {}
const objCreate = Object.create(null)
console.log(obj.hasOwnProperty) // ƒ hasOwnProperty() { [native code] }
console.log(objCreate.hasOwnProperty) // undefined

运行时间

对象字面量 < Object.create < new

🔸 this

难度:★☆☆☆☆

根据不同的使用场合,this 有不同的值,主要分为下面几种情况:

  • 默认绑定
  • 隐式绑定
  • new 绑定
  • 显示绑定
  • 箭头函数

默认绑定

非严格模式 this 指向 window

ts
// eslint-disable-next-line no-var
var name = '张三'
function person() {
  return this.name
}
console.log(person()) // 张三

严格模式 this 指向 undefined

ts
'use strict'
// eslint-disable-next-line no-var
var name = '张三'
function person() {
  console.log(this)
}
person() // undefined

隐式绑定

当函数作为某个对象方法使用时,this 指向上级对象。

ts
function person() {
  console.log(this.name)
}

const obj = {
  name: '张三',
  person,
  nest: {
    person,
  },
}

obj.person() // 张三
obj.nest.person() // undefined

this 永远指向的是最后调用它的对象

ts
function person() {
  console.log(this)
}

const obj = {
  name: '张三',
  person,
}

const obj2 = obj.person

obj2() // 严格模式 undefined,非严格模式 window

new 绑定

根据构造函数返回类型决定 this 指向,参考 new

ts
function Person() {
  this.name = '张三'
  // string、number、boolean、null、undefined、symbol、bigint
  return null
}

const person = new Person()
console.log(person.name) // 张三
ts
function Person() {
  this.name = '张三'
  return {}
}

const person = new Person()
console.log(person.name) // undefined

显示绑定

参考 bind、call、apply

箭头函数

箭头函数绑定父级作用域的上下文

ts
const obj = {
  name: '张三',
  fn() {
    console.log(this.name)
  },
  arrowFn: () => {
    console.log(this)
  },
}
obj.fn() // 张三
obj.arrowFn() // this 指向 严格模式 undefined,非严格模式 window

🔸 bind、call、apply

难度:★☆☆☆☆

  • bind Function 实例的 bind() 方法创建一个新函数,当调用该新函数时,它会调用原始函数并将其 this 关键字设置为给定的值,同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面。bind(thisArg, arg1, arg2, /* …, */ argN)
ts
const obj = {
  name: '张三',
  say(...args) {
    console.log(this.name, ...args)
  },
}

setTimeout(obj.say, 0) // ''  this === window
setTimeout(obj.say.bind(obj, 1, 2), 0) // 张三 1 2

const bindFn = obj.say.bind(obj, 1, 2)
bindFn(3, 4)
// 张三 1 2 3 4
  • apply Function 实例的 apply() 方法会以给定的 this 值和作为数组(或类数组对象)提供的 arguments 调用该函数。
ts
function fn(...args) {
  console.log(this, args)
}
const obj = {
  name: '张三',
}
fn.apply(obj, [1, 2]) // this 会变成传入的 obj,传入的参数必须是一个数组
fn(1, 2) // this 指向 严格模式 undefined,非严格模式 window
  • call 和 apply 使用方式几乎一样,只是参数是一个列表
ts
function fn(...args) {
  console.log(this, args)
}
const obj = {
  name: '张三',
}
fn.call(obj, 1, 2) // this 会变成传入的 obj,传入的参数必须是一个数组
fn(1, 2) // this 指向 严格模式 undefined,非严格模式 window

当第一个参数传入非引用类型时情况如下:

类型严格模式非严格模式
string原值Object(String()) 等价于 new String()
number原值Object(Number()) 等价于 new Number()
boolean原值Object(Boolean()) 等价于 new Boolean()
null原值window
undefined原值window
BigInt原值Object(BigInt())
Symbol原值Object(Symbol())

TIP

围绕原始数据类型创建一个显式包装器对象从 ECMAScript 6 开始不再被支持。然而,现有的原始包装器对象,如 new Boolean、new String以及new Number,因为遗留原因仍可被创建。

实现一个 myBind

  1. 修改 this 指向
  2. 动态传递参数
  3. 兼容 new 关键字

🪡 TODO

🔸 作用域和作用域链

难度:★☆☆☆☆

  • 全局作用域
  • 函数作用域
  • 块级作用域

全局作用域

任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问

ts
// 全局变量
const text = 'Hello World'
function say() {
  console.log(text)
}
say()
// Hello World

函数作用域

函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问。

ts
function say() {
  const text = 'Hello World'
  console.log(text)
}
say()
console.log(text)
// Hello World
// Uncaught ReferenceError: text is not defined

块级作用域

ts
{
  const text = 'Hello World'
  // eslint-disable-next-line vars-on-top, no-var
  var name = '张三'
  console.log(text)
}
// eslint-disable-next-line block-scoped-var
console.log(name)
console.log(text)
// Hello World
// 张三
// Uncaught ReferenceError: text is not defined

词法作用域

词法作用域(Lexical Scoping),也称为静态作用域,是编程语言中作用域的一种定义规则。它决定了变量和函数的可访问性基于代码的结构,在编写代码时就能确定,而非在代码运行时动态改变。换句话说,词法作用域是由代码的文本结构(即代码是如何写的)来决定的,而不是由函数调用的顺序或者执行时的上下文来决定的。

作用域链

当在 JavaScript 中使用一个变量的时候,首先 JavaScript 引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。

🔸 闭包

难度:★★★☆☆

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

简单理解:闭包 = 函数 + 函数外部变量。

形成闭包的条件

  1. 外部函数
  2. 外部函数变量
  3. 内部函数
  4. 内部函数引用外部函数变量
  5. 返回内部函数或通过其他方式保持对内部函数的引用

闭包作用

  • 封装模块、创建私有变量
  • 函数记忆、状态保持、延迟执行函数
  • 回调函数

闭包的缺陷

  • 内存占用:闭包会导致外部函数的变量无法被垃圾回收,从而增加内存占用。如果闭包会长时间存在,那么外部变量将无法被释放,可能导致内存泄漏。
  • 性能损耗:闭包涉及到作用域链的查找过程,会带来一定的性能损耗。在性能要求高的场景下,需要注意闭包的使用。

TIP

  • 闭包是 JavaScript 语言特性
  • 闭包不一定需要 return
  • 闭包不一定会造成内存泄露
  • 将闭包引用变量置为 null,可手动释放 闭包 占用的内存

最简单的闭包

ts
function outer() {
  const count = 0
  function inner() {
    console.log(count)
  }
  inner()
}
outer()

在浏览器下可以看到 count 变量和 inner 函数形成了 闭包,后续没有任何引用,outer() 执行完毕后生命周期结束。

count 放到 inner 里面看一下效果。

ts
function outer() {
  function inner() {
    const count = 0
    console.log(count)
  }
  inner()
}
outer()

闭包的应用

函数记忆

不使用闭包实现
ts
let count = 0
function sum() {
  count++
  console.log(count)
}
sum()

缺点

  • count 为全局公共变量,容易被污染

使用 闭包 实现

ts
function sum() {
  let count = 0
  return function () {
    count++
    console.log(count)
  }
}
const foo = sum()
foo()
foo()
foo()
// 1
// 2
// 3
const bar = sum()
bar()
bar()
bar()
// 1
// 2
// 3

优点

  • count 变量私有化

缺点

  • foobar 为全局变量,每次 sum() 开辟新的内存,使用完需及时销毁 foobar 变量,否则闭包内的环境会一直占用内存。

使用 class 实现,使用 class 实现性能优于 闭包

ts
console.time('class')
class Counter {
  constructor() {
    this.count = 0
  }

  add() {
    this.count++
    console.log(this.count)
  }
}
const c = new Counter()
c.add()
c.add()
c.add()
// 1
// 2
// 3
console.timeEnd('class') // class: 0.072998046875 ms

console.time('closure')
function counter() {
  let count = 0
  return function () {
    count++
    console.log(count)
  }
}
const foo = counter()
foo()
foo()
foo()
// 1
// 2
// 3
console.timeEnd('closure') // closure: 0.1240234375 ms

私有变量和方法

使用 class 实现

ts
class Person {
  #name = '张三'
  #say() {
    console.log(this.#name, 'Hello World')
  }

  constructor() {
    this.#say()
  }
}
const person = new Person()
// person.#name 报错
// person.#say() 报错

使用 闭包 实现

ts
function Person() {
  const _name = '张三'
  const _say = function () {
    console.log(_name, 'Hello World')
  }
  this.say = function () {
    _say()
  }
}

const person = new Person()
person.say()

单例模式

使用 class 实现

ts
class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance
    }
    this.text = 'Hello World'
    // 只执行一次
    console.log(this.text)
    Singleton.instance = this
  }

  say() {
    console.log('say', this.text)
  }
}
const instance1 = new Singleton()
const instance2 = new Singleton()
instance1.say()
instance2.say()
console.log(instance1 === instance2)
// Hello World
// say Hello World
// say Hello World
// true

使用 闭包 实现

ts
const say = (function () {
  const text = 'Hello World'
  // 只执行一次
  console.log(text)
  function _say() {
    console.log('say', text)
  }
  return _say
})()
say()
say()
// Hello World
// say Hello World
// say Hello World

回调函数

通过回调函数方式防止 var 变量提升。

变量提升

ts
const list = []
// eslint-disable-next-line vars-on-top, no-var
for (var i = 0; i < 3; i++) {
  list[i] = function () {
    console.log(i)
  }
}
list[0]()
list[1]()
list[2]()
// 3
// 3
// 3

使用 闭包 防止变量提升

ts
const list = []
// eslint-disable-next-line vars-on-top, no-var
for (var i = 0; i < 3; i++) {
  (function (i) {
    list[i] = function () {
      console.log(i)
    }
  })(i)
}
list[0]()
list[1]()
list[2]()
// 0
// 1
// 2

延迟执行函数

ts
function delay(message, time) {
  return function () {
    setTimeout(() => {
      console.log(message)
    }, time)
  }
}
const fn = delay('Hello World', 1000)
fn()

🔸 尾调用

难度:★☆☆☆☆

ts
function f(x) {
  return g(x)
}

以下情况不属于尾调用

ts
// 情况一
function f(x) {
  const y = g(x)
  return y
}
// 情况二
function f1(x) {
  return g(x) + 1
}
// 情况三
function f2(x) {
  g(x)
}

WARNING

目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。

参考资料:

🔸 高阶函数

难度:★★★★★

柯里化函数

🪡 TODO

🔸 正则

难度:★★★★☆

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions