前端开发基础面试题

491 阅读11分钟

夜来了 我点上白蜡烛 看它的眼泪淌成什么形象

JavaScript篇

typeof类型判断

typeof是否能正确判断类型,instanceof能正确判断对象的原理是什么

typeof对于原始数据类型来说,除了null都能显示正确的类型

如👆所示,在console执行typeof null之后,对于对象来说,除了函数都会显示object,所以typeof并不能准确的判断变量到底是什么类型

重点来了,如果我们想判断一个对象的正确类型,这时候可以使用instanceof,内部机制是通过原型链来判断的,如果判断的类型是对象,则会返回true,否则返回false

instanceof的原理

instanceof可以正确的判断对象的类型,内部机制是通过判断对象的原型链是不是能找到类型的prototype

instanceof的实现:

  • 首先获取类型的原型
  • 获取对象的原型
  • 循环判断对象的原型是否等于类型的原型,直到对象原型为null,因为原型链的最终为null

类型转换

首先我们要知道,在JS中类型转换只有三种情况,分别是:

  • 转换为boolean
  • 转换为number
  • 转换为string
转boolean

在进行条件判断时,除了undefined、null、false、NaN,'',0,-0,其他所有的值都转为true,包括对象

对象转原始类型

对象在转换类型的时候,会调用内置的[[ToPrimitive]]函数,对于该函数来说,算法逻辑一般如下

  • 调用x.valueOf(),如果转换为基础类型,就返回转换后的值
  • 调用x.toString()
  • 如果以上都没有返回原始类型,就会报错
四则运算(隐式转换)

特点:

  • 运算中其中一方为字符串,那么就会把另一方也转换为字符串
  • 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串

需要注意一点:'a' + + 'b'会返回aNaN,因为第一个加号会将之后的部分隐式转换为number,即NaN,

  • 对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字

this

先来看以下👇几个函数的调用场景

可以试着做一下,以上场景分别会打印出什么结果:

  • 对于直接调用foo来说,不管函数被放在哪个位置,this一定是指向window的
  • 对于obj.foo()来说,只需要记住,谁调用了函数,谁就是this,所以这个场景下,foo函数中的this就是obj对象
  • 对于new的方式来说,this是被永远绑定在c上面的,不会被任何方式改变this

箭头函数的this

function a() {
   return () => {
	  return () => {
	  console.log(this)
	 }
   }
}
	console.log(a()()())
  • 首先箭头函数是没有this的,箭头函数中的this只取决于包裹他的第一个普通函数的this,这里的为a,所以此时的this是window

  • 如果对一个函数进行多次bind

不管我们给函数bind几次,fn中的this永远是由第一次bind决定的,所以结果永远都是window

闭包

定义

闭包的定义其实很简单:函数A内部有一个函数B,在函数B可以访问到函数A中的变量,那么函数B就是闭包。 如下:

闭包存在的意义就是让我们可以间接访问函数内部的变量

经典面试题,循环中使用闭包解决var定义函数的问题

因为setTimeout是异步函数,所以会先把循环全部执行完毕,(即他需要等主进程全部运行完毕之后才会运行,当setTimeout()函数内部回调运行的时候,主进程已经全部运行完毕了)这时候i就是6了,程序会输出6个6。

注意:需要注意的一点是,很多小伙伴会认为产生这种现象的原因是闭包,其实不是的哈,需要特别注意😄

解决方法
  1. 可以使用闭包

首先,我们先使用一个立即执行函数(function(){}()将i传入到函数内部,这个时候值就会固定在参数j上面且不会发生改变,当下次执行timer这个闭包的时候,就可以使用外部函数的变量j,从而实现如下👇效果

2. 使用setTimeout的第三个参数,这个参数会被当成timer函数的惨呼传入

3. 使用let定义i(推荐)

let 声明的变量存在暂时性死区,即只要块级作用域中存在let,那么它所声明的变量就绑定了这个区域,不再受外部的影响。

拓展:var、let以及const的区别

  • var 和 let 用以声明变量,const 用于声明只读的常量;
  • var 声明的变量,不存在块级作用域,在全局范围内都有效,let 和 const 声明的,只在它所在的代码块内有效;
  • let 和 const 不存在像 var 那样的 “变量提升” 现象,所以 var 定义变量可以先使用,后声明,而 let 和 const 只可先声明,后使用;
  • let 声明的变量存在暂时性死区,即只要块级作用域中存在 let,那么它所声明的变量就绑定了这个区域,不再受外部的影响。
  • let 不允许在相同作用域内,重复声明同一个变量;
  • const 在声明时必须初始化赋值,一旦声明,其声明的值就不允许改变,更不允许重复声明; 参考文章

模块化

为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点

优点:

  • 解决命名冲突
  • 提供复用性
  • 提高代码可维护性
立即执行函数

在早期,使用立即执行函数实现模块化是常见的手段。

CommonJS

有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。

参考文章

进程与线程

进程与线程的区别是什么?JS单线程带来的好处?

进程描述了CPU在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。

线程是进程中更小的单位,描述了执行一段指令所需要的时间

为了更好理解,以浏览器作为实例,当打开一个tab页面时,其实就是创建了一个进程,一个进程可以有多个线程,比如渲染线程、JS引擎线程、HTTP请求线程等。当你发起一个请求时,其实就是创建了一个线程,当请求结束之后,线程可能会被销毁

JS单线程优点:节省内存,节约上下文切换时间

手写call、apply、bind函数

实现call
  • 首先context为可选参数,如果不传入的话默认上下文为window
  • 接下来给context创建一个fn属性,并将值设置为需要调用的函数
  • 因为call可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
  • 然后调用函数并将对象上的函数删除
Function.prototype.myCall = function(context) {
	if(typeof this != 'function') {
		// throw语句用来抛出一个用户自定义的异常
		throw new TypeError('error')
	}
	context = context || window
	context.fn = this
	const args = [...arguments].slice(1)
	const result = context.fn(...args)
	delete context.fn
	return result
}

apply实现

Function.prototype.myApply = function(context) {
	if (typeof this !== 'function') {
		throw new TypeError('Error')
	}
	context = context || window context.fn = this
	let result // 处理参数和 call 有区别  
	if (arguments[1]) {
		result = context.fn(...arguments[1])
	} else {
		result = context.fn()
	}
	delete context.fn
	return result
}

bind实现

bind需要返回一个函数,可以直接调用函数,或者new的方式

Function.prototype.myBind = function(context) {
	if (typeof this !== 'function') {
		throw new TypeError('Error')
	}
	const _this = this
	const args = [...arguments].slice(1) // 返回一个函数 
	return function F() {
		// 因为返回了一个函数,我们可以 new F(),所以需要判断     
		if (this instanceof F) {
			return new _this(...args, ...arguments)
		}
		return _this.apply(context, args.concat(...arguments))
	}
}

new

new的原理是什么?通过new创建对象和通过字面量创建有什么区别

在调用new的过程中会发生四件事情

  • 新生成一个对象
  • 链接到原型
  • 绑定this
  • 返回新对象

实现new的过程

  • 创建一个空对象
  • 获取构造函数
  • 设置空对象的原型
  • 绑定this并执行构造函数
  • 确保返回值为对象

为什么0.1 + 0.2 != 0.3

原因

因为js采用的是jeee 754双精度版本(64位),将64位份为三段:

  • 第一位表示符号
  • 接下去的11位用来表示指数
  • 其他的位数表示有效位

0.1 在二进制中是无限循环的一些数字,js采用的浮点数标准会裁掉我们的数字,裁掉之后就会出现精度丢失的问题,也就造成了0.1不是0.1,而是:0.100000000000000002,0.2、0.同理

接下来我们做一个判断:

所以我们可以得到0.1 + 0.2:

解决方案
typeof(parseFloat((0.1 + 0.2).toFixed(10)))

浏览器的内核分别是什么

ie,内核:trident

firefox,内核:gecko

safari,内核:webkit

chrome,内核:blink

opera,内核:blink

每个HTML文件里开头都有个Doctype,知道这是什么吗

声明位于文档中最前面的位置,处于< html >标签之前,可告知浏览器文档使用哪种HTML或者XHTML规范(重点:告知浏览器按照哪种规范解析页面)

为什么利用多个域名来储存网站资源会更有效

CDN缓存更方便、突破浏览器并发限制、节约c锕ookie带宽、节约主域名的链接数、优化页面的相应速度

请描述一下cookies,seeionStorage和localStorage的区别

sessionStorage(会话存储),session中的数据,这些数据只有在同一个会话的页面才能访问,当前会话结束后数据也会随之销毁,因此sessionStorage不是一个持久化的本地存储,仅仅是会话级的存储。而 localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。

web storage和cookie的区别

两者的概念相似,区别是cookie的大小是受限的,并且每次请求一个新的页面的时候,cookie的值都会被发送出去,且需要指定作用域,不可以跨域调用。cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在,而Web storage仅仅是在本地“存储”数据。

简述src与href的区别

scr用于替换当前元素,href用于在当前文档和引用资源之间确立联系。

src指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在的位置;当浏览器解析到该元素的时候,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,这也是为什么js脚本放在底部而不是头部

href指向网络资源所在的位置,建立和当前元素(瞄点/文档/链接)之间的链接,若我们在文档中引入添加一个css文件,那么浏览器会识别该文档,并行下载资源并且不会停止对当前文档的处理。

网页制作的图片格式有哪些

png-8、png-24、jpeg、gif、svg(不做过多解释)

这里要说一个相对比较新的技术:Webp,这种图片格式是google开发的,旨在加快图片加载速度,图片压缩体积大约只有jpeg的2/3,并能节省大量的服务器带宽资源和数据空间,现在facebook等知名网站以及在测试并使用这种格式了,如果面试可以回答出来,是一个亮点

一个页面上有大量的图片,加载很慢,优化图片加载有哪些方法

  • 图片懒加载,这里可以参考👉实现图片懒加载
  • 如果图片为css图片,可以使用精灵图、iconfont、base64等技术
  • 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩后的缩略图,以提高用户体验

什么是塌陷,怎么解决的

原因:在标准文档流中,竖直方向的margin会出现叠加现象(水平方向不会塌陷),两个margin紧挨着,中间没有border或者padding,当两个margin直接接触,就产生了合并,即外边距合并,表现为较大的margin会覆盖掉较小的margin,竖直方向的两个盒子中间只有一个较大的margin,这就是margin塌陷现象。

这个问题在之前的文章:CSS定位基础知识有提到过,可以看一下,以下为几种解决方案:

rgba()和opciaty的透明效果有什么不同

rgba()只作用于元素的颜色或者背景的颜色,而opciaty作用于元素以及元素内所有内容的透明度

CSS中可以让文字在垂直和水平方向上重叠的两个属性是什么

垂直方向:line-height

水平方向:letter-spacing