前端面经总结(六)

前端面经总结(五)

SetTimeout循环输出数组

题目:

1
2
3
4
5
6
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
},0);
}
console.log(i)

由于var不是块级作用域,为全局作用域。setTimeout为异步操作,故每次执行循环,将setTimeout放入执行队列中,等全局i执行到5结束循环了再从队列中取出5次setTimeout执行,由于var i = 0全局可用,故最终输出6个5,5次setTimeout,1次全局console.log(i)

  1. 使用闭包
1
2
3
4
5
6
7
8
for(var i = 1; i < 5; i++){
(function(j){
setTimeout(function (){
console.log(j);
},0);
})(i);
}
console.log(i);

通过闭包,将i变量留在内存中,输出j时,引用外部函数变量i,i根据循环得出。

  1. let
1
2
3
4
5
6
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
},0);
}
console.log(i)

声明let是块级作用域,将i绑定到循环体的每次迭代,确保上一次迭代结束的值重新被赋值,且不会影响后面for循环的i值赋给前面的setTimeout里。又由于let是块级作用域,只在for循环的大括号内可用,故全局的console.log(i)并不存在i

js作用域

  1. 作用域

定义的变量可以产生作用/使用的区域。js作用域可以分为全局作用域局部作用域

  1. 执行上下文:当前代码运行的环境,主要分为全局级别函数级别

无论什么环境,只能存在一个全局级别的执行上下文,可以有多个函数执行上下文。函数每次被调用执行时,js引擎自动创建出一个函数上下问,并放入执行上下文堆栈中,外部上下文无法访问内部上下文中的变量,但内部上下文可以访问外部上下文中的变量

js中this指向

谁调用这个函数或方法,this关键字就指向谁

  1. 普通函数调用
1
2
3
4
5
6
7
8
9
10
11
// var name = "x1";//输出一样
function person(){
this.name = "x1";
console.log(this);
console.log(this.name);
}
person();

//output
window
x1

如上,全局对象window调用了person方法,故this指向全局对象window。故person内的this也指向window,故name为window的一个属性。

  1. 作为方法调用
1
2
3
4
5
6
7
8
9
10
var name = "xx";
var person={
name: "yy",
showName:function(){
console.log(this.name);
}
}
person.showName();//输出yy
var showNameA = person.showName;
showNameA();//输出xx

person.showName()明显为person调用showName方法,故this指向person,输出yy

而showNameA = person.showName时,showNameA相当于window的一个属性,将person.showName赋值给他,故调用showNameA时,是相当于window.showNameA。故this指向window,输出xx

  1. 作为构造函数调用
1
2
3
4
5
6
function Person(name){
this.name = name;
}
var personA = Person("xx");
console.log(personA.name);//undefined;
console.log(window.name);//xx

personA.name即window.personA.name,可见上述代码并没有定义此属性,故输出undefined;

而window.name。在personA赋值时,调用了window.Person(“xx”),此时将window的name赋值为xx。故输出window.name即为xx

  1. new
1
2
3
4
5
function Person(name){
this.name = name;
}
var personB = new Person("xx");
console.log(personB.name);

new操作符实例化对象的内部过程为

1
2
3
4
5
6
function person(name){
var o = {};
o._proto_ = Person.prototype;
Person.call(o,name);
return o;
}

由此可得,当使用new操作符实例化对象,首先创建一个新的对象,并将新对象的proto指向Person的prototype完成对原型属性和方法的继承。再通过使用call方法,将this的指向从Person改为新对象,并完成o.name = name;

此时使用new Person(“xx”);即将this指向personB,并赋值personB.name = “xx”;
故输出personB.name时,输出xx

  1. call/apply方法调用

call/apply方法最大的作用是改变this的指向,且都属于Function.prototype的一个方法。但他们的区别在于传递的参数不同,call传递函数运行作用域(this)和所有的参数序列(必须列举出来),apply传递函数运行作用域(this)和参数数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var name = "xx";
var person = {
name : "yy",
showName:function(){
console.log(this.name);
}
}
//call方法第一个参数函数运行作用域为空,则指向默认值window
Person.showName.call();//输出window.name,即xx

funtion FruitA(n1,n2){
this.n1=n1;
this.n2=n2;
this.change=function(x,y){
this.n1=x;
this.n2=y;
}
}

var fruitA=new FruitA("cheery","banana");
var FruitB={
n1:"apple",
n2:"orange"
};
//将fruitA的change方法this指向FruitB,故设置FruitB.n1 = pear;FruitB.n2 = peach;
fruitA.change.call(FruitB,"pear","peach");

console.log(FruitB.n1); //输出 pear
console.log(FruitB.n2);// 输出 peach
  1. bind方法

bind方法也是用来改变函数的this指向,且利用后续参数传参。但它与call/apply的区别为:bind方法不会立即执行,即它返回的是一个函数,执行时需要使用()调用,或将其绑定到事件上,通过事件触发调用。

1
2
fruitA.change.bind(FruitB,"pear","peach")();
document.onclick = fruitA.change.bind(FruitB,"pear","peach");
  1. eval方法

eval方法执行,this绑定到当前作用域的对象上。

1
2
3
4
5
6
7
8
9
10
var name = "xx";
var person = {
name: "yy";
showName:function(){
eval("console.log(this.name");
}
}
person.showName();//输出yy
var a = person.showName;
a();//即window.a,则this指向window,输出xx
  1. 箭头函数

箭头函数中this始终指向外部对象,且自身没有this,故自身不能new实例化,也不能使用call/apply/bind等方法来改变this指向

1
2
3
4
5
6
function timer(){
this.seconds = 0;
setInterval(() => this.seconds ++, 1000);//this指向外部对象,即实例化timer后的timer1的seconds,每秒+1
}
var timer1 = new timer();
setTimeout(() => console.log(timer1.seconds),3100);//3s后输出timer1.seconds即3

前端性能优化

  1. 优化DOM:

    • 删除不必要的代码和注释,最小化文件
    • 利用GZIP压缩文件
    • 结合HTTP缓存文件
  2. 图片懒加载,避免一次性加载过多文件导致请求阻塞

  3. 减少HTTP请求数量

    • 文件合并,图片压缩

    • 减少重定向

  4. 优化网络连接:使用CDN,将用户请求导向离用户最近的服务节点上

  5. 优化资源加载

    • 优化资源加载位置,即CSS放在head,先外链再内页,JS文件放在body底部,先外链再内页

    • 模块按需加载。

  6. 减少重绘回流

    重绘:页面中样式改变不影响它在文档流中的位置,如color/visibility等,浏览器将新样式赋予元素并重新绘制

    回流:当render tree中部分或全部元素尺寸/结构等属性改变,浏览器需要重新渲染部分或全部文档的过程

    • 避免使用层次较深的选择器,避免使用css表达式,元素适当定义高度等

    • 使用防抖或节流限制某方法的频繁触发

      防抖debounce:频繁触发的情况下,只要有足够的空闲时间才执行代码一次

      节流:一定时间内js方法只跑一次

    • 及时清理环境

  7. webpack优化

    • 打包公共代码

    • 动态导入,按需加载

    • 删除无用代码

CSS居中(行内/块级)

水平

  1. 行内
1
2
3
//父级元素
display: block;//设置为块级
text-align: center;//设置行内水平居中
  1. 块级元素

    • 宽度固定: margin: 0 auto;

    • 不定宽度:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
          //方法1
      //子元素
      display:inline-block/inline;//改为行内块级元素/行内元素
      //父元素
      text-align: center;

      //方法2
      transform: translateX(-50%);//在x轴位移50%

      //方法3
      //父元素
      display: flex;
      justify-content: center;
      ```
      ### 垂直

      1. 行内

      - 单行:设置行高=盒子高,即父元素和子元素height相等

      - 多行:设置```display:table-cell;vertical-align:middle;
  2. 块级

    • position:

      1
      2
      3
      4
      5
      6
      //父元素
      position: relative;
      //子元素
      position: absolute;
      top: 50%;
      margin: auto;
    • flex:display: flex; align-items: center;

垂直水平

  1. 已知高宽
1
2
3
4
5
6
7
8
9
//父元素
position: relative;
//子元素
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
  1. 未知高宽

    • 定位属性

      1
      2
      3
      4
      5
      6
      7
      //父元素
      position: relative;
      //子元素
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translateX(-50%) translateY(-50%);
    • flex

      1
      2
      3
      display: flex;
      justify-content: center;
      align-items: center;

闭包的理解及优缺点

闭包指有权访问另一个函数作用域中的变量的函数。可以在函数外部访问到函数内部的局部变量,且这些变量始终保存在内存中,不会随函数的结束而自动销毁

  • 优点

    1. 保护函数内变量安全

    2. 内存中保存变量,可以做缓存

    3. 匿名自执行函数可以减少内存消耗

  • 缺点

    1. 被引用的私有变量不能被销毁,增大了内存消耗

    2. 闭包涉及跨域访问,导致性能损失

position配置的相对元素

  • absolute:相对父级元素

  • relative:相对默认位置

  • fixed:相对浏览器窗口

  • static:默认位置

git常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ git config -global user.name <name> #设置提交者名字
$ git config -global user.email <email> #设置提交者邮箱
$ git config -global core.editor <editor> #设置默认文本编辑器
$ git config -global merge.tool <tool> #设置解决合并冲突时差异分析工具
$ git config -list #检查已有的配置信息
$ git clone <url> #克隆远程版本库
$ git init #初始化本地版本库

$ git add . #添加所有改动过的文件
$ git add <file> #添加指定的文件
$ git mv <old> <new> #文件重命名
$ git rm <file> #删除文件
$ git rm -cached <file> #停止跟踪文件但不删除
$ git commit -m <file> # 提交指定文件
$ git commit -m “commit message” #提交所有更新过的文件
$ git commit -amend # 修改最后一次提交
$ git commit -C HEAD -a -amend #增补提交(不会产生新的提交历史纪录)

$ git reset -hard HEAD #撤消工作目录中所有未提交文件的修改内容 比如删除也可以撤销
$ git checkout HEAD <file1> <file2> #撤消指定的未提交文件的修改内容
$ git checkout HEAD. #撤消所有文件
$ git revert <commit> #撤消指定的提交

$ git log #查看提交历史
$ git branch -v #每个分支最后的提交
$ git status #查看当前状态
$ git diff #查看变更内容

$ git branch #显示所有本地分支
$ git checkout <branch/tagname> #切换到指定分支或标签
$ git branch <new-branch> #创建新分支
$ git branch -d <branch> #删除本地分支
$ git tag #列出所有本地标签
$ git tag <tagname> #基于最新提交创建标签
$ git tag -d <tagname> #删除标签

$ git merge <branch> #合并指定分支到当前分支
$ git rebase <branch> #衍合指定分支到当前分支

$ git fetch <remote> #从远程库获取代码
$ git pull <remote> <branch> #下载代码及快速合并
$ git push <remote> <branch> #上传代码及快速合并
$ git push <remote> : <branch>/<tagname> #删除远程分支或标签
$ git push -tags #上传所有标签

js原生以class获取元素更改样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function getByClass(oParent, sClass){
var aResult=[];
var aEle=oParent.getElementsByTagName('*');

for(var i=0;i<aEle.length;i++){
if(aEle[i].className==sClass)
{
aResult.push(aEle[i]);
}
}

return aResult;
}

//用法如下
window.onload=function (){
var oUl=document.getElementById('ul1');
var aBox=getByClass(oUl, 'box');

for(var i=0;i<aBox.length;i++){
aBox[i].style.background='red';
}
};

java/js类的区别

  1. java面向对象, 有继承/封装/多态

  2. js基于对象,没有继承/封装/多态,js的继承通过原型链或娶她方法实现

  3. java类不可以直接运行,js的可以直接调用运行

前后端分离实现

核心思想:前端html页面通过ajax调用后端的restuful api接口并使用json数据进行交互。

涉及到的内容:

  1. 技术选型

  2. 搭建框架

  3. 设计接口和数据模型

  4. 解决跨域

  5. 数据存储和部署

浏览器隐身模式

浏览器隐身模式时,不会保存浏览记录/cookie/网站数据/表单信息。即浏览网页时存储在本地的数据。

jquery和Vue异同

  1. jquery首先需要获取dom对象,在对对象进行各项操作

  2. vue首先将值和js对象绑定,修改js对象的值,vue自动把dom值更新。即通过vue对象将数据和view分离,实现相互绑定

jquery和原生js异同

  1. 原生js会等到dom元素加载完毕且图片加载完毕后再执行,jquery会等到dom加载完成但不会等图片加载完成

  2. jquery可以写多个入口函数你,原生js不行

echart如何实现和Vue的响应

使用Vue中的watch监测数据变化,当数据更新后,调用echarts中图表的绘制方法即可。

1
2
3
4
5
6
watch: {
all(curVal) {
// 数据发生改变,便调用图表绘制方法传入最新数据绘制
this.drawLine(curVal.counts, curVal.days);
}
},

手写call/bind/apply

call

1
2
3
4
5
6
7
8
function call(context){
context = context || window;
context.fn = this;
const args = Array.from(arguments).slice(1);
const res = argument.length > 1 ? context.fn(...args):context.fn;
delete context.fn;
return res;
}

apply

1
2
3
4
5
6
7
function apply(context){
context = context || window;
context.fn = this;
const res = arguments[1]? context.fn(...arguments[1]) : context.fn();
delete context.fn;
return res;
}

bind

将新的this绑定到某函数func上,并返回func的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function bind(context,...args){
const fn = this;//调用bind的函数func
return function(...args){
return fn.apply(context,...args.concat(...args2));
}
}
//另一种
function bind(context){
console cxt = JSOn.parse(JSON.stringify(context)) || window;
cxt.func = this;
const args = Array.from(arguments).slice(1);
return function(){
const allArgs = args.concat(Array.from(arguments));
return allArgs.length > 0 ? cxt.func(...allArgs):cxt.func();
}
}

手写promise

Promise特征:

  1. 三个状态: pending/fulfilled/rejected。状态默认为pending,且只能从pendingfulfilled或从pendingrejected,一旦确认不会再改变

  2. new promise时,需要传递executor()执行器,执行器立刻执行。executor接收两个参数,分别是resolve/reject

  3. promise使用value保存成功状态的值,可以是undefined/thenable/promise

  4. promise使用reason保存失败状态的值

  5. promise必须有一个then方法,接收两个参数,分别是成功回调onFulfilled和失败回调onRejected

  6. 调用then,promise成功,则执行onFulfilled,参数为promise的value。promise失败,则执行onRejected,参数为promise的reason

  7. then中抛出异常,则将异常作为参数,传递给下一个then失败回调onRejected

promise原理及基础版代码(无链式调用和值穿透)

原理:发布订阅者模式,通过两个队列缓存成功的回调和失败的回调。当执行函数executor执行时,触发resolve和reject,依次调用成功或失败的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

class Promise{
constructor(executor){
//默认状态为pending
this.status = PENDING;
//存放成功状态值
this.value = undefined;
//存放失败状态值
this.reason = undefined;
//存放成功回调
this.onResolvedCallbacks = [];
//存放失败回调
this.onRejectedCallbacks = [];

//成功方法
let resolve = (value) => {
if(value instanceof Promise){
return value.then(resolve,reject);
}
setTimeout(()=>{
if(this.status === PENDING){
//状态置为成功
this.status = FULFILLED;
this.value = value;
//依次执行成功回调函数
this.onResolvedCallbacks.forEach(fn=>fn());
}
})
}

//失败方法
let reject = (reason)=>{
setTimeout(()=>{
if(this.status === PENDING){
//状态置为失败
this.status = REJECTED;
this.reason = reason;
//依次执行失败回调函数
this.onRejectedCallbacks.forEach(fn => fn());
}
})
}

try{
//立即执行executor方法,将resolve和reject传递出去
executor(resolve,reject);
}catch(error){
reject(error);
}
//then方法
then(onFulfilled,onRejected){
if(this.status === FULFILLED){
//链式调用
return fulfillPromise = new Promise((resolve,reject) => {
setTimeout(()=>{
try{
let result = onFulfilled(this.value);
resolve(result);
}catch(e){
reject(e);
}
})
})
//调用成功的回调函数
//onFulfilled(this.value);
}
if(this.status === REJECTED){
return rejectPromise = new Promise((resolve,reject) => {
setTimeout(()=>{
try{
let result = onRejected(this.reason);
resolve(result);
}catch(e){
reject(e);
}
})
})
//调用失败的回调函数
//onRejected(this.reason);
}
//若状态为pendig,依次将成功/失败回调函数存放起来
if(this.status=== PENDING){
this.onResolvedCallbacks.push(()=>{
try{
let result = onFulfilled(this.value);
resolve(result);
}catch(e){
reject(e);
}

});
this.onRejectedCallbacks.push(()=>{
try{
let result = onRejected(this.reason);
resolve(result);
}catch(e){
reject(e);
}

});
}
}
}

}

promise.all

promise.all解决并发问题,多个异步并发获取最终的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Promise.all = function(values){
if(!Array.isArray(values)){
const type = typeof values;
return new TypeError(`TypeError: ${type} ${values} is not iterable`);
}
return new Promise((resolve,reject)=>{
let resultArr=[];
let orderIndex = 0;
const processResultByKey = (value,index)=>{
resultArr[index] = value;
//若当前顺序与结果数量一致,则输出最终结果
if(++orderIndex === values.length){
resolve(resultArr);
}
}
for(let i = 0;i<value.length;i++){
let value = value[i];
if(value && typeof value.then === "function"){
value.then((value)=>{
processResultByKey(value,i);
},reject);
}else{
processResultByKey(value,i);
}
}
});
}

promise.race()

promise.race用来处理多个请求,哪个请求最先完成先用哪个

1
2
3
4
5
6
7
8
9
10
11
12
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i<promises.length;i++){
let val = promises[i];
if(val && typeof val.then === "function"){
val.then(resolve,reject);
}else{//普通值
resolve(val);
}
}
})
}

排序算法

冒泡排序

1
2
3
4
5
6
7
8
function bubble(arr){
for(let i = 0;i<arr.length -1;i++){
for(let j = 0; j<arr.length - 1- i;j++){
if(arr[j]>arr[j+1])
[arr[j],arr[j+1]] = [arr[j+1],arr[j]];
}
}
}

时间复杂度On2

优化(有序数组冒泡排序)

设置标识位,若没有发生交换,则说明有序,不需要排序

1
2
3
4
5
6
7
8
9
10
11
12
13
function bubble(arr){
let flag = true;
for(let i = 0;i < arr.length-1;i++){
for(let j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
flag = false;
[arr[j],arr[j+1]] = [arr[j+1],arr[j]];
}
}
if(flag)
break;
}
}

优化(部分有序)

设置变量记录有序的部分,则每次冒泡到此就不继续排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let arr = [3,2,1,4,5,6]
function bubble(arr){
for(let i = 0;i < arr.length-1;i++){
let len = arr.length -1-i;
let pos = -1;//记录有序的下标
for(let j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
pos = j;
[arr[j],arr[j+1]] = [arr[j+1],arr[j]];
}
}
len = pos;
if(pos == -1){
break;
}
}
}

快速排序

时间复杂度O(nlogn)

1
2
3
4
5
6
7
8
9
10
11
12
function quicksort(arr){
let mid = Math.floor(arr.length / 2);
let middle = arr.splice(mid,1)[0];
let left,right = [];
for(let i=0;i<a.length -1;i++){
if(arr[i] > middle)
right.push(arr[i]);
else
left.push(arr[i]);
}
return quicksort(left).concat([mid],quicksort(right));
}

归并排序

时间复杂度O(nlogn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function mergesort(arr){
function merge(a,b){
let left,right = 0;
let result = [];
while(left < a.length && right < b.length){
if(a[left] < b[right]){
result.push(a[left]);
left++;
}else{
result.push(b[right]);
right++;
}
}
while(left < a.length){
result.push(a[left]);
left++;
}
while(right < b.length){
result.push(b[right]);
right++;
}
}
function mergeArr(arr){
if(arr.length === 1)
return arr;
let mid = Math.floor(arr.length / 2);
let left = arr.slice(0,mid);
let right = arr.slice(mid,arr.length);
return merge(mergeArr(left),mergeArr(right));
}
}

树算法

webpack原理

  1. 初始化参数

  2. 根据参数初始化compiler对象,注册所有配置插件,监听webpack构建生命周期的事件节点,执行对象run方法执行编译

  3. 通过配置的entry入口解析文件构建语法树

  4. 递归调用所有配置的loader对文件进行转换,再找出模块依赖的模块,直到所有入口依赖的文件都经过处理

  5. 根据递归得到每个文件的结果,根据entry配置生成代码块chunk

  6. 输出所有chunk到文件系统。

webpack两个核心元素及常用Loader,Plugin

  1. 核心元素

    • compiler对象:类似于webpack的司机,有run-启动编译/newCompilation-创建编译器/emitAsset-处理编译后结果等方法
    • compilation对象:类似于webpack的发动机,被compiler用来创建新的编译或构建。它能访问所有模块和他们的依赖
  2. loader

    • vue-loader:.vue文件加载器
    • sass-loader:sass&scss文件加载器
    • postcss-loader:css样式处理工具,自动添加浏览器适配前缀,压缩css样式等
    • css-loader:css模块加载器
    • babel-loader:webpack加载器
    • eslint-loader:代码检查工具
  3. plugin

    • html-webpack-plugin:自动引入js资源文件到入口index.html文件
    • mini-css-extract-plugin:抽离css样式到独立的.css文件

webpack中plugin哪个时期触发

  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2024 Aweso Lynn
  • PV: UV: