回顾JavaScript(原理篇)

前言

曾经的javascript是跟着B站的视频学习的,也没有好好的看相关的书籍,导致在相关开发中,因不熟悉原理和方法,导致部分功能写的较为臃肿,因此打算重新回顾梳理一下前端之根本------JavaScript

1.JS原理

要回顾JS,首先应该明确JS的原理与相关执行逻辑,正所谓知其然,知其所以然,为了更好的了解JS的原理,首先要理解以下几个概念。

  • JS引擎
  • 执行上下文
  • JS标签
  • 回调函数
  • 调用栈
  • 事件循环
  • 作用域与作用域链
  • 原型与原型链

JS引擎

理解:专门处理JS文件的虚拟机

作用:

  1. 编译代码:将JS代码编译为不同CPU机器对应的汇编语言代码,再进而转化为机器语言,供电脑底层识别
  2. 垃圾回收:按照对应的垃圾回收算法,将一些不再被使用的内存释放掉
  3. 分配内存:JS也存在堆栈,堆存放一些对象等,栈则存放方法以及一些基础的数据类型
  4. 执行代码:执行JS代码,让页面动起来

种类:

  • JScore:WebKit 默认的内嵌 JS 引擎
  • V8:目前最主流JS引擎,性能yyds
  • Hermes:FaceBook推出的一款JS引擎
  • QuickJS:新兴的比较有潜力的JS引擎

最后再提一下最常用的V8引擎

V8引擎优点

  1. 基于即时编译JIT,先进的算法
  2. 优秀的垃圾回收算法及机制(内存管理)
  3. 多线程执行JS代码,以提高效率
  4. 跨平台支持,兼容多操作系统
  5. 开放源代码

V8引擎应用场景

  1. Web浏览器
  2. Node.js
  3. Electron
  4. 游戏开发

执行上下文RunTime

JS可以调用浏览器相关的API,比如Window对象,DOM等以及JS的事件循环(Event Loop)和事件队列(Callback Queue)等,都归功于RunTime

JS标签

xml 复制代码
<script>常规</script>
<script async>异步</script>
<script defer>延迟</script>

Script

当HTML遇到JS时,会等待JS(请求+执行),结束后才执行html

Async Script

Html和Js请求都是异步执行,JS请求成功后,html等待js去执行

Defer Script

Html和Js请求都是异步执行,JS请求成功后,也要等待HTML的解析

回调函数

理解:函数作为一个参数传递到另一个函数中,就是一个钩子

作用:解决异步编程问题

弊端:可能产生回调地狱的问题

什么是异步?

ini 复制代码
let res = $ajax.get('...');
console.log(res);

此时的res是undefined,因为ajax请求是异步的,打印res的时候,ajax还没执行完

javascript 复制代码
$ajax.get('...', (res)=> {
 console.log(res);
})

此时的res是正确的返回结果

常见的异步执行:

  • 定时器
  • 建立网络连接
  • 读取网络流数据
  • 向文件写入数据
  • Ajax提交
  • 请求数据库服务

JS引擎其实并不提供异步的支持,异步支持主要依赖于运行环境(浏览器或Node.js)

调用栈

调用栈负责管理函数调用的顺序和上下文,并确保函数能够按照正确的顺序执行和返回

起初JS主要用来实现页面相关的交互操作,并且多线程会造成一些复杂的同步问题,因此JS是被设计为单线程运行的

后来HTML5提供了Web Worker,在后台有一个独立的JS,专门用来处理一些耗时的数据操作(不会修改相关DOM等,不影响页面性能)

总结:如果有阻塞产生,便会导致浏览器卡死

go 复制代码
function func() {
 func();
}
func();

如上面的例子,当一个递归没有终止条件时,浏览器便会报错说调用栈溢出

事件循环

理解:负责监听 堆栈 和 回调函数队列

作用:处理异步操作的回调函数

原理:不断地检查任务队列(task queue)是否有待处理的任务。如果队列中有任务,则将任务从队列中取出,并执行相应的回调函数。执行完一个任务后,再检查队列是否还有其他任务,以此类推,形成循环。

步骤:

  1. 执行同步任务:按照代码的顺序执行同步任务。
  2. 执行微任务(Microtask):处理由 Promise、DOM树变化等产生的微任务。微任务会优先于下一个宏任务执行。
  3. 执行渲染:更新页面的渲染状态。
  4. 执行宏任务(Macrotask):处理定时器回调、事件回调、IO操作、网络请求等宏任务。宏任务的优先级次于微任务。
  5. 重复以上步骤:不断循环,直到所有任务都被处理完毕。

常见案例

css 复制代码
for (var i = 0; i < 3; i++) {
 setTimeout(function() {
 console.log(i);
 }, 1000 * i);
}

输出结果为3,3,3,而不是1,2,3

因为定时器的回调属于是宏任务,会等待for循环执行结束后再放入执行栈中执行,因此当for循环执行完毕时,i的值已经是3了,因此只会打印3,3,3
解决方案一:闭包

原理: 将 i 作为参数传递给闭包函数,使得输出从 0 开始,在每次迭代中,闭包函数就会捕获到当前迭代的值,并在 setTimeout 中使用。

javascript 复制代码
for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, 1000 * j);
  })(i);
}

解决方案二:let声明变量
let 声明的变量具有块级作用域,每次迭代循环时会创建一个新的变量绑定,这样就保证每个迭代的 setTimeout 获取的 i 都是其对应的值。

css 复制代码
for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000 * i);
}

作用域与作用域链

作用域

  • 全局作用域
    顾名思义就是全局性质的作用域,在任何地方都可以访问的到的最外层作用域,全局作用域位于所有函数之外
go 复制代码
举例
var a = 1;
function func(){
    console.log(a);
}
func(); //1
  • 函数作用域
    顾名思义就是只能在函数中使用,或指在函数内部声明的变量,函数作用域可以防止变量和函数名称冲突,并保护内部变量不被外部访问。
ini 复制代码
举例
var a = 1;
function func(){
    var a=2
    var b=1;
    console.log(a);
}
func(); //2
console.log(b); //报错:访问不到函数内的b,外部又没有,因此是undefined
  • 块级作用域
    ES6+的新特性,是指使用花括号 {} 创建的作用域(语句块),并且在语句块中声明的语句或变量只在当前语句块中起作用。
    通过 let 和 const 关键字可以在任意代码块(例如 if 语句、for 循环等)内创建块级作用域。
scss 复制代码
举例
function func() {
    if (true) { 
        let a = 1 ; 
        console.log(a); // 输出:1 
    } 
    console.log(a); // 报错:a is not defined
}
  func();

作用域链

理解:由多个嵌套作用域形成的链条就是作用域链

步骤:

  1. 当查找某个变量a的时候,会先从当前作用域中查找
  2. 如果未找到,就会从其父级作用域(外部一层)的变量对象中查找
  3. 如果还是未找到,就继续从其父级作用域(外部一层)的变量对象中查找
  4. 一直找到全局作用域的变量对象,全局作用域就相当于一个兜底的存在
  5. 如果还是未找到,就报错 a is not defined

代码举例

scss 复制代码
var a = 1;
function func1() {
    var b = 2;
    function func2() {
        var c = 3;
         console.log(a); //1
         console.log(b); //2
         console.log(c); //3
    }
         console.log(a); //1
         console.log(b); //2
         console.log(c); //c is not defined
}
         console.log(a); //1
         console.log(b); //b is not defined
         console.log(c); //c is not defined
func1();

原型与原型链 (继承的机制)

每个对象都有一个原型(prototype),并且通过原型链(prototype chain)连接在一起。

要说原型与原型链,首先应该说说构造函数Constructor

ini 复制代码
定义构造函数
function Person(name, age) {
this.name = name; this.age = age; }

创建实例 
const p = new Person('张三', 18);

JS通过构造函数来生成实例,但是这些实例都是一些个性化的内容,正常情况下它们应该拥有一些公共的属性,比如说都会说话,都会走路等等...

但是我们无法在构造函数中共享一些公共的属性,于是原型对象诞生了,它的作用就是用来存储这个构造函数的公共属性和方法

正如ChatGPT所说,如果我们想在构造函数中定义公共属性,可以使用构造函数的 prototype 属性

原型对象
  • JS的每个函数在创建的时候,都会生成一个属性prototype
  • 这个prototype属性指向一个对象,这个对象就是此函数的原型对象
  • 这个原型对象中又有个属性为constructor,指向该函数

图解

原型链
ini 复制代码
构造函数
function Preson(name, age) {
this.name = name;
this.age = age; }
创建公共方法
Preson.prototype.say = function () { 
console.log('哈哈哈哈'); } 
创建一个实例对象 
const p= new Preson('张三', 18); 
p.say(); // 哈哈哈哈

根据上述例子,say方法是定义在p的构造函数的原型上的,但是p却可以调用,这就是因为有原型链的存在,使得p可以一直沿着原型链去找say这个方法,最终在Person的原型上找到了这个方法。

  • 当调用某个方法时
  • JS会先显式的在该对象身上找
  • 当找不到时,便会去找那些隐式的同名方法
  • 找的规则就是沿着原型链一层一层的去找
  • 一直找到终极对象Object,再往上找时,就是null,因此原型链的尽头就是null
    • 如果找到了,就调用/使用
    • 如果还是找不到,就报错 func is not defined

图解

构造函数--->实例
  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象(这样this就指向了新对象)
  3. 执行构造函数中的代码(为新对象添加实例属性和实例方法)
  4. 返回新对象
    引用自一文搞懂JS原型与原型链(超详细,建议收藏) - 掘金 (juejin.cn)
显式原型与隐式原型

p.__proto__ === Person.prototype

ini 复制代码
function Person(name, age) {
this.name = name; this.age = age; 
}

创建实例 
const p = new Person('张三', 18);

一句话概括就是 实例 的隐式原型__proto__指向 构造函数 的显式原型prototype

图解

相关推荐
福兮说2 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
凤城老人3 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)4 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan4 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_5 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
yivifu6 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub
weixin_448290256 小时前
书庐开发实战教学
javascript·css·html
默_笙6 小时前
🛴 从散件到整机:DeepAgents 与 Agent 身上预留的那些"插槽"(前置介绍)
前端·javascript
一木 之林7 小时前
DeepSeek Agent 开发(一)
开发语言·前端·javascript
樱花落木兰9 小时前
分布式登录实战:Session 会话共享改造,Redis 存储用户登录状态
java·javascript·数据库·redis·分布式·缓存