【2024秋第2节课】JavaScript基础

389 阅读25分钟

22C659CFF053212D83B3D14F6A2012CF.png

JS起源

我们有必要先从这门语言的历史谈起。在 1995 年 Netscape 一位名为 Brendan Eich 的工程师创造了 JavaScript,随后在 1996 年初,JavaScript 首先被应用于 Netscape 2 浏览器上。最初的 JavaScript 名为 LiveScript,但是因为一个糟糕的营销策略而被重新命名,该策略企图利用 Sun Microsystem 的 Java 语言的流行性,将它的名字从最初的 LiveScript 更改为 JavaScript——尽管两者之间并没有什么共同点。这便是之后混淆产生的根源。

几个月后,Microsoft 随 IE 3 发布推出了一个与之基本兼容的语言 JScript。又过了几个月,Netscape 将 JavaScript 提交至 Ecma International(一个欧洲标准化组织),ECMAScript 标准第一版便在 1997 年诞生了,随后在 1999 年以 ECMAScript 第三版的形式进行了更新,从那之后这个标准没有发生过大的改动。由于委员会在语言特性的讨论上发生分歧,ECMAScript 第四版尚未推出便被废除,但随后于 2009 年 12 月发布的 ECMAScript 第五版引入了第四版草案加入的许多特性。第六版标准已经于 2015 年 6 月发布。

JS的调用和执行顺序

使用方式

HTML页面中任意位置加上<script></script>标签即可。

常见使用方式有以下几种:

  • 直接在<script></script>标签内写 javascript 代码。
  • 直接引入文件<script src="./static/js/index.js"></script>
  • 将所需要的代码通过import关键字引入到当前作用域(<script type="module"> import "./static/js/index.js" </script>)。

作用域、执行上下文、作用域链

JavaScript不同于其他大多数高级语言,比如Java语言有块级作用域,也就是由一个花括号对{……}的位置决定作用域,而在 ES6 之前,Javascript却不是这样的,它使用函数作用域全局作用域,直到ES6出现之后,才有了块级作用域

作用域(Scope)

作用域即函数或变量的可见区域。通俗点说,函数或者变量不在这个区域内,就无法访问到。

函数作用域

用函数形式以function(){……}类似的代码包起来的(省略号……)区域,即函数作用域

与函数作用域相对应的概念是全局作用域,也就是定义在最外层的变量或者函数,可以在任何地方访问到它们。

var a = "lanshan";//在全局作用域
function func(){
    var b="lanshan";//在函数作用域内
    console.log(a);
}

console.log(a);//>> lanshan
console.log(b);//>> Uncaught ReferenceError: b is not defined
func();//>> lanshan

如上,a定义在全局作用域内,任何地方都可见,所以函数func内能访问到a;而b定义在函数func内,可见区域就是函数代码块,后面的打印命令console.log(b)在函数func之外执行的,访问不到函数func内的b,因此输出Uncaught ReferenceError: b is not defined

任意代码片段外面用函数包装起来,就好像加了一层防护罩似的,可以将内部的变量和函数隐蔽起来,外部无法访问到内部的内容。

//全局作用域

function func(){//作用域A
    var a = "lanshan";

    function func1(){//作用域B。定义一个函数,把不想公开的内容隐藏起来
        var a = "2006";//这里的a把外层的a的值覆盖了
        var b = "b";

        //这里可以放有很多其他要对外隐藏的内容:变量或者函数
        //……
        //…

        console.log(a);
    }

    console.log(a);//>> lanshan
    console.log(b);//>> Uncaught ReferenceError: b is not defined
    func1();//>> 2006
}

上面示例了一个嵌套函数,等于有外层函数func的作用域A内嵌了函数func1的作用域B。在func1里面的打印命令console.log(a)访问变量a时,JS引擎会先从离自己最近的作用域A查找变量a,找到就不再继续查找,找不到就去上层作用域(此例中上层作用域是全局作用域)继续查找,此例中a已经找到且值为"lanshan",所以打印输出lanshan。依此类推,执行func1(),会执行func1函数内部的console.log(a),随即会在作用域B查找里面a,而作用B里面存在一个a的声明和赋值语句var a = "2006",所以最先找到a的值是2006,找到便不再继续查找,最终func1()输出2006而不是lanshan

但是每次都要定义一个“不重名的函数名“放在上一级作用域里,显得有点浪费内存空间,而且要想不重复的名字有点头疼。所以最好还是匿名函数的形式包起来,然后立即执行,也即IIFE。如下示例:

//全局作用域

function func(){//作用域A
    var a = "lanshan";

    (function(){//作用域B。一个IIFE形式的函数,把不想公开的内容隐藏起来
        var a = "2006";
        var b = "b";

        //这里可以放有很多其他要对外隐藏的内容:变量或者函数
        //……
        //…

        console.log(a);
    })();//>> 2006

    console.log(a);//>> lanshan
    console.log(b);//>> Uncaught ReferenceError: b is not defined
}

如上,用一个IIFE加匿名函数的写法,把变量b隐藏起来,函数外面就没法访问它,函数内部可以访问到它。

ES6带来了块级作用域

ES6规定,在某个花括号对{ }的内部let关键字生声明的变量和函数拥有块级作用域,这些变量和函数它们只能被花括号对{ }的内部的语句使用,外部不可访问。在你写下代码的时候,变量和函数的块级作用域就已经确定下来。块级作用域和函数作用域也可以统称为局部作用域

ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,在块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。但是这样的处理规则显然会对老代码产生很大的影响,出于向后(backward)兼容的考虑,在块级作用域中声明的函数依然可以在作用域外部引用。如果需要函数只在块级作用域中起作用,应该用let关键字写成函数表达式,而不是函数声明语句。为了证明该段论述,我们来看一段代码。

{
  function func(){//函数声明
    return 1;
  }
}
console.log(func());//>> 1

上面这段代码,函数func明明是在花括号内部声明的,按 ES6 原本的规范,外部应该是不可访问的,但实际上可以,证明JS引擎为了向后兼容在实现ES6规范的时候做了变通处理。再来看一段代码。

{
  var func = function (){//未使用let关键字的函数表达式
    return 1;
  }
}
console.log(func());//>> 1

上面这段代码与它之前那一段代码效果是一样。

{
  let func = function (){
    return 1;
  }
}
console.log(func());//>> func is not defined

上面这段代码证明,在花括号{}内部由let关键字声明的函数,才是真正的处于块级作用域内部。

为什么要引进块级作用域?

有了全局作用域和函数作用域,以及var已经挺好用的了,为何还要引进块级作用域和关键字let呢?

首先,的确,ES6 之前函数作用域和var结合也很好用,但是终究没有{}let结合来的块级作用域来的简洁!

其次,var声明的变量有副作用:声明提前

(function() {
  console.log(a); //>> undefined
  console.log(b); //>> ReferenceError
  var a = "lanshan"; //声明提前
  let b = "2006"; //由let关键字声明的变量,不存在提前的特性
})();

上面这段代码,其中var a = "lanshan" 含两个操作,一个是变量a声明(也即var a),一个是赋值(也即a = "lanshan")。声明提前的意思是,用var关键字声明的变量,其实可以看做是在函数体内最顶端声明的,所以console.log(a)输出undefined,代表该变量已经被声明过(但还未赋值)。声明提前这个特性,让很多程序员容易变得十分迷惑。按理说,变量(或函数)应是在声明之后才能读取(查找)的,但是var已经让这个常理变得近似诡异,let的出现能让这诡异回归常理。

再次,因为var声明变量有污染。

  for (var i = 0; i < 100; i++) {
    //……很多行代码
  }
  function func() {
    //……很多行代码
  }
  //……很多行代码
  console.log(i); //>> 100

循环里面的 i 在循环完毕后就没有用了,但并没有被回收掉,而是一直存在的“垃圾”变量,污染了当前的环境。而用let声明变量,事后这种垃圾变量会很快被回收掉。

  for (let i = 0; i < 100; i++) {
    //……很多行代码
  }
  function func() {
    //……很多行代码
  }
  //……很多行代码
  console.log(i); //>> ReferenceError

综上,你应该使用let,尽量的避免使用var,当然你想定义一个全局变量除外。

执行上下文(Execution Context)

定义

执行上下文就是当前 JavaScript 代码被解析和执行时所在的环境,也叫作执行环境。

它是一个抽象概念,意味着我们在脑海中理解一下就好,方便后续真正掌握JavaScript,而不要钻牛角尖去寻找它的具体实现。JavaScript 中运行任何的代码都是在执行上下文中运行,在该执行上下文的创建阶段,变量对象(Variable Object,本文接下来会详述)、作用域链、this指向会分别被确定。

类型

执行上下文总共有三种类型:

  • 全局执行上下文:这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:
    1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象;
    2. 将 this 指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
  • 函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤,具体过程将在本文后面讨论。
  • eval执行上下文(不讲)
执行上下文的生命周期

执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段

a. 创建阶段

当函数被调用,但未执行任何其内部代码之前,会做以下三件事:

  • 创建变量对象:首先初始化函数的参数 arguments,提升函数声明和变量声明(变量的声明提前有赖于var关键字)。
  • 创建作用域链:在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。
  • 确定 this 指向。
b. 执行阶段

创建完成之后,就会开始执行代码,在这个阶段,会完成变量赋值、函数引用、以及执行其他代码。

c. 回收阶段

函数调用完毕后,函数出栈,对应的执行上下文也出栈,等待垃圾回收器回收执行上下文。

执行上下文栈
var a = "lanshan"; //1.进入全局执行上下文
function out() {
    var b = "18";
    function inner() {
        var c = "91";
        console.log(a+b+c);
    }
    inner(); //3.进入inner函数的执行上下文
}
out(); //2.进入out函数的执行上下文

在代码开始执行时,首先会产生一个全局执行上下文,调用函数时,会产生函数执行上下文,函数调用完成后,它的执行上下文以及其中的数据都会被销毁,重新回到全局执行环境,网页关闭后全局执行环境也会销毁。其实这是一个入栈出栈的过程,全局上下文永远在栈底,而当前正在函数执行上下文在栈顶。以上代码的执行会经历以下过程:

  1. 当代码开始执行时就创建全局执行上下文,全局执行上下文入栈
  2. 全局执行上下文入栈后,其中的代码开始执行,进行赋值、函数调用等操作,执行到out()时,激活函数out创建自己的执行上下文,out函数执行上下文入栈
  3. out函数执行上下文入栈后,其中的代码开始执行,进行赋值、函数调用等操作,执行到inner()时,激活函数inner创建自己的执行上下文,inner函数执行上下文入栈
  4. inner函数上下文入栈后,其中的代码开始执行,进行赋值、函数调用、打印等操作,由于里面没有可以生成其他执行上下文的需要,所有代码执行完毕后,inner函数上下文出栈
  5. inner函数执行上下文出栈,又回到了out函数执行上下文环境,接着执行out函数中后面剩下的代码,由于后面没有可以生成其他执行上下文的需要,所有代码执行完毕后,out函数执行上下文出栈
  6. out函数执行上下文出栈后,又回到了全局执行上下文环境,直到浏览器窗口关闭,全局执行上下文出栈

我们可以发现:

  1. 全局执行上下文在代码开始执行时就创建,有且只有一个,永远在执行上下文栈的栈底,浏览器窗口关闭时它才出栈。
  2. 函数被调用的时候创建函数的执行上下文环境,并且入栈。
  3. 只有栈顶的执行上下文才是处于活动状态的,也即只有栈顶的变量对象才会变成活动对象。

变量对象(Variable Object,VO)

变量对象(VO)是一个类似于容器的对象,与作用域链、执行上下文息息相关。

变量对象的创建过程的三条规则:
  1. 建立arguments对象。检查当前执行上下文中的参数,建立该对象下的属性与属性值。
  2. 检查当前执行上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果该属性之前已经存在,那么该属性将会被新的引用所覆盖。
  3. 检查当前执行上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改

可以用以下伪代码来表示变量对象:

VO={
    Arguments:{},//实参
    Param_Variable:具体值,//形参
    Function:<function reference>,//函数的引用
    Variable:undefined//其他变量
}

当执行上下文进入执行阶段后,变量对象会变为活动对象(Active Object,AO)。此时原先声明的变量会被赋值。变量对象和活动对象都是指同一个对象,只是处于执行上下文的不同阶段

我们可以通过以下伪代码来表示活动对象:

AO={
    Arguments:{},//实参
    Param_Variable:具体值,  //形参
    Function:<function reference>,//函数的引用
    Variable:具体值//注意,这里已经赋值了喔
}

未进入执行上下文的执行阶段之前,变量对象中的属性都不能访问。但是进入执行阶段之后,变量对象转变为了活动对象(被激活了),里面的属性可以被访问了,然后开始进行执行阶段的操作。

全局执行上下文的变量对象

全局执行上下文的变量对象是window对象,而这个特殊,在this指向上也同样适用,this也是指向window

除此之外,全局执行上下文的生命周期,与程序的生命周期一致,只要程序运行不结束(比如关掉浏览器窗口),全局执行上下文就会一直存在。其他所有的执行上下文,都能直接访问全局执行上下文里的内容。

再看一段代码,留意注释
function func() {
    console.log('function func');
}
var func = "lanshan";
console.log(func); //>> lanshan
// 以上代码中,按三条规则,变量声明的 func 遇到函数声明的 func 应该会跳过,
// 可是为什么最后 func 的输出结果仍然是被覆盖了显示"lanshan"呢?
// 那是因为三条规则仅仅适用于变量对象的创建阶段,也即执行上下文的创建阶段。
// 而 func="lanshan" 是在执行上下文的执行阶段中运行的,输出结果自然会是"lanshan"。

这种现象很容易让人费解,其实也是因为var声明的变量允许重名导致的,若使用关键字let来声明变量,就可以避免这种令人费解的情况发生。

作用域链(Scope Chain)

定义

多个作用域对应的变量对象串联起来组成的链表就是作用域链,这个链表是以引用的形式保持对变量对象的访问。作用域链保证了当前执行上下文对符合访问权限的变量和函数的有序访问。

作用域链的最顶端一定是当前作用域(local scope)对应的变量对象,最底端一定是全局作用域对应的变量对象(全局VO)。

作用域链可以形象地比如为一个蒸笼。

最底下的一屉,相当于是全局作用域,它里面的蒸汽(变量和函数的可见性)可以渗透到整个蒸笼,底层之上的其他屉相当于局部作用域,这些上面屉的蒸汽只能影响更上面的屉。

作用域链可以理解为下面这种伪代码格式:

{
    Scope: [
        { //当前作用域对应的VO
            实参,
            形参,
            变量,
            函数
        }, 
        { //第二个作用域对应的VO
            实参,
            形参,
            变量,
            函数
        },
        ... 
        { //全局作用域对应的VO
            变量,
            函数
        }
    ]
}
变量/函数的查找机制

在本篇 “作用域(Scope)” 中我们已经了解到,查找变量/函数时JS引擎是从里离它最近作用域开始的查找的,也即从离它最近的变量对象(VO)开始查找。

如果在当前的变量对象里面找不到目标变量/函数,就在上一级作用域的变量对象里面查找。若这时找到了目标变量/函数,则停止查找;若找不到,一直回溯到全局作用域的变量对象里查找,若仍找不到目标变量/函数,停止查找。

变量与运算符

变量声明

letconstvar(不建议使用)来声名变量,作用范围为当前作用域。

  • let用来定义变量
  • const用来定义常量

例如:

let name = "lanshan",age = 18;

let obj = {
    name: "lanshan",
    age:18,
}

const pi = 3.14

变量类型

  • number:数值变量,例如2,2.5
  • string:字符串,例如"lanshan",单引号与双引号均可。
  • boolean:布尔值,例如true,false
  • object:对象,例如{name:"lanshan",age:18}
  • nullnull 是一个原始值,但它的类型在使用 typeof 操作符时会被错误地返回为 "object"。这是 JavaScript 的一个著名特性和历史遗留问题。
  • undefined: 一个变量已经声明但是未赋值,它的类型就是undefine
  • Array:数组,是特殊对象,用于存储多个有序的数据
  • Function:函数,函数也是特殊的对象类型,用于封装可重复执行的代码块,例如function add(a, b) {return a + b;}
  • Symbol: Symbol指的是独一无二的值。每个通过 Symbol()  生成的值都是唯一的
  • Bigint:整数类型只能表示2的1024次方以内的值, 而BigInt 没有位数限制,可以表示任意大小的整数 为了与整数区分,Bigint 类型的整数后面会带有n,如1n

注意:bigint 与 整数类型不互通,不能进行运算,不能使用Math

对象 ?

英文名Object,一个对象包含多个键值key:value对构成。

  • value可以是数组,对象,函数
  • 函数的this用来引用该函数的调用者
  • 对象属性通过.[]来访问

例如:

let cqupt = {
    age: 74,
    student_num:20015,
    money: -250000000,
    addMoney: function (other_money) {
        this.money += other_money;
    }
}

console.log(cqupt.money)
cqupt.addMoney(1000)
console.log(cqupt["money"])

数组 ?

js中数组是一种特殊的对象,数组中的元素可以是具体的值,数组,对象,函数,可以通过下标访问。

例如:

let lanshan = [
18,
"lanteam",
[1, 2, 3],
function () { console.log("麻") },
{ name: "lanshan",age:18}
]

lanshan[3]();

数组常用属性及方法

  • length,返回数组的长度
  • push(),向数组末尾添加一个或者多个元素
  • unshift(),向数组开头添加一个或者多个元素
  • pop(),删除数组末尾的元素,返回删除的元素
  • shift(),删除数组开头的元素并返回删除的元素
  • splice(start,num),删除下标从start开始的num个元素
  • indexOf(),返回数组元素第一次出现的下标,没找到返回-1
  • includes(),判断数组是否包含某个元素,返回布尔值
  • sort(),就地对数组的元素进行排序,并返回对相同数组的引用。默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。
    • 如果要按照数值排序可以为sort传入回调函数
    • array1.sort(function(a, b) { return a - b;});按照数值从小到大排序
    • array1.sort(function(a, b) { return b - a);})按照数值从大到小排序
    • compareFn 可选
      • 定义排序顺序的函数。返回值应该是一个数字,其符号表示两个元素的相对顺序:如果 a 小于 b,返回值为负数,如果 a 大于 b,返回值为正数,如果两个元素相等,返回值为 0NaN 被视为 0。该函数使用以下参数调用:
        • a 第一个用于比较的元素。不会是 undefined
        • b第二个用于比较的元素。不会是 undefined
      • 如果省略该函数,数组元素会被转换为字符串,然后根据每个字符的 Unicode 码位值进行排序。

函数 ?

上面已经说了函数用于封装可重复执行的代码块

定义方式

function add1(a, b) {
  return a + b;
}

let add2 = function (a, b) {
  return a + b;
};

//ES6
const add3 = (a, b) => {
  return a + b;
};

返回值即return后面的内容,多数情况是函数执行的结果

常见运算符

JavaScript运算符是用于操作数据值的符号。它们可以分为以下几类:

一、算术运算符

  1. 加法(+) - 用于数字相加,例如let a = 3 + 5;,这里a的值为8。 - 也可用于字符串拼接,如let str = "Hello " + "World";str的值为"Hello World"

  2. 减法(-) - 用于计算两个数字的差值,如let b = 7 - 3;b的值为4

  3. 乘法(*) - 计算两个数字的乘积,例如let c = 4 * 6;c的值为24

  4. 除法(/) - 用于计算两个数字相除的结果,如let d = 10 / 2;d的值为5。当除数为0时,会返回Infinity(被除数为正数)或-Infinity(被除数为负数)。

  5. 取模(%) - 也叫取余数,计算一个数除以另一个数的余数。例如let e = 7 % 3;e的值为1。它常用于判断一个数是否能被另一个数整除,或者在循环中实现周期性的操作。

  6. 自增(++)和自减(--) - 自增运算符++有两种用法:前置自增(++i)和后置自增(i++)。前置自增是先将变量的值加1,然后再使用变量的值;后置自增是先使用变量的值,然后再将变量的值加1。例如:

 let i = 3; 
 let j = ++i; // i先加1变为4,然后j的值为4 
 let k = i++; // k的值为4(先使用i的值),然后i加1变为5 
  • 自减运算符--的原理与自增运算符类似,也有前置自减(--i)和后置自减(i--)两种用法。

二、比较运算符

  1. 等于(==)和全等于(===)
  • 等于运算符==会在比较之前进行类型转换。例如"3" == 3的结果为true,因为"3"会被转换为数字3后再进行比较。 - 全等于运算符===不会进行类型转换,只有当比较的值和类型都完全相同时才返回true。例如"3" === 3的结果为false,因为它们的类型不同。
  1. 不等于(!=)和不全等于(!==)
  • 不等于运算符!===的反操作,在比较之前会进行类型转换。例如"3"!= 3的结果为false。 - 不全等于运算符!=====的反操作,不会进行类型转换。例如"3"!== 3的结果为true
  1. 大于(>)、小于(<)、大于等于(>=)和小于等于(<=)
  • 这些运算符用于比较两个数字的大小关系。例如5 > 3的结果为true2 <= 2的结果为true。当比较非数字类型时,JavaScript会尝试将其转换为数字进行比较,但这种转换可能会导致意外的结果。例如"5" > "3"的结果为true,因为字符串会按照字符编码(ASCII码等)转换为数字后再比较。

三、逻辑运算符

  1. 逻辑与(&&)
  • 当两个操作数都为true时,结果为true。它采用短路求值的方式,如果第一个操作数为false,则不会计算第二个操作数。例如:
let x = 3;
let y = 5;
let result = (x > 2) && (y < 10); // 结果为true,因为x > 2为true,y < 10也为true
  1. 逻辑或(||)
  • 只要两个操作数中有一个为true,结果就为true。同样采用短路求值,如果第一个操作数为true,则不会计算第二个操作数。例如:
let a1 = 3;
let a2 = 1;
let res = (a1 < 2) || (a2 > 0); // true
  1. 逻辑非(!)
  • 用于对操作数取反。如果操作数为true,则结果为false;如果操作数为false,则结果为true。例如let flag = true; let newFlag =!flag;newFlag的值为false

四、赋值运算符

简单赋值(=)

  • 将右边表达式的值赋给左边的变量。例如let num = 7;,将7赋值给变量num

复合赋值运算符(+=、-=、*=、/=、%=等)

  • 例如+=a += 3等价于a = a + 3。这是一种简写方式,其他复合赋值运算符的原理类似。如b *= 2等价于b = b * 2

五、其他运算符

  1. 三元运算符(? :)
  • 也叫条件运算符,语法为条件表达式?值1:值2。如果条件表达式为true,则返回值1;如果条件表达式为false,则返回值2。例如let max = (a > b)? a : b;,如果a大于b,则max的值为a,否则为b
  1. 逗号运算符(,)
  • 用于在一个表达式中执行多个操作。整个逗号表达式的值是最后一个表达式的值。例如let x = (1, 2, 3);x的值为3,因为先执行1,然后执行2,最后执行3,整个表达式的值取最后一个操作的值。

输入和输出

输入

  • 从HTML与用户交互中输入,例如通过input,textarea等标签获取用户的键盘输入,通过click,hover等事件获取用户的鼠标输入。
  • 通过AjaxWebSocket从服务端获取输入。
  • 标准输入(仅Nodejs),process.stdin,知道即可。

输出

  • 调用`console.log("你想输出的内容")"会将信息输出到浏览器控制台
  • 改变页面的HTML和CSS
  • 通过Ajax和WebSocket将结果返回到服务器

判断语句

JavaScript中的if-elseswitch-case语句和C++中类似

例如:

let score = 90;

if (score >= 90) {
    console.log("A");
} else if (score >= 60) {
    console.log("B");
} else {
    console.log("开始重修");
}

const day = 4;
switch (score) {
    case 1:
          console.log("星期一");
          break;
          
}

循环语句

JavaScript中的forwhiledo-while语句和C++中也类似

for循环

for(let  i = 0; i < 10; i++) {
    console.log(i);
}

枚举对象或者数组时可以用:

  • for-in循环,适用于遍历对象中的key
for (let key in obj) {
    console.log(key + ":" + obj[key]);
}
  • for-of适用于数组和可迭代对象(数组,字符串,Set,Map等)
for (let element of array) {
    console.log(element);
}
  • for-Each用于遍历数组的每个元素,并执行指定的回调函数,第一个回调参数是当前遍历到的元素element,第二个是当前的下标index,第三个是是当前正在遍历的数组array
array.forEach(function(element, index, arr) {})

whiledo-while循环用得较少,下来自行了解

常用库

什么是?库是一组预先编写好的JavaScript代码的集合。它就像是一个工具包,里面包含了许多实用的函数、对象和类,可以帮助开发者更高效地完成各种任务。

下来自行查阅资料,了解以下库的使用和使用细节

作业

  1. JS中{ },null,undefine,NaN有什么区别,用代码验证你的想法。

  2. 打印菱形:

    实现一个打印菱形的函数,函数接收一个参数为菱形的边长。

    打印示例:边长为7的菱形

    屏幕截图 2024-10-23 092717.png

  3. 深拷贝实现:

    手写一个深拷贝函数,能够处理基本类型、对象、数组、以及循环引用等。

  4. 判断数据类型:

    如何判断JavaScript中的数据类型