Giter VIP home page Giter VIP logo

hitao123.github.io's Introduction

Hi there 👋

hitao123's github stats

hitao123.github.io's People

Contributors

hitao123 avatar

Stargazers

 avatar

Watchers

 avatar  avatar

hitao123.github.io's Issues

如何搭建一个组件库

hui vue 组件库

一年之前觉得写一个组件就可以了,能给团队的小伙伴用就很开心了,后来看了 vant 有赞团队的组件库,很喜欢作者那种风格,参照一下,实现一个组件库,下面记录了一下自己实现组件库的过程

需求

  1. 方便组件添加
  2. 有各种组件用法的详细描述
  3. 能在线查看的, pc 和 mobile 都能查看 demo
  4. 能发布到 npm , 通过 npm i hui --save-dev, 项目中 import { Button } from 'hui' 引用即可
  5. 对于像 toast, dialog,这样的就不能像普通组件一样嵌入模板里面,需要通过函数的调用方式,
    通过options 来实现配置化

方便组件添加

  1. 我是先通过 vue-cli 生成了一个项目结构,接着添加组件路由,每一次新添加组件都要添加路由感觉好麻烦后面看了 vant build 目录脚本,通过 node 命令自动去读取目录,生成对应的路由,无需手动添加路由,顿时感觉先进了许多。可以看这次commit

此时项目结构目录

├── build # 构建脚本
├── config # 配置
├── docs # 文档网站
├── packages # 组件文件
├── test # 单元测试
└── utils # 公用方法
  1. 后来仔细看了 vant 的目录组织,觉得合理,重新调整了项目结构,里面也有调整
Hui
├─ build            # 构建脚本
├─ docs             # 文档网站
├─ packages         # 组件文件
├─ test             # 单元测试
└─ types            # 类型定义

有各种组件用法的详细描述

最开始组件库是没有用法描述的,只有在一个简单 mobile 的列表的 demo 展示,作为一个组件库展示的站点基本结构都是一样的, 导航部分,侧边栏是不同的组件,通过切换可以到不同的组件,中间作为展示部分,右边是模拟器,下半部分可以有导航,上下分页,页脚可以有响应的链接, vant 自己有一个 vant-doc 的组件,只需要相应的配置,就可以灵活的扩展你的站点了,我们的组件描述刚好可以放在 中间的 container 部分

因为每一个 packages 里面的组件都有相应的对该组件的描述,我们可以把这些 md 文件解析成对应的路由组件即可,这样就实现了就可以查看用法了,但是这又有一个问题, 我们平常只处理 js css less vue 这种格式的文件,md 的文件也需要相应的 webpack-loader 去处理, vant 里面是自己实现了一个 fast-vue-md-loader, 下面是核心代码

const hljs = require('highlight.js');
const MarkdownIt = require('markdown-it');

const wrapper = content => `
<template>
  <section v-html="content" v-once />
</template>
<script>
export default {
  created() {
    this.content = unescape(\`${escape(content)}\`);
  }
};
</script>
`;

const highlight = (str, lang) => lang && hljs.getLanguage(lang) ? hljs.highlight(lang, str, true).value : '';
const parser = new MarkdownIt({
  html: true,
  highlight
});

// webpack 会加载这个loader 当遇到 .md 文件
module.exports = function(source, options) {
  this.cacheable && this.cacheable();

  options = {
    wrapper,
    ...options
  };

  return options.wrapper(parser.render(source));
}

看到这里是不是很熟悉,这个把这个文件处理成类似 .vue 单文件,然后剩下的事情交给 vue-loader 做,就像当时引用组件一样,是不是很巧妙

能在线查看的, pc 和 mobile 都能查看 demo

在vant 发现一个 gh-pages 分支,但是代码都是静态文件,之前是了解 github 支持每个分支创建一个 gh-pages 分支通过 https://username.github.io/reponame/ 这种方式能访问你每一个项目 gh-pages 的静态资源, 我们可以把vue-cli build 之后的 dist 目录放到这个分支上就可以访问了, 这样解决了第三个问题,在根据 UA 来判断来使用哪一个入口,就可以实现在 pc 和移动端的访问了

PC 端 查看地址

能发布到 npm 支持不同打包工具

mint-ui 只能配合 webpack 打包工具使用,如何做到 fis3 这些打包工具也能使用呢? vant 使用的做法是,将package 通过 babel 打包成 commonjs 模块来实现其他工具也可以使用的

H5 直播相关技术

最近有接触到和直播相关的业务,就这一块业务深入探讨一下h5直播相关技术,分两个方面探讨,技术原理和实现方案

技术原理

音视频编码基础

视频编码:视频流传输中最为重要的编解码标准有国际电联的H.261、H.263、H.264。
音频编码:同视频编码类似,将原始的音频流按照一定的标准进行编码,上传,解码,同时在播放器里播放,当然音频也有许多编码标准,例如PCM编码,WMA编码,AAC编码等等

常见的直播协议

RTMP: 底层基于TCP,在浏览器端依赖Flash。
HTTP-FLV: 基于HTTP流式IO传输FLV,依赖浏览器支持播放FLV。
WebSocket-FLV: 基于WebSocket传输FLV,依赖浏览器支持播放FLV。WebSocket建立在HTTP之上,建立WebSocket连接前还要先建立HTTP连接。
HLS: Http Live Streaming,苹果提出基于HTTP的流媒体传输协议。HTML5可以直接打开播放。
RTP: 基于UDP,延迟1秒,浏览器不支持。

协议参数对比

传输协议 播放器 延迟 内存 CPU
RTMP Flash 1s 430M 11%
HTTP-FLV Video 1s 310M 4.4%
HLS Video 20s 205M 3%

直播模型

一个通用的直播模型一般包括三个模块:主播方、服务器端和播放端

f9a6a0a06cffc81dfad041886

实现方案

直接借助 原生 video 标签,直播流采用 HLS(.m3u8 格式)就可以简单的实现

<video id="video"  src="xx.m3u8" type="application/x-mpegURL"  x-webkit-airplay="h5" x5-video-player-fullscreen="true" webkit-playsinline="true" x5-playsinline="true" playsinline="true" preload="auto" ><h5>您的系统不支持Video标签</h5>
</video>
  1. 添加 controls 实现原生控制,如果想要定制样式就不要加上这一个参数
  2. 视频播放时局域播放,不脱离文档流
  3. x5-video-player-fullscreen 全屏设置
  4. poster 可以给视频添加封面

完整的技术方案demo 如下(mac 系统)

  1. 下载livego,注意选对你的操作系统和位数。
    解压,执行livego,服务就启动好了。它会启动RTMP(1935端口)服务用于主播推流,以及HTTP-FLV(7001端口)服务用于播放,以及 HLS (7002 端口)
  2. 使用OBS来推流,注意要配置好OBS,设置参考链接,将视频流格式设置 m3u8 格式(这个要注意设置一下)
  3. 前端代码实现
  4. 在电脑 chrome 浏览器端打不开,用手机扫码在手机浏览器观看,想在电脑端观看的可以使用链接的 flvjs播放器,操作步骤同上

参考链接

前端异常监控系统的落地

要想做前端监控,我们需要做到四个部分, 数据收集,数据清洗,数据分析,监控告警,数据部分先不管,先看下前端收集数据部分,是通过 web api Performance 实现的, Performance 的兼容性现在也还是可以的,下面主要看一下实现

我们可以在 onload 的时候发送性能数据到后端,在 js 执行错误的时候

初始化

export const monitorInit = () => {
  const monitor = {
    // 上报地址
    url: '',
    // 性能数据
    performance: {},
    // 资源数据
    resources: {},
    // 错误
    errors: [],
    // 用户数据
    user: {},
    // 清空 error 信息
    clearError() {
      monitor.errors = [];
    },
    // 上传监控数据
    upload() {
      // 自定义上传
      const data = {
        performance,
        resources,
        errors,
        user,
      };
      fetch(monitor.url, {
        method: 'POST',
        body: JSON.stringify(data),
        headers: new Headers({
          'Content-Type': 'application/json',
        }),
      });
    },
    // 设置数据上传地址
    setURL(url) {
      monitor.url = url;
    },
  };

  window.onload = () => {
    if (window.requestIdleCallback) {
      window.requestIdleCallback(() => {
        monitor.performance = getPerformance();
        monitor.resources = getResources();
      });
    } else {
      setTimeout(() => {
        monitor.performance = getPerformance();
        monitor.resources = getResources();
      }, 0);
    }
  };
};

获取性能数据

export const getResources = () => {
  // eslint-disable-next-line no-undef
  if (!window.performance) {
    return;
  }

  // eslint-disable-next-line no-undef
  const data = window.performance.getEntriesByType('resource');

  const resource = {
    xmlhttprequest: [],
    link: [],
    css: [],
    img: [],
    script: [],
    other: [],
    fetch: [],
    beacon: [],
    time: new Date().getTime(),
  };


  data.forEach((item) => {
    const resourceTypeArray = resource[item.initiatorType];

    // eslint-disable-next-line no-unused-expressions
    resourceTypeArray && resourceTypeArray.push({
      // 资源名称
      name: item.name,
      // 资源加载耗时
      duration: item.duration,
      // 资源大小
      size: item.transferSize,
    });
  });

  // eslint-disable-next-line consistent-return
  return resource;
};

export const getPerformance = () => {
  // eslint-disable-next-line no-undef
  if (!window.performance) {
    return;
  }

  // eslint-disable-next-line no-undef
  const { timing } = window.performance;

  const performance = {
    // 重定向耗时
    redirect: timing.redirectEnd - timing.redirectStart,
    // 白屏时间
    // whiteScreen: whiteScreen
    // dom 渲染耗时
    dom: timing.domComplete - timing.domLoading,
    // dns 查询时间
    dns: timing.domainLookupEnd - timing.domainLookupStart,
    // 加载时间
    domContentLoaded: timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart,
    load: timing.loadEventEnd - timing.loadEventStart,
    time: new Date(),
  };

  // eslint-disable-next-line consistent-return
  return performance;
};

获取错误数据

  // 捕获资源加载错误
  window.addEventListener('error', (e) => {
    const { target } = e;

    if (target !== window) {
      monitor.errors.push({
        type: target.localName,
        url: target.src || target.href,
        msg: `${target.src || target.href}is load error`,
        time: new Date().getTime(),
      });
    }
  });

  // 捕获 js 执行错误
  window.onerror = (msg, url, row, col, error) => {
    monitor.errors.push({
      type: 'javascript',
      row,
      col,
      msg: error && error.stack ? error.stack : msg,
      url,
      time: new Date().getTime(),
    });
  };

  // 捕获 promise 错误
  window.addEventListener('unhandledrejection', (e) => {
    monitor.errors.push({
      type: 'promise',
      msg: (e.reason && e.reason.msg) || e.reason || '',
      time: new Date().getTime(),
    });
  });

image

Performance

Vue 如何监听一个对象和数组的变化

监听一个对象和数组的变化

大家可能对 vue 使用 defineProperty 方法来实现底层双向绑定也很熟悉了,下面简单实现一下简单的监听一个对象和数组的变化

/**
 * 检测对象属性被更改
 */

const ARRAY = 0;
const OBJECT = 1;

/**
 * 数组变异方法
 * 这几个方法会改变原来数组
 */
const arrayAugmentations = [];
const aryMethods = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];

aryMethods.forEach(method => {
  let original = Array.prototype[method];
  arrayAugmentations[method] = function() {
    let result = original.apply(this, arguments);
    console.log('🍺 我是数组我被设置了', result)
    return result;
  };
});

/**
 * 对象添加新属性
 * @param {*} data 
 * @param {*} type 
 * 
 */
const objectAugmentations = {};

Object.defineProperty(objectAugmentations, '$add', {
  value: function(key, value) {
    if (this.hasOwnProperty(key)) return;
    Object.defineProperty(this, key, {
      configurable: true,
      get: function() {
        console.log('💖 我被获取了 我的值是', value)
        return value
      },
      set: function(val) {
        console.log('🐂 我被设置了 我设置的新值是', val)
        if (val === value) return
        value = val;
        return value
      }
    });
  },
  configurable: true,
});

Object.defineProperty(objectAugmentations, '$delete', {
  value: function(key) {
    if (!this.hasOwnProperty(key)) return;
    console.log('⛰ 我被删除了', key)
    delete this[key];
  },
  configurable: true,
  writable: true
});


function Observer(data, type) {
  if (!(this instanceof Observer)) throw Error('should be called by new')
  this.data = data;
  console.log(data, '===>');
  if (type === ARRAY) {
    data.__proto__ = arrayAugmentations; // eslint-disable-line
    this.link(data);
  } else if (type === OBJECT) {
    data.__proto__ = objectAugmentations; // eslint-disable-line
    this.walk(data);
  }
};

Observer.prototype.walk = function(data) {
  if (typeof data !== 'object') return;
  for(key in data) {
    if (data.hasOwnProperty(key)) {
      let value = data[key];
      if (typeof value == 'object') {
        Observer.create(value)
      }
      this.convert(key, value);
    }
  }
}

Observer.prototype.link = function(items) {
  items.forEach((value) => {
    let ob = Observer.create(value);
    if (!ob) return;
  });
};

Observer.prototype.convert = function(key, value) {
  console.log(key)
  Object.defineProperty(this.data, key, {
    configurable: true,
    enumerable: true,
    get: function() {
      console.log('💖 我被获取了 我的值是', value)
      return value
    },
    set: function(val) {
      console.log('🐂 我被设置了 我设置的新值是', val)
      if (val === value) return
      value = val;
      return value
    }
  });
}

/**
 * 根据不同的数据类型,调用observer构造函数
 * @param value {Any} 数据
 * @returns {Observer}
 */
Observer.create = function(value) {
  if (Array.isArray(value)) {
    return new Observer(value, ARRAY);
  } else if (typeof value === 'object') {
    return new Observer(value, OBJECT);
  }
};




let data = {
  a: 1,
  b: {
    c: {
      d: 3
    }
  },
  arr: [4, 5]
}

let app = Observer.create(data);

console.log()
console.log('====start==')
console.log(data.a);
data.a = 'aaa';
console.log(data.a)
console.log()
console.log(data.b.c.d);
data.b.c.d = 'dddd';
console.log(data.b.c.d)
console.log()
console.log(data.arr);
console.log()

console.log(data.arr.push(100))
console.log(data.arr)
console.log()
console.log(app.data.$add('age', 25))
console.log(data.age)
console.log(app.data.$delete('age'))
console.log('====end==')

参考

技术文章模板

开头简单介绍背景
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX摘要,包含关键下面要介绍的内容

本文包括以下内容:

  1. XXXXXXXXXXXXXXX
  2. XXXXXXXXXXXXX
  3. XXXXXXXXXXXXX

主题一

中间针对一些技术术语可以用 tips 解释一下

主题二

中间针对一些技术术语可以用 tips 解释一下

主题三

中间针对一些技术术语可以用 tips 解释一下

总结

模板和数据流

问题引出

data = {
  male: 'boy',
  hobby: 'code'
}
`I am a <% male > and i love <% hobby>`

解析模板并输出 I am a boy and i love code

重新认识 Babel

babel 是个转换器,可以将 ES6 转换为 ES5,从而在现有环境执行, 我们在项目中用到这个,但是只知道 babel 做了这一个工作,做一些配置,其他的就交给打包工具了,Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。如果想使用这些新的对象和方法,必须使用 babel-polyfill,为当前环境提供一个垫片。

下面我们通过初始化一个项目来看一下 我们项目的依赖在做一些什么工作,先来看一个简单的项目

simple

step one

初始化一个项目,使用 babel-cli 处理

mkdir learn-babel
npm init
touch .babelrc
touch example.js
npm i babel-cli babel-preset-env babel-preset-stage-2 --save-dev

step two

将下面一段配置复制到 babelrc

{
  "presets": [
    "env",
    "stage-2"
  ],
  "plugins": []
}

编辑 example.js

class Vue {
  constructor() {
    let a;
    const b = 10;
  }
  add(a, b) {
    return a + b 
  }
}


function React() {
  const b = 'react'
}

const Augular = 'angular';

修改 package.json script

  "scripts": {
    "build": "babel example.js --out-file compiled.js"
  },

npm run build

查看 compiled.js, 省略部分

'use strict';

var _createClass = function () { ... }();

function _classCallCheck(instance, Constructor) { ... }

var Vue = function () {
  function Vue() {
    _classCallCheck(this, Vue);

    var a = void 0;
    var b = 10;
  }

  _createClass(Vue, [{
    key: 'add',
    value: function add(a, b) {
      return a + b;
    }
  }]);

  return Vue;
}(); // 返回构造函数

function React() {
  var b = 'react'; // 直接转换成 var, 如果下面赋值,编译会直接报错
}

var Augular = 'angular'; // 直接转换成 var

babel-core

最近在写 hui 这个组件库的时候,在对单文件组织进行编译的时候发现需要用到 babel-core, 需要调用Babel的API进行转码,就要使用babel-core模块

step one

还是以上面项目为例

npm i babel-core --save-dev

touch babel.js

step two

const babel = require('babel-core');
const path = require('path');
const options = {
  babelrc: true,
  extends: path.resolve(__dirname, './.babelrc')
}

const res = babel.transform('class Vue {constructor() {let a;const b = 10;}add(a, b) {return a + b}} function React() {const b = "react"} const Augular = "angular";', options);
console.log(res)
// => { code, map, ast }

// 文件转码(异步)
babel.transformFile('example.js', options, function(err, result) {
  result; // => { code, map, ast }
});

// 文件转码(同步)
babel.transformFileSync('example.js', options);
// => { code, map, ast }

// // Babel AST转码
// babel.transformFromAst(ast, code, options);

step three

node babel.js

{ 
  metadata: { 
    usedHelpers: [ 'createClass', 'classCallCheck' ],
    marked: [],
    modules: { imports: [], exports: [Object] } 
  },
  options: { 
    filename: 'unknown',
    filenameRelative: 'unknown',
    inputSourceMap: undefined,
    env: {},
    mode: undefined,
    retainLines: false,
    highlightCode: true,
    suppressDeprecationMessages: false,
    presets: [],
    plugins:
    [ [Array],
      [Array],
      ...
    ],
    ignore: [],
    only: undefined,
    code: true,
    metadata: true,
    ast: true,
    extends: undefined,
    comments: true,
    shouldPrintComment: undefined,
    wrapPluginVisitorMethod: undefined,
    compact: 'auto',
    minified: false,
    sourceMap: undefined,
    sourceMaps: undefined,
    sourceMapTarget: 'unknown',
    sourceFileName: 'unknown',
    sourceRoot: undefined,
    babelrc: true,
    sourceType: 'module',
    auxiliaryCommentBefore: undefined,
    auxiliaryCommentAfter: undefined,
    resolveModuleSource: undefined,
    getModuleId: undefined,
    moduleRoot: undefined,
    moduleIds: false,
    moduleId: undefined,
    passPerPreset: false,
    parserOpts: false,
    generatorOpts: false,
    basename: 'unknown' 
  },
  ignored: false,
  code: '"use strict";\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value"in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }\n\nvar Vue = function () {\n  function Vue() {\n    _classCallCheck(this, Vue);\n\n    var a = void 0;var b = 10;\n  }\n\n  _createClass(Vue, [{\n    key: "add",\n    value: function add(a, b) {\n     return a + b;\n    }\n  }]);\n\n  return Vue;\n}();\n\nfunction React() {\n  var b = "react";\n}var Augular = "angular";',
  ast:
    Node {
      type: 'File',
      start: 0,
      end: 135,
      loc: SourceLocation { start: [Position], end: [Position] },
      program:
      Node {
        type: 'Program',
        start: 0,
        end: 135,
        loc: [SourceLocation],
        sourceType: 'module',
        body: [Array],
        directives: [Array],
        _letDone: true },
      comments: [],
      tokens:
      [ [Token],
        [Token],
        ...
      ] 
    },
  map: null 
}

可以看到 code 部分和上面 babel 命令行一样

babel-polyfill

babel-polyfill 为我们提供了一些什么功能,看名称 polyfill,应该知道是打补丁的,我们可以看我们在什么时候需要引用它,它的工作原理又是什么, 简单来说, babel-runtime 是供编译模块复用工具函数。babel-polyfill是转译没有的api,列入 ES6 里面 的 HashMap

在实现 hui 的过程中,了解到 babel-polyfillbabel-transform-runtime 是有区别的babel 官网解释, 如果在工具或者库里面寻找不会改变全局的,选择 transform-runtime plugin,这也意味着你不能使用像 Array.prototype.includes 这样的方法,怎么就改变了全局方法呢?查阅源码去寻找答案

// babel-polyfill  lib/index.js 依赖着三个模块,顺着这三个模块找一下

require("core-js/shim");

require("regenerator-runtime/runtime");

require("core-js/fn/regexp/escape");

if (global._babelPolyfill) {
  throw new Error("only one instance of babel-polyfill is allowed");
}

global._babelPolyfill = true;
// require("core-js/shim"); 依赖部分 就有es6.object.assign

require('./modules/es6.symbol');
require('./modules/es6.object.create');
require('./modules/es6.object.define-property');
require('./modules/es6.object.define-properties');
require('./modules/es6.object.get-own-property-descriptor');
require('./modules/es6.object.get-prototype-of');
require('./modules/es6.object.keys');
require('./modules/es6.object.get-own-property-names');
require('./modules/es6.object.freeze');
require('./modules/es6.object.seal');
require('./modules/es6.object.prevent-extensions');
require('./modules/es6.object.is-frozen');
require('./modules/es6.object.is-sealed');
require('./modules/es6.object.is-extensible');
require('./modules/es6.object.assign');
require('./modules/es6.promise');

我们来看 ./modules/es6.object.assign 是不是有对 全局有污染

...

参考文档

React hoc 探究

2018年都跟随公司的技术栈一直在写 Vue, 外边还有好多都是用 React 的,像 阿里系基本就是 React 技术栈的,想去阿里的同学, React 要下功夫,半年前业务用到 React ,当时在 youtubu 视频看到 es6 的装饰模式在 React 的运用,感觉离自己还远,当时就有 HOC(高阶组件)对应有 高阶函数(函数作为入参的函数),看到作者在使用 recompose 这个库,但是来 github 看了一下,发现 star 很多,使用它做HOC, 现在回过来看一下这样做的目的是什么,React 16.8 版本支持 React Hooks ,我看 recompose 作者已经鼓励去使用 React Hooks 了,感觉是 recompose 进入了标准,在 React 官方实现了

redux

redux 使用入门 这是最开始学习 redux 的时候写的如何使用 redux

react-redux

recompose

webpack 运行原理探究

webpack 运行原理探究

一直在使用 webpack 打包项目,对其中的原理一直不是很了解,想抽一点时间了解整个 webapck 构建过程,loader 机制
插件机制,code splitting tree shaking 有所了解,我是以 webapck v1.14.0 进行研究的, 代码仓库

demo1 只打包一个入口文件js, 比较简单,可以看看打出来的是什么东西,在浏览器里面是否可以正常运行

(function(modules) {
   // 模块缓存
   var installedModules = {}; // 私有属性
   // require function
   function __webpack_require__(moduleId) {
     // 判断模块是否在缓存中
     if(installedModules[moduleId]) return installedModules[moduleId].exports;
     // 创建一个新模块并放到缓存中
     var module = installedModules[moduleId] = {
       exports: {},
       id: moduleId,
       loaded: false
     };
     // 执行模块函数 这里能获取到外边参数, 如果这里没有模块导出,这里函数会被执行以下
     modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
      // 将模块置位已加载
      module.loaded = true;
       // 返回module 的 exports 属性, 看到这里才明白所有模块需要 module.exports, 你的模块会被导出
       return module.exports;
 }
 // 暴露 modules 对象 (__webpack_modules__)
 __webpack_require__.m = modules;
 // 暴露 module cache
 __webpack_require__.c = installedModules;
 // __webpack_public_path__
 __webpack_require__.p = "";
 // 加载入口模块 并返回 exports
 return __webpack_require__(0);
 })
 ([
/* 0 */
 (function(module, exports, __webpack_require__) {
  module.exports = __webpack_require__(1);
 }),
/* 1 */
 (function(module, exports, __webpack_require__) {
	var util1 = __webpack_require__(2)
	var util2 = __webpack_require__(3)
	// var css1 = require('./less/style.less')
	// var css2 = require('./less/style2.less')
	function OutInfo() {
	  util1('hahah');
	  util2('hbo');
	}
	OutInfo();
 }),
/* 2 */
 (function(module, exports, __webpack_require__) {
	var util2 = __webpack_require__(3);
	module.exports = function(name) {
	  console.log(util2(name) + '----' + '我是 util2方法 在 util1 里面')   
	}
 }),
/* 3 */
 (function(module, exports) {
	module.exports = function(name) {
	  console.log(name)
	}
 })
 ]);
 // 简单化就是一个自执行函数,modules 接受一个函数数组
 (function(modules) {
	 /// 
 })([(function(module, exports, __webpack_require__) { ... }),  (function(module, exports, __webpack_require__) { ... }), (function(module, exports) { ... })])

参考资料

实现一个简单的 promise

const PENDING = Symbol('pending');
const RESOLVE = Symbol('resolve');
const REJECT = Symbol('reject');

class PromiseA {
  constructor(executor) {
    this.state = PENDING;
    this.value = null;
    this.reason = null;
    this.onFulfilledQueue = [];
    this.onRejectedQueue = [];

    try {
        executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
        this.reject(e);
    }
  }

  static resolve(param) {
    if (param instanceof PromiseA) {
      return param;
    }

    return new PromiseA((resolve, reject) => {
      if (param && (typeof param === 'object' || typeof param === 'function')) {
        setTimeout(() => {
          param.then(resolve, reject);
        });
      } else {
        resolve(param);
      }
    });
  }

  static reject(param) {
    return new PromiseA((resolve, reject) => {
      reject(param);
    });
  }

  static race(promises) {
    return new PromiseA((resolve, reject) => {
      if (promises.length > 0) {
        for (let i = 0; i < promises.length; i++) {
          PromiseA.resolve(promises[i]).then(data => {
            resolve(data);
          }, (err) => {
            reject(err);
          })
        }
      }
    });
  }

  static all(promises) {
    return new PromiseA((resolve, reject) => {
      let count = 0;
      let result = [];
      for (let i = 0; i < promises.length; i += 1) {
        PromiseA.resolve(promises[i]).then(data => {
          result[i] = data;
          count += 1;
          if (count === promises.length) {
            resolve(result);
          }
        }, (err) => {
          reject(err);
        })
      }
    });
  }

  resolve(value) {
    if (this.state === PENDING) {
        this.state = RESOLVE;
        this.value = value;
        this.onFulfilledQueue.forEach(fn => {
            fn(value);
        });
    }
  }

  reject(reason) {
    if (this.state === PENDING) {
        this.state = REJECT;
        this.reason = reason;
        this.onRejectedQueue.forEach(fn => {
            fn(reason)
        });
    }
  }

  resolvePromise(promise, x, resolve, reject) {
    if (promise === x) {
      throw TypeError('cycle chain')
    }

    if (x && (typeof x === 'object' || typeof x === 'function')) {
      let flag = false;

      try {
        let then = x.then;
        if (typeof then === 'function') {
          then.call(x, (res) => {
            if (flag) {
              return;
            }
            flag = true;
            this.resolvePromise(promise, res, resolve, reject);
          }, (err) => {
            if (flag) {
              return;
            }
            flag = true;
            reject(err);
          });
        } else {
          if (flag) {
            return;
          }
          flag = true;
          resolve(x);
        }
      } catch (e) {
        if (flag) {
          return;
        }
        flag = true;
        reject(e);
      }
    } else {
      resolve(x);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
  
    let promise = new PromiseA((resolve, reject) => {
        if (this.state === RESOLVE) {
            setTimeout(() => {
              try {
                let result = onFulfilled(this.value);
                this.resolvePromise(promise, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            })
        } else if (this.state === REJECT) {
            setTimeout(() => {
              try {
                let result = onRejected(this.reason);
                this.resolvePromise(promise, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });
        } else if (this.state === PENDING) {
            this.onFulfilledQueue.push(() => {
              setTimeout(() => {
                try {
                  let result = onFulfilled(this.value);
                  this.resolvePromise(promise, result, resolve, reject);
                } catch (e) {
                  reject(e);
                }
              })
            });
            this.onRejectedQueue.push(() => {
              setTimeout(() => {
                try {
                  let result = onRejected(this.reason);
                  this.resolvePromise(promise, result, resolve, reject);
                } catch (e) {
                  reject(e);
                }
              });
            });
        }
    });

    return promise;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  finally(callback) {
    return this.then((value) => {
      return PromiseA.resolve(callback()).then(() => {
        return value;
      })
    }, (err) => {
      return PromiseA.resolve(callback()).then(() => {
        throw err;
      })
    });
  }
}

interview

interview 一些基础js问题也是必定会考察的,基础知识很重要,下面有一些常见的基础问题,回顾一下

css 相关

垂直居中的实现方式

.center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: (-50%, -50%)
}

.center {
  width: 200px;
  height: 200px;
  margin-left: -100px;
  margin-top: -100px;
}

.center {
  display: flex;
  align-items: center;
  justify-content: center;
}

实现简单的动画

// loading
.loading {
  width: 30px;
  height: 30px;
  border: 2px solid #ccc;
  border-radius: 50%;
  border-right-color: #f23132;
  animation: spin .2s linear infinite
}

@keyframes spin{
  0% {
    transform: rotate(0deg)
  }
  100% {
    transform: rotate(360deg)
  }
}

position 中 relative fixed absolute 的区别

relative fixed absolute 都会脱离文档流
relative 会占位,relative 相对于它自身进行定位
absolute 是相对于他的父级做参考的,只要父级设置了除 static 的其他属性,如果父级没有设置,就是向上查找,如果都没有设置,那就是 body。
fixed 是相对于浏览器视口的。

rem em px vw vh 单位

px 最基本的单位
rem 是以 html 根元素 font-size 为基准,如 html { font-size: 75px; } 1rem = 75px;
vw : 1vw 等于视口宽度的1% 
vh : 1vh 等于视口高度的1%
如 iphone width = 650px 1vw = 6.5px   height = 1200px 1vh = 12px
em 是相对于父级元素的 font-size 进行设置, 所以 em 会是动态的

盒子模型 box-sizing

border-box
计算宽度(样式宽度) = content +  padding-top + padding-bottom + border-top-width + border-bottom-width

content-box 
计算宽度 = content(样式宽度) + padding-top + padding-bottom + border-top-width + border-bottom-width

flex 布局

flex 布局

js 相关

基础数据类型

Number String Null Undefined Boolean Object Symbol

typeof undefined == 'undefined'
typeof null == 'object'
typeof NaN == 'number'

NaN == NaN // false
NaN == null // false
null == undefined // true

类型转换

=== 运算符判断相等的流程是怎样的

如果两个值不是相同类型,它们不相等
如果两个值都是null或者都是undefined,它们相等
如果两个值都是布尔类型true或者都是false,它们相等
如果其中有一个是NaN,它们不相等
如果都是数值型并且数值相等,他们相等, -0等于0
如果他们都是字符串并且在相同位置包含相同的16位值,他它们相等;如果在长度或者内容上不等,它们不相等;两个字符串显示结果相同但是编码不同==和===都认为他们不相等
如果他们指向相同对象、数组、函数,它们相等;如果指向不同对象,他们不相等

==运算符判断相等的流程是怎样的

如果两个值类型相同,按照===比较方法进行比较
如果类型不同,使用如下规则进行比较
如果其中一个值是null,另一个是undefined,它们相等
如果一个值是数字另一个是字符串,将字符串转换为数字进行比较
如果有布尔类型,将true转换为1,false转换为0,然后用==规则继续比较
如果一个值是对象,另一个是数字或字符串,将对象转换为原始值然后用==规则继续比较
其他所有情况都认为不相等

对象到字符串的转换步骤

如果对象有toString()方法,javascript调用它。如果返回一个原始值(primitive value如:string number boolean),将这个值转换为字符串作为结果
如果对象没有toString()方法或者返回值不是原始值,javascript寻找对象的valueOf()方法,如果存在就调用它,返回结果是原始值则转为字符串作为结果
否则,javascript不能从toString()或者valueOf()获得一个原始值,此时throws a TypeError

对象到数字的转换步骤
  1. 如果对象有valueOf()方法并且返回元素值,javascript将返回值转换为数字作为结果
  2. 否则,如果对象有toString()并且返回原始值,javascript将返回结果转换为数字作为结果
  3. 否则,throws a TypeError
!![] // true
转换过程分析

如何在基础类型去扩展方法

String.prototype.toMoney = function() {
  //var arr = [];
  //var arrStr =[];
  //if (Number(this)) {
    //arr = String(this).split('.');
    //console.log(arr)
    //var count = arr[0].length;
    //while(count >= 3) {
      //arrStr.unshift(arr[0].slice(count - 3, count));
      //count = count - 3;
    //}
    //console.log(arrStr);
    //arr[0].length % 3 && arrStr.unshift(arr[0].slice(0, arr[0].length % 3))
    //console.log(arrStr);
  //}
  // console.log(arrStr);
  // return arrStr.join(',') + '.' + arr[1] || '';


  // return this.split("").reverse().reduce((prev, next, index) => {
  //   return ((index % 3) ? next : (next + ',')) + prev
  // })

  return this.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}

event-loop

js 是单线程 一次只能做一件事
在输入的同时可以发请求, IO 所以说非阻塞(同步和 alert 是阻塞的)

task (macroTask) setInterval  setImedete setTimeOut XHR, IO
microTask Promise process.nextTick

console.log(1)
setTimeout(() => {
    console.log(2)
    new Promise(resolve => {
        console.log(4)
        resolve()
    }).then(() => {
        console.log(5)
    })
})

new Promise(resolve => {
    console.log(7)
    resolve()
}).then(() => {
    console.log(8)
})

setTimeout(() => {
    console.log(9)
    new Promise(resolve => {
        console.log(11)
        resolve()
    }).then(() => {
        console.log(12)
    })
})

// 1 7 8 2 4 5 9 11 12

事件捕获与冒泡

事件冒泡是微软的标准,事件捕获是网景的标准,W3C 统一标准先捕获后冒泡

addEventListener('click', function(){}, false) // default false 冒泡

事件委派

将事件绑定在父级,当有多个子元素需要绑定事件的时候,这样可以提神性能,不需要为每一个绑定事件,而且后面再添加子元素的时候,也是会有事件的

this 的理解

this 只在调用的时候才知道它的上下文,函数定义的时候是不知道的

var foo = 'windowFoo';
function Foo() {
  var foo = 'foo';
  console.log(this.foo)
}

var a = Foo(); // 'windowFoo';
var b = new Foo();  // undefined 

闭包

function close() {
 var age = 24;
 var name = "Bob";
 return function() {
  console.log(this.age, this.name) 
 }
}

原型链的理解(继承的实现)

class Person {
  constructor(name, age, male) {
    this.name = name;
    this.age = age;
    this.male = male;
  }
}

class Front extends Person {
  constructor(name, age, male, hobby) {
    super(name, age, male)
    this.hobby = hobby;
  }

  introduce() {
    console.log(`my name is ${this.name}, my age is ${this.age} my male is ${this.male}, my hobby is ${this.hobby}`);
    return this
  }
}


const person = new Front('huahaitao', 24, 'male', 'basketball')

person.introduce()


原型链实现的基本方式

function SuperType() {
  this.property = 'SuperType';
}

SuperType.prototype.getSuperValue = function() {
  return this.property;
}

function SubType() {
  this.subProperty = 'SubType';
}


SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
  return this.subProperty;
}

const instance = new SubType();
console.log(instance, instance.getSuperValue(), instance.getSubValue())

图解1
图解2
图解3
图解4

图解

深拷贝和浅拷贝

function shallowCopy(src) {
  var dest = {}
  for (var key in src) {
    if (src.hasOwnProperty(key)) {
      dest[key] = src[key]
    }
  }
  return dest
}

var obj1 = {
  'name' : 'zhangsan',
  'age' :  '18',
  'language' : [1,[2,3],[4,5]],
};

var obj2 = extend({}, obj1, true)

function isPlainObj(obj) {
  if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") {
    return false;
  }
  var proto = Object.getPrototypeOf(obj);
  if (!proto) { return true }
  return typeof (Object.hasOwnProperty.call(proto, 'constructor') && proto.constructor) == 'function'
}

function isArray(obj) {
  return Object.prototype.toString.call(obj) == '[object Array]'
}

function extend(target, src, deep) {
  for (var key in src) {
    if (deep && isPlainObj(src[key]) || isArray(src[key])) {
      if (isPlainObj(src[key]) && !isPlainObj(target[key])) target[key] = {}
      if (isArray(src[key]) && !isArray(target[key])) target[key] = []
      extend(target[key], src[key], deep)
    } else if (src[key] !== undefined) {
      target[key] = src[key]
    }
  }
  return target
}

apply call bind

apply call bind 都是可以改变 this 指向的, apply call 函数是会立即执行 bind 不会立即执行

debounce && throttle

// 连续的时间内只出现一次
function debounce(fn, delay) {
  var timer;
  return function() {
    var context = this;
    var args = arguments;

    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  };
}

/**
 * 节流 一定时间内一定执行一次
 * @param fn
 * @param threshhold
 * @return { Function }
 */
function throttle(fn, threshhold = 200) {
  var last;
  var timer;
  return function() {
    var context = this;
    var args = arguments;
    var now = +new Date();
    if (last && last < last + threshhold) {
      console.log(last && last < last + threshhold);
      cleartimer(timer);
      settimer(function() {
        last = now;
        fn.apply(context, args);
      }, threshhold);
    } else {
      last = now;
      fn.apply(context, args);
    }
  };
}

ES6 语法

  • let const
  • Promise 的实现原理
  • Array.from
  • Symbol
  • 解构赋值
  • class
  • async
  • Proxy
  • module (import export) 模块加载原理
  • Decratator
  • ...reset
  • 箭头函数

变量提升

var let const 区别
// var
function hoistVariable() {
    if (!foo) {
        var foo = 5;
    }
    console.log(foo); // 5
}

两个函数等价,下面是上面解析之后的样子

// var 的变量提升, var 作用域整个函数
function hoistVariable() {
    var foo;
    if (!foo) {
        foo = 5;
    }
    console.log(foo); // 5
}

// let 块级作用域

function hoistVariable() {
    if (!foo) {
        let foo = 5;
    }
    console.log(foo); // Uncaught ReferenceError: foo is not defined
}
hoistVariable()

// const 不能赋值
const foo = 3.14
foo = 4.14 // Uncaught TypeError: Assignment to constant variable

// 引用地址没有变
const foo = []
foo.push(2)

// 引用地址没有变
const foo = {}
foo.a = { name: 'test' }
函数提升
// 函数作用域提升
function hoistFunction() {
    foo(); // output: I am hoisted

    function foo() {
        console.log('I am hoisted');
    }
}

function hoistFunction() {
    function foo() {
        console.log('I am hoisted');
    }

    foo(); // output: I am hoisted
}
hoistFunction();

// 出现同名函数,后面覆盖前面的

function hoistFunction() {
    function foo() {
        console.log(1);
    }

    foo(); // output: 2

    function foo() {
        console.log(2);
    }
}

hoistFunction();

// 当变量和函数同名,函数会提到最前面

function hoistFunction() {

    foo(); // 2

    var foo = function() {
        console.log(1);
    };

    foo(); // 1

    function foo() {
        console.log(2);
    }

    foo(); // 1
}

hoistFunction();

promise 实现原理

promise 实现

http 相关

状态码

101 协议切换
200 正常响应
304 使用客户端缓存
401 未授权
405 GET POST 方法不支持
302 重定向 永久
301 重定向 暂时

500 服务器错误
404 页面不存在 资源不存在
403 forbidden

缓存

response header

cache-control: max-age=60;
E-tag: '2222cdcd';
Expires: 2018-12-12
last-modified: 2018-12-12

request header

If-None-Match: 9df406f3bc313e530c6f26d85001eb0b
if-modified-since: 2018-12-12

缓存分为 强制缓存和对比缓存(协商缓存)

强制缓存 不和服务器交互 (浏览器缓存, from disk)
cache-control 设置时间 单位 s
Expires GMT 时间,超过这个时间,过期就会向服务器发送请求

对比缓存 需要和服务器做交互的 返回 304
E-tag  文件hash 是否改变
last-modified 最近一次修改时间
若请求时间大于修改时间, 则返回 304 若小于 304 需重新请求资源

get 和 post 请求的区别

URL 的最大长度是 2048 个字符 2k
GET 安全性差
POST 无限制

session 和 cookie

session 因为 session id 的存在,通常要借助 cookie 实现

vue 相关

vue-router

去除 vue-router 自己实现 路由

function Router() {
  this.routes = {};
  this.currentUrl = '';
}

Router.prototype.route = function (path, callback) {
  this.routes[path] = callback || function () {};
};

Router.prototype.refresh = function () {
  console.log('触发一次 hashchange,hash 值为', location.hash);
  this.currentUrl = location.hash.slice(1) || '/';
  this.routes[this.currentUrl]();
};

window.Router = new Router();

var content = document.querySelector('body');
// change Page anything
function changeBgColor(color) {
  content.style.backgroundColor = color;
}
Router.route('/', function () {
  changeBgColor('white');
});
Router.route('/blue', function () {
  changeBgColor('blue');
});
Router.route('/green', function () {
  changeBgColor('green');
});

vuex

  • vuex 数据流
  • 如何实现数据变动

vue 双向数据绑定

  • data 如何实现数据变动 ui 变动
  • computed 和 watch 什么区别

vue render 函数

 vue 最后编译的两个 staticRender render function() {} 是干嘛的

职业相关

全栈

项目相关

  • canvas 手写板的优化
  • 图片上传

算法相关

// 数组去重

ES5

function removeDuplicates(arr) {
  var map = {}
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] in map) {
      arr.splice(arr[i], 1);
    } else {
      map[arr[i]] = true;
    }
  }
  return arr
}
removeDuplicates([1, 2, 2, 3, 3, 4, 4, 5])

ES6

Array.from(new Set([...[1, 2, 2, 3, 3, 4, 4, 5]]))

// flat

let data = [1, [2, [[3, 4], 5], 6]];

function flattern(arr, result) {
  for (let i = 0; i < arr.length; i++) {
    if (typeof arr[i] === 'number') {
      result.push(arr[i])
    } else {
      flattern(arr[i], result)
    }
  }
}

let result = []
result = flattern(data, result)

// random

function random(start, end, length) {
  let arr = [];
  start = start ? start : 1;
  end = end ? end : 10;
  length = length ? length : 10;
  let dis = end - start;
  for (let i = 0; i < length; i++) {
    arr.push(Math.floor(Math.random() * dis) + start)
  }
  return arr;
}
random()

排序算法

常见排序算法

快速排序 时间复杂度的计算

rem 适配方案

webpack 按需加载原理

webpack 依赖 lib 问题 配置

面试题

  1. 算出当前对象 key 出现的次数 (美团一面)
let map = new Map();

function getKeyCount(obj) {
  let keys = Object.keys(obj);

  for (let i = 0; i < keys.length; i += 1) {

    if (!map.has(keys[i])) {
      map.set(keys[i], 0);
    }

    if (typeof obj[keys[i]] === 'object') {
      if (map.has(keys[i])) {
        map.set(keys[i], map.get(keys[i]) + 1);
      }
      getKeyCount(obj[keys[i]]);
    } else {
      if (map.has(keys[i])) {
        map.set(keys[i], map.get(keys[i]) + 1);
      }
    }
  }
}


let obj = {
  a: {
    b: 2,
    c: 3
  },
  b: 2,
  c: {
    a: 1,
    b: 2,
    c: {
      a: 1,
      b: 2
    }
  }
};

getKeyCount(obj)

// a 3 b 4 c 3
console.log(map);
  1. 写一个链表实现增删查改(时间20min,抖音一面)
function LinkNode(val, next = null) {
  this.val = val;
  this.next = next;
}

function LinkList() {
  this.head = null;
  this.tail = null;
}

LinkList.prototype.add = function add(val) {
  const node = new LinkNode(val);
  if (this.head === null) {
    this.head = node;
    this.tail = node;
  } else {
    this.tail.next = node;
    this.tail = node;
  }
};


LinkList.prototype.find = function find(val) {
  if (this.head === null) {
    return null;
  }

  let current = this.head;

  while (current) {
    if (current.val === val) {
      return current;
    }
    current = current.next;
  }

  return null;
};

LinkList.prototype.remove = function remove(val) {
  if (this.head === null) {
    throw Error('LinkList is empty');
  }

  if (this.head.val === val) {
    // 头结点待删除
    this.head = this.head.next;
  }

  let currentNode = this.head;
  if (currentNode !== null) {
    while (currentNode.next) {
      if (currentNode.next.val === val) {
        currentNode.next = currentNode.next.next;
      } else {
        currentNode = currentNode.next;
      }
    }
  }
};

LinkList.prototype.toArray = function toArray() {
  const nodes = [];

  let currentNode = this.head;
  while (currentNode) {
    nodes.push(currentNode);
    currentNode = currentNode.next;
  }

  return nodes;
};


const ll = new LinkList();

ll.add(1);
ll.add(2);
ll.add(3);
ll.add(4);
ll.add(5);

console.log(ll.toArray());

ll.remove(3);

console.log(ll.toArray());
console.log(ll.find(4));
  1. 获取二叉树最小深度 (小红书一面)
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function(root) {

    if (root === null) {
        return 0;
    }

    if (root.left === null && root.right === null) {
        return 1;
    }

    let min = Number.MAX_SAFE_INTEGER;

    if (root.left) {
        min = Math.min(minDepth(root.left), min);
    }

    if (root.right) {
        min = Math.min(minDepth(root.right), min);
    }

    return min + 1;
};
  1. 实现一个接口请求重试3次之后抛出异常(bilibili 二面)
function retryAjax(url, num = 1) {
    if (num <= 0) {
        return Promise.reject(err);
    } else {
        return fetch(url)
            .then(res => {
                return res;
            })
            .catch(err => {
                return retryAjax(url, num - 1);
            });
    }
}

sap

算法题目

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

var lengthOfLongestSubstring = function(s) {
   let left = 0
   let right = 0
   let res = 0
   let win = new Map()

  while (right < s.length) {
       let c = s[right]
       right++
       if (win.get(c) == undefined) {
             win.set(c, 1)
       } else {
           win.set(c, win.get(c) + 1)
       }

       while (win.get(c) > 1) {
            let d = s[left]
            left++
            win.set(d, win.get(d) - 1)
       }
       res = Math.max(res, right - left)
  }

  return res

}

5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

var longestPalindrome = function(s) {
      let res = ''

      for (let i = 0; i < s.length; i += 1) {
         let s1 = helper(s, i, i)
         let s2 = helper(s, i, i + 1)
         res = s1.length > res.length ? s1 : res
         res = s2.length > res.length ? s2 : res
      }

      return res

      function helper(s, left, right) {
        while(left >= 0 && right < s.length && s[left] == s[right]) {
             left--
             right++
        }
        return s.substring(left + 1, right)
      }
}

基础知识

1、什么是 reflow && repaint

https://juejin.cn/post/6844903569087266823

reflow 比 repaint 的代价高

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为 repaint
浏览器窗口大小发生改变 元素尺寸或位置发生改变 元素内容变化(文字数量或图片大小等等)元素字体大小变化 添加或者删除可见的DOM元素 激活CSS伪类(例如::hover)称之为 reflow

2、defer 和 async 的作用

https://juejin.cn/post/6894629999215640583

async 加载完就执行,顺序无关,一般是广告脚本执行
defer 按照文档的顺序,在 domContentLoaded 之后执行

image

3、事件循环,输出顺序

https://juejin.cn/post/6844903512845860872

image

macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick

image

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})


看一下这段的输出

// 1,7,6,8,2,4,3,5,9,11,10,12

4、防抖和节流

https://juejin.cn/post/6844903669389885453

debounce

search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

throttle

鼠标不断点击触发,mousedown(单位时间内只触发一次)
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

5、什么是闭包

https://juejin.cn/post/6934500357091360781

闭包是指有权访问另外一个函数作用域中的变量的函数

6、vue 父子组件生命周期的顺序
image

7、输入 url 整个过程

https://juejin.cn/post/6844903832435032072

image

virtual DOM 探究

virtual DOM

最开始了解到这个词是在 react, 在开始步入前端这个领域的时候,还是用 angularjs 进行开发,一套 ng 走到底,要写 controller,感觉和写后台 jsp 很像,后来由于项目接触到 react, 慢慢接触 react, vue,也听了同事分享了 virtual DOM, 觉得好高深,为了更好的了解框架是如何运行的,剥离开框架单纯考虑一下 virtual DOM

什么是 virtual DOM

简单的说 virtual DOM 是一个 js 包含 tagName 属性、children 属性、props 属性的 Object, 因为任何一个 DOM 🌲 上的结点都包括这三个部分,我们使用 js Object 来模拟真实的 DOM 结点

为什么要使用 virtual DOM

了解了什么是 virtual DOM 之后,我们明白为什么要使用 virtual DOM,大家知道插入真实的 DOM 结点是很耗费资源的, 因为一个真实 DOM上有很多属性以及一些事件,参照大佬一个类比, 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。

vdom 
{
  children: [],
  tagName: 'div',
  props: {}
}
realDom
{
  accessKey: ""
  align: ""
  assignedSlot: null
  attributeStyleMap: StylePropertyMap {size: 0}
  attributes: NamedNodeMap {length: 0}
  autocapitalize: ""
  baseURI: "https://6f1b67f6e72c43fa8c5528eb6d357abb.production.codepen.plumbing/index.html?d=1548668444648"
  childElementCount: 0
  childNodes: NodeList []
  children: HTMLCollection []
  classList: DOMTokenList [value: ""]
  className: ""
  clientHeight: 0
  clientLeft: 0
  clientTop: 0
  clientWidth: 0
  contentEditable: "inherit"
  dataset: DOMStringMap {}
  dir: ""
  draggable: false
  firstChild: null
  firstElementChild: null
  hidden: false
  id: ""
  innerHTML: ""
  innerText: ""
  inputMode: ""
  isConnected: false
  isContentEditable: false
  lang: ""
  lastChild: null
  lastElementChild: null
  localName: "div"
  namespaceURI: "http://www.w3.org/1999/xhtml"
  nextElementSibling: null
  nextSibling: null
  nodeName: "DIV"
  nodeType: 1
  nodeValue: null
  nonce: ""
  offsetHeight: 0
  offsetLeft: 0
  offsetParent: null
  offsetTop: 0
  offsetWidth: 0
  onabort: null
  onauxclick: null
  onbeforecopy: null
  onbeforecut: null
  onbeforepaste: null
  onblur: null
  oncancel: null
  oncanplay: null
  oncanplaythrough: null
  onchange: null
  onclick: null
  onclose: null
  oncontextmenu: null
  oncopy: null
  oncuechange: null
  oncut: null
  ondblclick: null
  ondrag: null
  ondragend: null
  ondragenter: null
  ondragleave: null
  ondragover: null
  ondragstart: null
  ondrop: null
  ondurationchange: null
  onemptied: null
  onended: null
  onerror: null
  onfocus: null
  onfullscreenchange: null
  onfullscreenerror: null
  ongotpointercapture: null
  oninput: null
  oninvalid: null
  onkeydown: null
  onkeypress: null
  onkeyup: null
  onload: null
  onloadeddata: null
  onloadedmetadata: null
  onloadstart: null
  onlostpointercapture: null
  onmousedown: null
  onmouseenter: null
  onmouseleave: null
  onmousemove: null
  onmouseout: null
  onmouseover: null
  onmouseup: null
  onmousewheel: null
  onpaste: null
  onpause: null
  onplay: null
  onplaying: null
  onpointercancel: null
  onpointerdown: null
  onpointerenter: null
  onpointerleave: null
  onpointermove: null
  onpointerout: null
  onpointerover: null
  onpointerup: null
  onprogress: null
  onratechange: null
  onreset: null
  onresize: null
  onscroll: null
  onsearch: null
  onseeked: null
  onseeking: null
  onselect: null
  onselectionchange: null
  onselectstart: null
  onstalled: null
  onsubmit: null
  onsuspend: null
  ontimeupdate: null
  ontoggle: null
  onvolumechange: null
  onwaiting: null
  onwebkitfullscreenchange: null
  onwebkitfullscreenerror: null
  onwheel: null
  outerHTML: "<div></div>"
  outerText: ""
  ownerDocument: document
  parentElement: null
  parentNode: null
  prefix: null
  previousElementSibling: null
  previousSibling: null
  scrollHeight: 0
  scrollLeft: 0
  scrollTop: 0
  scrollWidth: 0
  shadowRoot: null
  slot: ""
  spellcheck: true
  style: CSSStyleDeclaration {alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "",}
  tabIndex: -1
  tagName: "DIV"
  textContent: ""
  title: ""
  translate: true
}

数据对比

virtual DOM diff 算法

上面说到的比较两个 virtual DOM 的改变的关键就是 diff 算法

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.