收集了一下自己喜欢的前端面试题

1,977 阅读22分钟

简要

收集了一些自己比较觉得好的面试题(如有侵权请告知删除)


HTML

HTML语义化

HTML语义化就是让页面内容结构化,它有如下优点

1、易于用户阅读,样式丢失的时候能让页面呈现清晰的结构。
2、有利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权重。
3、方便其他设备解析,如盲人阅读器根据语义渲染网页
4、有利于开发和维护,语义化更具可读性,代码更好维护,与CSS3关系更和谐
如:<header>代表头部 <nav>代表超链接区域 <main>定义文档主要内容 <article>可以表示文章、博客等内容 <aside>通常表示侧边栏或嵌入内容 <footer>代表尾部

HTML5新标签

有<header>、<footer>、<aside>、<nav>、<video>、<audio>、<canvas>等...

CSS

盒子模型

盒模型分为标准盒模型和怪异盒模型(IE模型)

box-sizingcontent-box //标准盒模型     元素的宽度等于style里的width+margin+border+padding宽度
box-sizingborder-box //怪异盒模型      元素宽度等于style里的width宽度

CSS新特性

transition:过渡 
transform:旋转、缩放、移动或者倾斜 
animation:动画 
gradient:渐变 
shadow:阴影 
border-radius:圆角

水平垂直居中

Flex布局

display: flex //设置Flex模式 
flex-direction: column //决定元素是横排还是竖着排 
flex-wrap: wrap //决定元素换行格式 
justify-content: space-between //同一排下对齐方式,空格如何隔开各个元素 
align-items: center //同一排下元素如何对齐 
align-content: space-between //多行对齐方式

水平居中

行内元素:display: inline-block;
块级元素:margin: 0 auto;
Flex: display: flex; justify-content: center

垂直居中

行高 = 元素高:line-height: height
flex: display: flex; align-item: center

多行元素的文本省略号

overflow : hidden; 
text-overflow: ellipsis; 
display: -webkit-box; 
-webkit-line-clamp: 3;  //设置可以展示多少行
-webkit-box-orient: vertical

JavaScript

JS的几条基本规范

1、不要在同一行声明多个变量
2、请使用===/!==来比较true/false或者数值
3、使用对象字面量替代new Array这种形式
4、不要使用全局变量
5Switch语句必须带有default分支
6、函数不应该有时候有返回值,有时候没有返回值
7For循环必须使用大括号
8IF语句必须使用大括号
9for-in循环中的变量 应该使用var关键字明确限定作用域,从而避免作用域污染

JS的基本数据类型/引用类型数据

数据类型
NumberStringBooleanNullundefinedobjectsymbol、bigInt

基本类型(单类型)
StringNumberbooleannullundefined

引用类型
object。里面包含的 functionArrayDate

数组操作

var arr =[1,2,3,4,5];
arr[0] arr[arr.length-1]//通过下标找到数组中指定的元素,访问数组的元素
arr.map: 遍历数组,返回回调返回值组成的新数组
arr.forEach: 无法break,可以用try/catchthrow new Error来停止
arr.filter: 过滤
arr.some: 有一项返回true,则整体为true
arr.every: 有一项返回false,则整体为false
arr.join: 通过指定连接符生成字符串
arr.toString() //数组转成字符串
arr.push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项【有误】
arr.unshift / shift: 头部推入和弹出,改变原数组,返回操作项【有误】
arr.sort(fn) / reverse: 排序与反转,改变原数组
arr.concat: 连接数组,不影响原数组, 浅拷贝
arr.slice(start, end): 返回截断后的新数组,不改变原数组
arr.splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组
arr.indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
arr.reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)
arr=[];//清空数组
typeof(arr) //判断是否是数组
Math.max.applynull,arr)或 Math.max(…arr)//找到数组中的最大值
Math.min.apply(null,arr)或 Math.min(…arr) //找到数组中的最小值
数组与其他值的运算(使用"+“会自动转为string,”-"会自动转为number)
arr2=[…arr1] // 数组赋值(对象扩展运算符的写法)
[…arr1, …arr2, …arr3] //合并数组
// ES5
var arr1 = str.split(’’); // [ “h”, “e”, “l”, “l”, “o” ] ////字符串转化成数组
// ES6
var arr2 = […str]; // [ “h”, “e”, “l”, “l”, “o” ] //字符串转化成数组

数组去重

**方法一**:利用ES6 Set去重(ES6中最常用)
let arr1=[1,2,1,2,6,3,5,69,66,7,2,1,4,3,6,8,9663,8] 	let set = new Set(arr1); 
//去重,但类型不是数组 console.log(set) // {1,2,6,3,5,69,66,7,4,8,9663,8]
Array.from(set );Array.of(set ) //转换成数组并去重

**方法二**:利用indexOf
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
        if (array .indexOf(arr[i]) === -1) {
            array .push(arr[i])
        }
    }
    return array;
}

**方法三**:利用includes
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array =[];
    for(var i = 0; i < arr.length; i++) {
            if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
                    array.push(arr[i]);
              }
    }
    return array
}

**方法四**:利用sort()
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return;
    }
    arr = arr.sort()
    var arrry= [arr[0]];
    for (var i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) {
            arrry.push(arr[i]);
        }
    }
    return arrry;
}

数组迭代

filter: var arr=[1,3,6,8,45,34,90,122,9,0]; 
var array = arr.filter(function(element){ return element>10 }) console.log(array)
//筛选数组中大于10的值, [45, 34, 90, 122]
//查看原数组,并没有改变
console.log(arr) // [1,3,6,8,45,34,90,122,9,0]

操作字符串的方法

var str = “hello world”;
str[0];//通过下标找到字符串指定位置的字符
str.toUpperCase() //转大写
str.toLowerCase() //转小写str.indexOf() //寻找字符串中的元素,是否存在某个字符串,没有返回-1;str.lastIndexOf() // 返回指定值在调用该方法的字符串中最后出现的位置,如果没找到则返回-1
str.concat() // 拼接,合并
str.slice(beginSlice,endSlice) // 返回被操作字符的子字符串,第一个参数为开始位置,第二个参数为结束位置,前包后不包(不改变原字符串)
str.substring() //
str.substr() //返回指定位置开始的指定长度的字符串
str.split() //分隔符将一个字符串分割成多个字符串,转化成数组
str.trim() //删除元素前置及后缀的所有空格
str.repeat(count) // 构造并返回一个新字符串,该字符串是循环完成后的新字符串(不能为-1)“abc”.repeat(0) // “” “abc”.repeat(1) // “abc” “abc”.repeat(2) // “abcabc”
str.startsWith(“str”) //检测字符串是不是以“str”开头的,根据判断返回true,false
str.endsWith(“str”) //是不是以“str”结尾的str.includes(“aaa”) //检测一个字符串是否在另一个字符串里包含,区分大小写
str.charAt()//根据下标查询访问字符串的某个字符,还可以使用 [ ] 的形式来访问,中括号填写的是字符串的下标
拼接字符串:
“”+""+"";
"\ \ ";
${};
模板字面量:``

let c = 123
let a = 123 456;
console.log(a) // 123
// 456
字符串去重:
[...new Set(str)].join("")

JS有哪些内置对象

ObjectJavaScript中所有对象的父对象

数据封装对象:ObjectArrayBooleanNumberString
其他对象:FunctionArgumentsMathDateRegExpError

get请求传参长度的误区

误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的(取决于数据库字段存储大小)

实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:
1、HTTP 协议 未规定 GET 和POST的长度限制
2GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度
3、不同的浏览器和WEB服务器,限制的最大长度不一样
4、要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte

闭包

函数A 里面包含了 函数B,而 函数B 里面使用了 函数A 的变量,那么 函数B 被称为闭包。
又或者:闭包就是能够读取其他函数内部变量的函数
function A() {
  var a = 1;
  function B() {
    console.log(a);
  }
  return B();
}

**闭包的特征**

函数内再嵌套函数
内部函数可以引用外层的参数和变量
参数和变量不会被垃圾回收制回收

**对闭包的理解**

使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,
缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
在js中,函数即闭包,只有函数才会产生作用域的概念
闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中
闭包的另一个用处,是封装对象的私有属性和私有方法

**闭包的好处**

能够实现封装和缓存等

**闭包的坏处**

就是消耗内存、不正当使用会造成内存溢出的问题

**使用闭包的注意点**

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露

解决方法是:在退出函数之前,将不使用的局部变量全部删除

介绍原型 原型链 作用域链

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

**原型和原型链的关系**
instance.constructor.prototype = instance.__proto__

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

**作用域链的特点**
所谓作用域,就是变量或者是函数能作用的范围。
作用域链就是变量随着作用长辈函数一级一级往上搜索,直到找到为止,找不到就报错,这个过程就是作用域链起的作用。

继承的方法有几种?

1构造函数继承

**原理**:利用call,apply,bind等方法,在构造函数内部去调用别的构造函数,为己所用.

**优点**:解决了原型链继承中引用类型的共享问题,同时可以在子类型构造函数中向超类型构造函数传递参数

**缺点**:原型对象的函数无法复用

2原型链继承

**存在的问题**:通过原型链实现继承时,原型实际上会变成另一个类型实例,而原先的实例属性也会变成原型属性,
如果该属性为引用类型时,所有的实例都会共享该属性,一个实例修改了该属性,其它实例也会发生变化,同时,
在创建子类型时,我们也不能向超类型的构造函数中传递参数

3组合继承

**原理**:构造函数继承与原型链继承的组合使用.

**优势**:既实现了原型对象内的函数复用,又可以在实例对象内灵活的传参.

**缺点**:无论什么情况下,都会调用两次超类型构造函数,一次是在创建子类型的时候,另一次是在子类型构造函数内部,子类型最终会包含超类型对象的全部实例属性,但是需要在调用子类型构造函数时重写这些属性

4原型式继承

原型式继承主要的借助原型可以基于已有的对象创建新的对象,基本思想就是创建一个临时性的构造函数,
然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例

5寄生式继承

寄生式继承其实和我们前面说的创建对象方法中的寄生构造函数和工程模式很像,创建一个仅用于封装继承过程的函数,该函数在内部以某种方法来增强对象,最后再返回该对象

**缺点**:不能实现函数的复用

6寄生组合式继承

寄生组合式继承只调用了一次SuperType构造函数,避免了在SubType.prototype上面创建的不必要的,多余的属性,
现在也是很多人使用这种方法实现继承

7ES6的Class继承

我们在前面创建对象中也提到了es6中可以使用Class来创建对象,而同样的道理,在es6中,也新增加了extends实现Class的继承,
Class 可以通过`extends`关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多

组件化和模块化

**组件化**

为什么要组件化开发
有时候页面代码量太大,逻辑太多或者同一个功能组件在许多页面均有使用,维护起来相当复杂,这个时候,
就需要组件化开发来进行功能拆分、组件封装,已达到组件通用性,增强代码可读性,维护成本也能大大降低

**组件化开发的优点**
很大程度上降低系统各个功能的耦合性,并且提高了功能内部的聚合性。这对前端工程化及降低代码的维护来说,
是有很大的好处的,耦合性的降低,提高了系统的伸展性,降低了开发的复杂度,提升开发效率,降低开发成本

组件化开发的原则
专一 可配置性 标准性 复用性 可维护性

**模块化**

**为什么要模块化**
早期的javascript版本没有块级作用域、没有类、没有包、也没有模块,这样会带来一些问题,如复用、依赖、冲突、代码组织混乱
等,随着前端的膨胀,模块化显得非常迫切

**模块化的好处**
避免变量污染 命名冲突 提高代码复用率 提高了可维护性 方便依赖关系管理

mouseover和mouseenter的区别

mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout

mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave

对This对象的理解

this总是指向函数的直接调用者(而非间接调用者)
如果有new关键字,this指向new出来的那个对象
在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window

Vue

说说你对 SPA 单页面的理解,它的优缺点分别是什么?

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,
SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,
UI 与用户的交互,避免页面的重新加载。

**优点:**
-   用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
-   基于上面一点,SPA 相对对服务器压力小;
-   前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

**缺点:**
-   初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
-   SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

vue生命周期

(1)生命周期是什么?

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

(2)各个生命周期的作用

生命周期描述
beforeCreate组件实例被创建之初,组件的属性生效之前
created组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用
beforeMount在挂载开始之前被调用:相关的 render 函数首次被调用
mountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
beforeUpdate组件数据更新之前调用,发生在虚拟 DOM 打补丁之前
update组件数据更新之后
activitedkeep-alive 专属,组件被激活时调用
deactivatedkeep-alive 专属,组件被销毁时调用
beforeDestory组件销毁前调用
destoryed组件销毁后调用

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

Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:

-   加载渲染过程
    父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
    
-   子组件更新过程
    父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

-   父组件更新过程
    父 beforeUpdate -> 父 updated

-   销毁过程
    父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

在哪个生命周期内调用异步请求?

可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,
可以将服务端端返回的数据进行赋值。但是本人推荐在 created 钩子函数中调用异步请求,
因为在 created 钩子函数中调用异步请求有以下优点:

-   能更快获取到服务端数据,减少页面 loading 时间;
-   ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

v-show 与 v-if 有什么区别?

v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。

所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。

绑定class的数组用法

对象方法 v-bind:class="{'orange': isRipe, 'green': isNotRipe}"

数组方法 v-bind:class="[class1, class2]"

行内 v-bind:style="{color: color, fontSize: fontSize+'px' }"

computed和watch有什么区别?

**computed:**
1. computed是计算属性,也就是计算值,它更多用于计算值的场景
2. computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算
3. computed适用于计算比较消耗性能的计算场景

**watch:**
1. 更多的是「观察」的作用,类似于某些数据的监听回调,用于观察props $emit或者本组件的值,当数据变化时来执行回调进行后续操作
2. 无缓存性,页面重新渲染时值不变化也会执行

**小结:**
1. 当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
2. 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化

直接给一个数组项赋值,Vue 能检测到变化吗?}

由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:

  • 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  • 当你修改数组的长度时,例如:vm.items.length = newLength
为了解决第一个问题,Vue 提供了以下操作方法:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

为了解决第二个问题,Vue 提供了以下操作方法:

// Array.prototype.splice
vm.items.splice(newLength)

父组件可以监听到子组件的生命周期吗?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

// Parent.vue
<Child @mounted="doSomething"/>
    
// Child.vue
mounted() {
  this.$emit("mounted");
}

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},
    
//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},    
    
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...     

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

谈谈你对 keep-alive 的了解?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:

  • 一般结合路由和动态组件一起使用,用于缓存组件;
  • 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
  • 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。

组件中 data 为什么是一个函数?

为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?

// data
data() {
  return {
	message: "子组件",
	childName:this.name
  }
}

// new Vue
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: {App}
})

因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

v-model 的原理?

我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

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

以 input 表单元素为例:

<input v-model='something'>
    
相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:
<ModelChild v-model="message"></ModelChild>

子组件:
<div>{{value}}</div>

props:{
    value: String
},
methods: {
  test1(){
     this.$emit('input', '小红')
  },
}

Vue 组件间通信有哪几种方式?

Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。

(1)props / $emit 适用 父子组件通信

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。

(2)ref$parent / $children 适用 父子组件通信

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父 / 子实例

(3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

(4)$attrs/$listeners 适用于 隔代组件通信

  • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

(5)provide / inject 适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

(6)Vuex 适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

你使用过 Vuex 吗?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

主要包括以下几个模块:

  • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
  • Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
  • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
  • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
  • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

使用过 Vue SSR 吗?说说 SSR?

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。

服务端渲染 SSR 的优缺点如下:

(1)服务端渲染的优点:

  • 更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
  • 更快的内容到达时间(首屏加载更快): SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;

(2) 服务端渲染的缺点:

  • 更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
  • 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。

vue-router 路由模式有几种?

vue-router 有 3 种路由模式:hash、history、abstract,对应的源码如下所示:

switch (mode) {
  case 'history':
	this.history = new HTML5History(this, options.base)
	break
  case 'hash':
	this.history = new HashHistory(this, options.base, this.fallback)
	break
  case 'abstract':
	this.history = new AbstractHistory(this, options.base)
	break
  default:
	if (process.env.NODE_ENV !== 'production') {
	  assert(false, `invalid mode: ${mode}`)
	}
}

其中,3 种路由模式的说明如下:

  • hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
  • history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
  • abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?

(1)hash 模式的实现原理

早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':

https://www.word.com#search

hash 路由模式的实现主要是基于下面几个特性:

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
  • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
  • 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用  JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
  • 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

history 路由模式的实现主要基于存在下面几个特性:

  • pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
  • 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

Vue 是如何实现数据双向绑定的?

Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据,如下图所示:

1.png

即:

  • 输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。
  • Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。

其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View。

Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。

实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。 1.png

Proxy 与 Object.defineProperty 优劣对比

Proxy 的优势如下:

  • Proxy 可以直接监听对象而非属性;
  • Proxy 可以直接监听数组的变化;
  • Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
  • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
  • Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

Object.defineProperty 的优势如下:

  • 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。

Vue 中的 key 有什么作用?

key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。

所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速

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

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

function createKeyToOldIdx (children, beginIdx, endIdx) {
  let i, key
  const map = {}
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key
    if (isDef(key)) map[key] = i
  }
  return map
}

你有对 Vue 项目进行哪些优化?

**(1)代码层面的优化**

-   v-if 和 v-show 区分使用场景
-   computed 和 watch 区分使用场景
-   v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
-   长列表性能优化
-   事件的销毁
-   图片资源懒加载
-   路由懒加载
-   第三方插件的按需引入
-   优化无限列表性能
-   服务端渲染 SSR or 预渲染

**(2)Webpack 层面的优化**

-   Webpack 对图片进行压缩
-   减少 ES6 转为 ES5 的冗余代码
-   提取公共代码
-   模板预编译
-   提取组件的 CSS
-   优化 SourceMap
-   构建结果输出分析
-   Vue 项目的编译优化

**(3)基础的 Web 技术的优化**

-   开启 gzip 压缩
-   浏览器缓存
-   CDN 的使用
-   使用 Chrome Performance 查找性能瓶颈

ES6

var、let、const之间的区别

var声明变量可以重复声明,而let不可以重复声明
var是不受限于块级的,而let是受限于块级
var会与window相映射(会挂一个属性),而let不与window相映射
var可以在声明的上面访问变量,而let有暂存死区,在声明的上面访问变量会报错
const声明之后必须赋值,否则会报错
const定义不可变的量,改变了就会报错
constlet一样不会与window相映射、支持块级作用域、在声明的上面访问变量会报错

解构赋值

**数组解构**
let [a, b, c] = [1, 2, 3]   //a=1, b=2, c=3
let [d, [e], f] = [1, [2], 3]    //嵌套数组解构 d=1, e=2, f=3
let [g, ...h] = [1, 2, 3]   //数组拆分 g=1, h=[2, 3]
let [i,,j] = [1, 2, 3]   //不连续解构 i=1, j=3
let [k,l] = [1, 2, 3]   //不完全解构 k=1, l=2

**对象解构**
let {a, b} = {a: 'aaaa', b: 'bbbb'}      //a='aaaa' b='bbbb'
let obj = {d: 'aaaa', e: {f: 'bbbb'}}
let {d, e:{f}} = obj    //嵌套解构 d='aaaa' f='bbbb'
let g;
(g = {g: 'aaaa'})   //以声明变量解构 g='aaaa'
let [h, i, j, k] = 'nice'    //字符串解构 h='n' i='i' j='c' k='e'

函数默认参数

在函数离 main先对参数做一个默认值赋值,然后再使用避免使用的过程中报错,再来看es6中的使用的方法:
function saveInfo({name= 'william', age= 18, address= 'changsha', gender= 'man'} = {}) {
  console.log(name, age, address, gender)
}
saveInfo()

forEach、for in、for of三者区别

forEach更多的用来遍历数
for in 一般常用来遍历对象或json
for of数组对象都可以遍历,遍历对象需要通过和Object.keys()
for in循环出的是key,for of循环出的是value

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

1、用了箭头函数,this就不是指向window,而是父级(指向是可变的)\
2、不能够使用arguments对象\
3、不能用作构造函数,这就是说不能够使用new命令,否则会抛出一个错误\
4、不可以使用yield命令,因此箭头函数不能用作 Generator 函数

Set、Map的区别

应用场景Set用于数据重组,Map用于数据储存

**Set:**\
1,成员不能重复\
2,只有键值没有键名,类似数组\
3,可以遍历,方法有add, delete,has

**Map:**\
1,本质上是健值对的集合,类似集合\
2,可以遍历,可以跟各种数据格式转换

promise对象的用法,手写一个promise

var promise = new Promise((resolve,reject) => {
    if (操作成功) {
        resolve(value)
    } else {
        reject(error)
    }
})
promise.then(function (value) {
    // success
},function (value) {
    // failure
})

Ajax

如何创建一个ajax

(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象\
(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息\
(3)设置响应HTTP请求状态变化的函数\
(4)发送HTTP请求\
(5)获取异步调用返回的数据\
(6)使用JavaScript和DOM实现局部刷新

同步和异步的区别

同步:
浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作

异步:
浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容

ajax的优点和缺点

**ajax的优点**

1、无刷新更新数据(在不刷新整个页面的情况下维持与服务器通信)\
2、异步与服务器通信(使用异步的方式与服务器通信,不打断用户的操作)\
3、前端和后端负载均衡(将一些后端的工作交给前端,减少服务器与宽度的负担)\
4、界面和应用相分离(ajax将界面和应用分离也就是数据与呈现相分离)

**ajax的缺点**

1、ajax不支持浏览器back按钮\
2、安全问题 Aajax暴露了与服务器交互的细节\
3、对搜索引擎的支持比较弱\
4、破坏了Back与History后退按钮的正常行为等浏览器机制

get和post的区别

1、get和post在HTTP中都代表着请求数据,其中get请求相对来说更简单、快速,效率高些 2、get相对post安全性低\
3、get有缓存,post没有\
4、get体积小,post可以无限大\
5、get的url参数可见,post不可见\
6、get只接受ASCII字符的参数数据类型,post没有限制\
7、get请求参数会保留历史记录,post中参数不会保留\
8、get会被浏览器主动catch,post不会,需要手动设置\
9、get在浏览器回退时无害,post会再次提交请求

如何解决跨域问题

跨域的概念:协议、域名、端口都相同才同域,否则都是跨域

**解决跨域问题:**

1、使用JSONP(json+padding)把数据内填充起来\
2、CORS方式(跨域资源共享),在后端上配置可跨域\
3、服务器代理,通过服务器的文件能访问第三方资源

Github

git常用的命令

从远程库克隆到本地:git clone 网站上的仓库地址
新增文件的命令:git add .
提交文件的命令:git commit –m或者git commit –a
查看工作区状况:git status –s
拉取合并远程分支的操作:git fetch/git merge或者git pull
查看提交记录命令:git reflog

微信图片_20210901171003.png

webpack

webpack打包原理

webpack只是一个打包模块的机制,只是把依赖的模块转化成可以代表这些包的静态文件。webpack就是识别你的 入口文件。识别你的模块依赖,来打包你的代码。至于你的代码使用的是commonjs还是amd或者es6的import。webpack都会对其进行分析。来获取代码的依赖。webpack做的就是分析代码。转换代码,编译代码,输出代码。webpack本身是一个node的模块,所以webpack.config.js是以commonjs形式书写的(node中的模块化是commonjs规范的)

模块热更新

模块热更新是webpack的一个功能,他可以使代码修改过后不用刷新就可以更新,是高级版的自动刷新浏览器

devServer中通过hot属性可以控制模块的热替换

通过配置文件

const webpack = require('webpack');
const path = require('path');
let env = process.env.NODE_ENV == "development" ? "development" : "production";
const config = {
  mode: env,
 devServer: {
     hot:true
 }
}
  plugins: [
     new webpack.HotModuleReplacementPlugin(), //热加载插件
  ],
module.exports = config;

通过命令行

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "NODE_ENV=development  webpack-dev-server --config  webpack.develop.config.js --hot",
  },

如何提高webpack构建速度

1、通过externals配置来提取常用库
2、利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来
3、使用Happypack 实现多线程加速编译
要注意的第一点是,它对file-loader和url-loader支持不好,所以这两个loader就不需要换成happypack了,其他loader可以类似地换一下
4、使用Tree-shaking和Scope Hoisting来剔除多余代码 5、使用fast-sass-loader代替sass-loader 6、babel-loader开启缓存
babel-loader在执行的时候,可能会产生一些运行期间重复的公共文件,造成代码体积大冗余,同时也会减慢编译效率 可以加上cacheDirectory参数或使用 transform-runtime 插件试试

// webpack.config.js
use: [{
    loader: 'babel-loader',
    options: {
        cacheDirectory: true
}]
// .bablerc
{
    "presets": [
        "env",
        "react"
    ],
    "plugins": ["transform-runtime"]
}

不需要打包编译的插件库换成全局"script"标签引入的方式

比如jQuery插件,react, react-dom等,代码量是很多的,打包起来可能会很耗时 可以直接用标签引入,然后在webpack配置里使用 expose-loader 或 externals 或 ProvidePlugin 提供给模块内部使用相应的变量

// @1
use: [{
    loader: 'expose-loader',
    options: '$'
    }, {
    loader: 'expose-loader',
    options: 'jQuery'
    }]
// @2
externals: {
        jquery: 'jQuery'
    },
// @3
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            'window.jQuery': 'jquery'
        }),

8、优化构建时的搜索路径

在webpack打包时,会有各种各样的路径要去查询搜索,我们可以加上一些配置,让它搜索地更快 比如说,方便改成绝对路径的模块路径就改一下,以纯模块名来引入的可以加上一些目录路径 还可以善于用下resolve alias别名 这个字段来配置 还有exclude等的配置,避免多余查找的文件,比如使用babel别忘了剔除不需要遍历的

### webpack的优点
专注于处理模块化的项目,能做到开箱即用,一步到位

可通过plugin扩展,完整好用又不失灵活

使用场景不局限于web开发

社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展

良好的开发体验

### webpack的缺点

webpack的缺点是只能用于采用模块化开发的项目

微信小程序

文件主要目录及文件作用

- component —————————————————— 组件文件夹
  - navBar                  —— 底部组件
    - navBar.js             —— 底部组件的 JS 代码
    - navBar.json           —— 底部组件的配置文件
    - navBar.wxml           —— 底部组件的 HTML 代码
    - navBar.wxss           —— 底部组件的 CSS 代码
- pages  ————————————————————— 页面文件夹
  - index                   —— 首页
    - index.js              —— 首页的 JS 代码
    - index.json            —— 首页的配置文件
    - index.wxml            —— 首页的 HTML 代码
    - index.wxss            —— 首页的 CSS 代码
- public ————————————————————— 图片文件夹
- utils —————————————————————— 工具文件夹
  - api.js                  —— 控制 API 的文件
  - md5.js                  —— 工具 - MD5 加密文件
  - timestamp.js            —— 工具 - 时间戳文件
- app.json ——————————————————— 设置全局的基础数据等
- app.wxss ——————————————————— 公共样式,可通过 import 导入更多
- project.config.json ———————— 项目配置文件

微信小程序生命周期

onLoad():页面加载时触发。
onShow():页面显示/切入前台时触发。
onReady():页面初次渲染完成时触发。
onHide():页面隐藏/切入后台时触发。
onUnload():页面卸载时触发。

如何封装数据请求

1,封装接口

项目/utils/api.js
// 将请求进行 Promise 封装
const fetch = ({url, data}) => {

  // 打印接口请求的信息
  console.log(`【step 1】API 接口:${url}`);
  console.log("【step 2】data 传参:");
  console.log(data);

  // 返回 Promise
  return new Promise((resolve, reject) => {
    wx.request({
      url: getApp().globalData.api + url,
      data: data,
      success: res => {
        
        // 成功时的处理 
        if (res.data.code == 0) {
          console.log("【step 3】请求成功:");
          console.log(res.data);
          return resolve(res.data);
        } else {
          wx.showModal({
            title: '请求失败',
            content: res.data.message,
            showCancel: false
          });
        }

      },
      fail: err => {
        // 失败时的处理
        console.log(err);
        return reject(err);
      }
    })
  })

}

/**
 * code 换取 openId
 * @data {
 *   jsCode - wx.login() 返回的 code
 * }
 */
export const wxLogin = data => {
  return fetch({
    url: "tbcUser/getWechatOpenId",
    data: data
  })
}

页面数据传递

通过 url 携带参数,在 onLoad() 中通过 options 获取 url 上的参数:
<navigator url="../index/index?userId={{userId}}"></navigator>

<!-- 这两段是分别在 HTML 和 JS 中的代码 -->

onLoad: function(options) {
  console.log(options.userId);
}

通过 Storage 来传递参数:
wx.setStorageSync('userId', 'jsliang');
wx.getStorageSync('userId');

WXML传递数据到 JS
login.wxml
<text bindtap="clickText" data-labelId="{{userId}}">点击传递数据到 JS</text>

login.js
clickText(e) {
  console.log(e.currentTarget.labelid)
}

组件调用传参
组件接收数据:component-tag-name
Component({
  properties: {
    // 这里定义了innerText属性,属性值可以在组件使用时指定
    innerText: {
      type: String,
      value: 'default value',
    }
  }
})

使用组件的页面定义 json
{
  "usingComponents": {
    "component-tag-name": "../component/component"
  }
}

使用组件的页面 HTML 代码
<view>
  <!-- 以下是对一个自定义组件的引用 -->
  <component-tag-name inner-text="Some text"></component-tag-name>
</view>

通过接口调用传递参数

加载性能优化方法

1、通过 this.$preload() 预加载用户可能点击的第二个页面

2、组件化页面,出现两次以上的部分都进行封装成组件

3、提取共用的 CSS 样式

4、优化图片:TinyPNG

微信小程序与原生APP、Vue、H5差异

**微信小程序优势**

1、无需下载\
2、打开速度较快\
3、开发成本低于原生APP

**微信小程序劣势**

1、限制多。页面大小不能超过 1M,不能打开超过 5 个层级的页面\
2、样式单一。小程序内部组件已经成宿,样式不可以修改\
3、推广面窄。跑不出微信,还不能跑入朋友圈

**微信小程序 VS 原生APP**\
微信小程序有着低开发成本、低获客成本、无需下载的优势

**微信小程序 VS H5**\
1、依赖环境不同。一个能在多种手机浏览器运行。一个只能在微信中的非完整的浏览器\
2、开发成本不同。一个可能在各种浏览器出问题。一个只能在微信中运行

**微信小程序 VS Vue**\
微信小程序看似就是阉割版的 Vue

微信小程序原理

本质上就是一个单页面应用,所有的页面渲染和事件处理,都在一个页面中进行
架构为数据驱动的模式,UI 和数据分离,所有页面的更新,都需要通过对数据的更改来实现
微信小程序分为两个部分:webview 和 appService。其中 webview 主要用来展示 UI,appServer 用来处理业务逻辑、数据及接口调用。它们在两个进程中进行,通过系统层 JSBridge 实现通信,实现 UI 的渲染、事件的处理

wxml与标准的html的异同

wxml基于xml设计,标签只能在微信小程序中使用,不能使用html的标签

网络协议

网络分层

目前网络分层可分为两种:OSI 模型和 TCP/IP 模型

OSI模型
应用层(Application)
表示层(Presentation)
会话层(Session)
传输层(Transport)
网络层(Network)
数据链路层(Data Link)
物理层(Physical)

TCP/IP模型
应用层(Application)
传输层(Host-to-Host Transport)
互联网层(Internet)
网络接口层(Network Interface)

HTTP/HTTPS

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

HTTP状态码

区分状态码
1××开头 - 信息提示
2××开头 - 请求成功
3××开头 - 请求被重定向
4××开头 - 请求错误
5××开头 - 服务器错误

常见状态码
200 - 请求成功,Ajax 接受到信息了
400 - 服务器不理解请求
403 - 服务器拒绝请求
404 - 请求页面错误
500 - 服务器内部错误,无法完成请求

性能优化

HTML优化

1、避免 HTML 中书写 CSS 代码,因为这样难以维护。
2、使用 Viewport 加速页面的渲染。
3、使用语义化标签,减少 CSS 代码,增加可读性和 SEO。
4、减少标签的使用,DOM 解析是一个大量遍历的过程,减少不必要的标签,能降低遍历的次数。
5、避免 src、href 等的值为空,因为即时它们为空,浏览器也会发起 HTTP 请求。
6、减少 DNS 查询的次数

CSS优化

1、优化选择器路径:使用 .c {} 而不是 .a .b .c {}。
2、选择器合并:共同的属性内容提起出来,压缩空间和资源开销。
3、精准样式:使用 padding-left: 10px 而不是 padding: 0 0 0 10px4、雪碧图:将小的图标合并到一张图中,这样所有的图片只需要请求一次。
5、避免通配符:.a .b * {} 这样的选择器,根据从右到左的解析顺序在解析过程中遇到通配符 * {} 6、会遍历整个 DOM,性能大大损耗。
7、少用 floatfloat 在渲染时计算量比较大,可以使用 flex 布局。
8、为 0 值去单位:增加兼容性。
9、压缩文件大小,减少资源下载负担。

JavaScript优化

1、尽可能把 <script> 标签放在 body 之后,避免 JS 的执行卡住 DOM 的渲染,最大程度保证页面尽快地展示出来
2、尽可能合并 JS 代码:提取公共方法,进行面向对象设计等……
3、CSS 能做的事情,尽量不用 JS 来做,毕竟 JS 的解析执行比较粗暴,而 CSS 效率更高。
4、尽可能逐条操作 DOM,并预定好 CSs 样式,从而减少 reflow 或者 repaint 的次数。
5、尽可能少地创建 DOM,而是在 HTML 和 CSS 中使用 display: none 来隐藏,按需显示。
6、压缩文件大小,减少资源下载负担。