JavaScript

JavaScript 基础入门

什么是 JavaScript?

虽然名字很像……但是和 Java 没有一点关系 🙃

让我们开始吧!

Javascript 可以运行在

  • 浏览器控制台
    • F12 -> Console 选项卡

和我们的 HTML 文件绑定在一起(后面会讲)

JavaScript 数据类型

弱类型语言的噩梦才刚刚开始

JavaScript 有 5 种原始数据类型:

  • Boolean (true, false)
  • Number (12, 1.618, -46.7, 0, etc.)
  • String ("hello", "world!", "12", "", etc.)
  • Null (null)
  • Undefined (undefined)

运算符

大多数和你想象的一样,除了……

ACTION ITEMS

  • 打开浏览器控制台
  • 输入 1 + 1
  • 输入 5 % 2
  • 输入 "hello" + " world"
  • 输入 1/3
  • 输入 8 > 3
  • 输入 1 + true

=== 和 ==,还有……

弱类型语言魅力时刻

ACTION ITEMS

  • 输入 "1" + 1
  • 输入 1 + "1"
  • 输入 "123" > 122
  • 输入 1 === 1
  • 输入 6 !== 7
  • 输入 1 == "1"
  • 输入 1 === "1"
  • 输入 6 != "6"
  • 输入 6 !== "6"

Warning

tl;dr:不要使用 ==!=

在 JavaScript 中,==!= 会自动将类型转换为相同的类型 (type coercion),而 ===!== 不会

Javascript 语法

函数、变量、常量、注释

// 这个函数找到两个数字的最大公约数
const greatestCommonDivisor = (a, b) => {
  while (b !== 0) {
    const temp = b;
    b = a % b;
    a = temp;
  }
  return a;
};
 
const x = 50;
const y = 15;
const gcd = greatestCommonDivisor(x, y); // 5
  • JavaScript 中的每个语句都以分号结尾;
  • 空白字符会被忽略。(但可以提高可读性)
  • 大括号表示代码块的开始和结束。
  • 注释不会影响代码的运行,但你应该使用它们来保持代码库的可读性!

定义变量和常量

let 和 const

let x = 5;
let y = 10;
let z = x + y;
let s = "string";
 
s = "another";
z = 1 + 1;
  • let 是定义变量的关键字。
  • 单个等号用于变量赋值
  • 变量名是我们能够"赋予"值的标识符
  • 在 JavaScript 中,当你第一次使用变量时,必须声明它
  • 我们使用 let 关键字来声明变量。之后,我们可以自由地改变其值

Note

JavaScript 约定使用 camelCase 命名变量。除了第一个单词外,每个单词都大写。

Warning

在严格模式下,当变量已经存在时重新用 let 声明变量不会工作,但在非严格模式下运行时不会报错

const ans = 42;
ans = 0; // 报错
  • const 是定义常量的关键字。
  • 安全代码的最佳实践。不该改的就不能改。

let vs. var

Warning

tl;dr:不要使用 var

  • 在 ES6 之前,我们使用 var 来定义变量。
  • 现在我们使用 let 来定义变量。
  • STFW
    • var 的作用域是函数作用域,而不是块作用域。
    • let 的作用域是块作用域。
    • var 会变量提升,而 let 不会。
    • var 可以重复声明变量,而 let 不能。

null vs undefined

  • undefined 表示“声明但未分配值”
  • null 表示“没有值”
    • 最佳实践: 使用 null 来清空一个变量。明确地说变量现在是空的。
let name; // undefined
name = "some"; // assigned
name = null; // no name

输出

  • 我们通常输出到控制台,便于快速调试!
    • console.log()
    • console.error()
    • console.warn()
    • console.info()
    • console.debug()
  • console.log() 是最常用的,而且可以接受多个参数,几乎什么都能输出
    • console.log("Hello", "world!");
    • console.log("The answer is", 42);
    • console.log("The answer is %d", 42);
    • console.log(`The answer is ${41 + 1}`); (template literals / 格式化字符串)
  • 使用 alert 弹窗
    • alert("Hello, world!");

数组

Array

当你想要存储一系列(理想情况下是相似的)项目时:

// 初始化
let pets = ["flower", 42, false, "bird"];
 
// 访问
console.log(pets[3]); // "bird"
 
// 替换
pets[2] = "hamster"; // ["flower", 42, "hamster", "bird"]
  • 数组是可变的,可以存储任何类型的数据
  • 数组是基于 0 的索引,这意味着第一个元素的索引是 0,第二个是 1,依此类推
// 初始化
let pets = ["cat", "dog", "guinea pig", "bird"];
 
// 从末尾删除
pets.pop(); // ["cat", "dog", "guinea pig"]
 
// 添加到末尾
pets.push("rabbit"); // ["cat", "dog", "guinea pig", "rabbit"]

控制流

条件语句

  • 我们经常希望根据不同的条件执行不同的操作。
  • 为此,我们使用条件运算符 if、else 和 else if:

Note

注意缩进(tab)!

这不是必需的,但它会使你的代码更具可读性。

if (hour < 12) {
  console.log("Good morning!");
} else if (hour < 16) {
  console.log("Good afternoon!");
} else if (hour < 20) {
  console.log("Good evening!");
} else {
  console.log("Good night!");
}

While 循环:

let z = 1;
while (z < 1000) {
  z = z * 2;
  console.log(z);
}

For 循环:

const pets = ["cat", "dog", "guinea pig", "bird"];
 
for (let i = 0; i < pets.length; i++) {
  const phrase = "I love my " + pets[i];
  console.log(phrase);
}

一种更“Pythonic”的迭代方式:

const pets = ["cat", "dog", "guinea pig", "bird"];
 
for (const pet of pets) {
  console.log("I love my " + pet);
}

面向对象

和一般的面向对象不同,Javascript 是基于原型的

对象

  • 对象 / Object 是属性的集合
  • 每个属性都有一个名称,描述其含义;以及一个值,描述该对象属性的值。
  • 语法:用大括号括起来,冒号分隔名称和值,逗号分隔值对。允许你将相关数据存储在一个变量中。
  • 可以将它们视为 JavaScript 中相当于 Python 字典的部分。
  • 那么,我们可以用它做什么呢?
const person = {
  name: "John",
  age: 30,
};

访问属性、解构

  • 如果你知道属性名称,有两种方法可以访问对象属性:
const myCar = {
  make: "Ford",
  model: "Mustang",
  year: 2005,
  color: "red"
};
 
console.log(myCar.model); // "Mustang"
console.log(myCar["color"]); // "red"
  • 或者你可以通过解构同时获取多个
const { model, color } = myCar;
console.log(model, color); // "Mustang" "red"

等价性?

let arr1 = [1, 2, 3];
let person1 = {
  name: "Bill Gates"
};
 
let arr2 = [1, 2, 3];
let person2 = {
  name: "Bill Gates"
};
 
arr1 === arr2; // false!
person1 === person2; // false!
  • === 对数组和对象意味着什么?
    • P.S. == 也是一样的结果,但是我们不使用

对象引用和拷贝

  • 对象变量是引用
    • 它们指向数据实际存储的位置
  • Javascript 中,=== 比较的是引用,而不是值,即检查两个数据是否存储在同一位置
    • Python 仍然会比较值

浅拷贝和深拷贝

  • 浅拷贝:只拷贝对象的第一层
  • 深拷贝:拷贝对象的所有层
// Shallow copy
let arr = [1, 2, 3];
let copyArr = arr;
// 修改 copyArr 也会修改 arr
// Deep copy
let arr = [1, 2, 3];
let copyArr = [...arr];
// 修改 copyArr 不会修改 arr

函数

定义函数

# Python
celsiusToFahrenheit = lambda tempCelsius: tempCelsius * 9/5 + 32
// Javascript
const celsiusToFahrenheit = (tempCelsius) => {
  const tempFahrenheit = tempCelsius * 9/5 + 32;
  return tempFahrenheit;
};
  • 这段代码的作用是,为这个函数分配内存空间,然后将函数存储在内存中。
  • 因此,我们在内存的某个地方记录了接收整数温度、进行计算并返回结果的功能。
  • 但为了真正使用这个函数,我们需要调用它。
  • 在 JS 中,我们通过将函数赋值给变量来实现这一点。
  • 注意,这里的语法与其他变量完全相同
  • const,表示我们不会改变这个变量指向的内容
const roomTemp = celsiusToFahrenheit(26); // 将 78.8 赋值给 roomTemp

回调函数

  • 在 JavaScript 中,函数可以像其他任何值一样被传递。
  • 这意味着我们可以将一个“回调”函数作为参数传递给另一个函数!

setTimeout()

const printSomething = () => {
  console.log("i sleep");
};

setTimeout() 在指定延迟后调用一个函数。它接受 2 个参数:要调用的函数(即回调函数)和延迟时间(以毫秒为单位)。

我们如何在 5 秒后打印消息?(多选)

A. setTimeout(printSomething, 5000);
B. setTimeout(printSomething(), 5000);
C. setTimeout(() => { console.log("i sleep"); }, 5000);

setInterval() vs setTimeout()

特性setInterval()setTimeout()
功能重复调用一个函数,每次调用之间有延迟。设置一个计时器,当计时器结束时执行一个函数。
返回值返回一个 intervalID ,可以传递给 clearInterval() 来终止。返回一个 timeoutID ,可以传递给 clearTimeout() 来取消。
参数func, delay, arg0, ..., argNfunc, delay, arg0, ..., argN

Note

查看 MDN 获取更多信息!

Practice!

Note

Write a function that takes in an array of temperatures (in Celsius) and outputs an array with all the temperatures converted to Fahrenheit.
DO NOT mutate (change) the input array!
Paste your code into the browser console (or write it directly there) to test.

tempF  = tempC * 9/5 + 32

Practice!

Cont'd

Note

What if adding:

  • Converting an array of Celsius temps to Fahrenheit (what we just did)
  • Converting an array of Fahrenheit temps to Celsius
  • Converting an array of Celsius temps to Kelvin
const arrCtoF = (arrC) => {
  const arrF = [];
    for (let i = 0; i <= arrC.length; i ++) {
    arrF.push(
      arrC[i] * 9/5 + 32
    );
  }
    return arrF;
}
const arrAtoB = (arrA, AtoB) => {
  const arrB = [];
    for (let i = 0; i <= arrA.length; i ++) {
    arrB.push(
      AtoB(arrA[i])
    );
  }
    return arrB;
}
const arrCtoF = arrAtoB(arrC, (tempC) => tempC * 9/5 + 32);

map

const arr = [1, 2, 3, 4, 5];
const arr2 = arr.map((x) => x * 2); // [2, 4, 6, 8, 10]

更加函数式!

更加数学化的函数

  • 函数式编程是一种编程范式,它将计算视为数学函数的求值,并避免改变状态和可变数据。
  • Eg. f(x) = x + 1 是一个函数,它接受一个输入 x 并返回一个输出 x + 1,这个函数没有副作用,即它不会改变任何外部状态,也不会因为外部状态的改变而改变自己的输出。
const addOne = (x) => x + 1;
  • 纯粹的函数式编程主要强调以下几点
    • 函数是一等公民:函数可以作为参数传递给其他函数,也可以作为返回值返回。
    • 只用表达式,不用语句:每一步都是有返回值的运算。
    • 无副作用:函数不会改变任何外部状态,也不会因为外部状态的改变而改变自己的输出。(I/O 也是副作用,所以函数式编程中通常会把 I/O 减少到最小)
    • 引用透明:只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
  • 我们这里主要介绍 Javascript 中函数式的思想的具体运用,这将有助于我们未来使用 React 的 Hooks 和 Functional Components。

函数链

  • Javascript 中有许多高阶函数,它们接受函数作为参数,并返回一个新函数。
    • Eg. map, filter, reduce
    • 甚至在网络请求中,我们也可以用 fetch(...).then(...).catch(...).finally(...) 来链式调用。
  • 接受一个函数作为参数,对数组中的每个元素应用该函数,并返回一个新数组。
  • 不会改变原数组,而是返回一个新数组。
  • 可以链式调用。
const arr = [1, 2, 3, 4, 5];
const arr2 = arr.map((x) => x * 2).filter((x) => x > 5); // [6, 8, 10]
// reduce
const sum = arr.reduce((acc, x) => acc + x, 0); // 15

回调函数的实际运用

addEventListener

  • 接受两个参数:事件类型和回调函数。
  • 回调函数会在事件发生时被调用。
// 按钮在点击时会调用回调函数
const button = document.querySelector("button");
button.addEventListener("click", () => {
  console.log("clicked");
});
// 窗口在按下按键时会调用回调函数
window.addEventListener("keydown", (event) => {
  console.log(event.key);
});

More at MDN - addEventListener

fetch

  • 接受一个 URL 作为参数,返回一个 Promise。
  • 可以链式调用。
await fetch("/api/v1/test")
  .then((response) => response.json())
  .then((data) => console.log("Data:", data))
  .catch((error) => console.error("Error:", error))
  .finally(() => console.log("Done."));

More at MDN - fetch

思考题

这个是什么?

const [count, setCount] = useState(0);

  • 类用于当我们有多个对象都表示同类型事物时。它们允许我们定义该类型或该类中每个对象都具有的属性和方法。

Note

别担心,类不会在我们的课堂上使用到!

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
 
  getArea = () => {
    return this.width * this.height;
  };
}
 
const rect = new Rectangle(6, 8);
console.log(rect.getArea()); // 48

Takeaway

JavaScript 是我们实现功能的关键!

  • 使用 let, const 声明变量。
  • boolean, number, string, null, undefined
  • 函数、数组、对象、类
  • if, else, while, for
  • setInterval() vs. setTimeout()