1、变量和常量
使用let和const声明变量和常量
let声明变量可以被修改
const声明常量不可修改,建议大写命名,使用下划线_做分割(例如:const BASE_URL = 'http://xxx.xx.com')
PS:建议声明使用常量const,当发现需要修改时再改为变量let。
//1、变量和常量
{let count = 0;const BASE_URL = 'https://xxx.xxx.com';
}
块级作用域
let和const声明的变量,会生成块级作用域。
可以使用大括号{}对代码块进行分割,只有在当前作用域才可以访问变量。
避免一个变量名在多个地方使用,导致变量名冲突的情况。
如下所示:
{let count = 0;count++;
}
console.log(count);
上述代码以及运行结果可以看出,在大括号以外是无法访问块级作用域中声明的变量。
2、模板字符串
模板字符串使用反引号`对字符串进行拼接,解决了大量字符串拼接使用加号+比较麻烦的情况
在模板字符串中可以使用标记${}引入变量,比如引入name变量${name}
在模板字符串中换行生效,效果如下:
//2、模板字符串
const str1 = 'hello world!';
const str2 = `hello everyone! ${str1}
这是换行的内容`;
console.log(str2)
3、解构赋值
数组解构赋值
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
上述代码是最基本的解构赋值写法
对象解构赋值
const { username, age: userAge, ...otherInfo} = {username: '李华',age: 18,gender: 'Male',category: 'user'
}
console.log(username, userAge, otherInfo)
//李华 18 {gender: 'Male', category: 'user'}
上述代码我们对username进行解构赋值,获取到了名称,通过对解构变量重命名,把age取值重命名为userAge,通过使用...获取剩余对象属性(注意:要获取剩余属性值,必须把...放在解构赋值最后获取)
4、数组和对象的扩展
4.1 扩展运算符
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [100, ...arr1, ...arr2, 10, 20];
console.log(arr3); //[100, 1, 2, 3, 4, 5, 6, 10, 20]
上述代码,可以看出扩展运算符的作用是展开数组,展开后可以拼接在其他数组中。
const obj1 = {a: 1
}
const obj2 = {b: 2
}const obj3 = {name: '李华',...obj1,...obj2
}
console.log(obj3); //{name: '李华', a: 1, b: 2}
上述代码可以看出,对象拼接和数组拼接也是类似的。
当我们需要对对象的值进行重复使用,可以使用扩展运算符,把需要扩展的属性放在属性后。
注意:对象拼接不考虑顺序问题。
4.2 数组方法 Array.from()
该方法适用于,将伪数组转为真实数组。
伪数组
(1)伪数组是一个对象(Object)
(2)伪数组拥有length属性,可以获取长度。当我们改变伪数组长度时,length不会改变。
(3)伪数组可以通过下标获取对应的值
(4)伪数组无法使用数组的内置方法(如map、filter、push、forEach等),可以使用Object的方法,使用for in遍历
function fn(){Array.from(arguments).forEach(function (item){console.log(item);})
}
fn(1,2,3,4,5,6) //1 2 3 4 5 6
4.3 对象方法 Object.assign()
Object.assign()方法用于对象浅拷贝。基于旧对象生成新对象。
对象浅拷贝
const objA = {name: '李华',age: 18
}const objB = Object.assign({}, objA);
objB.name = 'a';
console.log(objA, objB); //{name: '李华', age: 18} {name: 'a', age: 18}
对象合并
const objA = {name: '李华',age: 18
}const objB = {gender: 'Male'
}const objC = Object.assign({}, objA, objB);
console.log(objA, objB, objC);
//{name: '李华', age: 18} {gender: 'Male'} {name: '李华', age: 18, gender: 'Male'}
上述代码可以看到,我们把objA和objB合并到同一对象中,并赋值给objC,生成一个合并对象。
4.4 class 类
ES5,使用构造函数生成对象
class类,可以帮助我们快速生成多个对象 ,ES6中class是ES5构造函数的语法糖
class A {constructor(name, age) {this.name = name;this.age = age;}introduce(){console.log(`我的名字是${this.name}, 我的年龄是${this.age}`)}
}
const a1 = new A("李华", 18);
console.log(a1); //A {name: '李华', age: 18}
a1.introduce(); //我的名字是李华, 我的年龄是18
上述代码我们创建了一个A类,并使用A创建了一个a1实例对象,从打印中我们可以看出我们初始化传参,生成了一个对象。使用这种方式,我们可以快速生成一系列,结构相同,但值不同的对象,并且可以调用对象的方法。
class继承
如果我们想创建一个对象,继承另一对象的属性,需要在constructor中使用super方法(super表示调用父类的constructor方法)
class B extends A { //constructor(name, age, gender) {super(name, age);this.gender = gender;}sayHello() {console.log(`你好,我是${this.name}`)}
}
const b1 = new B('小华', 19, '女');
console.log(b1); //B {name: '小华', age: 19, gender: '女'}
b1.sayHello(); //你好,我是小华
b1.introduce(); //我的名字是小华, 我的年龄是19
上述代码创建B对象,继承A对象的属性和方法,创建一个b1实例对象,打印b1发现成功继承A的属性和方法。
5、箭头函数
const getSum1 = n => n + 3;
console.log(getSum1(2)); //5const getSum2 = (n1, n2) => n1 + n2;
console.log(getSum2(3,5)); //8const getSum3 = (n1, n2, ...other) => console.log(n1, n2, other);
getSum3(5,6,7,8,9); //5 6 (3)[7, 8, 9]const getResult = arr => {let sum = 0;arr.forEach(item => sum += item);return sum;
}
console.log(getResult([1,2,3,4,5])) //15
上述代码中接收一个参数、接收两个参数,返回计算结果。接收不确定参数返回结果,以及数组求和等。箭头函数可以理解为匿名函数的简写。箭头函数有默认返回值。
6、异步
常见的异步:定时器 Ajax
异步任务会在当前同步任务执行完毕后触发。时间只决定了异步任务之间的执行顺序,不影响同步任务的执行。
Promise是为了解决回调函数嵌套问题(回调地域)
回调地狱(Callback Hell)是指在JavaScript编程中,由于多层嵌套的回调函数导致的代码结构复杂、难以阅读和维护的现象。这种现象常见于处理多个异步操作时,每个异步操作的结果都需要作为下一个操作的输入,导致回调函数层层嵌套,形成类似金字塔的结构。
Promise的状态
pending (默认状态)
fulfilled (成功状态)
rejected (失败状态)
(1)Promise对象标记了在Promise对象中执行异步任务的结果,并且(2)通过回调函数的方式把执行结果返回给Promise对象。后续我们可以(3)基于这个结果,使用类似于同步的书写方式,来进行复杂的异步处理。从而避免了大量的代码嵌套。
const p1 = new Promise((resolve, reject) => {// resolve('任务成功得到的数据');reject('失败的信息')
})p1.then(data => {console.log('成功:', data);
})
.catch(error => {console.log('失败:', error);
})
上述代码我们可以看到,当我们Promise的状态为fulfilled就会执行then里面的代码逻辑,当Promise的状态为rejected就会执行catch代码逻辑。
promise对象的状态只能修改一次,一旦修改就会状态凝固
可以使用Promise链式编程解决回调地域的问题,如果上一个then返回了Promise对象,可以交给下一个then处理。
<script>let promise = new Promise((resolve, reject) => {setTimeout(()=>{resolve('任务1成功输出')}, 1000)})promise.then((res)=>{console.log(res)return new Promise((resolve, reject) => {setTimeout(()=>{resolve('任务2成功输出')}, 2000)})}).then((res2) => {console.log(res2)return new Promise((resolve, reject) => {setTimeout(()=>{resolve('任务3成功输出')},3000)})}).then((res3)=>{console.log(res3)})</script>
上述代码,我们可以看到,基本上都是重复的调用,因此我们可以做一个优化。
<script>function promiseFun(text, time) {return new Promise((resolve, reject) => {setTimeout(()=>{resolve(text)}, time)})}promiseFun('任务1执行', 1000).then((res1)=>{console.log(res1)return promiseFun('任务2执行', 2000)}).then((res2)=>{console.log(res2)return promiseFun('任务3执行', 3000)}).then((res3)=>{console.log(res3)})</script>
我们知道Ajax的底层就是用Promise封装的,大家可以使用以上方式封装一个自己的Ajax。
then的第二个回调方法
如果多个Promise执行,且第一个Promise执行失败。这时候需要对Promise的错误结果做不同处理,则需要使用then的第二个回调函数(then的第一个回调函数用来处理成功的状态,第二个回调函数用来处理失败的情况)
<script>let promise = new Promise((resolve, reject) => {reject('失败了')})promise.then((res)=>{console.log('成功信息',res)return new Promise((resolve, reject) => {reject('任务二失败了')})}, err => {console.log('失败信息', err) //(1)失败信息 失败了throw new Error('任务2失败,不再往下执行') //(3)Error: 任务2失败,不再往下执行...}).then(res1 => {console.log('任务2成功信息', res1)}, err2 => {console.log('任务2失败信息-----\n', err2) //(2)任务2失败信息-----})</script>
Async await
async是用来进行异步处理的语法糖。
async是基于Promise的一种异步操作的简化功能。
注意:async不能完全替代Promise,因为async本身就是基于Promise的(在某种特定情况下才能使用async)
与async搭配使用的关键字是await
- await 后面一般会跟一个promise对象,await会阻塞async函数的执行,直到等到 promise成功的结果(resolve的结果)
- await 只会等待 promise 成功的结果,如果失败了会报错,需要 try catch
举个例子
<script>async function asyncFun() {try {let result = await axios.get('xxx.xxx')console.log(result) //axios成功的结果} catch (err) {console.log('获取失败,错误信息', err)}}asyncFun()</script>
7、proxy 代理
如果我们想监视对象属性的操作,可以使用proxy代理的方式做处理。
即当我们更数据的时候可以同步修改页面上的展示。(数据驱动视图更新)(数据驱动原理)
这里对proxy做一个基础案例,更多方法,大家可以参考网站学习:Proxy() 构造函数 - JavaScript | MDN
<div id="container">你好</div><script>const obj = {name: '李华', age: 18};const container = document.getElementById("container");container.textContent = obj.name;/*** 第一个参数:谁的代理* 第二个参数:配置项** get参数:target数据对象(哪个属性被改)* property属性名* receiver是proxy实例,可以使用proxy的方法** set参数中,value是我们修改后的值,通过obj[property] = value,* 把对代理的操作反馈给原始数据对象。* 对代理对象的操作,不仅改变了原始数据本身,还会执行在set中的额外功能(更改页面内容)*/const p1 = new Proxy(obj, {get (target, property) {return obj[property]},set(target, property, value) {obj[property] = valuecontainer.textContent = obj.name}})console.log(p1.name) // 李华(从输出可以看到,我们已经成功获取到了对象的值)p1.age = 21p1.name = '小华'console.log(obj) //{name: '小华', age: 21} (从结果可以看到,我们操作代理对象会修改原始对象)</script>
8、Module 模块
ESModule
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><script src="index.js" type="module"></script>
</body>
</html>
注意:当我们要把index当做模块使用必须加上type="module",否则会报错,无法在js中引入模块。
下面是ESM的代码组织操作
index.js
// ESM
import moduleA from './a.js';
import moduleB from './b.js';
import { aTitle, aFn } from './a.js'
import { bTitle, bFn } from './b.js'console.log(moduleA);
console.log(moduleB);
console.log(aTitle, aFn, bTitle, bFn);
a.js
export const aTitle = 'a模块的标题'
export function aFn () {console.log('a模块的方法')
}export default {name: 'a模块'
}
b.js
export const bTitle = 'b模块的标题'
export function bFn () {console.log('b模块的方法')
}export default {name: 'b模块'
}
浏览器运行结果:
CommonJS
c.js
方式一:
module.exports = {a: 1,b: 2,c: 3
}
方式二:
exports.aa = 'aa'
exports.bb = 'bb'//上面方法等价于 const exports = module.exports
index.js
//CommonJS
const moduleC = require('./c');
console.log(moduleC);
node运行结果:
以上我们可以看到两种用法的差异。
ESModule是在浏览器中运行的,CommonJS是在node中运行的。