本来想分一期二期写的,想想第一篇博客要特殊点,哈哈。那就像流水账一样一直往下写吧。
题目不分先后不分重要,包括自己面试的一些比较有代表的知识点和别处看到的面试点。
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' @οnclick='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 更新------------------------ */