以下复习内容分成多个模块,是我自己的用的,你们不要看,我还没写完
JS部分
JS基础
- js的基本数据类型/引用数据类型,作用是什么呢。
基本数据类型
1.String,基本字符串类型 比如:'word', '1'
2.Number,数字类型 1,2
3.Boolean,布尔值类型, true,false
4.Null,空类型 typeof null === 'object'
5.Undefined,未定义类型, typeof undefined === 'undefined'
6.Symbol 唯一的值类型,typeof Symbol() === 'symbol'
7.BigInt 可以表示任意大的整数,大于2^53 - 1 typeof BigInt('10') === 'bigint'
引用数据类型/复杂数据类型
1.Object,{name: 'xxx'}
2.Array, ['str', 1]
3.Function,function(){}
- 说一下你对原型/原型链的理解。
从以下几点回答:
1. 开局一张图。
2. 理解几个名词:
构造函数(`function Person(){}`,Person叫做构造函数),
原型对象(Person.prototype),
实例(let f1 = new Person(), f1叫做实例)
3. 两个相等原则 Person.prototype.constructor == Person,f1.__proto__ == Person.prototype。
last answer: 实例,原型,构造函数都可以通__proto__关联起来,这种关系叫做原型链。
查找某个实例的属性可以通过原型链一级一级向上去找,直到Object.prototype =》null
- 判断数据类型的几种方法。
1. 基本数据类型可以通过typeof判断。
2. 也可以通过instanceof判断 [1,2] instanceof Array
3. Object.prototype.toString.call([1,2]).slice(8, -1)
4. [1,2].constructor
5. Array.isArray([])
- 变量提升,var/let/const的区别。
var存在变量提升,let/const不存在
let/const是块级作用域
const定义常量不能修改,在初始化的时候必须赋值
- this指向的问题。
1.this的指向,在普通函数中谁调用指向谁。
2.在箭头函数中,this的指向决定于外层函数。
3.call,apply,bind可以改变this的指向。
- 箭头函数和普通函数的区别。
1.箭头函数没有this指向的问题。
2.箭头函数不能作为构造函数。
3.箭头函数没有arguments。
- js的隐式转换的方法。
toString() 返回表示该对象的字符串,当对象被期望已字符串形式处理的时候,自动调用。
valueOf() 返回对象的原始值。
== 类型转换之后进行比较
!= 类型转换之后比较
!! 取反在取反 返回布尔值类型
~~ 对于浮点数,~~value可以代替parseInt(value)
~ 类似 arr.indexOf(e) > -1 === ~arr.indexOf(e)
- null和undefined的区别。
null 是空类型。在内存里的表示就是,栈中的变量没有指向堆中的内存对象。
可以在变量使用完成之后手动设置null,释放内存。
undefined (1)声明变量没有赋值。(2)访问了不存在的属性,或不存在的变量。(3)没有返回值的函数。
(4)使用void 处理表达式,void [] === undefined
- addEventLinstenr的第三个参数。
target.addEventListener(type, listener, options);
- type 表示监听事件类型的字符串
- listener 事件的回调参数
- options 可选参数配置 capture 捕获还是委托,once 只调用一次, passive为true不会调用preventDefault()
- 如何阻止事件冒泡。
e.stopPropagation()- 参考页面
- js继承的几种理解和区别。
- 原型链继承
function Person(){
this.colors = ['red', 'orange']
}
function Student(){}
Student.prototype = new Person()
let s1 = new Student()
s1.colors.push('yellow')
let s2 = new Student()
console.log(s2.colors)
特点:多个实例对引用类型的操作会被篡改,无法传参
- 构造函数继承
function Person(name){
this.name = name
}
Person.prototype.sayName = function(){
console.log(`我是:${this.name}`)
}
function Student(...arg){
Person.apply(this, arg)
}
Student.prototype.getName = function(){
console.log(`names:${this.name}`)
}
let s1 = new Student('李四')
s1.sayName() // Uncaught TypeError: s1.sayName is not a function
特点:无法调用父类构造函数上的原型方法,可以传参,父类的引用属性不会被共享。
- 组合继承
function Person(name){
this.name = name
this.colors = ['red', 'orange']
}
Person.prototype.sayName = function(){
console.log(`我是:${this.name}`)
}
function Student(){
Person.apply(this, arguments)
}
Student.prototype = new Person()
Student.prototype.constructor = Student
let s1 = new Student('李四')
s1.colors.push('blue')
let s2 = new Student('王五')
console.log(s2.colors) // ['red', 'orange']
s2.sayName() // 我是:王五
特点:结合两者的优点,可以传参数,可以调用父类原型的方法,父类的引用属性不会被共享。
调用了两次父类方法,会在__proto__上存在相同的属性
- 原型式继承
function create(O){
function F(){}
F.prototype = O
return new F()
}
const Person = {
friends: ['a', 'b']
}
consnt p1 = create(Person)
p1.friends.push('c')
- 寄生式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
const Person = {
friends: ['a', 'b']
}
function createStudent(o){
const p = object(o);
p.sayName = function(){
console.log(`我是:${this.name}`)
}
return p;
}
const s1 = createStudent(Person)
s1.friends.push('c')
const s2 = createStudent(Person)
console.log(s2.friends)
- 寄生式组合继承
function create(o){
function F(){}
F.prototype = o
return new F()
}
function insertPrototype(child,parent){
const p = create(parent.prototype)
p.constructor = child
child.prototype = p
}
function Person(name){
this.name = name
this.friends = ['a', 'b']
}
function Student(...arg){
Person.call(this,arg)
}
const s1 = new Student('张三')
s1.friends.push('c')
const s2 = nnew Student('李四')
console.log(s2.frindes)
特点:综合了以上的优点,多个实例之间不会影响属性方法,不会影响父类的引用数据类型,调用一次构造方法
- Class继承
class Person {
constructor(name){
this.name = name
}
sayName(){
console.log(`我是:${this.name}`)
}
}
class Student extends Person{
constructor(name, age){
super(name)
this.age = age
}
sayAge(){
console.log(`我的年龄是:${this.age}`)
}
}
const s1 = new Student('张三', 18)
- Map和Object的区别。
- Map: 是一种类似对象的数据结构,但不局限于对象的key只能是字符串
const obj = Object.create({})
obj.name = 'zhangsan'
const map1 = new Map()
map1.set(obj,'张三')
map1.set(111,'张三')
console.log(map1)
console.log(map1.get(obj))
console.log(map1.has(obj))
console.log(map1.size)
console.log(map1.delete(111))
console.log(map1)
- Object:是一种复杂数据类型,由key和value的键值对数据格式组成 MDN参考
- Set和Array的区别。
- Set是一种类似数组的数据结构,和数组的区别是值具有唯一性。经常用于数组的去重。
const set1 = new Set()
console.log(set1)
set1.add(1)
set1.add('1')
console.log(set1)
set1.delete(1)
console.log(set1)
console.log(set1.has(1))
console.log(set1.size)
console.log(set1)
- Array是数组结构。需要理解一下api的使用。 参考MDN
api很多,按照一定的标准分一下类
#### 返回iterator
- entries() Array Iterator对象,该对象包含数组中每个索引的键/值对。
- values() 方法返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值
- keys() 方法返回一个包含数组中每个索引键的Array Iterator对象。
#### 遍历查找满足条件
- every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值
- filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
- find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
- findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。
- includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。
- indexOf() 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
- lastIndexOf() 方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,
如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始
- some() 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。
#### 更改数组的长度
- pop()
- push()
- shift()
- unshift()
- concat()
- splice()
- reverse()
#### 循环数组做更多处理
- map()
- forEach()
- reduce()
- reduceRight()
- js中任务执行的的几种方法。异步任务都有哪些?说说你的理解。
- 同步执行:比如去火车站买票,只有一个窗口,这样只能同时一个人去购买,接下来的人等待,也就是阻塞其它任务。
- 异步执行:比如还在火车站买票,有人在买着,你可以先去做别的事情,等到你了你在执行,不会阻塞任务,可以通过回调函数的方式进入执行队列。
- 异步任务有如下几个:
- 定时任务
setTimeout,setInterval - 网络请求
ajax - 事件监听器
addEventListener
- 定时任务
- 对网络请求的异步任务我们有如下几种实现方式
- 回调函数
function request({url, method, data, success, error}){ let xhr = new XMLHttpRequest() xhr.open(method, url) xhr.send(data) xhr.onreadystatechange = function (){ if(xhr.readystate === 4){ if(xhr.status>=200 && xhr.state<=300){ success(JSON.parse(xhr.responseText)) } eles { error(xhr.statusText) } } } } request({ url: `http://localhost:3001/api/getList?id=${id = 1}`, method: 'get', data: {id: 1}, success: (res) => { console.log(res) // 如果使用回调函数 request({ url: `http://localhost:3001/api/getList?id=${id = 2}`, method: 'get', data: {id: 2}, success: (res) => { console.log(res) // ...依次嵌套形成了回调地狱 } }) } })- promise
const promise = new Promise((resolve, reject) => { resolve('success') }) promise.then(data=> { return data + '1' }).then(data=> { return data + '2' }) 具体详情的作用可以看下面的api实现中- generator
1. 通过带*的函数创建方式一样创建 generator函数。比如: function *getList(){ yiled 1 yiled 2 } 2. Generator是ES6提出的一种异步编程的方案。因为手动创建一个iterator十分麻烦, 因此ES6推出了generator,用于更方便的创建iterator。 也就是说,Generator就是一个返回值为iterator对象的函数 3. 可以使用 for of来遍历iterator对象比如: const arr = [1,2,3] const iterator = arr.entries() for(let value of iterator){ // value: [0,1] // value: [1,2] // value: [2,3] } 原生具备iterator接口的数据结构如下: Array/String/Map/Set/arguments/NodeList 这些数据结构都有一个Symbol.iterator属性,可以直接通过这个属性来直接创建一个迭代器 const arr1 = [1,2,3] const arrIterator = arr1[Symbol.iterator]() console.log(arrIterator.next()) 4. generaotr函数返回的就是一个Iterator对象。 使用要点 - 不能在箭头函数中使用yield - yield表达式只能用在 Generator函数里面 - 不能在return后使用yield语句 作用:用同步函数的写法实现异步- async/await
async/await是Generator函数的语法糖,就是一种封装好的简介写法。 本质上还是用Promise来实现的。 好处: - 自带执行器,不用想Generator函数一样需要手动调用next() - 更加清楚,代码简洁 - await后跟的不一定是Promise async function getList() { const data = await xhrHttpRequest({ url: `http://localhost:3001/list/?id=2&num=${id}`, method: 'get' }) const childId = (JSON.parse(data))?.['data']?.childId const res = await xhrHttpRequest({ url: `http://localhost:3001/detail/?id=${childId}`, method: 'get' }) console.log(res) }
- for/for in /for of/ map/ forEach 有什么区别。
- for循环,最基本的遍历数据的方式
let str = '';
for (let i = 0; i < 9; i++) {
str = str + i;
}
注意点:
一般是用来遍历对象
不建议用来遍历数组,但是可以遍历
const arr1 = [1,2,3]
arr1.prototype.a = 4
for(let i in arr1){
console.log(arr1[i])
}
- for...of在可迭代对象(包括
Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
不能遍历对象
以上的意思通俗讲就是可以遍历Iterator对象
for(let value of new Array(1,2,3) || new Map([1,2,3]) || new Set([1,2,3])){
console.log(value)
}
- map是数组的api,用来循环处理数组中的元素,返回一个新的数组
const arr1 = [1,2,3].map(el => {
return el * 2
})
- forEach,数组的api,和map不同的是,他每次调用返回的都是undefined,并且不会影响原数组。
特点如下:
1. 除了抛出异常以外,没有办法中止或跳出forEach()循环
2. 每次都是返回undefined
- 位运算符的理解。 MDN文档-表达式与运算符
&位与运算符|位或运算符~位非运算符^位异或<<位右移>>位左移
JS部分API的理解
- Array的一些常用api
- slice和splice的区别
- slice不会改变原数组
- slice常用于数组的浅拷贝
const arr1 = [1,2,3]
console.log(arr1.slice(0, 3))
slice(start, end)
- splice会改变原数组
- 经常用来删除数组中的指定元素,返回删除的数组
const arr2 = [1,2,3]
console.log(arr2.splice(1,2))
- 创建数组的方式都有哪几个
- new Array(1,2,3)
- [1,2,3]
- Array.from(NodeList)
- 遍历数组的方法
- map() 返回新数组,不改变原数组
- forEach() 返回undefined,不改变原数组
- 数组断言的方法
- some()
- every()
- filter()
- 查找数组中某些元素的方法
- includes()
- indexOf()
- lastIndexOf()
- Object的属性判断/遍历
- new Map()的遍历,取值等api
- new Set()
- Promise/all/race/allSetlled
- indexOf/includes的区别
- 数组的交并差
JS手写
- 几种继承的手写实现。
- 原型链继承
function Person(name, age) {
this.name = name;
this.age = age;
this.fruit = ['apple', 'banana', 'orange'];
}
Person.prototype.sayName = function () {
console.log(this.name);
}
function Student(name, age, grade) {
this.name = name
this.age = age
this.grade = grade;
}
Student.prototype = new Person();
let s1 = new Student('张三', 18, '一年级');
s1.fruit.push('pear');
let s2 = new Student('李四', 19, '二年级');
console.log(s2.fruit)
- 构造函数继承
function Person(name){
this.name = name
}
Person.prototype.sayName = function(){
console.log(`我是:${this.name}`)
}
function Student(...arg){
Person.apply(this, arg)
}
Student.prototype.getName = function(){
console.log(`names:${this.name}`)
}
let s1 = new Student('李四')
s1.sayName()
- 组合继承
function Person(name){
this.name = name
this.colors = ['red', 'orange']
}
Person.prototype.sayName = function(){
console.log(`我是:${this.name}`)
}
function Student(){
Person.apply(this, arguments)
}
Student.prototype = new Person()
Student.prototype.constructor = Student
let s1 = new Student('李四')
s1.colors.push('blue')
let s2 = new Student('王五')
console.log(s2.__proto__)
s2.sayName()
- 原型式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
const Person = {
friends: ['a', 'b']
}
const p1 = object(Person)
p1.friends.push('c')
const p2 = object(Person)
console.log(p2.friends)
- 寄生式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
const Person = {
friends: ['a', 'b']
}
function createStudent(o){
const p = object(o);
p.sayName = function(){
console.log(`我是:${this.name}`)
}
return p;
}
const s1 = createStudent(Person)
s1.friends.push('c')
const s2 = createStudent(Person)
console.log(s2.friends)
- 寄生组合式继承
function create(o){
function F(){}
F.prototype = o;
return new F();
}
function insertPrototype(subType, superType){
const p = create(superType.prototype)
p.constructor = subType
subType.prototype = p;
}
function Person(name){
this.name = name
this.friends = ['a', 'b']
}
function Student(){
Person.call(this, [...arguments])
}
insertPrototype(Student, Person)
const s1 = new Student('张三')
s1.friends.push('c')
const s2 = new Student('李四')
console.log(s2.friends)
- Class继承
class Person {
constructor(name){
this.name = name
}
sayName(){
console.log(`我是:${this.name}`)
}
}
class Student extends Person{
constructor(name, age){
super(name)
this.age = age
}
sayAge(){
console.log(`我的年龄是:${this.age}`)
}
}
const s1 = new Student('张三', 18)
- call/apply/bind的手写实现。
- call
Function.prototype._call = function (context = window) {
let fn = Symbol()
const arg = [...arguments].slice(1)
context[fn] = this
const result = context[fn](...arg)
delete context[fn]
return result
}
- apply
Function.prototype._apply = function (context = window, arg) {
if(!Array.isArray(arg)) throw new Error('参数必须是array')
let fn = Symbol()
context[fn] = this
const result = context[fn](arg)
delete context[fn]
return result
}
- bind
Function.prototype._bind = function (context = window){
const fn = function (){}
const arg = [...arguments].slice(1)
let _this = this
let result = function (){
let arg1 = [...arguments]
return _this.apply(this instanceof fn ? this : context, arg.concat(arg1))
}
fn.prototype = this.prototype
result.prototype = new fn()
return result
}
- new的作用及手写。
- 在new一个对象的时候都干了什么事情。
- 创建一个空对象
- 把空对象的__proto__绑定到构造函数的prototype
- 绑定this
- 返回结果:判断返回是在对象的原型上否
const _new = (fn,...arg) => {
if(typeof fn !== 'function') return false
let obj = {}
obj.__proto__ = fn.prototype
let result = fn.apply(obj, arg)
return result instanceof Object ? result : obj
}
- instanceof的作用以及手写。
const _instanceof = (value, type) => {
let left = value.__proto__;
let right = type.prototype;
while (true){
if(left === null){
return false
}
if(left === right){
return true
}
left = left.__proto__
}
}
- 防抖和节流的区别以及实现。
### 使用场景
- 浏览器的重绘和回流都会消耗一些性能,如DOM节点频繁的改变会造成卡顿等状态,浏览器窗口的缩小放大。
- 频繁发起相同网络请求也会造成服务器性能开销,比如做输入框的联想搜索
- 其实还有很多其它场景不在一一举例。
### 概念的理解
- 防抖--在事件触发n秒后执行回调,如果n秒内再次触发则重新计时。
- 节流--在规定时间内事件只会触发一次,如果多次触发只有一次生效。
## 相同和不同
- 相同:都是降低了高频事件的执行。
- 不同:防抖是重新计时,节流是不在执行
## 防抖的实现和应用
联想搜索,浏览器窗口的缩放
计时器版本
const debounce = (fn, time, immediate) => {
let timer = null
return (...arg) => {
if(timer) clearTimeout(timer)
if( immediate&& !timer){
timer = setTimeout(null, time)
fn.apply(this, arg)
} else {
timer = setTimeout(()=> {
fn.apply(this, arg)
}, time)
}
}
}
## 节流的实现和应用
浏览器的滚动,按钮的避免频繁点击
const throttle = (fn, time, immediate) => {
let timer = null, start = 0;
return (...arg) => {
if(immediate){
let now = new Date()
if(now - start > time){
fn.apply(this, arg)
start = now
}
} else {
if(!timer){
timer = setTimeout(() => {
timer = null
fn.apply(this, arg)
}, time)
}
}
}
}
- 函数的柯里化的应用场景以及实现。
#### 柯里化的理解
1. 概念是将一个有多个参数的函数转化为单个参数的函数。
2. 柯里化函数返回的是一个函数。
2. 柯里化本质上是降低通用性,提高适用性。
#### 柯里化的应用
1. 参数的复用
- ES6通用实现
const curry = fn => {
const next = (...arg) => {
if(fn.length === arg.length) return fn(...arg)
return (...arg1) => {
return next(...arg, ...arg1)
}
}
return next
}
- Demo
const checkTelNumber = (reg , str) => {
return reg.test(str)
}
const mobileReg = /^1\d{10}$/
const _check = curry(checkTelNumber)
const _checkMobile = _check(mobileReg)
console.log(_checkMobile(11581239162))
- Promise的理解以及和async的区别,以及几个常用api的手写。
- 在使用new Promise()的时候传入的是一个执行器executor,executor接受两个参数
resolve,reject,并且立即执行。
- 我们都知道的Promise有三个状态
pending
fulfilled
rejected
状态只能从 pending =》fulfilled
pending =》rejected
- Promise有一个then方法,接受两个参数,成功的回调(onFulfilled),失败的回调(onRejected)
如果onFulfilled不是函数则忽略,如果onRejected不是函数忽略
如果onFulfilled是函数,则在状态由pending转变为fulfilled的时候执行一次,第一个参数是执行的结果传入。
如果onRejected是函数,则第一个参数是失败的原因,函数执行一次。
class _Promise {
constructor(executor) {
// 定义初始的状态
this.status = "pending";
this.value = undefined; // 成功的值
this.reason = undefined; // 失败的值
// 支持异步回调,使用一个成功的队列和失败的队列把回调函数存起来
this.fulfilledList = [];
this.rejectedList = [];
// 在执行的时候循环这个队列
let resolve = (value) => {
// resolve的方法
if (this.status === "pending") {
this.status = "fulfilled";
this.value = value;
this.fulfilledList.map((fn) => fn());
}
};
let reject = (reason) => {
// reject的方法
if (this.status === "pending") {
this.status = "rejected";
this.reason = reason;
this.rejectedList.map((fn) => fn());
}
};
try {
executor(resolve, reject); // 直接执行 执行器函数
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 判断第一个参数是function否
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : null;
onRejected = typeof onRejected === "function" ? onRejected : null;
return new _Promise((resolve, reject) => {
if (this.status === "pending") {
this.fulfilledList.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
} catch (e) {
reject(e);
}
});
});
this.rejectedList.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
} catch (e) {
reject(e);
}
});
});
}
// 如果是function,且状态为fulfilled,执行成功回调
if (this.status === "fulfilled") {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
} catch (e) {
reject(e);
}
});
}
// 如果是function,且状态是rejected,执行失败回调
if (this.status === "rejected") {
setTimeout(() => {
try {
let x = onRejected(this.reason);
x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
} catch (e) {
reject(e);
}
});
}
});
}
static resolve(value) {
if (value instanceof _Promise) {
// 判断返回的是在 promise的原型链上
return value;
} else {
// 如果不在执行resolve
this.status = "fulfilled";
return new _Promise((resolve) => resolve(value));
}
}
static reject(value) {
this.status = "rejected";
return new _Promise((resolve, reject) => {
reject(value);
});
}
catch(onRejected) {
this.then(null, onRejected);
}
finally(callback) {
return new _Promise(() => {}).then(
(res) => {
return _Promise.resolve(callback()).then(() => res);
},
(err) => {
return _Promise.resolve(callback()).then(() => err);
}
);
}
// 传入一组,全部成功返回成功的数组,如果有任何一个失败返回这个失败的结果
static all(promises) {
let result = [];
let count = 0;
if (promises.length === 0) return Promise.resolve([]);
return new Promise((resolve, reject) => {
promises.forEach((p, i) => {
Promise.resolve(p)
.then((item) => {
count += 1;
result[i] = item;
if (promises.length === count) {
resolve(result);
}
})
.catch(reject);
});
});
}
// 全部执行,返回所有成功和失败的结果数组
static allSettled(promises) {
let result = [],count = 0;
return new _Promise((resolve, reject)=> {
promises.forEach((p, i)=> {
_Promise.resolve(p).then(item=> {
count +=1
result.push({
status: 'fulfilled',
value: item
})
if(promises.length === count){
resolve(result)
}
}, err=> {
count +=1
result.push({
status: 'rejected',
reason: err
})
if(promises.length === count){
resolve(result)
}
})
})
})
}
// 返回最先执行完的那个成功或者失败。
static race(promises) {
return new _Promise((resolve, reject) => {
promises.forEach(p => {
_Promise.resolve(p).then(resolve).catch(reject)
})
})
}
// 返回第一个成功的promise或者所有失败的 AggregateError集合
static any(promises){
let count = 0
return new _Promise((resolve, reject) => {
promises.forEach((p, i) => {
_Promise.resolve(p).then(res => {
resolve(res)
}, err => {
count +=1
if(count === promises.length){
return new AggregateError('all promise are rejected')
}
})
})
})
}
}
- 测试
const p1 = new _Promise((resolve, reject) => {
resolve(1);
});
const p2 = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 1000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("err");
}, 4000);
});
_Promise.all([p1, p2, p3]).then((item) => {
console.log(item);
});
p1.then((item) => {
console.log(item);
});
p2.then((item) => {
console.log(item);
});
- 实现一个发布订阅模式
class EventEmiter{
constructor(){
this.taskList = {}
}
emit(name, ...arg){
if(this.taskList[name]){
const task = this.taskList[name].slice()
for(let fn of task){
fn(...arg)
}
}
}
on(name, fn){
if(this.taskList[name]){
this.taskList[name].push(fn)
} else {
this.taskList[name] = [fn]
}
}
off(name, fn){
const task = this.taskList[name].slice()
if(task){
const index = task.findIndexof(f=> f===fn || f.callback ===fn)
if(index > -1){
task.splice(index, 1)
}
}
}
}
- 实现一个LRU算法。
- LRU算法是全称least recently used
class LRUCatch{
constructor(capacity){
this.capacity = capacity
this.maps = new Map()
}
get(key){
if(this.maps.has(key)){
const value = this.maps.get(key)
this.maps.delete(key)
this.maps.set(key, value)
return value
}
}
put(key, value){
if(this.maps.has(key)){
this.maps.delete(key)
}
if(this.maps.size >= this.capicity){
this.maps.delete(this.maps.keys().next().value)
}
this.maps.set(key, value)
}
}
- 实现数组去重。
1. 使用new Set()去重
const uniqueArr = arr => {
return [...new Set(arr)]
}
2. 使用for循环/ indexOf/includes
const uniqueArr1 = arr => {
const result = []
for(let value of arr){
if(!result.includes(value)){
result.push(value)
}
}
return result
}
3. 使用对象的属性唯一
const uniqueArr2 = arr => {
const obj = arr.reduce((pre, cur) => {
pre[cur + typeof cur] = cur
return pre
}, {})
return Object.values(obj)
}
- 实现图片懒加载(至少用两种方法)。
- 实现js的深拷贝/浅拷贝。
深拷贝:
const deepClone = (data, maps = new Map()) => {
const result = Array.isArray(data) ? [] : {}
if(maps.get(data)){
return data
}
maps.set(data, true)
for(let i in data){
if(data.hasOwnProperty(i)){
result[i] = typeof data[i] === 'object' ? deepClone(data[i]) : data[i]
}
}
return result
}
浅拷贝:
const shallowCopy = data => {
const result = Array.isArray(data) ? [] : {}
for(let i in data) {
if(data.hasOwnProperty(i)){
result[i] = data[i]
}
}
return result
}
const obj = {
color: ['red', 'green']
}
const copyObj = shallowCopy(obj)
obj.color.push('black')
console.log('obj', obj)
console.log('copyObj', copyObj)
- 实现一个深度优先遍历算法/广度优先遍历。
深度优先遍历:
// 非递归使用栈的特性
const dfs1 = (tree, callback) => {
const stack = [...tree]
while(stack.length){
const node = stack.pop()
if(node.children){
stack.push(...node.children.reverse())
}
}
}
// 利用递归
const dfs2 = (tree, callback) => {
tree.forEach(node => {
callback(node)
if(node.children){
dfs2(node.children, callback)
}
})
}
广度优先遍历
const bfs = (tree, callback) => {
const queen = [...tree]
while(queen.length){
const node = queen.shift()
callback(node)
if(node.children){
queen.push(...node.children)
}
}
}
- list转化为tree,tree转化为list。
const tree = [{
id: 0,
name: 'root',
children: [{
id: 1,
name: '父节点1',
children: [{
id: 2,
name: '子节点1',
children: [{
id: 3,
name: '孙子节点1',
}]
},
{
id: 5,
name: '子节点2'
}]
},
{
id: 6,
name: '父节点2',
children: [{
id: 7,
name: '子节点3'
}]
}]
}];
const treeToList = (tree, result = []) => {
tree.forEach(node => {
result.push(node)
if(node.children){
treeToList(node.children, result)
}
})
}
const listToTree = list => {
const result = []
const map = {}
for(let value of list){
const temp = {...value, children: []}
map[value.id] = temp
if(value.pid === 0){
result.push(temp)
} else {
map[value.pid].children.push(temp)
}
}
return result
}
- 实现一个sleep函数。
const sleep = time => {
return new Promise(resolve=> {
setTimeout(resolve, time)
})
}
使用:
const fn = async () => {
console.log('run')
await sleep(1000)
console.log('1s后run')
await sleep(2000)
console.log('2s后run')
}
fn()
- 实现模拟红灯3s后显示,绿灯2s后显示,黄灯1s后显示。
- 实现一个flat函数。
- 实现一个异步控制并行函数/串行函数,(并行demo:同时只能执行两个任务)。
- 封装一个JSONP
- 数组的api的手写实现
- map
- forEach
- reduce
- slice
- ...
- 实现获取url中的参数(至少用两种方法)。
- 实现一个变量名称转驼峰 has_name => hasName
- 判断对象是否循环引用。
- 实现一个自记忆函数
- 实现一个tree shaking
作用:
- 去除打包之后没有使用到的代码,没有饮用的代码块,不执行的代码。
大致思路如下:
-
框架部分
Vue常见问题
- 说一下vue和react的区别。
vue3的特点
1. 降低了模块之间的耦合度,可以单独使用某个模块
2. 使用tree-shaking,实现api按需引入,减少打包体积。
3. 允许自定义渲染器。
composition api(组合api) 对比React Hook的优点如下:
1. 不限制api的调用顺序,不用担心闭包变量的问题。
2. Vue的响应式系统会自动帮我们收集依赖,不需要手动收集。
3. 不需要手动缓存回调函数来避免不必要的组件更新,vue能够保证组件在必要时才会做出更新。
- 简单的讲一下MVVM原理。
- vue2.x和vue3.x的区别。
1. vue3使用了pnpm,workspace来实现monorepo环境。
2. vue2使用defineProperty来实现数据的劫持,当新增删除属性的时候无法监测到。
3. vue2对数组的处理是重写了数组的api
- vue2.x的响应式原理的实现。
- vue3的响应式原理。
### reactive的注意事项
- vue3.0的中,reactive收集依赖的时候利用栈的特征去收集当前activeEffect,有些浪费性能。
- vue3.2的时候在当前effect上增加一个parent属性进行标记。
- 使用WeakMap弱引用来收集依赖,当前activeEffect存放在Set中,同时要先判断一下Set有没有当前的activeEffect,这样性能会好一点,虽然可以利用Set的特性去重。
- 当前的activeEffect也要记录是谁收集了它,为了清理的时候方便。
- 在trigger的时候还要注意当前的effect和activeEffect是否相等避免死循环
- 3.0的时候用的是一个栈来保存当前的activeEffect
- 3.0之后
### 流程如下
1)设置一个响应式对象 new Proxy(), 在get中执行track,在set中执行trigger
2)设置一个副作用函数effect,使用一个变量activeEffect把effect与reactive关联起来。数据变化更新,把正在执行的effect函数作为全局变量,渲染取值的时候在get中进行依赖收集。
3)收集过程中使用了WeakMap => {对象: Map(属性:new Set(effect))
4) 数据变化的时候,通过对象的属性拿到所有的effect集合然后执行。
6. vue2.x中如何对数组的变化监听到。
7. computed和watch的区别。
vue3中的computed
effect加缓存,加一个收集依赖的功能。
watch
1. 使用ReactiveEffect
(1) ref的简单理解。
- data为什么是一个函数返回。
- vue生命周期的理解。
- vue ssr的生命周期有哪些。
- 简单讲一下EventLoop,nextTick()
- v-modal的原理是什么
- vue如何实现事件监听的。
- vue模版编译的原理。
- 讲一下diff算法的过程,vue2.x和vue3中有什么区别呢。
- vue中虚拟domkey的作用。
- v-if/v-show的使用。
- keep-alive的理解。
- vue的组件通信。
- vue ssr框架的问题,nuxt的使用。
- vue的优化从哪几方面做。
- vuex的理解,mutation,action的理解。
- 实现vuex的数据刷新页面还保存。
Vue3
- setup的理解。
React
- setState是同步还是异步的。
- hooks的理解。
- 多了我也不知道了。
TS
- type和interface有什么区别?
- any,void,unknown,never有什么区别。 参考
Webpack/Vite部分
原理
- webpack的作用是什么?
- 使用的基本流程请简单描述一下。
- 你多做过哪些webpack的优化
plugin/loader
- 如何实现一个plugin/loader
浏览器
- 进程和线程的区别。
- 浏览器的主要进程都有哪些。
- 输入一个url的的过程。
- TCP的三次握手和四次挥手。
- 浏览器的重绘和回流。
- 浏览器的存储。
- Web Work的作用以及原理。
- 浏览器的垃圾回收机制的理解。
跨域
- 跨域的几种解决办法。
请求状态
- 浏览器的请求状态都有哪几种。
安全
- 前端安全的理解。
- csrf的攻击。
算法
10大基础算法
- 冒泡排序。
- 选择排序。
- 插入排序。
- 快速排序。
- 归并排序。
- 堆排序。
- 希尔排序。
- 桶排序。
- 计数排序。
- 基数排序。
leetcode 不同类型经典题解
- 链表。
CSS/HTML
CSS3/HTML5的新特性
- 如何实现的网页主题。
- css3的一些新属性比如:filter ...
- 实现垂直水平居中。
- 请简单讲一下BFC。
- link和@import的区别。
- opacity和hidden和display的区别。
- css权重的问题。
- 移动端一像素的线如何实现。
- flex。
- Grid的属性。
- position的所有属性。
- css动画。
- canvas的了解,属性/常用。
less/sass
- 简单讲一下如何在项目中使用,比如在vue中如何全局使用less,结合项目说一下。
Git的常用操作知识
- 基础命令。
- 进阶命令。
- 高级命令。
Node的一些使用及常见问题
node常见的web框架
- node web框架的选择
node中间件
- 如何封装一个中间件。 参考链接
一些常见的面试题目
promise相关
- 使用Promise实现每隔1秒输出1,2,3。
const arr = [1,2,3];
arr.reduce((prev, next) => {
return prev.then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(next);
resolve();
}, 1000);
});
});
}, Promise.resolve());
- 实现红绿灯效果: 红灯3秒亮一次,黄灯2秒亮一次,绿灯1秒亮一次;如何让三个灯不断交替重复亮灯。
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
function light(cb, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
cb();
resolve();
}, time);
});
}
(function run() {
light(red, 3000).then(() => {
return light(yellow, 2000);
}).then(() => {
return light(green, 1000);
}).then(() => {
run();
});
}());
- list转化为tree结构