优先级
一级优先级:js、框架(vue/react)、项目介绍
二级优先级:html/css、浏览器相关
三级优先级:webpack、ts、git
js
js中的this指向
1、默认绑定,严格模式下指向undefined,非严格模式下执行window
2、隐式绑定,调用位置是否有上下文,如果有这将this绑定到这个上下文。
3、显示绑定,通过call,apply来绑定
let obj={name:111};
function sayName(){console.log(this.name)
}sayName.call(obj)
4、通过new来绑定,new调用函数时会创建一个新对象,并将该对象作为this的上下文
js的数据类型
7种基本数据类型:Number、String、Boolean、Null、Undefined、Symbol、BigInt
1种引用数据类型:Object(包括Array Function Date等)
基本数据类型:直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型:同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。
js的数据类型的转换
在 JS 中类型转换只有三种情况,分别是:Number/String/Boolean
Number转为数字:Number() parsetInt() parseFloat()
String转为字符串:toString() String()
Boolean转为布尔值:Boolean()
js数据类型的判断
1、typeof判断基本数据类型
typeof返回值:number/string/boolean/object/undefined/symbol/function
typeof对象除了函数都会返回object
2、instanceof用于判断引用数据类型
instanceof 就是判断一个实例是否属于某种类型(检测对象类型)
instaceof的原理:原型链的向上查找
function myInstanceof (left, right) {// 基本数据类型直接返回falseif (typeof left !== 'object' || left === null) return false// getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象let proto = Object.getPrototypeOf(left)while (true) {// 查找到尽头,还没找到if (proto == null) return false// 找到相同的原型对象if (proto == right.prototype) return trueproto = Object.getPrototypeOf(proto)}
https://blog.csdn.net/weixin_40013817/article/details/103182967`
3、constructor
能够判断基本数据类型和引用数据类型
console.log((2).constructor === Number); // true
console.log(('str').constructor === String); // true
console.log((true).constructor === Boolean); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
问题在于如果创建了一个对象,更改了它的原型,constructor会变得不可靠
function Fn(){};Fn.prototype=new Array();var f=new Fn();console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true
4、Object.prototype.toString.call()能够判断所有的数据类型,即使是null或者undefined
介绍 js 有哪些内置对象
Number对象:一个数值变量就是一个数值对象
String对象:提供了对字符串进行操作的属性和方法
Boolean:一个布尔变量就是一个布尔对象
Array对象:提供了数组操作方面的属性和方法
Date对象:可以获取系统的日期时间信息
Math对象:提供了数学运算方面的属性和方法
Object对象、RegExp对象、 Global对象、Function对象等
https://www.html.cn/qa/javascript/11579.html
new运算符做了什么
1.创建一个空对象(即{});
2.为新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
3.将新创建的对象作为this的上下文 ;
4.如果该函数没有返回对象,则返回this。
防抖节流
1、防抖
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
思路:每次触发事件之前都取消之前的延时调用方法
2、节流
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
思路:每次触发事件时都判断当前是否有等待执行的延时函数
3、相同点:
都可以通过setTimeout实现
目的都是降低回调函数执行频率,节省计算资源
4、不同点:
函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout和setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次
函数防抖关注一定时间连续触发的时间只在最后一次执行,而节流侧重于一段时间内只执行一次。
5、防抖应用场景
- 搜索框输入
- 手机号、邮箱验证输入检测
- 窗口大小resize
6、截流应用场景
- 滚动加载
- 高频点击提交
null、undefined的区别
1、首先null和undefined都是基本的数据类型
2、undefined 值是派生自 null 值的,因此 ECMA-262 规定对它们的相等性测试要返回 true
console.log(null == undefined); //true
3、typeof undefined返回的是undefined,typeof null返回的是’object’
4、无论在什么情况下 都没有必要把一个变量的值显式地设置为 undefined,而只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存 null
var、let、const的区别
1、var声明的变量会挂载在window上,而let和const声明的变量不会
2、var声明变量存在变量提升,let和const不存在变量提升
3、let和const声明形成块作用域
4、同一作用域下let和const不能声明同名变量,而var可以
5、const一旦声明必须赋值,不能使用null占位。声明后不能再修改。如果声明的是引用类型数据,可以修改其属性
call、apply、bind的区别
call,apply,bind的作用是改变函数运行时的this执行
1、call,apply是立即调用,bind是稍后调用
2、call,apply必须通过函数来调用,第一个参数为对象,若第一个参数为null或者undefined,则指向window
3、call和apply方法没有返回值(即返回undefined),bind方法返回一个原函数的拷贝,并具有指定的this值和初始参数
4、call和apply不能在箭头函数中使用(因为箭头函数没有自身的this,this在箭头函数中已经按照词法作用域绑定了,所以没有办法用call/apply来改变this执行)
本地存储及对应的应用场景
- cookie存储量比较小,只有4kb左右,可以设置过期时间
- sessionStorage和localStorage的存储量一般是5MB,但sessionStorage是一个会话级别的存储,数据只在浏览器关闭前有效
- localStorage是持久化存储,除非手动删除否则会一直存在
应用场景
cookie应用场景
1.判断用户是否登陆过网站,以便下次能够实现自动登录
2.保存上次登录时间等信息
3.保存上次查看的页面
4.浏览计数
sessionStorage:敏感账号一次性登录
localStorage:用于长期登录,适合长期保存在本地的数据
indexDB是一种底层API,用于在客户端存储大量结构化的数据(包括文件/二进制对象),Web Stroage(sessionStorage/localStorage)在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心,indexDB提供了这种场景的解决方案
indexDB是一个基于js的面向对象数据库。indexDB允许存储和检索用键索引的对象;可以存储结构化克隆算法支持的任何对象。
结构化克隆算法支持的类型包括:Number/String/Boolean/Set/Map/ObjectsNumber/String/Boolean/Set/Map/Objects
https://www.cnblogs.com/cencenyue/p/7604651.html
https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API
Set、WeakSet、Map、WeakMap的区别
Set
1.成员不能重复
2.只有健值,没有健名,有点类似数组。
3. 可以遍历,方法有add, delete,has
weakSet
成员都是对象
成员都是弱引用,随时可以消失。 可以用来保存DOM节点,不容易造成内存泄漏
不能遍历,方法有add, delete,has
Map
本质上是键值对的集合,类似集合
可以遍历,方法有get,set,has,delete,可以跟各种数据格式转换
weakMap
1.只接受对象作为名(null除外),不接受其他类型的值作为键名
键名所指向的对象,不计入垃圾回收机制
不能遍历,方法有get,set,has,delete
深拷贝、浅拷贝的区别
浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝: 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
浅拷贝的方法:
1、Object.assign()
2、…拓展运算符
深拷贝的方法
- 乞丐版:JSON.parse(JSON.stringify())
- 基础版:浅拷贝+递归(只考虑了普通的 object和 array两种数据类型)
function cloneDeep(target,map = new WeakMap()) {if(typeOf taret ==='object'){let cloneTarget = Array.isArray(target) ? [] : {};if(map.get(target)) {return target;}map.set(target, cloneTarget);for(const key in target){cloneTarget[key] = cloneDeep(target[key], map);}return cloneTarget}else{return target}
}
- 终极版
https://juejin.cn/post/6844903929705136141
3、使用lodash的cloneDeep(value)
伪数组与数组的区别
伪数组:
- 按索引方式储存数据(0:xxx,1:xxx)
- 具有length属性(但是length属性不是动态的,不会随着成员的变化而改变)
- 不具有数组的push(), forEach()等方法
伪数组转数组
- Array.from()
- …拓展运算符
- Array.prototype.slice.call()
- for of遍历
Promise
Promise基本特性
- Promise有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
- Promise对象接受一个回调函数作为参数, 该回调函数接受两个参数,分别是成功时的回调resolve和失败时的回调reject;另外resolve的参数除了正常值以外, 还可能是一个Promise对象的实例;reject的参数通常是一个Error对象的实例。
- then方法返回一个新的Promise实例,并接收两个参数onResolved(fulfilled状态的回调);onRejected(rejected状态的回调,该参数可选)
- catch方法返回一个新的Promise实例
- finally方法不管Promise状态如何都会执行,该方法的回调函数不接受任何参数
- Promise.all()方法将多个多个Promise实例,包装成一个新的Promise实例,该方法接受一个由Promise对象组成的数组作为参数(Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例),注意参数中只要有一个实例触发catch方法,都会触发Promise.all()方法返回的新的实例的catch方法,如果参数中的某个实例本身调用了catch方法,将不会触发Promise.all()方法返回的新实例的catch方法
- Promise.race()方法的参数与Promise.all方法一样,参数中的实例只要有一个率先改变状态就会将该实例的状态传给Promise.race()方法,并将返回值作为Promise.race()方法产生的Promise实例的返回值
- Promise.resolve()将现有对象转为Promise对象,如果该方法的参数为一个Promise对象,Promise.resolve()将不做任何处理;如果参数thenable对象(即具有then方法),Promise.resolve()将该对象转为Promise对象并立即执行then方法;如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为fulfilled,其参数将会作为then方法中onResolved回调函数的参数,如果Promise.resolve方法不带参数,会直接返回一个fulfilled状态的 Promise 对象。需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
- Promise.reject()同样返回一个新的Promise对象,状态为rejected,无论传入任何参数都将作为reject()的参数
Promise优点
- 统一异步 API
- Promise 与事件对比
和事件相比较, Promise 更适合处理一次性的结果。在结果计算出来之前或之后注册回调函数都是可以的,都可以拿到正确的值。 Promise 的这个优点很自然。但是,不能使用 Promise 处理多次触发的事件。链式处理是 Promise 的又一优点,但是事件却不能这样链式处理。 - Promise 与回调对比
解决了回调地狱的问题,将异步操作以同步操作的流程表达出来。 - Promise 带来的额外好处是包含了更好的错误处理方式
Promise缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
- Promise 真正执行回调的时候,定义 Promise 那部分实际上已经走完了,所以 Promise 的报错堆栈上下文不太友好。
Generator
generator(生成器)是es6引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
genenerator与普通函数比,generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数
generator还有另一个巨大的好处,就是把异步回调变成同步代码
可以使用yield关键字发起多个ajax请求
async/await
async
async返回的是一个Promise,如果在函数中return一个直接量,async会把这个直接量通过Promise.resolve()封装成Promise对象
Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。
await
await等待的是一个表达式,这个表达式的结果是一个Promise对象或者原始类型的值(此时等同于同步操作)
如果等到的是一个Promise对象,await就忙起来,它会阻塞后面的代码,等着Promise对象resolve,将后将resolve的值作为await表达式的运算结果。
注意点
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。
async function myFunction() {try {await somethingThatReturnsAPromise();} catch (err) {console.log(err);}
}// 另一种写法async function myFunction() {await somethingThatReturnsAPromise().catch(function (err){console.log(err);});
}
https://segmentfault.com/a/1190000007535316
http://www.ruanyifeng.com/blog/2015/05/async.html
闭包
定义:
闭包指有权访问另一个函数作用域中变量的函数,创建闭包的常用方式是函数内部创建另一个函数
特点
内部变量不能被外部访问
变量之间互不干涉,也不会污染全局变量
闭包中的变量不会被垃圾回收机制所回收
应用
防抖,节流,柯里化等
js同步、异步的区别
参考:https://www.cnblogs.com/lchsirblog/p/12048695.html
同步和异步的区别在于在这条流水线上各个流程的执行顺序不同。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才会执行后一个任务
异步任务指的是,不进入主线程,而进入任务队列,只有等主线程的任务执行完毕,任务队列通知主线程,请求执行任务,该任务才会进入主线程执行。
具体来说,异步运行机制如下:
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有那些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
- 主线程不断重复上面的第三步
原型与原型链
原型对象的作用是用来存放实例中共有的属性和方法,可以大大减少内存的消耗。
原型链:实例与原型之间的连接
当调用实例的某个属性或方法时,会首先在实例中查找,当未找到时,会到原型中查找,当原型中也未找到时,会到继承的原型中查找,到Object
作用域与作用域链
作用域分为全局作用域和局部作用域
全局作用域:最外层声明的函数和变量、未定义直接赋值的变量、所有window对象的属性
局部作用域:常见于函数内部
参考:https://github.com/mqyqingfeng/Blog/issues/6
在《JavaScript深入之变量对象》中讲到,当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
宏任务、微任务与事件循环
任务队列
首先我们需要明白以下几件事情:
- JS分为同步任务和异步任务
- 同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
根据规范,事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。 setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务。
宏任务
宏任务主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
微任务
Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
运行机制
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
js 创建对象的几种方式
1、使用对象字面量和Object构造函数,这两种方式可以用来创建单个对象,缺点在于创建大量相似对象时会产生大量重复代码
2、工厂模式解决了创建多个相似对象的问题,但没有解决对象识别的问题,即怎样知道一个对象的类型
3、创建自定义的构造函数可以将它们的实例标识为一种特定的类型,但是其中的每个方法都要在每个实例上重新创建一遍,即会导致不同的作用域链和标识符解析。
4、使用原型对象的好处是可以让所有对象共享它们所包含的属性和方法,通过hasOwnProperty判断属性是在实例中还是在原型中,只有属性在实例中才返回true
5、构造函数+原型模式。构造函数用于定义实例属性,原型用于定义共有的属性和方法。该模式是Ecmascript中使用最广泛、认可度最高的一种方式。
6、动态原型模式。该模式将所有的信息都封装在构造函数中,然后在构造函数中初始化原型,同时又保持了构造函数和原型的优点。
7、寄生构造函数模式。基本思想是创建一个函数,其作用是封装创建对象的代码,然后返回一个新对象
js 继承的几种实现方式
构造函数继承
原型继承
组合继承
寄生组合继承
es6继承(extends)
https://blog.csdn.net/weixin_45959504/article/details/106930563
js 获取原型的方法
person1.__proto__
person1.constructor.prototype
Object.getPrototypeOf(person1)
Ajax 是什么? 如何创建一个 Ajax?
//1:创建Ajax对象
var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容IE6及以下版本
//2:配置 Ajax请求地址
第二个参数表示请求地址,相对地址或者绝对地址都可,get请求携带参数要拼接到改路径后面
第三个表示是否为异步,true为异步,false为同步
// get请求不传参
xhr.open('get','index.xml',true);xhr.open('get',''http://localhost:3000/get?id=2",true);//3:发送请求
xhr.send(null); // 严谨写法// post 需要传递参数
xhr.send("name=jay&age=18")
//4:监听请求,接受响应
xhr.onreadystatechange=function(){
//4代表请求已完成,且响应已就绪if(xhr.readyState==4&&xhr.status==200 || xhr.status==304 )console.log(xhr.responseText)
}
https://www.cnblogs.com/tfxz/p/12701681.html
js 延迟加载的方式有哪些?
js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。
我了解到的几种方式是:
1、将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
2、给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步执行,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
3、给 js 脚本添加 async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
4、动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
window.addEventListener("load", function () { alert("open") });
window.onload = function () { alert("open") };
https://www.cnblogs.com/aisowe/p/11718724.html
谈谈你对模块化开发的理解
模块化主要是用来抽离公共代码,隔离作用域,避免变量冲突等。
IIFE: 使用自执行函数来编写模块化,特点:在一个单独的函数作用域中执行代码,避免变量冲突。
(function(){return {data:[]}
})()
AMD: 使用requireJS 来编写模块化,特点:依赖必须提前声明好。
define('./index.js',function(code){// code 就是index.js 返回的内容
})
CMD: 使用seaJS 来编写模块化,特点:支持动态引入依赖文件。
define(function(require, exports, module) { var indexCode = require('./index.js');
});
CommonJS: nodejs 中自带的模块化。
var fs = require('fs');
UMD:兼容AMD,CommonJS 模块化语法。
webpack(require.ensure):webpack 2.x 版本中的代码分割。
ES Modules: ES6 引入的模块化,支持import 来引入另一个 js 。
requireJS的核心原理是什么
require.js 的核心原理是通过动态创建 script 脚本来异步引入模块,然后对每个脚本的 load 事件进行监听,如果每个脚本都加载完成了,再调用回调函数
instanceof的原理是什么,如何实现
js的设计模式
1、单例模式
2、工厂模式
3、观察者模式
4、代理模式
5、策略模式
6、迭代器模式
https://segmentfault.com/a/1190000017787537
arguments 的对象是什么
1、实参的集合
2、是一个伪数组(按索引存储树,具有length属性,不具有push,forEach等方法)
3、伪数组转数组
Array.from()
…拓展运算符
Array.prototype.slice.call(arguments)
for of遍历
哪些操作会造成内存泄漏
内存泄漏:指一块被分配的内存既不能使用,又不能回收
js的回收机制有两种:标记清除和引用计数
1、意外的全局变量
2、被遗忘的定时器
3、脱离 DOM 的引用
4、滥用闭包
https://blog.csdn.net/gercke/article/details/113838694
https://blog.csdn.net/michael8512/article/details/77888000
es6的新特性
1、箭头函数
2、类的引入导出继承
3、字符串模板
4、Promise
5、let、const
6、async/await
7、默认参数
箭头函数与普通函数的区别
箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有以下几点差异:
1、函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
2、不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
3、不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
4、不可以使用 new 命令,因为:
没有自己的 this,无法调用 call,apply。
没有 prototype 属性 ,而 new 命令在执行时需要将构造函数的 prototype 赋值给新的对象的 proto
函数柯里化的实现
函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
// es6 实现
function curry(fn, ...args) {return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
https://juejin.cn/post/6844904200917221389#heading-49
手写call、apply、bind
手写深拷贝
手写promise
{}和[]的valueOf和toString的结果是什么?
DOM 操作——怎样添加、移除、移动、复制、创建和查找节点?
react
react生命周期
react有3个生命周期挂载阶段,更新阶段,和卸载阶段
挂载阶段:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
更新阶段:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
卸载阶段:
componentWillUnmount()
各个生命周期的作用:
constructor():初始化内部state和为事件处理函数绑定实例
static getDerivedStateFromProps():很少用,只有在state的值在任何时候都取决与props时使用
render():类组件必须要实现的方法,该方法返回的类型如下
React元素(jsx),数组或fragment时,Protals,字符串或数值,布尔值或null
componentDidMount():dom节点已经生成,发起异步请求
shouldComponentUpdate():性能优化的生命周期,可以将this.props与nextProps以及this.state和nextState进行比较,返回false跳过更新。推荐使用PureComponent,该组件会对props和state进行浅比较,减少不必要的更新。
getSnapshotBeforeUpdate():不常用,但它可能出现在ui处理中,该生命周期的任何返回值都将作为参数传递给componentDidUpdate()
componentDidUpdate():可以调用setState(),但必须包裹在条件语句中,否则会导致死循环
componentWillUnmount():清除timer,取消网络请求
react hooks的使用
Hooks是React版本16.8的新功能。之前我们说函数组件是无状态组件,有了Hooks之后,函数组件也可以管理自己的状态
基本规则:Hooks应该在外层使用,不应该在循环、条件或嵌套函数中使用,Hooks应该只在函数组件中使用
useState
const [age,setAge]=useState(18)
useState的返回值是当前state以及更新state的函数。所以可以使用数组解构的方式将这两个值解构出来
Effect Hook可以让我们在函数组件中执行副作用操作。(副作用:ajax请求,路由跳转等)
useEffect(() => {const subscription = props.source.subscribe();return () => {subscription.unsubscribe();};},[props.source],
);
useEffect相当于componentDidMount、componentDidUpdate、componentWillUnmount三个生命周期的统一
useEffect会在每次渲染后都执行
effect中可以返回一个函数,这个effect可选的清除机制。每个effect都可以返回一个清除函数
React何时清除effect?
React会在组件卸载的时候执行清除操作。同时React会在执行当前effect之前对上一个effect执行清除
useEffect第二个参数是一个数组,只有当数组的值发生变化时才会调用effect,如果只执行一次,传入空数组即可
useLayoutEffect与useEffect 的参数一致
useEffect 和 useLayoutEffect 的区别
useEffect在渲染绘制到页面后异步运行
useLayoutEffect 在渲染之后但在页面更新之前同步运行。
useLayoutEffect 相比 useEffect,通过同步执行状态更新可解决一些特性场景下的页面闪烁问题。
useEffect 可以满足百分之99的场景,而且 useLayoutEffect 会阻塞渲染,请谨慎使用。
https://juejin.cn/post/6844904008402862094
https://daveceddia.com/useeffect-vs-uselayouteffect/
useContext()共享状态钩子
<div className="App"><Navbar/><Messages/>
</div>
如果两个兄弟组件想共享状态
第一步就是使用 React Context API,在组件外部建立一个 Context。
const AppContext = React.createContext({});
组件封装代码如下。
<AppContext.Provider value={{username: 'superawesome'
}}><div className="App"><Navbar/><Messages/></div>
</AppContext.Provider>
上面代码中,AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享。
Navbar 组件的代码如下。
const Navbar = () => {const { username } = useContext(AppContext);return (<div className="navbar"><p>AwesomeSite</p><p>{username}</p></div>);
}
上面代码中,useContext()钩子函数用来引入 Context 对象,从中获取username属性。
useReducer():action 钩子
React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。
Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState。
useReducers()钩子用来引入 Reducer 功能。
const [state, dispatch] = useReducer(reducer, initialState);
上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。
useCallBack
const memoizedCallback = useCallback(() => {doSomething(a, b);},[a, b],
);
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。
如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。
useCallback、useMemo 的区别
简单理解呢 useCallback 与 useMemo 一个缓存的是函数,一个缓存的是函数的返回值。useCallback 是来优化子组件的,防止子组件的重复渲染。useMemo 可以优化当前组件也可以优化子组件,优化当前组件主要是通过 memorize来将一些复杂的计算逻辑进行缓存。
https://juejin.cn/post/6844904101445124110
https://juejin.cn/post/6844904001998176263
useRef
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
创建自己的hooks
const usePerson = (personId) => {const [loading, setLoading] = useState(true);const [person, setPerson] = useState({});useEffect(() => {setLoading(true);fetch(`https://swapi.co/api/people/${personId}/`).then(response => response.json()).then(data => {setPerson(data);setLoading(false);});}, [personId]); return [loading, person];
};
面代码中,usePerson()就是一个自定义的 Hook。
Person 组件就改用这个新的钩子,引入封装的逻辑。
const Person = ({ personId }) => {const [loading, person] = usePerson(personId);if (loading === true) {return <p>Loading ...</p>;}return (<div><p>You're viewing: {person.name}</p><p>Height: {person.height}</p><p>Mass: {person.mass}</p></div>);
};
自定义hooks
useDebounce/useThrottle
function useDebounce(fn, delay, dep = []) {const { current } = useRef({ fn, timer: null });useEffect(() => {current.fn = fn;}, [fn]);return useCallback(function () {clearTimeout(current.timer);current.timer = setTimeout(() => {current.fn.apply(this, arguments);}, delay);},dep);
}
function useThrottle(fn, delay, dep = []) {const { current } = useRef({ fn, timer: null });useEffect(function () {current.fn = fn;}, [fn]);return useCallback(function () {if (!current.timer) {current.timer = setTimeout(() => {delete current.timer;}, delay);current.fn.apply(this, arguments);}}, dep);
}
https://juejin.cn/post/6844904135091814407
https://juejin.cn/post/6854573217349107725
Hook 组件 VS 类组件
https://juejin.cn/post/6858444362616389639#heading-2
1、命令式 VS 声明式
在 Class 组件中,通常的写法是在生命周期检查 props 和 state,如果改变或者是符合某个条件就触发xxx副作用。在函数式组件中使用 Hooks 的写法是组件有xxx副作用,这个副作用依赖的数据是 props 和 state。一个是被动触发,一个是主动声明。
2、去生命周期化
在 hooks 组件中使用 useEffect 取代了类组件生命周期的调用,使得“生命周期”变成了一个“底层概念”,因此开发者能够将精力聚焦在更高的抽象层次上。
3、去 this 化
hooks组件不再需要手动绑定this
4、编译和压缩
实现相同的功能,类组件的编译完后的文件大小会比hooks组件的大,这时因为类组件需要继承React.Component,需要生成一些辅助函数,另外对于构建工具,class不能很好的压缩,并且会出现热重载出现不稳定的情况,综上,在代码压缩方面,函数组件会比类组件稍具优势。
react事件绑定原理(合成事件与原生事件的区别)
React合成事件和原生事件区别
React合成事件一套机制:React并不是将click事件直接绑定在dom上面,而是采用事件冒泡的形式冒泡到document上面,然后React将事件封装给正式的函数处理运行和处理。
React合成事件理解
如果DOM上绑定了过多的事件处理函数,整个页面响应以及内存占用可能都会受到影响。React为了避免这类DOM事件滥用,同时屏蔽底层不同浏览器之间的事件系统差异,实现了一个中间层——SyntheticEvent。
1、当用户在为onClick添加函数时,React并没有将Click事件绑定在DOM上面。
2、而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装交给中间层SyntheticEvent(负责所有事件合成)
3、所以当事件触发的时候,使用统一的分发函数 dispatchEvent 去指定事件函数执行。
https://www.jianshu.com/p/8d8f9aa4b033
react的按需加载
1、最简单直接的方式就是引入动态 import 实现代码分隔。
2、React.lazy
React.lazy中传入一个回调函数,该回调函数中使用imprt 引入一个组件(如b组件)
b组件在a组件return,这样之后在a组件渲染的时候才会加载b组件
3、假如在 React.lazy 时,import 失败或者异常时,我们需要给于提示,或者一个默认的组件,我们就需要使用 Suspense。Suspense有个fallback属性,可以是dom节点或组件
4、基于路由的代码分割
基于 路由 的代码分隔,也是我们通常所说的按需加载。是我们推荐的方式。
https://segmentfault.com/a/1190000017332094
react组件通信
父组件向子组件通信:
props
ref(首先子组件有个myFunc方法,父组件给子组件传递一个ref属性,并且采用call-ref的形式,这个callback函数接受react组件实例/原生dom元素作为它的参数。当父组件挂载时,react会执行这个ref回调函数,并将子组件的实例作为参数传给回调函数,然后我们把子组件的实例赋值给某个方法,如this.child,最后我们这个在父组件当中使用this.child来调用子组件的方法)
子组件向父组件通信
回调函数(子组件通过调用父组件传来的回调函数,从而将数据传给父组件)
非嵌套组件通信
将两者包裹在同一个父组件(状态提升)
跨组件通信
Context
应用场景:locale/theme/缓存数据
1、React.createContext()创建一个Context对象,假如某个组件订阅了这个对象,当react去渲染这个组件时,会从离这个组件最近的的一个Provider组件中读取当前的context值
2、Context.Provider每一个Context对象都有Provider属性,这个属性是一个react组件。在Provider组件以内的所有组件都可以通过它订阅context值的变动。具体来说,Provider组件有一个叫value的prop传递给所有内部组件,每当value的值发生变化时,Provider内部的组件都会根据新value值重新渲染
3、那内部的组件该怎么使用这个context对象里的东西呢
a. 假如内部组件是用class声明的有状态组件:我们可以把Context对象赋值给这个类的属性contextType,
b. 假如内部组件是用function创建的无状态组件:我们可以使用Context.Consumer,这也是Context对象直接提供给我们的组件,这个组件接受一个函数作为自己的child,这个函数的入参就是context的value,并返回一个react组件。
使用Context会使组件通信变的复杂,推荐使用redux
https://segmentfault.com/a/1190000023585646
https://blog.csdn.net/Charissa2017/article/details/105746685
react高阶组件
1.名词解释/作用
使函数复用,可以通过给组件传递方法来复用高阶组件中的函数方法。
高阶组件特点
高阶组件是一个函数
高阶组件接收一个组件作为参数进行使用,且需要在render函数中return返回这个组件
高阶组件的目的是为了: 复用组件,将多个组件都要使用的类似逻辑放在同一个地方进行处理,类似于在Vue中封装cookie以供重复使用
2.常用高阶组件4个
React.memo()
connect()
provider()
withRouter() withRouter(组件)包裹组件即可 目的:使用其可让组件内所有元素拥有 this.props.history/location/match等属性;
3.有自己封装过吗
拖拽封装,给组件里面的标签添加方法就可以实现拖拽,并返回标签的x与y坐标。
深对比,深复制封装、正则封装、页面路由跳转、路由数据接收解析。
react高阶组件的两种方式:属性代理、反向继承
1、基于属性代理:操作组件的props
2、基于反向继承:拦截生命周期、state、渲染过程
复用组件逻辑的一种高级技巧。
- 高阶组件是一个函数
- 高阶组件接收一个组件作为参数进行使用,返回值是一个类组件
- 高阶组件的目的是为了: 复用组件,将多个组件都要使用的类似逻辑放在同一个地方进行处理
- 高阶组件返回的一个类组件,而自定义Hook可以返回任何东西
- 高阶组件必须传递一个组件作为参数,而自定义Hook不需要
redux中间件
https://juejin.cn/post/6931308713345024008
https://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html
中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
redux-logger可以生成日志中间件,redux-logger一定要放在左右中间件最后,否则输出结果会不正确。
redux-thunk和redux-saga都是用来处理副作用,如ajax请求,
redux-thunk和redux-saga处理异步任务的时机不一样。
对于redux-thunk它是等异步任务执行完之后,再去调用dispath,然后store再去调用reducer。
redux-saga它是等执行完action和reducer之后,判断reducer中有没有这个action,如果没有rudex-saga的监听函数takeEvery就会监听到,等异步任务有结果就执行put方法,相当于dispatch
redux-promise可以diapatch一个Promise对象
react 路由传参及其区别
https://segmentfault.com/a/1190000023554534
1.params
优势 : 刷新地址栏,参数依然存在
缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。
2.query
优势:传参优雅,传递参数可传对象;
缺点:刷新地址栏,参数丢失
3.state
优缺点同query
4.search
优缺点同params
react Hooks中获取路由参数的方式
https://reactrouter.com/web/api/Hooks
1、useHistory
import { useHistory } from "react-router-dom";function HomeButton() {let history = useHistory();function handleClick() {history.push("/home");}return (<button type="button" onClick={handleClick}>Go home</button>);
}
2、useLocation
import React from "react";
import ReactDOM from "react-dom";
import {BrowserRouter as Router,Switch,useLocation
} from "react-router-dom";function usePageViews() {let location = useLocation();React.useEffect(() => {ga.send(["pageview", location.pathname]);}, [location]);
}function App() {usePageViews();return <Switch>...</Switch>;
}ReactDOM.render(<Router><App /></Router>,node
);
react阻止冒泡事件
A、阻止合成事件间的冒泡,用e.stopPropagation();
B、阻止合成事件与最外层document上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation();
C、阻止合成事件与除最外层document上的原生事件上的冒泡,通过判断e.target来避免
document.body.addEventListener('click',e=>{
// 通过e.target判断阻止冒泡if(e.target&&e.target.matches('a')){
return;}
console.log('body');})}
}
https://zhuanlan.zhihu.com/p/26742034
什么是redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
Redux设计思想
1)Web 应用是一个状态机,视图与状态是一一对应的。
2)所有的状态,保存在一个对象里面。
Redux三大原则
单一数据源(整个应用的state被存储在一棵object tree中,并且这个object tree只存在于唯一一个store中)
state是只读的(改变state的方法是触发action,action是一个用于描述已发生事件的普通对象)
使用纯函数来执行修改(为了描述如何改变state tree,需要我们编写reducers)
基本概念和API
Store
Store是存储数据的地方,整个应用只有一个store
State
定义具体的数据
Action
是一个对象,其中type属性是必须的
Reducer
为一个纯函数,接受一个状态,返回一个新状态
纯函数是指同样的输入必定得到同样的输出
遵循以下约束
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
redux工作流程
完整的过程用户dispatch一个action,Store自动调用reducer,并且传入两个参数:当前 State 和收到的 Action。Reducer会返回一个新的state,state一旦有变化,store就会调用监听函数,然后重新渲染视图
参考:https://www.cntofu.com/book/4/docs/introduction/ThreePrinciples.md
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
react-redux的用法
https://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html
React-Redux 将所有组件分成两大类:UI 组件和容器组件
UI 组件有以下几个特征
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
容器组件
负责管理数据和业务逻辑,不负责 UI 的呈现
带有内部状态
使用 Redux 的 API
connect
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
mapStateToProps()
mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。
作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。
mapDispatchToProps()
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数。
如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。
组件
connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。
一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。
React-Redux 提供Provider组件,可以让容器组件拿到state。
Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。
react中的路由模式
我们一直在使用的路由方式是BrowserRouter,也就是浏览器的路由方式,其实React还有几种路由方式:
1、BrowserRouter:浏览器的路由方式,也就是在开发中最常使用的路由方式
2、HashRouter:在路径前加入#号成为一个哈希值,Hash模式的好处是,再也不会因为我们刷新而找不到我们的对应路径
3、MemoryRouter:不存储history,所有路由过程保存在内存里,不能进行前进后退,因为地址栏没有发生任何变化
4、NativeRouter:经常配合ReactNative使用,多用于移动端
5、StaticRouter:设置静态路由,需要和后台服务器配合设置,比如设置服务端渲染时使用
受控组件与非受控组件
受控组件
HTML中的表单元素是可输入的,也就是有自己的可变状态
而React中可变状态通常保存在state中,并且只能通过setState()方法来修改
React将state与表单元素值value绑定在一起,用state的值来控制表单元素的值
简单来说,值受到react控制的表单元素
非受控组件
调用 React.createRef() 方法创建ref对象
将创建好的 ref 对象添加到文本框中
通过ref对象获取到文本框的值
简单来说,表单组件没有value prop就可以称为非受控组件
受控组件:渲染表单的React组件还控制着用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件。
有时候子组件的渲染受父组件的控制也叫受控组件
非受控组件:
表单自己管理自己的状态,这就叫做非受控组件
半受控
一个表单组中,一部分表单元素的取值交给了组件的state管理,一部分自己管理
component与pureComponent的区别
PureComponent自带通过props和state的浅对比来实现shouldComponentUpdate(),而Component没有。这样不需要开发者实现shouldComponentUpdate,就可以使用PureComponnent来进行简单判断以提升性能。
-
Component适宜用在数据层级繁杂的地方;
-
PureComponent适宜用在小组件中(如模态框, 错误信息展示 etc)
使用pureComponent的条件
- props 和 state 必须是 immutable object
- props 和 state 最好不要有层级嵌套; 否则深层的数据改变不会更新组件(可以用 forceUpdate 强制更新组件)
- PureComponent 的子组件也应该是PureComponent
react-router与react-router-dom的区别
react-router:提供了router的核心api。如Router、Route、Switch等,但没有提供有关dom操作进行路由跳转的api;
react-router-dom:提供了BrowserRouter、Route、Link等api,可以通过dom操作触发事件控制路由。
react中ref的方式
作用
获取react实例或者dom节点
处理非受控组件
处理立即执行的动画
方式一:createRef
将创建的ref放到子组件上,就可获得子组件实例
https://blog.csdn.net/tbapman/article/details/103616949
方式二:回调函数
父组件给子组件传递一个属性,该属性值为一个回调函数
子组件在componentDidMount时调用这个方法,将子组件的this传过去
此时可以在父组件获取子组件的实例
react中调用setState之后发生了什么
1、会将传入的对象与当前已有的对象进行浅合并
2、通过diff算法知道计算那些地方发生了改变,按需更新
react错误边界
- 在React中通常有一个组件树。其中任何一个发生错误都会破坏整个组件树,使用错误边界可以优雅地处理这些错误
- 错误边界的作用:如果发生错误回退显示UI,纪录错误
具体来说 定义一个错误类组件ErrorBoundary,内部声明两个状态error,errorInfo,使用componentDidCatch捕获,该方法有两个入参,error以及errorInfo,通过setState的方式对这两个值重新赋值。render方法里面判断errorInfo是否有值,有值显示错误提示信息,没值return this.props.children;将子组件包裹在ErrorBoundary中,如果子组件发生错误,其实就能捕获到错误。值得一提的是如果ErrorBoundary包裹了两个同名组件,其中任何一个发生错误,这两个同名组件的ui都会显示错误信息。而如果是分别包裹在ErrorBoundary中,则两个组件互不影响。
https://zh-hans.reactjs.org/docs/error-boundaries.html
https://codepen.io/gaearon/pen/wqvxGa?editors=0010
react如何性能优化
1、适当使用shouldComponentUpdate,避免不必要的渲染
2、不可变性是提高性能的关键。因此能用const就用const
3、使用列表或表格时始终使用keys,这会让React的更新速度更快
4、事件处理函数尽量在constructor中绑定,避免每次渲染重新绑定this
5、减少对真实dom的操作
react路由传参
1.params
优势 : 刷新地址栏,参数依然存在
缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。
2.query
优势:传参优雅,传递参数可传对象;
缺点:刷新地址栏,参数丢失
3.state
优缺点同query
4.search
优缺点同params
https://segmentfault.com/a/1190000023554534
react类组件为什么一定要绑定this
https://juejin.cn/post/6844903605984559118
因为类声明和原型方法是以严格模式运行,如果不指定this,就会指向undefined,而箭头函数可以避免这个问题,因为箭头函数的this已经按照词法作用域绑定了,当箭头函数包裹在类或构造函数中,它的上下文就是组件实例
react如何处理组件错误
错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
具体来说 定义一个错误类组件ErrorBoundary,内部声明两个状态error,errorInfo,使用componentDidCatch捕获,该方法有两个入参,error以及errorInfo,通过setState的方式对这两个值重新赋值。render方法里面判断errorInfo是否有值,有值显示错误提示信息,没值return this.props.children;将子组件包裹在ErrorBoundary中,如果子组件发生错误,其实就能捕获到错误。值得一提的是如果ErrorBoundary包裹了两个同名组件,其中任何一个发生错误,这两个同名组件的ui都会显示错误信息。而如果是分别包裹在ErrorBoundary中,则两个组件互不影响。
https://zh-hans.reactjs.org/docs/error-boundaries.html
https://codepen.io/gaearon/pen/wqvxGa?editors=0010
函数组件如何强制刷新
this.forceUpdate();
https://blog.csdn.net/u013970232/article/details/109199927
为什么React一定要先引入
JSX只是为React.createElement方法提供的语法糖。
所有的JSX代码最后都会通过React.createElement转换,Babel帮助我们完成了这个转换的过程。
所以使用了JSX的代码都必须引入React。
https://juejin.cn/post/6844904077755858951
react为什么要废除一些生命周期
3个将要废除的will生命周期(conmponetWillMount/willReceiveProps/willUpdate),这3个生命周期都有一个共同点:处于render阶段,它是允许暂停、终止。
在Fiber机制下,当一个任务执行一半被打断后,再次拿到主动权时,它是:重复执行一遍整个任务,也就是它被重启的方式不是接着上次执行继续。这就导致了处在render阶段的生命周期的任务有可能被重复执行
换个说法,假设一个付款请求,用户点击付款,需要支付10元,可能因为componentWill被打断重启多次,导致多次调用付款请求,最终需要支付40元
React16引入Fiber架构带来异步渲染机制。为了确保Fiber机制下数据和视图的安全性,且生命周期方法的行为更加的纯粹、可控和可预测。React16针对中长期被滥用的componentWill生命周期推行了具有强制性的最佳实践
https://tech.tuya.com/sheng-ming-zhou-qi/
https://react.docschina.org/blog/2018/03/27/update-on-async-rendering.html
React Fiber
React Fiber 是 React 引入的一种新的渲染引擎,通过增量渲染、优先级调度和错误处理等机制,提供了更好的用户交互体验和可控性,并支持渐进式更新。
React组件封装
request
downExcel
proPage
css
伪类与伪元素区别
https://segmentfault.com/a/1190000000484493
CSS 伪类用于向某些选择器添加特殊的效果。
CSS 伪元素用于将特殊的效果添加到某些选择器。
伪类种类
:link/:visited/:hover/:active/:focus/:first-child/:lang
伪元素种类
:before/:after/:first-letter/:first-line
伪类的效果可以通过添加一个实际的类来达到,而伪元素的效果则需要通过添加一个实际的元素才能达到,这也是为什么他们一个称为伪类,一个称为伪元素的原因。
css3 为了区分两者,已经明确规定了伪类用一个冒号来表示,而伪元素则用两个冒号来表示。
但因为兼容性的问题,所以现在大部分还是统一的单冒号,但是抛开兼容性的问题,我们在书写时应该尽可能养成好习惯,区分两者。
浏览器内核
- IE:Trident
- Firefox:Gecko
- Opera:Presto
- Chrome:Webkit
盒模型
盒模型非为标准盒模型和怪异盒模型,
标准盒模型为box-sizing:content-box;
标准盒模型的宽度和高度指的是内容区域的宽度盒高度
怪异盒模型为box-size:border-box;
怪异盒模型的高度和宽度指的是内容区域+border+padding
flex布局
https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
flex为弹性布局;
采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)
项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。
容器的属性
有6个属性
flex-direction属性决定主轴的方向(即项目的排列方向)
flex-direction: row | row-reverse | column | column-reverse;
flex-wrap项目的换行方式
flex-wrap: nowrap | wrap | wrap-reverse;
flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
justify-content属性定义了项目在主轴上的对齐方式。
justify-content: flex-start | flex-end | center | space-between | space-around;
align-items属性定义项目在交叉轴上如何对齐。
align-items: flex-start | flex-end | center | baseline | stretch;
align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
项目的属性
order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
flex-basis: | auto; /* default auto */(如300px)
flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。
align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
align-self: auto | flex-start | flex-end | center | baseline | stretch;
定位
https://www.ruanyifeng.com/blog/2019/11/css-position.html
- static默认值
- relative 相对于自身发生偏移 未脱离文档流
- absolute 有定位父级相对于定位父级发生偏移 ,没有定位父级相对于body发生偏移,元素脱离文档流
- fixed 固定定位 元素相对于视口来定位,这意味着即使页面滚动,它还是会停留在相同的位置
- sticky 粘性定位 粘性定位会在fixed和relative之间来回切换
两/三栏布局
水平/垂直居中
介绍2种方式
- 方法一:定位
.box{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto;}
- 方法二:定位
.box{position: absolute;top: 50%;left: 50%;margin-left: -100px;margin-top: -100px;}
- flex布局
.box{display:flex;justify-content:center;align-items:center;}
BFC
定义
BFC 全称为块级格式化上下文 (Block Formatting Context) 。BFC是 W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位以及与其他元素的关系和相互作用,当涉及到可视化布局的时候,Block Formatting Context提供了一个环境,HTML元素在这个环境中按照一定规则进行布局。一个环境中的元素不会影响到其它环境中的布局。比如浮动元素会形成BFC,浮动元素内部子元素的主要受该浮动元素影响,两个浮动元素之间是互不影响的。这里有点类似一个BFC就是一个独立的行政单位的意思。可以说BFC就是一个作用范围,把它理解成是一个独立的容器,并且这个容器里box的布局与这个容器外的box毫不相干。
触发BFC的条件
- float的值不能为none
- overflow的值不能为visible
- display的值为table-cell, table-caption, inline-block中的任何一个
- position的值不为relative和static
BFC的约束规则
- 内部的Box会在垂直方向上一个接一个的放置
- 属于同一个BFC的两个相邻Box的margin会发生重叠
- 每个元素的margin box的左边, 与包含块border box的左边相接触
- BFC的区域不会与float的元素区域重叠
- 计算BFC的高度时,浮动子元素也参与计算
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面元素,反之亦然
BFC的作用
- 不和浮动元素重叠(兄弟元素一个浮动,一个不浮动,会发生重叠,不浮动元素设置BFC之后即可不重叠)
- 清除元素内部浮动(即解决高度塌陷,子元素浮动,父元素不浮动)
- 防止垂直 margin 重叠(两个兄弟元素都设置margin:100,按理这两个兄弟元素的垂直距离是200,但实际上只有100,因为margin发生了重叠,任意一个兄弟元素设置成BFC即可)
参考:https://juejin.cn/post/6844903544726749198
清除浮动
-
一般有3种:.clearfix,clear:both,overflow:hidden
.clearfix{*zoom:1}//兼容ie .clearfix.after{content:'',display:block,clear:both }clear:both(缺点:需要额外创建标签)overflow:hidden(缺点:溢出的内容会隐藏)
css3的一些新特性
- 选择器:CSS3引入了一些新的选择器,如属性选择器、伪类选择器、伪元素选择器等
- 边框:CSS3中新增了圆角边框、阴影边框、渐变边框等一些非常实用的边框效果。
- 背景:CSS3背景样式增强了图像的控制,还增加了线性渐变、径向渐变、重复渐变等背景样式
- 动画:CSS3中增加了动画特效,可以让元素动起来,如旋转、移动、缩放等。
- 变形:CSS3中引入了变形功能,可以使用“transform”属性来实现旋转、翻转、平移、缩放等效果。
- 媒体查询:CSS3中的媒体查询可以根据设备的屏幕尺寸、分辨率、方向等条件来应用不同的样式,以适应不同的设备和屏幕。
浏览器
前端安全
参考:https://zhuanlan.zhihu.com/p/83865185
- CSRF(跨站请求伪造)
- XSS(跨站脚本攻击)
- CDN劫持
CSRF(跨站请求伪造)
你可以这么理解 CSRF 攻击:攻击者盗用了你的身份,以你的名义进行恶意请求。
防御措施:
- 修改数据严格使用post而不是get请求
- http协议中使用Referer属性来确定请求来源进行过滤(禁止外域)
- 请求地址添加token,使黑客无法伪造用户请求
- 显示验证方式:添加验证码、密码等
XSS(跨站脚本攻击)
攻击者在目标网站植入恶意脚本(js / html),用户在浏览器上运行时可以获取用户敏感信息(cookie / session)、修改web页面以欺骗用户、与其他漏洞相结合形成蠕虫等。
防御措施:
- 现代大部分浏览器都自带xss筛选器,vue/react等成熟框架也对xss进行一些防护
- 对用户输入内容和服务端返回内容进行过滤和转译
- 重要内容加密传输
- 对于url携带参数谨慎使用
CDN劫持
出于性能考虑,前端应用通常会把一些静态资源存放到CDN(Content Delivery Networks)上面,例如 js 脚本和 style 文件。这么做可以显著提高前端应用的访问速度,但与此同时却也隐含了一个新的安全风险。如果攻击者劫持了CDN,或者对CDN中的资源进行了污染,攻击者可以肆意篡改我们的前端页面,对用户实施攻击。
防御措施:
- 全链路https
- 对内容的md5验证
前端性能优化
1、减少 HTTP 请求(一次完整http的请求耗时较长,故需要减少)
2、使用 HTTP2(解析速度更快,采用多路复用,首部压缩等)
3、使用服务端渲染
4、静态资源使用CDN
5、css放在文件头部,js文件放在文件底部
6、使用iconfont代替图片图标
7、善用缓存、避免重复加载相同的资源
(具体来说可以通过expires设置一个绝对时间,或着通过max-age来设置一个相对时间
不过这样会产生一个问题,当文件更新了怎么办?怎么通知浏览器重新请求文件?
前端代码使用 webpack 打包,根据文件内容生成对应的文件名,每次重新打包时只有内容发生了变化,文件名才会发生变化。
8、压缩文件
在 webpack 可以使用如下插件进行压缩:
JavaScript:UglifyPlugin
CSS :MiniCssExtractPlugin
HTML:HtmlWebpackPlugin
https://segmentfault.com/a/1190000022205291
1、减少http的请求数,例如一个获取当前登陆人的接口,我们可以在父组件只发一次,避免在每个子组件都发一边
2、使用字体图标代替图片图标,字体图标是矢量图,可以设置大小颜色,且不会失真
3、善用缓存,避免资源重复加载。webpack打包的时候给output的filename添加一个contenthash属性,这个属性会根据文件内容创建出唯一的hash值,当文件内容发生变化,[contenthash] 也会发生变化。
output: {filename: '[name].[contenthash].js',chunkFilename: '[name].[contenthash].js',path: path.resolve(__dirname, '../dist'),
},
4、提却第三方库,一般三方库都比较稳定,我们可以把他们单独提取出来进行缓存。可以使用webpack4的splitChunk的cacheGroups属性
optimization: {runtimeChunk: {name: 'manifest' // 将 webpack 的 runtime 代码拆分为一个单独的 chunk。},splitChunks: {cacheGroups: {vendor: {name: 'chunk-vendors',test: /[\\/]node_modules[\\/]/,priority: -10,chunks: 'initial'},common: {name: 'chunk-common',minChunks: 2,priority: -20,chunks: 'initial',reuseExistingChunk: true}},}
},
5、压缩文件
压缩es6/css/image
uglifyjs-webpack-plugin:通过 UglifyES 压缩 ES6 代码,
css-loader:使用css-loader?minimize
imagemin-webpack-plugin:压缩图片文件。
extract-text-webpack-plugin:提取 JavaScript 中的 CSS 代码到单独的文件中
6、使用tree shaking去除死代码,去除不必要的console
7、避免频繁操作dom,频繁操作样式,减少重绘重排
项目相关
自我介绍
您好,我叫xx。商家公司是在禾观科技有限公司,主要负责中后台的研发,负责的系统有spm,wms,erp,cs,gds。由于我本身对技术也比较感兴趣,所以业务时间会学习一些新技术,比如微信小程序,flutter,以及今年比较火的chatgpt。我这边的主要情况就是这样。
项目中遇到什么困难,怎么解决的?
1、sse本地开发与部署时遇到的一些坑
前端使用sse的时候需要new 一个EventSoure对象,第一个参数是url,然后我的一个参数就写成’/ai/chat/answer’,然后本地开发的时候发现结果的返回了,但是结果不是分段返回的,而是一下子都返回了,后来经过排查发现可以url不能通过proxy代理,而是需要写完整的url,然后将url改成’http://localhost:8888/ai/chat/answer’就实现了想要的效果,然后部署的时候发现写出localhost会造成跨域,后来通过查阅资料,接口还是要写成’/ai/chat/answer’,同时location里面需要加一个proxy_buffering off保持长连接
location /ai/ {proxy_pass http://127.0.0.1:8888/ai/;#(关键参数)proxy_buffering off;}
2、微信小程序的支付功能
注册微信支付商户号,微信预支付的时候生成签名,生成Authorization ,前后端订单金额必读,以及到小程序上线,ssl证书的申请以及安装
3、商品的上新的时候需要填写产品的尺码,设计到复选框,下拉框与表格的联动,
比如复选框勾选了s,m,l,xl,xxl,如果基码选择了l,那么当商家填写了基码跟2个跳码的数值后,前端需要自动计算出这一列的数值,同时还要考虑精度失真的问题。
https://cloud.tencent.com/developer/article/1645202
https://zhuanlan.zhihu.com/p/147619338
https://segmentfault.com/a/1190000016734597
https://juejin.cn/post/7008100091739242526
华泰项目:
1、渲染大量数据
很多情况下我们会采用分页加载的方式,来避免一次性加载大量的数据,因为这会造成页面的性能问题。但是用户在分页加载浏览了大量数据之后,列表项也会逐渐增多,此时页面可能会存在卡顿的情况。又或是我们需要将数据一次性的呈现到用户面前,而不是采用分页加载的方式。在查找了相关资料后了解到虚拟列表技术。虚拟列表是按需显示思路的一种实现,即虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术。
主要的设计思路是:
利用可视区之外的Dom元素的复用
给容器加上Padding撑开尺寸
令可视区之外的区域仅仅是撑开的padding空白,而只需要渲染可视区域和上下几个内容区(为了避免数据过多而产生卡顿,需要提前和滞后渲染相应数据)。
接着,监听滚动事件,需要首先考虑一下临界状态,以及临界状态后的大致行为。
在下拉至设定阈值之后,将第一个元素追加到容器内,然后容器加上poddingTop,阈值变化。
在上拉至设定阈值之后,把最后一个元素插入到第一个元素之前,减少容器paddingTop,阈值变化。
虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除。
https://juejin.cn/post/6844903982742110216
2、先请求的数据后渲染
https://myunlessor.github.io/blog/2015/12/19/promise-solve-continuous-request-and-respone-not-in-order-problem/
https://www.jianshu.com/p/5beed7e3acac
页面上有两个按钮:本月数据/本周数据
先点击本月数据再点击本周数据,由于本月数据的数据量比较大,导致先加载到本周数据,等本月数据加载到数据又回重新渲染页面。这导致一个问题我们最后明明点击的是本周数据,页面显示的却是本月数据
简单粗暴方法:
前一个请求没有响应时,禁止发起下一个请求,用户体验不是很好pass
简单直观的方式:
另外一种思路:在每次响应后且在渲染之前,判断当前响应是不是对应最新一次请求的。是,则渲染;不是,则不渲染。
这种思路最容易想到的实现就是使用全局变量标记最新请求,局部变量标记当前请求,然后在响应回调中判断局部变量的值是否和全局变量的值一样。如果不一样,忽略响应结果;如果一样,我们可以断言这是最新请求的响应,直接渲染。
3、promise式
第一步定义promisify方法用于将request请求promise化
具体来说promisify方法接受一个fn参数,如果fn.then属性那么直接返回fn,否则返回一个function,该function具体来说先将arguments转成数组,最后返回一个Promise, new Promise(resolve=>fn(...args.concat(resolve)))
// 第二步 对promisify方法在进行一层装饰,装饰后的方法仍是promise化的
具体来说定义一个registry来存储Promise对象,从第二个位置开始存储,已发起的请求push进registry,最新的请求始终位于列表末尾。等到所有的请求都push进registry开始map对比,如果registry.indexOf(promise)===registry.length-1;说明registry最后一个请求的promise状态已经从pengding变成resolved,这时候去真正发起请求
不用空数组去存储promise是因为,如果拿空数组去indexOf promise会返回-1,而resigtry的length减1也等于-1,这会导致所有的请求都发一遍
// 第一步定义promisify方法用于将request请求promise化
let promisify=function(fn){return fn.then?fn:function(){let args=Array.from(arguments)return new Promise(resolve=>void fn(...args.concat(resolve)))}
}// 第二步 对promisify方法在进行一层装饰,装饰后的方法仍是promise化的let mutePrior=function(promisifiedFunc){// 用于存储`promise`对象,从第2个位置开始存储let registry=[0];return function(){let promise=promisifiedFunc(...arguments);// 已发起的请求(promise)入队,可以看到新发起的请求(promise)始终位列数组末尾registry.push(promise)return new Promise(function(...actions){let proxyCallbacks=actions.map(action=>function(result){console.log(registry,'registry')console.log(registry.indexOf(promise),'promise')if(registry.indexOf(promise)===registry.length-1){action(result)registry.length=1}})promise.then(...proxyCallbacks)})}
}
// 第三步测试用例
var result;// A function to simulate async request delay
var request = function (msec, mockedData, callback) {setTimeout(function () {callback(mockedData);}, msec);
};// decorate `request` with both `promisify` and `mutePrior`
request = mutePrior(promisify(request));var sample = [{ msec: 200, data: 'stale' },{ msec: 100, data: 'fresh' }
];// 模拟`先发起请求后响应`的情景
sample.forEach(function (item) {request(item.msec, item.data).then(function (resp) {result = resp;});
});// wait 1s, we inspect `result`
setTimeout(function () {// now our `result` is `fresh`, just as expectedconsole.log(result); // => `fresh`
}, 1000);
paai项目:
1、统一url
一般vue/react项目都有config文件,可统一配置url请求前缀。
paai项目之前请求的前缀都是在项目时写死的,这导致一个问题,每次讲代码部署到不同环境都需要全局替换前缀然后再推一次代码,这会导致很麻烦。后来老大提出,在项目最外层建立properties文件,如config_dev.properties,pre_config.properties。问题是从这些文件读取的信息怎么注入到项目里面。后来通过摸索在extension.py中加入文件读取的逻辑,信息是读取到了,怎么存,怎么取呢。一开始是想把这些读取到url存到cookie或者localStorage里面,但是有个问题,.py怎么存cookie,而且这些url按理说应该在程序里面运行,而不是存到外部。最后通过查看官网找到了PageConfig这个类。于是在extension.py文件将读取到的url注入到PageConfig这个类,如config.paaiUrl=paai_url,
然后使用PageConfig.getOption(‘paaiUrl’)
2、aeye
aeye主要用于数据可视化,将用户与电影的关系以关系图的形式显示。
前端任务使用cookecutter创建一个aeye包,该包包含两部分python和npm,python定义调用方法,npm定义如何处理及渲染数据。主要是在python包中定义一个Graph类,然后在该类中定义了一个set_data方法。
python代码
class Graph():
def set_data(self,js_data):
npm部分需要对数据进行处理,数据格式为arr=[userId,movieId,desc]];
echarts的关系图的主要有3个字段,links,nodes,categories,我需要将数组嵌套数组的数组的数据处理成eacharts规定的数据
第一步 将数组处理成标准字段
let links=arr.map((item)=>{
return {soruce:`userId${item[0]}`,target:`movieId${item[1]}`value:item[2]
}
})
第二步 统计所有节点(包含重复节点)
let totalArr=[];
links.forEach(({source,targe})=>{
totalArr.push(source,target)
})
第三步 数组去重
let uniqueArr=[];
uniqueArr=Array.from(new Set(totalArr))
第四步 每个节点出现的次数
let nodeNumber={}
for(let i=0;i<totalArr.length;i++){
let item=totalArr[i]
nodeNumber[item]=nodeNumber[item]+1||1;
}
第五步 每个tag出现的次数
let totalTag=[];
let tagNumber={};
links.forEach(item=>totalTag.push(item.value))
for(let i=0;i<totalTag.length;i++){
let item=totalTag[i]
tagNumber[item]=tagNumber[item]+1||1;
}
第六步 生成唯一节点
let nodes=uniqueArr.map(i=>{
return {
id:i,
name:i,
symbolSize:nodeNumber[i],
value:nodeNumber[i]
}
})
第七步 生成的nodes中包含的id必须连续,否则会造成连线错乱
let mapObj={}
nodes=nodes.map((item,index)=>{
mapObj[item.id]=index;
return Object.assign({},item,{id:index})
})
links=links.map(item=>{
const source=item.source;
const target=item.target;
const value=item.value;
return Object.assign({},item,{
source:mapObj[source],
target:mapObj[target]
value:`${value}KaTeX parse error: Expected 'EOF', got '}' at position 22: …mber[value]})` }̲) }) 第八步 将节点的次数…{prev}-${next})`
})
}
// 划分节点属于那个范围
for(let i=0;i<nodes.length;i++){
for(let j=0;j<categories.length;j++){
const min=categories[j].interval[0];
const max=categories[j].interval[1];
if(nodes[i].value>=min&&nodes[i].value<=max){
nodes[i].category=categories[j].index
}
}
}
webpack
谈谈你对webpack的理解
https://blog.csdn.net/yiyueqinghui/article/details/108680942
WebPack 是一个模块打包工具,可以使用WebPack管理模块,并分析模块间的依赖关系,最终编绎输出模块为HTML、JavaScript、CSS以及各种静态文件(图片、字体等),让开发过程更加高效。
webpack工作流程
https://zhuanlan.zhihu.com/p/363928061
初始化阶段
- 初始化参数
- 创建编译器对象
- 初始化编译环境
- 开始编译
- 确定入口
构建阶段
- 编译模块
- 完成模块编译
生成阶段
- 输出资源
- 写入文件系统
Rollup 与 webpack的区别
特性:
webpack 拆分代码, 按需加载;
Rollup 所有资源放在同一个地方,一次性加载,利用 tree-shake 特性来剔除项目中未使用的代码,减少冗余,但是webpack2已经逐渐支持tree-shake
资源:
webpack 相对来说拥有更大的社区支持,资源更加齐全
结论:
对于应用使用 webpack,对于类库使用 Rollup
介绍一下loader,常用的loader有那些,有什么作用
loader用于对模块的源代码进行转换。loader可以是我们在import或加载模块时预处理文件,因此loader类似于其他构建工具中的task,并提供了处理前端构建步骤的强大方法。loader可以将文件从不用的语言如ts转换成js,将内联图像转换成data url
file-loader:把文件输出到一个文件夹中
url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
babel-loader:把 ES6 转换成 ES5
ts-loader:把 TypeScript 转换成 JavaScript,
介绍一下plugin,常用的plugin有那些,有什么作用
plugin插件目的在于解决 loader 无法实现的其他事
用于修改行为
define-plugin:定义环境变量
ignore-plugin:用于忽略部分文件
commons-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过 UglifyES 压缩 ES6 代码,
介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面
1.当修改了一个或多个文件;
2.文件系统接收更改并通知webpack;
3.webpack重新编译构建一个或多个模块,并通知HMR服务器进行更新;
4.HMR Server 使用webSocket通知HMR runtime 需要更新,HMR运行时通过HTTP请求更新jsonp;
5.HMR运行时替换更新中的模块,如果确定这些模块无法更新,则触发整个页面刷新。
1、webpack是什么
WebPack 是一个模块打包工具,可以使用WebPack管理模块,并分析模块间的依赖关系,最终编绎输出模块为HTML、JavaScript、CSS以及各种静态文件(图片、字体等),让开发过程更加高效。
对于不同类型的资源,webpack有对应的模块加载器loader
加载文件
file-loader:把文件输出到一个文件夹中
url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
转换脚本语言
babel-loader:把 ES6 转换成 ES5
ts-loader:把 TypeScript 转换成 JavaScript,
转换样式文件
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性。
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
less-loader:把 Less 代码转换成 CSS 代码。
sass-loader:把 SCSS/SASS 代码转换成 CSS,在3-4使用 SCSS 语言中有介绍。
2、webpack的基本功能
1、代码转换:TypeScript 编译成 JavaScript、ES6转ES5、SCSS 编译成 CSS 等等(各种loader)
2、代码语法检测:自动检测代码是否符合语法 (eslint-loader)
3、代码分割:打包代码时,可以将代码切割成不同的chunk(块),实现按需加载,降低了初始化时间,提升了首屏渲染效率
4、监测代码更新,自动编译,刷新页面:监听本地源代码的变化,自动构建,刷新浏览器(自动刷新)
5、自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统(没用过)。
6、文件压缩:压缩 JavaScript、CSS、HTML 代码,缩小文件体积(比如说,打包后的js、css、html文件会去掉代码之间的空隔,紧凑显示)
7、模块合并:由于模块化的开发,一个页面可能会由多个模块组成,所以编译时需要把各个模块合并成一个文件(模块化开发引出的功能)
3、webpack的两大特色
自动分割(code splitting),即打包代码时,可以将代码切割成不同的chunk(块),实现按需加载,降低了初始化时间,提升了首屏渲染效率
loader 加载器可以处理各种类型的静态文件,并且支持串联操作
4、webpack配制说明
Webpack运行在node.js环境下,它的配置文件webpack.config.js遵循CommonJS规范,最终export出一个json对象。
webpack.config.js基础配置说明:
1、entry,指定了模块的入口,它让源文件加入构建流程中被webpack控制。可以是字符串也可以是对象
entry: {main: './path/to/my/entry/file.js'}
2、output,配置输出文件的存放位置、文件名、文件基础路径publicPath。
output: {filename: 'bundle.js',path: '/home/proj/public/assets'}
3、module,配置各种类型文件的解析规则,比如说.jsx文件、.js文件。
module: {rules: [{// 命中 JavaScript 文件test: /\.js$/,// 用 babel-loader 转换 JavaScript 文件// ?cacheDirectory 表示传给 babel-loader 的参数,用于缓存 babel 编译结果加快重新编译速度use: ['babel-loader?cacheDirectory'],// 只命中src目录里的js文件,加快 Webpack 搜索速度include: path.resolve(__dirname, 'src')},{// 命中 SCSS 文件test: /\.scss$/,// 使用一组 Loader 去处理 SCSS 文件。// 处理顺序为从后到前,即先交给 sass-loader 处理,再把结果交给 css-loader 最后再给 style-loader。use: ['style-loader', 'css-loader', 'sass-loader'],// 排除 node_modules 目录下的文件exclude: path.resolve(__dirname, 'node_modules'),},{// 对非文本文件采用 file-loader 加载test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/,use: ['file-loader'],},]
}
4、rosolve,配置alias(别名),或者定义寻找模块的规则。
alias: {'@': resolve('src')// 这样配置后 @ 可以指向 src 目录}
5、plugins,配置扩展插件,扩展webpack的更多功能。
plugins: [new webpack.optimize.CommonsChunkPlugin({...})
]
6、devServer,实现本地http服务等。
devServer: {static: {directory: path.join(__dirname, 'public'),},compress: true,port: 9000,},
5、有哪些常见的Plugin
plugin插件目的在于解决 loader 无法实现的其他事
用于修改行为
define-plugin:定义环境变量
ignore-plugin:用于忽略部分文件
用于优化
commons-chunk-plugin:提取公共代码
extract-text-webpack-plugin:提取 JavaScript 中的 CSS 代码到单独的文件中
prepack-webpack-plugin:通过 Facebook 的 Prepack 优化输出的 JavaScript 代码性能
uglifyjs-webpack-plugin:通过 UglifyES 压缩 ES6 代码,
imagemin-webpack-plugin:压缩图片文件。
webpack-spritesmith:用插件制作雪碧图
6、webpack热更新原理
7、webpack 做过哪些优化,开发效率方面、打包策略方面等等
https://juejin.cn/post/6844903651291447309#heading-0
一、优化构建速度
- 缩小文件的搜索范围
- 使用DllPlugin减少基础模块编译次数
- 使用HappyPack开启多进程Loader转换
- 使用ParallelUglifyPlugin开启多进程压缩JS文件
二、优化开发体验
- Webpack监听文件
- DevServer刷新浏览器
- 开启模块热替换HMR
三、优化输出质量-压缩文件体积
- 区分环境–减小生产环境代码体积
- 压缩代码-JS、ES、CSS
- 压缩ES6:第三方UglifyJS插件
- 压缩CSS:css-loader?minimize、PurifyCSSPlugin
- 使用Tree Shaking剔除JS死代码
四、优化输出质量–加速网络请求
- 使用CDN加速静态资源加载
- 多页面应用提取页面间公共代码,以利用缓存
- 分割代码以按需加载
五、优化输出质量–提升代码运行时的效率
- 使用Prepack提前求值
- 使用Scope Hoisting
六、使用输出分析工具
- 官方工具Webpack Analyse
- webpack-bundle-analyzer
webpack性能优化
针对webpack本身构建优化
1.优化Loader配置(善用include,exclude字段)
2.优化 resolve.modules 配置
3.优化 resolve.extensions 配置
通过loader和plugin优化
1.通过cache-loader开启缓存 ,再次构建文件没有变化直接拉取缓存
2.使用happypack开启多进程,提升打包效率
3.使用terserwebpackplugin开启多进程压缩
4.使用dllplugin将静态资源分离
5.使用splitchunksplugin进行代码分离
6.打包资源压缩
JS 压缩:UglifyjsWebpackPlugin
HTML 压缩:HtmlWebpackPlugin
CSS 压缩:MiniCssExtractPlugin
图片压缩:image-webpack-loader
Gzip 压缩:不包括图片
其他优化
使用 tree shaking去除死代码
输出Webpack构建信息的.json文件:webpack --profile --json > stats.json
--profile:记录构建中的耗时信息
--json:以json格式输出构建结果,最后只输出一个json文件(包含所有的构建信息)
然后对stats.json文件进行可视化分析
下载插件yarn add -D webpack-bundle-analyzer
然后在配置文件中加入如下代码
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports = {plugins: [new BundleAnalyzerPlugin()]
}
然后执行
webpack-bundle-analyzer bundle/output/path/stats.json
优化构建速度
webpack配置优化
1、优化Loader配置(善用include,exclude字段)
{test: /\.js$/,use: ['babel-loader?cacheDirectory',//开启转换结果缓存],include: path.resolve(__dirname, 'src'),//只对src目录中文件采用babel-loaderexclude: path.resolve(__dirname,' ./node_modules'),//排除node_modules目录下的文件
}
2、优化resolve.modules配置
- resolve.modules用于配置webpack去哪些目录下寻找第三方模块,默认是[‘node_modules’],但是,它会先去当前目录的./node_modules查找,没有的话再去…/node_modules最后到根目录;
- 所以当安装的第三方模块都放在项目根目录时,就没有必要安默认的一层一层的查找,直接指明存放的绝对位置
resolve: {modules: [path.resolve(__dirname, 'node_modules')],}
3、优化resolve.extensions配置
- 在导入没带文件后缀的路径时,webpack会自动带上后缀去尝试询问文件是否存在,而resolve.extensions用于配置尝试后缀列表;默认为extensions:[‘js’,‘json’];
- 及当遇到require(‘./data’)时webpack会先尝试寻找data.js,没有再去找data.json;如果列表越长,或者正确的后缀越往后,尝试的次数就会越多;
- 所以在配置时为提升构建优化需遵守:
- 频率出现高的文件后缀优先放在前面;
- 列表尽可能的小;
- 书写导入语句时,尽量写上后缀名
因为项目中用tsx较多,所以配置extensions: [“.tsx”,“.ts”],
使用DllPlugin减少基础模块编译次数
对于react和react-dom这些不会修改的三方库,让它们与业务代码分开打包
当webpack打包时遇到导入的模块在某个动态库链接库中,就直接去获取,而不用再去编译第三方库。
需要两个插件
DllPlugin插件:用于打包出一个个单独的动态链接库文件;
DllReferencePlugin:用于在主要的配置文件中引入DllPlugin插件打包好的动态链接库文件
1、配置webpack_dll.config.js构建动态链接库
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');module.exports = {mode: 'production',entry: {// 将React相关模块放入一个动态链接库react: ['react','react-dom','react-router-dom','react-loadable'],librarys: ['wangeditor'],utils: ['axios','js-cookie']},output: {filename: '[name]-dll.js',path: path.resolve(__dirname, 'dll'),// 存放动态链接库的全局变量名,加上_dll_防止全局变量冲突library: '_dll_[name]'},// 动态链接库的全局变量名称,需要与output.library中保持一致,也是输出的manifest.json文件中name的字段值// 如react.manifest.json字段中存在"name":"_dll_react"plugins: [new DllPlugin({name: '_dll_[name]',path: path.join(__dirname, 'dll', '[name].manifest.json')})]
}
2、配置webpack.config.js告诉webpack使用了哪些动态链接库
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
...
plugins: [// 告诉webpack使用了哪些动态链接库new DllReferencePlugin({manifest: require('./dll/react.manifest.json')}),new DllReferencePlugin({manifest: require('./dll/librarys.manifest.json')}),new DllReferencePlugin({manifest: require('./dll/utils.manifest.json')}),
]
使用HappyPack开启多进程Loader转换
npm i -D happypack
// webpack.config.json
const path = require('path');
const HappyPack = require('happypack');module.exports = {//...module:{rules:[{test:/\.js$/,use:['happypack/loader?id=babel']exclude:path.resolve(__dirname, 'node_modules')},{test:/\.css/,use:['happypack/loader?id=css']}],plugins:[new HappyPack({id:'babel',loaders:['babel-loader?cacheDirectory']}),new HappyPack({id:'css',loaders:['css-loader']})]}
}
优化打包体积
区分环境–减小生产环境代码体积
使用DefinePlugin指定环境
const DefinePlugin = require('webpack/lib/DefinePlugin');
//...
plugins:[new DefinePlugin({'process.env': {NODE_ENV: JSON.stringify('production')}})
]
压缩es6/css/image
uglifyjs-webpack-plugin:通过 UglifyES 压缩 ES6 代码,
css-loader:使用css-loader?minimize
imagemin-webpack-plugin:压缩图片文件。
extract-text-webpack-plugin:提取 JavaScript 中的 CSS 代码到单独的文件中
使用Tree Shaking剔除JS死代码
Tree Shaking可以剔除用不上的死代码,它依赖ES6的import、export的模块化语法
1、修改.babelrc以保留ES6模块化语句
{"presets": [["env", { "module": false }, //关闭Babel的模块转换功能,保留ES6模块化语法]]
}
2、通过webpack --display-used-exports --optimize-minimize 启动Webpack
–display-used-exports可以在控制台打印出关于代码剔除的提示
–optimize-minimize 剔除死代码
3、在使用第三方库时,需要配置 resolve.mainFields: [‘jsnext:main’, ‘main’] 以指明解析第三方库代码时,采用ES6模块化的代码入口
https://leetcode-cn.com/circle/discuss/wqMpBQ/
https://segmentfault.com/a/1190000018493260
https://whjin.github.io/frontend-dev-doc/posts/webpack/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAwebpack/%E4%BD%BF%E7%94%A8%20Tree%20Shaking.html
浏览器相关
从url输入到页面展现背后发生了什么
- 浏览器根据请求的url交给dns域名解析,找到真实ip,向服务器发起请求
- 服务器交给后台返回的数据,浏览器接收文件
- 浏览器对加载到的资源进行语法解析,建立相应的内部数据结构
- 载入解析到的资源文件,渲染页面完成
ts
typescript的数据类型
number
string
boolean
null
undefined
array
tuple元组 属于数组的一种 可以规范列表项的类型
enum
any
void
never
用ts定义数组的两种方式
let arr:string[]=['111','222']
或者
let arr:Array<string>=['111','222']
泛型
function getData<T>(value:T):T{return value;
}
getData<string>('1111')
type和interface的区别
https://blog.csdn.net/weixin_43550562/article/details/123546720
相同点:都是用来定义对象或函数的形状
不同点:
type可以声明基本类型,联合类型,元组类型,typeof操作符
interface可以声明合并,如果是type会报重复定义的警告
interface test {name: string}interface test {age: number}
http
http2.0与http1.1的区别
https://mp.weixin.qq.com/s/GICbiyJpINrHZ41u_4zT-A?
新的二进制格式,采用二进制格式,实现方便且健壮。
多路复用
header压缩
服务端推送
https
由于http明文传输的特性,在http的传输过程中任何人都可能截获修改或者伪造请求
在http的传输过程中不会验证通信方的身份,也就是没有用户验证。
在http的传输过程中不会验证报文的完整性,综上https应运而生
http是提供了3个关键的指标
1、数据加密
2、数据的一致性
3、身份认证
nginx正向代理与反向代理的区别
在正向代理中,客户端是知道真实的请求地址的,由代理服务器代表客户端向服务端发起请求;而在反向代理中,客户端并不知道真正的请求地址,由代理服务器代表后端服务器向客户端发送响应。
正向代理的主要作用是突破网络访问限制,反向代理的主要作用是实现负载均衡、提高网站可用性、保证访问安全等。
正向代理和反向代理的代理对象不同。正向代理代理客户端,反向代理代理服务器。
什么是 Git?Git 的优势是什么?
Git 是一个分布式版本控制系统,它能够跟踪文件的更改历史记录并协调多个开发者之间的工作。Git 的优势包括速度快、存储空间占用小、支持非线性开发流程等。
Git 中的三种状态是什么?
Git 中的三种状态是已提交(committed)、已修改(modified)和已暂存(staged)。已提交表示数据已经被安全地保存在本地数据库中,已修改表示修改了文件但还没有保存到数据库中,已暂存表示对一个已修改文件的当前版本做了标记,以便后续提交。
如何在 Git 中将代码推送到远程仓库?
要将代码推送到远程仓库,可以使用 git push 命令。例如:git push origin master 将本地 master 分支的代码更新到名为 origin 的远程仓库中。
如何撤销 Git 中的提交?
要撤销 Git 中的提交,可以使用 git revert 或 git reset 命令。git revert 会创建一个新的提交,将指定提交的修改内容逆向,而 git reset 则会将当前分支指向指定的提交,并且删除其后的提交记录。
Git 中如何解决冲突?
在 Git 中,当两个人同时修改了同一份文件并提交到仓库时,就会发生冲突。要解决冲突,需要用编辑器手动修改文件,将两个人的修改合并起来,然后在本地提交修改后再推送到远程仓库中。
如何在 Git 中查看提交历史记录?
要查看提交历史记录,可以使用 git log 命令。例如:git log --oneline 可以以单行显示提交记录,并列出 SHA-1 校验和和提交信息。也可以使用其他参数以不同的格式查看提交历史记录。
TypeScript 是什么?它和 JavaScript 有什么区别?
ts是js的超集,增加了很多新的特性和语法,比如静态类型检查、接口、泛型等,这些特性使得代码更加可靠、易于维护。
什么是类型注解?如何在 TypeScript 中使用类型注解?
类型注解是 ts中用来标注变量、函数参数、返回值等类型的语法,它可以让编译器检查代码中的类型错误,并提供更好的代码提示和错误提示。
在 TypeScript 中使用类型注解非常简单,只需要在变量或函数参数后面加上冒号 : 和对应的类型即可。例如:
let num1: number = 10;
let str1: string = 'hello';function add(num1: number, num2: number): number {return num1 + num2;
}class Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}sayHello() {console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old`);}
}
TypeScript 中有哪些基本数据类型?如何声明和使用它们?
TypeScript 中的基本数据类型包括:
number: 数字类型
string: 字符串类型
boolean: 布尔类型
null: 空值类型
undefined: 未定义类型
let num1: number = 10;
let str1: string = 'hello';
let bool1: boolean = true;
let null1: null = null;
let undefined1: undefined = undefined;
TypeScript 中的枚举是什么?如何使用枚举?
枚举(Enum)是 TypeScript 中用来表示一组有限可能值的数据结构。枚举在代码中通常用于表示某种状态或选项,可以使代码更加可读、易于维护。例如,我们可以使用枚举来表示一周中的每一天:
enum Weekday {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
}
如何定义和使用 TypeScript 中的数组和元组?
数组是ts中一组有序的数据,数组元素必须是同一种类型
let nums: number[] = [1, 2, 3];
let strs: string[] = ['hello', 'world'];
元组表示表示固定数量、不同类型的数据。
let tuple: [number, string] = [1, 'hello'];
元组中的每个元素都有一个固定的类型和位置,不能随意添加或删除元素。另外,元组的长度也必须与定义时的长度相同。
TypeScript 中的泛型是什么?如何使用泛型?
泛型 是在编译时期指定函数或类中使用的类型,并提高代码的重用性和安全性。
在 TypeScript 中定义一个泛型函数或类的方式是,在函数名或类名后面使用尖括号 <> 来表示需要指定的类型,例如:
function identity<T>(arg: T): T {return arg;
}class MyArray<T> {private elements: T[] = [];push(element: T) {this.elements.push(element);}pop(): T | undefined {return this.elements.pop();}get length(): number {return this.elements.length;}
}
如何定义和使用 TypeScript 中的接口?
定义对象或接口的形状,使得代码更加清晰可读、容易维护
什么是类?如何在 TypeScript 中定义和使用类?
类可以用来封装数据和行为,使得代码更加模块化、可扩展、易于维护。
class Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}sayHi() {console.log(`Hi, my name is ${this.name}.`);}
}
如何实现 TypeScript 中的继承和多态?
继承是面向对象编程中实现代码重用的一种机制,使得子类可以继承父类的属性和方法,并且可以添加或修改自己的属性和方法,从而对父类进行扩展或改进
class Animal {name: string;constructor(name: string) {this.name = name;}move(distance: number = 0) {console.log(`${this.name} moved ${distance}m.`);}
}class Dog extends Animal {bark() {console.log('Woof! Woof!');}
}let dog = new Dog('Dog');
dog.move(10); // 输出 Dog moved 10m.
dog.bark(); // 输出 Woof! Woof!
在 TypeScript 中,可以通过定义抽象类和抽象方法来实现多态
抽象类和抽象方法不能直接被实例化或调用,只能通过子类进行实现。
什么是命名空间?如何在 TypeScript 中使用命名空间?
命名空间(Namespace)是一种将代码包裹在一个独立的作用域中的机制,它可以避免全局命名冲突,使得代码更加模块化和可维护。在 TypeScript 中,可以使用 namespace 关键字来创建命名空间,例如:
namespace MyNamespace {export interface Person {name: string;age: number;}export function sayHello(person: Person) {console.log(`Hello, ${person.name}!`);}
}let person: MyNamespace.Person = { name: 'Alice', age: 25 };
MyNamespace.sayHello(person);
如何在 Node.js 中创建一个 Web 服务器?
在 Node.js 中创建一个 Web 服务器可以使用核心模块 http。下面是一个基本的示例:
const http = require('http');// 创建一个 HTTP 服务器
const server = http.createServer((req, res) => {// 设置 HTTP 响应头res.writeHead(200, {'Content-Type': 'text/plain'});// 发送响应数据 "Hello World!"res.end('Hello World!\n');
});// 监听端口
server.listen(3000, () => {console.log('Server running at http://localhost:3000/');
});
Node.js 中有哪些常用的模块?如何加载模块?
Node.js 提供了大量的核心模块,涵盖了文件操作、网络编程、加密、流处理等方面。常用的一些核心模块有:http:提供 HTTP 服务和客户端功能
fs:提供文件系统操作相关功能
path:提供路径相关的功能
os:提供系统相关信息的功能
crypto:提供加密和哈希算法相关的功能
stream:提供数据流处理相关的功能
events:提供事件相关的功能
child_process:提供创建和管理子进程相关的功能
要使用一个模块,可以使用 require() 方法加载该模块。例如,如果我们要使用 http 模块,可以这样做:
如何在 Node.js 中进行文件操作?
在 Node.js 中进行文件操作,可以使用核心模块 fs
const fs = require('fs');// 读取文件内容并输出到控制台
fs.readFile('path/to/file', 'utf-8', (err, data) => {if (err) throw err;console.log(data);
});// 写入文件内容
fs.writeFile('path/to/file', 'Hello, Node.js!', (err) => {if (err) throw err;console.log('文件已保存');
});// 复制文件
fs.copyFile('path/to/src', 'path/to/dest', (err) => {if (err) throw err;console.log('文件已复制');
});// 重命名文件
fs.rename('path/to/oldname', 'path/to/newname', (err) => {if (err) throw err;console.log('文件已重命名');
});
Node.js 中如何进行异步编程?
Node.js 提供了很多内置的异步编程 API,例如回调函数、Promise 和 async/await 等。以下是一些常见的异步编程方式:
如何进行进程管理和子进程管理?
在 Node.js 中,进程管理和子进程管理是非常重要的功能,它可以帮助我们更好地控制应用程序的运行环境和资源。Node.js 提供了内置的 process 模块和 child_process 模块来管理进程和子进程。以下是一些常见的进程管理和子进程管理操作:
在 Node.js 中,我们可以使用 process 模块来管理当前进程。例如,我们可以使用 process.argv 属性获取命令行参数、使用 process.cwd() 方法获取当前工作目录、使用 process.exit() 方法退出进程等。
Node.js 中如何进行错误处理?
例如 try-catch、setTimeout()、process.on(‘uncaughtException’) 等。以下是一些常见的错误处理方法:
setTimeout() 方法也可以用来进行错误处理。我们可以通过设置一个超时时间,在规定时间内等待代码执行完成,如果超时则抛出异常。
Node.js如何进行调试和性能优化?
除了使用 debugger 关键字外,还可以使用 Chrome DevTools 进行在线调试,或使用第三方工具如 node-inspector 进行调试。
Nginx 如何进行负载均衡?
Nginx 可以通过配置反向代理服务器来进行负载均衡。我们可以在 upstream 中指定多台服务器,并通过 proxy_pass 指令将请求转发给后端服务器。例如:
upstream backend {server backend1.example.com;server backend2.example.com;server backend3.example.com;
}server {listen 80;server_name example.com;location / {proxy_pass http://backend;}
}
如何重载 Nginx 配置文件?
nginx -s reload
移动端1px问题解决方案
https://juejin.cn/post/6959736170158751780
1px问题的指的是在一些移动端屏幕上1px会变得很粗
css的1px与移动设备的1px不能直接划等号,他们之间的比例关系有一个专门的属性来描述
window.devicePixelRatio=设备的物理像素/css像素
一个物理像素等于多少个设备像素取决于移动设备的屏幕特性和用户缩放比例
- 直接写 0.5px 兼容性不高
- 用图片代替边框
border: 1px solid transparent;
border-image: url('xxx.jpg') 2 repeat;
- 利用viewport+rem+js 实现的
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /><title>Document</title><style type="text/css"></style></head><body><script type="text/javascript">let viewport = document.querySelector('meta[name=viewport]')//下面是根据设备像素设置viewportif (window.devicePixelRatio == 1) {viewport.setAttribute('content', 'width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no')}if (window.devicePixelRatio == 2) {viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no')}if (window.devicePixelRatio == 3) {viewport.setAttribute('content', 'width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no')}function resize() {let width = screen.width > 750 ? '75px' : screen.width / 10 + 'px'document.getElementsByTagName('html')[0].style.fontSize = width}window.onresize = resize</script></body>
</html>
SEO
seo是指通过优化网站的内容和结构,提高网站在搜索网站中的排名和曝光度。
1.关键词研究:确定适合网站的关键词和短语,使其包含在网页的标题、描述、内容中
2.优化网站内容:确保您的网站内容高质量、有用,并与关键词相关
3.网站速度优化:优化网站加载速度,减少页面加载时间,包括压缩图像文件大小、优化代码等。
4.移动设备优化:确保您的网站对移动设备友好,具有响应式设计,并提供良好的用户体验。
5.优化网站结构:良好的网站结构使搜索引擎能够更好地理解和索引您的网站。使用清晰的导航菜单和内部链接,确保每个页面都可以从其他页面访问到。
6.优化图像:使用描述性文件名和ALT标签来描述图像,使其对搜索引擎可读,并确保图像大小适当。
http与https的区别
- 安全性:http是明文协议,数据在传输过程中不加密,https使用了安全套接字层(ssl/tls)来加密传输的数据,
- 数据传输方式:http使用明文传输数据,https使用ssl/tls传递数据
- 默认端口:http:80,https:443
- 证书要求:在https中,服务器需要使用数字证书来验证自己的身份
- 浏览器指示:http:http:// https:https://