JavaScript篇
1.JS
有哪些数据类型?有什么区别?
js
中,有undefined
,null
,number
,string
,boolean
,Symbol
,Bigint
,Object
八大数据类型可以分为简单数据类型
与引用数据类型
简单数据类型
存储在栈区,数据简单,使用频繁,占据空间小,大小固定引用数据类型
存储在堆区,数据复杂,占用空间大,大小不固定,在栈区存放的是指针,解释器在使用引用值时,会通过该地址在堆区中进行查找 数据结构中
栈区的数据存取方式一般为先进后出 堆区是一个优先队列,优先级可以依据大小规定 操作系统中
栈区内存由编译器自行分配释放,存放函数参数,变量等 堆区内存由开发者自行分配释放,如果不释放,程序结束后由垃圾回收机制自动释放
2.数据类型的检测方式有哪些?
使用typeof
检查(会将数组,对象,null都判断为object
) 使用instanceof
检查(引用类型) 使用constructor.name
检查(不能检查null
与undefined
) 使用Object.prototype.toString.call()
检查
3.判断数组的方法有哪些?
使用instanceof
判断 使用Array.isArray()
判断 使用constructor.name
判断 使用Object.prototype.toString.call()
判断 使用[].__proto__===Array.prototype
判断 使用Array.prototype.isPrototypeOf([])
判断
4.null
与undefined
的区别?
两者都是基本数据类型.null
代表一个空对象,undefined
代表未定义
声明变量时未赋值将会是undefined
,null
用来给一个对象变量初始化 使用typeof
对null
进行检查时会返回object
undefined
不是一个保留字,可以使用它作为变量名判断undefined==null
时会返回true
,判断undefined===null
时会返回false
5.typeof(null)
的结果是什么?为什么?
结果是object
在js
诞生之初,值被表示成为类型标记
和实际值
,对象的类型标签是0
.null
是一个空指针,指向0x00
地址,因此就有了该结果
6.instanceof
操作符的实现原理以及实现?
instanceof
用来判断构造函数的prototype
属性是否出现在对象原型链中的任意位置
function aIsInstanceofB ( a, b ) {
let a_proto= a. __proto__
let b_proto= b. prototype
while ( true ) {
if ( ! a_proto) return false
if ( a_proto=== b_proto) return true
a_proto= a_proto. __proto__
}
}
7.如何获取安全的undefined
值?
在js
中,当声明了变量尚未进行赋值时,该变量的值是undefined
如果希望在初始化时为变量赋值,并且要求其返回undefined
,可以使用let m=void 0
为其赋值
8.typeof(NaN)
是什么?
NaN
代表的是Not a Number
,是数字运算的错误结果NaN!==NaN
结果为true
typeof(NaN)
结果为number
9.isNaN()
与Number.isNaN()
有什么区别?
isNaN
会将传入的参数转换成数字类型,任何无法被转换为数字的类型都将返回true
Number.isNaN
首先会判断传入的值是否是数字类型,如果是数字类型再执行判断是否是NaN
10.==
操作符的强制转换规则是什么?
==
操作符对比两个数据是否值相等如果对比的两个数据类型不一致,就会进行强制类型转换
判断是否在比较null
与undefined
,是则返回true
判断是否在比较number
与string
,将string
转换为number
判断是否在比较boolean
与其他类型,将boolean
转换为number
判断是否在比较object
与string
,number
,symbol
类型,将object
转换为基本类型
11.其他类型的值是如何转换为字符串的?
null
将转换为'null'
,undefined
将转换为'undefined'
true
将转换为'true'
,false
将转换为'false'
number
类型的值将会直接被转换symbol
类型的值将会直接被转换,只允许进行显式转换object
类型除非有自行定义的toString()
方法,否则会转换为[object xxxx]
的形式
12.其他类型的值是如何转换为数值的?
undefined
转换为NaN
null
转换为0
true
转换为1
,false
转换为0
string
类型如果包含非数字字符,转换为NaN
,空字符串转换为0
symbol
类型不支持转换[]
转换为0
,如果数组只有一个数值元素或数值形式的字符串[num]
或["num"]
,被转换为num
{}
转换为NaN
13.其他类型的值是如何转换为布尔类型的?
undefined
,null
,NaN
,""
,0
,+0
,-0
均为false
其余的所有值均为true
14.||
与&&
操作符的返回值是什么?
逻辑或||
逻辑与&&
都可以用来实现短路操作 如果判断的数据为非boolean
类型的数据,会将其先转换为boolean
类型 对于逻辑或||
,如果第一个判断条件为true
,则直接返回第一个操作数;否则返回第二个操作数 对于逻辑与&&
,如果第一个判断条件为false
,则直接返回第一个操作数;否则返回第二个操作数
15. Object.is()
与操作符===
与==
的区别?
===
判断两个操作数是否值与类型均相等==
判断两个操作数是否值相等,若类型不等,会进行类型转换Object.is()
类似于===
,它会判断+0!==-0
,NaN===NaN
16.在js
中,什么是包装类型?
在js
中,基本数据类型中没有属性或者方法 为了操作方便,js
会在后台默认地将基本数据类型包装成为对象形式,以便调用方法或者属性 可以使用Object()
将基本数据类型显式包装成为对象,使用valueOf()
将包装对象转换为基本数据
17.在js
中,如何进行隐式类型转换?
在js
中,默认情况下
如果对象为Date
类型,先进行toString()
,若结果为原始值,返回;否则再进行valueOf()
,若结果为原始值,返回 如果对象为非Date
类型,先进行valueOf()
,若结果为原始值,返回;否则再进行toString()
,若结果为原始值,返回 +
操作符的任意操作数是string
,会将所有的操作数都转换成为string
,最终的计算结果也是string
-
,*
,/
运算会将两侧的操作数都转换为number
类型!!
会将操作数转换成为boolean
类型==
操作符会将操作数尽量转换为number
类型<
与>
,如果操作符两边的操作数都是字母,则按照字母表的顺序比较;其他情况下均会转换为数字比较
18.+
操作符什么时候用于字符串拼接?
如果+
操作符两边的操作数都是基本类型,存在至少一方为string
,就会用于拼接 如果+
操作符两边的操作数有对象类型,通过valueOf()
或者toString()
方法最终转换为string
,就会用于拼接
19.为什么会有BigInt
的提案?
在js
中,整数表示的最大安全范围是Number.MAX_SAFE_INTEGER
,如果整数超过这个范围,那么会损失精度,所以官方提出了BigInt
来解决
20.object.assign
和扩展运算符...
是浅拷贝还是深拷贝?有何区别?
两者都是浅拷贝 object.assign(target,...source)
接收多个参数,第一个是拷贝的目标,第二个参数及之后的参数是拷贝的源(可以接收多个源),并且返回拷贝后的对象,它只会拷贝可枚举的属性...
扩展运算符会将对象或者数组中的每一项都拷贝到一个新的对象或者数组中,不会复制继承的属性或类的属性
21.let
,const
与var
关键字的区别?
description let const var 是否存在变量提升 ❌ ❌ ✔️ 是否允许设置同名变量 ❌ ❌ ✔️ 全局变量是否会添加到window
❌ ❌ ✔️ 是否存在块级作用域 ✔️ ✔️ ❌ 是否存在暂时性死区 ✔️ ✔️ ❌ 是否需要设置初始值 ❌ ✔️ ❌ 是否允许更改变量 ✔️ ❌ ✔️
22.const
对象中的属性能够修改吗?
可以修改 const
表示不能够将数据的地址重新进行指向将对象设置为const
后,该对象的地址不能够被更改,但是该地址中存储的数据结构可以发生更改
23.如果new
一个箭头函数会发生什么?
箭头函数没有prototype
,没有this
指向,不能够使用new
创建 使用new
操作符后内部的步骤
创建一个新的对象 将新对象的__proto__
指向构造函数的prototype
将构造函数的this
指向新对象 返回新对象 箭头函数无法执行上述的二,三步骤
24.箭头函数与普通函数有什么区别?
语法更加简洁
只有一个形参时,省略()
返回值只有一行时,省略{}
省略function
关键字 不需要返回值,并且函数体只有一行时,使用viod statement
不能用作构造函数 不存在自己的this
指向,在定义箭头函数时this
指向已经固定,不能使用call
,apply
,bind
更改 不存在自己的arguments
参数数组 不存在自己的prototype
无法使用yeild
25.箭头函数的this
指向哪里?
箭头函数的this
指向定义自己的上下文中,并且该this
指向一经确定,无法更改
26.扩展运算符...
的使用场景是什么?
对于对象
扩展对象 合并对象,同时会更改对象中的同名属性 浅拷贝对象 对于数组
拆分数组,将会数组中的元素拆分成为单个元素 浅拷贝数组 将任何可迭代的对象转换成为数组 对于函数
27.proxy
可以实现什么功能?
在ES6
中,proxy
用来替换Object.defineProperty
可以实现数据响应式的操作 在vue3
中,响应式的数据都被包装在proxy
中
let pro= new Proxy ( target, handler)
const handler= {
set ( target, prop, value, receiver) {
console. log ( ` ${ prop} 属性已经更改为 ${ value} ` )
}
get ( target, prop, receiver) {
console. log ( ` 获取到 ${ prop} 属性值为 ${ target[ prop] } ` )
}
}
28.对对象解构及数组解构的理解?
解构是ES6
的新语法,能够从对象或者数组中快速获取对应的值 对于数组
以数组的对应位置为依据获取对应位置的元素值 let [a,b,c,,e]=[1,2,3,4,5]
,右侧对应位置的元素值将被赋予左侧,左侧的空位不赋值 对于对象
以对象中的属性名为依据获取对应属性名的属性值 let {name,age}={age:18,name:"Tom"}
,无关对象中的位置
29.如何提取深层嵌套中对象中的指定属性?
30.对rest
参数的理解?
当在函数的形参中使用...
扩展操作符时,可以将传递给函数的剩余形参整合到一个数组中 常用于处理函数的多余参数,或者处理函数参数不确定的情况
31.ES6
中模板语法与字符串的处理?
ES6
中的模板语法使用反引号来编写,在其中可以使用${}
包裹变量模板语法支持保留字符串中的空格,换行与缩进 模板语法支持进行一些简单的变量运算
32.new
操作符的实现原理?
创建一个空对象 设置空对象的__proto__
为构造函数的prototype
设置构造函数的this
指向该新的空对象 返回该对象
function myNew ( constructor, ... argus ) {
let obj = { }
obj. __proto__ = constructor. prototype
let res = constructor . apply ( obj, argus)
if ( res !== null && ( typeof ( res) === "function" || typeof ( res) === "object" ) ) {
return res
} else {
return obj
}
}
33.Map
与Object
的区别?
description Map
Object
原始键 默认不包含 包含原型链中已存在的键 键的类型 允许任何值作为键 键必须是string
或者Symbol
键的顺序 按照插入的顺序 无序 键的数量 使用size
属性获取 遍历获取 是否可以迭代 可以直接迭代 无法直接迭代 性能 频繁增删键值对时表现更好 未进行性能优化
34.Map
与WeakMap
的区别是什么?
Map
WeakMap
可枚举 不可枚举 允许任何值作为键值 只允许对象作为键值 保持键的插入顺序 不保持键的插入顺序
35.JS
中有哪些内置对象?
即标准内置对象,在全局作用域中的对象,在程序执行前,存在于全局作用域的由JS
定义的一些全局属性,方法或是用来实例化其他对象的构造函数对象 值属性的对象
Infinity
,NaN
,null
,undefined
函数属性的对象
基本对象
Object
,Error
,Function
,Boolean
,Symbol
数字与日期对象
字符串对象
可索引的集合对象
使用键的集合对象
结构化数据对象
控制抽象对象
反射
文件对象
36.常用的正则表达式有哪些?
匹配16进制颜色值
/#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/
匹配日期(yyyy-mm-dd)
/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[0-1])$/
匹配qq号(4-10位)
手机号码正则
37.对JSON
的理解?
JSON
是一种轻量级的数据交换格式,任何编程语言都可以使用它来交换数据在js
中,通常用作前后端交互或者进行本地存储 在JSON
格式中,属性值不能为函数,不能处理undefined
,Symbol
,Function
,RegExp
,BigInt
,Set
,Map
,WeakSet
,WeakMap
在js
中,存在JSON.stringify
与JSON.parse
两个方法
前者用来将一个符合JSON
数据格式的对象转换成为JSON
字符串 后者用来将一个符合JSON
字符串格式的数据解析成为普通的对象
38.JS
延迟加载脚本的方法有哪些?
延迟加载js
指的是在页面加载完之后再加载js
脚本 将js
脚本放在页面的最底部 为js
脚本设置一个延迟加载的定时器 使用动态创建Dom
的方式延迟加载js
脚本 为js
脚本添加async
属性,表示异步加载脚本,不会阻塞页面的解析过程,当脚本加载完成后,会立即执行脚本,如果解析尚未完成,仍然会阻塞页面解析 为js
脚本添加defer
属性,表示在文档解析的同时加载脚本,但是需要等到文档解析完成后再执行脚本
39.JS
中类数组对象是如何定义的?
类数组对象拥有length
属性与若干其他属性 类数组对象不能调用数组的方法 常见的类数组对象有获取的Dom
,arguments
数组
40.如何将类数组转换为数组?
使用Array.from(arrayLike)
方法转换 使用Array.prototype.slice.call(arrayLike)
方法转换 使用Array.prototype.splice.call(arrayLike,0)
方法转换 使用Array.prototype.concat.call([],arrayLike)
方法转换
41.数组有哪些常见的方法?
方法名 用途 includes
判断数组中是否包含某个元素 indexOf
判断元素的下标 slice
切分数组,不会改变原数组,返回切分后的新数组 splice
万能方法,增删数组,会影响原数组 pop
,push
从数组末尾删除/添加一个元素 shift
,unshift
从数组开头删除/添加一个元素 reverse
反转数组 concat
字符串拼接 join
将数组转换为字符串 sort
对数组进行排序 reduce
归并数组 map
遍历映射数组中的每一项为指定格式 forEach
遍历数组,执行相应的操作 filter
遍历筛选数组
42.Unicode
,UTF-8
,UTF-16
,UTF-32
有什么区别?
Unicode
是编码的字符集,UTF-XX
是字符集的编码规则如果字符全是英文或者英文占据大多数,使用UTF-8
有优势;如果字符全是汉字或者汉字占据大多数,使用UTF-16
有优势 UTF-8
需要判断每个字节的开头标志信息,如果出错,会导致后面的字节也出错;UTF-16
不会判断开头标志信息,出错不会影响后面的字节UTF-16
使用变长码元序列的编码方式,相较于使用定长码元序列的UTF-32
算法更加复杂,比同样是使用变长码元序列编码的UTF-8
也更复杂,因为添加了代理机制
43.常见的位运算符有哪些?计算规则是什么?
按位与&
运算,只有两者为1
才为1
使用num&1===0
判断奇偶性,为true
则为偶数,false
则为奇数 使用num&0
实现清零操作 按位或|
运算,只要存在1
就为1
按位异或^
运算,两者同为0
或1
时为0
,两者不同时为1
使用a=a^b
,b=a^b
,a=a^b
实现交换顺序 取反~
运算,0
变为1
,1
变为0
左移<<
运算符,将二进制位全体向左移,高位丢弃,低位补0
右移>>
运算符,将二进制位全体向右移,低位丢弃,正数高位补0
,负数高位补1
44.为什么函数的arguments
参数是类数组?如何遍历类数组?
在函数中,arguments
用来搜集所有传递的参数,并以对象方式进行存储 arguments
对象中存在length
属性,并且它的键是从0
依次递增的整数它没有常规数组中的方法,被类数组 遍历类数组
使用Array.prototype.forEach.call(arguments,(item)=>void console.log(item))
遍历 使用Array.from(arguments)
将其转换成为真数组 使用[...arguments]
将其转换为真数组
45.什么是Dom
与Bom
?
Dom
指的是文档对象模型,将网页文档当作一个对象,存放处理网页的各种接口与方法Bom
指的是浏览器对象模型,将浏览器当作一个对象,存放处理浏览器的各种接口与方法
46.escape
,encodeURI
,encodeURIComponent
的区别?
encodeURI
是将整个URI
进行转义,将其中的非法字符转换为合法字符,但是URI
中的某些特殊字符不会转义encodeURIComponent
是将URI
组件进行转义,URI
中的某些特殊字符也会进行转义escape
与encodeURI
类似,escape
会直接在字符的unicode
编码前添加%u
,encodeURI
会先将字符转换为UTF-8
格式,再添加%
47.对AJAX
的理解?实现一个AJAX
请求?
AJAX
是指通过js
的异步通信机制,获取到服务器中的相关数据后,更新当前网页的对应部分,而不必刷新整个网页
let xhr = new XMLHttpRequest ( )
xhr. open ( "get" , "url" )
xhr. setRequestHeader ( "content-type" , 'application/x-www-form-urlencoded' )
xhr. send ( 'prop=value&prop=value' )
xhr. onload = ( ) => {
let res = JSON . parse ( xhr. response)
console. log ( res) ;
}
48.JS
为什么要进行变量提升?它导致了什么问题?
在js
中,变量提升指的是无论在函数中任何处声明的变量,可以在变量声明前进行访问而不会报错 在ES6
中,使用let
与const
关键字声明的变量不会进行变量提升 js
在执行时,会存在解析与执行阶段
解析阶段,检查语法,对函数进行预编译,将代码中将要执行的变量先存储为undefined
,函数先声明好 执行阶段,按照代码顺序依次进行执行 变量提升主要是为了提高性能,提高容错率
49.什么是尾调用?有什么好处?
尾调用指的是在函数中的最后一步调用另一个函数 函数的执行是在栈中执行的,每当调用一个函数时,会将当前函数的上下文保留,如果在函数中调用另一个函数,那么会将当前函数的执行上下文与被调用的执行上下文一并保留 在尾调用中,由于已经是最后一步,所以不必再保存当前函数的执行上下文,进而节省内存 在ES6
中,尾调用优化只在严格模式中启用
50.ES6
模块与CommonJS
模块有何不同?
ES6
CommonJS
引用模块 浅拷贝模块 静态导入(只允许在顶层导入) 动态导入(允许在任何地方导入) 导入使用import
,导出使用export
导入使用require
,导出使用module.exports
浏览器支持 浏览器不支持 可以使用静态分析 不能使用静态分析
51常见的Dom
操作有哪些?
获取Dom
节点
document.query
系列document.getElement
系列 创建Dom
节点
使用document.createElement
创建 添加Dom
节点
使用node.appendChild
在节点后追加新节点 删除Dom
节点
使用node.remove
或者node.removeChild
方法 克隆节点
使用node.cloneNode(deep)
深度克隆节点 获取子节点
52.use strict
是什么?有什么区别?
在代码开头使用use strict
开启严格模式
严格模式有利于提升编译效率 严格模式有利于消除语法的不合理之处 严格模式有利于消除代码运行过程中的不安全之处 当开启严格模式后
禁止使用with
语句 禁止this
指向全局对象 对象不能有重名属性 禁止使用Object.prototype
中的方法 禁止使用arguments
对象中的callee
方法
53.如何判断一个对象是否属于某个类?
使用instanceof
判断 使用obj.constructor
判断 若要判断是否属于内置类,使用Object.prototype.toString.call()
判断
54.强类型语言与弱类型语言有什么区别?
强类型语言总是要求变量的定义必须符合规范,所有变量必须先定义再使用,一旦某个数据被定义成了某种类型,如果不显示进行转换,那么它仍然保留原有类型 弱类型语言会自动将变量的类型在必要时进行转换 强类型语言可以使得语法更加严谨,但相应地缺少了灵活性;弱类型语言在速度上比强类型语言更优
55.解释型语言与编译型语言有什么区别?
解释型语言,使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行
解释型语言在执行时才会将代码进行编译,不会事先进行编译,运行效率低 解释型语言只要某平台提供了相应的解释器就能够运行 javascript
,python
属性解释型语言 编译型语言,使用专门的编译器,针对特定的平台,将高级语言源代码一次性地编译成为可被该硬件执行地机器码,并且包装成为该平台可以识别的可执行程序的格式
一次性地编译,运行时脱离开发环境,运行效率高 与特定平台相关,一般无法移植到其他平台 c
,c++
属于编译型语言
56.for...in
与for...of
的区别?
for...in
for...of
会遍历对象的整个原型链 遍历当前对象 获取对象的key
获取对象的value
主要用于遍历object
用于遍历Array
,String
,ArrayLike
,Set
,Map
,Generator
ES3
新增ES6
新增
57.如何使用for...of
遍历对象?
使用for( let key of Object.keys(obj))
遍历对象获取对象的key
使用for(let val of Object.values(obj))
遍历对象获取对象的value
使用for(let [key,val] of Object.entries(obj))
遍历对象获取对象的key
与value
58.ajax
,fetch
与axios
有什么区别?
ajax
一种在无需重新加载整个网页的情况下,与后台服务器交换数据以实现页面的部分更新的技术 针对MVC
编程,不符合前端针对MVVM
编程的方式 基于原生XHR
实现,本身架构不清晰 不符合关注分离的原则 配置与调用混论,异步模型不友好 fetch
ES6
新增的方式,基于Promise
设计,基于原生js
语法简洁,更加语义化 更加底层,更加丰富的API
脱离了XHR
,使用新的实现方式 axios
一种基于Promise
封装的HTTP
客户端 支持监听请求与响应 客户端发起XHRHttpRequest
请求,服务端发起http
请求 客户端支持抵御XSRF
攻击
59.数组的遍历方法有哪些?
array.map
映射数组中的每一项为设定格式array.forEach
对数组中的每一项做处理array.filter
筛选数组中符合条件的项array.reduce
,array.reduceRight
合并数组中的项array.every
,array.some
判断数组中的元素是否符合某条件array.find
,array.findIndex
查找数组中符合条件的元素for...of
循环遍历数组
60.forEach
与map
方法有何区别?
forEach
遍历数组,没有返回值,只是单纯地对数组中的每一项进行相应处理,会改变原数组map
遍历数组,将数组中的每一项都转换成指定的格式,并且返回转换后的数组
61.对原型与原型链的理解?
在js
中,通常使用构造函数来创建一个对象,在构造函数内部,有一个prototype
属性,属性值是一个对象,这个对象包含了所有使用该构造函数创建出来的实例的公共的属性与方法 使用构造函数创建出对象后,在该对象中有__proto__
属性指向构造函数的prototype
属性,这里的__proto__
就是原型指针,在ES6
之后,可以通过Object.getPrototypeOf(obj)
来获取对象中的原型 当访问对象中的某个属性或方法时,首先会在当前对象中查找是否存在该属性或方法,如果本身不存在,则向原型指针指向的原型中查找,原型的指针是一个对象,也有自己的原型指针,依次向上查找,这就是原型链
62.原型修改与重写?
原型修改
直接通过ConstructorFun.prototype.prop/fun
在原型上添加属性或者方法
function Person ( name ) {
this . name = name
}
let p = new Person ( "Tom" )
Person . prototype. getName = function ( ) { }
console. log ( p. __proto__ === Person . prototype) ;
console. log ( p. __proto__ === p. constructor. prototype) ;
console. log ( p. constructor) ;
原型重写
直接通过Constructor.prototype=obj
将原原型对象进行覆盖 覆盖后,实例instance.constructor
将指向Object
,不再指向创建实例的构造函数 如果希望覆盖后,实例的constructor
仍然指向创建实例的构造函数,手动设置instance.constructor=ConstructorFun
Person . prototype = {
showName ( ) {
return this . name
}
}
let q = new Person ( "Bob" )
console. log ( q. __proto__ === Person . prototype) ;
console. log ( q. __proto__ === q. constructor. prototype) ;
console. log ( q. constructor) ;
63.原型链的指向问题?
function Person ( name ) {
this . name = name
}
let p = new Person ( "Tom" )
console. log ( p. __proto__ === Person . prototype) ;
console. log ( p. __proto__. __proto__ === Object . prototype) ;
console. log ( p. __proto__ . constructor. prototype. __proto__ === Object . prototype) ;
console. log ( p. constructor. prototype. __proto__ === Object . prototype) ;
console. log ( p. constructor === Person) ;
console. log ( Person . prototype. __proto__ === Object . prototype) ;
console. log ( Person . prototype. constructor. prototype. __proto__ === Object . prototype) ;
console. log ( Person . constructor. prototype. __proto__ === Object . prototype) ;
console. log ( Person . prototype. constructor === Person) ;
64.原型链的终点是什么?如何获取?
原型链的终点是null
通过Object.prototype.__proto__
获取
65.如何获取对象非原型链中的属性?
通过obj.hasOwnProperty(key)
方法判断对象中的属性是否是本身持有
66.对闭包如何理解?
闭包指的是有权访问另一个函数中变量的函数,创建闭包的方式通常是在函数中定义另一个函数,这个函数能够访问当前函数中的变量 闭包一般有两个用途
用来创建私有变量,在函数外部调用闭包函数使用函数内部的变量
function add ( ) {
let a = 666
return function ( b ) {
return a + b
}
}
console. log ( add ( ) ( 1 ) ) ;
用来将已经运行结束的函数上下文中的变量继续留在内存中,使其不被销毁
for ( var i = 1 ; i <= 5 ; i++ ) {
setTimeout ( ( ) => {
console. log ( i) ;
} , i * 1000 )
}
for ( let i = 1 ; i <= 5 ; i++ ) {
setTimeout ( ( ) => {
console. log ( i) ;
} , i * 1000 )
}
for ( var i = 1 ; i <= 5 ; i++ ) {
( function ( t ) {
setTimeout ( ( ) => {
console. log ( t) ;
} , i * 1000 )
} ) ( i)
}
for ( var i = 1 ; i <= 5 ; i++ ) {
setTimeout ( ( p ) => {
console. log ( p) ;
} , i * 1000 , i)
}
67.对作用域,作用域链的理解?
全局作用域
所有未定义直接赋值的变量自动拥有全局作用域 所有window
对象中的属性拥有全局作用域 最外层函数与最外层函数之外定义的变量拥有全局作用域 过多的全局作用域变量会污染全局命名空间 函数作用域
声明在函数内部的变量拥有函数作用域,一般只在函数内代码才能访问 内层作用域可以访问外层作用域,反之无法访问 块级作用域
使用ES6
新增的let
与const
关键字声明的变量具有块级作用域 块级作用域包含在代码块{}
中,超出代码块不再有效 作用域链
查找变量时,优先在当前作用域中查找 如果当前作用域中无法找到该变量,那么就会区父级作用域查找,依次向上级查找 直到查找到window
对象 这种链式查找的方式就是作用域链 作用域链用来保证对执行环境有权访问的所有对象和函数的有序访问,能够访问到外层环境
68.对执行上下文的理解?
全局执行上下文
任何不在函数内部的都是全局执行上下文,创建一个window
对象,并且设置this
指向window
一个程序只有一个全局执行上下文 函数执行上下文
函数被调用时,会创建一个新的执行上下文,一个程序可以有多个函数执行上下文 执行上下文栈
js
使用执行上下文栈来管理执行上下文当js
执行代码时,首先会创建一个全局执行上下文压入栈中 每当遇到函数调用时,会创建一个新的执行上下文并且压入栈中 执行上下文位于栈顶的函数会先被执行,执行结束后,将其弹出栈顶 所有函数执行完成之后,最后会将全局执行上下文弹出栈 创建执行上下文
创建阶段,进行this
绑定,创建词法环境组件,创建变量环境组件 执行阶段,完成对变量的分配,然后执行代码
69.谈谈对this
对象的理解
this
是执行上下文中的一个属性,用来指向最后一个调用这个方法的对象调用全局函数时,this
指向window
在对象中调用函数方法时,this
指向该对象 使用构造函数创建实例时,this
指向创建的实例对象 使用call
,apply
,bind
函数方法时,允许自定义this
的指向
使用call
绑定this
时,接收多个参数,第一个参数是this
指向的对象,第二个及之后的参数分别对应函数的参数,会执行函数 使用apply
绑定this
时,接收两个参数,第一个参数是this
指向的对象,第二个参数是以数组形式存放的函数参数,会执行函数 使用bind
绑定this
时,接收多个参数,第一个参数是this
指向的对象,第二个及之后的参数分别用来绑定函数的参数,它返回更改this
后的新函数,不会执行函数
70.call
与apply
的区别是什么?
两者皆可以改变函数的this
执行,并且可以执行函数 使用call
方法时,传递函数的参数时分别列举,原函数中需要多少个参数就需要传递多少个实参 使用apply
方法时,传递函数的参数时以数组的形式列举
71.如何实现call
,apply
与bind
函数?
Function . prototype. mycall = function ( obj, ... argus ) {
if ( Object . prototype. toString . call ( obj) !== "[object Object]" ) {
throw new TypeError ( "第一个参数必须为一个对象!" )
} else {
obj. fn = this ;
let res = obj. fn ( ... argus) ;
delete obj. fn;
return res;
}
}
function add ( ) {
console. log ( this ) ;
}
add. mycall ( { } ) ;
Function . prototype. myapply = function ( obj, argus ) {
if ( Object . prototype. toString . call ( obj) !== "[object Object]" ) {
throw new TypeError ( "第一个参数必须为一个对象!" )
}
if ( ! ( argus instanceof Array ) ) {
throw new TypeError ( "第二个参数必须为数组!" )
}
obj. fn = this
let res = obj. fn ( ... argus)
delete obj. fn
return res
}
Function . prototype. mybind = function ( obj, ... argus ) {
if ( Object . prototype. toString . call ( obj) !== "[object Object]" ) {
throw new TypeError ( "第一个参数必须为一个对象!" )
}
obj. fn = this
let params = [ ... argus]
return function Fn ( ... argus) {
params = [ ... params, ... argus]
let res = obj. fn ( ... params)
delete obj. fn
return res
}
}
function add ( a, b, c ) {
console. log ( this ) ;
console. log ( ` a= ${ a} b= ${ b} c= ${ c} ` ) ;
}
add. mybind ( { } ) ( 1 , 2 , 3 )
72.如何实现异步编程?
使用回调函数,容易形成回调地狱 使用Promise
,多个.then
链式调用时语义混乱 使用async
与await
,当函数内部执行到await
语句时,如果语句返回一个Promise
对象,它会等到Promise
对象状态变为resolve
时继续向下执行代码 使用generator
,遇到异步操作时转移到函数之外,异步操作执行完毕后转移回来
73.setTimeout
,Promise
与async/await
有何区别?
setTimeout
函数控制在一定时间后执行该参数函数中的代码Promise
是一个表示异步操作最终成功或者失败的对象,它使用.then
表示成功后执行的结果,.catch
表示失败后执行的结果,包含pending
,fulfilled
,rejected
三种状态async/await
是使用编写同步代码的方式编写异步代码,async
用来表示一个异步函数,await
右侧是一个返回Promise
对象的方法,await
只能够在async
中使用
74.对Promise
如何理解?
Promise
是一个构造函数,返回一个Promise
对象,接收一个回调函数作为参数,回调函数的参数包含resolve
与reject
两个参数,这两个参数可以分别设置规则承诺兑现与承诺拒绝Promise
是异步编程的一种解决方案,它是一个对象,解决了回调地狱.Promise
中保存着未来才会结束的某个事件的结果Promise
存在pending
,fulfilled
,rejected
三种状态,当承诺兑现时状态变为fulfilled
,被拒绝时状态变为rejected
,状态变更后不能再修改状态Promise
的缺点
一旦新建立了一个Promise
对象,其内部代码就会立即执行,不会中途停止 如果不设置回调函数,Promise
内部会出现错误,但是不会抛出到外部 处于pending
状态时,无法得知未来的结果
75.Promise
的基本用法是怎样的?
通常,使用new Promise((resolve,reject)=>{})
创建一个Promise
对象,其中的resolve
与reject
分别用来表示兑现承诺与拒绝承诺
使用Promise.resolve()
来创建一个兑现承诺的Promise
对象 使用Promise.reject()
来创建一个拒绝承诺的Promise
对象 .then((resolve)=>{},(reject)=>{})
方法接收两个回调函数作为参数,第一个回调函数用来处理承诺兑现后的结果,第二个回调函数用来处理拒绝承诺后的结果.catch((err)=>{})
方法接收一个回调函数作为参数,通常用来捕获错误.all([promise1,promise2,...])
方法接收一个数组作为参数,每个数组元素是一个Promise
对象,当所有的元素都兑现承诺resolve
时,它会变成fulfilled
状态,并且会返回一个结果数组;失败时会变成rejected
状态,返回最先被reject
的失败值.race([promise1,promise2,...])
方法接收的参数与.all
方法一致,但是只要第一个结束的promise
元素fulfilled
,它就会变为fulfilled
状态;反之第一个结束的promise
元素rejected
,它就会变为rejected
状态.finally(()=>{})
接收一个函数作为参数,不论最后是兑现承诺或者是拒绝承诺都会进入该方法
76.Promise
解决了什么问题?
77.对async/await
的理解?
async/await
是Generator
的语法糖,它使用同步的方式编写异步代码,它能够实现的效果均可以利用.then
来实现利用async
修饰一个函数时,表明该函数是一个异步函数,该函数会返回一个Promise
对象
如果该函数手动返回了一个普通值,会将其包装成Promise.resolve(value)
进行返回 如果该函数没有返回值,会返回Promise.resolve(undefined)
await
只能够在async
修饰的函数中使用,它后面跟一个异步方法,只有当该方法执行完成之后,才会继续执行后续的代码
78.await
在等待什么?
await
等待的是一个返回值await
后面可以接一个普通的含有结果的表达式,也可以接一个Promise
对象
如果是一个普通的结果,那么会将该结果作为等待的值 如果是一个Promise
对象,会将resolve
后的结果作为等待的值
79.使用async/await
的优势是什么?
在使用多重异步调用时,如果后一个异步所使用的参数需要是前一个异步的返回结果时,容易造成回调地狱,解决了回调地狱问题 使用编写同步代码的方式编写异步代码,使代码结构更加清晰明了
80.与Promise
相比,async/await
有什么优势?
async/await
Promise
同步形式的代码,更加易于阅读与理解 采用.then
方式,多级调用不易阅读 使用try/catch
语法捕获错误,更加友好 使用.catch
捕获错误,显得冗余 调试更加友好 不容易调试
81.并行与并发有什么区别?
并行是宏观概念,指在多个任务之间进行切换,进而完成每个任务 并发是微观概念,如果CPU
存在多个核心,那么就可以同时完成多个任务
82.什么是回调函数?它有什么缺点?如何解决回调地狱问题?
回调函数指的是一个被传递到另一个函数中稍晚时间执行的函数 回调函数通常用于处理异步操作的结果,如果多个回调函数之间存在依赖关系,那么很容易造成回调地狱 使用Promise
语法或者async/await
语法能够很容易地处理回调地狱问题
83.setTimeout
,setInterval
,requestAnimationFrame
各有什么特点?
setTimeout
用来设置一个定时器,定时器中的回调函数只会被调用一次setInterval
用来设置一个间隔器,每隔一段时间就会调用一次回调函数requestAnimationFrame
是一个用于在浏览器的下一次重绘时执行动画或更新内容的函数,可以模拟实现setTimeout
与setInterval
的功能
84.创建对象的方式有哪些?
function createPerson ( name, age ) {
let obj = { }
obj. name = name
obj. age = age
return obj
}
function Person ( name, age ) {
this . name = name
this . age = age
}
Person . prototype. fn = function ( ) { }
let p = new Person ( 'midshar' , 25 )
使用类创建 使用Object.create(originObj)
创建,这样创建的对象将会以originObj
作为原型
85.对象继承的方式有哪些?
原型链继承,将子类的prototype
指向父类的实例
function Parent ( name, age ) {
this . name = name
this . age = age
this . sayHi = function ( ) {
console. log ( 'Hello ' + this . name) ;
}
}
function Sub ( gender ) {
this . gender = gender
}
Sub . prototype = new Parent ( 'Tom' , 25 )
let sub = new Sub ( 'male' )
sub. sayHi ( )
构造函数继承,通过在子类中调用父类继承父类实例上的属性或方法
function Parent ( name, age ) {
this . name = name
this . age = age
}
Parent . prototype. sayHi = function ( ) {
console. log ( 'Hello ' + this . name) ;
}
function Sub ( name, age ) {
Parent . call ( this , name, age)
}
let sub = new Sub ( 'Tom' , 20 )
console. log ( sub) ;
组合继承,原型链继承原型中的属性或方法,构造函数继承实例中的属性或方法
function Parent ( name, age ) {
this . name = name
this . age = age
}
Parent . prototype. sayHi = function ( ) {
console. log ( 'Hello ' + this . name) ;
}
function Sub ( name, age, gender ) {
Parent . call ( this , name, age)
this . gender = gender
}
Sub . prototype = new Parent ( )
Sub . prototype. constructor = Sub
let sub = new Sub ( 'Tom' , 18 , 'male' )
sub. sayHi ( )
原型式继承,使用Object.create(originObj)
创建对象,新对象将会以originObj
为原型
寄生式继承,不会共享原型中的属性或方法
let baseObj = {
name : "baseObj" ,
getName ( ) {
console. log ( this . name) ;
}
}
function createProtoInhrit ( obj ) {
let copy = Object. create ( obj)
copy. sayHi = function ( ) {
console. log ( 'Hello ' + this . name) ;
}
return copy
}
let sub1 = createProtoInhrit ( baseObj)
sub1. name = "Tom"
let sub2 = createProtoInhrit ( baseObj)
sub2. name = "Bob"
sub1. sayHi ( )
sub1. getName ( )
sub2. sayHi ( )
sub2. getName ( )
function inheritPrototype ( child, parent ) {
var prototype = Object. create ( parent. prototype) ;
prototype. constructor = child;
child. prototype = prototype;
}
function Child ( name ) {
Parent . call ( this , name) ;
}
function Parent ( name ) {
this . name = name
}
inheritPrototype ( Child, Parent) ;
let sub = new Child ( "Tom" )
86.浏览器的垃圾回收机制是什么?
垃圾回收
当js
代码运行时,需要使用内存来存储变量与值,当这些变量与值不再需要时,就需要系统回收被占用的内存 回收机制
js
具有自动垃圾回收机制,会定期查找不再使用的变量,对象,释放其所占用的内存js
中存在两种变量:全局变量与局部变量.
全局变量的生命周期会一直持续到页面卸载 局部变量通常定义在函数内部,生命周期从函数开始执行到执行结束(特别地,当存在闭包时,函数执行结束后不会释放变量所占用的内存,因为函数外部仍然有指向该变量的值,该变量仍在使用) 垃圾回收的方式
标记清除
变量进入执行环境时,标记变量"进入环境",进入环境的变量无法被回收 变量离开执行环境时,标记变量"离开环境",此时的变量会被回收 引用计数
跟踪每个值被引用的次数,如果被引用的次数变为0,表示该变量已经没有价值,会被回收 减少垃圾回收
代码复杂时,垃圾回收的代价比较大,此时需要减少垃圾回收 优化清空数组,将数组的length
属性设置为0
优化对象使用,尽量复用对象,不再使用的对象,使用null
清空 优化函数使用,循环中的函数表达式,若是可以复用,尽量放在循环之外
87.哪些情况会导致内存泄漏?
意外的全局变量,未声明却使用的变量会成为全局变量,无法被及时回收. 被遗忘的计数器或回调函数,如果没有手动清除定时器,并且定时器中有使用外部变量,那么该变量不会被及时回收 脱离Dom
的引用,获取一个Dom
元素的引用,之后删除了该元素,由于一直保留对该Dom
的引用,导致其不会被及时回收 使用不合理的闭包
88.什么是事件循环机制?
在js
中,事件循环机制是js
运行时的核心机制,主要用来控制异步代码的执行 js
是一个单线程语言,但是浏览器支持多线程当js
代码在运行时,主线程会首先执行所有的同步代码,当遇到异步代码时,会将其放入一个事件队列,事件队列遵循先进先出原则;当主线程中的所有同步代码执行完毕之后,会从事件队列中取出一个异步代码进行执行,事件队列同时包含宏任务队列(可以有多个)与微任务队列(有且仅有一个),常见的宏任务有setTimeout
,setInterval
与setImmediate
,常见的微任务有Promise.then
,Promise.catch
,process.nextTick
与new MutationObserver
js
会优先取出微任务中的异步代码,当不存在微任务代码时,会再次去宏任务队列中查找是否有异步代码进行执行,所以一般地,微任务会先于宏任务进行执行