感慨岁月如梭==>前端基础

986 阅读10分钟

前言

也不知道是什么时候我曾经立下一天一遍博文的豪言壮志,如今打开一看,有关技术的文章的创建时间都定格在2017年,豪言壮志固然不可靠,脚踏实地才是硬道理。这几个月我的变化很大,曾经懒惰睡懒觉的我每天都坚持6点起床,现在我还在尝试学着胖哥(技术胖)10点半睡觉4点半起床,结果第一天就吃到了失败的苦果。以上内容交代了文章主人公从一个懒惰it行业从事者慢慢向业界前辈学习的经历。接下来我会记录下关于前端面试的一些问题和解答,不能保证完全是正确的,但是对我自身也算是种技术沉淀了。

主要内容分为 JS原生部分,vue框架使用部分和优化的部分。因为我最近在面试,所以可能会抽面试题来先写,其实我也没有太多思路,秉着没人看的心态拼命乱写,我还是希望有个人在下面大声说出博主你写错啦等等吐槽,我是菜鸡我为自己代盐。

由于是个人总结所以并不会太详细,也不会举例去说明,只能按我的表达出来

内容

1 JS原生

前端的根基所在,原生JS是限制很多前端向更高方向发展的一个瓶之一,废话不多说先从简单的开始吧

1 let/const区别

这题看起很简单,我会不假思索的答出const一般用来定义常量或者对象(对象类型在js中是保存一个指针地址所以不会有改变),而let我们就用来定义变量,const是需要初始化的,在定义的时候就需要初始化赋值,而let则不用一开始就初始化,两者都不存在变量提升(暂时性死区),并且会形成一个块级作用域,而且不能重复定义相同的变量名。现在我们稍微整理一下他们的异同点,同时给他俩来个参照物var,得出以下结论。

相同点:

  1. 块级作用域
  2. 不存在变量提升
  3. 暂时性死区
  4. 不可重复声明
  5. let、const声明的全局变量不会挂在顶层对象下面

不同点:

  1. const定义需要初始化
  2. const不可修改,只读
    补充个暂时性死区的解释:

当程序的控制流程在新的作用域(module function 或 block 作用域)进行实例化时,在此作用域中用let/const声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,所以是不能被访问的,如果访问就会抛出错误。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时死区。

翻译一下人话就是,在函数作用域,或者使用let/const时形成的块级作用域中,在执行到let/const之定义之前,定义的变量都是不可访问的,在语法上这个暂时性死区(TDZ)。

2 数组去重方法

面试超级喜欢问的问题,简单来说,非要追求逼格的话google一下可以找找到很多,这里我只介绍最实用的方法,在处理性能方面方法3>方法1>方法2

1 普通版

function unique(array) {
  let temp = []
  for (let i = 0, l = array.length; i < l; i++) {
    if (temp.indexOf(arr[i]) === -1) temp.push(arr[i])
  }
  return temp
}

最普通的for循环,时间复杂度O(n^2) 空间复杂度O(n),当然我们可以价格hash表让查找更加快速

function unique(array) {
  let temp = []
  let hash = {}
  for (let i = 0, l = array.length; i < l; i++) {
    if (!hash[array[i]]) {
      hash[array[i]] = true //存入hash表
      temp.push(arr[i])
    }
  }
  return temp
}

这样我们就可以把时间复杂度降到了O(n),但由于新建了一个对象所以消耗内存方面要远大于indexOf

2 filter版

function unique(array) {
  return array.filter((item, index, array) => {
    return array.indexOf(item) === index
  })
}

这个性能应该是最差的了,从空间复杂度和时间复杂度上推论的

3 es6版(set)

//Set 版本
function unique(array) {
  return [...new Set(array))]
}

经测试这个最好

4 强行ts版

function unique(array: Array<number>): Array<number> {
  let temp: Array<number> = []
  for (let i: number = 0, l: number = array.length; i < l; i++) {
    if (temp.indexOf(array[i]) === -1) temp.push(array[i])
  }
  return temp
}

ts用的非常少,随手写的,不够健壮性,只能去掉重复的数字类型数组,二维数据,和其它类型的数据都没有考虑到,因为比较随意啊,所以我也不考虑那么多了,总结下就好了

3 如何控制冒泡/捕获事件

首先,事件冒泡和事件捕获的提出都是为了解决事件执行顺序,我们在使用中可以通过addEventListener()的第三个参数来决定我们注册的事件是捕获时触发还是冒泡时触发,false的时候是冒泡触发,true则为捕获。下面我就简单举个例子,请看以下代码

<div id="el_1">
    <p class="el_2">有本事点我</p>
<div>
//假设我们给p添加一个点击事件

事件冒泡:是微软的方案,冒泡就像一块石头扔进水里,气泡会从底下往上冒,所以以上的事件执行顺序是p>div>body>html>document

事件捕获:网景公司提出,事件捕获和事件冒泡相反,这时候p元素的点击事件执行顺序是 document>html>body>div>p

细心的小伙伴一定会发现我们现在用的w3c标准其实是存在事件冒泡和事件捕获的,其实这就是w3c做的折中方案,这个方案就是先捕获后冒泡,到事件target上时,则是谁先注册谁就先执行,比如你在target上注册了一个事件捕获事件和事件冒泡事件,那决定他们执行顺序的其实就是注册的顺序。

4 什么是闭包

关于闭包,我想大家都很熟悉,这次我打算用变量对象的角度来解析闭包的原理。 闭包提供了函数外部访问函数内部变量的能力,同时由于无法被回收会导致内存泄露等问题。

我的描述建立在有一定基础的情况下的,默认你明白函数执行上下文的创建,js垃圾回收机制,作用域链等基础知识。

首先我们讲一下作用域的概念,作用域链其实由当前环境和上层环境一些类变量对象组成,它保证了当前环境对符合访问权限的变量和函数的有序访问。 这时候我们举例有一个执行上下文A和一个在执行上下文A下创建的函数也就是执行上下文B,当执行上下文A被激活的时候,这时候执行上下文就会被创建,在创建阶段分别会创建变量对象,确定this的指向,确定作用域链, 之后就是执行阶段,也就是执行我们写在函数体内的代码,分别是变量赋值,函数引用和其它代码,执行完毕后变执行上下文就会出栈,等待垃圾回收。 那么闭包是什么,上面提到了执行上下文B执行时如果访问了执行上下文A的变量对象,那么闭包就产生了,这时候函数B就是个闭包(这里是有不同解释的,各种大神书称内层函数为闭包,而chrome中则以外层函数为闭包),变量对象中包含了argument,声明的变量和声明的函数,而我们知道,函数的执行上下文,在执行完毕之后,生命周期结束,那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。可是闭包的存在,会阻止这一过程。闭包的运用就不多说了,有柯里化等

5 hash和histroy模式的区别

这一个我之前写过一篇总结,我就直接放进来了

history给我们保存状态的能力,通过pushState()添加激活历史条目,通过replaceState()修改当前激活的历史条目history接收三个参数

history.pushState({page:1},'title1','?page=1')
  • stateObj(状态对象) : 是一个JavaScript对象类型,我们可以通过这个这个对象保存数据,可以在新的历史条目里(新的页面)获取到这个对象

  • title(标题) :目前浏览器大多不支持,保险起见可以传一个空字符串

  • url(地址): 新的页面地址,可选,传入的地址必须是同源,否则pushState()会抛出异常l 不传或传空字符串新历史条目默认为当前文档url

通过onpopstate事件的event对象会拷贝一份改历史记录条目的state,下面我们来实现一个小案例。

  history.pushState({
      color: 'red',
    },
    '',
    '?color=red') //添加并激活一个历史条目,histroy.html?color=red
  history.back() //返回上一条历史条目 histroy.html
  setTimeout(() => {
    history.forward() //设置定时器后前进到下一条历史条目 ,histroy.html?color=red
  }, 1000)
  // 状态的历史记录条目发生变化时, popstate事件就会在对应window对象上触发.
  window.onpopstate = function (e) {
    console.log(e.state)
    if (e.state && e.state.color === 'red') {
      document.body.style.color = 'red'
    }
  }

通过pushstate把页面的状态保存在state对象中,当页面的url再变回这个url时,可以通过event.state取到这个state对象,从而可以对页面状态进行还原,这里的页面状态就是页面字体颜色,其实滚动条的位置,阅读进度,组件的开关的这些页面状态都可以存储到state的里面。

补充资料

  window.onpopstate = function (event) {
    alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
  };
  //绑定事件处理函数.
  history.pushState({
    page: 1
  }, "title 1", "?page=1"); //添加并激活一个历史记录条目 http://example.com/example.html?page=1,条目索引为1
  history.pushState({
    page: 2
  }, "title 2", "?page=2"); //添加并激活一个历史记录条目 http://example.com/example.html?page=2,条目索引为2
  history.replaceState({
    page: 3
  }, "title 3", "?page=3"); //修改当前激活的历史记录条目 http://ex..?page=2 变为 http://ex..?page=3,条目索引为3
  history.back(); // 弹出 "location: http://example.com/example.html?page=1, state: {"page":1}"
  history.back(); // 弹出 "location: http://example.com/example.html, state: null
  history.go(2); // 弹出 "location: http://example.com/example.html?page=3, state: {"page":3}

补充关于hash的一些知识点,我们histroy是html5 的 api,在此之前还有个hash,浏览器通过记录hash值让页面不刷新跳转,背后的原理其实onhashchange 事件,该事件是在window上的,下面通过一个小demo来理解

<body>
  <div>这是一段测试hash的小文字,改变hash值将触发hashchange事件,这段文字的颜色取自hash值</div>
</body>
window.onhashchange = function (e) {
    console.log('form:', e.newURL, 'from:', e.oldURL)
    document.body.style.color = location.hash.slice(1)
    console.log(location.hash)
  }

Snip20190820_2
Snip20190820_3
Snip20190820_4
Snip20190820_5

这样我们就实现了对页面状态的保持,在chrome中前进后退我们的页面,但我们只能通过修改#后面的值来达到我们需要的效果,这样自由性会非常低,所以有了我们的history模式

2 vue框架的使用

预告:先占个坑位,预告是单向数据流,mixin,低耦合可拓展路由配置

3 网页的优化

预告:组件分割,缓存,tree shaking,图片优化