【前端基础】2 - 1 JavaScript 基础与函数

§2-1 基础与函数

2-1.1 JavaScript 简述

JavaScript 不同于传统编程语言,它是一种运行在客户端(浏览器)的脚本编程语言,用于在网页上实现复杂的功能,从而使网页具有实时动态更新的特点,赋予网页交互的功能。

JavaScript 由 Brendan Eich)发明,其应用场景十分广泛,简洁而灵活。开发者基于 JavaScript 编写了大量实用工具,使得开发工作事半功倍。

JavaScript 组成:JavaScript 由以下部分组成。

  • ECMAScript:这是一套语言标准,规定的基本语法、数据类型、关键字、具体 API 设计规范等,是 JavaScript 的核心;
  • Web APIs:包含浏览器对象模型(BOM)和文档对象模型(DOM),前者用于操作浏览器(如页面弹窗、检测窗口宽度、存储数据到浏览器等),后者用于操作文档(如移动页面元素、修改页面元素大小、添加删除元素等)。

引入 JavaScript 的方法:和引入样式表一样,往网页文档中引入 JavaScript 也有三种方法。

  • 内部 JS:在 <body> 标签最末尾使用 <script> 直接包围 JavaScript 代码;

    <body>
    	<!-- 在 </body> 前的新行使用标签 <script> 引入 JavaScript -->
    	<script>
            /* 在这里书写 JavaScript 代码 */
        </script>
    </body>
    
  • 外部 JS:在 <body> 标签最末尾使用 <script src="..."> 引入外部 JavaScript;

    <body>
        <!-- 在 </body> 前的新行使用标签 <script> 引入 JavaScript -->
        <script src="..."></script>
    </body>
    
  • 内联 JS:将 JavaScript 代码写在标签内部,Vue 框架使用这种方法;

<script> 标签放在 HTML 文件底部附近的原因是浏览器会按照代码在文件中的顺序加载 HTML,若先加载的 JavaScript 期望修改其下方的 HTML,则可能会由于 HTML 尚未加载而失效。

引入外部 JS 后,<script> 标签内部无需书写代码,否则会被忽略。

2-1.2 JavaScript 基础语法

JavaScript 遵循 ECMAScript 标准,该标准规定了 JavaScript 的基本语法、数据类型和关键字等。

2-1.2.1 注释、语句和输入输出

注释:JavaScript 支持两种注释方式。

// 单行注释
/*
   块注释
   块注释内的内容会被忽略
*/

语句结束符:JavaScript 的语句结束符为 ;。在实际开发中,结束符可以省略。为了风格统一,建议统一全部补全结束符,或全部省略结束符。

输出方式:JavaScript 有三种输出的方式,三种方式都是通过调用函数实现的。

  • 向文档内写入元素;

    // document.write("string") 向文档内写入元素
    // 可以编写 HTML 代码,将会解析为对应的 HTML 元素
    document.write("hello world");
    
  • 使用弹窗输出;

    // alert("string") 让浏览器弹出弹窗
    alert("This is a pop-op message.");
    
  • 向控制台输出;

    // 使用 console.log("string") 向控制台打印
    console.log("hello world");
    

    输出信息需要开启浏览器的审查元素,切换到 Console(控制台)选项卡,即可看到往控制台上打印的信息。

    该输出方法适用于向控制台输出调试信息,方便调试。

输入方式:使用 prompt("tooltip"),弹出窗口,用户在弹窗中输入信息;

// 参数为弹窗所显示的文字
prompt("Please enter your name:");

该语句实际上是一个函数,它将返回用户的输入,可使用变量接受。

2-1.2.2 变量与常量

字面量(literal):是用于表达源代码中一个固定值的表示法。如 1000 为数字字面量,'hello' 为字符串字面量,[] 数组字面量,{} 对象字面量等。

变量:JavaScript 支持使用变量存储数据,不同于 C/C++, Java 等强类型语言,JavaScript 是一个弱类型语言,声明变量时无需声明变量的数据类型。

声明变量使用关键字 let 声明:

// 分步声明和初始化变量
let age;
age = 18;
console.log(age);

// 同时完成声明和初始化
let username = 'zebt';
console.log(username, age);

// 同时声明多个变量
let id = 1, gender = 'male';

一般而言,我们不推荐一行同时声明多个变量。为保证更好的可读性,应当一行声明一个变量。

变量标识符不能与 JavaScript 保留字重名,且不能以数字开头。变量标识符只能够使用英文字母、数字、下划线和 $,大小写敏感。变量命名时应当见名知意,遵循小驼峰命名。

我们也会在一些网页中看到使用 var 关键字声明变量。这是先前的做法,当前建议使用 let 声明变量。因为 var 具有下列问题:

  • 允许先使用,后声明(不合理);
  • 使用 var 声明的变量可以重复声明(不合理);
  • 变量提升、全局变量、无块级作用域等;

因此,使用 let 声明变量是更好的选择。

常量:使用 const 关键字声明不会被修改值的量

const PI = 3.1415;

常量在声明时必需被赋初值,一旦被赋值,就不允许被修改。一般而言,常量命名规范为全大写,使用下划线分隔单词。

使用 letconst 声明的变量和常量具有块级作用域。

程序中所使用的绝大多数变量在其生命周期之内,一旦被赋值就不会再发生改变。因此,声明变量时,优先考虑使用 const 声明常量。在后续开发中若需要修改变量的值,再将其声明修改为 let

作用域:JavaScript 的变量和常量有两个作用域:全局作用域与局部作用域。位于语句块和函数内部的变量和常量属于局部变量。

2-1.2.3 流程控制

JS 执行顺序:一般地,按照文档流顺序执行 JavaScript 代码,但 alert()prompt() 会跳过页面渲染先被执行。

分支语句:支持 if, switch 和三元运算符。

表达式为布尔表达式。除 0 外其余数字都为 true,除 '' 外其余字符串都为 true

表达式结构同 C/C++ 和 Java,此处不再赘述。

循环语句:循环语句的用法同 C/C++ 和 Java,此处不再赘述。

2-1.2.4 垃圾回收

JavaScript 具有垃圾回收(garbage collection, GC)机制。JavaScript 中内存的分配和回收都是自动完成的,内存在不使用时会被垃圾回收器自动回收。

JavaScript 环境中分配的内存,一般有如下的生命周期:

  • 内存分配:声明变量、函数、对象时,系统会为其分配内存;
  • 内存使用:即访问内存,对所在内存单元进行读写操作,即访问变量、调用函数等;
  • 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存。

一般而言,局部变量会在不使用时被垃圾回收器自动回收,而全局变量不会被回收(但会在关闭页面时被回收)。

注意:程序中所分配的内存空间出于某种原因未释放或无法释放,这种情况称为内存泄漏

JavaScript 的垃圾回收算法说明:程序运行时的数据会存放在堆栈空间中。栈(stack)是由操作系统负责自动分配和释放的存储空间,用于存储程序运行时函数的参数、局部变量等,基本数据类型也存储于栈中。堆(heap)一般由程序员手动申请和释放,用于存储引用数据类型,若不释放,该部分空间将由垃圾回收器回收。浏览器的两种常见的垃圾回收算法为引用计数法标记清除法

  • 引用计数法:由 Internet Explorer 使用的算法,在一个对象没有指向它的引用时(内存不再使用)回收对象。

    • 追踪记录对象被引用的次数;
    • 若被引用一次,则对象被引用次数累计增加;
    • 若引用减少,则计数器同步减少;
    • 若引用次数降至 0 时,释放内存;

    引用计数法是一种简单有效的算法,但这种算法会在两个对象内部存在相互嵌套引用时无法回收对象,造成内存泄露。

  • 标记清除法:现代浏览器通用的大多都是基于标记清除法的某种改进算法实现垃圾回收。

    • 标记清除算法将 “不再使用的对象” 定义为 “无法达到的对象”;
    • 从根部(全局对象)出发,定时扫描内存中的对象,凡是能够从根部到达的对象,都是还需要使用的;
    • 而从根部无法到达的对象将被标记为不再使用,稍后会被回收;

2-1.2.5 异常处理

异常处理也是一个几乎所有编程语言都涉及的问题。异常处理能够在程序运行时捕获异常并处理之,可提升代码的健壮性。

JavaScript 的异常处理主要涉及三个关键字:throw, try, catch

  • throw 抛出:用于在程序遇到意外问题时向外抛出异常或错误,一般使用 throw new Error(msg)msg 是一个用于提供错误信息的字符串。一旦程序抛出错误,程序就会中止执行。

    function add(x, y) {
        if (!x || !y) {
            throw new Error('Arguments cannot be null.');
        }
        return x + y;
    }
    
    console.log(add(1, 2));		// 3
    console.log(add());			// Arguments cannot be null.
    console.log(add(2, 3));	// 不执行
    

    上述示例程序说明了异常抛出时的程序执行情况。且注意到,一旦抛出异常,控制台中除了显示异常信息,还会提供抛出该异常的函数和所在代码行。

  • try...catch 捕获并处理异常try...catch 语句块用于捕获程序中可能抛出的异常并处理之而不会中止程序运行。有时,该语句块还会附加 finally 块用于执行善后工作。

    try 语句块用于执行程序中的语句,一旦抛出异常,中止 try 块剩余语句执行。并将抛出的异常交由 catch 块中处理。若有 finally 语句块,无论会是否抛出异常,该语句块都会在最后执行。trycatch 不能单独出现。

    try {
        // 可能抛出异常的 JavaScript 语句
    } catch (err) {
        // 异常处理
        console.log(err.message);	// 打印异常信息
    } finally {
        // 善后工作
    }
    

除了上述三个关键字外,JavaScript 还提供了一个调试器关键字 debuggerdebugger 独立成句,用于设置断点,程序会暂停在 debugger 语句上,方便调试。

// JavaScript 语句
debugger;		// 触发调试器,暂停程序
// JavaScript 语句

2-1.3 JavaScript 数据类型

虽然 JavaScript 是弱类型语言,但和其他主流编程语言一样,也具有数据类型。

JavaScript 的数据类型主要分为两种:基本数据类型(值类型)和引用数据类型(对象类型)。

基本数据类型有数字型(number)、字符串(string)、布尔型(boolean)、未定义型(undefined)、空类型(null);引用数据类型有对象(object)、数组(array)、函数(function)。

任何声明了但未赋初值的变量都属于未定义型,此时强行访问变量都会得到值 undefined。可通过检测变量是否为 undefined 来判断是否传入数据。

空类型 null 用于对象。若一个变量确定存放的是对象,而尚未准备好该对象,则可以暂时使用 null

JavaScript 的弱数据类型体现在,变量属于何种数据类型,只有在被赋值之后才能够确定。而强类型语言,如 Java,要求在声明变量时同时声明变量所属的数据类型。即使变量未被赋初值,变量的数据类型也是确定的。

此外,JavaScript 的变量支持动态类型。这意味着相同的变量可以被先后用作不同的类型。

let x;			// undefined
x = 5;			// number
x = 'hello';	// string

可使用 typeof 操作符查看变量数据类型:

typeof 3.14			// number
typeof 'hello'		// string
typeof false		// boolean
typeof [1,2,3,4]	// object

typeof 可用于检测数据类型。

2-1.3.1 数字型与运算

JavaScript 中的数字型类型并不像 Java 当中的一样,还能进一步划分为整型、长整型、浮点型。在 JavaScript 中,只要是数字,无论整数或实数(浮点数),都划分为数字型

数字型中有一个特殊的数字:NaN(非数,Not a Number)。非数通常用于表示一个计算错误,由一个不正确的或未定义的数学操作所产生。且非数具有黏性,任何对 NaN 的操作都会返回 NaN。

NaN + 2		// NaN

数字型变量可参与算术运算。算术运算符有求和(+)、求差(-)、求积(*)、求商(/)、求模(%)。算术运算符遵从一定的运算优先级,乘、除、模同级,加、减同级,前者运算优先级高于后者。可使用圆括号 () 提高计算优先级。

同样地,JavaScript 也支持自增自减运算以及简化的赋值运算(如 +=, *= 等)、逻辑运算(&&, ||, !)和位运算(&, |, ~, ^, <<, >>, >>>),此处不再赘述。

JavaScript 中支持以下关系运算:

运算符 解释
>, < 严格大于、小于
>=, <= 大于等于、小于等于
== 判断值是否相等(注意隐式类型转换)
=== 判断值和类型是否都相等
!= 判断值是否相等(注意隐式类型转换)
!== 判断两边值和类型是否不全等

鉴于 ===== 的特性,应当使用 === 判断相等。 且注意,NaN 不等于任何值,包括其自身。

2-1.3.2 字符串类型

使用单引号(')、双引号(")或反引号(`)包围的数据都称为字符串。推荐使用单引号包围。

引号必须成对出现,包围整个字符串。若引号包围的内容为空,即 '',这样的字符串称为空串。单双引号可以互相嵌套,但不允许自己嵌套自己(内双外单,或内单外双)。必要时可使用转义符 \ 输出单双引号。

let str1 = '这是一个字符串';
let str2 = '这是包围了"双引号"的单引号字符串';
let str3 = "这是包围了'单引号'的双引号字符串";
let str4 = `这是由反引号包围的字符串`;
let str5 = `这是'一个'"字符串"`;
let str6 = '这是使用\'转义符\'的字符串';

字符串拼接:使用 + 可实现字符串拼接。一旦表达式中出现字符串,使用 + 都会解释为字符串拼接。

let age = 18;
console.log("I am " + age + " this year.");

但使用字符串拼接仍然较为麻烦,我们可以使用模板字符串解决。

模板字符串

// 使用反引号包围
// 使用 ${varName} 访问变量
let username = prompt("Please enter your name:");
let age = prompt("Please tell us your age: ");
document.write(`I am ${username} and I am ${age}.`);

使用反引号包围,且使用 ${} 调用变量,省去了字符串拼接的麻烦写法,更加方便快捷。

2-1.3.3 数据类型转换

我们使用 prompt() 获取用户输入时,得到的总是字符串类型的数据。而我们在实际上往往可能需要将这些字符串数据转换为其他类型的数据(如数字类型),以便于后续的处理和计算。这凸显了数据类型转换的重要性。

JavaScript 支持数据类型转换,分为隐式类型转换显式类型转换两种

  • 隐式类型转换:某些运算执行时,由系统自动地执行数据类型转换。

    如含有 + 运算符的表达式中,一旦存在一个字符串,整个表达式中的所有操作数都将会被自动转换为字符串处理,即实现字符串拼接。

    除了 + 以外的算术运算符,如 -, *, / 等都会将数据转换为数字类型。

    1 + 1		// 2
    1 + '1'		// 11
    1 - 1		// 0
    1 - '1'		// 0
    +12			// 12 (number)
    +'123'		// 123 (number)
    

    隐式类型转换具有转换类型不明确的特点,需要靠经验总结。

    + 作为正号解析可将数据隐式转换为数字型。任何数据与字符串相加都会转换为字符串。

  • 显式类型转换:由代码显式地告诉系统所转换的数据类型。

    将数据显式转换为数字型的方法有:

    • Number(data):会将数据转换为数字类型,但转换失败时会得到 NaN
    • parseInt(data):只保留整数部分;
    • parseFloat(data):可保留小数部分;
    Number('123')				// 123
    Number('hello')				// NaN
    Number('12px')				// NaN
    
    parseInt('123')				// 123
    parseInt('12px')			// 12
    parseInt('12.34px')			// 12
    parseInt('abc12.34px')		// NaN
    
    parseFloat('123')			// 123
    parseFloat('12.34')			// 12.34
    parseFloat('12.34px')		// 12.34
    parseFloat('abc12.34px')	// NaN
    

2-1.4 函数

2-1.4.1 函数声明与函数表达式

函数用于封装一些可复用的功能。JavaScript 和浏览器支持内置的函数,这些函数随时可用。如:

document.write('hello world!');
alert('hello js!');

同样地,JavaScript 支持自定义函数。若要声明函数,应当使用关键字 function,并注意包含函数名称函数参数列表函数体

// 一个完整的函数声明
function fnName(param1, param2) {
    // do something
    return ret;
}

和其它语言一样,声明于函数中的变量具有有限的作用域,仅能在该函数中使用。当一个函数是一个对象的属性时,称为方法。在 JavaScript 中,函数参数传值的方式是值传递

示例:一个简单的乘法运算函数。

function multiply(num1, num2) {
    return num1 * num2;
}

// 调用函数
console.log(multiply(2, 3));	// 6

除了使用语句声明一个函数,还可以使用函数表达式创建函数。此时,这样的函数可以不必有名称,而是匿名的

// 不提供函数名称的匿名函数
const multiply = function (num1, num2) {
    return num1 * num2;
}

// 提供函数名称,该名称在函数内部使用,或在调试器堆栈跟踪中识别
const factorial = function fac(n) {
    // JavaScript 支持递归调用
    return n < 2 ? 1 : n * fac(n-1);
}

console.log(multiply(2, 3));	// 6
console.log(factorial(4));		// 24

使用函数表达式,将函数作为参数传递时将十分方便。

function process(fn, arr) {
    const res = new Array(arr.length);
    for (let i = 0; i < arr.length; i++) {
        res[i] = fn(arr[i]);
    }
    
    return res;
}

函数提升:仅在使用函数声明时,函数可以在函数声明前被调用,此时 JavaScript 解释器会将整个函数声明提前至当前作用域的顶部。但这并不适用于函数表达式,不能在函数表达式前调用该函数。

2-1.4.2 嵌套函数与闭包

JavaScript 允许在一个函数中嵌套另一个函数,且嵌套可以重复多次进行。嵌套函数(内部函数)是容器函数(外部函数)所私有的。

// 嵌套函数示例
function outside(x) {
    function inside(y) {
        return x + y;
    }
    return inside;
}

// 调用该函数,同时要为内部的嵌套函数传入参数
console.log(outside(1)(2));		// 3

嵌套函数只能够由容器函数访问,其自身形成了一个闭包(closure)。闭包就是捆绑在一起(被封闭)的函数与其周围状态(词法环境)的引用的组合。换句话说,闭包允许你从内部函数访问外部函数的作用域。

嵌套函数(内部函数)能够使用容器函数的变量,但容器函数不可使用嵌套函数的变量。无论嵌套层级有多深,这一点都成立。在多层嵌套函数中,闭包包含了多个作用域,它们递归地包含了所有包含它的函数作用域,称为作用域链

// 作用域链示例
function A() {
    // y,z 对 A 不可见
    let x;
    
    function B() {
        // z 对其不可见,但可用 x
        let y;
        
        function C() {
            // x,y 对其都可见
            let z;
            /* ... */
        }
    }
}

同样地,嵌套函数还需解决变量命名冲突的问题。对于具有命名冲突的变量,更近的作用域会有更高的优先级(就近原则)。这时会发生作用域的变量覆盖。

// 变量命名冲突示例
function outside() {
    const x = 5;
    function inside(y) {
        return x * 2;
    }
    return inside;
}

console.log(outside()(5));	// 20,而不是 10

但是,使用闭包可能会带来意外的内存泄漏问题。以下面的程序为例:

function count() {
    let i = 0;
    function increment() {
        i++;
        console.log(`This function has been called for ${i} time(s).`);
    }
    return increment;	// 返回被嵌套函数
}

const fn = count();		// 将会返回 increment()

我们可以子啊控制台中反复通过常量 fn 调用函数,函数会正确地记录我们调用它的次数。函数使用局部变量而不是全局变量以避免变量意外修改的问题。但是,通过作用域链,结合标记清除法的特点,count() 函数内的 i 变量可通过以下路径到达:

global --> fn() --> count() --> increment() --> i

因此,作为局部变量的 i 应当在函数执行完毕后被释放,但由于该变量可触及而未能够及时释放,进而造成了内存泄漏。

2-1.4.3 函数参数与 arguments 对象

函数的实际参数会被保存在一个类似于数组的 arguments 对象中。可在函数内以如下方式找出传入参数:

arguments[i];

arguments 本身是一个伪数组。其中,i 为参数序号,从 0 开始,参数数量为 arguments.length

JavaScript 的实参个数允许与形参个数不一致。此时,形参过多,会自动补全为 undefined;若实参过多,多于实参会被忽略,但会被保存在 arguments 中。

在 JavaScript 中,函数参数有两种特殊的语法:默认参数剩余参数

默认参数的默认值为 undefined。在一些情况下,应当对函数参数的默认值做判断。也可以直接设置函数参数的默认值。

function add(a, b = 1) {
    return a + b;
}

剩余参数允许将不确定数量的参数表示为数组,必须位于形参列表中的最后一项,至多有一个剩余参数,这类似于 Java 中的可变参数。

function add(addend, ...theArgs) {
    let result = addend;
    for (let i = 0; i < theArgs.length; i++) {
        result += theArgs[i];
    }
    return result;
}
console.log(add(1, 2, 3, 4, 5));    // 15

function multiplyVector(multiplier, ...theArgs) {
    return theArgs.map((a) => multiplier * a);
}

console.log(multiplyVector(2, 1, 2, 3));	// [2, 4, 6]

... 本身可作为展开运算符,将数组/对象中的元素提取出来(展开)并作为一个个独立的参数。如:

const arr = [0, 1, 2, 3];	// 不修改原数组
console.log(...arr);	// 0 1 2 3

2-1.4.4 this

this 是 JavaScript 为函数提供的关键字。在严格和非严格模式下,this 关键字会有所不同。

在绝大多数情况下,this 的值为函数的调用者(运行时绑定),其值不能够在执行的过程中重新赋值,但可以通过 Function.prototype.bind() 方法修改,无论该函数如何被调用。

若函数并没有通过任何东西调用,则 this 的值为 undefined,这仅当函数处于严格模式中。

若要使用严格模式,需要在程序中添加 'use strict' 语句,单独成句。

在非严格模式中,this 替换会保证 this 的值总是一个对象,基本上即为谁调用指向谁,这意味着:

  • 若函数的 this 设为 undefinednullthis 会被替换为 globalThis
  • 若函数的 this 设为一个基本数据类型,this 会被替换为该基本数据类型的包装对象;

回调函数的 this 取值取决于该回调函数如何被调用的,这由 API 实现者决定。一般来说,回调函数的 thisundefined。这意味着,在非严格模式下,函数的 this 是全局对象 globalThis。这在可迭代数组方法、Promise() 构造器中是如此。

但有些 API 允许你在调用回调函数时修改 this 的取值。例如,所有的可迭代数组方法以及如 Set.prototype.forEach() 的相关方法都接收一个可选参数 thisArg

偶尔,回调函数的 this 值为 undefined。例如,JSON.parse() 中的 reviver 参数和 JSON.stringify()replacer 参数的 this 值指的是正在被解析/被序列化的属性所属对象。

2-1.4.5 箭头函数

箭头表达式相比函数表达式,其语法更简短,且没有自己的 this, arguments, supernew.target,但支持剩余参数。且箭头函数总是匿名的。

箭头函数使得表达式更为简洁,这在调用传递函数的函数中十分有用。

// 以上文为例
function multiplyVector(multiplier, ...theArgs) {
    return theArgs.map((a) => multiplier * a);
}

console.log(multiplyVector(2, 1, 2, 3));	// [2, 4, 6]

箭头函数的 this 具有无绑定性,它将使用封闭上下文的 this 值。

const globalObject = this;
const getThis = () => this;
console.log(getThis() === globalObject);	// true

在上述例子中,箭头函数 getThis 通过以闭包的形式,从全局上下文绑定中将 this 指定为全局环境下的 this。这意味着,通过闭包,箭头函数的 this 值是 “自动绑定” 的,无论该函数如何被调用。

const obj = {
  getThisGetter() {
    const getter = () => this;
    return getter;
  }
}

const fn = obj.getThisGetter();
console.log(fn() === obj);		// true

由于箭头函数不存在 this,因此,箭头函数不适用于构造函数、原型函数、字面量对象中的函数、DOM 事件函数。

2-1.4.6 立即执行的函数

JavaScript 支持一种在声明函数的同时立即执行该函数的语法。

(function fn() {  })();

Flexible.js 就使用了立即执行函数使得被定义的函数在被声明后立即自执行。

注意,立即执行函数外层的分号不可省略。

2-1.4.7 预定义函数

JavaScript 具有顶级的内置函数,这些函数全局可用。

函数 描述
eval() 将传入的字符串当作 JavaScript 代码执行
isFinite() 判断传入的值是否为有限的数值
isNaN() 判断一个值是否为非数
parseFloat() 解析字符串参数,返回一个浮点数
parseInt() 解析字符串参数,返回一个整数
decodeURI() 对先前经过 encodeURI() 或其他类似方法编码过的 URI 进行解码
decodeURIComponent() 对先前经过 encodeURIComponent() 或其它类似方法编码的 URI 进行解码
encodeURI() 编码 URI,编码方式详见 MDN
encodeURIComponent() 编码 URI,编码方式详见 MDN

2-1.X 外部链接

JavaScript | MDN (mozilla.org)

What is JavaScript? - Learn web development | MDN (mozilla.org)

JavaScript 教程 | 菜鸟教程 (runoob.com)

Control flow and error handling - JavaScript | MDN (mozilla.org)

Functions - JavaScript | MDN (mozilla.org)

Closures - JavaScript | MDN (mozilla.org)

this - JavaScript | MDN (mozilla.org)

posted @ 2024-03-09 21:16  Zebt  阅读(5)  评论(0编辑  收藏  举报