- ==不同类型数据比较规则:
类型 类型 其他说明
对象 对象 比较是不是同一个内存地址
对象 字符串 对象先转为字符串,在和字符串进行比较
对象 布尔类型 两边都要先转为数值,false 是 0,true 是 1对象类型先隐式调用toString()方法,然后在Number()
对象 数字 对象要转为数字,在进行比较(对象先隐式调用toString()方法转为字符串,然后在把字符串转为数字使用Number()方法)
数字 布尔 布尔转成数字(false 是 0,true 是 1),在和数字进行比较
数字 字符串 字符串转成数字使用Number()方法,在和数字进行比较
布尔 数字/字符串 都转成数字在进行比较
null undefined true
null/undefined 其他类型 结果都是false
NaN NaN false
- 判断数组的五种方法:
1 | let arr = [] |
- null 和 undefined 的差异:
null 转为数字类型值为 0,而 undefined 转为数字类型为 NaN(Not a Number)
undefined 是代表调用一个值而该值却没有赋值,这时候默认则为 undefined
null 是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象)
设置为 null 的变量或者对象会被内存收集器回收
==、===与Object.is()的区别:
==:等同,比较运算符,两边值类型不同的时候,先进行类型转换,再比较;
===:恒等,严格比较运算符,不做类型转换,类型不同就是不等;
Object.is()与===差不多,主要区别是:+0 不等于-0。
NaN 等于自身。
1 | console.log(Object.is(NaN,NaN)) //true |
- isNaN 和 Number.isNaN 函数的区别:
- 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。
- 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。
1 | console.log(Number.isNaN("nihao")) //false |
- 什么是 JavaScript 中的包装类型?
在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象。可以使用valueOf方法将包装类型倒转成基本类型。
1 | var a = new Boolean( false ); |
- let、const、var的区别:
(1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。

暂时性死区:ES6 新增的 let、const 关键字声明的变量会产生块级作用域,如果变量在当前作用域中被创建之前被创建出来,由于此时还未完成语法绑定,如果我们访问或使用该变量,就会产生暂时性死区的问题,由此我们可以得知,从变量的创建到语法绑定之间这一段空间,我们就可以理解为‘暂时性死区’。
- 箭头函数:
- 箭头函数没有 arguments
- 箭头函数没有 prototype 属性,不能用作构造函数
- 箭头函数没有自己 this,它的 this 是词法的,引用的是上下文的 this,即在你写这行代码的时候就箭头函数的 this 就已经和外层执行上下文的 this 绑定了。箭头函数的 this 指向即使使用 call,apply,bind 也无法改变(这里也验证了为什么 ECMAScript 规定不能使用箭头函数作为构造函数,因为它的 this 已经确定好了无法改变)
- for…of跟for…in的区别:
for … of 是作为 ES6 新增的遍历方式,允许遍历一个含有 iterator 接口的数据结构并且返回各项的值 。
ES 中的 for … in 的区别如下
1、for … in 可以获取所有对象的键名,for … of 只能用在可迭代对象上,获取的是迭代器返回的 value 值,不能用在普通对象上,获取普通对象可以与Object.keys()搭配
2、for … in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历它的原型链
3、对于数组的遍历,for … in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for … of 只返回数组的下标对应的属性值
1 | let arr = [1,2,3,4,5,6] |
- JS 为什么放到后面,CSS 会阻塞渲染吗:
为什么外链 css 为什么要放头部?
首先整个页面展示给用户会经过 html 的解析与渲染过程。而外链 css 无论放在 html 的任何位置都不影响 html 的解析,但是影响 html 的渲染。如果将 css 放在尾部,html 的内容可以第一时间显示出来,但是会阻塞 html 行内 css 的渲染。浏览器的这个策略其实很明智的,想象一下,如果没有这个策略,页面首先会呈现出一个行内 css 样式,待外部css渲染完之后又突然变了一个模样。用户体验可谓极差,而且渲染是有成本的。如果将 css 放在头部,css 的下载解析是可以和 html 的解析同步进行的,放到尾部,要花费额外时间来解析 CSS,并且浏览器会先渲染出一个没有样式的页面,等 CSS 加载完后会再渲染成一个有样式的页面,页面会出现明显的闪动的现象。
为什么 script 要放在尾部?
因为当浏览器解析到 script 的时候,就会立即下载执行,中断 html 的解析过程,如果外部脚本加载时间很长(比如一直无法完成下载),就会造成网页长时间失去响应,浏览器就会呈现“假死”状态,这被称为“阻塞效应”。
具体的流程是这样的:
- 浏览器一边下载 HTML 网页,一边开始解析。
- 解析过程中,发现 script 标签
- 暂停解析,网页渲染的控制权转交给 JavaScript 引擎
- 如果 script 标签引用了外部脚本,就下载该脚本,否则就直接执行
- 执行完毕,控制权交还渲染引擎,恢复往下解析 HTML 网页
- JavaScript 脚本延迟加载的方式有哪些?
defer 属性: 给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
async 属性: 给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
动态创建 DOM 方式: 动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
使用 setTimeout 延迟方法: 设置一个定时器来延迟加载 js 脚本文件
让 JS 最后加载: 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
- 为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
arguments对象是所有(非箭头)函数中都可用的局部变量,你可以使用arguments对象在函数中引用函数的参数。
1 | //方法一:将数组的方法应用到类数组上,这时候就可以使用call和apply方法 |
- Eventloop:
JavaScript中有一个主线程和调用栈,所有的任务都会被放在调用栈等待主线程执行。
同步任务会在调用栈中等待主线程依次执行,异步任务会在异步任务有了结果之后,将注册的回调函数放入任务队列中等待主线程空闲的时候,被读取到栈内等待执行。
执行栈在执行完同步任务后,查看执行栈是否为空,如果为空,就会去执行宏任务,每次宏任务执行完后,检查微任务队列是否为空,如果不为空按照先进先出的规则执行完所有微任务后,设置微任务队列为null,然后继续执行宏任务,如此循环。
- 闭包:
在一个函数里面声明另一个函数,那么这个内部的函数就叫做闭包函数。闭包的产生,一个条件是闭包函数,另一个就是在内部函数中使用外部函数声明的参数和变量。闭包存在的意义,是让外部访问函数内部变量成为可能,就是因为闭包产生之后,会创建一个私有的作用域来保存内部函数中使用到的参数和变量。所以闭包的一个使用场景就是作为函数返回体,这样外部就可以通过闭包函数来获取到函数内部的参数和变量。每个闭包里面的环境都是独立的,互不干扰,所以闭包会发生内存泄露,需要谨慎使用。
例题:
1 | for(var i = 0; i<5;i++){ |
- 闭包的this是什么:
this是在运行时基于函数的执行环境来绑定的,闭包不属于任何对象,它不是一个对象的方法,那么它的this就是指向window对象。可以使用对象冒充强制改变this的指向,将this赋值给一个变量,闭包访问这个变量。(如下)
1 | let obj = { |
- 原型与原型链:
当我们声明一个函数的时候,会在函数内部自动创建一个原型对象,就是prototype,它是一个空的Object对象,我们可以在这个对象中添加属性,当我们使用new关键字创建一个新的对象时,会自动创建一个__proto__,它会指向构造函数的原型对象,这就是原型链。
当我们在这个对象中查找一个属性或方法时,会先在自己的私有属性中进行查找,如果找不到会顺着__proto__原型链进行查找,直到null都没有找到则报错。
- JS怎么控制一次加载一张图片,加载完再加载下一张?
1 | <script type = "text/javascript"> |
- call/apply/bind函数的区别:
call 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。 注意:该方法的作用和 apply() 方法类似,只有一个区别,就是 call()方法接受的是若干个 参数的列表,而 apply()方法接受的是一个包含多个参数的数组 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数。 注意:call()方法的作用和 apply() 方法类似,区别就是 call()方法接受的是参数列表,而 apply() 方法接受的是一个参数数组 。
bind()方法创建一个新的函数,当这个新的函数被调用时,其 this 置为提供的值,其参数 列表前几项,置为创建时指定的参数序列。
- 使用Promise实现限制并发请求函数:
所谓并发请求,即有待执行任务100个,限制每次只能发出num个。即同一时刻最多有num个正在发送的请求。
每当正在执行的任务之中有一个请求完成,则从待请求的接口中再取出一个发出。保证当前并发度仍旧为num。
直至最终请求完成。
设计思路:
简单思路如下:
1、创建一个控制器类,包含添加任务队列函数与执行函数
2、在add方法中使用sum来记录目前并发数,当sum小于最大并发数时,可以运行执行函数
3、在run方法中每次从队列中拿出一个任务执行,用promise异步执行,只有当前promise执行后才继续下一个任务执行
4、由于promise是异步的,sum的改变是在同步代码中,所以每次使用一个n来存放当前并发数
1 | //声明一个控制类,num为传入最大并发数 |
结果如下:

- 讲一下数组的方法,哪些会改变原数组哪些不会?
fill方法:用一个固定值填充一个数组中从起始索引到终止索引的全部元素,不包括最终索引,会改变原数组。
filter方法:创建一个新的数组,其包含通过所提供函数实现的测试的所有元素,不会改变原数组。
find方法:返回数组中满足提供的测试函数的第一个元素的值,不会改变原数组。
flat方法:按照一个深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回,不会改变原数组。
forEach方法:对数组中的每个元素执行一次给定的函数,会改变原数组
map方法:创建一个新数组,其结果是该数组中的每个元素是调用一个提供的函数后的返回值,不会改变原数组。
reduce方法:对数中的每个元素执行一个提供的reduce函数,将其结果汇总为单个返回,不会改变原数组。
some方法:测试数组中是不是至少有一个元素通过了被提供的函数,返回一个布尔值,不会改变原数组。
sort方法:对数组元素进行排序,并返回数组,默认排序时将元素转换为字符串,比较他们的UTF-16代码单元值,会改变原数组。
splice方法:通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组的形式返回被修改的内容,会改变原数组。第一个参数是指定修改的位置,第二个参数是要移除的参数个数,第三以及以后的参数是要添加进数组中的元素。
- 如何实现 ajax 请求,假如我有多个请求,我需要让这些 ajax 请求按照某种顺序一次执行,有什么办法呢?如何处理 ajax 跨域
我们可以将多个ajax请求封装进一个数组中,然后使用jQeury的deferred对象,它有一个done和fail函数分别接收的是请求成功执行与失败的回调,当成功执行时我们可以将数组的第一个元素弹出,然后递归执行发送函数,失败的话可以设置重新发送或者将数组第一个元素弹出继续发送下一个请求,最后返回一个promise对象。
- 说一下promise:
promise是es6用来进行异步编程,解决回调地狱的新技术,本质上promise是一个构造函数,初始状态pending。我们可以通过指定执行器函数来改变promise的状态,resolve就是改为resolved状态,reject就是改为rejected状态。我们可以在then函数中指定成功或失败的回调函数来获取成功的value或失败的reason,这就是promise的基本使用流程。
promise可以用于封装基于定时器的异步,也可以封装ajax异步请求。由于promise.then会返回一个promise对象所以可以实现链式调用,有效解决回调地狱问题。
关于事件循环:promise的执行器函数会在promise内部同步执行,回调函数是异步执行,会被放在微任务队列中。
关于API:
1 | promise.prototype.catch():是then的语法糖,相当于then(undefined,onrejected) |
关于then返回的promise状态:
如果抛出异常,新promise对象状态变为rejected,reason为抛出的异常
如果返回的是非promise值,新promise对象变为resolved,value为返回的值
如果返回的是一个promise,则新的promise结果是此promise的结果
关于中断promise链:
当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数,可以在回调函数中返回一个pending状态的promise对象。
- 实现动画有哪些方法:
1、使用transition
2、使用animation和@keyframes
3、使用js定时器加css改变
4、使用js定时器加canvas
5、使用requestAnimationFrame
- 使用过eslint好处:
eslint是js检查代码的工具,由于js语言是弱类型的所以有时候在开发过程中会有一些坑出现,比如==会涉及到弱类型转换,而eslint中是禁止使用==的。使用eslint虽然限制了语言的灵活性但是可以避免低级bug,提示删除多余代码、形成统一的团队代码风格。
- es6类的概念、继承的特点以及实现:
es6中的类是构造函数的另一种写法,它使对象原型的写法更加清晰,更像面向对象编程的写法。
使用class类声明一个全新的类,在类中有一个constructor函数,定义在这个函数里面的属性和方法,会在用new创建一个对象的时候赋值给对象。在类的其他地方声明的属性和方法会被声明在构造函数的原型对象上,供所有实例对象共同使用。
在类中可以用static声明静态方法,静态方法直接通过构造函数就可以使用,不能通过实例对象调用,否则会报错。
类的继承:使用extends关键字实现子类对父类的继承,可以通过super方法访问调用父类上的函数,也可以在子类中对父类的函数进行重写。
- 数据类型检测方式有哪些:
typeof:其中数组、对象和null会被判断为object
instanceof:正确判断对象的类型,依据是在其原型链中能否找到该类型的原型
constructor:
1、判断数据的类型 2、判断对象实例通过constructor对象访问它的构造函数,如果创建一个对象来改变他的原型则不能用来判断数据类型
1 | true.constructor === Boolean //true |
Object.prototype.toString.call():
1 | console.log(Object.prototype.toString.call([])) //[Object Array] |
为什么0.1+0.2 !== 0.3:
计算机是通过二进制存储数据的所以计算机计算的实际上是两个数的二进制的和,但是0.1和0.2的二进制数都是循环数,在JS中只有number一种数据类型,使用64位固定长度来表示,就是标准的双精度浮点数,在二进制科学表示法中小数部分最多只能保存52位,再加上表示符号的数,其实就是保留53位有效数字,剩余的舍去,得到的数转换为十进制就是0.30000000000000004
typeof NaN的结果是什么:
是number,NaN是一个特殊值,指不是一个数字
- ||和&&操作符返回值:
对于||来说如果条件是true会返回第一个操作数的值,如果是false就返回第二个的值
对于&&来说如果条件是true就返回第二个操作数的值,如果是false就返回第一个操作数的值
- 什么是JS中的包装类型:
在JS中基本数据类型是没有属性和方法的,但是为了便于操作基本类型的值在调用基本类型的属性或者方法时JS会在后台隐式的将基本类型的值转换为对象
- 如何提取高度嵌套的对象中的指定属性:
逐层解构
在解构出来的变量右侧通过冒号+{目标属性名}这种形式来进一步解构它:
1 | const school = { |
- rest参数的理解:
扩展运算符被用在函数形参上可以把一个分离的参数序列整合成一个数组:
1 | function mutiple(...args) { |
用于获取函数的多余参数或者处理函数参数个数不确定的情况。
- map与object的区别:
map和object对象都是用来保存键值对的,区别如下:
map默认情况下不包含任何键,只包含显式插入的键,即用set方法插入;object有一个原型,原型链上的键名可能与自己在对象上设置的键名起冲突
键的类型:map的键可以是任意值;object的键必须是string或者symbol
键的顺序:map的key是有序的,因此在迭代时可以以插入的顺序返回键值;object的键是无序的
size:map上的键值对个数可以通过size轻松获取,object只能手动计算
迭代:map可以直接迭代;但是object需要以某个方式获取它的键然后才能够迭代
性能:map在频繁增删键值对的场景下表现更好。
- 对JSON的理解:
JSON是一种基于文本的轻量级的数据交换格式,它可以被任何编程语言读取和作为数据格式来传递
在项目开发中使用JSON作为前后端数据交换的方式,在前端将一个符合JSON格式的数据结构序列化为JSON字符串,然后将它传递到后端,后端通过JSON格式的字符串解析后生成对应的数据结构。
JSON是基于JS的,JSON中属性值不能为函数,不能出现NaN这样的属性。
JSON.stringfy:传入一个JSON格式的数据结构将其转换为一个JSON字符串。
JSON.parse:将JSON格式的字符串转换为一个js数据结构
- 说说jsonp及其优缺点:
jsonp是JSON with padding的意思,它允许在服务器端集成script tags返回到客户端,通过javascript callback的形式实现跨域访问。跨域访问的原理是script标签的src属性不受同源策略所约束,所以可以获取任何服务器上脚本并执行。
jsonp简单的实现模式就是在客户端创建一个回调函数,然后在远程服务器上调用这个函数并且将json数据作为参数传递,完成回调。我们也可以通过动态创建script标签的形式来灵活调用远程服务。
1 | //客户端 |
优点:允许跨域,兼容性好。
缺点:只支持get请求不支持其他类型请求;jsonp在调用失败后不会返回各种状态码;安全心问题,存在注入漏洞。
- JS中类数组对象的理解:
一个拥有length属性和若干索引属性的对象就可以称为类数组对象。类数组对象不能调用数组的方法。
常见的将类数组对象转换为数组的方法:
1、通过call调用数组的slice方法:
1 | Array.prototype.slice.call(arguments) |
2、通过call调用数组的splice方法:
1 | Array.prototype.splice.call(arguments,0) |
3、通过apply调用数组的concat方法:
1 | Array.prototype.concat.apply([],arguments) |
4、通过Array.from来实现
1 | Array.from(arguments) |
- 常见的位运算符,其计算规则是什么:
&:与:两个位都为1时结果才为1
用途:判断奇偶;清零
|:或:只要有一个数是1结果就是1
^:异或:两个位相同为0,相异为1
~:取反:0变1,1变0
<<:左移
>>:右移
原码:一个数的二进制数
反码:正数反码与原码相同,负数反码除符号位,按位取反
补码:正数补码与原码相同,负数反码除符号位,按位取反最后加1
- JS为什么要进行变量提升:
JS在拿到一个函数或者变量的时候会有两步操作,即解析和执行:
在解析阶段,JS会检查语法并对函数进行预编译。解析的时候会创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量赋值为undefined,函数先声明好可使用。在一个函数执行之前也会创建一个函数执行上下文环境与全局执行上下文类似但是多出this、arguments和函数的参数。
在执行阶段,按照代码的顺序依次执行。
原因:
1、提高性能:在JS代码执行之前语法检查与预编译只进行一次,否则每次执行代码前都需要重新解析一遍该变量或函数。
2、容错性更好
- 常见的DOM操作:
1 | //DOM节点的获取: |
- use strict是什么意思,使用它有什么区别:
表示使用严格模式,使JS在更严格的条件下运行,目的如下:
消除JS语法的的不合理不严谨之处;消除代码运行的不安全之处,保证代码运行的安全;提高编译效率,增加运行速度。
严格模式的限制:
1、变量必须声明后再使用
2、禁止this指向全局对象
3、不能对只读属性赋值,否则报错
4、不能删除不可删除的属性,否则报错
5、不能使用with语句(with语句用于扩展一个语句的作用域链,将某个对象添加到作用域链的顶部,该对象会在代码执行后移除,存在语义不明以及一些兼容性问题的弊端)
6、eval不会在它的外层作用域引入变量(eval语句会将传入的字符串当做 JavaScript 代码进行执行。)
………
- 强类型语言与弱类型原因的区别:
强类型语言:要求变量的使用要严格符合定义,所有变量必须先定义再使用,一旦被指定为某个数据类型如果不经过强制转换就永远是这个数据类型。
弱类型语言:可以理解为变量类型是可以被忽略的语言。
- 解释性语言与编译型语言的区别:
解释性语言:每次运行都需要将源代码解析成机器码并执行,效率低;只要平台提供相应的解释器就可以运行源代码,方便源程序移植;如JS和python
编译型语言:一次性编译成平台相关的机器语言文件,所以只需编译一次,运行时脱离开发环境,运行效率高;与特定平台相关,一般无法移植到其他平台,如C++
- ajax和axios和fetch的区别:
Ajax是一种在无需加载整个页面的情况下能够更新部分页面的技术。通过在后台与服务器进行少量数据交换,ajax可以使网页实现异步更新。AJAX的运行时基于浏览器的ajax引擎,使用xmlhttp协议完成的。AJAX的缺陷是对浏览器后退机制的破坏(更新数据后不能通过后退按钮取消前一次操作);存在安全问题,暴露比以前更多的数据与服务器逻辑;对搜索引擎的支持比较弱;一些手持设备还不能很好的支持AJAX。
Fetch是一种http数请求的方式,是基于promise设计的,不是ajax的进一步封装,而是原生JS,没有使用XMLHttpRequest对象。优点:基于标准promise实现,支持async/await,更加底层,提供的API丰富(request,response);缺点是只对网络请求报错,对于400/500都当做成功的请求,不会reject,只有网络请求不能完成时fetch才会被reject;fetch默认不会带cookie,需要添加配置项(fetch(url,{credentials:”include”]}));fetch不支持abort与超时控制,使用setTimeout与promise.reject实现超时控制并不能阻止请求过程继续在后台运行造成流量的浪费;fetch没有办法原生监控请求的进度,而XHR可以;
Axios是一个基于promise的网络请求库,浏览器发起XMLHttpRequest请求;node端发起http请求,支持promiseAPI,对请求和返回进行转换;自动转换json数据;客户端支持抵御XSRF攻击。
- async与await原理:
async/await的用户就是用同步方式执行异步操作。
async与await其实是generator函数的语法糖,并对generator函数进行了改进。相比生成器函数,async自带执行器,不需要手动执行next方法;async函数的await命令后面,既可以是promise对象也可以是原始数据类型,它都会自动转成成功状态的promise对象。而且async函数的返回值是promise对象,可以使用then方法进行调用。
1 | //先请求完接口1后用接口1返回的数据当做接口2的请求参数 |
由于async隐式返回promise作为结果,那么可以理解为在await命令之后,await会产生一个微任务,相当于是promise.then(),但还是这个微任务产生的时机是在await执行之后,直接跳出async函数执行其他代码,这就是协程的运作。其他代码执行完毕后在回到async函数执行剩下的代码,注册到微任务队列中。
- commonjs(require)与es6(export)模块的区别:
commonjs模块加载过程是同步阻塞性加载,在缓存方面,commonjs在模块代码被运行前就已经以模块绝对路径为key,module对象为value写入cache,同一个模块被多次require时只会执行一次,重复的require得到的是相同的exports引用。
ES6模块时为了不再使用闭包与函数封装的方式进行模块化提供的模块化功能,使用export暴露、import引入。在node使用es6模块需要将js文件后缀改成mjs,或者配置package.json的type为module,告诉node以esmodule的形式加载模块。
es6模块加载过程:
1、es6模块会在程序开始前先根据模块的关系查找到所有的模块生成一个无环关系图然后将所有模块实例创建好
2、在内存中腾出空间给即将使用export暴露的内容,然后使import和export指向内存中的这些空间(连接)
3、基于第一步生成的无环图进行深度优先后遍历填值,如果此时访问到了尚未初始化完成的空间会抛出异常。
commonjs可以在运行时使用变量进行require但是import不可以因为es6模块会先解析所有模块在执行代码;
require会将完成的exports对象引入,但是import可以只引入部分必要的内容,这就是为什么使用tree-shaking时必须使用es6模块写法;
import另一个模块没有export的变量会在代码执行前报错,但是使用commonjs是在模块运行时才报错。
- 为什么浏览器端不建议使用commonjs:
因为在浏览器端我们如果封装一个模块来发送网络请求,由于网络请求会消耗一定时间所以一般使用异步加载的方式发送网络请求。但是当使用commonjs,由于commonjs加载模块是同步执行代码的,此时请求的等待时间可能随着网速快慢以及请求数据大小的影响,会使浏览器处于假死状态。
- documentfragment(文档片段接口):
documentfragment是一个类似于document的文档对象,它可以向document一样存储由节点组成的文档结构,但是它最大的特点就是它的变化不会触发dom树的重新渲染,所以我们可以把添加删除节点的所有操作使用documentfragment 保存起来,最后一次性插入到文档中,这样文档只会发生一次重新渲染。
使用:
1 | //创建一个frame对象 |
- MutationObserver:
MutationObserver的出现是为了取代废弃的MutationEvent,因为后者有严重的性能问题。MutationObserver可以用于观察指定dom元素的属性、子节点以及文本的变化,并且可以在dom元素被修改时异步执行回调。
我们可以通过创建一个MutationObserver实例,然后使用它的方法来指定对哪些元素以及元素哪些属性进行观察,通过mutationRecord和mutationObserve参数可以获取到观察的配置。每一个MutationObserver实例对象会把每一次变化的信息保存在MutationRecord实例中,然后添加到记录队列,通过数组进行保存,注册的回调函数只会执行一次。
MutationObserver对象有三个方法可以调用:
observer():通过该方法可以传入要观察的dom元素以及对目标节点的观察范围。该方法可以关联MutationObserver对象与dom元素,可以重复使用来复用一个MutationObserver对象观察多个不同的目标节点。
disconnect():使用该方法可以提前终止执行回调函数。
takeRecords():可以清空记录队列,取出并返回包含其中的所有MutationRecord实例的数组。
- 使用promise需要执行并行和独立的异步操作并收集所有结果(包括成功与失败)怎么办:
使用Promise.allSettled()方法可以并行执行独立的异步操作,并将异步操作的结果以一个数组返回,如果成功则是{status:”fulfilled”,value:value},失败则是{status:”rejected”,reason:reason}。可以使用then方法,也可以使用async/await提取所有的promise状态。
Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled。它的监听函数接收到的参数是数组results。
- 说说图片懒加载:
图片懒加载就是让一些不可视的图片不去加载,避免一次性加载太多图片导致请求阻塞,可以提高网网站的加载速度,提高用户体验。
实现:
1、首先对需要懒加载的图片标签自定义一个属性用来存放真正的图片或者原图地址,并且定义一个类名,表示该图片是需要懒加载的。
2、根据类名为图片设置一个未加载前的背景图。
3、在js中获取到所有需要懒加载的图片的元素集合,判断是否在可视区域,如在可视区域则将元素的src属性设置为真正的图片地址
判断是否出现在可视区域可以通过以下方法:
offsetTop表示元素的上边框至包含元素的上内边框之间的距离,作用对象是图片元素本身;scrollTop表示元素的滚动位置,clientHeight表示屏幕可视区域的高度,作用对象是文档对象。
所以当offsetTop < scrollTop + clientHeight的时候就是图片出现在视图中的时候。
1 | function isInViewPortOfOne (el) { |
getBoundingClientRect:返回值是一个对象,拥有left、top、right等属性为相对于视口的位置。通过获取元素的getBoundingClientRect属性的top值和页面的clientHeight进行对比,如果top值小于clientHeight,则说明元素出现在可视区域了

1 | class LazyImage { |
IntersectionObserver:可以判断两个元素是否重叠,因为不用进行事件监听所以性能会好很多。
1 | //创建观察者 |
node与浏览器事件循环区别:
1、浏览器事件循环:
浏览器由很多模块组成,有解析html和css的模块,有解析js的模块,有定时器模块,有ajax模块。
(1)宏任务:script标签中的整体代码、setTimeout、setInterval、I/O、UI渲染
(2)微任务队列:process.nextTick、Promise.then()、MutationObserver
事件循环:一开始整个脚本作为一个宏任务执行,执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列。当前宏任务执行完毕后立即执行微任务队列中的所有微任务。当前宏任务执行完毕开始检查渲染,然后GUI线程接管渲染(浏览器会在两个宏任务交接期间,对页面进行重新渲染)。渲染完毕后JS线程继续接管,开始下一个宏任务,依此循环,直到宏任务与微任务队列都为空。
2、node事件循环:
node事件循环与js一样存在宏任务与微任务,先执行主线程的任务接着再执行微任务。具体的执行顺序如下:
(1)宏任务队列:
timer queue:setTimeout、setInterval
poll queue:io事件
check queue:setImmediate
close queue:close事件
(2)微任务队列:
next tick queue:process.nextTick
other tick queue:promise的then函数、queueMicrotask
浏览器与node事件循环的区别:
1、在浏览器事件循环中每执行完毕一个宏任务就要检查执行微任务,而node事件循环则是在上一阶段执行完,下一阶段开始前执行微任务队列中的任务,也就是说node的微任务是在两个阶段之间执行的。
2、在浏览器事件循环中,process.nextTick与其他微任务优先级是一样的,在node中它的优先级要高于其他微任务。
- map与set的区别:
map是一个存储键值对的集合,与Object对象不同的是map存储的键可以是任意数据类型,而object对象的键只能是字符串或者是symbol,map对象可以通过set、get、delete等方法对键值对进行增删改查。(set,size,get方法等)
set是一个存储元素的集合,它只能存储不重复的值,与map不同的是它没有get方法,因为它存储的不是键值对,也可以使用set对数组去重,将数组作为参数新建一个set对象,然后对set对象使用扩展运算符即可。(add,has,delete方法等)
- 说说js垃圾回收机制:
标记清除法:当代码执行在一个环境中时(比如一个函数的时候),每声明一个变量就会对变量做一个标记,当离开当前执行环境进入另一执行环境的时候,等到垃圾回收执行时,会清除没有标记的变量,解决了引用计数无法清除循环引用变量的问题。
引用计数法:用一个值跟踪记录每个值被使用的次数,当声明一个变量并将一个引用类型赋值给该变量时,该值加1(指的是引用数据类型),当该变量被重新赋值时该值减1,当该值为0的时候说明没有变量在使用,会在垃圾回收的时候尽心回收。
- 常见的内存泄露场景:
意外的全局变量:当我们在全局对象中window声明一个变量使用完毕后没有使用null进行处理或者重新定义的时候,会出现内存泄露。可以使用use strict开启严格模式,这样当出现意外的全局变量时会报错。
1 | function fn(){ |
闭包:使用闭包我们可以从外部访问到内部函数的属性,会产生一个独立的作用域,过度使用闭包会导致内存占用过多。
计时器:使用完计时器完后需要手动清除计时器,否则可能由于其中引用的变量或函数被认为是需要的而不进行回收,出现内存泄露。
脱离DOM的使用 :当我们在js中操作使用一个变量来操作DOM的时候,如果在后面代码中删除DOM的时候没有对该变量进行清除的话会产生内存泄露。将变量设置为null。
- 实现水印(场景题):
1、使用js在页面上充满透明度较低的信息盒子。在一个大盒子中循环生成小的水印,为每一个水印展示要显示的信息。
2、在页面上覆盖一个固定定位的盒子,然后创建一个canvas画布,绘制出一个水印区域,将画布通过toDataUrl方法输出为一个图片,将这个图片设置为盒子的背景图,通过background-repeat:repeat实现充满屏幕的效果。也可以使用svg。
为了避免水印被恶意删除,可以通过MutationObserver观察节点变化,因为该API只能监听子节点的变化,所以可以通过监听水印的父节点,当有人对水印进行删除操作时,调用方法重新添加水印。
- 说说继承:
继承就是通过某种方式让对象可以访问到另一个对象中的属性与方法,这种方法称为继承。
1、原型链继承:通过实例化一个新的函数,子类的原型指向父类的实例,子类就可以调用父类原型对象上的私有属性和方法,本质上值重写了原型对象。
1 | function Parent(){ |
原型链继承的优点是简单易实现,父类新增原型方法与属性子类都能访问;缺点就是当改变父类构造函数上的属性时,Child的不同实例会相互影响。
2、借用构造函数继承:就是利用call或apply函数,在子类型构造函数的内部调用超类型构造函数。
1 | function Parent(name){ |
原理:通过call实现的继承本质上是改变了this的指向,让父类里面的this指向子类上下文,这样父类里面通过this设置的属性或者方法会被写到子类上。
缺点:只能继承父类构造函数上的属性与方法,不能继承父类原型上的属性与方法。
3、组合模式继承:是将原型链继承与借助构造函数继承的技术组合到一块的一种继承模式。
1 | function Parent(){ |
原理:使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性,这样既可以把方法定义在原型上实现重用,也可以让每个实例都有自己的属性。
缺点:父类构造函数会在Parent.call(this)和new Parent()执行两次,从而使得父类构造函数上的属性在子类自身和子类原型都存在,会导致子类本身删除本身相关的属性后在其原型依然存在。
4、共享原型继承:就是子类与父类共享一个原型。
1 | function Parent(){} |
共享原型继承的优点是简单易实现,缺点是只能继承父类原型的属性与方法,不能继承构造函数属性方法。
5、原型式继承:用于基于当前已有对象创建新对象。
1 | //es5的Object.create()方法规范类原型式继承 |
6、寄生式继承:是原型式继承的加强版,它结合原型式继承与工厂模式,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。
1 | function createAnother(origin){ |
7、寄生组合式继承:是寄生式继承的加强版,是伪类避免组合继承中无法避免要调用两次父类构造函数的最佳方案。
1 | //基本写法 |
本质是子类的原型继承自父类的原型,申明一个用于继承原型的inheritPrototype方法,通过这个方法我们能够将子类的原型指向超类的原型从而避免超类二次实例化。
8、class继承:通过class关键字定义类,子类可以通过extends继承父类
1 | class Parent { |
- 浏览器事件模型:
W3C将事件发生定义为三个阶段:捕获阶段、目标阶段与冒泡阶段
捕获阶段:首先window会捕获到事件之后document、documentElement、body会捕获到,再之后就是在body中DOM元素一层一层的捕获事件,直到到达事件的目标节点,捕获阶段的主要任务是建立传播路径。
目标阶段:真正点击的元素事件发生了两次,因为既在捕获阶段绑定了事件,又在冒泡阶段绑定了事件
冒泡阶段:事件在目标元素上触发后,并不在这个元素上终止,它会随着DOM树 一层层往上冒泡,回溯到根节点。

在事件回调函数里加上true则为事件捕获,会从根接电脑向下逐级触发各个节点直到目标节点触发事件处理函数。

可以使用e.stopPropagation()阻止事件传播,既可以是冒泡阶段也可以是捕获阶段;可以使用e.preventDefault()阻止事件的默认行为发生。
- 深度优先输出html上的所有标签:
1 | var nodeList = [] |
- 广度优先输出html上的所有标签:
1 | //递归版本 |
- for跟forEach循环的区别:
1、for循环支持break跳出循环但是forEach不允许
2、for循环可以控制循环起点,但是forEach默认从0开始
3、for循环中支持修改索引,但是forEach做不到
3、forEach删除自身元素index不会被重置
- 获取原型的方法:
1 | 1、p.__proto__ |
- typeof NaN 的结果是什么:
NaN 意指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value, 有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。 typeof NaN; // “number” NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不 成立)的值。而 NaN != NaN 为 true。
- || 和 && 操作符的返回值是什么:
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先进行 ToBoolean 强制 类型转换,然后再执行条件判断。 对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回 第二个操作数的值。 && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一 个操作数的值。 || 和 && 返回它们其中一个操作数的值,而非条件判断的结果。
- [“1”, “2”, “3”].map(parseInt) 答案是多少:
parseInt() 函数能解析一个字符串,并返回一个整数,需要两个参数 (val, radix),其中 radix 表示要解析的数字的基数。(该值介于 2 ~ 36 之间,并且字符串中的数字不能大于 radix 才能正确返回数字结果值)。此处 map 传了 3 个参数 (element, index, array), 默认第三个参数被忽略掉,因此三次传入的参数分别为 “1-0”, “2-1”, “3-2” 因为字符串的值不能大于基数,因此后面两次调用均失败,返回 NaN ,第一次基数为 0 ,按十进制解析返回 1。
详细资料可以参考:[为什么 “1”, “2”, “3”].map(parseInt) 返回 [1,NaN,NaN]?_迷渡的博客-CSDN博客
- undefined 与 undeclared 的区别?
已在作用域中声明但还没有赋值的变量,是 undefined 的。相反,还没有在作用域中声明 过的变量,是 undeclared 的。对于 undeclared 变量的引用,浏览器会报引用错误,如 ReferenceError: b is not defined 。但是我们可以使用 typeof 的安全防范机制来避免 报错,因为对于 undeclared(或者 not defined )变量,typeof 会返回 “undefined”。
- js 获取原型的方法:
- p.
__proto` - p.constructor.prototype
- Object.getPrototypeOf(p)
- {} 和 [] 的 valueOf 和 toString 的结果:
{ } 的 valueOf 结果为 {} ,toString 的结果为 “[object Object]”
[ ] 的 valueOf 结果为 [] ,toString 的结果为 “”
- Javascript 中,有一个函数,执行时对象查找时,永远不会去查找原型, 这个函数是?
hasOwnProperty 所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。这个方法可以用来检测一个对 象是否含有特定的自身属性,和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
- 谈一谈HTTP的缓存机制?
浏览器的缓存机制指的是通过在一段时间内保留已接收到的 web 资源的一个副本,如果在资源的有效时间内,发起了对这个资源的再一次请求,那么浏览器会直接使用缓存的副本,而不 是向服务器发起请求。使用 web 缓存可以有效地提高页面的打开速度,减少不必要的网络带宽 的消耗。web 资源的缓存策略一般由服务器来指定,可以分为两种,分别是强缓存策略和协商 缓存策略。
使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起 请求。强缓存策略可以通过两种方式来设置,分别是 http 头信息中的 Expires 属性和 Cache-Control 属性。服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。 在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间, 它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或 者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。Expires 是 http1.0 中的方式,因为它的一些缺点,在 http 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,常用的比 如我们可以通过设置 max-age 来指定资源能够被缓存的时间的大小,这是一个相对的时间,它 会根据这个时间的大小和资源第一次请求时的时间来计算出资源过期的时间,因此相对于 Expires 来说,这种方式更加有效一些。常用的还有比如 private ,用来规定资源只能被客户 端缓存,不能够代理服务器所缓存。还有如 no-store ,用来指定资源不能够被缓存,no-cache 代表该资源能够被缓存,但是立即失效,每次都需要向服务器发起请求。一般来说只需要设置其 中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires 。
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状 态,让浏览器使用本地的缓存副本。 如果资源发生了修改,则返回修改后的资源。协商缓存也可以通过两种方式来设置,分别是 http 头信息中的 Etag 和 Last-Modified 属性。 服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏 览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一 次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资 源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么 返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使 用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某 些文件在 1 秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改 变,这样会造成缓存命中的不准确。因为 Last-Modified 的这种可能发生的不准确性,http 中 提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下 一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上 次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进 行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。 当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。
使用协商缓存 的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保 持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商 缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际 的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判 断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求, 使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本, 如果协商缓存不命中,则浏览器返回最新的资源给浏览器。
- 说说requestAnimationFrame:
1、了解屏幕刷新频率
即图像在屏幕上更新的速度,也就是屏幕上的图像每秒钟出现的次数,单位是赫兹。当你对着电脑屏幕什么也不做的时候显示器也会以每秒60次的频率不断更新屏幕上的图像,之所以我们感受不到是因为人眼的视觉停留效应。
2、动画原理
动画本质就是让人眼看到图像被刷新而引起变化的视觉效果,这个变化要连贯、平滑,就可以在屏幕每一次刷新前移动图像位置,这样就会看到图像在流畅的移动,这就是视觉上形成的动画。
3、requestAnimationFrame
它是由系统来决定回调函数的执行时机,具体一点就是如果屏幕刷新频率是60Hz,那么回调函数就每16.7ms被执行一次,它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会出现丢帧,也不会出现动画卡顿的情况。
4、使用
1 | let progress = 0 |
- async/await对比promise的优势:
- 代码读起来更同步,promise的链式调用也有一定的阅读负担
- promise传递中间值非常麻烦,而async/await几乎是同步的写法,非常优雅
- 错误处理友好,可以使用try/catch进行错误捕获
- websocket的使用方法:
1 | let ws = new WebSocket('ws://localhost://999') |
- JS为什么异步,并发怎么办:
作为浏览器脚本语言,JS的主要用途就是与用户互动,以及操作DOM,这些用途决定了它只能是单线程,否则会带来复杂的同步问题。倘若多线程,一个线程在DOM节点添加内容,另一个删除内容,那该听谁的呢?
JS异步是因为单线程语言在执行一些比较耗时的操作时,为了不阻塞后面代码的执行往往需要执行异步的操作。
使用Promise可以实现并发控制问题。
- 两个网站的数据如何同步(例如购物车,浏览记录),cookie和session如何协同:
1、session方式
将购物车信息保存在服务端,可以保存1M信息。
但是对于大型网站会占有过多的服务器内存资源,造成服务器压力过大,session保存的信息会在用户退出登录后丢失,下次用户登录只能重新选择
2、cookie方式
购物车存储在客户端,不占用服务器资源可以持久化存储。
但是cookie有大小限制4k,而且不够安全。对于大型的电子商务网站需要对用户购买行为进行分析,保存在cookie中不能进行分析。
3、数据库存储
可以持久化存储与分析用户行为。
但是网站速度会变慢,成本与维护增加。
个人思路:使用SSO(单站点登录)实现两个系统的免登陆跳转,然后使用session存储用户信息,将购物车,历史记录等信息存储在后端数据库,跳转之后通过用户信息查询数据库获取后实现同步,数据存放在cookie中。
- setTimeout实际延迟时间(最小延迟:4ms):
由于js的事件循环机制,setTimeout只是将事件插入到任务队列中,必须等到当前代码执行完,主线程才会去执行它指定的回调函数,等到执行的实际时间=等待执行栈为空的时间+设定的延迟回调时间。
解决方法:
方法一
以后端返回服务器时间为准,通过定时向服务器发送请求,获取最新的时间时间偏差,以此来校准倒计时时间。但是这种方法消耗服务端资源较大,会存在并发问题;而且接口请求返回时间本身也存在时间偏差,增加了问题的不可控因素与复杂度
方法二
思路:用递归的方法执行倒计时,在每次递归调用setTimeout回调的时候,计算出时间偏差,在下一次执行setTimeout时,把原设定的延迟回调时间减去时间偏差即可。
1 | const interval = 1000 // 设定倒计时规则为每秒倒计时 |
- Eval 是做什么的:
1、eval()的作用 把字符串参数解析成 JS 代码并运行,并返回执行的结果;
例如:
1.1)eval(“2+3”);//执行加运算,并返回运算值。
1.2)eval(“varage=10”);//声明一个 age 变量
2、eval 的作用域在它所有的范围内容有效
示例 1:
1 | functiona(){ eval("var x=1"); //等效于 var x=1; console.log(x); //输出 1 } |
示例 2:
1 | functiona(){ window.eval("var x=1"); // 等效于 window.x=1;定义了全局变量 console.log(x); //输出 1 } |
3、注意事项 应该避免使用 eval,不安全,非常耗性能(2 次,一次解析成 js 语句,一次执行) 在 IE8 及 IE8 一下的版本就不支持了
4、其它作用 由 JSON 字符串转换为 JSON 对象的时候可以用 eval
4.1)varjson=”{name:’Mr.CAO’,age:30}”;
4.2)varjsonObj=eval(“(“+json+”)”);
4.3)console.log(jsonObj);
- Promise 中 reject 和 catch 处理上有什么区别:
- reject 是用来抛出异常,catch 是用来处理异常
- reject 是 Promise 的方法,而 catch 是 Promise 实例的方法
- reject 后的东西,一定会进入 then 中的第二个回调,如果 then 中没有写第二个回调,则进入 catch
- 网络异常(比如断网),会直接进入 catch 而不会进入 then 的第二个回调
- 手写一个简单的promise
1 | class Promise { |
- export 与 export default 的区别:
- export与export default均可用于导出常量、函数、文件、模块等
- 在一个文件或模块中,export、import可以有多个,export default仅有一个
- 通过export方式导出,在导入时要加{ },export default则不需要,并可以起任意名称
(1) 输出单个值,使用export default
(2) 输出多个值,使用export
(3) export default与普通的export不要同时使用
- Blob、ArrayBuffer、File、FileReader和FormData的区别:
Blob、ArrayBuffrt、File都是数据,而FileReader是一种读取数据的工具,FormData可以看做是一个应用数据的场景。
1、Blob:是一个不可修改的二进制对象,就是对二进制数据做了一个封装,可以看到它的整体属性大小(size)、类型(type),但是不能了解到它的细节,有一个slice方法可以实现对文件的分割,arrayBuffer方法返回一个以二进制形式展示的promise,stream方法返回一个ReadableStream对象,text方法返回一个文本对象的promise。
1 | const blob = new Blob(array,options) |
应用场景:文件上传、分片上传、文件预览、文件下载
1 | <!-- 文件预览 --> |
2、ArrayBuffer:是一个二进制数据通用的固定长度容器,可以对其进行读写,但需要借助TypeArray或DataView。相对Blob更底层,就是一段纯粹的内存上的二进制数据,可以对任何一个字节进行单独的修改。
3、File:也是二进制对象,可以让JS访问文件信息,继承于Blob,它继承Blob的属性方法,同时也有自己特有的属性与方法。
产生file的场景:
(1)new File构造函数
(2)通过<input type="file" />获取文件
(3)自由拖放操作生成的DataTransfer对象
1 | const file = new File(array,name[,options]) |
使用File对象的场景:
(1)借助FileRender转换成data url(base64)、text等
(2)URL.createObjectURL()生成blob url在线预览
1 | <input type="file" onchange="handlechange(event)" /> |
1 | //XMLHttpRequest.send()上传 |
4、FileReader:一个转换工具,使用filrReader可以将Blob读取为不同的格式(text、data url(base64)、ArrayBuffer)。
5、FormData:一种数据格式,用于将表单数据编译成键值对以便用AJAX来发送数据
- 说说文件的上传与下载:
1、下载
(1)超链接下载
①给超链接加上download属性,需要有href属性
1 | <a href="./imgs/logo.png" download="test">直接下载图片</a> |
②下载服务器端的资源:
对于blob,我们可以使用window.URL.createObjectURL(file/blob)方法生成url赋值给超链接的href属性然后模拟点击下载。blob url就是将一个file或Bolb类型的对象转化为utf-16字符串,并保存在当前操作的document下,存储在内存中。
1 | function aDownload2() { |
对于base64数据,直接将base数据赋值给超链接的href属性然后模拟点击下载就可以。
1 | async function aDownload3() { |
对于ArrayBuffer需要先转换为blob或file对象然后再生成blob url。
1 | function aDownload1() { |
(2)showSaveFilePicker下载
这是一个新的API调用该方法会显示允许用户选择保存路径的文件选择器。
1 | async function download3(blob, filename) { |
(3)FileSaver.js下载:
这是在客户端保存文件的解决方案,非常适合在客户端上生成文件的web应用程序,只需要记住它的saveAs方法
1 | //安装 |
(4)Jszip压缩下载
可以让下载的文件转为zip格式。它本身不具备下载功能,只是提供了将文件压缩成zip的功能,下载还是需要借助上面的FileSaver.js
1 | //安装 |
(5)附件形式下载
我们可以通过设置Content-Disposition响应头来指示响应的内容以何种形式展示,是以内联的形式还是以附件的形式下载并保存到本地。filename用来设置下载的文件的文件名。
1 | Content-Disposition: inline |
2、上传
(1)单文件上传
使用FormData对象的append方法将input标签上传的file对象追加进去,然后使用post请求上传
(2)多文件上传
在input标签中添加multiple属性表示支持多文件上传,然后对file对象进行遍历依次追加进FormData对象中,最后使用post实现上传。
(3)文件夹上传
在input标签中添加webkitdirectory属性表示是文件夹上传(IE不支持)。
(4)Jszip压缩上传
将文件压缩成压缩包然后再上传到服务器,还是使用前面的jszip库。
(5)拖拽上传
使用拖拽API,通过DataTransfer对象的files属性来获取文件列表,然后在利用FormData进行上传。
(6)复制粘贴上传
通过navigator.clipboard来获取Clipboard对象,然后通过navigator.clipboard.read()获取内容,对于不兼容的需要通过e.clipboardData.items来访问剪贴板中的内容。
- ES5中模仿块级作用域:
使用立即执行函数:
1 | function outputNumbers(count){ |
此时i绑定在内部立即执行函数的变量对象上,立即执行函数执行完毕就会销毁,i也会随之销毁。根据闭包机制,内部的立即执行函数也可以访问到count。这样就实现了块级作用域——无论在立即执行函数中声明什么变量都不会影响外部变量的使用。
- 说说es5与es6的作用域:
es5:全局作用域与局部作用域(闭包,try…catch,立即执行函数)
es6:全局作用域、局部作用域、块级作用域
- 说说dom事件级别:
DOM0:只能定义一个事件click,多事件会互相覆盖
1级的DOM没有关于事件的更新
DOM2:有两个方法用来添加与移除事件的处理程序(addEventListner和removeEventListner),可以为元素添加多个事件处理程序,触发时会按照添加顺序依次调用
DOM3:与DOM2一样只是增加了几个事件类型,比如说键盘事件(keyup)、鼠标事件(mousedown)
- 说说es6的proxyAPI:
proxy可以用来定义对象基本操作的自定义行为,它是一个构造函数,我们可以使用它来创建一个proxy实例对象,传入的第一个参数是target就是要拦截的对象,第二个参数是一个配置对象,以函数为属性,各属性中的函数分别定义了在执行各种操作时代理的行为。比如get函数属性可以拦截对象属性的获取,set可以拦截对象属性的设置。我们可以使用Reflect来调用对象的默认行为。
1 | let obj = { |
proxy的功能非常类似于设计模式中的代理模式,常用功能如下:
1、拦截和监视外部对象的访问
2、降低函数或类的复杂度
3、在复杂操作前对操作进行校验或对所需资源进行管理
1 | //使用proxy保障数据类型的准确性 |
- 图片传给服务器有几种格式:
1、file:创建formData对象来完成file上传:
1 | <input type="file" id="imgfile" name="pic" multiple> |
2、Base64:通过创建FileReader来完成
1 | <input type="file" id="imgfile"> |
3、Blob流
以上三者都是可以相互转换的。
- 说说Web Worker:
Web Worker的作用,就是为JS创造多线程环境,允许主线程将一些任务分配给后者运行,最后再把结果返回给主线程。
Web Worker一旦新建成功,就会始终运行不会被主线程的活动打断,这样有利于随时响应主线程的通信。一旦使用完毕就应该关闭。
Web Worker有以下几个注意点:
1、同源限制:分配给该线程的脚本文件必须与主线程的脚本文件同源
2、DOM限制:该线程所在的全局对象与主线程不一样所以无法读取主线程所在页面的DOM对象,但是可以读取navigator与location对象
3、通信联系:主线程与Web Worker线程不在同一个上下文环境,所以他们通信必须通过消息完成
4、脚本限制:Web Worker线程不能执行alert方法与confirm方法,但是可以发送AJAX请求
5、文件限制:Web Worker线程无法读取本地文件,不能打开本地的文件系统,所加载的脚本必须来自网络
基本用法:
1. 主线程
主线程采用new命令,调用worker构造函数,新建一个Worker线程。
1 | var worker = new Worker('work.js'); |
Worker()构造函数的参数是一个脚本文件,该文件就是Worker线程索要执行的任务。由于Worker不能读取本地文件,所以这个脚本必须来自网络。如果下载不成功(比如404错误),Worker就会默默的失败。然后,主线程调用worker.postMessage()方法,向Worker发消息。
1 | worker.postMessage('Hello World'); |
worker.postMessage()方法的参数,就是主线程传给Worker的数据,它可以是各种数据类型,包括二进制数据。接着,主线程通过worker.onmessage指定监听函数,接收子线程发出来的消息。
1 | worker.onmessage = function(event) { |
Worker 完成任务以后,主线程就可以把它关掉
1 | worker.terminate(); |
2. Worker线程
Worker线程内部需要有一个监听函数,监听message事件。
1 | // 写法一: |
也可以根据主线程发过来的数据,worker线程可以调用不同的方法。
1 | self.addEventListener('message', function(e) { |
上述代码中,self.close()用于Worker内部关闭自身监听
3. Worker加载脚本
Worker内部如果需要加载其他脚本,有一个专门的方法importScripts()。
1 | importScripts('script1.js); |
4. 错误处理
主线程可以监听Worker是否发生错误,Worker会触发主线程的error事件。
1 | worker.onerror(function(event) { |
Worker 内部也可以监听error事件。
5. 关闭worker
1 | // 主线程 |
数据通信
前面提及,主线程和worker之间的通信可以是文本,也可以是对象也可以是二进制数据【File、Blob、ArrayBuffer等类型】,但是这种关系是拷贝关系即是传值而不是传地址,worker对通信内容的修改,不应该到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符发给worker,后者再将其还原。
1 | // 主线程 |
但是,拷贝方式发送二进制数据,会造成性能问题。比如,主线程向 Worker 发送一个 500MB 文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects。这使得主线程可以快速把数据交给 Worker,对于影像处理、声音处理、3D 运算等就非常方便了,不会产生性能负担。
如果要直接转移数据的控制权,就要使用下面的写法。
1 | // Transferable Objects 格式 |
同页面的Web Worker
通常请下,Worker载入的是一个单独的JavaScript脚本,但是也可以载入与主线程在同一个网页的代码。
1 |
|
上面是一段嵌入网页的脚本,注意必须指定script标签的type属性是一个浏览器不认识的值,上例是app/worker。然后,读取这一段嵌入页面的脚本,用 Worker 来处理。
1 | var blob = new Blob([document.querySelector('#worker').textContent]); |
上面代码中,先将嵌入网页的脚本代码,转成一个二进制对象,然后为这个二进制对象生成 URL,再让 Worker 加载这个 URL。这样就做到了,主线程和 Worker 的代码都在同一个网页上面。
事例:worker 线程完成轮询
有时,浏览器需要轮询服务器状态,以便第一时间得知状态改变。这个工作可以放在 Worker 里面。
1 | function createWorker(f) { |
上面代码中,Worker 每秒钟轮询一次数据,然后跟缓存做比较。如果不一致,就说明服务端有了新的变化,因此就要通知主线程。
- 如何给localStorage设置过期时间:
实现原理:localStorage存取初始登录时进行缓存的时间戳以及当前进去页面的时间戳,然后用sessionStorage去保存是否勾选了多少天免登陆的按钮状态。
1 | //1、点击了登录成功后的回调函数里 |
- 如何将一个扁平化数组转换为一个树形结构数组:
1、只适合两层嵌套(递归)
1 | function arrayToTree(arr){ |
非递归:
1 | function arrayToTree(arr){ |
2、无论多少层,递归
1 | function arrayToTree(arr,rootNode){ |
- 如何将一个树形结构数据转换为扁平数据:
1 | function flat(arr){ |