2020-07-07 js面试知识点

1.js的数据类型有哪些,值是如何存储的?

//js一共有8种数据类型,其中有7中基本数据类型:undefined , null, boolean , number, string, symbol(es6新增),
bigint(es10新增),object。

object---引用数据类型(本质上是由一组无序的名值对组成的)。里面包含function,array,date等。
原始数据类型:直接存储在栈中,占据空间小,大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型:同时存储在栈和堆中,占据空间大,大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中
该实体的起始地址。

2.&& || !! 运算符分别能做什么?

&& 逻辑与,在其操作数中找到第一个虚值表达式并返回它。采用短路来防止不必要的工作
|| 逻辑或,在其操作数中找到第一个真值表达式并返回它,也是采用了短路。
!! 运算符可以将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简单方法。

3.js的数据类型的转换

在js中类型转换只有三种情况,分别是:
转换为布尔值(Boolean)
转换为数字(调用 Number(), parseInt(), parseFloat() 方法)
转换为字符串(调用 .toString() 或者 String() 方法)

4.js中数据类型的判断(typeof  , instanceof  , constructor , Object.prototype.toString.call())

typeof: 对于原始类型来说,除了 null 都可以显示正确的类型。【typeof null //显示为 object】
typeof对于对象来说,除了函数都会显示object,所以说typeof 并不能准确判断变量到底是
什么类型,所以想判断一个对象的正确类型,这时候可以考虑使用 instanceof 。

instanceof:可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是都能找到类型的
prototype。可以看出直接的字面量判断数据类型,instanceof 可以精准判断引用数据类型(
array,function,object),而基本数据类型不能被instanceof 精准判断。
instanceoc运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性。其
意思是判断对象是否是某一个数据类型的实例,重点关注一下是判断一个对象是否是数据类型的实例。

constructor:
console.log((2).constructor === Number);//true
console.log((true).constructor === Boolean);//true
console.log(('str').constructor === String);//true
console.log(([]).constructor == Array);//true
console.log((function(){}).constructor === Function);//true
console.log(({}).constructor === Object);//true
坑来了,如果创建一个对象,更改它的原型,constructor就变得不可靠了
function Fn(){};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor === Fn);//false
console.log(f.constructor === Array);//true

Object.prototype.toString.call():
使用 object对象的原型方法 toString,使用call进行狸猫换太子,借用object的toString方法

 5.js有哪些内置对象

js的内置对象主要指的是在程序执行前存在全局作用域里的由js定义的一些全局值属性,函数和用来实例化其他
对象的构造函数对象。一般我们经常用到的如全局变量值NaN,undefined,全局函数如parseInt(),
parseFloat()用来实例化对象的构造函数如Date,Object 等,还有提供数学计算的单体内置对象如Math对象

6.undefined  与 undeclared 的区别?

已在作用域中声明但还没有赋值的变量,是undefined。
还没有在作用域中声明过的变量,是 undeclared。

7.null 和 undefined 的区别?

两者都是基本数据类型。
undefined 代表的含义是未定义。
null代表的含义是空对象。
一般变量声明了但还没有定义的时候会返回undefined。
null主要用于赋值给一些可能会返回对象的变量,作为初始化。
当我们对两种类型使用typeof进行判断的时候,null类型会返回object。当我们使用==对两种类型的值进行
比较时会返回true,使用===会返回false。

8.{} 和 [] 的valueOf 和 toString 的结果是什么?

{}  的 valueOf 结果为 {}, toString 的结果是 "[object object]"
[] 的valueOf 结果为 [], toString的结果为""

9.javascript的作用域和作用域链

作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则是管理浏览器引擎如何在当前作用域
以及嵌套的作用域中根据变量进行变量查找。

作用域链:作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以
访问到外层环境的变量和函数。

作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。
作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作
用域链的最后一个对象。

10.js创建对象的几种方式?

第一种是 工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用
的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码
而没有建立起对象和类型间的关系。

第二种是 构造函数模式,js中每一个函数都可以作为构造函数,只要一个函数是通过new来调用的,那么就可以
把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的 prototype 属性,
然后将执行上下文中的this指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因
为this的值指向了新建的对象,因此我们可以使用this给对象赋值。构造函数模式相对于工厂模式的优点是,
所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型。但是构造函数存在一个缺点
就是,造成了不必要的函数对象的创建,因为在js中函数也是一个对象,因此如果对象属性中如果包含函数的话
那么每次我们都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。

第三种模式是原型模式,因为每一个函数都有一个prototype属性,这个属性是一个对象,它包含了通过构造函
数创建的所有实例都能共享的属性和方法。因此我们可以使用原型对象来添加公用属性和方法,从而实现代码的
复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是
没有办法通过传入参数来初始值,另一个是如果存在一个引用类型如array这样的值,那么所有的实例将共享一
个对象,一个实例对引用类型值得改变会影响所有得实例。

第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型得最常见方式。因为构造函数模式和原型
模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原
型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因
为使用了两种不同的模式,所以对于代码的封装性不够好。

第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否
存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好的对上面的混合模式
进行了封装。

第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,我对这个模式的理解是,它主要是基
于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象
的目的。他的一个缺点和工厂模式一样,无法实现对象的识别。

 11.JavaScript继承的几种实现方式?

第一种是以原型链的方式来实现继承
第二种方式是使用借用构造函数的方式
第三种方式是组合继承
第四种方式是原型式继承
第五种方式式寄生式继承
第六种方式是寄生式组合继承

12.寄生式组合继承的实现?

function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){
console.log("my name is"+this.name+".")
}
function Student(name,grade){
Person.call(this,name);
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.sayMyGrade = function(){
console.log("my grade is"+ this.grade +".")
}

13.this, call , apply , bind 的理解

在浏览器里,在全局范围内this指向window对象;
在函数中,this永远指向最后调用他的那个对象;
构造函数中,this指向new出来的那个新的对象;
call,apply,bind 中的this被强绑定在指定的那个对象上;
箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this,要知道前四种方式,都是调
用时确定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就确定下来。
apply,call,bind都是js给函数内置的一些api,调用他们可以为函数指定this的执行,同时也可以传参。

14.js获取原型的方法?

p.proto
p.constructor.prototype
Object.getPrototypeOf(p)

15.什么是闭包,为什么要用它?

闭包是指有权访问另一个函数作用域内变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,
创建的函数可以访问到当前函数的局部变量。

闭包有两个常用的用途:
闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,我们可以通过在外部调用闭包
函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。

函数的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对
象的引用,所以这个变量对象不会被回收。
function a(){
var n = 0;
function add(){
n++;
console.log(n);
}
return add;
}
var a1 = a();注意,函数名只是一个标识(指向函数的指针),而()才是执行函数;
a1();//1
a1();//2 第二次调用n变量还在内存中

其实闭包的本质就是作用域的一个特殊的应用,只要了解了作用域链的创建过程,就能够理解闭包的实现原理。

16.什么使 DOM 和 BOM?

DOM:指的是文档对象模型,它指的是把文档当作一个对象来对待,这个对象主要定义了处理网页内容的方法和接口

BOM:指的是浏览器对象模型,指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和
接口。BOM的核心是window,而window对象具有双重角色,它既是通过js访问浏览器窗口的一个接口,又是一个
global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在
window对象含有 location 对象, navigatior对象,screen对象等子对象,并且DOM的最根本的对象
document对象也是BOM的window对象的子对象。

17.事件委托是什么?

事件委托本质上是利用浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件
对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的
事件,这种方式称为事件代理。

18.什么是事件传播?

事件传播有三个阶段:
捕获阶段--事件从window开始,然后向下到每个元素,直到到达目标元素
目标阶段--事件已到达目标元素。
冒泡阶段--事件从目标元素冒泡,然后上升到每个元素,直到到达window。

19.DOM操作--添加,移除,移动,复制,创建和查找节点

//创建新节点
createDocumentFragment() 创建一个DOM片段
createElement() 创建一个具体的元素
createTextNode() 创建一个文本节点

//添加,移除,替换,插入
appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)

//查找
getElementById()
getElementsByName()
getElementsByTagName()
getElementsByClassName()
querySelector()
querySelectorAll()

//属性
getAttribute(key)
setAttribute(key,value)
hasAttribute(key)
removeAttribute(key)

20.js数组和对象有哪些原生方法?

//数组
length()//设置或返回 数组中元素的数目
push()//想数组的末尾添加一个或多个元素,并返回新的长度,也就是添加元素后的数组长度
shift()//用于把数组的第一个元素从其中删除,并返回第一个元素的值
unshift()//向数组的开头添加一个或多个元素,并返回新的长度
pop()//用于删除并返回数组的最后一个元素
splice()//用于插入,删除或替换数组的元素
concat()//方法用于连接两个或多个数组
join()//用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的
toString()//把数组转换为字符串,并返回结果
reverse()//用于颠倒数组中的元素的顺序
slice()//方法可从已有的数组中返回选定的元素
sort()//用于对数组的元素进行排序(从小到大)
indexOf()//返回获取项在数组中的索引
lastIndexOf()//返回获取项在数组中出现的最后一次索引
forEach()//循环遍历数组参数是一个匿名函数 默认返回为undefined
map()//循环遍历数组 参数是一个匿名函数

//对象
chartAt()//返回在指定位置的字符
charCodeAt()//返回在指定的位置的字符的Unicode编码
concat()//连接字符串
indexOf()//检索字符串
match()//找到一个或多个正则表达式的匹配
replace()//替换与正则表达式匹配的子串
search()//检索与正则表达式相匹配的值
slice()//提取字符串的片段,并在新的字符串中返回被提取的部分
split()//把字符串分割为字符串数组
toLocaleLowerCase()//把字符串转换为小写
toLocaleUpperCase()//把字符串转换为大写
toLowerCase()//把字符串转换为小写
toUpperCase()//把字符串转换为大写
substr()//从起始索引号提取字符串中指定数目的字符
substring()//提取字符串中两个指定的索引号之间的字符

21.Ajax是什么?

//Ajax是一种异步通信的方法,通过直接由js脚本服务器发起HTTP通信,然后根据服务器返回的数据
更新网页的相应部分,而不用刷新整个页面的一种方法。

//创建步骤:
创建xhr--->配置Ajax请求地址--->发送请求--->监听请求,接受响应

//例如
//创建Ajax对象
var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP')
//配置Ajax请求地址
xhr.open('get','index.xml',true)
//发送请求
xhr.send(null);
//监听请求,接受响应
xhr.onreadystatechange = function(){
if(xhr.readyStates==4&&xhr.status==200||xhr.status==304){
console.log(xhr.responseXML)
}
}

//jquery写法
$.ajax({
type:'post',
url:'',
async:true
data:data,
dataType:'jsonp',
success:function(res){
},
error:function(err){

}
})

//promise封装
function getJSON(url){
//创建一个promise对象
let promise = new Promise(function(resolve,reject){
let xhr = new XMLHttpRequest();
//新建一个http请求
xhr.open('GET',url,true)
//设置状态的监听函数
xhr.onreadystatechange = function(){
if(this.readyState !==4)return;
//当请求成功或失败时,改变promise的状态
if(this.status===200){
reslove(this.response);
}else{
reject(new Error(this.statusText));
}
}
//设置错误监听函数
xhr.onerror = function(){
reject(new Error(this.statusText));
}
//设置响应的数据类型
xhr.responseType='json';
//设置请求头信息
xhr.setRequestHeader('Accept','application/json');
//发送http请求
xhr.send(null);
})
return promise
}

22.js延迟加载方式有哪些?

//js的加载,解析和执行会阻塞页面的渲染过程,因此我们希望js脚本能够尽可能地延迟加载,提高页面的
渲染速度。

//4种方法:
1.将jis脚本放在文档的底部,来使js脚本尽可能地在最后来加载执行
2.给js脚本添加defer属性,这个属性会让脚本地加载与文档的解析同步解析,然后在文档解析完成后再执行
这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了defer属性的脚本按规范来说最后是顺序执
行的,但是在一些浏览器中可能不是这样。
3.给js脚本添加async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后
立即执行js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个async属性的脚本的执行顺序使不可
预测的,一般不会按照代码的顺序依次执行。
4.动态创建DOM标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建script
标签来引入js脚本。

23.模块化开发的理解?

//一个模块是实现一个特定功能的一组方法。

24.js的几种模块规范?

//四种模块加载方案
第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports定义模块的输出接口。
这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地
磁盘,所以读取非常快,所以以同步的方式加载没有问题。

第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块模块的加载不影响后面语句的执行,所有依赖
这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js实现AMD规范。

第三种是 CMD 方案,这种方案和AMD方案都是为了解决异步模块加载的问题,sea.js实现CMD规范。它和
require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。

第四种方案是 ES6提出的方案,使用 import 和 export 的形式来导入导出模块。

25.requireJS 的核心原理是什么?

//原理是通过动态创建script脚本来异步引入模块,然后对每个脚本的load事件进行监听,如果每个脚本
都加载完成了,再调用回调函数。

26.js的运行机制?

//1.js单线程:同一时间只能做一件事情。

//2.js事件循环
js代码执行过程中会有很多任务,这些任务总的分成两类:
同步任务
异步任务
宏任务:script脚本的执行,setTimeout,setInterval,setImmediate一类的定时事件,还有如I/O
操作,UI渲染。
微任务:promise回调,node中的process.nextTick,对Dom变化监听的mutationObserver。

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

1.意外的全局变量
2.被遗忘的计时器或回调函数(设置了setInterval定时器,而忘记取消它)
3.脱离DOM的引用(如果后面这个元素被删除的话)
4.闭包

28.ES6的新特性?

块作用域

箭头函数
模板字符串
加强的对象字面
对象解构
promise
模块
symbol
代理(proxy)set
函数默认参数
rest和展开

29.var,let和const的区别是什么?

var声明的变量会挂载在window上,而let和const声明的变量不会。
var声明变量存在变量提升,let和const不存在变量提升。
let 和 const 声明形成块作用域。
同一作用域下let 和 const 不能声明同名变量,而var可以。
暂存死区。
const:一旦声明必须赋值,不能使用null占位,声明后不能再修改,如果声明的是复合类型数据,可以修改
其属性。

30.什么是Set对象?

Set对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
可以使用set构造函数来创建set实例:
const set1 = new Set();
const set2 = new Set(['a','b','c','d','e']);
我们可以使用add方法 向set实例中添加一个新值,因为add方法返回set对象,所以我们可以以链式的方式
再次使用add。如果一个值已经存在于set对象中,那么它将不再被添加。
set2.add('f');
set2.add('g').add('h').add('i').add('k').add('k');
//后面这个k不会被添加到set对象中,因为它已经存在了。

//使用has方法检查set实例中是否存在特定的值
set2.has('a');//true

//使用size属性获得set实例的长度
set2.size;//returns 10

//使用clear方法删除 set中的数据
set2.clear();

//使用set对象来删除数组中重复的元素
const numbers = [1,2,3,4,5,6,6,7,8,8,10];
const uniqueNums = [...new Set(numbers)];//[1,2,3,4,5,6,7,8,10];

31.什么是proxy?

proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程,即对编程语言进行
编程。

proxy可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供
了一种机制,可以对外界的访问进行过滤和改写。
//代理,用在这里表示由它来代理某些操作,可以译为代理器。

32.什么是高阶函数?

高阶函数只是将函数作为参数或返回值的函数。

function higherOrderFunction(param,callback){
return callback(param);
}

33.js的深浅拷贝?

//浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的
就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就
会影响到另一个对象。

深拷贝:将一个对象从内存中完整的拷贝一份处理,从堆内存中开辟一个新的区域存放新对象,且修改新对象
不会影响原对象。

浅拷贝的实现方式:
Object.assign()方法:用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Array.prototype.slice():slice()方法返回一个新的数组对象,这一对象是由begin和end(不包括end)
决定的原数组的浅拷贝。原始数据不会被改变。
拓展运算符`...`:
let a = {
name:'jack',
flag:{
title:'xxx',
time:'xxx'
}
}
let b = {...a}


深拷贝实现方式:
乞丐版:JSON.parse(JSON.stringify(data)),缺点诸多(会忽略undefined,symbol,函数,不能
解决循环引用,不能处理正则,new Date())
基础班:浅拷贝+递归(只考虑object和array类型)

 34.手写call,apply,bind函数

//call函数的实现步骤
1.判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用call等方式调用的情况。
2.判断传入上下文对象是否存在,如果不存在,则设置为window。
3.处理传入的参数,截取第一个参数后的所有参数。
4.将函数作为上下文对象的一个属性
5.使用上下文对象来调用这个方法,并保存返回结果
6.删除刚才新增的属性
7.返回结果
Function.prototype.myCall = function(context){
//判断调用对象
if(typeof this !=='function'){
console.error('type error');
}
//获取参数
let args = [...arguments].slice(1),result=null;
//判断context是否传入,如果未传入则设置为 window
context = context || window
//将调用函数设为对象的方法
context.fn = this;
//调用函数
result = context.fn(...args);
//将属性删除
delete context.fn;
return result;
}

//apply 函数的实现步骤:
1.判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用call等方式调用的情况
2.判断传入上下文对象是否存在,如果不存在,则 设置为window
3.将函数作为上下文对象的一个属性
4.判断参数值是否传入
5.使用上下文对象来调用这个方法,并保存返回结果
6.删除刚才新增的属性
7.返回结果
Function.prototype.myApply = function(context){
//判断调用对象是否为函数
if(typeof this !=='function'){
throw new TypeError('error');
}
let result = null;
//判断context是否存在,如果未传入则为window
context = context || window;
//将函数设为对象的方法
context.fn = this;
//调用方法
if(arguments[1]){
result = context.fn(...arguments[1]);
}else{
result = context.fn();
}
//将属性删除
delete context.fn;
return result;
}

//bind 函数的实现步骤
1.判断调用对象是否为函数
2.保存当前函数的引用,获取其余传入参数值
3.创建一个函数返回
4.函数内部使用apply来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的
this给apply调用,其余情况都传入指定的上下文对象。
Function.prototype.myBind = function(context){
//判断调用对象是否为函数
if(typeof this !=='function'){
throw new TypeError('error')
}
//获取参数
var args = [...arguments].slice(1),fn=this;
return function Fn(){
//根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn?this:context,args.concat(...arguments)
)
}
}

35.new操作符的实现

new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
1.创建一个空的简单JavaScript对象,即{}
2.链接该对象(即设置该对象的构造函数)到另一个对象,
3.将步骤1新创建的对象作为this的上下文
4.如果该函数没有返回对象,则返回this。
function objectFactory(){
var obj={};
var Constructor = [].shift.apply(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj,arguments);
return typeof ret==='object'?ret:obj
}

36.promise?

//promise 有以下几种状态:
pending:初始状态,既不是成功,也不是失败状态
fulfilled:意味着操作成功完成
rejected:意味着操作失败

37.async/await?

async/await 是一种建立在promise之上的编写异步或非阻塞代码的新方法,被普遍认为是js异步操作的
最终且最优雅的解决方案。相对于promise和回调,他的可读性和见解读都很高。

async function test(){
return '1'
}
console.log(test());// promise {<resloved>:'1'}

async function doit(){
console.log('doit')
const time1=300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${reslut}`);
}
//注意await关键字只能在async function 中使用。在任何非 aysnc function的函数中使用awiat
关键字都会抛出错误。

38.节流与防抖

防抖:指在事件被触发n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。避免用户多次点击向
后端发送多次请求。

节流:指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位
时间内某事件被触发多次,只有一次能生效。可以使用在scroll函数的事件监听上,通过事件节流来降低
事件调用的频率。

//防抖实现:
function debounce(fn,wait){
var timer = null;
return function(){
var context =this,
args = arguments;
//如果此时存在定时器的话,则取消之前的定时器重新计时
if(timer){
clearTimeout(timer);
timer = null;
}
//设置定时器,使事件间隔指定事件后执行
timer = setTimeout(()=>{
fn.apply(context,args);
},wait)
}
}

//节流实现
function throttle(fn,delay){
var preTime = Date.now();
return function(){
var context = this,args = arguments,nowTime=Date.now();
//如果两次时间间隔超过了指定时间,则执行函数
if(nowTime-preTime>delay){
preTime = Date.now();
return fn.apply(context,args);
}
}
}

注:摘自公众号前端教程

原文地址:https://www.cnblogs.com/sunnyeve/p/13265400.html