前言:最开始学习js时,只学了一些基本语法,关于它在浏览器中如何运行,以及一些底层原理都不是特别懂,趁此次学习记录一下。
JS基本概念
JavaScript是一种用于web开发的脚本语言。
借鉴C语言的基本语法;借鉴Java语言的数据类型和内存管理;借鉴Scheme语言,将函数提升到“第一等公民”地位;借鉴Self语言,使用基于原型(prototype)的继承机制。
如今越来越多的Web应用和网站都使用JavaScript来实现动态化和交互性,JavaScript成为了一种通用的编程语言,可以在多个平台上运行,如Node.js等。
变革:
- Ajax技术:使web应用能够在不刷新整个页面的情况下实现局部更新。核心是通过JavaScript发起异步请求,然后通过DOM操作来更新页面。
- jQuery库:简化了JavaScript的编写,提供了丰富的API,让开发者更容易操作DOM、处理事件、实现动画等。
- MV框架兴起:如Angular、React、Vue,这些框架提供了更清晰的架构和更好的代码组织方式,让web应用变得更加易维护和可扩展。
- ES6标准:ES6标准带来了一批全新的JavaScript特性。如块级作用域、箭头函数、类、模块化等,让JavaScript更加现代化和易用。
基本特性
单线程:
JavaScript是单线程语言,意味着它只有一个主线程用于执行代码。即在任何给定的时间点,JavaScript只能执行一个任务。这与其他编程语言不同,如java、C#,支持多线程并行执行。JavaScript虽然是单线程的,但通过使用异步编程,如回调函数、Promise、async/await等,可以实现非阻塞式的并发操作。
浏览器的渲染进程中存在GUI线程、JS线程、事件触发线程、定时器触发线程、网络线程。
其中分析一下GUI线程和JS线程。
- GUI线程:负责渲染窗口、组件和处理用户交互事件的线程,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。
- JS线程:执行JavaScript代码的线程,等待任务队列的任务到来,然后加以处理,浏览器任何时候都只有一个JS引擎在运行JS程序。
- GUI线程和JS线程互斥。因为JS引擎可以修改DOM树,若JS在修改DOM结构的时候,GUI线程也在渲染页面,就会导致渲染线程获取的DOM元素信息可能与JS引擎操作DOM后的结果不一致,所以GUI线程需要和JS线程互斥。
数据类型
JavaScript是一种动态、弱类型语言,这意味着它允许在运行时动态地添加、修改和删除对象的属性,并且不需要指定变量的数据类型,相同的变量可以在不同上下文中被用作不同类型的值,这使得编程更加灵活,但也可能导致错误。
在其他语言中,声明变量时需要明确该变量的数据类型,如c语言,在声明变量时就要明确该变量数据类型。
JS数据类型分为基础类型和对象。
基础类型有:字符串、undefined、数字、null、symbol、bigint、布尔
对象有:函数、数组……
基础数据类型存在栈上,复杂数据类型存在堆上。对基本数据类型赋值时,进行的是值传递,而复杂数据类型则是将该变量的地址赋给另一变量。
const person={
name:'Mike',
age:18
}
const p1=person
p1.age=10
console.log(p1,person)
//{name: 'Mike', age: 10} {name: 'Mike', age: 10}
const str='aaaa'
let s1=str
s1='bbbb'
console.log(str,s1)
//aaaa bbbb
在上面的例子中,将person对象赋值给p1,通过p1改变age属性,person中的age也会随之改变;而将str赋值给s1,改变s1的值,str不会改变。
作用域
JS作用域有全局作用域、函数作用域、块级作用域。
全局作用域:即全局变量在全局范围内都可以被调用。
函数作用域:函数中声明的变量,只在该函数范围内存在,函数范围外无法访问该变量。
块级作用域:在一堆花括号内定义的变量或函数,只在这个花括号所包含的代码块内部可见,超出该代码块范围就不能访问。这种作用域限定了变量和函数的可见性和生命周期,有助于减少命名冲突和提高代码可维护性。JS中使用let和const关键字声明变量可以创建块级作用域。
变量提升
在js代码中,使用var声明变量会有变量提升,而使用let、const没有变量提升,提前访问会报错;function函数可以先调用再定义,但赋值给变量的函数无法提前调用。
console.log(a)//undefined
var a=1;
console.log(b)//Uncaught ReferenceError
let b=2;
getName()//1111
function getName(){
console.log(1111)
}
JS是怎么执行的
JavaScript是一种解释型语言,执行过程包括:
语法分析:将代码转换为抽象语法树(AST)表示形式。
预编译处理:创建变量对象、函数对象,并进行作用域链的建立
执行阶段:按照代码顺序逐行执行,遇到函数声明时会提前进行函数的预编译处理。
在执行阶段,JavaScript引擎会运行一个事件循环来实现异步编程。当执行到需要等待某个任务完成时,引擎会将该任务加入事件队列中,继续执行下面的代码。当主线程空闲时,引擎会从事件队列中取出任务并执行。
执行上下文
当JS引擎解析到可执行代码片段时,会先做一些执行前的准备工作,这一过程称为执行上下文。
- 全局执行上下文:代码开始执行就会创建,将它压到执行栈的栈底,每个生命周期内只有一份。
- 函数执行上下文:当执行一个函数时,这个函数内的代码会被编译,生成变量环境、词法环境等,当函数执行结束时该执行环境从栈顶弹出。
- 执行上下文涉及了变量声明提升、this绑定、创建词法环境、变量环境等。
进阶知识点
闭包
闭包是一种机制:函数嵌套函数,内部函数可以引用外部函数的参数和变量。参数和变量不会被垃圾回收机制收回。
通常情况下,当一个函数执行完毕后,其内部定义的变量都会被销毁。但是,在使用闭包的情况下,由于内部函数仍然可以访问它们,因此这些变量不会被立即销毁,而是一直保存在内存中,直到内部函数被销毁为止。
function outerFunction() {
let count = 0;
function innerFunction() {
count++;
console.log(count);
}
return innerFunction;
}
const myFunction = outerFunction();
myFunction(); // 输出 1
myFunction(); // 输出 2
myFunction(); // 输出 3
以上例子中,innerFunction可以访问count变量并将其递增,将返回值赋值给myFunction后,myFunction成为innerFunction的引用,可以在外部作用域中调用它,而innerFunction捕获了count变量,每次调用myFunction时,count的值会被更新,并输出不同值。
for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 输出 5、5、5、5、5
for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 会输出 0、1、2、3、4
垃圾回收 JavaScript的垃圾回收机制有标记清理和引用计数。
标记清理:当变量进入上下文时,会被加上存在于上下文的标记,离开上下文时,也会被加上离开上下文的标记。
引用计数每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为1。如果同一个值又被赋给另一个变量,那么引用数加1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减1。当一个值的引用数为0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。
以上就是最近学到的一些知识点,如果有哪些错误的地方或者有什么需要补充的,欢迎在评论区留言!