1.ES6是什么
ES6,全称是ECMAScript 6,是JavaScript语言的下一代标准,由ECMA国际组织在2015年6月正式发布。ES6也被称作ECMAScript 2015,从这个版本开始,ECMA组织决定每年发布一个新的ECMAScript版本,以使JavaScript语言能够持续发展和进化。
ES6引入了许多新特性,这些特性旨在提高JavaScript语言的编程效率、代码的可读性和可维护性。以下是一些ES6中的重要特性:
-
箭头函数(Arrow functions):提供了一种更简洁的函数书写方式。
const sum = (a, b) => a + b;
-
类(Classes):引入了类的概念,尽管JavaScript仍然是基于原型的,但类的语法更接近传统的面向对象语言。
class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a noise.`); } }
-
模块化 (Modules):通过
import
和export
关键字,支持模块的导入和导出,使得代码组织更加清晰。// 导出 export const myFunction = () => {}; // 导入 import { myFunction } from './myModule';
-
模板字符串(Template literals):允许使用反引号(`)创建字符串,并提供字符串插值功能。
const message = `Hello, ${name}!`;
-
解构赋值(Destructuring assignment):允许从数组或对象中提取数据,并赋值给变量。
const [a, b] = [1, 2]; const { x, y } = { x: 1, y: 2 };
-
let和const :引入了
let
和const
关键字用于声明变量,解决了var
的一些问题,如变量提升和作用域问题。 -
Promise:用于更优雅地处理异步操作,替代了传统的回调函数模式。
-
默认参数(Default parameters):允许在函数定义时为参数设置默认值。
-
展开运算符(Spread operator):允许在函数调用或数组字面量中展开数组或对象。
-
Set和Map:引入了新的数据结构Set和Map,提供了更丰富的集合操作。
ES6的这些新特性极大地推动了JavaScript的发展,使得这门语言更适合大型应用的开发,并且更加现代化和高效。随着现代浏览器的支持,ES6已经成为现代前端开发的标配。
ES与JavaScript的关系
2.let和const的简介
在JavaScript中,let、var 和 const 都是用来声明变量的关键字,但它们之间有几个关键的区别:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>let、const 与 var 的区别</title>
</head>
<body>
<script>
// 1.重复声明
// 已经存在的变量或常量,又声明了一遍
// var 允许重复声明,let、const 不允许
// let a = 1;
// // ...
// let a = 2;
// console.log(a);
// function func(a) {
// let a = 1;
// }
// func();
// 2.变量提升
// var 会提升变量的声明到当前作用域的顶部
// console.log(a);
// console.log(a);
// var a = 1;
// 相当于
// var a;
// console.log(a);
// a = 1;
// console.log(a);
// let、const 不存在变量提升
// console.log(a);
// let a = 1;
// 养成良好的编程习惯,对于所有的变量或常量,做到先声明,后使用
// 3.暂时性死区
// 只要作用域内存在 let、const,它们所声明的变量或常量就自动"绑定"这个区域,不再受到外部作用域的影响
// let、const 存在暂时性死区
// let a = 2;
// let b = 1;
// function func() {
// console.log(b);
// // console.log(a);
// // let a = 1;
// }
// func();
// 养成良好的编程习惯,对于所有的变量或常量,做到先声明,后使用
// 4.window 对象的属性和方法
// 全局作用域中,var 声明的变量,通过 function 声明的函数,会自动变成 window 对象的属性或方法
// let、const 不会
// var/function
// var age = 18;
// function add() {}
// console.log(window.age);
// console.log(window.add === add);
// let/const
// let age = 18;
// const add = function () {};
// console.log(window.age);
// console.log(window.add === add);
// 5.块级作用域
</script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>let、const 与 var 的区别</title>
</head>
<body>
<script>
// 块级作用域
// 1.什么是块级作用域
// var 没有块级作用域
// for (var i = 0; i < 3; i++) {
// // console.log(i);
// }
// console.log(i);
// let/const 有块级作用域
// for (let i = 0; i < 3; i++) {
// // i = i+1
// // console.log(i);
// }
// console.log(i);
// 2.作用域链
// function func() {
// for (let i = 0; i < 3; i++) {
// // console.log(i);
// }
// }
// func();
// console.log(i);
// 作用域链:内层作用域->外层作用域->...->全局作用域
// 3.有哪些块级作用域
// {}
// {
// let age = 18;
// // console.log(age);
// }
// console.log(age);
// {}
// for(){}
// while(){}
// do{}while()
// if(){}
// switch(){}
// function(){}
// const person = {
// getAge: function () {}
// };
</script>
</body>
</html>
1.作用域(Scope):
var 声明的变量拥有函数作用域(function scope),这意味着如果 var 变量在函数外部声明,它将是一个全局变量;如果在函数内部声明,它只能在那个函数内部被访问。
let 和 const 声明的变量拥有块作用域(block scope),这意味着它们的作用域限定在它们被声明的块(如一个花括号 {} 内部的区域)中。
2.变量提升(Hoisting):
var 声明的变量会被提升到其作用域的顶部,但在初始化之前不能使用,访问未初始化的变量会得到 undefined。
let 和 const 也会被提升,但是它们不允许在声明之前被访问,如果尝试这样做将会导致一个引用错误(ReferenceError)。
3.重复声明(Re-declaration):
在同一个作用域内,var 允许重复声明同一个变量。
let 和 const 不允许在同一个作用域内重复声明同一个变量。
4.重新赋值(Re-assignment):
使用 var 和 let 声明的变量可以被重新赋值。
使用 const 声明的变量必须在声明时初始化,并且一旦被赋值,其引用就不能再被改变。需要注意的是,const 保证的是变量引用的不可变性,而不是变量的值不可变。例如,如果 const 变量引用的是一个对象,那么对象的属性是可以被修改的。
5. window 对象的属性和方法
全局作用域中,var 声明的变量,通过 function 声明的函数,会自动变成 window 对象的属性或方法。let、const 不会
// 2.2.const 声明的常量,允许在不重新赋值的情况下修改它的值
// 基本数据类型
// const sex = 'male';
// sex = 'female'; //会报错
// 引用数据类型
// const person = { username: 'Alex' };
// // person = {};
// person.username = 'ZhangSan'; //通过.直接修改值,而不是复制操作,是可以的。
// console.log(person);
不知道用什么的时候先用const,也就是说不确定需求的时候用const,因为如果需要修改会报错,方便查找。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>const</title>
</head>
<body>
<script>
// 1.为什么需要 const
// let
// let sex = 'male';
// // ...
// sex = 'female';
// console.log(sex);
// const
// const sex = 'male';
// // ...
// sex = 'female';
// console.log(sex);
// const 就是为了那些一旦初始化就不希望重新赋值的情况设计的
// 2.const 的注意事项
// 2.1.使用 const 声明常量,一旦声明,就必须立即初始化,不能留到以后赋值
// const sex;
// sex='male'
// const sex = 'male';
// 2.2.const 声明的常量,允许在不重新赋值的情况下修改它的值
// 基本数据类型
// const sex = 'male';
// sex = 'female';
// 引用数据类型
// const person = { username: 'Alex' };
// // person = {};
// person.username = 'ZhangSan';
// console.log(person);
// 3.什么时候用 const,什么时候用 let
// var
// for (let i = 0; i < 3; i++) {}
// const username = 'Alex';
// // ...
// username = 'ZhangSan';
</script>
</body>
</html>
以下是这些关键字的简单比较:
var 是ES5及之前版本中的标准声明方式,现在一般不推荐使用,因为它的作用域和提升行为可能会导致代码中的意外行为。
let 是ES6(ECMAScript 2015)中引入的,用于声明块作用域的变量,通常在需要重新赋值的情况下使用。
const 也是ES6中引入的,用于声明块作用域的常量,当你不希望变量的引用改变时使用。
总结来说,现代JavaScript编程中推荐尽可能使用 const,只在变量需要被重新赋值时使用 let。这样做可以提高代码的可读性和可维护性。
3.模版字符串
(1)是什么
模板字符串(Template Literals)是ES6(ECMAScript 2015)中引入的一种新的字符串表示法,它允许开发者以更简单、更直观的方式创建和维护字符串。模板字符串使用反引号(`)而不是单引号(')或双引号(")来定义字符串。
以下是模板字符串的一些主要特点和用法:
-
多行字符串:模板字符串可以跨越多行,不需要使用反斜杠(\)来换行。
const multiLineString = `This is a string that spans multiple lines.`;
-
字符串插值 :可以在模板字符串中嵌入变量和表达式,这些变量和表达式会被解析并转换为字符串的一部分。插入变量或表达式时,需要使用
${expression}
的语法。const name = 'Alice'; const age = 30; const greeting = `Hello, my name is ${name} and I am${age} years old.`;
-
标签模板(Tagged Templates):模板字符串可以与一个函数一起使用,这种用法称为标签模板。函数的第一个参数是一个字符串数组,其余的参数对应于模板字符串中的插值表达式。
function myTag(strings, ...values) { // strings 是一个包含模板字符串中静态部分的数组 // values 是一个包含模板字符串中动态部分(即插值表达式)的数组 return strings.reduce((acc, str, i) => { return acc + str + (values[i] || ''); }, ''); } const result = myTag`Hello ${name}, how are you?`;
-
原始字符串 :模板字符串可以创建原始字符串,即字符串中的转义序列不会被特殊处理。这可以通过在模板字符串前加上
String.raw
来实现。const rawString = String.raw`This is a raw string: \n and \t are not special characters.`;
模板字符串因其灵活性和易读性,在现代JavaScript开发中被广泛使用。它们提供了一种简洁的方式来构建包含变量和表达式的大型字符串,而无需使用字符串连接或格式化函数。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>模板字符串是什么</title>
</head>
<body>
<script>
// 1.认识模板字符串
// const username1 = 'alex';
// // "alex"
// const username2 = `alex`;
// console.log(username1, username2, username1 === username2);
// 2.模板字符串与一般字符串的区别
const person = {
username: 'Alex',
age: 18,
sex: 'male'
};
// const info =
// '我的名字是:' +
// person.username +
// ', 性别:' +
// person.sex +
// ', 今年' +
// person.age +
// '岁了';
// console.log(info);
// const info = `我的名字是:${person.username}, 性别:${person.sex}, 今年${person.age}岁了`;
// console.log(info);
// 和其他东西一起使用的时候,使用模板字符串,方便注入
// 其他情况下使用模板字符串或一般字符串都行
</script>
</body>
</html>
(2)模版字符串的注意事项
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>模板字符串的注意事项</title>
</head>
<body>
<script>
// 1.输出多行字符串
// 一般字符串
// const info = '第1行\n第2行';
// console.log(info);
// 模板字符串
// const info = `第1行\n第2行`;
// const info = `第1行
// 第2行`;
// console.log(info);
// 模板字符串中,所有的空格、换行或缩进都会被保留在输出之中
// 2.输出 ` 和 \ 等特殊字符
// const info = `'\`\\`;
// console.log(info);
// 3.模板字符串的注入
// ${}
// const username = 'alex';
// const person = { age: 18, sex: 'male' };
// const getSex = function (sex) {
// return sex === 'male' ? '男' : '女';
// };
// const info = `${username}, ${person.age + 2}, ${getSex(person.sex)}`;
// console.log(info);
// 只要最终可以得出一个值的就可以通过 ${} 注入到模板字符串中
</script>
</body>
</html>
(3) 应用
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>模板字符串的应用</title>
<style>
body {
padding: 50px 0 0 300px;
font-size: 22px;
}
ul {
padding: 0;
}
p {
margin-bottom: 10px;
}
</style>
</head>
<body>
<p>学生信息表</p>
<ul id="list">
<li style="list-style: none;">信息加载中......</li>
</ul>
<script>
// 数据
const students = [
{
username: 'Alex',
age: 18,
sex: 'male'
},
{
username: 'ZhangSan',
age: 28,
sex: 'male'
},
{
username: 'LiSi',
age: 20,
sex: 'female'
}
];
const list = document.getElementById('list');
let html = '';
for (let i = 0; i < students.length; i++) {
html += `<li>我的名字是:${students[i].username},${students[i].sex},${students[i].age}</li>`;
}
// console.log(html);
list.innerHTML = html;
</script>
</body>
</html>
4.箭头函数
(1)是什么
html
<script>
// 1.认识箭头函数
// const add = (x, y) => {
// return x + y;
// };
// console.log(add(1, 1));
// 2.箭头函数的结构
// const/let 函数名 = 参数 => 函数体
// 3.如何将一般函数改写成箭头函数
// 声明形式
// function add() {}
// 声明形式->函数表达式形式
// const add = function () {};
// 函数表达式形式->箭头函数
const add = () => {};
</script>
(2)注意事项
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>箭头函数的注意事项</title>
</head>
<body>
<script>
// 1.单个参数
// 单个参数可以省略圆括号
// const add = x => {
// return x + 1;
// };
// console.log(add(1));
// 无参数或多个参数不能省略圆括号
// const add = () => {
// return 1 + 1;
// };
// const add = (x, y) => {
// return x + y;
// };
// console.log(add(1, 1));
// 2.单行函数体
// 单行函数体可以同时省略 {} 和 return
// const add = (x, y) => {
// return x + y;
// };
// const add = (x, y) => x + y;
// console.log(add(1, 1));
// 多行函数体不能再化简了
// const add = (x, y) => {
// const sum = x + y;
// return sum;
// };
// 3.单行对象
// const add = (x, y) => {
// return {
// value: x + y
// };
// };
// const add = (x, y) => ({
// value: x + y
// });
// 如果箭头函数返回单行对象,可以在 {} 外面加上 (),让浏览器不再认为那是函数体的花括号
// const add = (x, y) => [x, y];
// console.log(add(1, 1));
</script>
</body>
</html>
(3)非箭头函数中this指向
在JavaScript中,this
关键字的行为取决于函数的调用方式。在非箭头函数(即传统的函数表达式或函数声明)中,this
的指向通常不是在函数定义时确定的,而是在函数被调用时确定的。以下是几种常见的函数调用场景及其对应的this
指向:
-
作为对象的方法调用 : 当一个函数作为对象的方法被调用时,
this
指向该对象。const obj = { method: function() { return this; // 这里的this指向obj对象 } }; obj.method(); // 返回obj对象
-
独立函数调用 : 当函数不是作为对象的方法调用时(即独立调用),在非严格模式下,
this
指向全局对象(在浏览器中通常是window
对象),而在严格模式下,this
是undefined
。function func() { return this; // 非严格模式:指向全局对象,严格模式:undefined } func(); // 非严格模式返回全局对象,严格模式抛出TypeError
-
构造函数调用 : 当使用
new
关键字调用一个函数时,this
指向新创建的对象。function Constructor() { this.prop = 'value'; // 这里的this指向新创建的对象 } const instance = new Constructor(); instance.prop; // 'value'
-
使用
call
、apply
或bind
方法调用 :Function.prototype.call
、Function.prototype.apply
和Function.prototype.bind
方法可以显式地设置函数调用时this
的值。function func() { return this; } const context = { value: 'custom context' }; func.call(context); // 返回{ value: 'custom context' } func.apply(context); // 同上 const boundFunc = func.bind(context); boundFunc(); // 同上
-
作为DOM事件处理函数 : 当函数作为DOM事件处理程序被调用时,
this
通常指向触发事件的元素。document.getElementById('button').addEventListener('click', function() { console.log(this); // 指向button元素 });
理解this
的工作原理对于编写JavaScript代码至关重要,因为它经常会导致混淆和错误。记住,this
的值是在函数被调用时确定的,而不是在函数定义时。
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>非箭头函数中的 this 指向</title>
</head>
<body>
<script>
// 1.全局作用域中的 this 指向
// console.log(this); // window
// 2.一般函数(非箭头函数)中的 this 指向
// 'use strict';
function add() {
console.log(this);
}
// 严格模式就指向 undefined
// add(); // undefined->window(非严格模式下)
// window.add();
// const calc = {
// add: add
// };
// // calc.add(); // calc
// const adder = calc.add;
// adder(); // undefined->window(非严格模式下)
// document.onclick = function () {
// console.log(this);
// };
// document.onclick();
// function Person(username) {
// this.username = username;
// console.log(this);
// }
// const p = new Person('Alex');
// 只有在函数调用的时候 this 指向才确定,不调用的时候,不知道指向谁
// this 指向和函数在哪儿调用没关系,只和谁在调用有关
// 没有具体调用对象的话,this 指向 undefined,在非严格模式下,转向 window
</script>
</body>
</html>
(4)箭头函数中this指向
箭头函数(Arrow Functions)在JavaScript中的this
绑定规则与传统的函数表达式或函数声明不同。箭头函数不绑定自己的this
,而是继承其所在上下文的this
值。这意味着箭头函数中的this
值由其外围最近一层非箭头函数决定。
以下是箭头函数中this
指向的一些关键点:
-
继承上下文的
this
: 箭头函数不定义自己的this
值,它会捕获其所在上下文的this
值,即定义时的词法作用域中的this
。const obj = { method: function() { setTimeout(() => { console.log(this); // 这里的this指向obj对象 }, 1000); } }; obj.method(); // 打印obj对象
-
this
不会随调用方式改变 : 由于箭头函数不绑定自己的this
,所以即使使用call
、apply
或bind
方法,也无法改变箭头函数中的this
值。const arrowFunc = () => this; const context = { value: 'custom context' }; arrowFunc.call(context); // 这里的this不会改变,仍然指向定义时的上下文
-
没有自己的
arguments
对象 : 箭头函数没有自己的arguments
对象,但是可以访问外围函数的arguments
对象。const arrowFunc = () => arguments[0]; function outerFunc() { return arrowFunc(5); // 这里的arguments是outerFunc的 } outerFunc(10); // 返回10,因为箭头函数使用的是outerFunc的arguments
-
不能用作构造函数 : 由于箭头函数没有自己的
this
,因此它们不能用作构造函数,尝试使用new
关键字会抛出错误。const ArrowFunc = () => {}; const instance = new ArrowFunc(); // 抛出TypeError
-
没有
prototype
属性 : 箭头函数没有prototype
属性,因此也不能使用new.target
来检测函数是否被用作构造函数。
理解箭头函数的this
绑定规则对于避免常见的JavaScript错误非常有帮助,尤其是在处理异步代码和回调函数时。由于箭头函数的this
值是固定的,它们在处理事件处理器和定时器时特别有用,因为这些情况下传统函数的this
值可能会意外地改变。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>箭头函数中的 this 指向</title>
</head>
<body>
<script>
// 1.箭头函数中的 this 指向
// 箭头函数没有自己的 this
// const calc = {
// add: () => {
// console.log(this);
// }
// };
// calc.add(); // window
// 2.练习
// 'use strict';
const calc = {
add: function () {
// this
const adder = () => {
console.log(this);
};
adder();
}
};
// calc.add(); // calc
const addFn = calc.add;
addFn(); // undefined->window
</script>
</body>
</html>
(5)不适用箭头函数的场景
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>不适用箭头函数的场景</title>
</head>
<body>
<script>
// 1.作为构造函数
// 箭头函数没有 this
// const Person = () => {};
// new Person();
// 2.需要 this 指向调用对象的时候
// document.onclick = function () {
// console.log(this);
// };
// document.addEventListener(
// 'click',
// () => {
// console.log(this); //window
// },
// false
// );
// 3.需要使用 arguments 的时候
// 箭头函数中没有 arguments
// function add() {
// console.log(arguments);
// }
// add(1, 2,3,4,5);
// const add = () => console.log(arguments);
// add();
// 剩余参数
</script>
</body>
</html>
(6)箭头函数的应用
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>箭头函数的应用</title>
<style>
body {
padding: 50px 0 0 250px;
font-size: 30px;
}
#btn {
width: 100px;
height: 100px;
margin-right: 20px;
font-size: 30px;
cursor: pointer;
}
</style>
</head>
<body>
<button id="btn">开始</button>
<span id="result">0</span>
<script>
const btn = document.getElementById('btn');
const result = document.getElementById('result');
// const timer = {
// time: 0,
// start: function () {
// // this
// var that = this;
// // var self = this;
// btn.addEventListener(
// 'click',
// function () {
// setInterval(function () {
// console.log(this);
// // this.time++;
// // result.innerHTML = this.time;
// that.time++;
// result.innerHTML = that.time;
// }, 1000);
// },
// false
// );
// }
// };
const timer = {
time: 0,
start: function () {
// this
btn.addEventListener(
'click',
() => {
// this
setInterval(() => {
console.log(this);
this.time++;
result.innerHTML = this.time;
}, 1000);
},
false
);
}
};
timer.start();
</script>
</body>
</html>
5.解构赋值
在JavaScript中,解构赋值是一种特殊的语法,它允许你将数组或对象中的值快速地赋给不同的变量。这种语法简洁且易于理解,常用于提取函数返回的多个值、交换变量值等场景。
数组解构赋值
数组解构赋值允许你使用模式匹配的方式,将数组中的值一一对应地赋给变量。
let [a, b, c] = [1, 2, 3];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
默认值
你还可以为变量设置默认值,以防数组中对应的值不存在或为undefined
。
let [x, y = 5] = [1];
console.log(x); // 1
console.log(y); // 5
交换变量
解构赋值还可以用来交换两个变量的值,而无需使用第三个变量。
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1
忽略某些值
你可以通过逗号来忽略某些值。
let [,,third] = ["foo", "bar", "baz"];
console.log(third); // "baz"
对象解构赋值
对象解构赋值允许你使用对象的属性名来提取值,并将其赋给变量。
let { name, age } = { name: "Alice", age: 25 };
console.log(name); // "Alice"
console.log(age); // 25
变量名与属性名不同
如果你想要使用不同的变量名,可以在解构时指定:
let { name: myName, age: myAge } = { name: "Alice", age: 25 };
console.log(myName); // "Alice"
console.log(myAge); // 25
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>对象解构赋值的应用</title>
</head>
<body>
<script>
// 1.函数参数的解构赋值
// const logPersonInfo = user => console.log(user.username, user.age);
// const logPersonInfo = ({ age = 0, username = 'ZhangSan' }) =>
// console.log(username, age);
// // logPersonInfo({ username: 'alex', age: 18 });
// logPersonInfo({});
// // { age, username:username }={ username: 'alex', age: 18 }
// 2.复杂的嵌套
const obj = {
x: 1,
y: [2, 3, 4],
z: {
a: 5,
b: 6
}
};
// const { x, y, z } = obj;
// console.log(x, y, z);
const {
y,
y: [, yy],
z,
z: { b }
} = obj;
console.log(yy, y, z, b);//3 [2,3,4] {a:5,b:6} 6
// [, yy] = [2, 3, 4]
</script>
</body>
</html>
嵌套解构
解构也可以用于嵌套的对象和数组。
let {
name: outerName,
details: { innerName }
} = { name: "Alice", details: { innerName: " Wonderland" } };
console.log(outerName); // "Alice"
console.log(innerName); // " Wonderland"
用途
解构赋值在JavaScript中非常实用,它可以使代码更加简洁、清晰,尤其在处理函数返回值、处理JSON数据、遍历Map结构等场景中非常有用。
例如,处理函数返回的复杂对象:
function getPerson() {
return {
firstName: "John",
lastName: "Doe",
age: 30
};
}
let { firstName, lastName, age } = getPerson();
console.log(`${firstName}${lastName} is ${age} years old.`);
或者处理JSON数据:
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
解构赋值是ES6(ECMAScript 2015)引入的特性之一,极大地提高了JavaScript代码的编写效率和可读性。
6.对象字面量的增强与函数参数的默认值
(1)方括号语法
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>方括号语法</title>
</head>
<body>
<script>
// 1.方括号语法的用法
// const prop = 'age';
// const person = {};
// // person.prop = 18;
// person[prop] = 18;
// 方括号语法可以写在对象字面量中
// const person = {
// [prop]: 18
// };
// console.log(person);
// 2.方括号中可以放什么
// ${}
// [值或通过计算可以得到值的(表达式)]
// const prop = 'age';
// const func = () => 'age2';
// const person = {
// // [prop]: 18
// // [func()]: 18
// // ['sex']: 'male'
// ['s' + 'ex']: 'male'
// };
// console.log(person);
// 3.方括号语法和点语法的区别
// 点语法是方括号语法的特殊形式
const person = {};
// person.age 等价于 person['age']
// 属性名由数字、字母、下划线以及 $ 构成,并且数字还不能打头的时候可以使用点语法
// age18_$ √
// 18age ×
// 合法标识符可以用来作为变量或常量名
// 当你的属性或方法名是合法标识符时,可以使用点语法,其他情况下请使用方括号语法
</script>
</body>
</html>
(2)函数参数的默认值
html
<script>
// 1.认识函数参数的默认值
// 调用函数的时候传参了,就用传递的参数;如果没传参,就用默认值
// multiply(2, 1);
// multiply(2);
// 2.函数参数默认值的基本用法
// const multiply = (x, y) => {
// if (typeof y === 'undefined') {
// y = 1;
// }
// return x * y;
// };
const multiply = (x, y = 1) => x * y;
console.log(multiply(2));
</script>
html
<script>
// 1.默认值的生效条件
// 不传参数,或者明确的传递 undefined 作为参数,只有这两种情况下,默认值才会生效
// const multiply = (x, y = 1) => x * y;
// // console.log(multiply(2, 0));
// // console.log(multiply(2, null));
// console.log(multiply(2, undefined));
// console.log(multiply(2));
// 2.默认值表达式
// 如果默认值是表达式,默认值表达式是惰性求值的
// 3.设置默认值的小技巧
// 函数参数的默认值,最好从参数列表的右边开始设置
// const multiply = (x = 1, y) => x * y;
// console.log(multiply(undefined, 2));
const multiply = (x, y = 1) => x * y;
console.log(multiply(2));
</script>
html
<script>
// 1.接收很多参数的时候
// const logUser = (username = 'ZhangSan', age = 0, sex = 'male') =>
// console.log(username, age, sex);
// logUser('Alex', 18, 'male');
// logUser();
// 2.接收一个对象作为参数
// const logUser = options =>
// console.log(options.username, options.age, options.sex);
const logUser = ({ username = 'zhangsan', age = 0, sex = 'male' } = {}) =>
console.log(username, age, sex);
// logUser({
// username: 'alex',
// age: 18,
// sex: 'male'
// });
// logUser({ username: 'alex' });
// { username = 'zhangsan', age = 0, sex = 'male' } = { username: 'alex' }
// logUser({});
logUser();
// { username = 'zhangsan', age = 0, sex = 'male' } = {}
// { username = 'zhangsan', age = 0, sex = 'male' } = undefined
</script>
7.剩余参数和展开运算符
(1)剩余参数
在JavaScript中,剩余参数(Rest Parameters)是ES6(ECMAScript 2016)引入的一个特性,它允许你将一个不定数量的参数表示为一个数组。这在使用函数时特别有用,当你不确定函数将接收多少个参数时,或者当你想要处理一个参数序列时。
剩余参数的语法
剩余参数的语法是三个点(...),后面跟着一个标识符,这个标识符将代表一个数组,包含了传递给函数的所有剩余参数。
function func(a, b, ...args) {
// 'args' 是一个数组,包含了所有从第三个参数开始的额外参数
}
剩余参数的使用
下面是一个使用剩余参数的例子:
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 输出 6
console.log(sum(1, 2, 3, 4, 5)); // 输出 15
在这个例子中,sum
函数使用剩余参数来接收任意数量的数字参数,并使用 reduce
方法来计算它们的总和。
注意事项
-
剩余参数只能放在函数参数列表的最后。这是因为剩余参数会收集从它的位置开始直到函数结束的所有参数。
// 错误的用法
function wrongFunc(...args, last) {
// 这会抛出一个语法错误
} -
剩余参数和 arguments 对象的区别 。
arguments
对象包含了函数的所有参数,而剩余参数只包含那些没有在形参列表中明确指定的参数。function func(a, b, ...args) {
console.log(args); // 输出 [3, 4, 5]
console.log(arguments); // 输出 [1, 2, 3, 4, 5]
}func(1, 2, 3, 4, 5);
-
剩余参数是一个真正的数组 。这意味着你可以使用数组的方法,如
map
、filter
、reduce
等。function printNumbers(...numbers) {
numbers.forEach(number => console.log(number));
}printNumbers(1, 2, 3, 4, 5); // 分别打印 1 2 3 4 5
-
剩余参数的默认值。你可以给函数的参数设置默认值,但剩余参数本身不能有默认值。
function multiply(multiplier, ...theArgs) {
return theArgs.map(x => multiplier * x);
}let arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
剩余参数是JavaScript中处理不定数量参数的强大工具,它使得函数能够灵活地接收和处理参数,同时保持代码的简洁和可读性。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>剩余参数是什么</title>
</head>
<body>
<script>
// 1.认识剩余参数
// const add = (x, y, z, ...args) => {};
// 2.剩余参数的本质
const add = (x, y, ...args) => {
console.log(x, y, args);
};
// add();
// add(1);
// add(1, 2);
add(1, 2, 3, 4, 5);
// 剩余参数永远是个数组,即使没有值,也是空数组
// 3, 4, 5->[3, 4, 5]
</script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>剩余参数的注意事项</title>
</head>
<body>
<script>
// 1.箭头函数的剩余参数
// 箭头函数的参数部分即使只有一个剩余参数,也不能省略圆括号
// const add = (...args) => {};
// 2.使用剩余参数替代 arguments 获取实际参数
// const add = function () {
// console.log(arguments);
// };
// const add = (...args) => {
// console.log(args);
// };
// add(1, 2);
// 3.剩余参数的位置
// 剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错
const add = (x, y, ...args) => {
console.log(args);
};
</script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>剩余参数的应用</title>
</head>
<body>
<script>
// 1.完成 add 函数
// const add = (...args) => {
// let sum = 0;
// for (let i = 0; i < args.length; i++) {
// sum += args[i];
// }
// // reduce
// return sum;
// };
// // console.log(add());
// // console.log(add(1, 1));
// console.log(add(1, 2, 3));
// 2.与解构赋值结合使用
// 剩余参数不一定非要作为函数参数使用
// const [num, ...args] = [1, 2, 3, 4];
// 必须是最后一个
// const [...args,num] = [1, 2, 3, 4];
// console.log(num, args);
// const func = ([num, ...args]) => {};
// func([1, 2, 3]);
// const { x, y, ...z } = { a: 3, x: 1, y: 2, b: 4 };
// // 必须是最后一个
// // const { x, ...z, y } = { a: 3, x: 1, y: 2, b: 4 };
// console.log(x, y, z);
// const func = ({ x, y, ...z }) => {};
// func({ a: 3, x: 1, y: 2, b: 4 });
</script>
</body>
</html>
(2)展开运算符
展开运算符(Spread Operator),同样使用三个点(...)表示,是JavaScript中另一个非常有用的特性,它允许你将数组表达式或者字符串在语法层面展开。展开运算符和剩余参数看起来相同,但它们的使用场景和作用是不同的。
展开运算符的使用场景
1. 展开数组
你可以使用展开运算符将一个数组的所有元素展开成一个独立的参数序列。
let parts = ['shoulders', 'knees'];
let lyrics = ['head', ...parts, 'and', 'toes'];
console.log(lyrics); // 输出: ["head", "shoulders", "knees", "and", "toes"]
2. 展开字符串
展开运算符也可以用于字符串,它会将字符串分割成独立的字符。
let str = 'hello';
let chars = [...str];
console.log(chars); // 输出: ["h", "e", "l", "l", "o"]
3. 函数调用中使用展开运算符
你可以使用展开运算符来调用一个函数,将数组或字符串的元素作为参数传递给函数。
function sum(x, y, z) {
return x + y + z;
}
let numbers = [1, 2, 3];
console.log(sum(...numbers)); // 输出: 6
4. 构建新数组
你可以使用展开运算符来构建新数组,尤其是在数组的复制、合并等操作中。
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
// 复制数组
let arr1Copy = [...arr1];
// 合并数组
let combined = [...arr1, ...arr2];
console.log(arr1Copy); // 输出: [1, 2, 3]
console.log(combined); // 输出: [1, 2, 3, 4, 5, 6]
5. 展开对象
在ES2018(ECMAScript 2018)中,展开运算符也可以用于对象,不过它只能用于可枚举的属性。
let obj1 = { a: 1, b: 2 };
let obj2 = { ...obj1, c: 3 };
console.log(obj2); // 输出: { a: 1, b: 2, c: 3 }
注意事项
-
展开运算符不能用于展开一个对象中的所有属性到一个数组中。如果尝试这样做,会得到一个错误。
let obj = { a: 1, b: 2 };
let array = [...obj]; // 错误,展开运算符不能用于对象直接转为数组 -
展开运算符不能用于展开一个带有迭代器(Iterator)的对象。例如,不能直接展开一个Map或Set对象。
let map = new Map([['a', 1], ['b', 2]]);
let array = [...map]; // 错误,展开运算符不能用于Map对象 -
展开运算符不能用于展开一个未定义或null的值。如果尝试这样做,会得到一个错误。
let undefinedValue;
let array = [...undefinedValue]; // 错误,不能展开未定义的值let nullValue = null;
let array = [...nullValue]; // 错误,不能展开null
展开运算符是一个非常灵活的特性,它使得数组、字符串和对象的操作变得更加简单和直观。与剩余参数不同,展开运算符主要用于函数调用、数组构造和其他表达式上下文中,用于将数组或对象展开为一系列的值。
数组的展开运算符
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>数组展开运算符的基本用法</title>
</head>
<body>
<script>
// 1.认识展开运算符
// [3, 1, 2];
// Math.min
// console.log(Math.min([3, 1, 2]));
// console.log(Math.min(3, 1, 2));
// [3, 1, 2]->3, 1, 2
// 2.数组展开运算符的基本用法
// console.log(Math.min(...[3, 1, 2]));
// 相当于
console.log(Math.min(3, 1, 2));
</script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>区分剩余参数和展开运算符</title>
</head>
<body>
<script>
// 1.根本区别
// 展开运算符
// [3,1,2]->3,1,2
// 剩余参数
// 3,1,2->[3,1,2]
// 2.区分剩余参数和展开运算符
// 剩余参数
// const add = (...args) => {
// // console.log(args);
// // 展开运算符
// // console.log(...args);
// // console.log(...[1, 2, 3]);
// console.log(1, 2, 3);
// };
// add(1, 2, 3);
console.log([...[1, 2, 3], 4]);
// [1, 2, 3]->1,2,3
</script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>数组展开运算符的应用</title>
</head>
<body>
<p>1</p>
<p>2</p>
<p>3</p>
<script>
// 1.复制数组
// const a = [1, 2];
// // const b = a;
// // a[0] = 3;
// // console.log(b);
// const c = [...a];
// // const c = [1, 2];
// a[0] = 3;
// console.log(a);
// console.log(c);
// 2.合并数组
// const a = [1, 2];
// const b = [3];
// const c = [4, 5];
// // console.log([...a, ...b, ...c]);
// // console.log([...b, ...a, ...c]);
// console.log([1, ...b, 2, ...a, ...c, 3]);
// 3.字符串转为数组
// 字符串可以按照数组的形式展开
// console.log(...'alex');
// console.log('a', 'l', 'e', 'x');
// console.log([...'alex']);
// console.log('alex'.split(''));
// reverse
// 4.常见的类数组转化为数组
// arguments
// function func() {
// // console.log(arguments.push);
// console.log([...arguments]);
// }
// func(1, 2);
// NodeList
// console.log(document.querySelectorAll('p'));
// console.log([...document.querySelectorAll('p')].push);
</script>
</body>
</html>
对象的展开运算符
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>对象展开运算符的基本用法</title>
</head>
<body>
<script>
// 1.展开对象
// 对象不能直接展开,必须在 {} 中展开
// const apple = {
// color: '红色',
// shape: '球形',
// taste: '甜'
// };
// console.log(...apple);
// console.log([...apple]);
// 对象的展开:把属性罗列出来,用逗号分隔,放到一个 {} 中,构成新对象
// console.log({ ...apple });
// console.log({ ...apple } === apple);
// 2.合并对象
const apple = {
color: '红色',
shape: '球形',
taste: '甜'
};
const pen = {
color: '黑色',
shape: '圆柱形',
use: '写字'
};
// console.log({ ...pen });
// console.log({ ...apple, ...pen });
// 新对象拥有全部属性,相同属性,后者覆盖前者
// console.log({ ...pen, ...apple });
// 相当于
// console.log({
// use: '写字',
// color: '红色',
// shape: '球形',
// taste: '甜'
// });
// console.log({ pen, apple });
console.log({ ...pen, apple });
</script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>对象展开运算符的注意事项</title>
</head>
<body>
<script>
// 1.空对象的展开
// 如果展开一个空对象,则没有任何效果
// console.log({ ...{} });
// console.log({ ...{}, a: 1 });
// 2.非对象的展开
// 如果展开的不是对象,则会自动将其转为对象,再将其属性罗列出来
// console.log({ ...1 });
// console.log(new Object(1));
// console.log({ ...undefined });
// console.log({ ...null });
// console.log({ ...true });
// 如果展开运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象
// console.log({ ...'alex' });
// console.log([...'alex']);
// console.log(...'alex');
// console.log({ ...[1, 2, 3] });
// 3.对象中对象属性的展开
// 不会展开对象中的对象属性
const apple = {
feature: {
taste: '甜'
}
};
const pen = {
feature: {
color: '黑色',
shape: '圆柱形'
},
use: '写字'
};
// console.log({ ...apple });
// console.log({ ...apple, ...pen });
// 相当于
console.log({
feature: {
color: '黑色',
shape: '圆柱形'
},
use: '写字'
});
</script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>对象展开运算符的应用</title>
</head>
<body>
<script>
// 1.复制对象
// const a = { x: 1, y: 2 };
// // const b = a;
// const c = { ...a };
// console.log(c, c === a);
// 2.用户参数和默认参数
// add(1, 2);
// const logUser = ({
// username = 'ZhangSan',
// age = 0,
// sex = 'male'
// } = {}) => {
// console.log(username, age, sex);
// };
const logUser = userParam => {
const defaultParam = {
username: 'ZhangSan',
age: 0,
sex: 'male'
};
const param = { ...defaultParam, ...userParam };
// const param = { ...defaultParam, ...undefined };
console.log(param.username);
// const { username, age, sex } = { ...defaultParam, ...userParam };
// console.log(username, age, sex);
};
logUser();
</script>
</body>
</html>
(3)区别
剩余参数(Rest Parameters)和展开运算符(Spread Operator)在JavaScript中都是使用三个点(...)表示,但它们的使用场景和功能是不同的。以下是它们之间的主要区别:
1. 定义和用途
-
剩余参数:
- 定义:剩余参数允许我们将一个不定数量的参数表示为一个数组。
- 用途:主要用于函数定义中,将函数的参数收集到一个数组中。
-
展开运算符:
- 定义:展开运算符允许我们将一个数组展开为其中的各个元素。
- 用途:主要用于函数调用、数组构造、对象字面量等,将数组或对象的属性展开。
2. 语法位置
-
剩余参数:
- 出现在函数的参数列表中,通常在最后一个位置。
- 例子:
function myFunction(...args) { /* ... */ }
-
展开运算符:
- 出现在函数调用、数组构造或对象字面量中。
- 例子:
myFunction(...args);
或let newArray = [...oldArray, ...newElements];
3. 功能
-
剩余参数:
- 将多个参数合并成一个数组。
- 允许函数处理任意数量的参数。
- 例子:
function sum(...numbers) { return numbers.reduce((total, n) => total + n, 0); }
-
展开运算符:
- 将一个数组或对象的属性展开成独立的元素或属性。
- 可以用于数组复制、合并,或者将类数组对象转换为数组。
- 例子:
let parts = ['shoulders', 'knees']; let lyrics = ['head', ...parts, 'and', 'toes'];
4. 使用限制
-
剩余参数:
- 只能在函数定义的参数列表中使用。
- 只能有一个剩余参数,并且它必须是参数列表中的最后一个参数。
-
展开运算符:
- 可以在函数调用、数组构造、对象字面量等场景中使用。
- 可以多次使用,用于展开多个数组或对象。
5. 示例对比
// 剩余参数
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3)); // 输出: 6
// 展开运算符
let numbers = [1, 2, 3];
console.log(sum(...numbers)); // 输出: 6
在上面的示例中,sum
函数使用剩余参数来接收任意数量的参数,而调用 sum
函数时使用了展开运算符来将数组 numbers
的元素作为独立的参数传递给函数。
总结来说,剩余参数用于在函数内部收集参数,而展开运算符用于将数组或对象的元素展开为独立的值。尽管它们都使用三个点(...),但它们的作用和用途是不同的。
8.迭代器
在JavaScript中,迭代器(Iterators)是一种接口,它为各种不同的数据结构提供了一种统一的访问机制。迭代器是任何对象,它实现了next()
方法,该方法返回一个包含两个属性的对象:value
和done
。以下是迭代器的基本概念和用法:
迭代器协议
迭代器必须遵循迭代器协议,这意味着它必须提供一个next()
方法,该方法:
- 是一个无参数的或者可以接受一个参数的函数。
- 返回一个具有以下两个属性的对象:
value
:迭代器返回的下一个值。如果迭代器已经完成了迭代,则这个值是undefined
。done
:一个布尔值,表示迭代器是否完成了迭代过程。如果为true
,则value
属性会被忽略。
迭代器示例
下面是一个简单的迭代器示例:
function createIterator(items) {
let i = 0;
return {
next: function() {
let done = (i >= items.length);
let value = !done ? items[i++] : undefined;
return { done: done, value: value };
}
};
}
// 使用迭代器
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
在上面的例子中,createIterator
函数返回一个迭代器对象,它包含一个next
方法。每次调用next
方法,都会返回序列中的下一个值,直到没有更多值可以返回,此时done
属性为true
。
可迭代对象
为了使对象可迭代,它必须实现Symbol.iterator
属性,该属性是一个无参数的函数,返回一个迭代器。
let iterable = {
[Symbol.iterator]: function() {
let i = 0;
return {
next: function() {
if (i < 3) {
return { value: i++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
// 使用for...of循环迭代
for (let value of iterable) {
console.log(value);
}
// 输出:
// 0
// 1
// 2
在上面的例子中,iterable
对象是可迭代的,因为它实现了Symbol.iterator
属性。可以使用for...of
循环来迭代这个对象。
迭代器的优势
迭代器提供以下优势:
- 为不同的数据结构提供统一的遍历接口。
- 可以自定义迭代行为,而不仅仅是按照索引进行遍历。
- 支持惰性计算,即迭代器可以按需生成值,而不是一次性生成整个序列。
迭代器是现代JavaScript中许多其他语言特性的基础,例如生成器(Generators)和for...of
循环。通过使用迭代器,可以使代码更加模块化和灵活。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Iterator 是什么</title>
</head>
<body>
<script>
// 1.Iterator 的作用
// Iterator:遍历器(迭代器)
// for()
// [1,2].forEach
// new Set().forEach
// Iterator 也是用来遍历的
// 2.寻找 Iterator
// console.log(Iterator);
// console.log([1, 2][Symbol.iterator]());
// const it = [1, 2][Symbol.iterator]();
// console.log(it);
// 3.使用 Iterator
// const it = [1, 2][Symbol.iterator]();
// console.log(it.next()); // {value: 1, done: false}
// console.log(it.next()); // {value: 2, done: false}
// console.log(it.next()); // {value: undefined, done: true}
// console.log(it.next()); // {value: undefined, done: true}
// it:可遍历对象(可迭代对象)
// Symbol.iterator:可遍历对象的生成方法
// 4.什么是 Iterator
// Symbol.iterator(可遍历对象的生成方法) -> it(可遍历对象) -> it.next() -> it.next() -> ...(直到 done 为 true)
</script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Iterator 解惑</title>
</head>
<body>
<script>
// 1.为什么需要 Iterator 遍历器
// 遍历数组:for 循环和 forEach 方法
// 遍历对象:for in 循环
// Iterator 遍历器是一个统一的遍历方式
// console.log([][Symbol.iterator]());
// console.log({}[Symbol.iterator]);
// 2.如何更方便的使用 Iterator
// Symbol.iterator->it->next()
// 我们一般不会直接使用 Iterator 去遍历
// for..of
</script>
</body>
</html>