大前端面试题集锦——JavaScript篇

JavaScript篇

1.JS有哪些数据类型?有什么区别?

  • js中,有undefined,null,number,string,boolean,Symbol,Bigint,Object八大数据类型
  • 可以分为简单数据类型引用数据类型
    • 简单数据类型存储在栈区,数据简单,使用频繁,占据空间小,大小固定
    • 引用数据类型存储在堆区,数据复杂,占用空间大,大小不固定,在栈区存放的是指针,解释器在使用引用值时,会通过该地址在堆区中进行查找
  • 数据结构中
    • 栈区的数据存取方式一般为先进后出
    • 堆区是一个优先队列,优先级可以依据大小规定
  • 操作系统中
    • 栈区内存由编译器自行分配释放,存放函数参数,变量等
    • 堆区内存由开发者自行分配释放,如果不释放,程序结束后由垃圾回收机制自动释放

2.数据类型的检测方式有哪些?

  • 使用typeof检查(会将数组,对象,null都判断为object)
  • 使用instanceof检查(引用类型)
  • 使用constructor.name检查(不能检查nullundefined)
  • 使用Object.prototype.toString.call()检查

3.判断数组的方法有哪些?

  • 使用instanceof判断
  • 使用Array.isArray()判断
  • 使用constructor.name判断
  • 使用Object.prototype.toString.call()判断
  • 使用[].__proto__===Array.prototype判断
  • 使用Array.prototype.isPrototypeOf([])判断

4.nullundefined的区别?

  • 两者都是基本数据类型.null代表一个空对象,undefined代表未定义
    • 声明变量时未赋值将会是undefined,null用来给一个对象变量初始化
  • 使用typeofnull进行检查时会返回object
  • undefined不是一个保留字,可以使用它作为变量名
  • 判断undefined==null时会返回true,判断undefined===null时会返回false

5.typeof(null)的结果是什么?为什么?

  • 结果是object
  • js诞生之初,值被表示成为类型标记实际值,对象的类型标签是0.null是一个空指针,指向0x00地址,因此就有了该结果

6.instanceof操作符的实现原理以及实现?

  • instanceof用来判断构造函数的prototype属性是否出现在对象原型链中的任意位置
// 参数a代表被判断的值,参数代表构造函数
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.==操作符的强制转换规则是什么?

  • ==操作符对比两个数据是否值相等
  • 如果对比的两个数据类型不一致,就会进行强制类型转换
    • 判断是否在比较nullundefined,是则返回true
    • 判断是否在比较numberstring,将string转换为number
    • 判断是否在比较boolean与其他类型,将boolean转换为number
    • 判断是否在比较objectstring,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,constvar关键字的区别?

descriptionletconstvar
是否存在变量提升✔️
是否允许设置同名变量✔️
全局变量是否会添加到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
// target表示被代理的目标对象,handler用来配置额外功能
let pro=new Proxy(target,handler)
// 比如配置set与get
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
  	// 将构造函数的this指向空对象
  	let res = constructor.apply(obj, argus)
  	// 返回对象
  	if (res !== null && (typeof (res) === "function" || typeof (res) === "object")) {
    	return res
  	} else {
    	return obj
  	}
}

33.MapObject的区别?

descriptionMapObject
原始键默认不包含包含原型链中已存在的键
键的类型允许任何值作为键键必须是string或者Symbol
键的顺序按照插入的顺序无序
键的数量使用size属性获取遍历获取
是否可以迭代可以直接迭代无法直接迭代
性能频繁增删键值对时表现更好未进行性能优化

34.MapWeakMap的区别是什么?

MapWeakMap
可枚举不可枚举
允许任何值作为键值只允许对象作为键值
保持键的插入顺序不保持键的插入顺序

35.JS中有哪些内置对象?

  • 即标准内置对象,在全局作用域中的对象,在程序执行前,存在于全局作用域的由JS定义的一些全局属性,方法或是用来实例化其他对象的构造函数对象
  • 值属性的对象
    • Infinity,NaN,null,undefined
  • 函数属性的对象
    • parseInt,parseFloat
  • 基本对象
    • Object,Error,Function,Boolean,Symbol
  • 数字与日期对象
    • Number,Date,Math
  • 字符串对象
    • String,RegExp
  • 可索引的集合对象
    • Array
  • 使用键的集合对象
    • Map,Set,WeakMap,WeakSet
  • 结构化数据对象
    • JSON
  • 控制抽象对象
    • Promise,Generator
  • 反射
    • Reflect,Proxy
  • 文件对象
    • FileReader

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位)
    • /^[1-9][0-9]{4,10}$/
  • 手机号码正则
    • /^1[34578][0-9]{9}$/

37.对JSON的理解?

  • JSON是一种轻量级的数据交换格式,任何编程语言都可以使用它来交换数据
  • js中,通常用作前后端交互或者进行本地存储
  • JSON格式中,属性值不能为函数,不能处理undefined,Symbol,Function,RegExp,BigInt,Set,Map,WeakSet,WeakMap
  • js中,存在JSON.stringifyJSON.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
  • 按位异或^运算,两者同为01时为0,两者不同时为1
    • 使用a=a^b,b=a^b,a=a^b实现交换顺序
  • 取反~运算,0变为1,1变为0
  • 左移<<运算符,将二进制位全体向左移,高位丢弃,低位补0
  • 右移>>运算符,将二进制位全体向右移,低位丢弃,正数高位补0,负数高位补1
    • -1>>Infinity仍然为-1

44.为什么函数的arguments参数是类数组?如何遍历类数组?

  • 在函数中,arguments用来搜集所有传递的参数,并以对象方式进行存储
  • arguments对象中存在length属性,并且它的键是从0依次递增的整数
  • 它没有常规数组中的方法,被类数组
  • 遍历类数组
    • 使用Array.prototype.forEach.call(arguments,(item)=>void console.log(item))遍历
    • 使用Array.from(arguments)将其转换成为真数组
    • 使用[...arguments]将其转换为真数组

45.什么是DomBom?

  • Dom指的是文档对象模型,将网页文档当作一个对象,存放处理网页的各种接口与方法
  • Bom指的是浏览器对象模型,将浏览器当作一个对象,存放处理浏览器的各种接口与方法

46.escape,encodeURI,encodeURIComponent的区别?

  • encodeURI是将整个URI进行转义,将其中的非法字符转换为合法字符,但是URI中的某些特殊字符不会转义
  • encodeURIComponent是将URI组件进行转义,URI中的某些特殊字符也会进行转义
  • escapeencodeURI类似,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中,使用letconst关键字声明的变量不会进行变量提升
  • js在执行时,会存在解析与执行阶段
    • 解析阶段,检查语法,对函数进行预编译,将代码中将要执行的变量先存储为undefined,函数先声明好
    • 执行阶段,按照代码顺序依次进行执行
  • 变量提升主要是为了提高性能,提高容错率

49.什么是尾调用?有什么好处?

  • 尾调用指的是在函数中的最后一步调用另一个函数
  • 函数的执行是在栈中执行的,每当调用一个函数时,会将当前函数的上下文保留,如果在函数中调用另一个函数,那么会将当前函数的执行上下文与被调用的执行上下文一并保留
  • 在尾调用中,由于已经是最后一步,所以不必再保存当前函数的执行上下文,进而节省内存
  • ES6中,尾调用优化只在严格模式中启用

50.ES6模块与CommonJS模块有何不同?

ES6CommonJS
引用模块浅拷贝模块
静态导入(只允许在顶层导入)动态导入(允许在任何地方导入)
导入使用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)深度克隆节点
  • 获取子节点
    • 使用node.children

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...infor...of的区别?

for...infor...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))遍历对象获取对象的keyvalue

58.ajax,fetchaxios有什么区别?

  • 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.forEachmap方法有何区别?

  • 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); // true
  console.log(p.__proto__ === p.constructor.prototype); //true
  console.log(p.constructor); // Person
  • 原型重写
    • 直接通过Constructor.prototype=obj将原原型对象进行覆盖
    • 覆盖后,实例instance.constructor将指向Object,不再指向创建实例的构造函数
    • 如果希望覆盖后,实例的constructor仍然指向创建实例的构造函数,手动设置instance.constructor=ConstructorFun
  Person.prototype = {
    showName() {
      return this.name
    }
  }
  let q = new Person("Bob")
  // q.constructor = Person 未重新进行手动指向
  console.log(q.__proto__ === Person.prototype); // true
  console.log(q.__proto__ === q.constructor.prototype); // false
  console.log(q.constructor); // Object

63.原型链的指向问题?

  • 以下判断均为true
  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);// 输出6个6
      }, i * 1000)
    }

    // 使用let关键字声明
    for (let i = 1; i <= 5; i++) {
      setTimeout(() => {
        console.log(i);// 每隔1s顺序输出1-5
      }, i * 1000)
    }

    // 使用闭包固定参数,使其不被销毁
    for (var i = 1; i <= 5; i++) {
      (function (t) {
        setTimeout(() => {
          console.log(t);// 每隔1s顺序输出1-5
        }, i * 1000)
      })(i)
    }

    // 使用setTimeout的第三个参数
    for (var i = 1; i <= 5; i++) {
      setTimeout((p) => {
        console.log(p);// 每隔1s顺序输出1-5
      }, i * 1000, i)
    }

67.对作用域,作用域链的理解?

  • 全局作用域
    • 所有未定义直接赋值的变量自动拥有全局作用域
    • 所有window对象中的属性拥有全局作用域
    • 最外层函数与最外层函数之外定义的变量拥有全局作用域
    • 过多的全局作用域变量会污染全局命名空间
  • 函数作用域
    • 声明在函数内部的变量拥有函数作用域,一般只在函数内代码才能访问
    • 内层作用域可以访问外层作用域,反之无法访问
  • 块级作用域
    • 使用ES6新增的letconst关键字声明的变量具有块级作用域
    • 块级作用域包含在代码块{}中,超出代码块不再有效
  • 作用域链
    • 查找变量时,优先在当前作用域中查找
    • 如果当前作用域中无法找到该变量,那么就会区父级作用域查找,依次向上级查找
    • 直到查找到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.callapply的区别是什么?

  • 两者皆可以改变函数的this执行,并且可以执行函数
  • 使用call方法时,传递函数的参数时分别列举,原函数中需要多少个参数就需要传递多少个实参
  • 使用apply方法时,传递函数的参数时以数组的形式列举

71.如何实现call,applybind函数?

  • 实现call函数
Function.prototype.mycall = function (obj, ...argus) {
      // 判断obj是否是一个对象
      if (Object.prototype.toString.call(obj) !== "[object Object]") {
        throw new TypeError("第一个参数必须为一个对象!")
      } else {
        // 在 obj 上添加一个临时的 fn 属性,值为调用mycall的函数
        obj.fn = this;
        // 调用 fn 并传递参数
        let res = obj.fn(...argus);
        // 删除临时的 fn 属性
        delete obj.fn;
        // 返回结果
        return res;
      }
}
    function add() {
      console.log(this);
}
    add.mycall({});
  • 实现apply函数
// 实现方法与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
}
  • 实现bind方法
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链式调用时语义混乱
  • 使用asyncawait,当函数内部执行到await语句时,如果语句返回一个Promise对象,它会等到Promise对象状态变为resolve时继续向下执行代码
  • 使用generator,遇到异步操作时转移到函数之外,异步操作执行完毕后转移回来

73.setTimeout,Promiseasync/await有何区别?

  • setTimeout函数控制在一定时间后执行该参数函数中的代码
  • Promise是一个表示异步操作最终成功或者失败的对象,它使用.then表示成功后执行的结果,.catch表示失败后执行的结果,包含pending,fulfilled,rejected三种状态
  • async/await是使用编写同步代码的方式编写异步代码,async用来表示一个异步函数,await右侧是一个返回Promise对象的方法,await只能够在async中使用

74.对Promise如何理解?

  • Promise是一个构造函数,返回一个Promise对象,接收一个回调函数作为参数,回调函数的参数包含resolvereject两个参数,这两个参数可以分别设置规则承诺兑现与承诺拒绝
  • Promise是异步编程的一种解决方案,它是一个对象,解决了回调地狱.Promise中保存着未来才会结束的某个事件的结果
  • Promise存在pending,fulfilled,rejected三种状态,当承诺兑现时状态变为fulfilled,被拒绝时状态变为rejected,状态变更后不能再修改状态
  • Promise的缺点
    • 一旦新建立了一个Promise对象,其内部代码就会立即执行,不会中途停止
    • 如果不设置回调函数,Promise内部会出现错误,但是不会抛出到外部
    • 处于pending状态时,无法得知未来的结果

75.Promise的基本用法是怎样的?

  • 通常,使用new Promise((resolve,reject)=>{})创建一个Promise对象,其中的resolvereject分别用来表示兑现承诺与拒绝承诺
    • 使用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/awaitGenerator的语法糖,它使用同步的方式编写异步代码,它能够实现的效果均可以利用.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/awaitPromise
同步形式的代码,更加易于阅读与理解采用.then方式,多级调用不易阅读
使用try/catch语法捕获错误,更加友好使用.catch捕获错误,显得冗余
调试更加友好不容易调试

81.并行与并发有什么区别?

  • 并行是宏观概念,指在多个任务之间进行切换,进而完成每个任务
  • 并发是微观概念,如果CPU存在多个核心,那么就可以同时完成多个任务

82.什么是回调函数?它有什么缺点?如何解决回调地狱问题?

  • 回调函数指的是一个被传递到另一个函数中稍晚时间执行的函数
  • 回调函数通常用于处理异步操作的结果,如果多个回调函数之间存在依赖关系,那么很容易造成回调地狱
  • 使用Promise语法或者async/await语法能够很容易地处理回调地狱问题

83.setTimeout,setInterval,requestAnimationFrame各有什么特点?

  • setTimeout用来设置一个定时器,定时器中的回调函数只会被调用一次
  • setInterval用来设置一个间隔器,每隔一段时间就会调用一次回调函数
  • requestAnimationFrame是一个用于在浏览器的下一次重绘时执行动画或更新内容的函数,可以模拟实现setTimeoutsetInterval的功能

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() // Hello Tom
  • 构造函数继承,通过在子类中调用父类继承父类实例上的属性或方法
    • 缺点:无法访问父类原型中的方法
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);
// sub.sayHi() // TypeError
  • 组合继承,原型链继承原型中的属性或方法,构造函数继承实例中的属性或方法
    • 缺点:多次使用造成子类原型中属性冗余
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()
// 修正constructor指向
Sub.prototype.constructor = Sub
let sub = new Sub('Tom', 18, 'male')
sub.sayHi()
  • 原型式继承,使用Object.create(originObj)创建对象,新对象将会以originObj为原型
    • 缺点:无法向父类传递参数
  • 寄生式继承,不会共享原型中的属性或方法
    • 缺点:无法实现函数复用(Why?)
let baseObj = {
  name: "baseObj",
  getName() {
    console.log(this.name);
  }
}
function createProtoInhrit(obj) {
  // 新建对象以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"
// 不会共享原来的name:baseObj
sub1.sayHi() // Hello Tom
sub1.getName() // Tom
sub2.sayHi() // Hello Bob
sub2.getName() // Bob
  • 寄生组合式继承
// 寄生式继承原型上的属性或方法
function inheritPrototype(child, parent) {
  // 以父类的原型为原型
  var prototype = Object.create(parent.prototype);
  // 修正constructor的指向
  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,setIntervalsetImmediate,常见的微任务有Promise.then,Promise.catch,process.nextTicknew MutationObserver
  • js会优先取出微任务中的异步代码,当不存在微任务代码时,会再次去宏任务队列中查找是否有异步代码进行执行,所以一般地,微任务会先于宏任务进行执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Midshar.top

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值