JavaScript最全面试题

1.闭包

什么是闭包?

MDN 的解释:闭包是函数和声明该函数的词法环境的组合。
按照我的理解就是:闭包 = 『函数』和『函数体内可访问的变量总和』
说白了就是函数嵌套函数,内部函数能够访问外部函数的变量

闭包的作用

闭包最大的作用就是隐藏变量,闭包的一大特性就是 内部函数总是可以访问其所在的外部函数
中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后
基于此特性, JavaScript 可以实现私有变量、特权变量、储存变量等
我们就以私有变量举例,私有变量的实现方法很多,有靠约定的(变量名前加 _ , 有靠 Proxy
代理的,也有靠 Symbol 这种新数据类型的。

闭包的优点

可以隔离作用域,不造成全局污染

闭包的缺点

由于闭包长期驻留内存,则长期这样会导致内存泄露
如何解决内存泄露:将暴露全外部的闭包变量置为 null

适用场景

封装组件, for 循环和定时器结合使用 ,for 循环和 dom 事件结合 . 可以在性能优化的过程
, 节流防抖函数的使用 , 导航栏获取下标的使用

一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但是创建时所在词法环境仍然存在,以达到延长变量生命周期的目的

柯里化函数
柯里化的目的在于避免频繁的调用具有相同参数函数的同时,又能轻松的重用

注意事项: 
如果不是某些特定任务需要使用闭包,在其他函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响

2.谈谈你对原型链的理解?

原型对象

绝大部分的函数(少数内建函数除外)都有一个 prototype属性,这个属性是原型对象用来创建新对象实例,而所有被创建的对象都会共享原型对象,因此这些对象便可以访问原型对象的属性

js常被描述为一个基于原型的语言--每个对象拥有一个原型对象

当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次向上搜索,直到找到一个名字匹配的属性或者达到原型链的顶端

原型链

原因是每个对象都有 __proto__ 属性,此属性指向该对象的构造函数的原型

原型对象也可能拥有原型,并从中继承方法和属性,一层一层,以此类推,这种关系被称为原型链,他解释了为何一个对象会拥有定义在其他对象中的属性和方法

在对象实例和他的构造器之间建立一个链接,_proto_属性,是从构造函数的prototype属性中派生的,之后可以通过上溯原型链,在构造器中找到这些属性和方法

总结:

一起对象都是继承自object对象,object对象直接继承根源对象null

一切的函数对象,都是继承自Function对象

object对象直接继承自Function对象

Function对象的_proto_会指向自己的原型对象,最总还是继承自object对象

3.说一下JS继承(含ES6的)

JS 继承实现方式也很多,主要分 ES5 ES6 继承的实现

继承的优点:

继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码

再子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得于父类别不同的功能

先说一下 ES5 是如何实现继承的
ES5 实现继承主要是基于 prototype 来实现的,具体有三种方法
一是 原型链继承 :即 B.prototype=new A()
二是借 用构造函数继承 (call 或者 apply 的方式继承 )
是组合继承

实现方式:
原型链继承:是比较常见的一种继承方式之一,其中设计的构造函数、原型和实例,三者之间存在一定的关系,即每一个构造函数中都会有一个原型对象,原型对象有包含一个指向构造函数的指针,而实例则包含一个原型对象的指针

构造函数继承(借助 call)借助call调用Parent函数,父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法,父类的引用属性不会被共享,优化了原型链继承的弊端,但是它只能继承父类的实例属性和方法,不能继承原型属性或者方法

组合继承:组合继承就是将原型链继承和构造函数继承结合起来

原型式继承:借助Object.create方法实现普通对象的继承:因为它实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能

寄生式继承:就是利用浅拷贝的能力进行增强,添加一些其他方法

寄生组合式继承:借助解决普通对象的继承问题的Object.create 方法,在前面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式

4.说一下JS原生事件如何绑定

JS 原生绑定事件主要为三种:
一是 html 事件处理程序
二是 DOM0 级事件处理程序
三是 DOM2 级事件处理程序
html 事件现在早已不用了,就是在 html 各种标签上直接添加事件,类似于 css 的行内样
式,缺点是不好维护,因为散落在标签中 , 也就是耦合度太高

javascript中的事件,可以理解就是在HTML文档或者浏览器中发生的一种交互操作,使得网页具备互动性, 常见的有加载事件、鼠标事件、自定义事件等

事件流都会经历三个阶段:

事件捕获阶段(capture phase)
处于目标阶段(target phase)
事件冒泡阶段(bubbling phase)
 事件冒泡是一种从下往上的传播方式,由最具体的元素(触发节点)然后逐渐向上传播到最不具体的那个节点,也就是DOM中最高层的父节点

事件捕获与事件冒泡相反,事件最开始由不太具体的节点最早接受事件, 而最具体的节点(触发节点)最后接受事件

事件模型可以分为三种:

原始事件模型(DOM0级)html代码直接绑定,js代码绑定

特性:

绑定速度快

只支持冒泡,不支持捕获

同一个类型的事件只能绑定一次

标准事件模型(DOM2级)

事件捕获阶段:事件从document一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行
事件处理阶段:事件到达目标元素, 触发目标元素的监听函数
事件冒泡阶段:事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行
特性:

可以在一个DOM元素上绑定多个事件处理器,各自并不会冲突
当第三个参数(useCapture)设置为true就在捕获过程中执行,反之在冒泡过程中执行处理函数

IE事件模型(基本不用

IE事件模型共有两个过程:

事件处理阶段:事件到达目标元素, 触发目标元素的监听函数。
事件冒泡阶段:事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行

 

5.说一下JS原生常用dom操作方法?

查找:

getElementByid,
getElementsByTagName,
querySelector,
querySelectorAll

插入:

appendChild,insertBefore

删除:

removeChild

克隆:

cloneNode

设置和获取属性:

setAttribute(“ 属性名 ”,” ”)
getAttibute(“ 属性名 ”)

6.说一下ES6新增特性?

1. 新增了块级作用域 (let,const)
2. 提供了定义类的语法糖 (class)
3. 新增了一种基本数据类型 (Symbol)
4. 新增了变量的解构赋值
5. 函数参数允许设置默认值,引入了 rest 参数,新增了箭头函数
6. 数组新增了一些 API ,如 isArray / from / of 方法 ; 数组实例新增了 entries() keys() values()
方法
7. 对象和数组新增了扩展运算符
8. ES6 新增了模块化 (import/export)
9. ES6 新增了 Set Map 数据结构
10. ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
11. ES6 新增了生成器 (Generator) 和遍历器 (Iterator)

7.JS设计模式有哪些(单例模式观察者模式等)

JS 设计模式有很多,但我知道的有单例模式,观察者模式
单例模式:
就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果
不存在就创建了再返回,这就确保了一个类只有一个实例对象。在 JavaScript 里,单例作为一个命
名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
观察者模式 :
观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象
需要改变的时候,就应该考虑使用观察者模式。
总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而 使得各自的变化都不会影响到另一边的变化

8.说一下你对JS面试对象的理解

JS 面向对象主要基于 function 来实现的,通过 function 来模拟类,通过 prototype 来实现类方法的共享, 跟其他语言有着本质的不同,自从有了ES6 后,把面向对象类的实现更像后端语言的实现了,通过 class 来定义类,通过extends 来继承父类,其实 ES6 类的实现本质上是一个语法糖,不过对于开发简单了好多

9.说一下JS数组常用方法(至少6个)

1. Array.push(),向数组的末尾添加一个或多个元素,并返回新的数组长度。原数组改变
2. Array.pop(),删除并返回数组的最后一个元素,若该数组为空,则返回undefined。原数组改变
3. Array.unshift(),向数组的开头添加一个或多个元素,并返回新的数组长度。原数组改变。
4. Array.shift(),删除数组的第一项,并返回第一个元素的值。若该数组为空,则返回undefined。原数组改变。
5. Array.concat(arr1,arr2…),合并两个或多个数组,生成一个新的数组。原数组不变。
6. Array.join(),将数组的每一项用指定字符连接形成一个字符串。默认连接字符为 “,” 逗号。
7. Array.reverse(),将数组倒序。原数组改变。
8. Array.sort(),对数组元素进行排序。按照字符串UniCode码排序,原数组改变。
9.Array.map(function),原数组的每一项执行函数后,返回一个新的数组。原数组不变。(注意该方法和forEach的区别)。
10.Array.slice() 按照条件查找出其中的部分内容
11.Array.splice(index,howmany,arr1,arr2…) ,用于添加或删除数组中的元素。从index位置开始删除howmany个元素,并将arr1、arr2…数据从index位置依次插入。howmany为0时,则不删除元素。原数组改变。
12.Array.forEach(function),用于调用数组的每个元素,并将元素传递给回调函数。原数组不变。(注意该方法和map的区别,若直接打印Array.forEach,结果为undefined)。
13.Array.filter(function),过滤数组中,符合条件的元素并返回一个新的数组
14.Array.every(function),对数组中的每一项进行判断,若都符合则返回true,否则返回false。
15.Array.some(function),对数组中的每一项进行判断,若都不符合则返回false,否则返回true。
16.Array.reduce(function),reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
17.indexOf() 检测当前值在数组中第一次出现的位置索引

原数组改变的方法有:push pop shift unshift reverse sort splice forEach
不改变原数组的方法有:concat map filter join every some indexOf slice

10.说一下JS数组内置遍历方法有哪些和区别

forEach

这个方法是为了取代 for 循环遍历数组的,返回值为 undefined
item 代码遍历的每一项 ,
index: 代表遍历的每项的索引,
arr 代表数组本身

filter

是一个过滤遍历的方法,如果返回条件为 true ,则返回满足条件为 true 的新数组

map

这个 map 方法主要对数组的复杂逻辑处理时用的多,特别是 react 中遍历数据,也经常用到,
写法和 forEach 类似

some

这个 some 方法用于只要数组中至少存在一个满足条件的结果,返回值就为 true, 否则返回
fasel, 写法和 forEach 类似

every

这个 every 方法用于数组中每一项都得满足条件时,才返回 true ,否则返回 false, 写法和
forEach 类似

11.说一下JS作用域

JS 作用域也就是 JS 识别变量的范围,作用域链也就是 JS 查找变量的顺序
先说作用域, JS 作用域主要包括全局作用域、局部作用域和 ES6 的块级作用域
全局作用域:也就是定义在 window 下的变量范围,在任何地方都可以访问,
局部作用域:是只在函数内部定义的变量范围
块级作用域:简单来说用 let const 在任意的代码块中定义的变量都认为是块级作用域中的变
量,例如在 for 循环中用 let 定义的变量,在 if 语句中用 let 定义的变量等等
: 1. 尽量不要使用全局变量,因为容易导致全局的污染,命名冲突,对 bug 查找不利。
2. 而所谓的作用域链就是由最内部的作用域往最外部 , 查找变量的过程 . 形成的链条就是作用域链

12.说一下从输入URL到页面加载完中间发生了什么?

大致过程是这样的:
1. 通过 DNS 服务器: url=>ip 地址;
2. 到达 ip 地址对应的服务器;
3. 服务器接收用户的请求;
4. 把处理后的结果返回给客户端;
5. 客户端把结果渲染到浏览器即可,最后页面显示出来
输入了一个域名 , 域名要通过 DNS 解析找到这个域名对应的服务器地址 (ip), 通过 TCP 请求链接服务 , 通过 WEB服务器 (apache) 返回数据 , 浏览器根据返回数据构建 DOM , 通过 css 渲染引擎及 js 解析引擎将页面渲 染出来, 关闭 tcp 连接

13.说一下JS事件代理(也称事件委托)是什么,及实现原理?

JS 事件代理就是通过给父级元素(例如: ul )绑定事件,不给子级元素 ( 例如: li) 绑定事件,然后当点击 子级元素时,通过事件冒泡机制在其绑定的父元素上触发事件处理函数,主要目的是为了提升性能,因 为我不用给每个子级元素绑定事件,只给父级元素绑定一次就好了, 在原生 js 里面是通过 event 对象的 targe属性实现
jq 方式实现相对而言简单 $(“ul”).on(“click”,“li”,function(){// 事件逻辑 }) 其中第二个参数指的是触发事件的 具体目标,特别是给动态添加的元素绑定事件,这个特别起作用
事件代理,通俗的讲,就是把一个元素响应事件的函数委托到另一个元素
前面讲到,事件流都会经过三个阶段:捕获阶段--目标阶段--冒泡阶段
事件委托是在冒泡阶段完成的

应用场景

如果我们有一个列表,列表中有大量的列表项,我们需要在点击列表项的时候响应一个事件,如果给每一个列表项都绑定一个函数,对于内存消耗是非常大的

总结:

适合事件委托的事件有:clickmousedownmouseupkeydownkeyupkeypress

从上面应用场景中,我们就可以看到使用事件委托存在两大优点:

  • 减少整个页面所需的内存,提升整体性能
  • 动态绑定,减少重复工作

但是使用事件委托也是存在局限性:

  • focusblur这些事件没有事件冒泡机制,所以无法进行委托绑定事件

  • mousemovemouseout这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的

如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件

14.说一下js数据类型有哪些?

js 数据类型有:
基本数据类型
umber
string
Boolean
null
undefined
symbol ES6 新增)
复合类型有
Object
function

15.说一下 call,apply,bind区别?实现一个bind

call,apply,bind 主要作用都是改变 this 指向的,但使用上略有区别,说一下区别
call apply 的主要区别是在传递参数上不同, call 后面传递的参数是以逗号的形式分开的, apply
传递的参数是数组形式 [Apply 是以 A 开头的 , 所以应该是跟 Array( 数组 ) 形式的参数 ]
bind 返回的是一个函数形式,如果要执行,则后面要再加一个小括号 例如: bind(obj, 参数 1, 参数
2,)(),bind 只能以逗号分隔形式,不能是数组形式

总结:

  • 三者都可以改变函数的this对象指向
  • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefinednull,则默认指向全局window
  • 三者都可以传参,但是apply是数组,而call是参数列表,且applycall是一次性传入参数,而bind可以分为多次传入
  • bind是返回绑定this之后的函数,applycall 则是立即执行

实现bind

  • 修改this指向
  • 动态传递参数
  • 兼容new关键字

16.JavaScript的作用域链理解

JavaScript 属于静态作用域,即声明的作用域是根据程序正文在编译时就确定的,有时也称为词法作用 域。
其本质是 JavaScript 在执行过程中会创造可执行上下文,可执行上下文中的词法环境中含有外部词法环境 的引用,我们可以通过这个引用获取外部词法环境的变量、声明等,这些引用串联起来一直指向全局的 词法环境,因此形成了作用域链。

17.ES6模块与CommonJS模块有什么区别?

ES6 Module CommonJS 模块的区别:
CommonJS 是对模块的浅拷贝, ES6 Module 是对模块的引用 , ES6 Module 只存只读,不能改变
其值,具体点就是指针指向不能变,类似 const
import 的接口是 read-only (只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但
可以改变变量内部指针指向 , 可以对 commonJS 对重新赋值(改变指针指向),但是对 ES6 Module
赋值会编译报错。
ES6 Module CommonJS 模块的共同点:
CommonJS ES6 Module 都可以对引入的对象进行赋值,即对对象内部属性的值进行改变。

18.nullundefined的区别是什么?

null 表示为空,代表此处不应该有值的存在,一个对象可以是 null ,代表是个空对象,而 null 本身也是对 象。
undefined 表示『不存在』, JavaScript 是一门动态类型语言,成员除了表示存在的空值外,还有可能根 本就不存在(因为存不存在只在运行期才知道),这就是undefined 的意义所在

19.箭头函数的this指向哪里?

箭头函数不同于传统 JavaScript 中的函数 , 箭头函数并没有属于自己的 this, 它的所谓的 this 是捕获其所在上 下文的 this 值,作为自己的 this , 并且由于没有属于自己的 this, 而箭头函数是不会被 new 调用的,这个 所谓的this 也不会被改变 .

20.async/await是什么?

async 函数,就是 Generator 函数的语法糖,它建立在 Promises 上,并且与所有现有的基于 Promise
API 兼容。
1. Async— 声明一个异步函数 (async function someName(){...})
自动将常规函数转换成 Promise ,返回值也是一个 Promise 对象
只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数
异步函数内部可以使用 await
2. Await— 暂停异步的功能执行 (var result = await someAsyncCall()
放置在 Promise 调用之前, await 强制其他代码等待,直到 Promise 完成并返回结果
只能与 Promise 一起使用,不适用与回调
只能在 async 函数内部使用

21.async/await相比于Promise的优势?

代码读起来更加同步, Promise 虽然摆脱了回调地狱,但是 then 的链式调用也会带来额外的阅读负
Promise 传递中间值非常麻烦,而 async/await 几乎是同步的写法,非常优雅
错误处理友好, async/await 可以用成熟的 try/catch Promise 的错误捕获非常冗余
调试友好, Promise 的调试很差,由于没有代码块,你不能在一个返回表达式的箭头函数中设置断
点,如果你在一个 .then 代码块中使用调试器的步进 (step-over) 功能,调试器并不会进入后续
.then 代码块,因为调试器只能跟踪同步代码的『每一步』

22.JavaScript的基本类型和复杂类型是储存在哪里的?

基本类型储存在栈中,但是一旦被闭包引用则成为常住内存,会储存在内存堆中。
复杂类型会储存在内存堆中。

23.简述同步与异步的区别?

同步:
浏览器访问服务器请求,用户看得到页面刷新,重新发请求 , 等请求完,页面刷新,新内容出
现,用户看到新内容 , 进行下一步操作
代码从上往下依次执行,执行完当前代码,才能执行下面的代码。(阻塞)
异步: 浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内
容也会出现,用户看到新内容
代码从上往下依次执行,没执行完当前代码,也能执行下面的代码。(非阻塞)

24.JavaScript垃圾回收原理?

javascript 中,如果一个对象不再被引用,那么这个对象就会被 GC 回收;
如果两个对象互相引用,而不再被第 3 者所引用,那么这两个互相引用的对象也会被回收。

25.请描述值类型(基本数据类型)和引用类型的区别?

值类型

占用空间固定,保存在栈中(当一个方法执行时,每个方法都会建立自己的内存栈,在这个方
法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自
然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;栈中存储的是基础变量以及
一些对象的引用变量, 基础变量的值是存储在栈中 ,而引用变量存储在栈中的是 指向堆中的数组
或者对象的地址 ,这就是为何修改引用类型总会影响到其他指向这个地址的引用变量。)
保存与复制的是值本身
使用 typeof 检测数据的类型
基本类型数据是值类型

引用类型

占用空间不固定,保存在堆中(当我们在程序中创建一个对象时,这个对象将被保存到运行时
数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。
堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变
量所引用(方法的参数传递时很常见),则这个对象 依然不会被销毁 ,只有当一个对象没有任
何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。)
保存与复制的是指向对象的一个指针
使用 instanceof 检测数据类型
使用 new() 方法构造出的对象是引用型

26.深拷贝和浅拷贝的区别?如何实现?

浅拷贝 只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。浅拷贝只复制 对象的第一层属性
深拷贝 会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
对对象的属性进行递归复制

浅拷贝

使用 Object.assign({},obj) 第一个参数是一个空对象,第二个参数是你要复制的对象;通过这
个方法我们知道浅拷贝不能修改基础的数据类型,可以修改引用的数据类型;
ES6 中的 ... 扩展运算符来进行浅拷贝的实现;
Object.assign() 实现

深拷贝

对象只有一层的话可以使用上面的: Object.assign() 函数
转成 JSON 再转回来
JSON.stringify 把对象转成字符串,再用 JSON.parse 把字符串转成新的对象。

使用Object.create()方法

直接使用 var newObj = Object.create(oldObj) ,可以达到深拷贝的效果。

27.浏览器是如何渲染页面的?

渲染的流程如下:
1. 解析 HTML 文件,创建 DOM 树。
自上而下,遇到任何样式( link style )与脚本( script )都会阻塞(外部样式不阻塞后续外部脚本的加
载)。
2. 解析 CSS 。优先级:浏览器默认设置 < 用户设置 < 外部样式 < 内联样式 中的 style 样式;
3. CSS DOM 合并,构建渲染树( Render Tree
4. 布局和绘制,重绘( repaint )和重排( reflow

28.什么是JavaScript原型链 ? 有什么特点?

每个对象都会在其内部初始化一个属性,就是 prototype ( 原型 ) ,当我们访问一个对象的属性时
如果这个对象内部不存在这个属性,那么他就会去 prototype 里找这个属性,这 prototype
会有自己的 prototype ,于是就这样一直找下去,也就是我们平时所说的原型链的概念
关系: instance.constructor.prototype = instance.__proto__
特点:
JavaScript 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的
原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
当我们需要一个属性的时, Javascript 引擎会先看当前对象中是否有这个属性, 如果没有的
就会查找他的 Prototype 对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象

29.jsonjsonp的区别?

son 返回的是一串 json 格式数据;而 jsonp 返回的是脚本代码(包含一个函数调用)
jsonp 的全名叫做 json with padding ,就是把 json 对象用符合 js 语法的形式包裹起来以使其他的网站可以 请求到,也就是将json 封装成 js 文件传过去。

30.如何阻止冒泡?

event.stopPropagation()方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行。不让事件向documen上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开。

31.如何阻止默认事件?

event.preventDefault()方法
取消事件的默认动作。该方法将通知 Web 浏览器不要执行与事件关联的默认动作(如果存在这样的动作)

form表单如果 type 属性是 "submit",在事件传播的任意阶段可以调用任意的事件句柄,通过调用该方法,可以阻止提交表单。a元素中href连接,如果调用此方法是,连接不会被打开。

return false
这个方法比较暴力,它会同时阻止事件冒泡和默认事件,写上此代码,连接不会被打开,事件也不会传递到上一层的父元素;

可以理解为return false就等于同时调用了event.stopPropagation()和event.preventDefault()

32.JavaScript事件流模型都有什么?

事件冒泡 ”: 事件开始由最具体的元素接受,然后逐级向上传播
事件捕捉 ”: 事件由最不具体的节点先接收,然后逐级向下,一直到最具体的
“DOM 事件流 ”: 三个阶段 : 事件捕捉,目标阶段,事件冒泡

33.请你谈谈cookie的弊端?

缺点 :
1.Cookie 数量和长度的限制。每个 domain 最多只能有 20 cookie ,每个 cookie 长度 不能超过
4KB ,否则会被截掉。
2. 安全性问题。如果 cookie 被人拦截了,那人就可以取得所有的 session 信息。即使加密也与事无补,
因为拦截者并不需要知道 cookie 的意义,他只要原样转发 cookie 就可以达到目的了。
3. 有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存 一个计数 器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。

34.哪些操作会造成内存泄漏?

内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存

并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费

程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存

对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃

垃圾回收机制:

js具有自动垃圾回收机制,执行环境会负责管理代码执行过程中的内存

垃圾收集器会定期的找出那些不再继续使用的变量,然后释放内存

常用的两种实现方式:

标记清除:

JavaScript最常用的垃圾收回机制

当变量进入执行环境是,就标记这个变量为“进入环境“。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为“离开环境“

引用计数

语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放

内存泄露的一些场景:

1. setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
2. 闭包
3. 控制台日志
4. 循环 ( 在两个对象彼此引用且彼此保留时,就会产生一个循环 )

35.如何优化自己的代码?

代码重用
避免全局变量 ( 命名空间,封闭空间,模块化 mvc..)
拆分函数避免函数过于臃肿

36.JavaScript 中的强制转型是指什么?

JavaScript 中,两种不同的内置类型间的转换被称为强制转型。强制转型在 JavaScript 中有两种形 式:显式和隐式。
强制类型转型指将一个数据类型强制转换为其它的数据类型。一般是指,将其它的数据类型转换为String、Number、Boolean。

37.解释 JavaScript 中的相等性。

JavaScript 中有严格比较和类型转换比较:
严格比较(例如 === )在不允许强制转型的情况下检查两个值是否相等;
抽象比较(例如 == )在允许强制转型的情况下检查两个值是否相等。

一些简单的规则:

如果被比较的任何一个值可能是 true false ,要用 === ,而不是 ==
如果被比较的任何一个值是这些特定值(
0 “” [] ),要用 === ,而不是 ==
在其他情况下,可以安全地使用 == 。它不仅安全,而且在很多情况下,它可以简化代码,并且提
升代码可读性

38.你能解释一下 ES5 ES6 之间的区别吗?

ECMAScripts5,即ES5,是ECMAScripts的第五次修订,于2009年完成标准化ECMAScripts6,即ES6,是ECMAScripts的第六次修订,于2025年完成,也称ES2015ES6是继ES5之后的一次改进,相对于ES5更加简洁,提高了开发效率ES6新增的一些特性:

1)let声明变量和const声明常量,两个都有块级作用域ES5中是没有块级作用域的,并且var有变量提升,在let中,使用的变量一定要进行声明
2)箭头函数ES6中的函数定义不再使用关键字function(),而是利用了()=>来进行定义
3)模板字符串模板字符串是增强版的字符串,用反引号(`)标识,可以当作普通字符串使用,也可以用来定义多行字符串
4)解构赋值ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值
5)... 展开运算符可以将数组或对象里面的值展开;还可以将多个值收集为一个变量

39.解释 JavaScript “undefined”“not defined”之间的区别?

JavaScript 中,如果你试图使用一个不存在且尚未声明的变量, JavaScript 将抛出错误 “var name is not defined”,让后脚本将停止运行。但如果你使用 typeof undeclared_variable ,它将返回 undefined。
当我们试图访问一个被声明但未被定义的变量时,会出现 undefined 错误。
当我们试图引用一个既未声明也未定义的变量时,将会出现 not defined 错误。

40.匿名和命名函数有什么区别?

匿名函数通常是某一个事件触发后进行触发的。
命名函数可以进行预先的封装,在需要使用的地方通过调用函数名运行。

41.什么是 JavaScript 中的提升操作?

提升( hoisting )是 JavaScript 解释器将所有变量和函数声明移动到当前作用域顶部的操作。
有两种类型的提升:
变量提升 —— 非常少见
函数提升 —— 常见
无论 var (或函数声明)出现在作用域的什么地方,它都属于整个作用域,并且可以在该作用域内的任何地方访问它。

42.谈谈this对象的理解?


 函数的this关键字在js中表现得略有不同,在严格模式下和非严格模式下也会有一些差别

在绝大多数情况下,函数的调用方式决定了this的值

this关键字是在函数运行时自动生成一个内部对象,只能在函数内部使用,总能调用他的对象

在this的执行过程中,一旦被确定,就不能再进行更改

绑定规则:


默认绑定:全局环境中定义person函数,内部使用this关键字

隐式绑定:函数还可以作为某个对象的方法调用,这时this就指这个上级对象

new绑定:通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象

显示绑定:apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数

 箭头函数

es6中还提供了箭头函数语法,让我们在代码书写时就能确定this指向

虽然箭头函数的this能够在编译的时候就确定this的指向,但是我们也需要注意一些潜在的坑

箭头函数没有this指向,箭头函数不能作为构造函数

优先级


 new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级

 43.JavaScript中执行上下文和执行栈是什么?


简单来说,执行上下文是一种对js代码执行环境的抽象概念,也就是说只有js代码运行,那么它就一定是运行在执行的上下文中

执行上下文的类型分为三种:

全局执行上下文:只有一个,浏览器中的全局对象就是 window对象,this 指向这个全局对象
函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文
Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用
生命周期:
 执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段

创建阶段:

创建阶段做了三件事:

确定 this 的值,也被称为 This Binding
LexicalEnvironment(词法环境) 组件被创建
VariableEnvironment(变量环境) 组件被创建
词法环境

词法环境有两个组成部分:

全局环境:是一个没有外部环境的词法环境,其外部环境引用为null,有一个全局对象,this 的值指向这个全局对象

函数环境:用户在函数中定义的变量被存储在环境记录中,包含了arguments 对象,外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境

变量环境

变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性

在 ES6 中,词法环境和变量环境的区别在于前者用于存储函数声明和变量( let 和 const )绑定,而后者仅用于存储变量( var )绑定

执行阶段
在这阶段,执行变量赋值、代码执行

如果 Javascript 引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配 undefined 值

回收阶段
执行上下文出栈等待虚拟机回收执行上下文
 

44.==和===的区别?分别在什么情况下使用


相等操作符(==)
如果操作数相等,则会返回true,在比较中会先进行类型转换,再确定操作数是否相等

遵循以下规则:

两个都为简单类型,字符串和布尔值都会转换成数值,再比较

简单类型与引用类型比较,对象转化成其原始类型的值,再比较

两个都为引用类型,则比较它们是否指向同一个对象

null 和 undefined 相等

存在 NaN 则返回 false

全等操作符(===)
全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 true。即类型相同,值也需相同

undefined 和 null 与自身严格相等

区别?
相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换

null 和 undefined 比较,相等操作符(==)为true,全等为false

总结:
除了在比较对象属性为null或者undefined的情况下,我们可以使用相等操作符(==),其他情况建议一律使用全等操作符(===)

 

45.typeof和instanceof区别?

typeof

typeof操作符返回一个字符串,表示未经计算的操作数的类型

需要注意的是,null在typeof之后返回是有问题的结果,不能作为判断null的方法,如果想要在if语句中判断是否是null,直接通过===null就可以了

如果我们想要判断一个变量是否存在,可以使用typeof:(不能使用if(a),若a没有声明,则报错)

instanceof

 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

构造函数通过new可以实例对象,instanceof能判断这个对象是否是之前那个构造函数生成的对象

会顺着原型链去寻找,直到找到相同的原型对象,返回true,否则为false

区别:

  • typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值

  • instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型

  • typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断

以上两种方法都有弊端,并不能满足所有场景的需求

Object.prototype.toString,调用该方法,统一返回格式“[object Xxx]”的字符串

46.说说new操作符具体干了什么?

在js中,new操作符用于创建一个给定构造函数的实例对象

new通过构造函数Person创建出来的实例可以访问到构造函数中的属性

new通过构造函数Person创建出来的实例可以访问到构造函数原型链中的属性(即实例与构造函数通过原型链连接了起来)

流程

  • 创建一个新的对象obj

  • 将对象与构建函数通过原型链连接起来

  • 将构建函数中的this绑定到新建的对象obj

  • 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理

 JavaScript最全面试题_第1张图片

 47.ajax原理是什么?如何实现?

 AJAX全称(Async Javascript and XML),是异步的Javascript和XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,于服务器交换数据,并且更新部分网页

Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面

实现过程

  • 创建 Ajax的核心对象 XMLHttpRequest对象

  • 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接

  • 构建请求所需的数据内容,并通过XMLHttpRequest 对象的 send() 方法发送给服务器端

  • 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态

  • 接受并处理服务端向客户端响应的数据结果

  • 将处理结果更新到 HTML页面中

参数说明:

  • method:表示当前的请求方式,常见的有GETPOST

  • url:服务端地址

  • async:布尔值,表示是否异步执行操作,默认为true

  • user: 可选的用户名用于认证用途;默认为`null

  • password: 可选的密码用于认证用途,默认为`null

48.说说你对正则表达式的理解?应用场景?

是什么?

正则表达式是一种用来匹配字符串的强有力的武器

他的设计思想是用一种描述性的语言定义的一个规则,凡是符合规则的字符串,我们就认为他匹配了,否则,该字符串就是不合法

构建正则表达式有两种方式:

1.字面量创建,包含在斜杠之间的模式组成

const re = /\d+/g;

2.调用RegExp对象的构造函数

const re = new RegExp("\\d+","g");

const rul = "\\d+"
const re1 = new RegExp(rul,"g");

使用构建函数创建,第一个参数可以是一个变量,遇到特殊字符\需要使用\\进行转义

 匹配规则

规则 描述
\ 转义
^ 匹配输入的开始
$ 匹配输入的结束
* 匹配前一个表达式 0 次或多次
+ 匹配前面一个表达式 1 次或者多次。等价于 {1,}
? 匹配前面一个表达式 0 次或者 1 次。等价于{0,1}
. 默认匹配除换行符之外的任何单个字符
x(?=y) 匹配'x'仅仅当'x'后面跟着'y'。这种叫做先行断言
(?<=y)x 匹配'x'仅当'x'前面是'y'.这种叫做后行断言
x(?!y) 仅仅当'x'后面不跟着'y'时匹配'x',这被称为正向否定查找
(?y)x 仅仅当'x'前面不是'y'时匹配'x',这被称为反向否定查找
x|y 匹配‘x’或者‘y’
{n} n 是一个正整数,匹配了前面一个字符刚好出现了 n 次
{n,} n是一个正整数,匹配前一个字符至少出现了n次
{n,m} n 和 m 都是整数。匹配前面的字符至少n次,最多m次
[xyz] 一个字符集合。匹配方括号中的任意字符
[^xyz] 匹配任何没有包含在方括号中的字符
\b 匹配一个词的边界,例如在字母和空格之间
\B 匹配一个非单词边界
\d 匹配一个数字
\D 匹配一个非数字字符
\f 匹配一个换页符
\n 匹配一个换行符
\r 匹配一个回车符
\s 匹配一个空白字符,包括空格、制表符、换页符和换行符
\S 匹配一个非空白字符
\w 匹配一个单字字符(字母、数字或者下划线)
\W 匹配一个非单字字符

正则表达式标记

标志 描述
g 全局搜索。
i 不区分大小写搜索。
m 多行搜索。
s 允许 . 匹配换行符。
u 使用unicode码的模式进行匹配。
y 执行“粘性(sticky)”搜索,匹配从目标字符串的当前位置开始。

正则表达式的特性:

贪婪模式:尽可能多去匹配,

 懒惰模式:惰性量词就是在贪婪量词后面加个问号。表示尽可能少的匹配

分组:分组主要是用过()进行实现,反向引用,巧用$分组捕获

匹配方法:

方法 描述
exec 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回 null)。
test 一个在字符串中测试是否匹配的RegExp方法,它返回 true 或 false。
match 一个在字符串中执行查找匹配的String方法,它返回一个数组,在未匹配到时会返回 null。
matchAll 一个在字符串中执行查找所有匹配的String方法,它返回一个迭代器(iterator)。
search 一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。
replace 一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。
split 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的 String 方法。

应用场景:

 验证QQ合法性,校验用户账号的合法性

 48.说说你对事件循环的理解

 JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环

JavaScript中,所有的任务都可以分为

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行

  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等

JavaScript最全面试题_第2张图片同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环 

宏仁务

 宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

它的执行机制是:

  • 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
  • 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完

微任务

 一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

常见的微任务有:

  • Promise.then

  • MutaionObserver

  • Object.observe(已废弃;Proxy 对象替代)

  • process.nextTick(Node.js)

async与await

 async 是异步的意思,await则可以理解为 async wait。所以可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行

正常情况下,await命令后面是一个 Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值

不管await后面跟着的是什么,await都会阻塞后面的代码

分析过程:

  1. 执行整段代码,遇到 console.log('script start') 直接打印结果,输出 script start
  2. 遇到定时器了,它是宏任务,先放着不执行
  3. 遇到 async1(),执行 async1 函数,先打印 async1 start,下面遇到await怎么办?先执行 async2,打印 async2,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码
  4. 跳到 new Promise 这里,直接执行,打印 promise1,下面遇到 .then(),它是微任务,放到微任务列表等待执行
  5. 最后一行直接打印 script end,现在同步代码执行完了,开始执行微任务,即 await下面的代码,打印 async1 end
  6. 继续执行下一个微任务,即执行 then 的回调,打印 promise2
  7. 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印 settimeout

49.DOM常见的操作有哪些?

 文档对象模型 (DOM) 是 HTML 和 XML 文档的编程接口

它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容

任何 HTMLXML文档都可以用 DOM表示为一个由节点构成的层级结构

日常前端开发,我们都离不开DOM操作

在以前,我们使用Jqueryzepto等库来操作DOM,之后在vueAngularReact等框架出现后,我们通过操作数据来控制DOM(绝大多数时候),越来越少的去直接操作DOM

但这并不代表原生操作不重要。相反,DOM操作才能有助于我们理解框架深层的内容

下面就来分析DOM常见的操作,主要分为:

  • 创建节点:创建新元素,接受一个参数,即要创建元素的标签名
  • 查询节点:传入任何有效的css 选择器,即可选中单个 DOM元素(首个)
  • 更新节点:不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树
  • 添加节点:如果这个DOM节点是空的,例如,
    ,那么,直接使用innerHTML = 'child'就可以修改DOM节点的内容,相当于添加了新的DOM节点
  • 删除节点:删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉

50.说说你对BOM的理解,常见的BOM对象你了解哪些?

BOM (Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象

其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退,前进,刷新,浏览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏幕分辨率

浏览器的全部内容可以看成DOM,整个浏览器可以看成BOM

JavaScript最全面试题_第3张图片

window:

Bom的核心对象是window,它表示浏览器的一个实例

在浏览器中,window对象有双重角色,即是浏览器窗口的一个接口,又是全局对象

因此所有在全局作用域中声明的变量、函数都会变成window对象的属性和方法

location:

属性名 例子 说明
hash "#contents" utl中#后面的字符,没有则返回空串
host www.wrox.com:80 服务器名称和端口号
hostname www.wrox.com 域名,不带端口号
href http://www.wrox.com:80/WileyCDA/?q=javascript#contents 完整url
pathname "/WileyCDA/" 服务器下面的文件路径
port 80 url的端口号,没有则为空
protocol http: 使用的协议
search ?q=javascript url的查询字符串,通常为?后面的内容

 navigator JavaScript最全面试题_第4张图片

 screen

JavaScript最全面试题_第5张图片 

history 

 history对象主要用来操作浏览器URL的历史记录,可以通过参数向前,向后,或者向指定URL跳转

  • history.go()
  • history.forward():向前跳转一个页面
  • history.back():向后跳转一个页面
  • history.length:获取历史记录数

51. 举例说明你对尾递归的理解,有哪些应用场景?

 在数学与计算机科学中,是指在函数的定义中使用函数自身的方法

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数

一般来说,递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回

尾递归

 尾递归,即在函数尾位置调用自身(或是一个尾调用本身的其他函数等等)。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数

尾递归在普通尾调用的基础上,多出了2个特征:

  • 在尾部调用的是函数自身
  • 可通过优化,使得计算仅占用常量栈空间

应用场景

数组求和

使用尾递归优化求斐波那契数列

数组扁平化

数组对象格式化

52.Javascript本地存储的方式有哪些?区别及应用场景? 

cookie

cookie是指默写网站为了辨别用户身份而储存在用户本地终端上的数据,是为了解决HTTP无状态导致的问题

cookie每次请求都会被发送,如果不使用HTTPS对它进行加密,保存的信息很容易被窃取,导致安全风险

cookie容易进行cookie的欺骗

sessionStorage

sessionStorage和 localStorage使用方法基本一致,唯一不同的是生命周期,一旦页面(会话)关闭,sessionStorage 将会删除数据

localStorage

HTML5新的方法,IE8以上兼容

持久化的本地存储,除非手动删除,否则数据会一直存在

存储的信息在同一域中是共享的

大小5M左右

当本页操作(新增、修改、删除)了localStorage的时候,本页面不会触发storage事件,但是别的页面会触发storage事件

localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡

受同源策略影响

indexedDB

indexedDB是一种低级API,用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对该数据的高性能搜索

优点:

  • 储存量理论上没有上限
  • 所有操作都是异步的,相比 LocalStorage 同步操作性能更高,尤其是数据量较大时
  • 原生支持储存JS的对象
  • 是个正经的数据库,意味着数据库能干的事它都能干

缺点:

  • 操作非常繁琐
  • 本身有一定门槛

关于indexedDB的使用基本使用步骤如下:

  • 打开数据库并且开始一个事务

  • 创建一个 object store

  • 构建一个请求来执行一些数据库操作,像增加或提取数据等。

  • 通过监听正确类型的 DOM 事件以等待操作完成。

  • 在操作结果上进行一些操作(可以在 request对象中找到)

 应用场景:

  • 标记用户与跟踪用户行为的情况,推荐使用cookie
  • 适合长期保存在本地的数据(令牌),推荐使用localStorage
  • 敏感账号一次性登录,推荐使用sessionStorage
  • 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用indexedDB

53.说说你对函数式编程的理解?优缺点

 函数式编程是一种"编程范式"(programming paradigm),一种编写程序的方法论

主要的编程范式有三种:命令式编程,声明式编程和函数式编程

相比命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而非设计一个复杂的执行过程

纯函数

 函数式编程旨在尽可能的提高代码的无状态性和不变性,无副作用的函数就是纯函数

纯函数=无状态+数据不可变

特性:

  • 函数内部传入指定的值,就会返回确定唯一的值
  • 不会造成超出作用域的变化,例如修改全局变量或引用传递的参数

优势:

  • 使用纯函数,我们可以产生可测试的代码
  • 不依赖外部环境计算,不会产生副作用,提高函数的复用性

  • 可读性更强 ,函数不管是否是纯函数 都会有一个语义化的名称,更便于阅读

  • 可以组装成复杂任务的可能性。符合模块化概念及单一职责原则

高阶函数

函数的参数是一个函数,或者是函数的返回值是一个函数

 上面通过高阶函数 forEach来抽象循环如何做的逻辑,直接关注做了什么

柯里化

 柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程

关于柯里化函数的意义如下:

  • 让纯函数更纯,每次接受一个参数,松散解耦
  • 惰性执行

组合与管道

 组合函数,目的是将多个函数组合成一个函数

 组合函数与管道函数的意义在于:可以把很多小函数组合起来完成更复杂的逻辑

优缺点: 

 优点:

  • 更好的管理状态:因为它的宗旨是无状态,或者说更少的状态,能最大化的减少这些未知、优化代码、减少出错情况

  • 更简单的复用:固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响

  • 更优雅的组合:往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。更强的复用性,带来更强大的组合性

  • 隐性好处。减少代码量,提高维护性

缺点:

  • 性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销

  • 资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式

  • 递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作

 54.Javascript中如何实现函数缓存?函数缓存有哪些应用场景?

 函数缓存,就是将函数运算过的结果进行缓存

本质上就是用空间(缓存存储)换时间(计算过程)

常用于缓存数据计算结果和缓存对象

缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理

 实现方式:

实现函数缓存主要依靠闭包、柯里化、高阶函数

应用场景:

  • 对于昂贵的函数调用,执行复杂计算的函数
  • 对于具有有限且高度重复输入范围的函数
  • 对于具有重复输入值的递归函数
  • 对于纯函数,即每次使用特定输入调用时返回相同输出的函数

 55.说说 Javascript 数字精度丢失的问题,如何解决?

计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法

因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差

解决方案:

凑整

使用第三方库,Math.js BigDecimal.js

56.如何判断一个元素是否在可视区域中

 我们经常需要判断目标元素是否在视窗之内或者和视窗的距离小于一个值(例如 100 px),从而实现一些常用的功能

  • 图片的懒加载
  • 列表的无限滚动
  • 计算广告元素的曝光情况
  • 可点击链接的预加载

实现方式:

  • offsetTop、scrollTop:offsetTop,元素的上外边框至包含元素的上内边框之间的像素距离,scrollTop滚动轴与顶部之间像素距离

  • getBoundingClientRect:返回值是一个 DOMRect对象,拥有lefttoprightbottomxywidth, 和 height属性

  • Intersection Observer:

    Intersection Observer 即重叠观察者,从这个命名就可以看出它用于判断两个元素是否重叠,因为不用进行事件的监听,性能方面相比getBoundingClientRect会好很多

    使用步骤主要分为两步:创建观察者和传入被观察者

 57.大文件上传如何做断点续传?

不管怎样简单的需求,在量级达到一定层级时,会变得异常复杂

文件上传简单,但是当文件变大的时候就会变得复杂

上传大文件,以下几个变量会影响用户体验

  • 服务器处理数据的能力
  • 请求超时
  • 网络波动

分片上传:

 分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传

断点上传

断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分

一般实现方式有两种:

  • 服务器端返回,告知从哪开始
  • 浏览器端自行处理

使用场景:

  • 大文件加速上传:当文件大小超过预期大小时,使用分片上传可实现并行上传多个 Part, 以加快上传速度
  • 网络环境较差:建议使用分片上传。当出现上传失败的时候,仅需重传失败的Part
  • 流式上传:可以在需要上传的文件大小还不确定的情况下开始上传。这种场景在视频监控等行业应用中比较常见

 58.如何实现上拉加载,下拉刷新?

 上拉加载及下拉刷新都依赖于用户交互

上拉加载的本质就是页面触底,或者快要触底时的动作

  • scrollTop:滚动视窗的高度距离window顶部的距离,它会随着往上滚动而不断增加,初始值是0,它是一个变化的值

  • clientHeight:它是一个定值,表示屏幕可视区域的高度;

  • scrollHeight:页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示body所有元素的总长度(包括body元素自身的padding)

下拉刷新的本质就是页面本身置于顶部时,用户下拉时需要触发的动作

关于下拉刷新的原生实现,主要分成三步:

  • 监听原生touchstart事件,记录其初始位置的值,e.touches[0].pageY
  • 监听原生touchmove事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0表示向下拉动,并借助CSS3的translateY属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;
  • 监听原生touchend事件,若此时元素滑动达到最大值,则触发callback,同时将translateY重设为0,元素回到初始位置

使用better-scroll实现下拉刷新、上拉加载时要注意以下几点:

  • wrapper里必须只有一个子元素
  • 子元素的高度要比wrapper要高
  • 使用的时候,要确定DOM元素是否已经生成,必须要等到DOM渲染完成后,再new BScroll()
  • 滚动区域的DOM元素结构有变化后,需要执行刷新 refresh()
  • 上拉或者下拉,结束后,需要执行finishPullUp()或者finishPullDown(),否则将不会执行下次操作
  • better-scroll,默认会阻止浏览器的原生click事件,如果滚动内容区要添加点击事件,需要在实例化属性里设置click:true

 59.什么是单点登录?如何实现?

 单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一

SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统

SSO 一般都需要一个独立的认证中心(passport),子系统的登录均得通过passport,子系统本身将不参与登录操作

当一个系统成功登录以后,passport将会颁发一个令牌给各个子系统,子系统可以拿着令牌会获取各自的受保护资源,为了减少频繁认证,各个子系统在被passport授权以后,会建立一个局部会话,在一定时间内可以无需再次向passport发起认证

举个例子:

淘宝、天猫都属于阿里旗下,当用户登录淘宝后,再打开天猫,系统便自动帮用户登录了天猫,这种现象就属于单点登录

流程:

  • 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数

  • sso认证中心发现用户未登录,将用户引导至登录页面

  • 用户输入用户名密码提交登录申请

  • sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌

  • sso认证中心带着令牌跳转会最初的请求地址(系统1)

  • 系统1拿到令牌,去sso认证中心校验令牌是否有效

  • sso认证中心校验令牌,返回有效,注册系统1

  • 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源

  • 用户访问系统2的受保护资源

  • 系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数

  • sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌

  • 系统2拿到令牌,去sso认证中心校验令牌是否有效

  • sso认证中心校验令牌,返回有效,注册系统2

  • 系统2使用该令牌创建与用户的局部会话,返回受保护资源

用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话

用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心

全局会话与局部会话有如下约束关系:

  • 局部会话存在,全局会话一定存在
  • 全局会话存在,局部会话不一定存在
  • 全局会话销毁,局部会话必须销毁

 60.web常见的攻击方式有哪些?如何防御

Web攻击(WebAttack)是针对用户上网行为或网站服务器等设备进行攻击的行为

恶意植入代码,修改网站权限,获取网站用户隐私

web应用程序的安全性是人格基于web业务的重要组成部分

我们常见的Web攻击方式有

XSS (Cross Site Scripting) 跨站脚本攻击

根据攻击的来源,XSS攻击可以分成:

  • 存储型
  • 反射型
  • DOM 型

存储型

存储型 XSS 的攻击步骤:

  1. 攻击者将恶意代码提交到目标网站的数据库中
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等

反射型 XSS

反射型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。

反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。

由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见

DOM 型 XSS

DOM 型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码
  2. 用户打开带有恶意代码的 URL
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞

XSS的预防

通过前面介绍,看到XSS攻击的两大要素:

  • 攻击者提交而恶意代码
  • 浏览器执行恶意代码

CSRF(Cross-site request forgery)跨站请求伪造

一个典型的CSRF攻击有着如下的流程:

  • 受害者登录a.com,并保留了登录凭证(Cookie)
  • 攻击者引诱受害者访问了b.com
  • b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie
  • a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求
  • a.com以受害者的名义执行了act=xx
  • 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作

CSRF的特点

  • 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生
  • 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据
  • 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”
  • 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪

CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性

防止csrf常用方案如下:

  • 阻止不明外域的访问
    • 同源检测
    • Samesite Cookie
  • 提交时要求附加本域才能获取的信息
    • CSRF Token
    • 双重Cookie验证

SQL注入攻击:Sql 注入攻击,是通过将恶意的 Sql查询或添加语句插入到应用的输入参数中,再在后台 Sql服务器上解析执行进行的攻击

 

流程如下所示:

  • 找出SQL漏洞的注入点

  • 判断数据库的类型以及版本

  • 猜解用户名和密码

  • 利用工具查找Web后台管理入口

  • 入侵和破坏

预防方式如下:

  • 严格检查输入变量的类型和格式
  • 过滤和转义特殊字符
  • 对访问数据库的Web应用程序采用Web应用防火墙

61.什么是防抖和节流?有什么区别?如何实现?

 本质上是优化高频率执行代码的一种手段

定义

  • 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
  • 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时 

一个经典的比喻:

想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应

假设电梯有两种运行策略 debounce 和 throttle,超时设定为15秒,不考虑容量限制

电梯第一个人进来后,15秒后准时运送一次,这是节流

电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖

区别?

相同点:

  • 都可以通过使用 setTimeout 实现
  • 目的都是,降低回调执行频率。节省计算资源

不同点:

  • 函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
  • 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次

应用场景:

防抖在连续的事件,只需触发一次回调的场景有:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

节流在间隔一段时间执行一次回调的场景有:

  • 滚动加载,加载更多或滚到底部监听
  • 搜索框,搜索联想功能

你可能感兴趣的:(Javascript,javascript,开发语言,ecmascript)