面试总结(2019/03/17更新)

本来想分一期二期写的,想想第一篇博客要特殊点,哈哈。那就像流水账一样一直往下写吧。

题目不分先后不分重要,包括自己面试的一些比较有代表的知识点和别处看到的面试点。

1、JS继承(原型链继承)

继承这个题目其实挺大的,继承的方式也有很多种,目前我也不是全部理解各种方法的继承,以后摸透了写个继承专题吧。

直接挂代码

 1 // 声明 人 这个类
 2 function People(sex) {
 3   this.sex = sex;
 4   this.saySex = function saySex() {
 5     console.log(`我的性别是${this.sex}`);
 6   }
 7 }
 8 
 9 // 创建女性
10 function Woman() {
11 
12 }
13 Woman.prototype = new People('女');
14 // 这样Woman这个类别的原型对象是 人 构造的;
15 const girl = new Woman();
16 girl.saySex();

2、什么是闭包,手写一个闭包

这又是一个很大的问题啊,哈哈以后写个闭包专题详解。其实简单理解一下就是:一个函数记住并能够访问他所在的词法作用域时,即使函数在其他的作用域调用,这就产生了闭包。

贴一个典型的闭包例子

 1 function fn () {
 2   const str = 'HelloWorld';
 3   function bar () {
 4     console.log(str);
 5   };
 6   return bar;
 7 }
 8 
 9 var fo = fn();
10 fo() // 输出HelloWorld;

fo其实就是fn作用域中的函数bar,但是fo在调用的时候并不是在fn内部作用域中调用,即使如此依旧访问到了str这个变量,在这里产生了闭包。

3、使用闭包实现数据缓存

废话不多说,直接上代码

 1 const foo = (() => {
 2   const cache = {};
 3   return {
 4     getCache(key) {
 5       return cache[key];
 6     },
 7     setCache(key, val) {
 8       cache[key] = val;
 9     },
10   };
11 })();
12 
13 foo.setCache('name', 'dsj');
14 console.log(foo.getCache('name'));

直接用IIEF返回了一个对象,只暴露出两个方法用来存和取,在这里就用了闭包,函数调用时所在的词法作用域并不是函数声明所在的域,依旧访问到了想要的变量,产生了闭包。

4、url->页面生成过程

分为两大块来答
一个是网络http层面
首先输入url,进行DNS解析
DNS解析首先会进行缓存查找,分为以下5部
1. 浏览器先会在浏览器缓存中去查找该域名是否有对应的IP信息
2. 接着搜索操作系统中有没有相应的缓存信息,比如hosts文件中
3. 接着在路由器缓存中进行查找
4. 接着从网络服务商那边的缓存信息中进行查找
5. 以上方法如果找到就直接返回IP信息,如果都没有找到就会发起一个迭代DNS解析请求
1> 向根域名服务器发送请求,该服务器没有域名的全部信息,但是可以返回顶级域名服务器地址,如com域的顶级域名服务器地址
2> 接着向顶级域名服务器发送请求最终获取IP地址
6. 本地域名服务器(也就是网络服务商)将IP返回给操作系统同时把该IP缓存起来
7. 操作系统把IP返给浏览器,同时把IP缓存起来
8. 最后浏览器得到了IP开始建立连接
建立连接 => 3次握手
客户端发送带有SYN标志的数据包给服务端(请求连接)
服务端发送带有SYN/ACK标志的数据包给客户端(同意建立连接)
客户端发送带有ACK标志的数据包给服务端(开始连接)
PS:如果三次握手中某次某一方没接到信号,TCP协议会要求重新发送信号

建立连接后会开始数据的传递,比如相应的js文件,css文件,图片文件等。同时浏览器开始渲染页面
当数据传递全部完成,开始四次挥手,断开连接。

其中浏览器渲染页面过程如下:
解析HTML,遍历文档节点生成DOM树;
解析CSS文件生成 CSSOM规则树;
然后DOM树和和CSSOM树结合生成渲染Render树;
渲染树开始布局,计算每个节点的位置大小等信息;
然后绘制每个节点到屏幕上
PS:在生成DOM树的过程中,如果遇到script标签,会产生阻塞;
如果JS脚本中还对CSSOM进行了操作,浏览器会延迟脚本的执行和DOM的渲染,先生成CSSOM树;
渲染树进行绘制的时候,涉及到了重绘和回流
回流:当元素的大小布局等发生改变的时候就会触发回流,回流必然引发重绘;
重绘:当元素的颜色背景之类发生改变的时候触发重绘
每个页面必然发生一次回流,所以必然有一次重绘,也就是页面加载渲染的时候;
因为回流会导致渲染树重新构建,花销较大,所以应该尽可能的避免回流;
比如减少DOM操作等。

5、JS的数据类型,基本类型哪些,和引用类型的区别

数据类型: null, undefined, Number, String, Object, Boolean, Symbol
基本类型: undefined, null, boolean, number, String, 指的是保存在栈内存中的简单数据
引用类型: Array, Object等对象类型,指的是保存在堆内存中的对象,变量实际保存的是一个指针,指针指向内存中的一个位置,该位置保存对象

6、节流和防抖

节流和防抖都是用来预防某些操作重复触发函数,比如重复请求,造成性能卡顿问题。
防抖原理:无论如何触发事件,在n秒后我才执行这个函数,如果n秒内再次触发该函数就以新触发的为准,n秒后再执行

 1 function debounce(fuc, delay = 300) {
 2   let timer = null;
 3   return function fn() {
 4     const self = this; // 保存调用函数的this,防止setTimeout改变this指向
 5     const args = arguments; // 保存函数调用的传参
 6     if (timer) clearTimeout(timer); // 清除定时器
 7     timer = setTimeout(() => {
 8       fuc.apply(self, args); // 函数指定this,同时把参数传入
 9     }, delay);
10   };
11 }
12 
13 // 比如给一个div绑上一个mousemove事件,
14 function move(event) {
15   console.log(1, event);
16 }
17 div.onmousemove = debounce(move);

可以直接试一下。

节流原理:一段时间内某个函数只能触发一次,比如3秒内fn函数只能触发一次,便会在0秒是触发一次该函数直至3秒都不会再次触发
目前主流采用时间戳或者定时器来实现
时间戳方法会让函数立刻执行一次,但是在停止触发的时候并不会再执行了,如下

 1 function throttle(fuc, delay = 300) {
 2   let timer = 0;
 3   return function fn() {
 4     const self = this;
 5     const now = +new Date();
 6     const args = Array.prototype.slice.apply(arguments);
 7     if (now - timer > delay) {
 8       fuc.apply(self, args);
 9       timer = now;
10     }
11   };
12 }

7、手写bind实现

这个问题真的非常常见,但是知识点还蛮多的。写了一年业务代码很少用到 bind, call, apply这些函数,也对这些理解得不咋深,结果面试问到就露馅,回过头来多理解一下,多写写也搞懂了一些。

首先讲一下bind的用法,bind一般用来改变一个函数中this的指向,该方法会返回改变this后的函数,以下是一个范例 

 1 var obj = {
 2   name: '阿狸',
 3   age: '18',
 4 };
 5 function fuc(sex, from) {
 6   console.log(`我叫${this.name}`);
 7   console.log(`我是${sex}孩子,来自${from}`);
 8   console.log(`今年${this.age}岁啦`);
 9   return '哈哈哈哈哈';
10 }
11 
12 fuc.bind(obj, '女')('瓦罗兰大陆');

输出肯定是没啥问题的,可以直接贴f12试下,用法试了一回那么怎么手写一个实现怎么做呢?

 1 Function.prototype.bindHandler = function (ctx) {
 2   const self = this;
 3   // ctx也就是this要指向的对象
 4   return function() {
 5     self.apply(ctx);
 6   }
 7 }
 8 // 试验一下
 9 fuc.bindHandler(obj, '女', '瓦罗兰大陆');
10 // 发现结果有点不对,参数没有拿到,而且还有一个点没考虑到,fuc函数如果返回一个值,能拿到吗?明显不能,改进一下函数
11 Function.prototype.bindHandler = function (ctx) {
12   // ctx也就是this要指向的对象
13   const self = this;
14   // 保存一下除了ctx以外传进来的参数,
15   const args = Array.prototype.slice.apply(arguments, [1]);
16   return function() {
17     const argsOther = Array.prototype.slice.apply(arguments);
18     const result = self.apply(ctx, args.concat(argsOther));
19     return result;
20   }
21 }
22 // 再试一下, 完全objk。
23 fuc.bindHandler(obj, '女', '瓦罗兰大陆');
24 fuc.bindHandler(obj, '女')('瓦罗兰大陆');

搞定!

8、手写trim实现
其实就是去掉首尾空格,一个简单的正则搞定

1 String.prototype.trimHandler = function() {
2   return this.replace(/(^s*)|(s*$)/g, '');
3 }

先写到这里,下次再来更新!

/* ----------------------------------------------- 2019/3/4 更新------------------------  */

又回来啦,最近这段时间一直在面试。运气不错,拿到offer了。

躺了一天不能懒了,回来记录下面试过程中印象比较深的一些题吧。

 

续上

9、如何理解Nginx代理解决跨域
跨域问题在前端算是个必考点,其实不想说这个的,一个基础的前端开发是必然要了解跨域相关的知识。

不过面试次次问,那就说一下我的理解,希望给面到这个问题的同学一点帮助。

其实在这之前还得理解一下什么是 "同源策略",网上其实有很多解释了,不过我希望同学们记住一点,这是浏览器的一个功能!(划重点)

也就是说解决跨域,就为了绕过浏览器的这个限制。

在这里贴一个链接,里面有讲到同源策略和一些普遍的跨域方法,我就不赘述了(https://segmentfault.com/a/1190000007326671)。

我想说的主要是现在我们开发中很常用的配置nginx代理去解决跨域。

原理其实不复杂,就是绕过了浏览器的同源策略去解决。

                          

正常来说我们写代码发送请求给目标地址,会产生跨域,那么我们可以做个中转站服务器,让这个代理服务器可以我们的请求,然后这个代理服务器再转发给目标服务器。

服务器和服务器之间是可以直接请求的,之前说过同源策略是浏览器的功能嘛。这样就绕过了服务器的限制,解决了跨域,其实就这么简单。

不过一般来说正式项目上线,服务器会给配置一个网关,这个网关去过滤一部分请求,才能到目标服务器。不然服务器的安全性就太低了,任意请求被攻击怎么办?

这样我们在开发的时候就可以解决跨域问题,快乐的开发。至于上线,也可以配置Nginx代理服务器的,然后在服务器端改一下配置允许对应代理的请求即可,这部分一般是运维的工作。

 

10、JS实现事件绑定的几种方法

这个问题简单,但是经常让你手写一个兼容绑定事件的方法,可以瞟一眼。

假设有个函数

// 假设有个函数
function fn() {
  alert('Hello World !');
}
// 直线元素绑定
// html代码
 <div id='btn' @onclick='fn()'>按钮</div> 

// 或者通过js绑定
// 这是dom0级事件绑定,此方法只能绑定一个事件,绑定多个会被覆盖
 document.getElementById('btn').onclick = fn;

// dom2级事件绑定
// 如果绑定多个函数,会根据绑定的顺序来执行
 document.getElementById('btn').addEventListener('click', fn, false);
// 其中这个布尔值,true指的是在事件捕获阶段调用方法,false就是在冒泡阶段,默认为false

不过addEventListener有兼容问题,在IE8及以下版本要用attachEvent这个方法,那么最后贴一个兼容方法。

function addEventHandler(obj, type, fn) {
  // obj是元素对象,type是要绑定的事件类型,fn是函数
  // 能力判断
  if(element.addEventListener) { // Chrome,Firefox等浏览器,IE9及以上
    element.addEventListener(type, fn, false);
  } else if (element.attachEvent) { // IE8及以下 
    element.attachEvent("on" + type, fn);
  } else {
    element["on" + type] = fn;
  }
}

那么如果是移除事件呢?也有兼容性方法的,自己想想怎么写吧!基本类似。

 

11、说说对var, let, const的理解

每次面对这种XXX的理解的问题就很头大,只能尽力去把自己知道的表达出来。

我当时的回答:

var可以重复声明,存在声明提升,绑定全局作用域,例:

// 一般来说直接
console.log(test);
// 是会报错的,因为test没有声明。这大家都理解

// 那么下面这段代码缺会输出undefined
console.log(test);
var test = 1;

// 因为上面这段代码等同于
var test;
console.log(test);
test = 1;
// var的变量提升会把声明变量提升到"当前作用域"的顶部
// 同时var还会绑定全局作用域,直接用代码理解,前提是var在全局作用域中声明使用
var test = 'test';
console.log(test); // test
console.log(window.test); // test

但是const和let不存在声明提升,也不会绑定全局作用域,那么let和const之间也有区别

const用于声明常量,值被设定以后不能修改,否则报错,

不过const可以理解为不可修改绑定,可以修改其属性值,看代码

const num = 1;
num = 2; // 报错

const obj = { name: '张三' };
obj.name = '李四';
obj.sex = '男';
console.log(obj); // { name: '李四', sex: '男' };

而且let和const还有一个特性,临时死区(Temporal Dead Zone),简写为 TDZ (划重点!!!)。

之前在了解到let和const不会提升的时候,我就想过一个问题

var name = '张三';
function fn() {
  console.log(name);
}
fn();
// 这里会输出张三,因为js的作用域链,在自己的作用域找不到该变量时会去到上一级作用域找该变量

// 那么如果是以下情况
const name = '张三';
function fn() {
  console.log(name); // 此时fn的作用域未声明name
  const name = '李四'; // 不会提升变量
  console.log(name);
}
fn();

当初我的想法是,第一句console未声明name,那么会去上一级作用域找name,输出“张三”。
第二句console因为上一句代码声明了name,输出李四。
结果以上想法是错的,程序直接报错,就是因为临时死区的存在才会报错。
在fn中只要const或者let声明了一个变量,虽然不会提升到顶部,但会把变量放在DTZ中。
这样在声明之前访问变量就会报错,执行过变量声明语句之后才会从DTZ中移出来,我把这理解为一种另类的提升。

 

12、浏览器缓存禁用

实际开发中有这样一个场景,因为http的缓存机制,项目打包上线以后,可能页面加载的js是浏览器中的缓存,并不是最新的js文件,总不能告诉用户去手动清缓存吧?那有什么办法解决呢。

其实我对http缓存这一块了解不深,但是这个问题我觉得很实用,也是我项目中确实碰到的问题,然后查了下解决方案,有两种比较常用:

  (一) html里面通过meta标签去禁用缓存

<meta http-equiv="pragma" content="no-cache"> 
<meta http-equiv="Cache-Contro" content="no-cache, must-revalidate"> 
<meta http-equiv="expires" content="0">

  我了解不深怕解释不清,就不详说,这几个属性值大家可以自己查查了解,这样印象也会深一些。

(二)

  css和js带参数(形如.css?time=与.js?time=) ,加一个随机数或者时间戳,这样请求资源的路径变了,就不会加载缓存的资源。 

 

主要详写的是我容易忘或者比较特别的问题,当然面试还有很多问题,网上有非常多的解释也没有什么特别好说的,

我在此列举一些,看这篇帖子的同学们也可以去了解一下。

  • 垂直水平居中的几种方法(flex, position等)
  • 移动端页面适配(可以去了解一下手淘flexible.js的方案)
  • http状态码,三次握手四次挥手了解一下
  • 数组有哪些操作方法
  • cookie和本地存储
  • 页面加载性能优化(雪碧图减少http请求之类的)
  • 因为我的技术栈是vue,所以还问了关于vue的东西
  • vue的生命周期,各个周期代表的意义,实际运用(如mounted中获取页面数据)
  • vue的组件通信 ($emit,props等)
  • vue数据双向绑定实现的原理,这个问题5个面试里有3个都问,这个我没写过,一般我就说了下原理思路,可以百度看看,不过大部分说的是通过Object.defineProperty来监听,在vue3.0里面改用了proxy,建议都看看,也算体现对技术更新的热爱。后续我可能写一篇实现双向绑定的demo,也想自己啃一下。

 

虽然已经准备入职新公司,但是平常会看看有什么好玩的题,会分享在这里,或者另外写,下次再来更新!

有什么问题都可以给我留言哈~

 

/* ----------------------------------------------- 2019/3/17 更新------------------------  */

原文地址:https://www.cnblogs.com/jeodeng/p/10473636.html