复制
浅拷贝
- 切片(:)
- list()
- copy()
org = [1, 2, 3, 4, 5]
o1 = org[:] # 切片
o2 = list(org) # list()
o3 = org.copy() # copy()
深拷贝
- copy 模块中的 deepcopy()
org = [1, 2, 3, 4, 5]
od1 = copy.deepcopy(org)
反转 list
- 切片(:)
- reversed()
org = [1, 2, 3, 4, 5]
o1 = org[::-1] # 切片
o2 = reversed(org) # reversed()
换行
print(5 > /
2) # True
由于链表的结构特殊,没有像数组那样方便的库方法,因此其很多操作都需要用到底层算法解决。(不过也因此可以巩固一下)
例如需要手写排序操作。
排序
根据链表的类型可选择不同的排序算法。
单链表
如果要保持时间复杂度 O(nlogn),则需要使用分治策略(归并排序),拆分链表。(详细算法实现参照排序一文)
其中 n 是链表长度。
双向链表
双向链表既可以从前遍历,又可以从后遍历,所以不只可以使用归并排序,还可以使用快速排序。(我猜的,暂未进行实践)
技巧
寻找中间节点:快慢指针,找到 mid 后再根据需求是否断链(前指空)。
dummy 节点:快速返回新的链表。
寻找倒数第 n 个节点:前后指针,前指针走 n 步,然后两个指针再一起走。
函数在运行时会自动生成一个内部对象(执行上下文),而 this 指向的就是这个对象(执行上下文),所以 this 的指向也是动态的。
this 指向(绑定)与函数调用的位置有关。
箭头函数是没有 this 的,其永远指向全局(window/undefined)。其实质是代码在编译的时候就已经定义好了。
默认绑定
在非严格模式下,全局环境中 this 绑定是 window 对象,这也被称为默认绑定。
- 直接在全局调用 this;
- 在全局直接执行函数,函数中直接调用 this;
注意: esm 默认开始严格模式,去掉
type="module就可以了。
注意在对象中某个属性的值是一个引用函数时,应该使用回调函数或者匿名函数将这个引用的函数包裹起来,避免在执行上下文的时候直接调用,而不是通过这个对象的属性访问之后才调用的。
全局(window)添加属性
- 在全局作用域下使用 var 声明变量;
- 直接使用
window.x; - 在默认绑定的情况下直接使用 this(非严格模式);
隐式绑定
作为某个对象的方法(上下文对象)调用,this 绑定的是这个对象(上下文对象),这写也被称为隐式绑定。
注意:
- 如果该函数(方法)是箭头函数,无论有几层嵌套对象,this 会进行默认绑定(window/undefined)。
匿名函数和具名函数则不受其影响。
- 如果将该对象的函数赋值给了另一个变量,在全局中直接调用该变量对应的函数,也是相当于默认绑定。这也被称为绑定丢失现象。
const obj = {
a: 10,
b: function () {
console.log(this);
},
};
const copy_obj = obj.b;
copy_obj(); // undefined / window
obj.b(); // {a: 10, b: ƒ}
new 绑定
通过 new 关键字调用的函数会生成一个实例对象,this 绑定的就是这个实例对象。
注意:如果在这个构造函数中返回了一个引用数据类型(对象、数组、函数),this 指向的就是这个引用数据类型。
实例对象可以使用构造函数中this绑定的值,因为this绑定的就是整个实例对象。如果在实例对象(this)上找不到所访问的属性,就会去它的原型对象上找,遍历整条原型链条直到找到这个值或者 null 为止(null 返回 undefined)。
对象原型
js 中每一个函数都有一个原型对象(prototype),通过 new 关键字(构造函数)生成的实例对象都可以访问这个原型对象上的属性和方法。
实例对象可以通过__proto__属性访问这个原型对象;
原型对象可通通过constructor属性访问这个实例对象的构造函数;
原型对象也有它的原型对象,直到获取到 Object 的原型的原型(null)为止,就这样组成了一条原型链。
模式
使用 this 和原型对象可以实现类似继承的效果。
todo… which parttern
显示绑定
使用call()、apply()、bind()时,this 绑定的是指定对象。
- call():一个一个传参;
- apply():传递一个数组;
- bind():直接返回一个新函数;
其他
- 可以使用
Object.create(null)创建一个没有 this 绑定的对象; - 箭头函数会继承外层函数调用的 this 绑定(window/undefined);
- es6 之前的 self 同 this 机制;
浏览器每次刷新页面之前执行其中的回调函数,宏任务。返回一个整数编号,可用于传递给cancelAnimationFrame取消动画帧。
- requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
保证其回调函数在屏幕每一次的绘制间隔中执行一次,不会出现丢帧和动画卡顿的情况。
- 隐藏的元素不会触发回调。
保证更少的 CPU、GPU 消耗。
- 浏览器专门为动画提供的 API,浏览器在运行时会针对该 API 进行优化,如果页面不是激活状态下的话,动画会自动暂停,有效节省了 CPU 开销。
setTimeout 和 setInterval
- setTimeout 和 setInterval 定时器是不精准的,设置的间隔时间其实是在线程队列中等待的时间。由于 js 的单线程机制,在插入队列前有大量任务或者其中某个任务的执行时间过长,都会影响最终的执行时间。
- 并且浏览器为了防止过多任务的堆积,对其最小间隔时间做了限制(4ms/10ms),即使你设置 setTimeout(fn, 0),回调函数也不会立即执行,而是至少在 4 毫秒后执行。
- setInterval 因为其重复执行,其误差值可能会越来越大。
小结
js 事件循环前序队列事件的阻塞、延迟,浏览器的资源调度策略(最小间隔)等问题都会影响 setTimeout 和 setInterval 的精度。
requestAnimationFrame
执行回调与系统的绘制帧率同步(保持最佳绘制效率),不会因为间隔时间过短导致过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅。让网页动画有一个统一的刷新机制,节约系统资源,提高系统性能,改善视觉体验。
应用
页面滚动事件(*)
平滑回滚到页面顶部(在 requestAnimationFrame 事件中每次回滚一小段,直到回到顶部):
const scrollToTop = () => {
const c = document.documentElement.scrollTop || document.body.scrollTop;
console.log(c);
if (c > 0) {
window.requestAnimationFrame(scrollToTop); // 平滑回滚
window.scrollTo(0, c - c / 8);
// window.scrollTo(0, c - c);
}
};
document.addEventListener("scroll", () => {
window.requestAnimationFrame(() => {
console.log("scroll");
});
});
大量数据渲染
一般做法:使用定时器(setTimeout/setInterval)控制循环 loop 函数,在 loop 函数中遍历添加限制长度的数据并添加到页面中。
或者使用分页请求,前端设置 pageSize,每次点击不同的 pageCount 就重新请求一次。
优化:使用 requestAnimationFrame 代替 setTimeout。
//需要插入的容器
const ul = document.getElementById("container");
// 插入十万条数据
const total = 100000;
// 一次插入 20 条
const once = 20;
//总页数
const page = total / once;
//每条记录的索引
const index = 0;
//循环加载数据
function loop(curTotal, curIndex) {
if (curTotal <= 0) {
return false;
}
//每页多少条
const pageCount = Math.min(curTotal, once);
function addData() {
for (let i = 0; i < pageCount; i++) {
let li = document.createElement("li");
li.innerText = curIndex + i + " : " + ~~(Math.random() * total);
ul.appendChild(li);
}
loop(curTotal - pageCount, curIndex + pageCount);
}
window.requestAnimationFrame(addData);
// setTimeout(addData, 0);
}
loop(total, index);
监控页面卡顿(监测性能)
定义一些边界值来检测网页的帧率(fps),通过在 requestAnimationFrame 中执行一些代码,并计算执行时间来判断是否页面卡顿。(requestAnimationFrame 的绘制率是 60HZ,大多数人的视觉系统能够识别的最大帧率为 60 帧每秒)
1s 中 60 帧,1 帧 16.7ms,如果超过 16.7ms,则表示页面卡顿。
水印(*)
使用 requestAnimationFrame 添加页面水印(背景图片)。
防抖(Debounce)和节流(Throttle)是两种常用的优化前端性能的技术,用于控制事件触发频率,避免过多的事件触发导致性能问题。
防抖
事件触发之后等待一定时间再执行,如果这个时间内该事件又被触发了则重新计时。
function debounce(func, delay) {
let timer;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, arguments);
}, delay);
};
}
适用场景:
- 输入框搜索建议:用户在输入时,只有在停止输入一段时间后才触发搜索请求。;
- 窗口大小调整:窗口大小调整时,只在停止调整一段时间后触发重新计算布局。
节流
一定时间间隔内,无论事件触发多少次,只会执行一次。事件只会按照一定时间间隔一次一次执行。
function throttle(func, delay) {
let isRun = true;
return function () {
if (!isRun) return;
isRun = false;
setTimeout(() => {
func.apply(this, arguments);
isRun = true;
}, delay);
};
}
setTimeout 不精准,详情见 setTimeout 一章。
适用场景:
- 滚动加载:滚动页面时,只在一定时间间隔内加载更多内容,避免频繁加载。
- 防止按钮重复点击:按钮点击后在一定时间内禁用,防止用户多次点击。
基本使用
安装 wujie
npm i wujie在主应用中引入(以 Vue 应用为例)
index.js
import { createApp } from "vue"; import App from "./App.vue"; import { startApp } from "wujie"; // 引入wujie,通过其中的startApp来设置子应用 const app = createApp(App); setupApp({ name: "唯一id", url: "子应用地址", exec: true, el: "容器", sync: true, }); app.mount("#app");
Vue 组件封装
如果使用的是 vue2 或 vue3 的应用,可以直接使用封装好的组件。
安装 wujie-vue2/ wujie-vue3
# vue2 框架 npm i wujie-vue2 -S # vue3 框架 npm i wujie-vue3 -S在主应用中引入并挂载
import { createApp } from "vue"; import App from "./App.vue"; import Wujie from "wujie-vue3"; // 引入wujie-vue3 const app = createApp(App); app.use(Wujie); // 注册 app.mount("#app");在主应用中使用,接入子应用
在此之前要先启动子应用。
<WujieVue url="http://127.0.0.1:5147" name="vue3"></WujieVue> <!--子应用vue3--> ... <WujieVue width="100%" height="100%" name="xxx" :url="xxx" :sync="true" :fetch="fetch" :props="props" :beforeLoad="beforeLoad" :beforeMount="beforeMount" :afterMount="afterMount" :beforeUnmount="beforeUnmount" :afterUnmount="afterUnmount" </WujieVue> ...
封装原理
wujie-vue3是按照一个组件的原理去实现的。
import {
defineComponent,
h,
getCurrentInstance,
onMounted,
watch,
onBeforeUnmount,
} from "vue";
import { startApp, bus } from "wujie";
import type { PropType } from "vue";
import { Props } from "./type";
const WujieVue = defineComponent({
props: {
width: { type: String, default: "" },
height: { type: String, default: "" },
name: { type: String, default: "", required: true },
loading: { type: HTMLElement, default: undefined },
url: { type: String, default: "", required: true },
sync: { type: Boolean, default: undefined },
prefix: { type: Object, default: undefined },
alive: { type: Boolean, default: undefined },
props: { type: Object, default: undefined },
attrs: { type: Object, default: undefined },
replace: {
type: Function as PropType<Props["replace"]>,
default: undefined,
},
fetch: { type: Function as PropType<Props["fetch"]>, default: undefined },
fiber: { type: Boolean, default: undefined },
degrade: { type: Boolean, default: undefined },
plugins: { type: Array as PropType<Props["plugins"]>, default: null },
beforeLoad: {
type: Function as PropType<Props["beforeLoad"]>,
default: null,
},
beforeMount: {
type: Function as PropType<Props["beforeMount"]>,
default: null,
},
afterMount: {
type: Function as PropType<Props["afterMount"]>,
default: null,
},
beforeUnmount: {
type: Function as PropType<Props["beforeUnmount"]>,
default: null,
},
afterUnmount: {
type: Function as PropType<Props["afterUnmount"]>,
default: null,
},
activated: {
type: Function as PropType<Props["activated"]>,
default: null,
},
deactivated: {
type: Function as PropType<Props["deactivated"]>,
default: null,
},
},
setup(props, { emit }) {
const init = () => {
const instance = getCurrentInstance();
startApp({
// 微前端初始化所需方法
name: props.name,
url: props.url,
el: instance?.refs.wujie as HTMLElement,
loading: props.loading,
alive: props.alive,
fetch: props.fetch,
props: props.props,
attrs: props.attrs,
replace: props.replace,
sync: props.sync,
prefix: props.prefix,
fiber: props.fiber,
degrade: props.degrade,
plugins: props.plugins,
beforeLoad: props.beforeLoad,
beforeMount: props.beforeMount,
afterMount: props.afterMount,
beforeUnmount: props.beforeUnmount,
afterUnmount: props.afterUnmount,
activated: props.activated,
deactivated: props.deactivated,
});
};
// name和url可能是动态的,所以需要监听它们的变化,然后再初始化
watch([props.name, props.url], () => {
init();
});
// 传参函数
const hanlderEmit = (event: string, ...args: any[]) => {
emit(event, ...args); // 暴露给父组件(给bus做了一个中转)
};
onMounted(() => {
bus.$onAll(hanlderEmit); // 监听所有事件
init();
});
onBeforeUnmount(() => {
bus.$offAll(hanlderEmit); // 销毁
});
return () =>
h("div", {
style: {
width: props.width,
height: props.height,
},
ref: "wujie", // 便于之后获取到这个dom
});
},
});
WujieVue.install = function (app) {
app.component("WujieVue", WujieVue); // 组件注册
};
export default WujieVue;
注意:
- PropType:有很多复杂的数据结构是办法没有标注类型的(在 ts 中类型与接口不对应会报错),所以 vue3 提供一个 PropType 来与定义的结构相对应。(此处的类型对应的就是导出的 Props 里的类型,见下文)
- getCurrentInstance:在
setup()中的this还是 undefined,所以没有办法通过this.$refs来读取。在 vue3 中可以使用getCurrentInstance()来获取组件的实例。- bus:wujie 封装的用于传参的对象。
定义所需参数(wujie 的参数)
import type { plugin } from "wujie";
type lifecycle = (appWindow: Window) => any;
interface Props {
/** 唯一性用户必须保证 */
name: string;
/** 需要渲染的url */
url: string;
/** 需要渲染的html, 如果用户已有则无需从url请求 */
html?: string;
/** 渲染的容器 */
loading?: HTMLElement;
/** 路由同步开关, false刷新无效,但是前进后退依然有效 */
sync?: boolean;
/** 子应用短路径替换,路由同步时生效 */
prefix?: { [key: string]: string };
/** 子应用保活模式,state不会丢失 */
alive?: boolean;
/** 注入给子应用的数据 */
props?: { [key: string]: any };
/** js采用fiber模式执行 */
fiber?: boolean;
/** 子应用采用降级iframe方案 */
degrade?: boolean;
/** 自定义运行iframe的属性 */
attrs?: { [key: string]: any };
/** 自定义降级渲染iframe的属性 */
degradeAttrs?: { [key: string]: any };
/** 代码替换钩子 */
replace?: (codeText: string) => string;
/** 自定义fetch,资源和接口 */
fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
/** 子应插件 */
plugins: Array<plugin>;
/** 子应用生命周期 */
beforeLoad?: lifecycle;
/** 没有做生命周期改造的子应用不会调用 */
beforeMount?: lifecycle;
afterMount?: lifecycle;
beforeUnmount?: lifecycle;
afterUnmount?: lifecycle;
/** 非保活应用不会调用 */
activated?: lifecycle;
deactivated?: lifecycle;
}
export { Props };
React 组件封装
wujie 基于react的组件封装,使用参数基本和 Vue 的封装类似,只是换了 react 的写法。
预加载
- 解构出 preloadApp()
- 使用 preloadApp(),将
exec设置为 true 开启预加载
import { createApp } from "vue";
import App from "./App.vue";
import Wujie from "wujie-vue3";
const { preloadApp } = Wujie; // 解构出preloadApp()
const app = createApp(App);
app.use(Wujie);
app.mount("#app");
preloadApp({ name: "react", url: "http://127.0.0.1:5147", exec: true }); // 开启预加载(参数和setupApp相同,多了个exec)
应用通信
wujie 提供三种通信方式:
window 通信
由于子应用运行的iframe的src和主应用是同域的,所以相互可以直接通信。
主应用调用子应用的全局数据
window.document.querySelector("iframe[name=子应用id]").contentWindow.xxx;子应用调用主应用的全局数据
window.parent.xxx;
例子:
主应用定义一个全局变量:
var a = "abc";子应用通过
window.parent.a访问:const getParentA = () => { console.log(alter(window.parent.a)); };
注意:由于同源策略的限制,如果 iframe 的源与主页面的源不同,那么在控制 iframe 中的元素时可能会受到限制。(如果需要跨域控制 iframe 中的元素,可以考虑使用 postMessage API 来进行通信)
props 通信
主应用可以通过
props注入数据和方法<WujieVue name="xxx" url="xxx" :props="{ data: xxx, methods: xxx }" ></WujieVue>例子:
<template> <WujieVue url="http://127.0.0.1:5147" name="vue3" :props="{age: 23, name: '张三'}" ></WujieVue> </template>子应用可以通过
$wujie来获取:无界对子应用注入了
$wujie对象,可以通过$wujie或者window.$wujie获取。const props = window.$wujie?.props; // {data: xxx, methods: xxx}注意:直接写
window.$wujie.props可能能用,但是会报错,这是因为没有写$wujie的声明文件,在全局声明一下就好了:declare global { interface Window { $wujie: { props: Record<string, any>; }; } }
eventBus 通信
无界提供一套去中心化的通信方案,主应用和子应用、子应用和子应用都可以通过这种方式进行通信。
通过
$on监听,$emit触发双向传递消息。
主应用使用
// 如果使用wujie import { bus } from "wujie"; // 如果使用wujie-vue import WujieVue from "wujie-vue"; const { bus } = WujieVue; // 如果使用wujie-react import WujieReact from "wujie-react"; const { bus } = WujieReact; // 主应用监听事件 bus.$on("事件名字", function (arg1, arg2, ...) {}); // 主应用发送事件 bus.$emit("事件名字", arg1, arg2, ...); // 主应用取消事件监听 bus.$off("事件名字", function (arg1, arg2, ...) {});例子:
bus.$on("vue3", (data: any) => { console.log(date, "我是主应用"); });子应用使用
// 子应用监听事件 window.$wujie?.bus.$on("事件名字", function (arg1, arg2, ...) {}); // 子应用发送事件 window.$wujie?.bus.$emit("事件名字", arg1, arg2, ...); // 子应用取消事件监听 window.$wujie?.bus.$off("件名字", function (arg1, arg2, ...) {});例子:
const send = () => { window.$wujie.bus.$emit("vue3", "我是子应用"); };