JavaScript实用知识

2022/4/6 JavaScript

# 1. 杂记

# JavaScript 数组的常用方法

// 增
push()、unshift()、splice()、concat()
// 删
pop()、shift()、splice()、slice()
// 改
splice()
// 查
indexOf()、includes()、find()
// 排序方法
reverse()、sort()
// 转换方法
join()
// 迭代方法(都不改变原数组)
some()、every()、forEach()、filter()、map()
// 构造函数
Array.from()、Array.of()、Array()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# JavaScript 字符串的常用方法

// 增
+、${}、concat()
// 删
slice()、substr()、substring()
// 改
trim()、trimLeft()、trimRight()、repeat()、padStart()、padEnd()、toLowerCase()toUpperCase()
// 查
chatAt()、indexOf()、startWith()、includes()
// 转换方法
split()
// 模板匹配方法,针对正则表达式设计
match()、search()、replace()
1
2
3
4
5
6
7
8
9
10
11
12

# JavaScript 对象的常用方法

// 合并
Object.assign()
// 遍历
键名:Object.keys()、键值:Object.values()、键值对:Object.entries()
// 原型
Object.setPrototypeOf()、Object.getPrototypeOf()
1
2
3
4
5
6

# JavaScript 函数的扩展

  • 参数:ES6允许为函数的参数设置默认值。
  • 属性:length 属性 -- length 将返回没有指定默认值的参数个数(rest 参数也不会计入length属性;设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了);name 属性 -- 返回该函数的函数名。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
(function(...args) {}).length // 0
(function (a, b = 1, c) {}).length // 1
1
2
3
4
5

# == 和 === 的区别

  • 等于操作符 ==:操作数相等,则会返回 true;存在隐式转换,在比较中会先进行类型转换,再确定操作数是否相等。
  • 全等操作符 ===:只有两个操作数在不转换的前提下相等才返回 true,即类型相同,值也需相同。
== 总结:
- 两个都为简单类型,字符串和布尔值都会转换成数值,再比较;
- 简单类型与引用类型比较,对象转化成其原始类型的值,再比较;
- 两个都为引用类型,则比较它们是否指向同一个对象;
- null 和 undefined 相等;
- 存在 NaN 则返回 false。
1
2
3
4
5
6

# new 操作符具体都干了什么

  • JavaScript 中,new 操作符用于创建一个给定构造函数的实例对象。
  • 流程:【1】创建一个新的对象 obj;【2】将对象与构造函数通过原型链连接起来;【3】将构造函数中的 this 绑定到新建的对象 obj 上;【4】根据构造函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理(需要特别注意一下,下面有对比例子)。
// 构造函数返回值是基本数据类型
function Test(name) {
  this.name = name;
  return 1;
}
const t = new Test('xxx');
console.log(t.name); // 'xxx'

// 构造函数返回值是对象
function Test(name) {
  this.name = name;
  console.log(this); // Test { name: 'xxx' }
  return { age: 26 };
}
const t = new Test('xxx');
console.log(t); // { age: 26 }
console.log(t.name); // 'undefined'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 手写 new 的执行过程:
function mynew(Func, ...args) {
  // 1.创建一个新对象
  const obj = {};
  // 2.新对象原型指向构造函数原型对象
  obj.__proto__ = Func.prototype;
  // 3.将构建函数的this指向新对象
  let result = Func.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)
}
let p = mynew(Person, "huihui", 123)
console.log(p) // Person {name: "huihui", age: 123}
p.say() // huihui
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# this

  • this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。this 在函数执行过程中,this 一旦被确定了,就不可以再更改,只能通过 bind / call / apply 更改。

# super

  • this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字 super,指向当前对象的原型对象。

# 顶层对象

  • 在浏览器环境指的是 window 对象,在 Node 指的是 global 对象。

# 事件模型

  • 事件与事件流:javascript 中的事件,可以理解就是在 HTML 文档或者浏览器中发生的一种交互操作,使得网页具备互动性, 常见的有加载事件、鼠标事件、自定义事件等。事件流都会经历三个阶段:事件捕获阶段 (capture phase)、处于目标阶段 (target phase)、事件冒泡阶段 (bubbling phase)。
document.body.onclick = function() {
  console.log('body');
};
document.onclick = function() {
  console.log('document');
};
window.onclick = function() {
  console.log('window');
};
1
2
3
4
5
6
7
8
9
  • 事件模型:可以分为三种:原始事件模型(DOM0级)、标准事件模型(DOM2级)-- 有事件捕获 / 处理 / 冒泡阶段、IE事件模型(基本不用)-- 有事件处理 / 冒泡阶段。

# 事件循环

  • JavaScript 在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事。为什么要这么设计,跟 JavaScript 的应用场景有关。JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM ,如果是多线程,一个线程进行了删除 DOM ,另一个添加 DOM,此时浏览器该如何处理?为了解决单线程运行阻塞问题JavaScript 用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)。
  • 宏任务与微任务。

# 内存泄漏

  • 内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
  • 垃圾回收机制:Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。通常情况下有两种实现方式:标记清除引用计数
  • 标记清除:JavaScript最常用的垃圾收回机制。当变量进入执行环境是,就标记这个变量为 “进入环境“。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为 “离开环境“。
  • 引用计数:语言引擎有一张 "引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。

# 正则表达式

  • 它的设计思想是用一种描述性的语言定义一个规则,凡是符合规则的字符串,我们就认为它 “匹配” 了,否则,该字符串就是不合法的。
  • 使用方法如下:
var re = /pattern/flags;
var re = new RegExp("pattern", "flags");
1
2
  • 匹配方法:正则表达式常被用于某些方法,可以分成两类:字符串(str)方法:matchmatchAllsearchreplacesplit;正则对象下(regexp)的方法:testexec

# 递归

  • 一个函数在内部调用自身本身,这个函数就是递归函数。其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
  • 一般来说,递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

# 数字精度丢失的问题

  • 计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则 {符号位+(指数位+指数偏移量的二进制)+小数部分} 存储二进制的科学记数法。

  • javascript 语言中,0.1 和 0.2 都转化成二进制后再进行运算。

0.1 + 0.2 === 0.3 // false
1

# 大文件上传

  • 文件上传简单,文件变大就复杂。上传大文件时,以下几个变量会影响我们的用户体验:服务器处理数据的能力、请求超时、网络波动。涉及到分片上传断点续传两个概念。
  • 分片上传:就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传,并返回本次分片上传唯一标识,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。
  • 断点续传:指的是在下载或上传时,将下载或上传任务人为的划分为几个部分。每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分(读取文件的偏移量),而没有必要从头开始上传下载。

# 作用域、作用域链

  • 作用域:即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合。就是定义变量的区域,它有一套访问变量的规则,根据这套规则来管理浏览器引擎如何在当前作用域和嵌套作用域中中根据变量(标识符)进行变量查找。
  • 作用域链:当在 JavaScript 中使用一个变量的时候,首先 JavaScript 引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错。

# 原型、原型链

  • JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象。
  • 原型对象有一个自有属性 constructor,这个属性指向该函数本身。
  • 每个对象的 __proto__ 都是指向它的构造函数的原型对象 prototype 的。一切的函数对象(包括 Object 对象),都是继承自 Function 对象。
  • 原型链:原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

w2zC4g.png

# 执行上下文、执行栈

  • 执行上下文:简单的来说,执行上下文是对 Javascript 代码执行环境的一种抽象概念,只要有 Javascript 代码运行,那么它就一定是运行在执行上下文中。

# Set、Map 两种数据结构的理解

  • Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。
  • 集合:是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合。
  • 字典:是一些元素的集合。每个元素有一个称作 key 的域,不同元素的 key 各不相同。
  • 集合与字典的区别:共同点:集合、字典都可以存储不重复的值;不同点:集合是以 [值,值] 的形式存储元素,字典是以 [键,值] 的形式存储。
  • Set 的实例关于增删改查的方法:add()、delete()、has()、clear()。
  • Map 的实例关于增删改查有以下属性和操作方法:size 属性、set()、get()、has()、delete()、clear()。
  • WeakSetWeakMapWeackSet 成员只能是引用类型;WeakMap 只接受对象作为键名(null除外)。

# Generator 函数

  • Generator 函数:是 ES6 提供的一种异步编程解决方案,语法行为与传统函数(回调函数、Promise)完全不同。执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
  • 形式上,Generator 函数是一个普通函数,但是有两个特征:function 关键字与函数名之间有一个星号;函数体内部使用 yield 表达式,定义不同的内部状态。
  • yield 表达式可以暂停函数执行,next 方法用于恢复函数执行,这使得 Generator 函数非常适合将异步任务同步化。
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Proxy

  • Proxy 为构造函数,用来生成 Proxy 实例:var proxy = new Proxy(target, handler)
  • 参数:target 表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理);handler 通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
  • Reflect:若需要在 Proxy 内部调用对象的默认行为,建议使用 Reflect,其是 ES6 中操作对象而提供的新 API
var person = {
  name: "张三"
};
var proxy = new Proxy(person, {
  get: function(target, propKey) {
    return Reflect.get(target, propKey)
  }
});
proxy.name // "张三"
1
2
3
4
5
6
7
8
9
  • 如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则会报错。
const target = Object.defineProperties({}, {
  foo: {
    value: 123,
    writable: false,
    configurable: false
  },
});
const handler = {
  get(target, propKey) {
    return 'abc';
  }
};
const proxy = new Proxy(target, handler);
proxy.foo
// TypeError: Invariant check failed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 取消代理:Proxy.revocable(target, handler)
  • 使用场景:拦截和监视外部对对象的访问、降低函数或类的复杂度、在复杂操作前对操作进行校验或对所需资源进行管理。

# Module

  • 模块(Module),是能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)。两个基本的特征:外部特征和内部特征。外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能;内部特征是指模块的内部环境具有的特点(即该模块的局部数据和程序代码)。
  • 为什么需要模块化:代码抽象、代码封装、代码复用、依赖管理。
  • JavaScript 程序模块化的机制,例如:CommonJs (典型代表:node.js 早期)、AMD (典型代表:require.js)、CMD (典型代表:sea.js)。CommonJs:是一套 Javascript 模块规范,用于服务端。模块是同步加载的。AMD:Asynchronous ModuleDefinition(AMD),异步模块定义,采用异步方式加载模块。所有依赖模块的语句,都定义在一个回调函数中,等到模块加载完成之后,这个回调函数才会运行。
  • ES6 在语言标准的层面上,实现了 Module,即模块功能,完全可以取代 CommonJSAMD 规范,成为浏览器和服务器通用的模块解决方案。CommonJSAMD 模块,都只能在运行时确定这些东西,ES6 可以在编译时就完成模块加载。
  • ES6 模块功能主要由两个命令构成:export -- 用于规定模块的对外接口;import -- 用于输入其他模块提供的功能。动态加载 -- 允许仅在需要时动态加载模块,而不必预先加载所有模块,这存在明显的性能优势。
// 动态加载
// 将 import() 作为函数调用,将其作为参数传递给模块的路径。它返回一个 promise,它用一个模块对象来实现,让你可以访问该对象的导出
import('/modules/myModule.mjs').then((module) => {
  // Do something with the module.
});

// 复合写法
// 如果在一个模块之中,先输入后输出同一个模块,import 语句可以与 export 语句写在一起
export { foo, bar } from 'my_module'; 
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
1
2
3
4
5
6
7
8
9
10
11
12

# Decorator

  • Decorator,即装饰器,从名字上很容易让我们联想到装饰者模式。简单来讲,装饰者模式就是一种在不改变原类和使用继承的情况下,动态地扩展对象功能的设计理论。ES6Decorator 功能亦如此,其本质也不是什么高大上的结构,就是一个普通的函数,用于扩展类属性和类方法。
// 定义一个士兵,这时候他什么装备都没有
class soldier {
}
// 定义一个得到 AK 装备的函数,即装饰器
function strong(target){
  target.AK = true
}
// 使用该装饰器对士兵进行增强
@strong
class soldier {
}
// 这时候士兵就有武器了
soldier.AK // true
1
2
3
4
5
6
7
8
9
10
11
12
13
  • Docorator 修饰对象为下面两种:类的装饰、类属性的装饰。【1】类的装饰:当对类本身进行装饰的时候,能够接受一个参数,即类本身;【2】类属性的装饰:当对类属性进行装饰的时候,能够接受三个参数:类的原型对象、需要装饰的属性名、装饰属性名的描述对象。
// 1、类的装饰
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;

// 2、传递参数,可以在装饰器外层再封装一层函数
function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

// 3、类属性的装饰
function readonly(target, name, descriptor){
  descriptor.writable = false; // 将可写属性设为false
  return descriptor;
}
class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}
// 相当于以下调用
readonly(Person.prototype, 'name', descriptor);

// 4、多个装饰器
// 如果一个方法有多个装饰器,就像洋葱一样,先从外到内进入,再由内到外执行
function dec (id){
  console.log('evaluated', id);
  return (target, property, descriptor) => console.log('executed', id);
}
class Example {
  @dec(1)
  @dec(2)
  method() {}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  • 注意:装饰器不能用于修饰函数,因为函数存在变量声明情况。

# 2. 实用库

  1. await-to-js:处理 async await 错误。
  2. crypto-js:加密算法库。
Last Updated: 2023/04/22, 23:39:26
彩虹
周杰伦