【vue3js.cn】Js 系列
1. 说说JavaScript中的数据类型?存储上的差别?
在JavaScript中,我们可以分成两种类型:
基本类型
复杂类型
两种类型的区别是:存储位置不同
基本类型主要为以下6种:
- Number
- String
- Boolean
- Undefined
- null
- symbol
复杂类型统称为Object,我们这里主要讲述下面三种:
- Object
- Array
- Function
- Date
- Set
- Map
- RegExp
存储区别 基本数据类型和引用数据类型存储在内存中的位置不同:
- 基本数据类型存储在栈中
- 引用类型的对象存储于堆中
当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值
- 声明变量时不同的内存地址分配:
- 简单类型的值存放在栈中,在栈中存放的是对应的值
- 引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址
- 不同的类型数据导致赋值变量时的不同:
- 简单类型赋值,是生成相同的值,两个对象对应不同的地址
- 复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象
2. 数组的常用方法有哪些?
操作方法
- 新增
- push()
- unshift()
- splice()
- concat()
- 删除
- pop()
- shift()
- splice()
- slice()
- 修改
- splice()
- 查询
- indexOf()
- includes()
- find()
排序方法 数组有两个方法可以用来对元素重新排序:
- reverse()
- sort()
转换方法 join()
迭代方法
- some()
- every()
- forEach()
- filter()
- map()
3. JavaScript字符串的常用方法有哪些?
操作方法
- 新增
- concat()
- 删除
- slice()
- substr()
- substring()
- 修改
- trim()、trimLeft()、trimRight()
- repeat()
- padStart()、padEnd()
- toLowerCase()、 toUpperCase()
- 查询
- chatAt()
- indexOf()
- startWith()
- includes()
转换方法 split
模板匹配方法
- match()
- search()
- replace()
4. 谈谈 JavaScript 中的类型转换机制
常见的类型转换有:
- 强制转换(显示转换)
- 自动转换(隐式转换)
显示转换
- Number()
- parseInt()
- String()
- Boolean()
隐式转换
- undefined
- null
- false
- +0
- -0
- NaN
- ""
5. == 和 ===区别,分别在什么情况使用
等于操作符 等于操作符用两个等于号( == )表示,如果操作数相等,则会返回 true
前面文章,我们提到在JavaScript中存在隐式转换。等于操作符(==)在比较中会先进行类型转换,再确定操作数是否相等
全等操作符 全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 true。即类型相同,值也需相同
区别 相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换
6. 深拷贝浅拷贝的区别?如何实现一个深拷贝?
浅拷贝 浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
在JavaScript中,存在浅拷贝的现象有:
- Object.assign
- Array.prototype.slice(), Array.prototype.concat()
- 使用拓展运算符实现的复制
深拷贝 深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
- _.cloneDeep()
- jQuery.extend()
- JSON.stringify()
- 手写循环递归
7. 说说你对闭包的理解?闭包使用场景
一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包
使用场景 任何闭包的使用场景都离不开这两点:
- 创建私有变量
- 延长变量的生命周期
- 柯里化函数
例如计数器、延迟调用、回调等闭包的应用,其核心思想还是创建私有变量和延长变量的生命周期
8. 说说你对作用域链的理解
作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合
换句话说,作用域决定了代码区块中变量和其他资源的可见性
我们一般将作用域分成:
- 全局作用域
- 函数作用域
- 块级作用域
全局作用域 任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
函数作用域 函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
块级作用域 ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
9. JavaScript原型,原型链 ? 有什么特点?
原型 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身
原型链 原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法
在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法
10. Javascript如何实现继承?
继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码
在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能
下面给出JavaScripy常见的继承方式:
- 原型链继承
- 构造函数继承(借助 call)
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
11. 谈谈this对象的理解
函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别
在绝大多数情况下,函数的调用方式决定了 this 的值(运行时绑定)
this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象
根据不同的使用场合,this有不同的值,主要分为下面几种情况:
- 默认绑定
- 隐式绑定
- new绑定
- 显示绑定
12. typeof 与 instanceof 区别
typeof typeof 操作符返回一个字符串,表示未经计算的操作数的类型
instanceof instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
区别 typeof与instanceof都是判断数据类型的方法,区别如下:
- typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值
- instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
- 而typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断
13. 说说new操作符具体干了什么?
从上面介绍中,我们可以看到new关键字主要做了以下的工作:
- 创建一个新的对象obj
- 将对象与构建函数通过原型链连接起来
- 将构建函数中的this绑定到新建的对象obj上
- 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
14. ajax原理是什么?如何实现?
即异步的JavaScript 和XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页
实现 Ajax异步交互需要服务器逻辑进行配合,需要完成以下步骤:
- 创建 Ajax的核心对象 XMLHttpRequest对象
- 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
- 构建请求所需的数据内容,并通过XMLHttpRequest 对象的 send() 方法发送给服务器端
- 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
- 接受并处理服务端向客户端响应的数据结果
- 将处理结果更新到 HTML页面中
15. bind、call、apply 区别?如何实现一个bind?
call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向
apply、call、bind三者的区别在于:
- 三者都可以改变函数的this对象指向
- 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入
- bind是返回绑定this之后的函数,apply、call 则是立即执行
16. 说说你对正则表达式的理解?应用场景?
它的设计思想是用一种描述性的语言定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的
应用场景 常用于表单提交时的信息校验
17. 说说你对事件循环的理解
JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
在JavaScript中,所有的任务都可以分为
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等
微任务 一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
- Promise.then
- MutaionObserver
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务 宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
18. 说说你对BOM的理解,常见的BOM对象你了解哪些?
BOM (Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象
常用的BOM有:
- window
- location
- navigator
- screen
- history
19. 举例说明你对尾递归的理解,有哪些应用场景
尾递归,即在函数尾位置调用自身(或是一个尾调用本身的其他函数等等)。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数
尾递归在普通尾调用的基础上,多出了2个特征:
- 在尾部调用的是函数自身
- 可通过优化,使得计算仅占用常量栈空间
20. 说说 JavaScript 中内存泄漏的几种情况?
内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存
并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费
垃圾回收机制 通常情况下有两种实现方式:
- 标记清除
- 当变量进入执行环境是,就标记这个变量为“进入环境“。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为“离开环境“
- 垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉
- 引用计数
- 语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放
有了垃圾回收机制,不代表不用关注内存泄露。那些很占空间的值,一旦不再用到,需要检查是否还存在对它们的引用。如果是的话,就必须手动解除引用
常见内存泄露情况
- 意外的全局变量
- 定时器也常会造成内存泄露
- 闭包,维持函数内局部变量,使其得不到释放
- 没有清理对DOM元素的引用同样造成内存泄露
- 包括使用事件监听addEventListener监听的时候,在不监听的情况下使用removeEventListener取消对事件监听
21. Javascript本地存储的方式有哪些?区别及应用场景?
javaScript本地缓存的方法我们主要讲述以下四种:
- cookie
- sessionStorage
- localStorage
- indexedDB 关于cookie、sessionStorage、localStorage三者的区别主要如下:
- 存储大小:cookie数据大小不能超过4k,sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
- 有效时间:localStorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据; sessionStorage数据在当前浏览器窗口关闭后自动删除;cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
- 数据与服务器之间的交互方式,cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端; sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存
22. 说说你对函数式编程的理解?优缺点?
主要的编程范式有三种:命令式编程,声明式编程和函数式编程
相比命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而非设计一个复杂的执行过程
纯函数 纯函数是对给定的输入返还相同输出的函数,并且要求你所有的数据都是不可变的,即纯函数=无状态+数据不可变
高阶函数 高级函数,就是以函数作为输入或者输出的函数被称为高阶函数
柯里化 柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程
关于柯里化函数的意义如下:
- 让纯函数更纯,每次接受一个参数,松散解耦
- 惰性执行
组合与管道 组合函数,目的是将多个函数组合成一个函数
优缺点
- 优点
- 更好的管理状态:因为它的宗旨是无状态,或者说更少的状态,能最大化的减少这些未知、优化代码、减少出错情况
- 更简单的复用:固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响
- 更优雅的组合:往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。更强的复用性,带来更强大的组合性
- 隐性好处。减少代码量,提高维护性
- 缺点:
- 性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销
- 资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式
- 递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作
23. Javascript中如何实现函数缓存?函数缓存有哪些应用场景?
函数缓存,就是将函数运算过的结果进行缓存
本质上就是用空间(缓存存储)换时间(计算过程)
常用于缓存数据计算结果和缓存对象
如何实现 实现函数缓存主要依靠闭包、柯里化、高阶函数
应用场景 虽然使用缓存效率是非常高的,但并不是所有场景都适用,因此千万不要极端的将所有函数都添加缓存
以下几种情况下,适合使用缓存:
- 对于昂贵的函数调用,执行复杂计算的函数
- 对于具有有限且高度重复输入范围的函数
- 对于具有重复输入值的递归函数
- 对于纯函数,即每次使用特定输入调用时返回相同输出的函数
说说 Javascript 数字精度丢失的问题,如何解决? 理论上用有限的空间来存储无限的小数是不可能保证精确的,但我们可以处理一下得到我们期望的结果
当你拿到 1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示,如下
最后还可以使用第三方库,如Math.js、BigDecimal.js
24. 什么是防抖和节流?有什么区别?如何实现?
定义
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
function throttled(fn, delay) {
let timer = null
let starttime = Date.now()
return function () {
let curTime = Date.now() // 当前时间
let remaining = delay - (curTime - starttime) // 从上一次到现在,还剩下多少多余时间
let context = this
let args = arguments
clearTimeout(timer)
if (remaining <= 0) {
fn.apply(context, args)
starttime = Date.now()
} else {
timer = setTimeout(fn, remaining);
}
}
}
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
function debounce(func, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout); // timeout 不为null
if (immediate) {
let callNow = !timeout; // 第一次会立即执行,以后只有事件执行后才会再次触发
timeout = setTimeout(function () {
timeout = null;
}, wait)
if (callNow) {
func.apply(context, args)
}
}
else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
}
}
区别 相同点:
- 都可以通过使用 setTimeout 实现
- 目的都是,降低回调执行频率。节省计算资源 不同点:
- 函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
- 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
25. 如何判断一个元素是否在可视区域中?
判断一个元素是否在可视区域,我们常用的有三种办法:
- offsetTop、scrollTop
- getBoundingClientRect
- Intersection Observer
26. 大文件上传如何做断点续传?
分片上传 分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传
大致流程如下:
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
- 初始化一个分片上传任务,返回本次分片上传唯一标识;
- 按照一定的策略(串行或并行)发送各个分片数据块;
- 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
断点续传 断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分
每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度
一般实现方式有两种:
- 服务器端返回,告知从哪开始
- 浏览器端自行处理
27. 如何实现上拉加载,下拉刷新?
下拉刷新和上拉加载这两种交互方式通常出现在移动端中
开源社区也有很多优秀的解决方案,如iscroll、better-scroll、pulltorefresh.js库等等
上拉刷新 判断页面触底我们需要先了解一下下面几个属性
- scrollTop:滚动视窗的高度距离window顶部的距离,它会随着往上滚动而不断增加,初始值是0,它是一个变化的值
- clientHeight:它是一个定值,表示屏幕可视区域的高度;
- scrollHeight:页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示body所有元素的总长度(包括body元素自身的padding)
scrollTop + clientHeight >= scrollHeight
下拉刷新 关于下拉刷新的原生实现,主要分成三步:
- 监听原生touchstart事件,记录其初始位置的值,e.touches[0].pageY;
- 监听原生touchmove事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0表示向下拉动,并借助CSS3的translateY属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;
- 监听原生touchend事件,若此时元素滑动达到最大值,则触发callback,同时将translateY重设为0,元素回到初始位置
28. 什么是单点登录?如何实现?
单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一
简单来说,一个用户只能在一个设备或者系统中登录,无法实现异地同时登录
实现方案 token令牌,与后端接口配合,当用户成功登录后更新token,使之前的token过期,并接口全部有登录鉴权的安全检测
29. web常见的攻击方式有哪些?如何防御?
我们常见的Web攻击方式有/
- XSS (Cross Site Scripting) 跨站脚本攻击
- CSRF(Cross-site request forgery)跨站请求伪造
- SQL注入攻击
30. 说说var、let、const之间的区别
var、let、const三者区别可以围绕下面五点展开:
- 变量提升
- 暂时性死区
- 块级作用域
- 重复声明
- 修改声明的变量
- 使用
变量提升 var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错
暂时性死区 var不存在暂时性死区
let和const存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
块级作用域 var不存在块级作用域
let和const存在块级作用域
重复声明 var允许重复声明变量
let和const在同一作用域不允许重复声明变量
修改声明的变量 var和let可以
const声明一个只读的常量。一旦声明,常量的值就不能改变
使用 能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var
31. 你是怎么理解ES6新增Set、Map两种数据结构的?
Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构
什么是集合?什么又是字典?
- 集合
- 是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合
- 字典
- 是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同
区别?
- 共同点:集合、字典都可以存储不重复的值
- 不同点:集合是以[值,值]的形式存储元素,字典是以[键,值]的形式存储