honghu

JS 面试题整理

JS

this 相关

  this 关键字是函数运行时自动生成的一个内部对象, 只能在函数内部使用, 总是指向调用他的对象
  1. new 操作符具体干了什么

    1. 创建一个新的对象
    2. 将对象与构造函数通过原型链连接起来
    3. 将构造函数中的 this 指向新对象
    4. 返回这个新的对象
  2. this 的绑定规则和各种情况下的指向

    • this 总是指向函数的直接调用者, 在函数执行的时候才能确定 this 的指向

    • this 的绑定规则: (优先级: new > 显式绑定 > 隐式绑定 > 默认绑定)

      1. 默认绑定 --> 在全局函数中定义函数内部使用 this, this 指向 window
      2. 隐式绑定 --> 将函数作为对象的某个方法调用时, this 指向这个方法的上一级对象
      3. 显式绑定 --> 通过 call, bind, apply 更改 this 指向
      4. new 绑定 --> 通过 new 生成一个实例对象, this 指向这个实例对象
    • this 在各种情况的指向

      1. 全局环境 --> 指向 window
      2. call, bind, apply 函数 --> 指向第一个参数
      3. 构造函数调用 --> 指向 new 出来的实例对象
      4. 箭头函数 --> 函数体内的 this 对象, 就是定义时所在的对象
  3. 箭头函数

     箭头函数的 this 对象, 永远指向其上下文的 this, 任何方法都改变不了其指向, 普通函数的 this 指向调用他的那个对象
    
    • 优点: 更简洁; 不绑定 this , 会捕获上下文的 this 值作为自己的 this 值;

    • 特点:

      1. 箭头函数是匿名函数, 不能作为构造函数, 不可以使用 new 命令, 所以也没有原型属性 prototype;
      2. 箭头函数不能使用 yield 关键字, 所以箭头函数不能用作 Generator 函数;
    • 意外

      1. 绑定事件监听(this 错误的指向了 window)

        const button = document.getElementById("btn");
        button.addEventListener("click", () => {
        	console.log(this === window);
        	this.innerHTML = "clicked button";
        });
        
      2. 在原型上添加方法(this 错误的指向了 window)

  4. call apply bind 的作用和区别

    • 第一个参数都是 this 的指向
    • apply 的第二个参数是一个数组, call 和 bind 的第二个参数及以后的参数需要一个一个传
    • apply 和 call 绑定 this 后会立即调用, bind 不会, bind 会返回一个新函数
  5. 继承

原型和原型链

每个对象都会在其内部初始化一个属性, 就是 prototype(原型); 当我们访问一个对象的属性时, 如果这个对象内部不存在这个属性, 那么他就会去 prototype 里找这个属性, 这个 prototype 又会有自己的 prototype , 一直找下去, 一直检索到 Object 内建对象。这就是我们平时所说的原型链。

事件委托/事件代理

利用事件冒泡原理, 让自己所触发的事件由他的父级代替执行 优点:

  • 减少事件注册, 提高性能
  • 新增子元素不用重复绑定事件

缺点: Focus、blur 等事件没有冒泡机制, 所以无法事件委托

闭包以及应用场景

闭包: 一个函数内套一个函数并且返回了这个内部函数, 内部函数可以使用外部函数的参数

优点:

  1. 封装私有的方法和变量, 避免全局变量的污染;
  2. 延长变量生命周期;

应用:

  1. 回调函数
  2. 计时器

缺点: 闭包会使得函数中的变量都被保存在内存中, 内存消耗很大, 对性能有很大的负面影响, 而且参数和变量不会被垃圾回收机制回收, 可能导致内存泄露。 解决: 在退出函数之前, 将不使用的局部变量全部删除;

(function () {
	var a = 1;
	function add() {
		const b = 2;
		let sum = a + b;
		console.log(sum); // 3
	}
	add();
})();

柯里化

把接受多个参数的函数转化成只接受一个单一参数的函数 优点:

  1. 让纯函数更纯, 每次接收一个函数, 松散解耦
  2. 惰性执行
// 非柯里化
var add = function (x, y) {
	return x + y;
};
add(3, 4); // 7
// 柯里化
var add2 = function (x) {
	return function (y) {
		return x + y;
	};
};
add(3)(4);

高阶函数

接受其他函数作为参数, 或返回其他函数的函数

function foo() {
	var a = 2;
	function bar() {
		console.log(a);
	}
	return bar;
}
foo(); // 2

Promise

  1. 定义 --> 异步编程的一种解决方案, 解决了回调地狱的问题
  2. 状态
    • pending --> 初始状态
    • fulfilled --> 成功的操作
    • rejected --> 失败的操作
  3. 特点:
    • 外界不会影响到内部的状态;
    • 状态一旦改变就不会再改变;
  4. 缺点:
    • 如果没有回调函数, 外界得不到异步操作的返回结果
    • 定义之后立即执行, 无法打断, 且无法得知进行进度
  5. Promise.all() --> 将多个 promise 实例包装成一个 promise, 多个实例全部返回成功, promise 的状态才为成功, 一个失败则为失败
  6. Promise.race() --> 意为赛跑的意思, 也就是数组中的任务哪个获取的块, 就返回哪个, 不管结果本身是成功还是失败, 一般用来和定时器绑定做超时提示

异步编程的实现方式

  1. 回调函数 --> 优点: 简单、容易理解; 缺点: 不利于维护, 代码耦合高
  2. 事件监听
  3. 观察者模式(发布/订阅)
  4. Promise 对象
  5. Generator 函数
  6. async 函数

ES6

  • map sort reduce
  • 模板字符串
  • 展开运算符
  • 解构赋值
  • 箭头函数
  • 提供了原生的 Promise 对象
  • 增加了 let 和 const 命令, 用来声明变量
  • 引入 module 模块的概念(export 和 import)
  • for-of

ES7/8/9/10/11/12

  1. ES7

    • includes 方法
    • 幂运算 **
      • Math.pow(2, 3) // 8
      • 2 ** 3 // 8
  2. ES8

    • async await -> promise 的语法糖
    • Object.entries() -> 返回一个二维数组, 包含对象的键值对
    • Object.keys() -> 返回一个数组, 包含对象的键
    • Object.values() -> 返回一个数组, 包含对象的值
    • Object.getOwnPropertyDescriptors() -> 返回一个数组, 包含对象的属性描述符
      let obj = { a: 1, b: 2 };
      Object.getOwnPropertyDescriptors(obj); // [a: {configurable: true, enumerable: true, value: 1, writable: true}, b: {configurable: true, enumerable: true, value: 2, writable: true}]
      
    • padStart(targetLength, padString | 空格) -> 向字符串左侧填充指定的字符直到达到指定长度为止, 超出会被截断, 返回一个新的数组
    • padEnd(targetLength, padString) -> 向字符串右侧填充指定的字符直到达到指定长度为止
  3. ES9

    • async iterators 异步迭代器, asyncIterator 对象的 next() 方法返回一个 promise
    • Object rest properties -> 可以在对象中使用... 运算符, 展开剩余属性值
    let test = {
    	a: 1,
    	b: 2,
    	c: 3,
    	d: 4,
    };
    
    let { a, b, ...rest } = test;
    
    console.log(a); // 1
    console.log(b); // 2
    console.log(rest); // {c: 3, d: 4}
    
    • Object spread properties
    let test = {
    	a: 1,
    	b: 2,
    };
    let result = { c: 3, ...test };
    console.log(result); // {c: 3, a: 1, b: 2}
    
    • Promise.prototype.finally -> 在 Promise 结束的时候, 不管是结果是 resolved 还是 rejected, 都会调用 finally 中的方法
    const promise = new Promise((resolve, reject) => {
    	resolve("resolved");
    	reject("rejectd");
    });
    
    promise
    	.then((res) => {
    		console.log(res);
    	})
    	.finally(() => {
    		console.log("finally");
    	});
    
  4. ES10

    • flat() 方法将数组扁平化为一维数组
    • Object.fromEntries() -> 将键值对转换成对象
    let map = new Map([
    	["a", 1],
    	["b", 2],
    ]);
    let mapToObj = Object.fromEntries(map);
    console.log(mapToObj); // {a: 1, b: 2}
    
    let arr = [
    	["a", 1],
    	["b", 2],
    ];
    let arrToObj = Object.fromEntries(arr);
    console.log(arrToObj); // {a: 1, b: 2}
    
    let obj = { a: 1, b: 2 };
    let newObj = Object.fromEntries(
    	Object.entries(obj).map(([key, val]) => [key, val * 2])
    );
    console.log(newObj); // {a: 2, b: 4}
    
    • trimStart/trimEnd -> 删除字符串头部或尾部的空格, 返回删除后的字符串 别名: trimLeft/trimRight
  5. ES11

    • 空值合并操作符(??) -> 如果左边的值为 null 或 undefined, 则返回右边的值 , 否则返回左边的值
    undefined ?? "foo"; // 'foo'
    null ?? "foo"; // 'foo'
    "foo" ?? "bar"; // 'foo'
    // 逻辑或操作符(||) -> 会在左侧操作数为假值时返回右侧操作数, 在左侧操作数为 0、''、NaN、false 时返回右侧操作数
    0 || "foo"; // 'foo'
    "" || "foo"; // 'foo'
    NaN || "foo"; // 'foo'
    false || "foo"; // 'foo'
    
    • 可选链操作符(?) -> 允许读取位于连接对象链深处的属性的值, 而不必明确验证链中的每个引用都是否有效
    • BigInt -> 类似于 JavaScript 中的 Number, 创建比 2^53 - 1(Number 可创建的最大数字) 更大的整数, 并且可以进行精确计算(解决 number 无法进行大数运算的问题)
      1. BigInt 不能用于 Math 对象中的方法;
      2. BigInt 不能与任何 Number 实例混合运算(但可以比较), 两者必须转换成同一种类型。但是需要注意, BigInt 在转换成 Number 时可能会丢失精度。
      3. 当使用 BigInt 时, 带小数的运算会被向下取整
      4. BigInt 和 Number 不是严格相等, 但是宽松相等
    // 在一个整数字面量后面加 n, 例如 10n
    
  6. ES12

    • 逻辑运算符和赋值表达式(&&=, ||=, ??=)
    // &&=  -> x &&= y 等价于 x && (x=y)
    let a = 1;
    let b = 0;
    
    a &&= 2;
    console.log(a); // 2
    
    b &&= 2;
    console.log(b); // 0
    // ||=   -> x ||= y 等价于 x || (x=y)
    // ??=   -> x??= y 等价于 x?? (x=y)
    
    • replaceAll(pattern, replacement) -> 字符串方法: 返回一个新字符串, 字符串中所有满足 pattern 的部分都会被 replacement 替换掉。原字符串保持不变。
    // pattern 可以是一个字符串或 RegExp
    "aabbcc".replaceAll("b", "."); // 'aa..cc'
    // pattern 使用正则表达式搜索值时, 必须是全局的:
    "aabbcc".replaceAll(/b/, "."); // TypeError: replaceAll must be called with a global RegExp
    "aabbcc".replaceAll(/b/g, "."); // "aa..cc"
    
    • 数字分隔符
    • Promise.any

let,const 和 var 的区别

  • 变量提升: var 可以, let const 也有变量提升, 但在声明之前不可使用, 不可使用的代码区称为暂时性死区(TDZ)
  • 变量更改: var 和 let 可以, const 常量不可以 const 并不是让变量的值变得不可变, 而是让变量指向的内存地址不可变, 也就是说使用 const 声明的变量不能被重新赋值, 但是其所指向的内存中的数据是可以被修改的;
  • 作用域: var 是函数作用域, let 和 const 是块级作用域

变量提升

在生成执行环境时, 会有两个阶段: 创建阶段和执行阶段 JS 解释器在创建阶段, 会找出需要提升的变量和函数, 并提前在内存中开辟好空间, 函数整个存⼊内存中, 变量提前声明并赋值为 undefined 到了代码执行阶段, 就可以直接使用

防抖和节流

一些浏览器事件如 resize scroll 等事件触发频率太高, 极大浪费资源、降低性能, 防抖和节流就是为了解决这个问题;

  1. 防抖(debounce) --> n 秒后再执行该事件, 若在 n 秒内重复触发, 则重新计时 应用: 搜索框搜索输入、手机号/邮箱验证 防抖:关注的是操作之间的间隔, 这个间隔时间大于等于 wait, 则执行一次。

  2. 节流(throttle) --> n 秒内只运行一次, 若在 n 秒内重复触发, 也只有一次生效 应用: 滚动加载/加载更多 节流:关注的是操作的过程, 给这个高频触发的过程添加限制, 让它固定 wait 时间只能执行一次。

函数式编程

主要的三种编程范式: 命令式编程、声明式编程、函数式编程 函数式编程更强调程序执行的结果而非过程, 利用简单的单元组合逐层实现复杂的运算, 而非设计一个复杂的执行过程 优点:

- 更好的管理状态(函数式编程宗旨无状态或更少的状态);
- 更简单的复用(纯函数的固定输入固定输出);
- 减少代码量, 提高可维护性

缺点:

- 资源占用
- 函数式编程中常用递归操作, 存在递归陷阱
  1. 纯函数: 纯函数= 无状态+数据不可变 概念:

    1. 只要是同样的输入, 必定得到同样的输出;

    2. 必须遵守以下约束:

      • 不得改写参数数据;
      • 不会产生任何副作用, 如网络请求;
      • 不能调用 Date.now()或 Math.randow()等不纯的方法;
  2. 高阶函数

  3. 柯里化

断点续传

在上传或下载时, 将上传或下载任务人为的划分几个部分, 每一个部分采用一个线程进行上传或下载; 当遇到网络故障时, 可以从已下载或已上传的部分继续进行任务, 以节省时间, 提高速度

  • 原理: 将上传文件在服务器写成临时文件, 全部完成后再重命名为正式文件即可。中断后可根据临时文件大小计算上传偏移量, 从此位置继续上传
  • 实现方式: 当中断后重新上传时, 前端将唯一标识符发送给后端, 后端查找此任务是否已存在, 并返回相应的文件大小, 前端根据已上传进度继续上传

单点登录(sso/single sign on)

多个应用系统中, 用户只需要登录一次就可以访问所有相互信任的应用系统

  • 前端在不同域名下实现单点登录的方式: 将 token 保存在 localstorage 中, 每次发送接口请求时携带此 token; 前端拿到 token 后, 不光可以存在本地的 localstorage 中, 还可以通过**iframe + postMessage()**写到多个其他域的 localstorage 中, 同样支持跨域
// 获取 token
var token = result.data.token;
// 动态创建一个不可见的 iframe,  在 iframe 中加载一个跨域HTML
var iframe = document.createElement("iframe");
iframe.src = "http://app1.com/localstorage.html";
document.body.append(iframe);
// 使用 postMessage() 方法将 token 传递给 iframe
setTimeout(function () {
	iframe.contentWindow.postMessage(token, "http://app1.com");
}, 4000);
setTimeout(function () {
	iframe.remove();
}, 6000);
// 在这个iframe所加载的HTML中绑定一个事件监听器, 当事件被触发时,
// 把接收到的 token 数据写入 localStorage
window.addEventListener(
	"message",
	function (event) {
		localStorage.setItem("token", event.data);
	},
	false
);

递归

在函数定义中使用函数自身的函数

  • 应用场景: 数组求和、数组扁平化、数组对象格式化

js 原生方法

  1. 字符串方法

    • trim

    • split --> 把字符串按照指定的分割符, 拆分成数组里的每一项

    • slice

    • substring

    • indexOf

    • substr

    • toUpperCase, toLoweCase

    • chatAt

    • includes

    • concat

        "12+23+34".split("+"); --> [12,23,34]
      
  2. 数组方法

    • concat --> 添加任意数量元素到数组末尾, 不改变原数组, 返回新数组
    • push --> 添加元素到数组末尾
    • shift 删除第一项
    • splice --> 删除数组指定位置的元素, 传入开始位置和删除数量
    • unshift --> 添加元素到数组开头
    • pop 删除最后一项
      // 第二个参数不传默认截取到末尾
      [1, 2, 3].slice(1, 2) --> [1]
    
    • slice --> 截取数组指定位置的元素, 传入开始位置和结束位置, 不改变原数组, 返回新数组
      // 第二个参数不传默认截取到末尾
      [1, 2, 3].slice(1, 2) --> [2, 3]
    
    • indexOf
    • includes
    • find --> 返回第一个匹配的元素
    • reverse --> 反转数组
    • sort --> 排序
    • join --> 传入分隔符, 转换数组
      ["a", "b", "c"].join(",") --> "a,b,c"
    
    • some
    • every
    • filter --> 过滤
    • map --> 循环-有返回值
    • forEach --> 循环-无返回值
    • flat --> 数组扁平化
    • reduce --> 对数组中的每个元素执行一个自定义函数, 并将其结果汇总

数据类型及判断

  1. js 数据类型

    • 基础类型 number, string, boolean, undefined, null, symbol
    • 复杂类型(引用类型) object, array, function
  2. 几种判断方式 基础类型用 typeof 判断, 引用数据类型用 constructorObject.prototype.toString.call(target) 判断

    • typeof --> 对于基本类型, 除 null 以外, 均可以返回正确的结果(Null 返回 object); 对于引用类型, 除 function 以外, 一律返回 object 类型; 可以有效判断: number, string, boolean, undefined, symbol, object, function

        // 正确判断:
        typeof 123; // number
        typeof "123"; // string
        typeof true; // boolean
        typeof undefined; // undefined
        typeof Symbol(); // symbol
        typeof {}; // object
        typeof func1(){}; // function
        // 错误判断:
        typeof null; // object
      
    • constructor(原型属性) --> 通过原型的 constructor 属性判断类型, null 和 undefined 没有 constructor,所以不能判断

      const data = "";
      data.constructor; // 可以判断 number, string, boolean, symbol, object, function, 不能判断 null, undefined
      
    • toString --> Object 的原型方法, 可以正确判断基本类型和复杂类型

      const data = "";
      Object.prototype.toString.call(data); // 可以判断 number, string, boolean, undefined, null, symbol, object, array, function
      
    • instanceof --> 是用来判断 A 是否为 B 的实例, 检测的是原型, 不能判断一个对象实例具体属于哪种类型; 由于数组也是对象, 因此 arr instanceof Object 也为 true; 可以准确判断复杂数据类型, 对于基础数据类型的判断不准确

    • 通用写法:

    const getType = (obj) => {
    	let type = typeof obj;
    	if (type !== "object") {
    		return type;
    	}
    	return Object.prototype.toString
    		.call(obj)
    		.repalce(/^\[Object (\S+)\]$/, "$1");
    };
    
  3. 如何判断对象 --> Object.prototype.toString.call(obj)

  4. 如何判断数组 --> constructor, Object.prototype.toString.call(arr), isArray

  5. 如何判断空 --> !value && typeOf(value)!== undefined && value !== 0

  6. 如何判断对象相等 --> 用 JSON.stringify() 转换为字符串后再判断

  7. 类型转换机制

    • 显式转换 --> Number(), toString(), parseInt(), Boolean();
    • 隐式转换 --> 比较运算符: == != > < if while 算数运算符: + - * / %

堆栈

  • 栈: 基础数据类型 --> 占据空间小 、大小固定, 属于被频繁使用数据, 所以放入栈中存储 自动分配相对固定大小的内存空间, 并由系统自动释放, 内存可以及时回收
  • 堆: 复杂数据类型(引用数据类型) --> 占据空间大 、大小不固定, 如果存储在栈中, 将会影响程序运行的性能; 引用数据类型在栈中存储了指针, 该指针指向堆中的实体 动态分配内存, 内存大小不一, 也不会自动释放;
  1. 深拷贝和浅拷贝

    • 浅拷贝: 基本类型拷贝数据, 引用类型拷贝引用地址/指针 方法: Object.assign(), Array.prototype.slice(), Array.prototype.concat(), 扩展运算符实现的复制

    • 深拷贝: 开辟一个新的栈, 两个属性完全相同但对应两个不同的引用地址 方法: _.cloneDeep(), jQuery.extend(), JSON.stringify() JSON.stringify() 弊端:会忽略 undefined, symbol 和函数

  2. 手写深拷贝

数组和对象的遍历方式

  1. for in: 需要分析出 array 的每个属性, 这个操作性能开销很大; 用在 key 已知的数组上是非常不划算的。所以尽量不要用 for-in , 除非你不清楚要处理哪些属性, 例如 JSON 对象这样的情况;
  2. for: 循环每进行一次, 就要检查一下数组长度。读取属性(数组长度)要比读局部变量慢, 尤其是当 array 里存放的都是 DOM 元素, 因为每次读取都会扫描一遍页面上的选择器相关元素, 速度会大大降低;
  3. forEach: 无法遍历对象, 无返回值;

常用方法

  1. 去重

    1. ES6 set 去重:

      function unique(arr) {
      	return Array.from(new Set(arr));
      }
      var arr = [
      	1,
      	1,
      	"true",
      	"true",
      	true,
      	true,
      	15,
      	15,
      	false,
      	false,
      	undefined,
      	undefined,
      	{},
      	{},
      ];
      console.log(unique(arr));
      // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}
      // 空对象 {} 不重复
      // 优化:
      [...new Set(arr)];
      
    2. filter 去重

      function unique(arr) {
      	return arr.filter(function (item, index, arr) {
      		//当前元素, 在原始数组中的第一个索引==当前索引值, 否则返回当前元素
      		return arr.indexOf(item, 0) === index;
      	});
      }
      
    3. reduce+includes 去重

      function unique(arr){
        return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur]
        }
      
    4. 其他: 递归, hasOwnProperty, for 循环 + indexOf + push 新数组, for 循环嵌套 + splice...

  2. 排序

  • 冒泡排序
    1. 比较相邻的元素。如果第一个比第二个大, 就交换它们两个;
    2. 对每一对相邻元素作同样的工作, 从开始第一对到结尾的最后一对, 这样最终最大数被交换到最后的位置;
    3. 除了最后一个元素以外, 针对所有的元素重复以上的步骤;
    4. 重复步骤 1~3, 直到排序完成.
  • 快速排序
    1. 选取基准元素;
    2. 比基准元素小的元素放到左边, 大的放右边;
    3. 在左右子数组中重复步骤一二, 直到数组只剩下一个元素;
    4. 向上逐级合并数组.
  • 插入排序
    1. 从第一个元素开始, 该元素默认已经被排序;
    2. 取出下一个元素, 在已经排序的元素序列中从后向前扫描;
    3. 如果该元素(已排序)大于新元素, 将该元素移到下一位置;
    4. 重复步骤 3, 直到找到已排序的元素小于或者等于新元素的位置
    5. 将新元素插入到该位置;
    6. 重复步骤 2~5, 直到排序完成.
  • sort 原理: 快排和插入排序算法

Map(字典) 和 Set(集合) 的区别

Map 和 Set 都是构造函数, 以 new Set()或者 new Map()的形式初始化; Map 生成的是类对象(Map 对象)的数据结构, Set 生成的是类数组(Set 对象)的数据结构;
  1. Map:

    • 特点:

      1. 以键值对的形式(key-value)存储数据;
      2. Map 对象存储的数据是有序的, Object 对象是无序的;
      3. Map 对象的键值可以是任意类型, 而 Object 键只能是字符串;
    • 使用:

    // 初始化
    let myMap = new Map();
    // 接收初始值的初始化: 默认接收一个二维数组
    let defaultMap = new Map([
    	["name", "张三"],
    	["age", 20],
    ]);
    // defaultMap 展开:
    [
    	{ key: "name", value: "张三" },
    	{ key: "age", value: 20 },
    ];
    // 插入数据(键可以是任意类型):
    defaultMap.set(102, "number"); // number 类型作为键
    defaultMap.set({}, "某对象"); // 对象类型作为键
    展开: [
    	{ key: "name", value: "张三" },
    	{ key: "age", value: 20 },
    	{ key: 102, value: "number" },
    	{ key: {}, value: "某对象" },
    ];
    // 获取长度
    defaultMap.size; // 4
    // 获取对应键值
    defaultMap.get(102); // "number"
    // 删除对应键值对
    defaultMap.delete(102);
    // 查找某个值是否存在
    defaultMap.has("name");
    
  2. Set:

    • 特点:

      1. Set 对象存储的值是不重复的, 可以利用这一点实现数组去重;
        let arr = [1, 2, 3, 4, 5, 6, 3, 2, 5, 3, 2];
        console.log([...new Set(arr)]); // [1, 2, 3, 4, 5, 6]
        // 扩展运算符的作用是将 Set 类数组转换成数组;
        
      2. Set 对象可以存储任意类型的值, 不以键值对形式存储;
    • 使用:

    // 初始化:
    let mySet = new Set();
    // 接收数组作为初始值:
    let defaultSet = new Set(["张三", 18, true]);
    // defaultSet 展开:
    {
    	"张三", 12, true;
    }
    // 插入数据:
    defaultSet.add("李四");
    // 获取长度:
    defaultSet.size; // 4
    // 获取值:
    defaultSet.forEach((item) => {
    	console.log(item);
    });
    // 删除值:
    defaultSet.delete("李四");
    // 查找某个值是否存在:
    defaultSet.has(18);
    
  • 区别:
    1. Map 初始值是一个二维数组, 而 Set 是一维数组;
    2. Map 的键是不能修改, 但是键值是可以修改的;Set 只能通过迭代器来改变 Set 的值, 因为 Set 的值就是键;
    3. has 查找速度很快, 时间复杂度 O(1), 而数组查找的时间复杂度是 O(n);
    4. Map 对象和 Set 对象有唯一性都不允许键重复;

设计模式

  1. 单例模式:

    const Singleton = (function () {
      let instance;
    
      function createInstance() {
        // 创建单例对象的代码
        return {
          // 单例对象的属性和方法
        };
      }
    
      return {
        getInstance: function () {
          if (!instance) {
            instance = createInstance();
          }
          return instance;
        }
      };
    })();
    
    // 使用单例对象
    const singletonInstance1 = Singleton.getInstance();
    const singletonInstance2 = Singleton.getInstance();
    
    console.log(singletonInstance1 === singletonInstance2); // true
    
    
  2. 工厂方法模式:

  3. 建造者模式:

  4. 原型模式:

  5. 代理模式:

  6. 适配器模式:

  7. 桥接模式:

  8. 组合模式:

  9. 装饰器模式:

  10. 享元模式:

  11. 策略模式:

  12. 模板方法模式:

  13. 观察者模式:

  14. 状态模式:

  15. 访问者模式:

继承