🚀 作者 :“码上有前”
🚀 文章简介 :前端高频面试题
🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬
前端高频面试题--ES6篇
往期精彩内容
【前端高频面试题–HTML篇】
【前端高频面试题–CSS上篇】
【前端高频面试题–CSS下篇】
【前端高频面试题–JS上篇】
【前端高频面试题–JS下篇】
【前端高频面试题–ES6篇】
【前端高频面试题–ES7-ES11】
【前端–异步编程】
【前端高频面试题–TypeScript篇】
【前端高频面试题–git篇】
【前端高频面试题–微信小程序篇】
【前端高频面试题–Vue基础篇】
【前端高频面试题–虚拟DOM篇】
【前端高频面试题–Vue3.0篇】
【前端高频面试题–Vuex上篇】
【前端高频面试题–Vuex下篇】
【前端高频面试题–Vue生命周期篇】
【前端高频面试题–Vue组件通信篇】
【前端高频面试题–Vue路由篇】
【前端-Vue3创建一个新项目】
【前端大屏自适应缩放】
【前端Vue3 + TS项目开发一般流程】
ES6
ES6概念
ES6,全称 ECMAScript 6.0 ,2015.06 发版。ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JS 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
let和const
let
let变量的特点:
- 块级作用域,不在全局生效
- 不能重复声明
- 没有变量提升
- 不影响作用域链
//let声明的变量只在let所在的代码块内有效。
//块级作用域不仅仅是花括号,比如在if else while for语句里都是块级作用域
{
var a = 1;
let b = 2;
}
console.log(a);//1
console.log(b);//2
//let不能重复声明变量,var可以多次声明
{
let a = 1;
let a = 2;
var b = 2;
var b = 3;
console.log(a);//Identifier 'a' has already been declared
consle.log(b);//3
}
//let不存在变量提升
console.log(a);//ReferenceError: a is not defined
let a = 1;
console.log(b);//2
var b = 2;
//不影响作用域链
//即在自己作用域没有,逐步往上级作用域寻找
{
let boy = '李sha猪'
function sayName(){
console.log(boy);//
}
sayName();//'李sha猪'
}
Const
- 声明一个常量,且不可更改
- 一旦初始化必须赋予初始值,一般大写
- const定义的数组或对象可以更改其中的属性或者元素
- 没有变量提升,块级作用域,不影响作用域链
//const声明一个只读变量,声明之后不能再次声明
//const声明的变量一般首字母大写且为常量
//const声明的变量一旦初始化就必须赋予初始值
const HH; // SyntaxError: Missing initializer in const declaration
//不存在变量提升
const PI = "3.1415926";
console.log(PI); // 3.1415926
//可以修改对象或者数组里的元素或属性
const SCHOOL = ['nxlg','neu'];
SCHOOL.push('nxu');
console.log(SCHOOL);//['nxlg','neu','nxu'];
//常量所指向的地址没有改变,所以不会报错
解构赋值
- 数组的解构赋值
- 对象的解构赋值
//数组的解构赋值
const CITY = ['YC','SZS','SY'];
let [first,second,three] = CITY;
console.log(first,second,three);
//'YC','SZS','SY'
//对象的解构赋值
const BOY = {
name: '李sha猪',
age: 18 ,
sayName: function(){
console.log('我叫李sha猪');
}
}
let [a,b,c] = BOY;
console.log(a,b,c);//'李sha猪' 18 function(){}
//调用函数
c();//'我叫李sha猪'
//总结:这样的好处就是不用BOY.name、BOY.age这样去使用属性,而是可以直接a 、b这样使用变量,可以少些很多BOY。属性的解构使用的较少,方法的解构用的较多。
模板字符串
//使用反引号``
//声明
let boy = `我是李sha猪`;
console.log(boy,typeof boy);// 我是李sha猪 string
//内容中可直接出现换行符,就不用逐个逐个使用‘+’号连接
let str = `
富强民主文明和谐
平等自由公正法治
爱国敬业诚信友善
`
//可直接插入变量,将变量放在"${}"当中
let web = 'js';
let work = `我的工作要使用到${web}`;
console.log(woork);//我的工作要使用到js
对象简化
let name = 'csdn';
let age = 5;
let study = function(){
console.log(`我在${name}学习了${age}年`)
}
const iCsdn = {
name, // 完整写法name = name
age, // age = age;
study(){ //对象方法的简写,省略function 和 :
console.log(`我在${name}学习了${age}年`)
}
}
箭头函数
声明一个箭头函数
//声明一个普通函数
let fun = function(){};
//声明一个箭头函数,省略了function
let fun = () => {}
//调用函数方式一样
let result = fun();
箭头函数的特性
//1.this是静态的,始终指向声明函数时所在的作用域
//this的值不受call()、bind()方法影响
function getName(){
console.log(this.name)
}
let getName2 = () => {
console.log(this.name)
}
//设置window的name属性
window.name = '我是window的名字啦啦啦';
const me = { name: '我的名字嘟嘟嘟'}
//调用函数
getName();//'我是window的名字啦啦啦'
getName2();//'我是window的名字啦啦啦'
//使用call()方法调用改变this的指向对象
getName.call(me);//'我的名字嘟嘟嘟'
getName2.call(me);//'我是window的名字啦啦啦'
//2、不能作为构造函数实例化对象
let Student = (name,school) =>{
this.name = name;
this.age = age;
}
let me = new Student('李sha猪',18);
//直接报错
//3.不能使用arguments变量(arguments变量保存实参)
let fu = () => {
console.log(arguments);
}
fn(1,2,3);//报错:arguments is not undefined
//4.箭头函数的简写
//省略小括号,当形参只有一个的时候,
//当语句只有一条时可省略花括号和return,并且语句的结果就是返回值
let pow = a => a * a;
箭头函数的应用于场景
总结
- 不适合于this有关的回调,Dom函数的回调,对象的方法
- 适合与this无关的回调、定时器与数组方法
//需求,两秒后改变div的颜色
//首先获取div
let d = document.getElementById('div');
d.addEventListener('click',function(){
setTimeout(function(){
console.log(this);//此时这个this不是指向d,指向window
this.style.color = 'pink'
}
,2000)
})
//结果这个问题,是在外层保存这个this的值
let d = document.getElementById('div');
d.addEventListener('click',function(){
let _this = this;
console.log(_this);
setTimeout(function(){
console.log(_this);//此时这个_this会往上找
_this.style.color = 'pink'
}
,2000)
})
//当然现在可以用箭头函数
let d = document.getElementById('div');
d.addEventListener('click',function(){
//let _this = this;
//console.log(_this);
setTimeout(() => {
console.log(this);
//此时这个this指向声明时所在作用域下的this值(functio()指向d)
this.style.color = 'pink'
}
,2000)
})
//声明一个数组,从数组中返回 偶数的元素
let arr = [1,2,3,4,5,6,7]
let result = arr.filter(function(item){
if(item % 2 === 0 ){
return true;
}else{
return flase
}
})
console.log(result);//[2,4,6]
//现在使用箭头函数
let arr = [1,2,3,4,5,6,7]
//不使用匿名函数,使用箭头函数
let result = arr.filter(item => item % 2);
形参赋初始值
// 1.注意在函数中形参不能调换位置,因为传值是按照顺序的,
// 把含有默认参数的放置最后,实参没有传入值,对应的形参就是undefined
function add(a,b,c){
return a +b +c ;
}
let result = add(1,2);//1+2+undefined = NaN
//设置默认值
function add(a,b,c = 10){
return a +b +c ;
}
let result = add(1,2);//1+2+10 = 13
//与解构赋值相结合
// 2.可以和解构赋值结合
function con(option){
// 这样每次都要写option
let host = "option.host";
let username = "option.username";
}
// 也可以给属性赋予初始值
function con({host,username="hhh",post="3306"}){
console.log(host);
console.log(username);
console.log(post);
}
//调用函数
con({
host : "localhost",
username: "root"
})
Rest参数
// ES6引入rest参数,用于获取函数的实参,用来代替函数的arguments
// ES5获取实参的方式
function date(){
console.log(arguments);
// 会输出一个伪数组对象Arguments(2) ["ajiao", "baizhi", callee: ƒ,)
}
date('ajiao','baizhi');
//rest参数,返回的数组,那么就可以用数组的方法了
// 比如filter some eerry map都可以使用,就提高了效率
// 如果存在rest参数,那么就需要放在最后
function date1(...args){
console.log(args);
// (2) ["ajiao", "baizhi"] 返回一个数组、
}
date1('ajiao','baizhi');
//rest参数必须放在最后
function fun(a,b,...args){
console.log(a); //1
console.log(b); //2
console.log(args); //3) [3, 4, 6]
}
fun(1,2,3,4,6)
扩展运算符
// ... 扩展运算符能将数组转换成逗号分隔的参数序列
//将数组变成字符串序列,作用有点点像数组方法中的join,
//与字面量数组的方法互为逆作用的 arr = [...arr]
const tf = ['易烊千玺','王源','王俊凯'];
// 以上数组可以转化为=>'易烊千玺','王源','王俊凯'
function chunwan(){
console.log(arguments);
// 返回的是一个对象,只有一个元素就是tf数组
}
chunwan(tf);// Arguments [Array(3)],
chunwan(...tf);//["易烊千玺", "王源", "王俊凯"]
//chunwan("易烊千玺", "王源", "王俊凯") 一个参数变3个Arguments(3)
// rest是放在了函数的形参位置,...扩展运算符放在函数调用的实参中。
扩展运算符的应用
// 1.数组的合并
const kuaizi = ['王太利','肖央'];
const fenghuang = ['曾毅','玲花'];
// es5之前的用法:
const zuixuanxiaopingguo = fenghuang.concat(kuaizi);
console.log(zuixuanxiaopingguo);
// es6的用法,...把数组编程了逗号分隔的参数序列
const zuixuanxiaopingguo1 = [...fenghuang,...kuaizi];
console.log(zuixuanxiaopingguo1);
// 2.数组的克隆
//扩展运算符与[]是两种逆运算
//扩展运算符将[]变成几个‘’,而[]将多个‘’变成数组[]
const sanzhihua = ['e','g','m'];
const sanyecao = [...sanzhihua];
console.log(sanyecao);
// 要是有引用数据类型的话就是浅拷贝
// 3.将伪数组转为真正的数组
//因为扩展运算符可以将伪数组变成多个'',再用[]括起来就变成了真数组[]
const divs = document.getElementsByTagName("div");
const divs1 = document.querySelectorAll("div");
console.log(divs); //HTMLCollection(3) [div, div, div]
console.log(divs1);//NodeList(3) [div, div, div]
const divArr = [...divs];
console.log(divArr);//(3) [div, div, div] 转换成数组
Symbol的基本使用
//symbol是es6引入的新的原始数据类型symbol,表示独一无二的值,
// 特点:1.独一无二,来解决命名冲突的问题
// 2.不能与其他数据类型进行运算(加减乘除,连接拼串)
// 3.symbol定义的对象属性不能使用for...in循环遍历,但是可以使用reflect.ownKeys来获取对象的所有键名
let s = Symbol();
console.log(s,typeof s); //Symbol() 'symbol'
// 内部实现了唯一性,对于我们不可见
//参数是描述性的文字,表示为谁设置symbol
let s2 = Symbol('shahuhu');
let s3 = Symbol('shahuhu');
console.log(s2===s3);//false,
//类似于表示有两个傻乎乎 但是他们编号不一样,都表示独一无二的值
// Symbol.for()方法来创建,此处的Symbol为一个对象
let s4 = Symbol.for("shahuhu");
let s5 = Symbol.for("shahuhu");
console.log(s4,typeof s4);//Symbol(shahuhu) ‘object’
console.log(s4===s5);//true
对象添加Symbol类型的属性
//使用symbol属性唯一且安全
// symbol的使用:向对象添加方法 up down
let game ={}
let methods = {
up:Symbol(),
down: Symbol(),
};
game[methods.up] = function(){
console.log("我可以进步")
}
game[methods.down] = function(){
console.log("我可以下降")
}
console.log(game);
//又一例子
let youxi = {
name: "狼人杀",
[Symbol('say')]: function () {
console.log("我可以说话")
},
[Symbol('zibao')]:function(){
console.log("我可以自爆");
}
}
console.log(youxi);
Symbol内置值
//symbol对象的属性
//提供了11个内置的Symbol值,指向语言内部使用的方法
//手册查、、、
迭代器
// 迭代器是一种接口,为各种不同的数据结构提供统一的访问机制
// 任何数据结构只需要部署接口,接口完成遍历操作。
// 1.Es6创建了一种新的遍历命令for...of循环。iterator接口只提供for...of消费
// 2.原生具备iterator接口的数据(可用for...of遍历),可以理解为对象里面的属性。名称叫symbol.iterator
// Array Arguments Set Map String TypeArray NodeList
// 工作原理:1.创建一个指针对象,指向当前数据结构的起始位置
// 2.第一次调用对象的next方法,指针自动指向数据结构的第一个成员
// 3.接下里不断调用next,直至最后一个
// 4.没调一个next函数会返回一个包含value和done属性的对象
// {value: "唐僧", done: false}
const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
// for..of 遍历数组
for(let v in xiyou ){
console.log(v ); //0 ,1,2,3 输出的是数组下标
}
for(let v of xiyou ){
console.log(v ); //输出的是数组下标的内容
}
// 为什么数组可以用for...of遍历 ,因为在数组中有这个Symbol(Symbol.iterator)函数
console.log(xiyou);
let iterator = xiyou[Symbol.iterator]();
// 调用对象的next方法
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// {value: undefined, done: true} 最后才变成true,表示迭代完了
// 迭代器案例,干什么:自定义遍历数组
// 我们的需求是:每次遍历返回的是数组内stus内的成员
// 第一次返回zhuzhu,第二次返回hhhh ,.....
const banji = {
name : '1808',
stus: [
'zhuzhu',
'hhhh',
'dudu',
'xixi'
],
[Symbol.iterator](){
let index = 0 ;
let _this = this;
return {
next: function(){
if(index < _this.stus.length){
const result = {value: _this.stus[index], done: false};
// 下表自增 不然始终范湖小ming
index++;
return result;
}else{
return {value: undefined,done: true};
}
}
}
}
}
for(let v of banji){
console.log(v);
}
// 当然可以用下面的方法,但是不符合面向对象的方法。不能直接对对象的成员进行操作
// banji.stus.forEach();
// 那么怎么办呢,那就需要加上iterator接口,
生成器
// 生成器就是一个特殊的函数,来进行异步编程
// 之前的异步编程都是回调函数,一层套一层 比如:node fs ajax mongodb
// 用来解决异步调用
function * gen(){
console.log("hello generator")
}
let iterator = gen();
console.log(iterator); //返回值是迭代器对象:gen {<suspended>}可以调next方法
// 借用生成器函数的next方法调用
iterator.next();
// 声明函数特殊要加* 还可以加yield表达式,调用也特殊,要用next()函数
// yield表示函数代码的分隔符,线程暂时挂起,yield由next方法来执行
function * generator(){
console.log("111");
yield '天王盖地虎';
console.log("222");
yield '宝塔镇河妖';
console.log("333");
yield '脸红什么?精神焕发';
console.log("444");
}
let ite = generator();
// ite.next();//111
// ite.next();//222
// ite.next();//333
// ite.next();//444
// next()生成的是对象,即打印一个对象
console.log(ite.next());
console.log(ite.next());
console.log(ite.next());
console.log(ite.next());
// 因为是迭代器对象,所以用for...of,of的是自定义的迭代器对象,
for(let v of generator()){
console.log(v);
}
迭代器对象传入参数
function * gen(arg) {
console.log(arg);
let one = yield '啦啦啦';
console.log(one);
yield '哈哈哈';
yield '呼呼呼';
}
// 执行获取迭代器对象
let iterator = gen('第一个参数');
console.log(iterator.next());
// next方法可以传入实参,第二个参数将作为第一个yield的整体返回结果。
// 同理第一个next方法 就会被arg接收 ,第三个yield是第二个yield的接受参数
console.log(iterator.next('第二个参数'));
console.log(iterator.next('第三个参数'));
console.log(iterator.next());
Promise
//面试常问,内容较多,知识点重要,另起一篇文章
Set
// set是es6新增的数据类型,类似于数组,成员唯一,实现了iterator接口
// 可以使用扩展运算符和for...of进行遍历,有一些集合的属性和方法。
// 1.size:元素的个数
// 2.add 增加一个数,返回当前集合
// 3.delete 删除元素 返回当前集合
// 4.has 检测集合中是否包含某个元素,返回布尔值
let s = new Set();
let s1 = new Set(['猪猪','猪仔','宠物猪','宠物猪']);
// 会自动去重,有重复的话
console.log(s1,typeof s1);
console.log(s1.size); //3
s1.add('胖猪');
console.log(s1);
s1.delete('胖猪');
console.log(s1);
console.log(s1.has('雅娟猪猪'));
// 遍历
for(let v of s1){
console.log(v);
}
// 清空
s1.clear();
console.log(s1);
//set应用实践
let arr = [1,2,3,4,4,3,2,1,2,3,4]
// 1.数组去重
let result = [...new Set(arr)];
console.log(result);
// 2交集
let arr2 = [3,4,5,6];
let result2 = [...new Set(arr)].filter(item => {
// 判断元素在不在数组当中
let s2 = new Set(arr2);
if(s2.has(item)){
return true;
}else{
return false;
}
});
let result21 = [...new Set(arr)].filter(item => new Set(arr2).has(item));
console.log(result21);
console.log(result2);
// 3.并集
let arr3 = [3,4,5,6];
let result3 = [...arr2,...arr];
let result31 = [...new Set(result3)];
console.log(result31);
// 4.差集
// 比如在1对2里面求差集,那么就是1里面有但是集合2里面没有的
// 差集就是交集的逆运算
let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
console.log(diff);
Map
//Map类似于对象,键值对的集合
// 但是Map中的键值对不限制string,各种类型的值包括对象都可以当做键
// Map也实现了iterator,所以也可使用扩展运算符...和for..of进行遍历
// Map属性和方法:size set get has clear
// set:增加一个新元素,返回当前Map
// get:返回键值对象的键值
// 创建一个空Map
let m = new Map();
// 第一个参数是键,第二个参数是值
m.set('name','sahuhu');
console.log(m);
m.set("love",function love(){
console.log(" i love zhuzhu");
});
console.log(m);
let key = {'hhh':'每天笑哈哈'};
m.set(key,['zhu','zhuzhu','zhuzhuzhu']);
console.log(m);
// 长度
console.log(m.size);
// 删除
m.delete('name');
console.log(m);
// 获取
console.log(m.get('love'));
// 遍历
for(v of m){
console.log(v);
}
// 清空
m.clear();
console.log(m);
class类
// Es6提供了更接近传统语言的写法,引入了class类这个概念,作为对象的模板,通过class类关键字,可以定义类。
// 基本上,class只能作为语法糖,他的绝大多数功能ES5都能做,新的class写法只是让对象的原型的写法更加清晰,更面向对象,知识点:
// 1.class声明
// 2.construction定义构造函数初始化
// 3.exends继承父类
// 4.super调用父类构造方法
// 5.static定义静态方法和属性
// 6.父类方法可以重写
// es5方法 构造函数
function Phone(brand,price){
this.brand = brand ;
this.price = price ;
}
Phone.prototype.call = function(){
console.log('我可以打电话嗄');
}
let Huawei = new Phone('华为',5999);
Huawei.call();
console.log(Huawei);
// es6构造函数 class语法实现
class MyPhone {
// 构造方法,该方法自动执行,不可更改
// 放我们在使用new 类名的时候回自动加载
constructor(mybrand,myprice){
this.mybrand = mybrand ;
this.myprice = myprice ;
}
// 用call方法加方法
call(){
console.log('我可以打电话');
}
}
let vivo = new MyPhone('neo5',2399);
console.log(vivo)
数值扩展
// 0.Number.EPSILON 是Js表示的最小精度
// EPSILON 属性的值接近2. ...E-16次方
// 如果小个数的差值小于这个最小数EPSILON,那么就表示这两个数相等
// 运用与浮点数的运算中
console.log(0.1+0.2); //0.30000000000000004
function equal(a,b){
if(Math.abs(a-b)<Number.EPSILON){
return true;
}else{
return false;
}
}
console.log(0.1+0.2===0.3);
console.log(equal(0.1+0.2,0.3));//true
// 1.二进制和八进制
let b = 0b1010;//二进制
let o = 0o777;
let d = 100;
let x = 0xff;
console.log(b);
console.log(o);
console.log(d);
console.log(x);
// 2.Number.isFinite 检查一个数是否为有限数
console.log(Number.isFinite(100));//true
console.log(Number.isFinite(100/0));//false
console.log(Number.isFinite(Infinity));//false
// 3.Number.isNaN 检查一个数是否为NaN
console.log(Number.isNaN(100));//false
console.log(Number.isNaN('hh'));//false
console.log(Number.isNaN(NaN));//true
// 4.Number.parseTnt Number.parseFloat字符串转整数
let s = '520iloveyou';
let f = '3.1415926wahhh'
console.log( Number.parseInt(s))//520 会自动截断
console.log( Number.parseFloat(f))//3.1415926 会自动截断
// 5.Number.isInteger判断一个数是否为整数
console.log( Number.isInteger(s))//false
console.log( Number.isInteger(4))//true
// 6.Math.trunc 将数字的小数部分抹掉
console.log( Math.trunc(3.5))//3
// 7.Math.sign 判断一个数到底为正数,负数还是零
console.log( Math.sign(3.5))//1
console.log( Math.sign(-3.5))//-1
console.log( Math.sign(0))//0
对象扩展
//对象方法的扩展
//1.Object.is 判断两个对象是否完全相等
console.log(Object.is(101,'101')) //flase
//作业与全等号很像,但是又有不像
console.log(Object.is(NaN,NaN)) //true
console.log(NaN === NaN) //flase
// 2.Object.assign 对象的合并
const config1 = {
host:'localhost',
port:'3306',
name:'shahuhu',
pass:'root',
test: 'test'
}
const config2 = {
host:'http://www.baidu.com',
port:'3308',
name:'zhuzhu',
pass:'root',
test1: 'test1'
}
// 要是对象属性重名,后面的会把前面的覆盖掉,没有的话就会合并
console.log(Object.assign(config1,config2));
// 3.Object.setPrototyptOf Ojecst.getPrototypeOf
// 设置原型对象,虽然这样可以改变对象的原型对象,最好还是通过Object.create就把原型设置上
const zhuzhu = {
name: '傻乎乎'
}
const addr = {
local : ['小王子','玫瑰','猴面包树']
}
Object.setPrototypeOf(zhuzhu,addr);
console.log(zhuzhu);
console.log(Object.getPrototypeOf(zhuzhu));
模块化
概念:模块化是指将一个大的文件拆分成许多小文件,然后将小文件组合起来
模块化的好处:
- 防止命名冲突
- 代码复用
- 高维护性
模块化产品:
ES6之前的模块化规范
- CommonJS => NodeJS 、Browserify
- AMD => requireJS
- CMD => sealJS
模块化语法:
模块化主要由两个命令构成:export和import
- import:用于输入其他模块提供的功能
- export:用于规定并输出模块的对外接口
//暴露 ,新建一个js文件取名为me.js
export let me = '大帅哥';
export function add(){};
//引入me.js的模块内容,把所有的模块内容存到变量m中去了
import * as m from '.src/js/me.js'
console.log(m);
//暴露汇总:三种方式分别是分别暴露、统一暴露、默认暴露
//分别暴露
export let me = '大帅哥';
export function add(){};
//统一暴露
export {me , add}
//默认暴露
export default{
me: 'shuai',
add: function(){}
}
//引入汇总:三种分别是通用的导入方式,解构赋值的暴露,简便形式的暴露,可以使用别名
//1.通用的导入方式
import * as m1 from ./src/js/me.js
//解构赋值暴露
import {me ,school} from './src/js/m1.js'
//如果引入有重复引用,使用别名
import {me as mee} from ./src/js/m1.js
import {default as mee} from ./src/js/m1.js
//简便形式 只针对默认暴露
import m1 from './src/js/m1.js'