2021-03-31更新前端面试题

写在前面:说到底,就算你证明了 A 比 B 牛逼,也不意味着你或者你的项目就牛逼了… 比起争这个,不如多想想怎么让自己变得更牛逼吧。

css

css3盒模型

1.每个元素都相当于一个巨型盒子,是由内边距(padding),外边距(margin),边框(border),内容(content)组成。
2.
盒子模型有两种 标准盒模型(w3c盒模型),ie盒模型
3.
我们定义的width
在标准盒模型(w3c盒模型)就是content的宽
在ie盒模型就是content+padding+margin的宽
4.
通过设置box-sizing:content-box 变为标准盒模型(w3c盒模型)
通过设置box-sizing:border-box ie盒模型

js

1、get和post区别

1、位置不同 get在url里发送。pos在请求体里发送
2、速度不同 get传输速度比post快
3、大小不同 get发送数据有限制 post发动没有限制
4、安全性不同 post在请求体里发送,安全性更高

2、es6+新特性一览

1、模块化module export import
2、箭头函数
3、模板字符串
4、函数传参默认参数
5、解构赋值
6、promise
es7:
7、includes()判断数据是否包含指定的值 返回ture false
es8:
8、async/await
9、object.values()
10、object.keys()
11、object.entries()
es9:
…展开运算符

3、跨域的解决方式

1、jsonp 前端使用jsonp来解决跨域 (只支持get请求,容易受到XSS攻击)
2、cors:后端配置cors来解决跨域

//app.js中设置
var app = express();
// CORS:设置允许跨域中间件
var allowCross= function (req, res, next) {
  // 设置允许跨域访问的 URL(* 表示允许任意 URL 访问)
  res.header("Access-Control-Allow-Origin", "*");
  // 设置允许跨域访问的请求头
  res.header("Access-Control-Allow-Headers", "X-Requested-With,Origin,Content-Type,Accept,Authorization");
  // 设置允许跨域访问的请求类型
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  // 设置允许服务器接收 cookie
  res.header('Access-Control-Allow-Credentials', 'true');
  next();
};
app.use(allowCross);

3、node中间件
4、nginx反向代理(下载安装nginx 修改配置)

4、ajax原理

1、浏览器让xhr去服务端获取数据
2、xhr向服务端请求数据
3、服务端返回数据给xhr
4、xhr告诉浏览器接收到了服务器的数据了
5、浏览器拿到xhr返回的数据来响应界面
6、创建ajax对象 let xhr = new XMLHttpRequest()
7、向服务器发送请求 xhr.open()
8、服务端响应xhr.send()

//1-创建核心对象
//该对象有兼容问题,低版本浏览器应使用ActiveXObject
const xhr = new XMLHttpRequest();
//2-连接服务器
//open(method,url,async)
xhr .open("POST","http://localhost:3000",true)
//设置请求头
xhr .setRequestHeader("Content-Type", "application/x-www-form-urlencoded")//3-发送请求
//send方法发送请求参数,如为GET方法,则在open中url后拼接
xhr .send({id:666})
//4-接收服务器响应
//onreadystatechange事件,会在xhr 的状态发生变化时自动调用
xhr .onreadystatechange =function(){
  //状态码共5种:0-未open  1-已open  2-已send  3-读取响应  4-响应读取结束
  if(xhr .readyState == 4 && xhr .status == 200){
  	alert("ajax请求已完成")
  }
}

5、深浅copy

浅拷贝只copy指针,而不是复制本身。数据会相互影响
方法:
1、object.assign() : 需要注意目标对象只有一层时候。是深copy
2、扩展运算符

深拷贝是将一个对象从内存中完全的拷贝出
方法:
1、手写遍历递归赋值
2、结合使用json.parse()和json.stringify()

6、js数据类型

基本数据类型存在栈中 复杂数据类型栈中存储了指针 该指针指向数据实体存在的堆中
1、基本数据类型 String Number Boolean Null Underfined Symbol(es6)
2、引用数据类型 Object Array Function

7、js判断数据类型的方法

1.利用typeof可以判断数据类型
2.A instanceof B 可以判断a是否为b的实例 (不能检测null和underfined)
3.B.constructor == A 可以判断A是否为B的原型 (不过函数的constructor是不稳定的,这个主要体现在把类的原型进行重写,在重写的过程中可以会覆盖掉constructor,这样检测出来的结果就不准确了)
4.Object.prototype.toSring.call( xxx ) [object,object]

8、var let const的区别

var 函数作用域 变量提升
let const 块级作用域 无变量提升

9、什么是执行上下文和执行栈

变量和函数的执行上下文,决定了他们的行为以及可以访问那些数据,每个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在这个对象上(DOM中的全局上下文关联的对象就是window对象)
每个函数调用自己的上下文,当代码执行流进入函数时,函数的执行上下文被推到一个执行栈中,在函数执行完之后,执行栈会弹出该函数的上下文,在其上的所有变量和函数都会被销毁,并将控制权返还给之前的执行上下文,js的执行流就是通过这执行栈进行控制的。

10、什么是作用域和作用域链

作用域可以理解为一个独立的底盘,可以理解为标识符所生效的返回。作用域最大的作用就是隔离变量,不同的作用域下同名变量不会有冲突,es6中有全局作用域、函数作用域、块级作用域三层概念。
当一个变量在当前的块级作用域没有被定义,回向父级作用域寻找,如果父级作用域没有找到,就会在一层一层的向上寻找,直到找到全局作用域为止,这种一层一层的关系,就是作用域链

11、作用域和执行上下文的区别是什么

1、函数的执行上下文只在函数被调用时候生成,而作用域在创建的时候已经生成
2、函数的作用域会包含若干个执行上下文(也有可能为零个,当函数没有被调用时)

12、this的指向问题

1、全局作用域中的函数 this指向window

 function aaa() {  console.log(this);}  aaa()

2、对象内部的函数 this指向对象本身

   var a = 222
      var obj ={ 
        a:2,
        fn:function(){
          console.log(this.a);
        }
      }
      obj.fn()  //2

3、构造函数 this指向生成的实例

function Person(name,age) {
    this.name = name
     this.age = age
   }
var p1 = new Person('战三',111)

4、由apply call bind改造的函数,其this指向第一个参数

fn.apply(db,[a,b]) 后面参数需要为数组
fn.call(db,a,b) 后面参数为字符串
fn.bind(db,a,b)() 返回一个新函数 需要再次执行

5、箭头函数 箭头函数没有自己的this 看其外层是否有函数,如果有,外层函数的this就是内部箭头函数的this 如果没有 则this是window

13、什么是闭包

闭包就是引用了其他函数作用域中的变量函数,这种模式通常是在嵌套结构中实现,里面的函数访问外面函数的变量,外面的变量的是这个内部函数的一部分,必报有如下作用:
1、加强封装模拟实现私有变量
2、实现常驻内存变量
闭包不能滥用,否则会导致内容泄露,影响网页的性能,闭包使用完了后,要立即是释放资源,将引用变量指向null

14、什么是原型和原型链

原型:js声明构造函数(用来实例化对象的函数)时,会在内存中创建一个对应的对象,这个对象就是原函数的原型,构造函数默认有一个prototype属性,prototype的值指向函数的原型,同时原型中也有一个constructor属性,constructor指向原函数。
通过构造函数实例化出来的对象,并不具有prototype属性,其默认有个一个__proto__属性。__proto__的值指向构造函数的原型对象,在原型对象上添加或者修改的属性,在所有实例化出的对象上都可以共享。
原型链:在实例化的对象中访问一个属性时,首先会在该对象内部寻找,如果找不到,则会向其__proto__指向的原型中查找,找不到继续向上级原型查找,知道找到或object.ptototype为止,这种链状过程既原型链

15、何为防抖和节流 如何实现

防抖和节流都是防止短时间内高频触发事件的方案
防抖原理:如果一定时间内多次执行了某事件,则只执行其中的最后一次
节流原理:要执行的事件每隔一段时间会被冷却,无法执行
应用场景:搜索实时检索,滚动改变相关事件在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16、实现异步的方法

1、回调函数模式(会造成回调地狱)
2、事件监听模式
3、发布订阅模式
4、promise :promise三种状态(pending(初始化),fulfilled(成功),rejected(失败))
5、async/await

17、实现继承的方法

1、原型链继承

function Person() {}
Person.prototype.eat = function () {
  console.log('eatting...')
}
function Man (name){
  this.name = name
}
Man.prototype = new Person()
var man1 = new Man('zhansan')
man1.eat()
console.log(man1.name);

2、构造函数继承(经典继承)

 function Person() {
 this.color = ['black'];
 }
 function Man() {
   Person.call(this);
 }
 var man1 = new Man();
 man1.color.push('white');
 var man2 = new Man();
 man2.color.push('red');
 console.log(man1.color); // ['black', 'white']
 console.log(man2.color); // ['black', 'red']

3、es6继承

 class Person {
 //对象的属性
  constructor(props) {
    this.name = props.name || "Unknown";
  }
  //父类的方法
  eat() {
    console.log(this.name + "吃披萨");
  }
}
//class继承
class Man extends Person {
  //props是继承过来的属性,myAttr是自己的属性
  constructor(props, myAttr) {
    super(props); //调用实现父类的构造函数,相当于获得父类的this指向
    this.type = props.type || "Unknown"; //父类的属性
    this.attr = myAttr; //自己的私有属性
  }
  //自己私有的方法
  run() {
    console.log(this.name + "喜欢交朋友");
  }
  myattr() {
    console.log(this.type + this.attr);
  }
}
//通过new实例化
var man = new Man(
  {
    name: "小明",
    type: "男生",
  },
  "爱唱歌"
);
man.eat();
man.run();
man.myattr();

js基础算法

js数组去重

1.循环 + indexOf
2.es6 set方法
3.arr.sort() 前后对比
4.arr.filter()
5.arr.reduce

var arr1 = [1, 2, 3, 4, 2, 3, 4]
function repeat(arr) {
	var newArr = []
	  for (let i = 0; i < arr.length; i++) {
	    if (newArr.indexOf(arr[i]) === -1) {
	      newArr.push(arr[i])
	    }
  	}
  return newArr
}
repeat(arr1)
var arr1 = [1, 2, 3, 4, 2, 3, 4];
function repeat(arr) {
  var hash = {};
  var newArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (hash[arr[i]] === undefined) {
      newArr.push(arr[i]);
      hash[arr[i]] = 1;
    }
  }
  console.log(hash);
  return newArr;
}

2.es6 set方法

let arr = [1, 5, 6, 1, 9, 9, 2, 1];
 function unique(arr) {
   // return Array.from(new Set(arr))
   return [...new Set(arr)]
 }
 console.log(unique(arr));

3.arr.sort() 当前的元素和上一个元素对不
4.arr.filter() 当前元素的下标不等于找到的下标 就证明前面出现过 (舍弃)

 let arr = [1, 5, 6, 1, 9, 9, 2, 1];
      function unique(arr) {
        if (!Array.isArray(arr)) {
          console.log("type error!");
          return;
        }
        return arr.filter((item, index) => {
          console.log(item+'--->'+ arr.indexOf(item) + '--->' +index);
          // 1-- -> 0-- -> 0
          // 5-- -> 1-- -> 1
          // 6-- -> 2-- -> 2
          // 1-- -> 0-- -> 3
          // 9-- -> 4-- -> 4
          // 9-- -> 4-- -> 5
          // 2-- -> 6-- -> 6
          // 1-- -> 0-- -> 7
          return arr.indexOf(item) === index;
        });
      }
      console.log(unique(arr)); // [1, 5, 6, 9, 2]

5.arr.reduce()

 let arr = [1, 5, 6, 1, 9, 9, 2, 1];
  function unique(arr) {
    if (!Array.isArray(arr)) {
      console.log("type error!");
      return;
    }
    return arr.reduce((prev, cur,) => {
      return prev.includes(cur) ? prev : [...prev, cur]
    },[])
  }
  console.log(unique(arr)); // [1, 5, 6, 9, 2]

js数组排序

找到了一篇很好的文章
1.冒泡排序
2.快速排序
3.选择排序
4.插入排序
外层控制轮数 内层控制对比的次数 挨着元素对比

  var arr = [2,3,1,4,5,8,2]
      // 冒泡排序
   Array.prototype.swap = function (cur, orgin) { //大  小
      var temp = this[orgin]
      this[orgin] = this[cur]
      this[cur] = temp
    }
    function mySort(arr) {
      if (Array.isArray(arr) || arr.length === 1) arr
      for (let i = 0; i < arr.length-1; i++) {
        for (let j = 0; j < arr.length-i-1; j++) {
          if (arr[j] > arr[j+1]) {
            arr.swap(j,j+1)
          }
        }
      }
      return arr
    }

2.选择排序

function selectSort(arr) {
  for (let i = 0; i < arr.length; i++) {
     let minIndex = i
     for (let j = i+1; j < arr.length; j++) {
       if (arr[j]<arr[minIndex]) {
         minIndex = j
       }
     }
     arr.swap(i,minIndex)
     // [arr[i],arr[minIndex]] = [arr[minIndex], arr[i]]
   }
   return arr
 }
 console.log(selectSort(arr));

3、快速排序

function quickSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  var pivot = arr[Math.floor(arr.length / 2)];
  arr.splice(arr.indexOf(pivot), 1);
  var left = [];
  var right = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
}
console.log(quickSort(arr));

vue

1、spa单页面的理解,优缺点是什么?

spa(single-page application)仅在web页面初始化时加载对应的html、css、js。一旦页面加载完成,spa不因为用户的操作而进行页面的重新加载或跳转;而是利用路由机制实现html内容的交换,可以避免页面的重新加载。
优点:
1.用户体验好、快。内容改变不需要重新加载整个页面,避免了不必要的跳转和渲染
2.spa相对服务器压力小
3.前后端分离,架构清晰,前端负责交互逻辑,后端负责数据处理
缺点:
1.首屏加载慢:为实现spa页面,需要加载页面的时候将js、css、统一加载,部分页面按需加载
2.不利于seo:由于所有的内容都在一个页面中动态替换展示,所以seo上有天然的弱势。

2、new Vue() 发生了什么

new Vue()是创建了Vue实例,内部执行了根实例的初始化过程

3、vue.use 是干什么用的

vue.use是用来使用插件的,我们可以在插件中扩展全局组件、指令、原型方法。

4、vue响应式数据的理解

根据数据类型来做不同的处理,数组和对象类型值发生变化时劫持
1、对象内部通过defineReactive方法,使用Object.defineProperty()监听数据属性的ge来进行数据的依赖收集,再通过set来完成数据更新的派发
2、数组则是重写数组方法来实现的。扩展他的七个变量方法,通过监听这些方法做到依赖收集和派发更新;push pop shift unshift splice reverse sort
3、vue3中使用了proxy来实现响应式数据

5、Vue的模板编译原理?

1、template模板转化成ast语法树 .vue中的template是通过vue-loade来进行处理的,并不是通过运行时的编译
2、对静态语法做静态的标记
3、重新生成代码

模板引擎的实现原理就是通过new Function + with来实现的 vue-loader中处理template属性主要是靠的是vue-template-compiler

6、Proxy和object.defineproperty优略势对比?

1、proxy可以直接监听对象而非属性
2、proxy可以直接监听数据变化
3、proxy有多达13种拦截方法,包括但不限于apply、ovnKeys、deleteProperty,has等
4、proxy返回的是一个新对象,而defineproperty只能遍历对象属性直接修改
5、proxy作为新标准将受到浏览器商家的重点支持的性能优化,也就是传说中的新标准红利
6、object.defineproperty兼容性好,支持ie9,而proxy存在浏览器兼容性问题

7、vue生命周期?

1、创建前:beforeCreate

获取不到props和data里面的数据 数据的初始化在initstate中

2、创建后:created

可以获取到数据,但组件没有挂载,所以看不到

3、挂载前:beforeMount

开始创建VDOM

4、挂载后:mounted

将VDOM渲染为真正的DOM并渲染数据,如果有子组件,会递归挂载子组件,并当所有的子组件全部挂载完毕,才会执行根组件的挂载钩子

5、更新前:beforeUpdate

数据更新之前触发

6、更新后:update

数据更新之后触发

7、销毁前:beforeDestory

组件销毁前 适合移除事件,定时器等 否则可能造成内存泄漏问题 如果有子组件 也会递归销毁子组件

8、销毁后:destory

9、激活前:activated
10、激活后:deactivated

keep-active激活后才会触发的两个生命周期 keep-active包括的组件不会进行销毁,而是缓存到内存中并执行deactivated钩子函数,命中缓存渲染后执行actived钩子函数

8、vue中组件的data为什么是一个函数?

每次使用组件时都会对组件进行实例化操作,并且会调用data函数返回一个对象作为组件的数据源,这样可以保证多个组件间数据互不影响,如果data是一个对象的话,对象是引用类型,会影响多有实例,所以为了保证组件不同实例之间data不冲突,data必须是一个函数
当我们使用new Vue()时 data写成对象和函数都是可以的,因为new Vue的方式生成一个根组件,也就不存在共享data的情况了

9、组件通信的几个方式?

组件通信一般分为

  • 父传子

this.$emit (‘tellFather’) @tellFather=“sonEvent”
v-bind:属性(父)/ props接受(子)

  • 兄弟组件

1.通过父组件作中间传递
2.bus总线
3.this. p a r e n t . parent. parent.children ($children 中可以通过组件 name 查询到需要的组件实例,然后进行通信)

const $bus = new Vue()
bus.$emit("say",message)
bus.$on('"say',(message)={console.log(message)})
  • 跨多层级组件通信
    vue2.2新增的API 不推荐直接在业务中使用 provide inject
// 父组件 A
export default {
  provide: {
    data: 1
  }
}
// 子组件 B
export default {
  inject: ['data'],
  mounted() {
    // 无论跨几层都能获得父组件的 data 属性
    console.log(this.data) // => 1
  }
}
  • 任意组件

1.vuex
2.localstorage/sessionStorage
3.bus总线都可以实现

9、keep-alive?

页面缓存 include/exclude 包含不包缓存的组件

10、vue-router

1、hash模式:通过监听hash的变化来执行js代码,从而实现页面的改变
2、history模式:通过historyApi+popState只要刷新这个url就会请求服务器,每一次路由改变都会重新加载
3、路由守卫

const whiteList = ["login", "index"];
router.beforeEach((to, from, next) => {
  const hasToken = false;
  if (hasToken) {
    if (to.path === '/login') {
      next({path:'/index'})
      return
    }
    next()
  } else {
    if (whiteList.indexOf(to.name) !== -1) {
      next()
    } else {
      next('/login')
    }
  }
});

beforeEach 路由前守卫
beforeEnter 路由后守卫
4、router传参
query ====》this. r o u t e r . p u s h ( p a t h : " / x x x " . q u e r y : i d : i d ) = = = 》 t h i s . router.push(path:"/xxx".query:{id:id}) ===》 this. router.push(path:"/xxx".query:id:id)===this.route.query.id
params ====》this. r o u t e r . p u s h ( n a m e : " / x x x " . p a r a m s : i d : i d ) = = = 》 t h i s . router.push(name:"/xxx".params:{id:id}) ===》 this. router.push(name:"/xxx".params:id:id)===this.route.params.id

11、vue页面刷新一般使用

1、forceUpdate() 迫使Vue实例重新render 渲染虚拟Dom(只会触发beforupdate updated)
2、数据改变儿而改变Dom结构的时候的操作都应该放进Vue.nextTick()回调函数中
3、$set

12、v-if和v-show

v-if真正的条件渲染,只会在条件为真时才会渲染 切换时开销大
v-show不管条件是什么都会渲染 渲染时开销大 通过控制display来进行显示隐藏

13、computed和watch的区别

computed:计算属性,依赖其他属性值来进行计算,并且computed的值有缓存,只有他的依赖的属性值发生改变时,computed才会重新计算
watch:监听属性,监听数据的变化,每当监听的数据发生变数都会执行回调,惊醒后续的操作

14、v-mode的原理

v-mode本事是一个语法糖,可以看成:value @input方法的语法糖

15、vue性能优化

1、尽量减少data中的数据
2、防抖节流
3、key保持唯一
4、更多情况下使用v-if代替v-show
5、spa页面采用keep-alive缓存组件
6、第三方模块采用按需加载
7、图片懒加载
8、使用cdn加载第三方模块
9、压缩代码

16、vuex

vuex是vue的状态管理模式 (辅助函数 mapstate …)
1、state 存储状态(变量)
2、getter 对数据获取之前再次编译,可以理解为state的计算属性
3、mutations 修改状态的方法
4、actions 异步操作
5、modules store的子模块 让每个模块拥有自己的state mutation getters action 使得结构非常清晰,方便管理

17、vue3.0有什么变化

1、使用proxy代替defineproperty实现响应式数据
2、api进行改变 vue2组件库与vue3组件库不互通
3、创建vue实例用 impor { createpp } from ‘vue’ const app = createApp()
4、删除掉了过滤器 建议使用方法或者计算属性
vue的插槽

18、vue插槽

插槽就是子组件中提供给父组件使用的一个占位符,用表示。父组件可以在这个占位符中填充任何模板代码,如html、组件等,填充的内容会替代子组件的标签
1、默认插槽:如果父组件没有传值到子组件。那子组件就用自己默认的内容显示在插槽上
2、具名插槽:子组件中插槽的位置用名称命名好,父组件要用v-slot 名称来显示在子组件中相对应的插槽
3、作用域插槽:作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传递过来的插槽数据来进行不同的方式展示和内容填充插槽内容

19、v-for的key有什么作用

需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置去插入新的节点,key的作用主要是为了高效的更新虚拟DOM

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值