前端面试题

https://segmentfault.com/a/1190000018719463
https://godbmw.com/passages/2019-03-27-javascript-second/
最近在准备面试,过程中遇到了很多自己看似知道,其实并不准确的知识点,在这里记录一下。

一、Javascript

1、深拷贝和浅拷贝

  1. 在JS中,函数和对象都是浅拷贝(地址引用);其他的,例如布尔值、数字等基础数据类型都是深拷贝(值引用)。ES6的Object.assign()和ES7的…解构运算符都是“浅拷贝”。
  2. 如何实现深拷贝?自己手动撸“轮子”或者借助第三方库(例如lodash):
  • 手动实现深拷贝(递归):

    //定义检测数据类型的函数
    function checkedType(target){
    	return Object.prototype.toString.call(target).slice(8,-1)
    }
    //定义深拷贝函数
    function clone(target){
    	let result, type = checkedType(target)
    	if(type === 'Array'){
    		 result = []
    	}else if(type === 'Object'){
    		 result = {}
    	}else{
    		return target
    		}
    		//遍历目标数据
    	for(let item in target){
    			let value = target[item]
    			if(checkedType(value)==='Object' || checkedType(value)==='Array'){
    			result[item] = clone(value)	
    		}else{
    			result[item] = value
    		}
    	}
    	return result
    }
    
  • 借助第三方库:jq的extend(true, result, src1, src2[ ,src3])、lodash的_.cloneDeep(src)

  • JSON.parse(JSON.stringify(src)):这种方法有局限性,如果属性值是函数或者一个类的实例的时候,无法正确拷贝

  • 借助HTML5的MessageChannel:这种方法有局限性,当属性值是函数的时候,会报错

2、数据类型的判断

js中的基本类型有六种:Number、String、Boolean、Null、Undefined和es6中的symbol;Object不属于基本类型,它是复杂类型。

  1. 使用typeof,缺点是不能判断null、[]、Date、RegExp等类型,因为都会返回object。
typeof Symbol() //symbol
typeof '' //string
typeof 1 //number
typeof true //boolean
typeof undefined //undefined
typeof new Function() //function
typeof null //object,无法判断
typeof [] //object,无法判断
typeof new Date() //object,无法判断
typeof new RegExp() //object,无法判断

  1. instanceof 表达式为:A instanceof B,如果A是B的实例,则返回true.不能用来检测null和undefined和基本类型.
  2. constructor 起作用与instanceof类似,但其还可以检测基本数据类型,不过这个方法是不稳定的,这主要体现在把类的原型进行重写,在重写的过程中很有可能出现把之前的constructor给覆盖了,这样检测的结果就不准确了.
  3. Object.prototype.toString.call(target),这个是最准确最常用的方式,都可以检测出来.
Object.prototype.toString.call(null) //[object Null]
Object.prototype.toString.call([]) //[object Array]
Object.prototype.toString.call(1) //[object Number]

3、this 指向问题

有如下几种情况:

  • 情形1:对于直接调用函数来说,不管该函数放在什么地方,this一定是window;
  • 情形2:对于obj.func()来说,谁调用了函数,谁就是this,所以此时func函数的this就是obj对象;
  • 情形3:在构造函数中,类中(函数体中)出现的this.xxx=xxxx中的this是当前类的一个实例;
  • 情形4:call\apply\bind:this是第一个参数;
  • 情形5:箭头函数this指向:箭头函数没有自己的this,看其外层是否有函数,如果有呢,外层函数的this就是内部箭头函数的this,如果没有,则this就是window。
//情形1
function func(){
	console.log(this.a)
}
var a =1
func()// 1

//情形2
function func(){
	console.log(this)
}
var obj = {fn:fn}
obj.fun() //this -> obj

//情形3
function Fun(name,age){
	this.name=name;
	this.age =age;
}
var p1 = new Fun("alex",24)//this->p1

//情形4
function add(c,d){
	return this.a + this.b+c+d
}
var obj = {a:1,b:2}
add.call(obj,10,20)//this->obj
add.apply(obj.[10,20]) //this->obj

//情形5
let obj={
	name:'alex',
	getName:function(){
		return ()=>{
		console.log(this) //obj
		}
}
}

4、 同步与异步

  1. 场景:
  • 定时任务:setTimeout,setInterval
  • 网络请求:ajax请求,动态加载
  • 事件绑定
  1. Event Loop
//异步任务
Promise.resolve().then(()=>{
  console.log('Promise1')
  setTimeout(()=>{
    console.log("setTimeout2")
  },0)
})
//同步任务
console.log(1)
setTimeout(()=>{
  console.log("setPromise2")
  Promise.resolve().then(()=>{
    console.log('Promise2')
  })
})
//最终输出
1
"Promise1"
"setPromise2"
"Promise2"
"setTimeout2"
下面这段代码输出顺序?(小米面试题)
setTimeout(function(){
  console.log("time1")
})
var pro = new Promise(function(resolve,reject){
  console.log("pro1")
  setTimeout(function(){
    console.log("time2")
  },3000)
  console.log("pro2")
})
 pro.then(function(){
  console.log("pro3")
})

setTimeout(function(){
  console.log("time3")
})

5、原型链与继承

  1. 原型链+借用构造函数的组合继承。核心是在子类构造函数中通过call方法继承父类的属性,然后改变子类的原型来继承父类的函数。
  • 优点:构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数
  • 缺点:继承父类函数的时候调用了父类的构造函数,导致子类原型上多了不需要的父类属性,存在内存上的浪费。同时会导致子类的构造函数是Parent.
function Parent(value){
	this.val = value
}

Parent.prototype.getValue = function(){
	console.log(this.val)
}

function Child(value){
	Parent.call(this,value)
}

Child.prototype = new Parent();
const child = new Child(1);
child.getValue();  //1
console.log(child instanceof Parent);  //true

  1. 寄生组合方式。核心是将父类的原型赋值给子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确找到子类的构造函数。
function Parent(value){
	this.val = value
}

Parent.prototype.getValue = function(){
	console.log(this.val)
}

function Child(value){
	Parent.call(this,value)
}

Child.prototype = Object.create(Parent.prototype,{
	constructor:{
		value:Child,
		enumerable:false,
		writable:true,
		configurable:true,
	}
})
const child = new Child(1)
child.getValue() //1
console,log(child instanceof Parent)//true
  1. ES6中的class继承。class可以通过extends关键字实现继承,还可以通关static关键字定义类的静态方法,其只是语法糖,本质仍然是基于原型实现的。
class Parent{
	constructor(value){
		this.val = value	
	}
	getVal(){
		console.log(this.val )
	}
}
class Child extends Parent{
	constructor(value){
		super(value)
	}
}
let child = new Child(1)
child.getVal()//1
console.log(child instanceof Parent)

6、DOM操作与BOM操作

  1. 事件代理
  2. 事件流
  • 捕获阶段
  • 目标阶段
  • 冒泡阶段

7、什么是重绘和回流?

8、let、const、var
//情形一
var a;
console.log(a);//输出什么?(undefined)
a = 1//情形二
var a = 1;
console.log(a);//输出?
a=2;
//情形三
//可以重复声明b吗?
let b = 1;
try{
	console.log(b);
	let b = 2;
}catch(e){
	conosle.log(e)
}
//情形四,可以这样重复声明b吗?
let b = 1;
function test(){
	let b = 2;
	console.log(b)
}


9、以下代码输出顺序是什么?

代码二
var bb = 1;
function aa(bb) {
    bb = 2;//bb虽然没有用var声明,但是bb并不是全局中的bb,因为函数aa传进了一个参数bb,相当于在函数体内声明了bb,所以此处的bb是引用的arguments对象上的bb
    alert(bb);
};
aa(bb);
alert(bb);
代码三

下面span标签的width、height分别是多少?
解析:首先span是行内标签,不支持设置宽高,但其float设置了left,所以span转换成块级元素,支持狂傲,height100%,所以height=200px;而width=auto,由内容撑开,但i标签是absolute,脱离文档流,所以span标签的width=0。

<div style=”width:400px;height:200px;”>
  <span style=”float:left;width:auto;height:100%;”>
           <i style=”position:absolute;float:left;width:100px;height:50px;”>hello</i>
  </span>
</div>
代码四

考察函数声明和函数表达式的区别,函数声明,可以提前调用函数;而函数表达式不可以,因为赋值操作不会被提前。

 (function() {

var x=foo();
var foo=function foo() {
return “foobar”
};
return x;
})(); 

10、HTTP/HTTPS

超文本传输协议,是一个基于请求与相应、无状态的应用层协议,常基于TCP/IP协议传输数据。
版本之间差异:

版本产生时间内容
http0.91991年不涉及数据包传输,规定客户端和服务器之间通信格式,只能GET请求(没有作为正式的标准)
http1.01996年传输格式不限制,增加了put、post、delete等等命令
http1.11997年持久连接(长连接)、管道机制、节约带宽、HOST域
http2.02015年多路复用、服务器推送、头信息压缩、二进制协议等等
  • 问题1:为什么要有三次握手?
  • 问题2:https与http的区别?https有内容加密、验证身份、保护数据完整性功能。
  • 问题3:什么是多路复用?
  • 问题4:为什么要有四次挥手?因为TCP是全双工模式。

1)请求(客户端->服务端[request])
GET(请求的方式) /newcoder/hello.html(请求的目标资源) HTTP/1.1(请求采用的协议和版本号)
Accept: /(客户端能接收的资源类型)
Accept-Language: en-us(客户端接收的语言类型)
Connection: Keep-Alive(维护客户端和服务端的连接关系)
Host: localhost:8080(连接的目标主机和端口号)
Referer: http://localhost/links.asp(告诉服务器我来自于哪里)
User-Agent: Mozilla/4.0(客户端版本号的名字)
Accept-Encoding: gzip, deflate(客户端能接收的压缩数据的类型)
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT(缓存时间)
Cookie(客户端暂存服务端的信息)

Date: Tue, 11 Jul 2000 18:23:51 GMT(客户端请求服务端的时间)

2)响应(服务端->客户端[response])
HTTP/1.1(响应采用的协议和版本号) 200(状态码) OK(描述信息)
Location: http://www.baidu.com(服务端需要客户端访问的页面路径)
Server:apache tomcat(服务端的Web服务端名)
Content-Encoding: gzip(服务端能够发送压缩编码类型)
Content-Length: 80(服务端发送的压缩数据的长度)
Content-Language: zh-cn(服务端发送的语言类型)
Content-Type: text/html; charset=GB2312(服务端发送的类型及采用的编码方式)
Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服务端对该资源最后修改的时间)
Refresh: 1;url=http://www.it315.org(服务端要求客户端1秒钟后,刷新,然后访问指定的页面路径)
Content-Disposition: attachment; filename=aaa.zip(服务端要求客户端以下载文件的方式打开该文件)
Transfer-Encoding: chunked(分块传递数据到客户端)
Set-Cookie:SS=Q0=5Lb_nQ; path=/search(服务端发送到客户端的暂存数据)
Expires: -1//3种(服务端禁止客户端缓存页面数据)
Cache-Control: no-cache(服务端禁止客户端缓存页面数据)
Pragma: no-cache(服务端禁止客户端缓存页面数据)
Connection: close(1.0)/(1.1)Keep-Alive(维护客户端和服务端的连接关系)
Date: Tue, 11 Jul 2000 18:23:51 GMT(服务端响应客户端的时间)
在服务器响应客户端的时候,带上Access-Control-Allow-Origin头信息,解决跨域的一种方法。

11、new一个对象的过程都发生了什么?

  1. 创建一个新对象,比如:var obj = {};
  2. 将新对象的_proto_属性指向构造函数的原型对象;
  3. 将构造函数的作用域指向新对象,即将this指向obj;
  4. 执行构造函数内部的代码,将属性添加给this对象;
  5. 返回新对象obj;

12、类和构造函数的区别?如何编写代码实现构造函数不用new关键字会报错?

通常我们所谓的类就是用new来构造实例的普通函数。
通常实例化一个构造函数的时候,里面的this是指向被实例化的实例的。如下示例所示:

function Person(name, age){
	this.name = name;
	this.age = age;
}

但有的时候我们想直接执行函数来进行实例化,而不是用new。这个时候构造函数里面的this指向便不再是被实例化的实例了,就变成了Ins函数的接受者全局变量window,严格模式下则会直接抛出错误。
通常的解决方案是判断this是否是构造函数的实例,若是则继续执行,若不是则进行new的操作。代码如下:

function Person(name, age){
	if(!this instanceof Person){
		return new Person(name, age);
	}
	this.name = name;
	this.age = age;
}

在构造函数参数个数固定的情况下这种方案是可行的。这种方式的一个缺点是它需要额外的函数调用,因此代价有点高。
在进行构造函数实例化的时候,如果构造函数内部没有明确return语句返回值,那么该构造函数会自动的构造一个实例对象返回,但是如果我们明确返回了非简单数据类型(简单数据类型还是会自动构造实例对象返回)的对象的话,构造函数实例化的时候返回的就是该对象。如此,我们可以构造如下的通用情况下的构造函数:

function Person(name, age){
	let self = this instanceof Person ? this : Object.create(Person.prototype);
	self.name = name;
	self.age = age;
}

13、字符串相关问题

1、改写字符串形式,例如:“ILoveYou”改成“i_love_you”,注意正则表达式不要加引号。replace第二个参数可以是一个函数,对匹配到的词做更多处理。

function func(str){
	return str.replace(/[A-Z]g/, function(word){return "_" + word.toLowerCase()})
}

2、将把 “Doe, John” 转换为 “John Doe” 的形式

function(str){
	return str.replace(/(\w+)\s*, \s*(\w+)/, "$2 $1");
}

3、我们将把所有的花引号替换为直引号

function func(str){
	return str.replace(/"([^"]*)"/g, "'$1'")
}

4、将一个数组随机打乱

function randArr(arr){
	var len = arr.length;
	for(var i=0; i< len; i++){
		var j = parseInt(Math.random()*(len-1));
		var tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;		
	}
	return arr;
}

14、说一说浏览器缓存

浏览器缓存主要分为:强缓存和协议缓存,强缓存主要涉及的响应头有expires、cach-control,前者是http1.0的产物,后者是http1.1中的,前者需要结合last-modified一起使用,两者同时存在时后者优先级高于前者;当日期过期后呢,即强缓存失效,此时进入协议缓存,浏览器发出的请求会携带if-modified-since头部,该头部的值来源于之前的last-modified,服务器接收到请求后,比对if-modified-since的值如果文件修改时间没变,说明缓存仍可以使用,则返回304,not-modified,表示浏览器可以继续使用之前的缓存,如果变化了则返回200和请求的文件。但是使用这种策略有两个弊端,一是当在本地打开缓存文件时last-modified会被修改,二是last-modified的最小单位是秒,可能会出现服务端刚修改了文件,然后接着就收到了请求,并且last-modified的时间与其相差在一秒以下,此时服务器仍会认为命中返回304,但其实文件已经被修改了。解决这个问题的方法是使用etag,它是http1.1的产物,它是由服务端产生的代表文件的唯一标识,可以认为是由文件内容产生的哈希值,所以当我们修改文件后产生的新的etag肯定会和之前的不一样,当浏览器对A文件再次发出请求时,会将etag值放到if-none-match请求头中,然后服务端会比对该值,如果与服务端相同,则返回304.etag的优先级高于if-modified-since。

15、说一说前端模块化开发

前端模块化开发可以总结为一个渐进的过程,由浅入深如下:

  1. 使用函数来定义一组语句;
  2. 将一组函数封装到一个对象中,通过对象调用(此种方法的缺点是通过对象能够访问到其中的对象变量,无法封装细节);
  3. 为了弥补上面的缺点,出现了立即执行函数,在立即执行函数中返回一个对象,对象中包含功能函数,通过闭包的方式解决上面的问题;
  4. 对于中大型项目上面的方式有很多缺点比如难以维护、不容易协同开发等,然后出现了一些规范:
    • commonjs:主要在服务端使用,特点是同步加载因为代码就存在服务端所以同步加载没有问题,但是在浏览器端由于文件需要请求服务端,同步加载会造成堵塞,所以不适用于前端,webpack天生支持commonjs。
    • AMD(Asynchromous Module Definition) 异步模块定义,适用于前端,异步加载,根据该规范实现的库是requireJs。
    • CMD(Common Module Definition)通用模块定义,适用于前端,同步加载,根据该规范实现的库是seaJS。
    • UMD(Universal Module Definition)是AMD和commonjs的综合产物,目的是为了解决跨平台问题,在前端使用异步加载,后端使用同步加载。
  5. ES6引入了内置的模块系统,import、export,使用需要通过babel转译。

16、cookie和session的区别

两者出现的原因是http是一个无状态协议,仅凭http无法识别用户,为了弥补这一缺点,所以提出了cookie和session,cookie存在于客户端,最初目的是用来识别用户,比如用户登录网站后一定时间内再次登录不用再次输入账号,它的存储容量有限制大概4M左右,不同浏览器有些许差别;session存在于服务端,也是为了存储一些信息,比如购物车,用户加入购物车后,在下次登录购物车中的东西还存在,这里使用的就是session。

17、异步编程的实现方式

  1. 回调函数,比如通过setTimeOut的方式将回调函数加入异步队列,实现异步编程;
  2. 事件监听;
  3. 发布\订阅模式
    这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。首先,f2向"信号中心"jQuery订阅"done"信号。
jQuery.subscribe("done", f2);

然后,f1进行如下改写:

  function f1(){

    setTimeout(function () {

      // f1的任务代码

      jQuery.publish("done");

    }, 1000);

  }

jQuery.publish(“done”)的意思是,f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。

此外,f2完成执行后,也可以取消订阅(unsubscribe)。

jQuery.unsubscribe(“done”, f2);

这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

  1. promise、async\await

17、数组去重

18、VUE

https://www.cnblogs.com/yszblog/p/10135969.html

1、说一下vue如何通信

(1)父组件向子组件通信,通过props。
父组件:

<template>
<div>
  <son v-bind:msg="name" />
</div>
</template>
<script>
import Son from './Son';
export default {
	name: "app",
	data:{name : "Alex"},
	components:{Son}
}
</script>

子组件:

<template>
<div>
  {{name}}
</div>
</template>
<script>
export default {
	name: "son",
	props:[name]
}
</script>

(2)子组件向父组件通信通过 . e m i t ( e v e n t , v a l ) , e v e n t 是 父 组 件 在 子 组 件 上 定 义 的 事 件 , 通 过 在 子 组 件 中 触 发 该 事 件 , 并 将 v a l 作 为 参 数 传 给 该 监 听 函 数 , 就 能 实 现 子 组 件 向 父 组 件 通 信 。 ( 3 ) 兄 弟 组 件 之 间 通 信 , 可 以 定 义 一 个 v u e 实 例 作 为 通 信 的 “ b u s ” , 比 如 c h i l d 1 要 给 c h i l d 2 传 参 , 可 以 在 c h i l d 1 中 通 过 .emit(event, val), event是父组件在子组件上定义的事件,通过在子组件中触发该事件,并将val作为参数传给该监听函数,就能实现子组件向父组件通信。 (3)兄弟组件之间通信,可以定义一个vue实例作为通信的“bus”,比如child1要给child2传参,可以在child1中通过 .emit(event,val),eventval3vuebuschild1child2child1.emit触发“bus”的事件,然后在child2中通过$.on监听该事件,来达到通信的目的。
(4)通过provide / inject 方法,可以以一个祖先组件向所有子孙后代注入依赖(内容)。
(5)vuex,通过状态管理插件,实现状态共享,类似于定义一个全局变量。

2、说一下vue的虚拟dom
3、说一下vue的生命周期函数
4、说一下diff算法
5、vue的双向绑定实现原理

通过Object.defineProperty()劫持数据的getter和setter属性,然后结合发布者\订阅者模式来实现双向绑定。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值