0%

Introduction

Promise就像它的名字一样,是指在未来某个时间将会发生的事情。这个事情会生成一个结果,我们叫做Result。

Promise有三种状态:

  • Pending: 初始状态,既不是成功状态,也不是失败状态。
  • Fulfilled: 意味着操作成功完成。
  • Rejected: 意味着操作失败。

这里,FullfilledRejected又统称为Settled。也就是说,一个Promise只要执行完毕有就算是Settled了, 无论是成功还是失败。

Promise API

Promise.all

Promise.all接收一个promise数组,返回一个新的promise。这个新的promise会在所有promise都resolve之后resolve,或者在任何一个promise reject之后reject。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 1 resolved');
}, 1000);
});

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 2 resolved');
}, 2000);
});

Promise.all([promise1, promise2]).then((values) => {
console.log(values);
});

output:

1
['Promise 1 resolved', 'Promise 2 resolved']

假设Promise 2 reject了,那么Promise.all会reject,并且返回的promise的值是Promise 2 reject的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 1 resolved');
}, 1000);
});

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(Error('Promise 2 rejected'));
}, 2000);
});

Promise.all([promise1, promise2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error);
});

output:

1
Error: Promise 2 rejected

注意,Promise.all大部分情况下是异步返回的,只有一种情况例外,那就是传递一个空数组。

1
Promise.all([]); // 立即同步返回 Promise { [] }

我们可以使用console.log来验证上述代码是同步返回的

1
2
3
4
console.log('start');
const p = Promise.all([]);
console.log(p);
console.log('end');

输出结果如下,可见Promise.all([])是同步返回的。

1
2
3
start
Promise { [] }
end

除了传递空数组外,其他所有情况都是异步返回,比如直接传递非promise数组。

1
const p = Promise.all([1, 2, 3]); // 异步返回。

思考题:
以下代码输会输出promise one running...吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise one running...');
resolve('one');
}, 1000);
});

const p2 = new Promise((resolve, reject) => {
reject('rejected');
});

Promise.all([p1, p2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error);
});

解析:会输出’promise one running…’, 虽然Promise.all会在任何一个promise reject之后立即reject,但是剩下的promise还是会继续执行,直到resolve或者reject。注意这就是promise的不可取消性,如何要取消,请使用RxJS中的observable。

到这里我们需要总结一下Promise.all的返回值,一共有以下几种情况:

  1. 所有promise都resolve,返回一个数组,数组中包含了所有promise的resolve值。
  2. 任何一个promise reject,返回的promise会reject,其值是第一个reject的promise的值。
  3. 传递一个空数组,返回的promise会立即resolve,值是一个空数组。

Promise.allSettled

这个方法与Promise.all类似,不同的是,Promise.allSettled会等待所有promise都settled之后返回。settled的意思是promise已经resolve或者reject了。

Promise.allSettled的返回值是一个数组,数组中的每个元素都是一个对象,包含了promise的状态和值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('one');
}, 1000);
});

const p2 = new Promise((resolve, reject) => {
reject('rejected');
});

// Using .catch:
Promise.allSettled([p1, p2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error);
});

以上代码输出如下:

1
2
3
4
[
{ status: 'fulfilled', value: 'one' },
{ status: 'rejected', reason: 'rejected' }
]

Promise.any

输入的Promise数组中,任意一个promise resolve,返回的promise就resolve。如果所有的promise都reject,返回的promise就reject。

以下代码输出quick,因为p2最快resolve。

1
2
3
4
5
6
const p1 = Promise.reject(0);
const p2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const p3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));

const promises = [p1, p2, p3];
Promise.any(promises).then((value) => console.log(value));

当所有promise都reject时,Promise.any会返回``AggregateError`,这个错误包含了所有的reject值。

1
2
3
4
const p1 = Promise.reject(0);
const p2 = Promise.reject(1);
const promises = [p1, p2];
Promise.any(promises).then((value) => console.log(value));

如果给Promise.any传递一个空数组,返回的promise会reject,错误信息是[AggregateError: All promises were rejected] { [errors]: [] }.

Promise.race

这个API最简单了,看名字就知道了,谁先返回就是谁,不论是resolve还是reject。
Promise.race接收一个promise数组,返回一个新的promise。这个新的promise会在任意一个promise resolve或者reject之后resolve或者reject。

下面代码输出two,因为p2最快resolve。

1
2
3
4
5
6
7
8
9
10
11
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});

const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});

Promise.race([p1, p2]).then((value) => {
console.log(value);
});

注意:如果给Promise.race传递一个空数组,返回的promise会一直pending,因为没有promise可以resolve或者reject。这就好比一个没有人参加的百米赛跑,永远不会有人冲过终点。

Promise in setTimeout

setTimeout is macro-task, and promise is micro-task. So the promise will be executed before the setTimeout.

1
2
3
4
5
6
7
8
9
10
const promise1 = Promise.resolve('Promise 1 resolved');
const promise2 = Promise.resolve('Promise 2 resolved');

const promise = Promise.all([promise1, promise2]);
console.log(promise);

// Using setTimeout, we can execute code after the queue is empty
setTimeout(() => {
console.log(promise);
});

output:

1
2
Promise { <pending> }
Promise { [ 'Promise 1 resolved', 'Promise 2 resolved' ] }

Why the output?

  1. Promise.all is a micro-task, so it will be executed before the setTimeout macro-task.
  2. The promise is pending when we log it for the first time.
  3. After the Promise.all is executed, the promise is resolved
  4. The setTimeout is a macro-task, so it will be executed after the queue is empty(no micro-task in the queue).
  5. The promise is resolved when we log it for the second time

Introduction

Promise就像它的名字一样,是指在未来某个时间将会发生的事情。这个事情会生成一个结果,我们叫做Result。

Promise有三种状态:

  • Pending: 初始状态,既不是成功状态,也不是失败状态。
  • Fulfilled: 意味着操作成功完成。
  • Rejected: 意味着操作失败。

这里,FullfilledRejected又统称为Settled。也就是说,一个Promise只要执行完毕有就算是Settled了, 无论是成功还是失败。

Promise API

Promise.all

Promise.all接收一个promise数组,返回一个新的promise。这个新的promise会在所有promise都resolve之后resolve,或者在任何一个promise reject之后reject。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 1 resolved');
}, 1000);
});

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 2 resolved');
}, 2000);
});

Promise.all([promise1, promise2]).then((values) => {
console.log(values);
});
``
output:
```javascript
['Promise 1 resolved', 'Promise 2 resolved']

假设Promise 2 reject了,那么Promise.all会reject,并且返回的promise的值是Promise 2 reject的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 1 resolved');
}, 1000);
});

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(Error('Promise 2 rejected'));
}, 2000);
});

Promise.all([promise1, promise2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error);
});

output:

1
Error: Promise 2 rejected

注意,Promise.all大部分情况下是异步返回的,只有一种情况例外,那就是传递一个空数组。

1
Promise.all([]); // 立即同步返回 Promise { [] }

我们可以使用console.log来验证上述代码是同步返回的

1
2
3
4
console.log('start');
const p = Promise.all([]);
console.log(p);
console.log('end');

输出结果如下,可见Promise.all([])是同步返回的。

1
2
3
start
Promise { [] }
end

除了传递空数组外,其他所有情况都是异步返回,比如直接传递非promise数组。

1
const p = Promise.all([1, 2, 3]); // 异步返回。

思考题:
以下代码输会输出promise one running...吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise one running...');
resolve('one');
}, 1000);
});

const p2 = new Promise((resolve, reject) => {
reject('rejected');
});

Promise.all([p1, p2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error);
});

解析:会输出’promise one running…’, 虽然Promise.all会在任何一个promise reject之后立即reject,但是剩下的promise还是会继续执行,直到resolve或者reject。注意这就是promise的不可取消性,如何要取消,请使用RxJS中的observable。

到这里我们需要总结一下Promise.all的返回值,一共有以下几种情况:

  1. 所有promise都resolve,返回一个数组,数组中包含了所有promise的resolve值。
  2. 任何一个promise reject,返回的promise会reject,其值是第一个reject的promise的值。
  3. 传递一个空数组,返回的promise会立即resolve,值是一个空数组。

Promise.allSettled

这个方法与Promise.all类似,不同的是,Promise.allSettled会等待所有promise都settled之后返回。settled的意思是promise已经resolve或者reject了。

Promise.allSettled的返回值是一个数组,数组中的每个元素都是一个对象,包含了promise的状态和值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('one');
}, 1000);
});

const p2 = new Promise((resolve, reject) => {
reject('rejected');
});

// Using .catch:
Promise.allSettled([p1, p2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error);
});

以上代码输出如下:

1
2
3
4
[
{ status: 'fulfilled', value: 'one' },
{ status: 'rejected', reason: 'rejected' }
]

Promise.any

输入的Promise数组中,任意一个promise resolve,返回的promise就resolve。如果所有的promise都reject,返回的promise就reject。

以下代码输出quick,因为p2最快resolve。

1
2
3
4
5
6
const p1 = Promise.reject(0);
const p2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const p3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));

const promises = [p1, p2, p3];
Promise.any(promises).then((value) => console.log(value));

当所有promise都reject时,Promise.any会返回``AggregateError`,这个错误包含了所有的reject值。

1
2
3
4
const p1 = Promise.reject(0);
const p2 = Promise.reject(1);
const promises = [p1, p2];
Promise.any(promises).then((value) => console.log(value));

如果给Promise.any传递一个空数组,返回的promise会reject,错误信息是[AggregateError: All promises were rejected] { [errors]: [] }.

Promise.race

这个API最简单了,看名字就知道了,谁先返回就是谁,不论是resolve还是reject。
Promise.race接收一个promise数组,返回一个新的promise。这个新的promise会在任意一个promise resolve或者reject之后resolve或者reject。

下面代码输出two,因为p2最快resolve。

1
2
3
4
5
6
7
8
9
10
11
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});

const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});

Promise.race([p1, p2]).then((value) => {
console.log(value);
});

注意:如果给Promise.race传递一个空数组,返回的promise会一直pending,因为没有promise可以resolve或者reject。这就好比一个没有人参加的百米赛跑,永远不会有人冲过终点。

Promise in setTimeout

setTimeout is macro-task, and promise is micro-task. So the promise will be executed before the setTimeout.

1
2
3
4
5
6
7
8
9
10
const promise1 = Promise.resolve('Promise 1 resolved');
const promise2 = Promise.resolve('Promise 2 resolved');

const promise = Promise.all([promise1, promise2]);
console.log(promise);

// Using setTimeout, we can execute code after the queue is empty
setTimeout(() => {
console.log(promise);
});

output:

1
2
Promise { <pending> }
Promise { [ 'Promise 1 resolved', 'Promise 2 resolved' ] }

Why the output?

  1. Promise.all is a micro-task, so it will be executed before the setTimeout macro-task.
  2. The promise is pending when we log it for the first time.
  3. After the Promise.all is executed, the promise is resolved
  4. The setTimeout is a macro-task, so it will be executed after the queue is empty(no micro-task in the queue).
  5. The promise is resolved when we log it for the second time

Debug JavaScript Applications

我平时调试的时候,基本都是以打log的方式实现的,也就是用console.log一顿输出,这种方式简单粗暴,但是确实比较费时,还是要掌握正统的调试方法。现在步骤记录如下。

  1. 启动项目
  2. 来到项目所在的网页,按下F12打开开发者工具。
  3. 找到source tab, 然后找到以webpack://开头的源码。
  4. 依次展开目录,找到自己的代码,单击代码行左边的行号,就可以添加断点,然后按下F5,刷新页面,这时如果触发到断点,就会停下来。

js-debug

如果你的项目比较新,使用的是ESBuild + Vite来构建的,那么源文件如下图所示:
js-debug-esbuild

Set breakpoint in code

如果觉得以上办法设置断点太麻烦,也可以在代码里面设置,找到对应的代码行数,在前面加一行叫做 debugger,待项目热更新完毕,页面就会停在断点处。十分的方便。

set breakpoint

是不是很方便呢?

JavaScript Data Types

JavaScript是一种弱类型语言,它有八种数据类型,分别是:

  • String
  • Number
  • BigInt
  • Boolean
  • Null
  • Undefined
  • Symbol
  • Object

除了Object之外,其他的数据类型都是基本类型(Primitive types)。
注意:JavaScript中的Array、Function、Set、Map、RegExp等等都是Object类型。

如何判断一个变量的类型

在JavaScript中,我们可以使用typeof操作符来判断一个变量的类型。typeof操作符返回一个字符串,表示变量的类型。

1
2
3
4
5
6
7
8
9
typeof 'Hello'; // string
typeof 123; // number
typeof true; // boolean
typeof null; // object
typeof undefined; // undefined
typeof Symbol('Hello'); // symbol
typeof {}; // object
typeof []; // object
typeof function(){}; // function

注意:虽然Function类型是Object的子类型,但是typeof操作符返回的是function

typeof无法精准的判断一个变量的类型,比如typeof null返回的是object,而typeof []返回的是object,这就需要我们使用其他方法来判断。

如何精准的判断一个变量的类型

在JavaScript中,我们可以使用Object.prototype.toString方法来精准的判断一个变量的类型。

1
2
3
4
5
Object.prototype.toString.call('Hello'); // [object String]
Object.prototype.toString.call(123); // [object Number]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call([]); // [object Array]

注意:Object.prototype.toString方法返回的是一个字符串,表示变量的类型。所以我们可以封装一个函数来判断一个变量的类型。(注意:判断数组可以用更好的方法 - Array.isArray方法)

1
2
3
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}

以下几个需要注意:

1
2
3
4
5
6
typeof typeof 1; // string, typeof 永远返回字符串。
typeof null; // object, 这是一个历史遗留问题。看这里:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#typeof_null
typeof NaN; // number, NaN是一个特殊的number类型。
typeof class C {}; // function, class是一个语法糖,本质上还是function。
typeof something; // undefined, 如果变量没有声明,那么typeof返回undefined。不会报错。
typeof document.all; // undefined, All current browsers expose a non-standard host object document.all with type undefined

instanceof 操作符

instanceof操作符用来判断一个对象是否是某个构造函数的实例。

1
2
3
const arr = [1, 2, 3];
arr instanceof Array; // true
arr instanceof Object; // true

References:

JavaScript identifiers

标识符命名规则

JavaScript标识符可以使用如下字符:

  • Unicode字母(包括中文)
  • 字母(A-Z,a-z)
  • 数字(0-9)
  • 下划线(_)- 通常用于变量的前缀或者后缀,用来表示私有变量
  • 美元符号($)- 通常用于一些library,比如jQuery。

注意事项

  • 标识符不能以数字开头
  • 标识符不能包含空格
  • 标识符不能是保留字

JavaScript 保留字

JavaScript 保留字是一些特殊的关键字,它们有特殊的用途,不能用作标识符。下面是一些JavaScript保留字:

1
2
3
4
break case catch class const continue debugger default delete do
else enum export extends false finally for function if import in instanceof
new null return super switch this throw true try typeof var void while with
yield

在strict mode下,还有一些额外的保留字:

1
implements interface let package private protected public static

冷知识

  • undefined/NaN/Infinity等等都属于全局变量,而不是一个关键字,所以你可以使用他们作为变量名,但是不建议这样做。比如下面的代码在Node环境是合法的。

    1
    2
    let undefined = 'Hello';
    console.log(undefined); // Hello
  • 可以使用中文作为变量名,但是不建议这样做。比如下面代码是合法的

    1
    2
    const 你好 = 'Hello';
    console.log(你好); // Hello

Cannot use import statement outside a module

产生这个错误的原因是因为你使用了import或者export语句,但是没有指定模块类型。在JavaScript中,有两种常用的模块类型:CommonJS和ES6 Modules。CommonJS是Node.js的模块化规范,它使用require来引入模块,使用module.exports来暴露接口。ES6 Modules是ES6的模块化规范,它使用import来引入模块,使用export来暴露接口。

解决办法

浏览器端

如果是浏览器端,需要将<script>标签的type属性设置为module,这样浏览器会将这个脚本当作ES6 Modules来处理。此时无论js文件的扩展名是.js还是.mjs都可以正常工作。

1
2
3
<script type="module">
import {add} from './math-utils.js';
</script>
1
2
3
4
// math-utils.js
export function add(a, b) {
return a + b;
}

Node.js端

方法一: 将文件的后缀名改为.mjs,这样Node.js会将这个文件当作ES6 Modules来处理。

1
2
3
4
// math-utils.mjs
export function add(a, b) {
return a + b;
}
1
2
// index.mjs
import {add} from './math-utils.mjs';

方法二: 在package.json中添加"type": "module"字段,这样Node.js会将所有的文件当作ES6 Modules来处理。

1
2
3
4
5
6
7
8
// package.json
{
"name": "json",
"version": "1.0.0",
"type": "module", // 添加这一行,就可以支持ESModule了。
"dependencies": {
}
}

总结

  • js - 常规js文件
  • mjs - ES6 Modules文件, 使用importexport来引入和暴露接口
  • cjs - CommonJS文件,使用requiremodule.exports来引入和暴露接口

Node.js环境可以使用任何上述文件格式,浏览器端其实不在意文件的后缀名,只要<script>标签的type属性设置为module,就可以使用ES6 Modules。

JavaScript Modules

Why use modules?

早期的JavaScript只是做一些简单的交互,所以不需要模块化。但是随着JavaScript的发展,现在的JavaScript已经可以做很多事情了,比如构建大型的应用程序,而Node.js的出现使得JavaScript甚至可以胜任某些Server端的工作,这时候模块化就显得非常重要了。

模块化的好处有很多,比如:

  • 代码复用
  • 代码隔离
  • 代码维护
  • 依赖管理

JavaScript模块化主要经历了以下几个阶段:

  • IIFE
  • CommonJS
  • AMD
  • UMD
  • ES6 Modules

IIFE

IIFE(Immediately Invoked Function Expression)是一种立即执行的函数表达式,它可以用来模拟模块化。IIFE的特点是:

  • 使用闭包来隔离作用域
  • 通过返回一个对象来暴露接口
1
2
3
4
5
6
const module = (function() {
...
return {
// expose some interface
};
})();

下面的代码使用IIFE封装了一个Person类,其中getName方法是public的,其他方法是private的。因为IIFE是定义后就立即执行的,所以这里我们使用一个person变量来接收IIFE的返回值,这样就可以调用getName方法了。

想要向外暴露任何接口,只需要在返回的对象中添加即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const person = (function person(firstName, lastName) {
const _firstName = firstName;
const _lastName = lastName;

function getFirstName() {
return _firstName;
}

function getLastName() {
return _lastName;
}

function getName() {
return `${getFirstName()} ${getLastName()}`;
}

return {
getName: getName,
};
})('Philip', 'Zhang');

console.log(person.getName()); // 'Philip Zhang'

CommonJS

CommonJS是Node.js的模块化规范,它的特点是:

  • 使用require来引入模块
  • 使用module.exports来暴露接口
1
2
// index.js
const module = require('./module');
1
2
3
4
// module.js
module.exports = {
// expose some interface
};

AMD

AMD(Asynchronous Module Definition)是另一种模块化规范,它的特点是:

  • 使用define来定义模块
  • 使用require来引入模块
1
2
3
4
5
6
define(['module'], function(module) {
...
return {
// expose some interface
};
});

UMD

UMD(Universal Module Definition)是一种通用的模块化规范,它的特点是:

  • 支持CommonJS和AMD
  • 通过判断typeof module来判断当前环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['module'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory(require('module'));
} else {
// Browser
root.module = factory(root.module);
}
}(this, function (module) {
...
return {
// expose some interface
};
}));

ES6 Modules

ES6 Modules是JavaScript的模块化规范,它的特点是:

  • 使用import来引入模块
  • 使用export来暴露接口
1
2
// index.js
import module from './module';
1
2
3
4
// module.js
export default {
// expose some interface
};

Node.js中的ES6 Modules

Node.js从v13.2.0开始支持ES6 Modules,但是需要使用.mjs后缀名。

1
2
// index.mjs
import module from './module.mjs';
1
2
3
4
// module.mjs
export default {
// expose some interface
};

总结

JavaScript模块化的发展经历了很多阶段,从IIFE到CommonJS、AMD、UMD,最后到ES6 Modules。ES6 Modules是JavaScript的官方模块化规范,它的特点是简洁、易用,所以在现代的JavaScript开发中,推荐使用ES6 Modules。

References:

如何运行 JavaScript

运行JavaScript代码有很多方式,以下总结四种方式:

  • 使用html运行
  • 使用浏览器控制台运行
  • 使用Node.js REPL(Read-Eval-Print Loop)交互式运行
  • 使用Node.js运行JavaScript文件
  • 使用编辑器运行

使用html运行

在html文件中,使用<script>标签引入JavaScript文件,然后在浏览器中打开html文件即可运行JavaScript代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Run JavaScript</title>
</head>
<body>
<script type="text/javascript">
console.log('Hello, world!');
</script>
</body>
</html>

使用浏览器控制台运行(Chrome)

在浏览器中打开网页,然后按F12(Windows)或者Ctrl+Shift+I(Mac)打开控制台,输入JavaScript代码即可运行。

使用Node.js REPL交互式运行

在终端中输入node命令,进入Node.js REPL环境,然后输入JavaScript代码即可运行。

1
2
3
4
$ node
> console.log('Hello, world!');
Hello, world!
undefined

使用Node.js运行JavaScript文件

在终端中输入node命令,后面跟上JavaScript文件路径,即可运行JavaScript文件。

1
2
$ node index.js
Hello, world!

使用编辑器运行

  • VS Code可以使用Code Runner插件运行JavaScript文件,安装插件后,右键点击文件,选择Run Code即可运行。
  • WebStorm可以直接右键点击文件,选择Run即可运行。

今天有幸了解到了另外一个IED,Komodo,主要是针对Python和Perl的,但是也支持JavaScript,有空下载来试试。

跨域问题一则

问题提出

今天在编写一个简单的html时,遇到了如下问题,html中引入了一个js文件,且html和js位于同一个目录下。

index.html

1
2
3
4
5
6
7
8
<html>
<body>
<script type="module">
import add from "./index.js";
console.log(add(1, 2));
</script>
</body>
</html>

index.js

1
2
3
export default function add(a, b) {
return a + b;
}

当我使用浏览器打开html文件时,Chrome浏览器控制台报错:

1
Access to script at 'file:///Users/philip/Projects/test.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

查看network tab,发现如下信息,在status一栏,发现跨域错误:CORS error

Alt text

首先复习一下什么是同源策略
同源策略是浏览器的一个安全功能,它要求一个网页只能与其本身的服务器进行交互,而不能与其他域的服务器进行交互。这样可以防止恶意的网站窃取数据。

从具体的实现来分析,协议,域名,端口号都相同的两个url就是同源的。以https://www.example.com:8080为例,它的组成部分如下:

如果省略端口,那么http默认是80,https默认是443。

那么问题来了,为什么我引入的js文件会报错呢?因为浏览器认为这是一个跨域请求,而不是同源请求。这是因为我们的html文件是通过http://协议打开的(双击html文件时,会使用系统默认的浏览器打开,这时观察浏览器中的地址,可知是http协议。),而html中引用的js文件是通过file://协议引入的,协议不同,所以浏览器认为这是一个跨域请求。

解决办法

  1. WebStorm - WebStorm打开网页时没有这个问题,因为WebStorm内置了一个服务器,所以它的协议是http://,所以不会报错。
  2. VSCode - 安装插件Live Server,使用Live Server打开网页,也不会报错。
    1. npm install -g live-server
    2. Open terminal, cd 到html文件所在目录
    3. run live-server .

当然也可以使用http-server

问题提出

今天刚到公司,前端组的小美找到我,说有一个数组类型搞不定,让我帮忙看一下,简单来说就是小美调用api后返回的数据无法赋值给下面这个类型。

先看一下这个返回值类型定义,注意这里的students是一个数组字面量,而不是普通的数组,数组中的元素是一个对象,对象中有nameage两个属性。

1
2
3
4
5
6
7
8
interface response {
students: [
{
name: string;
age: number;
}
];
}

下面是小美的API返回的数据,注意这个返回值是一个数组,而非数组字面量。

1
2
3
4
5
6
7
8
9
10
11
12
const data: response = {
students: [
{
name: 'Philip',
age: 18,
},
{
name: 'Tom',
age: 20,
},
],
};

返回值中students数组中的元素个数是不对的,因为students的类型定义是一个元素的数组字面量,但是返回值中的students却包含两个元素(这相当于把students当作数组类型来使用,而不是当作数组字面量来使用)。所以TS会给出如下提示:

1
2
xxx is not assignable to type [{ name: string; age: number; }]
Source has 2 element(s) but target allows only 1

解决办法

为了避免上面对的错误提示,我们可以使用Array类型来定义students

1
2
3
4
5
6
interface response {
students: Array<{
name: string;
age: number;
}>;
}

如此定义,则students是一个数组,不再是数组字面量,这样就可以包含任意个元素了。当然也可以使用[]来定义students

1
2
3
4
5
6
interface response {
students: {
name: string;
age: number;
}[];
}

这种方法和上面的方法是等价的。这时,我们再使用上面的data就不会报错了。

1
2
3
4
5
6
7
8
9
10
11
12
const data: response = {
students: [
{
name: 'Philip',
age: 18,
},
{
name: 'Tom',
age: 20,
},
],
};

但是有一种特殊情况,就是类型定义有时是无法修改的,比如定义在第三方库中,这时候我们不直接赋值,而是把返回值push到数组中,这样就不会报错了。

1
2
3
4
5
6
7
8
9
10
const data: response = {
students: [
{
name: 'Philip',
age: 18,
},
],
};

data.students.push({ name: 'John', age: 20 })