3. 深拷贝和浅拷贝的区别是什么?实现一个深拷贝
查看解析
深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。
深拷贝
深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。 深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
浅拷贝
浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
可以使用 for in
、 Object.assign
、 扩展运算符 ...
、Array.prototype.slice()
、Array.prototype.concat()
等,例如:
可以看出浅拷贝只最第一层属性进行了拷贝,当第一层的属性值是基本数据类型时,新的对象和原对象互不影响,但是如果第一层的属性值是复杂数据类型,那么新对象和原对象的属性值其指向的是同一块内存地址。
深拷贝实现
1.深拷贝最简单的实现是:
JSON.parse(JSON.stringify(obj))
JSON.parse(JSON.stringify(obj))
是最简单的实现方式,但是有一些缺陷:
-
对象的属性值是函数时,无法拷贝。
-
原型链上的属性无法拷贝
-
不能正确的处理 Date 类型的数据
-
不能处理 RegExp
-
会忽略 symbol
-
会忽略 undefined
2.实现一个 deepClone 函数
-
如果是基本数据类型,直接返回
-
如果是
RegExp
或者Date
类型,返回对应类型 -
如果是复杂数据类型,递归。
-
考虑循环引用的问题
4. call/apply 的实现原理是什么?
查看解析
call
和 apply
的功能相同,都是改变 this
的执行,并立即执行函数。区别在于传参方式不同。
-
func.call(thisArg, arg1, arg2, ...)
:第一个参数是this
指向的对象,其它参数依次传入。 -
func.apply(thisArg, [argsArray])
:第一个参数是this
指向的对象,第二个参数是数组或类数组。
一起思考一下,如何模拟实现 call
?
首先,我们知道,函数都可以调用 call
,说明 call
是函数原型上的方法,所有的实例都可以调用。即: Function.prototype.call
。
-
在
call
方法中获取调用call()
函数 -
如果第一个参数没有传入,那么默认指向
window / global
(非严格模式) -
传入
call
的第一个参数是 this 指向的对象,根据隐式绑定的规则,我们知道obj.foo()
,foo()
中的this
指向obj
;因此我们可以这样调用函数thisArgs.func(...args)
-
返回执行结果
apply
的实现思路和 call
一致,仅参数处理略有差别。如下:
5. 柯里化函数实现
查看解析
在开始之前,我们首先需要搞清楚函数柯里化的概念。
函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
函数柯里化的主要作用:
-
参数复用
-
提前返回 – 返回接受余下的参数且返回结果的新函数
-
延迟执行 – 返回新函数,等待执行
6. 如何让 (a == 1 && a == 2 && a == 3) 的值为true?
查看解析
- 利用隐式类型转换
==
操作符在左右数据类型不一致时,会先进行隐式转换。
a == 1 && a == 2 && a == 3
的值意味着其不可能是基本数据类型。因为如果 a 是 null 或者是 undefined bool类型,都不可能返回true。
因此可以推测 a 是复杂数据类型,JS 中复杂数据类型只有 object
,回忆一下,Object 转换为原始类型会调用什么方法?
-
如果部署了
[Symbol.toPrimitive]
接口,那么调用此接口,若返回的不是基本数据类型,抛出错误。 -
如果没有部署
[Symbol.toPrimitive]
接口,那么根据要转换的类型,先调用valueOf
/toString
-
非Date类型对象,
hint
是default
时,调用顺序为:valueOf
>>>toString
,即valueOf
返回的不是基本数据类型,才会继续调用valueOf
,如果toString
返回的还不是基本数据类型,那么抛出错误。 -
如果
hint
是string
(Date对象的hint默认是string) ,调用顺序为:toString
>>>valueOf
,即toString
返回的不是基本数据类型,才会继续调用valueOf
,如果valueOf
返回的还不是基本数据类型,那么抛出错误。 -
如果
hint
是number
,调用顺序为:valueOf
>>>toString
- 利用数据劫持(Proxy/Object.defineProperty)
- 数组的
toString
接口默认调用数组的join
方法,重写join
方法
7. 什么是BFC?BFC的布局规则是什么?如何创建BFC?
查看解析
Box 是 CSS 布局的对象和基本单位,页面是由若干个Box组成的。
元素的类型 和 display
属性,决定了这个 Box 的类型。不同类型的 Box 会参与不同的 Formatting Context。
Formatting Context
Formatting Context 是页面的一块渲染区域,并且有一套渲染规则,决定了其子元素将如何定位,以及和其它元素的关系和相互作用。
Formatting Context 有 BFC (Block formatting context),IFC (Inline formatting context),FFC (Flex formatting context) 和 GFC (Grid formatting context)。FFC 和 GFC 为 CC3 中新增。
-
BFC内,盒子依次垂直排列。
-
BFC内,两个盒子的垂直距离由
margin
属性决定。属于同一个BFC的两个相邻Box的margin会发生重叠【符合合并原则的margin合并后是使用大的margin】 -
BFC内,每个盒子的左外边缘接触内部盒子的左边缘(对于从右到左的格式,右边缘接触)。即使在存在浮动的情况下也是如此。除非创建新的BFC。
-
BFC的区域不会与float box重叠。
-
BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
-
计算BFC的高度时,浮动元素也参与计算。
如何创建BFC
-
根元素
-
浮动元素(float 属性不为 none)
-
position 为 absolute 或 fixed
-
overflow 不为 visible 的块元素
-
display 为 inline-block, table-cell, table-caption
BFC 的应用
-
防止 margin 重叠 (同一个BFC内的两个两个相邻Box的
margin
会发生重叠,触发生成两个BFC,即不会重叠) -
清除内部浮动 (创建一个新的 BFC,因为根据 BFC 的规则,计算 BFC 的高度时,浮动元素也参与计算)
-
自适应多栏布局 (BFC的区域不会与float box重叠。因此,可以触发生成一个新的BFC)
8. 异步加载JS脚本的方式有哪些?
查看解析
<script>
标签中增加async
(html5) 或者defer
(html4) 属性,脚本就会异步加载。
<script src="../XXX.js" defer></script>
defer
和 async
的区别在于:
-
defer
要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),在window.onload 之前执行; -
async
一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。 -
如果有多个
defer
脚本,会按照它们在页面出现的顺序加载 -
多个
async
脚本不能保证加载顺序
动态创建
script
标签
动态创建的 script
,设置 src
并不会开始下载,而是要添加到文档中,JS文件才会开始下载。
XHR 异步加载JS
9. ES5有几种方式可以实现继承?分别有哪些优缺点?
查看解析
ES5 有 6 种方式可以实现继承,分别为:
1. 原型链继承
原型链继承的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
缺点:
-
通过原型来实现继承时,原型会变成另一个类型的实例,原先的实例属性变成了现在的原型属性,该原型的引用类型属性会被所有的实例共享。
-
在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数。
2. 借用构造函数
借用构造函数的技术,其基本思想为:
在子类型的构造函数中调用超类型构造函数。
优点:
-
可以向超类传递参数
-
解决了原型中包含引用类型值被所有实例共享的问题
缺点:
- 方法都在构造函数中定义,函数复用无从谈起,另外超类型原型中定义的方法对于子类型而言都是不可见的。
3. 组合继承(原型链 + 借用构造函数)
组合继承指的是将原型链和借用构造函数技术组合到一块,从而发挥二者之长的一种继承模式。基本思路:
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承,既通过在原型上定义方法来实现了函数复用,又保证了每个实例都有自己的属性。
缺点:
- 无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
优点:
-
可以向超类传递参数
-
每个实例都有自己的属性
-
实现了函数复用
4. 原型式继承
原型继承的基本思想:
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
在 object()
函数内部,先穿甲一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例,从本质上讲,object()
对传入的对象执行了一次浅拷贝。
ECMAScript5通过新增 Object.create()
方法规范了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象(可以覆盖原型对象上的同名属性),在传入一个参数的情况下,Object.create()
和 object()
方法的行为相同。
在没有必要创建构造函数,仅让一个对象与另一个对象保持相似的情况下,原型式继承是可以胜任的。
缺点:
同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
5. 寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
基于 person
返回了一个新对象 -—— person2
,新对象不仅具有 person
的所有属性和方法,而且还有自己的 sayHi()
方法。在考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。
缺点:
-
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下。
-
同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
6. 寄生组合式继承
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法,基本思路:
不必为了指定子类型的原型而调用超类型的构造函数,我们需要的仅是超类型原型的一个副本,本质上就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。寄生组合式继承的基本模式如下所示:
-
第一步:创建超类型原型的一个副本
-
第二步:为创建的副本添加
constructor
属性 -
第三步:将新创建的对象赋值给子类型的原型
至此,我们就可以通过调用 inheritPrototype
来替换为子类型原型赋值的语句:
优点:
只调用了一次超类构造函数,效率更高。避免在SuberType.prototype
上面创建不必要的、多余的属性,与其同时,原型链还能保持不变。
因此寄生组合继承是引用类型最理性的继承范式。
10. 隐藏页面中的某个元素的方法有哪些?
查看解析
隐藏类型
屏幕并不是唯一的输出机制,比如说屏幕上看不见的元素(隐藏的元素),其中一些依然能够被读屏软件阅读出来(因为读屏软件依赖于可访问性树来阐述)。为了消除它们之间的歧义,我们将其归为三大类:
-
完全隐藏:元素从渲染树中消失,不占据空间。
-
视觉上的隐藏:屏幕中不可见,占据空间。
-
语义上的隐藏:读屏软件不可读,但正常占据空。
完全隐藏
1.display
属性
display: none;
复制代码
2.hidden 属性
HTML5 新增属性,相当于 display: none
复制代码
视觉上的隐藏
1.利用 position
和 盒模型 将元素移出可视区范围
- 设置
posoition
为absolute
或fixed
,通过设置top
、left
等值,将其移出可视区域。
position:absolute;
left: -99999px;
复制代码
- 设置
position
为relative
,通过设置top
、left
等值,将其移出可视区域。
position: relative;
left: -99999px;
height: 0
复制代码
- 设置 margin 值,将其移出可视区域范围(可视区域占位)。
margin-left: -99999px;
height: 0;
复制代码
2.利用 transfrom
- 缩放
transform: scale(0);
height: 0;
复制代码
- 移动
translateX
,translateY
transform: translateX(-99999px);
height: 0
复制代码
- 旋转
rotate
transform: rotateY(90deg);
复制代码
3.设置其大小为0
- 宽高为0,字体大小为0:
height: 0;
width: 0;
font-size: 0;
复制代码
- 宽高为0,超出隐藏:
height: 0;
width: 0;
overflow: hidden;
复制代码
4.设置透明度为0
opacity: 0;
复制代码
5.visibility
属性
visibility: hidden;
复制代码
6.层级覆盖,z-index
属性
position: relative;
z-index: -999;
复制代码
再设置一个层级较高的元素覆盖在此元素上。
7.clip-path 裁剪
clip-path: polygon(0 0, 0 0, 0 0, 0 0);
复制代码
语义上的隐藏
aria-hidden 属性
读屏软件不可读,占据空间,可见。
复制代码
11. let、const、var 的区别有哪些?
查看解析
| 声明方式 | 变量提升 | 暂时性死区 | 重复声明 | 块作用域有效 | 初始值 | 重新赋值 |
| — | — | — | — | — | — | — |
| var | 会 | 不存在 | 允许 | 不是 | 非必须 | 允许 |
| let | 不会 | 存在 | 不允许 | 是 | 非必须 | 允许 |
| const | 不会 | 存在 | 不允许 | 是 | 必须 | 不允许 |
1.let/const 定义的变量不会出现变量提升,而 var 定义的变量会提升。
2.相同作用域中,let 和 const 不允许重复声明,var 允许重复声明。
3.const 声明变量时必须设置初始值
4.const 声明一个只读的常量,这个常量不可改变。
这里有一个非常重要的点即是:在JS中,复杂数据类型,存储在栈中的是堆内存的地址,存在栈中的这个地址是不变的,但是存在堆中的值是可以变得。有没有相当常量指针/指针常量~
一图胜万言,如下图所示,不变的是栈内存中 a 存储的 20,和 b 中存储的 0x0012ff21(瞎编的一个数字)。而 {age: 18, star: 200} 是可变的。
12. 说一说你对JS执行上下文栈和作用域链的理解?
查看解析
在开始说明JS上下文栈和作用域之前,我们先说明下JS上下文以及作用域的概念。
执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。
执行上下文类型分为:
-
全局执行上下文
-
函数执行上下文
执行上下文创建过程中,需要做以下几件事:
-
创建变量对象:首先初始化函数的参数arguments,提升函数声明和变量声明。
-
创建作用域链(Scope Chain):在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。
-
确定this的值,即 ResolveThisBinding
作用域
作用域负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。—— 摘录自《你不知道的JavaScript》(上卷)
作用域有两种工作模型:词法作用域和动态作用域,JS采用的是词法作用域工作模型,词法作用域意味着作用域是由书写代码时变量和函数声明的位置决定的。(with
和 eval
能够修改词法作用域,但是不推荐使用,对此不做特别说明)
作用域分为:
-
全局作用域
-
函数作用域
-
块级作用域
JS执行上下文栈(后面简称执行栈)
执行栈,也叫做调用栈,具有 LIFO (后进先出) 结构,用于存储在代码执行期间创建的所有执行上下文。
规则如下:
-
首次运行JavaScript代码的时候,会创建一个全局执行的上下文并Push到当前的执行栈中,每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push当前执行栈的栈顶。
-
当栈顶的函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文的控制权将移动到当前执行栈的下一个执行上下文。
以一段代码具体说明:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
Vue 编码基础
2.1.1. 组件规范
2.1.2. 模板中使用简单的表达式
2.1.3 指令都使用缩写形式
2.1.4 标签顺序保持一致
2.1.5 必须为 v-for 设置键值 key
2.1.6 v-show 与 v-if 选择
2.1.7 script 标签内部结构顺序
2.1.8 Vue Router 规范
Vue 项目目录规范
2.2.1 基础
2.2.2 使用 Vue-cli 脚手架
2.2.3 目录说明
2.2.4注释说明
2.2.5 其他
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-0FRH5ebD-1712963884191)]
[外链图片转存中…(img-UClx4mUh-1712963884192)]
[外链图片转存中…(img-wmx01oGd-1712963884192)]
[外链图片转存中…(img-WPEvjA9m-1712963884193)]
[外链图片转存中…(img-QUsTIMFu-1712963884193)]
[外链图片转存中…(img-EBblOKhy-1712963884193)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-m961Sv2O-1712963884194)]
Vue 编码基础
2.1.1. 组件规范
2.1.2. 模板中使用简单的表达式
2.1.3 指令都使用缩写形式
2.1.4 标签顺序保持一致
2.1.5 必须为 v-for 设置键值 key
2.1.6 v-show 与 v-if 选择
2.1.7 script 标签内部结构顺序
2.1.8 Vue Router 规范
Vue 项目目录规范
2.2.1 基础
2.2.2 使用 Vue-cli 脚手架
2.2.3 目录说明
2.2.4注释说明
2.2.5 其他
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-jtSo3vCd-1712963884194)]