深入理解JS | 青训营笔记

76 阅读5分钟

深入理解JS

一、JS基本概念

JavaScript是一种高级的、面向对象的编程语言,通常用于在Web页面中添加交互和动态效果。不仅在前端开发,后端开发中,JavaScript结合node.js能够实现搭建服务器、处理HTTP请求、访问数据库等功能。

image.png

JavaScript的发展:

image.png

JavaScript的渲染进程

在现代浏览器中,JavaScript 代码通常运行在渲染进程中。它主要负责处理网页的交互和动态效果,以及添加、删除或修改DOM元素等。

浏览器进程:

现代浏览器进程作用
Browser进程主进程,管理其他进程
GPU进程处理图形操作
插件进程运行插件
Utility进程处理无交互任务
渲染进程处理网页渲染

JavaScript的特点

bd24a2e959d59363cf95ed1656edd3b.png

JavaScript的数据类型

JavaScript的数据类型可以分为两类,一类为原始类型(Primitive types)和引用类型(Reference types)。

9b280eccb6fd60049b5b8bca421fbfe.png

代码实现数据检测

function checkDataType(data) {
  return Object.prototype.toString.call(data).slice(8, -1);
}
console.log(checkDataType(123)); // 输出:Number
console.log(checkDataType('hello')); // 输出:String
console.log(checkDataType(true)); // 输出:Boolean
console.log(checkDataType(undefined)); // 输出:Undefined
console.log(checkDataType(null)); // 输出:Null
console.log(checkDataType([1, 2, 3])); // 输出:Array
console.log(checkDataType({})); // 输出:Object
console.log(checkDataType(function(){})); // 输出:Function
console.log(checkDataType(Symbol())); // 输出:Symbol
console.log(checkDataType(new Date())); // 输出:Date

JavaScript的作用域

JavaScript的作用域指的是变量、函数和对象在代码中被访问的范围。JavaScript采用词法作用域,即变量的作用域由它们在代码中声明的位置决定。

JavaScript有两种作用域:

作用域
全局作用域代码中任何地方都可以访问的变量和函数
局部作用域变量和函数只能在其定义的函数内部访问。

全局作用域代码示例:

var globalVar = "I am a global variable!";
let anotherGlobalVar = "I am also a global variable!";

function myFunction() {
  console.log(globalVar); // 输出"I am a global variable!"
  console.log(anotherGlobalVar); // 输出"I am also a global variable!"
}

myFunction();

局部作用域代码示例:

function myFunction() {
  let localVar = "I am a local variable!";
  const anotherLocalVar = "I am also a local variable!";

  console.log(localVar); // 输出"I am a local variable!"
  console.log(anotherLocalVar); // 输出"I am also a local variable!"
}

myFunction();

JavaScript变量提升

在 JavaScript 中,变量提升是指在代码执行前将变量声明“移动”到它们所在作用域的顶部,这意味着可以在其声明之前使用这些变量。

image.png

代码示例:

console.log(myVar); // 输出"undefined"
var myVar = "Hello World!";

在使用myVar之前声明了它,但JavaScript引擎将会在执行代码前将其声明“提升”至作用域的顶部。

console.log(myVar); // 输出"undefined"
var myVar = "Hello World!";

myVar的声明被提升到作用域的顶部,但由于我们在声明前尝试访问变量的值,所以输出结果为undefined。

myFunction();

function myFunction() {
  console.log("Hello World!");
}

在调用myFunction之前定义了该函数。由于函数声明也被提升到作用域的顶部,因此即使在调用函数之前声明该函数。

*** 需要注意的是,使用letconst关键字声明的变量并不会被提升。如果尝试在其声明之前访问这些变量,则会引发一个ReferenceError错误。

二、JavaScript是怎么执行的

JavaScript代码的执行分为两个阶段:解析和运行

image.png

解析阶段

在解析阶段,JavaScript引擎会对代码进行词法分析和语法分析,生成抽象语法树(Abstract Syntax Tree,AST),并且检查代码中是否有语法错误。如果有错误,就会在这个阶段报错。

graph TD
词法分析/语法解析 --> token/AST-->代码生成

运行阶段

在运行阶段,JavaScript引擎会根据生成的AST逐行执行代码。在执行过程中,JavaScript引擎会将变量和函数声明提升到作用域顶部,以便在后续的执行中能够正确地访问它们。同时,JavaScript引擎也会对代码进行优化,例如使用 JIT(Just-In-Time)编译器将频繁执行的代码编译成本地机器码,从而提高代码的执行效率。

graph TD
AST --> 编译-->机器码-->指令序列/代码生成 -->垃圾回收 

image.png

三、JavaScript进阶

闭包

JavaScript中的闭包是指一个函数可以访问其外部环境中定义的变量,即使这些变量在函数被调用后已经不存在也可以访问。简单来说,闭包就是一个函数和其相关的引用环境组合而成的实体。

image.png

代码示例:

function outerFunction() {
  var outerVariable = "I am in the outer function!";

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

var innerFunc = outerFunction();
innerFunc(); // 输出: "I am in the outer function!"

This

在 JavaScript 中,this 关键字指向当前执行代码的对象。它的值取决于函数被调用的方式。

当一个函数被直接作为全局对象的属性调用时,this 指向全局对象 window:

function myFunction() {
  console.log(this);
}

myFunction(); // 输出: Window { ... }

当一个函数被作为对象的方法调用时,this 指向该对象:

var myObject = {
  myMethod: function() {
    console.log(this);
  }
};

myObject.myMethod(); // 输出: Object { ... }

当一个函数被使用 call()apply() 方法调用时,可以通过这些方法的第一个参数来指定 this 的值

function myFunction() {
  console.log(this);
}

var myObject = {};

myFunction.call(myObject); // 输出: Object { ... }

当一个函数被作为构造函数调用时,this 指向新创建的对象:

function Person(name) {
  this.name = name;
  console.log(this);
}

var person1 = new Person("Alice"); // 输出: Person { name: "Alice" }

image.png

垃圾回收

JavaScript的垃圾回收是自动进行的,它通过标记清除算法来实现。当一个对象不再被引用时,它就会被标记为垃圾对象,并在垃圾回收器运行时被清除。

image.png

代码示例:

function createObject() {
  var obj = {};
  return obj;
}

var obj1 = createObject(); // 创建一个对象并将它赋值给obj1
var obj2 = createObject(); // 再创建一个对象并将它赋值给obj2

obj1.reference = obj2; // obj1 引用了 obj2,形成了循环引用

obj1 = null; // 将 obj1 设为 null,此时它所引用的对象被标记为垃圾对象
obj2 = null; // 同样将 obj2 设为 null,但它所引用的对象并不会被立即清除

// 垃圾回收器运行后,发现 obj2 所引用的对象也没有被其他变量引用,因此该对象被标记为垃圾对象
// 最终内存被释放,程序结束

笔记参考