学习记录-JS篇

81 阅读22分钟

js基本数据类型以及它们的区别

共八种:Undefined、Null、Object、String、Boolean 、Number、BigInt、Symbol

其中BigInt、Symbol是ES6新增类型:

  • Symlbol代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
  • BigInt是一种数字类型的数据,他可以表示任意精度格式的证书,使用BigInt可以安全的 存储和操作大整数,即使这个数已经超出了Number能够白哦是的安全整数范围。

这些数据可以分为原始数据类型和引用数据类型:

  • 栈:原始数据类型(Undefined、Null、Staring、Boolean、Number)
  • 堆:引用数据类型(Object、Array、Function)

两种类型的区别在于存储位置的不同

  • 原始数据类型直接存储在栈(stack)中的简单数据端,占据空寂教案小、大小固定,属于被频繁使用数据,所以放入栈中存储;
  • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:

  • 数据结构中,栈中数据的存取方式为先进后出;
  • 堆是一个有限队列,是按优先级来进行排序的,优先级可以按照大小来规定。 在操作系统中,内存被分为栈区和堆区:
  • 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区内存一般由开发者分配释放,若开发者不复式房,程序结束时可能由垃圾回收机制回收。

数据类型监测的方式有哪些?

  • typeof:判断基本数据类型,数组、对象、null都会被判定为object,其他都判断正确。
  • instanceof:判断引用数据类型,检测某个对象是否是某个构造函数的实例。本质是通过原型链进行判断。instanceof会一直沿着原型链查找,直到找到Constructor.prototype。找不到就返回false

判断规则:

object instanceof Constructor
// 等同于
object.__proto__===Constructor.prototype
// 或者
Object.getPrototypeOf(object)===Constructor.prototype
  • constructor:判断引用数据类型,获取构造函数。construtor可以被修改,null/undefined没有constructor
    变量.constructor===构造函数
  • Object.prototype.toString.call():判断所有数据类型。不受原型链影响。目前最精确的判断方式。
Object.protottype.toString.call(变量)
console.log(Object.prototype.toString.call(123));    // [object Number]
console.log(Object.prototype.toString.call('hi'));   // [object String]
console.log(Object.prototype.toString.call(true));   // [object Boolean]
console.log(Object.prototype.toString.call(null));   // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call([]));     // [object Array]
console.log(Object.prototype.toString.call({}));     // [object Object]
console.log(Object.prototype.toString.call(new Date()));  // [object Date]
console.log(Object.prototype.toString.call(/abc/));  // [object RegExp]
console.log(Object.prototype.toString.call(new Error())); // [object Error]

  • Array.isArray():判断数组。
  • isNaN():判断NaN,无法区分Object和NaN。isNaN()在判断时,会尝试将传入的值转换为数字(Number('123')),然后再判断转换后的值是否为有效数字。注意:[ ]和null通过Number()转化后值为0,判断isNaN的结果是false。如果要精确判断是否是NaN,推荐使用Number.isNaN(),该方法不会做默认number自动转换。

简述JS中的this

this是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this的指向可以通过四种调用模式来判断。

  • 函数调用模式:当一个函数不是一个对象的属性时候,直接作为函数来调用时候,this指向全局对象。
  • 方法调用模式:如果一个函数作为一个对象的方法来调用时,this指向这个对象。
  • 构造器调用模式:如果一个函数用new调用时,函数调用前会新创建一个对象,this指向这个新创建的对象。
  • apply、call和bind调用模式:这三种方法都可以显示的指定调用函数的this指向。
方法特点是否立即执行参数形式返回值
call改变 this 指向,立即执行✅ 是逗号分隔参数函数执行结果
apply改变 this 指向,立即执行✅ 是数组形式参数函数执行结果
bind改变 this 指向,但不立即执行,返回新函数❌ 否逗号分隔参数返回新函数
function sayName(age) {
  console.log(`我叫 ${this.name},今年 ${age} 岁`);
}

const person1 = { name: '张三' };
const person2 = { name: '李四' };
const person3 = { name: '王八' };

sayName.call(person1, 18);   // 我叫 张三,今年 18 岁
sayName.apply(person2, [20]);   // 我叫 李四,今年 20 岁const newFn=sayName.bind(person3, 40);  
newFn()  //我叫 王八,今年 40 岁

箭头函数和普通函数有什么区别

特性普通函数(function)箭头函数(=>)
this 指向运行时决定,谁调用指向谁定义时决定,指向外层作用域的 this
✅ 是否绑定 this可通过 call/apply/bind 改变无法改变 this
✅ argumentsarguments没有 arguments,用 rest 参数替代
✅ 构造函数可作为构造函数 (new)不能作为构造函数 (new)
✅ 原型对象prototype 属性没有 prototype
✅ 使用场景适合复杂逻辑、构造函数等适合回调函数this不变的场景

AMD和CommonJS的区别

CommonJS:CommonJS是一种服务模块规范,最早用于Node.js,同步加载模块。

特性:

  • 使用require()引入模块
  • 使用module.exports导出模块。
  • 同步加载,即等模块加载完才继续执行。

原理:

  • 同步加载,当执行require('./main.js'),Node.js会:
    1. 读取文件。
    2. 执行文件,将module.exports缓存。
    3. 返回缓存的结果。 如果加载的文件很大,页面就会卡死,导致白屏。所以CommonJS不适合浏览器。

AMD:AMD(Asynchronous Module Definition)异步模块定义,主要用于浏览器端,通过require.js提供。

特性:

  • 使用define()定义模块
  • 使用require()加载模块。
  • 异步加载模块,避免浏览器阻塞。

原理:

  • 异步加载。当require(['math.js'])执行行:
    1. 先下载math.js文件。
    2. 下载完成后立即执行回调函数。
    3. 不阻塞页面渲染。

ES6 Module和ComonJS模块的区别:

对比项ES6 Module (ESM)CommonJS (CJS)
引入方式使用 import 关键字使用 require() 方法
导出方式使用 export / export default使用 module.exportsexports
导入时是否静态分析✔ 是静态分析(编译时确定依赖关系)❌ 不是静态分析(运行时加载模块)
是否支持异步加载✔ 原生支持动态 import()❌ 不支持,需要配合其他工具(如 require.ensure
作用域模块内是独立作用域,变量不会污染全局模块内是独立作用域,变量不会污染全局
模块加载时机编译时就确定依赖关系,预加载运行时加载,边执行边加载
this 指向顶层 thisundefined顶层 this 指向 module.exports
是否允许循环引用✔ 支持循环引用,但存在“暂时性死区”✔ 支持循环引用,变量是已导出的对象
模块缓存✔ 会缓存,但动态 import() 可重新加载✔ 会缓存,重复 require 只会加载一次
浏览器支持✔ 原生支持 (现代浏览器)❌ 不支持,需打包工具 (如 Webpack)
使用环境✔ 推荐用于前端和现代 Node.js (>=v14.13.1)✔ Node.js 原生支持 (CommonJS环境)

let、const、var的区别

var:声明的变量为全局变量,存在变量提升(变量可以在声明之前使用。),可重复声明。 let:声明的变量是块级作用域,存在变量提升,但是会出现暂时性死区。即声明之前,该变量不可用。 const:声明的变量是块级作用域,必须设置初始值且不能更改。

new操作符的实现原理

new操作符的主要作用是创建一个新的对象,并且将该对象的原型链指向构造函数的prototype,从而实现继承。

手写new:

function myNew(){
  let newObj=null
  let constructor=Array.prototype.shift.call(arguments)
  let result=null
  
  if(typeof constructor!=='function'){
      console.error('type error')
      return 
  }
  
  newObj=Object.create(constructor.prototype)
  result=constructor.apply(newObj,arguments)
  
  let flag=result && typeof result ==='object' || typeof result==='function')
  return flag?result:newObj
}

数组有哪些原生方法

  • 转换字符串:toString(),toLocaleString(),join():
    • tostring返回一个字符串表示指定的数组以及元素
    • toLocaleString返回一个字符串表示数组中的所有元素。每个元素通过调用它们自己的toLocaleString方法转换为字符串,并且使用特定语言环境的字符串分隔开。
  • 尾部操作:pop(),push():
    • pop删除数组最后一个元素并返回该元素的值
    • push在数组最后位置增加一个或多个元素,都会改变原数组长度。
  • 头部操作:shift(),unshift(),
    • shift删除第一个元素并返回第一个元素的值,unshift在头部添加一个元素并返回数组的新长度,都会改变原数组长度。
  • 反转排序:reverse(),sort(),concat():
    • reverse方法就地反正数组中的元素,并返回同一数组的引用。数组的第一个元素会变成最后一个,最后一个变成第一个。数组中的元素顺序将被反正,变为与之前相反的方向。 要在不改变原始数组的情况下反转数组中的元素,使用toReversed()
    • sort就地对数组的元素进行排序,并返回对相同数组的引用。默认排序是将元素转换为字符串,然后按照它们的UTF-16码元值升序排序。 要在不改变原始数组的情况下排序数组中的元素,使用toSorted()
    • concat用于合并两个或多个数组。不会改变现有数组而是返回一个新数组。
  • 截取:slice()
    • slice返回一个新的数组对象,这一对象是一个由start和end决定的原数组的浅拷贝(包括start,不包括end),其中start和end代表了数组元素的所有。原始数组不会被改变。
  • 索引查找:findIndex(),indexOf(),lastIndexOf()
    • findIndex:返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1
    • indexOf:返回数组中第一次出现给定元素的下标,如果不存在则返回-1;
    • lastIndexOf:返回数组中给定元素最后一次出现的索引,如果不存在则返回-1.该安放方法从fromIndex开始向前搜索数组。
  • 迭代:every(),some(),filter(),map(),forEach()
    • every:测试一个数组内的所有元素是否都能通过指定函数的测试。返回一个布尔值
    • some:测试一个数组中是否至少由一个元素通过了由提供的函数实现的测。如果在数组中找到一个元素使得提供的函数返回true,则返回true,否则返回false。不会修改原数组。
    • filter:创建给定数组一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素。
    • map:传教一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。
    • forEach:对数组的每个元素执行一次给定的函数。
  • 递归:reduce(),reduceRight()
    • reduce:对数组中的每个元素按序执行一个提供的reducer函数,每一次运行reducer会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
    • reduceRight:对累加器(accumulator)和数组的每个值(按从右到左的顺序)应用一个函数,并使其称为单个值。
const array1 = [1, 2, 3, 4];

// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
  (accumulator, currentValue) => accumulator + currentValue,
  initialValue,
);

//accumulator:上一次调用cb的结果,指定了initialValue为accumulator的初始值。
//currentValue当前元素的值。

console.log(sumWithInitial);
// Expected output: 10

for in 和for of 的区别

for in:遍历对象。返回对象中所有可枚举的属性包括原型链上的属性。 for of:遍历数组和类数组。of前边的是数组中的键值

Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};

const iterable = [3, 5, 7];
iterable.foo = "hello";

for (const i in iterable) {
  console.log(i);
}
// "0"、"1"、"2"、"foo"、"arrCustom"、"objCustom"

for (const i in iterable) {
  if (Object.hasOwn(iterable, i)) {
    console.log(i);
  }
}
// "0" "1" "2" "foo"

for (const i of iterable) {
  console.log(i);
}
// 3 5 7

原型和原型链

  • 原型(prototype):
    1. 每个函数都有一个内置的prototype属性,用于存放该函数的共享属性和方法。
    2. 每个对象都有一个__proto__属性,指向其构造函数的prototype
    3. 实例对象可以通过__proto__找到它的原型对象,从而实现原型继承。
  • 原型链:原型链是一种查找机制。当访问对象的属性或方法时,如果对象本身没有该属性/方法,就会沿着__proto__向上查找,直到:
    • 找到该属性/方法并执行;
    • 或者找到Object.prototype,最后返回null。
p1 ---> Person.prototype ---> Object.prototype ---> null
  • Object.prototype.__proto__的值是null,表示原型链到此结束。
  • 所有对象的原型最终都指向Object.prototype

手写原型链继承

function Animal(){
    this.type='动物'
}
Animal.prototype.sayTye=function(){
    console.log(this.type)
}
function Dog(name){
    this.name=name
}
//继承原型链
Dog.prototype=new Animal()
Dog.prototype.constructor=Dog

const dog=new Dog('狗')
console.log(dog.type)   //动物
dog.sayType()       //动物

如何获得对象非原型链上的属性?

  • hasOwnProperty():方法返回一个布尔值,表示对象自有属性(而不是继承来的属性)中是否具有指定的属性。

    语法:object1.hasOwnProperty("property1")

  • hasOwn:旨在取代 [Object.prototype.hasOwnProperty()]

    语法:Object.hasOwn(object1, "prop")

在支持 [Object.hasOwn]的浏览器中,建议使用 [Object.hasOwn()],而非 hasOwnProperty()

对闭包,作用域(链),执行上下文的理解。

闭包:闭包是指有权访问另一个函数作用域中变量的函数。 创建闭包最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。

闭包的用途:

  1. 创建私有变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量。
  2. 控制变量不被回收。闭包能够使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用。

作用域、作用链的理解:

  • 作用域:变量和函数的可访问范围,决定了变量在哪些地方能被访问到。

    JS中常见的作用域类型:

    全局作用域:

    • 在任何地方都能访问的变量,属于全局作用域;
    • 挂载在window对象上(浏览器环境)

    函数作用域:

    • 函数内部定义的变量,只能在函数内部访问,外部无法访问;
    • 函数执行结束,变量就会被销毁。

    块级作用域:

    • 使用let/const声明的变量,只能在{}内部访问;
    • 不会挂载到window上;
    • 避免变量污染。

    词法作用域:

    • 函数的作用域在定义时就决定了,而不是在调用时决定;
    • 不管函数在什么地方调用,它的作用域取决于函数定义的位置。
  • 作用域链:当访问变量时,JS会沿着作用域链向上查找,直到找到变量或者到达全局作用域。

执行上下文 执行上下文就是JS代码执行时的环境,用于存储变量、函数、对象以及this指向等信息。JS代码执行必须依赖执行上下文。

  • 代码开始执行->生成执行上下文
  • 结束执行->销毁执行上下文

执行上下文的生命周期

  • 创建阶段: 当函数被调用时,JS引擎会先创建执行上下文,并进行如下操作:
    1. 创建变量对象(Variable Object,VO)
      • 把变量、函数声明提前创建并赋值
      • 变量声明提升,函数声明提升
    2. 创建作用域
      • 将当前函数的作用域链与父级作用域链关联
      • 通过作用域链查找变量
    3. 确定this指向
      • 在全局执行上下文中,this指向window
      • 在函数执行上下文中:
        • 普通函数:this指向调用者
        • 箭头函数:this指向定义时的父作用域
  • 执行阶段
    • 开始执行代码;
    • 将变量赋值、调用函数
    • 按作用域链查找变量
  • 回收阶段:
    • 函数执行完毕,上下文被销毁;
    • 局部变量被销毁
    • 闭包除外,闭包中的变量依然存在

总结:在执行js代码之前,需要先解析代码。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明和可使用。这一步执行完了,才开始正式的执行程序。

实现call、apply、bind函数

核心原理

  • 将函数的this指向对象;
  • 立即执行call、aplly,或返回函数bind
  • apply支持数组传参,call不支持
  • bind返回一个新函数,不会立即执行

手写call

Function.prototype.myCall=function(context,...args){
    //   判断context是否传入,如果没有则指向window
    context=context||winow
    //   将当前函数挂载到context对象上
    //   this指向调用myCall的函数
    context.fn=this
    
    //   执行函数
    const result=context.fn(...args)
    
    //   删除临时属性
    delete context.fn
    
    //   返回执行结果
    return result
}

手写apply

Function.prototype.myApply=function(context,args){
    context=context||window
    context.fn=this
    
    let result;
    if(args){
        result=context.fn(...args)
    }else{
        result=context.fn()
    }
    
    delete context.fn
    
    return result

}

手写bind

Function.prototype.myBind=function(context,...args){
    const fn=this
    
  function boundFunc(...innerArgs){
      if(this instanceof boundFunc){
          return new fn(...args,...innerArgs)
      }
      return fn.apply(context,[...args,...innerArgs])
  }
  
  boundFunc.prototype=Object.create(fn.prototype)
  return boundFunc
}

总结:

  1. call:立即执行,逗号传参;
  2. apply:立即执行,数组传参;
  3. bind:返回新函数,支持 new。

异步编程的实现方式

异步编程就是不阻塞主线程,通过回调、Promise、async/await等方式处理异步任务,使代码执行时候可以同时进行多个任务。

Generator:ES6半异步解决方案,通过yield关键字控制函数暂停/继续执行,实现异步编程或按需生成数据。

特点:

  • 使用 function* 定义;
  • 内部使用 yield 关键字暂停执行;
  • 通过 next() 恢复执行;
  • 返回一个迭代器对象(Iterator)
function* gen() {
  console.log('开始');
  yield 1;
  console.log('中间');
  yield 2;
  console.log('结束');
}
const g = gen();

console.log(g.next());  // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next());// { value: undefined, done: true }


  • Generator并不会立即执行函数

  • 需要手动调用next()

  • 遇到yield就暂停,等待下一次next执行

Async/Await:ES7异步方案,代码同步化,提升可读性 Promise:异步解决方案,避免回调地狱,可链式调用 回调函数:最早的异步处理方式,容易形成回调地狱

setTimeout、promise、Async/await的区别

  • setTimeout:宏任务,浏览器提供的api,将回到函数瑞图宏任务队列,等待主线程空闲才执行
  • promise:微任务
    • promise是js内部的异步机制
    • 会将.then()或.catch()中的回调推入微任务队列
    • 微任务优先级>宏任务
  • async/await:本质是promise的语法糖,微任务

promise.all和promise.rase的区别和使用场景

  • all:将多个promise实例包装成一个新的promise实例,同时,成功和失败的返回值是不同,成功时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。 适用于多个promise需要同步获得结果
  • rase:返回多个promise中结果获得最快的结果,无论成功失败。适用于模拟并发池做promise数量并发限制。

哪些情况会导致内存泄漏

  • 全局变量未释放:定义在window上变量无法释放,占用内存。尽量避免使用全局变量
  • 未清理的定时器:setTimeout/setInterval未清理导致内存泄漏。即使清理定时器。
  • 闭包未释放:闭包引用外部变量,导致无法释放。避免长时间占用内存
  • DOM引用未释放:DOM被移除但JS仍然引用。组件卸载时清理DOM引用
  • 事件监听未销毁:添加的事件未移除,导致内容泄漏。removeEventListerner及时移除监听
useEffect(() => {
  const dom = document.getElementById('box');
  
  return () => {
    dom = null;
  };
}, []);

ES6有哪些新特性

  1. 箭头函数

  2. 解构赋值

  3. 模板字符串

  4. promise

  5. symbol:symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值,不能与其他数据类型进行运算

  6. 新的变量声明方式 let和const

  7. 模块化:es6新增了模块化,根据功能封装模块,通过import导入,然后通过export导出也可以使用export default导出

  8. for of循环:用于遍历可迭代对象(数组类数组,Map,Set)中的元素

  9. 扩展运算符:...展开数组或对象

  10. Map和Set,引入了两种新的数据结构,分别用于存储键值对和唯一值。

  11. proxy:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

    语法:var objProxy=new Proxy(obj,handler)

ver obj=new Proxy({},{
    get:function(){
        console.log('getting ${propKey}')
        return Relfect.get(target,propKey,receiver)
    },
    set:function(target,propKey,value,receiver){
        console.log('setting ${propKey}')
        return Reflect.set(target,propKey,value,receiver)
    }
})
  1. 类(Class):引入了面向对象编程中类的概念
  2. 默认参数:在定义函数时可以给参数设置默认值

匿名函数的典型应用场景是什么

应用场景特点示例
立即执行函数(IIFE)创建独立作用域,防止变量污染(() => {})()
回调函数异步回调、事件监听、setTimeout、Promise 等arr.forEach(() => {})
闭包持久化变量,防止变量污染return () => {}
高阶函数参数将函数作为参数传递arr.map(() => {})
事件处理函数DOM 事件、React事件button.onclick = () => {}
函数式编程提升代码简洁性,避免命名污染const fn = () => {}

函数柯里化

柯里化函数(Currying Function)是一种高阶函数,它将一个接收多个参数的函数转换成多个只接收一个参数的函数。本质就是通过闭包实现参数的缓存。

优点:

  1. 提升代码的复用性
    • 每次调用只需要传入一个参数
    • 可延迟传参,避免重复传参
  2. 提升代码的可维护性:
    • 通过函数拆分,保持函数单一职责
    • 更易于测试和维护
  3. 常用于函数组合
    • React/Redux等状态管理中大量使用
    • 比如:Redux的dispatch
function checkPermission(role, action) {
  if (role === 'admin') {
    return action === 'delete';
  }
}

console.log(checkPermission('admin', 'delete')); // true

// 改写成柯里化

function checkPermission(role){
    return function action(action){
         return role === 'admin'&& action === 'delete';
    }
}
const isAdmin=checkPermission('admin')
console.log(isAdmin('delete'))

事件循环

事件循环是js中处理异步操作的机制。js是单线程的,通过栈和队列的配合,执行同步和异步任务。

任务队列的种类

JavaScript 中的任务队列根据任务的不同,分为以下几类:

  1. 宏任务(Macro Tasks)

    • 主要包括 setTimeoutsetIntervalsetImmediate(Node.js)、I/O 操作等。
    • 宏任务队列中的任务,会在每次事件循环时被取出执行。
  2. 微任务(Micro Tasks)

    • 包括 Promise 的回调、process.nextTick(Node.js)、MutationObserver 等。
    • 微任务会在当前执行栈的代码执行完成后,紧接着执行,而优先级高于宏任务

事件循环的执行顺序:

  1. 执行栈中执行同步任务。
  2. 执行完同步任务后,先执行所有的微任务。
  3. 执行完微任务后,事件循环会开始执行宏任务队列中的任务。
console.log('Start');

setTimeout(() => {
  console.log('setTimeout 1');
  Promise.resolve().then(() => {
    console.log('Promise 2');
  });
}, 0);

setTimeout(() => {
  console.log('setTimeout 2');
  Promise.resolve().then(() => {
    console.log('Promise 3');
  });
}, 0);

Promise.resolve().then(() => {
  console.log('Promise 1');
});

console.log('End');
// 输出

Start
End
Promise 1
setTimeout 1
Promise 2
setTimeout 2
Promise 3

解释:

  1. 执行同步代码 console.log('Start')console.log('End')
  2. 第一个 Promise.resolve().then(...) 是微任务,在同步代码执行完后立即执行。
  3. 两个 setTimeout 是宏任务,它们在同步代码和微任务执行完后依次执行。
  4. 每个 setTimeout 执行时会触发一个微任务队列,因此每个 Promise.resolve().then(...) 都会被依次执行。

设计模式

常见的JS设计模式包括创建型模式、结构型模式和行为型模式。

  • 创建型模式:专注于对象的创建,尤其是如何创建对象而不直接暴露对象创建的逻辑。

    1. 单例模式(Singleton Pattern)
      • 保证一个类只有一个实例,并提供一个全局访问点。
      • 适用场景:需要一个共享实例(如配置管理器、数据库连接等)
    2. 工厂模式
      • 定义一个创建对象的接口,但由子类决定实例化哪一个类
      • 适用场景:当创建对象的过程复杂且需要动态生成时 3. 构造器模式
      • 使用构造函数创建对象,并初始化对象属性。
      • 适用场景:需要通过初始化数据来创建对象时。
  • 结构型模式:结构性模式关注如何将类或对象组合成更大的结构,目的是通过减少类或对象之间的依赖关系来优化程序设计。

    1. 适配器模式
      • 使得原本接口不兼容的类能够合作工作。
      • 适用场景:需要将一个接口转换成零一个接口时。
    2. 装饰者模式:
      • 动态的给对象添加职责,而不影响其他对象的行为。
      • 适用场景:需要动态的为对象增加功能时
  • 行为型模式(Behavioral Patterns):行为型模式专注于对象之间的通信和职责分配,特别是如何通过简单的通信机制来实现复杂行为。

    1. 观察者模式(Observer Pattern)

      • 允许一个对象(主题)通知其依赖对象(观察者)发生了变化。
      • 适用场景:实现类似事件机制的功能,或者一个对象状态变化时,通知多个观察者。
    2. 策略模式(Strategy Pattern)

      • 定义一系列算法,并使得它们可以互换,客户端可以根据需要选择不同的算法。
      • 适用场景:当有多个算法需要应用到某个对象时,可以使用策略模式。
    3. 命令模式(Command Pattern)

      • 将请求封装为对象,以便使用不同的请求、队列或日志请求。
      • 适用场景:实现操作的撤销和恢复

发布-订阅模式

发布-订阅模式是一种解耦的设计模式,发布者不直接与订阅者交互,而是通过一个事件中心进行消息传递。