知识点整理

文章目录

  • 一、前端常规问题
    • (一)ES6
      • ES6新增内容
      • 事件循环 微任务宏任务
      • js事件循环机制event-loop 宏任务微任务
      • 箭头函数与普通函数的区别
      • this 指向
      • var let const
      • 数组新增:扩展运算符 ...
      • JavaScript对象ES6新增方法
      • ES6数组新增方法
      • Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构
      • for of 和for in区别
      • map和forEach
      • promise和async/await区别
      • **promise**系列
      • 面向对象的三个特征
      • 原生JS中获取DOM元素的方法
    • (二)js
      • 数组去重
      • null和undefined的区别
      • 数组和类数组的区别
      • 预编译,scope链
      • 闭包
      • 如何实现一个私有变量
      • js中的数据类型
      • 数组常用方法
      • 手动实现Array.prototype.map方法
      • 手动实现Array.prototype.filter方法 (过滤)
      • 手动实现Array.prototype.reduce方法(累加器)
      • 遍历对象的方法
      • object系列
      • 字符串常用方法(增删改查)
      • 类型转换
      • JavaScript的继承
      • 如何解决异步回调地狱
      • 前端中的事件流
      • 如何让事件先冒泡后捕获
      • 事件委托(事件代理)
      • mouseover和mouseenter的区别
      • js的new操作符做了哪些事
      • 改变函数内部this指针的指向函数(bind,apply,call区别)
      • js各种位置 clientHeight scrollHeight,offsetHeight以及scrollTop,offsetTop,clientTop的区别
      • 瀑布流
      • js拖拽功能的实现
      • html5的拖放功能
      • 异步加载js方法
      • commonJs、AMD:requireJS、CMD规范
      • 实现一个once函数 传入函数的参数只执行一次
      • 将原生ajax封装成promise
      • js监听对象属性的改变
      • setTimeout、setInterval和requestAnimationFrame
      • 那些操作会造成内存泄露
      • 什么是高阶函数
      • 原型与原型链
      • 点击按钮传送验证码 保证60s后才能再次发送验证码(刷新之后也要等60s完才发送):采用localstorage存储信息。
      • 深浅拷贝
      • 防抖
      • 节流
      • 图片懒加载
      • 作用域
      • 单例模式
      • 发布订阅模式
      • 策略模式
      • 类创建和继承
      • 函数柯里化实现
    • (三)css
      • CSS3新特性
      • position属性比较
      • display属性比较
      • opacity=0、visibility:hidden、display:none区别
      • 实现三角形
      • 盒模型
      • flex弹性布局
      • Flex 容器的属性
      • 清除浮动的方法
      • 解决浮动引起的高度塌陷
      • 多余文本显示省略号
      • Echarts图表自适应
      • 盒子垂直居中
      • 伪类和伪元素
      • 重绘与重排
      • 如何减少和避免重排
      • 实现BFC
      • src与href
      • link 和@import
      • 样式优先级
      • 三栏布局
      • 两列等高布局
      • 多行文本省略
      • AntD改变主题色
    • (四)html
      • html5新特性
      • 各大浏览器特点
      • 语义化标签
      • HTML5浏览器端存储
      • localStorage、sessionStorage和cookie
      • Doctype
      • 页面加载优化
      • 对SPA的理解
    • (五)webPack
      • 配置
      • webpack-dev-server
      • 解决跨域
      • 启动指令
      • Webpack性能调优
      • 常用loader
      • Webpack优化
    • 体积优化
    • 速度优化
      • Webpack说一下
    • (六)JSON、JSONP
      • JSON格式
      • JSON格式转换
      • JSONP实现
    • (七)Ajax
      • Ajax
      • XMLHttpRequest对象
      • ajax解决浏览器缓存问题
    • 最有效的办法是禁止页面缓存
    • (八)浏览器和网络安全
      • 浏览器的 v8 引擎回收机制
      • js中的垃圾回收机制
      • TCP/IP、HTTP协议等
      • http请求方式
      • 常用http响应头
      • fetch请求方式
      • 状态码
      • 前端领域里面,有哪些潜在的风险
      • 详细介绍 csrf 攻击原理,以及怎么攻击的
      • 跨域
      • 介绍一下 cors 跨域
      • 当服务器配置了允许部分域跨域,浏览器的请求会发生什么改变?(似乎会对请求多做一次预检测),跨域会携带 cookie 吗?
      • get请求传参长度的误区
      • get和post请求在缓存方面的区别
      • 地址栏输入url敲回车后发生了什么
      • Web 缓存
      • http缓存
      • CDN
      • 常用的设计模式
    • (九)ES7
    • (十)ES8
    • (十一)ES9
    • (十二)ES10
    • (十三)ES11
    • (十四)ES12
  • 二、vue
      • vue2和vue3区别
      • 理解
      • MVVM
      • MVC
      • SFC
      • 双向绑定
      • vue响应式原理
      • Vue改变复杂类型数据不会响应的解决方法
      • Vue 模板编译原理
      • vue 内置指令
      • computed和watch区别
      • commputed缓存原理
      • 生命周期、钩子函数
      • 插槽slot
      • v-if 和 v-show
      • v-if 和 v-for
      • 实例和组件定义data的区别
      • data里面为什么放函数
      • $ route和$ router的区别
      • vue-router实现原理
      • Vue路由守卫
      • 路由守卫判断用户是否已经登录的步骤
      • vue-router的核心实现api
      • vue-router实现懒加载
      • vue hash路由和history路由的区别和原理
      • keep-alive原理
      • vue组件this里有什么东西
      • vue中普通函数中的this和箭头函数中的this
      • vue怎么实现父组件调用子组件的方法(注意不是子组件向父组件传值)
      • vue父子组件的传值
      • 组件通信、vuex怎么用
      • Vuex
      • 如何使用vuex
      • $nextTick
      • 自定义指令
      • Vue函数式组件
      • Vue SSR
      • vue的diff算法
      • Vue 性能优化
      • 首屏加载速度慢怎么解决
      • 渲染时机
      • 实现一个SPA
  • 三、React
      • 说说对React的理解?有哪些特性?
      • 区分Real DOM和Virtual DOM
      • 了解 Virtual DOM 吗?解释一下它的工作原理
      • 什么是JSX和它的特性?
      • 类组件和函数组件之间有什么区别
      • 说说对 State 和 Props的理解,有什么区别
      • 说说对React refs 的理解?应用场景?
      • setState是同步还是异步
      • super()和super(props)有什么区别?
      • 说说对React事件机制的理解?
      • React事件绑定的方式有哪些?区别?
      • React*组件生命周期*有几个阶段
      • 详细解释 React 组件的生命周期方法
      • react在哪个生命周期做优化
      • 受控组件和非受控组件的区别
      • React中的key有什么作用?
      • react组件之间如何通信
      • 什么是高阶组件?
      • 说说对React Hooks的理解?解决了什么问题?
      • hook函数
      • useEffect函数如何模拟生命周期
      • useCallback与useMemo的区别
      • 在React中组件间过渡动画如何实现
      • React context是什么?
      • 说说你对Redux的理解?其工作原理?
      • redux工作流程
      • React组件怎么获取redux里的数据
      • React组件如何更新redux里的数据
      • 为什么 React Router 中使用 Switch 关键字
      • 前端工程化
      • TypeScript
      • type和interface的区别
      • 泛型
      • react性能优化
  • 四、数据结构问题

一、前端常规问题

自我介绍+项目
知识图谱
开发文档
设计模式

(一)ES6

ES6新增内容

  1. 块级作用域:通过letconst关键字声明变量,实现了块级作用域。
  2. 箭头函数:更简洁的函数定义方式,可以使用箭头函数替代匿名函数。
  3. 默认参数:可以为函数的参数设置默认值,简化函数定义。
  4. 模板字符串:通过反引号${}实现更方便的字符串拼接和表达式注入
  5. 解构赋值(…):可以从数组或者对象中提取数据,赋值给变量。
  6. 增强的对象字面量:可以更方便地定义对象,支持定义属性、方法和简写等。
  7. 类和继承:引入了classextends关键字,支持面向对象的编程方式
  8. 模块化:使用importexport关键字实现模块化开发,可以更方便地管理代码和依赖。导入improt、导出export default
  9. Promise:提供了一种更优雅的方式处理异步操作,支持链式调用、错误处理和并行等。
  10. Symbol:引入了一种新的数据类型,用于创建唯一的对象属性名,增强了对象属性的安全性和保护性
  11. 数组和对象的新方法(如Array.includes()和Object.assign())
  12. for…of 循环 ES6引入了for…of循环,它可用于迭代数组,字符串,MapSet和其他可迭代的对象。forEachfilter过滤
  13. Proxy和Reflect对象
  14. Generator 函数,它可以创建可暂停的函数。Generator函数可以用于异步编程,状态机和迭代器等情况
  • rest参数 (可变参数)

事件循环 微任务宏任务

事件循环是指一种机制,用于处理 JavaScript 中的异步任务,例如定时器任务、网络请求等。JavaScript 引擎会在主线程中维护一个任务队列,其中包括宏任务和微任务,事件循环会不断地从这个任务队列中取出一个任务来执行,直到队列为空。

宏任务指的是一些需要在主线程中执行的任务,例如 setTimeout、setInterval、I/O 操作等。当这些任务加入到任务队列中时,事件循环会等待主线程空闲时再去执行它们。

微任务指的是一些需要在当前任务执行结束后立即执行的任务,例如 Promise 的 resolve 和 reject 回调函数、MutationObserver 的回调函数等。当这些任务加入到任务队列中时,事件循环会尽快执行它们。在执行宏任务时,如果它产生了微任务,那么这些微任务会先被放入到微任务队列中,等待当前宏任务执行完毕后立即执行

总结一下,事件循环是一种处理 JavaScript 中异步任务的机制,包括宏任务和微任务,其中微任务具有优先级。在执行宏任务时,如果它产生了微任务,那么这些微任务会被放到微任务队列中等待执行。

最后一个没有的原因是因为resolve()的意思是把 Promise对象实例的状态从pending变成 fulfilled(即成功),最后一个没有执行resolve()即不执行then
例题

js事件循环机制event-loop 宏任务微任务

例题

箭头函数与普通函数的区别

  • 都是匿名函数
  1. this 永远指向其上下文的 this ,任何方法都改变不了其指向,如 call() , bind() , apply()
  2. 箭头函数没有自己的this绑定,它的this指向是在定义函数时确定的。而普通函数的this指向是在函数调用时确定的。
  3. 箭头函数不能使用arguments对象,但是可以使用rest参数来取代arguments对象。
  4. 箭头函数没有prototype属性,因此不能用作构造函数
  5. 箭头函数不能使用new关键字调用,因为它没有this绑定
  6. 箭头函数写法更为简洁明了,适合处理单一功能的函数。而普通函数更为灵活,适合处理复杂的业务逻辑
  7. 箭头函数不能作为Generator函数,而普通函数可以。

总的来说,箭头函数比普通函数更为简洁和直观,可以使代码更为易读易懂。但是,在一些特殊情况下,比如需要使用this或者需要定义构造函数时,仍然需要使用普通函数

this 指向

this 箭头函数中的this

var let const

var,let和const都是用于声明变量的关键字,但是它们有一些区别。

  • var:在ES5中,var是用于声明变量的关键字,它的作用域是函数作用域或全局作用域。在ES6中,var仍然可以使用,但是一般不推荐使用,因为它容易造成变量的提升问题

  • let:在ES6中,let是用于声明变量的关键字,它的作用域是块级作用域。它可以重新赋值,但不能重新声明已经存在的变量

  • const:在ES6中,const也是用于声明变量的关键字,但它用于声明常量。它的作用域也是块级作用域,并且不能重新赋值,也不能重新声明已经存在的常量

由此可见,使用let和const可以避免变量提升问题,让代码更加安全可靠。同时,合理使用const还可以让代码更加易于维护。

暂时性死区
暂时性死区(Temporal Dead Zone,简称TDZ)是指在ES6规范中,let和const声明的变量,在声明前使用会抛出异常,这个异常即为暂时性死区

具体来说,JS引擎会在代码块开始执行前,扫描该块内的所有变量声明(var声明的变量除外),将这些变量全部记录在一个地方,然后就可以开始执行代码了。如果在代码块中使用了一个尚未声明的变量,JS引擎会根据这个变量所出现的位置,去查询这个变量是否存在记录中。如果记录中存在这个变量,JS引擎就会将这个变量的值设置为undefined。但是,如果这个变量在记录中不存在,JS引擎就会抛出一个ReferenceError错误,表示这个变量在暂时性死区中
以下是一个例子:

console.log(x); // ReferenceError: x is not defined
let x = 'hello';

在这个例子中,由于x变量在声明前被使用了,所以会抛出一个ReferenceError错误。这是因为let声明的变量在声明前不会被赋值,也不会被提升,而是处于暂时性死区中。

数组新增:扩展运算符 …

  1. 在函数定义中,扩展运算符可以用于将一个不确定数量的参数收集为一个数组。例如:
function sum(...args) {
  return args.reduce((total, num) => total + num);
}
console.log(sum(1, 2, 3)); //输出6
console.log(sum(1, 2, 3, 4, 5)); //输出15
  1. 在函数调用中,扩展运算符可以用于传入一个数组,并将数组的值作为参数传入函数。例如:
function add(a, b, c) {
  return a + b + c;
}
const arr = [1, 2, 3];
console.log(add(...arr)); //输出6
  1. 扩展运算符还可以用于合并数组,例如:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [...arr1, ...arr2];
console.log(arr3); //输出[1, 2, 3, 4, 5, 6]
  1. 扩展运算符在对象解构时也非常有用
const person = { name: 'Alice', age: 25, address: '123 Main Street' };
const { name, ...rest } = person;
console.log(name); //输出Alice
console.log(rest); //输出{ age: 25, address: '123 Main Street' }
  1. 在解构赋值中,未被读取的可遍历的属性,分配到指定的对象上面
const obj = { a: 1, b: 2, c: 3 };
const { a, b } = obj;
console.log(a, b); // 输出:1 2

在上面的代码中,对象 obj 中的属性 c 并没有被读取,也没有被分配到任何对象中。

然而,在解构赋值时,我们可以通过使用 ... 操作符来捕获这些未被读取的属性,并将它们分配到指定的对象上。例如:

const obj = { a: 1, b: 2, c: 3 };
const { a, ...rest } = obj;
console.log(a, rest); // 输出:1 { b: 2, c: 3 }

JavaScript对象ES6新增方法

  1. Object.assign():将一个或多个对象的属性复制到另一个对象中。
  2. Object.keys():返回一个给定对象的所有可枚举属性的数组
  3. Object.values():返回一个给定对象的所有可枚举属性值的数组
  4. Object.entries():返回一个给定对象的所有可枚举属性的键值对数组
  5. Object.is():比较两个值是否相等
  6. Object.setPrototypeOf():设置一个对象的原型(即__proto__属性)。
  7. Object.getOwnPropertySymbols():返回一个给定对象自身的所有symbol属性的数组。
  8. Object.getOwnPropertyDescriptors():返回一个给定对象自身的所有属性的描述符对象。
  9. Object.fromEntries():将一个键值对的数组转换成一个对象
    例题

ES6数组新增方法

  1. Array.from():将类数组对象可迭代对象转换为真正的数组。
  2. Array.of()创建一个新数组,无论传入的参数数量或类型如何,都将其作为一个元素添加到数组中。
  3. Array.prototype.fill():使用指定的值填充数组中的所有元素
  4. Array.prototype.find():返回第一个满足条件的数组元素
  5. Array.prototype.findIndex():返回第一个满足条件的数组元素的索引
  6. Array.prototype.keys():返回一个新的Array Iterator对象,该对象包含数组中每个索引的键
  7. Array.prototype.values():返回一个新的Array Iterator对象,该对象包含数组中每个索引的值
  8. Array.prototype.entries():返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对

Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构

Set和Map是ES6中新增的数据结构。
Set是一种无序且不重复的集合,其中的每个元素都是唯一的,可以用来存储一些不需要重复的数据,如数字、字符串等。Set具有以下特点:

  • Set集合中的元素是唯一的,不会重复。
  • Set集合中的元素是无序的。
  • 可以使用迭代器访问Set集合中的元素。

Set的创建方法如下:

let set = new Set(); // 创建一个空的Set
let set = new Set([1, 2, 3]); // 创建一个包含1、2、3的Set

Set的常用方法包括:

  • add(value):向Set集合中添加元素。
  • delete(value):从Set集合中删除元素。
  • has(value):判断Set集合中是否包含某个元素。
  • clear():清空Set集合。
  • size:获取Set集合中元素的个数

Map是一种键值对的集合,其中的每个元素都由键和值组成,可以用来存储一些键值对的数据,如对象、字符串等。Map具有以下特点:

  • Map集合中的键是唯一的,值可以重复
  • Map集合中的键是无序的。
  • 可以使用迭代器访问Map集合中的键值对。

Map的创建方法如下:

let map = new Map(); // 创建一个空的Map
let map = new Map([
  ["name", "张三"],
  ["age", 18],
  ["gender", "男"]
]); // 创建一个包含三个键值对的Map

Map的常用方法包括:

  • set(key, value):向Map集合中添加键值对。
  • get(key):获取Map集合中指定键的值
  • delete(key):从Map集合中删除指定键值对。
  • has(key):判断Map集合中是否包含某个键。
  • clear():清空Map集合。
  • size:获取Map集合中键值对的个数

for of 和for in区别

  1. 区别在于迭代的对象不同,for in是遍历键名,for of是遍历键值
  2. for...of不能循环普通对象,需要和Object.keys()搭配使用
  3. 最好在循环对象属性的时候使用for...in,在遍历数组的时候的时候使用for...of

for...of循环用于遍历可迭代对象(如数组、字符串、Set、Map等),它会循环遍历集合中的值,而不是索引。语法如下:

for (const element of iterable) {
  // do something with element
}

for...in循环用于遍历对象的可枚举属性(包括自身的属性和继承的属性),它会循环遍历对象的属性名。语法如下:

for (const key in object) {
  if (object.hasOwnProperty(key)) {
    // do something with object[key]
  }
}

总之,for...of用于遍历集合中的值,for...in用于遍历对象的属性名。

map和forEach

相同
每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组)
不同

  • forEach没有返回值
  • map会分配内存空间存储新数组并返回
    map和forEach都是用于遍历数组的方法。

map方法将数组中的每个元素按照一定规则进行转换,并返回一个新的数组,不改变原数组。例如,将一个数组中的每个元素都加1,即可使用如下代码:

const arr = [1, 2, 3, 4];
const newArr = arr.map(item => item + 1);
console.log(newArr); // [2, 3, 4, 5]

forEach方法则是遍历数组中的每个元素,并执行指定的函数回调。例如,将一个数组中的每个元素都打印出来,即可使用如下代码:

const arr = [1, 2, 3, 4];
arr.forEach(item => console.log(item));

其中的函数回调可以接受三个参数:当前元素、当前元素的索引和数组本身。例如:

const arr = [1, 2, 3, 4];
arr.forEach((item, index, array) => console.log(item, index, array));

需要注意的是,map方法返回一个新的数组,而forEach方法并不返回任何值。

promise和async/await区别

  1. promise是ES6的内容,async/await属于ES7
  2. async/await是基于Promise实现的,它不能用于普通的回调函数
  3. async/await使异步代码看起来像同步代码,解决回调地狱问题的代码更加的清晰
  4. promise的错误可以通过catch来捕捉(建议尾部捕获错误);
    async/await既可以用.then,又可以用try-catch捕捉
  5. async/await无法完全替代promise不同网络请求无法同时发起

promise系列

添加链接描述

  • 简单实现promise
  • MDN使用promise
  • promise.all
    MDN文档

面向对象的三个特征

面向对象编程中,有三个基本特征,即:封装、继承和多态。

  1. 封装(Encapsulation):封装是指将数据和操作数据的方法打包在一起,形成一个不可分割的独立实体。这个实体对于外部代码来说是隐蔽的外部代码需要使用它提供的接口来进行访问。封装的目的是提高代码的安全性可维护性,同时也方便代码复用
  2. 继承(Inheritance):继承是指子类可以继承父类的属性和方法。子类可以使用父类的属性和方法,同时也可以重写或扩展父类的方法。继承的目的是提高代码的重用性和可扩展性
  3. 多态(Polymorphism):多态是指同一方法不同情况下的不同表现形式。在面向对象编程中,多态的实现需要借助继承和接口。多态的目的是增加代码的灵活性和可维护性

这三个特征是面向对象编程的基础,也是代码设计的关键。在面向对象编程的实践中,要充分运用封装、继承和多态,为代码设计提供更好的抽象和封装。

原生JS中获取DOM元素的方法

  1. 通过ID获取元素:使用document.getElementById(id)方法,其中id为元素的ID属性值。
  2. 通过标签名称获取元素:使用document.getElementsByTagName(tagName)方法,其中tagName为标签名称。
  3. 通过类名获取元素:使用document.getElementsByClassName(className)方法,其中className为元素的class属性值。
  4. 通过名称获取元素:使用document.getElementsByName(name)方法,其中name为元素的name属性值。
  5. 通过选择器获取元素:使用document.querySelector(selector)方法,其中selector为CSS选择器表达式。
  6. 通过标签名称和类名获取元素:使用document.querySelectorAll(selector)方法,其中selector为组合的CSS选择器表达式。
  7. 通过自定义属性获取元素:使用document.querySelector('[attribute=value]')方法,其中attribute为自定义属性名,value为属性值。
  8. 获取html的方法(document.documentElement)
  9. 获取html的方法(document.documentElement)

(二)js

数组去重

  1. 使用Set对象去重
let arr = [1, 1, 2, 2, 3, 3];
let newArr = [...new Set(arr)];
console.log(newArr); // [1, 2, 3]
  1. 使用循环去重
let arr = [1, 1, 2, 2, 3, 3];
let newArr = [];
for(let i = 0; i < arr.length; i++){
    if(newArr.indexOf(arr[i]) === -1){
        newArr.push(arr[i]);
    }
}
console.log(newArr); // [1, 2, 3]

第二种方式比第一种方式效率低,因为它需要使用循环来检查元素是否已经存在于新数组中。而第一种方式使用Set对象会自动去重,所以速度更快。

null和undefined的区别

在JavaScript中,nullundefined都代表着没有值的情况,但是两者有一些细微的区别:

  1. undefined表示声明了变量但没有给它赋值,或者对象的属性没有被赋值。
  2. null表示变量被明确地赋值为null,表示该变量的值为空
  3. 当使用非严格比较(==)时,它们是相等的。
  4. 但是,在使用严格比较(===)时,它们是不相等的,因为它们的类型不同
  5. 当你想要明确地表示某个变量的值为空时,应该使用null而不是undefined。

总之,null是在程序中明确地将变量赋值为空,而undefined是在程序中隐式地将变量赋值为空。

数组和类数组的区别

数组和类数组的区别主要在于它们的数据类型属性

  1. 数据类型数组是 JavaScript 中的一种数据结构,它可以存储多个值,并且这些值都是同一种数据类型。而类数组是指一组类似数组的对象,它们有类似数组的属性和方法,但并不是真正的数组,因为它们没有完整的数组功能和方法。
  2. 属性数组具有一系列的属性,如 length 属性表示数组的长度,而类数组一般没有 length 属性,如果想获取类数组的长度,需要使用类数组的相关方法来实现。
  3. 方法数组拥有很多方法,如 push()、pop()、shift()、unshift()等,而类数组只有部分数组方法可以使用,如 slice()、splice()等。

总的来说,数组是一种完整的数据类型,有自己的属性和方法,而类数组只是一组具有类似数组属性和方法的对象,不能完全替代数组。

预编译,scope链

添加链接描述

闭包

JavaScript闭包是指函数可以访问其创建时所在的词法作用域即使函数在其创建时已经离开了该作用域。换句话说,闭包可以“记住”其创建时的环境。

闭包的常见用途包括:

  1. 保护变量:使用闭包可以避免变量被外界修改,增加代码的稳定性和可靠性。

  2. 延长变量生命周期:闭包可以使得在函数执行完毕后仍能访问到函数内部的变量,从而实现变量的长期存储。

  3. 封装私有变量:使用闭包可以将变量封装在函数内部,外部无法访问,从而提高代码的安全性。

  4. 实现模块化:通过使用闭包可以实现类似于模块化的功能,将一些变量和方法封装在函数内部,从而避免全局命名冲突

  5. 函数内套函数

  6. 嵌套函数可访问声明于它们外部作用域的变量
    能够读取其他函数内部变量的函数 或者子函数在外调用,子函数所在的父函数的作用域不会被释放

add5 是等于 makeAdder(5) 返回的 function(y),所以add5(2)为function(2)因为闭包,能访问外层x=5,所以return x+y=7

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5); //add5 是等于 makeAdder(5) 返回的 function(y),所以add5(2)为function(2)因为闭包,能访问外层x=5,所以return x+y=7
var add10 = makeAdder(10);

console.log(add5(2));  // 7 
console.log(add10(2)); // 12
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}
var add5 = makeAdder(5)(40);
var add10 = makeAdder(10);
console.log(add5);  // 45
console.log(add10(2)); // 12

如何实现一个私有变量

在JavaScript中,可以使用闭包来实现私有变量。

  1. 定义一个函数,这个函数中包含一些内部变量和方法,这些变量和方法都是私有的,外部无法访问。
  2. 在这个函数内部定义一个返回对象,这个返回对象中包含可以被外部访问的公有方法。这些公有方法可以访问和操作内部的私有变量和方法。
  3. 使用闭包将这个返回对象和内部变量和方法关联起来,防止外部直接访问内部变量和方法。
function Person(name) {
  var age = 0; //私有变量
  function increaseAge() { //私有方法
    age++;
  }
  //返回对象
  return {
    getName: function() {
      return name;
    },
    getAge: function() {
      return age;
    },
    grow: function() {
      increaseAge();
    }
  }
}
var person = Person('张三');
console.log(person.getName()); //输出:张三
console.log(person.getAge()); //输出:0
person.grow();
console.log(person.getAge()); //输出:1

在这个例子中,age和increaseAge方法都是私有的,只能在Person函数内部访问。getName、getAge和grow方法是可访问的公有方法,可以访问和操作age变量和increaseAge方法。使用闭包将返回对象和内部变量和方法关联起来,保证了age和increaseAge方法的私有性。

js中的数据类型

添加链接描述

数组常用方法

push(): 在尾部添加一个或多个元素,并返回新的数组长度
pop(): 删除尾部元素,并返回该元素的
unshift(): 在头部添加一个或多个元素,并返回新的数组长度
shift(): 删除头部元素,并返回该元素的
slice(): 返回开始位置到结束位置之间选定的数组部分,修改原数组
splice(): 从指定位置删除/插入制定数量的元素,并返回被删除的元素
reverse(): 颠倒数组中元素的顺序
sort(): 按照升序或降序排列数组中的元素
concat(): 合并两个或多个数组,并返回新数组
join(): 将数组为字符串并连接其中的元素
indexOf(): 返回匹配元素的第一个索引,如果没有找到则返回-1
lastIndexOf(): 返回匹配元素的最后一个索引,如果没有找到则返回-1
forEach(): 遍历数组中的每个元素,并对其进行操作
map(): 将数组中的每个元素传递给函数,并返回包含结果的新数组
filter(): 返回包含数组中满足条件的所有元素的新数组
reduce(): 从左到右执行累加器函数,以便将数组减少为单个值
isArray(): 确定一个值是否为数组
includes() 返回布尔值,表示是否至少找到一个与指定元素匹配的项
every(): 如果数组中所有元素都满足条件,则返回true否则返回false
some(): 如果数组中至少有一个元素满足条件,则返回true否则返回false
from(): 将类数组结构转换为数组实例
of(): 一组参数转换为数组实例
keys() 返回数组索引
values() 返回数组
entries() 返回数组 索引和值
find()返回第一个匹配的元素
findIndex()返回第一个匹配元素的索引
添加链接描述

手动实现Array.prototype.map方法

  1. 定义一个函数 map,它接收一个回调函数作为参数,回调函数接收三个参数 currentValue(当前元素值)、index(当前索引)和 array(原始数组)。
  2. 定义一个空数组 result,用于存储回调函数处理后的值。
  3. 使用 for 循环遍历原始数组,依次将每个元素传入回调函数进行处理,并将处理后的值存储到 result 数组中。
  4. 返回 result 数组。
Array.prototype.myMap = function (callback) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    const value = callback(this[i], i, this);
    result.push(value);
  }
  return result;
};
// 使用示例
const arr = [1, 2, 3];
const mappedArray = arr.myMap((value) => value * 2);
console.log(mappedArray); // [2, 4, 6]

手动实现Array.prototype.filter方法 (过滤)

可以通过遍历数组,筛选出符合条件的元素,将其放入一个新数组中返回,从而手动实现Array.prototype.filter方法。

Array.prototype.myFilter = function(callback) {
  const newArray = [];
  for (let i = 0; i < this.length; i++) {
    if (callback(this[i], i, this)) {
      newArray.push(this[i]);
    }
  }
  return newArray;
}
const arr = [1, 2, 3, 4, 5];
const result = arr.myFilter(item => item % 2 === 0);
console.log(result); // [2, 4]

这里使用了回调函数作为参数,回调函数接收当前遍历到的元素、元素索引以及原数组作为参数,如果回调函数返回true,则将该元素加入新数组中。

手动实现Array.prototype.reduce方法(累加器)

Array.prototype.reduce方法是一个高阶函数,用于遍历数组并对每个元素进行处理,最终返回一个结果

手动实现Array.prototype.reduce方法的步骤如下:

  1. 接收两个参数,第一个参数是回调函数,第二个参数是初始值
  2. 遍历数组,对每个元素调用回调函数,传入四个参数:累加器、当前元素、当前索引、原数组。
  3. 回调函数的返回值作为下一个累加器的值,最终得到一个结果。
  4. 如果没有初始值,则将第一个元素作为初始值,从数组第二个元素开始遍历。
Array.prototype.myReduce = function(callback, initialValue) {
  var accumulator = initialValue !== undefined ? initialValue : this[0];
  for (var i = initialValue !== undefined ? 0 : 1; i < this.length; i++) {
    accumulator = callback(accumulator, this[i], i, this);
  }
  return accumulator;
};
var arr = [1, 2, 3];
var sum = arr.myReduce(function(acc, curr) {
  return acc + curr;
}, 0);
console.log(sum); // 输出6

使用方法与原生的Array.prototype.reduce方法一样

遍历对象的方法

  1. for...in循环:使用for…in循环可以遍历对象的所有属性,包括对象继承而来的属性。
  2. Object.keys(obj):Object.keys()方法返回一个包含对象所有属性名的数组,可以遍历对象自身的属性。
  3. Object.values(obj):Object.values()方法返回一个数组,包含对象所有可枚举属性的值。
  4. Object.entries(obj):Object.entries()方法返回一个包含对象所有可枚举属性的键值对的数组。
  5. Object.getOwnPropertyNames(obj):Object.getOwnPropertyNames()方法返回一个数组,包含对象自身的所有属性名,无论属性是否可枚举
  6. 使用Symbol.iterator:可以使用Symbol.iterator方法定义对象的遍历器,然后使用for…of循环或者展开运算符(…)遍历对象的属性和值。
  7. JSON.stringify(obj)JSON.parse(str):使用JSON.stringify()将对象转换为JSON格式的字符串,使用JSON.parse()将JSON格式的字符串转换为对象,并遍历对象的属性和值。

object系列

添加链接描述

字符串常用方法(增删改查)

  1. charAt(index): 返回指定位置的字符。
  2. concat(str1, str2): 连接两个或多个字符串,并返回新的字符串。
  3. indexOf(substr, [start]): 返回字符串中指定子串出现的位置,如果没有找到则返回-1。
  4. lastIndexOf(substr, [start]): 从字符串的末尾开始查找指定子串出现的位置,如果没有找到则返回-1。
  5. match(regexp): 将正则表达式和字符串进行匹配,并返回匹配结果。
  6. replace(regexp/substr, replacement): 替换字符串的指定部分
  7. search(regexp): 在字符串中查找指定内容的位置
  8. slice(start, [end]): 截取字符串的一部分并返回
  9. split([separator, [limit]]): 将字符串分割****为子字符串数组
  10. substr(start, [length]): 从指定位置开始截取字符串中指定长度的子字符串并返回。
  11. substring(start, [end]): 返回字符串的指定部分
  12. toLowerCase(): 将字符串转换为小写字母。
  13. toUpperCase(): 将字符串转换为大写字母。
  14. trim(): 去除字符串两端的空格并返回

类型转换

JavaScript 中存在显式类型转换隐式类型转换

  1. 显式类型转换:
    • Number() 将数据类型转换为数值型,包括字符串、布尔值等
    • String() 将数据类型转换为字符串类型
    • Boolean() 将数据类型转换为布尔类型
    • parseInt()字符串转换为整型
    • parseFloat()字符串转换为浮点型
    • toString()数据类型转换为字符串类型
  2. 隐式类型转换:
    • 数字与字符串相加,会将数字转换为字符串,然后进行拼接操作
    • 数字与布尔值相加,会将布尔值转换为数字,然后进行加法操作
    • 字符串与布尔值相加,会将布尔值转换为字符串,然后进行拼接操作
    • 条件语句中使用除了布尔值之外的其他基本数据类型时,会将其隐式转换为布尔值

JavaScript的继承

添加链接描述

如何解决异步回调地狱

异步回调地狱指的是在处理多个异步任务时,由于异步任务之间的依赖关系和回调函数的嵌套,导致代码可读性差、维护难度大的问题
为了解决异步回调地狱问题,可以采用以下方法:

  1. 使用 Promise
    Promise 是 ES6 中新增的异步编程解决方案,主要解决了回调函数多层嵌套的问题。通过使用 Promise,可以将异步操作转换为链式的代码结构,使得代码更加易读、易懂、易维护。
  2. 使用 async/await
    async/await 是 ES8 中引入的异步编程解决方案,基于 Promise 实现。async/await 通过使用 async 关键字和 await 关键字,使得异步代码的执行流程更加清晰,代码结构更加简洁、易读。
  3. 使用事件驱动模型
    事件驱动模型是一种将处理逻辑代码流中分离出来的编程模型,主要将异步任务的处理逻辑回调函数分离开,使代码结构更加清晰、易读、易维护。在事件驱动模型中,异步任务的处理逻辑会被封装在事件处理函数中,当异步任务完成后,触发相应的事件处理函数,执行处理逻辑。

前端中的事件流

前端中的事件流指的是事件在页面元素间传递的过程。常见的事件流有三种:

  1. 冒泡事件流
    在冒泡事件流中,事件从最内层的元素向外逐层传递。换句话说,事件首先被触发在最内层的元素,然后逐步向外传递,直到传递到最外层的元素。在这个过程中,父元素继承子元素的事件。冒泡事件流是默认的事件流
  2. 捕获事件流
    与冒泡事件流相反,在捕获事件流中,事件从最外层的元素开始捕获,一直传递到最内层的元素。在这个过程中,父元素不能继承子元素的事件。
  3. 组合事件流
    组合事件流是综合了冒泡事件流和捕获事件流的事件流。在组合事件流中,事件首先是从最外层元素捕获到最内层元素,然后再从最内层元素开始冒泡传递到最外层元素

在事件流中,事件对象可以通过 event.stopPropagation()event.preventDefault() 方法来停止事件的传播默认行为的发生

如何让事件先冒泡后捕获

一般是先捕获后冒泡,实现冒泡后捕获的效果,对于同一事件 监听捕获和冒泡 分别对应相应的处理函数 监听到捕获事件 先暂缓执行 直到冒泡事件被捕获后在执行捕获事件

事件委托(事件代理)

事件委托(Event Delegation)是一种用于提高页面性能并减少内存消耗的优化技巧,在页面中通过事件冒泡实现的。
事件代理是通过将事件处理器绑定到一个父级元素,而不是每个子元素上,来提高性能的技术。它利用了事件冒泡机制,将子元素的事件委托给它们的父级元素。
例如,假设我们有一个列表,其中包含多个项。如果我们要为每个列表项添加一个单击事件处理程序,我们可以使用事件委托来实现。相反,为每个列表项分别绑定事件处理程序可能会导致性能问题。

<ul id="menu">
  <li>首页li>
  <li>商品li>
  <li>分类li>
  <li>购物车li>
  <li>我的li>
ul>
document.getElementById('menu').addEventListener('click', function(event) {
  if (event.target.nodeName === 'LI') {
    console.log('你单击了选项 ' + event.target.textContent);
  }
});

在此示例中,我们只将单击事件处理程序绑定到列表的父级元素。当用户单击列表项时,事件冒泡到父级元素,并通过检查事件目标的节点名称来确定用户单击的是哪个列表项。
优点:
是我们只绑定了一个事件处理程序,而不是为每个列表项都绑定一个事件处理程序,这样可以提高页面性能,减少内存消耗。

mouseover和mouseenter的区别

添加链接描述

js的new操作符做了哪些事

添加链接描述

改变函数内部this指针的指向函数(bind,apply,call区别)

JavaScript中的函数是一等公民,因此它们可以赋值给变量,作为参数传递等。同时,函数具有this关键字,它指向调用该函数的对象。
有时候我们需要改变函数内部this指针的指向,这时可以使用bind、apply和call方法。

  1. bind方法
    bind()方法创建一个新的函数,新函数与原函数相同,但是this的指向被绑定到指定的对象上,不会立即执行
    语法:
function.bind(thisArg[, arg1[, arg2[, ...]]])

参数说明:

  • thisArg:函数运行时的this值。
  • arg1, arg2, …:函数运行时的参数列表。
    示例:
let obj1 = {
  name: 'Lucy',
  sayName: function() {
    console.log(this.name);
  }
};
let obj2 = {
  name: 'Bob'
};
let sayBobName = obj1.sayName.bind(obj2);
sayBobName(); // 输出:Bob
  1. apply方法
    apply()方法调用一个函数,this指向被绑定的对象,参数以数组形式传入。该方法不会创建新的函数。
    语法:
function.apply(thisArg, argsArray)

参数说明:

  • thisArg:函数运行时的this值。
  • argsArray:一个数组或类数组对象,该对象用于指定函数运行时的参数列表。
    示例:
let obj1 = {
  name: 'Lucy',
  sayName: function() {
    console.log(this.name);
  }
}; 
let obj2 = {
  name: 'Bob'
}; 
obj1.sayName.apply(obj2); // 输出:Bob
  1. call方法
    call()方法调用一个函数,this指向被绑定的对象,参数以逗号分隔的列表形式传入。该方法不会创建新的函数。
    语法:
function.call(thisArg, arg1, arg2, ...)

参数说明:

  • thisArg:函数运行时的this值。
  • arg1, arg2, …:函数运行时的参数列表。
    示例:
let obj1 = {
  name: 'Lucy',
  sayName: function() {
    console.log(this.name);
  }
};
let obj2 = {
  name: 'Bob'
};
obj1.sayName.call(obj2); // 输出:Bob

js各种位置 clientHeight scrollHeight,offsetHeight以及scrollTop,offsetTop,clientTop的区别

  • clientHeight元素的可见高度,包括padding但不包括border和margin
  • clientTop元素上边框(border)的宽度。
  • scrollHeight:元素内容的总高度,包括超出视口的部分,不包括border和margin
  • scrollTop:元素内容被卷起的高度,即滚动条向下滚动的距离
  • offsetHeight:元素在垂直方向上的高度,包括padding、border和可选的margin。
  • offsetTop:元素相对于父元素的顶部位置,与包含块相关

瀑布流

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。 瀑布流常用于图片库、社交网站和博客等网站,可以让用户更轻松的浏览大量内容。
如何判断浏览器是否触底
document.body.clientHeigth + document.body.scrollTop===document.body.scrollHeight

window.onscroll = function() {
  // 获取页面文档高度
  const offsetHeight = document.documentElement.offsetHeight;
  // 获取页面滚动高度
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  // 获取浏览器窗口高度
  const windowHeight = window.innerHeight;
  // 如果滚动到页面底部
  if (offsetHeight - scrollTop - windowHeight <= 0) {
    console.log("触底啦");
  }
}

js拖拽功能的实现

JS拖拽功能的实现主要分为以下几个步骤:

  1. 给需要拖拽的元素添加mousedown事件,当鼠标按下时记录鼠标坐标被拖拽元素的位置
  2. document对象添加mousemove事件,当鼠标移动时计算出被拖拽元素的新位置,并设置其样式
  3. document对象添加mouseup事件,当鼠标释放取消鼠标移动事件。
var dragElement = document.getElementById("dragElement"); // 获取元素
var mouseX = 0, mouseY = 0, offsetX = 0, offsetY = 0;
dragElement.addEventListener("mousedown", function(e) {
  // 鼠标坐标
  mouseX = e.clientX;
  mouseY = e.clientY;
  // 被拖拽元素的位置
  offsetX = mouseX - dragElement.offsetLeft;
  offsetY = mouseY - dragElement.offsetTop;
  // 给`document`对象添加`mousemove`事件
  document.addEventListener("mousemove", mousemoveHandler);
});
// 给`document`对象添加`mouseup`事件
document.addEventListener("mouseup", function() {
  document.removeEventListener("mousemove", mousemoveHandler);
});
// 计算元素的偏移值
function mousemoveHandler(e) {
  dragElement.style.left = (e.clientX - offsetX) + "px";
  dragElement.style.top = (e.clientY - offsetY) + "px";
}

在以上代码中,我们首先获取了被拖拽元素的dom节点和鼠标按下时的坐标。然后给document添加mousemove事件,当鼠标移动时更新被拖拽元素的样式,其中使用了鼠标坐标和元素的偏移值来计算新的位置。当鼠标释放时,移除mousemove事件。

html5的拖放功能

HTML5提供了拖放API,使得开发者可以在网页上实现拖放操作,丰富用户的交互体验。

DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Drag and Droptitle>
  <style>
    #drag {
      width: 100px;
      height: 100px;
      background-color: red;
      cursor: move;
    }
    #drop {
      width: 200px;
      height: 200px;
      border: 2px solid black;
    }
  style>
  <script>
    function allowDrop(event) {
      event.preventDefault();
    }
    function drag(event) {
      event.dataTransfer.setData("text", event.target.id);
    }
    function drop(event) {
      event.preventDefault();
      var data = event.dataTransfer.getData("text");
      event.target.appendChild(document.getElementById(data));
    }
  script>
head>
<body>
  <div id="drag" draggable="true" ondragstart="drag(event)">div>
  <div id="drop" ondrop="drop(event)" ondragover="allowDrop(event)">div>
body>
html>

在这个例子中,我们创建了一个红色的可拖拽元素(id为“drag”),以及一个“放置区域”(id为“drop”)。我们给可拖拽元素添加了draggable="true"属性,并设置了ondragstart事件处理函数drag()drag()函数使用数据传输对象将元素的id传递给拖放事件。

在放置区域上我们添加了ondragoverondrop事件处理函数。ondragover函数调用preventDefault()方法,用于防止浏览器默认地处理拖放事件ondrop函数中我们调用preventDefault()方法停止浏览器默认行为,然后获取传递的数据,并使用它在放置区域中定位拖拽元素。

这是一个简单的演示,不过它能够展示HTML5的拖放功能。

异步加载js方法

异步加载JavaScript代码是为了避免页面加载速度过慢而采用的一种方案。异步加载js方法可以有以下几种实现方式:

  1. 使用defer属性:将
<script defer src="script.js"></script>
  1. 使用async属性:将
<script async src="script.js"></script>
  1. 动态创建script标签并插入到head或body中:使用JavaScript动态创建script标签并插入到网页中,以异步方式加载并执行。
var script = document.createElement('script');
script.src = 'script.js';
document.head.appendChild(script);

commonJs、AMD:requireJS、CMD规范

CommonJSAMD:RequireJSCMD都是JavaScript模块化规范。每个模块都是一个单独的文件,有自己的作用域,模块之间通过特定的方式引用和调用。

  1. CommonJS规范:
    CommonJS是一种服务器端模块规范,即Node.js采用的模块规范。它的主要特点是通过require()方法同步加载依赖的模块,在Node.js中,一个文件就是一个模块,每个模块都有自己的作用域,要想在模块之间共享变量,可以通过exports对象把变量或函数暴露出去,其他模块则通过require()方法来引用这些变量或函数。
  2. AMD规范:
    AMD(Asynchronous Module Definition)规范是一种在浏览器环境下异步加载模块的规范,它的主要特点是可以在不阻塞页面加载的情况下异步加载模块。它使用define()函数来定义模块,使用require()函数来异步加载依赖的模块,依赖模块通过回调函数作为参数传递给require()函数。
    RequireJS是一个AMD模块加载器,它实现了AMD规范,可以在浏览器环境下异步加载模块。它的主要特点是可以通过配置文件来加载模块,使得模块的加载变得非常方便。
  3. CMD规范:
    CMD(Common Module Definition)规范是一种在浏览器环境下异步加载模块的规范,与AMD类似,它使用define()函数来定义模块,但与AMD不同的是,它使用require()函数来同步加载模块。CMD规范的代表Sea.js

实现一个once函数 传入函数的参数只执行一次

实现一个 once 函数:

function once(fn) {
  let result;
  console.log(result,'result')
  let hasBeenCalled = false;
  return function(...args) {
    if (!hasBeenCalled) {
      result = fn(...args);
      hasBeenCalled = true;
    }
    return result;
  }
}

once 函数接收一个函数作为参数,并返回一个新的函数。新的函数只会执行一次传入的函数,并返回其结果。在第一次执行之后,再次调用该新函数时会直接返回上一次调用的结果,不会再次执行传入的函数。
示例:

function add(a, b) {
  console.log('add called');
  return a + b;
}
const addOnce = once(add);
console.log(addOnce(1, 2)); // 输出 add called 和 3
console.log(addOnce(3, 4)); // 只输出 3,因为 add 函数只被调用了一次

将原生ajax封装成promise

function ajax(method, url, data) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.onload = function() {
      if (this.status >= 200 && this.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: this.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = function() {
      reject({
        status: this.status,
        statusText: xhr.statusText
      });
    };
    xhr.send(data);
  });
}

可以使用以下方式发起请求并处理响应:

ajax('GET', '/api/someUrl')
  .then(response => {
    console.log(response);
  })
  .catch(error => {
    console.error(error);
  });

这将使用GET请求发起一个/ajax/someUrl的API请求,并在请求成功或失败时分别打印响应或错误信息。

js监听对象属性的改变

可以使用ES6中引入的Proxy对象来监听对象属性的改变。Proxy对象可以拦截对象的各种操作,包括获取属性值、设置属性值、删除属性等,从而实现监听属性变化的功能。

let obj = { name: 'Tom', age: 20 };
let proxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log(`读取属性${key}`);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log(`设置属性${key}=${value}`);
    return Reflect.set(target, key, value, receiver);
  }
});
proxy.name = 'Jerry';
console.log(proxy.age);

在上述代码中,首先创建一个普通的对象obj,然后使用Proxy对象对其进行代理。在Proxy对象的构造器中,通过get和set函数分别实现对属性的读取设置的拦截。其中,target表示被代理的对象key表示属性名value表示属性值receiver表示Proxy对象本身
通过这种方式,当使用proxy对象对obj的属性进行读取或设置时,会触发相应的拦截操作,从而实现监听属性变化的功能。

setTimeout、setInterval和requestAnimationFrame

setTimeout、setInterval和requestAnimationFrame都是JavaScript中用于延迟执行或定时执行函数的方法,它们的作用相似但实现方式不同。
setTimeout方法用于延迟执行函数一次,它的语法是:

setTimeout(function, delay)

其中,第一个参数为需要延迟执行的函数,第二个参数为延迟的时间(单位为毫秒)。例如:

setTimeout(function() {
    console.log("Hello, world!");
}, 1000);

这段代码会在延迟1秒后输出"Hello, world!"。

setInterval方法用于定时执行函数,它的语法是:

setInterval(function, delay)

其中,第一个参数为需要定时执行的函数,第二个参数为定时时间(单位为毫秒)。例如:

setInterval(function() {
    console.log("Hello, world!");
}, 1000);

这段代码会每隔1秒钟就输出一次"Hello, world!"。

requestAnimationFrame方法也用于定时执行函数,但它的实现方式不同于setTimeout和setInterval。它的执行频率与屏幕的刷新频率相同,通常为每秒60次。它的语法是:

requestAnimationFrame(function)

其中,参数为需要定时执行的函数。例如:

function loop() {
    console.log("Hello, world!");
    requestAnimationFrame(loop);
}
loop();

这段代码会每隔1/60秒就输出一次"Hello, world!",直到程序停止或调用cancelAnimationFrame方法停止执行。相比于setTimeout和setInterval,requestAnimationFrame具有更好的性能和更准确的执行时间。但它也需要更多的代码实现。

那些操作会造成内存泄露

- 闭包
- 意外的全局变量
- 被遗忘的定时器
- 脱离dom的引用

什么是高阶函数

高阶函数指的是接收一个多个函数作为参数,并且返回另一个函数作为结果的函数。这意味着高阶函数可以操作其他函数,使得代码更加抽象和灵活。
例如,以下是一个将函数f作为参数并返回一个新函数的高阶函数:

def apply_func(func, arg):
    return func(arg)

当我们调用这个高阶函数并传入一个函数f和一个参数x时,它会返回f(x):

def square(x):
    return x ** 2
apply_func(square, 2)  # returns 4

在这个例子中,函数apply_func被定义为一个高阶函数,因为它接收一个函数作为参数,并在函数中使用它来计算结果。

原型与原型链

添加链接描述

点击按钮传送验证码 保证60s后才能再次发送验证码(刷新之后也要等60s完才发送):采用localstorage存储信息。

  1. 在前端页面中,添加一个发送验证码的按钮,并绑定点击事件:
<button id="sendCodeBtn">发送验证码button>
document.getElementById("sendCodeBtn").addEventListener("click", 
 function() {
  // 发送验证码的逻辑
});
  1. 发送验证码的逻辑如下:
let isSending = false; // 是否正在发送验证码
let countDownTime = 60; // 倒计时时间,单位为秒
function sendCode() {
  if (isSending) {
    return;
  }
  // 发送验证码的逻辑,例如通过 AJAX 请求发送验证码到后端
  isSending = true;
  countDown();
}
function countDown() {
  let btn = document.getElementById("sendCodeBtn");
  if (countDownTime > 0) {
    btn.disabled = true;
    btn.textContent = countDownTime + "秒后重试";
    countDownTime--;
    setTimeout(countDown, 1000);
  } else {
    btn.disabled = false;
    btn.textContent = "发送验证码";
    countDownTime = 60;
    isSending = false;
  }
}
  1. 当用户点击发送验证码按钮时,调用 sendCode() 方法即可开始发送验证码并启动倒计时。在倒计时过程中,按钮将被禁用,并显示剩余倒计时时间。当倒计时结束后,按钮将重新启用并显示“发送验证码”文本。
    注意:以上代码仅为示例,具体实现方式可能因开发环境和需求而异。

深浅拷贝

添加链接描述

防抖

添加链接描述

节流

添加链接描述

图片懒加载

图片防抖节流优化

作用域

添加链接描述

单例模式

添加链接描述

发布订阅模式

添加链接描述

策略模式

添加链接描述

类创建和继承

函数柯里化实现

(三)css

CSS3新特性

  • 边框和背景样式的控制(包括圆角、阴影、渐变等)
  • 文本效果(如文字阴影、填充、描边、换行等)
  • 字体控制(如自定义字体、字体大小、字体样式等)
  • 2D和3D转换(如旋转、缩放、倾斜等)
  • 动画和过渡效果
  • 多栏布局(如多列文字、多行文字等)
  • 盒模型的改进(如盒阴影、盒调整、盒大小等)
  • 媒体查询和响应式设计
  • 弹性盒子布局(Flexbox)
  • 网格布局(Grid Layout)
  1. 边框和背景样式的控制
  • 圆角(border-radius)
border-radius: 10px;
  • 阴影(box-shadow)
box-shadow: 5px 5px 5px #888888;
  • 渐变(linear-gradient)
background: linear-gradient(red, blue);
  1. 文本效果
  • 文字阴影(text-shadow)
text-shadow: 1px 1px 1px #888888;
  • 文字填充(text-fill-color)
-webkit-text-fill-color: blue;
  • 文字描边(-webkit-text-stroke)
-webkit-text-stroke: 1px black;
  • 文字换行(word-wrap)
word-wrap: break-word;
  1. 字体控制
  • 自定义字体(@font-face)
@font-face {
  font-family: "MyFont";
  src: url("myfont.woff2") format("woff2"),
       url("myfont.woff") format("woff");
}
  • 字体大小(font-size)
font-size: 14px;
  • 字体样式(font-style)
font-style: italic;
  1. 2D和3D转换
  • 旋转(rotate)
transform: rotate(45deg);
  • 缩放(scale)
transform: scale(1.5);
  • 倾斜(skew)
transform: skew(30deg);
  • 透视(perspective)
transform: perspective(1000px);
  1. 动画和过渡效果
  • 过渡(transition)
transition: all 1s ease;
  • 动画(animation)
@keyframes my-animation {
  from {
    transform: rotate(0);
  }
  to {
    transform: rotate(360deg);
  }
}

animation: my-animation 2s infinite;
  1. 多栏布局
  • 多列文字(column-count)
column-count: 3;
  • 多行文字(line-clamp)
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
  1. 盒模型的改进
  • 盒阴影(box-shadow)
box-shadow: 5px 5px 5px #888888;
  • 盒调整(box-sizing)
box-sizing: border-box;
  • 盒大小(calc)
width: calc(100% - 20px);
  1. 媒体查询和响应式设计
  • 媒体查询
@media screen and (max-width: 768px) {
  /* styles for small screens */
}
  • 响应式图片(img srcset)
<img src="image.jpg" srcset="image-small.jpg 480w, image-medium.jpg 768w, image-large.jpg 1200w" />
  1. 弹性盒子布局(Flexbox)
  • 容器属性
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
  • 项目属性
flex: 1 1 50%;
  1. 网格布局(Grid Layout)
  • 容器属性
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
  • 项目属性
grid-column: 1 / 3;
grid-row: 1 / 2;

这些新特性为开发人员提供了更多的选择和灵活性来创建美观、响应式且交互性强的网页和Web应用程序。

position属性比较

常用的属性值有:

  • static:默认值。元素在文档流中正常排布,不受top、right、bottom、left的影响。
  • relative:相对定位。元素在文档流中正常排布,但是可以通过设置top、right、bottom、left属性来相对于自身的位置进行微调。
  • absolute:绝对定位。元素脱离文档流,相对于其最近的已定位的祖先元素进行定位,如果没有已定位的祖先元素,则相对于body元素进行定位。
  • fixed:固定定位。元素脱离文档流,相对于浏览器窗口进行定位,不随滚动条滚动。
  • sticky:粘性定位。元素在超出指定阈值前表现为相对定位,当滚动到指定阈值时,元素表现为固定定位,即悬停效果。需要指定top、right、bottom、left其中一项才能生效。

display属性比较

  • block:块级元素,会以块状形式显示,可以设置宽度和高度,元素宽度默认为100%。
  • inline:内联元素,会顶格显示,宽高由内容撑开,不可以设置宽高
  • inline-block:内联块级元素,会以块状形式显示,但是可以和其他元素放在一行内,可以设置宽度和高度
  • none:不显示元素,元素将被隐藏,但依旧消耗空间
  • flex:Flex布局容器,用于设置弹性盒模型。
  • grid:元素将被显示为网格盒子。
  • 需要注意的是,虽然这些值通常用来控制元素的“显示”,但它们实际上会对元素的布局、定位、线框等方面产生影响,比如block元素会自动换行,inline元素不会换行,inline-block元素可以与其他元素同行等。

opacity=0、visibility:hidden、display:none区别

  • opacity=0 元素隐藏,不会改变页面布局,仍然占用页面空间, 可以触发绑定事件
  • visibility:hidden不会改变页面布局,可以理解为看不见摸得着,但仍会占据页面空间, 触发repaint
  • display:none会改变页面布局,可以理解为删掉了,看不见摸不着,触发reflow
  • 清除浮动

实现三角形

  1. 容器宽高设为0
  2. 容器边框设为透明,需要设置边框宽度
  3. 其中一条边框设置颜色、边框宽度(与容器边框宽度相同)
div{
        width: 0;
        height: 0;
        border: 200px transparent solid;
        border-left: blue 200px solid;
      }

盒模型

它具有: content(不包含 padding 和 border),padding,border,margin 四个属性,这就是盒子模型
盒模型有两种形式

  • 标准盒模型:box-sizing:content-box; 默认值,代表W3C盒模型,width只包含内容宽度
  • 怪异盒模型:box-sizing:border-box; 代表IE盒模型 width包含padding和border
    两种模式的区别
      标准模式会被设置的padding撑开,而怪异模式则相当于将盒子的大小固定好,再将内容装入盒子。盒子的大小并不会被padding所影响。

flex弹性布局

Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性

  • 任何一个容器都可以指定为 Flex 布局
  • 行内元素也可以使用 Flex 布局
    .box{
      display: inline-flex;
    }
    
  • Webkit 内核的浏览器,必须加上-webkit前缀
    .box{
      display: -webkit-flex; /* Safari */
      display: flex;
    }
    

设为 Flex 布局以后,子元素的floatclearvertical-align属性将失效

Flex 容器的属性

  • flex-direction:决定主轴的方向
    • row(默认值):主轴为水平方向,起点在左端。
    • row-reverse:主轴为水平方向,起点在右端。
    • column:主轴为垂直方向,起点在上沿。
    • column-reverse:主轴为垂直方向,起点在下沿
  • flex-wrap:设置是否换行
  • flex-flow:flex-direction属性和flex-wrap属性的简写形式
    默认值为row nowrap
  • justify-content:定义了Flex项目在主轴上的对齐方式
    • flex-start(默认值):左对齐
    • flex-end:右对齐
    • center: 居中
    • space-between:两端对齐,项目之间的间隔都相等。
    • space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍
    • space-evenly:每个弹性项目之间的距离相同,包含第一个和最后一个与弹性容器的距离
  • align-items:定义了Flex项目在侧轴上的对齐方式
    • stretch:默认值。元素被拉伸以适应容器
    • center:元素位于容器的中心
    • flex-start:元素位于容器的开头
    • flex-end:元素位于容器的末尾
    • baseline:元素位于容器的基线上
  • align-content:定义了多根轴线的对齐方式
    如果项目只有一根轴线,该属性不起作用

清除浮动的方法

  1. 父元素添加伪元素选择器可以动态添加一个子元素,完成清除浮动的操作
     .clearfix::after{
         content:"";
         display:block;
         clear:both;
         height:0;
         visibility:hidden;	 //规定元素是否可见,与display的区别在于,只是视觉上消失,在文档中的占位还在
     }
    
  2. 父元素添加overflow:hidden
    原理:将父元素变成一个BFC容器,容器里面的元素如何排列不会影响到容器外面的元素
  3. 通过
    将浮动元素和非浮动元素分隔开
    缺点:不符合工作中结构、样式、行为三者分离的要求
  4. inline-block清除浮动:display:inline-block
    缺点:margin左右auto居中失效

解决浮动引起的高度塌陷

不给父元素设置宽高时,父元素的宽高会被子元素的内容撑开。但是当子元素设置浮动属性后,子元素会溢出到父元素外,父元素的宽高也不会被撑开了,称之为“高度塌陷”
解决

  1. 给父元素设置:overflow: hidden
  2. 使用 after 伪元素:在末尾添加一个看不见的块元素来清除浮动

多余文本显示省略号

显示一行

overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;	//设置不换行

显示指定行数

display: -webkit-box;	//将对象作为弹性盒模型显示
-webkit-box-orient: vertical;	//指定弹性盒子子元素的排列方式:从顶部向底部垂直布置子元素
-webkit-line-clamp: 3;	//限制在一个块元素显示的文本的行数
overflow: hidden;

Echarts图表自适应

在图表数据初始化函数之后,调用echarts对象的**resize()**函数

window.onresize = function () {
  	echarts.resize()
}

盒子垂直居中

  1. 使用flex布局
   //写在父盒子中:
   display:flex;
   justify-content:center;
   align-item:center;
  1. position定位 + margin
  2. position定位 + transform
.container {
  position: relative;
}

.box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
 
  1. 使用 table 属性(已经被弃用,不建议使用):
.container {
  display: table;
}
.box {
  display: table-cell;
  vertical-align: middle;
}
  1. 使用 CSS 的 grid 布局:
.container {
  display: grid;
  place-items: center; /* 水平居中和垂直居中 */
}

例子

伪类和伪元素

伪类
用于当已有元素处于的某个状态时,为其添加对应的样式

  • :link 应用于未被访问过的链接
  • :hover 应用于鼠标悬停到的元素
  • :active 应用于被激活的元素
  • :visited 应用于被访问过的链接,与:link互斥。
  • :focus 应用于拥有键盘输入焦点的元素

伪元素
用于创建一些不在文档树中的元素,并为其添加样式

  • :first-child 选择某个元素的第一个子元素
  • :last-child 选择某个元素的最后一个子元素
  • :nth-child() 选择某个元素的一个或多个特定的子元素
  • :only-child 选择的元素是它的父元素的唯一一个子元素
  • :empty 选择的元素里面没有任何内容
  • :checked匹配被选中的input元素,这个input元素包括radio和checkbox。
  • :default匹配默认选中的元素,例如:提交按钮总是表单的默认按钮。
  • :disabled匹配禁用的表单元素
  • :enabled匹配没有设置disabled属性的表单元素
  • :valid匹配条件验证正确的表单元素

重绘与重排

重绘:当页面中的节点样式发生改变时,不影响它在文档流中的位置时,浏览器会对当前页面进行重绘
重排:指当页面布局和元素位置发生改变时,浏览器需要重新计算网页布局,并重新排列元素
重排一定会发生重绘,但重绘不一定会发生重排

如何减少和避免重排

重排的开销是很大的,因为它会涉及到整个页面的布局和元素位置的重新计算,而重绘的开销则比较小,只需要重新绘制少数几个元素即可。

  1. 样式集中改变:添加一个类,样式都在类中改变
  2. 使用absolute脱离文档流
  3. 使用 display:none ,不使用 visibility,也不要改变 它的 z-index
  4. 能用css3实现的就用css3实现
  5. 使用 transformopacity 来代替top、left等属性来执行动画效果,因为 transformopacity 的动画不会引起重排。
  6. 使用 flexboxgrid 布局来代替传统的 floatposition 布局,因为它们能更好地避免重排。
  7. 使用 requestAnimationFrame 方法来调用重排操作,因为该方法会在下一次重绘前执行操作,可以避免重排和重绘的多次触发。
  8. 将多个 DOM 操作合并成一个,尽量一次性完成多个 DOM 操作,以减少重排的次数。

总的来说,减少页面重排的次数,能够提高页面的性能和用户体验。

实现BFC

实现BFC(块级格式上下文),可以通过以下方式实现:

  1. overflow属性:将某个元素的overflow属性设置为visible值,如hidden、scroll等,就可以创建一个BFC。
  2. display属性:将某个元素的display属性设置为table-cell, table-caption, inline-block, flex, inline-flex等,也可以创建一个BFC。
  3. position属性:将某个元素的position属性设置为absolute或fixed,可以创建一个BFC。
  4. float属性:将某个元素设置为float元素,也可以创建一个BFC。

需要注意的是,BFC具有一些特性和规则,如:

  1. BFC内部的所有元素都会在垂直方向上形成一个完整的布局,不会发生重叠
  2. BFC内部的元素自动清除浮动(float)。
  3. BFC内部的块级元素水平方向上不会与浮动元素重叠
  4. BFC内部的元素的margin会发生重叠,但是与外部元素的margin不会重叠
  5. BFC不会影响父元素的布局,但是父元素的高度会包含BFC内部所有元素的高度(包括浮动元素)。
    添加链接描述

src与href

src和href都是HTML中用于引用资源的属性,但用法和含义有所不同。

src属性通常用于引用页面中嵌入的资源,例如图片、音频、视频或脚本等。它指定了资源的路径或URL。浏览器会解析这个路径或URL,然后加载并显示该资源。例如:

<img src="images/picture.jpg">
<script src="scripts/script.js">script>

href属性通常用于指定文档之间的超链接。它通常用于链接到其他HTML文档、CSS文件或JS文件等。当用户点击链接时,浏览器会打开指定的URL。例如:

<a href="https://www.example.com">Link to example.coma>
<link href="styles/style.css" rel="stylesheet">

link 和@import

link@import都用于引入外部资源,如CSS和JavaScript文件。
link是HTML中最常用的引入外部资源的方法,可以用于引入CSS、JavaScript、图标等。link标签包含relhreftype等属性,其中rel指示链接的类型,href指示资源的位置,type指示资源的MIME类型。例如:

<link rel="stylesheet" href="styles.css" type="text/css">
<link rel="icon" href="favicon.ico" type="image/x-icon">

@importCSS中引入外部CSS文件的方法。与link不同,@import是通过在CSS文件中使用@import规则来引入外部CSS文件的。例如:

@import url("styles.css");

在使用@import时需要注意以下几点:

  • @import只能用于引入CSS文件,不能用于引入其他类型的文件。
  • 必须放在CSS文件的最前面
  • 不支持Media Query,即不能根据不同的屏幕尺寸引入不同的CSS文件。
  • 增加页面加载时间,因为它必须在请求主CSS文件后再发起一个新的请求来获取外部CSS文件。

综上,link是HTML中引入资源的主要方式,而@import则是CSS中引入CSS文件的方式。如果要引入CSS文件,建议使用link

mask-size
设置蒙版绘画区域上蒙版图像的大小

样式优先级

  1. !important 标记
  2. 内联样式
  3. ID 选择器
  4. 类、属性和伪类选择器
  5. 元素选择器和伪元素选择器

在同一级别中,后面定义的样式会覆盖前面定义的样式。例如,在类选择器和属性选择器之间,后面定义的样式将覆盖前面定义的样式。
需要注意的是,如果两个样式具有相同的优先级,那么后面的样式将会覆盖前面的样式。

三栏布局

添加链接描述

两列等高布局

多行文本省略

添加链接描述

AntD改变主题色

config-overrides.js文件中,修改"@primary-color"属性为自己需要的颜色

(四)html

html5新特性

  1. 语义化标签:HTML5提供了一些新的语义化标签,如
    ,使得开发者能够更好地描述内容的语义。
  2. 增强型表单
    • 多个新的表单 Input 输入类型,如:color,url,date等
    • 新增表单属性:
      placehoder 属性、required 属性、min 和 max 属性(设置元素最小值与最大值)、pattern 属性(正则表达式验证 元素的值)、autofocus 属性(页面加载时,域自动获得焦点)、multiple 属性(规定 元素中可选择多个值)
  3. 新增视频 和音频 标签
  4. Canvas绘图:HTML5提供了一个元素,使得在Web页面上绘制图形和动画变得更加容易。
  5. SVG绘图
  6. 地理定位:HTML5提供了一种叫做Geolocation的API,允许Web应用程序获取用户的地理位置信息。
  7. Web Storage:HTML5提供了本地存储机制,如localStoragesessionStorage API,允许在客户端浏览器上存储数据。
  8. WebSocket:是一个持久化的协议,相对于HTTP这种非持久的协议来说
  9. 拖放:把 draggable 属性设为 true
  10. Web Workers:HTML5引入了Web Workers,允许在Web页面中使用多线程编程,提高应用程序的响应性能。

各大浏览器特点

开源:开放源代码

  • IE:Trident内核,兼容性好,非开源,微软公司开发的浏览器,曾经基于自身的EdgeHTML渲染引擎,现在改为基于Chromium开源项目,速度较快,内置了微软的语音助手Cortana。
  • chrome:Webkit内核,速度最快,开源,Google公司开发的浏览器,支持丰富的插件和应用程序
  • Safari:Webkit内核,开源,苹果公司开发的浏览器,内置于macOS和iOS系统中,速度快,流畅,对苹果生态系统的整合性较好。
  • firefox:Geckos内核,个性化定制插件,开源,Mozilla基金会开发的浏览器
  • opera:Presto内核,渲染速度快,开源,支持一些快捷操作和扩展。

解决浏览器兼容
CSS 兼容前缀

  • IE:-ms-
  • firefox:-moz-
  • opera:-o-
  • chrome 和 Safari :-webkit-
text-shadow: 2px 2px 4px #000000;
-webkit-text-shadow: 2px 2px 4px #000000; /* Chrome 和 Safari */
-moz-text-shadow: 2px 2px 4px #000000; /* Firefox */
-ms-text-shadow: 2px 2px 4px #000000; /* Internet Explorer */
 

语义化标签

:页眉通常包括网站标志、主导航、全站链接以及搜索框。

什么是语义化标签
在HTML中使用具有明确语义意义的标签来编写网页,使得网页的结构更加清晰、易于理解和维护。

使用语义化标签好处

  • 语义化标签具有可读性,使得文档结构清晰
  • 浏览器便于读取,有利于SEO优化
  • 展现在页面中时,用户体验好
  • 便于团队开发和维护

HTML5浏览器端存储

HTML5浏览器端存储指的是一种在Web浏览器中存储数据的技术,允许网站和应用程序在用户的浏览器上存储和检索数据,而无需依赖于服务器端。
HTML5浏览器端存储技术具有多种实现方式,包括:

  1. Web存储:即localStorage和sessionStorage两种API,用于在浏览器上存储简单的键值对数据。
  2. IndexedDB:一种高级的客户端数据库,允许开发人员创建和操作复杂的数据存储。
  3. Web SQL数据库:一种轻量级数据库,已被W3C淘汰并不推荐使用。

使用HTML5浏览器端存储技术可以提高Web应用程序的性能和用户体验,并减少对服务器端的依赖。

localStorage、sessionStorage和cookie

  1. localStorage:localStorage是HTML5中新增的本地存储方法,用于在浏览器中存储数据。它可以在浏览器关闭后仍然保留数据,并且可以跨浏览器标签使用。它支持存储大量数据,最大可以存储5-10MB左右。
  2. sessionStorage:sessionStorage也是HTML5中新增的本地存储方法,用于在浏览器中存储数据。与localStorage不同的是,sessionStorage仅在当前会话中有效,当浏览器关闭后,存储的数据将被清除。sessionStorage最大可以存储5-10MB左右。
  3. cookie:cookie是一种小型文本文件,存储在用户的计算机中。它可以在浏览器关闭后仍然保留数据,并且可以跨浏览器标签使用。它的大小通常限制在4KB左右。cookie的存储时间可以自定义,可以长期存储或短期存储。但是,cookie中存储的数据会被发送到服务器,存在一定的安全风险
    与服务器端通信:每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题。但Cookie需要程序员自己封装,源生的Cookie接口不友好

需要注意的是,localStorage和sessionStorage存储的数据仅在浏览器中有效不会发送到服务器,因此更安全。cookie虽然可以存储在服务器端,但存在被截获和篡改的风险,因此应该使用一些加密和验证等技术对其进行保护。

Doctype

HTML的Doctype是指在HTML文件开头声明文档类型的语句,它会告诉浏览器如何解析文档。典型的HTML5文档类型声明如下:

这个声明告诉浏览器,这个文档是HTML5文档,应该按HTML5规范来解析。在HTML4时代,Doctype声明是这样的:

这个声明告诉浏览器,这个文档应该按HTML4.01规范严格模式解析。另外还有一些其他的Doctype声明,例如HTML5的“简化版”、XHTML等。

是document type(文档类型)的简写,用来说明你用的XHTML或者HTML是什么版本

作用
Doctype声明位于文档的最前面,处于标签之前。目的是告知浏览器的解析器,用什么文档类型规范来解析这个文档。Doctype声明也是用于区分html和html5的方法之一。

严格模式与混杂模式

  • 严格模式:又称标准模式,是指浏览器按照 W3C 标准解析代码
  • 混杂模式:又称怪异模式或兼容模式,是指浏览器用自己的方式解析代码
    如何区分
    浏览器解析时到底使用严格模式还是混杂模式,与网页中的 DTD(文档类型定义) 直接相关
    1、如果文档包含严格的 DOCTYPE ,那么它一般以严格模式呈现。(严格 DTD ——严格模式)
    2、包含过渡 DTD 和 URI 的 DOCTYPE ,也以严格模式呈现,但有过渡 DTD 而没有 URI (统一资源标识符,就是声明最后的地址)会导致页面以混杂模式呈现。(有 URI 的过渡 DTD ——严格模式;没有 URI 的过渡 DTD ——混杂模式)
    3、DOCTYPE 不存在或形式不正确会导致文档以混杂模式呈现。(DTD不存在或者格式不正确——混杂模式)
    4、HTML5 没有 DTD ,因此也就没有严格模式与混杂模式的区别,HTML5 有相对宽松的语法,实现时,已经尽可能大地实现了向后兼容。( HTML5 没有严格和混杂之分)
    意义
    严格模式与混杂模式存在的意义与其来源密切相关,如果说只存在严格模式,那么许多旧网站必然受到影响。如果只存在混杂模式,那么会回到当时浏览器大战时的混乱,每个浏览器都有自己的解析模式。

页面加载优化

页面加载优化是指通过一系列技术手段,来减小网页加载所需的时间,增加用户体验度。

  1. 减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器
  2. 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能
  3. 当需要设置的样式很多时设置类名而不是直接操作内联style
  4. 少用全局变量、缓存DOM节点查找的结果,减少IO读取操作(IO流:I:就是input ;O:就是output ,故称:输入输出流)
  5. 避免使用CSS Expression(css表达式)
    css表达式:在css属性后使用expression()连接一段JavaScript表达式
    background-color: expression((new Date()).getHours()%2?"#FFFFFF": "#000000" );
    
    这里的CSS属性可以是元素固有的属性,也可以是自定义属性
  6. 避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢
  7. 代码优化:
    • 避免使用for…in(它能枚举到原型,所以很慢)
    • 在JS中倒序循环会略微提升性能
    • 减少迭代的次数
    • 基于循环的迭代比基于函数的迭代快8倍
    • 用Map表代替大量的if-else和switch会提升性能

对SPA的理解

SPA( single-page application:单页面应用 )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载
SPA常见的技术栈包括React、Angular和Vue等前端框架
优点:

  • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
  • SPA 相对对服务器压力小
  • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

  • 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
  • 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
  • SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上有着天然的弱势。

(五)webPack

配置

  • 入口:entry – 配置js的模块名和对应的地址
  • 出口:output – 配置js打包之后的路径
  • 模式:mode
    • development:开发模式 – 打包之后结构不会发生改变
    • production:生产模式 – 打包之后的所有代码都会被压缩(用于测试)
  • module:配置scss、图片、内部css
  • plugins:配置页面和外部css
  • loader:让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
    loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后就可以利用 webpack 的打包能力,对它们进行处理
    常用loader
    • babel:es6转成es5
    • css-loader
    • less-loader
    • url-loader:处理图片
  • plugins:webpack插件(自动打开浏览器、热更新等)
    • HtmlWebpackPlugin:自动打开浏览器
    • Hot Module Replacement:热更新

webpack-dev-server

一个小型的node.js Express服务器(静态文件服务器),能够用于快速开发应用程序
devServer中配置:

  • open: true 开服之后自动打开页面
  • hot: true 热更新

解决跨域

devServer中,通过proxy设置跨域地址

启动指令

npx webpack serve
使用原因
保留了单个模块的可维护性,减少了页面的http请求,减少了页面加载时间,从而增加了页面的显示速度,让整个应用的体验更好
① 模块化开发(import,require)
② 预处理(Less,Sass,ES6,TypeScript……)
③ 主流框架脚手架支持(Vue,React,Angular)
④ 庞大的社区(资源丰富,降低学习成本)

Webpack性能调优

自动化构建工具

  1. 提高构建速度
    • 缩小文件搜索范围
    • 缓存之前构建过的js
    • 提前构建第三方库
    • 并行构建而不是同步构建
    • HMR模块热替换
  2. 压缩打包体积
    • 删除冗余代码
    • 压缩代码
    • 代码分割,实现按需加载
    • Scope Hoisting
  3. 优化运行速度
    • 资源文件缓存
    • 预加载
  4. 优化开发体验
    • Dev-Server自动刷新
    • sourceMap提高调试体验

常用loader

  • style-loader:用于将css编译完成的样式,挂载到页面style标签上。需要注意loader执行顺序,style-loader放到第一位,因为loader都是从下往上执行,最后全部编译完成挂载到style上
  • css-loader:用于识别.css文件, 处理css必须配合style-loader共同使用,只安装css-loader样式不会生效。
  • sass-loader
  • postcss-loader
  • babel-loader
  • ts-loader
  • html-loader
  • url-loader
  • html-withimg-loader
  • vue-loader
  • eslint-loader
    添加链接描述

Webpack优化

  • 使用新版本:在项目上尽可能使用比较新的 webpack、Node、Npm、Yarn 版本,是我们提升打包速度的第一步
  • 体积优化

    • js 压缩
    • CSS 压缩
    • 图片压缩
    • 拆分代码
  • 速度优化

    • 分离两套配置一般来说在项目开发中,我们会区分开发和生产环境两套配置,各司其职
    • 合理使用 resolve.extensions
    • 减少查找过程
    • 优化 resolve.modules
    • 使用 resolve.alias 减少查找过程
    • 缩小构建目标
    • 利用多线程提升构建速度

添加链接描述

Webpack说一下

(六)JSON、JSONP

JSON格式

JSON是一种轻量级数据交换格式,常用于Web应用程序的数据传输。其全名为JavaScript Object Notation,是一种基于文本的数据格式。JSON数据基于键值对构成的集合,其中键名必须为字符串,值可以为数字、字符串、布尔类型、数组、对象或null。JSON格式通常采用Unicode字符集编码。

以下是一个JSON对象的例子:

{
  "name": "John",
  "age": 30,
  "isMarried": true,
  "hobbies": ["reading", "swimming"],
  "address": {
    "street": "Main St",
    "city": "New York"
  }
}

在此例中,name、age、isMarried、hobbies和address都是键名,对应的值为"John"、30、true、[“reading”, “swimming”]和{“street”: “Main St”, “city”: “New York”}。注意键名和字符串值都需要用双引号包裹。

JSON格式转换

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于读写和解析。在进行数据交换和存储时,常用JSON格式进行数据传输。下面是JSON格式转换的相关信息:

  • 从JSON转换为对象:可以使用JavaScript中的JSON.parse()方法将JSON格式的字符串转换为JavaScript对象。例如:

    const jsonStr = '{"name": "Alice", "age": 25}';
    const jsonObj = JSON.parse(jsonStr);
    console.log(jsonObj.name);  // 输出:Alice
    console.log(jsonObj.age);  // 输出:25
    
  • 对象转换为JSON:可以使用JavaScript中的JSON.stringify()方法将JavaScript对象转换为JSON格式的字符串。例如:

    const jsonObj = {name: "Alice", age: 25};
    const jsonStr = JSON.stringify(jsonObj);
    console.log(jsonStr);  // 输出:{"name":"Alice","age":25}
    
  • 在前后端数据交换时,通常会使用JSON格式进行数据传输。在前端中,可以使用fetch()或者axios等工具发送请求,并将数据以JSON格式发送到后端。在后端中,可以使用JSON.parse()方法将接收到的JSON格式的字符串转换为对象,进行数据处理,然后将处理结果以JSON格式返回给前端。

JSONP实现

JSONP(JSON with Padding)是一种跨域数据请求的方法。它利用动态创建 Script 标签可以跨域访问并获取数据的特性,将需要获取的数据包裹在一个回调函数中返回,在前端页面中通过调用该回调函数来获取数据。

下面是一个简单的例子:

服务端代码(假设服务端地址为 http://example.com/data):

callbackFunction({"name": "John", "age": 30});

客户端代码:

function callbackFunction(data) {
  console.log(data.name);
}

var script = document.createElement('script');
script.src = 'http://example.com/data?callback=callbackFunction';
document.body.appendChild(script);

服务端返回的数据会被包裹在 callbackFunction 中,由客户端的 script 标签加载并执行。当 script 标签加载完毕后,callbackFunction 就会被调用,得到返回的数据。

需要注意的是,服务端返回的数据必须是合法的 JSON 格式,否则将无法解析。同时,回调函数的名称应该在客户端代码中动态生成,避免被恶意利用。

(七)Ajax

Ajax

Ajax(Asynchronous JavaScript and XML)是一种基于浏览器端和服务器端之间异步通信的技术,可以让网页在不刷新的情况下动态地加载新内容。Ajax通常使用XMLHttpRequest对象来发送HTTP请求和接收HTTP响应。除了XMLHttpRequest,现在也有许多基于Ajax的开源库和框架,如jQuery、AngularJS、React等。

使用Ajax可以做到以下操作:

  1. 动态加载新内容,不需要刷新整个页面;
  2. 提升用户体验,不需要等待整个页面刷新,而是只需要更新局部内容;
  3. 减少带宽使用,只需要传输需要更新的数据,而不是整个页面。

使用Ajax的基本步骤如下:

  1. 创建XMLHttpRequest对象
  2. 指定请求的方法、URL和是否异步
  3. 发送HTTP请求
  4. 准备处理响应的回调函数
  5. 处理响应

XMLHttpRequest对象

  • 对象用于在后台与服务器交换数据

ajax解决浏览器缓存问题

  • Ajax在发送的数据成功后,会把请求的URL和返回的响应结果保存在缓存内,当下一次调用Ajax发送相同的请求时,它会直接从缓存中把数据取出来,这是为了提高页面的响应速度和用户体验
  • 最有效的办法是禁止页面缓存

1、在ajax发送请求前加上 xmlHttpRequest.setRequestHeader(“Cache-Control”,”no-cache”);

2、在服务端加 header(“Cache-Control: no-cache, must-revalidate”);

3、在ajax发送请求前加上 xmlHttpRequest.setRequestHeader(“If-Modified-Since”,”0″);

4、在 Ajax 的 URL 参数后加上 “?fresh=” + Math.random(); //当然这里参数 fresh 可以任意取了

5、第五种方法和第四种类似,在 URL 参数后加上 “?timestamp=” + new Date().getTime();
添加链接描述

(八)浏览器和网络安全

浏览器的 v8 引擎回收机制

V8 是谷歌开发的 JavaScript 引擎,它负责将 JavaScript 代码转换为机器代码并执行。在 V8 中,内存管理主要包括两个方面:分配回收

V8 的内存管理使用了一个叫做垃圾回收机制的算法来自动回收不再被引用的内存。在 V8 中,垃圾回收机制主要分为两种类型:新生代垃圾回收和老生代垃圾回收。

  1. 新生代垃圾回收

新生代垃圾回收主要针对年轻的对象,通常被认为是生命周期较短的对象。新生代内存空间的大小通常比老生代的空间要小很多

在 V8 中,新生代垃圾回收采用了复制算法。它将内存空间分为两个相等的空间,每次只有其中一个空间是活动空间,存放当前正在使用的对象。当一个对象经过多次垃圾回收后仍然存活,那么它就会被移到老生代的空间中。

  1. 老生代垃圾回收

老生代垃圾回收主要针对生命周期较长或者占用内存空间较大的对象。老生代垃圾回收通常需要进行全堆垃圾回收,这个过程会暂停整个程序的执行

在 V8 中,老生代垃圾回收采用了标记-清除算法。在标记阶段,垃圾回收机制会遍历堆中的所有对象,并标记所有还在使用的对象。在清除阶段,垃圾回收机制会将所有未标记的对象进行清除。

需要注意的是,在进行老生代垃圾回收的时候,由于需要暂停整个程序的执行,所以当程序内存占用过大,达到内存限制时,垃圾回收机制会进行强制性的垃圾回收,以防止程序出现内存泄漏等问题。

js中的垃圾回收机制

  • JavaScript中的垃圾回收机制主要是通过标记清除(Mark and Sweep)算法实现的。
  • 标记清除算法的基本原理是,垃圾回收器会定期扫描内存中的所有对象,并标记那些仍然其他对象所引用的对象。然后,垃圾回收器会清除所有未被标记的对象,释放它们占用的内存空间。
  • 具体来说,当 JavaScript程序中创建一个新的对象时,这个对象被分配到内存中的一块空间。当这个对象不再被程序中的任何其他对象所引用时,它就成为了一个垃圾对象。垃圾回收器会定期扫描内存中的所有对象,识别和标记这些垃圾对象,然后释放它们占用的内存空间。
  • JavaScript的垃圾回收器会自动执行这些操作
  • 只将需要的数据,存入变量。一旦这个数据不再使用了,我们需要手动的给变量赋值 null 来释放数据的引用。(-------解除引用)
    js垃圾回收机制原理

TCP/IP、HTTP协议等

TCP/IP是一组协议簇,包括以下协议:

  • IP协议(Internet Protocol):负责网络层的数据传输和路由。
  • TCP协议(Transmission Control Protocol):负责传输控制,保证数据传输的可靠性
  • UDP协议(User Datagram Protocol):与TCP协议类似,但不保证数据传输的可靠性

HTTP协议(HyperText Transfer Protocol)是一种应用层协议,用于在Web浏览器和Web服务器之间传递信息,可通过TCP/IP协议传输

还有其他的协议,例如:

  • FTP协议(File Transfer Protocol):用于在网络上进行文件传输
  • SMTP协议(Simple Mail Transfer Protocol):用于电子邮件的传输
  • POP3协议(Post Office Protocol version 3):用于从邮件服务器接收电子邮件
  • IMAP协议(Internet Message Access Protocol):用于从邮件服务器接收管理电子邮件

http请求方式

HTTP请求方式包括以下常见的5种方法:

  1. GET:用于请求指定资源,并且必须是安全和幂等的,即重复请求不会对服务器产生副作用。GET方法是最常用的方法,一般用于请求数据,如获取网页内容、图片等资源。
  2. POST:用于向指定资源添加的数据,并且可能会修改服务器上的数据内容。POST方法不能被缓存,也不具备安全和幂等的特性,即重复请求会对服务器产生副作用
  3. PUT:用于向指定资源更新或者替换数据,PUT方法也不具备安全和幂等的特性。
  4. DELETE:用于删除指定的资源。
  5. HEAD:用于请求指定资源的头部信息,HEAD方法和GET方法类似,但是它只返回请求头不返回实际的资源内容。该方法主要用于客户端查看网页或者资源的元数据,如页面的标题、资源的大小、类型等。

常用http响应头

常用的 HTTP 响应头如下:

  1. Content-Type:指示响应中包含的内容类型
  2. Content-Length:指示响应正文的长度
  3. Content-Encoding:指示响应正文的压缩编码方式
  4. Cache-Control:指示客户端和代理服务器如何缓存响应
  5. Expires:指示响应的过期时间
  6. Last-Modified:指示资源最后修改时间
  7. Location:指示客户端应该重定向到的 URL
  8. Set-Cookie:指示服务器要设置的 cookie。
  9. Server:指示服务器软件的名称和版本号
  10. X-Content-Type-Options:指示浏览器是否应该在尝试嗅探响应内容类型时强制使用 Content-Type 头中指定的类型。

fetch请求方式

Fetch是一种现代的Web API,它提供了一种使用JavaScript进行网络请求的简单方式。Fetch API使用简单和直观,并且在浏览器和Node.js中都有良好的支持。
Fetch API提供了两个主要方法:fetch()和fetchEvent()。这两个方法都可以用来发起HTTP请求,但使用的方式略有不同。

  1. fetch()

fetch()方法用于请求网络资源,如图片、文本文件、JSON数据等。它返回一个Promise对象,该对象在请求完成后将被解析并返回一个Response对象

fetch()方法的基本语法如下:

fetch(url, options)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

其中,url参数是要请求的资源的URL地址,options参数是一个可选的配置对象,用于指定请求的参数,如请求方法、请求头和请求体等。
2. fetchEvent()

fetchEvent()方法用于在Service Worker中处理网络请求事件。它是由Service Worker API提供的。

fetchEvent()方法的基本语法如下:

self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request)
      .then(response => console.log(response))
      .catch(error => console.error(error))
  );
});

其中,fetch(event.request)方法用于在Service Worker中发起网络请求,event.respondWith()方法用于将fetch()返回的Promise对象传递给浏览器,从而正确处理网络请求事件。

状态码

添加链接描述

前端领域里面,有哪些潜在的风险

  1. XSS攻击(跨站脚本攻击):攻击者通过注入恶意脚本的方式,使得用户在浏览器访问页面时,执行恶意代码,从而实现攻击目的。
  2. CSRF攻击:攻击者利用受害者的身份,发起一些不正当的请求,例如进行转账、修改密码等。
  3. 点击劫持(ClickJacking):攻击者通过CSS和iframe技术,将用户点击的位置覆盖在恶意页面上,使得用户在不知情的情况下,实际上是在访问恶意页面。
  4. 接口安全:部分接口如果没有进行足够的安全防护,则有可能被攻击者利用,进行一些不正当的操作。
  5. iframe
  6. opener
  7. CND劫持

详细介绍 csrf 攻击原理,以及怎么攻击的

CSRF(Cross-Site Request Forgery,跨站请求伪造)攻击是指攻击者通过设置恶意网站,在用户不知情的情况下,利用已经登录过的用户的身份,向目标站点发出恶意请求,造成一系列的危害。
攻击步骤如下:

  1. 攻击者在自己的网站中,构造一个请求链接,并设置一些参数,同时该请求的提交方式为GET或者POST。
  2. 攻击者在自己的网站中,通过iframe标签,将请求链接插入到一个隐藏的iframe标签中。
  3. 攻击者通过Javascript代码,自动加载iframe标签,并触发请求链接,同时在cookie中,携带了登录的认证信息。
  4. 目标网站在接收到请求链接以后,会验证cookie认证信息,如果验证通过,则认为是已经登录的用户发出的请求,从而执行请求中所包含的操作。
    如何攻击:

例如,一家银行的网站已经登录成功,此时攻击者打开恶意网站,恶意网站中插入一个银行网站的转账请求链接,此时:

  1. 用户在未注销登录的情况下,访问了恶意网站,并不知情。
  2. 恶意网站中,有一个隐藏的iframe标签,自动提交银行网站的转账请求。
  3. 银行网站中,携带了cookie信息,自动进行了认证。
  4. 银行网站中,接收到该请求,因为是已经登录的用户发出的,所以执行了转账操作。
    因此,在设计Web应用程序时,需要采取适当的安全措施,来防止CSRF攻击。对于敏感的操作,需要使用令牌验证机制,或验证码等方式,来保证只有合法用户才能进行操作。

跨域

指的是在浏览器中,通过JavaScript从一个源(Origin)获取到数据或执行操作,而该源与当前页面的域(Domain)不同。由于安全原因,浏览器禁止通过JavaScript跨域访问其他源的资源,因为这可能会带来安全问题(如XSS攻击)。

  1. jsonp请求;jsonp的原理是利用script标签的跨域特性,可以不受限制地从其他域中加载资源,类似的标签还有img.
  2. CORS;CORS背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是应该失败。
  3. 代理方式:通过服务器代理请求来解决跨域请求的问题。代理方式:通过服务器代理请求来解决跨域请求的问题。
  4. window.postMessage;window.postMessages是html5中实现跨域访问的一种新方式,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源。
  5. document.domain;这种方式用在主域名相同子域名不同的跨域访问中
  6. window.name;window的name属性有个特征:在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
  7. Web Sockets;web sockets原理:在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。

介绍一下 cors 跨域

CORS (Cross-Origin Resource Sharing) 是一种浏览器的安全策略,用于限制跨域请求访问其他域的资源。

当服务器配置了允许部分域跨域,浏览器的请求会发生什么改变?(似乎会对请求多做一次预检测),跨域会携带 cookie 吗?

当服务器配置了允许部分域跨域时,浏览器会在请求头添加 Origin 字段,表示当前请求来自哪个域。如果服务端允许该请求的来源域,将在响应头中添加Access-Control-Allow-Origin字段,表示允许该来源的请求访问资源。如果不允许,则会抛出一个跨域错误

在请求跨域资源时,浏览器会发出一个 OPTIONS 请求,该请求称作“预检请求”,用于询问服务器是否允许跨域请求。只有在收到服务器允许的响应之后才会发出正式的请求。

对于跨域的 AJAX 请求,如果需要发送 Cookie,则需要设置 withCredentials 属性为 true。同时,服务器端还需要设置 Access-Control-Allow-Credentials 字段为 true,表示允许跨域请求携带 Cookie。

需要注意的是,CORS 只是一种浏览器策略,如果使用非浏览器方式访问资源,依然可以跨域获取资源并携带 Cookie。因此,在进行敏感操作时,需要在服务器端进行更严格的鉴权和访问控制。

get请求传参长度的误区

在GET请求中,请求参数通常是通过URL的查询字符串传递的。然而,URL的长度是有限制的,当一定数量的参数被添加到URL时,其长度会超过浏览器和服务器接受的最大长度限制,导致请求无法完成。这种情况称为请求参数“过长”。

一些人常常认为GET请求不能传输大量数据或者传输的数据要有限制,但实际上这种想法是有误的。虽然GET请求的URL长度是有限制的,但并不代表我们不能传输大量的数据,也不应该限制传输数据的数量。

事实上,根据HTTP标准规定,客户端发起的所有请求(包括GET和POST请求)都有长度限制,这个限制取决于实现服务器端和客户端的技术细节。在现代Web应用程序中,使用正常的GET请求传递数量巨大的数据或参数已经成为了常见的做法。

此外,HTTP 1.1标准对URL长度的限制进行了增加,使得对GET请求传参的数据处理变得更加容易和灵活。如果需要传输超大的数据,请考虑将数据分片,进行多次传输或者选择其他适合的数据传输方式,例如POST或WebSocket等。

因此,对于GET请求传参长度的误区,应该彻底否定。GET请求可以传输大量的数据,但是依赖于服务器和客户端技术的实际限制。建议在应用程序中使用适当的数据传输方案,并且预留一定的数据长度余量以防止出现传输失败的情况

get和post请求在缓存方面的区别

GET请求和POST请求在缓存方面有以下区别:

  1. GET请求会被缓存,而POST请求不会。这是因为在HTTP缓存模型中,GET请求被认为是幂等的,也就是说多次请求得到的结果是一样的,因此可以被缓存。而POST请求则不是幂等的,因为它可能会对服务器产生副作用(比如修改数据),因此不能被缓存。
  2. GET请求的缓存是可以被浏览器缓存和代理服务器缓存的,而POST请求只能被浏览器缓存。这是因为代理服务器无法判断POST请求中包含的数据是否对缓存系统产生了影响,所以不能缓存POST请求。
  3. GET请求的缓存可以使用URL作为缓存的键值,而POST请求的缓存只能使用HTTP响应头中的Cache-Control和Expires等字段作为缓存的键值。这是因为URL中包含的参数会影响GET请求的响应内容,如果以URL作为缓存键值,需要对每个不同的参数生成不同的缓存条目,会导致缓存冗余,而不能有效利用缓存。

综上所述,GET请求的缓存相对于POST请求更加灵活和高效,但是在一些需要保证数据安全性的场景下,使用POST请求更加合适。

地址栏输入url敲回车后发生了什么

  1. DNS查询
    DNS:域名系统 – Internet上解决网上机器命名的一种系统
  2. 建立连接:客户端与服务器通过三次握手建立连接
    三次握手
    • 第一次握手:浏览器向服务器发起询问:是否处于正常工作状态? 浏览器发送请求到服务器
    • 第二次握手:服务器告诉浏览器:一切正常,你可以向我发送请求 服务器接收到请求,然后处理请求,最终响应给浏览器
    • 第三次握手,浏览器告诉服务器:了解,准备发送请求 浏览器解析并且渲染服务器响应数据
  3. 发送请求
  4. 接收响应
  5. 渲染页面
  6. 断开连接:通过四次挥手断开连接
    四次挥手
    • 第一次挥手:浏览器向服务器发起询问:可否断开连接?
    • 第二次挥手:服务器:确认数据是否响应完毕
    • 第三次挥手:服务器:数据已响应完毕,可以断开连接
    • 第四次挥手:浏览器:断开连接

Web 缓存

数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存
浏览器缓存:HTTP 缓存、indexDB、cookie、Web Storage、WebSql、Application Cache、PWA

http缓存

http缓存:当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源

  • 强缓存 状态码200
    强制缓存在缓存数据未失效的情况下(即Cache-Controlmax-age没有过期或者Expires的缓存时间没有过期),那么就会直接使用浏览器的缓存数据,不会再向服务器发送任何请求
    这种方式页面的加载速度是最快的,性能也是很好的,但是在这期间,如果服务器端的资源修改了,页面上是拿不到的,因为它不会再向服务器发请求了
    • Expires:指缓存过期的时间,超过了这个时间点就代表资源过期
    • Cache-Control
    • Pragma
  • 协商缓存 状态码304
    第一次请求时服务器返回的响应头中没有Cache-ControlExpires或者Cache-ControlExpires过期,或者它的属性设置为no-cache时(即不走强缓存),那么浏览器第二次请求时就会与服务器进行协商,与服务器端对比判断资源是否进行了修改更新
    • Last-modified/If-Modified-Since
    • ETag/If-None-Match

使用HTTP缓存
在标签中嵌入标签,这种方式只对页面有效,对页面上的资源无效

 // 其他主流浏览器识别的标签
  // 仅有IE浏览器才识别的标签

CDN

概述
CDN的全称是Content Delivery Network,即内容分发网络。其目的是通过在现有的Internet中增加一层新的CACHE(缓存)层,将网站的内容发布到最接近用户的网络”边缘“的节点,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。从技术上全面解决由于网络带宽小、用户访问量大、网点分布不均等原因,提高用户访问网站的响应速度

优化原理
CDN网络是在用户和服务器之间增加Cache层,主要是通过接管DNS实现,将用户的请求引导到Cache上获得源服务器的数据

常用的设计模式

常用的设计模式有以下几种:

  1. 工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪个类。把对象的实例化延迟到子类中进行, 以达到解耦的目的
  2. 单例模式:保证一个类只有一个实例,并提供一个全局访问点
  3. 观察者模式:定义对象间的一种一对多的依赖关系,从而当一个对象状态发生改变时,所有依赖于它的对象得到通知并且自动更新
  4. 适配器模式:将一个类的接口转换成客户希望另一个接口。适配器模式让那些接口不兼容的类可以一起工作
  5. 装饰器模式动态地给一个对象添加一些额外的职责。就增加功能而言,装饰模式比生成子类更为灵活
  6. 策略模式:定义一系列的算法,将每一种算法封装起来,并且使它们之间可以互换
  7. 模板方法模式:定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
  8. 迭代器模式:提供一种方法,访问一个容器对象中的每个元素,而又不需暴露该对象的内部细节
  9. 代理模式:为其他对象提供一种代理控制对这个对象访问

(九)ES7

ES7(ECMAScript 2016)引入了以下内容:

  1. Array.prototype.includes()方法:检查数组中是否包含某个元素,返回布尔类型。
  2. 指数运算符(**):用于求幂运算,如2 ** 3表示2的3次方。

(十)ES8

ES8(2017年)

  1. async/await:一种新的异步编程模式,使异步操作更加清晰和易于理解。
  2. Object.getOwnPropertyDescriptors()方法:用于获取对象属性的描述符。
  3. 字符串填充方法:padStart()padEnd()方法,用于向字符串填充指定的字符。
  4. Object.values()Object.entries()方法:用于获取对象的值数组和键/值对数组。
  5. SharedArrayBuffer和Atomics:提供了一种新的多线程编程方式,允许多个线程访问共享内存。

(十一)ES9

ES9(2018年)

  1. 异步迭代:通过AsyncIterator接口和for-await-of循环,使异步操作的迭代更加容易。
  2. Promise.finally()方法:在Promise状态改变时,无论成功或失败,都会执行finally方法。
  3. Rest/Spread属性:可以在对象和数组中使用…语法,用于展开对象的属性和数组的元素。
  4. 标准化正则表达式:支持具名捕获组,更加清晰易读。
  5. 更加智能的正则表达式:引入RegExp的s(dotAll)标志,可以匹配换行符,以及RegExp的u(Unicode)标志,可以正确处理Unicode字符。

(十二)ES10

ES10(2019年)

  1. Array.prototype.flat()和Array.prototype.flatMap()方法:用于将嵌套数组展开并转换为单个层数组。
  2. String.prototype.trimStart()和String.prototype.trimEnd()方法:用于去除字符串的开头或结尾的空格。
  3. Object.fromEntries()方法:将键值对数组转换为对象。
  4. 可选的catch绑定:try-catch语句中的catch块可以省略参数。
  5. 更好的Symbol使用:Symbol.prototype.description属性可以获取Symbol的描述字符串。

(十三)ES11

ES11(也称为ES2020)的一些新特性包括:

  1. 可选的链式操作符:可以使用“?.”来访问对象的属性,如果该属性不存在,则返回 undefined。
  2. Promise.allSettled()方法:可以接受一组 Promise,返回每个 Promise 的状态和结果,而不管它们是否被解决。
  3. BigInt类型:引入了一种新类型BigInt来表示任意精度的整数。BigInt类型在数值范围上比Number类型更加广泛。

(十四)ES12

ES12(也称为ES2021)的一些新特性包括:

  1. String.prototype.replaceAll()方法:可以替换一个字符串中的所有匹配项,而不只是第一个,类似于正则表达式中的全局替换。
  2. Promise.any()方法:接受一组 Promise,只要其中一个变为已解决状态,就返回该 Promise 的结果,而不是所有 Promise 的结果。
  3. WeakRefs:允许创建对对象的弱引用,这些弱引用不会阻止垃圾回收器释放对象。这是一种有效地管理内存的方法。

二、vue

vue2和vue3区别

添加链接描述

理解

  • Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
  • 创建用户界面的开源JavaScript框架,也是一个创建单页应用的Web应用框架,数据驱动(MVVM),组件化,指令系统

MVVM

MVVM:Model–View–ViewModel
(1)Model 层
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。
(2)View 层
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。
(3)ViewModel 层
ViewModel 是由前端开发人员组织生成和维护的视图数据层。

  • MVVM在概念上是真正将页面数据逻辑分离的模式,它把数据绑定工作放到一个JS里去实现,而这个JS文件的主要功能是完成数据的绑定,即把model绑定到UI的元素上
  • MVVM另一个重要特性是双向绑定,它更方便去同时维护页面上都依赖于某个字段的N个区域,而不用手动更新它们

MVC

model数据层、view视图层、controller控制层、

  • 各部分之间的通信都是单向
  • View 传送指令到 Controller,Controller 完成业务逻辑后,要求 Model 改变状态,Model 将新的数据发送到 View,用户得到反馈
  • MVC模型关注的是Model的不变,所以在MVC模型里,Model不依赖于View,但是 View是依赖于Model的
传送指令
要求改变形态
新数据
反馈
View
Controller 完成业务逻辑后
Model
用户

SFC

单页面组件

双向绑定

  • Vue 的双向绑定指的是数据的双向绑定,即数据的变化可以自动更新视图,在视图中的变化也可以自动更新数据
  • 在 Vue 中,双向绑定通过 v-model 指令实现。v-model 指令可以用在 input、select、textarea 等表单元素中,它绑定了一个数据属性表单元素的 value、checked、selected 等属性。
  • 当绑定的数据属性发生变化时,v-model 会自动更新表单元素的值,反之,当表单元素的值发生变化时,v-model 会自动更新绑定的数据属性。
  • 总结:绑定数据变化,v-model自动更新表单元素,表单元素值变化,自动更新绑定的数据值

缺点

  1. 性能问题:双向绑定会对性能产生影响,因为每当数据改变时,视图都要被重新渲染,这会消耗一定的性能。
  2. 调试困难:由于数据和视图的状态是相互关联的,因此在调试过程中很难确定是数据引起的问题还是视图引起的问题。
  3. 开发者需要理解框架:使用双向绑定需要对Vue框架的运作机制有一定的了解,否则会出现一些意外的行为。
  4. 可读性降低:双向绑定会导致代码变得更加难以理解,因为数据和视图的状态是彼此关联的,难以分离。
  5. 不利于维护:在复杂的应用程序中,双向绑定可能会导致代码变得混乱和难以维护。

vue响应式原理

  • 初始化时重新定义data中写的变量,添加getset方法,当变量改变时,vue收到通知,从而实现响应
  • 新添加的变量不会变成响应式,Vue.set()方法可以将属性设置为响应式this.$set来调用
Object.defineProperty(obj, 'name', { // 参数分别为:对象名,对象的属性名,执行的函数
	get: function(){
		console.log('get',_name); //监听
		return _name;
	},
	set: function(newVal){
		console.log('set',newVal); //监听
		_name = newVal;
	}
})

Vue改变复杂类型数据不会响应的解决方法

当 Vue 监听一个数据对象时,Vue 会递归地遍历该对象的所有属性,从而使 Vue 能够监听到该对象的变化。但如果我们直接改变了一个对象的某个属性,而不是整个对象本身,Vue 并不会察觉到这个变化,从而也不会更新视图。

在两种情况下修改 数据,Vue 不会触发视图更新:

  1. 在实例创建之后添加新的属性到实例上(给响应式对象新增属性)
  2. 直接更改数组下标来修改数组的值
    若data中数据类型较为复杂,方法methods中改变对象的属性,页面并不会改变
    解决方法:
this.$set(对象,新属性,新属性值)	  //没有该属性,则是直接添加
this.$set(数组,下标,值)

原理:vue监听不到复杂类型数据的属性,使用this.$set()来进行强制更新,进而解决问题

Vue 模板编译原理

Vue 的编译过程就是将 template 转化为 render 函数的过程,分为以下三步:

  1. 模板字符串 转换成 element ASTs(解析器)
  2. 对 AST 进行静态节点标记,主要用来做虚拟 DOM 的渲染优化(优化器)
  3. 使用element ASTs 生成 render 函数代码字符串(代码生成器)

vue 内置指令

  • v-once
    • 定义它的元素或组件只渲染一次,用于优化性能,包括元素或组件的所有节点,首次渲染后,不再随数据的变化重新渲染,将被视为静态内容。
  • v-cloak
    • 解决屏幕闪动的好方法
    • 这个指令保持在元素上直到关联实例结束编译 – 解决初始化慢到页面闪动的最佳实践。
    • v-cloak
  • v-bind( : )
    • 绑定属性,动态更新HTML元素上的属性。例如 v-bind:class。
  • v-on(@)
    • 绑定事件监听器。例如 v-on:click v-on:keyup
  • v-html
    • 赋值就是变量的innerHTML
    • 注意防止xss攻击
    • 将元素的 innerHTML 设置为表达式的值
  • v-text
    • 更新元素的textContent 将元素的文本内容设置为表达式的值
  • v-model
    • 实现双向数据绑定
    • 在普通标签。变成value和input的语法糖,并且会处理拼音输入法的问题。
    • 再组件上。也是处理value和input语法糖。
  • v-if / v-else / v-else-if
    • 可以配合template使用;在render函数里面就是三元表达式。
    • 根据表达式的值判断是否渲染 DOM 元素
  • v-show
    • 使用指令来实现
    • 最终会通过display来进行显示隐藏
    • 根据表达式的值判断是否显示 DOM 元素
  • v-for
    • 循环指令编译出来的结果是 -L 代表渲染列表
    • 优先级比v-if高最好不要一起使用,尽量使用计算属性去解决
    • 注意增加唯一key值,不要使用index作为key。
    • 从前插入导致index递增
  • v-pre
    • 跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度。
    • 用于优化性能
  • v-is
    • 动态切换组件
  • v-slot
    • 用于具名插槽

computed和watch区别

computed和watch都是Vue.js中用于处理数据的关键字

  • 计算属性computed
  1. 支持缓存,只有依赖数据发生变化时,才会重新进行计算函数,提高了代码的性能和效率
  2. 可以将一个属性定义成计算属性
  3. 计算属性内不支持异步操作;
  4. 计算属性的函数中都有一个get(默认具有,获取计算属性)和set(手动添加,设置计算属性)方法;
  5. 计算属性是自动监听依赖值的变化,从而动态返回内容
  • 监听属性watch
  1. 一个观察器,可以监听一个属性的变化
  2. 不支持缓存,只要数据发生变化,就会执行监听函数;
  3. 侦听属性内支持异步操作;
  4. 侦听属性的值可以是一个对象,接收handler回调,deep(深度监听),immediate(立即监听)三个属性;
  5. 监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些事情。
  • computed用于简单的计算属性,watch用于监听复杂数据的变化

commputed缓存原理

commputed本质是一个惰性观察者;当计算数据存在于data或者props会警告

  • 其缓存机制本质是通过一个dirty属性控制的,只有dirty为true时才会重新计算结果替换缓存
  • dirty只有当其响应式数据发送变化时才会设置为true,重新计算后会再次被设置为false

Vue初次运行会对commputed属性进行初始化处理,初始化的时候会对每一个cmmputed属性watcher包装起来,这里会生成一个dirty属性值为true,然后执行
Computed缓存原理就是在计算属性的值不发生变化的情况下,使用缓存的结果,减少重复的计算。

生命周期、钩子函数

添加链接描述

插槽slot

添加链接描述

v-if 和 v-show

  • 添加链接描述

v-if 和 v-for

但是当数据量较大时,它们可能会导致性能问题。因此,在使用v-forv-if时,需要注意它们的性能问题,避免循环嵌套和冗余的渲染。

  • v-for 优先级比 if
  • 永远不要把v-ifv-for同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)
  • 在 Vue.js 中,v-if 和 v-for 通常不建议同时使用。因为当v-for循环次数较多时,v-if 每次都需要验证一条条件语句,这会导致性能问题。在这种情况下,建议使用计算属性(computed property)来过滤要渲染的数据,以提高性能

实例和组件定义data的区别

  • 实例是由 Vue 实例化创建的,而组件是由 Vue 组件系统创建的。它们的区别在于它们所管理的 data 是不同的。
  • vue实例时定义data属性 既可以是一个对象 也可以是一个函数
  • 组件定义data属性 只能是一个函数,返回一个对象, 如果定义对象会有警告

data里面为什么放函数

  • 使用对象时 多个组件实例公用一个内存地址 当一个被修改时对所有都产生了影响
  • 而函数不会 返回的对象内存地址并不相同
  • 采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染

$ route和$ router的区别

  • $route对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query对象等。name(当前路径名字)、mate(路由元信息)、pathhashqueryparamsfullPathmatched
  • $router全局路由实例,是router构造方法的实例,操作路由,pushgoreplace
  • 用this.$router.relpace(‘/’)做404页面
//常规方法
this.$router.push("/login");
//使用对象的形式 不带参数
this.$router.push({ path:"/login" });
//使用对象的形式,参数为地址栏上的参数
this.$router.push({ path:"/login",query:{username:"jack"} }); 
使用对象的形式 ,参数为params 不会显示在地址栏
this.$router.push({ name:'user' , params: {id:123} });

$route.path 字符串,等于当前路由对象的路径,会被解析为绝对路径,如/home/ews

$route.params 对象,包含路由中的动态片段和全匹配片段的键值对,不会拼接到路由的url后面

$route.query 对象,包含路由中查询参数的键值对。会拼接到路由url后面

$route.router 路由规则所属的路由器

$route.name 当前路由的名字,如果没有使用具体路径,则名字为空
————————————————
版权声明:本段为CSDN博主「xunfengZ」的原创文章,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xunfengZ/article/details/109670979

vue-router实现原理

Vue-Router 的实现原理涉及到以下几个核心部分:

  1. 路由映射表
    Vue-Router 的路由映射表是一个数组,每一个元素表示一个路由。路由的配置包括路由路径、路由名称、路由组件等信息。
  2. 路由入口
    路由入口是指当 URL 路径匹配成功时,Vue-Router 会将对应的路由组件加载进来并渲染到页面中。
  3. 路由匹配
    Vue-Router 会监听 URL 的变化,当 URL 改变时,会进行路由匹配。路由匹配过程中,Vue-Router 会根据当前 URL 的路径从路由映射表中找到对应的路由。
  4. 状态管理
    Vue-Router 路由状态的管理是通过 History API 和浏览器的路由管理机制实现的。在 URL 路径发生变化时,Vue-Router 会通过监听 History API 的 popstate 事件来进行更新,同时也会通过 pushState 和 replaceState 方法来改变 URL 路径。
  5. 钩子函数
    Vue-Router 中的钩子函数包括全局钩子函数、路由级别的钩子函数和组件级别的钩子函数。这些钩子函数可以用来在路由导航过程中做一些额外的处理,比如用户权限判断、拦截跳转等。其中,全局钩子函数和路由级别的钩子函数可以通过插件形式进行扩展。

总的来说,Vue-Router 的实现原理主要分为路由映射表的维护路由匹配路由入口的渲染状态管理以及钩子函数的执行等几个方面。在这些方面的细节实现中,Vue-Router 采用了一些 JavaScript 的技巧和浏览器 API,使得整个路由的实现逻辑较为清晰和高效。

Vue路由守卫

  • 全局的路由钩子函数:beforeEachafterEach(一般用于全局进行权限跳转)
    • beforeEach: 每一个路由改变之后页面加载之前执行,三个参数(to:将要进入的路由对象、from:即将离开的路由对象、next:跳转方法)next必须调用
    • afterEach每一次路由改变之后、页面加载之后执行;
  • 单个路由钩子函数:beforeEnterbeforeLeave(路由内部钩子,一般在路由表里)
    • beforeEnter:进入指定路由跳转时需要执行的逻辑
    • beforeLeave:离开指定路由跳转时需要执行的逻辑
  • 组件内的路由钩子函数:beforeRouterEnterbeforeRouteLeavebeforeRouteUpdate
    Vue 路由守卫是一种机制,用于在进入/离开路由时,对路由进行检查和操作。可以使用路由守卫来判断用户是否已经登录,如果用户未登录,则可以重定向到登录页面或进行其他操作。

路由守卫判断用户是否已经登录的步骤

  1. 在路由定义时添加路由守卫:
const router = new VueRouter({
  routes: [
    {
      path: '/dashboard',
      name: 'dashboard',
      component: Dashboard,
      meta: { requiresAuth: true } // 添加 meta 字段
    },
    // ...
  ]
})

在上面的代码中,当用户尝试访问 /dashboard 路由时,路由元信息的 requiresAuth 字段被设置为 true。这表示该路由需要用户登录才能访问

  1. 在路由实例的 beforeEach 方法中检查是否需要用户登录:
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // 判断用户是否已经登录
    if (!isAuthenticated()) {
      // 如果未登录,则重定向到登录页面
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next()
  }
})

在上面的代码中,beforeEach 方法会在每次路由切换之前被调用。它会检查将要访问的路由是否需要用户登录,如果需要,则会调用 isAuthenticated 方法判断用户是否已经登录。如果用户未登录,则会重定向到登录页面,并且将当前路由信息作为查询参数传递给登录页面(这样用户登录成功后可以重定向回当前路由)。如果用户已经登录,则允许访问该路由。

  1. 实现 isAuthenticated 方法:
function isAuthenticated() {
  // 判断用户是否已经登录
  // 例如,可以从 localStorage 中获取保存的用户信息
  // 如果用户已经登录,则返回 true;否则返回 false
  return localStorage.getItem('user') !== null
}

在上面的代码中,isAuthenticated 方法可以根据自己的实际需求进行实现。这里的示例实现是从 localStorage 中获取保存的用户信息,如果用户已经登录,则返回 true;否则返回 false。
通过以上步骤,就可以利用路由守卫判断用户是否已经登录。当用户访问需要登录才能访问的路由时,如果用户未登录,则会被重定向到登录页面。如果用户已经登录,则允许访问该路由。

vue-router的核心实现api

  1. VueRouter:创建VueRouter实例;
  2. routes:路由配置数组,包含一个或多个路由对象;
  3. router-link:路由链接组件,用于在应用中生成导航链接
  4. router-view:路由视图组件,用于显示当前路由匹配的组件;
  5. router.beforeEach:全局前置守卫,用于在路由跳转前进行拦截和处理;
  6. router.afterEach:全局后置守卫,用于在路由跳转后进行处理;
  7. router.push:路由跳转方法,用于通过编程方式进行路由跳转
  8. router.replace:路由替换方法,用于替换当前路由而不是添加新的历史记录
  9. router.go:路由跳转到历史记录中的指定位置方法;
  10. router.back:路由返回上一个历史记录位置方法;
  11. router.forward:路由前进到下一个历史记录位置方法;
  12. router.getMatchedComponents:获取当前路由匹配的组件数组方法;
  13. router.resolve:解析路由到对应的URL方法。

vue-router实现懒加载

在使用 Vue 构建的单页应用程序中,我们可能会遇到一个问题:如果我们使用了大量的组件,那么这些组件会一次性被打包到同一个 JS 文件中,这会导致用户需要等待很长时间才能看到页面。这时候,我们可以使用 Vue Router 的懒加载来解决这个问题。
懒加载是指只在需要的时候才加载某些组件,而不是将所有组件一次性加载。在 Vue Router 中,我们可以通过 webpack 的代码分割功能来实现懒加载。

import Vue from 'vue'
import Router from 'vue-router'

const Home = () => import('@/components/Home.vue')
const About = () => import('@/components/About.vue')
const Contact = () => import('@/components/Contact.vue')

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/about',
      name: 'About',
      component: About
    },
    {
      path: '/contact',
      name: 'Contact',
      component: Contact
    }
  ]
})

在这个例子中,我们使用了 import() 函数来实现懒加载。当需要访问某个组件时,才会加载该组件的代码。
需要注意的是,在使用 import() 函数时,需要确保使用了 webpack 的动态导入功能,即使用 @babel/plugin-syntax-dynamic-import 插件对代码进行转换。
通过使用懒加载,我们可以有效地减少页面加载时间,提高用户体验。

vue hash路由和history路由的区别和原理

  1. Hash 模式
    Hash 模式下,路由路径的格式为 #/your/path,# 后面的内容就是路由路径。hash 模式的优点是兼容性较好,可以兼容到 IE8,缺点是 URL 中始终带有 # 符号,看起来不太美观。

  2. History 模式
    History 模式下,路由路径的格式为 /your/path,没有 # 符号。history 模式的优点是 URL 看起来更加美观,缺点是需要后端支持

原理
Hash 模式的路由实现原理相对简单,它利用了浏览器的锚点机制,通过监听 window.location.hash 的变化来实现路由跳转,而不会重新加载页面,这种方式实现起来比较简单,也兼容所有浏览器。
History 模式的路由实现原理利用了 HTML5 标准中的 history API,在不刷新页面的情况下改变浏览器的 URL,但是这种方式需要服务器端的支持,需要在服务器配置一个 fallback,意味着如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是单页面应用的入口。

keep-alive原理

  • keep-alive用于保存组件的渲染状态,当组件在切换时不会被销毁,可以避免组件的重复渲染
  • keep-alive包裹的组件会新增两个生命周期函数:
    • activeted:当 keep-alive 包含的组件再次渲染的时候触发
    • deactiveted:当 keep-alive 包含的组件销毁的时候触发
  • 常用的两个属性 include/exclude,允许组件有条件的进行缓存
    添加链接描述
    如何使用

vue组件this里有什么东西

在 Vue 组件实例中,this 包含了以下属性和方法:

  • data:组件的数据对象,即所谓的响应式数据。
  • props:父组件传递给子组件的属性集合。
  • computed:计算属性对象。
  • methods:事件方法对象。
  • watch:观察属性对象。
  • $el:组件对应的 DOM 元素。
  • $refs:子组件或 DOM 元素的引用对象。
  • $emit:触发当前组件实例上的自定义事件。
  • $on:监听当前组件实例上的自定义事件。
  • $nextTick:在下次 DOM 更新循环结束之后执行回调。
  • $destroy:销毁当前组件实例。

除此之外,如果你在组件中使用了 Vuex 状态管理库,则 this 还会包含 $store 属性,表示 Vuex 的 Store 实例。

vue中普通函数中的this和箭头函数中的this

  • 普通函数中的this :Vue所有的生命周期钩子方法(如created,mounted, updated以及destroyed)里使用this,this指向调用它的Vue实例,即(new Vue)
  • 箭头函数中的this
    • 箭头函数没有自己的this, 它的this是继承而来
    • 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window
    • 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this

vue怎么实现父组件调用子组件的方法(注意不是子组件向父组件传值)

  • 通过ref直接调用子组件的方法
  • 通过组件的 e m i t 、 emit、 emiton方法;
    添加链接描述

vue父子组件的传值

添加链接描述

组件通信、vuex怎么用

添加链接描述

Vuex

vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无法持久化、内部核心原理是通过创造一个全局实例 new Vue)

五个属性:

  • state(初始化数据)
  • actions(异步处理数据)
  • mutations(唯一能修改state的操作)
  • getters(从state中动态获取相关数据)
  • modules(模块化)

actions和mutations区别

  1. actions可以异步,mutations必须同步
  2. mutations是唯一可以修改state的方法(commit
  3. actions修改state需要经过mutations(dispatch)

Vuex 页面刷新数据丢失怎么解决
需要做 vuex 数据持久化,一般使用本地储存的方案来保存数据,可以自己设计存储方案,也可以使用第三方插件。
推荐使用 vuex-persist插件,它是为 Vuex 持久化储存而生的一个插件。不需要手动存取 storage,而是直接将状态保存至 cookie 或者 localStorage中。

Vuex 为什么要分模块并且加命名空间
模块:为了防止store变得过于臃肿
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能会变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
命名空间: 让模块具有更高的封装度复用性 添加 namespaced:true
默认情况下,模块内部的 action、mutation、getter是注册在全局命名空间的 — 这样使得多个模块能够对同一 mutation 或 action 做出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced:true 的方式使其成为带命名的模块。当模块被注册后,他所有 getter、action、及 mutation 都会自动根据模块注册的路径调整命名。

如何使用vuex

辅助函数
Vuex属性使用的语法糖

  • mapState():用于将 store 中的 state 映射为组件的计算属性。
    js
  • mapGetters():用于将 store 中的 getters 映射为组件的计算属性。
  • mapMutations:用于将 store 中的 mutations 映射为组件的方法。
  • mapActions:用于将 store 中的 actions 映射为组件的方法。

原始方法
this.$store

  1. 安装Vuex:
npm install vuex --save
  1. 创建一个Vuex Store:
    在你的项目中创建一个store.js文件,这个文件将会包含你的应用程序的状态和状态变更方法。在这个文件中,引入Vuex和你需要使用的状态变更方法,然后创建一个store实例。
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 0, // 你的状态
  },
  mutations: {
    increment(state) { // 你的状态变更方法
      state.count++;
    },
  },
});
  1. 在Vue组件中使用Vuex:
    在Vue组件中,你可以使用mapStatemapGettersmapMutationsmapActions四种辅助函数来使用Vuex。
<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="incrementCount">Increment</button>
  </div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';

export default {
  computed: mapState(['count']),
  methods: mapMutations(['increment']),
  name: 'MyComponent',
};
</script>

在上面的组件中,mapStatestore中的count映射到组件的count计算属性,mapMutationsincrement映射到组件的incrementCount方法上。当用户点击按钮时,incrementCount方法将会触发increment方法,修改store中的状态。

$nextTick

在 Vue 中,每次数据发生变化,DOM 都不会立即更新。Vue 会在下一个时间循环中更新 DOM,以避免频繁操作 DOM 对性能的影响。如果你需要在数据更新后立即操作 DOM,就需要使用 $nextTick 方法
Vue提供的一个全局API,是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用$nextTick,可以在回调中获取更新后的DOM
Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作非常重要。
nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后调用
应用场景:需要在视图更新之后,基于新的视图进行操作

自定义指令

指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素添加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法
生命周期(钩子函数):
1、bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
2、inserted:被绑定元素插入父节点时调用。
3、update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较前后的绑定值。
4、componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
5、unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数

  • el:指令所绑定的元素,可以用来直接操作 DOM。
  • binding:一个对象,包含以下 属性:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用
    原理:
    1、在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
    2、通过 genDirectives 生成指令代码
    3、在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子。
    4、当执行指令对应钩子函数时,调用对应指令定义方法。

如何注册自定义指令

  1. 使用Vue.directive注册一个全局自定义指令
  2. 钩子函数中写下指令的具体内容
  3. 在元素标签中使用
<template>
  <div>
    <input v-uppercase type="text" v-model="message">
    <p>{{ message }}p>
  div>
template>
<script>
export default {
  directives: {
    uppercase: {
      bind: function(el) {
        el.addEventListener('input', function() {
          var val = el.value.toUpperCase();
          el.value = val;
        });
      }
    }
  },
  data() {
    return {
      message: '',
    }
  }
}
script>

在上面的示例中,我们定义了一个名为 uppercase 的自定义指令,它在 bind 钩子函数中注册了一个 input 事件监听器,监听用户在输入框中输入的内容,并将其转换为大写字母。最后,我们将自定义指令 v-uppercase 绑定到了一个文本输入框上。

Vue函数式组件

Vue函数式组件是一种特殊的组件,它只有一个函数作为组件本身,而不是像普通组件那样的组件选项对象。

函数式组件被设计用来优化渲染性能减少内存消耗,因为它们缺少实例和实例创建过程中的额外开销

Vue.component('my-functional-component', {
  functional: true,
  render: function(createElement, context) {
    // 函数式组件的实现
  }
})

在这个示例中,functional选项告诉Vue这是一个函数式组件。render函数接收两个参数:createElementcontextcreateElement是用来创建VNode的函数,context包含了一些组件上下文信息。

需要注意的是,在函数式组件中,没有实例上下文,因此无法访问this。如果需要访问父组件传递的props、data等信息,可以使用context.props、context.children、context.data等属性来获取。

由于函数式组件只是一个函数,它没有状态(无法访问this.state),也就无法使用生命周期钩子函数。因此,函数式组件通常只用于简单的UI组件,如按钮、图标等,而不是用于复杂的业务逻辑组件

与普通组件的区别

  1. 函数式组件需要在声明组件是指定 functional
  2. 不需要实例化,所以没有this,this通过render函数的第二个参数来代替
  3. 没有生命周期钩子函数,不能使用计算属性,watch
  4. 不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
  5. 因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
  6. 函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)
    优点
  7. 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件
  8. 函数式组件结构比较简单,代码结构更清晰

Vue SSR

SSR :服务端渲染
Vue的SSR(Server Side Rendering)是一种在服务器端渲染Vue组件生成HTML页面并将其发送到客户端的技术。

生成HTML
vue组件
服务器渲染
客户端

优点:

  1. 更好的SEO:由于搜索引擎爬虫通常只能获取HTML和CSS,使用SSR可以让搜索引擎更好地索引您的网页。
  2. 更快的首次加载时间:使用SSR可以让用户在浏览器加载JavaScript文件之前就能看到渲染完的页面,从而减少了等待时间。
  3. 更好的用户体验:使用SSR可以提供更快的首次渲染时间和更好的页面性能,从而提高用户体验。
    缺点:
    开发条件会受限制,服务器端渲染只支持 beforeCreatecreated 两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。 服务器会有更大的负载需求
    使用Vue的SSR需要对Vue框架有一定的掌握,需要了解一些服务器端JavaScript环境(如Node.js)的知识。同时,需要对Vue的特定API和生命周期钩子函数有一定的了解,以便在服务器端正确地渲染组件。 SSR 有着更好的 SEO、并且首屏加载速度更快。

vue的diff算法

添加链接描述

Vue 性能优化

  • 使用虚拟 DOM:Vue 的虚拟 DOM 可以减少 DOM 操作,提高渲染性能。开启生产模式下的编译器,会自动将模板转换为渲染函数,这样可以减少一些运行时的开销。
  • 对象层级不要过深,否则性能就会差
  • 不需要响应式的数据不要放在 data 中(可以使用 Object.freeze() 冻结数据)
  • 合理使用 v-if 和 v-show:v-if 在切换时会销毁和重建组件,而 v-show 仅仅是通过 CSS 控制是否显示。在需要频繁切换的情况下,使用 v-show 会更加高效。
  • computed 和 watch 区分场景使用
  • v-for 遍历必须加 key,key值保证唯一,且避免同时使用 v-if
  • 避免使用 v-for 和 v-if 同时存在:当 v-for 和 v-if 同时使用时,会导致重复渲染,降低性能。可以使用 computed 属性或者过滤器来处理需要过滤的数据。
  • 合理使用组件:组件是 Vue 的核心,但是组件如果使用不当也可能会降低性能。在大型应用中,需要精细地设计组件的嵌套层次,避免过多的组件嵌套和不必要的组件间通信。
  • 使用异步组件:异步加载组件可以减少首屏加载时间,提高用户体验。可以使用 Vue 的异步组件或者第三方库如 webpack 的 code splitting 功能。
  • 大数据列表和表格性能优化 - 虚拟列表 / 虚拟表格
  • 防止内部泄露,组件销毁后把全局变量和时间销毁
  • 图片懒加载、路由懒加载
  • 异步路由
  • 第三方插件的按需加载
  • 适当采用 keep-alive 缓存组件
  • 防抖、节流的运用
  • 服务端渲染 SSR or 预渲染
  • 使用 CDN 加载 Vue 和第三方库:放在 CDN 上的资源会被浏览器缓存,可以减少网络请求,提高加载速度。

首屏加载速度慢怎么解决

  • 减小入口文件积:路由懒加载
  • 静态资源本地缓存:采用HTTP缓存,设置Cache-Control,Last-Modified,Etag等响应头;采用Service Worker离线缓存
  • UI框架按需加载
  • 图片资源的压缩
  • 组件重复打包
  • 开启GZip压缩
  • 使用SSR:vue应用建议使用Nuxt.js实现服务端渲染

渲染时机

  • created请求时,是在页面渲染出来之前做的事情,如果数据过大,可能造成页面白屏过久,因为created还没有dom元素生成,并且也不能操作dom元素。
  • mounted请求时,是在页面渲染出来之后做的事情,此时可以操作dom元素

实现一个SPA

添加链接描述

三、React

说说对React的理解?有哪些特性?

  1. React,用于构建用户界面的 JavaScript 库,提供了 UI 层面的解决方案,遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效,使用虚拟DOM来有效地操作DOM,遵循从高阶组件到低阶组件的单向数据流,帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面
  2. 特性
  • JSX语法
  • 单向数据绑定
  • 虚拟DOM
  • 声明式编程
  • Component(组件化)
  1. 优势
  • 高效灵活
  • 声明式的设计,简单使用
  • 组件式开发,提高代码复用率
  • 单向响应的数据流会比双向绑定的更安全 速度更快

区分Real DOM和Virtual DOM

  1. Real DOM
  • Real DOM,真实DOM, 意思为文档对象模型,是一个结构化文本的抽象,在页面渲染出的每一个结点都是一个真实DOM结构
  • 更新缓慢
  • 可以直接更新 HTML
  • 如果元素更新,则创建新DOM
  • DOM操作代价很高
  • 消耗的内存较多
  1. Virtual DOM
  • Virtual Dom,本质上是以 JavaScript 对象形式存在的对 DOM 的描述。创建虚拟DOM目的就是为了更好将虚拟的节点渲染到页面视图中,虚拟DOM对象的节点与真实DOM的属性一一照应
  • 更新更快
  • 无法直接更新HTML
  • 如果元素更新,则更新JSX
  • DOM操作非常简单
  • 很少的内存消耗

了解 Virtual DOM 吗?解释一下它的工作原理

  1. Virtual DOM 是一个轻量级的 JavaScript 对象,它最初只是 real DOM 的副本。它是一个节点树,它将元素、它们的属性和内容作为对象及其属性。 React 的渲染函数从 React 组件中创建一个节点树。然后它响应数据模型中的变化来更新该树,该变化是由用户或系统完成的各种动作引起的。
  2. 工作原理
  • 每当底层数据发生改变时,整个 UI 都将在 Virtual DOM 描述中重新渲染
  • 然后计算之前 DOM 表示与新表示的之间的差异
  • 完成计算后,将只用实际更改的内容更新 real DOM

什么是JSX和它的特性?

  1. JSX 是JavaScript XML的缩写,不是html或xml,基于ECMAScript的一种新特性,一种定义带属性树结构的语法
  2. 特性
  • 自定义组件名首字母大写
  • 嵌套;在render函数中return返回的只能包含一个顶层标签,否则也会报错
  • 求值表达式;JSX基本语法规则,遇到HTML标签(以<开头),就用HTML规则解析;遇到代码块(以{开头),就用JS规则解析
  • 驼峰命名
  • class属性需要写成className
  • JSX允许直接在模板插入JS变量。如果这个变量是一个数组,则会展开这个数组的所有成员
  • 在JSX中插入用户输入是安全的,默认情况下ReactDOM会在渲染前,转义JSX中的任意值,渲染前,所有的值都被转化为字符串形式,这能预防XSS攻击。

类组件和函数组件之间有什么区别

  1. 类组件
  • 无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props
  • 所有 React 组件都必须是纯函数,并禁止修改其自身 props
  • React是单项数据流,父组件改变了属性,那么子组件视图会更新
  • 属性 props是外界传递过来的,状态 state是组件本身的,状态可以在组件中任意修改
  • 组件的属性和状态改变都会更新视图
  1. 函数组件
  • 函数组件接收一个单一的 props 对象并返回了一个React元素
  • 函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件
  1. 语法上的区别:
    函数式组件是一个纯函数,它是需要接受props参数并且返回一个React元素就可以了。类组件是需要继承React.Component的,而且class组件需要创建render并且返回React元素,语法上来讲更复杂。

  2. 调用方式
    函数式组件可以直接调用,返回一个新的React元素;类组件在调用时是需要创建一个实例的,然后通过调用实例里的render方法来返回一个React元素。

  3. 状态管理
    函数式组件没有状态管理,类组件有状态管理。

  4. 使用场景
    类组件没有具体的要求。函数式组件一般是用在大型项目中来分割大组件(函数式组件不用创建实例,所有更高效),一般情况下能用函数式组件就不用类组件,提升效率。

说说对 State 和 Props的理解,有什么区别

  1. State
  • 一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是state,一般在 constructor 中初始化
  • 当需要修改里面的值的状态需要通过调用setState来改变,从而达到更新组件内部数据的作用,并且重新调用组件render方法
  • setState还可以接受第二个参数,它是一个函数,会在setState调用完成并且组件开始重新渲染时被调用,可以用来监听渲染是否完成
  1. Props
  • React的核心思想就是组件化思想,页面会被切分成一些独立的、可复用的组件,组件从概念上看就是一个函数,可以接受一个参数作为输入值,这个参数就是props,所以可以把props理解为从外部传入组件内部的数据
  • react具有单向数据流的特性,所以他的主要作用是从父组件向子组件中传递数据
  • props除了可以传字符串,数字,还可以传递对象,数组甚至是回调函数
  • 在子组件中,props在内部不可变的,如果想要改变它看,只能通过外部组件传入新的props来重新渲染子组件,否则子组件的props和展示形式不会改变
  1. 相同点
  • 两者都是 JavaScript 对象
  • 两者都是用于保存信息
  • props 和 state 都能触发渲染更新
  1. 区别
  • props 是外部传递给组件的,而 state 是在组件内被组件自己管理的,一般在 constructor 中初始化
  • props 在组件内部是不可修改的,但 state 在组件内部可以进行修改
  • state 是多变的、可以修改

说说对React refs 的理解?应用场景?

  1. React 中的 Refs提供了一种方式,允许我们访问 DOM节点或在 render方法中创建的 React元素。
  2. 本质为ReactDOM.render()返回的组件实例,如果是渲染组件则返回的是组件实例,如果渲染dom则返回的是具体的dom节点
  3. 如何使用
  • 传入字符串,使用时通过 this.refs.传入的字符串的格式获取对应的元素
  • 传入对象,对象是通过 React.createRef() 方式创建出来,使用时获取到创建的对象中存在 current 属性就是对应的元素
  • 传入函数,该函数会在 DOM 被挂载时进行回调,这个函数会传入一个 元素对象,可以自己保存,使用时,直接拿到之前保存的元素对象即可
  • 传入hook,hook是通过 useRef() 方式创建,使用时通过生成hook对象的 current 属性就是对应的元素
  1. 应用场景 在某些情况下,我们会通过使用refs来更新组件,但这种方式并不推荐,过多使用refs,会使组件的实例或者是DOM结构暴露,违反组件封装的原则 但下面的场景使用refs非常有用:
  • 对Dom元素的焦点控制、内容选择、控制
  • 对Dom元素的内容设置及媒体播放
  • 对Dom元素的操作和对组件实例的操作
  • 集成第三方 DOM 库

setState是同步还是异步

添加链接描述

super()和super(props)有什么区别?

  • super()就是将父类中的this对象继承给子类的,没有super()子类就得不到this对象
    添加链接描述

说说对React事件机制的理解?

  1. React基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等,在React中这套事件机制被称之为合成事件;
  2. 合成事件是 React模拟原生 DOM事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器
  3. 执行顺序
  • React 所有事件都挂载在 document 对象上
  • 当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件
  • 所以会先执行原生事件,然后处理 React 事件
  • 最后真正执行 document 上挂载的事件
  1. 总结
  • React 上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
  • React 自身实现了一套事件冒泡机制,所以这也就是为什么我们 event.stopPropagation()无效的原因。
  • React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback
  • React 有一套自己的合成事件 SyntheticEvent

React事件绑定的方式有哪些?区别?

添加链接描述

React组件生命周期有几个阶段

  1. 初始渲染阶段:这是组件即将开始其生命之旅并进入 DOM 的阶段
  • getDefaultProps:获取实例的默认属性
  • getInitialState:获取每个实例的初始化状态
  • componentWillMount:组件即将被装载、渲染到页面上
  • render:组件在这里生成虚拟的 DOM 节点
  • componentDidMount:组件真正在被装载之后
  1. 更新阶段:一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染。这些只发生在这个阶段
  • componentWillReceiveProps:组件将要接收到属性的时候调用
  • shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止 render 调用,后面的函数不会被继续执行了)
  • componentWillUpdate:组件即将更新不能修改属性和状态
  • render:组件重新描绘
  • componentDidUpdate:组件已经更新
  1. 卸载阶段:这是组件生命周期的最后阶段,组件被销毁并从 DOM 中删除。
  • componentWillUnmount:组件即将销毁

详细解释 React 组件的生命周期方法

  • componentWillMount() – 在渲染之前执行,在客户端和服务器端都会执行。
  • componentDidMount() – 仅在第一次渲染后在客户端执行。
  • componentWillReceiveProps() – 当从父类接收到 props 并且在调用另一个渲染器之前调用。
  • shouldComponentUpdate() – 根据特定条件返回 true 或 false。如果你希望更新组件,请返回true ,不想更新组件则返回 false就会阻止render渲染。默认情况下,它返回 true。
  • componentWillUpdate() – 在 DOM 中进行渲染之前调用。
  • componentDidUpdate() – 在渲染发生后立即调用。
  • componentWillUnmount() – 从 DOM 卸载组件后调用。用于清理内存空间。

react在哪个生命周期做优化

  • shouldComponentUpdate,这个方法用来判断是否需要调用 render 方法重绘 dom。
    因为 dom 的描绘非常消耗性能,如果我们能在这个方法中能够写出更优化的 dom diff 算法,可以极大的提高性能。
shuoldComponentUpdate(nextProps, nextState) {
    if (nextProps === this.props && nextState === this.state) {
        return flase;
    }
    return true;
}
// 可以使用react提供的组件PureComponent达到相同的目的。
class App exntends React.PureCompoennt {}

受控组件和非受控组件的区别

  • 受控组件是React控制的组件,input等表单输入框值不存在于 DOM 中,而是以我们的组件状态存在。每当我们想要更新值时,我们就像以前一样调用setState。
  • 不受控制组件是您的表单数据由 DOM 处理,而不是React 组件,Refs 用于获取其当前值

React中的key有什么作用?

跟Vue一样,React 也存在diff算法,而元素key属性的作用是用于判断元素是新创建的还是被移动的元素,从而减少不必要的Diff,因此key的值需要为每一个元素赋予一个确定的标识。

如果列表数据渲染中,在数据后面插入一条数据,key作用并不大;前面的元素在diff算法中,前面的元素由于是完全相同的,并不会产生删除创建操作,在最后一个比较的时候,则需要插入到新的DOM树中。因此,在这种情况下,元素有无key属性意义并不大。
如果列表数据渲染中,在前面插入数据时,当拥有key的时候,react根据key属性匹配原有树上的子元素以及最新树上的子元素,只需要将元素插入到最前面位置,当没有key的时候,所有的li标签都需要进行修改
并不是拥有key值代表性能越高,如果说只是文本内容改变了,不写key反而性能和效率更高,主要是因为不写key是将所有的文本内容替换一下,节点不会发生变化,而写key则涉及到了节点的增和删,发现旧key不存在了,则将其删除,新key在之前没有,则插入,这就增加性能的开销
总结
良好使用key属性是性能优化的非常关键的一步,注意事项为:

  • key 应该是唯一的
  • key不要使用随机值(随机数在下一次 render 时,会重新生成一个数字)
  • 避免使用 index 作为 key

react组件之间如何通信

  • 父子:父传子:props; 子传父:子调用父组件中的函数并传参
  • 兄弟:利用redux实现和利用父组件
  • 所有关系都通用的方法:利用PubSub.js订阅

什么是高阶组件?

  1. 高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。基本上,这是从React的组成性质派生的一种模式,我们称它们为“纯”组件, 因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件的任何行为。
  • 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧
  • 高阶组件的参数为一个组件返回一个新的组件
  • 组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件

说说对React Hooks的理解?解决了什么问题?

  • Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性,因此,现在的函数组件也可以是有状态的组件,内部也可以维护自身的状态以及做一些逻辑方面的处理
  • 最常见的hooks有:useState、useEffect
  • hooks的出现,使函数组件的功能得到了扩充,拥有了类组件相似的功能;

hook函数

  • useState:让函数式组件拥有状态
  • useEffect:模拟生命周期
  • useRef:获取dom元素
  • useMemo:实现计算属性
  • useCallback:缓存函数

useEffect函数如何模拟生命周期

可以接收两个参数:

  • 第一个参数是回调函数,这个函数称为effect函数,主要是处理你的业务逻辑
    • 第一个参数返回的是一个清理函数,不能返回promise对象,不能直接使用async/await
    • 在回调参数中使用async/await:
      1. 使用自执行函数(立即执行函数)
        useEffect(()=>{
        	// 使用自执行函数 IIFE
        	(async function fn(){
        		await otherFn();
        	})()
        },[])
        
      2. 回调参数内部定义一个async函数
        useEffect(()=>{
        	const fn=async ()=>{
        		// do something
        		await otherFn()
        	}
        	fn()
        },[])
        
      3. 在useEffect外部定义async函数,在回调参数中去执行
        const fn=async ()=>{
        	// do something
        	await otherFn()
        }
        useEffect(()=>{
        	fn()
        },[])
        
  • 第二个参数是一个数组:数组里面可以填写你要监控的对象
  1. 模拟componentDidMount
    第二个参数是一个空数组,所以它只在初始化的时候执行一次,后面就不执行
  2. 模拟componentDidUpdate
    • 不放第二个参数
    • 放第二个参数:监听这个属性
  3. 模拟componentWillUnMount
    要在effect函数里面返回一个函数,在这个返回函数里面就可以去做一些资源清理工作

useCallback与useMemo的区别

  • useMemo :计算结果是 return 回来的值, 主要用于 缓存计算结果的值
    应用场景: 需要计算的状态
  • useCallback :计算结果是 函数, 主要用于 缓存函数
    应用场景: 需要缓存的函数
    函数式组件每次任何一个 state 的变化,整个组件都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费

在React中组件间过渡动画如何实现

  1. 在react中,react-transition-group是一种很好的解决方案,其为元素添加enter,enter-active,exit,exit-active这一系列勾子,可以帮助我们方便的实现组件的入场和离场动画
  • CSSTransition:在前端开发中,结合 CSS 来完成过渡动画效果
  • SwitchTransition:两个组件显示和隐藏切换时,使用该组件
  • TransitionGroup:将多个动画组件包裹在其中,一般用于列表中元素的动画

React context是什么?

Context来实现跨层级的组件数据传递
添加链接描述

说说你对Redux的理解?其工作原理?

  • redux将所有的状态进行集中管理,当需要更新状态的时候,仅需要对这个管理集中处理,而不用去关心状态是如何分发到每一个组件内部的
  • redux是一个实现集中管理的容器,遵循三大基本原则:单一数据源、state 是只读的、使用纯函数来执行修改
    • 单一事实来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。
    • 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
    • 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。
  • redux并不是只应用在react中,还与其他界面库一起使用,如Vue
  • 工作原理:redux要求我们把数据都放在 store公共存储空间,一个组件改变了 store 里的数据内容,其他组件就能感知到 store的变化,再来取数据,从而间接的实现了这些数据传递的功能

redux工作流程

  • Store:保存数据的容器
  • state:store里存储的数据 (store里面可以拥有多个state)
    Redux规定一个state对应一个View,只要state相同,view就是一样的
  • Action:State的变化,会导致View的变化;但是,用户接触不到State,只能接触到View;所以,State的变化是View导致的;Action就是View发出的通知 ,表示State应该要发生变化了
    Action是一个对象,其中,type属性是必须的,表示Action的名称,其他的可以根据需求自由设置
  • Action Creator(动作创建器):View要发送多少种消息,就会有多少种Action
  • dispatch:是View发出Action的唯一方法
    接收一个Action作为参数,将它发送给store,通知store来改变state
  • Reducer:Store收到Action以后 ,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Rducer
    Rducer是一个纯函数,它接受Action和当前State作为参数,返回一个新的State
    (Reducer必须是一个纯函数,也就是说函数返回的结果必须由参数state和action决定,而且不产生任何副作用也不能修改state和action对象)

React组件怎么获取redux里的数据

用高阶组件Connect建立组件与redux的连接,Connect的第一个参数是mapStateToProps,将redux的数据映射到组件props中

React组件如何更新redux里的数据

dispatch派发action,通知reducer同步更新state数据

vuex和redux的区别

  1. vuex有modules,模块化
  2. vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer
  3. vuex无需订阅重新渲染函数,只要生成新的state,就能自动重新渲染数据

为什么 React Router 中使用 Switch 关键字

  • 由于router和switch对于路由的渲染策略不同,对router来说,如果有的链接既可以被路由A匹配,又可以被路由B匹配,那么Router会同时渲染它们
  • 对于switch来说,它只会渲染符合条件的第一个路径,避免重复匹配

前端工程化

含义:使用软件工程的技术和方法来进行前端的开发流程、技术、工具、经验等规范化、标准化
主要目的:为了提高效率和降低成本,即提高开发过程中的开发效率,减少不必要的重复工作时间

如何实现
模块化组件化规范化自动化

  1. 模块化
    • JS模块化
    • css模块化
    • 资源模块化
      优点:依赖关系单一化资源处理集成化项目结构清晰化
  2. 组件化
    从UI拆分下来的每个包含模板(HTML)+样式(CSS)+逻辑(JS)功能完备的结构单元,称之为组件
  3. 规范化
    • 目录结构的制定
    • 编码规范
    • 前后端接口规范
  4. 自动化:任何简单机械的重复劳动都应该让机器去完成
    • 图标合并
    • 持续集成
    • 自动化构建
    • 自动化部署
    • 自动化测试

TypeScript

TypeScript的设计目的是解决JavaScript的“痛点”:弱类型没有命名空间,导致很难模块化,不适合开发大型程序

  • 编译时的强类型
    申明变量的类型,那么任何其他类型的赋值将会引起编译错误
  • 模块化
    利用TypeScript的关键词module,可以达到类似于命名空间的效果,而export可以控制是否被外部访问

type和interface的区别

相同:都可以描述一个对象或者函数
不同

  1. type可以声明 基本类型,联合类型,元组(存储的元素数据类型不同) 的别名,interface不行
  2. type 语句中可以使用 typeof 获取类型实例
  3. type 支持类型映射,interface不支持
  4. interface能够声明合并,type不能

泛型

宽泛的类型,通常用于类和函数
泛型的本质是参数化类型,通俗的将就是所操作的数据类型被指定为一个参数,这种参数类型可以用在接口方法的创建中,分别成为泛型类泛型接口泛型方法
函数在定义时,若指定返回值不为void或者any,则 在函数 内 必须 写 return
TypeScript可以使用泛型来创建可重用的组件。支持当前数据类型,同时也能支持未来的数据类型。扩展灵活。可以在编译时发现你的类型错误,从而保证了类型安全

解决的问题
函数传入不确定类型参数,传入什么类型就返回什么类型
例:判断字符串、数组的最小值,定义了泛型,可直接return一个值,函数复用,而不用去写两个函数

react性能优化

  1. 传参优化:在传递大量数据到子组件的时候,可以使用对象传递数据
    具体做法:state中定义对象→render中用const保存该对象→组件传参
  2. key值定义:在遍历渲染的时候要求对每个遍历后的节点都需要加上一个唯一的key
  3. 分片打包
    路由组件的懒加载
    • loadable插件可以实现组价的懒加载,第三方插件
    • React.lazy()也可以实现组件的懒加载,官方提供
  4. Fragment
    在写DOM元素的时候,有时候必须在外层包裹一层组件的时候,可以使用<>或者是空标签包裹
  5. 组件的卸载和加载
    针对频繁切换是否展示的组件,可以通过样式css的display:none属性去控制
    如果组价不是频繁的切换是否展示,可以用true ?:null三元的方式去写
  6. 计算缓存
    当计算量大,比较耗时的时候,可以将这个计算结果进行缓存
    • 函数组件里面可以使用useMemo来对结果缓存
    • 函数组件还可以使用useCallback来对函数进行缓存
  7. 其他性能优化
    • 组件化:达到代码可复用的目的
    • 减少DOM 操作
    • 尽量减少http请求
    • 使用雪碧图,或者精灵图将小图片存放在一个大的图片里面,通过css样式定位的方式去展示相应的图标

四、数据结构问题

  • 数字全排列
  • 合并两个有序数组
  • 二叉树路径和
  • 找峰值
  • 数组排序
  • 实现计算机求字符串值
  • 反转链表 n级嵌套
  • 爬楼梯问题
  • 最长回文子串
  • 裴波拉
  • 完全二叉
  • 最大子序和
  • 子字符串匹配

你可能感兴趣的:(前端面试,面试)