组件库相关
- element 的按需加载是如何实现的
- 按需加载的前提是每个组件都是独立的小模块,拥有单独的 js,样式,文档等文件。
- 使用了 babel-plugin-component 插件。这个插件会在编译阶段将完整的导入语句转换为按需导入,同时会自动引入样式。当然我们也可以手动按需引入。
import { Button } from "element-ui";
import Button from "element-ui/lib/button";
import "element-ui/lib/theme-chalk/button.css";
- vue 样式 scope 的原理?
- 解析带有 scoped 的 style 标签
- 为组件生成唯一哈希 id
- 使用 postcss 插件处理 css 选择器
- 为模板中的元素添加对应的 data 属性
从而实现样式隔离
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
- 手写一个 router
- 生成标签点击(这一点还有一个虚拟 dom 生成页面的前置条件),监听 hashchange 方法,
- 使用数组来管理当前路由信息,内部方法 push 等来操作路由栈,同时修改 url 的 hash 值(添加 go,beforeRouteEnter 等函数,方便实例调用)
- 之后使用新的 dom 来展示视图变化
- 销毁路由相关事件
class Router {
constructor(routes, options) {
if (routes && Array.isArray(routes)) {
const defaultOpts = {
mode: "history",
};
this.clickFn = null;
this.hashChangeFn = null;
this.routerStack = [];
this.opts = Object.assign({}, defaultOpts, options);
this.routeList = routes;
this.routerView = document.getElementById("router");
this.init();
} else {
throw new Error("路由格式非法");
}
}
init() {
this.addEventListener();
this.next();
}
addEventListener() {
this.clickFn = this.handleLinkClick.bind(this);
this.hashChangeFn = this.hashChange.bind(this);
this.routerLink.addEventListener("click", this.clickFn, false);
window.addEventListener("hashchange", this.hashChangeFn, false);
}
destroy() {
this.routerLink.removeEventListener("click", this.clickFn, false);
window.removeEventListener("hashchange", this.hashChangeFn, false);
}
push(args) {
if (typeof args === "string") {
this.setLocationHash(args);
} else {
const { path, query } = args;
this.setLocationHash(path, query);
}
}
go(delta) {
const l = this.routerStack.length;
if (l && l > 1) {
const tempRouter = this.routerStack.splice(l - 2, 1);
const prevHash = this.getHash(tempRouter[0]);
this.setLocationHash(prevHash);
}
}
hashChange(e) {
const { oldURL, newURL } = e;
const oldPath = this.getUrlPath(oldURL);
const newPath = this.getUrlPath(newURL);
this.matchRouteData = this.matchRouter(newPath);
const { path, vDom, query } = this.matchRouteData;
this.oldHash = oldPath;
this.beforeRouteEnter(oldPath, path, this.next.bind(this, path, query));
}
getUrlPath(url) {
const arr = url.split("#");
if (arr && arr.length === 2) {
return this.getHash(arr[1]);
}
}
getHash(str) {
const reg = /\#?([^#?]+)(\?*[^?]*)/g;
const res = str.replace(reg, `$1`);
return res;
}
handleLinkClick(e) {
if (e && e.target.classList.contains("xui-route")) {
const nowPath = e.target.getAttribute("data-path");
this.setLocationHash(nowPath);
}
}
setLocationHash(nowPath, currentQuery) {
this.matchRouteData = this.matchRouter(nowPath);
const { path, query } = this.matchRouteData;
const resQuerys = currentQuery || query;
if (resQuerys) {
location.hash = path + this.formatQuerys(resQuerys);
} else {
location.hash = path;
}
}
setLinkStyle(path) {
const tempPath = this.getHash(path);
const linkDom = this.routerLink.childNodes.forEach((item) => {
});
}
next(currentPath, currentQuery, isSaveRouter = true) {
const dataPath = currentPath || this.oldHash;
this.setLinkStyle(dataPath);
this.renderRouterView();
}
formatQuerys(querys) {
let str = "?";
for (const i in querys) {
str += `${i}=${querys[i]}`;
}
return str;
}
renderRouterView() {
const { vDom } = this.matchRouteData;
const dom = virtual2Dom(vDom);
this.routerBox.append(dom);
this.afterRouteEnter();
}
matchRouter(path) {
const tempPath = this.getHash(path);
return this.routeList.find((route) => route.path === tempPath);
}
beforeRouteEnter(from, to, next) {
next();
}
afterRouteEnter() {
}
}
- 虚拟 dom 转真实 dom 的函数
- 使用 createDocumentFragment 来创建文档碎片,拼接操作完成之后再插入到 dom 中
- 循环递归调用处理嵌套标签数据,注意样式,标签属性,以及自定义属性
const vDom = [
{
type: "div",
text: "这是列表页list",
attrs: {
style: {
color: "red",
fontSize: 20,
},
"data-type": "xui-index",
},
},
{
type: "embed",
src: "../resources/test.pdf",
attrs: {
style: {
width: "100%",
},
},
},
];
function virtual2Dom(arr) {
const content = document.createDocumentFragment();
function _add(arr, bool) {
let fragment = document.createDocumentFragment();
arr.forEach((item, index) => {
let tempDom = document.createElement(item.type);
tempDom.textContent = item.text;
fragment.appendChild(tempDom);
if (tagHasSrcs.includes(item.type)) {
tempDom.src = item.src;
}
if (item.attrs) {
for (var i in item.attrs) {
const val = item.attrs[i];
if (i === "style") {
for (var j in val) {
tempDom.style[j] = val[j];
}
} else {
tempDom.setAttribute(i, val);
}
}
}
if (item.events) {
for (var k in item.events) {
const fn = item.events[k];
if (typeof fn === "function" && htmlEvents.includes(k)) {
tempDom.addEventListener(k, fn, false);
} else {
console.warn("事件不合法");
}
}
}
if (item.children && item.children.length) {
tempDom.appendChild(_add(item.children));
}
});
return fragment;
}
content.appendChild(_add(arr));
return content;
}
- 简单写一下 Ajax
var xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readystate == 4) {
if (xhr.status >= 200 && xhr.status < 300) {
} else if (xhr.status >= 400) {
}
}
};
- 简单说说 Vue2 的响应式系统
- 使用 Object.defineProperty 给 data 函数里的每个属性都添加一个 getter,setter
- 也会给响应式属性添加 dep 实例,dep 实例主要是实现依赖收集和派发更新
- watcher 主要用来维护属性更新等,watcher 会很多,放在 dep 里进行管理
- 通过 diff 算法,将新旧 dom 通过 vnode 形式计算出来,最后 patch 到真实 dom 上