面试之es6

214 阅读17分钟

前言

几乎每次面试的时候都会被问到(😂😂)关于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()
  • 遍历 Setkeys()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 循环用于遍历可迭代对象(如数组、字符串、SetMap 等),提供了更简洁的语法。

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);
}

-   直接遍历可迭代对象的值,无需手动访问索引。