前端基础概念

调用堆栈

1
2
3
4
5
6
调用栈是解释器(比如浏览器中的 JavaScript 解释器)追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。

每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
当分配的调用栈空间被占满时,会引发“堆栈溢出”错误。

原始类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 最新的 ECMAScript 标准定义了 9 种数据类型:
# 6 种原始类型,使用 typeof 运算符检查:

undefined: typeof => 'undefined'
Boolean: typeof => 'boolean'
Number: typeof => 'number'
String: typeof => 'string'
BigInt: typeof => 'bigint'
Symbol: typeof => 'symbol'

# 对象和函数类型
null: typeof => 'object'
Object: typeof => 'object'
Function: typeof => 'function'

# 内建对象 typeof => 'function'
Date
Array
Map
Set
WeakMap
WeakSet
Regxp

值类型和引用类型

1
2
3
4
# 值类型:
string, number, boolean, undefined, bigint, symbol 和 null
# 引用类型:
Object, Array, Function,...

隐式, 显式, 名义和鸭子类型

JavaScript 是一种弱类型或者说动态语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据:

1
2
3
4
5
6
var foo = 42;   # foo is a Number now
foo = "bar"; # foo is a String now
foo = true; # foo is a Boolean now

# 隐式转换为布尔:“truthy”和“falsy”。当 JavaScript 需要一个布尔值时(例如:if 语句),任何值都可以被使用。 最终这些值将被转换为 true 或 false。
'', +0, -0, NaN, null, undefined, 0, 转换为boolean类型的时候都是false

== 与 ===, typeof 与 instanceof

1
2
3
4
5
6
7
8
9
10
 # == 会进行隐式转换
1.如果比较的两者中有布尔值(Boolean),会把 Boolean 先转换为对应的 Number,即 0 和 1,然后进行比较。
2.如果比较的双方中有一方为 Number,一方为 String时,会把 String 通过 Number() 方法转换为数字,然后进行比较。
3.如果比较的双方中有一方为 Boolean,一方为 String时,会将双方转换为数字,然后再进行比较。
4.如果比较的双方中有一方为 Number,一方为Object时,则会调用 valueOf 方法将Object转换为数字,然后进行比较。
# === 不会进行转换

# typeof 返回数据类型的 类型标签

# instanceof 运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

this, call, apply 和 bind

与其他语言相比,函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。
在绝大多数情况下,函数的调用方式决定了this的值 (运行时绑定),所以this的值在每次函数调用的时候都可能不同。es5 中引入了bind来设置this的值。
需要指出的是在es2015中引入的箭头函数中,不提供自身的this绑定,其this值保持为闭合词法的上下文值

1
2
# call ,apply
二者作用类似,都是指定具体this和参数调用函数,区别是call调用时参数以列表的形式传递,而apply则是以数组的形式传递参数

函数作用域, 块级作用域和词法作用域

作用域:当前执行的上下文。值和表达式在其中“可见“或者可以被访问到的上下文,js作用域以链的形式存在。

1
2
3
4
5
6
js代码执行时,首先回创建一个全局的执行上下文

# 函数作用域
通常情况下,在函数内部定义的变量不能在函数之外的任何地方访问,同时内部函数包含外部函数作用域。
# 块级作用域
在js中函数块是🈯️被{}包裹住的相关联的状态集合,例如一个for循环体,一个条件if判断都可以纳入一个块级作用域范围

闭包

你可以在函数的内部再定义一个函数,嵌套函数对其容器函数是私有的,他自身也形成了一个闭包,当闭包内包含外部作用域的参数和变量时,会保存其值,当一个闭包中进行变量查找时,更近的作用域有更高的优先权,这就形成了作用域链

map, reduce, filter 等高阶函数

1
2
3
4
5
6
# map
map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
# reduce
reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
# filter
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

变量提升

其实质是变量的声明提升

1
2
3
4
5
console.log(b)  # undefined
var b; # 声明提升

console.log(c) # VM315:1 Uncaught ReferenceError: c is not defined
let c; # let 和 const 声明的无法提升变量声明

Promise

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

1
2
3
4
一个 Promise 必然处于以下几种状态之一:
# 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
# 已兑现(fulfilled): 意味着操作成功完成。
# 已拒绝(rejected): 意味着操作失败。

立即执行函数, 模块化, 命名空间

递归

算法

数据结构

消息队列和事件循环

JavaScript有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其它语言中的模型截然不同,比如 C 和 Java。

一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。

在 事件循环 期间的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。被处理的消息会被移出队列,并作为输入参数来调用与之关联的函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。

函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。

1
2
3
4
# 之所以称之为 事件循环,是因为它经常按照类似如下的方式来被实现:
while (queue.waitForMessage()) {
queue.processNextMessage();
}

在浏览器里,每当一个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加进消息队列。如果没有事件监听器,这个事件将会丢失。所以当一个带有点击事件处理器的元素被点击时,就会像其他事件一样产生一个类似的消息。

setTimeout, setInterval 和requestAnimationFrame

1
2
3
4
5
6
# setTimeout
由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined,这和所期望的this的值是不一样的。
定时器
需要做this绑定,或者使用函数包裹,箭头函数解决
# requestAnimationFrame
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

多态和代码复用

1
# 多态

按位操作符, 类数组对象和类型化数组

1
2
3
4
5
6
7
8
9
10
11
# 按位与
两个操作数都为1时,则在该位返回1
# 按位或
两个操作数对应位只要有一个为1,则在该位返回1
# 按位异或
两个操作数的对应位只有一个为1,则在该位返回1
# 按位取反
遇0则返回1,反之亦然
# 应用
1、通过位运算符取整
3.14|0 ; // 3

DOM 树和渲染过程

DOM(Document Object Model——文档对象模型)是用来呈现以及与任意 HTML 或 XML文档交互的API。DOM 是载入到浏览器中的文档模型,以节点树的形式来表现文档,每个节点代表文档的构成部分

等待资源加载时间和大部分情况下的浏览器单线程执行是影响Web性能的两大主要原因。

通过了解浏览器单线程的本质与最小化主线程的责任可以优化Web性能,来确保渲染的流畅和交互响应的及时。

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
# DNS查找
对于一个web页面来说导航的第一步是要去寻找页面资源的位置。如果导航到https://example.com, HTML页面 被定为到IP地址为 93.184.216.34 的服务器。如果以前没有访问过这个网站,就需要进行DNS查找。
# TCP握手
TCP的”三次握手“技术经常被称为”SYN-SYN-ACK“—更确切的说是 SYN, SYN-ACK, ACK—因为通过TCP首先发送了三个消息进行协商,开始一个TCP会话在两台电脑之间。 是的,这意味着每台服务器之间还要来回发送三条消息,而请求尚未发出。
# TLS协商
为了在HTTPS上建立安全连接,另一种握手是必须的。更确切的说是TLS协商 ,它决定了什么密码将会被用来加密通信,验证服务器,在进行真实的数据传输之前建立安全连接。在发送真正的请求内容之前还需要三次往返服务器。
# 响应
一旦我们建立了到web服务器的连接,浏览器就代表用户发送一个初始的HTTP GET请求,对于网站来说,这个请求通常是一个HTML文件。 一旦服务器收到请求,它将使用相关的响应头和HTML的内容进行回复。
# tcp慢开始/14kb规则
第一个响应包是14kb大小。这是慢开始的一部分,慢开始是一种均衡网络连接速度的算法。慢开始逐渐增加发送数据的数量直到达到网络的最大带宽。
# 拥塞控制
当服务器用TCP包来发送数据时,客户端通过返回确认帧来确认传输。由于硬件和网络条件,连接的容量是有限的。 如果服务器太快地发送太多的包,它们可能会被丢弃。意味着,将不会有确认帧的返回。服务器把它们当做确认帧丢失。拥塞控制算法使用这个发送包和确认帧流来确定发送速率。
# 解析
一旦浏览器收到数据的第一块,它就可以开始解析收到的信息。
“推测性解析”,“解析”是浏览器将通过网络接收的数据转换为DOM和CSSOM的步骤,通过渲染器把DOM和CSSOM在屏幕上绘制成页面。
即使请求页面的HTML大于初始的14KB数据包,浏览器也将开始解析并尝试根据其拥有的数据进行渲染。这就是为什么在前14Kb中包含浏览器开始渲染页面所需的所有内容,或者至少包含页面模板(第一次渲染所需的CSS和HTML)对于web性能优化来说是重要的。但是在渲染到屏幕上面之前,HTML、CSS、JavaScript必须被解析完成。
# 构建DOM树
第一步是处理HTML标记并构造DOM树。HTML解析涉及到 tokenization 和树的构造。HTML标记包括开始和结束标记,以及属性名和值。 如果文档格式良好,则解析它会简单而快速。解析器将标记化的输入解析到文档中,构建文档树。
当解析器发现非阻塞资源,例如一张图片,浏览器会请求这些资源并且继续解析。当遇到一个CSS文件时,解析也可以继续进行,但是对于<script>标签(特别是没有 async 或者 defer 属性)会阻塞渲染并停止HTML的解析。尽管浏览器的预加载扫描器加速了这个过程,但过多的脚本仍然是一个重要的瓶颈。
# 预加载扫描器
# 构建cssom树
DOM和CSSOM是两棵树. 它们是独立的数据结构。浏览器将CSS规则转换为可以理解和使用的样式映射。浏览器遍历CSS中的每个规则集,根据CSS选择器创建具有父、子和兄弟关系的节点树。
# javascript编译
当CSS被解析并创建CSSOM时,其他资源,包括JavaScript文件正在下载(多亏了preload scanner)。JavaScript被解释、编译、解析和执行。脚本被解析为抽象语法树。一些浏览器引擎使用”Abstract Syntax Tree“并将其传递到解释器中,输出在主线程上执行的字节码。这就是所谓的JavaScript编译。
# 渲染
渲染步骤包括样式、布局、绘制,在某些情况下还包括合成。
在解析步骤中创建的CSSOM树和DOM树组合成一个Render树,然后用于计算每个可见元素的布局,然后将其绘制到屏幕上。

知识点:
Layout
a、回流
回流是对页面的任何部分或整个文档的任何后续大小和位置的确定。
b、重绘

new 与构造函数, instanceof 与实例

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

1
2
3
4
5
6
7
8
9
10
11
12
#  new 关键字会进行如下的操作:

1、创建一个空的简单JavaScript对象(即{});
2、链接该对象(设置该对象的constructor)到另一个对象 ;
3、将步骤1新创建的对象作为this的上下文 ;
4、如果该函数没有返回对象,则返回this。

# 当代码 new Foo(...) 执行时,会发生以下事情:

1、一个继承自 Foo.prototype 的新对象被创建。
2、使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
3、由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

原型继承与原型链

有些人认为JavaScript并不是真正的面向对象语言,在经典的面向对象语言中,您可能倾向于定义类对象,然后您可以简单地定义哪些类继承哪些类,JavaScript使用了另一套实现方式,继承的对象函数并不是通过复制而来,而是通过原型链继承(在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 `proto__)指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象(proto` ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。__

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

Object.prototype 属性表示 Object 的原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 原型链继承
function Person(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
};
Person.prototype.greeting = function() {
// do something
};

# 通过call和apply继承

function Teacher(first, last, age, gender, interests, subject) {
Person.call(this, first, last, age, gender, interests);

this.subject = subject;
}

注:每一个函数对象(Function)都有一个prototype属性,并且只有函数对象有prototype属性,因为prototype本身就是定义在Function对象下的属性。当我们输入类似var person1=new Person(…)来构造对象时,JavaScript实际上参考的是Person.prototype指向的对象来生成person1。另一方面,Person()函数是Person.prototype的构造函数,也就是说Person===Person.prototype.constructor(不信的话可以试试)。在定义新的构造函数Teacher时,我们通过function.call来调用父类的构造函数,但是这样无法自动指定Teacher.prototype的值,这样Teacher.prototype就只能包含在构造函数里构造的属性,而没有方法。因此我们利用Object.create()方法将Person.prototype作为Teacher.prototype的原型对象,并改变其构造器指向,使之与Teacher关联。

* 在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。

Object.create 和 Object.assign

1
2
3
4
5
6
7
# Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__(原型对象)。新对象的原型就是调用 create 方法时传入的第一个参数:
var a = {a: 1};
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

工厂函数和类

设计模式

Memoization

纯函数, 函数副作用和状态变化

耗性能操作和时间复杂度

JavaScript 引擎

二进制, 十进制, 十六进制, 科学记数法

偏函数, 柯里化, Compose 和 Pipe

代码整洁之道

promise执行顺序

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
setTimeout(()=>{
console.log(1)
})

setTimeout(()=>{
console.log(2)
}, 500)

setTimeout(()=>{
console.log(2)
}, 1000)

new Promise((resolve, reject)=>{
resolve(3)
}).then(res=>{
console.log(res)
return 4
}).then(res=>{
console.log(res)
})
new Promise((resolve, reject)=>{
resolve(5)
}).then(res=>{
console.log(res)
return 6
})
文章作者: SIR_LIU
文章链接: https://gofugui.github.io/2021/04/08/前端基础概念/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 程序员之家