day1


链接:https://www.nowcoder.com/discuss/679551?type=post&order=recall&pos=&page=0&ncTraceId=&channel=-1&source_id=search_post_nctrack&subType=2&gio_id=08D1079E8A937EB4F1FB441663571984-1651154740637
来源:牛客网

2021.7.2 一面
项目聊了半个小时
1.讲讲浏览器缓存,为什么要设计Etag和modify两种
http缓存分为:

  • 强缓存

    1. 强缓存通过 Expires 和Cache-control 控制
    2. 因为expires有服务器和浏览器时间不同步问题,expires是绝对时间,cache-control是相对时间
  • 协商缓存

    • 协商缓存通过 Last-modify 和 Etag 控制
    • 因为 last-modify 有精度问题到秒,etag 没有精度问题,只要资源改变,etag值就会改变

2.http1.1 http2.0 https
http基础

  • get/post/head,head只响应头部
  • options是http支持的方法

http1:

  • 短连接
  • 支持缓存

http2
二进制分帧
多路复用
头部压缩

https
HTTPS采用的是混合方式加密。 服务器拿到这个加密的密钥,解密获取密钥,再使用对称加密算法,和用户完成接下来的网络通信

3.vue双向绑定实现方式
vue2:defineproperty:当set时候就可以触发dom的update
observer、deps、watcher、view
为什么无法监听数组,因为length是length的configurable是false,提供了变异的set方法

vue3:Proxy
转成了类数组,对每个key的监听

4.proxy优点
对数据层做了拦截层,可以实现不同的效果
如sandbox

5.VDOM的优点
避免过度的渲染html,避免浏览器过度回流和重绘,解约开销
缺点就是:首次运算就慢,可以上ssr
如何实现:分了三个步骤

  • compile
    • dom => vdom
  • diff
    • 节点比对
    • react分别对tree diff、component diff 以及 element diff 进行算法优化
  • patch
    • 对应的vdom渲染到节点上

6.Promise的优点,特性
避免地狱回调
7.作用域
作用域分为:

  • 局部作用域
  • 全局作用域

JavaScript 变量生命周期在它声明时初始化。
局部变量在函数执行完毕后销毁。
全局变量在页面关闭后销毁。

8.为什么要设计this
函数的this取决于调用位置。

9.前端工程化有了解过吗

  • 前后端分离
  • 模块化开发
  • 组件化开发
  • 规划代码风格及流程
  • ci/cd

10.common.js了解吗
应用于node模块的规范,require/module.exports
运行在浏览器端的JavaScript由于也缺少类似的规范,在ES6出来之前,前端也实现了一套相同的模块规范(例如: AMD),用来对前端模块进行管理。
循环引用:
module缓存过,所以不存在循环问题。

自ES6起,引入了一套新的ES6 Module规范,
es-moudle存在于编译时,所以导入导出不能放在代码块

11.tree-shaking原理有看过吗
代码静态分析时,移除未引用的模块
es6较为容易实现,实现未三个阶段

  • 收集graph依赖图
  • 检测是否有依赖
  • 删除产物

    1. 实现new操作符
      13.
      var fullname = ‘1’;
      var obj = {
      fullname: ‘2’,
      prop: {
      fullname: ‘3’,
      getFullname: () => {
      return this.fullname;
      }
      }
      };
      console.log(obj.prop.getFullname());
      var test = obj.prop.getFullname;
      console.log(test());
      14.
      // 路径总和:给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,
      // 判断该树中是否存在 根节点到叶子节点 的路径,
      // 这条路径上所有节点值相加等于目标和 targetSum 。 ```javascript var searchTargetSum = (root, targetSum) => { if (!root) { return 0 }

    // 执行条件 if (targetSum === root.val && !root.left && !root.right) { return true } return searchTargetSum(root.left, targetSum - root.val) || searchTargetSum(root.right, targetSum - root.val) } ```

2021.7.6 二面
1. 如何进行技术学习
吸收:官方文档、订阅、互相cr
输出:文章分享、技术分享

  1. 字符串转对象函数是怎么编写的
    Json.parse、eval

    1. async/await相比Promise用法的区别
      本质是编程Promise.resolve()
  1. promise.then().then().catch()为什么可以一直then下去
    在promimse的执行完成时又返回了一个promise
  1. 实现promise timeout 方法,可以选择函数或者原型方法。如果promise对象在ms时间内未fullfilled,则reject。

    1. const promiseTimeout = (promise, timeout) => {
    2. const delay = new Promise((resolve, reject) => {
    3. setTimeout(reject, timeout)
    4. })
    5. return Promise.race([
    6. delay,
    7. promise
    8. ])
    9. }
  2. ws 建立连接的流程

  • tcp三次握手
  • 客户端传送http基础信息
  • ws服务器确认
  • onopen
  1. TCP HTTP区别,在哪一层
    http基于tcp
    image.png
    8. 实现instanceof
    作用:
  • 判断类型
  • 是否属于某原型

寻找原型链即可

  1. 如何实现request cancelation(取消xhr、axios请求)
    xhr.abort()

    10. dsl -> C端展现(跨端相关,这个不了解)
  1. 一面提到的tree shaking原理后来有去看吗

  2. 图片的旋转、缩放如何实现
    transform:
    scale

  3. 实现图片无限旋转
    animation: keyframe

  4. function fn(){
    console.log(this.x);
    }
    fn.bind({x: 1}).bind({x: 2})() // 打印结果?
    只会bind第一次的

15. 实现bind

Function.prototype.bind2 = function (context) {
    var self = this;
    var bindArgs = Array.prototype.slice.call(arguments, 1);
    var fNOP = function () {};

    var fbound = function () {
        var args = bindArgs.concat(Array.prototype.slice.call(arguments));
        self.apply(this instanceof self ? this : context, args);
    }

    fNOP.prototype = this.prototype;
    fbound.prototype = new fNOP();

    return fbound;

}
  1. 判断一个单链表中是否有环 ```javascript /**
    • Definition for singly-linked list.
    • function ListNode(val) {
    • this.val = val;
    • this.next = null;
    • } */

/**

  • @param {ListNode} head
  • @return {boolean} */ // 双指针,i跑1个单位,j跑两个单位。 var hasCycle = function(head) { var slow = head var fast = head while(slow !== null && fast.next !== null && fast.next.next !== null) {
     slow = slow.next
     fast = fast.next.next
     if (slow === false) {
         return true
     }
    
    } return false }; ```
  1. 实现柯里化
    const curry = f => a => b => f(a, b)
    

day2

作者:yphy
链接:https://www.nowcoder.com/discuss/801017?type=all&order=recall&pos=&page=1&ncTraceId=&channel=-1&source_id=search_all_nctrack&gio_id=08D1079E8A937EB4F1FB441663571984-1651154695463
来源:牛客网

异步加法

可以分为并行实现、串行实现

function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(null, a + b);
  }, 500);
}

var promiseAdd = (a, b) => new Promise((resolve, reject) => {
  asyncAdd(a, b, (err, res) => {
    if (err) {
      reject(err)
    } else {
      resolve(res)
    }
  })
})

var serailSum = async function (..args) {
  // 串行核心是每上个promise处理完,给下个处理
  return args.reduce((task, current) => {
    task.then(res => promiseAdd(res, current))
  }, Promise.resolve(0))
} 

var parallelSum = async (..args) => {
  // 全部放在Promise.all里,得到第一组然后,再递归合并为0
  var tasks = []

  if (args.length === 1) return args[0]
  for (let j = 0; j < args.length; j+2) {
    tasks.push(promiseAdd(args[j], args[j + 1] || 0))
  }
  const res = await Promise.all(tasks)
  return parallelSum(...res)
}

[] == ![]

为true
整体流程如下

  • [] == false
  • [] == 0
  • ‘’ == 0
  • 0 == 0

{} == !{}

  • {} == ! {}
  • {} == false
  • {} == 0
  • NaN == 0 -> false

repaint 和 reflow
重绘、回流
重绘是指部分区域style发生变化,如字体颜色,背景会重新绘制
回流是文档流涉及到布局变化重新渲染。回流代价会比重绘更高

用 React 实现一个树型结构
1、核心是维护一个树形数据结构

{
  id: 'a',
  val: 'a-val',
  children: [
    {
      id: 'b',
      val: 'b-val',

    }
  ]
}

function Tree(props) {
  conste { data } = props;

  return data.map(item => {
     return (
       <div>
         <div>item.val</div>
         <div>{(item.children && item.children.length > 0) ? <Tree data={item}>: ''}</div>
       </div>
     )
  })
}

promise顺序

//Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。


const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve();
  console.log(2);
})


promise.then(() => {
  console.log(3);
})

console.log(4);

如果让你来设计一个分页功能, 你会怎么设计? 前后端如何交互?

  • 总条数
  • 总页数
  • 当前页面
  • 当前页树数据

前端请求:页数、每页数量
服务端返回的数据


const start = (page - 1) * pageSize
const end = page * pageSize


HTTP 缓存
强缓存

  • cache-control 相对,优先级最大
  • expires 绝对时间

协商缓存

  • etag 文件hash值
  • last-modify

TCP/UDP
tcp安全性比udp可靠

  • 面向连接
  • 面向字节流
  • 双工通信

udp:

  • 不需要三次握手
  • 单播、多播、广播
  • 没有拥塞控制不稳定

URL参数解析:

function decode(url) {
    let arr = url.split("://");
    const protocol = arr[0];
    url = arr[1] || '';
    arr = url.split('/');
    const host = arr.shift();
    url = arr.join('/');
    arr = url.split('?');
    const path = arr.length === 2 ? '/' + arr.shift() : '';
    url = arr[0];
    arr = url.split('#');
    const hash = arr.length === 2 ? arr[1] : '';
    const query = {};
    arr = arr[0].split('&');
    for (let i = 0; i < arr.length; i++) {
        const [key, value] = arr[i].split('=');
        if (query[key]) {
            if (Array.isArray(query[key])) {
                query[key].push(value);
            } else {
                query[key] = [
                    query[key],
                    value
                ]
            }
        } else {
            query[key] = value;
        }
    }
    return {
        host,
        protocol,
        path,
        query,
        hash
    }
}

const url = 'http://www.tiktok.com/a/b?key=1&key=2&key=3&test=4#hehe'
console.log(JSON.stringify(decode(url)));

day3

作者:秋日拾遗
链接:https://www.nowcoder.com/discuss/756610?type=all&order=recall&pos=&page=1&ncTraceId=&channel=-1&source_id=search_all_nctrack&gio_id=08D1079E8A937EB4F1FB441663571984-1651154695463
来源:牛客网

1、进程和线程
线程是进程最小单位
多进程是指操作系统能同时运行多个任务(程序)。多线程是指在同一程序中有多个顺序流在执行

2、String和new String的区别
String返回的是字符串基本类型
new是引用对象

3、OSI七层模型,当你打开app时发生了什么?
Osi7层理论研究。
在我们应用中更多是tcp-ip的应用层
1、域名请求
2、请求dns服务器拿到ip
3、http请求头给tcp协议,相邻层回话
3、ip寻址
4、以太网帧二进制数据,网络接口层
5、转电信号

4、【手撕】Ajax发起的过程

var getJSON = (url) => {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url, true)
    xhr.send()
    xhr.onreadystatechange = (e) => {
      if (xhr.readyState !== 4) {
        return;
      }
      if (xhr.readystate === 4 && xhr.status === 200) {
        resolve(xhr.responseText)
      } else {
        reject(e)
      }
    }
  })
}


5、数据库了解吗?讲讲事务(不会)
一致性、原子性、持久性、隔离性

6、DOM和BOM区别,常用api有哪些?
document object、browserObject

JSON如何转换为DOM?虚拟DOM了解吗?


7、【手撕】获取cookie方法

var getCookie = (key) => {
    var list = document.cookie.split('; ')
    var res = {}
    list.map(k => {
      var [key, val] = k.split('=')
      res[key] = unescape(val)
    })
    return res[key] || ''
}


8、【手撕算法】最小的K个数

/**
 * @param {number[]} arr
 * @param {number} k
 * @return {number[]}
 */
 // 先排序,slice就好
var getLeastNumbers = function(arr, k) {
    const list = quickSort(arr, 0, arr.length - 1)
    return list.slice(0, k)
};
// 取得中间基址,核心是分区,然后分区处理,递归
function swap(items, leftIndex, rightIndex){
    var temp = items[leftIndex];
    items[leftIndex] = items[rightIndex];
    items[rightIndex] = temp;
}
function partition(items, left, right) {
    var pivot   = items[Math.floor((right + left) / 2)], //middle element
        i       = left, //left pointer
        j       = right; //right pointer
    while (i <= j) {
        while (items[i] < pivot) {
            i++;
        }
        while (items[j] > pivot) {
            j--;
        }
        if (i <= j) {
            swap(items, i, j); //sawpping two elements
            i++;
            j--;
        }
    }
    return i;
}

function quickSort(items, left, right) {
    var index;
    if (items.length > 1) {
        index = partition(items, left, right); //index returned from partition
        if (left < index - 1) { //more elements on the left side of the pivot
            quickSort(items, left, index - 1);
        }
        if (index < right) { //more elements on the right side of the pivot
            quickSort(items, index, right);
        }
    }
    return items;
}

9、reduce实现一个filter

// reduce用法
// reduce((total, current, arr, index) => nextVal, [])
var filter = (arr, fn) => arr.reduce((prev, current, index, arr) => {
  fn(current, index) ? prev.push(current) : null
}, []))

reduce实现flattern

var flattern = (arr) => {
  return arr.reduce((pre, current, index) => {
    if (Array.isArray(current)) {
      return pre.concat(flattern(current))
    } else {
      pre.push(current)
      return pre
    }
  }, [])
}

reduce实现map

var map = (arr, fn) => {
  return arr.reduce((pre, current, index) => {
    pre.push(fn(current))
    return pre
  }, [])
}

reduce实现max

var max = (arr) => arr.reduce((pre, current, index) => Math.max(pre, current), arr[0])

reducer实现去重

var removeDuplicates = arr => arr.reduce((pre, current, index) => {
  if (!pre.includes(current)) {
    pre.push(current)
  }
  return pre
}, [])

day4

作者:lllbbbwww
链接:https://www.nowcoder.com/discuss/707669?type=post&order=recall&pos=&page=0&ncTraceId=&channel=-1&source_id=search_post_nctrack&subType=2&gio_id=08D1079E8A937EB4F1FB441663571984-1651154740637
来源:牛客网

1. 实现一个模板语法 underscore _.template:

var tpl = "hello: <%= name %>";


var template = tpl => data => tpl.replace(/<%= (.*?) %>/g, (match, key) => data[key])
  // tpl字符串根据变量标识符,直接替换
  // 转换成多个str存入数组,最后join
  // 最后 new Function('obj', ` with(obj) {${tpl}}`)
var compiled = template(tpl);
compiled({name: 'Kevin'}); // "hello: Kevin"

'hello: Kevin'


2. 压缩字符串 ‘aaaaaabbbbbcccca’ => ‘a6b5c4a1’

/**
 * @param {string} S
 * @return {string}
 */
// 双指针,第一个j选中字母开始,第二个k选择计数
var compressString = (S) => {
  let res = ''

  for (let j = 0 ;j < S.length; j++) {
    let k = j + 1
    while(S[k] === S[j]) {
      k++
    }
    res += (S[j] + String(k - j))
    j = k - 1
  }
  if (res.length >= S.length) {
      return S
  } else {
    return res
  }
}


3. 第一题因为优点紧张,忘了api,花了可能有15分钟,第二题秒答,大概过去了20分钟的样子,后来问了很多实习相关,就一直聊实习的工作内容,你做的事情,我当时答的也不算特别好。。。

day5

基础题
1.HTTPS? VS HTTP 知道哪些对称加密/非加密算法
https是带有加密http。
两个协议唯一区别是https使用SSL来加密正常的http请求和响应。
对称加密:用的是同一个密匙,一定条件下可以解决安全问题。

2.HTTPS 详细的SSL/TLS握手过程?
transport layer security


3.React hooks理念, diff原理,为什么要key prop
react节点数
组件对比
再是component的key、props对比

4.插入大量DOM节点,react 、 原生分别怎么处理,原生会用到哪些方法
batching、diff
fragment

5.React使用Fragment,原生使用createDocumentFragment

6.JWT是什么,cookie细节
Json web token

7.模块化了解吗?AMD CMD CommonJS ESMoudle

8.CSRF 详细说,防御方法

  • 避免get请求
  • 增加refer限制、增加toke

9.CSS position 详细说
relative 相对定位,基于自身原先布局
absolute绝对定位,基于static的第一个父元素偏离位置
fixed固定屏幕
inherit继承父元素

10.UI组件库详细(我的项目)
11.微前端(我的项目)
12.最近学习的新技术
13.编程
手写节流防抖,详细问场景

// if (now - last > delay) setTimeout program || refresh last
// else run program
var throttle = function (func, delay) {
  var last,timerId
  return function() {
    var [context, args] = [this, arguments]
    var now = Date.now()

    if (last && now - last < delay) {
      clearTimeout(timerId)
      timerId = setTimeout(function () {
        last = Date.now()
        func.apply(context, args)
      }, delay)
    } else {
        last = Date.now()
        func.apply(context, args)
    }
  }
}

// 防抖
var debounce = function (func, delay) {
  var timer
  return function () {
    let args = [...arguments]
    let context = this

    clearTimeout(timer)
    setTimeout(function () {
      func.apply(context, args)
    }, delay)
  }
}

function throttle(fn, delay, fristTrigger = false) {
  let timer = null; // 定时器默认为null
  let last = 0; // 最后一次时间戳默认为0
  return function (...arg) {
    const context = this;
    if (fristTrigger) {
      // 一定时间内执行第一次
      // 当前时间戳 - 之前时间戳 > delay 再执行
      const cur = Date.now();
      if (cur - last > delay) {
        fn.call(context, ...arg);
        last = cur;
      }
    } else {
      // 没有定时器再执行 执行清空定时器 ,一定时间内执行最后一次
      if (!timer) {
        timer = setTimeout(() => {
          timer = null;
          fn.call(context, ...arg);
        }, delay);
      }
    }
  };
}

promise实现
https://juejin.cn/post/6940531182706622500

// 三个状态:PENDING、FULFILLED、REJECTED
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    // 默认状态为 PENDING
    this.status = PENDING;
    // 存放成功状态的值,默认为 undefined
    this.value = undefined;
    // 存放失败状态的值,默认为 undefined
    this.reason = undefined;

    // 调用此方法就是成功
    let resolve = (value) => {
      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
      }
    } 

    // 调用此方法就是失败
    let reject = (reason) => {
      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
      }
    }

    try {
      // 立即执行,将 resolve 和 reject 函数传给使用者  
      executor(resolve,reject)
    } catch (error) {
      // 发生异常时执行失败逻辑
      reject(error)
    }
  }

  // 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      onRejected(this.reason)
    }
  }
}

原生xhr + promise封装

var fetchJSON = (url) => new Promise((resolve, reject) => {
  var xhr = new XMLHttpRequest()
  xhr.open('GET', url)
  xhr.onreadystatechange = e => {
    if (xhr.readyStatus !== 4) return
    if (xhr.readyStatus === 4 && xhr.status === 200) {
      resolve(xhr.responseText)
    } else {
      reject(xhr.responseText)
    }
  }
  xhr.send(null)
})

二面
1.项目深问
2.项目相关,微框架,为什么使用,难点与思考
3.项目相关,websocket兼容性,原生方法,丢包怎么解决
4.项目相关,RN白屏怎么处理,线上问题怎么定位问题
5.项目相关,什么情况下会OOM,怎么解决;JS什么情况下会堆栈溢出,怎么排查,怎么解决

  • 递归
  • 排查:打点、堆快照、查看栈调用时的内存变化

6.长列表的滚动与刷新,虚拟列表怎么实现,intersectionObserve,getBoundingClientRect
7.JS动画与CSS动画?JS动画怎么不阻塞,CSS动画为什么不阻塞

JS为ui线程
css为gpu渲染
css不会重排、重绘

8.合成线程:transform,filter,opacity,will-change
通常的dom为render layer
视频、webgl这种高消耗资源Graphics Layer
renderlayer可以升级为Graphics layer,但是消耗内存
优化:

  • 减少合成层
  • 减少层大小

9.webpack热更新是怎么实现的,怎么实现不刷新页面的更新?

  • webscocket
  • 坚挺文件修改,webpackHotUpdatehmr
  • 更新webpack_modules

10.原生JS有哪些不刷新页面的更新

  • iframe
    • postMessage通信
    • parent.window调用父节点方法
    • 子调父getElementById.childWindow
  • ajax、jsonp

编程:
1.输出随机数数组,不重复的随机整数[a,b),长度为c

2.二叉树,求从父节点到子节点每条路径组成的数字之和
三面
1.项目深度和广度上都有提问
2.最大子序合

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    // 动态规划
    var res = nums[0];
    var pre = 0;
    for(let k of nums) {
        pre = Math.max(k, pre + k)
        res = Math.max(res, pre)
    };
    return res
};

3.附加题:如果你是黑客,怎么实现CSRF攻击?
比如在qq空间。可以自定义装修,插入外链图片

day6

https://www.nowcoder.com/discuss/861304

  1. 介绍项目
  2. jwt在项目中的具体实现,必要性?为什么不用微信API的鉴权?(答官方文档建议开发者自定登录态)

  3. tcp与udp区别?应用场景?

tcp:文件传输ftp、回话
udp:域名转换

  1. get与post区别,应用场景?在项目中的使用?

  2. http缓存控制,协商缓存?

强缓存:cache-control > expires
协商缓存:etag > last modify(精度问题)

  1. https如何保证安全的?加密方式?公私钥交换过程?

  2. 跨域是什么?产生条件?在微信小程序中的运用?解决了什么?

不同tab通信
iframe+storechange绑定

  1. jsonp有了解过吗?(答没有什么了解,之前看到跨域解决方案的时候有看到过对比,没有深入了解,熟悉的是cors和nginx反向代理。然后也没有追问下去了)

利用script没有跨域限制执行了全局函数,同时把变量透传给当前函数

  1. 看输出,解释原因复制代码

一面题:
1. react hooks解决了什么问题

  • service手里
  • 数据和ui的解耦
  1. setstate和usestate的区别
    3.vue的v-if和v-show
    4.浏览器缓存原理
    5.script标签的defer和async
    6.请求放created里还是mounted里.
    7.异步请求延迟完成会导致mounted时并未获取数据,如何解决
    watch:this.$nextTick
    8.box-sizing相关计算,
    border-box:盒模型尺寸Math.max(boder+padding, width)
    content-box:盒模型尺寸算上padding、border
    9.实现promise.all

    var promiseAll = (arr) => {
    var count = 0
    var res = []
    
    return new Promise((resolve, reject) => {
     arr.map((j, index) => {
       j().then(res => {
         count++
         res.push(res)
         if (count === arr.length) {
           resolve()
         }
       })
     })
    })
    }
    

    10.实现add(a)(b)(c) ```javascript var add = function (…args) { return args.reduce((a, b) => a + b) }

var currying = function(fn) { var args = [] return function tmp (…currentArgs) { if (currentArgs.length) { args = […args, …currentArgs] return tmp } else { let val = fn.apply(this, args) args = [] return val } } } var add1 = currying(add) console.log(add1(1)(2)())

11.用两个栈实现一个队列

二面题:<br />抖音社区安全部门<br />文件上传的请求body(content-type)<br />cookie和token的区别<br />cookie都有哪些字段
```javascript
path限制可以访问此cooki路径,expires过期时间,httponly

如何清除cookie
代码混淆的作用
如何排查线上问题(fiddler)
工程化问题:首屏加载优化、tree-shaking原理、如何抽离插件库使其只打包一次(CommonsChunkPlugin)、

npm包发布步骤
npm publish

css三角形

width:0px;
height: 0px;
border: 40px solid ;
border-color: #06a43a transparent transparent;