八种:
闭包就是能够读取其他函数内部变量的函数。例如在JS中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成 “定义在一个函数内部的函数” 。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
举例:创建闭包最常见方式,就是在一个函数内部创建另一个函数。
下面例子中的closure 就是一个闭包
function func(){
var a = 1, b = 2;
fuction closure(){
return a + b;
}
return closure;
}
优点:
能够读取函数内部的变量;
让这些变量一直存在于内存中,不会在调用结束后被垃圾回收机制回收;
避免全局变量的污染
缺点:
由于闭包会使用函数中的变量存在在内存中,内存消耗很大,所以不能滥用闭包;解决的办法是退出函数之前,将不使用的局部变量删除;
核心:限制某一个方法被频繁触发,而一个方法之所以会被频繁触发,大多数情况下是因为 DOM 事件的监听回调。
**共同点:**都为防止事件频繁触发
不同点:
指在一定时间内,函数被触发多次,只执行一次(第一次或最后一次),将多次执行变为最后一次执行
应用场景:
例:1s之内的不触发事件,超过的再触发
function debounce(fn, delay) {
let timer;
return function() {
if(timer !== null){
clearTimeout(timer) //如果上一次有值,则清除上一次延时器
}
timer = setTimeout(function() {
fn.apply(this,arguments)
}, delay)
}
}
let inp = document.querySelector('.username')
inp.oninput = debounce(()=>{
console.log(inp.value);
},500)
原理:
将多次执行变成每隔一段时间执行调用一次函数,而不是一触发事件就调用一次,这样就会减少资源浪费。
应用场景:
需要间隔一定时间触发回调来控制函数调用频率
窗口调整、页面滚动、抢购和疯狂点击
DOM 元素的拖拽功能实现(mousemove)
搜索联想(keyup)
计算鼠标移动的距离(mousemove)
Canvas 模拟画板功能(mousemove)
射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次
function throttle(fn, delay) {
let lastTime = 0;
return function () {
var nowTime = Date.now(); // 记录当前函数触发的时间
if (nowTime - lastTime > delay) {
fn.call(this); // 修正this指向问题
lastTime = nowTime; // 同步时间
}
}
}
document.onscroll = throttle(function () {
console.log('scroll事件被触发了' + Date.now())
}, 1000)
cookie和token没可比性
cookie一般指存储方式 token属于校验规则
cookie是种客户端存储信息的方式,而token是信息本身,不是一个纬度
token本质上就是个验证身份的方式,应该和session这种方式比,一个不需要后端存储,一个需要;一个天然适合分布式,一个需要做额外工作。
问:token能使用localstorage保存吗?
答:一般都是这样做的token存在cookie里就按照cookie的方式处理,存在storage里就需要手动注入来携带
多数情况,SessionId放到cookie里,但一样可以放到localstorage中
[ 评论区 ]:
我试过跨域拿cookie,有浏览器兼容性问题,后面都改成localstorage存了
这问题我也常常喜欢问求职者,只要对方能回答出来,cookie是一种客户端存储介质,token是认证机制的生成物,就过关了。要是能解释出来为什么需要把token放在cookie中,还可以放在哪里,token是如何生效的,为什么要用token来携带认证信息(HTTP的无状态性),等等就是加分项。
this的最终指向的是那个调用它的对象。
改变this指向的方法:
_this=this;
这三个方法的作用都是改变函数的执行上下文,换句话说就是改变函数体内部的this指向,以此来扩充函数依赖的作用域
call:xxx.call(对象名,参数1,参数2,...)
call可以改变this的指向,并且直接调用该函数,第一个参数是this的指向,第二个参数及其以后的参数都是想要传递的参数数据。
function test(a,b){
console.log(this);
console.log(a + b);
}
test(1,2); // window 3
var obj = { name:'lsj'};
window.test.call(obj,3,5); // {name:'lsj'} 8
apply:xxx.apply(对象名,[...])
作用:和call方法一样是修改内部的 this 指向的,区别在于apply的第二个参数必须是一个数组(部署了Iterator接口的类数组对象也是可以的)
function test(a,b){
console.log(this);
console.log(a + b);
}
test(1,2); // window 3
var obj = {name:'lsj'};
window.test.call(obj,[3,5]); // {name:'lsj'} 8
bind: xxx.bind(对象名,参数1,参数2,...)
作用:也是用于改变this的指向,传参与call一样
例子:未使用bind方法前,foo()中的this指向window,使用后指向obj对象
var obj = {key:"value"}
var foo = function(){
console.log(this)
}
foo.bind(obj)() // obj
区别:(三者的第一个参数都是this需要指向的对象)
应用场景:
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
//Array.from(object)可以从具有length属性或可迭代对象的任何对象返回Array对象
//...用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
var arr = [1,2,2,2,3,4,4,5,6,6,7]
let newArr = [...new Set(arr)] //...ES6展开符
let newArr = Array.from(new Set(arr)) //Array.from
console.log(newArr)
let arr = [1, 2, 2, 2, 3, 4, 2, 7, 4, 5, 6, 6, 7]
let newArr = []
arr.forEach((item,index) => {
if (newArr.indexOf(item) == -1) //indexOf - 新数组找不到的加进去
if (arr.indexOf(item) == index //indexOf - 老数组中找到第一个就加进去
if (!newArr.includes(item)) //includes
{
newArr.push(item)
}
})
console.log(newArr);
问题:要拷贝一个内容会变化的数组,使用了=赋值,slice(),concat()方法都不行,修改了原数组后拷贝数组也变了。
原因是这个数组内容是object,而object是引用类型,需要使用深拷贝
最后使用var newArr = JSON.parse(JSON.stringify(arr));
解决
string boolean number null undefined object(Array function data) bigInt symbol
如果改变的值是基本类型(或是整个对象),就不会影响原数组
但如果改变的值是引用类型,如obj.name,对其进行增删改,会影响原数组
浅拷贝1:Object.assign
const newObj = Object.assign({}, oldObj);
浅拷贝2:arr.slice()
const newArr = oldArr.slice();
浅拷贝3:arr.concat()
const newArr = oldArr.concat();
浅拷贝4:es6展开运算符
const newObj = {...oldObj}
const newArr = [...oldArr]
使用JSON.stringify和JSON.parse,通过JSON.stringify转化成字符串再通过JSON.parse()解析成原数组
不仅可拷贝数组还能拷贝对象(但不能拷贝函数)
const newObj = JSON.parse(JSON.stringify(oldObj));
const newArr = JSON.parse(JSON.stringify(oldArr));
缺点:
JSON.stringify()有一些局限,比如对于RegExp类型和Function类型则无法完全满足,而且不支持有循环引用的对象。
解决: 可以通过lodash库解决
const newArr = _.cloneDeep(oldArr);
宏任务与微任务之间的执行顺序(同步任务->微任务->宏任务)
宏任务是由宿主(浏览器)发起的,而微任务由JavaScript自身发起。
在ES3以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。在ES5之后,JavaScript引入了Promise,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。
宏任务 | 微任务 | |
---|---|---|
发起者 | 宿主(Node、浏览器) | JS引擎 |
具体事件 | script/ setTimeout/ setInterval / setImmediate等 | Promise.then |
区别1. 变量提升
区别2. 作用域不同(let cosnt是块级作用域)
区别3. 重复声明
PS. let与const区别
Promise ,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise本身是同步,then的内容是异步:
Promise很好地解决了回调地狱的问题,它包含三种状态:pending、fulfilled(resolved)、rejected。