社招中级前端笔试面试题总结_社招 前端面试(2)

console.log(obj) //{a:2,b:{c:2}} console.log(obj2); //{a:1,b:{c:2}}
/* 数组的拷贝 */
let arr = [1, 2, 3];
let newArr = […arr]; //跟arr.slice()是一样的效果



> 
> 扩展运算符 和 `object.assign` 有同样的缺陷,也就是`实现的浅拷贝的功能差不多`,但是如果属性都是`基本类型的值,使用扩展运算符进行浅拷贝会更加方便`
> 
> 
> 


**方法三:concat 拷贝数组**



> 
> 数组的 `concat` 方法其实也是浅拷贝,所以连接一个含有引用类型的数组时,需要注意修改原数组中的元素的属性,因为它会影响拷贝之后连接的数组。不过 `concat` 只能用于数组的浅拷贝,使用场景比较局限。代码如下所示。
> 
> 
> 



let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr); // [ 1, 2, 3 ]
console.log(newArr); // [ 1, 100, 3 ]


**方法四:slice 拷贝数组**



> 
> `slice` 方法也比较有局限性,因为`它仅仅针对数组类型`。`slice方法会返回一个新的数组对象`,这一对象由该方法的前两个参数来决定原数组截取的开始和结束时间,是不会影响和改变原始数组的。
> 
> 
> 



slice 的语法为:arr.slice(begin, end);



let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); //[ 1, 2, { val: 1000 } ]



> 
> 从上面的代码中可以看出,这就是`浅拷贝的限制所在了——它只能拷贝一层对象`。如果`存在对象的嵌套,那么浅拷贝将无能为力`。因此深拷贝就是为了解决这个问题而生的,它能解决多层对象嵌套问题,彻底实现拷贝
> 
> 
> 


**手工实现一个浅拷贝**


根据以上对浅拷贝的理解,如果让你自己实现一个浅拷贝,大致的思路分为两点:


* 对基础类型做一个最基本的一个拷贝;
* 对引用类型开辟一个新的存储,并且拷贝一层对象属性。



const shallowClone = (target) => {
if (typeof target === ‘object’ && target !== null) {
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = target[prop];
}
}
return cloneTarget;
} else {
return target;
}
}



> 
> 利用类型判断,针对引用类型的对象进行 for 循环遍历对象属性赋值给目标对象的属性,基本就可以手工实现一个浅拷贝的代码了
> 
> 
> 


**2. 深拷贝的原理和实现**


`浅拷贝只是创建了一个新的对象,复制了原有对象的基本类型的值,而引用数据类型只拷贝了一层属性,再深层的还是无法进行拷贝`。深拷贝则不同,对于复杂引用数据类型,其在堆内存中完全开辟了一块内存地址,并将原有的对象完全复制过来存放。


这两个对象是相互独立、不受影响的,彻底实现了内存上的分离。总的来说,`深拷贝的原理可以总结如下`:



> 
> 将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
> 
> 
> 


**方法一:乞丐版(JSON.stringify)**



> 
> `JSON.stringify()` 是目前开发过程中最简单的深拷贝方法,其实就是把一个对象序列化成为 `JSON` 的字符串,并将对象里面的内容转换成字符串,最后再用 `JSON.parse()` 的方法将 `JSON` 字符串生成一个新的对象
> 
> 
> 



let a = {
age: 1,
jobs: {
first: ‘FE’
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = ‘native’
console.log(b.jobs.first) // FE


**但是该方法也是有局限性的** :


* 会忽略 `undefined`
* 会忽略 `symbol`
* 不能序列化函数
* 无法拷贝不可枚举的属性
* 无法拷贝对象的原型链
* 拷贝 `RegExp` 引用类型会变成空对象
* 拷贝 `Date` 引用类型会变成字符串
* 对象中含有 `NaN`、`Infinity` 以及 `-Infinity`,`JSON` 序列化的结果会变成 `null`
* 不能解决循环引用的对象,即对象成环 (`obj[key] = obj`)。



function Obj() {
this.func = function () { alert(1) };
this.obj = {a:1};
this.arr = [1,2,3];
this.und = undefined;
this.reg = /123/;
this.date = new Date(0);
this.NaN = NaN;
this.infinity = Infinity;
this.sym = Symbol(1);
}
let obj1 = new Obj();
Object.defineProperty(obj1,‘innumerable’,{
enumerable:false,
value:‘innumerable’
});
console.log(‘obj1’,obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log(‘obj2’,obj2);


![](https://img-blog.csdnimg.cn/img_convert/a892c47208290325225b3099609d813b.webp?x-oss-process=image/format,png)



> 
> 使用 `JSON.stringify` 方法实现深拷贝对象,虽然到目前为止还有很多无法实现的功能,但是这种方法足以满足日常的开发需求,并且是最简单和快捷的。而对于其他的也要实现深拷贝的,比较麻烦的属性对应的数据类型,`JSON.stringify` 暂时还是无法满足的,那么就需要下面的几种方法了
> 
> 
> 


**方法二:基础版(手写递归实现)**



> 
> 下面是一个实现 deepClone 函数封装的例子,通过 `for in` 遍历传入参数的属性值,如果值是引用类型则再次递归调用该函数,如果是基础数据类型就直接复制
> 
> 
> 



let obj1 = {
a:{
b:1
}
}
function deepClone(obj) {
let cloneObj = {}
for(let key in obj) { //遍历
if(typeof obj[key] ===‘object’) {
cloneObj[key] = deepClone(obj[key]) //是对象就再次调用该函数递归
} else {
cloneObj[key] = obj[key] //基本类型的话直接复制值
}
}
return cloneObj
}
let obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2); // {a:{b:1}}


虽然利用递归能实现一个深拷贝,但是同上面的 `JSON.stringify` 一样,还是有一些问题没有完全解决,例如:


* 这个深拷贝函数并不能复制不可枚举的属性以及 `Symbol` 类型;
* 这种方法`只是针对普通的引用类型的值做递归复制`,而对于 `Array、Date、RegExp、Error、Function` 这样的引用类型并不能正确地拷贝;
* 对象的属性里面成环,即`循环引用没有解决`。


这种基础版本的写法也比较简单,可以应对大部分的应用情况。但是你在面试的过程中,如果只能写出这样的一个有缺陷的深拷贝方法,有可能不会通过。


所以为了“拯救”这些缺陷,下面我带你一起看看改进的版本,以便于你可以在面试种呈现出更好的深拷贝方法,赢得面试官的青睐。


**方法三:改进版(改进后递归实现)**



> 
> 针对上面几个待解决问题,我先通过四点相关的理论告诉你分别应该怎么做。
> 
> 
> 


* 针对能够遍历对象的不可枚举属性以及 `Symbol` 类型,我们可以使用 `Reflect.ownKeys` 方法;
* 当参数为 `Date、RegExp` 类型,则直接生成一个新的实例返回;
* 利用 `Object` 的 `getOwnPropertyDescriptors` 方法可以获得对象的所有属性,以及对应的特性,顺便结合 `Object.create` 方法创建一个新对象,并继承传入原对象的原型链;
* 利用 `WeakMap` 类型作为 `Hash` 表,因为 `WeakMap` 是弱引用类型,可以有效防止内存泄漏(你可以关注一下 `Map` 和 `weakMap` 的关键区别,这里要用 `weakMap`),作为检测循环引用很有帮助,如果存在循环,则引用直接返回 `WeakMap` 存储的值


如果你在考虑到循环引用的问题之后,还能用 `WeakMap` 来很好地解决,并且向面试官解释这样做的目的,那么你所展示的代码,以及你对问题思考的全面性,在面试官眼中应该算是合格的了


**实现深拷贝**



const isComplexDataType = obj => (typeof obj === ‘object’ || typeof obj === ‘function’) && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date) {
return new Date(obj) // 日期对象直接返回一个新的日期对象
}

if (obj.constructor === RegExp){
return new RegExp(obj) //正则对象直接返回一个新的正则对象
}

//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) {
return hash.get(obj)
}
let allDesc = Object.getOwnPropertyDescriptors(obj)

//遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

// 把cloneObj原型复制到obj上
hash.set(obj, cloneObj)

for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== ‘function’) ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}



// 下面是验证代码
let obj = {
num: 0,
str: ‘’,
boolean: true,
unf: undefined,
nul: null,
obj: { name: ‘我是一个对象’, id: 1 },
arr: [0, 1, 2],
func: function () { console.log(‘我是一个函数’) },
date: new Date(0),
reg: new RegExp(‘/我是一个正则/ig’),
[Symbol(‘1’)]: 1,
};
Object.defineProperty(obj, ‘innumerable’, {
enumerable: false, value: ‘不可枚举属性’ }
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj // 设置loop成循环引用的属性
let cloneObj = deepClone(obj)
cloneObj.arr.push(4)
console.log(‘obj’, obj)
console.log(‘cloneObj’, cloneObj)


我们看一下结果,`cloneObj` 在 `obj` 的基础上进行了一次深拷贝,`cloneObj` 里的 `arr` 数组进行了修改,并未影响到 `obj.arr` 的变化,如下图所示


![](https://img-blog.csdnimg.cn/img_convert/02522ff185adfbe077830753efcc775a.webp?x-oss-process=image/format,png)


#### 点击刷新按钮或者按 F5、按 Ctrl+F5 (强制刷新)、地址栏回车有什么区别?


* **点击刷新按钮或者按 F5:** 浏览器直接对本地的缓存文件过期,但是会带上If-Modifed-Since,If-None-Match,这就意味着服务器会对文件检查新鲜度,返回结果可能是 304,也有可能是 200。
* **用户按 Ctrl+F5(强制刷新):** 浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前从来没有请求过,返回结果是 200。
* 地址栏回车: 浏览器发起请求,按照正常流程,本地检查是否过期,然后服务器检查新鲜度,最后返回内容。


参考 [前端进阶面试题详细解答](https://bbs.csdn.net/topics/618166371)


#### 对this对象的理解


this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。


* 第一种是**函数调用模式**,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
* 第二种是**方法调用模式**,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。
* 第三种是**构造器调用模式**,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
* 第四种是 **apply 、 call 和 bind 调用模式**,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。


这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。


#### 常见的浏览器内核比较


* **Trident:** 这种浏览器内核是 IE 浏览器用的内核,因为在早期 IE 占有大量的市场份额,所以这种内核比较流行,以前有很多网页也是根据这个内核的标准来编写的,但是实际上这个内核对真正的网页标准支持不是很好。但是由于 IE 的高市场占有率,微软也很长时间没有更新 Trident 内核,就导致了 Trident 内核和 W3C 标准脱节。还有就是 Trident 内核的大量 Bug 等安全问题没有得到解决,加上一些专家学者公开自己认为 IE 浏览器不安全的观点,使很多用户开始转向其他浏览器。
* **Gecko:** 这是 Firefox 和 Flock 所采用的内核,这个内核的优点就是功能强大、丰富,可以支持很多复杂网页效果和浏览器扩展接口,但是代价是也显而易见就是要消耗很多的资源,比如内存。
* **Presto:** Opera 曾经采用的就是 Presto 内核,Presto 内核被称为公认的浏览网页速度最快的内核,这得益于它在开发时的天生优势,在处理 JS 脚本等脚本语言时,会比其他的内核快3倍左右,缺点就是为了达到很快的速度而丢掉了一部分网页兼容性。
* **Webkit:** Webkit 是 Safari 采用的内核,它的优点就是网页浏览速度较快,虽然不及 Presto 但是也胜于 Gecko 和 Trident,缺点是对于网页代码的容错性不高,也就是说对网页代码的兼容性较低,会使一些编写不标准的网页无法正确显示。WebKit 前身是 KDE 小组的 KHTML 引擎,可以说 WebKit 是 KHTML 的一个开源的分支。
* **Blink:** 谷歌在 Chromium Blog 上发表博客,称将与苹果的开源浏览器核心 Webkit 分道扬镳,在 Chromium 项目中研发 Blink 渲染引擎(即浏览器核心),内置于 Chrome 浏览器之中。其实 Blink 引擎就是 Webkit 的一个分支,就像 webkit 是KHTML 的分支一样。Blink 引擎现在是谷歌公司与 Opera Software 共同研发,上面提到过的,Opera 弃用了自己的 Presto 内核,加入 Google 阵营,跟随谷歌一起研发 Blink。


#### Virtual Dom 的优势在哪里?


Virtual Dom 的优势」其实这道题目面试官更想听到的答案不是上来就说「直接操作/频繁操作 DOM 的性能差」,如果 DOM 操作的性能如此不堪,那么 jQuery 也不至于活到今天。所以面试官更想听到 VDOM 想解决的问题以及为什么频繁的 DOM 操作会性能差。


首先我们需要知道:


DOM 引擎、JS 引擎 相互独立,但又工作在同一线程(主线程) JS 代码调用 DOM API 必须 挂起 JS 引擎、转换传入参数数据、激活 DOM 引擎,DOM 重绘后再转换可能有的返回值,最后激活 JS 引擎并继续执行若有频繁的 DOM API 调用,且浏览器厂商不做“批量处理”优化, 引擎间切换的单位代价将迅速积累若其中有强制重绘的 DOM API 调用,重新计算布局、重新绘制图像会引起更大的性能消耗。


其次是 VDOM 和真实 DOM 的区别和优化:


1. 虚拟 DOM 不会立马进行排版与重绘操作
2. 虚拟 DOM 进行频繁修改,然后一次性比较并修改真实 DOM 中需要改的部分,最后在真实 DOM 中进行排版与重绘,减少过多DOM节点排版与重绘损耗
3. 虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部


#### 画一条0.5px的线


* **采用transform: scale()的方式**,该方法用来定义元素的2D 缩放转换:



transform: scale(0.5,0.5);


* **采用meta viewport的方式**




这样就能缩放到原来的0.5倍,如果是1px那么就会变成0.5px。viewport只针对于移动端,只在移动端上才能看到效果


#### CSS 如何阻塞文档解析?


理论上,既然样式表不改变 DOM 树,也就没有必要停下文档的解析等待它们。然而,存在一个问题,JavaScript 脚本执行时可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题。所以如果浏览器尚未完成 CSSOM 的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟 JavaScript 脚本执行和文档的解析,直至其完成 CSSOM 的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建 CSSOM,然后再执行 JavaScript,最后再继续文档的解析。


#### 冒泡排序--时间复杂度 n^2


题目描述:实现一个冒泡排序


实现代码如下:



function bubbleSort(arr) {
// 缓存数组长度
const len = arr.length;
// 外层循环用于控制从头到尾的比较+交换到底有多少轮
for (let i = 0; i < len; i++) {
// 内层循环用于完成每一轮遍历过程中的重复比较+交换
for (let j = 0; j < len - 1; j++) {
// 若相邻元素前面的数比后面的大
if (arr[j] > arr[j + 1]) {
// 交换两者
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
// 返回数组
return arr;
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));


#### 代码输出结果



var obj = {
say: function() {
var f1 = () => {
console.log(“1111”, this);
}
f1();
},
pro: {
getPro:() => {
console.log(this);
}
}
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();


输出结果:



1111 window对象
1111 obj对象
window对象


**解析:**


1. o(),o是在全局执行的,而f1是箭头函数,它是没有绑定this的,它的this指向其父级的this,其父级say方法的this指向的是全局作用域,所以会打印出window;
2. obj.say(),谁调用say,say 的this就指向谁,所以此时this指向的是obj对象;
3. obj.pro.getPro(),我们知道,箭头函数时不绑定this的,getPro处于pro中,而对象不构成单独的作用域,所以箭头的函数的this就指向了全局作用域window。


#### JSONP


JSONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;





### 最后

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

❤️ 谢谢支持,喜欢的话别忘了 关注、点赞哦。

1 window对象
1111 obj对象
window对象


解析:

  1. o(),o是在全局执行的,而f1是箭头函数,它是没有绑定this的,它的this指向其父级的this,其父级say方法的this指向的是全局作用域,所以会打印出window;
  2. obj.say(),谁调用say,say 的this就指向谁,所以此时this指向的是obj对象;
  3. obj.pro.getPro(),我们知道,箭头函数时不绑定this的,getPro处于pro中,而对象不构成单独的作用域,所以箭头的函数的this就指向了全局作用域window。
JSONP

JSONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;

最后

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

❤️ 谢谢支持,喜欢的话别忘了 关注、点赞哦。

前端校招面试题精编解析大全

  • 30
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值