目录
2)Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由 pending变成fulfilled 或者 由pending变成rejected
跳转查看 ES6、7、8、9最常用的新特性_Joker_Yang*的博客-CSDN博客
2、利用for嵌套for,然后splice去重(ES5中最常用)
6、利用hasOwnProperty(PS:所有的都去重了)
JavaScript
一、Promise的理解
Promise 是一种为了避免回调地狱的异步解决方案。Promise 是一种状态机: pending(进行中)、fulfilled(已成功)和rejected(已失败)只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
回调地狱:回调函数中嵌套回调函数的情况就叫做回调地狱。
回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。
1、Promise是什么?
Promise是最早由社区提出和实现的一种解决异步编程的方案,比其他传统的解决方案(回调函数和事件)更合理和更强大。
ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
2、Promise是为解决什么问题而产生的?
Promise是一种用于异步编程的JavaScript对象,它可以解决回调地狱的问题并提供更好的代码可读性和可维护性。
3、Promise的两个特点
1)Promise对象的状态不受外界影响
① pending 初始状态
② fulfilled 成功状态
③ rejected 失败状态
Promise 有以上三种状态,只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态
2)Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由 pending变成fulfilled 或者 由pending变成rejected
4、Promise的三个缺点
1)无法取消Promise,一旦新建它就会立即执行,无法中途取消
2)如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
3)当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成
5、Promise在哪存放成功回调序列和失败回调序列?
1)onResolvedCallbacks 成功后要执行的回调序列 是一个数组
2)onRejectedCallbacks 失败后要执行的回调序列 是一个数组
以上两个数组存放在Promise 创建实例时给Promise这个类传的函数中,默认都是空数组。
每次实例then的时候传入onFulfilled成功回调、onRejected 失败回调。如果此时的状态是pending,则将onFulfilled和onRejected push到对应的成功回调序列数组和失败回调序列数组中;如果此时的状态是fulfilled,则onFulfilled立即执行;如果此时的状态是rejected,则onRejected立即执行。
上述序列中的回调函数执行的时候 是有顺序的,即按照顺序依次执行
二、箭头函数和普通函数的区别
区别在于
1、箭头函数没有this,所以需要通过查找作用域链来确定this的值,这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this;
2、箭头函数没有自己的arguments对象,但是可以访问外围函数的arguments对象;
3、不能通过new关键字调用,同样也没有new.target值和原型;
1、语法更加简洁、清晰;
2、箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this;
3、箭头函数继承而来的this指向永远不变;
4、.call()、.apply()、.bind() 无法改变箭头函数中this的指向;
5、箭头函数不能作为构造函数使用;
6、箭头函数没有自己的arguments,可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表;
7、箭头函数没有原型 prototype;
8、箭头函数不能用作Generator函数,不能使用yeild关键字;
9、箭头函数不具有super,不具有new.target;
三、ES6新特性
跳转查看 ES6、7、8、9最常用的新特性_Joker_Yang*的博客-CSDN博客
四、实现继承的几种方式
1、原型链继承
父类的实例作为子类的原型
function Woman() {
}
Woman.prototype = new People();
Woman.prototype.name = '秋雅';
let womanObj = new Woman();
优点:简单易于实现,父类的新增的实例与属性子类都能访问
缺点:可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面;无法实现多继承;创建子类实例时,不能向父类构造函数中传参数
2、借用构造函数继承(伪造对象、经典继承)
复制父类的实例属性给子类
function Woman(name) {
//继承了People
People.call(this); // People.call(this,'秋雅');
this.name = name || 'renbo'
}
let womanObj = new Woman();
优点:解决了子类构造函数向父类构造函数中传递参数;可以实现多继承(call或者apply多个父类)
缺点:方法都在构造函数中定义,无法复用;不能继承原型属性/方法,只能继承父类的实例属性和方法
3、实例继承(原型式继承)
function Wonman(name) {
let instance = new People();
instance.name = name || '秋雅';
return instance;
}
let wonmanObj = new Wonman();
优点:不限制调用方式,简单,易实现
缺点:不能多次继承
五、Null 和 undefined 的区别
undefined 表示一个变量没有被声明,或者被声明了但没有被赋值(未初始化),一个没有传入实参的形参变量的值为undefined,如果一个函数什么都不返回,则该函数默认返回undefined。
null 则表示"什么都没有",即"空值"。Javascript将未赋值的变量默认值设为 undefined ;Javascript从来不会将变量设为 null 。它是用来让程序员表明某个用var声明的变量时没有值的;
六、call、bind、apply的区别
apply方法
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
call方法
call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
bind方法
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
七、前端缓存的理解 或者 前端数据持久化的理解
前端缓存分为HTTP缓存和浏览器缓存。其中HTTP缓存是在HTTP请求传输时用到的缓存,主要在服务器代码上设置;而浏览器缓存则主要由前端开发在前端js上进行设置。
缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种,如下:
①不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求
②存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存
③存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
①协商缓存生效,返回304
②协商缓存失效,返回200和请求结果
八、防抖和节流
防抖(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
节流(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。**节流会稀释函数的执行频率。对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版
九、闭包
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值
十、数组去重
1、利用ES6 Set去重(ES6中最常用)
function unique(arr) {
return Array.from(new Set(arr))
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。
2、利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) { //第一个等同于第二个,splice方法删除第二个
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {}, {}]
// NaN和{}没有去重,两个null直接消失了
双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。
3、利用indexOf去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array.indexOf(arr[i]) === -1) {
array.push(arr[i])
}
}
return array;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {}, {}]
// NaN、{}没有去重
新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。
4、利用sort()
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
arr = arr.sort()
var arrry = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {}, {}, "a", false, null, true, "true", undefined]
// NaN、{}没有去重
利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。
5、利用includes
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
// includes 检测数组是否有某个值
if (!array.includes(arr[i])) {
array.push(arr[i]);
}
}
return array
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
// {}没有去重
6、利用hasOwnProperty(PS:所有的都去重了)
利用hasOwnProperty 判断是否存在对象属性
function unique(arr) {
var obj = {};
return arr.filter(function (item, index, arr) {
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}]
// 所有的都去重了
7、利用filter
function unique(arr) {
return arr.filter(function (item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {}, {}]
8、利用递归去重
function unique(arr) {
var array = arr;
var len = array.length;
array.sort(function (a, b) { //排序后更加方便去重
return a - b;
})
function loop(index) {
if (index >= 1) {
if (array[index] === array[index - 1]) {
array.splice(index, 1);
}
//递归loop,然后数组去重
loop(index - 1);
}
}
loop(len - 1);
return array;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {}, null, NaN, NaN, "NaN", 0, "a", {}, undefined]
9、利用Map数据结构去重
function arrayNonRepeatfy(arr) {
let map = new Map();
// 数组用于返回结果
let array = new Array();
for (let i = 0; i < arr.length; i++) {
// 如果有该key值
if (map.has(arr[i])) {
map.set(arr[i], true);
} else {
// 如果没有该key值
map.set(arr[i], false);
array.push(arr[i]);
}
}
return array;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))
// [1, "a", "true", true, 15, false, 1, {}, null, NaN, NaN, "NaN", 0, "a", {}, undefined]
创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。