目录
- JS的基本概念
- JS是怎么执行的
- JS的进阶知识点
- 课程总结
1. JS 的基本概念
JavaScript(简称JS
)是一门脚本语言,用于为网页添加交互效果
和动态功能
。它由三个基本部分组成: ECMAScript
、DOM
和BOM
。
ECMAScript 是 JavaScript 的核心语言,
规范了 JavaScript 的基本语法、数据类型、流程控制
等。DOM(文档对象模型) 是针对 XML 文档的一个 API(应用程序编程接口),也适用于 HTML 文档。它把整个页面映射为一个多层节点结构,通过
DOM API
可以对页面上的任何元素进行操作
。BOM(浏览器对象模型) 是指
浏览器提供的一组 JavaScript API
,通过它们可以获取和控制浏览器窗口和标签页等浏览器本身的功能
。BOM 包含了很多对象,比如window、location、navigator
等。
除了这些基本概念,JavaScript 还有一些特性和技术,比如闭包、原型链、异步编程
等,这些都是深入学习 JavaScript
需要了解的内容。
1.1 动态 , 弱类型
JavaScript 的变量是动态的,因为在声明变量时不需要指定变量类型
,变量的类型是在程序运行过程中自动推断
出来的。也就是说,同一个变量在不同的时候
可以存储不同类型
的值。例如,一个变量可以先存储数字类型的值,然后再存储字符串类型的值。
JavaScript 的变量是弱类型的,是因为它们的类型可以随时发生改变
,而且不需要进行类型转换就能进行运算。比如,在 JavaScript 中,一个变量可以存储数字类型的值,另一个变量可以存储字符串类型的值,但是它们仍然可以进行加法操作,并且会自动将字符串转换成数字再进行计算。
这种动态和弱类型的特性使得 JavaScript 在编写灵活性高的应用程序时非常有优势,但也存在一些问题。如果不小心让变量存储了错误类型的值,可能会导致程序出错或者产生意料之外的结果,因此要格外小心处理变量类型的问题。
1.2 变量提升
在使用var时,下面的代码不会报错. 这是因为这个关键字声明的变量会自动提升到函数作用域顶部
function test(){
console.log(age);
var age = 13;
}
test() // undefined
之所以不会报错,是因为ECMAScript 运行的时候会把它看成等价于如下代码:
function test1(){
var age;
console.log(age);
age = 13
}
test1()
这就是所谓的变量提升,就是把所有变量声明都拉到函数作用域的顶部.
函数声明也会被提升到作用域顶部,如下所示:
foo(); // 支持调用,输出 "bar"
function foo() {
console.log("bar");
}
以上代码中,函数 foo 虽然在调用前定义了,但是在代码执行时已经被提升到了作用域顶部,因此可以正常调用并输出 "bar"。
let 和 const 不存在变量提升
2.JS是怎么执行的
2.1 讲解执行大概过程
JavaScript的执行分为两个阶段:编译和执行。
- 编译阶段
当JavaScript代码被加载时,JavaScript引擎会首先把它编译成字节码或机器码。编译阶段包括:
- 词法分析:将代码按照语法规则分解成一个个单独的词汇单元(例如变量名、操作符、关键字等),这些词汇单元称为 Token。
- 语法分析:将词法单元转换为 AST(Abstract Syntax Tree,抽象语法树)。AST是一种用于表示程序代码的树形数据结构,每个节点代表程序中的一个语言单元(如函数、语句、表达式等),可以方便地对代码进行分析和优化。
- 代码生成:将AST转换为可执行代码(机器码或字节码),并将其存储在内存中以供后续执行。
在编译过程中,JavaScript 引擎会执行一些静态检查,如语法检查和类型检查。如果发现代码存在错误,编译阶段会立即停止并抛出错误信息。
- 执行阶段
编译阶段完成后,引擎开始执行代码。代码执行的具体流程是:
- 作用域创建:在进入执行上下文时,JavaScript 引擎会创建一个新的作用域(即执行上下文),并将其加入到执行上下文栈中。
- 变量提升:JavaScript 引擎会扫描当前作用域的所有变量和函数声明,并将它们提升到作用域的顶部,以便正确地处理变量访问和函数调用。这就是前面提到的“变量提升”机制。
- 代码执行:JavaScript 引擎按照编译阶段生成的可执行代码进行执行,逐行解释执行代码,并根据当前状态来更新变量和对象的值。
在执行过程中,JavaScript引擎还会进行一些性能优化,如 JIT(Just-In-Time)编译、内联缓存等,以提高代码的运行速度和效率。
总体来说,JavaScript 的执行流程比较复杂,但是了解其基本原理和流程对于我们编写高效、健壮的 JavaScript 代码非常重要。
2.2 JIT(Just-In-Time)讲解
JIT(Just-In-Time)编译是一种动态编译技术,可以提高JavaScript代码的执行效率。在 JIT 编译中,JavaScript 引擎会将频繁执行的代码动态地编译成机器码并缓存起来,以便后续快速执行。
具体来说,JIT 编译分为三个阶段:
- 解释执行
当 JavaScript 代码被加载时,JavaScript 引擎首先对其进行解释执行,即逐行读取代码并执行相应的操作。由于解释执行需要频繁地解析和执行代码,因此效率较低。
- 编译阶段
在解释执行过程中,JavaScript 引擎会监测代码的执行频率,并对频繁执行的代码进行编译优化。编译过程包括 AST 解析、基础块分析、控制流分析、数据流分析等步骤,最终生成优化后的代码。这些代码被称为机器码。
- 优化阶段
优化阶段是 JIT 编译的核心。在每次执行频繁的代码时,JavaScript 引擎会对其进行优化,并重新生成机器码。这些优化包括内联缓存、函数内联、去除未使用的代码等。优化的目标是尽可能地减少不必要的计算、内存访问等操作,以提高代码的执行效率。
总体来说,JIT 编译技术可以大大提高 JavaScript 代码的执行效率。由于 JIT 编译需要根据代码的实际执行情况进行优化,因此它对于频繁执行的函数和循环特别有效。不过,在少量执行的代码中,JIT 编译可能会增加执行时间,因此需要按需使用。
3. JS的进阶知识点
3.1 闭包
JavaScript 中的闭包是一个非常强大的概念,很多开发者在学习 JavaScript 时都会遇到这个问题。本篇文章将介绍 JavaScript 中的闭包,同时提供一些例子来帮助您更好地理解。
什么是闭包?
首先,我们需要明白闭包是什么。简单的说,闭包是指可以访问独立变量的函数。具体来说,当一个内部函数引用了其外部函数的变量时,就形成了一个闭包。
下面的代码示例将更好地说明这一点:
// 外部函数
function outerFunction() {
const outerVariable = '123';
// 内部函数
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const myInnerFunction = outerFunction();
myInnerFunction(); // 输出:123
在上面的示例代码中,outerFunction
函数返回了 innerFunction
函数。这意味着 innerFunction
可以访问 outerFunction
中定义的变量。因此,当 myInnerFunction()
被调用时,它会输出 I am outside!
。
闭包的优点
闭包的最大优点是它们可以帮助我们隐藏或封装数据。这使得我们可以编写很多高效和安全的代码。其中一个优点是,闭包可以“记住”其父级函数中的数据,即使该函数已经退出并且不再存在。
下面是一个例子,它使用闭包来实现私有变量:
function createCounter() {
let count = 0;
return function() {
count++;
console.log(count);
}
}
const counterA = createCounter();
counterA(); // 输出:1
counterA(); // 输出:2
const counterB = createCounter();
counterB(); // 输出:1
在这个例子中,createCounter
函数返回一个函数,该函数可以递增计数器并输出值。由于 count
变量只在 createCounter
函数内部定义,因此外部代码无法直接访问它。这使得我们可以安全地保护数据,并确保对其进行处理的代码仅在闭包范围内。
闭包的缺点
虽然闭包非常有用,但它们也有一些缺点。其中,最大的问题是它们可能会浪费内存
。JavaScript 中的垃圾收集器将不会回收闭包中未使用的变量
。如果你创建了很多这样的闭包,那么就可能导致内存泄漏和性能问题
。
下面是一个稍微复杂的例子,它演示了在 JavaScript 中使用闭包的一些缺点:
function createBigObject() {
const bigObject = new Array(70000).fill('x').join('');
return function() {
console.log(bigObject);
}
}
const myBigObjectFunction = createBigObject();
myBigObjectFunction();
在这个例子中,createBigObject
函数返回了一个包含大量数据的闭包。每次调用 myBigObjectFunction
都会输出这个巨大的字符串。由于 JavaScript 不会回收未使用的闭包变量,因此可能会导致内存泄漏和性能问题。
结论
在本文中,我们介绍了 JavaScript 中的闭包概念,并提供了几个示例来更好地理解它们。闭包是强大而有用的,但也需要小心使用,以避免出现内存泄漏和性能问题。在正确使用闭包的情况下,它们可以使代码更加灵活,可重用,并且能够实现很多高效、安全的功能。
3.2 This
普通函数this 指向Window
this 在 JavaScript 中是一个非常重要的概念。它指的是函数所属的对象,具体取决于函数的调用方式。下面是一些常见的使用方式
1. 作为对象的方法 :
const person = {
name: 'Alice',
sayHello() {
console.log(`你好,我的名字是 ${
this.name}`);
}
};
person.sayHello(); // 输出 "你好,我的名字是 Alice"
在这个例子中,this
指的是 person
对象
2. 作为构造函数:
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // 输出 "Alice"
在这个例子中,this
指的是 Person
构造函数创建的新对象。
3. 使用 call 或 apply 显式地设置 this:
function sayHello() {
console.log(`你好,我的名字是 ${
this.name}`);
}
const person1 = {
name: 'Alice' };
const person2 = {
name: 'Bob' };
sayHello.call(person1); // 输出 "你好,我的名字是 Alice"
sayHello.apply(person2); // 输出 "你好,我的名字是 Bob"
在这个例子中,call 和 apply 被用来将 this 分别设置为 person1 和 person2。
3.3 事件循环
当 JavaScript 运行时,它会将代码分为两类:同步代码和异步代码。同步代码是按顺序执行的,而异步代码则是在后台执行的,不会阻塞主线程。事件循环是 JavaScript 处理异步代码的机制
事件循环是一个不断运行的循环,它会检查消息队列中是否有待处理的消息。如果有,它会将消息从队列中取出并执行。如果没有,它会等待新的消息到达。
下面是一个简单的例子,演示了事件循环的工作原理:
console.log('start');
setTimeout(() => {
console.log('timeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
});
console.log('end');
输入内容:
start ==> end ==> promise ==> timeout
解释:
首先输出 start 和 end,然后遇到 Promise.resolve(),将其放入微任务队列中,继续执行,遇到 setTimeout,将其放入宏任务队列中,最后输出 promise。由于 setTimeout 的延时时间为 0,因此会在当前宏任务执行完毕后立即执行,输出 timeout。
结论:
微任务队列先于 宏任务队列先执行