最全的面试题地址
浏览器、js相关
原型原型链
什么叫原型:原型分为显式原型和隐式原型,js中所有的对象本质上都是通过new关键字出来的,js中的函数都有prototype显示原型,本质是一个对象,该对象是其实例的原型,所有的实例对象都有一个__proto__(谷歌浏览器已更新为[[prototype]],即隐式原型),该隐式原型指向prototype对象
每个原型都有一个constructor属性,该属性指向构造函数
原型链:每个实例对象都有个__proto__指向构造函数的prototype,他的构造函数的原型对象都有一个__proto__指向它的构造函数的原型对象,一层一层的查找路线叫做原型链
深浅拷贝
深拷贝:对变量内存地址和值的拷贝一份创建新的内存地址
浅拷贝:对变量内存地址和值进行复制,只对其引用
深浅拷贝的方法
深拷贝
1、通过递归的方式实现拷贝
function deepCopy(obj){
if (typeof obj !== 'object') return obj
let result
if (obj instanceof Array) {
result = []
}
if (obj instanceof Object) {
result = {}
}
for (const key in obj) {
result[key] = deepCopy(obj(key))
}
return result
}
2、JSON.parse()和JSON.stringify()
3、lodash中的cloneDeep()
浅拷贝
1、直接赋值
2、Object.assign()
3、解构赋值
宏任务微任务
js是一种单线程语言,按照顺序一行一行执行,如果某一个任务执行时间太长,就会堵塞后面的任务执行,为了解决这个问题,就把任务分为同步任务和异步任务,异步任务又分为宏任务和微任务
宏任务有哪些:setTimeout、setInterval、setImmediate、异步ajax
微任务有哪些:process.nextTick、promise.resolve().then()、async/await
案例
console.log(1)
setTimeout(() => {
console.log(2)
}, 100);
setTimeout(() => {
console.log(3)
}, 100);
new Promise((resolve,reject) => {
console.log(4)
resolve()
}).then((resolve)=>{
console.log(5)
}).then((resolve)=>{
console.log(6)
})
console.log(7)
输出为:1 4 7 5 6 2 3
执行顺序为:同步任务 > 微任务 > 宏任务
http缓存&浏览器缓存
浏览器缓存是指在本地缓存
http缓存是指在请求头和相应头中的对应信息来控制缓存,根据是否有缓存以及缓存是否有效,分为强缓存和协商缓存
缓存内容看这篇文章就够了
节流防抖
防抖:在单位时间内,不停的触发只执行最后一次的操作
适用场景:用户连续点击按钮、搜索
代码实现:
let timeId
function debounce(fn, delay) {
if (timeId) clearTimeout(timeId)
timeId = setTimeout(() => fn(), delay)
}
节流:在单位时间内,不停的触发只执行第一次的操作
适用场景:鼠标滚动、滚动条滑动、窗口resize
代码实现:
let timeId
function throe(fn, delay) {
if (timeId) return
timeId = setTimeout(() => {
fn()
clearTimeout(timeId)
}, delay)
}
闭包
闭包实际是一个可以读取另外一个函数内部变量的函数
闭包的作用:
1、封装变量,可以将变量私有化
2、通过闭包可以进行模块化封装
3、在构造函数内部实现私有变量和方法
4、缓存数据不被污染
柯里化函数
维基百科对柯里化函数的定义:函数柯里化,就是可以将一个接受多个参数的函数分解成多个接收单个参数的函数的技术,直到接收的参数满足了原来所需的数量后,才执行原函数
柯里化函数的中心思想是需要满足一个入参长度的限制,当满足这个长度时返回值,否则返回该函数继续调用
返回一个函数,这种函数的表现形式是不是很熟悉呢?其实就是闭包的具体应用,下面就是实现的具体代码
let sumList = []
// marketLength函数主要是来规定参数长度的
function marketLength(length) {
function sum() {
// 查看入参
console.log('========', arguments)
// 将入参转为数组
const arg = Array.from(arguments)
// 合并两个数组
sumList = [...sumList, ...arg]
// 如果参数长度达到需求求和,否则返回函数继续进行接收参数
if (sumList.length >=length) {
const he = sumList.slice(0,length).reduce((p, v)=> p+v)
sumList = []
return he
} else {
return sum
}
}
// 将负责判断的函数进行返回
return sum
}
// 上面返回的sum是不是在函数内返回呢?这就是柯里化函数,也是闭包的具体实现
浏览器输入链接发生了什么事
1、分析链接是否有效,有效的链接包括协议、域名、端口号以及文件地址
2、dns域名解析,找到相对应的ip地址,返回给浏览器
3、浏览器和服务器建立tcp连接(三次握手)
4、浏览器发送请求
4、服务器接受响应
5、tcp断开连接(四次挥手)
tcp三次握手四次挥手详细分析
call/apply/bind
call/apply/bind这三个函数都是改变this指向,但在使用时也有一些差异
相同点:
1、接收的第一个参数都是该函数的this指向
2、都可以利用后续参数传参
不同点:
1、call和bind从第二个参数开始都是传入的参数且不限量
2、apply接收的第二个参数都是数组
3、call和apply都是立即执行函数
4、bind没有立即执行而是返回一个函数
call、apply、bind的手动实现
call的实现
Function.prototype.newCall = function (obj) {
if (typeof this !== 'function') {
// 可以在这里打印this,查看this
console.log('-------this', this)
return throw new Error('this指向必须是函数')
}
obj = obj || window
obj.fn = this
// 将参数转为数组
const arg = Array.from(arguments).slice(1)
// 执行函数
obj.fn(...arg)
delete obj.fn
}
apply实现
Function.prototype.newapply = function (obj) {
if (typeof this !== 'function') {
// 可以在这里打印this
console.log('-------this', this)
// 如果this指向不是函数,就抛出错误
return throw new Error('this指向必须是函数')
}
obj = obj || window
obj.fn = this
// 将参数转为数组
// 这里是取第二个参数,第二参数本来就是数组
// call和apply仅此一个区别
const arg = Array.from(arguments)[1]
// 执行函数
obj.fn(...arg)
delete obj.fn
}
bind实现
Function.prototype.newbind = function (obj) {
if (typeof this !== 'function') {
// 可以在这里打印this
console.log('-------this', this)
return throw new Error('this指向必须是函数')
}
const _this = this
// 将参数转为数组
const arg = Array.from(arguments).slice(1)
// 返回一个函数,并改变this指向
return function() {
_this.apply(obj, arg)
}
}
以上三个函数的纯js实现,主要核心在于判断this指向,并且在传如的obj上保存fn函数,最后用obj中的fn实际执行,这样就改变了this指向
this工作原理
通过上面三个方法可以改变this指向,那this具体怎么指定呢?那就需要介绍下严格模式和非严格模式了
如何确认this的值
全局环境下
开启严格模式:window对象
非严格模式: window对象
函数内部
1、函数直接调用
开启严格模式:undefined对象
非严格模式: window对象
2、对象方法调用
开启严格模式:调用对象
非严格模式: 调用对象
严格模式开启方式:
脚本开启: use strict
函数内部开启: use strict
⚠️ use strict需要写在代码顶部
在调用的时候可以通过call、apply函数进行确认this的指向
创建时确认this, bind和箭头函数
cookie,sessionStorage 和 localStorage 的区别
区别 | cookie | sessionStorage | localStorage |
存储时间 | 可以定义存储时间的长短 | 关闭网页就失效 | 永久 |
存储大小 | 4kb | 5M | 5M |
与服务端通信 | 携带在请求头中发给后端 | 不参与 | 不参与 |
读写难度 | 难 | 容易 | 容易 |
cookie
什么是跨域?怎么解决
首先要明确跨域只存在浏览器而不存在服务器,服务器与服务器之间是不存在跨域的
浏览器什么情况下属于跨域呢?当协议、域名、端口号不一致的时候就会跨域
所以解决跨域的思路一般是通过代理服务器去完成我们的请求
跨域的解决办法有配置正向代理、反向代理、negix代理
1、JSONP
在网页中一些标签不受浏览器的同源策略影响,img,iframe,link,script
JSONP就是利用script标签不受浏览器的同源策略影响
jsonp优点是可以进行跨域,缺点是只支持get请求,且存在安全隐患
具体实现方案是带一个callback函数来接收返回的数据
2、前端配置
前端webpack支持配置代理,也就是正向代理,
devServer: {
port: 8080,
proxy: {
"/api": {
target: "http://192.168.122.55:8088" // 后端接口
}
}
}
3、cors
需要后端在响应头中将Access-Control-Allow-Origin设置为*或者前端需要跨域的地址
如果需要在请求头中加入cookie,前端需要设置
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
// 或者
axios.defaults.withCredentials = true
浏览器垃圾回收机制
模块化演变
ES5、ES6相关
new一个函数发生了什么
1.创建一个空对象
2.实例对象的__proto__指向构造函数的prototype
3.改变this指向,指向obj
4.判断构造函数的返回值是不是对象,如果是返回该对象,否则返回创建的对象
构造函数
function Fun(param) {
let age = 22;
this.name = "tom";
this.getAge = function () {
return age + param;
};
}
new的过程
function myNew(fn) {
// 1、新建一个空对象
let obj = {};
// 2、实例对象的__proto__等于构造函数的prototype
obj.__proto__ = fn.prototype;
// 3、改变this指向,指向obj
let arg = Array.from(arguments).slice(1);
console.log('----arg-----', arg)
// result是fn的返回值
let result = fn.apply(obj, arg);
// 4、判断返回值是不是对象,是的话就返回这个返回值
if (Object.prototype.toString.call(result) === "[object Object]") {
return result;
}
// fn没有返回值,就返回obj
return obj;
};
类和构造函数的区别
1、class构造函数必须使用new,否则会报错,function构造函数可以不使用new,this是window对象
2、class类不存在声明提升,function函数存在声明提升
3、class不可以使用apply、bind、call来改变this指向,function函数可以
4、类构造函数,方法可以直接定义在类内。function函数不可以写在方法内,需要通过fun.prototype添加方法
let var const
区别 | let | const | var |
变量提升 | 否 | 否 | 是 |
重复声明 | 否 | 否 | 是 |
定义之后再次赋值 | 是 | 否 | 是 |
暂时性死区 | 是 | 是 | 否 |
块级作用域 | 是 | 是 | 否 |
ES5继承和ES6继承的区别
es5中使用构造函数继承,一个构造函数继承另外一个构造函数,分别是构造函数继承、原型链继承、混合继承
es6中的继承引入了extend是关键字继承
先介绍下es6中的关键字,class(定义类的关键字)、constructor、super()(继承父类的属性)、static(用来定义静态属性和方法)、#(用来定义私有属性和方法)、extends(用来继承类)
es5继承
function Person(name) {
this.name = name
}
Person.prototype.getName = function() {
console.log('我是父类的属性')
}
function Son(age) {
// 继承父类属性
Person.call(this, 'tom')
this.age = age
}
// 1、继承父类原型上的方法
Son.prototype = new Person();
Son.prototype.getName = function() {
console.log('我是子类的属性-------')
}
// 2、继承父类原型上的方法,这两种都可以
// Son.prototype = Object.create(Person.prototype)
// 因为原型上的constructor指向构造函数,所以需要重新指定constructor
// 如果不执行Son.prototype.constructor = Son,Son的构造函数指向是Person
Son.prototype.constructor = Son
const son = new Son(18)
es6继承
class Parent {
// 共有属性
name
// 私有属性
// 使用关键字#
#name = '小任'
// 静态属性
// 使用关键字 static
static staName = "小刘"
constructor(name) {
this.name = name
}
// 共有方法
getPublicName() {
console.log('-----私有属性----', this.#name)
this.#getSiyouName() //私有方法调用
}
//私有方法
#getSiyouName() {
console.log('-----私有方法----', this.name)
}
// 静态方法
static getSticName() {
console.log('-----静态方法----', this.staName)
}
}
// 静态方法的调用,子类继承不到
Parent.getSticName()
// 静态属性的调用,子类继承不到
Parent.staName
// 关键字extends继承
class Son extends Parent {
constructor(name,age){
// super关键字继承父类
super(name)
this.age = age
}
}
const son = new Son('heh', '18')
// 注意
// 1、静态方法和属性的调用,只能通过构造函数,子类继承不到
// 2、私有属性和方法只能在构造函数内使用
ES6中的super
在子类的构造函数中必须使用super
ES5和ES6的区别
1、变量声明方式增加了let,const
2、块级作用域
3、字符串模块
4、箭头函数
5、类和继承
6、模块化
7、promise对象
async/await
async/await是promise的语法糖,每个使用async的函数返回值都是promise.resolve(),所以需要then函数来接收,接收错误需要配合try{}catch(err){}来捕获错误
async function synFun() {
return 1
}
console.log(synFun())
控制台的返回值是,且状态是fulfilled
手写promise
Promise实质上是一个类,像then、reject、resolve、all、race...函数都在在其原型上面的封装,接下来我们就来实现一个简单的Promise吧
数组处理方法总结
1、push(): 向数组的末尾添加一个元素,改变原数组
2、pop():将数组末尾的一个元素删除,改变原数组
3、shift():将数组第一个元素删除,改变原数组
4、unshift():向数组的首位添加元素,改变原数组
5、forEach():循环遍历数组,相当于for循环
6、map():有返回值,返回一个新的数组
7、filter():有返回值,过滤出符合条件的元素
8、reduce():用于求和计算
9、sort():排序
arr.sort((a,b) => a-b) // 升序
arr.sort((a,b) => b-a) // 降序
10、reverse():翻转数组,该变原数组
11、splice(): 接收三个参数,第一参数开始下标,第二个参数是删除几个元素,第三个参数是添加的元素,直接改变原数组
12、join():将数组切割为字符串,不该变原数组
13、Array.isArray():判断是否是为数组,返回值是布尔值
14、indexOf():查找数组中是否包含某个元素,包含返回下标,否则返回-1
15、slice():截取数组的一部分,不影响原数组
TS相关
所有类型定义
基本类型定义
const numberType: number = 0
const stringType: string = 'string'
const booleanType: boolean = true
const nullType: null = null
const undefinedType: undefined = undefined
const lianheType: number | string = 1 // 也可以定义为字符串1
数组类型定义
// 数组定义方法有多种,最简单的是【类型+括号】表示数组
const arrNumType: number[] = [1,2,3]
const arrStrType: string[] = ['1','2','3']
// 也可以用泛型来定义
const arrStrType: Array<number> = [1,2,3]
对象类型定义
// 对象定义
// 分为三种情况,第一种是key值确定、第二种是key值不确定,可有可无、第三种是任意属性
// 当是第二种情况时,需要用?来定义
// 当是第三种情况时,需要用【】中括号来定义
// 定义对象一般需要接口interface或者type来定义
// 对象类型首位字母必须大写
interface IObj = {
a: number; // 必须
b?: number; // 可选
[c: string]: unknown; // 任意属性
}
const d: IObj = {
a: 1,
b: 1,
e: 1
}
函数类型定义
// 函数类型限制,有输入类型和输出类型限制
// 有返回值时
function a(x: number):number {
return x
}
// 没有返回值时
function a(x: number):void {
xxxxxxxxx
}
// 还有一种情况是输入输出类型不一定,这个时候需要用到泛型
// // 什么是泛型?是指在定义函数、接口、和类时,不预先指定具体的类型,而在使用的时候再指定具体的类型
// 有返回值时
function a<T,W>(x: T): W {
return x.length
}
a<String, number>('1111')
// 但这个时候还会报错,因为x的类型并没有定义,正常来说字符串和数组是有长度属性的,但是数字类型的并没有长度类型
// 这个时候需要用到extends属性来继承
interface IWtype {
length: number
}
// 有返回值时
function a<T,W extends IWtype>(x: T): W {
return x.length
}
a<String, number>('1111')
// 没有返回值时
function a<T>(x: T) {
xxxxxxxxx
}
a<String>('1111')
什么是TS
ts是js的加强版,添加了静态类型,拓展了js的语法
ts和js的区别
ts中的类
ts中的类是对类的进一步约束,约束规则如下
Typescript的类的相关知识及进阶教程_typescript实现类和方法-CSDN博客
修饰符
public: 正常定义的变量或者方法就是oublic,公共的,任何地方都可以访问
private: 私有的,只有自身内部可以访问
protected: 受保护的,自身内部和子类中可以访问
readonly: 只读,仅在构造函数内可修改
static: 静态的,经过static修饰过的变量、方法,只能通过类.方法、类.变量进行访问
#: 私有的,仅在其自身内部可以访问
访问器
对类的成员访问(get)和赋值(set)的方式
class demo {
_name: string
constructor(name: string) {
this._name = name
}
// 访问器获取
get name(): string {
return this._name
}
// 访问器修改
set name(value: string) {
this._name = value
}
}
抽象类与抽象方法
抽象类和抽象方法使用关键字abstract来定义,abstract定义的类不能被实例化,定义的抽象方法只有签名,没有实现,具体实现需要在派生类中实现
// demo是抽象类
abstract class demo {
_name: string
constructor(name: string) {
this._name = name
}
// 父类中的抽象方法
abstract setAge():void
}
class xiao extends demo {
age: number
constructor(age: number) {
this.age = age
}
// 在子类中具体实现
setAge(val: number) {
this.age = val
}
}
implements/接口和类的结合
接口可以用来描述类的形状,来约束类
interface demoIn {
_name: string
setName: (val: string) => void
}
class demo implements demoIn {
_name: string
constructor(name: string) {
this._name = name
}
// 父类中的抽象方法
setName(val: string):void {
this._name = val
}
}
类的Mixin
范型和类的结合
// T代表范型
class Demo<T> {
name: T
constructor(name: T) {
this.name = name
}
handleName(val:T, type: number): void {
this.name = val
}
handleName(val:T, type: T): void {
this.name = val + '!'
}
handleName(val:T, type: boolean): void {
this.name = val + 'true'
}
}
// 在实例化时使范型为string
const shili = new Demo<string>('xiaoxiao')
shili.handleName('xiaoxiao', 1)
shili.handleName('xiaoxiao', '1')
shili.handleName('xiaoxiao', true)
类的方法重载
类的方法重载是指在类中可以定义同名的方法,然后根据入参的类型和个数不同来确定要调用的方法
class Demo {
name: String
constructor(name: string) {
this.name = name
}
handleName(val:string, type: number): void {
this.name = val
}
handleName(val:string, type: string): void {
this.name = val + '!'
}
handleName(val:string, type: boolean): void {
this.name = val + 'true'
}
}
Demo.handleName('xiaoxiao', 1)
Demo.handleName('xiaoxiao', '1')
Demo.handleName('xiaoxiao', true)
类的扩展和重写
在typeScript中,可以通过extends继承类的属性和方法,也可以对方法进行重写
类的静态成员和命名空间
类的静态成员是属于类本身的,不属于实例,为了组织和管理类的静态成员,我们可以使用命名空间
namespace LeiName {
// 使用export导出变量
export const name = "xiaoliu"
// 使用export导出方法
export function handelData(val: string): string {
return val
}
}
// 使用
// 变量
LeiName.name
// 方法
LeiName.handelData('发大财')
类型断言
类型断言是一种方式,用于告诉编译器某个值的具体类型
class Demo {
_name: string
constructor(name: string) {
this._name = name
}
// 父类中的抽象方法
setName(val: string):void {
this._name = val
}
}
const shili = new Demo('xiaoliu')
// 用类型断言表示shili是属于Demo的
console.log((shili as Demo)._name)
ts类中的装饰器
装饰器是一个函数,可以注入到类、方法、属性、参数上来扩展类、方法、属性、参数的功能,以下是不同装饰器的作用
类装饰器
function demo1(target) {
// target就是Demo类
target.protertype.demoDate = '我是demo数据'
}
@demo1
class Demo{
// 定义一个空类,通过装饰器给类扩展功能
}
装饰器工厂
function demo1(option) {
// 通过option的type可以来返回不同的值,进行封装
// 返回值是一个函数,这种模式是装饰器结构
if (option?.type === 'delete') {
return function(target) {
// target就是Demo类
target.protertype.demoDate = '我是demo数据'
}
}
}
@demo1({type: 'delete'})
class Demo{
// 定义一个空类,通过装饰器给类扩展功能
}
装饰器组合
装饰器组合是指多个装饰器、多个装饰器工厂、装饰器和装饰器工厂组合一起使用,这个时候会有一个执行的先后顺序之后,规则是:先从上到下执行装饰器工厂,拿到真实的装饰器,然后从下到上执行装饰器
属性装饰器
function demo1(target, attr) {
// target是Demo类
// attr是属性名称
target[attr] = '在这里赋初始值'
}
// 装饰器工厂
function demo2(option) {
// 通过option的type可以来返回不同的值,进行封装
// 返回值是一个函数,这种模式是装饰器结构
if (option?.type === 'delete') {
return function(target,attr) {
// target就是Demo类
target[attr] = '在这里赋初始值'
}
}
}
class Demo{
// 定义一个属性,通过装饰器对属性进行赋值
// 如果此时访问name,必定是undefined
@demo1
name: string
}
方法装饰器
方法装饰器和属性以及类装饰器是一样的,这里就不再追加代码了
ts中的枚举
使用关键字enum进行枚举,是一种特殊的数据类型,允许变量具有预定义的用户指定的值,
如果没有给指定的值,默认是从0依次递加
// 数字枚举
// 如果不指定默认值,默认是从0开始的
enum enumNumber {
one, // 0
two, // 1
...
weiba // n
}
// 如果指定首位的默认值,后面的就是依次递增
enum enumNum {
one=10, // 赋值为10
two, // 11
three, // 12
...,
weibu
}
// 字符串枚举
enum dataType {
type1='默认1',
type2='默认2',
type3='默认3',
type4='默认4',
}
// 使用
// dataType.type1
// 异构枚举
// 也就是说里面的内容不止有字符串,有任何类型
enum enumUg {
booleaan=true,
num=1,
str="11"
}
interface和type的异同
相同之处
1、都可以用来声明对象类型和函数类型
2、都可以继承(interface是extends,type是&)
不同之处
1、type还可以声明基本类型、元组类型、联合类型(number | string)、typeof操作符来声明
2、interface可以将重复声明合并,type会报错
omit和pick
ts内置的两个类型
1、可以从已有的类型定义中减去一个类型,接收两个字段,omit<类型定义,需要排除的类型>
// 删除已经定义的类型中的某个类型定义
interface Person {
name: String;
id: Number;
sex: String;
}
type NewP = omit<Person, id>
type NewP1 = omit<Person, id | name>
2、可以从已有的类型定义中保留某些类型定义,pick<类型定义,需要保留的类型>
// 保留已经定义的类型中的某个类型定义
interface Person {
name: String;
id: Number;
sex: String;
}
type NewP = pick<Person, id>
type NewP1 = pick<Person, id | name>
void和never
void定义的类型表示值是null或者undefind
never定义的类型是永远不会有值
1、void可以指是null和undefined的联合类型,如果某个函数没有返回值可以使用void
function ():void {
console.log('我没有返回值,所以返回值为void')
}
2、never是一个底层类型,永远不会出现值的类型
比如函数抛出错误或者是一个无限循环的函数不会有返回值这种情况可以使用never
any和unknown
any类型定义的变量可以赋值给任意类型的变量,任何类型的变量也可以给any赋值
unknown类型定义的变量只可以赋值给unknown类型或者any类型,如果想要赋值给其他类型也可以进行类型判断或者类型断言
any
let a: any
let b: number = 1
a = 1
a = []
// any类型的值可以赋值给number类型定义的b,且不会报错
b = a
unknown
let a: unknown
let b: number = 1
a = 1
// unknown定义的类型赋值给b会报错,解决办法是使用类型断言或者类型判断
b = a
// 正确的写法是
if (typeof a === 'number') {
b = a
}
// 也可以进行断言写
b = a as number 或者 b = <number>a
TS高级类型
1、交叉类型(&)
type定义的类型,可以通过&进行扩展服用,类似于运算符中的&&
type p1 = {
x: string
}
// 第一种写法
type p2 = {
y: string
}
const obj: p1&p2 = {
x: '11',
y: '22
}
// 第二种写法
type p2 = p1&{
y: string
}
const obj: p2 = {
x: '11',
y: '22
}
2、联合类型(|)
类似于运算符中的或,满足其中一个即可
// 基本联合类型
type aType = number | string | boolean
let a: aType = 1
a = '1'
a = true
// 引用联合类型
type a1Type = {
name: string
}
type a2Type = {
sex: string
}
type aType = a1Type | a2Type
const a: aType = {
name: 'liu'
}
3、类型别名(type)
可以使用type对类型起另外的名字
type aType = number
a: aType = 1
4、类型索引(keyof)
keyof一个接口返回一个联合类型的别名
type aType = {
name: string;
age: number
}
type keyType = keyof aType; // 返回值是name | age
5、类型约束(extends)
接口的继承,对接口类型的一个扩展或者复用
6、映射类型(in)
映射类型,使用的关键字是in,类似于for....in,循环出key值
在进行循环时有几个关键字
[p in k]: k[p] // 拷贝所有属性
[p in k]?: k[p] // 所有属性添加?
[p in k]-?: k[p] // 所有属性去掉?
[readonly p in k]-?: k[p] // 所有属性变成可读属性
[-readonly p in k]-?: k[p] // 去掉可读属性
7、条件类型(类似于三目运算)
类似于三目运算,当符合某个条件时使用一个类型,否则使用另外一个类型
const a = true
type a1Type = {
name: string
}
type a2Type = {
age: string
}
type a ? a1Type : a2Type
css相关
BFC格式化上下文
文档流
visible和display
visible设置为hidden依旧占用文档流,display不占用文档流
vw/vh、百分比
vw/vh相对于页面显示窗口的大小、百分比是相对于父元素
rem、em
rem是相对于根标签也就是html标签的fontsize,em是相对于父元素的fontsize
清除浮动
当使用float:left在移动块元素时,元素就会脱离文档流,父元素就不能撑起整个元素的高度
想要解决这个问题,可以将浮动清除
第一种方法是使父元素成为BFC结构
第二种方法是在父元素内新增一个空元素给其clear:both,清除浮动
可以脱离文档流的方法
float、fixed、absolute
可以使元素成为BFC结构的代码
overflow: hidden
display: inline-block
display: table-cell
display: flex
websocket和轮询
0、轮询是通过setInterval来按照一定的时间间隔向服务器发送请求
1、websocket是html5新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道
2、websocket并不是一个全新的协议,而是利用了http协议来建立连接
----2.1、websocket是怎么建立连接的?也是需要浏览器发起连接,请求协议是标准的http协议
----2.2、请求格式如下
GET ws://localhost:3000/ws/chat HTTP/1.1 // 地址以ws://开头
Host: localhost
Upgrade: websocket // 表示这个连接将要被转为websocket连接
Connection: Upgrade // 表示这个连接将要被转为websocket连接
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string // 用于标识连接
Sec-WebSocket-Version: 13 // 指定websocket协议版本
----2.3、响应格式如下
HTTP/1.1 101 Switching Protocols // 响应代码101表示表示本次连接的协议将要被更改
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string
websocket构造函数的属性以及具体使用
const ws = new WebSocket('ws://xxxxxxxxx')
// ws和wss类似于http和https
// ws.readyState属性
// 0 表示正在连接
// 1 表示连接成功,可以通信了
// 2 表示连接正在关闭
// 3 表示连接已经关闭,或者打开连接失败
ws.onopen = function (){
console.log('----------连接成功的回调函数')
}
ws.onerror = function (){
console.log('----------连接失败的回调函数')
}
ws.onmessage = function (data){
console.log('----------接收服务器返回数据的回调函数')
}
ws.send() // 向服务器端发送数据的函数
ws.onclose = function (){
console.log('----------WebSocket连接关闭的回调函数')
}
为什么http协议不能做到websocket这样的功能?
因为http协议是请求-响应协议,请求必须由浏览器发送给服务器,服务器响应请求,返回数据给浏览器
websocket相关面试题
websocket和socket的区别
1、socket是应用层和TCP/IP协议通信的中间软件抽象层,它是一组接口,提供一套调用TCP/IP协议的API
2、websocket像http一样,是应用层的协议,都是基于socket的上层协议
websocket和http的区别
相同点
1、都是基于TCP的可靠传输协议
2、都工作在应用层
不同点
1、websocket是一种全双工通信协议,相比于http是不需要一直发送请求,只需要等待服务器响应即可
2、协议表示符是ws、wss
3、支持的数据类型不同,http通常支持文本、图像、音频等静态数据类型,websocket客户端和服务端是通过二进制形式进行的,因此可以支持更加复杂的类型,比如视频流和实时游戏数据
简单说下tcp/ip协议
react相关
所有的react问题可看下面链接总结
103个关于「React的高频面试」问题整理,适合面试或者全面学习 - 知乎
react生命周期
初始化阶段
construcstor
getDerivedStateFromProps(旧版本的getDefaultProps废弃)
render
componentDidMount
更新阶段
getDerivedStateFromProps
shouldComponentUpdate
render
getSnapShortBeforeUpdate(componentWillUpdate废弃)
compontDidUpdate
销毁阶段
compontWillUnmount
计划移除的生命周期
在16.4版本之后,被废弃的三个函数都是在render之前,因为fiber的出现,很可能因为高优先级任务的出现打断现有任务导致它们被执行多次
componentWillmount
componentWillupdate
componentWillReceviesProps
react hook介绍
react版本16.8之后才有hook函数
1、useState()
2、useEffect()
可以用来模拟声明周期,接收两个参数,第一个参数是函数,第二个参数是依赖值是个数组
当函数有返回值的时候,在组件被销毁时可以把上一次的事件监听或者延时器进行清除,以免造成内存泄漏
第二个依赖参数如果不写,每次组件更新都会调用
第二个参数是空数组,只有在第一次挂载时调用
第二个数组有值时,当值更新时会调用
3、useMemo()
4、useCallback()
5、useContext()
需要配合creatContext函数使用,const context = creatContext接收一个store数据,通过返回值的provide向后续组件传值,后续组件通过useContext(context)接收
6、useReduce()
解决复杂state的处理办法,usereduce接收三个参数,第一个参数是reduce函数,第二个参数是初始化数据,第三个是处理初始化数据的初始化函数,返回值是state和dispatch,dispach用来触发reduce函数
7、useRef()
8、memo
当组件被memo包括之后,如果父组件传过来的props没有更新,那组件就不会被重新渲染
需要注意的一点是memo对props的比较是浅比较,如果props中的属性是引用数据类型发生改变,那组件是不会更新的,解决这个的办法就是重新返回一个新的对象
memo也不是用的越多越好,对于一些经常被渲染且没必要时不用时时渲染
自定义hook
说说对react的理解,有哪些特性
react是一个用于构建用户界面的js库,提供了UI层面的解决方案
特性
1、单向数据流
2、组件化
3、jsx语法
4、虚拟dom
5、声明式编程
什么是函数组件?什么是类组件?
1、函数组件是用函数创建的组件,在react16.8版本之前,函数组件又叫做无状态组件
2、类组件是通过class类创建的组件
有什么区别?
1、在16.8之前,class组件成为有状态组件,因为他可以通过setState维护自己的状态,函数组件没有自己的状态,需要通过父组件传过来的props来展示UI
2、在16.8之后,函数组件通过hook有了自己的状态,建议统一使用函数组件
setState方法是如何工作的?setState是同步还是异步?
setState是如何工作的涉及到源码,后面再分析
setState是同步还是异步?
setState在react管理的事件机制中是异步的,因为如果有多个setState触发,react是会批量更新的,而在其他时候是立即更新的
1、在同步操作中,setState是异步的
// num初始值是0
handleSetState() {
console.log('---数量----', this.state.num)
this.setState({
num: this.state.num++
})
console.log('---数量----', this.state.num)
}
// 打印出的值是0 0
// 如果上述代码写在for循环中依次改变num的值,react实际上只会更新一次
2、在异步操作中,setState是同步的
// num初始值是0
handleSetState() {
setTimeout(()=> {
console.log('---数量----', this.state.num)
this.setState({
num: this.state.num++
})
console.log('---数量----', this.state.num)
},0)
}
// 打印出的值是0 1
// 如果上述代码写在for循环中依次改变num的值,react实际上会更新n次
什么是事件合成
React实现缓存的方式有哪些?他们有什么区别?
super和super(props)的区别
1、在react中,React.component组件基于ES6的类,所以在继承于React.component的组件需要super来继承父组件中的props
2、super的语法糖是:React.component.prototype.constauctor.call(this, props),将this绑定到子类上
3、在调用super的时候无论是否传props值,react都会把props绑定到类实例上,只有一个区别就是在构造函数内部,如果使用this.props是undefined,打印props不带this是一个对象
class Child extends React.component {
constructor(props) {
console.log('--------0000000', props) // {}
console.log('--------11111111', this.props) // undefined
}
}
react引入css的方式有哪些?
1. 可以行内引入
2. 可以使用import引入
3. 可以使用tailwindCss
react组件间的通信方式
1. 父传子组件通信用props
2. 子传父使用props+回调函数
3. 嵌套多层组件传递可以使用props、发布订阅模式、第三方库redux、context
发布订阅模式
// 可以使用pubsub库来实现发布订阅
// 订阅消息
// 有两个参数
// 第一个参数是订阅消息的名称
// 第二个参数是一个回调函数,回调函数接收两个参数
// 第一个参数是订阅消息的消息名, 可以使用简写方式为_,第二个参数是发布消息的数据
const token = pubsub.subscribe('name', (_, res) => {})
// 发布消息
pubsub.publish('name', '我是发送的数据')
// 取消订阅
// 接收的参数是订阅消息时的返回值
pubsub.unsubscribe(token)
context方式
import React from 'react';
const context = React.creatContext()
// 在app组件中通过context.Provider组件的value值进行传递
// app.js
function App(){
return (
<div>
<context .Provider value={windowProp}>
<component />
</context.Provider>
</div>
)
}
// 在组件中接收
this.context.属性
// 在函数组件中接收可以使用hook函数
const 接收的属性 = useContext(context)
react fiber
react hook的限制
react声明组件的方式
一、React.creatContext()
import React from 'react'
export default React.creatContext({
// 混入
mixins: [], // 在数组中写入混入的值
// 初始化state
getInitialState: function() {
return {
data: '在这里定义你的初始值吧'
}
},
// 给props定义初始值
getDefaultProps: function() {
return {
name: '我是默认值'
}
}
// 属性类型的定义
propsTypes: {
name: React.PropTypes.string
}
// 渲染函数
render: function() {
return <div>11111</div>
}
})
二、class
class DefaultCom extends React.component {
// 初始化
constructor(props) {}
}
三、函数组件
const DefaultFun = () => {
// 这里可以使用hook
return (
<div>这里是渲染的内容</div>
)
}
react插槽
react-Intl
react hook解决了什么问题
首先需要明确的是hook只能在函数组件中使用,在react16.8版本之后新增的hook,开始广泛使用函数组件,也就是解决了类组件的繁杂之处
1. 首先是状态难复用的问题
下面我分几点来阐述
一、类式组件的状态复用是通过高阶函数、render props、context,虽然这些也可以解决问题,但是显而易见这不是最佳的方案
二、hook诞生之后,函数组件也可以管理状态,可以通过hook来单独封装复用好的部分,函数组件也不需要复杂的生命周期等
2. 复杂组件难以理解
因为类式组件的状态难服用问题,很难将组件拆分成更小的部分,所以组件变得很冗杂,hook诞生之后就解决了这个问题
3. class类的额外学习
react的类式组件使用关键字class来写的,class是es6的新语法,而函数式组件是函数或者是箭头函数,几乎没有学习成本
react hook的使用限制有哪些
1、不要在循环、条件、嵌套函数中调用hook
2、在react的函数组件中调用hook