1. 基本数据类型
- Number
- String
- Boolean
- Undefined
- null
- BigInt
- Symbol
2. 引用数据类型
- Object
- Array
- Function
函数实际上是对象,每个函数都是Function类型的实例,而Function也有属性和方法,跟其他引用类型一样 - 其他引用类型
除了上述说的三种之外,还包括Date、RegExp、Map、Set
存储差别
基本数据类型和引用数据类型存储在内存中的位置不同:
- 基本数据类型存储在栈中
- 引用类型的对象存储在堆中
当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值
3. instanceof运算符的实现原理及实现
instanceof运算符适用于检测构造函数的prototype属性是否出现某个实例对象的原型链上
原理:
instanceof运算符的原理是基于原型链的查找。当使用 obj instanceof constructor的时候,js引擎会从obj的原型链上查找constructor.prototype的存在,如果找到返回true,没有找到则继续在原型链上寻找,如果查找到原型链顶端依然没有找到则返回false
- instanceof适用于引用数据类型,不能用于简单数据类型的检查
4. typeof与instanceof
typeof
typeof操作符返回一个字符串,表示未经计算的操作数的类型
typeof 1;//'number'
typeof '1';//'string'
typeof undefined;//'undefined'
typeof true;//'boolean'
typeof null;//'object'
判断一个变量a是否存在(不要使用if(a) ,因为如果a未声明,会报错)
if(typeof a != ‘undefined’){
// 变量存在
}
如果需要通用检测数据类型,可以采用Object.prototype.toString,调用该方法,统一返回格式"[Object Xxx]"类型的字符串
instanceof
instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
object instanceof constructor
object为实例对象,constructor为构造函数
构造函数通过new可以实例对象,instanceof能判断这个对象是否是之前那个构造函数生成的对象
区别
typeof和instanceof都是判断数据类型的方法
- typeof会返回一个运算数的基本类型,但是intanceof返回的是布尔值
- instanceof可以准确判断引用数据类型但是不能判断简单数据类型。typeof则恰好相反(function除外)
5. 为什么0.1+0.2 !== 0.3.如何让其相等
在计算机计算中,会把数据转换为二进制来进行运算。但是0.1是不能直接用二进制来表示的,它的二进制是无限循环的,所以人们使用了一种采取一定精度使用近似值表示一个小数的方法,就是IEEE 754。十进制浮点数无法完全精确转换为二进制浮点数,所以在计算时会产生误差
解决方案
- 将其先转换为整数,在相加之后转回小数。具体做法为先乘10相加后除以10
let x = (0.1*10 + 0.2*10)/10;
console.log(x === 0.3);
- 使用number对象的toFixed方法,只保留一位小数点
(n1 + n2).toFixed(2);
6. 判断数组的方法有哪些?
- 通过原型链判断
obj.__proto__ === Array.prototype;
- 通过es6的Array.isArray()判断
Array.isArray(obj);
- 通过instanceof做判断
obj instanceof Array;
- 通过Object.prototype.toString.call()做判断
Object.prototype.toString.call(obj).slice(8,-1)==="Array"
7. 将类数组arguments转换为数组的方法
- Array.from(arguments)
- Array.prototype.slice.call(arguments)
- […arguments]
8. foreach 和 map 方法的区别
两个方法都是用来遍历数组的
foreach会改变原数组,没有返回值
map不会改变原数组,返回一个经过操作之后的新数组
9. == 和 === 的区别
== 操作符
等于操作符在比较中会先进行类型转换,再确定操作数是否相等
遵循以下规则:
- 如果任意操作数是布尔值,则将其转换为数值再比较是否相等
let result = (true == 1);// true
- 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等
let result = ("11" == 11);// true
- 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法取得其原始值,再根据前面的规则进行比较
let obj = {
valueOf:function(){
return 1;
}
}
let result = (Object == 1);// true
- null 与 undefined相等
- 如果有任一操作数是NaN,则相等操作符返回false
let result = (NaN == NaN);// false
- 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true
let obj1 = {name:"kkkk"};
let obj2 = {name:"kkkk"};
let result = (obj1 == obj2);// false
总结
- 两个都为简单类型,字符串和布尔值都会转换成数值,在比较
- 简单类型与引用类型比较,对象转换成其原始类型的值,再比较
- 两个都为引用类型,则比较它们是否指向同一个对象
- null和undefined相等
- 存在NaN则返回false
=== 操作符
只有两个操作数在不转换的前提下相等才返回true。即类型相同,值也需相等
undefined 和null 与自身严格相等
let result1 = (null === undefined);// false
let result2 = (null === null);// true
区别
== 操作符会做类型转换,再进行值的比较,=== 运算符不会做类型转换
但在比较null的情况的时候,我们一般使用相等操作符==
const obj = {};
console.log(obj.x == null);// true
10. let const var的区别
块级作用域解决的问题:
1、内层变量名可能会覆盖外层变量名
2、用来计数的变量可能会泄露为全局变量
变量提升:
let const 不存在变量提升,必须先声明后使用,如果未声明就访问,就会触发ReferenceError,这个阶段称为暂时性死区
var可以重复声明变量,后面的变量会覆盖前面的,let、const不允许重复声明
var let 声明变量后可以不先设置初始值,但是const必须设置
- const定义的是常量,之后不能重新赋值
11. 箭头函数和普通函数的区别
- 箭头函数是匿名函数,不能作为构造函数使用new关键字
- 不能使用argument
- 箭头函数没有自己的this,会取上下文作为自己的this
- 箭头函数没有prototype
- 箭头函数不能使用yield关键字
12. js常用数据结构
- 数组
- 对象
- 栈
- 队列
- 链表
- 树
- 图
- 堆
13. DOM
14. BOM:
浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象,其作用就是跟浏览器做一些交互效果,比如如何进行页面的前进、后退、刷新、浏览器窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏幕分辨率。
-
window
BOM的核心对象,表示一个浏览器实例。在浏览器中,window对象有双重角色,即是浏览器窗口的一个接口,又是全局对象
因此所有在全局作用域中声明的变量、函数都会变成window对象的属性和方法 -
location
属性:
hash:url中#后面的字符,没有则返回空串
host:域名+端口号
hostname:域名
href:完整url
pathname:服务器下面的文件路径
port:url的端口号,没有则返回空
protocol:协议
search:url的查询字符串,通常为?后面的内容
location.reload(),此方法可以重新刷新当前页面 -
navigator
navigator对象主要用于获取浏览器的属性,区分浏览器的类型。
navigator对象接口定义的属性和方法 -
screen
保存的纯粹是客户端能力信息,也就是浏览器窗口外面客户端显示器的信息,比如像素宽度和像素高度 -
history
history对象主要是操作浏览器url的历史记录,可以通过参数向前,向后,或者向指定url跳转
history.go()
history.forward()
history.back()
history.length()
15. js原型:
js中每个对象都有一个指向另一个对象的引用,这个对象就被称为原型。它包含对象共享的属性和方法。
js原型链:
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联关系是一种链状的结构,这种原型对象的链状结构关系称为原型链。
原型链其实是一种查找规则。
实现原型继承:
通过原型来继承构造函数 2、 将constructor指向原来的构造函数
js原型的应用场景
- 继承
通过原型,可以实现对象之间的继承关系。子对象的原型链会指向父对象的原型,从而实现属性和方法的共享。 - 原型链
原型链是 JavaScript 中实现继承的机制,通过原型链,对象可以沿着原型链访问其原型上的属性和方法。 - 原型方法
将方法添加到对象的原型上,可以使得所有通过该原型创建的实例都能够共享这些方法,提高代码的复用性。
如将全局事件总线挂载到全局时,Vue.prototype.$bus = Bus - 性能优化
在 JavaScript 中,每个对象通过原型链访问属性和方法,而不是直接在对象上定义,这有助于减少内存占用和提高代码执行效率。 - 动态性
可以在运行时动态地修改原型对象,添加、修改或删除原型上的属性和方法,从而影响通过该原型创建的所有实例。
16. this
定义:函数的this关键字是函数运行时自动生成的一个函数内部对象,只能在函数内部使用,总指向调用它的对象
绑定规则:
- 全局上下文中的 this: 当函数在全局上下文中被调用时,this 指向全局对象,在浏览器中通常是 window 对象。
- 函数作为对象的方法调用时的 this: 当函数作为对象的方法被调用时,this 指向调用该方法的对象。
- 使用 call()、apply() 或 bind() 明确指定 this: 这些方法允许显式地设置函数执行时的 this 值。
- 构造函数中的 this: 当一个函数被用作构造函数并使用 new 关键字调用时,this 指向新创建的对象。
- 箭头函数中的 this: 箭头函数的 this 是在定义时确定的,指向其父作用域中的 this 值,而不是在调用时确定。
17. 事件模型
JavaScrip中事件模型是一种处理用户交互和其他异步操作的机制。基于事件驱动的编程范式,通过添加事件监听器来响应事件的发生。当事件发生时,相关的事件处理函数会被调用,执行特定的代码逻辑。通过事件模型,可以实现与用户的交互以及页面元素之间的动态交互。
事件模型分为三种:原始事件模型、标准事件模型、IE事件模型
18. 事件与事件流
事件:
事件是指用户在浏览器中执行的动作,如点击、鼠标移动、键盘输入等。每个事件都会触发相应的事件对象,并可以通过事件监听器来捕获和处理事件。
事件流:
事件流描述了事件在页面中传播和触发的顺序。事件流分为冒泡(bubble)阶段和捕获(capture)阶段。在冒泡阶段中,事件从最具体的元素(触发事件的元素)向最不具体的元素(文档根元素)传播。而在捕获阶段中,事件从最不具体的元素向最具体的元素传播。在捕获阶段和冒泡阶段之间存在一个目标阶段,代表事件到达目标元素的阶段。
取消事件冒泡
js:event.stopPropagation()方法
事件冒泡和事件捕获的区别
事件冒泡和事件捕获是两种不同的事件传播方式,默认是冒泡,他们的区别在于事件的传播方式不同
- 事件冒泡是自下而上,从子元素冒泡到父元素,触发的是父元素上的事件处理
- 事件捕获是事件从文档的根元素开始,逐级向下传播到较为具体的元素。即从父元素到子元素。
事件代理(事件委托)
事件代理是一种利用事件冒泡机制来简化事件处理的技术。它通过将事件处理程序绑定到一个父元素上,然后利用事件冒泡的特性,让父元素代理子元素的事件处理。这样可以减少事件处理程序的数量,提高性能,并且可以处理动态添加或移除子元素的事件。
- 列表/表格项的事件处理
- 动态元素的事件处理
- 性能优化
- 减少内存占用
19. 闭包:
一个函数对于周围状态的引用捆绑在一起,这样的组合就是闭包
简单来说就是内层函数+外层函数的变量
使用场景:实现数据的私有,创建全局私有变量
20. map和Object的区别
- 键的类型:
map的键可以是任意数据类型(包括对象,函数,NAN),但是Object的键只能是字符串和Symbol类型 - 键值对的顺序
map中的键值是按照插入的顺序存储的,而对象中的键值没有顺序 - 键值对的遍历
map的键值对可以使用for…of遍历,而Object的键值对需要手动遍历 - 继承关系
map没有继承关系,Object是所有对象的基类
21. 垃圾回收机制
垃圾回收:
js代码运行时,需要分配内存空间来存储变量和值,当变量不在参与运行之后,系统会自动回收占用的内存空间,避免因不及时清理造成系统卡顿、内存溢出。
内存泄露:
程序中分配的内存由于某种原因没有被释放或者无法释放,就叫做内存泄露(意外的全局对象、被遗忘的计时器或回调函数、闭包、脱离DOM的引用)
如何进行垃圾回收:
- 标记清除法
通过标记所有活动对象,然后清除所有未标记(即未被引用)的对象来完成内存回收。 - 引用计数法
这种算法会为每个对象维护一个引用计数,当引用计数为零时表示对象不再被引用,可以被回收。这种算法简单直观,但容易出现循环引用导致内存泄漏的问题。
22. call、apply、bind的区别
都能修改this指向
- 传参:call、bind都是传入一个对象,apply是传入一个数组
- 调用函数:call和apply在修改this指向后会立即执行函数,但是bind函数改变this的指向后会返回this被改变后的函数,需要手动调用才会执行
23. new操作符的实现原理
- 创建一个空对象
- 将空对象的__proto__指向构造函数的原型对象
- 将构造函数的this指向空对象
- 返回创建的对象
手写new:
function myNew(constructor,...args){
// 创建一个空对象
const obj = {};
// 将空对象的__proto__指向构造函数的原型对象
obj.__proto__ = constructor.prototype;
// 利用apply方法,将constructor指向对象,并传入参数
const result = constructor.apply(obj,args);
// 如果执行结果有返回值并且是一个对象,返回执行的结果,否则返回新创建的对象
return result instanceof Object ? result : obj;
}
24. 宏任务与微任务
宏任务和微任务是js事件循环中的两种任务类型,他们在执行顺序和优先级上有所不同
- 宏任务:宏任务是常规的事件任务类型,他们在事件循环中按照顺序依次执行。
常规的宏任务包括:
script:整体代码脚本
setTimeoutr和setInteral:通过两个函数设置的延时或定时执行的回调函数
I/O:在nodejs中,来自文件、数据库等的回调也是宏任务
UI渲染
postMessage和MessageChannel:通过这两个方法发送和接受消息
Promise
async - 微任务:微任务是比宏任务更高优先级的任务类型,它们在每次事件循环的末尾执行,即执行完一个宏任务后,会立即执行所有等待的微任务。
常见的微任务包括:
Promise.then和Promise.catch中的回调
浏览器环境:用于监视DOM更改的API,当DOM发生变化时,其回调函数会被添加到微任务队列中
queueMicrotask
promise的回调
await
25. 事件循环
js中的事件循环是一种重要机制,用于处理异步操作和协调任务执行的操作。
核心思想:在单线程中高效管理任务执行,确保任务执行的顺序
事件循环包括三个部分:执行栈,任务队列和事件循环本身。
- 执行栈:执行栈是一个用于管理函数调用关系和执行上下文的数据结构。当一个函数被调用的时候,它的执行上下文会被压入栈中。当函数执行完毕后,它的函数上下文会从执行栈中被弹出
- 任务队列:任务队列用于存储待执行的任务。这些任务通常是异步任务的回调。当异步操作完成后,它的回调函数会被添加到任务队列中等待执行。任务队列有多个,其中包含宏任务和微任务。
- 事件循环:协调执行栈和任务队列之间工作的核心机制。当执行栈为空时,事件循环会从任务队列中取出一个任务(首先检查微任务,然后再检查宏任务),将其推进执行栈中执行。这个过程会不断重复,形成一个循环,因此叫做事件循环
事件循环工作流程: - 执行同步任务
- 处理微任务
- 执行宏任务
- 重复循环
26. 消息队列
js的消息队列是一种管理和处理异步任务的机制,它保证了异步任务的执行顺序并且遵循事件循环机制。js引擎使用消息队列来存储和管理待执行的异步任务,并在适当的时机将它们从队列中取出并执行。
消息队列的应用
- 通过消息队列可以实现异步编程,如定时器、事件监听、网络请求等,避免阻塞主线程。
- Promise 对象的异步执行结果处理就是借助了消息队列的机制。
27. defer和async
async
异步加载下载js脚本文件,在加载下载完成之后立马执行js脚本文件,多个async脚本文件执行时执行顺序没有保障,执行js的过程中,会阻塞html的解析和渲染。
defer
异步加载下载js文件,在下载完成后会等到html文件解析完成后再执行defer脚本文件,多个defer脚本文件执行时顺序有保障,会先来后到的执行。执行过程中会阻塞html的解析和渲染。
两者区别
- 执行的事件不同,async是下载完成后立马执行,而defer需要等html完全的解析之后才会执行
- 执行顺序。多个defer的执行顺序都是有保障的,而多个async的执行顺序没有保障。
- 如果想要获取到dom就需要使用defer。
28. css和js的解析顺序
默认情况下,浏览器会在解析完HTML和CSS后再解析JavaScript
当浏览器遇到 script 标签或者外部 JavaScript 文件(通过 script src=“…” 引入)时,会停止解析 HTML,先执行 JavaScript 代码。
29. require和import的区别
- 加载方式:require是在运行时加载模块,import是在编译时加载模块
- 规范:require遵循commonjs/AMD规范,import遵循es6规范
- 使用位置:require可以写在文件的任意位置,但是import只能写在文件的顶部
- 导出方式:require导出的是exports对象,import通过exports导出的是指定输出的代码
- 性能:由于require是运行时加载,性能较低,而import是编译时加载,性能较高。
- 值得改变:通过require引入的模块,其值一旦确定就不能改变,import引入模块的值可以改变
30. js异步
js异步是指在js中,某些操作不会立马执行,而是会延迟一段时间或者等待其他任务完成再执行。
异步操作通常通过promise、async/await等方法实现
31. js处理并发
- 使用promise,async/await
- 事件驱动:JavaScript 是事件驱动的语言,通过事件机制可以处理并发的用户交互、网络请求等操作。通过监听事件并注册相应的处理函数,可以在不同的事件发生时执行相应的逻辑,实现并发处理。
- Web Worker:Web Workers 是 HTML5 提供的一种在后台运行脚本的机制,可以在单独的线程中执行 JavaScript 代码。通过 Web Workers,可以在后台处理计算密集型任务或者网络请求,从而不影响主线程的执行。
js是单线程语言,意味着在 JavaScript 运行时环境中,只有一个主线程用于执行代码和处理事件。
32. js包括哪些部分
- ECMAscript:js语言的核心部分,描述了js的语法
- 文档对象模型DOM:DOM是js操作网页内容的接口。它将HTML文档表示为一个树形结构,使js可以操作和修改页面的内容样式结构。
- 浏览器对象模型BOM:BOM提供了js与浏览器交互的接口。
- 事件处理
- 异步编程
- 模块化
- ES6+新特性
33. 数组的方法
不会改变原数组的方法:
map,filter,slice,concat,reduce,every,some
34. 字符串方法
不会改变原始字符串,而是返回一个新的字符串
- 字符串的查找和提取
indexOf lastIndexOf chatAt substring slice - 字符串的拼接 concat
- 字符串的转换
toLowerCase,toUpperCase,trim - 其他操作
match,replace,split
35. Generator函数
Generator是es6引入的一种新的函数类型,它允许函数在执行过程中被暂停和恢复。其原理主要依赖于函数的执行上下文和迭代器协议
- 执行上下文
- 迭代器协议
36. ES6的新特性
- let和const声明
- 箭头函数
- 模版字符串
- 解构赋值
- 默认参数
- 展开运算符
- 类和继承
- 模块化
- Promise
- Generator
37. 进程和线程
- 进程是程序执行的一次过程,是操作系统资源分配的基本单位。每个进程都有独立的内存空间,包含程序的代码、数据和堆栈等。不同进程之间相互独立,通信需要通过进程间通信(IPC)机制
- 线程是进程中的实体,是CPU调度的基本单位。线程共享相同的进程地址空间,包含代码段、数据段和堆栈等,可以直接访问进程的共享资源
区别:
- 进程和线程的最大区别在于资源分配和执行方式。进程是独立的程序执行过程,拥有独立的地址空间;而线程是进程中的执行实体,共享相同的地址空间。
- 进程间通信需要较为复杂的 IPC 机制,而线程之间可以直接通过共享内存等方式进行通信,更加高效便捷。
38. 前端设计模式:
- MVC模式——将应用程序分为三个组件:模型(数据处理)、视图(UI)和控制器(逻辑层)
- MVVM模式——将MVC中的控制器分为绑定器和视图模型两部分,使视图与数据绑定更简单
- 观察者模式——当被观察对象状态改变时通知所有观察者
- 工厂模式——通过工厂类创建对象,避免使用new关键字直接创建对象。
- 单例模式——确保只有一个实例存在的设计模式。
- 装饰器模式——动态地向对象添加新的行为,同时保持与原始类的接口的兼容性。
- 适配器模式——允许不兼容的接口之间进行通信。
39. Promise
Promise是js中用来处理异步操作的对象。它代表了一个异步操作的最终完成或失败,并且可以获取其结果。避免了回调地狱的问题。
一个Promise实例有两个重要的方法:then()和catch()。
then() 方法用于指定当 Promise 对象状态变为 resolved(完成态)时要执行的回调函数,而 catch() 方法用于指定当 Promise 对象状态变为 rejected(拒绝态)时要执行的回调函数。
Promise.all() 方法接收一个 Promise 对象的数组作为参数,当所有的Promise对象都变成完成态时,它才会变成完成态;
Promise.race() 方法接收一个 Promise 对象的数组作为参数,当其中任意一个Promise对象变成态或者拒绝态时,它就会相应地变成完成态或拒绝态。
40. async和await底层原理
async/await是建立与promise之上的语法糖。它使异步代码能够以同步的方式书写。
其底层原理主要依赖于promise的链式调用和js的事件循环机制。
- 在底层,async/await通过编译器被转换成了Promise的链式调用。编译器会把await表达式转换成Promise.then()的调用,并处理错误。
- async/await捕获异常:通过async函数内部使用try/catch捕获异常