JavaScript
对 MDN web Docs 中JS学习内容的总结.
base
javascript 是一门解释性的编程语言,它一般用于配合 html,css 动态更新网页的内容.它一般在用户端运行,当然它也可以在服务器端运行.
我们可以使用Application Programming Interfaces (APIs) 来利用 js 实现很多有意思的功能.API 分为browsers APIs
和third party APIs
,前者为浏览器自带的 API,后者会第三方提供的 API.
一般每一个标签页都会运行在一个单独的环境,于是 js 无法得到其他页的数据.这用于保证数据安全.
类似 css,在 html 中 js 有如下方式引入:
internal:
<script>
// JavaScript goes here
</script>
external:
<script src="script.js" defer></script>
inline:
<script>
function createParagraph() {
let para = document.createElement("p");
para.textContent = "You clicked the button!";
document.body.appendChild(para);
}
</script>
<button onclick="createParagraph()">Click me!</button>
同样的,inline 不被推荐使用.
在 html 中我们有async and defer
来控制 js 的执行,async
是下载完立刻执行,defer
是待所有资源加载完后按 html 中的顺序执行.注意它们仅对 src 指定的 js 文件有效.
js 中的注释和 c++类似:
// I am a comment
/*
I am also
a comment
*/
js 代码的每一行都要以分号结尾.
variables
variables
,即存放值的容器.在 js 中,我们使用 let 声明变量:
let myName;
赋值:
myName = 3;
声明和初始化一起:
let myDog = 'Rover';
在过去的 js 中,我们使用var
来进行let
的工作,但var
中有一些很奇怪的设计,如它可以在声明前初始化变量,可以多次声明变量,在let
中这些操作都被禁止了,这让我们减少潜在的错误.所有尽可能使用let
.
js 的变量命名有如下需要注意:
- 不要用下划线开头,这在 js 中是有特殊含义的.
- 不要用数字开头,这是被禁止的.
- 不要用 js 的保留字如
let
. - js 的变量名大小写敏感,a 和 A 是不同的变量.
js 中的变量的类型是动态的,所以我们可以随时为同一个变量更新一个不同类型的值.它主要有如下的类型:
- numbers
- strings
- arrays
- Boolean
- objects
js 中也有常量,用const
声明和初始化:
const count = 1;
在初始化后不可再修改,但我们可以改变值的内容:
const bird = { species: "Kestrel" };
bird.species = "Striated Caracara";
如果是 array 我们也可以修改它.
我们应该尽可能使用常量,只在必要时使用变量.
basic math
在 js 中常用的只有一种数字类型Number
(实际还有一种少用的BigInt
用于大数).它可以进行整数小数的相关计算.
对于一个字符串数字,我们可以用Number(str)
调用构造函数将其转化.
基本的加减乘除和幂运算:
Operator | Name | Purpose | Example |
---|---|---|---|
+ | Addition | Adds two numbers together. | 6 + 9 |
- | Subtraction | Subtracts the right number from the left. | 20 - 15 |
* | Multiplication | Multiplies two numbers together. | 3 * 7 |
/ | Division | Divides the left number by the right. | 10 / 5 |
% | Remainder (sometimes called modulo) | Returns the remainder left over after you've divided the left number into a number of integer portions equal to the right number. | 8 % 3 (returns 2, as three goes into 8 twice, leaving 2 left over). |
** | Exponent | Raises a base number to the exponent power, that is, the base number multiplied by itself, exponent times. It was first Introduced in EcmaScript 2016. | 5 ** 2 (returns 25 , which is the same as 5 * 5 ). |
++
,--
和 c++中的一样,放变量前面返回加后值,放后面返回原来的值.
赋值的相关运算符也和 c++中的一样:
Operator | Name | Purpose | Example | Shortcut for |
---|---|---|---|---|
+= | Addition assignment | Adds the value on the right to the variable value on the left, then returns the new variable value | x += 4; | x = x + 4; |
-= | Subtraction assignment | Subtracts the value on the right from the variable value on the left, and returns the new variable value | x -= 3; | x = x - 3; |
*= | Multiplication assignment | Multiplies the variable value on the left by the value on the right, and returns the new variable value | x *= 3; | x = x * 3; |
/= | Division assignment | Divides the variable value on the left by the value on the right, and returns the new variable value | x /= 5; | x = x / 5; |
比较的运算符就稍有不同了:
Operator | Name | Purpose | Example |
---|---|---|---|
=== | Strict equality | Tests whether the left and right values are identical to one another | 5 === 2 + 4 |
!== | Strict-non-equality | Tests whether the left and right values are not identical to one another | 5 !== 2 + 3 |
< | Less than | Tests whether the left value is smaller than the right one. | 10 < 6 |
> | Greater than | Tests whether the left value is greater than the right one. | 10 > 20 |
<= | Less than or equal to | Tests whether the left value is smaller than or equal to the right one. | 3 <= 2 |
>= | Greater than or equal to | Tests whether the left value is greater than or equal to the right one. | 5 >= 4 |
注意===
和!==
,实际上 js 是有==
和!=
的,但它们不会检查值的类型,使用前值能更严格地进行比较.
string
js 中的 string 使用单引号或双引号围起来:
const string = "The revolution will not be televised.";
我们应该只使用一种引号,在一个引号里加另一种是允许的:
const dblSgl = "I'm feeling blue.";
但使用同种引号则需要转义字符:
const bigmouth = "I've got no right to take my place...";
如果我们想要连接字符串,直接用加运算符:
greeting + ", " + name;
但我们也可以使用template literal
,它和字符串类似,使用**`**包裹,不同的是它可以使用变量:
const greeting = `Hello, ${name}`;
变量如果是数字也会自动转化成字符串.我们也可以添加检查的表达式:
const output = `I like the song ${song}. ${(score / highestScore) * 100}%.`;
书写多行也只需要直接在源代码换行:
const output = `I like the song.
I gave it a score of 90%.`;
但原来的字符串需要加\n
换行.故template literal
书写字符串更有可读性.
数字和字符串的简单相互转换如下:
const myNum = Number(myString);
const myString2 = myNum2.toString();
有一些字符串的 methods 需要了解.
得到长度:
browserType.length;
访问某个字符(类似数组):
browserType[0];
检查是否包含某个字串:
browserType.includes("zilla");
得到字符串的某个部分,注意不改变原来的字符串,参数是两个索引,且左闭右开:
browserType.slice(1, 4);
转换大小写,同样不改变原来字符串:
radData.toLowerCase();
radData.toUpperCase();
替换部分字符串,同样不改变而返回新的字符串,前一个参数是要替换的字符,注意有多个匹配的话只替换第一个:
const updated = browserType.replace("moz", "van");
查询某个子串在字符串中第一次出现时的位置(索引):
const semiColon = station.indexOf(";");
利用字符串中的某个分隔符(或分隔字符串)分割字符串形成数组:
let myArray = myData.split(",");
array
js 中的 array 创建如下:
let random = ["tree", 795, [0, 1, 2]];
如上所示,它的内容可以是不同类型的量,甚至是数组本身.
它的长度用length
访问:
shopping.length;
使用下标访问和修改数组:
shopping[0] = 1;
查找数组内容:
birds.indexOf("Owl");
在数组的后面添加(push
)删除(pop
)元素:
myArray.push("Cardiff");
myArray.pop();
在数组的前面添加(unshift
)删除(shift
)元素:
myArray.unshift("Edinburgh");
myArray.shift();
合并数组:
data_type = data_type.concat(array);
遍历数组:
for (let bird of birds) {
console.log(bird);
}
如果我们想对数组的每一个元素进行操作并返回新的数组,使用map()
,它的参数是一个带一个参数函数:
const doubled = numbers.map(double);
类似的,如果像使用某种条件筛选数组并返回符合条件的数组元素:
const longer = cities.filter(isLong);
如果想将数组转变成字符串:
let myNewString = myArray.join(",");
这样会在数组的元素间自动加上参数的字符串分隔然后形成字符串.
如果我们想让数组元素不分隔:
arr.join("");
我们也可以使用toString()
来转换,但这样会自动加,
分隔且不可修改. 正确按数字大小排序数组:
weeks.sort(function (a, b) {
return a - b;
});
building blocks
conditions
js 中的条件写法和 c++的非常类似:
if (choice === "sunny") {
para.textContent =
"It is nice and sunny outside today. Wear shorts! Go to the beach, or the park, and get an ice cream.";
} else if (choice === "rainy") {
para.textContent =
"Rain is falling outside; take a rain coat and an umbrella, and don't stay out for too long.";
} else if (choice === "snowing") {
para.textContent =
"The snow is coming down — it is freezing! Best to stay in with a cup of hot chocolate, or go build a snowman.";
} else if (choice === "overcast") {
para.textContent =
"It isn't raining, but the sky is grey and gloomy; it could turn any minute, so take a rain coat just in case.";
} else {
para.textContent = "";
}
和 c++的一样,现检查if
的条件,然后一个个检查else if
,如果符合就执行对应的代码块,都不符合就会执行else
的代码块.
如果条件是一个变量,则不是false
, undefined
, null
, 0
, NaN
, (''
) 中的值的变量都为 true.
我们可以使用&&
,||
,!
来表示逻辑中的与或非用于构成条件.
js 中同样有switch
用于简化一些条件的书写:
switch (expression) {
case choice1:
run this code
break;
case choice2:
run this code instead
break;
// include as many cases as you like
default:
actually, just run this code
}
case
的值会被用于检查是否和 expression 的值相同,相同则执行相应代码块.
注意break
和default
,如果没有break
则会从该条条件开始执行所有的代码块.default
则会在没有条件符合时执行.
loop
首先是一般的for
循环:
for (initializer; condition; final - expression) {
// code to run
}
先给定一个初始条件(通常是定义一个局部变量),然后给定允许的条件,最后再给个会改变变量的表达式.
然后按 initializer-condition-code-final-experssion 的顺序执行直到条件不满足.
while
:
initializer;
while (condition) {
// code to run
final - expression;
}
类似,只是初始和改变的表达式不再括号中.
do-while
:
initializer;
do {
// code to run
final - expression;
} while (condition);
和while
类似,但code
会首先被执行一次,然后再进行和while
一样的操作.
for..of..
for (const item of array) {
// code to run
}
用于遍历如数组之类的元素集合.注意不一定要const
,不过如果是let
,修改该变量不会对原数组产生影响.
除此之外还有break
用于跳出循环.continue
用于进入下一次循环.
function
函数一般的声明如下:
function random(number) {
return Math.floor(Math.random() * number);
}
调用:
random(1);
btn.onclick = random; //注意作为参数时不能加括号,不然会执行。
注意调用可在声明之后.参数的个数可以为 0.函数内部也可以调用其他的函数.
在函数外定义的变量称为在global scope
,它可以在该文件的任何地方调用.
在函数内定义的变量则在自己的scope
里,只能在函数内使用.
我们可以给定默认值让参数可选:
function hello(name = "Chris") {
console.log(`Hello ${name}!`);
}
这样调用时可以给参数也可以不给.
有时我们需要传函数作为参数,此时可以使用匿名函数(即没有名字的函数):
textBox.addEventListener("keydown", function (event) {
console.log(`You pressed "${event.key}".`);
});
除了没有名字且不可在声明前调用,和正常的函数一样.
我们可以用另外的方式来声明匿名函数:
textBox.addEventListener("keydown", (event) => {
console.log(`You pressed "${event.key}".`);
});
如果函数只有一行可以去掉花括号:
textBox.addEventListener("keydown", (event) =>
console.log(`You pressed "${event.key}".`)
);
如果参数只有一个可以去掉括号:
textBox.addEventListener("keydown", (event) =>
console.log(`You pressed "${event.key}".`)
);
如果只有一行且为返回值,可以去掉return
:
const doubled = originals.map((item) => item * 2);
可以使用 rest parameter 语法使用函数剩下的所有参数:
function myFun(a, b, ...manyMoreArgs) {
console.log("a", a);
console.log("b", b);
console.log("manyMoreArgs", manyMoreArgs);
}
myFun("one", "two", "three", "four", "five", "six");
// a, "one"
// b, "two"
// manyMoreArgs, ["three", "four", "five", "six"] <-- notice it's an array
event
event
是在系统中发生的行为或事件.在 web 中,event
一般与 element 相对应(如按钮被点击).
与event
相对应的有event handler
,它会在 event 发生时被执行.在js
中它是一个函数.
event handler
我们可以利用对应的 properties 来应用event handler
:
const btn = document.querySelector("button");
function random(number) {
return Math.floor(Math.random() * (number + 1));
}
btn.onclick = function () {
const rndCol =
"rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
document.body.style.backgroundColor = rndCol;
};
我们还可以在 html 中应用:
<button onclick="bgChange()">Press me</button>
<!--在script中写有函数-->
<button onclick="alert('Hello, this is my old-fashioned event handler!');">
Press me
</button>
<!--直接插入js-->
这样使用难以维护,不推荐.
在现代浏览器我们还可以使用addEventListener
:
const btn = document.querySelector("button");
function bgChange() {
const rndCol =
"rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
document.body.style.backgroundColor = rndCol;
}
btn.addEventListener("click", bgChange);
这样有两个好处:
- 可移除
event handler
. - 可以应用多个.
移除可以调用对应函数:
btn.removeEventListener("click", bgChange);
也可以使用一个额外的控制器:
const controller = new AbortController();
btn.addEventListener(
"click",
function () {
var rndCol =
"rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
document.body.style.backgroundColor = rndCol;
},
{ signal: controller.signal }
); // pass an AbortSignal to this handler
这样的话调用如下语句时所有相关的event handler
都会被移除:
controller.abort(); // removes any/all event handlers associated with this controller
other concept
有时我们会空间event handler
带一个event
参数,它是一个会自动传进的参数,带有额外信息,可进行一些额外的操作.
function bgChange(e) {
const rndCol =
"rgb(" + random(255) + "," + random(255) + "," + random(255) + ")";
e.target.style.backgroundColor = rndCol;
console.log(e);
}
我们可以使用e.target
访问发出event
信号的 element.
有时我们阻止 event 的默认行为(如form
中有 submit 会自动发送信息并显示成功):
form.onsubmit = function (e) {
if (fname.value === "" || lname.value === "") {
e.preventDefault();
para.textContent = "You need to fill in both names!";
}
};
我们还需要理解浏览器关于有父 element 的 element 的event handler
的处理.它经历三个阶段:
- capturing:从 html 开始,一个个检查有无对应
event
的 handler 并执行,直到该 element 的直接父 element 停止. - target:检查对应的 element 有无 handler,有就执行.然后检查 event 的
bubbling
查看是否进行第三阶段. - bubbling:从直接父 element 开始,检查有无注册了的
event handler
并执行,直到 html.
我们写的函数注册event handler
默认是注册到第三阶段.
我们可以控制不执行:
video.onclick = function (e) {
e.stopPropagation();
video.play();
};
我们也可以将函数注册到第一阶段,只需将addEventListener
的第三个参数设为 true.
我们可以利用这些实现event delegation
,即只要某 element 的子 element 发出对应信号就会执行一定操作.
objects
object literal
js 的 objects 指一系列数据和功能的集合.其中数据被称为properties
,功能被称为methods
.
我们可以使用object literal
来定义对象:
const person = {
name: ["Bob", "Smith"],
age: 32,
gender: "male",
interests: ["music", "skiing"],
bio: function () {
alert(
this.name[0] +
" " +
this.name[1] +
" is " +
this.age +
" years old. He likes " +
this.interests[0] +
" and " +
this.interests[1] +
"."
);
},
greeting: function () {
alert("Hi! I'm " + this.name[0] + ".");
},
};
如上所示,使用name:value
的格式书写,用逗号分隔,value 可以是量也可以是函数.
调用则可以使用dot notation
(即点):
person.interests[1];
person.bio();
此时我们说类型作为namespace
,用以调用里面的各种 name.
其中调用 properties 也可以使用bracket notation
(即方括号):
person["age"];
后者的好处是可以用变量来调用. 移除 property:
delete obj.property;
对象可以嵌套,调用时多写一层即可:
person.name.first;
此时我们说 name 是sub-namespace
.
上面的函数里调用成员时使用了this
:
greeting: function() {
alert('Hi! I\'m ' + this.name.first + '.');
}
this
指的就是对象本身,这个用法在用类创建对象时会非常有用.
注意 js 的this
和一般编程语言不同,它是根据上下文得到含义的,也就是说,在函数中使用也不会出错:
method 里的 this 返回的是 obj, function 里的 this 非严格模式下返回的是 global or window ,严格模式下返回 undefined, 箭头函数里没有自己的 this.
我们可以修改 properties 的值:
person.age = 3;
为 object 添加特殊的属性:
Object.defineProperty(object1, "property1", {
value: 42,
writable: false,
});
我们也用类似的方法可以创建成员:
person["eyes"] = "hazel";
constructor
js 的constructor
书写如下:
function Person(name) {
this.name = name;
this.greeting = function () {
alert("Hi! I'm " + this.name + ".");
};
}
然后调用:
let person1 = new Person("Bob");
这样我们就可以利用它创建对象了.其中this
指针在具体对象中指代其本身,new
告诉解释器要创建对象.
注意对每一个对象中的函数,它都会新建一个对应的函数.
还有一些其他的可以创建对象的方法:
let person1 = new Object();
person1.name = "Bob";
这样是先利用Object()
创建一个新对象然后再给值.
我们可以直接用object literal
作为参数:
let person1 = new Object({
name: "Chris",
age: 38,
greeting: function () {
alert("Hi! I'm " + this.name + ".");
},
});
我们也可以用已有对象建立新对象:
let copy1 = Object.create(obj); //如果property有对象则会复制reference
var copy2 = JSON.parse(JSON.stringify(obj)); //深度拷贝
objects prototype
js 通过prototype chain
实现继承,即每一个构造函数都有一个prototype
属性,用于存放可以被继承的属性或函数.当继承时,该属性的内容被继承.一个个对象的对应关系形成链.这样的话新的对象的函数会是指向前面函数的指针,避免了重复创建函数的问题.
在浏览器中,我们可以使用obj.__proto__
来查看它继承了的对象.
我们这样添加可继承的事物:
Person.prototype.farewell = function () {
alert(this.name.first + " has left the building. Bye for now!");
};
注意Object()
的相关性质会被自动放入prototype
中,其他的则需自己添加.
需要进行一次实例化才能在浏览器中看见对应的prototype
.
基于此,我们构造函数的书写一般如下:
// Constructor with property definitions
function Test(a, b, c, d) {
// property definitions
}
// First method definition
Test.prototype.x = function() { ... };
// Second method definition
Test.prototype.y = function() { ... };
// etc.
注意只要对象的调用在更新之后,它仍会被更新,即使对象的定义在构造函数的更新之前.
上面我们使用的create
其实也继承了参数的属性,跟上面是同样机制.
对象也有constructor
property:
person1.constructor;
可以用它获取构造函数的名字:
person1.constructor.name;
inheritance
js 中的继承书写如下:
function Teacher(first, last, age, gender, interests, subject) {
Person.call(this, first, last, age, gender, interests);
this.subject = subject;
}
这样我们从 Person 的构成函数派生形成了一个新构造函数,注意传入参数里this
是必须的.
然后我们可以设置prototype
:
Teacher.prototype = Object.create(Person.prototype);
但此时 Teacher 的 constructor property 会指向 Person,为解决这个问题,我们需要进行设置:
Object.defineProperty(Teacher.prototype, "constructor", {
value: Teacher,
enumerable: false, // so that it does not appear in 'for in' loop
writable: true,
});
class
ECMAScript 2015
提供了类似 c++语言中的class
的写法:
class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first,
last,
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
greeting() {
console.log(`Hi! I'm ${this.name.first}`);
}
farewell() {
console.log(`${this.name.first} has left the building. Bye for now!`);
}
}
上面是class declaration
的写法,还要class assignment
的写法:
// unnamed
let Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// output: "Rectangle"
// named
let Rectangle = class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// output: "Rectangle2"
在解释器其实会将其转化为上面的构造函数的相关内容.下面的函数会被自动加入prototype
中. 在 class 的 body 使用strict mode
. 使用static
创建 class 的 properties 和 methods,它们只可通过 class 本身调用,不可通过 class instance 调用:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static displayName = "Point";
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; // undefined
p1.distance; // undefined
p2.displayName; // undefined
p2.distance; // undefined
console.log(Point.displayName); // "Point"
console.log(Point.distance(p1, p2)); // 7.0710678118654755
使用public field declarations
定义 properties:
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
加#
进行 private field declaration,定义的相关内容只可在 class 内访问:
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
派生写法:
class Teacher extends Person {
constructor(subject, grade) {
super(); //让this可以,如果Person有参数需要给相应的参数.
this.subject = subject;
this.grade = grade;
}
}
返回继承的 class 的内容:
class Lion extends Cat {
speak() {
super.speak();
console.log(`${this.name} roars.`);
}
}
使用Mix-ins
,进行函数的继承:
let calculatorMixin = (Base) =>
class extends Base {
calc() {}
};
let randomizerMixin = (Base) =>
class extends Base {
randomize() {}
};
//use
class Foo {}
class Bar extends calculatorMixin(randomizerMixin(Foo)) {}
如果后来我们想修改里面的 properties,使用getters
和setters
:
class Teacher extends Person {
constructor(first, last, age, gender, interests, subject, grade) {
super(first, last, age, gender, interests);
// subject and grade are specific to Teacher
this._subject = subject;
this.grade = grade;
}
get subject() {
return this._subject;
}
set subject(newSubject) {
this._subject = newSubject;
}
}
这样我们就可以使用 subject 修改_subject 了.且我们可以在每次属性被调用或被修改时进行一些操作.
json
json
是一类使用 js 中的类写法为格式的文件,用以传递数据.把 json 字符串转化为 js 对象称为deserialization
,将 js 对象转化为 json 字符串被称为serialization
.json
的一般格式如下:
{
"squadName": "Super hero squad",
"formed": 2016,
"active": true,
"powers": [
"Immortality",
"Heat Immunity",
"Inferno",
"Teleportation",
"Interdimensional travel"
]
}
有几点需要注意:
字符串必须使用双引号.
properties 必须用双引号分隔.
json
也可以是数组:
[
...
]
它甚至可以一个数字,一个字符串.
在 js 中对json
的处理使用JSON
对象:
let myString = JSON.stringify(myObj); //转化为json
const superHeroes = JSON.parse(superHeroesText); //转化为js对象
asynchronous
在 js 中使用asynchronous
可以将费时的工作(如查询数据库)放到另一线程中,避免导致网页加载时间过长.
js 中的异步操作会放在event loop
中,它会在非异步操作执行完后执行.js 实际上还是一个单线程语言。
callback
在 js 中我们可以使用Async callbacks
,即给定一个函数作为参数,它会在调用的函数执行完毕后执行.
如:
btn.addEventListener("click", () => {
alert("You clicked me!");
let pElem = document.createElement("p");
pElem.textContent = "This is a newly-added paragraph.";
document.body.appendChild(pElem);
});
function loadAsset(url, type, callback) {
let xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = type;
xhr.onload = function () {
callback(xhr.response);
};
xhr.send();
}
上述代码中的 callback 会在之后执行,不影响它下面的代码.
注意不是所有的 callback 都是异步的(如forEach
).
promise
promise
指一个代表函数执行是否成功的中间态对象,它是modern web APIs
,用于更好的控制异步操作。
当它被创建但没有执行时,我们说它pending
,当它被返回时,我们说它被resolved
.成功的叫fullfilled
,函数的返回信息可以作为参数,失败的叫rejected
,返回错误信息(reason
).
我们可以使用then()
在那之后执行相关操作,它也返回promise
:
fetch("products.json")
.then(function (response) {
return response.json();
})
.then(function (json) {
let products = json;
initialize(products);
})
.catch(function (err) {
console.log("Fetch problem: " + err.message);
});
fetch
本身是一个异步函数,它返回promise
对象.
注意它不会对如404
之类的网络错误作出响应,我们需要检查response
:
let promise2 = promise.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
});
注意try..catch
对它不起作用,需要使用特定的catch
.
它相对于 callback 有以下好处:
- 可以使用多个
then
进行多步异步操作,这在 callback 中很难实现( callback hell). - 可以让代码运行顺序准确.
- 更容易处理异常.
- 不丢失相关操作的控制权.(callback 使用第三方库会导致).
如果我们想对多个promise
同时进行相应:
Promise.all([a, b, c]).then(values => {
...
});
此时all
会返回一个promise
,如果成功的话还会有一个数组作为参数.
注意如果 all 中的参数某一个出现问题,它仍会fullfill
,只该参数返回undefined
.
如果我们想让某些代码无论成功失败都指向,在最后使用finally
:
myPromise
.then((response) => {
doSomething(response);
})
.catch((e) => {
returnError(e);
})
.finally(() => {
runFinalCode();
});
我们可以使用promise constructor
来让一些老的异步 API(如setTImeout
)可以使用promise
:
let timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Success!");
}, 2000);
});
此时不可能出现reject
,有无setTimeout
没有失败状态.
如果我们想使用reject
:
function timeoutPromise(message, interval) {
return new Promise((resolve, reject) => {
if (message === "" || typeof message !== "string") {
reject("Message is empty or not a string");
} else if (interval < 0 || typeof interval !== "number") {
reject("Interval is negative or not a number");
} else {
setTimeout(() => {
resolve(message);
}, interval);
}
});
}
timeouts and internals
我们可以使用setTimeout
让某个函数在另一个线程等待一定的时间后运行:
let myGreeting = setTimeout(() => {
alert("Hello, Mr. Universe!");
}, 2000);
第一个参数是函数,第二个参数是等待时间(毫秒).它的返回值则可以用于控制停止执行:
clearTimeout(myGreeting);
类似的,我们使用setTimeinternal
来让某个函数没间隔一段时间就运行一次:
const myInterval = setInterval(myFunction, 2000);
clearInterval(myInterval);
实际上我们也可以使用setTimeout
来实现间隔循环运行:
let i = 1;
setTimeout(function run() {
console.log(i);
i++;
setTimeout(run, 100);
}, 100);
这与setInternal
有些许不同.setInternal
的间隔时间包括执行时间,而setTimeout
不包括,也就是说后者可以保证某个确定的时间间隔.
当我们将setTimeout
的时间设为 0 时,它仍会等待主线程的非异步操作结束后再执行.
类似的还有requestAnimationFrame
:
rAF = requestAnimationFrame(draw);
cancelAnimationFrame(rAF);
它会在网页每次画面刷新时执行,不需要我们给定时间参数.它会尽可能让自己运行得快并接近 60 帧.它一般绘制动画.
我们可以添加一个参数得到它的时间:
let startTime = null;
function draw(timestamp) {
if (!startTime) {
startTime = timestamp;
}
currentTime = timestamp - startTime;
// Do something based on current time
requestAnimationFrame(draw);
}
draw();
上述函数均在主线程运行.
async and await
我们可以使用async
让一个函数或类的方法返回promise
:
async function hello() {
return "Hello";
}
这样它会返回promise
且它的返回值会在then
中作为参数.
在有async
的函数中,我们可以使用await
来省略then
:
async function myFetch() {
let response = await fetch('coffee.jpg');
...
}
这样response
会等到 fetch 执行完成后才会得到返回值,它会直接是该函数的返回值而不是promise
.
如果使用了await
省略then
,我们可以直接使用 try..catch 来处理异常:
async function myFetch() {
try {
let response = await fetch("coffee.jpg");
} catch (e) {
console.log(e);
}
}
await
对promise
的all
等函数仍适用.
正常情况下如果我们使用多个await
,它们会等待一个执行完后再执行另一个:
async function timeTest() {
await timeoutPromise(3000);
await timeoutPromise(3000);
await timeoutPromise(3000);
}
但我们可以让它们同时执行:
async function timeTest() {
const timeoutPromiseResolve1 = timeoutPromiseResolve(5000);
const timeoutPromiseReject2 = timeoutPromiseReject(2000);
const timeoutPromiseResolve3 = timeoutPromiseResolve(3000);
const results = await Promise.all([
timeoutPromiseResolve1,
timeoutPromiseReject2,
timeoutPromiseResolve3,
]);
return results;
}
注意最后利用all
是为了方便异常处理.
APIs
document
就总体而言.
window
对象代表网页所在的标签页.
Navigator
对象代表浏览器的状态特征.
Document
对象指具体加载的网页,用 DOM 代表.
document 的相关 API 主要用于操作DOM
.
利用 css 的 selector 选择 element:
const link = document.querySelector("a"); //选择第一个出现的element.
const arr = document.querySelectorAll("a"); //选择所有element并放回类似数组的对象.
较老的选择方法:
const elementRef = document.getElementById("myId"); //利用id选择
const elementRefArray = document.getElementsByTagName("p"); //选择某个特定tag,返回类似数组的对象.
创建 element:
const para = document.createElement("p");
修改属性:
link.textContent = "Mozilla Developer Network";
使用classList
修改 class 的列表(注意它本身是只读的):
// If the control is not active there is nothing to do
if (!select.classList.contains("active")) return;
// We need to get the list of options for the custom control
var optList = select.querySelector(".optList");
// We close the list of option
optList.classList.add("hidden");
// and we deactivate the custom control itself
select.classList.remove("active");
加入某个 element 的最后:
sect.appendChild(para);
注意如果是已有的 element 则它会被移动到最后,不会产生新的 element.如果想复制用Node.cloneNode()
.
删除某个 element:
sect.removeChild(linkPara); //删除子element.
linkPara.remove(); //删除本身,就浏览器不支持.
修改 css:
para.style.color = "white";
para.style.backgroundColor = "black";
注意 css 中用连字符熟悉的 attribute 需要使用小驼峰方式重新书写.以上语句会变成添加到inline css
中.
设置attribute
:
para.setAttribute("class", "highlight");
设置tabIndex
(即按 tab foucus 到的顺序):
element.tabIndex = index;
var index = element.tabIndex;
它的值是一个正整数或 0,它的顺序的规则:
- 是如果设置了则按设置的数的大小排序,若同样大按出现的顺序排序.
- 如果为 0 或不支持,则按出现的次序排序.
fetch data from server
在以前,我们使用XMLHttpRequest
来请求数据(Ajax),首先,我们需要创建一个对象:
let request = new XMLHttpRequest();
指定链接和 http 请求类型:
request.open("GET", url);
指定响应的类型:
request.responseType = "text";
指定得到响应时的 callback:
request.onload = function () {
poemDisplay.textContent = request.response;
};
最后发出请求:
request.send();
我们现在可以使用更方便的fetch
:
fetch(url)
.then(function (response) {
return response.text();
})
.then(function (text) {
poemDisplay.textContent = text;
});
fetch(url, {
method: "post",
body: data,
});
它是一个异步函数,返回promise
,而且我们不需要在处理请求类型和发送请求.
当是如图片之类的文件时,我们可以通过blob
处理成二进制文件,然后生成临时链接;
fetch(url)
.then(function (response) {
return response.blob();
})
.then(function (blob) {
// Convert the blob to an object URL — this is basically a temporary internal URL
// that points to an object stored inside the browser
let objectURL = URL.createObjectURL(blob);
// invoke showProduct
showProduct(objectURL, product);
});
third-party APIs
要使用第三方的 API,有时我们需要在 html 进行连接:
<script src="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.js"></script>
<link
type="text/css"
rel="stylesheet"
href="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.css"
/>
然后再在 js 中使用.
有时我们直接通过 http 请求得到数据.
一般它们都需要 API keys 才能使用,这是 API 提供商为避免滥用而设立的.
drawing graphic
我们可以在 html 中创建canvas
,然后在 js 中画图.
首先选择并定义它的长宽并保存:
const canvas = document.querySelector(".myCanvas");
const width = (canvas.width = window.innerWidth);
const height = (canvas.height = window.innerHeight);
然后获取作图区域(context):
const ctx = canvas.getContext("2d");
填充背景色:
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.fillRect(0, 0, width, height);
第二个函数实际是画一个矩形并用颜色填充,头两个参数是矩形的左上角所在位置,后两个参数为宽度和高度.canvas
的坐标默认以左上为(0,0)以保证全体坐标都为正.
我们可以改变原点位置:
ctx.translate(width / 2, height / 2);
旋转整个canvas
(弧度制):
ctx.rotate(degToRad(5));
注意上述颜色可以是透明的:
ctx.fillStyle = "rgba(255, 0, 255, 0.75)";
如果我们想画边框图而不是填充全部:
ctx.strokeStyle = "rgb(255, 255, 255)";
ctx.lineWidth = 5;
ctx.strokeRect(25, 25, 175, 200);
参数的意义同上.其中lineWidth
用于设置边框宽度.
我们还可以使用路径来画图.
ctx.fillStyle = "rgb(255, 0, 0)";
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.lineTo(100, 50);
ctx.lineTo(50, 50);
ctx.fill();
其中beginPath
为开始路径画图(0,0),moveTo
是移动而不画线,lineTo
则画线,最后将路径围成的图像进行填充.
我们可以不画线而画圆:
ctx.arc(150, 106, 50, degToRad(0), degToRad(360), false);
前两个参数为圆的中心点,第三个参数为半径,第四个和第五个参数为起始角度和结束角度(弧度制),最后选择顺时针逆时针(false 为逆时针)。0度方向为水平向右.
注意如果路径没有形成封闭图片,浏览器会自动在起点和终点补充连线形成封闭图像,所以想画不完整的圆需要画到圆心的直线.
我们也可以利用stroke
来画只有边线的图像。
我们还可以画字:
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
ctx.font = "36px arial";
ctx.strokeText("Canvas text", 50, 50);
ctx.fillStyle = "red";
ctx.font = "48px georgia";
ctx.fillText("Canvas text", 50, 150);
font
的设置和 css 中的相同,最后画图的函数的后两个参数指定文本框的左下角.
我们还可以添加图片:
let image = new Image();
image.src = "firefox.png";
image.onload = function () {
ctx.drawImage(image, 20, 20, 185, 175, 50, 50, 185, 175);
};
drawImage
的第一个参数为图像 element,第二和第三个参数指定裁剪图像的左上位置,第四第五则指定裁剪图像的宽度和高度,6,7 指定画图位置的左上角,8,9 为画图的宽度和高度.
另外我们可以使用requestAnimationFrame
等函数绘制动画,注意每一次我们都需要刷新画图并画新的图像,我们无法控制已画的图像.
我们还可以使用webGL
画 3d 图像,通常我们会使用第三方库(如 three.js)来简化这一操作.
video and audio
我们可以使用相关的 api 自己写一个播放控件,具体方法是在 html 搭好框架后再在 js 中写相关的功能.它的图标可以使用 web icon font 以便于切换.
首先我们需要选定 element:
const media = document.querySelector("video");
下面的 APIs 负责视频开始和暂停:
media.play();
media.pause();
想要复原的话暂停的基础上设置时间为 0:
media.currentTime = 0;
该 property 的单位是秒.
如果想加速减速播放,可使用setTimeInternal
在暂停的基础上来让currentTime
增加减少.
用media.duration
可获得整个视频的时长(秒),用于进度条相关控件的实现.
client-side storage
我们可以在用户端存储一些数据便于加快网页加载,防止多次下载同样的内容.
在过去我们使用cookies
来存储,现在我们使用 Web Storage 和 IndexDB 来存储,前者用于存储简单的数据类似(如字符串,数字),后者用于存储复杂的数据类型(如视频).
它们会根据不同的网站生成分离的数据库.
Web Storage
我们可以使用sessionStorage
(仅在标签页加载时临时存储)和localStorage
(即使标签页关闭也存在).它们的用法类似.其中的数据以name:value
的格式组织.
添加数据:
localStorage.setItem("name", "Chris");
读取数据:
let myName = localStorage.getItem("name");
IndexDB
的操作就复杂得多了.
首先我们需要创建一个变量指向数据库:
// Create an instance of a db object for us to store the open database in
let db;
然后添加请求并得到数据库:
window.onload = function () {
// Open our database; it is created if it doesn't already exist
// (see onupgradeneeded below)
let request = window.indexedDB.open("notes_db", 1);
// onerror handler signifies that the database didn't open successfully
request.onerror = function () {
console.log("Database failed to open");
};
// onsuccess handler signifies that the database opened successfully
request.onsuccess = function () {
console.log("Database opened successfully");
// Store the opened database object in the db variable. This is used a lot below
db = request.result;
// Run the displayData() function to display the notes already in the IDB
displayData();
};
};
注意open
的第一个参数为数据库名,第二个为版本号.
当数据库不存在或无该高版本时,会触发onupgradeneeded
:
// Setup the database tables if this has not already been done
request.onupgradeneeded = function (e) {
// Grab a reference to the opened database
let db = e.target.result;
// Create an objectStore to store our notes in (basically like a single table)
// including a auto-incrementing key
let objectStore = db.createObjectStore("notes_os", {
keyPath: "id",
autoIncrement: true,
});
// Define what data items the objectStore will contain
objectStore.createIndex("title", "title", { unique: false });
objectStore.createIndex("body", "body", { unique: false });
console.log("Database setup complete");
};
里面我们创建了一个表并创建了 index.表的元素会自动被分配一个id
.
添加新数据:
let newItem = { title: titleInput.value, body: bodyInput.value };
// open a read/write db transaction, ready for adding the data
let transaction = db.transaction(["notes_os"], "readwrite");
// call an object store that's already been added to the database
let objectStore = transaction.objectStore("notes_os");
// Make a request to add our newItem object to the object store
let request = objectStore.add(newItem);
遍历:
let objectStore = db.transaction('notes_os').objectStore('notes_os');
objectStore.openCursor().onsuccess = function(e) {
// Get a reference to the cursor
let cursor = e.target.result;
// If there is still another data item to iterate through, keep running this code
if(cursor) {
...
// Iterate to the next item in the cursor
cursor.continue();
}
else{
//当指针为空时进行处理
}
注意此时需要把 id 进行存储方便删除.
删除:
// Define the deleteItem() function
function deleteItem(e) {
// retrieve the name of the task we want to delete. We need
// to convert it to a number before trying it use it with IDB; IDB key
// values are type-sensitive.
let noteId = Number(e.target.parentNode.getAttribute('data-note-id'));
// open a database transaction and delete the task, finding it using the id we retrieved above
let transaction = db.transaction(['notes_os'], 'readwrite');
let objectStore = transaction.objectStore('notes_os');
let request = objectStore.delete(noteId);
// report that the data item has been deleted
上述操作仍需要加载 html,css,js 等文件,不可离线,我们也可以利用service worker
来实现离线可访问.
service worker
指一个被注册的为某个网站处理请求的 js 文件.它可以存下 http 请求的内容.
注册:
// Register service worker to control making site work offline
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register(
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js"
)
.then(function () {
console.log("Service Worker Registered");
});
}
注意路径是相对于网站跟路径而言的.
安装(即网站加载时开始控制请求):
self.addEventListener("install", function (e) {
e.waitUntil(
caches.open("video-store").then(function (cache) {
return cache.addAll([
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/",
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.html",
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js",
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/style.css",
]);
})
);
});
里面用了几个 cache API,open
指打开存储的缓存(通过新建对象),addALL
指请求相关的内容并加入缓存中.
处理请求,有缓存返回缓存,没缓存进行网络请求:
self.addEventListener("fetch", function (e) {
console.log(e.request.url);
e.respondWith(
caches.match(e.request).then(function (response) {
return response || fetch(e.request);
})
);
});
Validating forms
js 提供了Constraint Validation API用于验证数据.下列 element 支持它们:
HTMLButtonElement
(represents a<button>
element)HTMLFieldSetElement
(represents a<fieldset>
element)HTMLInputElement
(represents an<input>
element)HTMLOutputElement
(represents an<output>
element)HTMLSelectElement
(represents a<select>
element)HTMLTextAreaElement
(represents a<textarea>
element) 有如下的 attributes:validationMessage
: 返回表述规范的文字.如果 control 不需要验证数据或符合规范,返回空字符.validity
: 返回ValidityState
object,有如下 attributes:patternMismatch
: 值不符合pattern
时返回 true.tooLong
:值长过规定的最长时返回 true.tooShort
: 值长过规定的最短时返回 true.rangeOverflow
: 值大于最大值时返回 true.rangeUnderflow
: 值大于最小值时返回 true.typeMismatch
: 值不符合 type 规定的规范时返回 true(whentype
isemail
orurl
).valid
: 值符合要求时返回 true.valueMissing
: 值是必须的却为空时返回 true.
willValidate
: 值需要被检查时返回 true. 它还有如下的 methods:checkValidity()
:检查是否符合要求. 如果不符合, 返回 false 并让该 element 发出invalid
eventreportValidity()
: 执行checkValidity()
method, 若为 false 就像用户进行了提交后一样告诉用户出错.setCustomValidity(message)
:添加不符合要求时浏览器显示的信息.当添加时,该 element 被认为是invalid
.添加空字符代表valid
. 浏览器默认的错误显示不可通过 css,且在不同浏览器的实现不同,故我们可以通过novalidate
属性让 form 关闭浏览器默认的验证和错误显示,然后使用自己设计的验证系统.
sending form data
使用fetch
和FormData
:
let form = document.querySelector("form");
form.addEventListener("submit", async (e) => {
// on form submission, prevent default
e.preventDefault();
// construct a FormData object, which fires the formdata event
data = new FormData(form);
await fetch(url + "input_a_bill", {
method: "post",
body: data,
});
});
即使有文件它也会自动处理.
others
Destructuring assignment
用于解包数组或对象的语法. 数组的基础用法:
const [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"
定义声明分开:
let a, b;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
长度超过数组会被赋值为 undefined. 可以设置默认值:
let a, b;
[a = 5, b = 7] = [1];
console.log(a); // 1
console.log(b); // 7
理由这种语法可以进行值的交换:
let a = 1;
let b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
const arr = [1, 2, 3];
[arr[2], arr[1]] = [arr[1], arr[2]];
console.log(arr); // [1,3,2]
可以处理函数的返回值:
function f() {
return [1, 2];
}
let a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2
可以忽略掉一些值:
function f() {
return [1, 2, 3];
}
const [a, , b] = f();
console.log(a); // 1
console.log(b); // 3
const [c] = f();
console.log(c); // 1
将剩下的值赋给一个变量:
const [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]
对象基本语法:
const user = {
id: 42,
isVerified: true,
};
const { id, isVerified } = user;
console.log(id); // 42
console.log(isVerified); // true
于定义分开(()
是必须的):
let a, b;
({ a, b } = { a: 1, b: 2 });
使用新的名字:
const o = { p: 42, q: true };
const { p: foo, q: bar } = o;
console.log(foo); // 42
console.log(bar); // true
默认值:
const { a = 10, b = 5 } = { a: 3 };
console.log(a); // 3
console.log(b); // 5
用于函数参数:
const user = {
id: 42,
displayName: "jdoe",
fullName: {
firstName: "John",
lastName: "Doe",
},
};
function userId({ id }) {
return id;
}
function whois({ displayName, fullName: { firstName: name } }) {
return `${displayName} is ${name}`;
}
console.log(userId(user)); // 42
console.log(whois(user)); // "jdoe is John"
设置参数的默认值(使得该函数可不带参数):
function drawChart({
size = "big",
coords = { x: 0, y: 0 },
radius = 25,
} = {}) {
console.log(size, coords, radius);
// do some chart drawing
}
drawChart({
coords: { x: 18, y: 30 },
radius: 30,
});
数组和对象的 destruction 混用:
const metadata = {
title: "Scratchpad",
translations: [
{
locale: "de",
localization_tags: [],
last_edit: "2014-04-14T08:43:37",
url: "/de/docs/Tools/Scratchpad",
title: "JavaScript-Umgebung",
},
],
url: "/en-US/docs/Tools/Scratchpad",
};
let {
title: englishTitle, // rename
translations: [
{
title: localeTitle, // rename
},
],
} = metadata;
console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"
在循环中使用:
const people = [
{
name: "Mike Smith",
family: {
mother: "Jane Smith",
father: "Harry Smith",
sister: "Samantha Smith",
},
age: 35,
},
{
name: "Tom Jones",
family: {
mother: "Norah Jones",
father: "Richard Jones",
brother: "Howard Jones",
},
age: 25,
},
];
for (const {
name: n,
family: { father: f },
} of people) {
console.log("Name: " + n + ", Father: " + f);
}
// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"
使用 computed property name:
let key = "z";
let { [key]: foo } = { z: "bar" };
console.log(foo); // "bar"
将剩下的赋给一个变量:
let { a, b, ...rest } = { a: 10, b: 20, c: 30, d: 40 };
a; // 10
b; // 20
rest; // { c: 30, d: 40 }
destruction 可以让不合法的 identifier 合法:
const foo = { "fizz-buzz": true };
const { "fizz-buzz": fizzBuzz } = foo;
console.log(fizzBuzz); // true
当进行 destruction 时,js 会检查 prototype 链:
let obj = { self: "123" };
obj.__proto__.prot = "456";
const { self, prot } = obj;
// self "123"
// prot "456" (Access to the prototype chain)
modules
以前 js 通常只需要单文件运行,故没有 module 功能,现代浏览器为起添加了此功能。 注意只有在 modules 中可以使用 modules,故引入文件需:
<script type="module" src="main.js"></script>
modules 的核心的import
和export
. 首先是export
,声明 moudule 可以被 import 的事物,可以是 functions, var
, let
, const
, 或 classes:
export const name = "square";
注意必须是top-level item
,故不能在函数内 export. 更场景的用法是在文件结尾一次性 export:
export { name, draw, reportArea, reportPerimeter };
我们还可以设置一个默认 export:
export default function(ctx) {
...
}
然后是import
:
import { name, draw, reportArea, reportPerimeter } from "./modules/square.js";
import 默认:
import defaultExport from "module-name";
import { default as randomSquare } from "./modules/square.js";
只执行代码不使用:
import "/modules/my-module.js";
其中./
表示当前路径.如果写/
前缀则需补全前面的文件夹名. import 后不可修改,但可以修改 properties,类似 const. module 和通常的 js 文件有以下不同:
- 必须使用 server,使用本地文件运行会发送 CORS 错误.
- 使用
strict mode
. - 自动 defer.
- 引入几次都只执行一次.
- import 进的功能在 console 不可用. 我们可以使用
as
(import,export 均可)来重命名避免命名冲突:
// inside module.js
export { function1 as newFunctionName, function2 as anotherNewFunctionName };
// inside main.js
import { newFunctionName, anotherNewFunctionName } from "./modules/module.js";
我们还可以创建一个 module 对象来方便访问:
import * as Module from "./modules/module.js";
Module.function1();
我们可以阻止多个文件作为一个 module 被import
:
export { Square } from "./shapes/square.js";
export { Triangle } from "./shapes/triangle.js";
export { Circle } from "./shapes/circle.js";
注意在其中不能写代码,js 会直接定位到相应的文件. 我们还可以使用import()
来动态引入.它返回promise
:
import("./modules/myModule.js").then((module) => {
// Do something with the module.
});
在 module 中可以使用await
,引入的文件会自动等待:
// fetch request
const colors = fetch("../data/colors.json").then((response) => response.json());
export default await colors;
常见问题:
.js
文件必须正确配置MIME-type
.- 本地运行会导致 CORS 错误.
- 如果使用
.mjs
表示 module,有很多环境会不支持.
animation
控制 animation event:
.slidein {
animation-duration: 3s;
animation-name: slidein;
animation-iteration-count: 3;
animation-direction: alternate;
}
@keyframes slidein {
from {
margin-left: 100%;
width: 300%;
}
to {
margin-left: 0%;
width: 100%;
}
}
var element = document.getElementById("watchme");
element.addEventListener("animationstart", listener, false);
element.addEventListener("animationend", listener, false);
element.addEventListener("animationiteration", listener, false);
element.className = "slidein";
注意我们是在 js 中让动画开始(添加 class),不如 start 事件会在 js 执行前发出.