目录
1. 简介
2. 变量与数据类型
2.1 声明变量
2.2 基本类型
2.3 对象类型
2.3.1 函数 Function
2.3.2 数组Array
2.3.3 对象 Object ⭐️⭐️
3. 运算符和表达式
1) ===
2) ||
4) ...
5) [] {}
4. 控制语句
1) for in
2) for of
3) try catch
5. API
5.1 环境准备
5.2 前端案例
5.3 利用模板
5.4 Fetch API
跨域问题(重要)
5.5 模块化
JavaScript(简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式、函数式编程范式。
JavaScript的标准是ECMAScript 。ES.截至 2012 年,所有浏览器都完整的支持ECMAScript 5.1,旧版本的浏览器至少支持ECMAScript 3 标准。2015年6月17日,ECMA国际组织发布了ECMAScript的第六版,该版本正式名称为 ECMAScript 2015,但通常被称为ECMAScript 6 或者ES2015
JavaScript是一门世界上最热门的脚本语言,可以用来更改页面内容,控制多媒体,制作图像、动画等等
最新版本已经到es6
(1)let
let 变量名 = 值
let 声明的变量可以被多次赋值,例如
let a = 100; // 初始值是 100
a = 200; // ok, 被重新赋值为 20
2) const
const 修饰的叫常量,只能赋值一次
const b = 300; // 初始值是 300
b = 400; // error, 不能再次赋值
const 并不意味着它引用的内容不可修改,例如
const c = [1,2,3];
c[2] = 4; // ok, 数组内容被修改成 [1,2,4]
c = [5,6]; // error, 不能再次赋值
3) var
var 声明的变量可以被多次赋值,例如
x var f = 100;f = 200;
1,2) undefined 和 null
执行表达式或函数,没有返回结果,出现 undefined
访问数组不存在的元素,访问对象不存在的属性,出现 undefined
定义变量,没有初始化,出现 undefined
例 :
console.log(1); // 函数没有返回值, 结果是 undefined
let a = 10; // 表达式没有返回值, 结果是 undefined
let b = [1,2,3];
console.log(b[10]); // 数组未定义元素是 undefined
let c = {"name":"张三"};
console.log(c.age); // 对象未定义属性是 undefined
let d;
console.log(d); // 变量未初始化是 undefined
二者共同点
都没有属性、方法
二者合称 Nullish
二者区别
undefined 由 js 产生
null 由程序员提供
3) string ⭐️
js 字符串三种写法
let a = "hello"; // 双引号
let b = "world"; // 单引号
let c = `hello`; // 反引号
html 代码如下,用 java 和 js 中的字符串如何表示?
超链接
java 显得比较繁琐,a标签里的双引号需要用到转义字符
String s1 = "超链接";
String s2 = """
超链接""";
js 就比较灵活
let s1 = '超链接';
let s2 = `超链接`;
模板字符串(Template strings)
需求:拼接 URI 的请求参数,如
/test?name=zhang&age=18
/test?name=li&age=20
传统方法拼接
let name = ; // zhang li ...
let age = ; // 18 20 ...
let uri = "/test?name=" + name + "&age=" + age;
模板字符串方式(必须是用 `` 符号)
let name = ; // zhang li ...
let age = ; // 18 20 ...
let uri = `/test?name=${name}&age=${age}`;
4,5) number 和 bigint⭐️
number 类型标识的是双精度浮动小数,例如
10 / 3; // 结果 3.3333333333333335
既然是浮点小数,那么可以除零
10 / 0; // 结果 Infinity 正无穷大
-10 / 0; // 结果 -Infinity 负无穷大
浮点小数都有运算精度问题,例如
2.0 - 1.1; // 结果 0.8999999999999999
字符串转数字
parseInt("10"); // 结果是数字 10
parseInt("10.5"); // 结果是数字 10, 去除了小数部分
parseInt("10") / 3; // 结果仍视为 number 浮点数, 因此结果为 3.3333333333333335
parseInt("abc"); // 转换失败,结果是特殊值 NaN (Not a Number)
要表示真正的整数,需要用 bigint,数字的结尾用 n 表示它是一个 bigint 类型
10n / 3n;
6) boolean ⭐️
在 js 中,并不是 boolean 才能用于条件判断,你可以在 if 语句中使用【数字】、【字符串】... 作为判断条件
let b = 1;
if(b) { // true
console.log("进入了");
}
这时就有一个规则,当需要条件判断时,这个值被当作 true 还是 false,当作 true 的值归类为 truthy,当作 false 的值归类为 falsy
下面值都是 falsy
false
Nullish (null, undefined)
0, 0n, NaN
"" '' ``
即长度为零的字符串
剩余的值绝大部分都是 truthy
注意:有几个容易被当作 falsy 实际是 truthy 的
"false", "0"
即字符串的 false 和 字符串的零
[]
空数组
{}
空对象
1. 定义函数
function 函数名(参数) {
// 函数体
return 结果;
}
例
function add(a, b) {
return a + b;
}
2. 调用函数
add(1, 2);// 返回 3
js 中的函数调用特点:对参数的类型和个数都没有限制,例如
add('a', 'b'); // 返回 ab
add(4, 5, 6); // 返回 9, 第三个参数没有被用到, 不会报错
add(1); // 返回 NaN, 这时 b 没有定义是 undefined, undefined 做数学运算结果就是 NaN
3. 默认参数
java 中(spring)要实现默认参数的效果得这么做:
指定 defaultValue 默认值
@RestController
public class MyController {
@RequestMapping("/page")
@ResponseBody
public void page(
@RequestParam(defaultValue="1") int page,
@RequestParam(defaultValue="10") int size
){
// ...
}
}
Js:
function pagination(page = 1, size = 10) {
console.log(page, size);
}
4. 匿名函数
语法:(在外面在套一层括弧~)
(function (参数) {
// 函数体
return 结果;
})
例
(function(a,b){
return a + b;
})
5. 使用场景
第一种场景:定义完毕后立刻调用(双括号)
(function(a,b){
return a + b;
})(1,2)
第二种场景:作为其它对象的方法,例如
页面有元素
点我啊
此元素有一个 onclick 方法,会在鼠标单击这个元素后被执行,onclick 方法刚开始是 null,需要赋值后才能使用
document.getElementById("p1").onclick = (function(){
console.log("我要被点死啦~...");
});
6. 箭头函数
语法:
(参数) => {
// 函数体
return 结果;
}
如果没有参数,() 还是要保留
如果只有一个参数,() 可以省略
如果函数体内只有一行代码,{} 可以省略
如果这一行代码就是结果,return 可以省略
document.getElementById("p1").onclick = () => console.log("aa");
7. 函数是对象
以下形式在 js 中非非非非非非常见!
1. 可以参与 赋值,例,具名函数也能参与赋值
function abc() {
console.log("bb");
}
document.getElementById("p1").onclick = abc;
2. 有属性、有方法,执行 console.dir(abc)
,输出结果如下
ƒ abc()
arguments: null
caller: null
length: 0
name: "abc"
➡prototype: {constructor: ƒ}
[[FunctionLocation]]: VM1962:1
➡[[Prototype]]: ƒ ()
➡[[Scopes]]: Scopes[1]
其中带有 f 标记的是方法,不带的是属性
带有 ➡ 符号的可以继续展开,限于篇幅省略了
带有 [[ ]]
的是内置属性,不能访问,只能查看
相对重要的是 [[Prototype]]
和 [[Scopes]]
会在后面继承和作用域时讲到
3.可以作为方法参数
function a() {
console.log('a')
}
function b(fn) { // fn 将来可以是一个函数对象
console.log('b')
fn(); // 调用函数对象
}
b(a)
4. 可以作为方法返回值
function c() {
console.log("c");
function d() {
console.log("d");
}
return d;
}
c()()
c()调用完是d,所以c()()就变成了d()
8. 函数作用域
函数可以嵌套(js 代码中很常见,只是嵌套的形式更多是匿名函数,箭头函数)
function a() {
function b() {
}
}
看下面的例子
function c() {
var z = 30;
}
var x = 10;
function a() {
var y = 20;
function b() {
// 看这里
console.log(x, y);
}
b();
}
a();
b方法的作用于就是如下图
这里后台是只显示了不包括自己方法体内的作用域
以函数为分界线划定作用域,所有函数之外是全局作用域
查找变量时,由内向外查找
在内层作用域找到变量,就会停止查找,不会再找外层
所有作用域都找不到变量,报错
作用域本质上是函数对象的属性,可以通过 console.dir 来查看调试
补充: let 和 var的区别(作用域)
如果函数外层引用的是 let 变量,那么外层普通的 {} 也会作为作用域边界,最外层的 let 也占一个 script 作用域
let x = 10;
if(true) {
let y = 20;
function b() {
console.log(x,y);
}
console.dir(b);
}
如果函数外层引用的是 var 变量,外层普通的 {} 不会视为边界
var x = 10;
if(true) {
var y = 20;
function b() {
console.log(x,y);
}
console.dir(b);
}
如果 var 变量出现了重名,则他俩会被视为同一作用域中的同一个变量
var e = 10;
if(true) {
var e = 20;
console.log(e); // 打印 20
}
console.log(e); // 因为是同一个变量,还是打印 20
如果是 let,则视为两个作用域中的两个变量
let e = 10;
if(true) {
let e = 20;
console.log(e); // 打印 20
}
console.log(e); // 打印 10
要想里面的 e 和外面的 e 能区分开来,最简单的办法是改成 let,或者用函数来界定作用域范围
所以,大多数情况下,推荐使用let声明变量~
语法
// 创建数组
let arr = [1,2,3];
// 获取数组元素
console.log(arr[0]); // 输出 1
// 修改数组元素
array[0] = 5; // 数组元素变成了 [5,2,3]
// 遍历数组元素,其中 length 是数组属性,代表数组长度
for(let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
push、shift、splice
let arr = [1,2,3];
arr.push(4); // 向数组尾部(右侧)添加元素, 结果 [1,2,3,4]
arr.shift(); // 从数组头部(左侧)移除元素, 结果 [2,3,4]
arr.splice(1,1); // 删除【参数1】索引位置的【参数2】个元素,结果 [2,4]
join
let arr = ['a','b','c'];
arr.join(); // 默认使用【,】作为连接符,结果 'a,b,c'
arr.join(''); // 结果 'abc'
arr.join('-'); // 结果 'a-b-c'
map、filter、forEach
let arr = [1,2,3,6];
function a(i) { // 代表的新旧元素之间的变换规则
return i * 10
}
// arr.map(a) // 具名函数,结果 [10,20,30,60]
// arr.map( (i) => {return i * 10} ); // 箭头函数
arr.map( i => i * 10 ); // 箭头函数
传给 map 的函数,参数代表旧元素,返回值代表新元素
map 的内部实现(伪代码)
function map(a) { // 参数是一个函数
let narr = [];
for(let i = 0; i < arr.length; i++) {
let o = arr[i]; // 旧元素
let n = a(o); // 新元素
narr.push(n);
}
return narr;
}
filter 例子
let arr = [1,2,3,6];
arr.filter( (i)=> i % 2 == 1 ); // 结果 [1,3]
传给 filter 的函数,参数代表旧元素,返回 true 表示要留下的元素
forEach 例子
let arr = [1,2,3,6];
/*for(let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}*/
arr.forEach( (i) => console.log(i) );
两个称呼
高阶函数,map,filter,forEach
回调函数,例如作为参数传入的函数
语法
let obj = {
属性名: 值,
方法名: 函数,
get 属性名() {},
set 属性名(新值) {}
}
例1
let stu1 = {
name: "小明",
age: 18,
study: function(){
console.log(this.name + "爱学习");
}
}
例2
let name = "小黑";
let age = 20;
let study = function(){
console.log(this.name + "爱学习");
}
let stu2 = { name, age, study }
例3(重点)
let stu3 = {
name: "小白",
age: 18,
study(){
console.log(this.name + "爱学习");
}
}
注意:对象方法这么写,仅限于对象内部
例4 get,set 方法
let stu4 = {
_name: null, /*类似于java中私有成员变量*/
get name() {
console.log("进入了get");
return this._name;
},
set name(name) {
console.log("进入了set");
this._name = name;
}
}
调用 get,set
stu4.name = "小白"
console.log(stu4.name)
特色:属性增删
对比一下 Java 中的 Object
Java 的 Object 是以类作为模板来创建,对象不能脱离类模板的范围,一个对象的属性、能用的方法都是确定好的
js 的对象,不需要什么模板,它的属性和方法可以随时加减
let stu = {name:'张三'};
stu.age = 18; // 添加属性
delete stu.age; // 删除属性
stu.study = function() { // 添加方法
console.log(this.name + "在学习");
}
添加 get,set,需要借助 Object.definePropery
let stu = {_name:null};
Object.defineProperty(stu, "name", {
get(){
return this._name;
},
set(name){
this._name = name;
}
});
参数1:目标对象
参数2:属性名
参数3:get,set 的定义
特色:this
先来对 Java 中的 this 有个理解
public class TestMethod {
static class Student {
private String name;
public Student(String name) {
this.name = name;
}
public void study(Student this, String subject) {
System.out.println(this.name + "在学习 " + subject);
}
}
public static void main(String[] args) {
Student stu = new Student("小明");
// 下面的代码,本质上是执行 study(stu, "java"),因此 this 就是 stu
stu.study("java");
}
}
Java 中的 this 是个隐式参数
Java 中,我们说 this 代表的就是调用方法的那个对象
js 中的 this 也是隐式参数,但它与函数运行时上下文相关
例如,一个“落单”的函数
function study(subject) {
console.log(this.name + "在学习 " + subject)
}
测试一下
study("js"); // 输出 在学习 js
返回一个空字符串。
这是因为此时函数执行,全局对象 window 被当作了 this,window 对象的 name 属性是空串
同样的函数,如果作为对象的方法
let stu = {
name:"小白",
study
}
这种情况下,会将当前对象作为 this
stu.study('js'); // 输出 小白在学习 js
还可以动态改变 this
let stu = {name:"小黑"};
study.call(stu, "js"); // 输出 小黑在学习 js
这回 study 执行时,就把 call 的第一个参数 stu 作为 this
有一个例外是,在箭头函数内出现的 this,以外层 this 理解
用匿名函数
let stu = {
name: "小花",
friends: ["小白","小黑","小明"],
play() {
this.friends.forEach(function(e){
console.log(this.name + "与" + e + "在玩耍");
});
}
}
stu.play()
输出结果为:
与小白在玩耍
与小黑在玩耍
与小明在玩耍
前面的this.name,就是空字符串啦
如果用箭头函数的话,
let stu = {
name: "小花",
friends: ["小白","小黑","小明"],
play() {
this.friends.forEach(e => {
console.log(this.name + "与" + e + "在玩耍");
})
}
}
this.name 所在的函数是箭头函数,因此 this 要看它外层的 play 函数,play 又是属于 stu 的方法,因此 this 代表 stu 对象
输出结果为:
小花与小白在玩耍
小花与小黑在玩耍
小花与小明在玩耍
还有一种不用箭头函数的做法,就是定义一个变量等于this
let stu = {
name: "小花",
friends: ["小白","小黑","小明"],
play() {
let me = this;
this.friends.forEach(function(e){
console.log(me.name + "与" + e + "在玩耍");
});
}
}
特色:原型继承
let father = {
f1: '父属性',
m1: function() {
console.log("父方法");
}
}
let son = Object.create(father);
console.log(son.f1); // 打印 父属性
son.m1(); // 打印 父方法
father 是父对象,son 去调用 .m1 或 .f1 时,自身对象没有,就到父对象找
son 自己可以添加自己的属性和方法
son 里有特殊属性 __proto__
代表它的父对象,js 术语: son 的原型对象
不同浏览器对打印 son 的 __proto__
属性时显示不同
Edge 打印 console.dir(son) 显示 [[Prototype]]
Firefox 打印 console.dir(son) 显示
特色:基于函数的原型继承
出于方便的原因,js 又提供了一种基于函数的原型继承
函数职责
负责创建子对象,给子对象提供属性、方法,功能上相当于构造方法
函数有个特殊的属性 prototype,它就是函数创建的子对象的父对象
注意!名字有差异,这个属性的作用就是为新对象提供原型
function cons(f2) {
// 创建子对象(this), 给子对象提供属性和方法
this.f2 = f2;
this.m2 = function () {
console.log("子方法");
}
}
// cons.prototype 就是父对象
cons.prototype.f1 = "父属性";
cons.prototype.m1 = function() {
console.log("父方法");
}
配合 new 关键字,创建子对象
let son = new cons("子属性")
子对象的 __proto__
就是函数的 prototype
属性
Json
之前我们讲 http 请求格式时,讲过 json 这种数据格式,它的语法看起来与 js 对象非常相似,例如:
一个 json 对象可以长这样:
{
"name":"张三",
"age":18
}
一个 js 对象长这样:
{
name:"张三",
age:18
}
那么他们的区别在哪儿呢?我总结了这么几点
本质不同
json 对象本质上是个字符串,它的职责是作为客户端和服务器之间传递数据的一种格式,它的属性只是样子货
js 对象是切切实实的对象,可以有属性方法
语法细节不同
json 中只能有 null、true|false、数字、字符串(只有双引号)、对象、数组
json 中不能有除以上的其它 js 对象的特性,如方法等
json 中的属性必须用双引号引起来
json 字符串与 js 对象的转换
JSON.parse(json字符串); // 返回js对象
JSON.stringify(js对象); // 返回json字符串
主要有以下几种:
+ - * / % **
+= -= *= /= %= **=
++ --
位运算、移位运算
== != > >= < <=
=== !==
⭐️
&& || !
⭐️
?? ?.
⭐️
...
⭐️
解构赋值 ⭐️
像加减乘除这种和Java中的一样,说起来没啥意义,挑几个没见过的来写~
严格相等运算符,用作逻辑判等,又称绝对等于,值和类型都相等才算相等
1 == 1 // 返回 true
1 == '1' // 返回 true,会先将右侧的字符串转为数字,再做比较
1 === '1' // 返回 false,类型不等,直接返回 false
typeof 查看某个值的类型
typeof 1 // 返回 'number'
typeof '1' // 返回 'string'
需求:如果参数 n 没有传递,给它一个【男】
function test(n = '男') {
console.log(n);
}
还可以这样
function test(n) {
if(n === undefined) {
n = '男';
}
console.log(n);
}
还可以这样:三位运算符
function test(n) {
n = (n === undefined) ? '男' : n;
console.log(n);
}
甚至可以这样:
function test(n) {
n = n || '男';
console.log(n);
}
它的语法是:
值1 || 值2
如果值1 是 Truthy,返回值1,如果值1 是 Falsy 返回值 2
3) ?? 与 ?.
??
语法
值1 ?? 值2
值1 是 nullish,返回值2
值1 不是 nullish,返回值1
需求,如果参数 n 没有传递或是 null,给它一个【男】
如果用传统办法
function test(n) {
if(n === undefined || n === null) {
n = '男';
}
console.log(n);
}
用??
function test(n) {
n = n ?? '男';
console.log(n);
}
?.
需求,函数参数是一个对象,可能包含有子属性
例如,参数可能是
let stu1 = {
name:"张三",
address: {
city: '北京'
}
};
let stu2 = {
name:"李四"
}
let stu3 = {
name:"李四",
address: null
}
现在要访问子属性(有问题)
像stu1,有address属性,不会报错,但是像stu2,stu3就会出现报错
function test(stu) {
console.log(stu1.address.city)
}
现在就是说,通过某种方法,实现如果有该子属性,就输出,如果没有,也不报错
用传统办法
function test(stu) {
if(stu.address === undefined || stu.address === null) {
console.log(undefined);
return;
}
console.log(stu.address.city)
}
用?.
它的意思呢,就是说: 某个属性是 nullish 时,短路并返回 undefined
function test(stu) {
console.log(stu.address?.city)
}
这是一个展开运算符
作用1:打散数组,把元素传递给多个参数
let arr = [1,2,3];
function test(a,b,c) {
console.log(a,b,c);
}
需求,把数组元素依次传递给函数参数
传统写法
test(arr[0],arr[1],arr[2]); // 输出 1,2,3
展开运算符写法
test(...arr);
打散可以理解为【去掉了】数组外侧的中括号,只剩下数组元素
作用2:复制数组或对象
数组:
let arr1 = [1,2,3];
let arr2 = [...arr1]; // 复制数组
对象
let obj1 = {name:'张三', age: 18};
let obj2 = {...obj1}; // 复制对象
注意:展开运算符复制属于浅拷贝,例如
let o1 = {name:'张三', address: {city: '北京'} }
let o2 = {...o1};
作用3:合并数组或对象
合并数组
let a1 = [1,2];
let a2 = [3,4];
let b1 = [...a1,...a2]; // 结果 [1,2,3,4]
let b2 = [...a2,5,...a1] // 结果 [3,4,5,1,2]
合并对象
let o1 = {name:'张三'};
let o2 = {age:18};
let o3 = {name:'李四'};
let n1 = {...o1, ...o2}; // 结果 {name:'张三',age:18}
let n2 = {...o3, ...o2, ...o1}; // 结果{name:'李四',age:18}
复制对象时出现同名属性,后面的会覆盖前面的
解构赋值
[ ]
用在声明变量时
let arr = [1,2,3];
let [a, b, c] = arr; // 结果 a=1, b=2, c=
用在声明参数时
let arr = [1,2,3];
function test([a,b,c]) {
console.log(a,b,c) // 结果 a=1, b=2, c=3
}
test(arr);
{ }
用在声明变量时
let obj = {name:"张三", age:18};
let {name,age} = obj; // 结果 name=张三, age=18
用在声明参数时
let obj = {name:"张三", age:18};
function test({name, age}) {
console.log(name, age); // 结果 name=张三, age=18
}
test(obj)
主要有以下几种
if ... else
switch
while
do ... while
for
for ... in
⭐️
for ... of
⭐️
try ... catch
⭐️
向if...else,swtich,while这些Java中见惯不惯的就不讲啦~直接说标星星的
主要用来遍历对象
let father = {name:'张三', age:18, study:function(){}};
for(const n in father) {
console.log(n);
}
其中 const n 代表遍历出来的属性名
注意1:方法名也能被遍历出来(它其实也算一种特殊属性)
注意2:遍历子对象时,父对象的属性会跟着遍历出来
let son = Object.create(father);
son.sex = "男";
for(const n in son) {
console.log(n);
}
注意3:在 for in 内获取属性值,要使用 [] 语法,而不能用 . 语法
for(const n in son) {
console.log(n, son[n]);
}
主要用来遍历数组,也可以是其它可迭代对象,如 Map,Set 等
let a1 = [1,2,3];
for(const i of a1) {
console.log(i);
}
let a2 = [
{name:'张三', age:18},
{name:'李四', age:20},
{name:'王五', age:22}
];
for(const obj of a2) {
console.log(obj.name, obj.age);
}
for(const {name,age} of a2) {
console.log(name, age);
}
抛异常嘛,和Java一样
let stu1 = {name:'张三', age:18, address: {city:'北京'}};
let stu2 = {name:'张三', age:18};
function test(stu) {
try {
console.log(stu.address.city)
} catch(e) {
console.log('出现了异常', e.message)
} finally {
console.log('finally');
}
}
1) 安装 nvm
nvm 即 (node version manager),好处是方便切换 node.js 版本
参考大佬:nvm的简介、安装、使用(简单明了)_仰.的博客-CSDN博客_nvm
安装注意事项
要卸载掉现有的 nodejs
提示选择 nvm 和 nodejs 目录时,一定要避免目录中出现空格
选用【以管理员身份运行】cmd 程序来执行 nvm 命令
首次运行前设置好国内镜像地址(npm安装完后)
nvm node_mirror http://npm.taobao.org/mirrors/node/
nvm npm_mirror https://npm.taobao.org/mirrors/npm/
首先查看有哪些版本
nvm list available
输出
| CURRENT | LTS | OLD STABLE | OLD UNSTABLE |
|--------------|--------------|--------------|--------------|
| 18.7.0 | 16.16.0 | 0.12.18 | 0.11.16 |
| 18.6.0 | 16.15.1 | 0.12.17 | 0.11.15 |
| 18.5.0 | 16.15.0 | 0.12.16 | 0.11.14 |
| 18.4.0 | 16.14.2 | 0.12.15 | 0.11.13 |
| 18.3.0 | 16.14.1 | 0.12.14 | 0.11.12 |
| 18.2.0 | 16.14.0 | 0.12.13 | 0.11.11 |
| 18.1.0 | 16.13.2 | 0.12.12 | 0.11.10 |
| 18.0.0 | 16.13.1 | 0.12.11 | 0.11.9 |
| 17.9.1 | 16.13.0 | 0.12.10 | 0.11.8 |
| 17.9.0 | 14.20.0 | 0.12.9 | 0.11.7 |
| 17.8.0 | 14.19.3 | 0.12.8 | 0.11.6 |
| 17.7.2 | 14.19.2 | 0.12.7 | 0.11.5 |
| 17.7.1 | 14.19.1 | 0.12.6 | 0.11.4 |
| 17.7.0 | 14.19.0 | 0.12.5 | 0.11.3 |
| 17.6.0 | 14.18.3 | 0.12.4 | 0.11.2 |
| 17.5.0 | 14.18.2 | 0.12.3 | 0.11.1 |
| 17.4.0 | 14.18.1 | 0.12.2 | 0.11.0 |
| 17.3.1 | 14.18.0 | 0.12.1 | 0.9.12 |
| 17.3.0 | 14.17.6 | 0.12.0 | 0.9.11 |
| 17.2.0 | 14.17.5 | 0.10.48 | 0.9.10 |
建议安装 LTS(长期支持版)
nvm install 16.16.0
nvm install 14.20.0
执行 nvm list
会列出已安装版本
切换到 16.16.0
nvm use 16.16.0
切换到 14.20.0
nvm use 14.20.0
2) 检查 npm
npm 是 js 的包管理器,就类似于 java 界的 maven,要确保它使用的是国内镜像
检查镜像
npm get registry
如果返回的不是 https://registry.npm.taobao.org/
,需要做如下设置
npm config set registry https://registry.npm.taobao.org/
3) 搭建前端服务器
新建一个保存项目的 client 文件夹,进入文件夹,打开powershell 执行
npm install express --save-dev
就会出现这三个文件,这就说明环境已经搭好了
然后修改package.json文件,将服务器改成module
{
"type": "module",
"devDependencies": {
"express": "^4.18.1"
}
}
编写 main.js 代码
import express from 'express'
const app = express()
app.use(express.static('./'))
app.listen(7070)
最后执行 main.js 代码(运行前端服务器)
node main.js
初步效果
架构
前端只有静态页面,使用 Express 服务器
后端使用 Tomcat 服务器,通过 SpringBoot、MyBatis 等框架获取数据库数据
1) 查找元素
document.getElementById - 根据 id 值查找一个元素
[document|元素].querySelector - 根据选择器查找第一个匹配元素
[document|元素].querySelectorAll - 根据选择器查找所有匹配元素
例如,有下面的Html代码:
学生列表
编号
姓名
性别
年龄
1
张三
男
18
执行
document.querySelector('.title'); // 找到 学生列表
执行
document.querySelector('.col'); // 找到 编号
执行
document.querySelectorAll('.col');
/*
找到的是一个集合
编号
姓名
性别
年龄
1
张三
男
18
*/
执行
const thead = document.querySelector('.thead');
// 只在 thead 元素范围内找
thead.querySelectorAll('.col');
/*
找到的是一个集合
编号
姓名
性别
年龄
*/
根据 id 属性查找既可以用
document.getElementById("id值")
也可以用
document.querySelector("#id值")
2) 修改元素内容
元素.innerHTML
元素.textContent
例如
document.querySelector('.title').innerHTML = '侠客列表'
它们的区别:
innerHTML 会解析内容中的标签,例如
textContext 不会解析内容中的标签
给 innerHTML 或 textContent 赋值空串,可以实现清空标签内容的效果。
直接看代码:
学生列表
编号
姓名
性别
年龄
xx
xx
xx
xx
Fetch API 可以用来获取远程数据,它有两种方式接收结果,同步方式与异步方式
格式:
fetch(url, options) // 返回 Promise
同步方式
const 结果 = await Promise
// 后续代码
await 关键字必须在一个标记了 async 的 function 内来使用
后续代码不会在结果返回前执行
异步方式
Promise
.then(结果 => { ... })
// 后续代码
后续代码不必等待结果返回就可以执行
例: 在 express 服务器上有 students.json 文件
[
{ "id": 1, "name": "张三", "sex": "男", "age": 18 },
{ "id": 2, "name": "李四", "sex": "女", "age": 17 }
]
现在用 fetch api 获取这些数据,并展示
同步方式
fetch('students.json') 内部会发送请求,但响应结果不能立刻返回,因此 await 就是等待响应结果返回
其中 resp.json() 也不是立刻能返回结果,它返回的也是 Promise 对象,也要配合 await 取结果
异步方式
第一个 then 是在响应返回后,才会调用它里面的箭头函数,箭头函数参数即 resp 响应对象
第二个 then 是在 json 解析完成后,才会调用它里面的箭头函数,箭头函数参数即解析结果(本例是 array 数组)
上一个 then 返回的是 Promise 对象时,才能链式调用下一个 then
如图:
通俗的讲,浏览器中的html是来自我们的前端服务器,localhost7070的,但我们获取数据的请求是发送到localhost8080,也就是我们的后端服务器,这里没有通过浏览器的同源检测,所以后端响应的数据无法读取到。
同源判断有以下规则:
只要协议、主机、端口之一不同,就不同源,例如
http://localhost:7070/a 和 https://localhost:7070/b 就不同源
同源检查是浏览器的行为,而且只针对 fetch、xhr 请求
如果是其它客户端,例如 java http client,postman,它们是不做同源检查的
通过表单提交、浏览器直接输入 url 地址这些方式发送的请求,也不会做同源检查
更多相关知识请参考
跨源资源共享(CORS) - HTTP | MDN (mozilla.org)
两种解决方式:
1. 请求响应头解决
fetch 请求跨域,会携带一个 Origin 头,代表【发请求的资源源自何处】,目标通过它就能辨别是否发生跨域
我们的例子中:student.html 发送 fetch 请求,告诉 tomcat,我源自 localhost:7070
目标资源通过返回 Access-Control-Allow-Origin 头,告诉浏览器【允许哪些源使用此响应】
我们的例子中:tomcat 返回 fetch 响应,告诉浏览器,这个响应允许源自 localhost:7070 的资源使用
具体怎么返回这个 Access-Control-Allow-Origin 头呢?90
很简单,我们只需要在后端服务请求的代码上方,加上一个注解@CrossOrigin("前端服务器地址")
这样服务器返回的响应头里就会有 Access-Control-Allow-Origin了
2. 通过代理的方式
第一步,需要先安装一个插件
npm install http-proxy-middleware --save-dev
在main.js中导入
import {createProxyMiddleware} from 'http-proxy-middleware'
设置代理地址:
app.use('/api', createProxyMiddleware({ target: 'http://localhost:8080', changeOrigin: true }));
main.js
import express from 'express'
import {createProxyMiddleware} from 'http-proxy-middleware'
const app = express()
app.use('/api', createProxyMiddleware({ target: 'http://localhost:8080', changeOrigin: true }));
app.use(express.static('./'))
app.listen(7070)
这样我们的api请求就代表我们localhost:8080接口了。
单个导出 const、let、function
export const a = 10;
export let b = 20;
export function c() {
console.log('c');
}
一起导出
const a = 10;
let b = 20;
function c() {
console.log('c')
}
export {a,b,c}
导出 default,只能有一个
export const a = 10;
export let b = 20;
export function c() {
console.log('c')
}
export default b;
import 语法
整个导入
import * as module from '/1.js'
console.log(module.a) // 输出10
console.log(module.b) // 输出20
module.c() // 输出c
单个导入
import {a,c} from '/1.js'
console.log(a) // 输出10
c() // 输出c
导入默认
import x from '/1.js'
console.log(x) // 输出20