组件库相关

  1. element 的按需加载是如何实现的
    1. 按需加载的前提是每个组件都是独立的小模块,拥有单独的 js,样式,文档等文件。
    2. 使用了 babel-plugin-component 插件。这个插件会在编译阶段将完整的导入语句转换为按需导入,同时会自动引入样式。当然我们也可以手动按需引入。
import { Button } from "element-ui";
// 插件转换
import Button from "element-ui/lib/button";
import "element-ui/lib/theme-chalk/button.css";
  1. vue 样式 scope 的原理?
    1. 解析带有 scoped 的 style 标签
    2. 为组件生成唯一哈希 id
    3. 使用 postcss 插件处理 css 选择器
    4. 为模板中的元素添加对应的 data 属性 从而实现样式隔离
<style>
  .example[data-v-f3f3eg9] {
    color: red;
  }
</style>

<template>
  <div class="example" data-v-f3f3eg9>hi</div>
</template>
  1. 手写一个 router
    1. 生成标签点击(这一点还有一个虚拟 dom 生成页面的前置条件),监听 hashchange 方法,
    2. 使用数组来管理当前路由信息,内部方法 push 等来操作路由栈,同时修改 url 的 hash 值(添加 go,beforeRouteEnter 等函数,方便实例调用)
    3. 之后使用新的 dom 来展示视图变化
    4. 销毁路由相关事件
// 自定义路由库
class Router {
  constructor(routes, options) {
    // 设置路由的基本信息,方便后续对路由进行操作
    if (routes && Array.isArray(routes)) {
      const defaultOpts = {
        mode: "history",
      };
      // 路由点击和hashchange的引用
      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() {
    // 给路由绑定相关信息,比如点击,hashchange等
    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) {
    // 主要用于设置路由信息,可以是string的方式,也可以是对象{path: 'index', query: ''}
    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) {
    // 匹配 index #index  #index?a=1  #index?a=1&b=2
    // 最终返回 index
    const reg = /\#?([^#?]+)(\?*[^?]*)/g;
    const res = str.replace(reg, `$1`);
    return res;
  }
  handleLinkClick(e) {
    // 点击链接,设置url的hash
    if (e && e.target.classList.contains("xui-route")) {
      const nowPath = e.target.getAttribute("data-path");
      this.setLocationHash(nowPath);
    }
  }
  setLocationHash(nowPath, currentQuery) {
    // 设置url上的路由信息
    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) {
    // 点击设置link标签颜色
    const tempPath = this.getHash(path);
    const linkDom = this.routerLink.childNodes.forEach((item) => {
      // ...
    });
  }
  next(currentPath, currentQuery, isSaveRouter = true) {
    // 内部方法,主要用来设置路由栈,设置link颜色,渲染路由视图信息
    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() {
    // 路由视图更新后的钩子函数
  }
}
  1. 虚拟 dom 转真实 dom 的函数
    1. 使用 createDocumentFragment 来创建文档碎片,拼接操作完成之后再插入到 dom 中
    2. 循环递归调用处理嵌套标签数据,注意样式,标签属性,以及自定义属性
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%",
      },
    },
  },
];
// vdom 变真实dom
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;
}
  1. 简单写一下 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) {
      // 接口失败了
    }
  }
};
  1. 简单说说 Vue2 的响应式系统
    1. 使用 Object.defineProperty 给 data 函数里的每个属性都添加一个 getter,setter
    2. 也会给响应式属性添加 dep 实例,dep 实例主要是实现依赖收集和派发更新
    3. watcher 主要用来维护属性更新等,watcher 会很多,放在 dep 里进行管理
    4. 通过 diff 算法,将新旧 dom 通过 vnode 形式计算出来,最后 patch 到真实 dom 上

results matching ""

    No results matching ""