10、数组去重
参考:https://segmentfault.com/a/1190000016418021
Set 去重
const arr = [1, 2, 3, 3, 3, 2, 3, 4, 5, 4, 4]; const newArr = [...new Set(arr)]; // [ 1, 2, 3, 4, 5 ]
indexOf 去重
function unique(arr) { let newArr = []; for (let i = 0; i < arr.length; i++) { if (newArr.indexOf(arr[i]) === -1) newArr.push(arr[i]); } return newArr; }
splice 去重
function unique(arr) { for (let i = 0; i < arr.length; i++) { for (let j = i + 1; j < arr.length; j++) { if (arr[i] === arr[j]) { arr.splice(j, 1); j--; } } } return arr; }
includes 去重
function unique(arr) { const newArr = []; for (let i = 0; i < arr.length; i++) { if (!newArr.includes(arr[i])) { newArr.push(arr[i]); } } return newArr; }
filter 去重
function unique(arr) { return arr.filter(function (item, index, arr) { //当前元素,在原始数组中的第一个索引===当前索引值,否则返回当前元素 return arr.indexOf(item) === index; }); }
findIndex数组对象去重
function unique(arr) { // todo:待总结数组去重方法 // 方法一 return arr.filter((item, index) => { return arr.findIndex((child) => child.id === item.id) === index; }); }
Map数组对象去重
function unique(arr) { const res = new Map(); return arr.filter((item) => !res.has(item.id) && res.set(item.id, 1)); }
reduce 数组对象去重
function unique(arr) { let obj = {}; return arr.reduce((pre, item) => { obj[item.id] ? "" : (obj[item.id] = true && pre.push(item)); return pre; }, []); }
lodash 库数组和数组对象去重
import { isEqual, uniqWith, uniqBy } from "lodash"; let arr = [ { id: 1, name: "sli", year: 2012 }, { id: 2, name: "ap", year: 2015 }, { id: 1, name: "alslion", year: 2012 }, { id: 3, name: "pose", year: 2012 }, { id: 3, name: "pose", year: 2012 }, ]; // 根据id去掉相同的元素: console.log(uniqBy(arr, "id")); // 深检查数组每一项进行去重: console.log(uniqWith(arr, isEqual));
11、逻辑运算符 && 和 ||
||运算符: 条件 1 || 条件 2
- 若条件 1 为 true、返回条件 1
- 若条件 1 为 false、不管 || 后面是 true 还是 false、都是返回||后面的值、即则返回条件 2;
console.log(0 || ""); // '' console.log("" || 0); // 0
&&运算符: 条件 1 && 条件 2
- 若条件 1 为 false、就返回返回条件 1 的值;
- 若条件 1 为 true, 无论条件 2 为 true 或者 false, 都将返回条件 2 的值;
console.log(0 && ""); // 0 console.log("" && 0); // ''
12、new 的过程
分析一下 new 的整个过程:
- 1、创建一个空对象
- 2、this 指向 obj 空对象,并调用构造函数
- 3、继承构造函数的原型
- 4、返回对象
简单实现一下 new:
function myNew(fn, ...args) { // 第一步:创建一个空对象 const obj = {}; // 第二步:this指向obj 空对象,并调用构造函数 fn.apply(obj, args); // 第三步:继承构造函数的原型 obj.__proto__ = fn.prototype; // 第四步:返回对象 return obj; }
13、事件循环 Event Loop (结合着14条讲)
是什么?
首先,JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,如果前面一个任务耗时太长,后续的任务不得不等待,可能会导致程序假死的问题,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
在JavaScript
中,所有的任务都可以分为
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如
ajax
网络请求,setTimeout
定时函数等
同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。过程不断重复就是事件循环
14、async 和 await 宏任务 和 微任务
参考:https://www.mianshiya.com/qd/bf4a0bf261c7e2500090d9482499675f
宏任务 和 微任务
下面代码执行顺序是什么
console.log(1) // 同步 setTimeout(()=>{ // 宏任务 console.log(2) }, 0) new Promise((resolve, reject)=>{ console.log('new Promise') // 同步 resolve() }).then(()=>{ console.log('then') // 微任务 }) console.log(3) // 同步
1
=>'new Promise'
=> 3
=> 'then'
=> 2
1、什么是宏任务和微任务
Javascript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是:
微任务:
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
- Promise.then
- MutaionObserver
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务:
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
promise里面的代码是同步任务 promise的方法.then()等是异步任务 微任务
2、宏任务和微任务的执行顺序
每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务之后,再继续执行下一个宏任务。
1、微任务比宏任务的执行时间要早
2、微任务在DOM渲染之前执行,宏任务在DOM渲染之后执行
async与await
async
是异步的意思,await
是等待。所以可以理解async
就是用来声明一个异步方法,而await
是用来等待异步方法执行
async
async
函数返回一个promise
对象
await
正常情况下,await
命令后面是一个 Promise
对象,返回该对象的结果。如果不是 Promise
对象,就直接返回对应的值
不管await
后面跟着的是什么,await
都会阻塞后面的代码(加入微任务列表)
下面代码执行顺序是什么:
async function async1() { console.log('async1 start') await async2() console.log('async1 end') } async function async2() { console.log('async2') } console.log('script start') setTimeout(function () { console.log('settimeout') }) async1() new Promise(function (resolve) { console.log('promise1') resolve() }).then(function () { console.log('promise2') }) console.log('script end')
分析过程:
- 执行整段代码,遇到
console.log('script start')
直接打印结果,输出script start
- 遇到定时器了,它是宏任务,先放着不执行
- 遇到
async1()
,执行async1
函数,先打印async1 start
,下面遇到await
怎么办?先执行async2
,打印async2
,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码 - 跳到
new Promise
这里,直接执行,打印promise1
,下面遇到.then()
,它是微任务,放到微任务列表等待执行 - 最后一行直接打印
script end
,现在同步代码执行完了,开始执行微任务,即await
下面的代码,打印async1 end
- 继续执行下一个微任务,即执行
then
的回调,打印promise2
- 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印
settimeout
所以最后的结果是:script start
、async1 start
、async2
、promise1
、script end
、async1 end
、promise2
、settimeout
15、call、apply、bind 的区别
- 都可以改变
this
指向 - call 和 apply 会
立即执行
,bind 不会,而是返回一个函数 - call 和 bind 可以接收
多个参数
,apply
只能接受两个,第二个是数组
- bind 参数可以分多次传入
16、继承
首先,继承的是 属性 和 原型方法
Class 继承
参考:ES6:https://es6.ruanyifeng.com/#docs/class-extends
Class 可以通过
extends
关键字实现继承,让子类继承父类的属性和方法子类如果写
constructor()
就必须要写super()
,且要写在最前面,否则报错,只有super()
方法才能让子类实例继承父类。👊ps: ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用super()方法,子类就得不到自己的this对象。
class Parent { constructor(x,y) { this.x = x this.y = y ... } toString() { ... } } class Son extends Parent { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } }
ES5 继承
js 中有很多中继承的方式,不过每一种继承方式都有优缺点,重点掌握 ES5 继承,别的继承方式基本都是 ES5 继承的语法糖
先创造子类实例,通过Parent.call(this, arg1, arg2...)将父类的属性方法添加到this上,继承了父类的属性
再通过 Son.prototype = Object.create( Father.prototype )将父类的原型继承过来
最后可以通过Son.prototype.constructor = Son 将子类的原型指到子类身上
function Father(name) { this.name = name; } Father.prototype.get = function () { return "黑马"; }; // 继承的是 属性 和 原型方法 function Son(name) { Father.call(this, name); } // Son.prototype = new Father() // 相互影响 会存在一个 {name:undefined} // Object.create 创造出一个空对象 // 让当前对象的__proto__ 指向传入的对象 Son.prototype = Object.create(Father.prototype); Son.prototype.constructor = Son; const son = new Son("程序员"); console.log(son); console.log(son.get() + son.name); // 黑马程序员
17、原型链
什么是原型原型链?
每个函数都有一个 prototype 原型(原型就是对象),原型对象有一个 constructor 属性,指向的是构造函数
访问对象的某一个属性或者方法时,会从对象自身查找,如果查找不到,就会去原型链上去找,原型的最终目的就是让所有的实例能够共享其属性和方法
查找顺序:
自身 >__proto__> 构造函数的原型对象 >__proto__> Object 的原型对象 >__proto__> null
18、堆和栈 TODO ⭐⭐
参考:https://juejin.cn/post/6844903618999500808
19、ES6 Set 和 Map TODO ⭐⭐
20、includes
includes()
方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false
语法
arr.includes(valueToFind[, fromIndex])
valueToFind
需要查找的元素值。Note: 使用includes()
比较字符串和字符时是区分大小写。fromIndex
可选
从fromIndex
索引处开始查找valueToFind
。如果为负值,则按升序从array.length + fromIndex
的索引开始搜 (即使从末尾开始往前跳fromIndex
的绝对值个索引,然后往后搜寻)。默认为 0。
[1, 2, 3].includes(2); // true [1, 2, 3].includes(4); // false [1, 2, 3].includes(3, 3); // false [1, 2, 3].includes(3, -1); // true [1, 2, NaN].includes(NaN); // true
21、find
find()
方法返回数组中满足提供的测试函数的第一个元素的值。否则返回undefined
arr.find(callback[, thisArg])
callback
在数组每一项上执行的函数,接收 3 个参数:element
当前遍历到的元素。index
可选当前遍历到的索引。array
可选数组本身thisArg
可选
执行回调时用作this
的对象
find findindex indexOf includes filter
22、hasOwnProperty
参考:https://juejin.cn/post/6966053301615853582
hasOwnProperty
是Object.prototype的一个方法他能判断一个对象是否包含自定义属性而不是原型链上的属性
hasOwnProperty
是 JavaScript 中唯一一个处理属性但是不查找原型链的函数
23、JS如何判断两个对象是否相等
const a = { name: 'zs', age: 18, job: { sex: '男' } } const b = { name: 'zs', age: 18, job: { sex: '男' } }
JSON.stringify()转字符串
console.log(JSON.stringify(a) === JSON.stringify(b)) // true
缺陷:哪怕两个对象的内部数据相等,但只要每个数据对应的位置不同,其结果也为false
函数检测
const isEqual = (a, b) => { const aKeysList = Object.keys(a) const bKeysList = Object.keys(b) if (aKeysList.length !== bKeysList.length) { return false } for (const aKey in a) { if (a[aKey] !== b[aKey]) { return false } } return true } console.log(isEqual(a, b)) // true
缺陷:若对象当中嵌有引用类型数据,则此方法则不适用,需要进行改进
函数检测的基础上递归
const isEqual = (a, b) => { const aKeysList = Object.keys(a) const bKeysList = Object.keys(b) if (aKeysList.length !== bKeysList.length) { return false } for (const aKey in a) { if (typeof a[aKey] === 'object' || typeof b[aKey] === 'object') { if (!isEqual(a[aKey], b[aKey])) { return false } } else if (a[aKey] !== b[aKey]) { return false } } return true } console.log(isEqual(a, b)) // true
24、for···in和for···of的区别
参考:https://blog.csdn.net/weixin_43638968/article/details/109291957
首先一句话:(for···in取key,for··of取value)
①从遍历数组角度来说,for···in遍历出来的是key(即下标),for···of遍历出来的是value(即数组的值);
②从遍历字符串的角度来说,同数组一样。
③从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。
④如果要使用for…of遍历普通对象,需要配合Object.keys()一起使用。
25、创建对象的几种方式
参考:https://juejin.cn/post/6844904126233444360
字面量
const obj = { name: '张三', }
new Object
const obj2 = new Object()
构造函数
function Person(name, age) { this.name = name this.age = age sang() { console.log('唱歌') } } const p = new Person('张三', 22)
ES6 class
class Person { constructor(name, age) { // constructor构造函数 this.name = name this.age = age } sayname() { //原型上的 console.log(this.name) } } const per = new Person('dz', 23)
26、变量提升与函数提升
变量提升
将变量声明提升到它所在作用域的最开始的部分。
- 通过var定义(声明)的变量,在定义语句之前就可以访问到
- 值:undefined;
console.log(a); //undefined var a = 1;
因为有变量提升的缘故,上面代码实际的执行顺序为:
var a; console.log(a); a = 1;
函数提升
js中创建函数有两种方式:函数声明式和函数表达式
1、函数声明提升
js在执行之前,会把foo函数提升到最前面,所以我们在fun函数定义之前就可以使用fun函数。
fun(); function fun(){ console.log("aa"); }
打印结果为aa;说明以函数声明来定义函数时,可以在定义函数之前访问到定义的函数。
2、函数表达式提升
var fun = function() { console.log('函数表达式'); };
此种声明方式我们可以理解为一个普通变量的提升,在js代码执行之前会把fun提升带最前面,在函数赋值之前,fun是undefined,如果调用fun(),将会报错。
fun(); var fun = function (){ console.log("aa"); }
此时打印的结果为报错Uncaught TypeError: fun is not a function
,因为在js代码执行之前,会把fun提升到最前面,值为undefined,不是一个函数,以函数的形式来进行调用时将会报错。
27、暂时性死区
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
function fn() { console.log(a); // ReferenceError: Cannot access 'a' before let/const a = 78 } fn()
28、ES6新增了哪些语法
- class
- 箭头函数
- 解构赋值
- 字符串模板
- async/await
- 引入 module 模块
- generators(生成器)
- Map和Set
29、假如有10万条数据需要处理,前端应该怎么处理?
参考:https://juejin.cn/post/6844904184689475592
参考:https://juejin.cn/post/7101206944534233125#heading-29
思路:
- 采用懒加载+分页(前端维护懒加载的数据分发和分页)
- 使用虚拟滚动技术(目前react的antd4.0已支持虚拟滚动的select长列表)