前端面试 - css , 布局 ,js ,vue

:html,css相关

1. 介绍一下标准盒子模型和低版本IE盒子模型

标准盒子模型   宽度 = content+border+padding+margin
低版本IE盒子模型    宽度 = 内容宽度(content+border+padding)+margin
也就是说区别在于, 低版本IE盒子模型的内容宽度直接包含了border和padding
 

2,关于box-sizing

box-sizing:用来控制元素盒子模型的解析模式,默认是content-box。

content-box:为标准的盒子模型

borer-box:为IE传统盒子模型,content包含了border+padding+content的宽/高

3,css的选择器有哪些?css有哪些属性可以继承?css的优先级顺序?

css的选择器:id选择器,类选择器(class),后代选择器(ul li),标签选择器(p a),相邻选择器(div+span),子选择器(ul >li),伪类选择器(a:hover,div:before,div:after),通配符选择器(*),属性选择器(input[type='text'])

css可以继承的属性:font-size,color,font-family

css不可以继承的属性:width,height,padding,margin,border

优先级:!important  >  style内联样式 >  id >  class >  其他

4,css3新增的伪类有哪些?

:first-of-type  选中属于其父元素的特定类型的第一个元素

:last-of-type  选中属于其父元素的特定类型的最后一个元素

:nth-child(2)  选择属于其父元素下面的第二个子元素

:only-of-type  选中属于其父元素下的唯一特定类型元素

:only-child  选中属于其父元素下的唯一子元素

:disabled  禁止选中

:checked  多选单选的选中状态

5,如何左右居中一个div?左右居中一个浮动的元素? 左右居中一个绝对定位的元素?

div

width:100px;
height:200px;
margin:0 auto;

浮动的元素

width:100px;
height:200px;
position:absolute;
top:50%;
left:50%;
margin-left:-50px;
margin-top:-100px;

绝对定位的元素

position:absolute;
left:0;
right:0;
margin:0 auto;

6,position有哪些值,有什么作用?

static 默认值

relative 相对定位,还占有位置,在原位置的基础上定位

absolute 绝对定位,向上找到唯一一个不是static的元素,相对此元素定位

fixed  固定定位,相对可视窗口定位

sticky  粘性布局

注意:sticky使用有几点需要注意:

1。父元素宽度/高度要大于设置sticky的元素的宽度/高度;

2。sticky的属性,必须指定top、bottom、left、right4个值之一,否则只会处于相对定位

3。sticky只在其父元素内生效

4。父元素不可以设置overflow:hidden 或者overfloe:auto 属性

7,请解释一下CSS3的flexbox(弹性盒布局模型),以及适用场景?

传送门

8,用css设置一个三角形

设置div的宽高为0,配合border和transparent属性

width:0;height:0;
border-bottom:100px solid red;
border-left:50px solid transparent;
border-right:50px solid transparent;

9,display:none和visibility:hidden的区别?

display:none:元素在页面在不再显示,没有其对应的位置(回流+重绘)

visibility:hidden:元素隐藏,但是页面扔保留其位置(重绘)

10,对BFC规范的理解?

传送门

11,清楚浮动的方法

1.父元素设置高度

2,父元素设置overflow:hidden或者overflow:auto

3,浮动元素最后加一个空div标签,设置clear:both

4,使用伪类元素atter

.box:after{
    content:'';
    display:block;
    height:0;
    clear:both;
    visibility:hidden;
}
.box{
    *zoom:1; /*ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行*/
}

 5,使用:after和:before双伪类元素清除浮动

.box:after,.box:before{
    content:'';
    display:table;
}
.box:after{
    clear:both;
}
.box{
    *zoom:1;
}

12.浏览器的渲染机制

https://www.jianshu.com/p/05eb1b17b298

需要注意的是:

  • 当我们浏览器获得HTML文件后,会自上而下的加载,并在加载过程中进行解析和渲染。
  • 加载说的就是获取资源文件的过程,如果在加载过程中遇到外部CSS文件和图片,浏览器会另外发送一个请求,去获取CSS文件和相应的图片,这个请求是异步的,并不会影响HTML文件的加载。
  • 但是如果遇到Javascript文件,HTML文件会挂起渲染的进程,等待JavaScript文件加载完毕后,再继续进行渲染。
    为什么HTML需要等待JavaScript呢?因为JavaScript可能会修改DOM,导致后续HTML资源白白加载,所以HTML必须等待JavaScript文件加载完毕后,再继续渲染,这也就是为什么JavaScript文件在写在底部body标签前的原因。


 

浏览器整个流程如上图所示:

  1. 当用户输入一个URL时,浏览器就会向服务器发出一个请求,请求URL对应的资源
  2. 接受到服务器的响应内容后,浏览器的HTML解析器,会将HTML文件解析成一棵DOM树,DOM树的构建是一个深度遍历的过程,当前节点的所有子节点都构建完成以后,才会去构建当前节点的下一个兄弟节点。
  3. 将CSS解析成CSSOM树(CSS Rule Tree)
  4. 根据DOM树和CSSOM树,来构建Render Tree(渲染树),注意渲染树,并不等于DOM树,因为一些像head或display:none的东西,就没有必要放在渲染树中了。
  5. 有了Render Tree,浏览器已经能知道网页中有哪些节点,各个节点的CSS定义,以及它们的从属关系,下一步操作就是Layout,顾名思义,就是计算出每个节点在屏幕中的位置。
  6. Layout后,浏览器已经知道哪些节点要显示,每个节点的CSS属性是什么,每个节点在屏幕中的位置是哪里,就进入了最后一步painting,按照算出来的规则,通过显卡,把内容画到屏幕上。

13,footer位置的自动适配(主内容不足一屏时显示在最底部,超出一屏时跟随主内容显示)

html

<div class="page"></div>
<div class="footer"></div>

css

.page{min-height: 100vh;padding-bottom: 50px;}
.footer{height:50px;background: #ccc;margin-top: -50px;}


主要注意的是:主内容的min-height,footer的height和margin-top
 

二:js相关

1,js数据类型

JS的数据类型有8种。

在ES5的时候,我们认知的数据类型确实是 6种:Number、String、Boolean、undefined、object、Null。

ES6 中新增了一种 Symbol 。这种类型的对象永不相等,即始创建的时候传入相同的值,可以解决属性名冲突的问题,做为标记。

谷歌67版本中还出现了一种 bigInt。是指安全存储、操作大整数。(但是很多人不把这个做为一个类型)。

JS数据类型:JS 的数据类型有几种?

     8种。Number、String、Boolean、Null、undefined、object、symbol、bigInt。

JS数据类型:Object 中包含了哪几种类型?

      其中包含了Data、function、Array等。这三种是常规用的。

JS数据类型:JS的基本类型和引用类型有哪些呢?

    基本类型(单类型):除Object。 String、Number、boolean、null、undefined。

    引用类型:object。里面包含的 function、Array、Date。

  2, JS 中 typeof 输出分别是什么?

  { } 、[ ] 输出 object。

    console.log( ) 输出 function。

    注意一点:NaN 是 Number 中的一种,非Number 。

String

String

字符串

Number

Number

数据类型

Boolean

Boolean

布尔型

Undefined

Undefined

没有初始化、定义的值

Null

Object

不存在的对象

NaN

Number

Number 中的特殊数值

Object

Function

 注)用 isNaN() 检测是否是非数值型。

NaN == NaN 为什么是 false。其实 js 规定的NaN 不等于NaN。

参考文章:https://blog.csdn.net/u013592575/article/details/95087953

3,闭包

参考文章:1.https://blog.csdn.net/weixin_43586120/article/details/89456183
                 2.
https://blog.csdn.net/dovlie/article/details/76339244?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.base
                  3.https://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

1、概念

闭包函数:声明在一个函数中的函数,叫做闭包函数。

闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
 

2、特点

  让外部访问函数内部变量成为可能;

  局部变量会常驻在内存中;

  可以避免使用全局变量,防止全局变量污染;

  会造成内存泄漏(有一块内存空间被长期占用,而不被释放)

最后总结一下闭包的好处与坏处

好处

①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突

②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)

③匿名自执行函数可以减少内存消耗

坏处

①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;

②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

4, 原型、原型链

参考文章:https://www.cnblogs.com/loveyaxin/p/11151586.html

原型: 对象中固有的__proto__属性,该属性指向对象的prototype原型属性。

原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype所以这就是我们新建的对象为什么能够使用toString()等方法的原因。

特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

传送门

js延迟加载的方法

传送门

js绑定事件的方式

传送门

js事件委托

传送门

浏览器的垃圾回收机制

找出那些不再继续使用的变 量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间), 周期性地执行这一操作。

(1).标记清除

先所有都加上标记,再把环境中引用到的变量去除标记。剩下的就是没用的了

(2).引用计数

跟踪记录每 个值被引用的次数。清除引用次数为0的变量 ⚠️会有循环引用问题 。循环引用如果大量存在就会导致内存泄露。

传送门

浏览器的内存泄漏

传送门

浏览器禁止缓存的方法

神拷贝和浅拷贝

传送门

es6的语法

传送门

传送门2

三,vue相关

1,如何理解Vue 的单向数据流

单向数据流:父级 prop 的更新会向下流动到子组件中,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值

2,Vue2.0 响应式数据的原理

整体思路是数据劫持+观察者模式

对象内部通过 defineReactive 方法,使用 Object.defineProperty 将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。当页面使用对应属性时,每个属性都拥有自己的 dep 属性,存放他所依赖的 watcher(依赖收集),当属性变化后会通知自己对应的 watcher 去更新(派发更新)。

相关代码如下

class Observer {
  // 观测值
  constructor(value) {
    this.walk(value);
  }
  walk(data) {
    // 对象上的所有属性依次进行观测
    let keys = Object.keys(data);
    for (let i = 0; i < keys.length; i++) {
      let key = keys[i];
      let value = data[key];
      defineReactive(data, key, value);
    }
  }
}
// Object.defineProperty数据劫持核心 兼容性在ie9以及以上
function defineReactive(data, key, value) {
  observe(value); // 递归关键
  // --如果value还是一个对象会继续走一遍odefineReactive 层层遍历一直到value不是对象才停止
  //   思考?如果Vue数据嵌套层级过深 >>性能会受影响
  Object.defineProperty(data, key, {
    get() {
      console.log("获取值");

      //需要做依赖收集过程 这里代码没写出来
      return value;
    },
    set(newValue) {
      if (newValue === value) return;
      console.log("设置值");
      //需要做派发更新过程 这里代码没写出来
      value = newValue;
    },
  });
}
export function observe(value) {
  // 如果传过来的是对象或者数组 进行属性劫持
  if (
    Object.prototype.toString.call(value) === "[object Object]" ||
    Array.isArray(value)
  ) {
    return new Observer(value);
  }
}

参考链接:https://juejin.cn/post/6961222829979697165#heading-14

响应式数据原理详解传送门

3,Vue 如何检测数组变化

数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP 切片思想)

所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新

相关代码如下

// src/obserber/array.js
// 先保留数组原型
const arrayProto = Array.prototype;
// 然后将arrayMethods继承自数组原型
// 这里是面向切片编程思想(AOP)--不破坏封装的前提下,动态的扩展功能
export const arrayMethods = Object.create(arrayProto);
let methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "reverse",
  "sort",
];
methodsToPatch.forEach((method) => {
  arrayMethods[method] = function (...args) {
    //   这里保留原型方法的执行结果
    const result = arrayProto[method].apply(this, args);
    // 这句话是关键
    // this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4)  this就是a  ob就是a.__ob__ 这个属性就是上段代码增加的 代表的是该数据已经被响应式观察过了指向Observer实例
    const ob = this.__ob__;

    // 这里的标志就是代表数组有新增操作
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
      default:
        break;
    }
    // 如果有新增的元素 inserted是一个数组 调用Observer实例的observeArray对数组每一项进行观测
    if (inserted) ob.observeArray(inserted);
    // 之后咱们还可以在这里检测到数组改变了之后从而触发视图更新的操作--后续源码会揭晓
    return result;
  };
});

参考链接:https://juejin.cn/post/6961222829979697165#heading-14

响应式数据原理详解传送门

4, Vue3.0 和 2.0 的响应式原理区别

Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。

相关代码如下

import { mutableHandlers } from "./baseHandlers"; // 代理相关逻辑
import { isObject } from "./util"; // 工具方法

export function reactive(target) {
  // 根据不同参数创建不同响应式对象
  return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {
  if (!isObject(target)) {
    return target;
  }
  const observed = new Proxy(target, baseHandler);
  return observed;
}

const get = createGetter();
const set = createSetter();

function createGetter() {
  return function get(target, key, receiver) {
    // 对获取的值进行放射
    const res = Reflect.get(target, key, receiver);
    console.log("属性获取", key);
    if (isObject(res)) {
      // 如果获取的值是对象类型,则返回当前对象的代理对象
      return reactive(res);
    }
    return res;
  };
}
function createSetter() {
  return function set(target, key, value, receiver) {
    const oldValue = target[key];
    const hadKey = hasOwn(target, key);
    const result = Reflect.set(target, key, value, receiver);
    if (!hadKey) {
      console.log("属性新增", key, value);
    } else if (hasChanged(value, oldValue)) {
      console.log("属性值被修改", key, value);
    }
    return result;
  };
}
export const mutableHandlers = {
  get, // 当获取属性时调用此方法
  set, // 当修改属性时调用此方法
};

5,  Vue 的父子组件生命周期钩子函数执行顺序

  • 加载渲染过程

父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted

  • 子组件更新过程

父 beforeUpdate->子 beforeUpdate->子 updated->父 updated

  • 父组件更新过程

父 beforeUpdate->父 updated

  • 销毁过程

父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed

6,v-model的实现原理

v-model 只是语法糖而已

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

在普通标签上

    <input v-model="sth" />  //这一行等于下一行
    <input v-bind:value="sth" v-on:input="sth = $event.target.value" />

v-model底层原理实际上是分别利用了v-bind用来绑定value的值,用v-on去绑定input标准事件,这是事件用来监听当输入域内容发生变化的时候来执行一些事情。具体做的事情就是通过$event这个事件对象获取到最新的输入域的值,然后把最新的值赋值给旧的值,从而进行数据的更新。这样的话就完成了双向数据绑定。
 

7, v-for 为什么要加 key

key 特殊 attribute 主要用做 Vue 的虚拟 DOM 算法的提示,以在比对新旧节点组时辨识 VNodes。如果不使用 key,Vue 会使用一种算法来最小化元素的移动并且尽可能尝试就地修改/复用相同类型元素。而使用 key 时,它会基于 key 的顺序变化重新排列元素,并且 key 不再存在的元素将始终被移除/销毁。

更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。

更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快

它也可以用于强制替换元素/组件而不是重复使用它。当你遇到如下场景时它可能会很有用:

  • 完整地触发组件的生命周期钩子
  • 触发过渡
<transition>
  <span :key="text">{
   
   { text }}</span>
</transition>

当 text 发生改变时,<span> 总是会被替换而不是被修改,因此会触发过渡。 

8,vue-router 路由钩子函数是什么 执行顺序是什么

何为路由守卫?路由守卫有点类似于ajax的请求拦截器,就是请求发送之前先给你拦截住做一些事情之后再去发送请求,同样这里的路由守卫意思差不多;简单理解为就是你在进路由之前,首先把你拦住,对你进行检查;这是不是有点中学门口的保安?进来之前拦住,有学生证就进,没有学生证就不让进;当然,路由守卫不仅仅只是在你进入之前拦住你,还有其他的钩子函数进行其他操作。

vue-router一共给我们提供了三大类钩子函数来实现路由守卫:

    1、全局钩子函数(beforeEach、afterEach)

    2、路由独享的钩子函数(beforeEnter)

    3、组件内钩子函数(beforeRouterEnter、beforeRouterUpdate、beforeRouterLeave)

详细学习传送门

执行顺序:

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会
  13. 作为回调函数的参数传入。
     

9,NextTick的作用,应用场景以及实现原理

Vue中关于NextTick的知识点总结

作用:nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

应用场景:
需要在视图更新之后,基于新的视图进行操作。

实现原理:
nextTick方法主要是使用了宏任务和微任务,定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空队列。

 10,keep-alive 使用场景和原理

<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。

当组件在 <keep-alive> 内被切换时,它的 mounted 和 unmounted 生命周期钩子不会被调用,取而代之的是 activated 和 deactivated。(这会运用在 <keep-alive> 的直接子节点及其所有子孙节点。)

主要用于保留组件状态或避免重新渲染。

diff原理:

传送门

传送门

vuex的5个属性及使用方法

传送门

vue中的事件绑定原理

传送门

为什么vue采用异步渲染

传送门

vue@2为什么要引入虚拟Dom,谈谈对虚拟Dom的理解?

  • 随着现代应用对页面的功能要求越复杂,管理的状态越多,如果还是使用之前的JavaScript线程去频繁操作GUI线程的硕大Dom,对性能会有很大的损耗,而且也会造成状态难以管理,逻辑混乱等情况。引入虚拟Dom后,在框架的内部就将虚拟Dom树形结构与真实Dom做了映射,让我们不用在命令式的去操作Dom,可以将重心转为去维护这棵树形结构内的状态即可,状态的变化就会驱动Dom发生改变,具体的Dom操作vue帮我们完成,而且这些大部分可以在JavaScript线程完成,性能更高。
  • 虚拟Dom只是一种数据结构,可以让它不仅仅使用在浏览器环境,还可以用与SSR以及Weex等场景。

传送门

用VNode来描述一个DOM结构

虚拟节点就是用一个对象来描述一个真实的DOM元素。首先将 template (真实DOM)先转成 ast , ast 树通过 codegen 生成 render 函数, render 函数里的 _c 方法将它转为虚拟dom

vue的全局组件

传送门

vue的自定义指令

传送门

this指向 https://www.cnblogs.com/pssp/p/5216085.html

改变函数内部 this 指针的指向函数(bind,apply,call的区别:

https://blog.csdn.net/weixin_43287506/article/details/86248690

https://www.cnblogs.com/pssp/p/5787116.html

异步加载Js的方式有哪些?

https://www.cnblogs.com/Lolita-web/p/10456967.html

Guess you like

Origin blog.csdn.net/yangdl6/article/details/119143920