关于javascript:来自大厂-10-前端面试题附答案整理版

33次阅读

共计 25701 个字符,预计需要花费 65 分钟才能阅读完成。

如何提⾼ 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.js
const 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.js
module.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 不扫描该文件,这种形式对于大型的类库很有帮忙

DNS 记录和报文

DNS 服务器中以资源记录的模式存储信息,每一个 DNS 响应报文个别蕴含多条资源记录。一条资源记录的具体的格局为

(Name,Value,Type,TTL)

其中 TTL 是资源记录的生存工夫,它定义了资源记录可能被其余的 DNS 服务器缓存多长时间。

罕用的一共有四种 Type 的值,别离是 A、NS、CNAME 和 MX,不同 Type 的值,对应资源记录代表的意义不同:

  • 如果 Type = A,则 Name 是主机名,Value 是主机名对应的 IP 地址。因而一条记录为 A 的资源记录,提供了标 准的主机名到 IP 地址的映射。
  • 如果 Type = NS,则 Name 是个域名,Value 是负责该域名的 DNS 服务器的主机名。这个记录次要用于 DNS 链式 查问时,返回下一级须要查问的 DNS 服务器的信息。
  • 如果 Type = CNAME,则 Name 为别名,Value 为该主机的标准主机名。该条记录用于向查问的主机返回一个主机名 对应的标准主机名,从而通知查问主机去查问这个主机名的 IP 地址。主机别名次要是为了通过给一些简单的主机名提供 一个便于记忆的简略的别名。
  • 如果 Type = MX,则 Name 为一个邮件服务器的别名,Value 为邮件服务器的标准主机名。它的作用和 CNAME 是一 样的,都是为了解决标准主机名不利于记忆的毛病。

谈谈你对状态治理的了解

  • 首先介绍 Flux,Flux 是一种应用单向数据流的模式来组合 React 组件的利用架构。
  • Flux 蕴含了 4 个局部,别离是 DispatcherStoreViewActionStore 存储了视图层所有的数据,当 Store 变动后会引起 View 层的更新。如果在视图层触发一个 Action,就会使以后的页面数据值发生变化。Action 会被 Dispatcher 进行对立的收发解决,传递给 Store 层,Store 层曾经注册过相干 Action 的解决逻辑,解决对应的外部状态变动后,触发 View 层更新。
  • Flux 的长处是单向数据流,解决了 MVC 中数据流向不清的问题,使开发者能够疾速理解利用行为。从我的项目构造上简化了视图层设计,明确了分工,数据与业务逻辑也对立寄存治理,使在大型架构的我的项目中更容易治理、保护代码。
  • 其次是 Redux,Redux 自身是一个 JavaScript 状态容器,提供可预测化状态的治理。社区通常认为 Redux 是 Flux 的一个简化设计版本,它提供的状态治理,简化了一些高级个性的实现老本,比方撤销、重做、实时编辑、工夫旅行、服务端同构等。
  • Redux 的外围设计蕴含了三大准则:繁多数据源、纯函数 Reducer、State 是只读的
  • Redux 中整个数据流的计划与 Flux 大同小异
  • Redux 中的另一大外围点是解决“副作用”,AJAX 申请等异步工作,或不是纯函数产生的第三方的交互都被认为是“副作用”。这就造成在纯函数设计的 Redux 中,解决副作用变成了一件至关重要的事件。社区通常有两种解决方案:

    • 第一类是在 Dispatch 的时候会有一个 middleware 中间件层,拦挡散发的 Action 并增加额定的简单行为,还能够增加副作用。第一类计划的风行框架有 Redux-thunk、Redux-Promise、Redux-Observable、Redux-Saga 等。
    • 第二类是容许 Reducer 层中间接解决副作用,采取该计划的有 React LoopReact Loop 在实现中采纳了 Elm 中分形的思维,使代码具备更强的组合能力。
    • 除此以外,社区还提供了更为工程化的计划,比方 rematch 或 dva,提供了更具体的模块架构能力,提供了拓展插件以反对更多功能。
  • Redux 的长处很多:

    • 后果可预测;
    • 代码构造严格易保护;
    • 模块拆散清晰且小函数构造容易编写单元测试;
    • Action 触发的形式,能够在调试器中应用工夫回溯,定位问题更简略快捷;
    • 繁多数据源使服务端同构变得更为容易;社区计划多,生态也更为凋敝。
  • 最初是 Mobx,Mobx 通过监听数据的属性变动,能够间接在数据上更改触发 UI 的渲染。在应用上更靠近 Vue,比起 Flux 与 Redux 的手动挡的体验,更像开自动挡的汽车。Mobx 的响应式实现原理与 Vue 雷同 ,以 Mobx 5 为分界点,5 以前采纳 Object.defineProperty 的计划,5 及当前应用 Proxy 的计划。 它的长处是样板代码少、简略粗犷、用户学习快、响应式自动更新数据 让开发者的心智累赘更低。
  • Mobx 在开发我的项目时简略疾速,但利用 Mobx 的场景,其实齐全能够用 Vue 取代。如果纯用 Vue,体积还会更玲珑

垃圾回收

  • 对于在 JavaScript 中的字符串,对象,数组是没有固定大小的,只有当对他们进行动态分配存储时,解释器就会分配内存来存储这些数据,当 JavaScript 的解释器耗费完零碎中所有可用的内存时,就会造成零碎解体。
  • 内存透露,在某些状况下,不再应用到的变量所占用内存没有及时开释,导致程序运行中,内存越占越大,极其状况下能够导致系统解体,服务器宕机。
  • JavaScript 有本人的一套垃圾回收机制,JavaScript 的解释器能够检测到什么时候程序不再应用这个对象了(数据),就会把它所占用的内存开释掉。
  • 针对 JavaScript 的来及回收机制有以下两种办法(罕用):标记革除,援用计数
  • 标记革除

v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。

  • 新创建的对象或者只经验过一次的垃圾回收的对象被称为新生代。经验过屡次垃圾回收的对象被称为老生代。
  • 新生代被分为 From 和 To 两个空间,To 个别是闲置的。当 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收。当咱们执行垃圾回收算法的时候应用逻辑将会进行,等垃圾回收完结后再继续执行。

这个算法分为三步:

  • 首先查看 From 空间的存活对象,如果对象存活则判断对象是否满足降职到老生代的条件,如果满足条件则降职到老生代。如果不满足条件则挪动 To 空间。
  • 如果对象不存活,则开释对象的空间。
  • 最初将 From 空间和 To 空间角色进行替换。

新生代对象降职到老生代有两个条件:

  • 第一个是判断是对象否曾经通过一次 Scavenge 回收。若经验过,则将对象从 From 空间复制到老生代中;若没有经验,则复制到 To 空间。
  • 第二个是 To 空间的内存应用占比是否超过限度。当对象从 From 空间复制到 To 空间时,若 To 空间应用超过 25%,则对象间接降职到老生代中。设置 25% 的起因次要是因为算法完结后,两个空间完结后会替换地位,如果 To 空间的内存太小,会影响后续的内存调配。

老生代采纳了标记革除法和标记压缩法。标记革除法首先会对内存中存活的对象进行标记,标记完结后革除掉那些没有标记的对象。因为标记革除后会造成很多的内存碎片,不便于前面的内存调配。所以了解决内存碎片的问题引入了标记压缩法。

因为在进行垃圾回收的时候会暂停利用的逻辑,对于新生代办法因为内存小,每次进展的工夫不会太长,但对于老生代来说每次垃圾回收的工夫长,进展会造成很大的影响。为了解决这个问题 V8 引入了增量标记的办法,将一次进展进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行

深浅拷贝

1. 浅拷贝的原理和实现

本人创立一个新的对象,来承受你要从新复制或援用的对象值。如果对象属性是根本的数据类型,复制的就是根本类型的值给新对象;但如果属性是援用数据类型,复制的就是内存中的地址,如果其中一个对象扭转了这个内存中的地址,必定会影响到另一个对象

办法一:object.assign

object.assign是 ES6 中 object 的一个办法,该办法能够用于 JS 对象的合并等多个用处,其中一个用处就是能够进行浅拷贝。该办法的第一个参数是拷贝的指标对象,前面的参数是拷贝的起源对象(也能够是多个起源)。

object.assign 的语法为:Object.assign(target, ...sources)

object.assign 的示例代码如下:

let target = {};
let source = {a: { b: 1} };
Object.assign(target, source);
console.log(target); // {a: { b: 1} };

然而应用 object.assign 办法有几点须要留神

  • 它不会拷贝对象的继承属性;
  • 它不会拷贝对象的不可枚举的属性;
  • 能够拷贝 Symbol 类型的属性。
let obj1 = {a:{ b:1}, sym:Symbol(1)}; 
Object.defineProperty(obj1, 'innumerable' ,{
    value:'不可枚举属性',
    enumerable:false
});
let obj2 = {};
Object.assign(obj2,obj1)
obj1.a.b = 2;
console.log('obj1',obj1);
console.log('obj2',obj2);

从下面的样例代码中能够看到,利用 object.assign 也能够拷贝 Symbol 类型的对象,然而如果到了对象的第二层属性 obj1.a.b 这里的时候,前者值的扭转也会影响后者的第二层属性的值,阐明其中 仍旧存在着拜访独特堆内存的问题 ,也就是说 这种办法还不能进一步复制,而只是实现了浅拷贝的性能

办法二:扩大运算符形式

  • 咱们也能够利用 JS 的扩大运算符,在结构对象的同时实现浅拷贝的性能。
  • 扩大运算符的语法为:let cloneObj = {...obj};
/* 对象的拷贝 */
let obj = {a:1,b:{c:1}}
let obj2 = {...obj}
obj.a = 2
console.log(obj)  //{a:2,b:{c:1}} console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2
console.log(obj)  //{a:2,b:{c:2}} console.log(obj2); //{a:1,b:{c:2}}
/* 数组的拷贝 */
let arr = [1, 2, 3];
let newArr = [...arr]; // 跟 arr.slice()是一样的成果

扩大运算符 和 object.assign 有同样的缺点,也就是 实现的浅拷贝的性能差不多 ,然而如果属性都是 根本类型的值,应用扩大运算符进行浅拷贝会更加不便

办法三:concat 拷贝数组

数组的 concat 办法其实也是浅拷贝,所以连贯一个含有援用类型的数组时,须要留神批改原数组中的元素的属性,因为它会影响拷贝之后连贯的数组。不过 concat 只能用于数组的浅拷贝,应用场景比拟局限。代码如下所示。

let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr);  // [1, 2, 3]
console.log(newArr); // [1, 100, 3]

办法四:slice 拷贝数组

slice 办法也比拟有局限性,因为 它仅仅针对数组类型slice 办法会返回一个新的数组对象,这一对象由该办法的前两个参数来决定原数组截取的开始和完结工夫,是不会影响和扭转原始数组的。

slice 的语法为:arr.slice(begin, end);
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr);  //[1, 2, { val: 1000} ]

从下面的代码中能够看出,这就是 浅拷贝的限度所在了——它只能拷贝一层对象 。如果 存在对象的嵌套,那么浅拷贝将无能为力。因而深拷贝就是为了解决这个问题而生的,它能解决多层对象嵌套问题,彻底实现拷贝

手工实现一个浅拷贝

依据以上对浅拷贝的了解,如果让你本人实现一个浅拷贝,大抵的思路分为两点:

  • 对根底类型做一个最根本的一个拷贝;
  • 对援用类型开拓一个新的存储,并且拷贝一层对象属性。
const shallowClone = (target) => {if (typeof target === 'object' && target !== null) {const cloneTarget = Array.isArray(target) ? []: {};
    for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = target[prop];
      }
    }
    return cloneTarget;
  } else {return target;}
}

利用类型判断,针对援用类型的对象进行 for 循环遍历对象属性赋值给指标对象的属性,根本就能够手工实现一个浅拷贝的代码了

2. 深拷贝的原理和实现

浅拷贝只是创立了一个新的对象,复制了原有对象的根本类型的值,而援用数据类型只拷贝了一层属性,再深层的还是无奈进行拷贝。深拷贝则不同,对于简单援用数据类型,其在堆内存中齐全开拓了一块内存地址,并将原有的对象齐全复制过去寄存。

这两个对象是互相独立、不受影响的,彻底实现了内存上的拆散。总的来说,深拷贝的原理能够总结如下

将一个对象从内存中残缺地拷贝进去一份给指标对象,并从堆内存中开拓一个全新的空间寄存新对象,且新对象的批改并不会扭转原对象,二者实现真正的拆散。

办法一:乞丐版(JSON.stringify)

JSON.stringify() 是目前开发过程中最简略的深拷贝办法,其实就是把一个对象序列化成为 JSON 的字符串,并将对象外面的内容转换成字符串,最初再用 JSON.parse() 的办法将 JSON 字符串生成一个新的对象

let a = {
    age: 1,
    jobs: {first: 'FE'}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

然而该办法也是有局限性的

  • 会疏忽 undefined
  • 会疏忽 symbol
  • 不能序列化函数
  • 无奈拷贝不可枚举的属性
  • 无奈拷贝对象的原型链
  • 拷贝 RegExp 援用类型会变成空对象
  • 拷贝 Date 援用类型会变成字符串
  • 对象中含有 NaNInfinity 以及 -InfinityJSON 序列化的后果会变成 null
  • 不能解决循环援用的对象,即对象成环 (obj[key] = obj)。
function Obj() {this.func = function () {alert(1) }; 
  this.obj = {a:1};
  this.arr = [1,2,3];
  this.und = undefined; 
  this.reg = /123/; 
  this.date = new Date(0); 
  this.NaN = NaN;
  this.infinity = Infinity;
  this.sym = Symbol(1);
} 
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{ 
  enumerable:false,
  value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);

应用 JSON.stringify 办法实现深拷贝对象,尽管到目前为止还有很多无奈实现的性能,然而这种办法足以满足日常的开发需要,并且是最简略和快捷的。而对于其余的也要实现深拷贝的,比拟麻烦的属性对应的数据类型,JSON.stringify 临时还是无奈满足的,那么就须要上面的几种办法了

办法二:根底版(手写递归实现)

上面是一个实现 deepClone 函数封装的例子,通过 for in 遍历传入参数的属性值,如果值是援用类型则再次递归调用该函数,如果是根底数据类型就间接复制

let obj1 = {
  a:{b:1}
}
function deepClone(obj) {let cloneObj = {}
  for(let key in obj) {                 // 遍历
    if(typeof obj[key] ==='object') {cloneObj[key] = deepClone(obj[key])  // 是对象就再次调用该函数递归
    } else {cloneObj[key] = obj[key]  // 根本类型的话间接复制值
    }
  }
  return cloneObj
}
let obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2);   //  {a:{b:1}}

尽管利用递归能实现一个深拷贝,然而同下面的 JSON.stringify 一样,还是有一些问题没有齐全解决,例如:

  • 这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
  • 这种办法 只是针对一般的援用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的援用类型并不能正确地拷贝;
  • 对象的属性外面成环,即 循环援用没有解决

这种根底版本的写法也比较简单,能够应答大部分的利用状况。然而你在面试的过程中,如果只能写出这样的一个有缺点的深拷贝办法,有可能不会通过。

所以为了“援救”这些缺点,上面我带你一起看看改良的版本,以便于你能够在面试种呈现出更好的深拷贝办法,博得面试官的青眼。

办法三:改进版(改良后递归实现)

针对下面几个待解决问题,我先通过四点相干的实践通知你别离应该怎么做。

  • 针对可能遍历对象的不可枚举属性以及 Symbol 类型,咱们能够应用 Reflect.ownKeys 办法;
  • 当参数为 Date、RegExp 类型,则间接生成一个新的实例返回;
  • 利用 ObjectgetOwnPropertyDescriptors 办法能够取得对象的所有属性,以及对应的个性,顺便联合 Object.create 办法创立一个新对象,并继承传入原对象的原型链;
  • 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱援用类型,能够无效避免内存透露(你能够关注一下 MapweakMap 的要害区别,这里要用 weakMap),作为检测循环援用很有帮忙,如果存在循环,则援用间接返回 WeakMap 存储的值

如果你在思考到循环援用的问题之后,还能用 WeakMap 来很好地解决,并且向面试官解释这样做的目标,那么你所展现的代码,以及你对问题思考的全面性,在面试官眼中应该算是合格的了

实现深拷贝

const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) {if (obj.constructor === Date) {return new Date(obj)       // 日期对象间接返回一个新的日期对象
  }

  if (obj.constructor === RegExp){return new RegExp(obj)     // 正则对象间接返回一个新的正则对象
  }

  // 如果循环援用了就用 weakMap 来解决
  if (hash.has(obj)) {return hash.get(obj)
  }
  let allDesc = Object.getOwnPropertyDescriptors(obj)

  // 遍历传入参数所有键的个性
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

  // 把 cloneObj 原型复制到 obj 上
  hash.set(obj, cloneObj)

  for (let key of Reflect.ownKeys(obj)) {cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
  }
  return cloneObj
}
// 上面是验证代码
let obj = {
  num: 0,
  str: '',
  boolean: true,
  unf: undefined,
  nul: null,
  obj: {name: '我是一个对象', id: 1},
  arr: [0, 1, 2],
  func: function () { console.log('我是一个函数') },
  date: new Date(0),
  reg: new RegExp('/ 我是一个正则 /ig'),
  [Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {enumerable: false, value: '不可枚举属性'}
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj    // 设置 loop 成循环援用的属性
let cloneObj = deepClone(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)

咱们看一下后果,cloneObjobj 的根底上进行了一次深拷贝,cloneObj 里的 arr 数组进行了批改,并未影响到 obj.arr 的变动,如下图所示

vue 渲染过程

  • 调用 compile 函数, 生成 render 函数字符串 , 编译过程如下:

    • parse 应用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为形象语法树 AST。模板 -> AST(最耗费性能)
    • optimize 遍历 AST,找到其中的一些动态节点并进行标记,不便在页面重渲染的时候进行 diff 比拟时,间接跳过这一些动态节点,优化 runtime 的性能
    • generate 将最终的 AST 转化为 render 函数字符串
  • 调用 new Watcher 函数, 监听数据的变动, 当数据发生变化时,Render 函数执行生成 vnode 对象
  • 调用 patch 办法, 比照新旧 vnode 对象, 通过 DOM diff 算法, 增加、批改、删除真正的 DOM 元素

nextTick

nextTick 能够让咱们在下次 DOM 更新循环完结之后执行提早回调,用于取得更新后的 DOM

nextTick次要应用了宏工作和微工作。依据执行环境别离尝试采纳

  • Promise
  • MutationObserver
  • setImmediate
  • 如果以上都不行则采纳setTimeout

定义了一个异步办法,屡次调用 nextTick 会将办法存入队列中,通过这个异步办法清空以后队列

谈一谈队头阻塞问题

什么是队头阻塞?

对于每一个 HTTP 申请而言,这些工作是会被放入一个工作队列中串行执行的,一旦队首工作申请太慢时,就会阻塞前面的申请解决,这就是 HTTP 队头阻塞 问题。

有什么解决办法吗👇

并发连贯

咱们晓得对于一个域名而言,是容许调配多个长连贯的,那么能够了解成减少了工作队列,也就是说不会导致一个工作阻塞了该工作队列的其余工作,在 RFC 标准 中规定客户端最多并发 2 个连贯,不过理论状况就是要比这个还要多,举个例子,Chrome 中是 6 个。

域名分片

  • 顾名思义,咱们能够在一个域名下分出多个二级域名进去,而它们最终指向的还是同一个服务器,这样子的话就能够并发解决的工作队列更多,也更好的解决了队头阻塞的问题。
  • 举个例子,比方TianTian.com,能够分出很多二级域名,比方Day1.TianTian.comDay2.TianTian.com,Day3.TianTian.com, 这样子就能够无效解决队头阻塞问题。

meta 标签:主动刷新 / 跳转

假如要实现一个相似 PPT 自动播放的成果,你很可能会想到应用 JavaScript 定时器管制页面跳转来实现。但其实有更加简洁的实现办法,比方通过 meta 标签来实现:

<meta http-equiv="Refresh" content="5; URL=page2.html">

下面的代码会在 5s 之后主动跳转到同域下的 page2.html 页面。咱们要实现 PPT 自动播放的性能,只须要在每个页面的 meta 标签内设置好下一个页面的地址即可。

另一种场景,比方每隔一分钟就须要刷新页面的大屏幕监控,也能够通过 meta 标签来实现,只需去掉前面的 URL 即可:

<meta http-equiv="Refresh" content="60">

meta viewport 相干

<!DOCTYPE html>  <!--H5 规范申明,应用 HTML5 doctype,不辨别大小写 -->
<head lang=”en”> <!-- 规范的 lang 属性写法 -->
<meta charset=’utf-8′>    <!-- 申明文档应用的字符编码 -->
<meta http-equiv=”X-UA-Compatible”content=”IE=edge,chrome=1″/>   <!-- 优先应用 IE 最新版本和 Chrome-->
<meta name=”description”content=”不超过 150 个字符”/>       <!-- 页面形容 -->
<meta name=”keywords”content=””/>     <!-- 页面关键词 -->
<meta name=”author”content=”name, [email protected]”/>    <!-- 网页作者 -->
<meta name=”robots”content=”index,follow”/>      <!-- 搜索引擎抓取 -->
<meta name=”viewport”content=”initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no”> <!-- 为挪动设施增加 viewport-->
<meta name=”apple-mobile-web-app-title”content=”题目”> <!--iOS 设施 begin-->
<meta name=”apple-mobile-web-app-capable”content=”yes”/>  <!-- 增加到主屏后的题目(iOS 6 新增)是否启用 WebApp 全屏模式,删除苹果默认的工具栏和菜单栏 -->
<meta name=”apple-itunes-app”content=”app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL”>
<!-- 增加智能 App 广告条 Smart App Banner(iOS 6+ Safari)-->
<meta name=”apple-mobile-web-app-status-bar-style”content=”black”/>
<meta name=”format-detection”content=”telphone=no, email=no”/>  <!-- 设置苹果工具栏色彩 -->
<meta name=”renderer”content=”webkit”> <!-- 启用 360 浏览器的极速模式(webkit)-->
<meta http-equiv=”X-UA-Compatible”content=”IE=edge”>     <!-- 防止 IE 应用兼容模式 -->
<meta http-equiv=”Cache-Control”content=”no-siteapp”/>    <!-- 不让百度转码 -->
<meta name=”HandheldFriendly”content=”true”>     <!-- 针对手持设施优化,次要是针对一些老的不辨认 viewport 的浏览器,比方黑莓 -->
<meta name=”MobileOptimized”content=”320″>   <!-- 微软的老式浏览器 -->
<meta name=”screen-orientation”content=”portrait”>   <!--uc 强制竖屏 -->
<meta name=”x5-orientation”content=”portrait”>    <!--QQ 强制竖屏 -->
<meta name=”full-screen”content=”yes”>              <!--UC 强制全屏 -->
<meta name=”x5-fullscreen”content=”true”>       <!--QQ 强制全屏 -->
<meta name=”browsermode”content=”application”>   <!--UC 利用模式 -->
<meta name=”x5-page-mode”content=”app”>   <!-- QQ 利用模式 -->
<meta name=”msapplication-tap-highlight”content=”no”>    <!--windows phone 点击无高亮
设置页面不缓存 -->
<meta http-equiv=”pragma”content=”no-cache”>
<meta http-equiv=”cache-control”content=”no-cache”>
<meta http-equiv=”expires”content=”0″>

参考:前端进阶面试题具体解答

diff 算法是怎么运作

每一种节点类型有本人的属性,也就是 prop,每次进行 diff 的时候,react 会先比拟该节点类型,如果节点类型不一样,那么 react 会间接删除该节点,而后间接创立新的节点插入到其中,如果节点类型一样,那么会比拟 prop 是否有更新,如果有 prop 不一样,那么 react 会断定该节点有更新,那么重渲染该节点,而后在对其子节点进行比拟,一层一层往下,直到没有子节点

  • 把树形构造依照层级合成,只比拟同级元素。
  • 给列表构造的每个单元增加惟一的 key 属性,不便比拟。
  • React 只会匹配雷同 classcomponent(这外面的 class 指的是组件的名字)
  • 合并操作,调用 componentsetState 办法的时候, React 将其标记为 – dirty. 到每一个事件循环完结, React 查看所有标记 dirtycomponent从新绘制.
  • 抉择性子树渲染。开发人员能够重写 shouldComponentUpdate 进步 diff 的性能

优化⬇️

为了升高算法复杂度,Reactdiff 会预设三个限度:

  1. 只对同级元素进行 Diff。如果一个DOM 节点 在前后两次更新中逾越了层级,那么 React 不会尝试复用他。
  2. 两个不同类型的元素会产生出不同的树。如果元素由 div 变为 p,React 会销毁div 及其子孙节点,并新建 p 及其子孙节点。
  3. 开发者能够通过 key prop来暗示哪些子元素在不同的渲染下能保持稳定。思考如下例子:

Diff 的思路

该如何设计算法呢?如果让我设计一个Diff 算法,我首先想到的计划是:

  1. 判断以后节点的更新属于哪种状况
  2. 如果是 新增,执行新增逻辑
  3. 如果是 删除,执行删除逻辑
  4. 如果是 更新,执行更新逻辑
  5. 按这个计划,其实有个隐含的前提——不同操作的优先级是雷同的
  6. 然而 React 团队 发现,在日常开发中,相较于 新增 删除 更新 组件产生的频率更高。所以 Diff 会优先判断以后节点是否属于 更新

基于以上起因,Diff 算法 的整体逻辑会经验两轮遍历:

  • 第一轮遍历:解决 更新 的节点。
  • 第二轮遍历:解决剩下的不属于 更新 的节点。

diff 算法的作用

计算出 Virtual DOM 中真正变动的局部,并只针对该局部进行原生 DOM 操作,而非从新渲染整个页面。

传统 diff 算法

通过循环递归对节点进行顺次比照,算法复杂度达到 O(n^3),n 是树的节点数,这个有多可怕呢?——如果要展现 1000 个节点,得执行上亿次比拟。。即使是 CPU 快能执行 30 亿条命令,也很难在一秒内计算出差别。

React 的 diff 算法

  1. 什么是和谐?

将 Virtual DOM 树转换成 actual DOM 树的起码操作的过程 称为 和谐。

  1. 什么是 React diff 算法?

diff算法是和谐的具体实现。

diff 策略

React 用 三大策略 将 O(n^3)复杂度 转化为 O(n)复杂度

策略一(tree diff):

  • Web UI 中 DOM 节点跨层级的挪动操作特地少,能够忽略不计。

策略二(component diff):

  • 领有雷同类的两个组件 生成类似的树形构造,
  • 领有不同类的两个组件 生成不同的树形构造。

策略三(element diff):

对于同一层级的一组子节点,通过惟一 id 辨别。

tree diff

  • React 通过 updateDepth 对 Virtual DOM 树进行层级管制。
  • 对树分层比拟,两棵树 只对同一档次节点 进行比拟。如果该节点不存在时,则该节点及其子节点会被齐全删除,不会再进一步比拟。
  • 只需遍历一次,就能实现整棵 DOM 树的比拟。

那么问题来了,如果 DOM 节点呈现了跨层级操作,diff 会咋办呢?

答:diff 只简略思考同层级的节点地位变换,如果是跨层级的话,只有创立节点和删除节点的操作。

如上图所示,以 A 为根节点的整棵树会被从新创立,而不是挪动,因而 官网倡议不要进行 DOM 节点跨层级操作,能够通过 CSS 暗藏、显示节点,而不是真正地移除、增加 DOM 节点

component diff

React 对不同的组件间的比拟,有三种策略

  1. 同一类型的两个组件,按原策略(层级比拟)持续比拟 Virtual DOM 树即可。
  2. 同一类型的两个组件,组件 A 变动为组件 B 时,可能 Virtual DOM 没有任何变动,如果晓得这点(变换的过程中,Virtual DOM 没有扭转),可节俭大量计算工夫,所以 用户 能够通过 shouldComponentUpdate() 来判断是否须要 判断计算。
  3. 不同类型的组件,将一个(将被扭转的)组件判断为dirty component(脏组件),从而替换 整个组件的所有节点。

留神:如果组件 D 和组件 G 的构造类似,然而 React 判断是 不同类型的组件,则不会比拟其构造,而是删除 组件 D 及其子节点,创立组件 G 及其子节点。

element diff

当节点处于同一层级时,diff 提供三种节点操作:删除、插入、挪动。

  • 插入:组件 C 不在汇合(A,B)中,须要插入
  • 删除:

    • 组件 D 在汇合(A,B,D)中,但 D 的节点曾经更改,不能复用和更新,所以须要删除 旧的 D,再创立新的。
    • 组件 D 之前在 汇合(A,B,D)中,但汇合变成新的汇合(A,B)了,D 就须要被删除。
  • 挪动:组件 D 曾经在汇合(A,B,C,D)里了,且汇合更新时,D 没有产生更新,只是地位扭转,如新汇合(A,D,B,C),D 在第二个,毋庸像传统 diff,让旧汇合的第二个 B 和新汇合的第二个 D 比拟,并且删除第二个地位的 B,再在第二个地位插入 D,而是(对同一层级的同组子节点)增加惟一 key 进行辨别,挪动即��。

总结

  1. tree diff:只比照同一层的 dom 节点,疏忽 dom 节点的跨层级挪动

如下图,react 只会对雷同色彩方框内的 DOM 节点进行比拟,即同一个父节点下的所有子节点。当发现节点不存在时,则该节点及其子节点会被齐全删除掉,不会用于进一步的比拟。

这样只须要对树进行一次遍历,便能实现整个 DOM 树的比拟。

这就意味着,如果 dom 节点产生了跨层级挪动,react 会删除旧的节点,生成新的节点,而不会复用。

  1. component diff:如果不是同一类型的组件,会删除旧的组件,创立新的组件
  1. element diff:对于同一层级的一组子节点,须要通过惟一 id 进行来辨别
  2. 如果没有 id 来进行辨别,一旦有插入动作,会导致插入地位之后的列表全副从新渲染
  3. 这也是为什么渲染列表时为什么要应用惟一的 key。

diff 的有余与待优化的中央

尽量减少相似将最初一个节点挪动到列表首部的操作,当节点数量过大或更新操作过于频繁时,会影响 React 的渲染性能

与其余框架相比,React 的 diff 算法有何不同?

diff 算法探讨的就是虚构 DOM 树发生变化后,生成 DOM 树更新补丁的形式。它通过比照新旧两株虚构 DOM 树的变更差别,将更新补丁作用于实在 DOM,以最小老本实现视图更新

具体的流程是这样的:

  • 实在 DOM 与虚构 DOM 之间存在一个映射关系。这个映射关系依附初始化时的 JSX 建设实现;
  • 当虚构 DOM 发生变化后,就会依据差距计算生成 patch,这个 patch 是一个结构化的数据,内容蕴含了减少、更新、移除等;
  • 最初再依据 patch 去更新实在的 DOM,反馈到用户的界面上。

在答复有何不同之前,首先须要阐明下什么是 diff 算法。

  • diff 算法是指生成更新补丁的形式 ,次要利用于 虚构 DOM 树变动后,更新实在 DOM。所以 diff 算法肯定存在这样一个过程:触发更新 → 生成补丁 → 利用补丁
  • React 的 diff 算法,触发更新的机会次要在 state 变动与 hooks 调用之后。此时触发虚构 DOM 树变更遍历,采纳了深度优先遍历算法。但传统的遍历形式,效率较低。为了优化效率,应用了分治的形式。将繁多节点比对转化为了 3 种类型节点的比对 ,别离是 树、组件及元素,以此晋升效率。

    • 树比对:因为网页视图中较少有跨层级节点挪动,两株虚构 DOM 树只对同一档次的节点进行比拟。
    • 组件比对:如果组件是同一类型,则进行树比对,如果不是,则间接放入到补丁中。
    • 元素比对:次要产生在同层级中,通过标记节点操作生成补丁,节点操作对应实在的 DOM 剪裁操作。同一层级的子节点,能够通过标记 key 的形式进行列表比照。
  • 以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构 。为了使整个更新过程 可随时暂停复原,节点与树别离采纳了 FiberNode 与 FiberTree 进行重构fiberNode 应用了双链表的构造,能够间接找到兄弟节点与子节点
  • 而后拿 Vue 和 Preact 与 React 的 diff 算法进行比照

    • PreactDiff 算法相较于 React,整体设计思路类似,但最底层的元素采纳了实在 DOM 比照操作,也没有采纳 Fiber 设计。Vue 的 Diff 算法整体也与 React 类似,同样未实现 Fiber 设计
  • 而后进行横向比拟,React 领有残缺的 Diff 算法策略,且领有随时中断更新的工夫切片能力,在大批量节点更新的极其状况下,领有更敌对的交互体验。
  • Preact 能够在一些对性能要求不高,仅须要渲染框架的简略场景下利用。
  • Vue 的整体 diff 策略与 React 对齐,尽管不足工夫切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,前期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其余的场景简直都能够应用防抖和节流去进步响应性能。

学习原理的目标就是利用。那如何依据 React diff 算法原理优化代码呢?这个问题其实按优化形式逆向答复即可。

  • 依据 diff 算法的设计准则,应尽量避免跨层级节点挪动。
  • 通过设置惟一 key 进行优化,尽量减少组件层级深度。因为过深的层级会加深遍历深度,带来性能问题。
  • 设置 shouldComponentUpdate 或者 React.pureComponet 缩小 diff 次数。

变量晋升

当执行 JS 代码时,会生成执行环境,只有代码不是写在函数中的,就是在全局执行环境中,函数中的代码会产生函数执行环境,只此两种执行环境。

b() // call b
console.log(a) // undefined

var a = 'Hello world'

function b() {console.log('call b')
}

想必以上的输入大家必定都曾经明确了,这是因为函数和变量晋升的起因。通常晋升的解释是说将申明的代码移动到了顶部,这其实没有什么谬误,便于大家了解。然而更精确的解释应该是:在生成执行环境时,会有两个阶段。第一个阶段是创立的阶段,JS 解释器会找出须要晋升的变量和函数,并且给他们提前在内存中开拓好空间,函数的话会将整个函数存入内存中,变量只申明并且赋值为 undefined,所以在第二个阶段,也就是代码执行阶段,咱们能够间接提前应用

  • 在晋升的过程中,雷同的函数会笼罩上一个函数,并且函数优先于变量晋升
b() // call b second

function b() {console.log('call b fist')
}
function b() {console.log('call b second')
}
var b = 'Hello world'

var 会产生很多谬误,所以在 ES6 中引入了 letlet不能在申明前应用,然而这并不是常说的 let 不会晋升,let晋升了,在第一阶段内存也曾经为他开拓好了空间,然而因为这个申明的个性导致了并不能在申明前应用

createElement 过程

React.createElement():依据指定的第一个参数创立一个 React 元素

React.createElement(
  type,
  [props],
  [...children]
)
  • 第一个参数是必填,传入的是似 HTML 标签名称,eg: ul, li
  • 第二个参数是选填,示意的是属性,eg: className
  • 第三个参数是选填, 子节点,eg: 要显示的文本内容
// 写法一:var child1 = React.createElement('li', null, 'one');
    var child2 = React.createElement('li', null, 'two');
    var content = React.createElement('ul', { className: 'teststyle'}, child1, child2); // 第三个参数能够离开也能够写成一个数组
      ReactDOM.render(
          content,
        document.getElementById('example')
      );

// 写法二:var child1 = React.createElement('li', null, 'one');
    var child2 = React.createElement('li', null, 'two');
    var content = React.createElement('ul', { className: 'teststyle'}, [child1, child2]);
      ReactDOM.render(
          content,
        document.getElementById('example')
      );

数字证书是什么?

当初的办法也不肯定是平安的,因为没有方法确定失去的公钥就肯定是平安的公钥。可能存在一个中间人,截取了对方发给咱们的公钥,而后将他本人的公钥发送给咱们,当咱们应用他的公钥加密后发送的信息,就能够被他用本人的私钥解密。而后他伪装成咱们以同样的办法向对方发送信息,这样咱们的信息就被窃取了,然而本人还不晓得。为了解决这样的问题,能够应用数字证书。

首先应用一种 Hash 算法来对公钥和其余信息进行加密,生成一个信息摘要,而后让有公信力的认证核心(简称 CA)用它的私钥对音讯摘要加密,造成签名。最初将原始的信息和签名合在一起,称为数字证书。当接管方收到数字证书的时候,先依据原始信息应用同样的 Hash 算法生成一个摘要,而后应用公证处的公钥来对数字证书中的摘要进行解密,最初将解密的摘要和生成的摘要进行比照,就能发现失去的信息是否被更改了。

这个办法最要的是认证核心的可靠性,个别浏览器里会内置一些顶层的认证核心的证书,相当于咱们主动信赖了他们,只有这样能力保证数据的平安。

工程化

介绍一下 webpack 的构建流程

外围概念

  • entry:入口。webpack 是基于模块的,应用 webpack 首先须要指定模块解析入口(entry),webpack 从入口开始依据模块间依赖关系递归解析和解决所有资源文件。
  • output:输入。源代码通过 webpack 解决之后的最终产物。
  • loader:模块转换器。实质就是一个函数,在该函数中对接管到的内容进行转换,返回转换后的后果。因为 Webpack 只意识 JavaScript,所以 Loader 就成了翻译官,对其余类型的资源进行转译的预处理工作。
  • plugin:扩大插件。基于事件流框架 Tapable,插件能够扩大 Webpack 的性能,在 Webpack 运行的生命周期中会播送出许多事件,Plugin 能够监听这些事件,在适合的机会通过 Webpack 提供的 API 扭转输入后果。
  • module:模块。除了 js 领域内的 es module、commonJs、AMD 等,css @import、url(...)、图片、字体等在 webpack 中都被视为模块。

解释几个 webpack 中的术语

  • module:指在模块化编程中咱们把应用程序宰割成的独立性能的代码模块
  • chunk:指模块间依照援用关系组合成的代码块,一个 chunk 中能够蕴含多个 module
  • chunk group:指通过配置入口点(entry point)辨别的块组,一个 chunk group 中可蕴含一到多个 chunk
  • bundling:webpack 打包的过程
  • asset/bundle:打包产物

webpack 的打包思维能够简化为 3 点:

  • 所有源代码文件均可通过各种 Loader 转换为 JS 模块(module),模块之间能够相互援用。
  • webpack 通过入口点(entry point)递归解决各模块援用关系,最初输入为一个或多个产物包 js(bundle) 文件。
  • 每一个入口点都是一个块组(chunk group),在不思考分包的状况下,一个 chunk group 中只有一个 chunk,该 chunk 蕴含递归剖析后的所有模块。每一个 chunk 都有对应的一个打包后的输入文件(asset/bundle

打包流程

  1. 初始化参数:从配置文件和 Shell 语句中读取并合并参数,得出最终的配置参数。
  2. 开始编译:从上一步失去的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 办法开始执行编译。
  3. 确定入口:依据配置中的 entry 找出所有的入口文件。
  4. 编译模块:从入口文件登程,调用所有配置的 loader 对模块进行翻译,再找出该模块依赖的模块,这个步骤是递归执行的,直至所有入口依赖的模块文件都通过本步骤的解决。
  5. 实现模块编译:通过第 4 步应用 loader 翻译完所有模块后,失去了每个模块被翻译后的最终内容以及它们之间的依赖关系。
  6. 输入资源:依据入口和模块之间的依赖关系,组装成一个个蕴含多个模块的 chunk,再把每个 chunk 转换成一个独自的文件退出到输入列表,这一步是能够批改输入内容的最初机会。
  7. 输入实现:在确定好输入内容后,依据配置确定输入的门路和文件名,把文件内容写入到文件系统。

简版

  • Webpack CLI 启动打包流程;
  • 载入 Webpack 外围模块,创立 Compiler 对象;
  • 应用 Compiler 对象开始编译整个我的项目;
  • 从入口文件开始,解析模块依赖,造成依赖关系树;
  • 递归依赖树,将每个模块交给对应的 Loader 解决;
  • 合并 Loader 解决完的后果,将打包后果输入到 dist 目录。

在以上过程中,Webpack 会在特定的工夫点播送出特定的事件,插件在监听到相干事件后会执行特定的逻辑,并且插件能够调用 Webpack 提供的 API 扭转 Webpack 的运行后果

构建流程外围概念:

  • Tapable:一个基于公布订阅的事件流工具类,CompilerCompilation 对象都继承于 Tapable
  • Compiler:compiler 对象是一个全局单例,他负责把控整个 webpack 打包的构建流程。在编译初始化阶段被创立的全局单例,蕴含残缺配置信息、loaders、plugins 以及各种工具办法
  • Compilation:代表一次 webpack 构建和生成编译资源的的过程,在 watch 模式下每一次文件变更触发的从新编译都会生成新的 Compilation 对象,蕴含了以后编译的模块 module, 编译生成的资源,变动的文件, 依赖的状态等
  • 而每个模块间的依赖关系,则依赖于 AST 语法树。每个模块文件在通过 Loader 解析实现之后,会通过 acorn 库生成模块代码的 AST 语法树,通过语法树就能够剖析这个模块是否还有依赖的模块,进而持续循环执行下一个模块的编译解析。

最终 Webpack 打包进去的 bundle 文件是一个 IIFE 的执行函数。

// webpack 5 打包的 bundle 文件内容

(() => { // webpackBootstrap
    var __webpack_modules__ = ({'file-A-path': ((modules) => {// ...})
        'index-file-path': ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {// ...})
    })

    // The module cache
    var __webpack_module_cache__ = {};

    // The require function
    function __webpack_require__(moduleId) {
        // Check if module is in cache
        var cachedModule = __webpack_module_cache__[moduleId];
        if (cachedModule !== undefined) {return cachedModule.exports;}
        // Create a new module (and put it into the cache)
        var module = __webpack_module_cache__[moduleId] = {
                // no module.id needed
                // no module.loaded needed
                exports: {}};

        // Execute the module function
        __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

        // Return the exports of the module
        return module.exports;
    }

    // startup
    // Load entry module and return exports
    // This entry module can't be inlined because the eval devtool is used.
    var __webpack_exports__ = __webpack_require__("./src/index.js");
})

webpack 具体工作流程

组件之间通信

  • 父子组件通信
  • 自定义事件
  • redux 和 context

context 如何使用

  • 父组件向其下所有子孙组件传递信息
  • 如一些简略的信息:主题、语言
  • 简单的公共信息用 redux

在跨层级通信中,次要分为一层或多层的状况

  • 如果只有一层,那么依照 React 的树形构造进行分类的话,次要有以下三种状况:父组件向子组件通信 子组件向父组件通信 以及 平级的兄弟组件间相互通信
  • 在父与子的状况下,因为 React 的设计实际上就是传递 Props 即可。那么场景体现在容器组件与展现组件之间,通过 Props 传递 state,让展现组件受控。
  • 在子与父的状况下 ,有两种形式,别离是回调函数与实例函数。回调函数,比方输入框向父级组件返回输出内容,按钮向父级组件传递点击事件等。实例函数的状况有些特地,次要是在父组件中 通过 React 的 ref API 获取子组件的实例 ,而后是 通过实例调用子组件的实例函数。这种形式在过来常见于 Modal 框的显示与暗藏
  • 多层级间的数据通信,有两种状况。第一种是一个容器中蕴含了多层子组件,须要最底部的子组件与顶部组件进行通信。在这种状况下,如果一直透传 Props 或回调函数,不仅代码层级太深,后续也很不好保护。第二种是两个组件不相干,在整个 React 的组件树的两侧,齐全不相交。那么基于多层级间的通信个别有三个计划。

    • 第一个是应用 React 的 Context API,最常见的用处是做语言包国际化
    • 第二个是应用全局变量与事件。
    • 第三个是应用状态治理框架,比方 Flux、Redux 及 Mobx。长处是因为引入了状态治理,使得我的项目的开发模式与代码构造得以束缚,毛病是学习老本绝对较高

并发与并行的区别?

  • 并发是宏观概念,我别离有工作 A 和工作 B,在一段时间内通过工作间的切换实现了这两个工作,这种状况就能够称之为并发。
  • 并行是宏观概念,假如 CPU 中存在两个外围,那么我就能够同时实现工作 A、B。同时实现多个工作的状况就能够称之为并行。

实现一个扇形

用 CSS 实现扇形的思路和三角形基本一致,就是多了一个圆角的款式,实现一个 90°的扇形:

div{
    border: 100px solid transparent;
    width: 0;
    heigt: 0;
    border-radius: 100px;
    border-top-color: red;
}

TCP 的流量管制机制

一般来说,流量管制就是为了让发送方发送数据的速度不要太快,要让接管方来得及接管。TCP 采纳大小可变的 滑动窗口 进行流量管制,窗口大小的单位是字节。这里说的窗口大小其实就是每次传输的数据大小。

  • 当一个连贯建设时,连贯的每一端调配一个缓冲区来保留输出的数据,并将缓冲区的大小发送给另一端。
  • 当数据达到时,接管方发送确认,其中蕴含了本人残余的缓冲区大小。(残余的缓冲区空间的大小被称为窗口,指出窗口大小的告诉称为窗口通告。接管方在发送的每一确认中都含有一个窗口通告。)
  • 如果接管方应用程序读数据的速度可能与数据达到的速度一样快,接管方将在每一确认中发送一个正的窗口通告。
  • 如果发送方操作的速度快于接管方,接管到的数据最终将充斥接管方的缓冲区,导致接管方通告一个零窗口。发送方收到一个零窗口通告时,必须进行发送,直到接管方从新通告一个正的窗口。

Nginx 的概念及其工作原理

Nginx 是一款轻量级的 Web 服务器,也能够用于反向代理、负载平衡和 HTTP 缓存等。Nginx 应用异步事件驱动的办法来解决申请,是一款面向性能设计的 HTTP 服务器。

传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于 event-driven 模型的。正是这个次要的区别带给了 Nginx 在性能上的劣势。

Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其余的 worker process,这一点和 Apache 十分像,然而 Nginx 的 worker process 能够同时解决大量的 HTTP 申请,而每个 Apache process 只能解决一个。

如何解决 1px 问题?

1px 问题指的是:在一些 Retina 屏幕 的机型上,挪动端页面的 1px 会变得很粗,呈现出不止 1px 的成果。起因很简略——CSS 中的 1px 并不能和挪动设施上的 1px 划等号。它们之间的比例关系有一个专门的属性来形容:

window.devicePixelRatio = 设施的物理像素 / CSS 像素。

关上 Chrome 浏览器,启动挪动端调试模式,在控制台去输入这个 devicePixelRatio 的值。这里选中 iPhone6/7/8 这系列的机型,输入的后果就是 2:这就意味着设置的 1px CSS 像素,在这个设施上理论会用 2 个物理像素单元来进行渲染,所以理论看到的肯定会比 1px 粗一些。解决 1px 问题的三种思路:

思路一:间接写 0.5px

如果之前 1px 的款式这样写:

border:1px solid #333

能够先在 JS 中拿到 window.devicePixelRatio 的值,而后把这个值通过 JSX 或者模板语法给到 CSS 的 data 里,达到这样的成果(这里用 JSX 语法做示范):

<div id="container" data-device={{window.devicePixelRatio}}></div>

而后就能够在 CSS 中用属性选择器来命中 devicePixelRatio 为某一值的状况,比如说这里尝试命中 devicePixelRatio 为 2 的状况:

#container[data-device="2"] {border:0.5px solid #333}

间接把 1px 改成 1/devicePixelRatio 后的值,这是目前为止最简略的一种办法。这种办法的缺点在于兼容性不行,IOS 零碎须要 8 及以上的版本,安卓零碎则间接不兼容。

思路二:伪元素先放大后放大

这个办法的可行性会更高,兼容性也更好。惟一的毛病是代码会变多。

思路是 先放大、后放大:在指标元素的前面追加一个 ::after 伪元素,让这个元素布局为 absolute 之后、整个伸开展铺在指标元素上,而后把它的宽和高都设置为指标元素的两倍,border 值设为 1px。接着借助 CSS 动画特效中的放缩能力,把整个伪元素放大为原来的 50%。此时,伪元素的宽高刚好能够和原有的指标元素对齐,而 border 也放大为了 1px 的二分之一,间接地实现了 0.5px 的成果。

代码如下:

#container[data-device="2"] {position: relative;}
#container[data-device="2"]::after{
      position:absolute;
      top: 0;
      left: 0;
      width: 200%;
      height: 200%;
      content:"";
      transform: scale(0.5);
      transform-origin: left top;
      box-sizing: border-box;
      border: 1px solid #333;
    }
}

思路三:viewport 缩放来解决

这个思路就是对 meta 标签里几个要害属性下手:

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

这里针对像素比为 2 的页面,把整个页面缩放为了原来的 1 / 2 大小。这样,原本占用 2 个物理像素的 1px 款式,当初占用的就是规范的一个物理像素。依据像素比的不同,这个缩放比例能够被计算为不同的值,用 js 代码实现如下:

const scale = 1 / window.devicePixelRatio;
// 这里 metaEl 指的是 meta 标签对应的 Dom
metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`);

这样解决了,但这样做的副作用也很大,整个页面被缩放了。这时 1px 曾经被解决成物理像素大小,这样的大小在手机上显示边框很适合。然而,一些本来不须要被放大的内容,比方文字、图片等,也被无差别放大掉了。

正文完
 0