ES6模块与CommonJS模块有什么异同?

ES6 Module和CommonJS模块的区别:

  • CommonJS是对模块的浅拷⻉,ES6 Module是对模块的引⽤,即ES6 Module只存只读,不能扭转其值,也就是指针指向不能变,相似const;
  • import的接⼝是read-only(只读状态),不能批改其变量值。 即不能批改其变量的指针指向,但能够扭转变量外部指针指向,能够对commonJS对从新赋值(扭转指针指向),然而对ES6 Module赋值会编译报错。

ES6 Module和CommonJS模块的共同点:

  • CommonJS和ES6 Module都能够对引⼊的对象进⾏赋值,即对对象外部属性的值进⾏扭转。

函数柯里化

柯里化(currying) 指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只承受一个参数。

对于曾经柯里化后的函数来说,当接管的参数数量与原函数的形参数量雷同时,执行原函数; 当接管的参数数量小于原函数的形参数量时,返回一个函数用于接管残余的参数,直至接管的参数数量与形参数量统一,执行原函数。

CDN的原理

CDN和DNS有着密不可分的分割,先来看一下DNS的解析域名过程,在浏览器输出的解析过程如下:
(1) 查看浏览器缓存
(2)查看操作系统缓存,常见的如hosts文件
(3)查看路由器缓存
(4)如果前几步都没没找到,会向ISP(网络服务提供商)的LDNS服务器查问
(5)如果LDNS服务器没找到,会向根域名服务器(Root Server)申请解析,分为以下几步:

  • 根服务器返回顶级域名(TLD)服务器如.com.cn.org等的地址,该例子中会返回.com的地址
  • 接着向顶级域名服务器发送申请,而后会返回次级域名(SLD)服务器的地址,本例子会返回.test的地址
  • 接着向次级域名服务器发送申请,而后会返回通过域名查问到的指标IP,本例子会返回www.test.com的地址
  • Local DNS Server会缓存后果,并返回给用户,缓存在零碎中

CDN的工作原理: (1)用户未应用CDN缓存资源的过程:

  1. 浏览器通过DNS对域名进行解析(就是下面的DNS解析过程),顺次失去此域名对应的IP地址
  2. 浏览器依据失去的IP地址,向域名的服务主机发送数据申请
  3. 服务器向浏览器返回响应数据

(2)用户应用CDN缓存资源的过程:

  1. 对于点击的数据的URL,通过本地DNS零碎的解析,发现该URL对应的是一个CDN专用的DNS服务器,DNS零碎就会将域名解析权交给CNAME指向的CDN专用的DNS服务器。
  2. CND专用DNS服务器将CND的全局负载平衡设施IP地址返回给用户
  3. 用户向CDN的全局负载平衡设施发动数据申请
  4. CDN的全局负载平衡设施依据用户的IP地址,以及用户申请的内容URL,抉择一台用户所属区域的区域负载平衡设施,通知用户向这台设施发动申请
  5. 区域负载平衡设施抉择一台适合的缓存服务器来提供服务,将该缓存服务器的IP地址返回给全局负载平衡设施
  6. 全局负载平衡设施把服务器的IP地址返回给用户
  7. 用户向该缓存服务器发动申请,缓存服务器响应用户的申请,将用户所需内容发送至用户终端。

如果缓存服务器没有用户想要的内容,那么缓存服务器就会向它的上一级缓存服务器申请内容,以此类推,直到获取到须要的资源。最初如果还是没有,就会回到本人的服务器去获取资源。

CNAME(意为:别名):在域名解析中,实际上解析进去的指定域名对应的IP地址,或者该域名的一个CNAME,而后再依据这个CNAME来查找对应的IP地址。

快排--工夫复杂度 nlogn~ n^2 之间

题目形容:实现一个快排

实现代码如下:

function quickSort(arr) {  if (arr.length < 2) {    return arr;  }  const cur = arr[arr.length - 1];  const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1);  const right = arr.filter((v) => v > cur);  return [...quickSort(left), cur, ...quickSort(right)];}// console.log(quickSort([3, 6, 2, 4, 1]));

documentFragment 是什么?用它跟间接操作 DOM 的区别是什么?

MDN中对documentFragment的解释:

DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document应用,就像规范的document一样,存储由节点(nodes)组成的文档构造。与document相比,最大的区别是DocumentFragment不是实在 DOM 树的一部分,它的变动不会触发 DOM 树的从新渲染,且不会导致性能等问题。

当咱们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 本身,而是它的所有子孙节点。在频繁的DOM操作时,咱们就能够将DOM元素插入DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和间接操作DOM相比,将DocumentFragment 节点插入DOM树时,不会触发页面的重绘,这样就大大提高了页面的性能。

如何对我的项目中的图片进行优化?

  1. 不必图片。很多时候会应用到很多润饰类图片,其实这类润饰图片齐全能够用 CSS 去代替。
  2. 对于挪动端来说,屏幕宽度就那么点,齐全没有必要去加载原图节约带宽。个别图片都用 CDN 加载,能够计算出适配屏幕的宽度,而后去申请相应裁剪好的图片。
  3. 小图应用 base64 格局
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 抉择正确的图片格式:

    • 对于可能显示 WebP 格局的浏览器尽量应用 WebP 格局。因为 WebP 格局具备更好的图像数据压缩算法,能带来更小的图片体积,而且领有肉眼辨认无差别的图像品质,毛病就是兼容性并不好
    • 小图应用 PNG,其实对于大部分图标这类图片,齐全能够应用 SVG 代替
    • 照片应用 JPEG

深拷贝

实现一:不思考 Symbol

function deepClone(obj) {    if(!isObject(obj)) return obj;    let newObj = Array.isArray(obj) ? [] : {};    // for...in 只会遍历对象本身的和继承的可枚举的属性(不含 Symbol 属性)    for(let key in obj) {        // obj.hasOwnProperty() 办法只思考对象本身的属性        if(obj.hasOwnProperty(key)) {            newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key];        }    }    return newObj;}

实现二:思考 Symbol

// hash 作为一个查看器,防止对象深拷贝中呈现环援用,导致爆栈function deepClone(obj, hash = new WeakMap()) {    if(!isObject(obj)) return obj;    // 查看是有存在雷同的对象在之前拷贝过,有则返回之前拷贝后存于hash中的对象    if(hash.has(obj)) return hash.get(obj);    let newObj = Array.isArray(obj) ? [] : {};    // 备份存在hash中,newObj目前是空对象、数组。前面会对属性进行追加,这里存的值是对象的栈    hash.set(obj, newObj);    // Reflect.ownKeys返回一个数组,蕴含对象本身的(不含继承的)所有键名,不论键名是 Symbol 或字符串,也不论是否可枚举。    Reflect.ownKeys(obj).forEach(key => {        // 属性值如果是对象,则进行递归深拷贝,否则间接拷贝        newObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key];    });    return newObj;}

代码输入后果

function Person(name) {    this.name = name}var p2 = new Person('king');console.log(p2.__proto__) //Person.prototypeconsole.log(p2.__proto__.__proto__) //Object.prototypeconsole.log(p2.__proto__.__proto__.__proto__) // nullconsole.log(p2.__proto__.__proto__.__proto__.__proto__)//null前面没有了,报错console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null前面没有了,报错console.log(p2.constructor)//Personconsole.log(p2.prototype)//undefined p2是实例,没有prototype属性console.log(Person.constructor)//Function 一个空函数console.log(Person.prototype)//打印出Person.prototype这个对象里所有的办法和属性console.log(Person.prototype.constructor)//Personconsole.log(Person.prototype.__proto__)// Object.prototypeconsole.log(Person.__proto__) //Function.prototypeconsole.log(Function.prototype.__proto__)//Object.prototypeconsole.log(Function.__proto__)//Function.prototypeconsole.log(Object.__proto__)//Function.prototypeconsole.log(Object.prototype.__proto__)//null

这道义题目考查原型、原型链的根底,记住就能够了。

动静布局求解硬币找零问题

题目形容:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算能够凑成总金额所需的起码的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

示例1:输出: coins = [1, 2, 5], amount = 11输入: 3解释: 11 = 5 + 5 + 1示例2:输出: coins = [2], amount = 3输入: -1

实现代码如下:

const coinChange = function (coins, amount) {  // 用于保留每个指标总额对应的最小硬币个数  const f = [];  // 提前定义已知状况  f[0] = 0;  // 遍历 [1, amount] 这个区间的硬币总额  for (let i = 1; i <= amount; i++) {    // 求的是最小值,因而咱们预设为无穷大,确保它肯定会被更小的数更新    f[i] = Infinity;    // 循环遍历每个可用硬币的面额    for (let j = 0; j < coins.length; j++) {      // 若硬币面额小于指标总额,则问题成立      if (i - coins[j] >= 0) {        // 状态转移方程        f[i] = Math.min(f[i], f[i - coins[j]] + 1);      }    }  }  // 若指标总额对应的解为无穷大,则意味着没有一个符合条件的硬币总数来更新它,本题无解,返回-1  if (f[amount] === Infinity) {    return -1;  }  // 若有解,间接返回解的内容  return f[amount];};

如何提⾼webpack的打包速度?

(1)优化 Loader

对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,而后对 AST 持续进行转变最初再生成新的代码,我的项目越大,转换代码越多,效率就越低。当然了,这是能够优化的。

首先咱们优化 Loader 的文件搜寻范畴

module.exports = {  module: {    rules: [      {        // js 文件才应用 babel        test: /\.js$/,        loader: 'babel-loader',        // 只在 src 文件夹下查找        include: [resolve('src')],        // 不会去查找的门路        exclude: /node_modules/      }    ]  }}

对于 Babel 来说,心愿只作用在 JS 代码上的,而后 node_modules 中应用的代码都是编译过的,所以齐全没有必要再去解决一遍。

当然这样做还不够,还能够将 Babel 编译过的文件缓存起来,下次只须要编译更改过的代码文件即可,这样能够大幅度放慢打包工夫

loader: 'babel-loader?cacheDirectory=true'

(2)HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特地是在执行 Loader 的时候,长时间编译的工作很多,这样就会导致期待的状况。

HappyPack 能够将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来放慢打包效率了

module: {  loaders: [    {      test: /\.js$/,      include: [resolve('src')],      exclude: /node_modules/,      // id 前面的内容对应上面      loader: 'happypack/loader?id=happybabel'    }  ]},plugins: [  new HappyPack({    id: 'happybabel',    loaders: ['babel-loader?cacheDirectory'],    // 开启 4 个线程    threads: 4  })]

(3)DllPlugin

DllPlugin 能够将特定的类库提前打包而后引入。这种形式能够极大的缩小打包类库的次数,只有当类库更新版本才有须要从新打包,并且也实现了将公共代码抽离成独自文件的优化计划。DllPlugin的应用办法如下:

// 独自配置在一个文件中// webpack.dll.conf.jsconst path = require('path')const webpack = require('webpack')module.exports = {  entry: {    // 想对立打包的类库    vendor: ['react']  },  output: {    path: path.join(__dirname, 'dist'),    filename: '[name].dll.js',    library: '[name]-[hash]'  },  plugins: [    new webpack.DllPlugin({      // name 必须和 output.library 统一      name: '[name]-[hash]',      // 该属性须要与 DllReferencePlugin 中统一      context: __dirname,      path: path.join(__dirname, 'dist', '[name]-manifest.json')    })  ]}

而后须要执行这个配置文件生成依赖文件,接下来须要应用 DllReferencePlugin 将依赖文件引入我的项目中

// webpack.conf.jsmodule.exports = {  // ...省略其余配置  plugins: [    new webpack.DllReferencePlugin({      context: __dirname,      // manifest 就是之前打包进去的 json 文件      manifest: require('./dist/vendor-manifest.json'),    })  ]}

(4)代码压缩

在 Webpack3 中,个别应用 UglifyJS 来压缩代码,然而这个是单线程运行的,为了放慢效率,能够应用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。

在 Webpack4 中,不须要以上这些操作了,只须要将 mode 设置为 production 就能够默认开启以上性能。代码压缩也是咱们必做的性能优化计划,当然咱们不止能够压缩 JS 代码,还能够压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,咱们还能够通过配置实现比方删除 console.log 这类代码的性能。

(5)其余

能够通过一些小的优化点来放慢打包速度

  • resolve.extensions:用来表明文件后缀列表,默认查找程序是 ['.js', '.json'],如果你的导入文件没有增加后缀就会依照这个程序查找文件。咱们应该尽可能减少后缀列表长度,而后将呈现频率高的后缀排在后面
  • resolve.alias:能够通过别名的形式来映射一个门路,能让 Webpack 更快找到门路
  • module.noParse:如果你确定一个文件下没有其余依赖,就能够应用该属性让 Webpack 不扫描该文件,这种形式对于大型的类库很有帮忙

代码输入后果

Promise.resolve(1)  .then(2)  .then(Promise.resolve(3))  .then(console.log)

输入后果如下:

1

看到这个题目,好多的then,实际上只须要记住一个准则:.then.catch 的参数冀望是函数,传入非函数则会产生值透传

第一个then和第二个then中传入的都不是函数,一个是数字,一个是对象,因而产生了透传,将resolve(1) 的值间接传到最初一个then里,间接打印出1。

实现节流函数和防抖函数

函数防抖的实现:

function debounce(fn, wait) {  var timer = null;  return function() {    var context = this,      args = [...arguments];    // 如果此时存在定时器的话,则勾销之前的定时器从新记时    if (timer) {      clearTimeout(timer);      timer = null;    }    // 设置定时器,使事件间隔指定事件后执行    timer = setTimeout(() => {      fn.apply(context, args);    }, wait);  };}

函数节流的实现:

// 工夫戳版function throttle(fn, delay) {  var preTime = Date.now();  return function() {    var context = this,      args = [...arguments],      nowTime = Date.now();    // 如果两次工夫距离超过了指定工夫,则执行函数。    if (nowTime - preTime >= delay) {      preTime = Date.now();      return fn.apply(context, args);    }  };}// 定时器版function throttle (fun, wait){  let timeout = null  return function(){    let context = this    let args = [...arguments]    if(!timeout){      timeout = setTimeout(() => {        fun.apply(context, args)        timeout = null       }, wait)    }  }}

对事件循环的了解

因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保障代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会始终期待其返回后果,而是会将这个事件挂起,继续执行执行栈中的其余工作。当异步事件执行结束后,再将异步事件对应的回调退出到一个工作队列中期待执行。工作队列能够分为宏工作队列和微工作队列,当以后执行栈中的事件执行结束后,js 引擎首先会判断微工作队列中是否有工作能够执行,如果有就将微工作队首的事件压入栈中执行。当微工作队列中的工作都执行实现后再去执行宏工作队列中的工作。

Event Loop 执行程序如下所示:

  • 首先执行同步代码,这属于宏工作
  • 当执行完所有同步代码后,执行栈为空,查问是否有异步代码须要执行
  • 执行所有微工作
  • 当执行完所有微工作后,如有必要会渲染页面
  • 而后开始下一轮 Event Loop,执行宏工作中的异步代码

分片思维解决大数据量渲染问题

题目形容:渲染百万条构造简略的大数据时 怎么应用分片思维优化渲染

实现代码如下:

let ul = document.getElementById("container");// 插入十万条数据let total = 100000;// 一次插入 20 条let once = 20;//总页数let page = total / once;//每条记录的索引let index = 0;//循环加载数据function loop(curTotal, curIndex) {  if (curTotal <= 0) {    return false;  }  //每页多少条  let pageCount = Math.min(curTotal, once);  window.requestAnimationFrame(function () {    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);  });}loop(total, index);
扩大思考:对于大数据量的简略 dom 构造渲染能够用分片思维解决 如果是简单的 dom 构造渲染如何解决?

这时候就须要应用虚构列表了 大家自行百度哈 虚构列表和虚构表格在日常我的项目应用还是很频繁的

代码输入后果

Promise.resolve('1')  .then(res => {    console.log(res)  })  .finally(() => {    console.log('finally')  })Promise.resolve('2')  .finally(() => {    console.log('finally2')      return '我是finally2返回的值'  })  .then(res => {    console.log('finally2前面的then函数', res)  })

输入后果如下:

1finally2finallyfinally2前面的then函数 2

.finally()个别用的很少,只有记住以下几点就能够了:

  • .finally()办法不论Promise对象最初的状态如何都会执行
  • .finally()办法的回调函数不承受任何的参数,也就是说你在.finally()函数中是无奈晓得Promise最终的状态是resolved还是rejected
  • 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异样则返回异样的Promise对象。
  • finally实质上是then办法的特例

.finally()的谬误捕捉:

Promise.resolve('1')  .finally(() => {    console.log('finally1')    throw new Error('我是finally中抛出的异样')  })  .then(res => {    console.log('finally前面的then函数', res)  })  .catch(err => {    console.log('捕捉谬误', err)  })

输入后果为:

'finally1''捕捉谬误' Error: 我是finally中抛出的异样

插入排序--工夫复杂度 n^2

题目形容:实现一个插入排序

实现代码如下:

function insertSort(arr) {  for (let i = 1; i < arr.length; i++) {    let j = i;    let target = arr[j];    while (j > 0 && arr[j - 1] > target) {      arr[j] = arr[j - 1];      j--;    }    arr[j] = target;  }  return arr;}// console.log(insertSort([3, 6, 2, 4, 1]));

公布订阅模式(事件总线)

形容:实现一个公布订阅模式,领有 on, emit, once, off 办法

class EventEmitter {    constructor() {        // 蕴含所有监听器函数的容器对象        // 内部结构: {msg1: [listener1, listener2], msg2: [listener3]}        this.cache = {};    }    // 实现订阅    on(name, callback) {        if(this.cache[name]) {            this.cache[name].push(callback);        }        else {            this.cache[name] = [callback];        }    }    // 删除订阅    off(name, callback) {        if(this.cache[name]) {            this.cache[name] = this.cache[name].filter(item => item !== callback);        }        if(this.cache[name].length === 0) delete this.cache[name];    }    // 只执行一次订阅事件    once(name, callback) {        callback();        this.off(name, callback);    }    // 触发事件    emit(name, ...data) {        if(this.cache[name]) {            // 创立正本,如果回调函数内持续注册雷同事件,会造成死循环            let tasks = this.cache[name].slice();            for(let fn of tasks) {                fn(...data);            }        }    }}

哪些操作会造成内存透露?

  • 第一种状况是因为应用未声明的变量,而意外的创立了一个全局变量,而使这个变量始终留在内存中无奈被回收。
  • 第二种状况是设置了 setInterval 定时器,而遗记勾销它,如果循环函数有对外部变量的援用的话,那么这个变量会被始终留在内存中,而无奈被回收。
  • 第三种状况是获取一个 DOM 元素的援用,而前面这个元素被删除,因为咱们始终保留了对这个元素的援用,所以它也无奈被回收。
  • 第四种状况是不合理的应用闭包,从而导致某些变量始终被留在内存当中。

代码输入后果

// afunction Foo () { getName = function () {   console.log(1); } return this;}// bFoo.getName = function () { console.log(2);}// cFoo.prototype.getName = function () { console.log(3);}// dvar getName = function () { console.log(4);}// efunction getName () { console.log(5);}Foo.getName();           // 2getName();               // 4Foo().getName();         // 1getName();               // 1 new Foo.getName();       // 2new Foo().getName();     // 3new new Foo().getName(); // 3

输入后果:2 4 1 1 2 3 3

解析:

  1. Foo.getName(), Foo为一个函数对象,对象都能够有属性,b 处定义Foo的getName属性为函数,输入2;
  2. getName(), 这里看d、e处,d为函数表达式,e为函数申明,两者区别在于变量晋升,函数申明的 5 会被后边函数表达式的 4 笼罩;
  3. Foo().getName(), 这里要看a处,在Foo外部将全局的getName从新赋值为 console.log(1) 的函数,执行Foo()返回 this,这个this指向window,Foo().getName() 即为window.getName(),输入 1;
  4. getName(), 下面3中,全局的getName曾经被从新赋值,所以这里仍然输入 1;
  5. new Foo.getName(), 这里等价于 new (Foo.getName()),先执行 Foo.getName(),输入 2,而后new一个实例;
  6. new Foo().getName(), 这 里等价于 (new Foo()).getName(), 先new一个Foo的实例,再执行这个实例的getName办法,然而这个实例自身没有这个办法,所以去原型链__protot__上边找,实例.protot === Foo.prototype,所以输入 3;
  7. new new Foo().getName(), 这里等价于new (new Foo().getName()),如上述6,先输入 3,而后new 一个 new Foo().getName() 的实例。

什么是执行栈

能够把执行栈认为是一个存储函数调用的栈构造,遵循先进后出的准则。 当开始执行 JS 代码时,依据先进后出的准则,后执行的函数会先弹出栈,能够看到,foo 函数后执行,当执行结束后就从栈中弹出了。

平时在开发中,能够在报错中找到执行栈的痕迹:

function foo() {  throw new Error('error')}function bar() {  foo()}bar()

能够看到报错在 foo 函数,foo 函数又是在 bar 函数中调用的。当应用递归时,因为栈可寄存的函数是有限度的,一旦寄存了过多的函数且没有失去开释的话,就会呈现爆栈的问题

function bar() {  bar()}bar()