2023前端面试题整理总结~

自己想换工作,所以准备看看面试题,边学习边记录下来,也方便自己查看。
比较初级哦,因为自己也不是很厉害~但会加油哒
从2022年7月24开始记录哒
到2022年8月12日停止记录(有觉得好的会持续更新)
2023年在持续更新中~~

简述浏览器的渲染过程,重绘和重排在渲染过程中的哪一部分?

基本流程包括:解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树

重绘:元素的视觉表现属性被改变即触发重绘,如visibility、opacity ,不会影响到 dom 结构。改变某个元素的背景色、文字颜色、边框颜色等等
不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。

重排/回流:就是渲染树的一部分必须要更新,并且节点的尺寸发生了变化。重排也会触发重绘。常见情况:
1) DOM 操作,添加或者删除可见的DOM元素等
2) 元素位置改变
3) 修改 width、display 等 CSS 属性
4) 内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变
5) 浏览器窗口变化(滚动或缩放)
6) 页面渲染初始化
注意:重排(回流)必将引起重绘,而重绘不一定会引起回流。 display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。

什么是闭包,什么是立即执行函数,它的作用是什么?简单说一下闭包的使用场景

闭包:定义在一个函数内部的函数。
闭包的使用场景:settimeout、回调、函数防抖、封装私有变量

立即执行函数:声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;
			也可以说立即执行函数是一种语法,让你的函数在定义以后立即执行;
立即执行函数的作用:
	1.不必为函数命名,避免了污染全局变量。
	2.立即执行函数内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
	3.封装变量。

localstorage 与 cookie 的区别是什么?

存储大小
cookie:一般不超过4K(因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识)
localStorage:5M或者更大

数据有效期
cookie:一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器cookie失效,若设置了时间,cookie就会存放在硬盘里,过期才失效
localStorage:永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除,因此用作持久数据

作用域
cookie:在所有同源窗口中都是共享的
localStorage:在所有同源窗口中都是共享的

通信
ccokie:十种携带在同源的http请求中,即使不需要,故cookie在浏览器和服务器之间来回传递;如果使用cookie保存过多数据会造成性能问题
localStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信;不会自动把数据发送给服务器,仅在本地保存

易用性
cookie:需要自己进行封装,原生的cookie接口不够友好
localStorage:原生接口可以接受,可以封装来对Object和Array有更好的支持

应用场景
cookie:判断用户是否登录过网站,以便实现下次自动登录或记住密码;保存事件信息等
localStorage:常用于长期登录(判断用户是否已登录),适合长期保存在本地的数据

简述 CSS 有哪些上下文类型?

1、后代选择器,可选择当前元素的所有后代元素;
2、父子选择器,可选择当前元素的所有子元素;
3、同级相邻选择器,可选择拥有共同父级且相邻的元素;
4、同级所有选择器,可选择拥有共同父级的后续所有元素。

简述 ES6 的新特性

...、箭头函数、promise、async/await、let和const、set集合、导入improt 导出export default、smybol新的基本类型

Javascript 可以保存的最大数值是多少?

超过16位整数就无法精确地表示了  超过这个最大值,所有的奇数都会+1或者-1变成偶数,无法准确赋值

CSS

1.margin和padding的区别?
它们的作用对象不同,padding针对于自身的,margin作用于外部对象的。

2.vw和百分比有什么区别?
百分比有继承关系,继承至父级;vw只和设备的宽度有关系;

3.行内与块级元素
行内元素:不会自动换行,不可以设置宽高,由内容决定,对margin设置左右方向有效,而上下无效;
padding设置左右方向有效,而上下无效;
块级元素:默认占满宽度,宽度有继承关系,可以自动换行,设置margin和padding都有效;

4.如何让谷歌浏览器支持小字体

//利用缩放来让字体变小,字体为12px的时候就小不了了。
.small-font{
	transform:scale(0.5);
	-webkit-transform:scale(0.5);
}

5. flex:1代表什么?

flex:1 === flex: 1 1 0;
第一个参数表示:flex-grow 定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
第二个参数表示:flex-shrink 定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
第三个参数表示:flex-basis 给上面两个属性分配多余空间之前,计算项目是否有多余空间。默认值为auto,即项目本身大小。

JS

1.let、var、const

区别:
let : 块级作用域;暂时性死区(声明变量前,该变量是不可用的);不存在变量提升;不需要初始化;
var : 不是块级作用域;不存在暂时性死区;存在变量提升;声明的是全局变量,给全局添加属性;不需要初始化;可以重复声明
const : 块级作用域;暂时性死区;不存在变量提升;必须初始化;不允许改变指针;

var a = 100;
function test() {
	a = 10;
	console.log(a);
	console.log(this.a);
	var a;
	console.log(a);
}
test()
// 10 100 10
for (var i = 0; i < 5; i++) {
	setTimeout(function() {
		console.log(i)
	}, 0)
}
// 5 5 5 5 5
for (let i = 0; i < 5; i++) {
	setTimeout(function() {
		console.log(i)
	}, 0)
}
// 0 1 2 3 4
内存中的堆和栈
栈:先进后出,自动分配释放
堆:先进先出,手动释放,容易内存泄漏

基本数据类型:null、undefined、String、Number、Boolean、Symbol(ES6) 基本数据类型可以直接访问,
按值进行分配,存放在**栈内存**中的简单数据段
引用型:OBject,存放在**堆内存**中,实际栈保存的是一个指针,这个指针指向另一个位置(**堆内存**)

2.深拷贝与浅拷贝

深拷贝和浅拷贝只针对Object和Array这样的引用数据类型的。

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,考本的就是基本类型的值,如果属性是应用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟了一个新的区域存放新对象,且修改新对象不会影响原对象。

数组与对象赋值是浅拷贝;一维的数组和对象可以看做是深拷贝;二维的是浅拷贝;方法不能拷贝;

浅拷贝的实现方式:
1.展开运算符…
2.Object.assign()

let obj1 = {
	person: {
		name: "不完美女孩"
	},
	age: '14'
};
let obj2 = Object.assign({}, obj1);
obj2.person.name = "哈哈哈";
obj2.age = '23'
console.log(obj1); // { person: { name: '哈哈哈'}, age: '14' }

3.Array.prototype.concat()

let arr = [1, 3, {
   name: '不完美女孩'
    }];
let arr2 = arr.concat();    
arr2[2].name = '哈哈哈哈';
console.log(arr); //[ 1, 3, { name: '哈哈哈哈' } ]

4.Array.prototype.slice()

let arr = [1, 3, {
   name: '不完美女孩'
    }];
let arr2 = arr.slice();    
arr2[2].name = '哈哈哈哈';
console.log(arr); //[ 1, 3, { name: '哈哈哈哈' } ]

标准的深拷贝应该怎么写?
1.JSON.parse(JSON.stringify())

2.function dee(socur) {
	const targetObj = socur.constructor === Array ? [] : {};
	for (let keys in socur) {
		if (socur.hasOwnProperty(keys)) {
			// 应用数据类型
			if (socur[keys] && typeof socur[keys] === 'object') {
				targetObj[keys] = socur[keys].constructor === Array ? [] : {};
				// 递归
				targetObj[keys] = dee(socur[keys])
			} else {
				// 基础数据类型直接赋值
				targetObj[keys] = socur[keys]
			}
		}
	}
	return targetObj
}

let objc = {
	a: '嗯嗯',
	b: 1,
	c: [1, 2, 3],
	d: {
		arr: '10',
		age: '78'
	}
}
let newobjc = dee(objc)
newobjc.a = '哦哦'
newobjc.c.push(5)
newobjc.d.arr = '修改'
console.log(objc, newobjc)

3.promise
为什么会出现promise?
JavaScript是单线程语言,在执行任务的时候,会先执行第一个任务再执行第二个任务,假如第一个任务是个耗时的任务那么第二个任务就一直阻塞不能执行,那么这个时候就需要异步来处理这个操作;

同步:主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务;
异步:不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程;

最常用的异步操作就是将回调函数作为异步回调的参数来使用,而promise是通过then来做的;
new promise 的时候就会被立即执行,因而为了实现用的时候调用所以将promise写在函数里面;

面试题练习感觉这个很不错,可以看一看。

// 面试题 
Promise.resolve().then(()=>{
	console.log('Promise1');
	setTimeout(()=>{
		console.log('setTimeout2')
	},0)
})
setTimeout(()=>{
	console.log('setTimeout1')
	Promise.resolve().then(()=>{
		console.log('Promise2')
	})
})
// Promise1 setTimeout1 Promise2 setTimeout2

4.原型与原型链

(1) 原型 prototype
常规的数组 [ ] 和对象 { } 是没有原型的,原型是函数function特有的;
每一个函数都会有prototype属性,被称为显式原型;
每一个实例对象都会有__proto__属性,其被称为隐式原型;

(2) 原型链 proto => [[prototype]]
原型链是大家都有的。
获取对象时,如果这个对象身上本身没有这个属性时,它就会去他的原型__proto__上去找,如果还找不到,就去原型的原型上去找…一直找到最顶层(Object.prototype)为止,Object.prototype对象也有__proto__属性值为null。

每一个prototype原型上都会有一个constructor属性,指向它关联的构造函数。

找私有属性用:hasOwnProperty

这里有两道经典面试题可以看看

function Person() {

}
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin

// prototype
function Person() {

}
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin

// __proto__
function Person() {

}
var person = new Person();
console.log(person.__proto__ === Person.prototype) // true

//constructor
function Person() {

}
console.log(Person === Person.prototype.constructor) // true

//综上所述
function Person() {

}
var person = new Person()
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
//顺便学习一下ES5得方法,可以获得对象得原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true

//实例与原型
function Person() {

}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin

//原型得原型
var obj = new Object();
obj.name = 'Kevin',
	console.log(obj.name) //Kevin

//原型链
console.log(Object.prototype.__proto__ === null) //true
// null 表示"没用对象" 即该处不应该有值

// 补充
function Person() {

}
var person = new Person()
console.log(person.constructor === Person) // true
//当获取person.constructor时,其实person中并没有constructor属性,当不能读取到constructor属性时,会从person的原型
//也就是Person.prototype中读取时,正好原型中有该属性,所以
person.constructor === Person.prototype.constructor

//__proto__
//其次是__proto__,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于Person.prototype中,实际上,它
// 是来自与Object.prototype,与其说是一个属性,不如说是一个getter/setter,当使用obj.__proto__时,可以理解成返回了
// Object.getPrototypeOf(obj)
 总结:

 1、当一个对象查找属性和方法时会从自身查找,如果查找不到则会通过__proto__指向被实例化的构造函数的prototype

 2、隐式原型也是一个对象,是指向我们构造函数的原型

 3、除了最顶层的Object对象没有__proto_,其他所有的对象都有__proto__,这是隐式原型

 4、隐式原型__proto__的作用是让对象通过它来一直往上查找属性或方法,直到找到最顶层的Object的__proto__属性,它的值是null,这个查找的过程就是原型链

5.防抖与节流

函数防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
应用场景:搜索框搜索输入。只需用户最后一次输入完,再发送请求。
手机号、邮箱验证输入检测。
窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

function debounce(fn, delay) {
    let timer;
    return function () {
        let args = arguments;
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(()=> {
            fn.apply(this, args); 
        }, delay);
    };
}

函数节流:每隔一段时间,只执行一次函数。
应用场景:滚动加载,加载更多或滚到底部监听。
谷歌搜索框,搜索联想功能。
高频点击提交,表单重复提交。

function throttle(fn, delay) {
    let timer;
    return function () {
        let _this = this;
        let args = arguments;
        if (timer) {
            return;
        }
        timer = setTimeout(function () {
            fn.apply(_this, args);
            timer = null; // 在delay后执行完fn之后清空timer,此时timer为假,throttle触发可以进入计时器
        }, delay)
    }
}

6.闭包
闭包是什么? 方法里面返回方法
闭包存在的意义是什么?(1) 延长变量的生命周期;(2) 创建私有环境;

7.箭头函数和普通函数有什么区别?
(1). 箭头函数更加简洁;
(2). 箭头函数不会创建自己的this,所以它没有自己的this,它只会在自己作用域的上一层继承this,所以箭头函数中的this指向在它定义时就确认了,之后不会再改变,所以箭头函数的this值永远不会改变;
(3). call()、apply()、bind()等方法不能改变箭头函数this的的指向;
(4). 箭头函数不能作为构造函数使用;
(5). 箭头函数没有自己的arguments;
(6). 箭头函数没有prototype(原型),原型是undefined;

8.get请求传参长度的误区
HTTP协议从未规定GET/POST的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。
(1) HTTP协议未规定GET和POST的长度限制
(2) GET的最大长度显示是因为浏览器和web服务器限制了URL的长度
(3) 不同的浏览器和WEB服务器,限制的最大长度不一样
(4) 要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度为8182byte

9.get和post请求在缓存方面的区别
get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
post不同,post一般做的是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适用于请求缓存。

10.DOM操作与BOM操作
(1) DOM操作
例如:document.getElementById 就是dom操作
DOM事件模型和事件流
DOM事件模型分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
(1)捕获阶段:事件从window对象自上而下向目标节点传播的阶段;
(2)目标阶段:真正的目标节点正在处理事件的阶段;
(3)冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。
如何阻止冒泡?
通过 event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行。

事件代理(事件委托)
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理。
事件代理优点:
使代码简洁;减少浏览器的内存占用;

(2) BOM操作
BOM(浏览器对象模型)是浏览器本身的一些信息的设置和获取,例如获取浏览器的宽度、高度,设置让浏览器跳转到哪个地址。
例如:window.screen对象:包含有关用户屏幕的信息
window.location对象:用于获得当前页面的地址(URL),并把浏览器重定向到新的页面
window.history对象:浏览历史的前进后退等
window.navigator对象:常常用来获取浏览器信息、是否移动端访问等等

11.跨域
什么是跨域?
当协议、子域名、主域名、端口号中任意一个不相同时都算做不同域,不同域之间相互请求资源,就算作“跨域”。

常见的几种跨域解决方案(https://github.com/ljianshu/Blog/issues/55):
JSONP:利用同源策略对 script 标签不受限制,不过只支持GET请求
CORS:实现 CORS 通信的关键是后端,服务端设置 Access-Control-Allow-Origin 就可以开启,备受推崇的跨域解决方案,比 JSONP 简单许多
Node中间件代理或nginx反向代理:主要是通过同源策略对服务器不加限制
vue-cli代理跨域:devServer

12.New操作符做了什么事情?
(1) 创建了一个新对象;
(2) 将对象与构造函数的原型链连接起来;
(3) 将构造函数中的this绑定到新建的对象上;
(4) 根据构造函数返回类型做判断,如果是值类型,返回创建的对象;如果是引用类型,返回这个引用类型的对象;

13.说一下eventloop
event loop是一个执行模型,在不同的地方有不同的实现。
宏队列和微队列
宏队列,macrotask,也叫tasks。例如:

  • setTimeout
  • setInterval
  • DOM事件
  • AJAX请求
  • setImmediate(Node独有的)
  • I/O
  • requestAnimationFrame(浏览器独有)
  • UI rendering(浏览器独有)

微队列,microtask,也叫jobs。例如

  • Promise
  • async/await
  • Object.observe
  • process.nextTick(Node独有)
  • MutationObserver

微任务 > DOM渲染 > 宏任务

14.Set 和 Map有什么区别?
(1) Map是键值对,Set是值得合集,当然键和值可以是任何的值;
(2) Map可以通过get方法获取,而set不能因为它只有值;
(3) 都能通过迭代器进行for…of遍历;
(4) Set的值是唯一的可以做数组去重,而Map由于没有格式限制,可以做数据存储;

Map与对象的互换

const obj = {}
const map = new Map([
	['a', 2],
	['b', 3]
])
for (let [key, value] of map) {
	obj[key] = value
}
console.log(obj)
// {a: 2, b: 3}

15.Loader和Plugin 有什么区别
Loader:直译为“加载器”。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到‘loader’。所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。Plugin:直译为“插件”。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。在Webpack运行的生命周期中会广播出许多事件,Plugin可以 监听这些事件,在合适的时机通过Webpack提供的API改变输出结果。

16.在地址栏里输入一个地址回车会发生那些事情?
(1) 解析URL:首先会对URL进行解析,分析所需要使用的传输协议和请求的资源路径。如果输入的URL中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查URL中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。
(2) 缓存判断:浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。
(3) DNS解析:下一步首先需要获取的是输入的URL中的域名的IP地址,首先判断本地是否有该域名的IP地址缓存,如果有则使用,如果没有则向本地DNS服务器发起请求。本地DNS服务器也会检查是否存在缓存,如果没有就会向根域名服务器发起请求,获得负责的顶级域名服务器的地址后,再向顶级域名服务器请求,然后获得负责的权威域名服务器的地址后,再向权威域名服务器发起请求,最终获得域名的IP地址后,本地DNS再将这个IP地址返回给请求的用户。用户向本地DNS服务器发起请求属于递归请求,本地DNS服务器向各级域名服务器发起请求属于迭代请求。
(4) 获取MAC地址:当浏览器得到IP地址后,数据传输还需要知道目的主机MAC地址,因为应用层下发数据给传输层,TCP协议会指定源端口号和目的端口号,然后发给网络层。网络层会将本地地址作为源地址,获取的IP地址作为目的地址。然后将下发给数据链路层,数据链路层的发送需要加入通信双方的MAC地址,本机的MAC地址作为源MAC地址,目的MAC地址需要分情况处理。通过将IP地址与本机的子网掩码相与,可以判断是否与主机在同一个子网里,如果在同一个子网里,可以使用APR协议获取到目的地址MAC地址,如果不在一个子网里,那么请求应该转发给网关,由它代为转发,此时同样可以通过APR协议来获取网关的MAC地址,此时目的主机的MAC地址应该为网关的地址。
(5) TCP三次握手:下面是TCP建立连接的三次握手的过程,首先客户端向服务器发送一个SYN连接请求报文段和一个随机序号,服务端接收到请求后向客户端发送一个SYN ACK报文段,确认连接请求,并且也向客户端发送一个随机序号。客户端接收服务器的确认应答后,进入连接建立的状态,同时向服务器也发送一个ACK确认报文段,服务器端接收到确认后,也进入连接建立状态,此时双方的连接就建立起来了。
(6) HTTPS握手: 如果使用的是 HTTPS 协议, 在通信前还存在 TLS 的一个四次握手的过程。 首先由客户端向服务器端发送使用的协议的版本号、 一个随机数和可以使用的加密方法。 服务器端收到后, 确认加密的方法, 也向客户端发送一个随机数和自己的数字证书。 客户端收到后, 首先检查数字证书是否有效, 如果有效, 则再生成一个随机数, 并使用证书中的公钥对随机数加密, 然后发送给服务器端, 并且还会提供一个前面所有内容的 hash 值供服务器端检验。 服务器端接收后, 使用自己的私钥对数据解密, 同时向客户端发送一个前面所有内容的 hash 值供客户端检验。 这个时候双方都有了三个随机数, 按照之前所约定的加密方法, 使用这三个随机数生成一把秘钥, 以后双方通信前, 就使用这个秘钥对数据进行加密后再传输。
(7) 返回数据: 当页面请求发送到服务器端后, 服务器端会返回一个 html 文件作为响应, 浏览器接收到响应后, 开始对 html 文件进行解析, 开始页面的渲染过程。
(8) 页面渲染: 浏览器首先会根据 html 文件构建 DOM 树, 根据解析到的 css 文件构建 CSSOM 树, 如果遇到 script 标签, 则判端是否含有 defer 或者 async 属性, 要不然 script 的加载和执行会造成页面的渲染的阻塞。 当 DOM 树和 CSSOM 树建立好后, 根据它们来构建渲染树。 渲染树构建好后, 会根据渲染树来进行布局。 布局完成后, 最后使用浏览器的 UI 接口对页面进行绘制。 这个时候整个页面就显示出来了。
(9) TCP四次挥手: 最后一步是 TCP 断开连接的四次挥手过程。 若客户端认为数据发送完成, 则它需要向服务端发送连接释放请求。 服务端收到连接释放请求后, 会告诉应用层要释放 TCP 链接。 然后会发送 ACK 包, 并进入 CLOSE_WAIT 状态, 此时表明客户端到服务端的连接已经释放, 不再接收客户端发的数据了。 但是因为 TCP 连接是双向的, 所以服务端仍旧可以发送数据给客户端。 服务端如果此时还有没发完的数据会继续发送, 完毕后会向客户端发送连接释放请求, 然后服务端便进入 LAST
- ACK 状态。 客户端收到释放请求后, 向服务端发送确认应答, 此时客户端进入 TIME -
WAIT 状态。 该状态会持续 2 MSL( 最大段生存期, 指报文段在网络中生存的时间, 超时会被抛弃) 时间, 若该时间段内没有服务端的重发请求的话, 就进入 CLOSED 状态。 当服务端收到确认应答后, 也便进入 CLOSED 状态。

17.UDP和TCP有什么区别

2023前端面试题整理总结~_第1张图片
18.哪些情况会导致内存泄漏

(1) 意外的全局变量:由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收
(2) 被遗忘的计时器或回调函数:设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
(3) 脱离 DOM 的引用:获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
(4) 闭包:不合理的使用闭包,从而导致某些变量一直被留在内存当中。

19.说一下常见的检测数据类型的几种方式?
(1) typeof 其中数组、对象、null都会被判断为Object,其他判断都正确
(2) instanceof 只能判断引用数据类型,不能判断基本数据类型
(3) constructor 它有2个作用 一是判断数据的类型,二是对象实例通过constructor对象访问它的构造函数。需要注意的事情是如果创建一个对象来改变它的原型,constructor就不能来判断数据类型了
(4) Object.prototype.toString.call() 使用 object 对象的原型方法 tostring 来判断数据类型

instanceof和typeof的区别:
instanceof:
返回值为布尔值。
instanceof 用于判断一个变量是否属于某个对象的实例。

typeof:
返回值是一个字符串, 用来说明变量的数据类型。
typeof 一般只能返回如下几个结果: number, boolean, string, function, object, undefined。

20.说一下怎么把类数组转换为数组?

//通过call调用数组的slice方法来实现转换
Array.prototype.slice.call(arrayLike)

//通过call调用数组的splice方法来实现转换
Array.prototype.splice.call(arrayLike,0)

//通过apply调用数组的concat方法来实现转换
Array.prototype.concat.apply([],arrayLike)

//通过Array.from方法来实现转换
Array.from(arrayLike)

21.说一下数组如何去重,你有几种方法?

let arr = [1,1,"1","1",true,true,"true",{},{},"{}",null,null,undefined,undefined]

// 方法1
let uniqueOne = Array.from(new Set(arr)) console.log(uniqueOne)

// 方法2
let uniqueTwo = arr => {
    let map = new Map(); //或者用空对象 let obj = {} 利用对象属性不能重复得特性
    let brr = []
    arr.forEach( item => {
        if(!map.has(item)) { //如果是对象得话就判断 !obj[item]
            map.set(item,true) //如果是对象得话就obj[item] =true 其他一样
            brr.push(item)
        }
    })
    return brr
}
console.log(uniqueTwo(arr))

//方法3
let uniqueThree = arr => {
    let brr = []
    arr.forEach(item => {
        // 使用indexOf 返回数组是否包含某个值 没有就返回-1 有就返回下标
        if(brr.indexOf(item) === -1) brr.push(item)
        // 或者使用includes 返回数组是否包含某个值 没有就返回false 有就返回true
        if(!brr.includes(item)) brr.push(item)
    })
    return brr
}
console.log(uniqueThree(arr))

//方法4
let uniqueFour = arr => {                                         
     // 使用 filter 返回符合条件的集合
    let brr = arr.filter((item,index) => {
        return arr.indexOf(item) === index
    })
    return brr
}
console.log(uniqueFour(arr))

22.说一下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、如果对象中存在循环引用的情况也无法正确实现深拷贝;

23.说一下for…in 和 for…of的区别?

for…of遍历获取的是对象的键值, for…in获取的是对象的键名;
for…in会遍历对象的整个原型链, 性能非常差不推荐使用,而for…of只遍历当前对象不会遍历原型链;
对于数组的遍历,for…in会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of只返回数组的下标对应的属性值;
总结:for…in循环主要是为了遍历对象而生,不适用遍历数组; for…of循环可以用来遍历数组、类数组对象、字符串、Set、Map以及Generator对象

24.说一下HTTP和HTTPS协议的区别?
1、HTTPS协议需要CA证书,费用较高;而HTTP协议不需要
2、HTTP协议是超文本传输协议,信息是明文传输的,HTTPS则是具有安全性的SSL加密传输协议;
3、使用不同的连接方式,端口也不同,HTTP协议端口是80,HTTPS协议端口是443;
4、HTTP协议连接很简单,是无状态的;HTTPS协议是具有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全

25.文档碎片减少DOM操作

let list = document.getElementById('list')
let fragment = document.createDocumentFragment()
for (let i = 0; i < 5; i++) {
	const item = document.createElement('li');
	item.innerHTML = `项目${i}`
	// list.appendChild(item)
	fragment.appendChild(item)
}
list.appendChild(fragment)

26.判断数组的方式有哪些?

1.object.prototype.toString.call()
2.通过原型链判断:obj._proto_==Array.prototype
3.Es6:Array.isArray(obj)
4.obj instanceof Array
5.Array.prototype.isPrototypeOf(obj)

27.如何判断一个对象是空对象?

1.使用JSON自带的.stringify万法来判断:
 if(Json.stringify(obj)=='{}'){
	console.log('空对象')
 }
2.使用ES6新增的方法Object.keys()来判断:
 if(Object.keys(Obj).length<0){
	console.log('空对象')
 }

ES6

1.Es6常见的语法你知道哪一些?

let promise 箭头函数 展开运算符 Object.assign()

Vue

1.MVC和MVVM
MVVM的特点: 在MVVM的框架下,视图和模型是不能直接通信的,它们通过ViewModal来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信。

2.v-model的原理

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

<input type="text" placeholder="输入内容" id="inp">
输入的内容是:<span id="box"></span>
<script>
	let obj = {}
	document.getElementById('inp').addEventListener('keyup', function() {
		obj.inp = event.target.value
	})
	Object.defineProperty(obj, 'inp', {
		set: function(val) {
			console.log('设置值')
			document.getElementById('box').innerHTML = val
		},
		get: function() {
			console.log('取值')
		}
	})
</script>

3.data()为什么是函数?
每一个组件都有自己的私有作用域,确保各组件数据不会被干扰。

4.v-if与v-show的区别?使用场景分别是什么?
v-if:显示隐藏是将dom元素整个添加或删除;适用于运行时条件很少改变。
v-show:隐藏则是为该元素添加css–display:none,dom元素依旧还在;适用于非常频繁地切换。(不能用于权限操作)

5.虚拟dom
(1) 虚拟dom是什么?
vue2.x才有的虚拟dom;本质是js对象;
(2) 虚拟dom在vue中做了什么?
[1].将真实dom转化虚拟dom(js对象);
[2].更新的时候做对比;
(3) 虚拟dom是如何提升vue的渲染效率的?
[1].局部更新(节点更新);
[2].将直接操作dom的地方拿到两个js对象之中去做比较;

6.DIFF中的patch() 方法
还不太明白,后续明白了补充。

7. r o u t e r 和 router和 routerroute的区别

route是当前正在跳转的路由对象,可以从route里面获取hash,name,path,query,mathsr等属性方法(接收参数时使用)

router跳转连接就可以使用

query和params 有何区别?
用法:query用path来引入,params只能用name来传递,不能使用path。
如果params中把name写成了path,接收参数页面将会是undefined。
query更像是get请求(会在地址栏显示参数);params更像post方法传递(不会在地址栏显示)。

// query传递
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
<!-- 跳转并携带query参数,to的对象写法 -->
<!-- name:'detail' 可以写路由的name 也可以直接写路径 -->
<router-link 
	:to="{
		path:'/home/message/detail',
		query:{
		   id:666,
        title:'你好'
		}
	}"
>跳转</router-link>


// params传递
<router-link :to="`/home/news/shownews/${n.id}/${n.name}`">{{ n.name }}</router-link> 
<router-link
  :to="{
    // 用params传递参数时用到他的对象写法中不可使用 path:'路径'
    name: 'shownews',
    params: {
      id: n.id,
      name: n.name,
    },
  }"
>
  {{ n.name }}
</router-link>

// 获取
$route.params.id
$route.query.id


不借助router-link 也可以利用 $router的两个API 
 //$router的两个API
 //push跳转 有操作记录
 this.$router.push({
 	name:'xiangqing',
 		params:{
 			id:xxx,
 			title:xxx
 		}
 })
 //replace 跳转没有操作记录  把之前的replace跳转记录销毁
 this.$router.replace({
 	name:'xiangqing',
 		params:{
 			id:xxx,
 			title:xxx
 		}
 })


// 缓存路由组件:让不展示的路由组件保持挂载,不被销毁。
<keep-alive include="News">
   <router-view></router-view>
</keep-alive>

两个生命周期函数:
actived:路由组件被激活时触发;
deactived:路由组件失活时触发。

路由守卫的作用 : 对路由进行权限管理,必须符合条件才能访问。
路由守卫有三种: 全局守卫、独享守卫、组件内守卫
在所有的路由发生改变前都执行  使用路由守卫就不能直接暴露路由实例,需要接收一下

// 全局守卫

// 然后调用里面的beforeEach((to,from,next)=>{})  
// 有三个参数to:去哪个路径,from:从哪个路径里来,next:是个函数调用的时候next()放行
// 配置在实例对象外 初始化时调用,每次发生路由变化前调用
 router.beforeEach((to,from,next)=>{
 	console.log('beforeEach',to,from)
 	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
 		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
 			next() //放行
 		}else{
 			alert('暂无权限查看')
 			// next({name:'guanyu'})
 		}
 	}else{
 		next() //放行
 	}
 })
 
 //全局后置守卫:初始化时执行、每次路由切换后执行
 router.afterEach((to,from)=>{
 	console.log('afterEach',to,from)
 	if(to.meta.title){ 
 		document.title = to.meta.title //修改网页的title
 	}else{
 		document.title = 'vue_test'
 	}
 })


// 独享守卫
//放在需要进行权限设置的路由里面,参数语法和全局一样  当访问这个路径前才执行beforeEnter()
beforeEnter(to,from,next){
 	console.log('beforeEnter',to,from)
 	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
 		if(localStorage.getItem('school') === 'atguigu'){
 			next()
 		}else{
 			alert('暂无权限查看')
 			// next({name:'guanyu'})
 		}
 	}else{
 		next()
 	}
 }



//第三种,组件守卫
// 放在组件里和methods,components同级别 ,必须是通过路由规则进入该组件才可以调用beforeRouteEnter(),beforeRouteLeave()

//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
   },
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
   }

8.什么是vue声明周期?
Vue实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载DOM→渲染、更新→渲染、卸载等一系列过程,我们称这是Vue的声明周期。

Vue声明周期的作用是什么?
它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

Vue生命周期总共有几个阶段?
8个阶段:创建前/后,载入前/后,更新前/后,销毁前/后。

第一次页面加载会触发那几个钩子?
第一次页面加载时会触发beforeCreate,created,beforeMount,mounted这几个钩子

每个生命周期适合那些场景?
1、beforeCreate(创建前) :数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
2、created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。
3、beforeMount(挂载前) :在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
4、mounted(挂载后) :在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。
5、beforeUpdate(更新前) :响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
6、updated(更新后):在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
7、beforeDestroy(销毁前) :实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。
8、destroyed(销毁后) :实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
另外还有 keep-alive 独有的生命周期,分别为 activateddeactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。

9.简述Vue的响应式原理
当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。
每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

10.Hash和history有什么区别
vue项目默认是hash
1.hash就是指url后面的#号以及后面的字符,history没有带#;
2.原理:
(1) hash通过监听浏览器的onhaschange()事件变化,查找对应的路由规则;
(2) history原理:理由H5的history中新增的两个API push State()和replace State() 和一个事件onpopstate监听url变化;
3.hash能兼容到IE8,history只能兼容到IE10;
4.由于hash值变化不会导致浏览器向服务器发出请求,而且hash改变会触发hashchange事件(hashchange只能改变#后面的url片段);虽然hash路径出现在url中,但是不会出现在HTTP请求中,对后端完全没有影响,因此改变hash值不会重新加载页面,基本都是使用hash来实现前端路由的。

11.为什么 Vuex 的 mutation 中不能做异步操作?
(1) Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过Action来提交mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。
(2) 每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的跟踪,给调试带来困难。

12.computed 计算属性和watch监听区别?

computed 必须return,不能有异步,所依赖的属性不变的时候会调用缓存(使用:当一个属性受多个属性影响时,例如:购物车结算)
watch 可以没有return,可以有异步,每次监听变化都会调用回调(使用:当一个数据影响多条数据时,例如:搜索框)

13.Vue的父子组件生命周期钩子函数执行顺序?

<!-- 加载渲染过程 -->
<!-- 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted -->
<!-- 子组件更新过程 -->
<!-- 父beforeUpdate -> 子beforeUpdate -> 子updaed -> 父updated -->
<!-- 父组件跟新过程 -->
<!-- 父beforeUpdate -> 父updated -->
<!-- 销毁过程 -->
<!-- 父beforeDestroy -> 子beforeDestroy -> 子destroyed ->父destroyed -->

14.说一下vue3.0你了解多少?

vue2中,我们一般会采用mixin来复用逻辑代码,挺好用的,但是存在一些问题:例如代码来源不清晰、方法属性等冲突。
基于此在vue3中引入了Composition API(组合API),使用纯函数分隔复用代码。

Fragment在vue2中,组件必须只有一个根节点,很多时候会添加一些没有意义的节点用于包裹。Fragment组件就是用于解决这个问题的

15.父子组件传值

props、$emit、$refs、bus、vuex、provide / inject

父传子:
在父组件中,用v-bind动态绑定一个自定义属性,给子组件传递数据。
在子组件中,使用props接收父组件传过来的数据。

子传父:
子组件通过事件的方式,利用$emit给父组件传值。($emit的第一个参数是父组件自定义事件的方法名,后面的“value”是子组件要给父组件传递的数据)。
在父组件中,绑定一个自定义事件,用来接收子组件传来的值。

$parent 可以用来从一个子组件访问父组件的实例
$ref 可以用来获取子组件的数据(ref只在dom渲染完后才会有,可以在生命周期mounted(){}钩子中调用,或者在this.$nextTick(()=>{})中调用)。

provide / inject:
传递的时候:以对象形式传递过去
provide() {
    return {
      sonDate: '子组件数据',
      childDate: '孙组件数据'
    }
  },
 接收的时候和props接收方式一样:
 inject:['参数']

Bus
bus.js
import Vue from 'vue'
const bus = new Vue()
export default bus 
使用:先引入bus文件
传递:bus.$emit('message','传递的值')
接收:bus.$on('message',(e)=>{e就是获取到的值})

16.defineProperty和proxy的区别

Object.defineProperty 是 Es5 的方法,Proxy 是 Es6 的方法
defineProperty 不能监听到数组下标变化和对象新增属性,Proxy 可以
defineProperty 是劫持对象属性,Proxy 是代理整个对象
defineProperty 局限性大,只能针对单属性监听,所以在一开始就要全部递归监听。Proxy 对象嵌套属性运行时递归,用到才代理,
也不需要维护特别多的依赖关系,性能提升很大,且首次渲染更快。
defineProperty 会污染原对象,修改时是修改原对象,Proxy 是对原对象进行代理并会返回一个新的代理对象,修改的是代理对象
defineProperty 不兼容 IE8,Proxy 不兼容 IE11

17.vuex有哪几种属性?

state:存放公共数据的地方;
getter:获取根据业务场景返回的数据;
mutations:唯一修改state的方法,修改过程是同步的;
action:异步处理,通过分发操作触发mutation;
moule:将store模块分割,减少代码臃肿;

18.vue , 微信小程序 , uni-app属性的绑定

vue和uni-app动态绑定一个变量的值为元素的某个属性的时候,会在属性前面加上冒号":";
小程序绑定某个变量的值为元素属性时,会用两个大括号{{}}括起来,如果不加括号,为被认为是字符串。

18.vue , 微信小程序 , uni-app的页面生命周期函数

vue:
	beforeCreate(创建前)
	created(创建后)
	beforeMount(载入前,挂载)
	mounted(载入后)
	beforeUpdate(更新前)
	updated(更新后)
	beforeDestroy(销毁前)
	destroyed(销毁后)
小程序/uni-app: 
	1. onLoad:首次进入页面加载时触发,可以在 onLoad 的参数中获取打开当前页面路径中的参数。
	2. onShow:加载完成后、后台切到前台或重新进入页面时触发
	3. onReady:页面首次渲染完成时触发
	4. onHide:从前台切到后台或进入其他页面触发
	5. onUnload:页面卸载时触发
	6. onPullDownRefresh:监听用户下拉动作
	7. onReachBottom:页面上拉触底事件的处理函数
	8. onShareAppMessage:用户点击右上角转发

19.vue、小程序、uni-app中的本地数据存储和接收

vue:
	存储:localstorage.setItem(‘key’,‘value’)
	接收:localstorage.getItem(‘key’)

微信小程序:
	存储:通过wx.setStorage/wx.setStorageSync写数据到缓存
	接收:通过wx.getStorage/wx.getStorageSync读取本地缓存,

uni-app:
	存储:uni.setStorage({key:“属性名”,data:“值”}) //异步 
		uni.setStorageSync(KEY,DATA) //同步
	接收:uni.getStorage({key:“属性名”,success(res){res.data}}) //异步
		uni.getStorageSync(KEY) //同步

20.vue-router 有哪几种导航守卫?

全局守卫、路由独享守卫、路由组件内的守卫

//路由的钩子函数总结有6个

全局的路由钩子函数:beforeEach、afterEach

单个的路由钩子函数:beforeEnter

组件内的路由钩子函数:beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate

21.nextTick的作用和使用场景?

vue中的nextTick主要用于处理数据动态变化后,DOM还未及时更新的问题,用nextTick就可以获取数据更新后最新DOM的变化,
数据更新和DOM更新不是同步,是异步的。

22.为什么说vue是一个渐进式的javascript框架,渐进式是什么意思?

vue允许你将一个页面分割成可复用的组件,每个组件都包含属于自己的html、css、js用来渲染网页中相应的地方。
对于vue的使用可大可小,它都会有相应的方式来整合到你的项目中。所以说它是一个渐进式的框架。
vue是响应式的(reactive)这是vue最独特的特性,也就是说当我们的数据变更时,vue会帮你更新所有网页中用到它的地方。

23.简述vue的响应式原理

当一个vue实例创建时,vue会遍历data选项的属性,用object.defineProperty将他们转为getter/setter并且在内部追踪相关依赖,
在属性被访问和修改时通知变化。

24.vue的性能优化

路由懒加载、图片懒加载、第三方组件库按需引入、keep-alive缓存页面、使用v-show复用DOM、避免v-if与v-for同时使用

25.为什么避免v-for和v-if同时使用?

v-for比v-if优先级高,使用的话每次v-for都会v-if判断,影响性能

26.异步请求适合在哪个生命周期调用?

可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

我一般在 created 钩子函数中调用异步请求,有以下优点:

能更快获取到服务端数据,减少页面 loading 时间;
ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

你可能感兴趣的:(前端,javascript,面试题)