2

css

z-index在什么情况下失效

z-index只作用于被定位了的元素上,还有就是子元素的z-index会被父的值覆盖(准确的说应该是使用z-index后,父元素和子元素处在不同的层叠上下文中,他们的z-index应该分别与他们的兄弟元素比较。)。

box-sizing有哪些值

content-boxborder-box 两个,前者是默认值,指设定的width只包含content的宽,不包含border和padding,后者都包括。
这个问的还挺多的有的是给html+css代码叫算大小的,有的是口头表达的。

display:none和visibility:hidden和opacity:0有什么区别

display:nonevisibility:hidden 和 opacity:0
不在文档流中;会引起回流(重排);不被子继承在文档流中;会引起重绘;相当于继承
visibility:hiddenopacity:0
不能监听事件;子元素可以通过设置 visibility: visible来取消隐藏可以监听事件;子元素不能通过opacity: 1来取消隐藏

点击劫持就是通过opacity可以监听事件的特性实现的;
opacity的继承其实和z-index相同,opacity的属性不为1时,会把元素放置在一个新的层叠上下文中。

flex相关属性值以及他们的意义

flex是布局的大方向,问的频率也挺高的,要么是问垂直居中的时候引出来,要么是叫你实现一个简单布局然后引出来,不清楚的可以看一哈阮一峰老师的教程(传送门)

css优化手段

这种优化的手段是说不完的,我也仅表达一下自己现阶段的一些理解。
1、提高加载速度:比如最最最基础的压缩文件大小,还有可以通过内联css来使浏览器开始页面渲染的时间提前,文件大小需要控制在14.6kb(因为初始拥塞窗口存在限制),还有就是chrome自带的 coverage 标签,可以分析js和css文件的使用率,然后我们去进一步做懒加载或者移除无用代码。
2、提高选择器的性能,比如不要嵌套太多复杂的层级,选择器从右到左匹配。
3、提高渲染速度,这个我也不太懂,只是最近看canvas的时候,mdn里关于canvas优化提到CSS transforms使用GPU,因此速度更快,找到了一篇文章介绍使用transform与否的对比演示,也解释了为什么会更快,感觉不错,文章传送门

JS

map和filter的区别

map的回调里return啥就是啥,filter的回调里返回的布尔值决定当前项是否会存到新的数组里。
我其实没懂这个是想问啥,因为笔试遇到这道题的时候其他题都还挺有意思的,有点把我整懵了。

// 2020.11.16更新

event.target和evevt.currentTarget的区别

面试的时候被问到这个东西,我当时不知道,面完之后查了一下,也是只知其然,最近才了解到可能面试官是想问事件委托。
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
优点:减少事件注册,节省内存;简化了dom节点更新时相应事件的更新,比如不用在新添加的子节点上绑定事件,也不用在需要删除时解绑事件。
需要注意的是事件委托基于冒泡,对于不冒泡的事件不支持。
// 2020.11.16更新 完

sleep

单独实现一个sleep很简单

function sleep(time) {
  return new Promise((res) => {
    setTimeout(res, time)
  })
}

但是面试过程中,碰到了一道笔试题非常有意思,大概是这样:

//实现
Person('Jack').eat('lunch').sleep(2).eat('dinner').firstSleep(5)
//输出

// 等待5s
firstSleep 5s
Hi, my name is Jack
Eat lunch
// 等待2s
sleep 2s
Eat dinner

我一开始的思路是使用setTimeoutPromisemacro/micro特征,即firstSleep使用Promise,其他全部使用setTimeout,在这样的思路下,sleep和eat大概是这样的

sleep(time) {
  this.sleepTime += time
  setTimeout(() => {
    log('balabala')
  }, this.sleepTime)
  return this
}
eat(food) {
  setTimeout(() => {
    log('balabala')
  }, this.sleepTime)
  return this
} 

那后面的firstSleep就根本没法写了,前面时常为this.sleepTime没有操作空间了。
产生这样的错误思路的原因是对上面说的sleep函数或者是浏览器的事件循环理解不透彻。
这里我们需要的是一个双向队列(好像是叫这个 吧),即正常情况的链式调用中往队列中push,遇到firstSleep就unshift,在Person的构造函数中定义一个setTimeout来开始执行这个双向队列中的函数(就像串联多个异步任务时用于连接每个任务的next() )。
代码:

const log = console.log
const deque = []
function next() {
  const fn = deque.shift()
  fn && fn()
}
function Person(name) {
  deque.push(() => {
    log(`Hi, my name is ${name}`)
    next()
  })
  setTimeout(() => {
    next()
  }, 0)
  return this
}
Person.prototype = {
  eat(food) {
    deque.push(() => {
      log(`Eat ${food}`)
      next()
    })
    return this
  },
  sleep(time) {
    deque.push(() => {
      setTimeout(() => {
        log(`sleep ${time}s`)
        next()
      }, time * 1000)
    })
    return this
  },
  sleepFirst(time) {
    deque.unshift(() => {
      setTimeout(() => {
        log(`sleepFirst ${time}s`)
        next()
      }, time * 1000)
    })
    return this
  }
}

new Person('Jack').eat('lunch').sleep(2).eat('dinner').sleepFirst(2)

再观察到题目中没有new关键字,写个函数包一下就好

function _Person(name) {
  return new Person(name)
}

currying

// 实现三数相加的add函数
add(1,2,3) // 6
add(1,2)(3) // 6
add(1)(2)(3) // 6

实现一个函数柯里化不难,主要通过判断当前参数数量与目标函数参数数量,不够的话返回函数,够了的话返回结果,两种实现手段如下:

const sum3 = (x, y, z) => x + y + z
const add = currying(sum3)

// 方法1
function currying1(fn) {
  /*
  *@param{ Number } n 目标函数期望剩余参数数量
  *@param{ Array } args 已有参数数组
  */
  function next(n, args) {
    return (...xs) => {
      if(n <= xs.length) {
        return fn(...args, ...xs)
      }
      return next(n - xs.length, [...args, ...xs])
    }
  }
  return next(fn.length, [])
}
// 方法2
function currying2(fn) {
  return (...a) => {
    if(a.length >= fn.length) {
      return fn(...a)
    }
    return currying2(fn.bind(null, ...a))
  }
}

思路都一样的,方法1中合并参数的[...args, ...xs]操作其实就是bind函数最后return里的合并arguments。

柯里化的部分就完了,但是如果还是这个add,想实现的是不知道多少个参数相加呢。
与柯里化相同的是在函数内部保存已经收集的参数,不同的是柯里化可以通过判断参数数量来决定返回值,新需求需要重写返回函数的toString来输出最后执行的返回值。

JavaScript calls the toString method automatically when a Function is to be represented as a text value, e.g. when a function is concatenated with a string.

代码:

function add() {
  var args = Array.prototype.slice.call(arguments)
  
  var adder = function() {
    args.push(...arguments)
    return adder
  }
  
  adder.toString = function() {
    return args.reduce((a, b) => a + b)
  }
  return adder
}

add(1, 2, 3)(4)(5) // f 15
add(1, 2, 3)(4)(5) + 0 // 15

看起来是成了,但是如果不转换类型的话,输出的结果前有个 f ,这让我很疑惑,找了很多资料也没有结果,如果你知道的话,还请不吝赐教。

Vue

父子组件钩子顺序

父beforeCreate 父created 父beforeMount
子beforeCreate 子created 子beforeMount 子mounted
父mounted
子组件是先于父组件mounted的。

数据变动之后会立即更新dom吗

这个题也挺有意思的,是在vue双向绑定原理之后问的,有意思不是在于多难或者多偏,而是能感到面试管真的在考验你的能力,不是看你会背多少面试题。题目是这样的

// ...
data() {
  return {
    title: 'abc'
  }
}
methods: {
  change: function() {
    this.title = '1'
    this.title = '2'
    this.title = '3'
    // 调用change之后dom更新几次
  }
}
// ...

猜也能猜到肯定是更新一次。但是我想到别的地方原因说错了淦。具体原因是Vue把更新都借用自己的 nextTick 去异步更新。
下面这段如果不熟的话建议配和Vue(2.6.10)源码一起食用。
调用 change 之后,顺序同步执行三次 defineProperty 里的 set, 也就是顺序同步三次 Watcherupdate 方法,update的核心是 queueWatcher,

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      // ...
    }
    // queue the flush
    if (!waiting) {
      waiting = true
      // ...
      nextTick(flushSchedulerQueue)
    }
  }
}

代码里的 has 就是用来过滤相同 watcher ,虽然后面的被无情抛弃了,但是只要有这个watcher的id,异步更新的时候使用的也是同步修改的数据。

vue-router两种模式 hash 和 history 的区别

最直观的区别是hash模式带 '#'
history模式使用h5新增的 pushStatereplaceState,他们用来修改浏览器的历史记录栈,修改时不会立即发送请求。

其他

sourcemap

简单来说 sourcemap 是一个存储源代码与编译代码对应位置映射的信息文件。
比如使用webpack在开发环境调试代码的时候,浏览器下载的并不是你写的源码,而且经过webpack压缩混淆合并等操作之后的代码,那为什么你的debugger还能在你写的地方生效呢,这就是sourcemap的作用。

一些感想

能看到这篇文章大概率说明你在准备面试,如果你是萌新没怎么面过,那我可以告诉你不管是大厂还是小作坊,面试过程有很大比重是介绍你简历中写的能力和项目,与其盲目的在面试题海里不知所措,不如好好准备简历拿下那些送分题。如果你是久经沙场的秃头码农 ,那也希望这篇文章能帮助到你。


cvSoldier
119 声望13 粉丝

原地tp