Web前端面试宝典

一、HTML+CSS

1、介绍一下你对浏览器内核的理解

主要分成两部分:渲染引擎(layout engineer 或 Rendering Engine)和JS引擎

(1) 渲染引擎:负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网 页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其 它需要编辑、显示网络内容的应用程序都需要内核。

(2) JS引擎:解析和执行javascript来实现网页的动态效果。

最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。

2、CSS3有哪些新特性?

(1) 实现圆角(border-radius),阴影(box-shadow);

(2) 文字特效(text-shadow),线性渐变(gradient),旋转(transform);

(3) 增加了更多的CSS选择器;

(4) 多重背景图片;

(5) 媒体查询,多栏布局;

(6) border-image:图片边框。

3、html5有哪些新特性?移除了那些元素?

新特性:

(1) 拖拽释放(Drag and drop)API;

(2) 语义化更好的内容标签(header、nav、footer、aside、article、section);

(3) 音频、视频 API(audio、video);

(4) 画布(Canvas) API;

(5) 地理(Geolocation) API

(6) 本地离线存储;localStorage长期存储数据,浏览器关闭后数据不丢失;sessionStorage的数据在浏览器关闭后自动删除;

(8) 表单控件,calendar、date、time、email、url、search;

(9) 新的技术webworker、websocket、Geolocation

移除的元素:

(1) 纯表现的元素:basefont、big、center、font、s、strike、tt、u;

(2) 对可用性产生负面影响的元素:frame、frameset、noframes。

4、px、em、rem 的区别?

(1) px像素,绝对单位。像素px是相对于显示器屏幕分辨率而言的,是一个虚拟单位。是计算机系统的数字化图像长度单位,如果px要换算成物理长度,需要指定精度DPI。

(2) em相对单位,相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对浏览器的默认字体尺寸。它会继承父级元素的字体大小,因此并不是一个固定的值。

(3) rem:CSS3新增的一个相对单位(root em,根em)。使用rem为元素设定字体大小事,仍然是相对大小,但相对的只是HTML根元素。通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。

5、描述一下渐进增强和优雅降级之间的不同?

(1) 渐进增强(progressive enhancement):针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。

(2) 优雅降级(graceful degradation):一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

区别:优雅降级是从复杂的现状开始,并试图减少用户体验的供给;而渐进增强则是从一个非常基础的、能够起作用的版本开始,并不断扩充,以适应未来环境的需要。降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带。

6、请描述一下cookie、sessionStorage和localStorage的区别?

(1) sessionStorage中的数据,这些数据只有在同一个会话中的页面才能访问,并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。

(2) localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。

(3) cookie的大小是受限的,并且每次你请求一个新的页面的时候,cookie都会被发送过去,这样无形中浪费了带宽,另外cookie还需要指定作用域,不可以跨域调用。

Web Storage的概念和cookie相似,区别是它是为了更大容量存储设计的。除此之外,Web Storage拥有 setItem、getItem、removeItem、clear等方法,不像cookie需要前端开发者自己封装setCookie、getCookie。但是cookie也是不可以或缺的:cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在,而Web Storage仅仅是为了在本地“存储”数据而生。

7、Sass、Scss、Less是什么?

(1) 他们都是CSS预处理器,是CSS上的一种抽象层。他们是一种特殊的语法/语言编译成CSS。例如Less是一种动态样式语言,将CSS赋予了动态语言的特性,如变量、继承、运算、函数。Less既可以在客户端上运行,也可以在服务端运行(借助Node.js)。

(2) Scss是Sass 3引入的新语法,是Sassy CSS的简写,是CSS3语法的超集。说白了Scss就是Sass的升级版,其语法完全兼容CSS3,并且继承了Sass的强大功能。Sass是以严格的缩进式语法规则来书写,不带大括号({})和分号(;),而Scss的语法书写和我们的CSS语法书写方式非常类似。

(3) 为什么要使用它们?结构清晰,便于扩展;可以方便地屏蔽浏览器私有语法差异,封装对浏览器语法差异的重复处理,减少无意义的机械劳动;可以轻松实现多重继承;完全兼容CSS代码,可以方便地应用到老项目中。Less只是在CSS语法上做了扩展,所以老的CSS代码也可以与Less代码一同编译。

8、列出display的值,说明他们的作用。

(1) block:块类型。默认宽度为父元素宽度,可设置宽高,换行显示。

(2) none:元素不显示,并从文档流中移除。

(3) inline:行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。

(4) inline-block:默认宽度为内容宽度,可以设置宽高,同行显示。

(5) flex:弹性布局,它能够扩展和收缩flex容器内的元素,以最大限度地填充可用空间。

(6) grid:网格布局,它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局。

(7) table:表格布局,解决了一部分需要使用表格特性但又不需要表格语义的情况。

(8) inherit:规定应该从父元素继承display属性的值。

9、flex布局的属性有哪些?

设置在容器上的属性:

(1) flex-direction:row | row-reverse | column | column-reverse,项目的排列方向;

(2) flex-wrap:nowrap | wrap | wrap-reverse,是否换行;

(3) flex-flow:flex-direction 和 flex-wrap 的简写;

(4) justify-content:flex-start | flex-end | center | space-between | space-around,在水平方向上的对齐方式;

(5) align-items:flex-start | flex-end | center | baseline | stretch,在垂直方向上的对齐方式;

(6) align-content:flex-start | flex-end | center | space-between | space-around | stretch,定义了多根轴线的对齐方式,如果项目只有一根轴线,那么该属性将不起作用

设置在项目上的属性:

(1) order:<number>,定义项目在容器中的排列顺序,数值越小,排列越靠前,默认值为0;

(2) flex-basis:<length> | auto,定义了在分配多余空间之前,项目占据的主轴空间,浏览器根据这个属性,计算主轴是否有多余空间;

(3) flex-grow:定义项目的放大比例。默认值为0,即如果存在剩余空间,也不放大;

(4) flex-shrink:定义项目的缩小比例。默认值为1,即如果空间不足,该项目将缩小,负值对该属性无效;

(5) flex:flex-grow、flex-shrink 和 flex-basis的简写。默认值:0 1 auto,即不放大、可缩小、大小就是项目本身的大小。

(6) align-self:auto | flex-start | flex-end | center | baseline | stretch,允许单个项目有与其他项目不一样的对齐方式。

二、JavaScript

10、javascript 有哪几种数据类型?

六种基本数据类型:

(1) undefined;

(2) null;

(3) string;

(4) boolean;

(5) number;

(6) symbol(ES6)。

一种引用类型:

(1) Object。

11、介绍 js 有哪些内置对象?

(1) Object 是 JavaScript 中所有对象的父对象;

(2) 数据封装类对象:Object、Array、Boolean、Number 和 String;

(3) 其他对象:Function、Arguments、Math、Date、RegExp、Error。

12、split()join() 的区别?

(1) 前者是切割成数组的形式;

(2) 后者是将数组转换成字符串。

13、数组方法 pop()push()unshift()shift() 的作用?

(1) Push():尾部添加;

(2) pop():尾部删除;

(3) Unshift():头部添加;

(4) shift():头部删除。

14、====== 的不同?

(1) =:赋值运算;

(2) ==:值比较,转化为同一类型后看值是否相同;

(3) ===:值比较,如果类型不同,它的结果就是不等。

15、eval 是做什么的?

(1) 它的功能是把对应的字符串解析成 JS 代码并运行;

(2) 应该避免使用 eval,不安全,非常耗性能。

16、undefined 和 null 有什么区别?

(1) null:表示“没有对象”,即该处不应该有值,转为数值时为 0。典型用法是:

  • 作为函数的参数,表示该函数的参数不是对象;
  • 作为对象原型链的终点。

(2) undefined:表示“缺少值”,就是此处应该有一个值,但是还没有定义,转为数值时为 NaN。典型用法是:

  • 变量被声明了,但没有赋值时,就等于 undefined;
  • 调用函数时,应该提供的参数没有提供,该参数等于 undefined;
  • 对象没有赋值的属性,该属性的值为 undefined;
  • 函数没有返回值时,默认返回 undefined。

17、函数的防抖和节流是什么?

(1) 防抖:通过 setTimeout 的方式,在一定的时间间隔内,将多次触发变成一次触发。

(2) 节流:让一个函数无法在很短的时间间隔内连续调用,而是间隔一段时间执行,降低触发频率。

(3) 这在我们为元素绑定一些事件的时候经常会用到,比如我们为 window 绑定了一个 resize 事件,如果用户一直改变窗口大小,就会一直触发这个事件处理函数,这对性能有很大影响。

18、var、let、const 之间的区别?

(1) var 声明变量可以重复声明,而 let 和 const 不可以重复声明;

(2) var 是不受限于块级的,而 let 和 const 是受限于块级;

(3) var 会与 window 相映射(会挂一个属性),而 let 和 const 不与 window 相映射;

(4) var 可以在声明的上面访问变量,而 let 和 const 有暂时性死区,在声明的上面访问变量会报错;

(5) const 声明之后必须赋值,否则会报错;

(6) const 定义不可变的量,改变了就会报错。

19、使用箭头函数应注意什么?

(1) 用了箭头函数,this 就不是指向 window,而是父级(指向是可变的);

(2) 不能够使用 arguments 对象;

(3) 不能用作构造函数,这就是说不能够使用 new 命令,否则会抛出一个错误;

(4) 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

20、深拷贝与浅拷贝的区别?

(1) 深拷贝递归地复制新对象中的所有值或属性;而浅拷贝只复制指向某个对象的指针,而不复制对象本身。

(2) 在深拷贝中,新对象中的更改不会影响原始对象;而在浅拷贝中,新对象中的更改,原始对象中也会跟着改。

(3) 在深拷贝中,原始对象不与新对象共享相同的属性;而在浅拷贝中,它们具有相同的属性。

21、解释下 JavaScript 中 this 是如何工作的?

this 在 JavaScript 中主要由以下五种使用场景:

(1) 作为函数调用,this 绑定全局对象,浏览器环境全局对象为 window;

(2) 内部函数内部函数的 this 也绑定全局对象,应该绑定到其外层函数对应的对象上,这是 JavaScript 的缺陷,用 that 替换;

(3) 作为构造函数使用,this 绑定到新创建的对象;

(4) 作为对象方法使用,this 绑定到该对象;

(5) 使用 apply 或 call 调用 this 将会被显式设置为函数调用的第一个参数。

22、什么是闭包?写一个简单的闭包。

闭包就是能够读取其他函数内部变量的函数。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

/**
 * 闭包函数
 */
function outer() {
    let num = 1
    function inner() {
        const n = 2
        return num + n
    }
    return inner
}

// 示例
console.log(outer()()) // 3

23、JavaScript 的原型和原型链是什么?有什么特点?

(1) 含义:每个对象都会在其内部初始化一个属性,就是 prototype(原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去 prototype 里找这个属性,这个 prototype 又会有自己的 prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。

(2) 关系:instance.constructor.prototype = instance.__proto__

(3) 特点:JavaScript 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。当我们需要一个属性时,JavaScript 引擎会先看当前对象中是否有这个属性,如果没有的话,就会查找他的 Prototype 对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象。

24、.call、.apply 和 .bind 的异同点是什么?

(1) 相同点:三者产生的作用是完全一样的,调用一个对象的一个方法,用另一个对象替换当前对象。也就是说,三个函数都是用来改变函数的 this 对象的指向的,三者第一个参数都是 this 要指向的对象,后续参数为传参。

(2) 不同点:call、bind 可以传入多个参数,参数间全都用逗号分隔;apply 的所有参数作为数组形式传入。apply、call 会立即调用函数;bind 则返回一个新的函数,需要自行调用。

dog.eat.call(cat, '鱼', '肉');
dog.eat.apply(cat, ["鱼", "肉"]);
let fun = dog.eat.bind(cat, "鱼", "肉");
fun();

25、hasOwnProperty 函数。

(1) javaScript 中 hasOwnProperty 函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法无法检查该对象的原型链中是否具有该属性;该属性必须是对象本身的一个成员。

(2) 使用方法:object.hasOwnProperty(proName) 其中参数 object 是必选项,一个对象的实例。proName 是必选项,一个属性名称的字符串值。

(3) 如果 object 具有指定名称的属性,那么 hasOwnProperty 函数方法返回 true,反之则返回 false。

26、JS 中的事件冒泡、事件捕获和事件委托。

(1) 事件冒泡:在 javascript 事件传播过程中,当事件在一个元素上触发之后,事件会逐级传播给先辈元素,直到 document 或 window 为止。

(2) 事件捕获:恰好与事件冒泡相反,它从顶层祖先元素开始,直到事件触发元素。

(3) 事件委托:又叫事件代理,只指定一个事件处理程序,就可以管理某一类型的所有事件。

27、JS 如何阻止事件冒泡和阻止默认事件?

(1) event.stoppropagation():阻止事件冒泡;

(2) event.preventdefault():阻止默认事件。

28、引起内存泄漏的操作有哪些?

(1) 全局变量引起;

(2) 闭包引起;

(3) dom 清空,事件未清除;

(4) 子元素存在引用;

(5) 被遗忘的计时器。

29、Promise 对象是什么?

Promise 是异步编程的一种解决方案,它是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。Promise 对象是一个构造函数,用来生成 Promise 实例。

Promise 的两个特点:对象状态不受外界影响;一旦状态改变,就不会再变,任何时候都可以得到结果(pending –> fulfilled 或 pending –> rejected)。

30、JS 中对象属性的描述。

在定义对象、定义属性时,属性描述符一共有 6 个:

(1) value:设置属性的值,默认值为:undefined。

(2) writable:设置属性值是否可写,默认值为:true。

(3) enumerable:设置属性是否可枚举,即是否允许使用 for/in 语句或 Object.keys() 函数遍历访问,默认值为:true。

(4) configurable:设置是否可设置属性特性,默认值为:true。如果为 false,将无法删除属性,不能够修改属性值,也不能修改属性的描述符的值。

(5) get:取值函数,默认值为:undefined。

(6) set:存值函数,默认值为:undefined。

三、jQuery

31、jQuery 库中的 $() 是什么?

(1) $() 函数是 jQuery() 函数的别称。

(2) $() 函数用于将任何对象包裹成 jQuery 对象,接着你就被允许调用定义在 jQuery 对象上的多个不同方法。

(3) 可以将一个选择器字符串传入 $() 函数,它会返回一个包含所有匹配的 DOM 元素数组的 jQuery 对象。

32、jQuery 中 addClass、removeClass、toggleClass 的使用。

(1) $(selector).addClass(class):为每个匹配的元素添加指定的类名;

(2) $(selector).removeClass(class):从所有匹配的元素中删除全部或者指定的类,删除 class 中某个值;

(3) $(selector).toggleClass(class):如果存在就删除一个类,如果不存在就添加一个类;

(4) $(selector).removeAttr(class):删除 class 这个属性。

33、JQuery 有几种选择器?

(1) 基本选择器:#id.classelement

(2) 层次选择器:parent > childprev + nextprev ~ siblings

(3) 基本过滤器选择器::first:last:not:even:odd:eq:gt:lt

(4) 内容过滤器选择器: :contains:empty:has:parent

(5) 可见性过滤器选择器::hidden:visible

(6) 属性过滤器选择器:[attribute][attribute=value][attribute!=value][attribute^=value][attribute$=value][attribute*=value]

(7) 子元素过滤器选择器::nth-child:first-child:last-child:only-child

(8) 表单选择器::input:text:password:radio:checkbox:submit

(9) 表单过滤器选择器::enabled:disabled:checked:selected

34、你使用过 jQuery 中的动画吗,是怎样用的?

(1) hide() 和 show() 同时修改多个样式属性。像高度、宽度、不透明度。

(2) fadeIn() 、fadeOut() 和 fadeTo() :只改变不透明度;

(3) slideUp() 、slideDown() 和 slideToggle() :只改变高度;

(4) animate() :自定义动画的方法。

35、jQuery 中有哪些方法可以遍历节点?

(1) children():取得匹配元素的子元素集合,只考虑子元素不考虑后代元素;

(2) next():取得匹配元素后面紧邻的同辈元素;

(3) prev():取得匹配元素前面紧邻的同辈元素;

(4) siblings():取得匹配元素前后的所有同辈元素;

(5) closest():取得最近的匹配元素;

(6) find():取得匹配元素中的元素集合,包括子代和后代。

36、jQuery 查询节点的选择器有哪些?

(1) :first:查询第一个;

(2) :last:查询最后一个;

(3) :odd:查询奇数但是索引从 0 开始;

(4) :even:查询偶数;

(5) :eq(index):查询相等的 , (6) :gt(index)查询大于 index 的 , (7) :lt 查询小于 index (8) :header 选取所有的标题等

37、jQuery 的事件委托方法 bind 、live、delegate、on 之间有什么区别?

bind【jQuery 1.3 之前】

(1) 定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;

(2) 语法:bind(type,[data],function(eventObject));

(3) 特点:

a. 适用于页面元素静态绑定。只能给调用它的时候已经存在的元素绑定事件,不能给未来新增的元素绑定事件。

b. 当页面加载完的时候,你才可以进行 bind(),所以可能产生效率问题。

live【jQuery 1.3 之后】

(1) 定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;

(2) 语法:live(type, [data], fn);

(3) 特点:

a. live 方法并没有将监听器绑定到自己(this)身上,而是绑定到了 this.context 上了。

b. live 正是利用了事件委托机制来完成事件的监听处理,把节点的处理委托给了 document,新添加的元素不必再绑定一次监听器。

c. 使用 live() 方法但却只能放在直接选择的元素后面,不能在层级比较深、连缀的 DOM 遍历方法后面使用。

delegate【jQuery 1.4.2 中引入】

(1) 定义和用法:将监听事件绑定在就近的父级元素上;

(2) 语法:delegate(selector,type,[data],fn);

(3) 特点:

a. 选择就近的父级元素,因为事件可以更快的冒泡上去,能够在第一时间进行处理。

b. 更精确的小范围使用事件代理,性能优于 .live()。可以用在动态添加的元素上。

on【1.7 版本整合了之前的三种方式的新事件绑定机制】

(1) 定义和用法:将监听事件绑定到指定元素上;

(2) 语法:on(type,[selector],[data],fn);

(3) 说明:on 方法是当前 jQuery 推荐使用的事件绑定方法。

总结:.bind() 、.live() 、.delegate() 、.on() 分别对应的相反事件为:.unbind() 、.die() 、.undelegate() 、.off() 。

四、Ajax

38、请描述一下 get 和 post 的区别?

(1) get:向指定的资源请求数据。请求的参数会附在 URL 之后(就是把数据放置在请求行中),以 ? 分割 URL 和传输参数,多个参数用 & 连接;请求的 url 长度有限制(2048 字符),请求方法是安全幂等的。

(2) post :向指定的资源提交要被处理的数据。请求的数据是封装在 http 消息包体中的;每次刷新或者后退都会重新提交。

39、Ajax 是什么?如何创建一个 Ajax?

ajax 的全称:Asynchronous Javascript And XML。异步传输 + js + xml。所谓异步,简单地解释就是:向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验。

创建步骤:

(1) 创建 XMLHttpRequest 对象,也就是创建一个异步调用对象;

(2) 创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息;

(3) 设置响应 HTTP 请求状态变化的函数;

(4) 发送 HTTP 请求;

(5) 获取异步调用返回的数据;

(6) 使用 JavaScript 和 DOM 实现局部刷新。

40、Ajax 请求的同源策略?

(1) 一段脚本只能读取来自于同一来源的窗口和文档的属性,这里的同一来源指的是主机名、协议和端口号的组合。协议:http/ftp;主机名:localhost;端口名:80。

(2) 同源策略带来的麻烦:Ajax 请求的协议、端口、域名如果不同时,会被浏览器阻止,但是可以通过 jsonp、cors、代理等方法来解决跨域问题。

41、axios 的特点有哪些?

(1) axios 是一个基于 promise 的 HTTP 库,支持 promise 的所有 API;

(2) 它可以拦截请求和响应;

(3) 它可以转换请求数据和响应数据,并对响应回来的内容自动转换为 json 类型的数据;

(4) 它安全性更高,客户端支持防御 XSRF。

42、解决跨域的几种方法?

(1) CORS 跨域资源共享(cross-origin-resource-sharing)

服务器端对于 CORS 的支持,主要就是通过设置 Access-Control-Allow-Origin 来进行的。如果浏览器检测到相应的设置,就可以允许 Ajax 进行跨域的访问——也就是常说的服务器加请求头。前端无需做任何事情,只需要后端使用第三方模块添加响应头即可。

(2) JSONP 解决跨域

借助 script 标签不受同源策略的影响进行数据请求。动态插入 script 标签,通过 script 标签引入一个 js 文件,这个 js 文件载入成功后会执行我们在 url 参数中指定的函数,并且会把我们需要的 json 数据作为参数传入。注意:只支持 GET 请求,不过兼容性比较好,无法判断请求是否失败,安全性不高。

(3) proxy 代理转发

正向代理:借助于我们的服务器,向数据服务器发送数据;

反向代理:与正向代理类似,但是不借助于脚本,而是直接使用服务器映射 url。

五、构建工具

43、什么是 bundle、chunk、module?

(1) bundle:是 webpack 打包出来的文件。

(2) chunk:是 webpack 在进行模块的依赖分析的时候,代码分割出来的代码块。

(3) module:是开发中的单个模块。

44、如何利用 webpack 来优化前端性能?

(1) 多入口情况下,使用 CommonsChunkPlugin 来提取公共代码;

(2) 通过 externals 配置来提取常用库;

(3) 利用 DllPlugin 和 DllReferencePlugin 预编译资源模块,通过 DllPlugin 来对那些我们引用但是绝对不会修改的 npm 包来进行预编译,再通过 DllReferencePlugin 将预编译的模块加载进来;

(4) 使用 Happypack 实现多线程加速编译;

(5) 使用 webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。原理上 webpack-uglifyparallel 采用了多核并行压缩来提升压缩速度;

(6) 使用 Tree-shaking 和 Scope Hoisting 来剔除多余代码。

45、如何在 vue 项目中实现按需加载?

为了快速开发前端项目,经常会引入现成的 UI 组件库如 ElementUI、iView 等,但是他们的体积和他们所提供的功能一样,是很庞大的。而通常情况下,我们仅仅需要少量的几个组件就足够了,但是我们却将庞大的组件库打包到我们的源码中,造成了不必要的开销。

Element 出品的 babel-plugin-component 和 AntDesign 出品的 babel-plugin-import,安装以上插件后,在 .babelrc 配置中或 babel-loader 的参数中进行设置,即可实现组件按需加载了。

六、移动端

46、移动端你们一般采用什么布局?移动端设计稿是多大的尺寸?

(1) 定宽布局。

(2) 一般移动端设计稿是 640 或者 750 的尺寸。

47、移动端 click 300 毫秒延迟原因?

移动端浏览器会有一些默认的行为,比如双击缩放、双击滚动。这些行为,尤其是双击缩放,主要是为网站在移动端的浏览体验设计的。而在用户对页面进行操作的时候,移动端浏览器会优先判断用户是否要触发默认的行为。可以通过引用 fastclick 插件来解决这个问题。

48、移动端 zepto 中的 tap 事件点击穿透问题。

点击蒙层(mask)上的关闭按钮,蒙层消失后发现触发了按钮下面元素的 click 事件。

zepto 的 tap 事件是绑定到 document 的,所以一般点击 tap 事件都会冒泡到 document 才会触发。当点击隐藏蒙层的时候默认也会触发到蒙层下面的元素执行事件。

49、什么是媒体查询?

媒体查询是 CSS3 中的新增内容,用于定义不同媒体类型在不同 CSS 属性时的样式表现,通过媒体查询,根据不同条件,使用不同的 css 样式。

50、什么是移动端 1px 问题?如何解决?

在移动端 Web 开发中,UI 设计稿中设置边框为 1px,前端在开发过程中如果出现 border: 1px,测试会发现在 retina 屏(高清屏)机型中,1px 会比较粗,即是较经典的移动端 1px 问题。

解决方案:

(1) 设置 height: 1px,根据媒体查询结合 transform 缩放为相应尺寸。

div {
    height: 1px;
    background: #000;
    -webkit-transform: scaleY(0.5);
    -webkit-transform-origin: 0 0;
    overflow: hidden;
}

(2) 用 ::after::before,设置 border-bottom:1px solid #000,然后再结合 transform 缩放,可以实现两根边线的需求。

div::after{
    content: '';
    width:100%;
    border-bottom: 1px solid #000;
    transform: scaleY(0.5);
}

(3) 用 ::after 设置 border: 1px solid #000; width: 200%; height: 200%,然后再缩放 scaleY(0.5);,优点是可以实现圆角,缺点是按钮添加 active 比较麻烦。

.div::after {
    content: '';
    width: 200%;
    height: 200%;
    position: absolute;
    top: 0;
    left: 0;
    border: 1px solid #bfbfbf;
    border-radius: 4px;
    -webkit-transform: scale(0.5,0.5);
    transform: scale(0.5,0.5);
    -webkit-transform-origin: top left;
}

(4) 媒体查询 + transfrom

/* 2倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.5);
        transform: scaleY(0.5);
    }
}
/* 3倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 3.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.33);
        transform: scaleY(0.33);
    }
}

七、Vue

51、如何理解 MVVM 原理?

(1) 什么是 MVVM?

随着前端对于控制逻辑的越来越轻量,MVVM 模式作为 MVC 模式的一种补充出现了,万变不离其宗,最终的目的都是将 Model 里的数据展示在 View 视图上,而 MVVM 相比于 MVC 则将前端开发者所要控制的逻辑做到更加符合轻量级的要求。

(2) ViewModel

在 Model 和 View 之间多了叫做 View-Model 的一层,将模型与视图做了一层绑定关系,在理想情况下,数据模型返回什么视图就应该展示什么。在 ViewModel 引入之后,视图完全由接口返回数据驱动,由开发者所控制的逻辑非常轻量。不过这里要说明的是,在 MVVM 模式下,Controller 控制逻辑并非就没了,像操作页面 DOM 响应的逻辑被 SDK(如 Vue 的内部封装实现)统一实现了,像非操作接口返回的数据是因为服务端在数据返回给前端前已经操作好了。

52、Vue 的生命周期

(1) beforeCreate:创建前。在数据观测和初始化事件还未开始前被调用。

(2) created:创建后。完成数据观测、属性和方法的运算、初始化事件,$el 属性还没有显示出来。

(3) beforeMount:载入前。在挂载开始之前被调用,相关的 render 函数首次被调用。实例已完成以下的配置:编译模板,把 data 里面的数据和模板生成 html。注意此时还没有挂载 html 到页面上。

(4) mounted:载入后。在 el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的 html 内容替换 el 属性指向的 DOM 对象。完成模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。

(5) beforeUpdate:更新前。在数据更新之前调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。

(6) updated:更新后。在由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。调用时,组件 DOM 已经更新,所以可以执行依赖于 DOM 的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

(7) beforeDestroy:销毁前。在实例销毁之前调用。实例仍然完全可用。

(8) destroyed:销毁后。在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

53、Vue 的每个生命周期内可以处理的事项?

(1) created:实例创建完成,可以进行一些数据、资源的请求。

(2) mounted:实例载入完成,可以进行一些 DOM 操作。

(3) beforeUpdate:可以进一步地更新状态,不会触发附加的重渲染过程。

(4) updated:可以执行依赖于 Dom 的操作,但在大多数情况下应该避免在此期间更改状态,因为这可能导致更新无限循环,在服务器渲染器件不被调用。

(5) destroyed:可以执行优化操作,清空定时器,解除绑定事件。

54、v-if 和 v-show 的区别?

(1) v-if 按照条件是否渲染,如果为 false,直接不再渲染 DOM;v-show 是 display 的 block 或 none。

(2) v-if 是惰性的,如果初始时条件为假,则什么也不做,只有条件第一次变为真时才会开始渲染;v-show 无论初始条件是什么,都会被渲染。

(3) v-if 有更高的切换开销,v-show 有更高的初始渲染开销。因此,需要频繁切换时适合使用 v-show,条件不轻易改变时,适合使用 v-if。

55、为什么 v-for 和 v-if 不能连用?

v-for 会比 v-if 的优先级高一些,如果连用的话,会导致每循环一次就会去 v-if 判断一次,而 v-if 是通过创建和销毁 DOM 元素来控制元素的显示与隐藏,所以就会不停地去创建和销毁元素,造成页面卡顿,性能下降。

56、父组件与子组件间如何互相传值?

(1) 父组件传给子组件:子组件通过 props 方法接收数据。

(2) 子组件传给父组件:$emit 方法传递参数。

57、vue 常用的修饰符?

(1) .prevent:提交事件不再重载页面;

(2) .stop:阻止单击事件冒泡;

(3) .self:当事件发生在该元素本身而不是子元素的时候会触发;

(4) .capture:事件侦听,事件发生的时候会调用。

58、Vue 中 key 值的作用?

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key 的作用主要是为了高效地更新虚拟 DOM。

59、什么是 Vue 的计算属性?

在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。

好处:

(1) 使得数据处理结构清晰;

(2) 依赖于数据,数据更新,处理结果自动更新;

(3) 计算属性内部 this 指向 vm 实例;

(4) 在 template 调用时,直接写计算属性名即可;

(5) 常用的是 getter 方法,获取数据,也可以使用 set 方法改变数据;

(6)相较于 methods,不管依赖的数据变不变,methods 都会重新计算,但是依赖数据不变的时候 computed 从缓存中获取,不会重新计算。

60、分别简述 computed 和 watch 的使用场景?

(1) computed:当一个属性受多个属性影响的时候就需要用到 computed。最典型的例子:购物车商品结算的时候。

(2) watch:当一条数据影响多条数据的时候就需要用 watch。例子:搜索数据。

61、vue 组件中 data 为什么必须是一个函数?

组件中的 data 写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的 data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据, 不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个 data,这样改一个全都改了。

62、$nextTick 的使用?

当你修改了 data 的值然后马上获取这个 dom 元素的值,是不能获取到更新后的值,你需要使用 $nextTick 这个回调,让修改后的 data 值渲染更新到 dom 元素之后再获取,才能成功。

63、Vue-router 的全局导航钩子函数?

(1) beforeEach:在路由切换开始时调用。

(2) afterEach:在每次路由切换成功进入激活阶段时被调用。

64、Vuex 是什么?怎么使用?哪种功能场景使用它?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vuex 的应用场景有:组件之间的状态、音乐播放、登录状态、加入购物车等。

Vuex 包含的内容:
在这里插入图片描述

(1) state:存放的数据状态,不可以直接修改里面的数据。

(2) mutations:定义方法来动态修改 Vuex 的 store 中的状态或数据。

(3) getters:类似 Vue 的计算属性,主要用来过滤一些数据。

(4) action:可以理解为通过将 mutations 里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。

(5) modules:项目特别复杂的时候,可以让每一个模块拥有自己的 state、mutation、action、getters,使得结构非常清晰,方便管理。

65、Vue 响应式数据原理?

(1) 核心点:Object.defineProperty。默认 Vue 在初始化数据时,会给 data 中的属性使用 Object.defineProperty 重新定义所有属性。当页面取到对应属性时,会进行依赖收集(收集当前组件中的 watcher),如果属性发生变化会通知相关依赖进行更新操作。

(2) 原理:Vue 初始化时会调用 initDatainitData 会获取当前用户传入的数据,并创建一个观测类 Observer 对数据进行观测,数据如果为对象类型,则会调用 this.walk(value) 方法,将数据遍历并使用 Object.definePreperty 方法重新定义。拦截属性的获取,进行依赖拦截属性的更新,对相关依赖进行通知。

66、Vue 中如何检测数组变化?

(1) 使用函数劫持方式,重写数组方法。

(2) Vue 将 data 中的数组进行了原型链重写,指向了自己定义的数组原型方法,这样当调用数组 api 时,可以通知依赖更新,如果数组中包含引用类型,会对数组中的引用类型再次进行监控。

67、为何 Vue 采用异步渲染?

Vue 是组件级更新,如果不采用异步更新,每次更新数据都会对当前组件进行重新渲染。所以为了性能考虑,Vue 会在本轮数据更新后,再去异步更新视图。

68、nextTick 及其实现原理?

nextTick 方法主要时使用了宏任务和微任务,定义了一个异步方法。多次调用 nextTick 会将方法存入队列中,通过这个异步方法清空当前队列。所以这个 nextTick 方法就是异步方法。

实现原理:

(1) nextTick(cb):调用 nextTick 传入 cb。

(2) callbacks.push(cb):将回调函数存入数组中。

(3) timerFunc():分别尝试采用 Promise 回调、MutationObserver 回调、setimmediat 回调、setTimeout 回调,执行 nextTick 中传入的方法(flushCallbacks)。

(4) 支持 Promise 写法的会返回 Promise。

69、简述一下 Vue 中 Computed 的特点。

默认 computed 也是一个 watcher,是具备缓存的,只有当依赖的属性发生变化时才会更新视图。

computed 的核心是做了一个 dirty 实现缓存的机制,当依赖的数据发生变化,会让计算属性的 watcher 的 dirty 变成 true。

70、Watch 中的 deep: true 是如何实现的?

当用户指定了 watch 中的 deep 属性为 true 时,如果当前监控的值是数组类型,会对对象中的每一项进行求值,此时会将当前 watcher 存入到对应属性的依赖中,这样数组中对象发生变化时也会通知数组更新。

71、Ajax 请求放在哪个生命周期中?

(1) 在 created 的时候,视图中的 dom 并没有被渲染出来,所以此时如果直接去操作 dom 节点,无法找到相关的元素。

(2) 在 mounted 中,由于此时 dom 已经渲染出来了,所以可以直接操作 dom 节点。一般情况下都放到 mounted 中,保证逻辑的统一性,因为生命周期是同步执行的,ajax 是异步执行的。

(3) 服务器渲染不支持 mounted 方法,所以在服务端渲染的情况下统一放到 created 中。

72、何时需要使用 beforeDestroy?

(1) 可能在当前页面中使用了 $on 方法,那就需要在组件销毁前解绑。

(2) 清除自己定义的计时器。

(3) 解除事件的绑定,如:scroll、mousemove等。

73、Vue 中模版编译的原理?

export const createCompiler = createCompilerCreator(function baseCompile(
  template: string,
  options: CompilerOptions
): CompiledResult {
  // 把 template 字符串转化成 ast 抽象语法树
  const ast = parse(template.trim(), options);
  // 优化 ast 抽象语法树
  if (options.optimize !== false) {
    optimize(ast, options);
  }
  // 将抽象语法树生成字符串形式的 js 代码
  const code = generate(ast, options);
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns,
  };
});
// 最后将生成的函数字符串转为 render 函数
const render = new Function(code);

可以看到,编译的主要过程为 4 步:

(1) 将 template 字符串转化成 ast 抽象语法树;

(2) 优化 ast 抽象语法树;

(3) 将抽象语法树生成字符串形式的 js 代码;

(4) 最后将生成的函数字符串转为 render 函数。

74、Vue 的全局 API?

(1) Vue.component():注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称。

(2) Vue.directive():注册或获取全局指令。

(3) Vue.filter():注册或获取全局过滤器(如设置日期时间的格式化)。

(4) Vue.use():声明并使用插件。

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

(6) Vue.set():向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。

(7) Vue.delete():删除对象的 property。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到 property 被删除的限制,但是你应该很少会使用它。

(8) Vue.mixin():全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。

75、Vue 实例的配置选项?

(1) data:组件中使用的 data 函数的形式,目的是为了实现数据的响应式;组件中使用 data 对象的写法是无法实现真正的数据响应式的。

(2) props:接收父级组件传递进来的数据(属性或者方法),实现父子组件之间的通信。

(3) computed:计算属性,在组件中某个数据发生变化,相关联的数据会自动的变化,也是响应式的数据。

(4) methods:用来定义方法,在定义的方法中,this 指向 Vue 实例本身。

(5) watch:状态监听功能,只需监听当前 Vue 实例中的数据变化,就会调用当前数据所绑定的事件处理方法。

(6) filters:在插值表达式中可以使用过滤器 {{data | filter}}| 称为管道符,管道符之前代码执行的结果会传到后面作为参数进行处理);在 v-bind 属性绑定中可以使用过滤器(用于属性绑定)。

76、computed 和 watch 的区别?

(1) computed:计算属性。组件加载的时候,计算属性会自动的执行。

(2) watch:监视。组件加载的时候默认是不执行,除非数据发生了变化才会执行。如果监视默认执行,需要进行配置。

77、Vue 的实例属性有哪些?

(1) $parent:父级实例,如果当前实例有的话。

(2) $children:当前实例的所有直接子组件。

(3) $data:Vue 实例观察的数据对象。Vue 实例代理了对其 data 对象 property 的访问。

(4) $options:用于当前 Vue 实例的初始化选项。需要在选项中包含自定义 property 时会有用处。

(5) $slots:用来访问被插槽分发的内容。

(6) $refs:一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。

(7) $attrs:包含了父作用域中不作为 prop 被识别(且获取)的 attribute 绑定(class 和 style 除外)。

(8) $listeners:包含了父作用域中的(不含 .native 修饰器的)v-on 事件监听器。

78、Vue 的实例方法有哪些?

Vue 实例方法可以分为:数据、事件、生命周期。

(1) 数据类:

$set(object,key,value):为对象 object 添加属性 key,并指定属性值 value。

$delete(object,key):删除对象 object 的属性 key。

$watch(data,callback[,options]):侦听的数据 data,回调函数,可选项。

(2) 事件类:

$on(event,callback):绑定事件。

$once(event,callback):绑定一次事件。

$off():解绑事件。

$emit():分发事件。

(3) 生命周期类:

$mount([elementOrSelector]):手动挂载 Vue 实例。

$forceUpdate():迫使 Vue 实例重新渲染。

$nextTick([callback]):将回调延迟到下次 DOM 更新循环之后执行。

$destory():销毁 Vue 实例。

79、列出 Vue 中常用的指令。

(1) v-text:标签中间的显示文本,相当于 DOM 中的 innerText。

(2) v-html:标签中间的显示内容,相当于 DOM 中的 innerHTML。

(3) v-show:设置标签内容显示或者隐藏,操作的是 css 中的 display。

(4) v-if:设置标签显示或者隐藏,在 DOM 树中是否存在标签。

(5) v-else

(6) v-else-if

(7) v-for:遍历数据。

(8) v-on:绑定事件。

(9) v-once

(10) v-bind:强制数据绑定。

(11) v-model:双向数据绑定。

(12) v-slot:插槽(包括普通插槽、具名插槽、作用域插槽)。

80、Vue 组件间多种通信方式。

(1) 常用的父子组件通讯方式:props$emit

(2) 通过 p a r e n t ∗ ∗ 、 ∗ ∗ parent**、** parentchildren 来访问组件实例,进而去获取或者改变父子组件的值。(不推荐使用)

(3) $ref:通过引用的方式获取子节点,常用于父组件中调用子组件的方法或者获取子组件的属性。

(4) provideinject:依赖注入,常见于插件或者组件库里。多个组件嵌套时,顶层组件 provide 提供变量,后代组件都可以通过 inject 来注入变量。

(5) EventBus:事件总线。任意两个组件通讯。

(6) a t t r s ∗ ∗ 、 ∗ ∗ attrs**、** attrslistener:适用于多级组件嵌套,但是不做中间处理的情况。比如祖先组件向孙子组件传递数据。

(7) Vuex:状态管理器。集中式存储管理所有组件的状态。

(8) localStorage/sessionStorage:持久化存储。

81、vuex 中的状态数据的响应式原理?

(1) 创建了一个 vm 对象。

(2) State 中的数据都是 vm 的 data 数据(是响应式的)。

(3) 组件中读取的 state 数据本质读取的就是 data 中的数据。

(4) 一旦更新了 state 中的数据,所有用到这个数据的组件就会自动更新。

82、如何避免页面刷新后 vuex 数据丢失问题?

(1) 绑定事件监听:在卸载前保存当前数据。

(2) 在初始时读取保存数据作为状态的初始值。

83、跳转/导航路由的两种基本方式?

(1) 声明式路由:<router-link to='/xxx' replace>xxx</router-link> 或者 a 标签。

(2) 编程式路由:this.$router.push(location) 或者 replace 的方式。

84、history 与 hash 路由的区别和原理?

区别:

(1) history:路由路径不带 #,刷新会携带路由路径,默认会出 404 问题,需要配置返回首页。

(2) hash:路由路径带 #,刷新不会携带路由路径,请求的总是根路径(返回首页),没有 404 问题。

原理:

(1) history:内部利用的是 history 对象的 pushState() 和 replaceState()(H5新语法)。

(2) hash:内部利用的是 location 对象的 hash 语法。

85、谈谈你对 keep-alive 的了解?

(1) keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们,防止重复渲染 DOM。

(2) keep-alive 是一个抽象组件,不会在 DOM 树中渲染。

(3) 使用时会多出两个生命周期:activated 和 deactivated。

86、你知道 vue 中 key 的作用和工作原理吗?说说你对它的理解。

(1) key 的作用主要是为了高效地更新虚拟 DOM,其原理是 vue 在 patch 过程中通过 key 可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个 patch 过程更加高效,减少 DOM 操作量,提高性能。

(2) 另外,若不设置 key,还可能在列表更新时引发一些隐蔽的 bug。

(3) vue 中在使用相同标签名元素的过渡切换时,也会使用到 key 属性,其目的也是为了让 vue 可以区分它们,否则 vue 只会替换其内部属性而不会触发过渡效果。

87、谈一谈你对 vue 组件化的理解?

(1) 组件是独立和可复用的代码组织单元。组件系统是 Vue 核心特性之一,它使开发者使用小型、独立和通常可复用的组件构建大型应用。

(2) 组件化开发能大幅提高应用开发效率、测试性、复用性等。

(3) 组件使用按分类有:页面组件、业务组件、通用组件。

(4) vue 的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们基于 VueComponent,扩展于 Vue。

(5) vue 中常见组件化技术有:属性 prop、自定义事件、插槽等,它们主要用于组件通信、扩展等。

(6) 合理的划分组件,有助于提升应用性能。

(8) 组件应该是高内聚、低耦合的。

(9) 遵循单向数据流的原则。

88、谈一谈对 vue 设计原则的理解?

(1) 易用性。vue 提供数据响应式、声明式模板语法和基于配置的组件系统等核心特性。这些使我们只需要关注应用的核心业务即可,只要会写 js、htmI 和 css 就能轻松编写 vue 应用。

(2) 灵活性。渐进式框架的最大优点就是灵活性,如果应用足够小,我们可能仅需要 vue 核心特性即可完成功能;随着应用规模不断扩大,我们才可能逐渐引入路由、状态管理、vue-cli 等库和工具,不管是应用体积还是学习难度都是一个逐渐增加的平和曲线。

(3) 高效性。超快的虚拟 DOM 和 diff 算法使我们的应用拥有最佳的性能表现。

追求高效的过程还在继续,vue3 中引入 Proxy 对数据响应式改进以及编译器中对于静态内容编译的改进都会让 vue 更加高效。

89、谈谈你对 MVC、MVP 和 MVVM 区别的理解?

这三者都是框架模式,它们设计的目标都是为了解决 Model 和 View 的耦合问题。

MVC 模式出现较早,主要应用在后端,如 Spring MVC、ASP.NET MVC 等,在前端领域的早期也有应用,如 Backbone.js。它的优点是分层清晰,缺点是数据流混乱、灵活性带来的维护性问题。

MVP 模式是 MVC 的进化形式,Presenter 作为中间层负责 MV 通信, 解决了两者耦合问题,但 P 层过于臃肿会导致维护问题。

MVVM 模式在前端领域有广泛应用,它不仅解决 MV 耦合问题,还同时解决了维护两者映射关系的大量繁杂代码和 DOM 操作代码,在提高开发效率、可读性同时还保持了优越的性能表现。

90、谈谈 vuex 的使用及其理解?

vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 提交修改信息,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。

而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。

91、vue-router 中导航钩子函数有哪些?

全局的钩子函数:

(1) beforeEach(to, from, next):路由改变前调用。

to:即将要进入的目标路由对象;

from:当前正要离开的路由对象;

next:路由控制函数,具体的执行效果依赖 next 方法调用的参数。

(2) afterEach(to, from):路由改变后调用。

单个路由独享钩子函数:

(1) beforeEnter(to, from, next):在路由配置上直接定义。

组件内的钩子函数:

(1) beforeRouteEnter(to, from, next):在渲染该组件的对应路由被 confirm 前调用。

(2) beforeRouteUpdate(to, from, next):在当前路由改变,但该组件被复用时调用。

(3) beforeRouteLeave(to, from ,next):当导航离开该组件的对应路由时被调用。

92、什么是递归组件?

概念:组件是可以在它们自己的模板中调用自身的。

递归组件,一定要有一个结束的条件,否则就会使组件循环引用,最终出现错误,我们可以使用 v-if=“false” 作为递归组件的结束条件。当遇到 v-if 为 false 时,组件将不会再进行演染。

93、你了解哪些 vue 性能优化的方式?

(1) 路由懒加载。Vue 是单页面应用,可能会有很多的路由引入,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就要更加高效了。

(2) 图片懒加载。对于图片过多的页面,为了加速页面加载速度,很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的提升,也提高了用户体验。

(3) keep-alive。对其包裹的组件进行状态的缓存,使其不被销毁,避免重新渲染。

(4) 区分使用 v-if 和 v-show。

(5) 区分使用 computed 和 watch。

(6) v-for 遍历必须为 item 添加 key,且避免同时使用 v-if。

(7) 事件的销毁。

(8) 按需引入第三方组件。

(9) 长列表性能优化。

(10) 子组件分隔。

94、你对 Vue3.0 的新特性有没有了解?

(1) 更快:虚拟 DOM 重写;优化 slots 的生成;静态树提升;基于 Proxy 的响应式系统。

(2) 更小:通过摇树优化核心库体积。

(3) 更容易维护:TypeScript + 模块化。

(4) 更加友好。

(5) 跨平台:编辑器核心和运行时核心与平台无关,使得 vue 更容易与任何平台(Web、Android、IOS)一起使用。

95、vue-router 的工作原理?

(1) URL 改变;

(2) 触发监听事件;

(3) 改变 vue-router 里面的 current 变量;

(4) 监视 current 变量;

(5) 获取对应的组件;

(6) Render 新组件。

96、install 在 Vuex & Vue-Router 中的处理?

Vue-Router 其实是在 install 函数里面使用了一个全局混入,在 beforeCreate 这个生命周期触发的时候把 this. o p t i o n s . r o u t e r 挂载到 V u e 的原型上,这样我们就可以使用 t h i s . options.router 挂载到 Vue 的原型上,这样我们就可以使用 this. options.router挂载到Vue的原型上,这样我们就可以使用this.router 来调用 router 实例。

Router.install = function(_Vue) {
    _Vue.mixin({
        beforeCreate() {
            if (this.$options.router) {
                _Vue.prototype.$router = this.$options.router
            }
        }
    })
}                                                         
export default Router;

_Vue.mixin 全局混入,相当于所有组件中混入这个方法;beforeCreate 是 Vue 的一个生命周期,在 create 之前执行。

97、服务端和客户端渲染的区别?

(1) 二者本质的区别:是谁来完成了 html 的完整拼接。服务端渲染是在服务端生成 DOM 树;客户端渲染是在客户端生成 DOM 树。

(2) 响应速度:服务端渲染会加快页面的响应速度;客户端渲染页面的响应速度慢。

(3) SEO 优化:服务端渲染因为是多个页面,更有利于爬虫爬取信息;客户端渲染不利于 SEO 优化。

(4) 开发效率:服务端渲染逻辑分离的不好,不利于前后端分离,开发效率低;客户端渲染是采用前后端分离的方式开发,效率更高,也是大部分业务采取的渲染方式。

98、Vue 中 mixin 的作用?

多个组件可以共享数据和方法,在使用 mixin 的组件中引入后,mixin 中的方法和属性也就并入到该组件中,可以直接使用。两者的钩子函数都会被调用,mixin 中的钩子首先执行。

99、什么是 vue 路由守卫?

路由守卫就是路由跳转过程中的一些钩子函数,在路由跳转的时候,做一些判断或其它的操作(权限验证)。

100、谈谈你对前端路由的理解?

概念:负责事件监听,根据不同的用户事件,显示不同的页面内容。

本质:用户事件与事件处理函数之间的对于关系。

优点:

(1) 用户体验好,页面初始化后,只需要根据路由变换页面内容,不需要再向服务器发送请求,内容变换速度快。

(2) 可以在浏览器中输入指定想要访问的 url。

(3) 实现了前后端分离,方便开发。

八、小程序

101、微信小程序文件格式及作用?

(1) WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。内部主要是微信自己定义的一套组件。

(2) WXSS(WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。

(3) js:逻辑处理,网络请求。

(4) json:小程序设置,如页面注册,页面标题及 tabBar。

102、微信小程序与 Vue 区别?

(1) 生命周期不一样,微信小程序生命周期比较简单。

(2) 数据绑定方式不同。微信小程序数据绑定需要使用 {{}};Vue 通过 : 就可以。

(3) 显示与隐藏元素不同。Vue 中使用 v-if 和 v-show 控制元素的显示和隐藏;小程序中使用 wx-if 和 hidden 控制元素的显示和隐藏。

(4) 事件处理方式不同。小程序中用 bindtap(bind+event),或者 catchtap(catch+event)绑定事件;Vue 中使用 v-on:event 或者 @event 绑定事件。

(5) 数据双向绑定也不一样。在 vue 中,只需要在表单元素上加上 v-model,然后再绑定 data 中对应的一个值,当表单元素内容发生变化时,data 中对应的值也会相应改变。微信小程序必须获取到表单元素改变的值,然后再把值赋给一个 data 中声明的变量。

103、简述微信小程序原理。

(1) 微信小程序采用 JavaScript、WXML、WXSS 三种技术进行开发,本质就是一个单页面应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原生的各种接口。

(2) 微信小程序的架构,是数据驱动的架构模式,它的 UI 和数据是分离的,所有的页面更新,都需要通过对数据的更改来实现。

(3) 微信小程序分为两个部分 webview 和 appService 。其中 webview 主要用来展现 UI ;appService 有来处理业务逻辑、数据及接口调用。它们在两个进程中运行,通过系统层 JSBridge 实现通信,实现 UI 的渲染、事件的处理。

104、微信小程序的生命周期函数?

(1) onLoad():页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。

(2) onShow():页面显示/切入前台时触发。

(3) onReady():页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。

(4) onHide():页面隐藏/切入后台时触发。如 navigateTo 或底部 tab 切换到其他页面,小程序切入后台等。

(5) onUnload():页面卸载时触发。如 redirectTo 或 navigateBack 到其他页面时。

105、微信小程序的优劣势?

(1) 优势:即用即走,不用安装,省流量,省安装时间,不占用桌面;依托微信流量,天生推广传播优势;开发成本比 App 低。

(2) 缺点:用户留存,即用即走是优势,也存在一些问题;入口相对传统 App 要深很多;限制较多,页面大小不能超过 2M;不能打开超过 10 个层级的页面。

106、微信小程序页面间有哪些传递数据的方法?

(1) 使用全局变量实现数据传递。在 app.js 文件中定义全局变量 globalData,将需要存储的信息存放在里面;

(2) 使用 wx.navigateTo 与 wx.redirectTo 的时候,可以将部分数据放在 url 里面,并在新页面 onLoad 的时候初始化;

(3) 使用本地缓存 Storage 相关。

107、微信小程序对 wx:if 和 hidden 使用的理解?

(1) wx:if:有更高的切换消耗。

(2) hidden:有更高的初始渲染消耗。

(3) 因此,如果需要频繁切换的情景下,用 hidden 更好;如果在运行时条件不大可能改变则 wx:if 较好。

108、微信小程序与 H5 的区别?

(1) 运行环境的不同。传统的 HTML5 的运行环境是浏览器,包括 WebView,而微信小程序的运行环境并非完整的浏览器,是微信开发团队基于浏览器内核完全重构的一个内置解析器,针对小程序专门做了优化,配合自己定义的开发语言标准,提升了小程序的性能。

(2) 开发成本的不同。只在微信中运行,所以不用再去顾虑浏览器兼容性,不用担心生产环境中出现不可预料的奇妙 BUG。

(3) 获取系统级权限的不同。

九、uni-app

109、概述一下 uniapp 的优缺点?

优点:

(1) 一套代码可以生成多端。

(2) 学习成本低,语法是 vue 的,组件是小程序的。

(3) 拓展能力强。

(4) 使用 HBuilderX 开发。

(5) 突破了系统对 H5 调用原生能力的限制。

缺点:

(1) 问世时间短,很多地方不完善。

(2) 社区不大。

(3) 官方对问题的反馈不及时。

(4) 在 Android 平台上比微信小程序和 IOS 差。

(5) 文件命名受限。

110、vue、微信小程序、uniapp 中属性的绑定。

vue 和 uniapp 动态绑定一个变量的值为元素的某个属性的时候,会在属性前面加上冒号 :

小程序绑定某个变量的值为元素属性时,会用两对大括号 {{ }} 括起来,如果不加括号,会被认为是字符串。

111、jQuery、vue、小程序、uniapp 中的本地数据存储和接收。

jQuery:

存储:$.cookie(‘key’, ‘value’)

获取:$.cookie(‘key’)

vue:

存储:localstorage.setItem(‘key’, ‘value’)

获取:localstorage.getItem(‘key’)

微信小程序:

存储:wx.setStorage({ key: ‘key’, data: ‘value’ }) / wx.setStorageSync(‘key’, ‘value’)

获取:wx.getStorage({ key: ‘key’, success: (res) => { } }) / wx.getStorageSync(‘key’)

uniapp:

存储:uni.setStorage({ key: ‘key’, data: ‘value’ }) / uni.setStorageSync(‘key’, ‘value’)

获取:uni.getStorage({ key: ‘key’, success: (res) => { } }) / uni.getStorageSync(‘key’)

112、在 uniapp 页面中可以调用哪些接口?

(1) getApp():用于获取当前应用实例,一般用于获取 globalData。

(2) getCurrentPages():用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。

(3) uni.$emit(eventName, OBJECT):触发全局的自定义事件,附加参数都会传给监听器回调函数。

(4) **uni. o n ( e v e n t N a m e , c a l l b a c k ) ∗ ∗ :监听全局的自定义事件,事件由 ‘ u n i . on(eventName, callback)**:监听全局的自定义事件,事件由 `uni. on(eventName,callback):监听全局的自定义事件,事件由uni.emit` 触发,回调函数会接收事件触发函数的传入参数。

(5) **uni. o n c e ( e v e n t N a m e , c a l l b a c k ) ∗ ∗ :监听全局的自定义事件,事件由 ‘ u n i . once(eventName, callback)**:监听全局的自定义事件,事件由 `uni. once(eventName,callback):监听全局的自定义事件,事件由uni.emit` 触发,但仅触发一次,在第一次触发之后移除该监视器。

(6) uni.$off([eventName, callback]):移除全局自定义事件监听器。如果没有传入参数,则移除 App 级别的所有事件监听器。

113、uniapp 页面的生命周期函数中常用的有哪些?

(1) onInit:监听页面初始化。

(2) onLoad:监听页面加载。

(3) onShow:监听页面显示。

(4) onReady:监听页面初次渲染完成。

(5) onHide:监听页面隐藏。

(6) onUnload:监听页面卸载。

(7) onResize:监听窗口尺寸变化。

(8) onPullDownRefresh:监听用户下拉动作。

(9) onReachBottom:监听页面滚动到底部的事件。

114、简述 rpx、vh、vw 的区别?

rpx:相当于把屏幕宽度分为 750 份,1 份就是 1rpx。

vw:视窗宽度,1vw 等于视窗宽度的 1%。

vh:视窗高度,1vh 等于视窗高度的 1%。

十、编程题

115、手写一个函数,深度比较两个对象是否完全一样。

/**
 * 判断是否是对象或数组
 * @param obj 对象
 */
function isObject(obj) {
    return typeof obj === 'object' && obj !== null
}
/**
 * 两个对象是否完全一样
 * @param obj1 对象1
 * @param obj2 对象2
 */
function isEqual(obj1, obj2) {
    if (!isObject(obj1) || !isObject(obj2)) {
        // 值类型(参与 equal 的一般不会是函数)
        return obj1 === obj2
    }
    if (obj1 === obj2) {
        return true
    }
    // 两个都是对象或数组,而且不相等
    // 1.先取出 obj1 和 obj2 的 keys,比较个数
    const obj1Keys = Object.keys(obj1)
    const obj2Keys = Object.keys(obj2)
    if (obj1Keys.length !== obj2Keys.length) {
        return false
    }
    // 2.以 obj1 为基准,和 obj2 依次递归比较
    for (let key in obj1) {
        // 比较当前 key 的 val —— 递归
        const res = isEqual(obj1[key], obj2[key])
        if (!res) {
            return false
        }
    }
    // 3.全相等
    return true
}

// 示例
const obj1 = {
    a: 10,
    b: {
        x: 100,
        y: 200
    }
}
const obj2 = {
    a: 10,
    b: {
        x: 100,
        y: 200
    }
}
console.log(isEqual(obj1, obj2)) // true

116、手写一个深拷贝函数

/**
 * 深拷贝
 * @param obj 要拷贝的对象
 */
function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        //  obj 是 null,或者不是对象和数组,直接返回
        return obj
    }
    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }
    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归
            result[key] = deepClone(obj[key])
        }
    }
    // 返回结果
    return result
}

// 示例
const obj1 = {
    name: '张三',
    age: {
        realAge: 20
    },
    arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.name = '李四'
obj2.age.realAge = 22
obj2.arr[0] = 'x'
console.log(obj1.name) // 张三
console.log(obj1.age.realAge) // 20
console.log(obj1.arr[0]) // a

117、给定两个数组,要求合并为一个,并去重排序。

/**
 * 数组合并
 * @param arr1 第1个数组
 * @param arr2 第2个数组
 */
function merge(arr1, arr2) {
    // 合并数组
    const newArr = [...arr1, ...arr2]
    // 去重
    const newSet = new Set(newArr)
    const mergeArr = Array.from(newSet)
    // 排序
    mergeArr.sort((a, b) => {
        return a - b;
    })
    return mergeArr
}

// 示例
const arr1 = [1, 9, 3, 6, 12]
const arr2 = [0, 2, 10, 3, 12, 12]
console.log(merge(arr1, arr2))

118、实现一个函数,可解析输入的 URL 中的 Query 参数。

/**
 * 获取参数
 * @param url 链接地址
 */
function getParams(url) {
    const obj = new Object()
    const data = url.slice(url.indexOf('?') + 1, url.length)
    const params = data.substr(1).split('&')
    for (let i = 0; i < params.length; i++) {
        const param = params[i].split('=')
        obj[param[0]] = param[1]
    }
    return obj
}

// 示例
var url = "http://www.baidu.com/we/index.html?id=123456&name=jack&age=20";
console.log(getParams(url))

119、完成一个函数,接受数组作为参数,数组元素为整数或者数组,数组元素包含整数或数组,函数返回扁平化后的数组。

/**
 * 数组扁平化
 * @param data 数组
 */
function flat(data, result) {
    for (let i = 0; i < data.length; ++i) {
        let d = data[i]
        if (typeof d === 'number') {
            result.push(d)
        } else {
            flat(d, result)
        }
    }
}

// 示例
const data = [1, [2, [[3, 4], 5], 6]]
const result = []
flat(data, result)
console.log(result)

120、Vue 动态绑定类名的几种方法?

(1) 我们可以传给 v-bind:class 一个对象,以根据值变化动态地绑定单个 class。此外,v-bind:class 指令也可以与普通的 class 属性共存。

<template>
	<div class="static" :class="{ 'active': isActive == true }"></div>
</template>

<script>
	export default {
		data() {
			return {
				isActive: true
			}
		}
	}
</script>

(2) 在对象中传入更多属性来动态切换多个 class。

<template>
	<div :class="{ 'active': isActive, 'text-danger': hasError }"></div>
</template>

<script>
	export default {
		data() {
			return {
				isActive: true,
				hasError: false
			}
		}
	}
</script>

(3) 计算属性的方式绑定 class。

<template>
	<div :class="classNames"></div>
</template>

<script>
	export default {
		data() {
			return {
				isActive: true,
				hasError: false,
				error: null
			}
		},
		computed: {
			classNames: function() {
				return {
					'active': this.isActive && !this.hasError,
					'text-danger': this.hasError && this.error.type === 'fatal'
				}
			}
		}
	}
</script>

(4) 数组的方式绑定 class。

<template>
	<div :class="[activeClass, errorClass]"></div>
</template>

<script>
	export default {
		data() {
			return {
				activeClass: 'active',
				errorClass: 'text-danger'
			}
		}
	}
</script>

(5) 三元表达式动态切换 class。

<template>
	<div>
		<div :class="[isActive ? activeClass : '', errorClass]"></div>
		<div :class="[{ 'active': isActive }, errorClass]"></div>
	</div>
</template>

<script>
	export default {
		data() {
			return {
				isActive: true,
				activeClass: 'active',
				errorClass: 'text-danger'
			}
		}
	}
</script>

121、实现路由组件传递参数。

(1) 通过 params 传递。

const routes = [
  { path: '/detail/:id', name: 'detail', component: Detail }
]

this.$router.push('/detail/10')
this.$router.push({ path: '/detail/10' })
this.$router.push({ name: 'detail', params: { id: 10 } })

(2) 通过 query 传递。

this.$router.push('/detail/10?kind=car')
this.$router.push({ path: '/detail/10', query: { kind: 'car' } })
this.$router.push({ name: 'detail', params: { id: 10 }, query: { kind: 'car' } })

const kind = this.$route.query.kind

(3) 通过 hash 传递。

this.$router.push('/detail/10#car')
this.$router.push({ path: '/detail/10', hash: '#car' })
this.$router.push({ name: 'detail', params: { id: 10 }, hash: 'car' })

const kind = this.$route.hash.slice(1)

(4) 通过 props 传递。

const routes = [
  { 
    path: '/hello',
    component: Hello,
    props: {
      name: 'World'
    }
  }
]

const Detail = {
  props: {
    name: {
      type: String,
      default: 'Vue'
    }
  },
  template: '<div> Hello {{ name }}</div>'
}

122、请用原型链或者 class 实现一个计算器,能够实现链式加减乘除。

使用原型链实现:

/**
 * 计算器
 * @param num 初始数字
 */
function myCalculator(num) {
  this.num = num;
}
// 加法
myCalculator.prototype.add = function (n) {
  this.num = this.num + n;
  return this;
};
// 减法
myCalculator.prototype.minus = function (n) {
  this.num = this.num - n;
  return this;
};
// 乘法
myCalculator.prototype.multi = function (n) {
  this.num = this.num * n;
  return this;
}
// 除法
myCalculator.prototype.div = function (n) {
  this.num = this.num / n;
  return this;
}
// 幂方
myCalculator.prototype.pow = function (n) {
  this.num = Math.pow(this.num, n);
  return this;
}

// 示例
const calculator = new myCalculator(21);
const res = calculator.add(1).minus(2).multi(3).div(4).pow(2);
console.log(res.num);

使用 class 实现:

/**
 * 计算器类
 */
class myCalculator {
  // 构造函数
  constructor(num) {
    this.num = num;
  }
  // 加法
  add(n) {
    this.num += n;
    return this;
  }
  // 减法
  minus(n) {
    this.num -= n;
    return this;
  }
  // 乘法
  multi(n) {
    this.num *= n;
    return this;
  }
  // 除法
  div(n) {
    this.num /= n;
    return this;
  }
  // 幂方
  pow(n) {
    this.num = Math.pow(this.num, n);
    return this;
  }
}

// 示例
const calculator = new myCalculator(21);
const res = calculator.add(1).minus(2).multi(3).div(4).pow(2);
console.log(res.num);

123、请手写一个函数,求数列前 N 项和。

数列的形式如下:

1、1、2、3、5、8、13、21、34……

/**
 * 数列求和
 * @param n 数量
 */
function sum(n) {
  if (n === 1 || n === 2) {
    return 1;
  }
  return sum(n - 2) + sum(n - 1);
}

// 示例
console.log(sum(2));  // 1
console.log(sum(5));  // 5
console.log(sum(9));  // 34

124、请手写一个函数,判断字符串是否是回文。

回文例如 abcba 或者 abccba 这样的字符串形式即可构成回文。

/**
 * 判断字符串是否是回文
 * @param str 字符串
 */
function isPalindrome(str) {
  const normalized = str.toLowerCase().match(/[a-z]/gi);
  return normalized.join('') === normalized.reverse().join('');
}

// 示例
console.log(isPalindrome('abcba'));   // true
console.log(isPalindrome('abccba'));  // true
console.log(isPalindrome('Go dog.')); // true
console.log(isPalindrome('hello'));   // false

125、请手写一个函数,解决小孩报数问题。

有 30 个小孩儿,编号从 1~30,围成一圈依次报数:1、2、3,数到 3 的小孩儿退出这个圈,然后下一个小孩重新报数:1、2、3,问最后剩下的那个小孩儿的编号是多少?

/**
 * 小孩报数问题
 * @param count 总人数
 * @param num 退出圈的数字
 */
function childNum(count, num) {
  // 声明一个数组,保存每个小孩的编号(1~30)
  const players = [];
  for (let i = 0; i < count; i++) {
    players[i] = i + 1;
  }
  let exitCount = 0; // 离开人数
  let counter = 0; // 当前报数
  let curIndex = 0; // 当前下标
  // 循环到剩1名未离开
  while (exitCount < count - 1) {
    // 离开的小孩编号标0,编号不为0则正常报数
    if (players[curIndex] !== 0) {
      counter++;
    }
    // 命中数字,等待离开
    if (counter == num) {
      players[curIndex] = 0; // 小孩离开,编号标0
      counter = 0; // 重置报数,下一个报1
      exitCount++; // 离开人数加1
    }
    // 未命中数字,下一位报数
    curIndex++;
    // 如果到最后一个小孩,重置下标,下一个从第1个小孩开始
    if (curIndex == count) {
      curIndex = 0;
    }
  }
  // 遍历所有小孩,找到编号未标0的那位
  return players.find((item) => {
    return item !== 0;
  });
}

// 示例
const num = childNum(30, 3);
console.log(num); // 29

126、实现数组的乱序输出。

/**
 * 数组乱序输出
 * @param arr 原始数组
 */
function shuffle(arr) {
  arr.sort((a, b) => {
    return Math.random() > 0.5 ? -1 : 1;
  });
  return arr;
}

// 示例
let arr = [1, 2, 3, 4, 5, 6, 7];
arr = shuffle(arr);
console.log(arr);

127、使用 CSS 实现 4 个角有装饰的边框。

<html>
  <head>
    <title>4角装饰的边框</title>
    <style>
      html {
        width: 100%;
        height: 100%;
      }
      body {
        width: 100%;
        height: 100%;
        background-color: #205ca4;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .box {
        width: 600px;
        height: 400px;
        border: 1px solid #21a7e1;
        box-shadow: 5px 5px 10px 10px rgba(24, 68, 124, 0.4);
        border-radius: 6px;
        position: relative;
      }
      .corner {
        z-index: 999;
        position: absolute;
        width: 19px;
        height: 19px;
        background: rgba(0, 0, 0, 0);
        border: 4px solid #1fa5f1;
      }
      .corner_left_top {
        top: -2px;
        left: -2px;
        border-right: none;
        border-bottom: none;
        border-top-left-radius: 6px;
      }
      .corner_right_top {
        top: -2px;
        right: -2px;
        border-left: none;
        border-bottom: none;
        border-top-right-radius: 6px;
      }
      .corner_right_bottom {
        bottom: -2px;
        right: -2px;
        border-left: none;
        border-top: none;
        border-bottom-right-radius: 6px;
      }
      .corner_left_bottom {
        bottom: -2px;
        left: -2px;
        border-right: none;
        border-top: none;
        border-bottom-left-radius: 6px;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="corner corner_left_top"></div>
      <div class="corner corner_right_top"></div>
      <div class="corner corner_right_bottom"></div>
      <div class="corner corner_left_bottom"></div>
    </div>
  </body>
</html>

在这里插入图片描述

128、使用 CSS 实现骰子 2 点的布局。

<html>
  <head>
    <title>骰子2点</title>
    <style>
      .box {
        width: 200px;
        height: 200px;
        background-color: wheat;
        border-radius: 20px;
        box-sizing: border-box;
        padding: 20px;
        display: flex;
        justify-content: space-between;
      }
      .item {
        width: 40px;
        height: 40px;
        background-color: red;
        border-radius: 20px;
      }
      .item_right_bottom {
        align-self: flex-end;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item item_left_top"></div>
      <div class="item item_right_bottom"></div>
    </div>
  </body>
</html>

128、使用 CSS 实现骰子 2 点的布局。

在这里插入图片描述

<html>
  <head>
    <title>骰子2点</title>
    <style>
      .box {
        width: 200px;
        height: 200px;
        background-color: wheat;
        border-radius: 20px;
        box-sizing: border-box;
        padding: 20px;
        display: flex;
        justify-content: space-between;
      }
      .item {
        width: 40px;
        height: 40px;
        background-color: red;
        border-radius: 20px;
      }
      .item_right_bottom {
        align-self: flex-end;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item item_left_top"></div>
      <div class="item item_right_bottom"></div>
    </div>
  </body>
</html>
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值