2023
记录2023,体系化查漏补缺。从HTML、CSS、JS、网络层面、框架、工程化、设计模式等方面总结。
JS相关
JS类型检测
JavaScript类型检测的几种方式
- typeof 操作符:返回一个字符串,表示值的类型,可能的值有 “undefined”、”boolean”、”number”、”string”、”bigint”、”symbol” 和 “object”。使用 typeof 检测基本数据类型比较方便,但是对于复杂数据类型(比如 null 或对象)可能会得到不准确的结果,所以使用时需要谨慎。
- instanceof 操作符:用于检测对象的类型,可以检测对象的原型链中是否存在某个构造函数。使用 instanceof 操作符检测一个对象是否属于某个类时,需要使用该类的构造函数进行检测,对于基本数据类型和 null 无法使用 instanceof 操作符检测。
- Object.prototype.toString() 方法:该方法返回一个表示对象的字符串,格式为 “[object 类型]”,其中类型指的是对象的内部属性 [[Class]] 的值。这种方式可以准确地检测所有类型的值,包括 null 和 undefined。
- Array.isArray() 方法:用于检测一个值是否为数组类型,返回一个布尔值。该方法只能检测数组类型,无法检测其他类型的值。
深浅拷贝
- 介绍深浅拷贝
- 手写深拷贝、浅拷贝
- JSON.parse(JSON.srtingfy())的弊端
- 深拷贝如何解决循环引用
讲深、浅拷贝之前,我们先了解一哈js的基础类型和对象类型。
JS基础类型、对象类型
- 基础类型 ->
栈内存 - 对象类型 ->
堆内存 - 对象类型在栈空间存储的是堆内存的引用地址
- 对于赋值: 基础类型会完整复制变量值、引用类型赋值时复制引用地址
介绍深浅拷贝
深拷贝:MDN介绍拷贝的对象和生成的对象的属性不共享相同的引用,简洁的来说:深拷贝拷贝的是值。
浅拷贝:与深拷贝相反,拷贝的是引用地址。
手写深拷贝、浅拷贝
//深拷贝
function deepClone(obj){
if(typeof obj !== 'object'){
return ;
}
let copyObj = Array.isArray(obj) ? [] : {};
for(let key in obj){
//遍历对象自身及原型链上所有可枚举不可枚举属性
if(obj.hasOwnProperty(key)){
copyObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}
return copyObj;
}
//浅拷贝 相对比深拷贝 缺少了递归赋值引用值的步骤
function shallowClone(obj){
if(typeof obj !== 'object'){
return
}
let copyObj = Array.isArray(obj) ? [] : {};
for(let key in obj){
copyObj[key] = obj[key]
}
return copyObj
}
JSON.parse(JSON.srtingfy())的弊端
- 无法对Date、RegExp、Error对象进行深拷贝
- 无法对函数进行深拷贝
- 无法解决循环引用
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
深拷贝如何解决循环引用
采用循环引用导致栈溢出代码 Uncaught RangeError: Maximum call stack size exceeded
const obj = {
name: 'echo'
};
const testObj = {
name: 'sixi',
relative: obj
};
obj.relative = testObj; //循环引用
const newObj = deepClone(obj);
console.log(newObj);
优化手写深拷贝代码:使用WeakMap对已遍历的对象进行保存,当递归调用的时候直接判断WeakMap是否存在,若已存在则直接返回,避免无限循环。
function deepClone(obj, hash = new WeakMap()){
if(typeof obj !== 'object'){
return ;
}
if(hash.has(obj)){
return hash.get(obj);
}
let copyObj = Array.isArray(obj) ? [] : {};
hash.set(obj, copyObj);
for(let key in obj){
//遍历对象自身及原型链上所有可枚举不可枚举属性
if(obj.hasOwnProperty(key)){
copyObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key], hash) : obj[key];
}
}
return copyObj;
}
关于闭包
- 讲讲闭包
- 闭包的缺点
- 如何解决闭包
讲闭包之前,我们先了解一哈作用域,作用域链、AO、GO。
作用域链、AO、GO
作用域: 指在程序中定义变量的区域(全局作用域、函数作用域、块级作用域)
AO:函数作用域
GO:全局作用域
块级作用域:ES6->const、let
function a(){
function b(){
const name = 'echo';
};
b();
};
a();
JS执行流程: JIT(及时性编译语言),先编译再执行 ->
- a被定义时,a的作用域链是GO;a被执行时,a的作用域链是AO->GO。
- b被定义时,b的作用域链是a的AO->GO;b被执行时,b的作用域链是b的AO->a的AO->GO。
- b被销毁时,销毁b的AO;
- a被销毁时,销毁a的AO;
讲讲闭包
闭包是指引用了另外函数作用域中的变量的函数。通常是指在嵌套函数中实现的。
闭包的优缺点
- 闭包存在,就会保存保留它们包含的函数作用域,导致内存消耗,v8等优化有垃圾回收机制(GC)。
- 闭包的处理速度和内存消耗方面对脚本性能具有负面影响。
如何解决闭包
- 对于引用的某些库,可以使用自带的实例销毁方法。
- 对于某些对象可以使用
global = null;解除对函数的引用,释放内存。
垃圾回收(GC机制)
栈和堆的垃圾回收
栈的垃圾回收
- 一个记录当前执行状态的指针(称为ESP),指向调用栈中的函数执行上下文
- 当一个函数执行结束后,JavaScript 引擎会通过向下移动
ESP来销毁该函数保存在栈中的执行上下文
堆的垃圾回收
引用计数法
标记清除法 (使用广泛)
标记清除法:标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁
标记:比如当变量进入执行环境时,反转某一位(通过一个二进制字符来表示标记),又或者可以维护进入环境变量和离开环境变量这样两个列表,可以自由的把变量从一个列表转移到另一个列表
需要从出发点去遍历内存中所有的对象去打标记,而这个出发点有很多,我们称之为一组 根 对象,而所谓的根对象,其实在浏览器环境中包括又不止于 全局Window对象、文档DOM树 等垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
然后从各个根对象开始遍历,把不是垃圾的节点(能访问到的节点)改成1
清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间
最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收
标记整理(Mark-Compact)算法对标记清除法的优化
this
调用函数时,会创建一个执行环境,this就根据函数的执行环境绑定。
- 浏览器中默认this指向Window
- 普通函数this -> Window对象
- 构造函数this -> 构造函数的实例对象
原型、原型链
- 每一个函数都会创建一个
prototype属性,该属性是函数的原型对象Person.prototype = Person.prototype - 每一个函数的原型都具有一个
constructor属性,该属性指向函数本身Person.prototype.constructor = Person constructor对象的原型设置为新创建的对象的原型
继承
原型链继承、借用构造函数(改变this指向)、组合继承(原型链+构造函数)、寄生继承、寄生组合、类借鉴红宝书
Java类的访问权限修饰符public和private是访问权限修饰符,用于控制外界对类内部成员的访问。
原型链继承
缺点:原型中包含引用值,其引用值会在所有实例中共享 可以类比 public
function Sixi(){
this.arr = [1,2]
}
function Echo(){}
Echo.prototype = new Sixi()
let instance = new Echo()
instance.arr.push(3)
console.log(instance?.arr) // [1, 2, 3]
let instance2 = new Echo()
console.log(instance2?.arr) // [1, 2, 3]
借用构造函数继承
缺点:
- 必须在构造函数中定义方法,导致函数不能重用。
- 子类不能访问父类原型上定义的方法 可以类比 private
function Sixi(age){ this.age = age; } function Echo(){ Sixi.call(this, '18'); this.name = 'echo_sixi' } let instance = new Echo(); console.log(instance?.age); //18 console.log(instance?.name); //echo_sixi
圣杯模式
代码实现
function Echo() {
this.name = '123'
};
function Sixi() {};
function inherit(child, parent) {
function F(){};
F.prototype = parent.prototype;
child.prototype = new F();
child.prototype.constructior = child;
}
inherit(Sixi, Echo)
Echo.prototype.name = '456'
const echo = new Echo()
const sixi = new Sixi()
console.log(sixi.name) // 456
类(class)
class Echo{
constructor(){
this.age = 18;
}
}
class Sixi extends Echo {
constructor(){
super()
this.name = 'echo_sixi'
}
}
let person = new Sixi()
console.log(person.age); //18
事件循环
- 讲讲eventLoop
- 宏任务
- 微任务
关于eventLoop
JS单线程
JS主线程 -> 执行栈(先进后出)
callback queue -> 任务队列(先进先出)
event loop执行流程
QA:
- eventloop的定义和作用
- eventloop的执行过程
- 异步任务的微任务和宏任务
eventloop是一种异步的编程机制,它可以在单线程中实现并发,通过轮询事件队列中的事件来判断是否有新的任务执行,从而实现异步操作。
在执行过程中,事件循环会将所有的异步任务(回调函数、Promise、async/await、定时器、事件监听 → 回调函数)放入一个事件队列中,然后在程序的执行期间不断去检查队列中是否有待处理的事件。当事件队列中有事件则取出执行,如果又包含了异步处理,则交给其它线程进行处理然后返回,继续检查队列中的下一个事件。事件循环采用轮询机制,可以保证任务的执行顺序,同时避免程序阻塞执行。
主线程执行过程中,遇到JavaScript程序执行的到异步操作,主线程将该任务交给系统或者其它线程进行处理,同时继续执行后面的代码。当异步任务操作完成后,系统或者其它线程会通知主线程,将结果放入事件队列中。主线程空闲时,会检查事件队列中是否有待处理的事件,如果有,则立即执行,没有则继续等待。
JavaScript的异步任务中,可以将异步任务分为:宏任务和微任务
宏任务:指需要在任务队列中排队等待的执行的任务 → 定时器、ajax请求等。
微任务:指当前任务完毕后需要立即执行的任务 → Promise的then、catch、finally;async/await等
事件循环启动时,会执行当前代码所在的宏任务代码块,当宏任务代码块被执行完了,就会立即执行所有可用的微任务,直到微任务队列为空。然后事件循环会继续执行下一个宏任务,重复这个过程。
宏任务
异步 Ajax 请求、
setTimeout、setInterval、
文件操作
MessageChannel
其它宏任务
微任务
Promise.then、.catch 和 .finally
process.nextTick
MutationObserver
其它微任务
关于异步
- ES6之前之后的异步编程
- 讲讲Generator函数
- 讲讲Promise对象
- async/await函数
ES6之前的异步编程方法
ES6之前实现异步编程的方法大概有四种:
- 回调函数
- 事件监听
- 发布订阅
- 构造函数实现Promise
ES6之后的异步编程方法
- Generator函数
- Promise对象
- async/await
script标签中的属性
script标签
默认的script标签执行脚本,会根据顺序来执行,如果遇到js,就会阻塞DOM渲染
defer
设置了defer的属性,支持异步下载并且不会影响到DOM的渲染,script是顺序执行
async
async和defer的区别,async不会按照顺序执行,是谁先加载完谁执行
网络相关
浏览器输入URL到页面展示发生了什么
- 浏览器进程:用户交互、子进程管理、文件存储
- 网络进程:为渲染进程和浏览器进程提供网络下载
- 渲染进程:网络下载的HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面
网络进程:
- 查找缓存
- DNS解析
- 建立TSL连接(请求协议HTTPS)
- 建立TCP连接(建立成功后浏览器会将构建请求行、请求头等信息,并把该域名相关的Cookie等数据附加到请求头中,然后项服务器发送构建请求信息)
- 重定向(检查状态码,如果是301/302,则需要重定向,从Location自动中读取地址)
- 响应数据类型处理(Content-Type)
浏览器进程:
- 检查url是否一致,不一致打开新的进程
- 解析html构建DOM树
- 解析CSS构建CSS树
- 将DOM树和CSS树生成布局树(DOM)
HTTP请求、处理流程
浏览器端发起HTTP请求流程
构建请求
查找缓存
浏览器缓存:保存本地资源副本,供下次请求时(拦截请求)直接使用的技术准备IP地址和端口
HTTP: 应用层协议、 TCP/IP传输层协议
IP地址获取:访问域名 -> 域名和IP是映射关系 采用DNS(域名系统) 将域名地址转换为IP地址
端口:URL上面携带的端口号、HTTP默认端口是80等待TCP队列
涉及知识点:
http/1.1和http2在Chrome的运行机制
http/1.1:1个TCP同时只能处理一个请求,浏览器为每个域名维护6个tcp连接
http2:可以并行请求资源,浏览器只会为每个域名维护1个tcp连接建立TCP连接
三次握手发送HTTP请求
请求行:请求方法、请求URL、HTTP版本协议;请求头;请求体
服务器处理HTTP请求流程
- 返回请求
响应行、响应头、响应体 - 断开连接
Connection:Keep-Alive - 重定向
响应头中Location字段:重定向地址
许多网站第二次打开速度快
DNS缓存、页面资源缓存
DNS缓存:浏览器本地的IP和域名映射
页面资源缓存:响应头Cache-Control:Max-age=2000
HTTP版本区别
http1.1
http1.1是目前主流的http协议版本
- 持久化链接 通过keep-alive来设置
- 管道机制,同一个tpc连接客户端可以发多个请求
- 支持断点续传,通过请求头的Range
- 支持chunked 数据分块,body里面分块 利于大文件传输 Transfer-Encoding: chunked
http2.0
- 二进制分帧
- 头部压缩 HPACK算法 哈夫曼编码
- 多路复用 解决队头阻塞
- 服务器推送
- 请求优先级
tip: 队头阻塞: HTTP开启长连接,公用一个TCP连接,当某个请求时间过长,其它请求只能处于阻塞状态
解决方案: 1. 并发连接 2.域名分片
HTTP和HTTPS的区别
- 端口号不同,http是80 https是443
- https需要CA证书
- http信息是明文传输的,https是由ssl+http协议构建可进行加密传输、身份认证的网络协议
渲染流水线
1.构建DOM树:浏览器无法直接识别HTML语言,讲HTML转为浏览器可以识别的DOM树,document
2.样式计算: 同上。document.styleSheets
1.解析为styleSheets
2.转换样式表中的属性值 -> 标准化 (color: #ffffff -> color: rgb(255,255,255))
3.计算每个节点具体样式(CSS继承、层叠)
继承:子节点继承父节点样式
层叠:样式叠加、样式属性相同根据优先级
盒子的最终样式:例如Chrome:F12->Elements->Computed
3.布局阶段:
1.创建布局树:遍历DOM树的可见节点,例如head标签、display:none、visibility: hidden等就会被排除
4.分层:3D、滚动、z-index、fixed等:渲染引擎为特定的节点生成专用图层,生成一颗对应的图层树(LayerTree)
例如Chrome: F12-> … ->More tools ->Layers
1.拥有层叠上下文属性的元素会单独一层
2.需要剪裁的地方也会被创建为图层
5.栅格化
合成线程会将图层划分为图块(256256、512512)
合成线程会按照视口附近的图块优先生成位图,实际生成的位图操作实际是由栅格化来执行的
栅格化:将图块转换为位图,使用GPU生成的位图的过程称为快速栅格化、成成的位图保存在GPU内存中、
6.合成和显示
所有图块都被光栅化,合成线程->DrawQuad命令->浏览器进程(将页面内容绘制到内存中,再显示到页面上)
重绘、重排、合成
知识点: 重绘、重排、合成
重绘:比如样式修改:背景色修改 -> 不会影响布局阶段和分层阶段
重排:比如样式修改:高度 -> 影响布局 新增BFC -> 影响布局、分层
对比:重绘比重排快->重绘相比重排 没有layout、layer阶段
合成:浏览器渲染跳过layout、layer阶段的操作
举例:transform属性 -> 所以transform来实现动画效果要好一些
浏览器缓存
浏览器缓存的方式可以减少网络带宽、降低服务器的压力、减少网络延迟、加快页面打开速度
常见的缓存有强缓存和协商缓存
强缓存
强缓存:第一次读取到服务器数据后,在设置的过期时间内都不会去重复请求,而是从本地缓存去读取。
本地缓存有memory cache、disk cache
如上图所示:
- memory cache一般缓存图片、样式、字体,退出进程数据就清除了
- disk cache一般缓存脚本,存储在硬盘上,需要手动清除
强缓存优先级高于协商缓存,通过设置响应头控制资源强缓存,使用强缓存,不需要请求服务器。
Cache-Control: max-age=3600 相对时间,单位秒
Expires: Mon, 14 Mar 2023 08:00:00 GMT 具体的时间,单位秒
同时使用,Cache-Control的优先级高
协商缓存
协商缓存和强缓存不同就是每次都会跟服务器通信,并带上缓存标识(if-Modified-Since / if-None-Match)
流程:
- 浏览器发送一个请求到服务器,请求包含一个资源的URL和一些缓存相关的头部信息,例如
if-None-Match或者if-Modified-Since - 服务器接收到请求后,会检查头部信息,确定浏览器是否有资源的最新版本
- 如果是最新版本,返回304状态码和空的响应体,告诉浏览器使用缓存资源
- 不是最新版本,返回200状态码和新的响应体,告诉浏览器更新缓存资源
浏览器请求协商缓存命中,则状态码为
304
Last-Modified和Etag的区别:
- Last-Modified不能对1s之内的资源变动进行判断
- Etag每一次都是一个新的Hash值
启发式缓存
启发式缓存:浏览器自带的缓存策略,每个浏览器变现各不相同。
如果请求头没设置Expires和Cache-control但是响应头返回了Last-Modified信息,这种浏览器会启用自带的缓存策略:
(当前时间 - Last-Modified)*0.1;
框架
React
React架构
React
知识点
主流浏览器刷新频率60Hz,每(1000ms/60Hz)16.6ms浏览器刷新一次
渲染线程和JS线程是互斥的,JS执行会中断渲染进程
在浏览器刷新一次的16.6ms时间中,它需要完成:JS脚本执行->样式布局->样式绘制
当JS执行时间超过16.6ms那么样式布局和样式绘制的处理就没有在此次刷新中执行
react15:栈实现、同步更新、协调器同步、递归执行
Stack reconciler(协调器):执行函数组件、class组件将JSX转为DOM树(diff算法、挂载、卸载、更新)
Renderer(渲染器):负责将变化组件渲染到页面上
react16: Fibe、扁平化的单链表的数据结构r、可中断的异步更新、循环迭代
Scheduler(调度器)、Reconciler(协调器)、Renderer(渲染器)
调度器:浏览器是否有剩余时间,浏览器API(requestIdleCallback)
let yieldInterval = 5; 源码为5毫秒
双缓冲技术:类似计算机图形学 中的 双缓冲策略
画图呈现到屏幕的过程 -> 创建位图对象,操作位图对象,最后再呈现位图对象
- WIP树(workInProgress tree)
- WIP节点不是全部新的。如果某颗子树不需要变动,直接复用
- 异常处理,有异常继续使用旧树
- WIP从旧树fork出来,第二阶段commit
Vue
Vue2和Vue3实现双向绑定的原理
Vue2使用Object.definePropty、Vue3使用Proxy
Vue的Api风格
Options API和Composition API。
- 选项式API是在组合式API基础上实现的。Options API以组件实例的概念为中心。
- Composition API 常配合
<script setup>,使用组合函数可以很好的组织和重用逻辑。
Composition API 可以重用逻辑,Vue2就不可以重用逻辑吗?
Vue2逻辑复用机制是mixins(接收一个数组),用于扩充父组件的对象和方法。
mixins的缺点:覆盖性会导致不稳定性,操作不当会造成全局污染
Vue的computed和watch
- computed 只有在依赖的响应式数据发生变化时才会重新计算,而 watch 则是在被监听的响应式数据发生变化时立即执行回调函数。
- computed 一般用于计算和处理数据,而 watch 一般用于监听数据的变化并执行一些副作用,如异步操作、界面更新等。
工程化
babel相关
babel的编译流程
babel的编译流程可以分别:解析、转换、生成
- 解析:使用@babel/parser,词法分析和语法分析,词法分析为标记化转为tokenization,语法分析为转换为AST树
- 转换:使用@babel/traverse, AST进行深度遍历并进行各种增删改
- 生成:使用@babel/generator, AST -> code
依赖注入
Babel默认只是转换箭头函数,而let Promise、includes这些没有转换
//转换前:
let array = [1, 2, 3, 4, 5, 6];
array.includes(item => item > 2);
new Promise()
//转转换后:
var array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {
return item > 2;
});
new Promise()
Babel把JavaScript语法分为
语法和api,语法在JavaScript中运行时是无法重写的。而api就可以支持方法重写
Babel只对语法进行处理,对于api就使用polyfill模块进行处理的,由于polyfill体积太大,使用babel7提供的useBuiltIns:(false, entry, usage: 按需添加)
@babel/runtime, @babel/plugin-transform-runtime 把 helpers 和 polyfill 功能拆分了。默认只提供 helpers
webpack
webpack自动编译的三种方式
- watch mode 观察模式
- webpack-dev-server web服务器
- webpack-dev-middleware webpack中间件 + express
webpack的热更新
什么是runtime?
runtime 包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑
什么是manifest?
当 compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 “manifest“
当完成打包并发送到浏览器时,会在运行时(Runtime)通过 Manifest 来解析和加载模块。
无论你选择哪种模块语法,那些import或require语句现在都已经转换为__webpack_require__方法,此方法指向模块标识符
通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。
总结:
- webpack-dev-server会创建两个服务->express的http服务和web socket服务 (sockjs建立webSocket长连接)
- webpack监听到文件或模块变化时会重新编译打包,生成的hash值
- 通过HMR runtime中的check方法拿到hash值向server端发送ajax请求和jsonp请求,服务器端会返回一个json文件(manifest)和js文件(update chunk)
ajax请求json文件 jsonp请求js模块文件 - 通过返回的json文件包含的hash值来获取最新模块代码,再利用runtime机制进行热更新
如果未配置热更新会在第二步后走以下操作
通过webSocket发送的type为ok时,直接执行reload操作,刷新浏览器
webpack的热更新的原文链接: https://zhuanlan.zhihu.com/p/30669007
Vite
https://cn.vitejs.dev/
使用原生ESM文件,无需打包
超快的热重载
参考:https://zhuanlan.zhihu.com/p/424842555
webpack优化方案
SplitChunksPlugin代码块分割- 模块懒加载
import()-> 比如router文件
why:如果模块不懒加载,最终代码会被打包到一个js文件中。 - 使用import() 时,使用
/* webpackPrefetch: ture*/ css-minimizer-webpack-plugin:使用cssnano 优化压缩CSSterser-webpack-plugin:压缩JS,webpack5默认自带,自定义配置需安装devServer.compress: truegzip compression
CI/CD
持续集成(Continuous Integration,CI)和 持续交付(Continuous Delivery,CD),是软件生产领域提升迭代效率的一种工作方式:开发人员提交代码后由 CI/CD 系统自动化地执行合并、构建、测试和部署等一系列管道化(Pipeline)的流程,从而尽早发现和反馈代码问题,以小步快跑的方式加速软件的版本迭代过程。
Hybrid App
目前项目中使用的是基于WebViewJavaScriptBridge
Hybrid App的优缺点
优点:
- 不受限应用商店审核,直接部署到线上环境。
- 页面有变动,不需要重新下载APP
- 部分页面由H5做,减少APP打包体积
缺点:
- 每次需求需花时间理清哪部分为APP页面,哪部分为H5页面
- H5在移动端上很多IOS和Android的UI兼容性问题
JSBrige
H5 -> IOS: 请求拦截 URL scheme
在H5中发起请求方式一般有location.href和iframe,使用iframe方式
连续多次修改window.location.href的值,Native层只能接受到最后一次请求。
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function () {
document.documentElement.removeChild(WVJBIframe);
}, 0);
IOS端拦截请求,解析、执行原生方法
客户端和H5通信: window注入
H5需要先注册监听事件,原生在某个时刻调用js的window.WebViewJavascriptBridge对象里方法。
响应式设计
响应式网页设计是让WEB页面适应不同屏幕的宽度。
媒介查询 → @media screen
灵活网格 → 百分比(%)
现代布局技术 → 多栏布局(column-count)、flex、 grid
响应式排版 → rem、vh、vw
视口元标签 → <meta name="viewport" content="width=device-width,initial-scale=1">
rem实现
- 插件 postcss-pxtorem + amfe-flexible
- 手动修改
function convert() {
const documentElement = document.documentElement;
// 设计图默认字体16, 屏宽375. 开发时按设计图大小编码即可.
documentElement.style.fontSize =
(16 * documentElement.clientWidth) / 375 + 'px';
}
export function pxToRem(px: number) {
return px / 16 + 'rem';
}
export default function() {
window.addEventListener('resize', convert, false);
convert();
}
设计模式
单例模式
JavaScript中的单例模式:单例模式核心 -> 确保只有一个实例,提供全局访问
观察者模式
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知。
在js开发中,我们一般用事件模型来代替传统的发布-订阅模式