关于前端:美团前端二面高频面试题合集

4次阅读

共计 7354 个字符,预计需要花费 19 分钟才能阅读完成。

手写 bind、apply、call

// call

Function.prototype.call = function (context, ...args) {
  context = context || window;

  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  context[fnSymbol](...args);
  delete context[fnSymbol];
}
// apply

Function.prototype.apply = function (context, argsArr) {
  context = context || window;

  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  context[fnSymbol](...argsArr);
  delete context[fnSymbol];
}
// bind

Function.prototype.bind = function (context, ...args) {
  context = context || window;
  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  return function (..._args) {args = args.concat(_args);

    context[fnSymbol](...args);
    delete context[fnSymbol];   
  }
}

节流

节流(throttle):触发高频事件,且 N 秒内只执行一次。这就好比公交车,10 分钟一趟,10 分钟内有多少人在公交站等我不论,10 分钟一到我就要发车走人!相似 qq 飞车的复位按钮。

核心思想 :应用 工夫戳或标记 来实现,立刻执行一次,而后每 N 秒执行一次。如果 N 秒内触发则间接返回。

利用:节流常利用于鼠标一直点击触发、监听滚动事件。

实现:

// 版本一:标记实现
function throttle(fn, wait){
    let flag = true;  // 设置一个标记
    return function(...args){if(!flag) return;
        flag = false;
        setTimeout(() => {fn.call(this, ...args);
            flag = true;
        }, wait);
    }
}

// 版本二:工夫戳实现
function throttle(fn, wait) {
    let pre = 0;
    return function(...args) {let now = new Date();
        if(now - pre < wait) return;
        pre = now;
        fn.call(this, ...args);
    }
}

代码输入问题

function fun(n, o) {console.log(o)
  return {fun: function(m){return fun(m, n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);

输入后果:

undefined  0  0  0
undefined  0  1  2
undefined  0  1  1

这是一道对于闭包的题目,对于 fun 办法,调用之后返回的是一个对象。咱们晓得,当调用函数的时候传入的实参比函数申明时指定的形参个数要少,剩下的形参都将设置为 undefined 值。所以 console.log(o); 会输入 undefined。而 a 就是是 fun(0)返回的那个对象。也就是说,函数 fun 中参数 n 的值是 0,而返回的那个对象中,须要一个参数 n,而这个对象的作用域中没有 n,它就持续沿着作用域向上一级的作用域中寻找 n,最初在函数 fun 中找到了 n,n 的值是 0。理解了这一点,其余运算就很简略了,以此类推。

说一下 JSON.stringify 有什么毛病?

1. 如果 obj 外面有工夫对象,则 JSON.stringify 后再 JSON.parse 的后果,工夫将只是字符串的模式,而不是对象的模式
2. 如果 obj 里有 RegExp(正则表达式的缩写)、Error 对象,则序列化的后果将只失去空对象;3、如果 obj 里有函数,undefined,则序列化的后果会把函数或 undefined 失落;4、如果 obj 里有 NaN、Infinity 和 -Infinity,则序列化的后果会变成 null
5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果 obj 中的对象是有构造函数生成的,则应用 JSON.parse(JSON.stringify(obj))深拷贝后,会抛弃对象的 constructor;6、如果对象中存在循环援用的状况也无奈正确实现深拷贝;

实现一个宽高自适应的正方形

  • 利用 vw 来实现:
.square {
  width: 10%;
  height: 10vw;
  background: tomato;
}
  • 利用元素的 margin/padding 百分比是绝对父元素 width 的性质来实现:
.square {
  width: 20%;
  height: 0;
  padding-top: 20%;
  background: orange;
}
  • 利用子元素的 margin-top 的值来实现:
.square {
  width: 30%;
  overflow: hidden;
  background: yellow;
}
.square::after {
  content: '';
  display: block;
  margin-top: 100%;
}

对 rest 参数的了解

扩大运算符被用在函数形参上时,它还能够把一个拆散的参数序列整合成一个数组

function mutiple(...args) {
  let result = 1;
  for (var val of args) {result *= val;}
  return result;
}
mutiple(1, 2, 3, 4) // 24

这里,传入 mutiple 的是四个拆散的参数,然而如果在 mutiple 函数里尝试输入 args 的值,会发现它是一个数组:

function mutiple(...args) {console.log(args)
}
mutiple(1, 2, 3, 4) // [1, 2, 3, 4]

这就是 … rest 运算符的又一层威力了,它能够把函数的多个入参收敛进一个数组里。这一点 常常用于获取函数的多余参数,或者像下面这样解决函数参数个数不确定的状况。

原型

构造函数是一种非凡的办法,次要用来在创建对象时初始化对象。每个构造函数都有 prototype(原型)(箭头函数以及 Function.prototype.bind()没有)属性,这个 prototype(原型)属性是一个指针,指向一个对象,这个对象的用处是蕴含特定类型的所有实例共享的
属性和办法,即这个原型对象是用来给实例对象共享属性和办法的。每个实例对象的__proto__都指向这个
构造函数 / 类的 prototype 属性。面向对象的三大个性:继承 / 多态 / 封装

对于 new 操作符:1. new 执行的函数, 函数外部默认生成了一个对象

2. 函数外部的 this 默认指向了这个 new 生成的对象

3. new 执行函数生成的这个对象, 是函数的默认返回值

ES5 例子:function Person(obj) {
    this.name = obj.name
    this.age= obj.age
}
// 原型办法
Person.prototype.say = function() {console.log('你好,', this.name)
}
// p 为实例化对象,new Person()这个操作称为构造函数的实例化
let p = new Person({name: '番茄', age: '27'})
console.log(p.name, p.age)
p.say()

ES6 例子:class Person{constructor(obj) {
      this.name = obj.name
        this.age= obj.age
  }
  say() {console.log(this.name)
  }
}

let p = new Person({name: 'ES6- 番茄', age: '27'})
console.log(p.name, p.age)
p.say()

src 和 href 的区别

src 和 href 都是 用来援用内部的资源,它们的区别如下:

  • src: 示意对资源的援用,它指向的内容会嵌入到以后标签所在的地位。src 会将其指向的资源下载并应⽤到⽂档内,如申请 js 脚本。当浏览器解析到该元素时,会暂停其余资源的下载和解决,直到将该资源加载、编译、执⾏结束,所以⼀般 js 脚本会放在页面底部。
  • href: 示意超文本援用,它指向一些网络资源,建设和以后元素或本文档的链接关系。当浏览器辨认到它他指向的⽂件时,就会并⾏下载资源,不会停⽌对以后⽂档的解决。罕用在 a、link 等标签上。

数组去重

第一种:通过 ES6 新个性 Set()
例如:var arr = [1, 2, 3, 1, 2]; var newArr= [...new Set(arr)]
第二种:封装函数利用 {} 和【】function uniqueEasy(arr) {if(!arr instanceof Array) {throw Error('以后传入的不是数组')
    }
    let list = []
    let obj = {}
    arr.forEach(item => {if(!obj[item]) {list.push(item)
            obj[item] = true
        }
    })
    return list
}

当然还有其余的办法,但自己我的项目中个别应用以上两种根本满足

map 和 foreach 有什么区别

foreach()办法会针对每一个元素执行提供得函数, 该办法没有返回值, 是否会扭转原数组取决与数组元素的类型是根本类型还是援用类型
map()办法不会扭转原数组的值, 返回一个新数组, 新数组中的值为原数组调用函数解决之后的值:

什么是闭包,闭包的作用是什么

当一个外部函数被调用,就会造成闭包,闭包就是可能读取其余函数外部变量的函数。闭包作用:局部变量无奈共享和短暂的保留,而全局变量可能造成变量净化,所以咱们心愿有一种机制既能够短暂的保留变量又不会造成全局净化。

行内元素有哪些?块级元素有哪些?空 (void) 元素有那些?

  • 行内元素有:a b span img input select strong
  • 块级元素有:div ul ol li dl dt dd h1 h2 h3 h4 h5 h6 p

空元素,即没有内容的 HTML 元素。空元素是在开始标签中敞开的,也就是空元素没有闭合标签:

  • 常见的有:<br><hr><img><input><link><meta>
  • 鲜见的有:<area><base><col><colgroup><command><embed><keygen><param><source><track><wbr>

px、em、rem 的区别及应用场景

三者的区别:

  • px 是固定的像素,一旦设置了就无奈因为适应页面大小而扭转。
  • em 和 rem 绝对于 px 更具备灵活性,他们是绝对长度单位,其长度不是固定的,更实用于响应式布局。
  • em 是绝对于其父元素来设置字体大小,这样就会存在一个问题,进行任何元素设置,都有可能须要晓得他父元素的大小。而 rem 是绝对于根元素,这样就意味着,只须要在根元素确定一个参考值。

应用场景:

  • 对于只须要适配少部分挪动设施,且分辨率对页面影响不大的,应用 px 即可。
  • 对于须要适配各种挪动设施,应用 rem,例如须要适配 iPhone 和 iPad 等分辨率差异比拟挺大的设施。

说一下 slice splice split 的区别?

// slice(start,[end])
// slice(start,[end])办法:该办法是对数组进行局部截取,该办法返回一个新数组
// 参数 start 是截取的开始数组索引,end 参数等于你要取的最初一个字符的地位值加上 1(可选)。// 蕴含了源函数从 start 到 end 所指定的元素,然而不包含 end 元素,比方 a.slice(0,3);// 如果呈现正数就把正数与长度相加后再划分。// slice 中的正数的绝对值若大于数组长度就会显示所有数组
// 若参数只有一个,并且参数大于 length,则为空。// 如果完结地位小于起始地位,则返回空数组
// 返回的个数是 end-start 的个数
// 不会扭转原数组
var arr = [1,2,3,4,5,6]
/*console.log(arr.slice(3))//[4,5,6] 从下标为 0 的到 3,截取 3 之后的数 console.log(arr.slice(0,3))//[1,2,3] 从下标为 0 的中央截取到下标为 3 之前的数 console.log(arr.slice(0,-2))//[1,2,3,4]console.log(arr.slice(-4,4))//[3,4]console.log(arr.slice(-7))//[1,2,3,4,5,6]console.log(arr.slice(-3,-3))// []console.log(arr.slice(8))//[]*/
// 集体总结:slice 的参数如果是负数就从左往右数,如果是正数的话就从右往左边数,// 截取的数组与数的方向统一,如果是 2 个参数则截取的是数的交加,没有交加则返回空数组 
// ps:slice 也能够切割字符串,用法和数组一样,但要留神空格也算字符

// splice(start,deletecount,item)
// start:起始地位
// deletecount:删除位数
// item:替换的 item
// 返回值为被删除的字符串
// 如果有额定的参数,那么 item 会插入到被移除元素的地位上。// splice: 移除,splice 办法从 array 中移除一个或多个数组,并用新的 item 替换它们。// 举一个简略的例子 
var a=['a','b','c']; 
var b=a.splice(1,1,'e','f'); 
 console.log(a) //['a', 'e', 'f', 'c']
 console.log(b) //['b']

 var a = [1, 2, 3, 4, 5, 6];
//console.log("被删除的为:",a.splice(1, 1, 8, 9)); // 被删除的为:2
// console.log("a 数组元素:",a); //1,8,9,3,4,5,6

// console.log("被删除的为:", a.splice(0, 2)); // 被删除的为:1,2
// console.log("a 数组元素:", a) //3,4,5,6
console.log("被删除的为:", a.splice(1, 0, 2, 2)) // 插入 第二个数为 0,示意删除 0 个  
console.log("a 数组元素:", a) //1,2,2,2,3,4,5,6

// split(字符串)
// string.split(separator,limit):split 办法把这个 string 宰割成片段来创立一个字符串数组。// 可选参数 limit 能够限度被宰割的片段数量。// separator 参数能够是一个字符串或一个正则表达式。// 如果 separator 是一个空字符,会返回一个单字符的数组,不会扭转原数组。var a="0123456";  
var b=a.split("",3);  
console.log(b);//b=["0","1","2"]
// 留神:String.split() 执行的操作与 Array.join 执行的操作是相同的。

Vuex 有哪些根本属性? 为什么 Vuex 的 mutation 中不能做异步操作?

有五种,别离是 State、Getter、Mutation、Action、Module
1、state => 根本数据(数据源寄存地)
2、getters => 从根本数据派生进去的数据
3、mutations => 提交更改数据的办法,同步
4、actions => 像一个装璜器,包裹 mutations,使之能够异步。5、modules => 模块化 Vuex

1、Vuex 中所有的状态更新的惟一路径都是 mutation,异步操作通过 Action 来提交 mutation 实现,这样能够不便地跟踪每一个状态的变动,从而可能实现一些工具帮忙更好地理解咱们的利用。2、每个 mutation 执行实现后都会对应到一个新的状态变更,这样 devtools 就能够打个快照存下来,而后就能够实现 time-travel 了。如果 mutation 反对异步操作,就没有方法晓得状态是何时更新的,无奈很好的进行状态的追踪,给调试带来艰难。

说一下 data 为什么是一个函数而不是一个对象?

JavaScript 中的对象是援用类型的数据,当多个实例援用同一个对象时,只有一个实例对这个对象进行操作,其余实例中的数据也会发生变化。而在 Vue 中,咱们更多的是想要复用组件,那就须要每个组件都有本人的数据,这样组件之间才不会互相烦扰。所以组件的数据不能写成对象的模式,而是要写成函数的模式。数据以函数返回值的模式定义,这样当咱们每次复用组件的时候,就会返回一个新的 data,也就是说每个组件都有本人的公有数据空间,它们各自保护本人的数据,不会烦扰其余组件的失常运行。

说一说前端性能优化计划

三个方面来阐明前端性能优化
一:webapck 优化与开启 gzip 压缩
    1.babel-loader 用 include 或 exclude 来帮咱们防止不必要的转译,不转译 node_moudules 中的 js 文件
    其次在缓存以后转译的 js 文件,设置 loader: 'babel-loader?cacheDirectory=true'    2. 文件采纳按需加载等等    3. 具体的做法非常简单,只须要你在你的 request headers 中加上这么一句:accept-encoding:gzip    4. 图片优化,采纳 svg 图片或者字体图标    5. 浏览器缓存机制,它又分为强缓存和协商缓存二:本地存储——从 Cookie 到 Web Storage、IndexedDB    阐明一下 SessionStorage 和 localStorage 还有 cookie 的区别和优缺点三:代码优化    1. 事件代理    2. 事件的节流和防抖    3. 页面的回流和重绘    4.EventLoop 事件循环机制    5. 代码优化等等

正文完
 0