(DONE) JS MDN Learn

系统学习 JavaScript 第一步——MDN 的 Learn 部分,相当于基础入门。

是否完成: Yes

first steps

building blocks

objects

asynchronous JS

Client-side web APIs

Solving common JavaScript problems


第一步

什么是 JavaScript?

从全局来看 JS,讨论 JS 是什么、你能用它做什么等问题,明确使用 JS 的目的。

抽象定义:JS 是一种编程语言,允许你在网页中实现复杂功能。

还有,如果可以通过 CSS 对文本进行改变就不应该通过 JS 实现。

一段更新文本的 JS 代码:

const para = document.querySelector('p');

para.addEventListener('click', updateName);

function updateName() {
  const name = prompt('Enter a new name');
  para.textContent = `Player 1: ${name}`;
}

有一些被称为 APIs(Application Programming Interfaces,应用编程接口)功能,大致分为两类——浏览器 APIs 和第三方 APIs。

JS 能在页面加载完毕后动态修改页面内容。每个浏览器标签都处在独立的执行环境互不干涉。JS 代码从上到下依次执行。

解释(interpreted)型代码与编译(compiled)型代码:对于解释型编程语言,代码从上到下依次执行,代码的执行结果是即刻返回的;对于编译型编程语言,在由电脑运行以前先被转换为另一种形式,例如 C 语言会被编译成机器码再被电脑执行。JS 是轻量级的解释型编程语言。现代 JS 解释器使用了一种名为即时编译(just-in-time compiling)的技术来改善性能,在实际运行 JS 代码时,代码会被转换为一种二进制格式,执行速度更快。虽然应用了该种技术,但 JS 仍被视为解释型编程语言,因为这里的编译过程是在运行时进行的而不是提前完成。

服务端(Server-side)代码与客户端(Client-side)代码:在本次 JS Learn 部分,讲述的全部是客户端 JS——运行在用户主机上的 JS 代码。服务端代码运行在服务器上,服务端编程语言有 PHP、Python。

动态代码与静态代码:动态包括客户端 JS 和服务端语言,它们能够在不同情况下更新网页显示不同内容,按需生成新内容。服务端代码在服务器生成新内容,而客户端 JS 在客户端的浏览器内生成。不会动态更新内容的网页被称为动态网页,我的博客就是这样的静态网页组成的,进而可称为静态博客。

第一次尝试 JavaScript

像程序员一样思考,通过程序解决现实生活中的问题。如何像程序员一样思考?

  • 知道程序的功能
  • 知道具备这些功能需要哪些代码层面的东西
  • 如何将代码组合在一起工作

这需要各种努力:熟悉编程语言语法、持续练习以及一点创造力。

这一节给一个“猜数字”的例子,输入1-100的任意数字,程序根据是否与给定数字相等,并返回相应结果。

出了什么问题?JavaScript 故障排除

错误种类:语法错误和逻辑错误。

存储你需要的信息——变量

变量是值的容器,值可能是数字、字符串、布尔值、数组、对象、等等。可通过 const、let、var 声明变量。变量的初始化,const 必须初始化。变量提升

JavaScript 中的基本数学——数字和操作符

十进制数的不同类型:整数、浮点数、双精度浮点数。不同的数字系统:二进制、八进制、十六进制。JavaScript 对应的数据类型——Number

一些 Number 方法:

  • toFixed() 保留几位小数
  • Number(string) 将字符串转化为数字

算术操作符:+、-、*、/、%、**(指数)。它们存在优先级,和数学中的一样,“先算乘除、后算加减”。自操作符: -- 、++。赋值操作符:=、 +=-=*=/= 。比较操作符: ===!== 、<、>、<=、>=。

在 JavaScript 中处理文本——字符串

JS 中的字符串——由单双引号围起的字符。用反斜杠转义引号。字符串拼接用 ``+ 。用 ``\n 能输出多行字符串。

通过 Number() 可以把字符串转换成数字;而相应的,通过 toString() 可以把数字转换成字符串。

可以在字符串中插入运算符。

有用的字符串方法

  • String length
  • String[0]
  • String.prototype.includes()
  • String.prototype.startsWith()
  • String.prototype.endsWith()
  • String.prototype.indexOf()
  • String.prototype.slice()
  • String.prototype.toLowerCase()
  • String.prototype.toUpperCase()
  • String.prototype.replace()
  • String.prototype.replaceAll()

数组

数组是一串字符串组成的一个分组。数组例子: ['abc', 'def', '123', '456']['abc', 'def', [ '123', '456' ]] 。第二个是一个多维数组。

  • Array.prototype.length
  • Array[0]
  • Array.prototype.indexOf()
  • Array.prototype.push()
  • Array.prototype.unshift()
  • Array.prototype.pop()
  • Array.prototype.shift()
  • Array.prototype.splice()
  • Array.prototype.map()
  • Array.prototype.filter()
  • String.prototype.split() 字符串转换成数组
  • Array.prototype.join() 数组转换成字符串
  • Array.prototype.toString() 数组转换成字符串

任务:蠢故事生成器

构建块

在代码中做决定——条件句

  • if…else
  • if…
  • if…else if…else

使用逻辑操作符: && , || , !

使用 switch 语句时,default 不是必须要加上去的。case 后只能跟一个值,可见以下对比:

switch (expression) {
  case value1 || value2:
    ...
}

switch (expression) {
  case value1:
  case value2:
    ...
}

第一个 switch 用法错误(如果是表达式就可以用逻辑操作符连接,如下所示),第二个是正确的。

switch (true) {
  case score >= 0 && score < 20:
    response = "";
    break;
  case score >= 20 && score < 40:
    response = "";
    break;
  case score >= 40 && score < 60:
    response = "";
    break;
  case score >= 60 && score < 80:
    response = "";
    break;
  case score >= 80 && score < 100:
    response = "";
    break;
}

三元操作符。

在进行条件判断时,false, undefined, null, 0, NaN, 空字符串 会返回 false ,其他情况均返回真。

循环

在一个元素集合中循环迭代,这些元素集合有几类——Array、Set、Map。

循环句式:

  • for…of

更特殊的循环方法:map(), filter()。

标准 for 循环:

for (initializer; condition; final-expression) {
  // code to run
}

注意,与 for…of 的区别。

break、continue

while:

initializer
while (condition) {
  // code to run

  final-expression
}

如何选择循环句式:

  1. 迭代数组时,不需要特别指定次序,使用 for…of 最佳
  2. 其他情况,用 for while do…while 彼此大概率可互换

person === "Phil" || person === "Lola"person === ("Phil" || "Lola") 在 if 句式中并不相同,为何?

phonebook[i].name.toLowerCase() 可以, phonebook[i][name].toLowerCase() 报错,为何?

while (i > 1) {
  if (isPrime(i) === true) {
    para.textContent += `${i} `
  } else {
    i--
    continue
  }

  i--
}

// Refer:
// https://discourse.mozilla.org/t/assessment-request-for-loops-3-skill-test-confused-on-using-continue-statement-with-loops/67100

上面代码,如果没有第一个 i-- 就会陷入无限循环。

函数——可复用的代码块

JS 有很多内建函数,比如 string.replace(), array.join(), Math.random() 等等。

如果函数是属于对象的就被称为方法。函数表达式、函数参数、指定默认函数参数、匿名函数与箭头函数、函数作用域。

可为函数指定默认参数。

function hello(name = "Jim") {
  console.log(`Hello ${name}!`);
}

hello();
hello("tianheg");

匿名函数,函数表达式:

(function () {
 alert('hello');
})

const helloAlert = function () {
  alert('hello');
}

与函数声明不同,函数表达式不提升。

函数作用域:

全局作用域

Test your skills: Functions 3 的解决办法:

const names = [
  "Chris",
  "Li Kang",
  "Anne",
  "Francesca",
  "Mustafa",
  "Tina",
  "Bert",
  "Jada"
];
const para = document.createElement("p");
const section = document.querySelector("section");

// Add your code here
// Refer https://codepen.io/MacNulty/project/editor/XxYjLw
function random(lowerBound, upperBound) {
  return Math.floor(Math.random() * (upperBound - lowerBound)) + lowerBound;
}

function chooseName() {
  return names[random(0, 7)];
}
para.textContent = chooseName();

// Don't edit the code below here!

section.appendChild(para);

构建自己的函数

btn.addEventListener("click", funcName)btn.addEventListener("click", funcName()) 有区别,前者只有 click 事件发生时才执行,后者只要页面 reload 就立即执行不等待 click 事件发生,在此种上下文中 funcName() 中的括号还被称为“函数调用运算符(function invocation operator)”。 btn.addEventListener("click", () => funcName("sth")) 此种匿名函数形式,则不会如上述第二种立即执行,该种不在立即执行的作用域中。

函数返回值

有些函数无返回值。通过函数返回计算值。使用 return 返回值。

介绍事件

btn.addEventListener("click", () => {
  const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
  document.body.style.backgroundColor = rndCol;
});

在 JS 中,面向网页的事件模型与用于其他环境的事件模型并不相同。

addEventListener(), removeEventListener() 第二个不明白如何使用,我以为添加一个事件,再通过第二个移除后,添加的事件会失效,但实际并非如此。使用 AbortController() 就可以:

const clickTarget = document.querySelector("button");

const controller = new AbortController();

clickTarget.addEventListener("click", changeBackground, {
  signal: controller.signal
});

controller.abort();

function changeBackground() {
  const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
  document.body.style.backgroundColor = rndCol;
}

function random(num) {
  return Math.floor(Math.random() * (num + 1));
}
<button>Change color</button>

为单独事件添加多个监听器:

myElement.addEventListener('click', functionA);
myElement.addEventListener('click', functionB);

当事件发生,两个函数都会执行。

其他注册事件处理程序的方式:

1, event handler properties

btn.onclick = () => {
  // ...
}

此时就不能设置多个监听函数了。

2, inline event handlers(写 MDN 文档的人不建议使用)

<button onclick="bgChange()">Press me</button>

事件对象

function bgChange(e) {
  const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
  e.target.style.backgroundColor = rndCol;
  console.log(e.target);
}

e.target 指代的就是,触发特定事件的元素,这里就是 button

阻止事件默认行为

const form = document.querySelector("form");
const fname = document.getElementById("fname");
const lname = document.getElementById("lname");
const para = document.querySelector("p");

form.addEventListener("submit", (e) => {
  if (fname.value === "" || lname.value === "") {
    e.preventDefault();
    para.innerHTML += "You need to fill in both names!<br>";
    para.style.color = "red";
  }
});
<form>
  <div>
    <label for="fname">First name: </label>
    <input type="text" id="fname">
  </div>
  <div>
    <label for="lname">Last name: </label>
    <input type="text" id="lname">
  </div>
  <div>
    <input type="submit" id="submit">
  </div>

</form>
<p></p>

Event bubbling

<body>
  <div id="container">
    <button>Click me!</button>
  </div>

  <pre id="output"></pre>
</body>
const output = document.querySelector("#output");

function handleClick(e) {
  output.textContent += `You clicked on a ${e.currentTarget.tagName} element\n`;
}

const container = document.querySelector("#container");
const button = document.querySelector("button");

document.body.addEventListener("click", handleClick);
container.addEventListener("click", handleClick);
button.addEventListener("click", handleClick);

事件触发的顺序是由内而外,依次进行的。正是因为这种元素间的包含关系,在部分情况下,会造成用户的困扰。例如,这个视频播放器例子。例子中的问题可通过 stopPropagation() 解决,它能阻止事件传递到父元素。

Event capture - 事件繁殖的一种可选形式

它像 event bubbling 但顺序是反过来的。

Event delegation - 不必单独为子元素设置事件,只需要设置父元素,子元素会被自动包含。

作业:图片库

关键的 JS 内容:

const displayedImage = document.querySelector(".displayed-img");
const thumbBar = document.querySelector(".thumb-bar");

const btn = document.querySelector("button");
const overlay = document.querySelector(".overlay");

/* Declaring the array of image filenames */
const images = ["pic1.jpg", "pic2.jpg", "pic3.jpg", "pic4.jpg", "pic5.jpg"];
/* Declaring the alternative text for each image file */
const alts = {
	"pic1.jpg": "Closeup of a human eye",
	"pic2.jpg": "draw",
	"pic3.jpg": "flower",
	"pic4.jpg": "ancient",
	"pic5.jpg": "butterfly",
};
/* Looping through images */
for (const image of images) {
	const newImage = document.createElement("img");
	newImage.setAttribute("src", `./images/${image}`);
	newImage.setAttribute("alt", alts[image]);
	thumbBar.appendChild(newImage);

	newImage.addEventListener("click", (event) => {
		displayedImage.src = event.target.src;
		displayedImage.alt = event.target.alt;
	});
}

/* Wiring up the Darken/Lighten button */
btn.addEventListener("click", (e) => {
  const btnClass = btn.getAttribute("class")
  if (btnClass === "dark") {
    btn.setAttribute("class", "light")
    btn.textContent = "Lighten"
    overlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)"
  } else {
    btn.setAttribute("class", "dark")
    btn.textContent = "Darken"
    overlay.style.backgroundColor = "rgba(0, 0, 0, 0)"
  }
});

对象

基础

const person = {
  name: ["Bob", "Smith"],
  age: 32,
  bio: function() {
    console.log(`${this.name[0]} ${this.name[1]} is ${this.age} years old.`)
  },
  introduceSelf: function() {
    console.log(`Hi! I'm ${this.name[0]}.`)
  }
}
person.bio()
person.introduceSelf()

// 当对象的键值是函数时,function 关键字可省略

const person = {
  name: ["Bob", "Smith"],
  age: 32,
  bio() {
    console.log(`${this.name[0]} ${this.name[1]} is ${this.age} years old.`)
  },
  introduceSelf() {
    console.log(`Hi! I'm ${this.name[0]}.`)
  }
}
person.bio()
person.introduceSelf()

以上定义出来的对象,被称为模板字面量。

通过 .[] 访问对象的键值。

将对象设为对象属性

const person = {
  name: {
    first: "Bob",
    last: "Smith",
  },
  // ...
}

person.name.first
person.name.last

对象有时被称为 associative arrays,因为它可以用 [] 访问内部键值。例如, person["name"]["first"] 。在某些情况下,只能用 [] ,比如,如果通过变量访问对象内部键值。

const person = {
  name: ["Bob", "Smith"],
  age: 32,
};

function logProperty(propertyName) {
  console.log(person[propertyName])
};

logProperty("name")

设置对象键值

person.age = 34
person["name"]["last"] = "Smith"

this 指代什么

The this keyword refers to the current object the code is being written inside — so in this case this is equivalent to person .

const person1 = {
  name: "Amy",
  introduceSelf() {
    console.log(`Hi! I'm ${this.name}.`)
  }
}
const person2 = {
  name: "Tom",
  introduceSelf() {
    console.log(`Hi! I'm ${this.name}.`)
  }
}
person1.introduceSelf()
person2.introduceSelf()

介绍构造器(constructors)

创建多个对象的一般方法:

function createPerson(name) {
  const obj = {}
  obj.name = name
  obj.introduceSelf = function () {
    console.log(`Hi! I'm ${this.name}.`)
  }
  return obj
}

const salva = createPerson("Salva")
console.log(salva.name)
salva.introduceSelf()
const frankie = createPerson("Frankie")
console.log(frankie.name)
frankie.introduceSelf()

使用构造器创建多个对象:

function Person(name) {
  this.name = name
  this.introduceSelf = function () {
    console.log(`Hi! I'm ${this.name}.`)
  }
}

const salva = new Person("Salva")
console.log(salva.name)
salva.introduceSelf()
const frankie = new Person("Frankie")
console.log(frankie.name)
frankie.introduceSelf()

Test your skills: 对象基础

function Cat(name, breed, color) {
  this.name = name;
  this.breed = breed;
  this.color = color;
  this.greeting = function() {
    console.log(`Hello, said ${this.name} the ${this.breed}.`)
}
}

const cat1 = new Cat('Bertie','Cymric', 'white');

// console.log(cat1.greeting());
// 应改为
cat1.greeting()

结果中为什么有 undefined。因为我在已有的 console.log() 上又套了一个。

原型

原型是 JS 对象彼此继承特性的关键机制。本节内容关于:

  1. 什么是「原型」
  2. 原型链的工作原理
  3. 如何设置一个对象的原型

原型链

Every object in JavaScript has a built-in property, which is called its prototype . The prototype is itself an object, so the prototype will have its own prototype, making what's called a prototype chain . The chain ends when we reach a prototype that has null for its own prototype.

当我们查找一个对象的属性时,如果对象本身不包含这个属性,就会查找对象的原型。如果仍然找不到,则搜索原型的原型。直到,要么找到目标属性,要么到达原型链的末端(返回 undefined ,或许不是 undefined,而是 null)。

var myDate = new Date()
var object = myDate
do {
  object = Object.getPrototypeOf(object)
  console.log(object)
} while (object)

属性覆盖(Shadowing properties)

var myDate = new Date(2000, 12, 13)
console.log(myDate.getYear()) // 101

myDate.getYear = function() {
  console.log("Something else!")
}
myDate.getYear() // Something else!

设置原型

两种设置原型的方法:

  1. Object.create()
  2. 构造器函数
// 方法 1
var personPrototype = {
  greet() {
    console.log("Hello!")
  }
}

var carl = Object.create(personPrototype)
carl.greet() // Hello!

// 方法 2
var personPrototype = {
  greet() {
    console.log(`Hello, my name is ${this.name}!`)
  }
}

function Person(name) {
  this.name = name
}

Object.assign(Person.prototype, personPrototype)

var reuben = new Person("Reuben")
reuben.greet() // Hello, my name is Reuben!

从方法 2 的创建过程中可见, name 属性直接在构造器中定义, greet() 方法则是在原型中定义。

直接通过构造器函数定义的属性,被称为自有属性。可通过 Object.hasOwn() 方法确定自有属性:

var personPrototype = {
  greet() {
    console.log(`Hello, my name is ${this.name}!`)
  }
}

function Person(name) {
  this.name = name
}

Object.assign(Person.prototype, personPrototype)

var reuben = new Person("Reuben")
console.log(Object.hasOwn(reuben, "name")) // true
console.log(Object.hasOwn(reuben, "age")) // false

原型和继承

原型支持某种继承方式。继承是面向对象编程语言的一个特点,它能表达出这样的想法——系统中的某些对象比其他对象更为特殊。

面向对象编程概念

面向对象编程(Object-oriented programming, OOP)是一种编程范式,是很多编程语言的基础内容,如 Java 和 C++。本节主要讨论:

  1. 类和实例(classes and instances)
  2. 继承(inheritance)
  3. 封装(encapsulation)
  4. 以上三个概念在 JS 中的体现

面向对象编程是关于,将对象模型化一系列对象的集合,每个对象代表了系统的一个方面。对象包括函数(或方法)和数据。对象提供对外接口供其他代码使用,但也保持自己的私有内部状态。系统的其他部分无需关心该对象的内部状态。

类和实例

Professor 类的伪代码:

class Professor
    properties
        name
        teaches
    constructor
        Professor(name, teaches)
    methods
        grade(paper)
        introduceSelf()

Each concrete professor we create is called an instance of the Professor class. The process of creating an instance is performed by a special function called a constructor .

继承

Student 类的伪代码:

class Student
    properties
        name
        year
    constructor
        Student(name, year)
    methods
        introduceSelf()

通过观察,发现 Professor 和 Student 有相同的部分,可以将他们提炼为 Person:

class Person
    properties
        name
    constructor
        Person(name)
    methods
        introduceSelf()

class Professor : extends Person
    properties
        teaches
    constructor
        Professor(name, teaches)
    methods
        grade(paper)
        introduceSelf()

class Student : extends Person
    properties
        year
    constructor
        Student(name, year)
    methods
        introduceSelf()

This feature - when a method has the same name but a different implementation in different classes - is called polymorphism. When a method in a subclass replaces the superclass's implementation, we say that the subclass overrides the version in the superclass.

封装

Keeping an object's internal state private, and generally making a clear division between its public interface and its private internal state, is called encapsulation .

封装的含义——将要用到的函数方法,初始化在最开始的 class 中。

class Student : extends Person
    properties
        private year
    constructor
        Student(name, year)
    methods
        introduceSelf()
        canStudyArchery() { return this.year > 1 }

student = new Student("Weber", 1)
student.year // error: 'year' is a private property of Student

OOP 与 JS

  • JS 中的构造器和原型概念,与 OOP 中的 class 相似。通过构造器的原型属性定义的方法,可被通过构造器创建的对象继承
  • 原型链是一种实现继承的方法。例如,如果 Student 的原型是 Person,那么 Student 可以继承来自 Person 的 name 属性,并覆盖 Person 的 introduceSelf() 方法

但要注意的是, JS 的这些功能和经典 OOP 概念还是有所区别的 。以下是进一步描述:

首先,在基于类的 OOP 中,类和对象是两个独立的构造体,而对象总是作为类的实例而被创建。而且,用于定义类(类语法本身)的功能和用于实例化对象(构造器)的功能,两者是有区别的。在 JS 中,我们能够也经常无 class 定义而创建对象,要么通过函数,要么通过对象字面量。

第二,虽然原型链长得像继承的层次并且在某方面表现得也像,但在其他方面却有所不同。实例化子类时,创建了一个单独的对象,它将子类中定义的属性与层次结构中进一步定义的属性结合起来。而对于原型,层次结构的每个层级都由独立的对象表示,它们通过 __proto__ 属性连接。这种原型链的表现不太像继承,更像是代表( delegation , 这个词在前述章节的学习中见过,在 Event delegation 中)。

Delegation is a programming pattern where an object, when asked to perform a task, can perform the task itself or ask another object (its delegate ) to perform the task on its behalf. In many ways, delegation is a more flexible way of combining objects than inheritance (for one thing, it's possible to change or completely replace the delegate at run time).

JS 中的类

类和构造器、继承

class Person {
  name;
  constructor(name) {
    this.name = name
  }
  introduceSelf() {
    console.log(`Hi! I'm ${this.name}.`)
  }
}
class Professor extends Person {
  teaches;
  constructor(name, teaches) {
    super(name)
    this.teaches = teaches
  }
  introduceSelf() {
    console.log(`My name is ${this.name}, and I will be your ${this.teaches} professor.`)
  }
  grade(paper) {
    const grade = Math.floor(Math.random() * (5 - 1) + 1)
    console.log(grade)
  }
}

// var giles = new Person("Giles")
// giles.introduceSelf()

var walsh = new Professor("Walsh", "Psychology")
walsh.introduceSelf()
walsh.grade('my paper')

封装

class Person {
  name;
  constructor(name) {
    this.name = name
  }
  introduceSelf() {
    console.log(`Hi! I'm ${this.name}.`)
  }
}
class Student extends Person {
  #year
  constructor(name, year) {
    super(name)
    this.#year = year
  }
  introduceSelf() {
    console.log(`Hi! I'm ${this.name}, and I'm in year ${this.#year}.`)
  }
  canStudyArchery() {
    return this.#year > 1
  }
}

const summers = new Student("Summers", 2)
summers.introduceSelf() // Hi! I'm Summers, and I'm in year 2.
summers.#year // Uncaught SyntaxError: reference to undeclared private field or method #year

私有方法

class Example {
  somePublicMethod() {
    this.#somePrivateMethod()
  }
  #somePrivateMethod() {
    console.log("You called me?")
  }
}

const myExample = new Example()
myExample.somePublicMethod()
myExample.#somePrivateMethod() // Uncaught SyntaxError: reference to undeclared private field or method #somePrivateMethod

Test your skills: JS 中的类

// OOJS 1
class Shape {
  name;
  sides;
  sideLength;
  constructor(name, sides, sideLength) {
    this.name = name
    this.sides = sides
    this.sideLength = sideLength
  }
  
  calcPerimeter() {
    const perimeter = this.sides * this.sideLength
    console.log(`${this.name}'s perimeter is ${perimeter}.`)
  }
}

const square = new Shape("square", 4, 5)
square.calcPerimeter()
const triangle = new Shape("triangle", 3, 3)
triangle.calcPerimeter()

// OOJS 2

class Square extends Shape {
  constructor(sideLength) {
    super("square", 4, sideLength)
  }
  calcArea() {
    console.log(`Square's area is ${this.sideLength ** 2}`)
  }
}

const square = new Square(4)
square.calcArea()

处理 JSON

Converting a string to a native object is called deserialization, while converting a native object to a string so it can be transmitted across the network is called serialization .

{
  "a": "b",
  "c": "d"
}

数组作 JSON:

[
  {
    "a": "b",
    "c": "d"
  },
  {
    "a": "b",
    "c": "d"
  }
]

注意:

  • JSON 是纯粹的字符串,包含特定的数据格式。它只有属性,并无方法
  • JSON 使用「双引号」,包裹字符串和属性名
  • 如果逗号/冒号放错了,JSON 格式就出错了
  • JSON 可以是除去数组或对象的其他形式
  • JSON 中只有被引号包裹的字符串才用作属性

Active learning: 处理一个 JSON 例子

async function populate() {
  const requestURL =
    "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json";
  const request = new Request(requestURL);
  const response = await fetch(request);
  const superHeroes = await response.json();

  populateHeader(superHeroes);
  populateHeroes(superHeroes);
}

function populateHeader(obj) {
  const header = document.querySelector("header");
  const myH1 = document.createElement("h1");
  myH1.textContent = obj.squadName;
  header.appendChild(myH1);

  const myPara = document.createElement("p");
  myPara.textContent = `Hometown: ${obj.homeTown} // Formed: ${obj.formed}`;
  header.appendChild(myPara);
}

function populateHeroes(obj) {
  const section = document.querySelector("section");
  const heroes = obj.members;

  for (const hero of heroes) {
    const myArticle = document.createElement("article"),
      myH2 = document.createElement("h2"),
      myPara1 = document.createElement("p"),
      myPara2 = document.createElement("p"),
      myPara3 = document.createElement("p"),
      myList = document.createElement("ul");

    myH2.textContent = hero.name;
    myPara1.textContent = `Secret identity: ${hero.secretIdentity}`;
    myPara2.textContent = `Age: ${hero.age}`;
    myPara3.textContent = `Superpowers:`;

    const superPowers = hero.powers;
    for (const power of superPowers) {
      const listItem = document.createElement("li");
      listItem.textContent = power;
      myList.appendChild(listItem);
    }

    myArticle.appendChild(myH2);
    myArticle.appendChild(myPara1);
    myArticle.appendChild(myPara2);
    myArticle.appendChild(myPara3);
    myArticle.appendChild(myList);

    section.appendChild(myArticle);
  }
}

populate();

转换对象和文本

内建的 JSON 对象,能够帮助将纯 JSON 字符与对象进行相互转化。JSON 对象的两个方法: parse()stringify() ,前者输入纯 JSON 字符串输出 JS 对象,后者输入 JS 对象输出 JSON 字符串。

let myObj = { name: "Chris", age: 38 }
console.log(myObj)
let myString = JSON.stringify(myObj)
console.log(myString)
let my2ndObj = JSON.parse(myString)
console.log(my2ndObj)

Test your skills: JSON

const section = document.querySelector("section");

let para1 = document.createElement("p");
let para2 = document.createElement("p");
let motherInfo = "The mother cats are called ";
let kittenInfo;
const requestURL =
  "https://mdn.github.io/learning-area/javascript/oojs/tasks/json/sample.json";

fetch(requestURL)
  .then((response) => response.text())
  .then((text) => displayCatInfo(text));

function displayCatInfo(catString) {
  let total = 0;
  let male = 0;

  // Add your code here
  const cats = JSON.parse(catString);
  for (let i = 0; i < cats.length; i++) {
    for (const kitten of cats[i].kittens) {
      total++;
      if (kitten.gender === "m") {
        male++;
      }
    }

    if (i < cats.length - 1) {
      motherInfo += `${cats[i].name}, `;
    } else {
      motherInfo += `and ${cats[i].name}.`;
    }
  }
  kittenInfo = `There are ${total} kittens in total, ${male} males and ${
    total - male
  } females.`;

  // Don't edit the code below here!

  para1.textContent = motherInfo;
  para2.textContent = kittenInfo;
}

section.appendChild(para1);
section.appendChild(para2);

对象构建实践

// setup canvas

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

const width = (canvas.width = window.innerWidth);
const height = (canvas.height = window.innerHeight);

// function to generate random number

function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

// function to generate random color

function randomRGB() {
  return `rgb(${random(0, 255)},${random(0, 255)},${random(0, 255)})`;
}

// Modeling a ball in program
class Ball {
  constructor(x, y, velX, velY, color, size) {
    this.x = x;
    this.y = y;
    this.velX = velX;
    this.velY = velY;
    this.color = color;
    this.size = size;
  }
  draw() {
    ctx.beginPath();
    ctx.fillStyle = this.color;
    ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
    ctx.fill();
  }
  update() {
    if (this.x + this.size >= width) {
      this.velX = -Math.abs(this.velX);
    }
    if (this.x - this.size <= 0) {
      this.velX = -Math.abs(this.velX);
    }
    if (this.y + this.size >= height) {
      this.velY = -Math.abs(this.velY);
    }
    if (this.y - this.size <= 0) {
      this.velY = -Math.abs(this.velY);
    }

    this.x += this.velX;
    this.y += this.velY;
  }
  collisionDetect() {
    for (const ball of balls) {
      if (this !== ball) {
        const dx = this.x - ball.x;
        const dy = this.y - ball.y;

        const distance = Math.sqrt(dx * dx + dy * dy);

        if (distance < this.size + ball.size) {
          ball.color = this.color = randomRGB();
        }
      }
    }
  }
}

const balls = [];
while (balls.length < 25) {
  const size = random(10, 20);
  const ball = new Ball(
    random(0 + size, width - size),
    random(0 + size, height - size),
    random(-7, 7),
    random(-7, 7),
    randomRGB(),
    size
  );
  balls.push(ball);
}

function loop() {
  ctx.fillStyle = "rgba(0, 0, 0, 0.25)";
  ctx.fillRect(0, 0, width, height);

  for (const ball of balls) {
    ball.draw();
    ball.update();
    ball.collisionDetect();
  }
  requestAnimationFrame(loop);
}

loop();

作业:添加功能到我们的弹跳球演示

异步 JS

介绍异步 JS

异步编程下,可以运行多个任务。不必等待即可执行下一个目标任务。很多由浏览器提供的函数会执行一定时间,因此它们是异步执行。例如:

  • 通过 fetch() 发起 HTTP 请求
  • 通过 getUserMedia() 接入用户的照相机或麦克风
  • 通过 showOpenFilePicker() 询问用户选择文件

下文阐述长时间运行的同步函数造成的问题。

同步编程

按顺序执行的代码,即是同步编程代码。

长时间执行的同步函数:

const MAX_PRIME = 1000000;

function isPrime(n) {
  for (let i = 2; i <= Math.sqrt(n); i++) {
    if (n % i === 0) {
      return false;
    }
  }
  return n > 1;
}

const random = (max) => Math.floor(Math.random() * max);

function generatePrimes(quota) {
  const primes = [];
  while (primes.length < quota) {
    const candidate = random(MAX_PRIME);
    if (isPrime(candidate)) {
      primes.push(candidate);
    }
  }
  return primes;
}

const quota = document.querySelector("#quota");
const output = document.querySelector("#output");

document.querySelector("#generate").addEventListener("click", () => {
  output.textContent = `Finished generating ${quota.value} primes!`;
});

document.querySelector("#reload").addEventListener("click", () => {
  document.location.reload();
});

在同步函数中,程序运行时无法响应其他操作。

事件处理器

它就是一种异步编程。

回调

什么是回调?

A callback is just a function that's passed into another function, with the expectation that the callback will be called at the appropriate time.

回调函数如果内嵌有回调函数,就会变得难以理解。

function doStep1(init, callback) {
	const result = init + 1;
	callback(result);
}
function doStep2(init, callback) {
	const result = init + 2;
	callback(result);
}
function doStep3(init, callback) {
	const result = init + 3;
	callback(result);
}

function doOperation() {
	doStep1(0, (result1) => {
    doStep2(result1, (result2) => {
      doStep3(result2, (result3) => {
        console.log(`${result3}`)
      })
    })
  })
}

doOperation();

期约的由来:

For these reasons, most modern asynchronous APIs don't use callbacks. Instead, the foundation of asynchronous programming in JavaScript is the Promise, and that's the subject of the next article.

如何使用期约

期约是现代 JS 异步编程的基础。期约是异步函数的返回对象,表示操作的当前状态。当期约返回给调用者时,操作通常还没有完成,但是期约对象提供了处理操作最终成功或失败的方法。

使用 fetch()

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json")
console.log(fetchPromise)
fetchPromise.then((response) => {
  console.log(`Received response: ${response.status}`)
})
console.log("Started request...")

链式期约

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json")

fetchPromise.then((response) => {
  const jsonPromise = response.json()
  jsonPromise.then(data => {
    console.log(data[0].name)
  })
})

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json")

fetchPromise
  .then(response => response.json())
  .then(data => {
  console.log(data[0].name)
})

的转变。这样的转变就被称为「链式期约(promise chaining)」。这样在进行连续异步函数调用时,就可以避免不断缩进问题,使得代码易于理解。

加上错误处理代码:

const fetchPromise = fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json")

fetchPromise
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`)
    }
    return response.json()
  })
  .then(data => {
    console.log(data[0].name)
  })

捕捉错误

const fetchPromise = fetch("bad-scheme://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json")

fetchPromise
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`)
    }
    return response.json()
  })
  .then(data => {
    console.log(data[0].price)
  })
  .catch(error => {
    console.error(`Could not get products: ${error}`)
  })

期约术语

期约可能处于三种状态:

  1. pending: 期约已经创建,相联系的异步函数运行结果尚未知晓。这就是期约在从 fetch() 调用返回时所处的状态,并且请求仍在执行。
  2. fulfilled: 异步函数成功执行。期约完成后,它会调用 then()。
  3. rejected: 异步函数执行失败。当期约被拒绝时,它会调用 catch()。

异步函数执行成功或失败与否,要看具体的 API。比如,对于 fetch() 来说,一个请求成功的话,服务器要返回内容;如果是网络问题,请求没有发送出去,则此时认为请求失败。

有时会用 settled 指代 fulfilled 或 rejected。如果一个期约处于 settled 状态,或者它已经被“锁定”以跟随另一个期约的状态,那么它就被解决了。

一篇讲解期约的文章《 Let's talk about how to talk about promises | JavaScript: The New Toys 》:

resolve 是什么含义?resolve 意味着决定期约的下一步行动取决于你。

When you resolve a promise with something like 42 or "answer" or {"example": "result"}, yes, you do fulfill the promise with that value. But if you resolve your promise to another promise (or more generally a thenable), you're telling your promise to follow that other promise and do what it does:

  • If the other promise is fulfilled, your original promise will fulfill itself with the other promise's fulfillment value
  • If the other promise is rejected, your original promise will reject itself with the other promise's rejection reason
  • If the other promise never settles, your original promise won't either

这些内容,不理解。

结合多个期约

使用 Promise.any() , Promise.all() ,更多见 Promise

async 和 await

在一个 async 函数内部,将 await 放在返回期约的函数之前。这使得代码会等到期约 settled 时,返回期约的 fulfilled 值或抛出 rejected 值。

重写之前的代码:

async function fetchProducts() {
  try {
    const response = await fetch("https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json")
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`)
    }
    const data = await response.json()
    console.log(data[0].name)
  } catch (error) {
    console.error(`Could not get products: ${error}`)
  }
}

fetchProducts()

对于上述 fetchProducts() 函数的后续使用,错误用法:

const promise = fetchProducts()
console.log(promise[0].name)

正确用法:

const promise = fetchProducts()
promise.then((data) => console.log(data[0].name))

注意,await 只能用在 async 内部,除非处于 JS 模块中(可以单独使用 await)。如果在一般脚本中使用,会报错: await is only valid in async functions, async generators and modules

实现基于期约的 API

实现一个返回期约的 API。

实现一个 alarm() API

function alarm(person, delay) {
  return new Promise((resolve, reject) => {
    if (delay < 0) {
      throw new Error("Alarm delay must not be negative");
    }
    setTimeout(() => {
      resolve(`Wake up, ${person}!`);
    }, delay);
  });
}

使用 alarm() API

function alarm(person, delay) {
  return new Promise((resolve, reject) => {
    if (delay < 0) {
      throw new Error("Alarm delay must not be negative");
    }
    setTimeout(() => {
      resolve(`Wake up, ${person}!`);
    }, delay);
  });
}

button.addEventListener("click", () => {
  alarm(name.value, delay.value)
    .then((message) => (output.textContent = message))
    .catch((error) => (output.textContent = `Couldn't set alarm: ${error}`));
});

搭配 async-await 使用 alarm()

button.addEventListener("click", async () => {
  try {
    const message = await alarm(name.value, delay.value);
    output.textContent = message;
  } catch (error) {
    output.textContent = `Couldn't set alarm: ${error}`;
  }
});

介绍 workers

Workers 可以让你在单独的线程中执行内容。

程序是单线程的,如果它长时间只运行一个任务,其他任务就无法执行。Workers 可以让我们在不同的线程运行任务。多线程代码有一个问题,如果两个线程修改同一个变量,就会造成错误结果,产生 bugs。为了避免这些问题,对 workers 代码进行了一定限制,它无法直接访问主代码的变量、只能与主代码交换信息、无法访问 DOM。

有三种不同的 workers:

  • dedicated workers
  • 共享 workers
  • service workers

使用 web workers(dedicated workers)

main.js:

const worker = new Worker("./generate.js")

document.querySelector("#generate").addEventListener("click", () => {
  const quota = document.querySelector("#quota").value
  worker.postMessage({
    command: "generate",
    quota
  })
})

worker.addEventListener("message", (message) => {
  document.querySelector("#output").textContent = `Finished generating ${message.data} primes!`
})

document.querySelector("#reload").addEventListener("click", () => {
  document.querySelector("#user-input").value = ""
  document.location.reload()
})

generate.js:

addEventListener("message", (message) => {
  if (message.data.command === "generate") {
    generatePrimes(message.data.quota);
  }

  function generatePrimes(quota) {
    function isPrime(n) {
      for (let c = 2; c <= Math.sqrt(n); ++c) {
        if (n % c === 0) return false;
      }
      return true;
    }

    const primes = [];
    const maximum = 1000000;

    while (primes.length < quota) {
      const candidate = Math.floor(Math.random() * (maximum + 1));
      if (isPrime(candidate)) primes.push(candidate);
    }

    postMessage(primes.length);
  }
});

其他类型 workers

  • Shared workers : 共享于几个运行于不同窗口的脚本
  • Service workers : 像代理服务器,能够缓存 Web 应用,以便无网络时仍能访问。是 PWA(Progressive Web Apps) 的关键组成部分。

作业:动画排序

Sequencing animations

回调版本:

function sequencingAnimations() {
  alice1.animate(aliceTumbling, aliceTiming).finished.then(() => {
    alice2.animate(aliceTumbling, aliceTiming).finished.then(() => {
      alice3.animate(aliceTumbling, aliceTiming);
    });
  });
}

期约链版本:

function sequencingAnimations() {
  const animation = alice1.animate(aliceTumbling, aliceTiming);
  animation.finished
    .then(() => alice2.animate(aliceTumbling, aliceTiming).finished)
    .then(() => alice3.animate(aliceTumbling, aliceTiming));
}

async-await 版本:

async function sequencingAnimations() {
  try {
    await alice1.animate(aliceTumbling, aliceTiming).finished
    await alice2.animate(aliceTumbling, aliceTiming).finished
    alice3.animate(aliceTumbling, aliceTiming)
  } catch (error) {
    console.error(`Could not play the animation: ${error}`)
  }
}

客户端 Web APIs

介绍 web APIs

什么是 APIs?

Application Programming Interfaces (APIs) 是编程语言中的构造体,允许开发人员更容易地创建复杂的功能。APIs 是对更复杂的代码的抽象。

客户端 JS 中的 APIs 分为两类:浏览器 APIs 和第三方 APIs。

JS、APIs 和其它 JS 工具,这三者的关系:a. JS 是内建于浏览器的高级脚本编程语言,能让你在 Web 应用中实现各种功能;b. 浏览器 APIs 是内建于浏览器的构造器,它基于 JS,允许你更容易地实现功能;c. 第三方 APIs 基于第三方平台,比如 Twitter、Facebook 等;d. JS 库由一个或多个文件写成的自定义函数组成,能够为 Web 应用提供更丰富的功能,比如 jQuery, React;e. JS 框架是 JS 库的进一步集成,它打包了 HTML、CSS、JS 和其他所需要的技术,让你能从零创建一个完整 Web 应用。

JS 库与框架的一个关键区别是:控制网页内容的主人不同。当从库中调用一个方法时,开发者处于控制地位;在框架中,控制权在框架手上,框架调用开发者的代码。

APIs 能做什么?

常用浏览器 APIs:DOM(操纵文档内容)、Fetch/XMLHttpRequest/Ajax(获取服务器数据)、Canvas/WebGL(画图和修图)、Audio/Video/WebRTC、Device、Client-side storage(Web Storage, IndexedDB)。

APIs 是如何工作的?

  • 基于对象
const AudioContext = window.AudioContext || window.webkitAudioContext
const audioCtx = new AudioContext()

const audioElement = document.querySelector("audio")
const playBtn = document.querySelector("button")
const volumeSlider = document.querySelector(".volume")

const audioSource = audioCtx.createMediaElementSource(audioElement)

playBtn.addEventListener("click", () => {
  if (audioCtx.state === 'suspended') {
    audioCtx.resume()
  }

  if (playBtn.getAttribute("class") === "paused") {
    audioElement.play()
    playBtn.setAttribute("class", "playing")
    playBtn.textContent = "Pause"
  } else if (playBtn.getAttribute("class") === "playing") {
    audioElement.pause()
    playBtn.setAttribute("class", "paused")
    playBtn.textContent = "Play"

  }
})

audioElement.addEventListener("ended", () => {
  playBtn.setAttribute("class", "paused")
  playBtn.textContent = 'Play'
})

const gainNode = audioCtx.createGain()

volumeSlider.addEventListener("input", () => {
  gainNode.gain.value = volumeSlider.value
})

audioSource.connect(gainNode).connect(audioCtx.destination)
  • 可识别的入口

对于 Audio API 来说,AudioContext 就是入口;对于 DOM API 来说,Document 就是入口;对于 Canvas API 来说,HTMLCanvasElement.getContext() 是其入口。

  • 使用事件处理状态变化
  • 在适当的地方有额外的安全机制

操作文档

节点:

  • 根节点
  • 孩子节点
  • 后代节点
  • 父节点
  • 同级(sibling)节点

Active learning: 基本 DOM 操作

const link = document.querySelector("a")
link.textContent = "Mozilla Developer Network"
link.href = "https://developer.mozilla.org/en-US/"

const sect = document.querySelector("section")
const para = document.createElement("p")
para.textContent = "We hope you enjoy the ride."
para.setAttribute("class", "highlight")
sect.appendChild(para)

const text = document.createTextNode(" — the premier source for web development knowledge.")
const linkPara = document.querySelector("p")
linkPara.appendChild(text)
sect.appendChild(linkPara)
// sect.removeChild(linkPara)
// linkPara.remove()
linkPara.parentNode.removeChild(linkPara)

Active learning: 动态购物清单

const list = document.querySelector('ul'),
  input = document.querySelector('input'),
  button = document.querySelector('button')

button.addEventListener('click', () => {
  const myItem = input.value
  input.value = ''

  const listItem = document.createElement('li')
  const listText = document.createElement('span')
  const listBtn = document.createElement('button')

  listItem.appendChild(listText)
  listText.textContent = myItem
  listItem.appendChild(listBtn)
  listBtn.textContent = 'Delete'
  list.appendChild(listItem)

  listBtn.addEventListener('click', () => {
    list.removeChild(listItem)
  })

  input.focus()
})

从服务器获取数据

这里的问题在哪里?

一个网页由 HTML、CSS、JS 和其他内容构成。网页加载的基本模式——浏览器创建 HTTP 请求至服务器,服务器响应请求,返回所请求的文件。

这种模式适用于部分网站。但是,其他一些网站,有的是数据驱动的,无法一次性将所有数据都加载至客户端,而且它们需要动态更新网站界面的小部分内容(其他的,例如菜单、页脚,是不变的)。这时,刚才描述的传统模式就不适用了。在传统模式下,即便我们只需要更新一部分网页内容,也需要重新加载整个网页。这非常低效,而且会导致糟糕的用户体验。

因此,更常用的方式是通过 JS APIs 从服务器请求数据更新网页内容,而无需重新加载页面。

主要使用的是 Fetch API。通过 DOM API 对网页内容进行更新。从服务器返回的数据有多种格式,有 JSON、HTML 或 text 文本。

常见的数据驱动网站有 Amazon、YouTube、eBay 等等。这种的模式:

  • 页面更新更快,不必等到刷新页面,意味着感觉上网站更快也更能响应操作。
  • 每次更新下载的数据更少,意味着更少浪费带宽。这可能在有宽带连接的桌面并不是问题,但是在移动端和没有无处不在的快速互联网服务的国家,这成为了主要问题。

注意:Asynchronous JavaScript and XML (Ajax)即是指此种技术。

为了进一步提升速度,有些网站也会将一些重复使用的文件和数据存储到用户电脑本地,这意味着如果用户经常访问这些网站,就不需要重复从服务端获取这部分数据。该部分数据只有在发生改变时才会更新。

Fetch API

两个练习例子。

XMLHttpRequest API

第三方 APIs

  • 地图类 API

    • Mapquest
  • 内容展示类 API

    • NYTimes
    • Youtube

绘制图形

  • Canvas
  • WebGL: Three.js

音视频 APIs

客户端存储

  • Cookies
  • Web Storage
  • IndexedDB

解决常见问题

常见初学者错误

  1. 拼写、大小写
  2. 分号位置
  3. 声明函数,而没有调用
  4. 函数作用域
  5. 在 return 之后还写代码
  6. 定义对象字面量,与普通赋值的区分

基本定义

  1. 什么是 JavaScript?JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions. JavaScript is a prototype-based, multi-paradigm, single-threaded, dynamic language, supporting object-oriented, imperative, and declarative (e.g. functional programming) styles. JavaScript's dynamic capabilities include runtime object construction, variable parameter lists, function variables, dynamic script creation (via eval), object introspection (via for…in and Object utilities), and source-code recovery (JavaScript functions store their source text and can be retrieved through toString()).
  2. 什么是变量?值的容器
  3. 什么是字符串?一段文本
  4. 什么是数组?对象列表
  5. 什么是一个循环?重复做某件事
  6. 什么是一个函数?能够执行特定功能的代码块
  7. 什么是一个事件?正在发生的事情,与用户产生交互
  8. 什么是对象?相关数据和功能的集合
  9. 什么是 JSON?基于文本的数据格式,依据 JS 的对象语法
  10. 什么是一个 web API?把常用的 APIs 抽象出来
  11. 什么是 DOM?HTML 结构树

基本用法

  • 如何在页面上加 JS?
  • 如果为 JS 加注释?
  • 如何声明变量?
  • 如何声明带值的变量?
  • 如何更新变量值?
  • JS 的数据类型
  • 松散类型的意思
  • Web 开发中处理的数据类型
  • JS 中的基本数学计算
  • JS 中运算符的优先级
  • JS 中的值的增减
  • JS 中对值的比较
  • JS 中创建字符串
  • 单双引号
  • 字符串中的转义字符
  • 拼接字符串
  • 拼接字符串和数字
  • 字符串的长度
  • 确定字符在字符串中的位置
  • 提取特定的子字符串
  • 改变字符串的大小写
  • 替换字符串的子段
  • 创建数组
  • 访问和改变数组项
  • 数组的长度
  • 增删数组项
  • 字符串和数组的相互转换
  • 错误的基本类型
  • 浏览器开发者工具
  • 在 JS console 中记录一个值
  • 如何使用断点和其他 JS 调试特性
  • 依靠变量值或其他条件,如何执行不同的代码块
  • 如何使用 if…else
  • 如何在一个决定块嵌入另一个
  • 如何使用 AND, OR 和 NOT 操作符
  • 如何通过一个条件处理大量选择
  • 如何使用三元操作符根据真或假测试在两个选项之间做出快速选择
  • 如何重复执行一段代码
  • 某条件满足后,如何退出循环
  • 某条件满足后,如何跳过
  • 如何使用 while, do…while

中级用例

  • 发现浏览器的内建函数
  • 函数与方法的区别
  • 创建自己的函数
  • 运行/调用/触发函数
  • 匿名函数
  • 调用函数时指定参数
  • 函数作用域
  • 返回值的用法
  • 创建对象
  • 点状标记
  • 括号标记
  • 读取和修改对象的属性和方法
  • 在对象的上下文中, this 指什么
  • 什么是面向对象编程
  • 如何创建构造器和实例
  • JS 中创建对象的不同方法
  • 结构化 JSON 数据,在 JS 读取
  • 加载 JSON 的数据到页面
  • 将 JSON 对象转化成文本字符串,然后再换回来
  • 如何使用事件处理器
  • 行内事件处理器
  • 如何使用 addEventListener()
  • 应该使用何种事件机制到页面
  • 如何使用事件对象
  • 阻止默认的事件表现
  • 事件在互嵌入的元素的触发
  • Event delegation 的工作原理
  • 对象原型
  • 如何使用构造器属性
  • 如何为构造器添加方法
  • 基于父构造器的成员,创建新的构造器
  • JS 的继承
  • 操作 DOM
欢迎通过「邮件」或者点击「这里」告诉我你的想法
Welcome to tell me your thoughts via "email" or click "here"