前言

大家好,今年的金三银四已经来了,也有人说是铜三铁四,不过我想说的是这重要吗,环境只是一个因素,它确实会影响大家找工作或者跳槽涨薪,但是影响不多,最重要的一个因素在于自己是否已经做好了准备。我呢为大家准备了百道面试题,为大家保驾护航,后续也会持续更新吧,毕竟还有金九银十与新技术的出现。

—>来点击我吧【希望能找到一些志同道合的前端小伙伴们,一起走向更远处,与更多的伙伴们交流学习

希望大家可以点个赞支持一下,整理不易

1.Vue3.0性能提升主要是体现在哪些方面

1.响应式系统

 1- Vue.js 2.x 中响应式系统的核心是 Object.defineProperty劫持整个对象然后进行深度遍历所有属性给每个属性添加`getter``setter`实现响应式
 2- Vue.js 3.x 中使用 Proxy 对象重写响应式系统
 3    - 可以监听动态新增的属性
 4    - 可以监听删除的属性
 5    - 可以监听数组的索引和length属性 
 6
 7* 实现原理:
 8
 9  * 通过Proxy代理): 拦截对象中任意属性的变化, 包括属性值的读写属性的添加属性的删除等
10
11  * 通过Reflect反射): 对源对象的属性进行操作
12
13  * MDN文档中描述的Proxy与Reflect
14
15    * Proxy:[Proxy - JavaScript | MDN](https:
16
17    * Reflect:[Reflect - JavaScript | MDN](https:
18                                           
19       new Proxy(data, {
20          
21          get (target, prop) {
22              return Reflect.get(target, prop)
23          },
24          
25          set (target, prop, value) {
26              return Reflect.set(target, prop, value)
27          },
28          
29          deleteProperty (target, prop) {
30              return Reflect.deleteProperty(target, prop)
31          }
32      })
33  
34      proxy.name = 'tom'   ![]

2.编译阶段

 1- Vue.js 2.x 通过标记静态节点优化 diff 的过程
 2- Vue.js 3.x 
 3  *   vue.js 3.x中标记和提升所有的静态节点diff的时候只需要对比动态节点内容
 4  *   Fragments升级vetur插件): template中不需要唯一根节点可以直接放文本或者同级标签
 5  *   静态提升(hoistStatic),当使用 hoistStatic,所有静态的节点都被提升到 render 方法之外.只会在应用启动的时候被创建一次,之后使用只需要应用提取的静态节点随着每次的渲染被不停的复用
 6  *   patch flag, 在动态标签末尾加上相应的标记,只能带 patchFlag 的节点才被认为是动态的元素,会被追踪属性的修改,能快速的找到动态节点,而不用逐个逐层遍历提高了虚拟dom diff的性能
 7  *   缓存事件处理函数cacheHandler,避免每次触发都要重新生成全新的function去更新之前的函数

3.源码体积

 1- 相比Vue2Vue3整体体积变小了除了移出一些不常用的AP
 2- tree shanking
 3  - 任何一个函数如refreavtivedcomputed等仅仅在用到的时候才打包
 4  - 通过编译阶段的静态分析找到没有引入的模块并打上标记,将这些模块都给摇掉

2.vue3有哪些新的组件

1.Fragment

 1*   在Vue2中: 组件必须有一个根标签
 2
 3*   在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
 4
 5*   好处: 减少标签层级, 减小内存占用

2.Teleport

什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

 1<teleport to="移动位置">
 2    <div v-if="isShow" class="mask">
 3        <div class="dialog">
 4            <h3>我是一个弹窗</h3>
 5            <button @click="isShow = false">关闭弹窗</button>
 6        </div>
 7    </div>
 8</teleport>

3.Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

       1import {defineAsyncComponent} from 'vue'
       2const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
      
    • 使用Suspense包裹组件,并配置好defaultfallback

       1<template>
       2    <div class="app">
       3        <h3>我是App组件</h3>
       4        <Suspense>
       5            <template v-slot:default>
       6                <Child/>
       7            </template>
       8            <template v-slot:fallback>
       9                <h3>加载中.....</h3>
      10            </template>
      11        </Suspense>
      12    </div>
      13</template>
      

3.Vue2.0 和 Vue3.0 有什么区别

 11. 响应式系统的重新配置使用proxy替换Object.defineProperty
 22. typescript支持
 33. 新增组合API更好的逻辑重用和代码组织
 44. v-if和v-for的优先级
 55. 静态元素提升
 66. 虚拟节点静态标记
 77. 生命周期变化
 88. 打包体积优化
 99. ssr渲染性能提升
1010. 支持多个根节点

4.Vue 生命周期

1.vue2.x的生命周期

 

 

 

 

 

 

2.vue3.0的生命周期

 

 

 

 

 

 

 1*   Vue3.0中可以继续使用Vue2.x中的生命周期钩子但有有两个被更名
 2
 3    *   `beforeDestroy`改名为 `beforeUnmount`
 4
 5    *   `destroyed`改名为 `unmounted`
 6
 7*   Vue3.0也提供了 Composition API 形式的生命周期钩子与Vue2.x中钩子对应关系如下
 8
 9    *   `beforeCreate`===>`setup()`
10
11    *   `created`=======>`setup()`
12
13    *   `beforeMount` ===>`onBeforeMount`
14
15    *   `mounted`=======>`onMounted`
16
17    *   `beforeUpdate`===>`onBeforeUpdate`
18
19    *   `updated` =======>`onUpdated`
20
21    *   `beforeUnmount` ==>`onBeforeUnmount`
22
23    *   `unmounted` =====>`onUnmounted`
24    
25    
26    
27Vue 实例有个完整的命周期也就是从开始创建初始化数据编译模版挂载Dom -> 渲染更新 -> 渲染卸载系列过程称这是Vue的命周期
281beforeCreate创建前) :数据观测和初始化事件还未开始此时 data 的响应式追踪event/watcher 都还没有被设置也就是说不能访问到datacomputedwatchmethods上的方法和数据
292created创建后) :实例创建完成实例上配置的 options 包括 datacomputedwatchmethods 等都配置完成但是此时渲染得节点还未挂载到 DOM所以不能访问到 `$el` 属性
303beforeMount挂载前) :在挂载开始之前被调用相关的render函数首次被调用实例已完成以下的配置编译模板把data里面的数据和模板生成html此时还没有挂载html到页面上
314mounted挂载后) :在el被新创建的 vm.$el 替换并挂载到实例上去之后调用实例已完成以下的配置用上面编译好的html内容替换el属性指向的DOM对象完成模板中的html渲染到html 页面中此过程中进行ajax交互
325beforeUpdate更新前) :响应式数据更新时调用此时虽然响应式数据更新了但是对应的真实 DOM 还没有被渲染
336updated更新后):在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用此时 DOM 已经根据响应式数据的变化更新了调用时组件 DOM已经更新所以可以执行依赖于DOM的操作然而在大多数情况下应该避免在此期间更改状态因为这可能会导致更新无限循环该钩子在服务器端渲染期间不被调用
347beforeDestroy销毁前) :实例销毁之前调用这一步实例仍然完全可用`this` 仍能获取到实例
358destroyed销毁后) :实例销毁后调用调用后Vue 实例指示的所有东西都会解绑定所有的事件监听器会被移除所有的子实例也会被销毁该钩子在服务端渲染期间不被调用
36

5.都说 Composition API 和 React Hook 很像,请问他们的区别是什么

 1 React Hook 从实现的角度来看React Hook 是基于 useState 的调用顺序来确定下一个 re 渲染时间状态从哪个 useState 开始所以有以下几个限制
 2
 3*   不在循环中条件调用嵌套函数 Hook
 4*   你必须确保它总是在你这边 React Top level 调用函数 Hook
 5*   使用效果使用备忘录 依赖关系必须手动确定
 6
 7 Composition API 是基于 Vue 的响应系统 React Hook 相比
 8
 9*   在设置函数中一个组件实例只调用一次设置 React Hook 每次重新渲染时都需要调用 Hook React 带来的 GC Vue 更大的压力性能也相对 Vue 对我来说也比较慢
10*   Compositon API 你不必担心调用的顺序它也可以在循环中条件在嵌套函数中使用
11*   响应式系统自动实现依赖关系收集而且组件的性能优化是由 Vue 内部完成的 React Hook 的依赖关系需要手动传递并且依赖关系的顺序必须得到保证让路 useEffectuseMemo 等等否则组件性能会因为依赖关系不正确而下降
12
13虽然Compoliton API看起来像React Hook来使用但它的设计思路也是React Hook的参考

6. Composition Api 与Options Api 有什么不同

1.Options Api

Options API,即大家常说的选项API,即以vue为后缀的文件,通过定义methodscomputedwatchdata等属性与方法,共同处理页面逻辑

如下图:

 

 

 

 

 

 

可以看到Options代码编写方式,如果是组件状态,则写在data属性上,如果是方法,则写在methods属性上…

用组件的选项 (datacomputedmethodswatch) 组织逻辑在大多数情况下都有效

然而,当组件变得复杂,导致对应属性的列表也会增长,这可能会导致组件难以阅读和理解

2.Composition Api

在 Vue3 Composition API 中,组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合)

即使项目很大,功能很多,我们都能快速的定位到这个功能所用到的所有 API

 

 

 

 

 

 

3.对比

下面对Composition ApiOptions Api进行两大方面的比较

  • 逻辑组织
  • 逻辑复用
逻辑组织
Options API

假设一个组件是一个大型组件,其内部有很多处理逻辑关注点(对应下图不用颜色)

 

 

 

 

 

 

可以看到,这种碎片化使得理解和维护复杂组件变得困难

选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块

Compostion API

Compositon API正是解决上述问题,将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去

下面举个简单例子,将处理count属性相关的代码放在同一个函数了

 1function useCount() {
 2    let count = ref(10)
 3    let double = computed(() => {
 4        return count.value * 2
 5    })
 6
 7    const handleConut = () => {
 8        count.value = count.value * 2
 9    }
10
11    console.log(count)
12
13    return {
14        count,
15        double,
16        handleConut,
17    }
18}

组件上中使用count

 1export default defineComponent({
 2    setup() {
 3        const { count, double, handleConut } = useCount();
 4        return {
 5            count,
 6            double,
 7            handleConut
 8        }
 9    },
10});

再来一张图进行对比,可以很直观地感受到 Composition API在逻辑组织方面的优势,以后修改一个属性功能的时候,只需要跳到控制该属性的方法中即可

 

 

 

 

 

 

逻辑复用

Vue2中,我们是用过mixin去复用相同的逻辑

下面举个例子,我们会另起一个mixin.js文件

 1export const MoveMixin = {
 2  data() {
 3    return {
 4      x: 0,
 5      y: 0,
 6    };
 7  },
 8
 9  methods: {
10    handleKeyup(e) {
11      console.log(e.code);
12      
13      switch (e.code) {
14        case "ArrowUp":
15          this.y--;
16          break;
17        case "ArrowDown":
18          this.y++;
19          break;
20        case "ArrowLeft":
21          this.x--;
22          break;
23        case "ArrowRight":
24          this.x++;
25          break;
26      }
27    },
28  },
29
30  mounted() {
31    window.addEventListener("keyup", this.handleKeyup);
32  },
33
34  unmounted() {
35    window.removeEventListener("keyup", this.handleKeyup);
36  },
37};

然后在组件中使用

 1<template>
 2  <div>
 3    Mouse position: x {{ x }} / y {{ y }}
 4  </div>
 5</template>
 6<script>
 7import mousePositionMixin from './mouse'
 8export default {
 9  mixins: [mousePositionMixin]
10}
11</script>

使用单个mixin似乎问题不大,但是当我们一个组件混入大量不同的 mixins 的时候

 1mixins: [mousePositionMixin, fooMixin, barMixin, otherMixin]

会存在两个非常明显的问题:

  • 命名冲突
  • 数据来源不清晰

现在通过Compositon API这种方式改写上面的代码

 1import { onMounted, onUnmounted, reactive } from "vue";
 2export function useMove() {
 3  const position = reactive({
 4    x: 0,
 5    y: 0,
 6  });
 7
 8  const handleKeyup = (e) => {
 9    console.log(e.code);
10    
11    switch (e.code) {
12      case "ArrowUp":
13        
14        position.y--;
15        break;
16      case "ArrowDown":
17        
18        position.y++;
19        break;
20      case "ArrowLeft":
21        
22        position.x--;
23        break;
24      case "ArrowRight":
25        
26        position.x++;
27        break;
28    }
29  };
30
31  onMounted(() => {
32    window.addEventListener("keyup", handleKeyup);
33  });
34
35  onUnmounted(() => {
36    window.removeEventListener("keyup", handleKeyup);
37  });
38
39  return { position };
40}

在组件中使用

 1<template>
 2  <div>
 3    Mouse position: x {{ x }} / y {{ y }}
 4  </div>
 5</template>
 6
 7<script>
 8import { useMove } from "./useMove";
 9import { toRefs } from "vue";
10export default {
11  setup() {
12    const { position } = useMove();
13    const { x, y } = toRefs(position);
14    return {
15      x,
16      y,
17    };
18
19  },
20};
21</script>

可以看到,整个数据来源清晰了,即使去编写更多的 hook 函数,也不会出现命名冲突的问题

小结
  • 在逻辑组织和逻辑复用方面,Composition API是优于Options API
  • 因为Composition API几乎是函数,会有更好的类型推断。
  • Composition APItree-shaking 友好,代码也更容易压缩
  • Composition API中见不到this的使用,减少了this指向不明的情况
  • 如果是小型组件,可以继续使用Options API,也是十分友好的

7.什么是SPA单页面应用,首屏加载你是如何优化的

 1单页Web应用single page web applicationSPA),就是只有一张Web页面的应用是加载单个HTML页面并在用户与应用程序交互时动态更新该页面的Web应用程序我们开发的`Vue`项目大多是借助个官方的`CLI`脚手架快速搭建项目直接通过`new Vue`构建一个实例并将`el:'#app'`挂载参数传入最后通过`npm run build`的方式打包后生成一个`index.html`称这种只有一个`HTML`的页面为单页面应用
 2
 3当然`vue`也可以像`jq`一样引入作为多页面应用的基础框架
 4
 5
 6SPA首屏优化方式
 7
 8减小入口文件积
 9静态资源本地缓存
10UI框架按需加载
11图片资源的压缩
12组件重复打包
13开启GZip压缩
14使用SSR

8.对Vue项目你做过哪些性能优化

 11`v-if``v-show`
 2
 3*   频繁切换时使用`v-show`利用其缓存特性
 4*   首屏渲染时使用`v-if`如果为`false`则不进行渲染
 5
 62`v-for``key`
 7
 8*   列表变化时循环时使用唯一不变的`key`借助其本地复用策略
 9*   列表只进行一次渲染时`key`可以采用循环的`index`
10
113侦听器和计算属性
12
13*   侦听器`watch`用于数据变化时引起其他行为
14*   多使用`compouter`计算属性顾名思义就是新计算而来的属性如果依赖的数据未发生变化不会触发重新计算
15
164合理使用生命周期
17
18*`destroyed`阶段进行绑定事件或者定时器的销毁
19*   使用动态组件的时候通过`keep-alive`包裹进行缓存处理相关的操作可以在`actived`阶段激活
20
215数据响应式处理
22
23*   不需要响应式处理的数据可以通过`Object.freeze`处理或者直接通过`this.xxx = xxx`的方式进行定义
24*   需要响应式处理的属性可以通过`this.$set`的方式处理而不是`JSON.parse(JSON.stringify(XXX))`的方式
25
266路由加载方式
27
28*   页面组件可以采用异步加载的方式
29
307插件引入
31
32*   第三方插件可以采用按需加载的方式比如`element-ui`
33
348减少代码量
35
36*   采用`mixin`的方式抽离公共方法
37*   抽离公共组件
38*   定义公共方法至公共`js`
39*   抽离公共`css`
40
419编译方式
42
43*   如果线上需要`template`的编译可以采用完成版`vue.esm.js`
44*   如果线上无需`template`的编译可采用运行时版本`vue.runtime.esm.js`相比完整版体积要小大约`30%`
45
4610渲染方式
47
48*   服务端渲染如果是需要`SEO`的网站可以采用服务端渲染的方式
49*   前端渲染一些企业内部使用的后端管理系统可以采用前端渲染的方式
50
5111字体图标的使用
52
53*   有些图片图标尽可能使用字体图标

9.Vue组件通信的方式有哪些

 1vue中8种常规的通信方案
 2
 3通过 props 传递
 4通过 $emit 触发自定义事件
 5使用 ref
 6EventBus
 7$parent 或$root
 8attrs listeners
 9Provide Inject
10Vuex
11
12组件间通信的分类可以分成以下
13
14父子关系的组件数据传递选择 props  与 $emit进行传递也可选择ref
15兄弟关系的组件数据传递可选择$bus其次可以选择$parent进行传递
16祖先与后代组件数据传递可选择attrs与listeners或者 Provide与 Inject
17复杂关系的组件数据传递可以通过vuex存放共享的变量

10.Vue常用的修饰符有哪些

 1 1表单修饰符
 2
 31`.lazy`
 4
 5在默认情况下`v-model` 在每次 `input` 事件触发后将输入框的值与数据进行同步可以添加 `lazy` 修饰符从而转为在 `change` 事件之后进行同步:
 6

​ ``` ​ (2)`.number` ​ 如果想自动将用户的输入值转为数值类型,可以给 `v-model` 添加 `number` 修饰符: ​ ``` ​ ``` ​ (3)`.trim` ​ 如果要自动过滤用户输入的首尾空白字符,可以给 `v-model` 添加 `trim` 修饰符: ​ ``` ​ ``` ​ 2、事件修饰符 ​ (1)`.stop` ​ 阻止单击事件继续传播。 ​ ```<div @click=“divClick”>点击

​ ``` ​ (2)`.prevent` ​ 阻止标签的默认行为。 ​ ``` 点击 ​ ``` ​ (3)`.capture` ​ 事件先在有`.capture`修饰符的节点上触发,然后在其包裹的内部节点中触发。 ​ ```<div @click=“divClick”>点击​ ``` ​ (4)`.self` ​ 只当在 event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的。 ​ ```<div @click.self=“divClick”>phrase点击​ ``` ​ (5)`.once` ​ 不像其它只能对原生的 DOM 事件起作用的修饰符,`.once` 修饰符还能被用到自定义的组件事件上,表示当前事件只触发一次。 ​ ``` 点击 ​ ``` (6)`.passive` ​ `.passive` 修饰符尤其能够提升移动端的性能 ​ ```

...

``` ​

 

 1### 11.Vue中的$nextTick有什么作用
 2
 3```javascript
 4const callbacks = []
 5let pending = false
 6
 7
 8 * 完成两件事:
 9 *   1、用 try catch 包装 flushSchedulerQueue 函数,然后将其放入 callbacks 数组
10 *   2、如果 pending 为 false,表示现在浏览器的任务队列中没有 flushCallbacks 函数
11 *     如果 pending 为 true,则表示浏览器的任务队列中已经被放入了 flushCallbacks 函数,
12 *     待执行 flushCallbacks 函数时,pending 会被再次置为 false,表示下一个 flushCallbacks 函数可以进入
13 *     浏览器的任务队列了
14 * pending 的作用:保证在同一时刻,浏览器的任务队列中只有一个 flushCallbacks 函数
15 * @param {*} cb 接收一个回调函数 => flushSchedulerQueue
16 * @param {*} ctx 上下文
17 * @returns 
18 */
19export function nextTick (cb?: Function, ctx?: Object) {
20  let _resolve
21  
22  callbacks.push(() => {
23    if (cb) {
24      
25      try {
26        cb.call(ctx)
27      } catch (e) {
28        handleError(e, ctx, 'nextTick')
29      }
30    } else if (_resolve) {
31      _resolve(ctx)
32    }
33  })
34  if (!pending) {
35    pending = true
36    
37    timerFunc()
38  }
39  
40  if (!cb && typeof Promise !== 'undefined') {
41    return new Promise(resolve => {
42      _resolve = resolve
43    })
44  }
45}
 1官方对其的定义
 2
 3在下次 DOM 更新循环结束之后执行延迟回调在修改数据之后立即使用这个方法获取更新后的 DOM
 4
 5什么意思呢
 6
 7我们可以理解成Vue 在更新 DOM 时是异步执行的当数据发生变化Vue将开启一个异步更新队列视图需要等队列中所有数据变化完成之后再统一进行更新
 8
 9Vue 的异步更新机制的核心是利用了浏览器的异步任务队列来实现的首选微任务队列宏任务队列次之
10
11当响应式数据更新后会调用 dep.notify 方法通知 dep 中收集的 watcher 去执行 update 方法watcher.update watcher 自己放入一个 watcher 队列全局的 queue 数组)。
12
13然后通过 nextTick 方法将一个刷新 watcher 队列的方法flushSchedulerQueue放入一个全局的 callbacks 数组中
14
15如果此时浏览器的异步任务队列中没有一个叫 flushCallbacks 的函数则执行 timerFunc 函数 flushCallbacks 函数放入异步任务队列如果异步任务队列中已经存在 flushCallbacks 函数等待其执行完成以后再放入下一个 flushCallbacks 函数
16
17flushCallbacks 函数负责执行 callbacks 数组中的所有 flushSchedulerQueue 函数
18
19flushSchedulerQueue 函数负责刷新 watcher 队列即执行 queue 数组中每一个 watcher run 方法从而进入更新阶段比如执行组件更新函数或者执行用户 watch 的回调函数

12.如何理解双向数据绑定

 1我们都知道 Vue 是数据双向绑定的框架双向绑定由三个重要部分构成
 2
 3数据层Model):应用的数据及业务逻辑
 4视图层View):应用的展示效果各类UI组件
 5业务逻辑层ViewModel):框架封装的核心它负责将数据与视图关联起来
 6而上面的这个分层的架构方案可以用一个专业术语进行称呼MVVM这里的控制层的核心功能便是数据双向绑定” 。自然我们只需弄懂它是什么便可以进一步了解数据绑定的原理
 7
 8理解ViewModel
 9它的主要职责就是
10
11数据变化后更新视图
12视图变化后更新数据
13当然它还有两个主要部分组成
14
15监听器Observer):对所有数据的属性进行监听
16解析器Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

13.v-show和v-if有什么区别?你可以讲讲吗

 1v-show v-if 的作用效果是相同的(不含v-else),都能控制元素在页面是否显示,在用法上也是相同的
 2
 3- 区别 
 4控制手段不同
 5编译过程不同
 6编译条件不同
 7
 8控制手段v-show隐藏则是为该元素添加css
 9
10编译过程v-if切换有一个局部编译/卸载的过程切换过程中合适地销毁和重建内部的事件监听和子组件v-show只是简单的基于css切换
11
12编译条件v-if是真正的条件渲染它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建只有渲染条件为假时并不做操作直到为真才渲染
13
14v-show 由false变为true的时候不会触发组件的生命周期
15
16v-if由false变为true的时候触发组件的beforeCreatecreatebeforeMountmounted钩子由true变为false的时候触发组件的beforeDestorydestoryed方法
17
18性能消耗v-if有更高的切换消耗v-show有更高的初始渲染消耗

14.有用过keep-alive吗?它有什么作用

 1`vue`中支持组件化并且也有用于缓存的内置组件`keep-alive`可直接使用使用场景为`路由组件``动态组件`
 2
 3*   `activated`表示进入组件的生命周期`deactivated`表示离开组件的生命周期
 4*   `include`表示匹配到的才缓存`exclude`表示匹配到的都不缓存
 5*   `max`表示最多可以缓存多少组件
 6
 7
 8关于keep-alive的基本用法
 9
10<keep-alive>
11  <component :is="view"></component>
12</keep-alive>
13使用includes和exclude:
14
15<keep-alive include="a,b">
16  <component :is="view"></component>
17</keep-alive>
18
19
20<keep-alive :include="/a|b/">
21  <component :is="view"></component>
22</keep-alive>
23
24
25<keep-alive :include="['a', 'b']">
26  <component :is="view"></component>
27</keep-alive>
28匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值),匿名组件不能被匹配
29
30设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated与deactivated):
31
32首次进入组件时:beforeRouteEnter > beforeCreate > created> mounted > activated > ... ... > beforeRouteLeave > deactivated
33
34再次进入组件时:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated

15.你可以实现一个虚拟DOM吗

先看浏览器对HTML的理解

 1<div>  
 2    <h1>My title</h1>  
 3    Some text content  
 4      
 5</div>

当浏览器读到这些代码时,它会建立一个DOM树来保持追踪所有内容,如同你会画一张家谱树来追踪家庭成员的发展一样。 上述 HTML 对应的 DOM 节点树如下图所示:

 

 

 

 

 

 

 1每个元素都是一个节点每段文字也是一个节点甚至注释也都是节点一个节点就是页面的一个部分就像家谱树一样每个节点都可以有孩子节点 (也就是说每个部分可以包含其它的一些部分)。
 2
 3**再看`Vue``HTML template`的理解**
 4
 5Vue 通过建立一个**虚拟 DOM** 来追踪自己要如何改变真实 DOM因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点包括及其子节点的描述信息我们把这样的节点描述为虚拟节点 (virtual node)”,也常简写它为**VNode**”。“虚拟 DOM是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼
 6
 7简言之浏览器对HTML的理解是DOM树Vue对`HTML`的理解是虚拟DOM最后在`patch`阶段通过DOM操作的api将其渲染成真实的DOM节点

如何实现虚拟DOM

首先可以看看vueVNode的结构

源码位置:src/core/vdom/vnode.js

 1export default class VNode {
 2  tag: string | void;
 3  data: VNodeData | void;
 4  children: ?Array<VNode>;
 5  text: string | void;
 6  elm: Node | void;
 7  ns: string | void;
 8  context: Component | void; 
 9  functionalContext: Component | void; 
10  key: string | number | void;
11  componentOptions: VNodeComponentOptions | void;
12  componentInstance: Component | void; 
13  parent: VNode | void; 
14  raw: boolean; 
15  isStatic: boolean; 
16  isRootInsert: boolean; 
17  isComment: boolean; 
18  isCloned: boolean; 
19  isOnce: boolean; 
20
21  constructor (
22    tag?: string,
23    data?: VNodeData,
24    children?: ?Array<VNode>,
25    text?: string,
26    elm?: Node,
27    context?: Component,
28    componentOptions?: VNodeComponentOptions
29  ) {
30    
31    this.tag = tag
32    
33    this.data = data
34    
35    this.children = children
36    
37    this.text = text
38    
39    this.elm = elm
40    
41    this.ns = undefined
42    
43    this.context = context
44    
45    this.functionalContext = undefined
46    
47    this.key = data && data.key
48    
49    this.componentOptions = componentOptions
50    
51    this.componentInstance = undefined
52    
53    this.parent = undefined
54    
55    this.raw = false
56    
57    this.isStatic = false
58    
59    this.isRootInsert = true
60    
61    this.isComment = false
62    
63    this.isCloned = false
64    
65    this.isOnce = false
66  }
67
68  
69  
70  get child (): Component | void {
71    return this.componentInstance
72  }
73}

这里对VNode进行稍微的说明:

  • 所有对象的 context 选项都指向了 Vue 实例
  • elm 属性则指向了其相对应的真实 DOM 节点
 1vue`是通过`createElement`生成`VNode

源码位置:src/core/vdom/create-element.js

 1export function createElement (
 2  context: Component,
 3  tag: any,
 4  data: any,
 5  children: any,
 6  normalizationType: any,
 7  alwaysNormalize: boolean
 8): VNode | Array<VNode> {
 9  if (Array.isArray(data) || isPrimitive(data)) {
10    normalizationType = children
11    children = data
12    data = undefined
13  }
14  if (isTrue(alwaysNormalize)) {
15    normalizationType = ALWAYS_NORMALIZE
16  }
17  return _createElement(context, tag, data, children, normalizationType)
18}

上面可以看到createElement 方法实际上是对 _createElement 方法的封装,对参数的传入进行了判断

 1export function _createElement(
 2    context: Component,
 3    tag?: string | Class<Component> | Function | Object,
 4    data?: VNodeData,
 5    children?: any,
 6    normalizationType?: number
 7): VNode | Array<VNode> {
 8    if (isDef(data) && isDef((data: any).__ob__)) {
 9        process.env.NODE_ENV !== 'production' && warn(
10            `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
11            'Always create fresh vnode data objects in each render!',
12            context`
13        )
14        return createEmptyVNode()
15    }
16    
17    if (isDef(data) && isDef(data.is)) {
18        tag = data.is
19    }
20    if (!tag) {
21        
22        return createEmptyVNode()
23    }
24    ... 
25    
26    if (Array.isArray(children) &&
27        typeof children[0] === 'function'
28    ) {
29        data = data || {}
30        data.scopedSlots = { default: children[0] }
31        children.length = 0
32    }
33    if (normalizationType === ALWAYS_NORMALIZE) {
34        children = normalizeChildren(children)
35    } else if ( === SIMPLE_NORMALIZE) {
36        children = simpleNormalizeChildren(children)
37    }
38  
39    ...
40}

可以看到_createElement接收5个参数:

  • context 表示 VNode 的上下文环境,是 Component 类型
  • tag 表示标签,它可以是一个字符串,也可以是一个 Component
  • data 表示 VNode 的数据,它是一个 VNodeData 类型
  • children 表示当前 VNode的子节点,它是任意类型的
  • normalizationType 表示子节点规范的类型,类型不同规范的方法也就不一样,主要是参考 render 函数是编译生成的还是用户手写的

根据normalizationType 的类型,children会有不同的定义

 1if (normalizationType === ALWAYS_NORMALIZE) {
 2    children = normalizeChildren(children)
 3} else if ( === SIMPLE_NORMALIZE) {
 4    children = simpleNormalizeChildren(children)
 5}

simpleNormalizeChildren方法调用场景是 render 函数是编译生成的

normalizeChildren方法调用场景分为下面两种:

  • render 函数是用户手写的
  • 编译 slotv-for 的时候会产生嵌套数组

无论是simpleNormalizeChildren还是normalizeChildren都是对children进行规范(使children 变成了一个类型为 VNodeArray),这里就不展开说了

规范化children的源码位置在:src/core/vdom/helpers/normalzie-children.js

在规范化children后,就去创建VNode

 1let vnode, ns
 2
 3if (typeof tag === 'string') {
 4  let Ctor
 5  ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
 6  if (config.isReservedTag(tag)) {
 7    
 8    vnode = new VNode(
 9      config.parsePlatformTagName(tag), data, children,
10      undefined, undefined, context
11    )
12  } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
13    
14    
15    vnode = createComponent(Ctor, data, context, children, tag)
16  } else {
17    vnode = new VNode(
18      tag, data, children,
19      undefined, undefined, context
20    )
21  }
22} else {
23  
24  vnode = createComponent(tag, data, context, children)
25}
 1createComponent`同样是创建`VNode

源码位置:src/core/vdom/create-component.js

 1export function createComponent (
 2  Ctor: Class<Component> | Function | Object | void,
 3  data: ?VNodeData,
 4  context: Component,
 5  children: ?Array<VNode>,
 6  tag?: string
 7): VNode | Array<VNode> | void {
 8  if (isUndef(Ctor)) {
 9    return
10  }
11 
12  const baseCtor = context.$options._base
13
14  
15  if (isObject(Ctor)) {
16    Ctor = baseCtor.extend(Ctor)
17  }
18
19  
20  
21  if (typeof Ctor !== 'function') {
22    if (process.env.NODE_ENV !== 'production') {
23      warn(`Invalid Component definition: ${String(Ctor)}`, context)
24    }
25    return
26  }
27
28  
29  let asyncFactory
30  if (isUndef(Ctor.cid)) {
31    asyncFactory = Ctor
32    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
33    if (Ctor === undefined) {
34      return createAsyncPlaceholder(
35        asyncFactory,
36        data,
37        context,
38        children,
39        tag
40      )
41    }
42  }
43
44  data = data || {}
45
46  
47  
48  resolveConstructorOptions(Ctor)
49
50  
51  if (isDef(data.model)) {
52    transformModel(Ctor.options, data)
53  }
54
55  
56  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
57
58  
59  if (isTrue(Ctor.options.functional)) {
60    return createFunctionalComponent(Ctor, propsData, data, context, children)
61  }
62
63  
64  
65  const listeners = data.on
66  
67  
68  data.on = data.nativeOn
69
70  if (isTrue(Ctor.options.abstract)) {
71    const slot = data.slot
72    data = {}
73    if (slot) {
74      data.slot = slot
75    }
76  }
77
78  
79  installComponentHooks(data)
80
81  
82  const name = Ctor.options.name || tag
83  const vnode = new VNode(
84    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
85    data, undefined, undefined, undefined, context,
86    { Ctor, propsData, listeners, tag, children },
87    asyncFactory
88  )
89  if (__WEEX__ && isRecyclableComponent(vnode)) {
90    return renderRecyclableComponentTemplate(vnode)
91  }
92
93  return vnode
94}

稍微提下createComponent生成VNode的三个关键流程:

  • 构造子类构造函数Ctor
  • installComponentHooks安装组件钩子函数
  • 实例化 vnode

小结

createElement 创建 VNode 的过程,每个 VNodechildrenchildren 每个元素也是一个VNode,这样就形成了一个虚拟树结构,用于描述真实的DOM树结构

16.为什么data属性是一个函数而不是一个对象,具体原因是什么

 1是不是一定是函数得看场景并且也无需担心什么时候该将`data`写为函数还是对象因为`vue`内部已经做了处理并在控制台输出错误信息
 2
 3**场景一**`new Vue({data: ...})`  
 4这种场景主要为项目入口或者多个`html`页面各实例化一个`Vue`这里的`data`即可用对象的形式也可用工厂函数返回对象的形式因为这里的`data`只会出现一次不存在重复引用而引起的数据污染问题
 5
 6**场景二**组件场景中的选项  
 7在生成组件`vnode`的过程中组件会在生成构造函数的过程中执行合并策略
 8

// data合并策略
strats.data = function (
 parentVal,
 childVal,
 vm
) {
 if (!vm) {
   if (childVal && typeof childVal !== ‘function’) {
     process.env.NODE_ENV !== ‘production’ && warn(
       ‘The “data” option should be a function ’ +
       ‘that returns a per-instance value in component ’ +
       ‘definitions.’,
       vm
     );

     return parentVal
   }
   return mergeDataOrFn(parentVal, childVal)
 }

 return mergeDataOrFn(parentVal, childVal, vm)
};

 1
 2如果合并过程中发现子组件的数据不是函数`typeof childVal !== 'function'`成立进而在开发环境会在控制台输出警告并且直接返回`parentVal`说明这里压根就没有把`childVal`中的任何`data`信息合并到`options`中去
 3
 4
 5上面讲到组件data必须是一个函数不知道大家有没有思考过这是为什么呢
 6
 7在我们定义好一个组件的时候vue最终都会通过Vue.extend()构成组件实例
 8
 9这里我们模仿组件构造函数定义data属性采用对象的形式
10
11function Component(){
12 
13}
14Component.prototype.data = {
15  count : 0
16}
17创建两个组件实例
18
19const componentA = new Component()
20const componentB = new Component()
21修改componentA组件data属性的值componentB中的值也发生了改变
22
23console.log(componentB.data.count)  
24componentA.data.count = 1
25console.log(componentB.data.count)  
26产生这样的原因这是两者共用了同一个内存地址componentA修改的内容同样对componentB产生了影响
27
28如果我们采用函数的形式则不会出现这种情况函数返回的对象内存地址并不相同
29
30function Component(){
31  this.data = this.data()
32}
33Component.prototype.data = function (){
34    return {
35      count : 0
36    }
37}
38修改componentA组件data属性的值componentB中的值不受影响
39
40console.log(componentB.data.count)  
41componentA.data.count = 1
42console.log(componentB.data.count)  
43vue组件可能会有很多个实例采用函数返回一个全新data形式使每个实例对象的数据不会受到其他实例对象数据的污染

17.Vue2的初始化过程你有过了解吗,做了哪些事情

 1new Vue走到了vue的构造函数中`src\core\instance\index.js`文件
 2
 3this._init(options)
 4
 5然后从Mixin增加的原型方法看initMixin(Vue),调用的是为Vue增加的原型方法_init
 6
 7
 8
 9function initMixin (Vue) {
10  Vue.prototype._init = function (options) {
11     var vm = this; 创建vm, 
12     ...
13     
14     vm.$options = mergeOptions(  
15       resolveConstructorOptions(vm.constructor), 
16       options || {},  
17       vm 
18     );
19  }
20  ...
21   initLifecycle(vm); 
22   initEvents(vm); 
23   initRender(vm); 
24   callHook(vm, 'beforeCreate'); 
25   ...
26   initState(vm);  
27   ...
28   callHook(vm, 'created');  
29   
30   if (vm.$options.el) {
31      vm.$mount(vm.$options.el); 
32   }
33 }
34
35总结
36
37所以从上面的函数看来new vue所做的事情就像一个流程图一样展开了分别是
38
39-   合并配置
40-   初始化生命周期
41-   初始化事件
42-   初始化渲染
43-   调用 `beforeCreate` 钩子函数
44-   init injections and reactivity这个阶段属性都已注入绑定而且被 `$watch` 变成reactivity但是 `$el` 还是没有生成也就是DOM没有生成
45-   初始化state状态初始化了datapropscomputedwatcher
46-   调用created钩子函数
47
48在初始化的最后检测到如果有 el 属性则调用 vm.$mount 方法挂载 vm挂载的目标就是把模板渲染成最终的 DOM

18.Vue3初始化的一个大概流程

 1- 初始化的一个大概流程
 2
 3createApp() => mount() => render() => patch() => processComponent() => mountComponent()
 4
 5- 简易版流程编写
 6
 71.Vue.createApp() 实际执行的是renderer的createApp()
 8
 92.renderer是createRenderer这个方法创建
10
113.renderer的createApp()是createAppAPI()返回的
12
134.createAppApi接受到render之后创建一个app实例定义mount方法
14
155.mount会调用render函数将vnode转换为真实dom
16
17createRenderer() => renderer => renderer.createApp() <= createAppApi()
18
19
20<div id="app"></div>
21
22<script>
23    
24    const createAppAPI = render => {
25        return function createApp(rootComponent) {
26            
27            const app = {
28                mount(rootContainer) {
29                    
30                    const vnode = {
31                        tag: rootComponent
32                    }
33                    
34                    render(vnode, rootContainer)
35                }
36            }
37            return app;
38        }
39    }
40
41    
42    const Vue = {
43        createApp(options) {
44            
45            
46            return renderer.createApp(options)
47        }
48    }
49
50    
51    const createRenderer = options => {
52        
53        const patch = (n1, n2, container) => {
54            
55            const rootComponent = n2.tag;
56            const ctx = { ...rootComponent.data()}
57            
58            const vnode = rootComponent.render.call(ctx);
59
60            
61            const parent = options.querySelector(container)
62            const child = options.createElement(vnode.tag)
63            if (typeof vnode.children === 'string') {
64                child.textContent = vnode.children
65            } else {
66                
67            }
68            
69            options.insert(child, parent)
70        }
71
72        
73        const render = (vnode, container) => {
74            patch(container._vnode || null, vnode, container)
75            container._vnode = vnode;
76        }
77
78        
79        return {
80            render,
81            createApp: createAppAPI(render)
82        }
83    }
84
85    const renderer = createRenderer({
86        querySelector(el) {
87            return document.querySelector(el)
88        },
89        createElement(tag) {
90            return document.createElement(tag)
91        },
92        insert(child, parent) {
93            parent.appendChild(child)
94        }
95    })
96
97    Vue.createApp({
98        data() {
99            return {
100                bar: 'hello,vue3'
101            }
102        },
103        render() {
104            return {
105                tag: 'h1',
106                children: this.bar
107            }
108        }
109    }).mount('#app')
110</script>

19.vue3响应式api如何编写

 1var activeEffect = null;
 2function effect(fn) {
 3  activeEffect = fn;
 4  activeEffect();
 5  activeEffect = null; 
 6}
 7var depsMap = new WeakMap();
 8function gather(target, key) {
 9  
10  if (!activeEffect) return;
11  let depMap = depsMap.get(target);
12  if (!depMap) {
13    depsMap.set(target, (depMap = new Map()));
14  }
15  let dep = depMap.get(key);
16  if (!dep) {
17    depMap.set(key, (dep = new Set()));
18  }
19  dep.add(activeEffect)
20}
21function trigger(target, key) {
22  let depMap = depsMap.get(target);
23  if (depMap) {
24    const dep = depMap.get(key);
25    if (dep) {
26      dep.forEach((effect) => effect());
27    }
28  }
29}
30function reactive(target) {
31  const handle = {
32    set(target, key, value, receiver) {
33      Reflect.set(target, key, value, receiver);
34      trigger(receiver, key); 
35    },
36    get(target, key, receiver) {
37      gather(receiver, key); 
38      return Reflect.get(target, key, receiver);
39    },
40  };
41  return new Proxy(target, handle);
42}
43
44function ref(name){
45    return reactive(
46        {
47            value: name
48        }
49    )
50}

20.在Vue项目中你是如何做的SSR渲染

 1与传统 SPA (单页应用程序 (Single-Page Application)) 相比服务器端渲染 (SSR) 的优势主要在于
 2
 3*   更好的 SEO由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面
 4*   更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备
 5
 6Vue.js 是构建客户端应用程序的框架默认情况下可以在浏览器中输出 Vue 组件进行生成 DOM 和操作 DOM然而也可以将同一个组件渲染为服务器端的 HTML 字符串将它们直接发送到浏览器最后将这些静态标记"激活"为客户端上完全可交互的应用程序
 7
 8服务器渲染的 Vue.js 应用程序也可以被认为是"同构""通用"因为应用程序的大部分代码都可以在服务器和客户端上运行
 9
10* Vue SSR是一个在SPA上进行改良的服务端渲染
11* 通过Vue SSR渲染的页面需要在客户端激活才能实现交互
12* Vue SSR将包含两部分服务端渲染的首屏包含交互的SPA
13
14使用ssr不存在单例模式每次用户请求都会创建一个新的vue实例
15实现ssr需要实现服务端首屏渲染和客户端激活
16服务端异步获取数据asyncData可以分为首屏异步获取和切换组件获取
17首屏异步获取数据在服务端预渲染的时候就应该已经完成
18切换组件通过mixin混入在beforeMount钩子完成数据获取

21.怎么看Vue的diff算法

 1diff 算法是一种通过同层的树节点进行比较的高效算法
 2
 3diff整体策略为深度优先同层比较
 4比较只会在同层级进行, 不会跨层级比较
 5比较的过程中循环从两边向中间收拢
 6
 7- 当数据发生改变时订阅者watcher就会调用patch给真实的DOM打补丁
 8- 通过isSameVnode进行判断相同则调用patchVnode方法
 9- patchVnode做了以下操作
10  - 找到对应的真实dom称为el
11  - 如果都有都有文本节点且不相等将el文本节点设置为Vnode的文本节点
12  - 如果oldVnode有子节点而VNode没有则删除el子节点
13  - 如果oldVnode没有子节点而VNode有则将VNode的子节点真实化后添加到el
14  - 如果两者都有子节点则执行updateChildren函数比较子节点
15- updateChildren主要做了以下操作
16  - 设置新旧VNode的头尾指针
17  - 新旧头尾指针进行比较循环向中间靠拢根据情况调用patchVnode进行patch重复流程调用createElem创建一个新节点从哈希表寻找 key一致的VNode 节点再分情况操作

22.从01构建一个Vue项目你需要做哪些内容

 1*   架子选用合适的初始化脚手架(`vue-cli2.0`或者`vue-cli3.0`)
 2*   请求数据`axios`请求的配置
 3*   登录登录注册系统
 4*   路由路由管理页面
 5*   数据`vuex`全局数据管理
 6*   权限权限管理系统
 7*   埋点埋点系统
 8*   插件第三方插件的选取以及引入方式
 9*   错误错误页面
10*   入口前端资源直接当静态资源或者服务端模板拉取
11*   `SEO`如果考虑`SEO`建议采用`SSR`方案
12*   组件基础组件/业务组件
13*   样式样式预处理起公共样式抽取
14*   方法公共方法抽离

23. 介绍一下 js 的数据类型有哪些,值是如何存储的

 1JavaScript 一共有 8 种数据类型其中有 7 种基本数据类型UndefinedNullBooleanNumberStringSymboles6 新增表示独一无二的值 BigIntes10 新增);
 2
 31 种引用数据类型——ObjectObject 本质上是由一组无序的名值对组成的)。里面包含 functionArrayDateJavaScript 不支持任何创建自定义类型的机制而所有值最终都将是上述 8 种数据类型之一
 4
 5原始数据类型直接存储在****stack占据空间小大小固定属于被频繁使用数据所以放入栈中存储
 6
 7引用数据类型同时存储在****stack****heap占据空间大大小不固定引用数据类型在栈中存储了指针该指针指向堆中该实体的起始地址当解释器寻找引用值时会首先检索其在栈中的地址取得地址后从堆中获得实体

24. JS 中Object.prototype.toString.call()判断数据类型

 1var a = Object.prototype.toString;
 2console.log(a.call(2));
 3console.log(a.call(true));
 4console.log(a.call(
 5console.log(a.call([]));
 6console.log(a.call(function(){}));
 7console.log(a.call({}));
 8console.log(a.call(undefined));
 9console.log(a.call(null));https://link.juejin.cn?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000011467723%23articleHeader24 "https://segmentfault.com/a/1190000011467723#articleHeader24")

25. null 和 undefined 的区别?

 1首先 Undefined Null 都是基本数据类型这两个基本数据类型分别都只有一个值就是 undefined null
 2
 3undefined 代表的含义是未定义null 代表的含义是空对象其实不是真的对象请看下面的**注意**!)。一般变量声明了但还没有定义的时候会返回 undefinednull 主要用于赋值给一些可能会返回对象的变量作为初始化
 4
 5其实 null 不是对象虽然 typeof null 会输出 object但是这只是 JS 存在的一个悠久 Bug JS 的最初版本中使用的是 32 位系统为了性能考虑使用低位存储变量的类型信息000 开头代表是对象然而 null 表示为全零所以将它错误的判断为 object虽然现在的内部类型判断代码已经改变了但是对于这个 Bug 却是一直流传下来
 6
 7undefined js 中不是一个保留字这意味着我们可以使用 undefined 来作为一个变量名这样的做法是非常危险的 会影响我们对 undefined 值的判断但是我们可以通过一些方法获得安全的 undefined比如说 void 0
 8
 9当我们对两种类型使用 typeof 进行判断的时候Null 类型化会返回object”,这是一个历史遗留的问题当我们使用双等 号对两种类型的值进行比较时会返回 true使用三个等号时会返回 false

26. {}和 [] 的 valueOf 和 toString 的结果是什么?

 1{}  valueOf 结果为 {} ,toString 的结果为 "[object Object]"
 2
 3[]  valueOf 结果为 [] ,toString 的结果为 ""

27. Javascript 的作用域和作用域链

 1**作用域** 作用域是定义变量的区域它有一套访问变量的规则这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量标识符进行变量查找
 2
 3**作用域链** 作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问通过作用域链我们可以访问到外层环境的变量和 函数
 4
 5作用域链的本质上是一个指向变量对象的指针列表变量对象是一个包含了执行环境中所有变量和函数的对象作用域链的前 端始终都是当前执行上下文的变量对象全局执行上下文的变量对象也就是全局对象始终是作用域链的最后一个对象
 6
 7当我们查找一个变量时如果当前执行环境中没有找到我们可以沿着作用域链向后查找
 8
 9作用域链的创建过程跟执行上下文的建立有关

28. 谈谈你对 this、call、apply 和 bind 的理解

 11.  在浏览器里在全局范围内 this 指向 window 对象
 22.  在函数中this 永远指向最后调用他的那个对象
 33.  构造函数中this 指向 new 出来的那个新的对象
 44.  callapplybind 中的 this 被强绑定在指定的那个对象上
 55.  箭头函数中 this 比较特殊, 箭头函数 this 为父作用域的 this不是调用时的 this. 要知道前四种方式, 都是调用时确定, 也就是动态的, 而箭头函数的 this 指向是静态的, 声明的时候就确定了下来
 66.  applycallbind 都是 js 给函数内置的一些 API调用他们可以为函数指定 this 的执行, 同时也可以传参

 

 

 

 

 

 

29. JavaScript 原型,原型链? 有什么特点?

 1 js 中我们是使用构造函数来新建一个对象的每一个构造函数的内部都有一个 prototype 属性值这个属性值是一个对这个对象包含了可以由该构造函数的所有实例共享的属性和方法当我们使用构造函数新建一个对象后在这个对象的内部 将包含一个指针这个指针指向构造函数的 prototype 属性对应的值 ES5 中这个指针被称为对象的原型一般来说我们 是不应该能够获取到这个值的但是现在浏览器中都实现了 **proto** 属性来让我们访问这个属性但是我们最好不要使用这 个属性因为它不是规范中规定的ES5 中新增了一个 Object.getPrototypeOf() 方法我们可以通过这个方法来获取对 象的原型
 2
 3当我们访问一个对象的属性时如果这个对象内部不存在这个属性那么它就会去它的原型对象里找这个属性这个原型对象又 会有自己的原型于是就这样一直找下去也就是原型链的概念原型链的尽头一般来说都是 Object.prototype 所以这就 是我们新建的对象为什么能够使用 toString() 等方法的原因
 4
 5特点
 6
 7JavaScript 对象是通过引用来传递的我们创建的每个新对象实体中并没有一份属于自己的原型副本当我们修改原型时 之相关的对象也会继承这一改变

参考文章: 《JavaScript 深入理解之原型与原型链》

30. 什么是闭包,为什么要用它?

 1- 能够访问其它函数内部变量的函数称为闭包
 2- 能够访问自由变量的函数称为闭包
 3
 4场景
 5至于闭包的使用场景其实在日常开发中使用到是非常频繁的
 6
 7- 防抖节流函数
 8- 定时器回调
 9- 等就不一一列举了
10
11优点
12闭包帮我们解决了什么问题呢
13**内部变量是私有的可以做到隔离作用域保持数据的不被污染性**
14
15缺点
16同时闭包也带来了不小的坏处
17**说到了它的优点`内部变量是私有的,可以做到隔离作用域`,那也就是说垃圾回收机制是无法清理闭包中内部变量的那最后结果就是内存泄漏**

31. 三种事件模型是什么?

 1**事件** 是用户操作网页时发生的交互动作或者网页本身的一些操作现代浏览器一共有三种事件模型
 2
 31.  **DOM0 级模型**这种模型不会传播所以没有事件流的概念但是现在有的浏览器支持以冒泡的方式实现它可以在网页中直接定义监听函数也可以通过 js 属性来指定监听函数这种方式是所有浏览器都兼容的
 42.  **IE 事件模型** 在该事件模型中一次事件共有两个过程事件处理阶段和事件冒泡阶段事件处理阶段会首先执行目标元素绑定的监听事件然后是事件冒泡阶段冒泡指的是事件从目标元素冒泡到 document依次检查经过的节点是否绑定了事件监听函数如果有则执行这种模型通过 attachEvent 来添加监听函数可以添加多个监听函数会按顺序依次执行
 53.  **DOM2 级事件模型** 在该事件模型中一次事件共有三个过程第一个过程是事件捕获阶段捕获指的是事件从 document 一直向下传播到目标元素依次检查经过的节点是否绑定了事件监听函数如果有则执行后面两个阶段和 IE 事件模型的两个阶段相同这种事件模型事件绑定的函数是 addEventListener其中第三个参数可以指定事件是否在捕获阶段执行

32. js 数组和字符串有哪些原生方法, 列举一下

 

 

 

 

 

 

 

 

 

 

 

 

33. js 延迟加载的方式有哪些

 1js 的加载解析和执行会阻塞页面的渲染过程因此我们希望 js 脚本能够尽可能的延迟加载提高页面的渲染速度
 2
 31.  将 js 脚本放在文档的底部来使 js 脚本尽可能的在最后来加载执行
 42.  给 js 脚本添加 defer 属性这个属性会让脚本的加载与文档的解析同步解析然后在文档解析完成后再执行这个脚本文件这样的话就能使页面的渲染不被阻塞多个设置了 defer 属性的脚本按规范来说最后是顺序执行的但是在一些浏览器中可能不是这样
 53.  给 js 脚本添加 async 属性这个属性会使脚本异步加载不会阻塞页面的解析过程但是当脚本加载完成后立即执行 js 脚本这个时候如果文档没有解析完成的话同样会阻塞多个 async 属性的脚本的执行顺序是不可预测的一般不会按照代码的顺序依次执行
 64.  动态创建 DOM 标签的方式我们可以对文档的加载事件进行监听当文档加载完成后再动态的创建 script 标签来引入 js 脚本

34. js 的几种模块规范?

 1js 中现在比较成熟的有四种模块加载方案
 2
 3*   第一种是 CommonJS 方案它通过 require 来引入模块通过 module.exports 定义模块的输出接口这种模块加载方案是服务器端的解决方案它是以同步的方式来引入模块的因为在服务端文件都存储在本地磁盘所以读取非常快所以以同步的方式加载没有问题但如果是在浏览器端由于模块的加载是使用网络请求因此使用异步加载的方式更加合适
 4*   第二种是 AMD 方案这种方案采用异步加载的方式来加载模块模块的加载不影响后面语句的执行所有依赖这个模块的语句都定义在一个回调函数里等到加载完成后再执行回调函数require.js 实现了 AMD 规范
 5*   第三种是 CMD 方案这种方案和 AMD 方案都是为了解决异步模块加载的问题sea.js 实现了 CMD 规范它和 require.js 的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同
 6*   第四种方案是 ES6 提出的方案使用 import export 的形式来导入导出模块

35. AMD 和 CMD 规范的区别?

 1它们之间的主要区别有两个方面
 2
 31.  第一个方面是在模块定义时对依赖的处理不同AMD 推崇依赖前置在定义模块的时候就要声明其依赖的模块 CMD 推崇就近依赖只有在用到某个模块的时候再去 require
 42.  第二个方面是对依赖模块的执行时机处理不同首先 AMD CMD 对于模块的加载方式都是异步加载不过它们的区别在于 模块的执行时机AMD 在依赖模块加载完成后就直接执行依赖模块依赖模块的执行顺序和我们书写的顺序不一定一致 CMD 在依赖模块加载完成后并不执行只是下载而已等到所有的依赖模块都加载好后进入回调函数逻辑遇到 require 语句 的时候才执行对应的模块这样模块的执行顺序就和我们书写的顺序保持一致了
 5
 6
 7define(function(require, exports, module) {
 8  var a = require("./a");
 9  a.doSomething();
10  
11  var b = require("./b"); 
12  b.doSomething();
13  
14});
15
16
17define(["./a", "./b"], function(a, b) {
18  
19  a.doSomething();
20  
21  b.doSomething();
22  
23});

36. ES6 模块与 CommonJS 模块、AMD、CMD 的差异。

 11语法上
 2CommonJS 使用的是 module.exports = {} 导出一个模块对象require(‘file_path’) 引入模块对象
 3ES6使用的是 export 导出指定数据import 引入具体数据
 4
 52、CommonJS 模块输出的是一个值的拷贝ES6 模块输出的是值的引用
 6
 7CommonJS 模块输出的是值的拷贝也就是说一旦输出一个值模块内部的变化就影响不到这个值
 8
 9ES6 Modules 的运行机制与 CommonJS 不一样JS 引擎对脚本静态分析的时候遇到模块加载命令import就会生成一个只读引用等到脚本真正执行时再根据这个只读引用到被加载的那个模块里面去取值换句话说ES6的import 有点像 Unix 系统的符号连接”,原始值变了import加载的值也会跟着变因此ES6模块是动态引用并且不会缓存值模块里面的变量绑定其所在的模块
10
113、CommonJS 模块是运行时加载ES6 模块是编译时加载
12
13运行时加载: CommonJS 模块就是对象即在输入时是先加载整个模块生成一个对象然后再从这个对象上面读取方法这种加载称为运行时加载”。
14
15编译时加载: ES6 模块不是对象而是通过 export 命令显式指定输出的代码import时采用静态命令的形式即在import时可以指定加载某个输出值而不是加载整个模块这种加载称为编译时加载
16
17PSCommonJS 加载的是一个对象即module.exports属性),该对象只有在脚本运行完才会生成 ES6 模块不是对象它的对外接口只是一种静态定义在代码静态解析阶段就会生成

37. JS 的运行机制

 1异步任务分类宏任务微任务
 2同步任务和异步任务分别进入不同的执行"场所"
 3先执行主线程执行栈中的宏任务
 4执行过程中如果遇到微任务进入Event Table并注册函数完成后移入到微任务的任务队列中
 5宏任务执行完毕后立即执行当前微任务队列中的所有微任务依次执行
 6主线程会不断获取任务队列中的任务执行任务再获取再执行任务也就是常说的Event Loop(事件循环)。

38. 简单介绍一下 V8 引擎的垃圾回收机制

 1v8 的垃圾回收机制基于分代回收机制这个机制又基于世代假说这个假说有两个特点一是新生的对象容易早死另一个是不死的对象会活得更久基于这个假说v8 引擎将内存分为了新生代和老生代
 2
 3新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代经历过多次垃圾回收的对象被称为老生代
 4
 5新生代被分为 From To 两个空间To 一般是闲置的 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收当我们执行垃圾回收算法的时候应用逻辑将会停止等垃圾回收结束后再继续执行这个算法分为三步
 6
 71首先检查 From 空间的存活对象如果对象存活则判断对象是否满足晋升到老生代的条件如果满足条件则晋升到老生代如果不满足条件则移动 To 空间
 8
 92如果对象不存活则释放对象的空间
10
113最后将 From 空间和 To 空间角色进行交换
12
13新生代对象晋升到老生代有两个条件
14
151第一个是判断是对象否已经经过一次 Scavenge 回收若经历过则将对象从 From 空间复制到老生代中若没有经历则复制到 To 空间
16
172第二个是 To 空间的内存使用占比是否超过限制当对象从 From 空间复制到 To 空间时 To 空间使用超过 25%则对象直接晋升到老生代中设置 25% 的原因主要是因为算法结束后两个空间结束后会交换位置如果 To 空间的内存太小会影响后续的内存分配
18
19老生代采用了标记清除法和标记压缩法标记清除法首先会对内存中存活的对象进行标记标记结束后清除掉那些没有标记的对象由于标记清除后会造成很多的内存碎片不便于后面的内存分配所以了解决内存碎片的问题引入了标记压缩法
20
21由于在进行垃圾回收的时候会暂停应用的逻辑对于新生代方法由于内存小每次停顿的时间不会太长但对于老生代来说每次垃圾回收的时间长停顿会造成很大的影响为了解决这个问题 V8 引入了增量标记的方法将一次停顿进行的过程分为了多步每次执行完一小步就让运行逻辑执行一会就这样交替运行

相关资料:

《深入理解 V8 的垃圾回收原理》

《JavaScript 中的垃圾回收》

39. 哪些操作会造成内存泄漏?

 1*   1. 意外的全局变量
 2*   2. 被遗忘的计时器或回调函数
 3*   3. 脱离 DOM 的引用
 4*   4. 闭包

40.ES6有哪些新特性?

 1*   块作用域
 2*   类
 3*   箭头函数
 4*   模板字符串
 5*   加强的对象字面
 6*   对象解构
 7*   Promise
 8*   模块
 9*   Symbol
10*   代理proxySet
11*   函数默认参数
12*   展开

41. 什么是箭头函数?

 1var getCurrentDate = function (){
 2  return new Date();
 3}
 4
 5
 6const getCurrentDate = () => new Date();
 7
 8箭头函数表达式的语法比函数表达式更简洁并且没有自己的`this,arguments,super或new.target`箭头函数表达式更适用于那些本来需要匿名函数的地方并且它不能用作构造函数
 9
10箭头函数没有自己的 this它捕获词法作用域函数的 this如果我们在全局作用域声明箭头函数 this 值为 window 对象

42. 什么是高阶函数?

 1高阶函数只是将函数作为参数或返回值的函数
 2
 3function higherOrderFunction(param,callback){
 4    return callback(param);
 5}

43. 手写 call、apply 及 bind 函数

1.实现call函数

实现步骤:

  • 处理边界:

    • 对象不存在,this指向window;
  • 将「调用函数」挂载到「this指向的对象」的fn属性上。

  • 执行「this指向的对象」上的fn函数,并传入参数,返回结果。

 1Function.prototype.mu_call = function (context, ...args) {
 2    
 3    if (!context || context === null) {
 4      context = window;
 5    }
 6    
 7    let fn = Symbol();
 8
 9    
10    context[fn] = this;
11
12    
13    return context[fn](...args);
14  };
15
16  
17  var value = 2;
18  var obj1 = {
19    value: 1,
20  };
21  function bar(name, age) {
22    var myObj = {
23      name: name,
24      age: age,
25      value: this.value,
26    };
27    console.log(this.value, myObj);
28  }
29  bar.mu_call(null); 
30  bar.mu_call(obj1, 'tom', '110'); 

2.实现apply函数

实现步骤:

  • 与call一致
  • 区别于参数的形式
 1Function.prototype.mu_apply = function (context, args) {
 2  //obj不存在指向window
 3  if (!context || context === null) {
 4    context = Window
 5  }
 6  // 创造唯一的key值  作为我们构造的context内部方法名
 7  let fn = Symbol()
 8
 9  //this指向调用call的函数
10  context[fn] = this
11
12  // 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了
13  return context[fn](...args)
14}
15
16// 测试
17var value = 2
18var obj1 = {
19  value: 1,
20}
21function bar(name, age) {
22  var myObj = {
23    name: name,
24    age: age,
25    value: this.value,
26  }
27  console.log(this.value, myObj)
28}
29bar.mu_apply(obj1, ["tom", "110"])

3.实现bind函数

 1Function.prototype.mu_bind = function (context, ...args) {
 2    if (!context || context === null) {
 3      context = window
 4    }
 5    // 创造唯一的key值  作为我们构造的context内部方法名
 6    let fn = Symbol()
 7    context[fn] = this
 8    let _this = this
 9    //  bind情况要复杂一点
10    const result = function (...innerArgs) {
11      // 第一种情况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象
12      // 此时由于new操作符作用  this指向result实例对象  而result又继承自传入的_this 根据原型链知识可得出以下结论
13      // this.__proto__ === result.prototype   //this instanceof result =>true
14      // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype
15      if (this instanceof _this === true) {
16        // 此时this指向指向result的实例  这时候不需要改变this指向
17        this[fn] = _this
18        this[fn](...[...args, ...innerArgs])
19        delete this[fn]
20      } else {
21        // 如果只是作为普通函数调用  那就很简单了 直接改变this指向为传入的context
22        context[fn](...[...args, ...innerArgs])
23        delete context[fn]
24      }
25    }
26    // 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
27    // 实现继承的方式: 使用Object.create
28    result.prototype = Object.create(this.prototype)
29    return result
30  }
31  function Person(name, age) {
32    console.log(name)
33    console.log(age)
34    console.log(this)
35  }
36  // 构造函数原型的方法
37  Person.prototype.say = function () {
38    console.log(123)
39  }
40
41  // 普通函数
42  function normalFun(name, age) {
43    console.log(name)
44    console.log(age)
45    console.log(this)
46    console.log(this.objName)
47    console.log(this.objAge)
48  }
49
50  let obj = {
51    objName: '我是obj传进来的name',
52    objAge: '我是obj传进来的age',
53  }
54
55  // 先测试作为构造函数调用
56  //   let bindFun = Person.mu_bind(obj, '我是参数传进来的name')
57  //   let a = new bindFun('我是参数传进来的age')
58  //   a.say()
59
60  //   再测试作为普通函数调用a
61  let bindFun = normalFun.mu_bind(obj, '我是参数传进来的name')
62  bindFun('我是参数传进来的age')

参考文章: 高频JavaScript手写面试题,你“行”吗

44. 函数柯里化的实现

 1
 2function curry(fn, args) {
 3  
 4  let length = fn.length;
 5
 6  args = args || [];
 7
 8  return function() {
 9    let subArgs = args.slice(0);
10
11    
12    for (let i = 0; i < arguments.length; i++) {
13      subArgs.push(arguments[i]);
14    }
15
16    
17    if (subArgs.length >= length) {
18      
19      return fn.apply(this, subArgs);
20    } else {
21      
22      return curry.call(this, fn, subArgs);
23    }
24  };
25}
26
27
28function curry(fn, ...args) {
29  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
30}

参考文章: 《JavaScript 专题之函数柯里化》

45. 实现一个 new 操作符

首先需要了解new做了什么事情:

  • 首先创建了一个空对象。
  • 将空对象proto指向构造函数的原型prototype
  • 使this指向新创建的对象,并执行构造函数。
  • 执行结果有返回值并且是一个对象, 返回执行的结果, 否则返回新创建的对象。
 1function mu_new(fn,...arg){
 2    
 3    const obj = {};
 4    
 5    Object.setPrototypeOf(obj, fn.prototype)
 6    
 7    const result = fn.apply(obj,arg);
 8    
 9    return result instanceof Object ? result : obj;
10}
11
12
13function Dog(name){
14    this.name = name;
15    this.say = function(){
16        console.log('my name is' + this.name);
17    }
18}
19
20const dog = mu_new(Dog, "傻🐶");
21dog.say() 

46. 可以讲讲Promise吗,可以手写实现一下吗?

 1Promise 是异步编程的一种解决方案比传统的解决方案——回调函数和事件——更合理和更强大它由社区最早提出和实现ES6 将其写进了语言标准统一了用法原生提供了`Promise`对象
 2
 3所谓`Promise`简单说就是一个容器里面保存着某个未来才会结束的事件通常是一个异步操作的结果从语法上说Promise 是一个对象从它可以获取异步操作的消息Promise 提供统一的 API各种异步操作都可以用同样的方法进行处理
 4
 5**那我们来看看我们所熟知的`Promise`的基本原理**
 6
 7+ 首先我们在调用Promise时会返回一个Promise对象
 8+ 构建Promise对象时需要传入一个executor函数Promise的主要业务流程都在executor函数中执行
 9+ 如果运行在excutor函数中的业务执行成功了会调用resolve函数如果执行失败了则调用reject函数
10+ Promise的状态不可逆同时调用resolve函数和reject函数默认会采取第一次调用的结果
11
12**结合Promise/A+规范我们还可以分析出哪些基本特征**
13
14Promise/A+的规范比较多在这列出一下核心的规范。[Promise/A+规范](https://link.juejin.cn/?target=https%3A%2F%2Fpromisesaplus.com%2F)
15
16+ promise有三个状态pendingfulfilledrejected默认状态是pending
17+ promise有一个value保存成功状态的值有一个reason保存失败状态的值可以是undefined/thenable/promise
18+ promise只能从pending到rejected, 或者从pending到fulfilled状态一旦确认就不会再改变
19+ promise 必须有一个then方法then接收两个参数分别是promise成功的回调onFulfilled, 和promise失败的回调onRejected
20+ 如果then中抛出了异常那么就会把这个异常作为参数传递给下一个then的失败的回调onRejected
21
22`CustomPromise`还实现不了基本原理的3,4两条那我们来根据基本原理与Promise/A+分析下还缺少什么
23
24- promise有三个状态pendingfulfilledrejected
25- executor执行器调用reject与resolve两个方法
26- 还需要有保存成功或失败两个值的变量
27- then接收两个参数分别是成功的回调onFulfilled,失败的回调onRejected

手写实现promise

47. 什么是 async/await 及其如何工作, 可以手写async吗

 11Async声明一个异步函数
 2  - 自动将常规函数转换成Promise返回值也是一个Promise对象
 3  - 只有async函数内部的异步操作执行完才会执行then方法指定的回调函数
 4  - 异步函数内部可以使用await
 52Await暂停异步的功能执行(var result = await someAsyncCall();)
 6  - 放置在Promise调用之前await强制其他代码等待直到Promise完成并返回结果
 7  - 只能与Promise一起使用不适用与回调
 8  - 只能在async函数内部使用

手写实现async

48. instanceof 的优缺点是什么,如何实现

优缺点:

  • 「优点」:能够区分Array、Object和Function,适合用于判断自定义的类实例对象
  • 「缺点」:Number,Boolean,String基本数据类型不能判断

实现步骤:

  • 传入参数为左侧的实例L,和右侧的构造函数R
  • 处理边界,如果要检测对象为基本类型则返回false
  • 分别取传入参数的原型
  • 判断左侧的原型是否取到了null,如果是null返回false;如果两侧原型相等,返回true,否则继续取左侧原型的原型。
 1// 传入参数左侧为实例L, 右侧为构造函数R
 2function mu_instanceof(L,R){
 3    // 处理边界:检测实例类型是否为原始类型
 4    const baseTypes = ['string','number','boolean','symbol','undefined']
 5
 6    if(baseTypes.includes(typeof L) || L === null) return false
 7
 8    // 分别取传入参数的原型
 9    let Lp = L.__proto__
10    let Rp = R.prototype
11
12    // 判断原型
13    while(true){
14        if(Lp === null) return false
15        if(Lp === Rp) return true
16        Lp = Lp.__proto__
17    }
18}
19
20// 验证
21const isArray = mu_instanceof([],Array)
22console.log(isArray)
23const isDate = mu_instanceof('2023-01-09',Date)
24console.log(isDate)

49. js 的节流与防抖

1.防抖

函数防抖是在事件被触发n秒后再执行回调,如果在「n秒内又被触发」,则「重新计时」

 1function debounce(fn, wait) {
 2    let timer = null;
 3    return function () {
 4      if (timer != null) {
 5        clearTimeout(timer);
 6      }
 7      timer = setTimeout(() => {
 8        fn();
 9      }, wait);
10    };
11  }
12  
13  function handle() {
14    console.log(Math.random());
15  }
16  
17  window.addEventListener('resize', debounce(handle, 1000));

2.节流

当事件触发时,保证一定时间段内只调用一次函数。例如页面滚动的时候,每隔一段时间发一次请求

实现步骤:

  • 传入参数为执行函数fn,等待时间wait。
  • 保存初始时间now。
  • 返回一个函数,如果超过等待时间,执行函数,将now更新为当前时间。
 1function throttle(fn, wait, ...args) {
 2    var pre = Date.now();
 3    return function () {
 4      
 5      var context = this;
 6      var now = Date.now();
 7      if (now - pre >= wait) {
 8        
 9        fn.apply(context, args);
10        pre = Date.now();
11      }
12    };
13  }
14
15  
16  var name = 'mu';
17  function handle(val) {
18    console.log(val + this.name);
19  }
20  
21  window.addEventListener('scroll', throttle(handle, 1000, '木由'));

50.HTML、XML、XHTML 的区别

 1- `HTML`超文本标记语言是语法较为松散的不严格的`Web`语言
 2- `XML`可扩展的标记语言主要用于存储数据和结构可扩展
 3- `XHTML`可扩展的超文本标记语言基于`XML`作用与`HTML`类似但语法更严格

51. HTML、XHTML和HTML5区别以及有什么联系

 1XHTML与HTML的区别
 2
 3- `XHTML`标签名必须小写
 4- `XHTML`元素必须被关闭
 5- `XHTML`元素必须被正确的嵌套
 6- `XHTML`元素必须要有根元素
 7
 8XHTML与HTML5的区别
 9
10- `HTML5`新增了`canvas`绘画元素
11- `HTML5`新增了用于绘媒介回放的`video``audio`元素
12- 更具语义化的标签便于浏览器识别
13- 对本地离线存储有更好的支持
14- `MATHML``SVG`可以更好的`render`
15- 添加了新的表单控件`calendar``date``time``email`
16
17HTMLXHTMLHTML5之间联系
18
19- `XHTML``HTML`规范版本
20- `HTML5``HTML``XHTML`以及`HTML DOM`的新标准

52.行内元素有哪些?块级元素有哪些? 空(void)元素有那些?

 1- 行内元素`a`, `b`, `span`, `img`, `input`, `select`, `strong`;
 2- 块级元素`div`, `ul`, `li`, `dl`, `dt`, `dd`, `h1-5`, `p`
 3- 空元素`<br>`, `<hr>`, `<img>`, `<link>`, `<meta>`;

53. 页面导入样式时,使用link和@import有什么区别

 1- `link`属于`HTML`标签`@import``css`提供的
 2- 页面被加载时`link`会同时被加载`@import`引用的css会等到页面被加载完再加载
 3- `@import`只在`IE5`以上才能识别`link``XHTML`标签无兼容问题
 4- `link`方式的样式的权重高于`@import`的权重

54. 如何理解语义化标签

 1概念
 2
 3语义化是指根据内容的结构化内容语义化),选择合适的标签代码语义化),便于开发者阅读和写出更优雅的代码的同时让浏览器的爬虫和机器很好的解析
 4
 5语义化的好处
 6
 7- 用正确的标签做正确的事情
 8- 去掉或者丢失样式的时候能够让页面呈现出清晰的结构
 9- 方便其他设备解析如屏幕阅读器盲人阅读器移动设备以意义的方式来渲染网页
10- 有利于`SEO`和搜索引擎建立良好沟通有助于爬虫抓取更多的有效信息爬虫依赖于标签来确定上下文和各个关键字的权重
11- 便于团队开发和维护语义化更具可读性遵循W3C标准的团队都遵循这个标准可以减少差异化

55. property和attribute的区别是什么

 1- `property``DOM`中的属性`JavaScript`里的对象;
 2- `attribute``HTML`标签上的特性它的值只能够是字符串;
 3
 4简单的理解就是`Attribute`就是`DOM`节点自带的属性例如`html`中常用的`id``class``title``align``Property`是这个`DOM`元素作为对象其附加的内容例如`childNodes``firstChild`

56. html5有哪些新特性、移除了那些元素

 1新特性
 2
 3**HTML5 现在已经不是 SGML 的子集主要是关于图像位置存储多任务等功能的增加**
 4
 5- 拖拽释放`(Drag and drop)` `API`
 6- 语义化更好的内容标签`header`, `nav`, `footer`, `aside`, `article`, `section`);
 7- 音频视频API(`audio`, `video`);
 8- 画布`(Canvas)` `API`;
 9- 地理`(Geolocation)` `API`;
10- 本地离线存储 `localStorage` 长期存储数据浏览器关闭后数据不丢失
11- `sessionStorage` 的数据在浏览器关闭后自动删除;
12- 表单控件:`calendar``date``time``email``url``search` ;
13- 新的技术`webworker`, `websocket`, `Geolocation`
14
15移除元素
16
17**纯表现元素**
18
19- `<basefont>` 默认字体不设置字体以此渲染
20- `<font>` 字体标签
21- `<center>` 水平居中
22- `<u>` 下划线
23- `<big>`字体
24- `<strike>`中横字
25- `<tt>`文本等宽
26
27**对可用性产生负面影响的元素**
28
29`<frameset>`,`<noframes>``<frame>`

57. 什么是前端的结构,样式和行为相分离?以及分离的好处是什么?

 1结构样式和行为分离
 2
 3若是将前端比作一个人来举例子结构`HTML`就相当于是人体的骨架”,样式就相当于人体的装饰”,例如衣服首饰等行为就相当于人做出的一系列动作”。
 4
 5在结构样式和行为分离就是将三者分离开各自负责各自的内容各部分可以通过引用进行使用
 6
 7在分离的基础上我们需要做到代码的**精简****重用****有序**
 8
 9分离的好处
10
11- 代码分离利于团队的开发和后期的维护
12- 减少维护成本提高可读性和更好的兼容性

58. 如何对网站的文件和资源进行优化

 1- 文件合并目的是减少`http`请求);
 2- 文件压缩目的是直接减少文件下载的体积);
 3- 使用缓存
 4- 使用`cdn`托管资源
 5- `gizp`压缩需要的js和css文件
 6- 反向链接网站外链接优化
 7- meta标签优化`title`, `description`, `keywords`),`heading`标签的优化,`alt`优化

59. Html5中本地存储概念是什么,有什么优点,与cookie有什么区别?

 1`HTML5``Web storage`的存储方式有两种`sessionStorage``localStorage`
 2
 3- `sessionStorage`用于本地存储一个会话中的数据当会话结束后就会销毁
 4-`sessionStorage`不同`localStorage`用于持久化的本地存储除非用户主动删除数据否则数据永远不会过期
 5- `cookie`是网站为了标示用户身份而储存在用户本地终端`Client Side`上的数据通常经过加密)。
 6
 7**区别**
 8
 9- **从浏览器和服务器间的传递看**`cookie`数据始终在同源的http请求中携带即使不需要),`cookie`在浏览器和服务器间来回传递`sessionStorage``localStorage`不会自动把数据发给服务器仅在本地保存
10- **从大小看**存储大小限制不同`cookie`数据不能超过`4k`只适合保存很小的数据`sessionStorage``localStorage` 虽然也有存储大小的限制但比`cookie`大得多可以达到5M或更大
11- **从数据有效期看**`sessionStorage`在会话关闭会立刻关闭因此持续性不久`cookie`只在设置的cookie过期时间之前一直有效即使窗口或浏览器关闭`localStorage`始终有效
12- **从作用域看**`sessionStorage`不在不同的浏览器窗口中共享即使是同一个页面`localStorage``cookie`都是可以在所有的同源窗口中共享的

60. 常见的浏览器内核有哪些

 1- `Trident`内核IE最先开发或使用的, 360浏览器
 2- `Webkit`内核Google ChromeSafari搜狗浏览器,360极速浏览器阿里云浏览器等
 3- `Gecko`内核Mozilla FireFox (火狐浏览器) ,K-Meleon浏览器
 4- `Presto`内核Opera浏览器

61. LocalStorage本地存储在HTML5中有什么用途

 1`localStorage`本地存储相当于一个轻量级的数据库可以在本地永久的储存数据除非人为删除)。此外还可以在断网情况下读取本地缓存的`cookies`
 2
 3- 使用`localStorage`保存数据`localStorage.setItem(key, value)`;
 4- 使用`localStorage`获取保存的数据: `localStorage.getItem(key)`;
 5- 清除`localStorage`保存的数据`localStorage.removeItem(key)`;
 6- 清除全部`localStorage`对象保存的数据: `localStorage.clear( )`;

62. 为什么利用多个域名来存储网站资源会更有效

 1- `CDN`缓存更加方便
 2- 突破浏览器并发限制
 3- 节约`cookie`宽带
 4- 节约主域名的连接数优化页面下响应速度
 5- 防止不必要的安全问题

63. HTML中几种图片格式的区别以及使用

 1页面中常用的几种图片格式有: `png`, `jpg(jpeg)`,`gif`, `bmp`
 2
 31)、**Png格式的特征**
 4
 5特征图片背景透明可以支持的颜色有很多
 6
 7使用范围: 比较广在目前使用频率最高
 8
 92)、**jpg格式特征**
10
11特征图片不支持透明静态图支持的颜色也比较多可压缩
12
13使用范围使用范围较广可使用作为电脑做面壁纸手机屏保等可根据需求来确实使用图片的分辨率
14
153)、**gif格式特征**
16
17特征动态图支持的颜色较少
18
19使用范围在目前看到的在网站内使用频率较低

64.DNS是什么

 1全称 Domain Name System , 即域名系统
 2
 3> 万维网上作为域名和 IP 地址相互映射的一个分布式数据库能够使用户更方便的访问互联网而不用去记住能够被机器直接读取的 IP 数串DNS 协议运行在 UDP 协议之上使用端口号 53
 4
 5简单的说, 通过域名, 最终得到该域名对应的 IP 地址的过程叫做域名解析或主机名解析)。
 6
 7```text
 8www.zuofc.com (域名)  - DNS解析 -> 111.222.33.444 (IP地址)


有 dns 的地方, 就有缓存。浏览器、操作系统、Local DNS、根域名服务器,它们都会对 DNS 结果做一定程度的缓存。

DNS 查询过程如下:

  1. 首先搜索浏览器自身的 DNS 缓存, 如果存在,则域名解析到此完成。
  2. 如果浏览器自身的缓存里面没有找到对应的条目,那么会尝试读取操作系统的 hosts 文件看是否存在对应的映射关系, 如果存在,则域名解析到此完成。
  3. 如果本地 hosts 文件不存在映射关系,则查找本地 DNS 服务器 (ISP 服务器, 或者自己手动设置的 DNS 服务器), 如果存在, 域名到此解析完成。
  4. 如果本地 DNS 服务器还没找到的话, 它就会向根服务器发出请求, 进行递归查询。
 1### 65.什么是强缓存
 2
 3```ruby
 4强缓存
 5浏览器在加载资源时,会先根据本地缓存资源的 header 中的信息判断是否命中强缓存,如果命中则直接使用缓存中的资源不会再向服务器发送请求。
 6
 7这里的 header 中的信息指的是 expires 和 cahe-control.
 8
 9Expires
10该字段是 http1.0 时的规范,它的值为一个绝对时间的 GMT 格式的时间字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱(本地时间也可以随便更改)。
11
12Cache-Control(优先级高于 Expires)
13Cache-Control 是 http1.1 时出现的 header 信息,主要是利用该字段的 max-age 值来进行判断,它是一个相对时间,例如 Cache-Control:max-age=3600,代表着资源的有效期是 3600 秒。cache-control 除了该字段外,还有下面几个比较常用的设置值:
14
15no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。
16no-store:禁止使用缓存,每一次都要重新请求数据。
17public:可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。
18private:只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。
19Cache-Control 与 Expires 可以在服务端配置同时启用,同时启用的时候 Cache-Control 优先级高。

66.什么是协商缓存

 1当强缓存没有命中的时候浏览器会发送一个请求到服务器服务器根据 header 中的部分信息来判断是否命中缓存如果命中则返回 304告诉浏览器资源未更新可使用本地的缓存
 2
 3这里的 header 中的信息指的是 Last-Modify/If-Modify-Since ETag/If-None-Match.
 4
 5Last-Modify/If-Modify-Since
 6浏览器第一次请求一个资源的时候服务器返回的 header 中会加上 Last-ModifyLast-modify 是一个时间标识该资源的最后修改时间只能精确到秒所以间隔时间小于 1 秒的请求是检测不到文件更改的。)。
 7
 8当浏览器再次请求该资源时request 的请求头中会包含 If-Modify-Since该值为缓存之前返回的 Last-Modify服务器收到 If-Modify-Since根据资源的最后修改时间判断是否命中缓存
 9
10如果命中缓存则返回 304并且不会返回资源内容并且不会返回 Last-Modify
11
12缺点:
13
14短时间内资源发生了改变Last-Modified 并不会发生变化
15
16周期性变化如果这个资源在一个周期内修改回原来的样子了我们认为是可以使用缓存的但是 Last-Modified 可不这样认为, 因此便有了 ETag
17
18ETag/If-None-Match
19Etag 是基于文件内容进行编码的可以保证如果服务器有更新一定会重新请求资源但是编码需要付出额外的开销
20
21 Last-Modify/If-Modify-Since 不同的是Etag/If-None-Match 返回的是一个校验码ETag 可以保证每一个资源是唯一的资源变化都会导致 ETag 变化服务器根据浏览器上送的 If-None-Match 值来判断是否命中缓存
22
23 Last-Modified 不一样的是当服务器返回 304 Not Modified 的响应时由于 ETag 重新生成过response header 中还会把这个 ETag 返回即使这个 ETag 跟之前的没有变化
24
25Last-Modified ETag 是可以一起使用的服务器会优先验证 ETag一致的情况下才会继续比对 Last-Modified最后才决定是否返回 304

67.打开 Chrome 浏览器一个 Tab 页面,至少会出现几个进程?

 1最新的 Chrome 浏览器包括至少四个: 1 个浏览器Browser主进程1 GPU 进程1 个网络NetWork进程多个渲染进程和多个插件进程, 当然还有复杂的情况
 2
 3页面中有 iframe 的话, iframe 会单独在进程中
 4
 5有插件的话插件也会开启进程
 6
 7多个页面属于同一站点并且从 a 打开 b 页面会共用一个渲染进程
 8
 9装了扩展的话扩展也会占用进程
10
11这些进程都可以通过 Chrome 任务管理器来查看

68.即使如今多进程架构,还是会碰到单页面卡死的最终崩溃导致所有页面崩溃的情况,讲一讲你的理解?

 1提供一种情况就是同一站点, 围绕这个展开也行
 2
 3Chrome 的默认策略是每个标签对应一个渲染进程但是如果从一个页面打开了新页面而新页面和当前页面属于同一站点时那么新页面会复用父页面的渲染进程官方把这个默认策略叫 process-per-site-instance
 4
 5更加简单的来说就是如果多个页面符合同一站点这几个页面会分配到一个渲染进程中去, 所以有这样子的一种情况, 一个页面崩溃了会导致同一个站点的其他页面也奔溃这是因为它们使用的是同一个渲染进程
 6
 7有人会问为什么会跑到一个进程里面呢?
 8
 9你想一想呀, 属于同一家的站点比如下面三个:
10
11https:
12https:
13https:
14它们在一个渲染进程中的话它们就会共享 JS 执行环境也就是 A 页面可以直接在 B 页面中执行脚本了, 有些时候就是有这样子的需求嘛

69.TCP 建立连接过程讲一讲,为什么握手需要三次?

 1**三次握手**
 2
 3第一次握手
 4客户端向服务端发送连接请求报文段该报文段的头部中 SYN=1ACK=0seq=x请求发送后客户端便进入 SYN-SENT 状态
 5
 6PS1SYN=1ACK=0 表示该报文段为连接请求报文
 7PS2x 为本次 TCP 通信的字节流的初始序号
 8TCP 规定SYN=1 的报文段不能有数据部分但要消耗掉一个序号
 9第二次握手
10服务端收到连接请求报文段后如果同意连接则会发送一个应答SYN=1ACK=1seq=yack=x+1
11该应答发送完成后便进入 SYN-RCVD 状态
12
13PS1SYN=1ACK=1 表示该报文段为连接同意的应答报文
14PS2seq=y 表示服务端作为发送者时发送字节流的初始序号
15PS3ack=x+1 表示服务端希望下一个数据报发送序号从 x+1 开始的字节
16第三次握手
17当客户端收到连接同意的应答后还要向服务端发送一个确认报文段表示服务端发来的连接同意应答已经成功收到
18该报文段的头部为ACK=1seq=x+1ack=y+1
19客户端发完这个报文段后便进入 ESTABLISHED 状态服务端收到这个应答后也进入 ESTABLISHED 状态此时连接的建立完成
20
21**为什么连接建立需要三次握手而不是两次握手**
22
23在谢希仁著计算机网络第四版中讲三次握手的目的是为了防止已失效的连接请求报文段突然又传送到了服务端因而产生错误”。在另一部经典的计算机网络一书中讲三次握手的目的是为了解决网络中存在延迟的重复分组的问题这两种不用的表述其实阐明的是同一个问题
24
25谢希仁版计算机网络中的例子是这样的,“已失效的连接请求报文段的产生在这样一种情况下client 发出的第一个连接请求报文段并没有丢失而是在某个网络结点长时间的滞留了以致延误到连接释放以后的某个时间才到达 server本来这是一个早已失效的报文段 server 收到此失效的连接请求报文段后就误认为是 client 再次发出的一个新的连接请求于是就向 client 发出确认报文段同意建立连接假设不采用三次握手”,那么只要 server 发出确认新的连接就建立了由于现在 client 并没有发出建立连接的请求因此不会理睬 server 的确认也不会向 server 发送数据 server 却以为新的运输连接已经建立并一直等待 client 发来数据这样server 的很多资源就白白浪费掉了采用三次握手的办法可以防止上述现象发生例如刚才那种情况client 不会向 server 的确认发出确认server 由于收不到确认就知道 client 并没有要求建立连接。”
26
27
28**四次挥手**
29
30第一次挥手
31 A 认为数据发送完成则它需要向 B 发送连接释放请求该请求只有报文头头中携带的主要参数为
32FIN=1seq=u此时A 将进入 FIN-WAIT-1 状态
33
34PS1FIN=1 表示该报文段是一个连接释放请求
35PS2seq=uu-1 A B 发送的最后一个字节的序号
36第二次挥手
37B 收到连接释放请求后会通知相应的应用程序告诉它 A B 这个方向的连接已经释放此时 B 进入 CLOSE-WAIT 状态并向 A 发送连接释放的应答其报文头包含
38ACK=1seq=vack=u+1
39
40PS1ACK=1 TCP 连接请求报文段以外TCP 通信过程中所有数据报的 ACK 都为 1表示应答
41PS2seq=vv-1 B A 发送的最后一个字节的序号
42PS3ack=u+1 表示希望收到从第 u+1 个字节开始的报文段并且已经成功接收了前 u 个字节
43A 收到该应答进入 FIN-WAIT-2 状态等待 B 发送连接释放请求
44
45第二次挥手完成后A B 方向的连接已经释放B 不会再接收数据A 也不会再发送数据 B A 方向的连接仍然存在B 可以继续向 A 发送数据
46
47第三次挥手
48 B A 发完所有数据后 A 发送连接释放请求请求头FIN=1ACK=1seq=wack=u+1B 便进入 LAST-ACK 状态
49
50第四次挥手
51A 收到释放请求后 B 发送确认应答此时 A 进入 TIME-WAIT 状态该状态会持续 2MSL 时间若该时间段内没有 B 的重发请求的话就进入 CLOSED 状态撤销 TCB B 收到确认应答后也便进入 CLOSED 状态撤销 TCB
52
53为什么 A 要先进入 TIME-WAIT 状态等待 2MSL 时间后才进入 CLOSED 状态
54为了保证 B 能收到 A 的确认应答
55 A 发完确认应答后直接进入 CLOSED 状态那么如果该应答丢失B 等待超时后就会重新发送连接释放请求但此时 A 已经关闭了不会作出任何响应因此 B 永远无法正常关闭

70.从输入 URL 到页面展示,这中间发生了什么

 1URL解析
 2  - 首先判断你输入的是一个合法的URL 还是一个待搜索的关键词并且根据你输入的内容进行对应操作
 3DNS 查询
 4  - DNS查询对应ip
 5TCP 连接
 6  - 在确定目标服务器服务器的IP地址后则经历三次握手建立TCP连接
 7HTTP 请求
 8  - 当建立tcp连接之后就可以在这基础上进行通信浏览器发送 http 请求到目标服务器
 9响应请求
10  - 当服务器接收到浏览器的请求之后就会进行逻辑操作处理完成之后返回一个HTTP响应消息
11页面渲染
12  - 当浏览器接收到服务器响应的资源后首先会对资源进行解析
13
14  查看响应头的信息根据不同的指示做对应处理比如重定向存储cookie解压gzip缓存资源等等
15  查看响应头的 Content-Type的值根据不同的资源类型采用不同的解析方式
16  关于页面的渲染过程如下
17
18  解析HTML构建 DOM
19  解析 CSS生成 CSS 规则树
20  合并 DOM 树和 CSS 规则生成 render
21  布局 renderLayout / reflow ),负责各元素尺寸位置的计算
22  绘制 renderpaint ),绘制页面像素信息
23  浏览器会将各层的信息发送给 GPUGPU 会将各层合成composite ),显示在屏幕上

71.什么是 CDN

 1全称 Content Delivery Network, 即内容分发网络
 2
 3摘录一个形象的比喻, 来理解 CDN 是什么
 4
 510 年前还没有火车票代售点一说,12306.cn 更是无从说起那时候火车票还只能在火车站的售票大厅购买而我所在的小县城并不通火车火车票都要去市里的火车站购买而从我家到县城再到市里来回就是 4 个小时车程简直就是浪费生命后来就好了小县城里出现了火车票代售点甚至乡镇上也有了代售点可以直接在代售点购买火车票方便了不少全市人民再也不用在一个点苦逼的排队买票了
 6
 7简单的理解 CDN 就是这些代售点 (缓存服务器) 的承包商, 他为买票者提供了便利, 帮助他们在最近的地方 (最近的 CDN 节点) 用最短的时间 (最短的请求时间) 买到票(拿到资源), 这样去火车站售票大厅排队的人也就少了也就减轻了售票大厅的压力(起到分流作用, 减轻服务器负载压力)。
 8
 9用户在浏览网站的时候CDN 会选择一个离用户最近的 CDN 边缘节点来响应用户的请求这样海南移动用户的请求就不会千里迢迢跑到北京电信机房的服务器假设源站部署在北京电信机房上了
10
11CDN 缓存
12关于 CDN 缓存, 在浏览器本地缓存失效后, 浏览器会向 CDN 边缘节点发起请求类似浏览器缓存, CDN 边缘节点也存在着一套缓存机制CDN 边缘节点缓存策略因服务商不同而不同但一般都会遵循 http 标准协议通过 http 响应头中的
13
14Cache-control: max-age   //后面会提到
15的字段来设置 CDN 边缘节点数据缓存时间
16
17当浏览器向 CDN 节点请求数据时CDN 节点会判断缓存数据是否过期若缓存数据并没有过期则直接将缓存数据返回给客户端否则CDN 节点就会向服务器发出回源请求从服务器拉取最新数据更新本地缓存并将最新数据返回给客户端CDN 服务商一般会提供基于文件后缀目录多个维度来指定 CDN 缓存时间为用户提供更精细化的缓存管理
18
19CDN 优势
20CDN 节点解决了跨运营商和跨地域访问的问题访问延时大大降低
21大部分请求在 CDN 边缘节点完成CDN 起到了分流作用减轻了源服务器的负载

72. 说说HTTP与HTTPS的区别

 1HTTPS是HTTP协议的安全版本HTTP协议的数据传输是明文的是不安全的HTTPS使用了SSL/TLS协议进行了加密处理相对更安全
 2HTTP HTTPS 使用连接方式不同默认端口也不一样HTTP是80HTTPS是443
 3HTTPS 由于需要设计加密以及多次握手性能方面不如 HTTP
 4HTTPS需要SSLSSL 证书需要钱功能越强大的证书费用越高

73.webpack文件指纹策略:hash chunkhash contenthash

 1hash策略是以项目为单位的项目内容改变则会生成新的hash内容不变则hash不变
 2
 3chunkhash策略是以chunk为单位的当一个文件内容改变则整个相应的chunk组模块的hash回发生改变
 4
 5contenthash策略是以自身内容为单为的
 6
 7推荐使用csscontenthash
 8
 9jschunkhash

74.说说webpack的构建流程

 

 

 

 

 

 

 11.初始化参数解析webpack配置参数合并shell传入和webpack.config.js文件配置的参数形成最后的配置结果
 22.开始编译上一步得到的参数初始化compiler对象注册所有配置的插件插件监听webpack构建生命周期的事件节点做出相应的反应执行对象的 run 方法开始执行编译
 33.确定入口从配置的entry入口开始解析文件构建AST语法树找出依赖递归下去
 44.编译模块递归中根据文件类型和loader配置调用所有配置的loader对文件进行转换再找出该模块依赖的模块再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
 55.完成模块编译在经过第4步使Loader 翻译完所有模块后得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
 66.输出资源根据⼊⼝和模块之间的依赖关系组装成个个包含多个模块的 Chunk再把每个 Chunk 转换成个单独的件加到输出列表这步是可以修改输出内容的最后机会
 77.输出完成在确定好输出内容后根据配置确定输出的路径和件名件内容写件系统

75.说说Loader和Plugin的区别?编写Loader,Plugin的思路

一、区别

  • loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
  • plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事

从整个运行时机上来看,如下图所示:

 

 

 

 

 

 

可以看到,两者在运行时机上的区别:

  • loader 运行在打包文件之前
  • plugins 在整个编译周期都起作用

Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过Webpack提供的 API改变输出结果

对于loader,实质是一个转换器,将A文件进行编译形成B文件,操作的是文件,比如将A.scssA.less转变为B.css,单纯的文件转换过程

二、编写loader

在编写 loader 前,我们首先需要了解 loader 的本质

其本质为函数,函数中的 this 作为上下文会被 webpack 填充,因此我们不能将 loader设为一个箭头函数

函数接受一个参数,为 webpack 传递给 loader 的文件源内容

函数中 this 是由 webpack 提供的对象,能够获取当前 loader 所需要的各种信息

函数中有异步操作或同步操作,异步操作通过 this.callback 返回,返回值要求为 string 或者 Buffer

代码如下所示:

 1module.exports = function(source) {
 2    const content = doSomeThing2JsString(source);
 3    
 4    
 5    const options = this.query;
 6    
 7    
 8    console.log('this.context');
 9    
10    
11     * this.callback 参数
12     * errorError | null loader 出错时向外抛出一个 error
13     * contentString | Buffer经过 loader 编译后需要导出的内容
14     * sourceMap为方便调试生成的编译后内容的 source map
15     * ast本次编译生成的 AST 静态语法树之后执行的 loader 可以直接使用这个 AST进而省去重复生成 AST 的过程
16     */
17    this.callback(null, content); 
18    return content; 
19}

一般在编写loader的过程中,保持功能单一,避免做多种功能

less文件转换成 css文件也不是一步到位,而是 less-loadercss-loaderstyle-loader几个 loader的链式调用才能完成转换

三、编写plugin

由于webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务

在之前也了解过,webpack编译会创建两个核心对象:

  • compiler:包含了 webpack 环境的所有的配置信息,包括 options,loader 和 plugin,和 webpack 整个生命周期相关的钩子
  • compilation:作为 plugin 内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化,一次新的 Compilation 将被创建

如果自己要实现plugin,也需要遵循一定的规范:

  • 插件必须是一个函数或者是一个包含 apply 方法的对象,这样才能访问compiler实例
  • 传给每个插件的 compilercompilation 对象都是同一个引用,因此不建议修改
  • 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住

实现plugin的模板如下:

 1class MyPlugin {
 2    
 3  apply (compiler) {
 4    
 5    compiler.hooks.emit.tap('MyPlugin', compilation => {
 6        
 7        console.log(compilation);
 8        
 9        
10    })
11  }
12}

emit 事件发生时,代表源文件的转换和组装已经完成,可以读取到最终将输出的资源、代码块、模块及其依赖,并且可以修改输出资源的内容

76.提高webpack的构建速度

 1优化 loader 配置
 2合理使用 resolve.extensions
 3优化 resolve.modules
 4优化 resolve.alias
 5使用 DLLPlugin 插件
 6使用 cache-loader
 7terser 启动多线程
 8合理使用 sourceMap

77.webpack 热更新是怎么做到的

 1通过webpack-dev-server创建两个服务器提供静态资源的服务express和Socket服务
 2express server 负责直接提供静态资源的服务打包后的资源直接被浏览器请求和解析
 3socket server 是一个 websocket 的长连接双方可以通信
 4 socket server 监听到对应的模块发生变化时会生成两个文件.jsonmanifest文件.js文件update chunk
 5通过长连接socket server 可以直接将这两个文件主动发送给客户端浏览器
 6浏览器拿到两个新的文件后通过HMR runtime机制加载这两个文件并且针对修改的模块进行更新

78.webpack中常见的Loader

 1style-loader: 将css添加到DOM的内联样式标签style里
 2css-loader :允许将css文件通过require的方式引入并返回css代码
 3less-loader: 处理less
 4sass-loader: 处理sass
 5postcss-loader: 用postcss来处理CSS
 6autoprefixer-loader: 处理CSS3属性前缀已被弃用建议直接使用postcss
 7file-loader: 分发文件到output目录并返回相对路径
 8url-loader: 和file-loader类似但是当文件小于设定的limit时可以返回一个Data Url
 9html-minify-loader: 压缩HTML
10babel-loader :用babel来转换ES6文件到ES

79.webpack中常见的Plugin

 

 

 

 

 

 

80.Git常用的命令有哪些

 1**基本操作**
 2  
 3git init 初始化仓库默认为 master 分支
 4
 5git add . 提交全部文件修改到缓存区
 6
 7git add <具体某个文件路径+全名> 提交某些文件到缓存区
 8
 9git diff 查看当前代码 add后 add 哪些内容
10
11git diff 
12
13git status 查看当前分支状态
14
15git pull <远程仓库名> <远程分支名> 拉取远程仓库的分支与本地当前分支合并
16
17git pull <远程仓库名> <远程分支名>:<本地分支名> 拉取远程仓库的分支与本地某个分支合并
18
19git commit -m "<注释>" 提交代码到本地仓库,并写提交注释
20
21git commit -v 提交时显示所有diff信息
22
23git commit 
24
25**提交规则**
26
27feat: 新特性,添加功能
28
29fix: 修改 bug
30
31refactor: 代码重构
32
33docs: 文档修改
34
35style: 代码格式修改, 注意不是 css 修改
36
37test: 测试用例修改
38
39chore: 其他修改, 比如构建流程, 依赖管理
40
41**分支操作**
42  
43git branch 查看本地所有分支
44
45git branch -r 查看远程所有分支
46
47git branch -a 查看本地和远程所有分支
48
49git merge <分支名> 合并分支
50
51git merge 
52
53git branch <新分支名> 基于当前分支,新建一个分支
54
55git checkout 
56
57git branch -D <分支名> 删除本地某个分支
58
59git push <远程库名> :<分支名> 删除远程某个分支
60
61git branch <新分支名称> <提交ID> 从提交历史恢复某个删掉的某个分支
62
63git branch -m <原分支名> <新分支名> 分支更名
64
65git checkout <分支名> 切换到本地某个分支
66
67git checkout <远程库名>/<分支名> 切换到线上某个分支
68
69git checkout -b <新分支名> 把基于当前分支新建分支,并切换为这个分支
70
71**远程操作**
72
73git fetch [remote] 下载远程仓库的所有变动
74
75git remote -v 显示所有远程仓库
76
77git pull [remote] [branch] 拉取远程仓库的分支与本地当前分支合并
78
79git fetch 获取线上最新版信息记录,不合并
80
81git push [remote] [branch] 上传本地指定分支到远程仓库
82
83git push [remote] 
84
85git push [remote] 
86
87**撤销操作**
88
89git checkout [file] 恢复暂存区的指定文件到工作区
90
91git checkout [commit] [file] 恢复某个commit的指定文件到暂存区和工作区
92
93git checkout . 恢复暂存区的所有文件到工作区
94
95git reset [commit] 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
96
97git reset 
98
99git reset [file] 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
100
101git revert [commit] 后者的所有变化都将被前者抵消,并且应用到当前分支
102
103reset:真实硬性回滚,目标版本后面的提交记录全部丢失了
104
105revert:同样回滚,这个回滚操作相当于一个提价,目标版本后面的提交记录也全部都有
106
107**存储操作**
108
109git stash 暂时将未提交的变化移除
110
111git stash pop 取出储藏中最后存入的工作状态进行恢复,会删除储藏
112
113git stash list 查看所有储藏中的工作
114
115git stash apply <储藏的名称> 取出储藏中对应的工作状态进行恢复,不会删除储藏
116
117git stash clear 清空所有储藏中的工作
118
119git stash drop <储藏的名称> 删除对应的某个储藏

81.标准的CSS盒子模型及其和低版本的IE盒子模型的区别?

标准(W3C)盒子模型:width = 内容宽度(content) + border + padding + margin

低版本IE盒子模型: width = 内容宽度(content + border + padding)+ margin

图片展示:

 

 

 

 

 

 

 

 

 

 

 

 

区别: 标准盒子模型盒子的heightwidthcontent(内容)的宽高,而IE盒子模型盒子的宽高则包括content+padding+border部分。

82.几种解决IE6存在的bug的方法

 1-`float`引起的双边距的问题使用`display`解决
 2-`float`引起的3像素问题使用`display: inline -3px`;
 3- 使用正确的书写顺序`link visited hover active`解决超链接`hover`点击失效问题
 4- 对于`IE``z-index`问题通过给父元素增加`position: relative`解决
 5- 使用`!important`解决`Min-height`最小高度问题
 6- 使用`iframe`解决`select``IE6`下的覆盖问题
 7- 使用`over: hidden`, `zoom: 0.08`, `line-height: 1px`解决定义1px左右的容器宽度问题

83.CSS选择符有哪些?哪些属性可以继承?

 1常见的选择符有一下
 2
 3`id`选择器`#content`),类选择器`.content`), 标签选择器`div`, `p`, `span`), 相邻选择器`h1+p`), 子选择器`ul>li`), 后代选择器`li a`), 通配符选择器`*`), 属性选择器`a[rel = "external"]`), 伪类选择器`a:hover`, `li:nth-child`
 4
 5可继承的样式属性`font-size`, `font-family`, `color`, `ul`, `li`, `dl`, `dd`, `dt`;
 6
 7不可继承的样式属性`border`, `padding`, `margin`, `width`, `height`

84.position的值relative和absolute定位原点?

 1首先使用`position`的时候应该记住一个规律是**子绝父相**’。
 2
 3`relative`相对定位): 生成相对定位的元素定位原点是元素本身所在的位置
 4
 5`absolute`绝对定位):生成绝对定位的元素定位原点是离自己这一级元素最近的一级`position`设置为`absolute`或者`relative`的父元素的左上角为原点的
 6
 7`fixed`老IE不支持):生成绝对定位的元素相对于浏览器窗口进行定位
 8
 9`static`默认值没有定位元素出现在正常的流中忽略 `top`, `bottom`, `left`, `right``z-index` 声明)。
10
11`inherit`规定从父元素继承 `position` 属性的值
12
13**更新一个属性**
14
15`sticky`: (新增元素目前兼容性可能不是那么的好),可以设置 position:sticky 同时给一个 (top,bottom,right,left) 之一即可
16
17**注意**
18
19- 使用`sticky`必须指定topbottomleftright4个值之一不然只会处于相对定位
20- `sticky`只在其父元素内其效果且保证父元素的高度要高于`sticky`的高度
21- 父元素不能`overflow:hidden`或者`overflow:auto`等属性

85.CSS3有哪些新特性?

 1关于`CSS`新增的特性有以下
 2
 3- 选择器;
 4- 圆角`(border-raduis)`;
 5- 多列布局`(multi-column layout)`;
 6- 阴影`(shadow)`和反射`(reflect)`;
 7- 文字特效`(text-shadow)`;
 8- 文字渲染`(text-decoration`);
 9- 线性渐变`(gradient)`;
10- 旋转`(rotate`/缩放`(scale)`/倾斜`(skew)`/移动`(translate)`;
11- 媒体查询`(@media)`;
12- `RGBA`和透明度 ;
13- `@font-face`属性;
14- 多背景图 ;
15- 盒子大小;
16- 语音;

86.用纯CSS创建一个三角形的原理是什么?

实现步骤: 1.首先保证元素是块级元素;2.设置元素的边框;3.不需要显示的边框使用透明色。

 1css: 
 2    * {margin: 0; padding: 0;}
 3    .content {
 4        width:0;
 5        height:0;
 6        margin:0 auto;
 7        border:50px solid transparent;
 8        border-top: 50px solid pink;
 9    }
10
11html: 
12    <div class="content"></div>

87.什么是响应式设计?响应式设计的基本原理是什么?如何兼容低版本的IE?

响应式网站设计(Responsive Web design)是一个网站能够兼容多个终端,而不是为每一个终端做一个特定的版本。

关于原理: 基本原理是通过媒体查询(@media)查询检测不同的设备屏幕尺寸做处理。

关于兼容: 页面头部必须有mate声明的viewport

 1<meta name="’viewport’" content="”width=device-width," initial-scale="1." maximum-scale="1,user-scalable=no”"/>

88.CSS优化、提高性能的方法有哪些?

 1- 多个`css`可合并并尽量减少`http`请求
 2- 属性值为0时不加单位
 3-`css`文件放在页面最上面
 4- 避免后代选择符过度约束和链式选择符
 5- 使用紧凑的语法
 6- 避免不必要的重复
 7- 使用语义化命名便于维护
 8- 尽量少的使用`!impotrant`可以选择其他选择器
 9- 精简规则尽可能合并不同类的重复规则
10- 遵守盒子模型规则

89.display:inline-block 什么时候会显示间隙?

 1- 有空格时候会有间隙可以删除空格解决
 2- `margin`正值的时候可以让`margin`使用负值解决
 3- 使用`font-size`时候可通过设置`font-size:0``letter-spacing``word-spacing`解决

90. 什么是外边距重叠? 重叠的结果是什么?

 1首先外边距重叠就是 `margin-collapse`相邻的两个盒子可能是兄弟关系也可能是祖先关系的外边距可以结合成一个单独的外边距这种合并外边距的方式被称为折叠结合而成的外边距称为折叠外边距
 2
 3折叠结果遵循下列计算原则
 4
 5- 两个相邻的外面边距是正数时折叠结果就是他们之中的较大值
 6- 两个相邻的外边距都是负数时折叠结果是两者绝对值的较大值
 7- 两个外边距一正一负时折叠结果是两者的相加的和

91.有哪几种隐藏元素的方法?

 1- `visibility: hidden;` 这个属性只是简单的隐藏某个元素但是元素占用的空间任然存在
 2- `opacity: 0;``CSS3`属性设置0可以使一个元素完全透明
 3- `position: absolute;` 设置一个很大的 left 负值定位使元素定位在可见区域之外
 4- `display: none;` 元素会变得不可见并且不会再占用文档的空间
 5- `transform: scale(0);` 将一个元素设置为缩放无限小元素将不可见元素原来所在的位置将被保留
 6- `<div hidden="hidden">` `HTML5`属性,效果和`display:none;`相同但这个属性用于记录一个元素的状态
 7- `height: 0;` 将元素高度设为 0并消除边框
 8- `filter: blur(0);` `CSS3`属性括号内的数值越大图像高斯模糊的程度越大到达一定程度可使图像消失`(此处感谢小伙伴支持)`

92.对BFC规范(块级格式化上下文:block formatting context)的理解

 1`BFC`规定了内部的`Block Box`如何布局一个页面是由很多个`Box`组成的元素的类型和`display`属性决定了这个`Box`的类型不同类型的`box`会参与不同的`Formatting Context`决定如何渲染文档的容器),因此`Box`内的元素会以不用的方式渲染也是就是说`BFC`内部的元素和外部的元素不会相互影响
 2
 3定位方案
 4
 5- 内部的`box`会在垂直方向上一个接一个的放置
 6- `box`垂直方向的距离由`margin`决定属于同一个`BFC`的两个相邻`Box``margin`会发生重叠
 7- 每个元素`margin box`的左边与包含块`border box`的左边相接触
 8- `BFC`的区域不会与float box重叠
 9- `BFC`是页面上的一个隔离的独立容器容器里面的元素不会影响到外面的元素
10- 计算`BFC`的高度时浮动元素也会参与计算
11
12满足下列条件之一就可以出发BFC
13
14- 根元素变化`html`
15- `float`的值不为`none`默认);
16- `overflow`的值不为`visible`默认);
17- `display`的值为`inline-block`, `tabke-cell``table-caption`
18- `position`的值为`absolute``fixed`;

93.经常遇到的浏览器的兼容性有哪些?原因,解决方法是什么,常用hack的技巧 ?

 11)、问题`png24`位的图片在`ie`浏览器上出现背景解决做成`png8`
 2
 32)、问题浏览器默认的`margin``padding`不同解决添加一个全局的`*{ margin: 0; padding: 0;}`
 4
 53)、问题`IE`,可以使用获取常规属性的方法来获取自定义属性,也可以使用`getAttribute()`获取自定义属性`Firefox`,只能使用`getAttribute()`获取自定义属性解决统一通过`getAttribute()`获取自定义属性
 6
 74)、问题`IE`,`event`对象有`x`,`y`属性,但是没有`pageX`,`pageY`属性`Firefox`,`event`对象有`pageX`,`pageY`属性,但是没有`x`,`y`属性解决使用`mX(mX = event.x ? event.x : event.pageX;)`来代替`IE`下的`event.x`或者`Firefox`下的`event.pageX`

94. 怎么让Chrome支持小于12px 的文字?

 1.shrink {
 2    -webkit-transform: scale(0.8);
 3    -o-transform: scale(1);
 4    display: inilne-block;
 5}

95. :link、:visited、:hover、:active的执行顺序是怎么样的?

 1`L-V-H-A``l(link)ov(visited)e h(hover)a(active)te`即用喜欢和讨厌两个词来概括

96. CSS属性overflow属性定义溢出元素内容区的内容会如何处理?

 1- 参数是`scroll`的时候一定会出滚动条
 2- 参数是`auto`的时候子元素内容大于父元素时出现滚动条
 3- 参数是`visible`的时候溢出的内容出现在父元素之外
 4- 参数是`hidden`的时候溢出隐藏

97. css样式引入方式的优缺点对比

 1内嵌样式优点方便书写权重高缺点没有做到结构和样式分离
 2内联样式优点结构样式相分离缺点没有彻底分离
 3外联样式优点完全实现了结构和样式相分离缺点需要引入才能使用

98. position 跟 display、overflow、float 这些特性相互叠加后会怎么样?

 1- `display`属性规定元素应该生成的框的类型
 2- `position`属性规定元素的定位类型
 3- `float`属性是一种布局方式定义元素往哪个方向浮动
 4
 5**叠加结果**有点类似于优先机制`position`的值-- `absolute/fixed`优先级最高有他们在时`float`不起作用`display`值需要调整`float`或者`absolute`定位的元素只能是块元素或者表格

99. 什么是回流(重排)和重绘以及其区别?

 1- 回流重排),`reflow`:`render tree`中的一部分或全部因为元素的规模尺寸布局隐藏等改变时而需要重新构建
 2- 重绘`(repaint`):`render tree`中的一些元素需要更新属性而这些属性只影响元素的外观风格而不会影响布局时称其为**重绘**例如颜色改变等
 3
 4- 增加或者删除可见的`dom`元素
 5- 元素的位置发生了改变
 6- 元素的尺寸发生了改变例如边距宽高等几何属性改变
 7- 内容改变例如图片大小字体大小改变等
 8- 页面渲染初始化
 9- 浏览器窗口尺寸改变例如`resize`事件发生时等
10- 重排回流一定会引发重绘**
11

100.说说 px、em、rem的区别及使用场景

 1**三者的区别**
 2
 3- px是固定的像素一旦设置了就无法因为适应页面大小而改变
 4- em和rem相对于px更具有灵活性他们是相对长度单位其长度不是固定的更适用于响应式布局
 5- em是相对于其父元素来设置字体大小这样就会存在一个问题进行任何元素设置都有可能需要知道他父元素的大小而rem是相对于根元素这样就意味着只需要在根元素确定一个参考值
 6
 7**使用场景**
 8
 9- 对于只需要适配少部分移动设备且分辨率对页面影响不大的使用px即可
10- 对于需要适配各种移动设备使用rem例如需要适配iPhone和iPad等分辨率差别比较挺大的设备
个人笔记记录 2021 ~ 2025