数据类型和变量
声明变量
- let:声明的变量可以多次赋值。
- const:常量,只能赋值一次,引用内容可以修改。
- var:在同一个作用域多次声明同一个变量不会报错,从第二个开始当作赋值操作。 var和let的区别
数据类型
基本数据类型
-
undefined:
- 访问数组不存在的元素,访问对象不存在的属性
- 使用没有初始化值的变量
-
null:null由程序员主动提供
-
String:
-
三种写法(单双反):" ' `
-
模板字符串拼接:拼接字符(即url)必须要用反引号
let name = ; // zhang li ... let age = ; // 18 20 ... let uri = `/test?name=${name}&age=${age}`;
-
-
number:浮点数
- 可以除零,正数除零Infinity(正无穷大),负数除零-Infinity(负无穷大)
- 浮点数都有运算精度问题
parseInt(“xxx”)字符串转number
-
bigint:数字结尾加n
-
boolean:
- Truthy:除Falsy外的值
- Falsy:false,Nulish(null,undefined),0,0n,NaN,空字符串。
对象类型
Function
语法
定义函数:
function 函数名(参数) {
// 函数体
return 结果;//可有可无
}
- 对实参的类型和个数都没有限制,没有用到的形参是undefined。
- 在函数中对形参赋值相当于是默认值。
匿名函数:
语法:
(function (参数) {
// 函数体
return 结果;
})
使用场景:
-
定义完毕后立刻调用
(function(a,b){ return a + b; })(1,2) -
作为对象的方法(类似Java的匿名内部类)
例如:
document.getElementById("p1").onclick = (function(){ console.log("鼠标单击了..."); });
箭头函数:
语法:
(参数) => {
// 函数体
return 结果;
}
规则和Java的Lambda表达式一致。
对象
js中函数的本质是对象,这点和java完全不一样。
-
函数可以参与赋值
-
有属性,有方法
-
可以作为方法参数(高阶函数)
-
可以作为方法返回值(高阶函数)
function c() { console.log("c"); function d() { console.log("d"); } return d; } c() //打印c。此时c()是d函数的对象 c()() //c d
作用域
函数可以嵌套。
- 最外面一层是全局作用域。
- 查找变量时,由内向外查找。
- 作用域本质上是函数对象的属性,可以通过 console.dir 来查看调试,查看时不会显示本身(即红色的)。
闭包
- 函数定义时,它的作用域已经确定好了,因此无论函数将来去了哪,都能从它的作用域中找到当时那些变量。
- 闭包:函数能够访问自己的作用域中变量
var与let的区别
如果函数外层引用的是 let 变量,那么外层普通的 {} 也会作为作用域边界,最外层的 let 也占一个 script 作用域。
普通的 {} :除了定义函数之外的 {}。
最外层(橙色)是全局作用域。
使用 var 变量,外层普通的 {} 不会视为边界,不便于区分同名变量。
var e = 10;
if(true) {
var e = 20;
console.log(e); //20
}
console.log(e); //20
Array
-
join
let arr = ['a','b','c']; arr.join(); // 默认使用【,】作为连接符,结果 'a,b,c' arr.join(''); // 结果 'abc' arr.join('-'); // 结果 'a-b-c' -
map
let arr = [1,2,3,6]; //要求变成[10,20,30,60] function a(i) { // 代表的新旧元素之间的变换规则 return i * 10 } arr.map(a); //把函数作为map的参数 arr.map( i => i * 10) //箭头函数 -
filter
let arr = [1,2,3,6]; //要求只剩下奇数,即过滤偶数 arr.filter( (i) => i % 2 == 1 ); // 结果 [1,3] -
forEach
let arr = [1,2,3,6]; //遍历 arr.forEach( (i) => console.log(i) );
map,filter,forEach都不会改变原数组。
回调函数:作为参数传入的函数。
Object
语法
let obj = {
属性名: 值,
方法名: 函数,
get 属性名() {},
set 属性名(新值) {}
}
例1
let stu1 = {
name: "小明",
age: 18,
study: function(){
console.log(this.name + "爱学习");
}
}
例2(用的最多)
let stu3 = {
name: "小白",
age: 18,
study(){ //对象函数这么写,仅限于对象内部
console.log(this.name + "爱学习");
}
}
get,set(和java差别很大,js是调用get和set方法的属性名)
let stu4 = {
_name: null, //约定私有属性,js没有私有属性的概念
get name() {
console.log("进入了get");
return this._name;
},
set name(name) {
console.log("进入了set");
this._name = name;
}
}
//调用
stu4.name = "小白"
console.log(stu4.name)
成员增删
js的对象属性和方法可以随时加减。
let stu = {name:'张三'};
stu.age = 18; // 添加属性
delete stu.age; // 删除属性
stu.study = function() { // 添加方法
console.log(this.name + "在学习");
}
this
在java中调用对象的非静态方法时,会传入一个隐性参数。
js中this是动态改变的,与函数运行时上下文相关。
通过call函数动态改变this,把 call 的第一个参数 stu 作为 this,因此可以把任何对象当成this。
function study(subject) {
console.log(this.name + "在学习 " + subject)
}
let stu = {name:"小黑"};
study.call(stu, "js"); // 输出 小黑在学习 js
在箭头函数内出现的 this,以外层 this 理解。
let stu = {
name: "小花",
friends: ["小白","小黑","小明"],
play() {
this.friends.forEach(e => {
console.log(this.name + "与" + e + "在玩耍");
})
}
}
stu.play()
this.name 所在的函数是箭头函数,因此 this 要看它外层的 play 函数,play 又是属于 stu 的方法,因此 this 代表 stu 对象
如果是匿名函数的话,this.name 所在的函数是 "落单" 函数,此时this代表window。
let stu = {
name: "小花",
friends: ["小白","小黑","小明"],
play() {
this.friends.forEach(function(e){
console.log(this.name + "与" + e + "在玩耍"); //this.name为""
});
}
}
stu.play()
原型继承
Java中继承是发生在子类和父类之间,对象之间没有继承。
js中继承是发生在对象之间。
对象的_proto_代表它的父对象,js术语:原型对象。
let son = Object.create(father); //创建father的子对象
基于函数的原型继承
通过Object.create(father)创建的对象只有父对象的成员,处于方便,js提供了基于函数的原型继承。
函数作用:相当于构造器,负责创建子对象,给子对象提供属性和方法。
函数有个特殊的属性prototype(区别上图的_proto_),他就是函数创建的对象的父对象。
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
- json 中只能有 null、true|false、数字、字符串(双引号)、json对象、数组。
- json 中的属性必须用双引号引起来。
动态类型
静态类型语言,如 Java,值有类型,变量也有类型、赋值给变量时,类型要相符。
而 js 属于动态类型语言,值有类型,但变量没有类型,赋值给变量时,没要求。
运算符
===
严格相等运算符,用作逻辑判等
||
在一些老旧代码中用作函数默认值。
function test(n) {
n = n || '男';
console.log(n);
}
值1 || 值2:如果 值1 是 Truthy,返回 值1,如果 值1 是 Falsy 则返回 值2。
??与?
值1 ?? 值2:值1不是nullish,返回值1,否则返回值2。
?:当某个属性是nulish时,直接返回undefined。
console.log(stu.address?.city) //若stu.address为nulish直接返回undefined,避免报错
...展开运算符
作用1:
打散数组,把元素传递给多个参数。
test(arr[0],arr[1],arr[2]); //传统写法
test(...arr);
打散理解为去除了数组外侧的 []
作用2:
复制数组或对象。
let arr1 = [1,2,3];
let arr2 = [...arr1]; // 复制数组
let obj1 = {name:'张三', age: 18};
let obj2 = {...obj1}; // 复制对象
展开运算符复制属于浅拷贝,只会复制一层,更深层次的是引用。
作用3:
合并数组或对象。
let a1 = [1,2];
let a2 = [3,4];
let b1 = [...a1,...a2]; // 结果 [1,2,3,4]
let o1 = {name:'张三'};
let o2 = {age:18};
let o3 = {name:'李四'};
let n2 = {...o3, ...o2, ...o1}; // 结果{name:'李四',age:18}
合并对象时,后面的属性会覆盖前面的。
[],{} 解构赋值
[] 数组:
//用在声明变量时
let arr = [1,2,3];
let [a, b, c] = arr; // 结果 a=1, b=2, c=3
//用在声明参数时
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)
和...的区别:...是在实参使用,[] {} 是在形参使用。
控制语句
for in
主要用来遍历对象。
let father = {name:'张三', age:18, study:function(){}};
for(const n in father) {
console.log(n, father[n]);
}
- const n 代表遍历出来的成员(属性和方法)名。
- 遍历子对象时,父对象的属性会跟着遍历出来。
- 在 for in 内获取属性值,要使用 [] 语法,而不能用 . 语法。
for of
主要用来遍历数组,也可以是其它可迭代对象,如 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);
}
常用API
查找元素
document.getElementById(): 根据 id 值查找一个元素。document.querySelector():根据选择器(.是class,#是id)查找第一个匹配元素。document.querySelectorAll():根据选择器查找所有匹配元素。
修改元素
innerHTML:会解析内容中的标签。
textContent:不会解析内容中的标签。
利用模板修改
<div class="tbody">
//把数据动态填入此处
</div>
<template id="tp"> //模板
<div class="row ">
<div class="col">xxx</div>
<div class="col">xxx</div>
<div class="col">xxx</div>
<div class="col">xxx</div>
</div>
</template>
<script>
let array = [
{id: 1, name: "张三", sex: '男', age: 18},
{id: 2, name: "李四", sex: '女', age: 22}
]
const tp = document.getElementById("tp"); //拿到tp模板
const row = tp.content; //拿到模板中所有div元素
const [c1,c2,c3,c4] = row.querySelectorAll(".col"); //从模板所有的div元素中找.col的元素
const tbody = document.querySelector(".tbody") //拿到一个父元素
for (const {id, name, sex, age} of array) {
c1.textContent = id;
c2.textContent = name;
c3.textContent = sex;
c4.textContent = age;
const newNode = document.importNode(row, true);//复制元素包括子元素
tbody.appendChild(newNode)//添加子元素
}
</script>
Fetch
Fetch API 可以用来获取远程数据,它有两种方式接收结果,同步方式与异步方式。
格式:
fetch(url, options) // 返回 Promise,并不是最终结果
同步方式:
const 结果 = await Promise
// 后续代码
- await 关键字必须在一个标记了 async 的 function 内来使用。
- 后续代码会等待结果返回再执行。
异步方式:
Promise
.then(结果 => { ... })
// 后续代码
- 后续代码不必等待结果返回就可以执行。
- then可以链式调用,需要结果是一个Promise对象。
跨域问题
- 只要协议、主机、端口号有一个不同,就不同源。
- 同源检查是浏览器的行为,其它客户端,例如postman,它们是不做同源检查的。而且只针对 fetch、xhr 请求。
解决1(后端服务器解决):
-
fetch 请求跨域,会携带一个 Origin 头,代表发请求的资源源自何处,目标通过它就能辨别是否发生跨域。
- 上图中,student.html 发送 fetch 请求给 tomcat,告诉tomcat我来自localhost:7070。
-
目标资源通过返回 Access-Control-Allow-Origin 头,告诉浏览器允许哪些源使用此响应。
- 上图中,tomcat 返回 fetch 响应,告诉浏览器,这个响应允许源自 localhost:7070 的资源使用。
解决2(前端服务器解决):
在前端服务器使用代理对象,浏览器访问的是前端服务器,前端服务器再访问后端服务器,就不会产生跨域问题。
//1.安装代理插件
npm install http-proxy-middleware --save-dev
//2.在express 服务器启动代码中加入
import {createProxyMiddleware} from 'http-proxy-middleware'
//3.创建代理对象,意思是访问7070/api的请求都通过代理对象。
app.use('/api', createProxyMiddleware({ target: 'http://localhost:8080', changeOrigin: true }));
导入导出
导出:
export const a = 10; //导出单个
export {a,b,c} //导出多个
export default b; //默认导出,一个js文件中只能有一个export default
导入:
<script type="module">
import 语句
</script>
import {a,c} from '相对路径' //导入单个或多个
import * as hello from '相对路径' //导入全部
import hello from '相对路径' //导入默认,名字随便起,因为默认就一个。