acme申请RSA类型的证书(兼容性更多)默认是ECC类型
加上 --keylength 2048 参数
acme.sh --issue -d *.xx.com --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please --force --keylength 2048 加上 --keylength 2048 参数
acme.sh --issue -d *.xx.com --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please --force --keylength 2048 ES5:2009发布;ES6正式名称ES2015,后续每年迭代ES2016~ES2025,日常统称ES6。下面梳理最常用、工作高频差异。
只有 var
没有块级作用域(if/for循环无法隔离变量)
var a = 1;
var a = 2; // 合法
for(var i=0;i<3;i++){}
console.log(i); // 3 泄露到外层新增 let / const
{ } 隔离const 声明常量(引用类型可修改内部属性,不能重赋值)
let a = 1;
const PI = 3.14;
{
let b = 10
}
console.log(b) // 报错()=>{} 【ES6新增】ES5只有普通function
// ES5
var add = function(a,b){return a+b}
// ES6
const add = (a,b) => a + b;箭头函数特点
this:继承外层作用域this(最常用,解决回调this指向问题)arguments,可用剩余参数 ...args 替代prototype默认参数
// ES5
function fn(x){x = x || 0}
// ES6
function fn(x = 0){}剩余参数 ...rest
function sum(a,...rest){}快速提取数组、对象属性
// 对象解构
const user = {name:"张三",age:20}
// ES5
const name = user.name;
// ES6
const {name,age} = user;
//数组解构
const [a,b] = [10,20]... ES6const arr2 = [...arr1,4,5]; //数组拷贝/合并
const obj2 = {...obj1,c:10}; //对象浅拷贝模板字符串 ` (反引号) ES6
// ES5
var str = "name:" + name + ",age:"+age;
// ES6
const str = `name:${name},age:${age}`支持换行、插值 ${表达式}
includes()、startsWith()、endsWith()const name = "小明"
// ES5
const obj = {name:name, say:function(){}}
// ES6 属性简写、方法简写
const obj = {
name,
say(){}
}
// 计算属性名
const obj = {["key"+1]:100}Array.from() 类数组转数组entries() keys() values() 迭代器for循环、forEach、for...in(遍历对象key,不适合数组)ES6:for...of 遍历可迭代对象(数组、Set、Map、字符串)
for(const item of arr){}for...in:遍历键;for...of:遍历值
ES5仅有:Object、Array
ES6新增:
Set:集合,元素唯一Map:键可以是任意类型(ES5对象key只能字符串/Symbol)WeakSet / WeakMap 弱引用,利于GC垃圾回收Symbol:唯一不可重复的值,用作私有属性标识ES5 原型模拟面向对象
// ES5
function Person(name){this.name = name}
Person.prototype.say = function(){}
// ES6 class
class Person{
constructor(name){
this.name = name
}
say(){}
}⚠️ class只是原型的语法糖,不是真正面向对象,不存在继承隔离;支持 extends 继承、super()
import / export ES6ES5没有原生模块,依靠:IIFE、RequireJS、CommonJS(Node.js)
ES6 原生ESM标准
//导出
export const a = 1;
export default fn;
//导入
import fn, {a} from './mod.js'CommonJS:运行时加载;ESM:编译期静态导入,支持Tree-Shaking
ES5异步:回调函数 → 回调地狱
ES6 原生Promise,解决嵌套回调
后续ES2017新增 async / await(Promise语法糖)
new Promise((resolve,reject)=>{})
async function query(){
await fetch()
}Object.assign() 对象合并(ES6)| 特性 | ES5 | ES6 |
|---|---|---|
| 变量 | var,函数作用域 | let/const,块级作用域 |
| 函数 | function | 箭头函数、默认参数、rest参数 |
| 字符串 | 字符串拼接 | 模板字符串 `...${}` |
| 对象 | 基础字面量 | 属性简写、方法简写、计算属性 |
| 循环 | for / forEach / for..in | for...of 迭代器 |
| 异步 | 回调函数 | Promise + async/await |
| 面向对象 | 原型手写构造函数 | class / extends 继承语法糖 |
| 模块 | 无原生模块(CMD/AMD) | import / export ESM |
| 数据结构 | Array、Object | Set、Map、Symbol、Weak系列 |
| 解构 | 手动取值 | 对象/数组解构赋值 |
| 运算符 | 无展开语法 | ...展开运算符 |
JsonOb 响应式:数据劫持 + 变化监听完整技术细节

先纠正PPT笔误: Object.Defefirbairty → Object.defineProperty
这套方案是Vue2 同源响应式方案,也是很多游戏引擎、小程序自研状态库 JsonOb 的底层实现。
一、整体流程对照PPT架构
plaintext
原始JS对象
↓
Object.defineProperty 深度劫持(get/set)【数据拦截】
↓
数据发生修改 → set触发 → 发送变更信号
↓
信号广播系统(订阅发布模式)
↓
通知所有依赖当前字段的UI组件,执行UI更新
二、核心1:Object.defineProperty 数据劫持原理
Object.defineProperty(obj, key, descriptor)
对对象的指定属性重新定义,拦截 读取(get)、赋值(set)。
javascript
let rawData = { name:"test" };
// 劫持name属性
Object.defineProperty(rawData, 'name', {
get(){
// 读取时:收集当前正在执行的UI渲染函数(依赖收集)
track()
return this._value;},
set(newVal){
if(newVal === this._value) return; // 值无变化跳过
this._value = newVal;
// 修改时:触发信号派发,通知订阅者更新
trigger()}
})
普通 defineProperty 只能劫持一层属性,嵌套对象 {a:{b:1}} 内层b不会监听。
实现方案:
1. 递归遍历对象所有属性;
2. 如果属性值依然是 Object/Array ,继续对它执行 defineProperty 包装;
这就是PPT写的【深度属性劫持】。
1. 无法监听新增属性、删除属性
obj.xxx = 1 新增字段不会触发set; delete obj.xxx 无钩子。
解决方案:框架提供 $set / $del API。
2. 不能原生监听数组下标修改
arr[2] = 999 不会触发set。
三、核心2:数组监听怎么实现(PPT第二条)
原生数组无法靠defineProperty监听下标变更,JsonOb采取 方法重写拦截:
1. 复制数组原型 Array.prototype ;
2. 重写会改变数组内容的变异方法: push pop shift unshift splice sort reverse ;
3. 当业务调用 arr.push() 时,先走重写后的包装函数:
javascript
// 伪代码思路
const rawPush = Array.prototype.push;
Array.prototype.push = function(...args){
const res = rawPush.apply(this, args);
trigger(this); // 广播数组变更
return res;
}
注意: arr[0] = xxx 、 arr.length = 10 依然无法被捕获,框架一般同样提供API规避。
四、核心3:依赖收集 & 信号广播(订阅发布)
劫持只是拿到“数据变化通知”,还需要知道哪些UI依赖这个字段。
1. UI组件执行渲染函数,读取响应式字段 data.name ;
2. 触发 get() ,执行 track ;
3. 把「当前渲染函数」存入当前属性的依赖列表。
数据结构简易模型:
plaintext
targetMap = {
对象: {
属性名: [组件更新回调1, 组件更新回调2]}
}
当 set 检测数值改变:
1. 获取该属性保存的所有依赖回调;
2. 批量执行回调 → UI组件执行刷新。
也就是PPT里的【信号广播】。
五、PPT重点特性:enableBatch 批量变更
场景:一帧内连续多次修改数据
plaintext
data.a = 1
data.b = 2
data.c = 3
不做批处理:每次赋值立刻触发UI刷新 → 3次渲染,性能浪费。
enableBatch实现逻辑:
1. 开启批量模式后, trigger 不会立刻执行更新;
2. 将所有变更任务存入队列;
3. 等到当前JS执行栈结束(下一帧/微任务时机);
4. 去重、合并所有变更,统一执行一次UI更新。
游戏引擎、前端渲染库高频修改场景极其重要,减少重绘次数。
六、内存管理(PPT最后一条)
这套响应式很容易产生内存泄漏:
对象销毁后,依赖列表依然保存UI组件引用,组件无法GC。
JsonOb处理方案:
1. 组件销毁时,主动清除对应属性的依赖回调;
2. 使用弱引用容器(WeakMap)保存targetMap;
3. 对外提供销毁API,解除对象劫持,清空订阅列表。
七、拓展对比:defineProperty vs Proxy
你这套JsonOb使用 Object.defineProperty(Vue2方案)
现代方案用 Proxy (Vue3):
1. Proxy原生支持监听新增属性、数组下标、删除;
2. 不需要递归深度劫持,可以惰性代理;
缺点:不支持低版本运行环境。
很多小游戏引擎、老框架继续沿用defineProperty实现的JsonOb。
八、完整数据流总结(对应PPT框图)
1. 开发者修改响应式数据;
2. Object.defineProperty set 拦截赋值(数据拦截);
3. 判断值是否真正变化,避免无效通知;
4. 发送变更信号(批量模式下先入队列);
5. 信号广播遍历所有订阅该字段的UI组件;
6. 各个UI组件执行自身更新逻辑;
7. 组件卸载后自动清理依赖引用,防止内存泄露。
如果你需要,我可以写一份精简可运行的 JsonOb最小实现Demo(defineProperty版本),直观复现这套劫持+广播逻辑。
Cocos Creator 主动检测碰撞(3.8.8适用)
一、两种碰撞检测模式区分
1. 被动检测(默认):绑定碰撞组件+碰撞回调,物体接触自动触发onCollisionEnter
2. 主动检测(你要的):代码手动调用接口,任意时刻主动查询:有没有碰到、碰到哪些物体
二、2D主动碰撞检测(刚体/碰撞盒通用)
typescript
import { _decorator, Component, Node, Collider2D, Physics2DManager, Rect } from 'cc';
const { ccclass, property } = _decorator;
@ccclass
export class Test extends Component {
start() {
this.checkOverlap();
}
checkOverlap() {
// 获取自身2D碰撞盒
const collider = this.node.getComponent(Collider2D);
if (!collider) return;
// 主动查询所有和自己重叠的碰撞物体
const resultArr = Physics2DManager.instance.queryColliders(collider.worldAABB);
for (const coll of resultArr) {
// 排除自己
if (coll.node === this.node) continue;
console.log("主动检测到碰撞目标:", coll.node.name);
}
}}
适合:判断前方有没有障碍物、点击选中物体
typescript
// 从自身位置向右发射一条长射线
const origin = this.node.worldPosition;
const end = origin.add3f(200, 0, 0);
const hits = Physics2DManager.instance.raycast(origin, end);
if (hits.length > 0) {
console.log("射线碰到物体", hits[0].collider.node.name);}
三、3D主动碰撞检测(软体、3D物体适用)
typescript
import { PhysicsSystem, Collider } from 'cc';
// 获取自身碰撞包围盒
const selfCollider = this.node.getComponent(Collider);
const box = selfCollider.worldBounds;
// 查询所有和盒子重叠的3D碰撞体
const hits = PhysicsSystem.instance.queryColliders(box);
for (const hit of hits) {
if (hit.node !== this.node) {
console.log("主动检测碰撞3D物体:", hit.node.name);
}}
typescript
const start = this.node.worldPosition;
const dir = new Vec3(0, -1, 0); // 向下检测(落地判断)
const rayHit = PhysicsSystem.instance.raycastClosest(start, dir, 100);
if (rayHit) {
console.log("下方碰到地面/物体");}
四、适用场景(对应你之前软体肉块需求)
1. 软体肉块掉落,主动向下射线检测:判断什么时候碰到桌子、凹凸物体
2. 肉块滑动时每帧主动查询周围碰撞体,贴合物体边缘
3. 不依赖自动碰撞回调,完全自己控制检测时机(每帧/间隔几秒检测一次)
五、关键注意点
1. 必须开启物理引擎:2D勾选 项目设置-物理2D;3D勾选物理系统
2. 所有需要被检测的物体,必须挂载对应 Collider2D / Collider 碰撞组件
3. 性能优化:
六、简易落地检测示例(肉块掉桌面)
每帧向下射线,主动判断是否接触台面,接触后开启滑动软体动画:
typescript
update() {
const pos = this.node.worldPosition;
// 向下发射射线,长度50
const isHit = PhysicsSystem.instance.raycastClosest(pos, new Vec3(0,-1,0), 50);
if (isHit) {
// 碰到物体,执行贴边滑动逻辑
}}
先分清两个概念:
每次构建会覆盖build文件夹,必须用构建模板build-templates永久保存修改:
build-templates/web-mobilebuild/web-mobile/index.html、style-mobile.css到上面文件夹;修改css,替换背景图、全屏、隐藏进度条:
#splash {
position: absolute;
width: 100%;
height: 100%;
background: #000 url(./myLogo.png) no-repeat center;
background-size: 80%; /* logo占屏幕80% */
}
/* 隐藏默认进度条 */
.progress-bar {
display: none !important;
}build-templates/web-mobile/,下次构建自动生效。Scene_Loading,设为启动场景(项目设置 → 项目数据 → 默认启动场景选Scene_Loading);节点结构:
Canvas
├─ Sprite_Bg 背景图
├─ Sprite_Logo 你的品牌logo(可加缓动缩放动画)
├─ ProgressBar 进度条组件(ProgressTimer)
└─ Label_Progress 文字:0%核心逻辑:异步预加载所有游戏资源、实时更新进度、加载完成跳转主场景
import { _decorator, Component, ProgressTimer, Label, director, resources, Vec3, tween } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('LoadingManager')
export class LoadingManager extends Component {
@property(ProgressTimer) progressBar!: ProgressTimer;
@property(Label) progressText!: Label;
onLoad() {
// Logo入场缩放动画
const logo = this.node.getChildByName("Sprite_Logo");
tween(logo)
.set({ scale: new Vec3(0,0,0) })
.to(0.6, { scale: new Vec3(1,1,1) })
.start();
// 开始预加载资源
this.loadGameAssets();
}
// 批量预加载所有游戏资源(肉块预制体、液体贴图、场景)
async loadGameAssets() {
const bundleList = ["textures", "prefabs", "scenes"];
let total = bundleList.length;
let loaded = 0;
for(const bundleName of bundleList) {
await new Promise(res => {
resources.loadBundle(bundleName, (finish, totalAll) => {
// 更新实时加载进度
const curPercent = (loaded + finish / totalAll) / total;
this.updateProgress(curPercent);
}, ()=>{
loaded++;
res(null);
})
})
}
// 全部加载完成,跳转主游戏场景
director.loadScene("Scene_Game");
}
// 更新进度条+文字
updateProgress(percent: number) {
this.progressBar.progress = percent;
this.progressText.string = `${Math.floor(percent * 100)}%`;
}
}proj.android/app/src/main/res/mipmap-xxx;values/styles.xml设置全屏启动页背景色。LaunchScreen.storyboard;| 方案 | 效果 | 适用场景 | 开发成本 |
|---|---|---|---|
| 方案一:替换引擎Splash | 静态图片,无动画,仅开机一瞬间 | 只需要换个Logo,无动态效果需求 | 极低 |
| 方案二:Loading加载场景 | 骨骼动画、液体/软体、动态进度、转场动画 | 你的肉块液体游戏、需要炫酷加载页 | 中等(推荐) |
| 方案三:原生启动图 | APP安装包冷启动黑屏阶段画面 | 安卓iOS打包,解决黑屏 | 低 |
build-templates模板目录保存自定义html/css,否则每次构建覆盖;