into-piece / Step-By-Step

每天一题向前端架构师前进
4 stars 1 forks source link

20200519 欢聚时代面试 #27

Open into-piece opened 4 years ago

into-piece commented 4 years ago

双向绑定

https://zhuanlan.zhihu.com/p/45081605

vue2 defindproperty双向绑定与vue3 proxy的区别

vue2 defindproperty的缺陷:

代替循环遍历

在使用Object.defineProperty时,我们必须循环遍历所有的域值才能劫持每一个属性,说实在这就是个hack。

Object.keys(data).forEach((prop) => { ... }

而Proxy的劫持手段则是官宣标准——直接监听data的所有域值。

//data is our source object being observed
const observer = new Proxy(data, {
    get(obj, prop) { ... },
    set(obj, prop, newVal) { ... },
    deleteProperty() {
        //invoked when property from source data object is deleted
    }
})

Proxy构造函数的第一个参数是原始数据data;第二个参数是一个叫handler的处理器对象。Handler是一系列的代理方法集合,它的作用是拦截所有发生在data数据上的操作。这里的get()和set()是最常用的两个方法,分别代理访问和赋值两个操作。在Observer里,它们的作用是分别调用dep.depend()和dep.notify()实现订阅和发布。直接反映在Vue里的好处就是:我们不再需要使用Vue.$set()这类响应式操作了。除此之外,handler共有十三种劫持方式,比如deleteProperty就是用于劫持域删除。

监听数组变化

Proxy不需要各种hack技术就可以无压力监听数组变化;甚至有比hack更强大的功能——自动检测length。除此之外,Proxy还有多达13种拦截方式,包括construct、deleteProperty、apply等等操作;而且性能也远优于Object.defineProperty,这应该就是所谓的新标准红利吧。

vue如何在defindproperty中实现对数组的劫持

使用了函数劫持的方式,重写了数组的方法,Vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。

const arrayProto = Array.prototype//原生Array的原型
export const arrayMethods = Object.create(arrayProto);
[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
].forEach(function (method) {
  const original = arrayProto[method]//缓存元素数组原型
  //这里重写了数组的几个原型方法
  def(arrayMethods, method, function mutator () {
    //这里备份一份参数应该是从性能方面的考虑
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    const result = original.apply(this, args)//原始方法求值
    const ob = this.__ob__//这里this.__ob__指向的是数据的Observer
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

//定义属性
function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}
// 以上代码 相当于
var bar = [1,2];
bar.__proto__ = arrayMethod;
// 执行
bar.push(3); 就会触发arrayMethods中的代码:ob.dep.notify()就会被触发,就会通知watcher触发template的更新。

webpack构建深入

https://juejin.im/post/5d614dc96fb9a06ae3726b3e#heading-6

缓存、多核、抽离以及拆分

缓存(cache loader

cache loader:将 loader 的编译结果写入硬盘缓存,再次构建如果文件没有发生变化则会直接拉取缓存

多核(happypack

Happypack 的作用就是将文件解析任务分解成多个子进程并发执行。子进程处理完任务后再将结果发送给主进程。所以可以大大提升 Webpack 的项目构件速度

抽离

DllPlugin

DLL(Dynamic Link Library)文件为动态链接库文件,在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。

为什么要使用Dll

通常来说,我们的代码都可以至少简单区分成业务代码和第三方库。如果不做处理,每次构建时都需要把所有的代码重新构建一次,耗费大量的时间。然后大部分情况下,很多第三方库的代码并不会发生变更(除非是版本升级),这时就可以用到dll:把复用性较高的第三方模块打包到动态链接库中,在不升级这些库的情况下,动态库不需要重新打包,每次构建只重新打包业务代码。

还是上面的例子:把每次构建,当做是生产产品的过程,我们把生产螺丝的过程先提取出来,之后我们不管调整产品的功能或者设计(对应于业务代码变更),都不必重复生产螺丝(第三方模块不需要重复打包);除非是产品要使用新型号的螺丝(第三方模块需要升级),才需要去重新生产新的螺丝,然后接下来又可以专注于调整产品本身。

作用

首先从前面的介绍,至少可以看出dll的两个作用

  1. 分离代码,业务代码和第三方模块可以被打包到不同的文件里,这个有几个好处:

    • 避免打包出单个文件的大小太大,不利于调试
    • 将单个大文件拆成多个小文件之后,一定情况下有利于加载(不超出浏览器一次性请求的文件数情况下,并行下载肯定比串行快)
  2. 提升构建速度。第三方库没有变更时,由于我们只构建业务相关代码,相比全部重新构建自然要快的多。

Externals

将这些不需要打包的静态资源从构建逻辑中剔除出去,而使用 CDN 的方式,去引用它们。

拆分(集群编译)

rem

em:根据父元素的字体大小,父元素字体大小是16px,1em就是16px。

rem:root em,根据根元素=》html的字体大小来判定。

适配方案:根元素字体大小根据屏幕显示宽度来进行变动,切分成10份,当屏幕宽度为750时,一份1rem为75,当屏幕宽度为350时,一份1rem为35,则可以根据相应的设计图大小与元素大小的比例,换算成对应的rem。

const docEl = document.documentElement
const width = docEl.clientWidth
const rem = width / 10 + 'px'
docEl.style.fontSize = rem

animation动画

animation-duration

1px的问题

原因

那么为什么会产生这个问题呢?主要是跟一个东西有关,DPR(devicePixelRatio) 设备像素比,它是默认缩放为100%的情况下,设备像素和CSS像素的比值。

window.devicePixelRatio=物理像素 /CSS像素

复制代码目前主流的屏幕DPR=2 (iPhone 8),或者3 (iPhone 8 Plus)。拿2倍屏来说,设备的物理像素要实现1像素,而DPR=2,所以css 像素只能是 0.5。一般设计稿是按照750来设计的,它上面的1px是以750来参照的,而我们写css样式是以设备375为参照的,所以我们应该写的0.5px就好了啊! 试过了就知道,iOS 8+系统支持,安卓系统不支持。

解决方案

  1. 关于移动端开发1px线的一些理解和解决办法

  2. 7种方法解决移动端Retina屏幕1px边框问题

  3. 使用Flexible实现手淘H5页面的终端适配

WWDC对iOS统给出的方案

在 WWDC大会上,给出来了1px方案,当写 0.5px的时候,就会显示一个物理像素宽度的 border,而不是一个css像素的 border。 所以在iOS下,你可以这样写。

缺点: 无法兼容安卓设备、 iOS 8 以下设备。

viewport + rem 实现

同时通过设置对应viewport的rem基准值,这种方式就可以像以前一样轻松愉快的写1px了。

在devicePixelRatio = 2 时,输出viewport:

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

在devicePixelRatio = 3 时,输出viewport:

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

伪类 + scale

元素本身不设置border,用设置伪类设置绝对定位+宽度100%+高度1px,然后scale(0.5)让高度缩小一半,新边框就相当于0.5px。

react-native 如何使用 code-push 热更新

https://github.com/lisong/code-push-server/blob/master/docs/react-native-code-push.md

code push

code push 调用 react native 的打包命令,将当前环境的非 native 代码全量打包成一个 bundle 文件,然后上传到微软云服务器(Windows Azure)。 在 app 中启动页(或 splash 页)编写请求更新的代码(请求包含了本地版本,hashCode、appToken 等信息),微软服务端对比本地 js bundle 版本和微软服务器的版本,如果本地版本低,就下载新的 js bundle 下来后实现更新(code push 框架实现)。

https://www.jianshu.com/p/8e08c7661275

RN对性能监控的思考及工具分享

Gulp和Webpack功能实现对比 (我为什么要替换gulp)

基于任务运行

gulp是侧重于前端开发的整个过程的控制管理,通过给gulp配置不同的task来让gulp实现不同的功能,从而构建整个前端开发流程。

打包是根据在某个文件路径下的所有文件类型进行打包

基于模块化打包

webpack更侧重于模块打包,把所有开发资源看作模块。

打包:从入口开始,用loader对文件进行处理解析后获取文件模块之间的依赖关系,通过递归获取整个依赖关系图。

所以,Webpack中对资源文件的处理是通过入口文件产生的依赖形成的,不会像Gulp那样,配置好路径后,该路径下所有规定的文件都会受影响。

模块化就是对内容的管理,是为了解耦合。

特点:

  1. 文件模块化:不管是 CSS、JS、Image 还是 HTML 都可以互相引用,通过定义 entry.js,对所有依赖的文件进行跟踪,将各个模块通过 loader 和 plugins 处理,然后打包在一起。
  2. 按需加载:打包过程中 Webpack 通过 Code Splitting 功能将文件分为多个 chunks,还可以将重复的部分单独提取出来作为 commonChunk,从而实现按需加载。

综上所述,Webpack 特别适合配合 React.js、Vue.js 构建单页面应用以及需要多人合作的大型项目,在规范流程都已约定好的情况下往往能极大的提升开发效率与开发体验。

animation

// animation-name,animation-duration, animation-timing-function,animation-delay,animation-iteration-count,animation-direction,animation-fill-mode 和 animation-play-state

@keyframes spin {
  from {
    transform: rotate(0)
  }
  to {
    transform: rotate(360deg)
  }
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
  animation: spin 4s linear infinite;
}

设计模式

redux中间件

就是对dispatch的增强,通过高阶函数+compose,获取dispatch

// 调用applyMiddleware
applyMiddleware(thunk, logger)

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
        'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => {
        return dispatch(...args)
      }
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
}

react native的生命周期

react use-redux 自上而下的数据管理

热更新