2023


2023

记录2023,体系化查漏补缺。从HTML、CSS、JS、网络层面、框架、工程化、设计模式等方面总结。

JS相关

JS类型检测

JavaScript类型检测的几种方式

  1. typeof 操作符:返回一个字符串,表示值的类型,可能的值有 “undefined”、”boolean”、”number”、”string”、”bigint”、”symbol” 和 “object”。使用 typeof 检测基本数据类型比较方便,但是对于复杂数据类型(比如 null 或对象)可能会得到不准确的结果,所以使用时需要谨慎。
  2. instanceof 操作符:用于检测对象的类型,可以检测对象的原型链中是否存在某个构造函数。使用 instanceof 操作符检测一个对象是否属于某个类时,需要使用该类的构造函数进行检测,对于基本数据类型和 null 无法使用 instanceof 操作符检测。
  3. Object.prototype.toString() 方法:该方法返回一个表示对象的字符串,格式为 “[object 类型]”,其中类型指的是对象的内部属性 [[Class]] 的值。这种方式可以准确地检测所有类型的值,包括 null 和 undefined。
  4. Array.isArray() 方法:用于检测一个值是否为数组类型,返回一个布尔值。该方法只能检测数组类型,无法检测其他类型的值。

深浅拷贝

  1. 介绍深浅拷贝
  2. 手写深拷贝、浅拷贝
  3. JSON.parse(JSON.srtingfy())的弊端
  4. 深拷贝如何解决循环引用

讲深、浅拷贝之前,我们先了解一哈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())的弊端

  1. 无法对Date、RegExp、Error对象进行深拷贝
  2. 无法对函数进行深拷贝
  3. 无法解决循环引用
  4. 如果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;
}

关于闭包

  1. 讲讲闭包
  2. 闭包的缺点
  3. 如何解决闭包

讲闭包之前,我们先了解一哈作用域,作用域链AOGO

作用域链、AO、GO

作用域: 指在程序中定义变量的区域(全局作用域、函数作用域、块级作用域)

AO:函数作用域

GO:全局作用域

块级作用域:ES6->const、let

function a(){
    function b(){
        const name = 'echo';
    };
    b();
};
a();

JS执行流程: JIT(及时性编译语言),先编译再执行 ->

  1. a被定义时,a的作用域链是GO;a被执行时,a的作用域链是AO->GO。
  2. b被定义时,b的作用域链是a的AO->GO;b被执行时,b的作用域链是b的AO->a的AO->GO。
  3. b被销毁时,销毁b的AO;
  4. a被销毁时,销毁a的AO;

讲讲闭包

闭包是指引用了另外函数作用域中的变量的函数。通常是指在嵌套函数中实现的。

闭包的优缺点

  • 闭包存在,就会保存保留它们包含的函数作用域,导致内存消耗,v8等优化有垃圾回收机制(GC)。
  • 闭包的处理速度和内存消耗方面对脚本性能具有负面影响。

如何解决闭包

  • 对于引用的某些库,可以使用自带的实例销毁方法。
  • 对于某些对象可以使用 global = null; 解除对函数的引用,释放内存。

垃圾回收(GC机制)

栈和堆的垃圾回收

栈的垃圾回收

  • 一个记录当前执行状态的指针(称为ESP),指向调用栈中的函数执行上下文
  • 当一个函数执行结束后,JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文

堆的垃圾回收

  • 引用计数法

  • 标记清除法 (使用广泛)

    标记清除法:标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁

  • 标记:比如当变量进入执行环境时,反转某一位(通过一个二进制字符来表示标记),又或者可以维护进入环境变量和离开环境变量这样两个列表,可以自由的把变量从一个列表转移到另一个列表
    需要从出发点去遍历内存中所有的对象去打标记,而这个出发点有很多,我们称之为一组 根 对象,而所谓的根对象,其实在浏览器环境中包括又不止于 全局Window对象、文档DOM树 等

  • 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0

  • 然后从各个根对象开始遍历,把不是垃圾的节点(能访问到的节点)改成1

  • 清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间

  • 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收

标记整理(Mark-Compact)算法对标记清除法的优化

this

调用函数时,会创建一个执行环境,this就根据函数的执行环境绑定。

  1. 浏览器中默认this指向Window
  2. 普通函数this -> Window对象
  3. 构造函数this -> 构造函数的实例对象

原型、原型链

  • 每一个函数都会创建一个prototype属性,该属性是函数的原型对象 Person.prototype = Person.prototype
  • 每一个函数的原型都具有一个constructor属性,该属性指向函数本身 Person.prototype.constructor = Person
  • constructor对象的原型设置为新创建的对象的原型

image.png

继承

原型链继承、借用构造函数(改变this指向)、组合继承(原型链+构造函数)、寄生继承、寄生组合、类
借鉴红宝书

Java类的访问权限修饰符
publicprivate是访问权限修饰符,用于控制外界对类内部成员的访问。

原型链继承

缺点:原型中包含引用值,其引用值会在所有实例中共享 可以类比 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]

借用构造函数继承

缺点:

  1. 必须在构造函数中定义方法,导致函数不能重用。
  2. 子类不能访问父类原型上定义的方法 可以类比 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

事件循环

  1. 讲讲eventLoop
  2. 宏任务
  3. 微任务

关于eventLoop

eventLoop.png

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
其它微任务

关于异步

  1. ES6之前之后的异步编程
  2. 讲讲Generator函数
  3. 讲讲Promise对象
  4. async/await函数

ES6之前的异步编程方法

ES6之前实现异步编程的方法大概有四种:

  • 回调函数
  • 事件监听
  • 发布订阅
  • 构造函数实现Promise

ES6之后的异步编程方法

  • Generator函数
  • Promise对象
  • async/await

script标签中的属性

script标签

默认的script标签执行脚本,会根据顺序来执行,如果遇到js,就会阻塞DOM渲染

defer

设置了defer的属性,支持异步下载并且不会影响到DOM的渲染,script是顺序执行

async

async和defer的区别,async不会按照顺序执行,是谁先加载完谁执行

网络相关

浏览器输入URL到页面展示发生了什么

show.webp

  • 浏览器进程:用户交互、子进程管理、文件存储
  • 网络进程:为渲染进程和浏览器进程提供网络下载
  • 渲染进程:网络下载的HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面

网络进程:

  1. 查找缓存
  2. DNS解析
  3. 建立TSL连接(请求协议HTTPS)
  4. 建立TCP连接(建立成功后浏览器会将构建请求行、请求头等信息,并把该域名相关的Cookie等数据附加到请求头中,然后项服务器发送构建请求信息)
  5. 重定向(检查状态码,如果是301/302,则需要重定向,从Location自动中读取地址)
  6. 响应数据类型处理(Content-Type)

浏览器进程:

  1. 检查url是否一致,不一致打开新的进程
  2. 解析html构建DOM树
  3. 解析CSS构建CSS树
  4. 将DOM树和CSS树生成布局树(DOM)

HTTP请求、处理流程

浏览器端发起HTTP请求流程

  1. 构建请求

  2. 查找缓存
    浏览器缓存:保存本地资源副本,供下次请求时(拦截请求)直接使用的技术

  3. 准备IP地址和端口
    HTTP: 应用层协议、 TCP/IP传输层协议
    IP地址获取:访问域名 -> 域名和IP是映射关系 采用DNS(域名系统) 将域名地址转换为IP地址
    端口:URL上面携带的端口号、HTTP默认端口是80

  4. 等待TCP队列
    涉及知识点:
    http/1.1和http2在Chrome的运行机制
    http/1.1:1个TCP同时只能处理一个请求,浏览器为每个域名维护6个tcp连接
    http2:可以并行请求资源,浏览器只会为每个域名维护1个tcp连接

  5. 建立TCP连接
    三次握手

  6. 发送HTTP请求
    请求行:请求方法、请求URL、HTTP版本协议;请求头;请求体

服务器处理HTTP请求流程

  1. 返回请求
    响应行、响应头、响应体
  2. 断开连接
    Connection:Keep-Alive
  3. 重定向
    响应头中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的区别

  1. 端口号不同,http是80 https是443
  2. https需要CA证书
  3. 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 cachedisk cache
image.png

如上图所示:

  • 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
流程:

  1. 浏览器发送一个请求到服务器,请求包含一个资源的URL和一些缓存相关的头部信息,例如if-None-Match 或者if-Modified-Since
  2. 服务器接收到请求后,会检查头部信息,确定浏览器是否有资源的最新版本
  3. 如果是最新版本,返回304状态码和空的响应体,告诉浏览器使用缓存资源
  4. 不是最新版本,返回200状态码和新的响应体,告诉浏览器更新缓存资源

浏览器请求协商缓存命中,则状态码为 304

Last-Modified和Etag的区别:

  1. Last-Modified不能对1s之内的资源变动进行判断
  2. Etag每一次都是一个新的Hash值

启发式缓存

启发式缓存:浏览器自带的缓存策略,每个浏览器变现各不相同。

如果请求头没设置Expires和Cache-control但是响应头返回了Last-Modified信息,这种浏览器会启用自带的缓存策略:(当前时间 - Last-Modified)*0.1;

框架

React

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

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自动编译的三种方式

  1. watch mode 观察模式
  2. webpack-dev-server web服务器
  3. webpack-dev-middleware webpack中间件 + express

webpack的热更新

什么是runtime?
runtime 包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑

什么是manifest?
compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 “manifest
当完成打包并发送到浏览器时,会在运行时(Runtime)通过 Manifest 来解析和加载模块。
无论你选择哪种模块语法,那些import或require语句现在都已经转换为__webpack_require__方法,此方法指向模块标识符
通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。

总结:

  1. webpack-dev-server会创建两个服务->express的http服务和web socket服务 (sockjs建立webSocket长连接)
  2. webpack监听到文件或模块变化时会重新编译打包,生成的hash值
  3. 通过HMR runtime中的check方法拿到hash值向server端发送ajax请求和jsonp请求,服务器端会返回一个json文件(manifest)和js文件(update chunk)
    ajax请求json文件 jsonp请求js模块文件
  4. 通过返回的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优化方案

  1. SplitChunksPlugin代码块分割
  2. 模块懒加载import() -> 比如router文件
    why:如果模块不懒加载,最终代码会被打包到一个js文件中。
  3. 使用import() 时,使用/* webpackPrefetch: ture*/
  4. css-minimizer-webpack-plugin :使用cssnano 优化压缩CSS
  5. terser-webpack-plugin:压缩JS,webpack5默认自带,自定义配置需安装
  6. devServer.compress: true gzip compression

CI/CD

持续集成(Continuous Integration,CI)和 持续交付(Continuous Delivery,CD),是软件生产领域提升迭代效率的一种工作方式:开发人员提交代码后由 CI/CD 系统自动化地执行合并、构建、测试和部署等一系列管道化(Pipeline)的流程,从而尽早发现和反馈代码问题,以小步快跑的方式加速软件的版本迭代过程。

Hybrid App

目前项目中使用的是基于WebViewJavaScriptBridge

Hybrid App的优缺点

优点:

  1. 不受限应用商店审核,直接部署到线上环境。
  2. 页面有变动,不需要重新下载APP
  3. 部分页面由H5做,减少APP打包体积

缺点:

  1. 每次需求需花时间理清哪部分为APP页面,哪部分为H5页面
  2. 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)、flexgrid

响应式排版 → 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开发中,我们一般用事件模型来代替传统的发布-订阅模式


文章作者: echo_sixi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 echo_sixi !
  目录