性能优化
1.webpack打包文件体积过大?(最终打包为一个js文件)
2.如何优化webpack构建的性能
3.移动端的性能优化
4.Vue的SPA 如何优化加载速度
5.移动端300ms延迟
6.页面的重构
所有的知识点都有详细的解答,我整理成了280页PDF《前端校招面试真题精编解析》。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
//单行函数体可以同时省略 {} 和 return
const add = (x,y) => x+y;
// 多行函数体不能再化简了
const add = (x, y) => {
const sum = x + y;
return sum;
};
(3) 单行对象
// 3.单行对象
const add = (x, y) => {
return {
value: x + y
};
};
const add = (x, y) => ({
value: x + y
});
// 如果箭头函数返回单行对象,可以在 {} 外面加上 (),让浏览器不再认为那是函数体的花括号
const add = (x, y) => [x, y]; //返回单行数组是不会出现简写单行对象的问题的,依旧按照化简单行函数的原则进行化简即可。
非箭头函数的this指向
(1)全局作用域于的this指向(简单) 指向的是window
// 1.全局作用域中的 this 指向
console.log(this); // window
(2)一般非箭头函数的this指向
// 2.一般函数(非箭头函数)中的 this 指向
// 'use strict';
function add() {
console.log(this);
}
// 只有在函数调用的时候 this 指向才确定,不调用的时候,不知道指向谁
// this 指向和函数在哪儿调用没关系,只和谁在调用有关
// 没有具体调用对象的话,this 指向 undefined,在非严格模式下,转向 window
// 严格模式('use strict')就指向 undefined
add(); // undefined->window(非严格模式下,浏览器把他从undefined转化为window)
const calc = {
add: add
};
calc.add(); // calc
const adder = calc.add;
adder(); // undefined->window(非严格模式下)
(2)箭头函数的this指向
// 1.箭头函数中的 this 指向
// 箭头函数没有自己的 this
const calc = {
add: () => {
console.log(this);
}
};
calc.add(); // window
//首先在箭头函数作用域中寻找this,由于箭头函数没有自己的 this,因此向外寻找,外面就是全局作用域 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
不适用箭头函数的场景
//(1)不能用箭头函数书写构造函数
// (2) 需要 this 指向调用对象的时候,主要用于给dom绑定事件,因为箭头函数没有自己的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();
3 解构赋值:数据解析出来=>赋值
数组:
数组的解构是:解析出右边变量的数据,赋值给相同索引值的常量/变量,不取的数据可以用逗号跳过
数组的默认值:只有当数组的成员严格===undefined的时候,默认值才能生效
// const [a,,c] = [1,2,3];
// const [a,b] = []; // a = undefined, b=undefined
// const [a =2,b] = [1]; // a = 1, b=undefined
// const [a=1,b=2]=[]; // a = 1, b=2
const [a=1,b=2]=[3,null]; // a = 1, b=null,没有严格等于undefined
console.log(b);
数组的默认值如果是一个表达式,那么该默认表达式就是惰性求值的,用到的时候会执行,用不到就不执行
const func = ()=>{
console.log(func);
return 2
};
// const [x=func()] = [1]; // x=1,不执行func
// const [x=func()] = [1]; //x=2,执行func
console.log(x);
类数组:
(1) arguments
function func(){
const [a,b]=arguments;
console.log(a,b);
};
func(); //undefined undefined
func(1,2) //a=1,b=2
(2) NodeList
console.log(document.querySelectorAll('p')); //NodeList(2) [p, p]
const [p1,p2] = document.querySelectorAll('p');
console.log(p1,p2); //<p></p> <p></p>
(3)函数参数
const arr =[1,1];
const add = ([x=0,y=0]=[]) => {
return x+y;
} //这里一定要是[x=0,y=0]=[]才能满足不传参数时,严格等于undefined
console.log(add()); //0
console.log(add(arr)); //2
(4)交换变量
let x = 1;
let y = 2;
[y,x] = [x,y]; //这就是数组解构赋值的原理,先把右边的数据解构出来,然后根据索引值赋值给变量
console.log(x,y); //2,1
对象:
对象解构赋值看两点:
(1) 结构匹配 {}={}
(2)相同的属性名完成对应的属性值赋值
const{username:username1,age:age2} = {username:'alex',age:12};
console.log(username1,age2);
//这就是找到相同的属性名,然后把对应的属性值赋值到username1,age2,这样打印结果是alex 12
默认值:
(1)首先也是严格等于=== undefined时,默认值才能有效
这里注意默认值是书写方式是: username='alex’不是username:‘alex’,书写上的语法
const Info = ({username='alex',age=12}={})=>{
console.log(username,age);
};
Info() //alex 12
Info({username:'wumingna',age:12}); //wumingna 12
(2)和数组一样,默认值如果是表达式的话,是惰性求值的
(3)当将一个已经声明的变量用于对象的解构赋值时,要加上圆括弧,数组不用在意这个
let x =2;
({x} = {x:1});
console.log(x);
(4)对象可以取到继承的属性,根据原型链查找
其他类型数据
字符串:
字符串的解构赋值既可以参照数组也可以按照对象,都是根据index赋值的
// 数组
const [a,b,,,e] = 'hello';
console.log(a,b,e); //h,e,o
// 对象
const {0:a,1:b,3:c} = 'hello'
console.log(a,b,c); //h,e,l
数值和布尔值
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString
属性,因此变量s
都能取到值。
undefined和null
他们是无法转化为对象的,所以他们是不能进行结构赋值的,会报错
4 对象字面量增强
对象字面量是什么
是对象的一种写法,通常我们创建一个Object类型的实例的方法有两个
(1)实例化构造函数
var person=new Object();
person.name="Mina";
person.age=20;
(2)使用对象字面量的形式
const person = {
name:'Mina'
age:20
}
对象字面量有几种简洁表示法:
(1)对象的属性名和变量或者常量一样的时候,可以只写属性名
const name = 'wuva';
const person = {
neme
}
(2)对象方法的简介表示,省略冒号和function
const person = {
speak(){
console.log('speak')
}
}
方括号语法增强
方括号语法的用法:
const prop = 'age';
const person = {};
person.prop = 18; //此时属性名是prop
person[prop] = 18; //会先对方括号中的prop的求值,此时属性名是age
// 方括号语法可以写在对象字面中
const person2 = {
[prop] : 16
};
console.log(person2); // 会先对方括号中的prop的求值,此时属性名是age {age: 16}
方括号里面可以放什么
(1)${}
(2)值或者表达式
方括号和点语法的区别
点语法是方括号的一种特殊形式,
当属性名是合法字符串的时候,可以使用点语法,其他情况使用方括号语法,因此更推荐使用方括号语法
5 函数参数的默认值
(1) 函数参数默认值的生效条件是不传参或者明确传递undefined作为参数
(2) 默认参数是表达式的时候是惰性求值的
(3)小技巧:函数默认参数的设置从参数列表的右边开始设置
假如从参数列表的左边设置:
const mul = (x=1,y) => x*y;
console.log(mul(undefined,2)); //麻烦
6 剩余参数
剩余参数的本质:剩余参数永远都是一个数组,即使没有值,也是一个空数组
注意事项:
(1)箭头函数的参数即使只有一个剩余参数,也不能省略圆括弧
const add = (...args) => {};
(2)使用剩余参数代替argument获取实际参数
const add = function () {
console.log(arguments);
};
const add = (...args) => {
console.log(args);
};
add(1, 2);
(3)剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错
const add = (x, y, ...args) => {
console.log(args);
};
7 展开运算符
认识展开运算符
...[2,3,4] => 3,2,1 // 1.认识展开运算符 // [3, 1, 2]; ==》怎么找到数组的最大值和最小值 // Math.min console.log(Math.min([3, 1, 2])); // math.min不能接收数组,接收的是参数列表的形式,如下面一行 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));
区分剩余参数和展开运算符
根本区别:
剩余参数: 3,2,1 --> [3,2,1]
展开运算符: [3,2,1] -->3,2,1
数组的展开运算符的应用
(1)复制数组(地址不相同)
// const a = [1, 2];
// // const b = a;
// // a[0] = 3;
// // console.log(b); //这样指向的同一个地址,修改a,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']) // ["a","l","e","x"]
//类数组
// (1) arguments
function func() {
console.log([...arguments]);
}
func(1, 2);
//(2) NodeList
console.log([...document.querySelectorAll('p')].push);
对象的展开运算符
(1)对象不能直接展开,要在{}中展开,将属性罗列出来放到{}中 构成一个新的对象
const apple = {
color: '红色',
shape: '球形',
taste: '甜'
};
console.log({...apple}); //{color: '红色', shape: '球形', taste: '甜'}
// console.log(...apple); //报错
// console.log([...apple]); //报错
(2)合并对象,中间用逗号隔开,合并的对象具有所有属性,相同的属性后者覆盖前者
const apple = {
color: '红色',
shape: '球形',
taste: '甜'
};
const pen = {
color: '黑色',
shape: '圆柱形',
use: '写字'
};
console.log({ ...apple, ...pen }); //{color: '黑色', shape: '圆柱形', taste: '甜', use: '写字'}
注意事项
(1)如果展开一个空对象,则没有任何效果
(2)展开的不是一个对象的时候,会自动将其转化为对象,再将其属性罗列出来,找不到属性时候就是空对象
(3)展开的是字符串的时候,会自动将字符串转换为一个类数组对象,返回的不是一个空对象
(4)对于对象的展开,不会展开对象中的对象
对象展开运算符的应用
(1)复制对象(地址不同)
// const a = { x: 1, y: 2 };
// // const b = a; //这样其实是地址相同
const c = { ...a }; //正确的复制操作
// console.log(c, c === a);
(2)默认参数和实际参数
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();
7 Set和Map
Set
认识Set
Set是一个无序且没有重复值的数据结构,Set是没有下标标识每个值的,因此不能像数组一样通过下标值访问Set成员
const s = new Set([1,2,3,3,4])
console.log(s)
小知识点:Set内部NaN被视为相等的元素只会加入一个,但是其实NaN !== NaN, 但是两个空对象视为两个不同的元素
Set的方法和属性
属性:Set.size:返回 Set 实例的成员总数。
操作方法:
- Set.add(value) :添加某个值,返回 Set 结构本身。
- Set.delete(value) :删除某个值,返回一个布尔值,表示删除是否成功。
- Set.has(value) :返回一个布尔值,表示该值是否为 Set 的成员。
- Set.clear() :清除所有成员,没有返回值。
遍历方法: Set的遍历顺序就是成员的添加顺序
Set 结构的实例有四个遍历方法,可以用于遍历成员。
- Set.keys() :返回键名的遍历器
- Set.values() :返回键值的遍历器
- Set.entries() :返回键值对的遍历器
- Set.forEach() :使用回调函数遍历每个成员
let s = new Set([1,2,3]);
s.forEach(function(value,key,set){
console.log(value);
console.log(this); //#document
},document);
// 该函数的参数与数组的 forEach 一致,依次为键值、键名、集合本身(上例省略了该参数),doucument所在参数位置是修改函数的this指向,不写就是默认window
keys
方法、 values
方法、 entries
方法返回的都是遍历器对象
。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys
方法和 values
方法的是相等的,都是成员的值。这意味着,可以省略values
方法,直接用 for...of
循环遍历 Set
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
Set构造函数的参数
1.数组
2.字符串、argument、NodeList、Set等具有 iterable 接口的其他数据结构。
Set的注意事项
1.判断重复的方式
// Set 对重复值的判断基本遵循严格相等(===)
// 但是对于 NaN 的判断与 === 不同,Set 中 NaN 等于 NaN
// 两个空对象是不严格相等的
const s = new Set([1, 2, 1]);
const s = new Set([NaN, 2, NaN,{},{}]); // {NaN,2,{},{}}
console.log(NaN === NaN); //false NaN其实是不等于NaN的
console.log({} === {}); //false
- 什么时候用Set
(1)数组或者字符串去重
(2)只需要遍历,不需要通过下标去访问
(3)为了使用Set的属性和方法时
Set去重(最常用)
1 数组去重
// 方法1:
new_s1 = [...new Set(s)];
//方法2:
new_s2 = Array.from(new Set(s)) ; //Array.from 方法可以将 Set 结构转为数组
console.log(new_s2);
2.字符串去重
Set->数组->字符串
const s = new Set('abbagn');
console.log([...s].join('')); //abgn
Map
认识Map
map也是一种键值对的数据结构,由于对象的键只能采用字符串,这给他带来了很大的限制,为了能够让各种类型的值都可以当做键,由此提出来Map数据结构。
Map的属性和方法
属性:
Map.size :返回Map结构的成员总数 ,对象没有size属性
操作方法:
(1)Map.set(key,value): 添加新成员,如果key已经存在,后者覆盖前者,set 方法返回的是当前的 Map 对象,因此可以采用链式写法。
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
(2)Map.get(key) :通过键获取某个成员。如果找不到的话,返回undefined
(3)Map.has(key) : 返回一个布尔值,表示某个键是否在当前 Map 对象之中
(4)Map.delete(key): 删除某个键,返回 true 。如果删除失败,返回 false ,不报错
(5)Map.clear(): 一键清除
遍历方法:
- Map.keys() :返回键名的遍历器。
- Map.values() :返回键值的遍历器。
- Map.entries() :返回所有成员的遍历器。
- Map.forEach() :遍历 Map 的所有成员。
m.forEach(function(value,key,m){
console.log(value,key);
console.log(this); //doucument
},document)
Map构造函数的参数
<script>
// 1.数组
// 只能传二维数组,而且必须体现出键和值
console.log(
new Map([
['name', 'alex'], //第一个键值对
['age', 18]
])
);
// 2.Set、Map 等
// Set
// Set 中也必须体现出键和值,一般还是二维数组的形式
// const s = new Set([
// ['name', 'alex'],
// ['age', 18]
// ]);
// console.log(new Map(s));
// console.log(s);
// Map
// 复制了一个新的 Map
const m1 = new Map([
['name', 'alex'],
['age', 18]
]);
console.log(m1);
const m2 = new Map(m1);
console.log(m2, m2 === m1); // false
</script>
Map的注意事项
- 判断键名是否相等,遵循严格相等,例外的就是NaN也是等于NaN
- 什么时候使用Map:
如果只是需要key->value的结构或者需要字符串以外的值作为键,使用Map更合适,因为Map相对对象具有更多的方法
与其他数据结构的互相转换
(1) Map转数组
const myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
(2) 数组转Map
new Map([[true, 7], [{foo: 3}, ['abc']}])
(3) Map 转为对象
function mapToObj(m){
let obj = Object.create(null);
m.forEach(function(key,value){
obj[key] = value;
});
return obj;
}
const myMap = new Map().set('yes', true).set('no', false);
console.log(mapToObj(myMap)); //{true: 'yes', false: 'no'}
(4)对象转Map,可以通过Object.entries()函数 或者如上写一个转换函数
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
8 遍历器和For…of…
认识遍历器
1.遍历器(迭代器) Iterator
2.寻找 Iterator
console.log([1, 2][Symbol.iterator]());
const it = [1, 2][Symbol.iterator]();
console.log(it); // Array Iterator {}
3.使用 Iterator
const it = [1, 2][Symbol.iterator](); //返回一个遍历器的对象
console.log(it.next()); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false} 此时遍历并没有结束,因为此时done为false,还要接着遍历
console.log(it.next()); // {value: undefined, done: true} ,此时done为true,遍历完成
console.log(it.next()); // {value: undefined, done: true}
// it:可遍历对象(可迭代对象)
// Symbol.iterator:可遍历对象的生成方法
4.什么是 Iterator,主要是下面这个过程被称为iterator
// 4.什么是 Iterator,主要是下面这个过程被称为iterator
// Symbol.iterator(可遍历对象的生成方法) -> it(可遍历对象) -> it.next() -> it.next() -> ...(直到 done 为 true)
遍历器的意义
- 遍历数组、 Set 、Map等:for of, forEach
遍历对象:for in 循环
例子:
//for...of 循环只会遍历出那些 done 为 false 时,对应的 value 值
const arr = [1, 2, 3];
for (const item of arr) {
console.log(item);
}
//for...of的本质
const it = arr[Symbol.iterator]();
let next = it.next();
console.log(next);
while (!next.done) {
console.log(next.value);
next = it.next();
console.log(next);
}
// 2.与 break、continue 一起使用
const arr = [1, 2, 3];
for (const item of arr) {
if (item === 2) {
// break;
continue;
}
console.log(item);
}
// 3.在 for...of 中取得数组的索引
const arr = [1, 2, 3];
// keys() 得到的是索引的可遍历对象,可以遍历出索引值
console.log(arr.keys()); //得到一个可遍历对象
for (const key of arr.keys()) {
console.log(key); //0 1 2
}
//values() 得到的是值的可遍历对象,可以遍历出值
for (const value of arr.values()) {
console.log(value);
} //和下面的方法结果一样,这种方式其实没有必要
for (const value of arr) {
console.log(value);
}
// entries() 得到的是索引+值组成的数组的可遍历对象
for (const entries of arr.entries()) {
console.log(entries); // [0,1] [1,2] [2,3]
}
// 结合解构赋值,将索引和值结构出来[index,value]
for (const [index, value] of arr.entries()) {
console.log(index, value);
}
//对象的遍历
let es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (let e in es6) {
console.log(e);
}
2.如何更方便的使用 Iterator
Iterator 遍历器是一个统一的遍历方式,无论是数组还是对象,都可以使用iterator遍历,但是将Symbol.iterator->it->next()这种机制很麻烦,所以我们一般不会直接使用 Iterator 去遍历,通常也不会用,但是我们需要了解iterator的运行机制即可,开发人员利用这套遍历的流程封装好了,即for..of
原生可遍历和非原生可遍历
for ……of……只能循环原生可遍历数据
何为原生可遍历数据:只要有Symblo.iterator()方法且这个方法可以生成可遍历的对象就是原生可遍历
原生可遍历:数组、字符串、类数组、Set、Map
非原生可遍历:
(1) 一般的对象,对象的遍历一般使用 for…in,也可以手动添加 Symbol.iterator 方法变成可遍历对象; (2) 有 length 和索引属性的对象,可以额外添加一个方法,因为比较简单,这里给出一个小例子
//原生可遍历的例子
for (const item of [1, 2, 3]) {
console.log(item);
}
for (const item of 'hi') {
console.log(item);
}
for (const item of new Set([1, 2])) {
console.log(item);
}
for (const elem of document.querySelectorAll('p')) {
console.log(elem);
elem.style.color = 'red';
}
// 一般的对象手动添加Symbol.iterator 方法
const person = { sex: 'male', age: 18 };
person[Symbol.iterator] = () => {
let index = 0;
return {
next() {
index++;
if (index === 1) {
return {
value: person.age, //手动可随意指定
done: false
};
} else if (index === 2) {
return {
value: person.sex,
done: false
};
} else {
return {
done: true
};
}
### 最后
如果你已经下定决心要转行做编程行业,在最开始的时候就要对自己的学习有一个基本的规划,还要对这个行业的技术需求有一个基本的了解。有一个已就业为目的的学习目标,然后为之努力,坚持到底。如果你有幸看到这篇文章,希望对你有所帮助,祝你转行成功。
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**
![](https://img-blog.csdnimg.cn/img_convert/54a6b04f29167333b139d2753f60db9f.png)