【前端面试-JS】基础面试题
JS 基础面试
延迟加载js方式
- async - 后台下载 
- 执行会中断页面渲染 
- 下载好后立刻执行,没有顺序(谁先下载完谁先执行) 
 
- defer - 后台下载
- 不会中断页面渲染(等页面渲染完后执行)
- 顺次执行(多个js文件会按先后顺序执行)
 

数据类型
基本类型
string, number, boolean, undefined, null, symbol(, bigint)
引用类型
object(包括数组等)
数据类型的隐式转换
true+1 // 2
‘name’ + true // ‘nametrue’ (字符串和其他类型相加,都是转化成字符串)
undefined + 1 // NaN(是一个number)
typeof null // object
null 和undefined 区别
undefined
一个变量最原始的状态;此处应该有一个值,但是还没有定义;转为数值时为NaN
- 变量被声明了,但没有赋值
- 调用函数时,但没有传递实参
- 访问对象上不存在的属性或者未定义的变量
- 函数没有返回值时,默认返回undefined
null
表示一个被人为的重置为空对象;该处的值为空;转为数值时为0
典型用法:
- 作为函数的参数,表示该函数的参数不是对象。 
- 作为对象原型链的终点。 
在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。
== 和 === 区别
==
比较的是值
===
不仅比较值,还比较类型
微任务和宏任务
背景知识
js是单线程,但是有异步处理。js是同步任务执行完毕后再去执行异步任务。
什么是宏任务和微任务
微任务: promise.then
宏任务:定时器,用户交互事件,ajax,读取文件
js异步执行原则
1 同步
2 process.nextTick
3 异步
3.1 微任务: promise.then
3.2 宏任务:定时器,用户交互事件,ajax,读取文件
4 setImmediate方法
注意:
同步的任务都执行完后,才会执行事件循环的内容。
要执行宏任务,前提是清空所有微任务。
js代码执行流程: 同步执行完后 => 事件循环(宏任务和微任务) => 微任务 => 宏任务 => 微任务 …
 
进入事件循环:请求,定时器,事件…
JS作用域
1 除函数外,js没有块级作用域
2 作用域链:内部可以访问到外部的变量,但是外部不能访问内部的变量。
注意:如果有内部,优先查找内部,如果没有,就查找外部
3 声明变量没有用var let const,那这个变量就是全局的(window的)
4 js变量提升机制
5 优先级: 声明变量 > 声明普通函数 > 参数 > 变量提升
var
- 变量提升
- 变量覆盖
- 没有块级作用域
const
- 声明之后必须赋值
- 定义的值不可修改
- 其他和let一样
let
- 没有变量提升
- 不允许重复声明
- 所声明的变量,只在let命令所在的代码块内有效
暂时性死区
使用 let 或 const 声明的变量,在声明赋值没有到达之前,访问该变量都会导致报错,就连一直以为安全的 typeof 也不再安全
| 1 |  | 
注意:任何在暂时性死区内访问变量的企图都会导致 运行时错误(Runtime Error)。只有执行到变量的声明语句时,该变量才会从暂时性死区内被移除并可以安全使用。
JS对象
1 对象通过new操作符构建的,所以对象不相等(=== 是false)
2 对象是引用类型(地址)
3 对象的key是字符串类型
| 1 |  | 
4 对象如何找属性或者方法(原型链)
对象本身找 => 构造函数找 => 对象原型找 __proto__=> 构造函数原型找  prototype => 对象上一层原型查找 
instanceof
判断对象的类型,原理是通过判断对象的原型链中是不是能找到构造函数的 prototype。
语法
object instanceof constructor
| 1 |  | 
判断变量是否是数组
1 isArray
| 1 |  | 
2 instanceof
| 1 |  | 
3 原型prototype
| 1 |  | 
4 isPrototypeOf
| 1 |  | 
5 constructor
| 1 |  | 
new 操作符具体做了什么
1 创建了一个空对象
2 将空对象的原型,指向构造函数的原型
fn.prototype === new fn().__proto__
3 将空对象作为构造函数的上下文(改变this的方向)
| 1 |  | 
4 对构造函数有返回值的处理判断
如果返回的是基本类型,则没有影响,如果是对象,则就是返回值
| 1 |  | 
手写new
| 1 |  | 
闭包
1 闭包是什么
闭包是一个函数加上创建函数的作用域链接,闭包“关闭”了函数的自由变量。
通俗的描述:函数嵌套函数,内部函数就是闭包
正常情况下,函数执行完后,内部的变量会销毁,但是有了闭包后,函数内部的变量就可以保存。等内部的函数(闭包)执行外后,该变量才会被销毁。
| 1 |  | 
2 闭包解决什么问题
- 延长变量的生命周期(原理是内部函数可以访问到外部函数的局部变量) 
- 实现模块化功能(创建私有环境) - vue 的组件中的data是一个函数就是利用闭包的思想,可以保护组件的数据不被其他组件影响 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16- const cal = (function(){
 let a = 10, b = 20
 function add(){
 reurn a+b
 }
 function sub(){
 return a-b
 }
 return {
 add,
 sub
 }
 })()
 console.log(cal.add()) // 30
 console.log(cal.sub()) // -10
3 闭包缺点
变量驻留在内存中,造成内存损耗
原型对象
原型
每个对象都有原型对象,原型对象上的所有属性和方法,该对象都能访问。
| 1 |  | 
获取原型的方法
- 通过对象的 - __proto__
- 通过构造函数(或者类)的 - prototype- new 创建的是一个对象。 
| 1 |  | 
类与继承
1 类
ES5创建对象用构造函数,ES6创建对象用class
| 1 |  | 
2 继承
- ES6
| 1 |  | 
- ES5: prototype
| 1 |  | 
原型链
- 原型链是什么 - 一个对象有原型,其原型又是一个对象,该对象也有原型,可以看,一直往上,有一条原型链存在。(最顶端是 - Obejct.prototype)
- 解决什么问题 - 对象共享属性和共享方法 
- 谁有原型 - 函数:prototype - 对象: - __proto__
- 对象查找属性或者方法的顺序 - 对象本身找 => 构造函数找 => 对象原型找 - __proto__=> 构造函数原型找 prototype => 对象上一层原型查找
JS继承
1 ES6
2 原型链
3 构造函数继承
4 组合式继承
call, apply, bind区别
共同点:功能一致
- 改变this指向,
- 语法:函数.call() 、函数.apply()、函数.bind()
区别
- call, apply可以立即执行,bind不会立即执行(返回的是一个函数)
- 参数:apply第二个参数是数组,call和bind有多个参数是需要挨着写(arg1,arg2,arg3,…)
场景
- 继承:子类可以继承父类的方法
| 1 |  | 
- 求最大值
| 1 |  | 
2 bind
| 1 |  | 
手写bind, call, aplly
- 手写 - bind- 1 
 2
 3
 4
 5- Function.prototype.myBind = function(obj,...args){
 return (...rest)=>{
 this.call(obj,...args,...rest)
 }
 }
- 手写 - call- 1 
 2
 3
 4
 5
 6
 7
 8- Function.prototype.myCall = function (obj, ...args) {
 // this表示f.myCall的f函数
 obj.fn = this
 const result = obj.fn(...args)
 // 删除 fn
 delete obj.fn
 return result
 }
- 手写 - apply
| 1 |  | 
this指向
1 普通函数或作为对象属性
函数作为对象的属性被调用的时候,其this指向调用该函数的对象,否则其this指向window
| 1 |  | 
2 事件绑定
在进行事件绑定的时候,事件绑定函数中的this是绑定事件的元素
3 构造函数(new Fn)
构造函数(new Fn)执行,函数中的this是当前类的实例
| 1 |  | 
4 箭头函数
箭头函数中没有自身的this,所用到的this都是其最近父级上下文中的this
| 1 |  | 
5 call/apply/bind改变this指向
call/apply/bind传入的第一个参数即为函数的this
| 1 |  | 
- call在执行时,第一个参数为- this指向,之后的参数为- fn执行时的参数
- apply在执行时,第一个参数为- this指向,之后的参数为- fn执行时的参数组成的数组,数组的每一项会和- fn的每一个参数进行对应
- bind在执行时,第一个参数为预先传入- this指向,之后的参数为实际调用- fn前预先传入的参数,返回值为一个函数- fixedThisFn,- fixedThisFn内部会调用- fn并指定其- this指向
promiss和async函数
1 promiss用法
| 1 |  | 
2 async函数
| 1 |  | 
3 async await
await 后面是一个promiss对象,可以拿到resolve的值
| 1 |  | 
4 手写promiss
一个简单的 Promise 的粗糙实现,关键点在于
- 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
- Promise构造函数接受一个函数作为参数,该函数的两个参数分别是- resolve和- reject。
- 设置两个队列,onResolvedCallbacks,onRejectedCallbacks,用于存放成功和失败的回调函数,当状态发生改变时依次从数组中取出执行。
- 利用setTimeout实现resolve和rejected异步
- then返回- promiss
| 1 |  | 
可枚举、不可枚举
对象的属性所有的特性有以下几种:
| 1 |  | 
其中enumerable表示可枚举特性(true为可,false为不可)。
对象拷贝
基本类型存储在栈内存中,对象存储在堆内存中
1 浅拷贝
| 1 |  | 
缺点:对象的属性只能是基本类型,不能是对象,所以该方法只能进行浅拷贝
2 深拷贝(对象里面包含对象的拷贝)
| 1 |  | 
3 对象的拷贝
- 方法一 Object.assign() - 1 
 2
 3
 4
 5
 6- const person = {
 name: 'Wes Bos',
 age: 80
 };
 const cap2 = Object.assign({}, person, { number: 99, age: 12 });
 console.log(cap2); // Object {name: "Wes Bos", age: 12, number: 99}
- 方法二 JSON 转换 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- const wes = {
 name: 'Wes',
 age: 100,
 social: {
 twitter: '@wesbos',
 facebook: 'wesbos.developer'
 }
 };
 const dev2 = JSON.parse(JSON.stringify(wes));
 console.log(dev2);
防抖节流
防抖
防止触发事件过于频繁,只执行最后一次
1 手写防抖
| 1 |  | 
2 防抖应用场景
- 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
- 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
- 文本编辑器实时保存,当无任何更改操作一秒后进行保存
节流
控制执行次数
1 手写节流
| 1 |  | 
2 节流应用场景
- scroll事件,每隔一秒计算一次位置信息等
- 浏览器播放事件,每个一秒计算一次进度信息等
- input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (也可做防抖)
总结 (简要答案)
- 防抖:防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。**代码实现重在清零 clearTimeout**。防抖可以比作等电梯,只要有一个人进来,就需要再等一会儿。业务场景有避免登录按钮多次点击的重复提交。
- 节流:控制流量,单位时间内事件只能触发一次,与服务器端的限流 (Rate Limit) 类似。**代码实现重在开锁关锁 timer=timeout; timer=null**。节流可以比作过红绿灯,每等一个红灯时间就可以过一批。
- debounce: Grouping a sudden burst of events (like keystrokes) into a single one.
- throttle: Guaranteeing a constant flow of executions every X milliseconds. Like checking every 200ms your scroll position to trigger a CSS animation.
ES6 filter函数
| 1 |  | 
forEach 和map区别
- forEach- 没有返回值
- 不会被break打断
- 遍历的是value
 
- map- 有返回值,返回是数组,默认返回值是undefined
- 不会被break打断
- 遍历的是value和key
 
ES6 解构
数组结构
| 1 |  | 
对象解构
| 1 |  | 
正则表达式
常用的正则表达式有哪些?
| 1 |  | 
虚拟列表
类数组转化为数组的方法
题目描述:类数组拥有 length 属性 可以使用下标来访问元素 但是不能使用数组的方法 如何把类数组转化为数组?
实现代码如下:
| 1 |  | 
为什么0.1+0.2 ! == 0.3,如何让其相等
在开发过程中遇到类似这样的问题:
| 1 |  | 
这里得到的不是想要的结果,要想等于0.3,就要把它进行转化:
| 1 |  | 
toFixed(num) 方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢?
计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。0.1的二进制是0.0001100110011001100...(1100循环),0.2的二进制是:0.00110011001100...(1100循环),这两个数的二进制都是无限循环的数。那JavaScript是如何处理无限循环的二进制小数呢?
一般我们认为数字包括整数和小数,但是在 JavaScript 中只有一种数字类型:Number,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从“0舍1入”的原则。
根据这个原则,0.1和0.2的二进制数相加,再转化为十进制数就是:0.30000000000000004。