JS面试知识点
JS面试知识点
JunsJS 基础
let,const,var
- var 声明变量的作用域是函数级别的,不受块级作用域的限制。在全局作用域中声明的变量会成为全局对象的属性。
- let 声明的变量是块级作用域的,只在声明的块内有效。在 for 循环中,每次迭代都会创建一个新的变量。
- const 声明的变量也是块级作用域的,和 let 类似,但是其值不能被重新赋值,只能被赋值一次。
数据类型
首先分为两种,基本类型和引用类型
基本:
- number
- string
- boolean
- undefined
- symbol
- null
引用统称 object
- array
- object
- function
区别:
- 原始数据类型存储在栈里面,因为频繁使用,占据空间小,大小固定
- 引用数据类型存储在堆里面,占据空间大,大小不固定。
- 在栈里面存储了指针,指针指向堆中该实体的其实位置。
- 寻找引用值时,会现在栈里面检索地址,再从堆里面获得实体
判断数据类型
typeof 3 // 'number' |
typeof
能判断基本类型,但是 null 和其他一些输出 obj
null,array,object 都输出 object,function 输出 function
instanceof
- 沿着原型链去找,检测实例对象是不是属于某一个构造函数
- 不能检测基本数据类型
- 不一定准确,原型链可能被修改,在原型链上找到构造函数就返回 true
[] instanceof Array
true
Object.prototype.toString
- 专门检测数据类型的方法,返回值是字符串如
[object String]
toString.call(null)
,[object Null],可以从第八位截取
- 专门检测数据类型的方法,返回值是字符串如
检测数组
Array.isArray([])
,true
判断变量是否是数组的方式:
Array.isArray()
toString.call()
instanceof
- 原型链
constructor.name
[String 常用方法](https://web.qianguyihao.com/04-JavaScript基础/15-内置对象 String:字符串的常见方法.html#内置对象简介)
Number 和 Math
Array
常见方法:
数组元素的添加和删除
方法 | 描述 | 是否改变原数组 |
---|---|---|
push() | 向数组的最后面插入一个或多个元素,返回结果为新数组的长度 | 是 |
pop() | 删除数组中的最后一个元素,返回结果为被删除的元素 | 是 |
unshift() | 在数组最前面插入一个或多个元素,返回结果为新数组的长度 | 是 |
shift() | 删除数组中的第一个元素,返回结果为被删除的元素 | 是 |
splice() | 从数组中删除指定的一个或多个元素,返回结果为被删除元素组成的新数组 | 是 |
slice() | 从数组中提取指定的一个或多个元素,返回结果为新的数组 | 不是 |
concat() | 合并数组:连接两个或多个数组,返回结果为新的数组 | 不是 |
fill() | 填充数组:用固定的值填充数组,返回结果为新的数组 | 是 |
sort()
,按照的是 Unicode 编码排序,所以需要在里面写函数,改变原数组reverse()
,反转数组,改变原数组
查找元素
方法 | 描述 | 备注 |
---|---|---|
indexOf(value) | 从前往后索引,检索一个数组中是否含有指定的元素 | |
lastIndexOf(value) | 从后往前索引,检索一个数组中是否含有指定的元素 | |
includes(item) | 数组中是否包含指定的内容 | |
find(function()) | 找出第一个满足「指定条件返回 true」的元素 | |
findIndex(function()) | 找出第一个满足「指定条件返回 true」的元素的 index | |
every() | 确保数组中的每个元素都满足「指定条件返回 true」,则停止遍历,此方法才返回 true | 全真才为真。要求每一项都返回 true,最终的结果才返回 true |
some() | 数组中只要有一个元素满足「指定条件返回 true」,则停止遍历,此方法就返回 true | 一真即真。只要有一项返回 true,最终的结果就返回 true |
遍历
- for
- forEact
- for of
- map
- filter
- reduce
forEach 只能遍历,不能改动原数组,map 返回新数组
数组排序
reduce
/* |
filter
// filter() |
for…in 和 for…of
in 是遍历对象,of 是遍历数组
in 获取的是键名,of 是键值对的值
for…of 可以配合 for 循环的语句使用,可以随时跳出循环
let arr = [1, 2, 5] |
常用对象
- Array
- String
- Date
- Globle
- window
- Math
== 和 === 的区别
- 如果
x
和y
的类型相同:- 如果
x
是undefined
,返回true
; - 如果
x
是null
,返回true
; - 如果
x
是Number
类型,则:- 如果
x
是NaN
,则返回false
; - 如果
y
是NaN
,则返回false
; - 如果
x
和y
相同,则返回true
; - 如果
x
是-0
和y
是+0
,则返回true
; - 如果
y
是-0
和x
是+0
,则返回true
; - 其他情况返回
false
;
- 如果
- 如果
x
是string
类型,并且x
和y
的完全相同的值(长度相同,对应位置的字符相同),则返回true
; - 如果
x
的Boolean
类型,并且x
和y
都是true
或者false
,则返回true
,否则返回false
; - 如果
x
和y
引用同一个对象,则返回true
,否则返回false
;
- 如果
- 如果
x
为null
且y
为undefined
,则返回true
; - 如果
y
为null
且x
为undefined
,则返回true
; - 如果
x
是number
类型且y
是string
类型,则返回x == ToNumber(y)
的比较结果; - 如果
y
是number
类型且x
是string
类型,则返回y == ToNumber(x)
的比较结果; - 如果
x
是boolean
类型,返回ToNumber(x) == y
的比较结果; - 如果
y
是boolean
类型,返回ToNumber(y) == x
的比较结果; - 如果
x
是string
类型或者number
类型,并且y
是object
类型,返回x == ToPrimitive(y)
的比较结果; - 如果
y
是string
类型或者number
类型,并且x
是object
类型,返回y == ToPrimitive(x)
的比较结果; - 否则返回
false
;
JS 进阶
原型链
__proto__
作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的
每个对象的__proto__
都是指向它的构造函数的原型对象prototype
的
理解为xxx.__proto__ === 构造他的函数.prototype
// person 的构造函数是 Person |
// Person 的构造函数是 Object |
// Person 的构造函数是 Object |
// Object 的原型的__proto__指向 null |
总结:
- 一切对象都是继承自
Object
对象,Object
对象直接继承根源对象null
- 一切的函数对象(包括
Object
对象),都是继承自Function
对象 Object
对象直接继承自Function
对象Function
对象的__proto__
会指向自己的原型对象,最终还是继承自Object
对象
function Person(name) { |
原型链 - JavaScript Guidebook (tsejx.github.io)
- 对象:
__proto__
和constructor
是对象独有的。 - 函数:
prototype
是函数独有的。但是函数也是对象,所以函数也有__proto__
和constructor
。
闭包
闭包的定义:指有权访问另一个函数作用域中的变量的函数,一般情况就是在一个函数中包含另一个函数。
闭包的作用:
- 访问函数内部变量、保持函数在环境中一直存在,不会被垃圾回收机制处理
- 函数内部声明的变量是局部的,只能在函数内部访问到,但是函数外部的变量是对函数内部可见的。
- 子级可以向父级查找变量,逐级查找,直到找到为止或全局作用域查找完毕。
- 因此我们可以在函数内部再创建一个函数,这样对内部的函数来说,外层函数的变量都是可见的,然后我们就可以访问到他的变量了。
保留了作用域链,外部可以访问
function outerFn() { |
用途:
封装私有变量
function add() {
let count = 0
function addCount() {
count++
console.log(count)
}
return addCount
}
let test = add()
test() //1
test() //2
test() //3做缓存
模块化编程
缺点:容易内存泄露,所以要及时释放闭包,或者使用立即执行函数
使用场景:
return
返回一个函数- 节流防抖
- 柯里化
this 指向
事件循环,宏任务,微任务
运行机制:
- 所有同步任务都在主线程上执行,形成一个 执行栈(Execution Context Stack)
- 主线程之外,还存在一个 任务队列(Task Queue)。只要异步任务有了运行结果,就在 任务队列 之中放置一个事件
- 一旦 执行栈 中的所有同步任务执行完毕,系统就会读取 任务队列,看看里面有哪些待执行事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
- 主线程不断重复上面的第三步
JavaScript 的异步任务根据事件分类分为两种:宏任务(MacroTask)和微任务(MicroTask)
- 宏任务:main script、setTimeout、setInterval、setImmediate(Node.js)、I/O(Mouse Events、Keyboard Events、Network Events)、UI Rendering(HTML Parsing)、MessageChannel
- 微任务:Promise.then(非 new Promise)、process.nextTick(Node.js)、MutationObserver
宏任务与微任务的区别在于队列中事件的执行优先级。进入整体代码(宏任务)后,开始首次事件循环,当执行上下文栈清空后,事件循环机制会优先检测微任务队列中的事件并推至主线程执行,当微任务队列清空后,才会去检测宏任务队列中的事件,再将事件推至主线程中执行,而当执行上下文栈再次清空后,事件循环机制又会检测微任务队列,如此反复循环。
宏任务与微任务的优先级
- 宏任务的优先级高于微任务
- 每个宏任务执行完毕后都必须将当前的微任务队列清空
- 第一个
<script>
标签的代码是第一个宏任务 process.nextTick
优先级高于Promise.then
console.log(1) |
setTimeout(() => { |
Promise 异步
promise 的三个状态:
- pending,等待除了下面俩个就一直在这个状态
- fulfiled,成功的回调,resolve
- rejected,失败回调
promise.all 等:https://juejin.cn/post/7069805387490263047
[async/await 异步](https://web.qianguyihao.com/06-JavaScript基础:异步编程/10-Async Await 函数详解.html)
深拷贝
浅拷贝:
- 如果属性是基础类型,拷贝的就是值
- 如果是引用类型,拷贝的就是内存地址(指针)
浅拷贝和赋值的区别:
- 赋值是赋的对象在栈内存中的地址,不是堆内存中的数据。所以俩个对象指向的同一个存储空间,改一个都会变化
- 浅拷贝是按位拷贝,会创建一个新对象,新对象有原始对象属性值的一份精确拷贝。基本类型就是拷贝值,引用类型就拷贝内存地址
用 for in 浅拷贝:
const obj1 = { |
ES6 提供了语法糖Object.assgin()
Object.assign(obj2, obj1) |
深拷贝:(递归)
const obj1 = { |
考虑了循环引用的深拷贝
const _completeDeepClone = (target, map = new Map()) => { |
节流防抖
节流
function throttle(fn, timeout) { |
防抖
function debounce(fn, wait) { |
正则
ES6
- 多了 let,const,块级作用域
- 解构赋值
- 箭头函数,没有自己的 this
- Set,不允许重复,数组去重:
[...new Set(arr)]
- 模板字符串
name:&{name}
- Promise
- Set,Map
跨域
见计网部分的
this
this 的五种情况
- 作为普通函数执行时,
this
指向window
。 - 当函数作为对象的方法被调用时,
this
就会指向该对象
。 - 构造器调用,
this
指向返回的这个对象
。 - 箭头函数 箭头函数的
this
绑定看的是this所在函数定义在哪个对象下
,就绑定哪个对象。如果有嵌套的情况,则 this 绑定到最近的一层对象上。 - 基于 Function.prototype 上的
apply 、 call 和 bind
调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply
接收参数的是数组,call
接受参数列表,bind
方法通过传入一个对象,返回一个this
绑定了传入对象的新函数。这个函数的this
指向除了使用new
时会被改变,其他情况下都不会改变。若为空默认是指向全局对象 window。
new 运算符的实现机制
- 首先创建了一个新的
空对象
设置原型
,将对象的原型设置为函数的prototype
对象。- 让函数的
this
指向这个对象,执行构造函数的代码(为这个新对象添加属性) - 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function objectFactory() { |
const _new = function () { |
call,apply,bind
作用都是改变 this 的指向
基本语法:
fun.call(thisArg, param1, param2, ...) |
call
和apply
返回的是 fun 的执行结果,立即执行
bind
的返回值是 fun 的拷贝,并拥有指定的this
值和初始参数,返回函数,不立即执行
传入的参数:
thisArg
- fun 的 this 指向 thisArg 对象
- 非严格模式下:thisArg 指定为 null,undefined,fun 的 this 指向 window
- 严格模式下:fun 的 this 为 undefined
- 值为原始值(数字,字符串,布尔值)的时候 this 会指向原始值的自动包装对象,如 String,Number,Boolean
param:
- 如果没有或者 null , undefined,表示不需要传入
- apply 第二个参数是数组
调用他们的必须是函数,因为他们是挂在 Function 上面的方法,如Object.prototype.toString.call(data)
核心理念:复用方法,代码复用,节省内存
call 和 apply 用哪个:
- 效果完全一样,区别如下
- 参数数量/顺序确定就用 call,参数数量/顺序不确定的话就用 apply。
- 考虑可读性:参数数量不多就用 call,参数数量比较多的话,把参数整合成数组,使用 apply。
- 参数集合已经是一个数组的情况,用 apply,比如获取数组最大值/最小值
跨域
跨域的原因是:浏览器的同源策略,意思是指 JS 只能操作与其宿主网页有相同的”协议 + 域名 + 端口号”三个部分的 DOM
所以就像使用 AJAX 向其他地址发请求时浏览器就会拒绝
解决方案:
代理服务器
常见的解决跨域的方式,用自己的服务器来转发请求,比如用 Nginx 配置反向代理服务器。
缺点:需要配置额外的服务器,高并发场景下可能导致性能问题
跨域资源共享 CORS
浏览器解决跨域的方式,可以让服务器允许与其他域名的客户端交互
服务器在响应头中添加Access-Control-Allow-Origin
字段来告诉浏览器允许哪些来源的请求跨域
虽然 CORS 的安全性比代理服务器更高,但是需要服务器端进行相应的配置,同时一些老旧的浏览器可能不支持 CORS。
JSONP
JSON with Padding 的缩写,利用 script 标签可以跨域的特性来实现数据传输的方式
就是在页面插入一个<scripy>
标签来加载远程数据
<script src="http://example.com/api/user?callback=handleResponse"></script> |
<script> |
JSONP 是一种比较老的跨域技术,它存在一些安全风险,容易受到中间人攻击,同时只支持 GET 请求,并且无法使用 POST 等其他请求方式。
WebSocket
双向通信的协议,可以实现浏览器和服务器之间高效实时通信,基于 TCP 协议,可以服务器主动推送消息到客户端。
基于 TCP,不受同源策略限制,性能和安全更优秀
postMessage
嗦嗦 postMessage 和 webSocket - 掘金 (juejin.cn)
postMessage 是 HTML5 标准中的 API,它可以给我们解决如下问题:
- 页面和新打开的窗口间数据传递
- 多窗口之间数据传递
- 页面与嵌套的 iframe 之间数据传递
- 上面三个场景之间的
跨域传递
postMessage 接受两个参数,用法如下:
- 参数一:发送的数据
- 参数二:你要发送给谁就写谁的地址
(协议 + 域名 +端口
),也可以设置为*
,表示任意窗口,为/
表示与当前窗口同源的窗口