前言
几乎每次面试的时候都会被问到(😂😂)关于es6的问题,ES6,也称为ECMAScript 2015,是JavaScript语言的一个重要版本更新,它引入了大量新特性来增强语言的功能和开发者的编程体验。
let和const
在es5的时候是用var来声明变量的,var会带来变量声明提升的问题,
//var a//声明提升
console.log(a)//打印出来是undefined;
var a=2;
这种情况会在我们开发过程中或者大型项目中带来不少的问题,于是es6提供了let和const1.解决了变量声明提升的问题。
console.log(a);//报错
let a=10;
2.另外呢let还是一个块级作用域
if(1===1){
let b=10;
}
console.log(b);//外部作用域不能访问内部作用域
var a=2;
var a=4;
console.log(a);//4
var 声明的变量可以有覆盖性,如果我们程序写的越来越多,不小心在下面在下面又声明了一个同名的变量,那么会把上面的变量给覆盖掉会带来不少的问题。(在浏览器当中用var声明的全局变量会被添加到window对象上面去。)所以let有一个特性是不能重复声明。
let a=1;
let a=3;
console.log(a)//报错
值得注意的是const与let大差不差但是const是声明常量的一旦被赋值就不允许修改。那这种const声明带来的好处是什么呢?我们在用第三方框架的时候,很多第三方的人在写内部js文件的时候基本大部分变量都是const,这样从外部对他的内部变量进行修改的时候就不会允许修改。 如果const是声明了一个对象呢?能够修改吗?
const person={
name:'小fu'
}
person.name='alex'
console.log(person)//{name:"alex"}
const person={
name:'小fu'
}
person={
age:20
}
console.log(person);//会报错
let 对比于var解决了啥? 1.for循环经典例子
var arr=[];
//var i
for(var i=0;i<10;i++){
arr[i]=function(){
return i;
}
}
console.log(arr[5]())//打印为10;这是因为有var这个变量声明提升(提升到当前函数所在作用域),跳出当前块级作用域。再去执行的时候i就是10。如果改为let i=0就能正常了
2.let不会污染全局变量
let 关键字的一个重要特性是它具有块级作用域(block scope),这意味着 let 声明的变量只在声明它的代码块(即 {} 包围的区域)内有效。这与 var 不同,var 具有函数作用域或全局作用域,可能会导致全局污染。通过使用 let。
function exampleLet(){
let y = 20; }
exampleLet();
console.log(y); // 抛出 ReferenceError: y is not defined
建议:在默认情况下用const ,而只有在知道变量值需要被修改的情况下才使用let。
模板字符串
let str="我是"fu"";
console.log(str)//报错
此时如果我们let str="我是\"fu\""
console.log(str);
我是"fu"
转义字符固然好用,但es6引入了更方便的模板字符串。
let str=`我是
"fu"`;
console.log(str)//输出效果跟之前一样
模板字符串支持换行,可以通过模板字符串给dom元素增加html
let container=document.querySelector(".container");
let banana="香蕉";
let apple="苹果"
container.innerHTML=`
<ul>
<li>${banana}</li>
<li>${apple}</li>
<ul>`
由此可见模板字符串允许在字符串中嵌入表达式,使用 ${expression} 语法。表达式可以是变量、函数调用、算术运算等任何有效的 JavaScript 表达式。模板字符串可以直接包含多行文本,而不需要使用换行符 \n 或字符串连接符 +。这对于编写 HTML 片段、SQL 查询或其他多行文本非常有用。
板字符串常用于构建动态的 API 请求 URL 或请求体,特别是在使用 fetch 或其他 HTTP 客户端库时。
let userId = 123;
let apiUrl = `https://api.example.com/users/${userId}`;
fetch(apiUrl) .then(response => response.json()) .then(data => console.log(data));
模板字符串可以保留字符串中的转义字符,例如反斜杠 ``。你可以通过 String.raw 标签来获取原始字符串,这在处理正则表达式或其他需要大量转义字符的场景中非常有用。
let path = String.raw`C:\Users\Alice\Documents`;
console.log(path);
// 输出: C:\Users\Alice\Documents
let regex = String.raw`\d{3}-\d{2}-\d{4}`;
console.log(regex);
// 输出: \d{3}-\d{2}-\d{4}
tring.raw 是 ES6(ECMAScript 2015)引入的一个标签函数(tag function),它用于处理模板字符串,确保字符串中的所有转义字符都被保留为字面值。换句话说,String.raw 会返回一个“原始”版本的字符串,其中所有的反斜杠 `` 和其他特殊字符都不会被解释为转义字符。
箭头函数
箭头函数(Arrow Functions)是ES6(ECMAScript 2015)引入的一种新的函数定义方式。它提供了一种更简洁的语法来书写函数表达式,并且在某些情况下改变了this关键字的行为。
箭头函数使用=>符号来定义,可以大大简化函数的书写形式。
- 单个参数:如果函数只有一个参数,可以省略圆括号。
- 多个参数:如果有多个参数,需要使用圆括号将参数包裹起来。
- 无参数:如果函数没有参数,必须使用空的圆括号。
- 单行返回值:如果函数体只有一行代码并且是一个返回值,可以省略大括号和
return关键字。 - 多行函数体:如果函数体有多行代码或包含复杂的逻辑,则需要使用大括号,并显式地使用
return关键字。 关于箭头函数其实聊的最多的是this的绑定。 箭头函数当中并没有this的绑定。es5中this指向是取决于调用该函数的上下文对象。
//es5
let PageHandle={
id:123,
init:function(){
document.addEventListener('click',function(event){
//this.doSomething is not a function 我是给文档添加的事件//所以指向的是document
// console.log(this);
this.doSomeThing(event.type);
})
},
doSomeThings:function(type){
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandle.init();
//我们可以用es6的箭头函数来解决
let PageHandle={
id:123,
init:function(){
//箭头函数没有this指向,箭头函数内部的this值只能通过查找作用域链来确定,一旦使用箭头函数,当前就不存在作用域链。//原本是通过作用域链来访问调用该函数的对象,但是现在是箭头函数所有他的this就是所找到的上层作用域链的this
document.addEventListener('click',(event)=>{
this.doSomeThing(event.type);
})
},
doSomeThings:function(type){
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandle.init();
使用箭头函数的注意事项: 1.使用箭头函数,函数内部没有arguments(剩余参数)
let getVal=(a,b)=>{
console.log(this);
console.log(arguments);
return a+b;
}
console.log(getVal(1,3));
2.箭头函数不能使用new关键字来实例化对象。 function函数也是一个对象,但箭头函数不是一个对象,它是一个语法糖
解构赋值
解构赋值是对赋值运算符的一种扩展,它针对数组和对象来进行操作,优点是:代码书写上简洁易懂
let node={
type:'idea',
name:'foo'
}
在es5中:
//let type=node.type;
//let name=node.name;
在es6中
let {type,name}=node;//1.完全解构
console.log(type,name);//iden foo
let obj={
a:{
name:"张三"
},
b:[],
c:'hello,world'
}
//2.不完全解构 可以忽略
//let {a}=obj;
//console.log(a);//{name:"张三"}
//3.剩余运算符
let {a,...res}=obj;
console.log(res);//{b:Array(0),c:"hello,world"}
//4.默认值
let {a,b=30}={a:20};
//{
a:20,
b:30
}
对数组解构:
let arr=[1,2,3];
let [a,b]=arr;
console.log(a,b);//不完全解构
//可嵌套
let [a,[b],c]=[1,[2],3];
函数之默认值、剩余参数
//1.带参数默认值的函数
//es5的写法
function add(a,b){
a=a||10;
b=b||20;
return a+b;
}
console.log(add());//30
//es6
function add(a=10,b=20){
return a+b;
}
console.log(add());
//当我们只传入一个参数的时候
function add(a,b=20){
return a+b;
}
console.log(add(30));//50
//2.默认的表达式也可以是一个函数
function add(a,b=getVal(5)){
return a+b;
}
function getVal(val){
return val+5
}
console.log(add(10));//20
//es5的写法arguments是一个类数组对象。是函数调用时候传递的参数。
function pick(obj){
let result=Object.create(null);
for(let i=1;i<arguments.length;i++){
result[arguments[i]]=obj[arguments[i]]
}
return result;
}
let book={
title:'es6',
author:'fu',
year:2003
}
let bookData=pick(book,'author','year','author');
console.log(bookData);
//es6写法:剩余参数(由三个点...和一个紧跟着的具名参数指定的...keys)
function pick(obj,...keys){
// console.log(keys); 剩余参数解决了arguments问题
let result=Object.create(null);
for(let i=0;i<keys.length;i++){
result[keys[i]]=obj[keys[i]];
}
return result;
}
let book={
title:'es6',
author:'fu',
year:2003
}
let bookData=pick(book,'title','year','author');
console.log(bookData);
function checkArgs(...args){
console.log(args);//替代arguments
}
checkArgs('a','b','c')
说白了剩余参数解决了arguments的问题,用arguments不好进行遍历操作。arguments是一个伪数组,不好进行数组上的操作。
扩展运算符
//剩余运算符:把多个独立的合并到一个数组当中(函数形参...rest) //扩展运算符:将一个数组分割,并且将各个项作为分离的参数,传给函数
const maxNum=Math.max(20,30);
console.log(maxNum);//30
//处理数组中的最大值,使用apply
const arr=[10,20,50,30,90,100,40];
console.log(Math.max.apply(null,arr));//100
//es6 扩展运算更方便
console.log(Math.max(...arr));
//你可以使用扩展运算符将一个数组的内容复制到另一个数组中,或者将多个数组合并为一个新的数组。
类(ES6面向对象)
es5造类
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.sayName=function(){
return this.name
}
let p1=new Person('fu',18);
console.log(p1)
首先一点的是:ES6中关于面向的所有写法,本质都是语法糖。 新增了很多关键字: class关键字:用来定义类 extends关键字:用来继承
class Person{
//属性定义
//第一种,直接定义在类的内部
name="fu";
age="20";
//方法是直接定义在类中8
say(){
alert(this.name+"---"+this.age);
}
}
let p1=new Person();
alert(p1.name)
p1.age=30;
alert(p1.age)
p1.say();
class Person{
//实例化的时候会被立即调用constructor
constructor(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
}
say(){
alert(this.name+"---"+this.age);
}
}
let p2=new Person("张三",18,"男");
p2.say()
extends关键字:用来继承 static关键字:用来定义静态方法该方法属于类不属于对象。person.说明没定义在原型链上,定义在构造函数上。
类的继承
class Animal{
constructor(name,age){
this.name=name;
this.age=age;
}
sayName(){
return this.name;
}
sayAge(){
return this.age;
}
}
class Dog extends Animal{
constructor(name,age,color){
super(name,age);
this.color=color;
}
sayColor(){
return `${this.name}是${this.age}岁了,它的颜色是${this.color}`
}
//重写父类的方法
sayName(){
return this.name+super.sayAge+this.color;
}
}
let d1=new Dog('小黄',28,'red');
console.log(d1.sayName());
console.log(d1.sayColor());
es6模块化
在历史上js是没有模块化的,像后端语言都会支持模块化语言的发展,CommonJS是用于服务器的,AMD是用于浏览器的。es6在语言标准的层面上实现了模块功能,而且实现相当简单可以完全取代CommonJS和AMD。 ES6 module成为了浏览器和服务器通用的一种模块的解决方案。 es6的模块的功能主要由两个命令构成:export 和 import //export 用于规定模块的对外接口,import 是用于输入其它模块提供的功能
export const name="张三";//modules/index.js export const age=18; export function sayName(){ return 'my name is fu' } 说白了其实是export {name,age,sayName}
const obj={ foo:'foo' } export default obj;
import obj,{name,age,sayname} from './modules/index.js'
console.log(name,age,sayname);
一个模块可以抛出多个变量。
对象拓展功能
es6允许我们直接写入变量和函数作为对象的属性和方法
const name='fu'
const age=20;
//const person={
// name:name,
// age:age,
// sayName:function(){
// }
//}
//es6的简写方式
const person={
name,
age,
sayName(){
console.log(this.name);
}
}
person.sayName();
function fn(x,y){
return {x,y};
}
console.log(fn(10,20));
let cart={
whell:4,
set(newVal){
if(newVal){
if(newVal<this.wheel){
throw new Error('轮子数太少了')
}
},
get(){
return this.wheel;
}
}
cart.set(6);
cart.get()
const obj={};
obj.isShow=true;
const name='a';
obj[name+'bc']=123;
//console.log(obj);
obj['f'+'bc']=function(){
console.log(this);
}
const name='a';//有一些随机变化的变量就用b
const obj={
isShow:true,
[name+'bc']:123,
['f'+name](){
console.log(this);
}
}
console.log(obj);
//对象扩展的方法
is() ===
//比较两个值是否严格相等,既比较值又比较数据类型,===也可以但是不能判断NaN
console.log(NaN===NaN);FALSE;如果是用is()方法来比较的话那么就是相等的
console.log(Object.is(NaN,NaN));
//assign()
//对象的合并
//Object.assign(target,obj1,obj2...)
//返回合并之后的新对象
let newObj=Object.assign({},{a:1},{b:2});
console.log(newObj);
Symbol
//原始数据类型Symbol,它表示的是独一无二的值 //最大的用途:用来定义对象的私有变量
const name=Symbol('name');
const name2=Symbol('name');
console.log(name===name2);//false
let s1=Symbol('s1');
console.log(s1);
let obj={
[s1]:'fu'
};
obj[s1]='fu'
//如果用Symbol定义的对象中的变量,取值时候一定要用[变量名]
console.log(obj[s1]);
//console.log(obj.s1);
要写成下边的形式
let obj={
[s1]:'fu'
};
Object.keys() 方法返回一个包含对象自身所有可枚举属性的数组。
Object.getOwnPropertySymbols() 是 JavaScript 中的一个静态方法,用于返回一个数组,该数组包含指定对象自身所有的符号属性(Symbol)的键(键名数组)。符号(Symbol)是 ES6 引入的一种新的原始数据类型,它确保每个创建的符号都是唯一的,常用于对象属性的键,以避免命名冲突。
获取Symbol声明的属性名还可以用Reflect.ownKeys(obj)返回是一个数组。阅读源码的时候经常会看到Symbol
let person = {
"first name": "Alice",
age: 25,
city: "New York"
};
// 访问包含空格的属性名
console.log(person["first name"]); // 输出 "Alice"
// 使用变量作为属性名
let prop = "age";
console.log(person[prop]); // 输出 25
// 动态构建属性名
let key = "city";
console.log(person[key]); // 输出 "New York"
BigInt(es11)
我们之前的数据类型number它的取值范围为(-2^53-1至+2^53-1) bigint是一个基础的数据类型,可以表示任意精度的整数。并且可以超过数字类型的安全整数限制。
const a=1n
const b=1
console.log(a)//1n
console.log(a==b)//true
这两个数值是一样的
const b=BigInt(2)
打印b//2n
const a=3n
a+b//5n
a*b//6n
a/b //1n
a-b //1n
值得注意的是bigint类型和number类型是不能混用的,需要将number转化为bigint类型再运算
const a=3n
const num=3
a*num//会出现报错不能进行混合运算 但可以进行a*BigInt(num)进行转换
bigint类型不支持+值将值转换为number类型的方法
const a='1'
const b=+a
b//就为1通过强制转换称为了number类型
const c=3n
const d=+c //报错
对于比较:number类型和bigint类型可以混合比较,number类型和bigint类型可以==(数值),但不可以===因为数据类型不一样,
const a=0
if(a){
}else{
console.log(111)
}
const b=BigInt(0)
if(b){
}else{
console.log(111)
}//111
Set
Set我们在es6中称为集合,表示无重复值的有序列表。引入的 Set 是一种 对象,但它是一种特殊类型的对象,用于存储唯一的值集合。Set 对象允许你存储任何类型的值(包括原始值和对象引用),并且每个值只能出现一次,确保了集合中的元素是唯一的。Set 构造函数只能接受一个参数,且该参数必须是一个可迭代对象(如数组、字符串、Set 等)。我们不能直接在 new Set() 中同时传递多个参数(例如既传数组又传字符串),因为构造函数只接受一个参数。
Set上的方法:
Set 提供了丰富的方法来操作集合中的元素,包括添加、删除、检查、遍历和获取集合的大小。以下是对 Set 方法的简要总结:
- 添加和删除元素:
add()、delete()、clear() - 检查元素是否存在:
has() - 遍历
Set:keys()、values()、entries()、forEach() - 获取
Set的大小:size
let set=new Set();
console.log(set)
//添加元素
set.add(2);
set.add('4');
set.add([1,2,3])
//删除元素
set.delete(2);
//校验某个值是否在set中
console.log(set.has('4'));
console.log(set.size);
set.forEach((val,key)=>{
console.log(val);
console.log(key);发现val和key是一样字的Set里边key和value相等。值就是键,键就是值。
})
在这里边我们不会使用forEach进行遍历。 //我们可以将set转换成数组 let set2=new Set([1,2,3,3,3,4]); let arr=[...set2] console.log(set2);//{1,2,3,4}
...:这是扩展运算符(Spread Operator),它允许你将可迭代对象(如Set、数组、字符串等)展开为单独的元素。上述的代码可以将数组去重!!
说完Set,之后不得不提的是WeakSet了 1.不能传入非对象类型的参数 2.不可迭代 3.没有forEach() 4.没有size属性
Map
Map类型是键值对的有序列表,键和值是任意类型。
Map 是 JavaScript 中用于存储键值对(key-value pairs)的集合对象。与 Object 不同,Map 的键可以是任何类型的数据(包括原始值和对象),并且它保留了插入顺序。Map 提供了许多有用的方法来操作键值对,使得它在处理复杂数据结构时非常灵活和强大。
let map=new Map();
map.set('name','张三')
map.set('age',20)
console.log(map.get('name'));
console.log(map);
map.has('name');
map.delete('name');
map.clear();
console.log(map);
map.set()
迭代器Interator
//Iterator //是一种新的遍历机制,两个核心 //使用迭代 //迭代器是一个接口,能快捷的访问数据,通过Symbol.iterator来创建迭代器 通过迭代器的next()获取迭代之后的结果(是一个对象) 我们可以把迭代器认为是用于遍历数据结构的指针(数据库的游标)
const items=['one','two','three'];
console.log(items);
const ite=items[Symbol.iterator]();
console.log(ite.next());//{value:"one",done:false}
console.log(ite.next());//{value:"two",done:false}
console.log(ite.next());//{value:"three",done:false}
console.log(ite.next());//{value:undefined,done:true}
生成器
//generator函数 可以通过yield关键字,将函数挂起,为了改变执行流提供了可能,同时为了做异步编程提供了方案。 //它跟普通函数的区别 //1.function 后面 函数名之前有个* //2.只能在函数内部使用yield表达式,让函数挂起
function* func(a){
console.log('start');
yield 2;
yield 3;
}
//返回一个遍历器对象, 可以调用next()
let fn=func();
console.log(fn.next());
console.log(fn.next());
console.log(fn.next())
//{value:2,done:false}
//{value:3,done:false}
//{value:undefined,done:true}
//总结:generator函数是分段执行的,yield语句是暂停执行 而next()恢复执行。
function*add(){
console.log('start');
let x=yield '2';
console.log('one'+x);
let y=yield '3';
console.log('two:'+y);
return x+y;
}
const fn=add();
console.log(fn.next());//{value:'2',done:false}
console.log(fn.next());//{value:'3',done:false}
Promise
promise 确实在 ECMAScript 2015(通常称为 ES6)中被引入为语言的内置特性。然而,Promise 的概念和实现早在此之前就已经存在,并且在 ES6 之前,许多库和框架已经提供了类似的异步处理机制。ES6 将 Promise 标准化并纳入了 JavaScript 语言规范,使其成为原生支持的特性。
- ES6 之前,开发者通常使用回调函数(callback functions)来处理异步操作。虽然回调函数可以完成任务,但它们容易导致“回调地狱”(callback hell),即嵌套过多的回调函数,代码变得难以维护。
- 为了改善这种情况,一些第三方库(如 Q、Bluebird、When 等)开始提供
Promise的实现。这些库提供了更优雅的方式来处理异步操作,避免了回调地狱的问题。 -
- 随着
Promise的普及,JavaScript 社区进一步发展了异步编程的工具和语法糖。例如,ES2017 引入了async/await语法,它使得异步代码看起来更像是同步代码,进一步简化了异步编程的复杂性。
- 随着
async/await实际上是基于Promise实现的,因此理解Promise是掌握async/await的基础。
Promise一个优点是可以通过链式调用.then()和.catch()方法来处理复杂的异步流程。
通过在函数声明前加上async关键字,可以将任何函数转换为返回Promise的异步函数。这意味着你可以使用.then()和.catch()来处理它们的结果。
在async/await中,错误处理可以通过传统的try...catch语句实现,这使得异步代码的错误处理更加直观。Async函数
1. 函数的返回值为Promise对象
2. Promise对象的结果由async函数执行的返回值决定
Await 右侧的表达式一般为promise对象,但也可以是其它的值,如果表达式是promise对象,await返回的是promise成功的值。如果是其它值直接将此值作为await的返回值。注意await必须写在async函数当中,但async函数中可以没有await 3. 如果await的promise失败了,就会抛出异常,需要通过try...catch捕获处理
for of
描述:for...of 循环用于遍历可迭代对象(如数组、字符串、Set、Map 等),提供了更简洁的语法。
const arr = [1, 2, 3];
// 传统 for 循环
for (let i = 0; i < arr.length; i++)
{
console.log(arr[i]);
}
// for...of 循环
for (let value of arr) {
console.log(value);
}
- 直接遍历可迭代对象的值,无需手动访问索引。