JS的基本概念
数据类型
在 JS中,有七种数据类型,分别是:
- 数字(Number):表示数值,可以是整数或小数。例如:12,3.14。
- 字符串(String):表示文本,由零个或多个字符组成。例如:"hello",'world'。
- 布尔(Boolean):表示逻辑上的真或假。只有两个取值:true和false。
- undefined:表示未定义的值。如果一个变量没有被赋值,则它的值为undefined。
- null:表示空值或不存在的对象。常用于初始化一个变量,或清空一个对象的属性。
- 对象(Object):表示复合值,可以包含多个属性和方法。例如:{name: '张三', age: 18}。
- 符号(Symbol):表示独一无二的值,主要用于定义对象的属性名。
除了以上提到的 7 种基本数据类型外,还有一个特殊的数据类型叫做NaN,表示不是一个数字(Not a Number)。NaN 是一个数字类型,但是它的值不是一个有效的数值。例如:0 / 0 或者 "abc" - 1 都会返回NaN。
另外需要注意的是,在 JavaScript 中,所有的数据类型都是动态类型。也就是说,一个变量在声明的时候并不需要指定其数据类型,而是根据赋值的内容自动推断出来。同时,同一个变量也可以在不同的时刻存储不同类型的值。
作用域
在JS中,作用域是指变量和函数的可访问范围。JavaScript采用词法(静态)作用域,也就是说,一个变量或函数可以在它们被定义的位置以及它们的嵌套作用域中被访问。
具体来说,JavaScript中有两种作用域:全局作用域和局部作用域。全局作用域指的是在整个程序中都可访问的变量或函数,而局部作用域指的是只能在特定代码块中访问的变量或函数。局部作用域又分为块级作用域、函数作用域。
//全局
var company --·"Bytedance";
function·showCompany( {
console.log( company);}
showCompany()
//局部
//函数作用域
var company ="Bytedance" ;
function showCompany() {
company = -"douyin";console.log(company);}
showCompany()
//块级作用域
{
const company = "Bytedance";
console. log(1,company);
}
console. log(2, -company);
当JavaScript代码执行时,会先在当前作用域中查找变量或函数,如果没有找到,就会逐层向父级作用域中查找,直到找到该变量或函数,或者查找到全局作用域仍未找到,则会抛出“未定义”的错误。
作用域链描述了当前执行环境的作用域、父级作用域以及全局作用域之间的关系。每当一个新的函数被创建时,JavaScript引擎就会为其创建一个自己的作用域,并将其添加到作用域链的顶端,随着函数的执行结束,其作用域会从作用域链中移除。
作用域的正确使用可以有效避免命名冲突和变量污染等问题,同时也是实现封装和信息隐藏等编程原则的基础。
变量提升
在 JavaScript 中,变量声明会被提升到它所在作用域的顶部,这个过程叫做“变量提升”(hoisting)。具体来说,JavaScript 引擎会在代码执行之前对所有变量进行声明,但是不会对变量的赋值进行提升。这意味着,在变量声明之前使用该变量会得到一个 undefined 的值。
console.log(a); // 输出 undefined
var a = 10;
上面的代码会被解释器理解为:
var a; // 变量声明被提升
console.log(a); // 输出 undefined
a = 10; // 变量赋值
需要注意的是,只有使用 var 声明的变量才会被提升,而使用 let 或 const 声明的变量不会被提升。此外,函数声明也会被提升,但是函数表达式不会被提升。因此,在编写 JavaScript 代码时,应该尽可能地将变量和函数的声明放在其被使用的位置附近,以避免造成不必要的困惑和错误。
JS是怎么执行的
JS代码的执行是由JS引擎实现的,不同的浏览器有不同的JS引擎。其中,V8引擎是常用于Chrome和Node.js的JS引擎。
JS代码的执行过程可以分为以下几个步骤:
- 语法分析:JS引擎会先对代码进行词法分析和语法分析,生成语法树和抽象语法树。
- 预编译:JS引擎会在代码执行前做一些预处理工作,例如变量声明提升、函数声明提升等。
- 执行代码:JS引擎按照语句顺序执行代码,遇到函数调用时进入函数执行并在执行完毕后返回原来的位置继续执行。
- 垃圾回收:JS引擎会定期进行垃圾回收,释放不再使用的内存。
以下是一个简单的JS代码示例,演示了上述过程:
function greet(name) {
console.log("Hello, " + name + "!");
}
var myName = "John";
greet(myName);
这段代码会先经过语法分析和预编译,生成如下的执行环境:
全局执行环境:
- 变量对象:
- myName: undefined
- greet: function
- 作用域链:[全局变量对象]
- this: 全局对象
greet执行环境:
- 变量对象:
- name: "John"
- 作用域链:[greet变量对象, 全局变量对象]
- this: 全局对象
接下来,代码会按照顺序执行。首先执行var myName = "John";语句,此时全局变量对象中的myName属性被赋值为"John"。
然后执行greet(myName);语句,进入greet函数执行环境。这时候,在greet函数执行环境中访问name变量会得到"John",所以console.log语句会输出Hello, John!。
代码执行完毕后,JS引擎会进行垃圾回收,释放不再使用的内存。
JS进阶知识点
闭包
闭包是指在一个函数内部定义的函数可以访问外部函数的变量,即使该外部函数已经执行完毕。这样的函数称为闭包函数。
以下是一个简单的 JavaScript 闭包函数示例:
function outerFunction() {
var outerVariable = "I am outside!";
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
var closure = outerFunction();
closure(); // 输出: "I am outside!"
在此示例中,innerFunction 是在 outerFunction 中定义的。它可以访问 outerFunction 内的变量 outerVariable,即使 outerFunction 已经执行完毕并返回了 innerFunction。
通过 outerFunction 的返回值,我们可以将 innerFunction 赋给 closure 变量。然后,通过调用 closure() 来执行 innerFunction,从而输出 "I am outside!"。
使用闭包函数可以创建私有变量和方法,以及实现模块化编程等功能。但闭包也可能会导致内存泄漏问题,需要注意垃圾回收机制。
this
JavaScript中的this关键字用于指代当前执行上下文中的对象。它的值取决于函数的调用方式。
下面是一些常见的this的绑定方法:
- 默认绑定:如果一个函数没有被显式地作为一个对象的方法调用,那么
this会指向全局对象(在浏览器中是window对象,在Node.js环境中是global对象)。
function sayHello() {
console.log(this);
}
sayHello(); // 输出: Window (浏览器环境)
- 隐式绑定:当一个函数作为对象的方法调用时,
this会自动绑定到该对象。
const person = {
name: "Alice",
sayHello() {
console.log(`Hello, my name is ${this.name}.`);
},
};
person.sayHello(); // 输出: Hello, my name is Alice.
- 显式绑定:使用
call、apply或bind方法将this绑定到指定的对象。
function sayHello() {
console.log(`Hello, my name is ${this.name}.`);
}
const person1 = { name: "Alice" };
const person2 = { name: "Bob" };
sayHello.call(person1); // 输出: Hello, my name is Alice.
sayHello.apply(person2); // 输出: Hello, my name is Bob.
const sayHelloToCharlie = sayHello.bind({ name: "Charlie" });
sayHelloToCharlie(); // 输出: Hello, my name is Charlie.
- 箭头函数绑定:箭头函数的
this始终指向它定义时所在的上下文,而不是调用时的上下文。
const person = {
name: "Alice",
sayHello: () => {
console.log(`Hello, my name is ${this.name}.`);
},
};
person.sayHello(); // 输出: Hello, my name is undefined.
垃圾回收
JavaScript 垃圾回收是一种自动内存管理机制,它负责跟踪不再使用的对象和变量,并在适当的时候释放它们所占用的内存。
具体来说,JavaScript 引擎使用标记-清除算法来进行垃圾回收。该算法分为两个阶段:
- 标记阶段:引擎会从根对象开始遍历内存中所有对象,并标记那些仍然被根对象或其他正在被使用的对象所引用的对象。
- 清除阶段:引擎会清除所有未标记的对象并回收它们所占用的内存空间。
以下是一个简单的 JavaScript 代码示例,演示了如何创建和销毁变量,并通过调用 window.performance.memory 检查内存使用情况。
let a = {foo: 'bar'}; // 创建一个对象并将其赋值给变量 a
console.log(window.performance.memory.usedJSHeapSize); // 输出当前内存使用量
a = null; // 将变量 a 的值设为 null,释放对对象的引用
console.log(window.performance.memory.usedJSHeapSize); // 再次输出当前内存使用量,应该比上一次少
在这个示例中,当 a 的值被设置为 null 时,它不再引用先前创建的对象,这意味着该对象现在可以被垃圾回收器清除并释放其所占用的内存。