2019前端面试题汇总(一年经验)

写在开头

跳槽是为了得到更高的薪酬,或者为了寻找更具发展空间的公司一种常见的行为。我也于今年完成了一次跳槽,自然也有很多的感悟,作成本文供大家参考。
本文主要分成两个部分,一部分是我自己整理的面试大纲,另外一部分是面试官考察的问题。

面试大纲

关于面试大纲,我认为每个人都是千差万别的。因为面试官都是对简历所写的项目经验进行深挖或者对你所掌握的基本技能进行考察。
我在简历上写的技能点包含了HTML/CSS/JavaScript/ES6/HTTP协议/Vue/微信小程序/Node/Express/MySQL/ 面向对象/设计模式
根据以上,整理出面试大纲

一、HTML

<li>标签之间有空格怎么处理?

原因:行框的排列会受到中间空白(回车空格)等的影响,因为空格也属于字符,这些空白也会被应用样式,占据空间,所以会有间隔,把字符大小设为0或者将<li>标签写在一排,就没有空格了。

二、CSS

CSS3新特性
  1. 支持rgba和透明度
  2. 支持媒体查询
  3. 支持自定义字体
  4. 对长的不可分割单词换行word-wrap:break-word;
  5. 文字阴影text-shadow: 5px 5px 5px #ff0000;
  6. 盒阴影box-shadow: 10px 10px 5px #888888
  7. 支持圆角border-radius: 50%;
  8. 边框图片border-image: url(border.png) 30 30 round
CSS优先级如何计算?
  • 元素和伪元素:1
  • 类选择器、属性选择器或伪类:10
  • id选择符:100
  • 内联样式:1000
  1. !important声明的样式优先级最高,如果冲突再进行计算。
  2. 继承得到的样式的优先级最低。
  3. 如果优先级相同,则选择最后出现的样式。
清除浮动的几种方式和各自的优缺点
  1. 添加空div,使用clear: both;缺点:进行清除浮动;会添加很多无意义的空标签,有违结构与表现的分离,在后期维护中将是噩梦;
  2. 父元素使用overflow: hidden;缺点:不能配合position一起使用,超出的部分会被隐藏;
  3. 父元素使用overflow: auto;缺点:超出部分会出现滚动条;
  4. 父级定义高度
  5. 推荐使用的方法:父级div定义伪类:after和zoom
<style>
.clearfloat {
    *zoom: 1;
}
.clearfloat:after {
    content: "";
    height: 0;
    display: block;
    clear: both;
    visibility: hidden;
}
</style>
<!-- *是为了兼容低版本IE7浏览器 zoom是ie属性,设置缩放比例 -->
<div class="box clearfloat">
  <div class="left">
  <div class="right">
</div>
如何水平居中一个浮动元素?
#test {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: green;
  <!-- 三行代码缺一不可 -->
  margin: 0 auto;
  left: 0;
  right: 0;
}
<div id="test"></div>
如何水平居中一个浮动元素?
#test {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: green;
  <!-- 三行代码缺一不可 -->
  margin: 0 auto;
  left: 0;
  right: 0;
}
<div id="test"></div>
利用CSS画三角形
  #triangle {
    width: 0;
    height: 0;
    border-top: 40px solid transparent;
    border-left: 40px solid transparent;
    border-right: 40px solid transparent;
    border-bottom: 40px solid greenyellow;
  }
<div id="triangle"><div>
display: none;visibility: hidden; 的区别?
  1. display:none; 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)
  2. visibility:hidden; 隐藏对应元素,在文档布局中仍保留原来的空间(重绘)
positionfloat 值相互叠加会发生什么?

position:absolute/fixed; 优先级最高,有他们在时,float不起作用,display值需要调整。

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

BFC规定了内部的Block Box如何布局。
定位方案:

  1. 内部的Box会在垂直方向上一个接一个放置。
  2. Box垂直方向的距离由margin决定,属于同一个BFC的两个相邻Box的margin会发生重叠。
  3. 每个元素的margin box 的左边,与包含块border box的左边相接触。
  4. BFC的区域不会与float box重叠。
  5. BFC是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。
  6. 计算BFC的高度时,浮动元素也会参与计算。

满足下列条件之一就可触发BFC

  1. 根元素,即html
  2. float的值不为none(默认)
  3. overflow的值不为visible(默认)
  4. display的值为inline-block、table-cell、table-caption
  5. position的值为absolute或fixed
为什么会出现浮动和什么时候需要清除浮动?清除浮动的方式?

浮动元素碰到包含它的边框或者浮动元素的边框停留。由于浮动元素不在文档流中,所以文档流的块框表现得就像浮动框不存在一样。浮动元素会漂浮在文档流的块框上。
浮动带来的问题:

  1. 父元素的高度无法被撑开,影响与父元素同级的元素
  2. 与浮动元素同级的非浮动元素(内联元素)会跟随其后
  3. 若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构。

清除浮动的方式:

  1. 父级div定义height
  2. 最后一个浮动元素后加空div标签 并添加样式clear:both。
  3. 包含浮动元素的父标签添加样式overflow为hidden或auto。
  4. 父级div定义zoom
:after::after的区别是什么?
  1. 在CSS3的规范里:表示伪类,::表示伪元素
  2. :after在CSS2.1的规范中,表示伪元素,随着WEB的发展,在CSS3的规范中,伪元素的语法被修改成使用双冒号,成为::after
实现一个高度自适应的div,里面有两个div,一个高度100px,希望另一个填满剩下的高度
  html, body {
    padding: 0;
    margin: 0;
    width: 100%;
    height: 100%;
  }
  #box {
    position: relative;
    height: 100%;
    width: 200px;
    background-color: beige;
  }
  #box .block-1 {
    width: 200px;
    height: 100px;
    background-color: rosybrown;
  }
  #box .block-2 {
    position: absolute;
    top: 100px;
    bottom: 0;
    width: 200px;
    background-color: red;
  }
  <div id="box">
    <div class="block-1"></div>
    <div class="block-2"></div>
  </div>

三、JavaScript

简单说说继承的方式和优缺点?
原型链继承
function Parent(name) { 
    this.name = name;
}
Parent.prototype.sayName = function() {
    console.log('parent name:', this.name);
}
function Child(name) {
    this.name = name;
}
Child.prototype = new Parent('father');
Child.prototype.constructor = Child;
Child.prototype.sayName = function() {
    console.log('child name:', this.name);
}
var child = new Child('son');
child.sayName();    // child name: son
// 这种方法存在两个缺陷:
// 1.子类型无法给超类型传递参数,在面向对象的继承中,我们总希望通过 var child = new Child('son', 'father'); 让子类去调用父类的构造器来完成继承。而不是通过像这样 new Parent('father') 去调用父类。
// 2.Child.prototype.sayName 必须写在 Child.prototype = new Parent('father') 之后,不然就会被覆盖掉。
类式继承
function Parent(name) { 
    this.name = name;
}
Parent.prototype.sayName = function() {
    console.log('parent name:', this.name);
}
Parent.prototype.doSomthing = function() {
    console.log('parent do something!');
}
function Child(name, parentName) {
    Parent.call(this, parentName);
    this.name = name;
}
Child.prototype.sayName = function() {
    console.log('child name:', this.name);
}
var child = new Child('son');
child.sayName();      // child name: son
child.doSomthing();   // TypeError: child.doSomthing is not a function
// 解决了原型链继承带来的问题
// 但存在缺陷:每次创建一个 Child 实例对象时候都需要执行一遍 Parent 函数,无法复用一些公用函数。
组合式继承
function Parent(name) { 
    this.name = name;
}
Parent.prototype.sayName = function() {
    console.log('parent name:', this.name);
}
Parent.prototype.doSomething = function() {
    console.log('parent do something!');
}
function Child(name, parentName) {
    Parent.call(this, parentName);      // 第二次调用
    this.name = name;
}

Child.prototype.sayName = function() {
    console.log('child name:', this.name);
}
Child.prototype = new Parent();         // 第一次调用
Child.prototype.construtor = Child;
var child = new Child('son');
child.sayName();      
child.doSomething(); 
// 第一次调用构造函数显然是没有必要的,因为第一次调用构造函数时候不需要函数内部的那些实例属性,这么写只是想获得其原型上的方法罢了
// 下面的寄生组合式继承解决了这个问题
寄生组合式继承
function Parent(name) {
    this.name = name;
}
Parent.prototype.sayName = function() {
    console.log('parent name:', this.name);
}
function Child(name, parentName) {
    Parent.call(this, parentName);  
    this.name = name;    
}
Child.prototype = Object.create(Parent.prototype);   //修改
Child.prototype.construtor = Child;
Child.prototype.sayName = function() {
    console.log('child name:', this.name);
}
var parent = new Parent('father');
parent.sayName();      // parent name: father
var child = new Child('son', 'father');
child.sayName();       // child name: son
// tips:
/*
  Object.create(proto, [propertiesObject])
  Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
  
  proto
    新创建对象的原型对象。
  propertiesObject 可选。
    如果没有指定为undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。
  
*/
谈谈JS中的this
  1. this是在执行上下文创建时确定的一个在执行过程中不可更改的变量
  2. this只在函数调用阶段确定,也就是执行上下文创建的阶段进行赋值,保存在变量对象中。

执行上下文,就是JavaScript引擎在执行一段代码之前将代码内部会用到的一些变量函数this提前声明然后保存在变量对象中的过程。

运算符优先级
  1. ()括号 .成员访问
  2. ()函数调用 new
  3. ++ -- !
  4. * %
  5. + -
  6. >= <= > <
  7. == !==
  8. & ^ | 位运算符
  9. && || 逻辑运算符
  10. a ? x : y 条件运算符
  11. = op = 运算赋值
// 本题的涉及考点主要还是运算符的优先级
function Foo() {
  getName = function() {
    console.log(1)
  }
  return this
}
Foo.getName = function() {
  console.log(2)
}
Foo.prototype.getName = function() {
  console.log(3)
}
var getName = function() {
  console.log(4)
}
function getName() {
  console.log(5)
}

// 写出以下函数的执行结果
Foo.getName()            // 2
getName()                // 4
Foo().getName()          // 1  
getName()                // 1
new Foo.getName()        // 2
// new (Foo.getName)()
new Foo().getName()      // 3
// (new Foo()).getName()
new new Foo().getName()  // 3
// new((new Foo()).getName)()
简单聊一聊包装对象?

引用类型包装对象的区别在于生存期
引用类型所创建的对象会一直存在于堆内存中,而基本包装对象只存在于一瞬间

var str = 'hello';
str.number = 10;   //假设我们想给字符串添加一个属性number ,后台会有如下步骤
/*
  var str = new String('hello'); // 1 找到对应的包装对象类型,然后通过包装对象创建出一个和基本类型值相同的对象
  str.number = 10; // 2 通过这个对象调用包装对象下的方法 但结果并没有被任何东西保存
  str =null; // 3 这个对象又被销毁
*/
alert(str.number); //undefined  当执行到这一句的时候,因为基本类型本来没有属性,后台又会重新重复上面的步骤
/*
 var str = new String('hello'); // 1 找到基本包装对象,然后又新开辟一个内存,创建一个值为hello对象
 str.number = undefined   // 2 因为包装对象下面没有number这个属性,所以又会重新添加,因为没有值,所以值是未定 ;然后弹出结果
 str =null; // 3 这个对象又被销毁
*/
instanceof 和 typeof

instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置(Instanceof 的使用规则是: A instanceof B A沿着proto这条线来找,同时B沿着prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。)

函数/变量提升
// 1、函数提升优先级高于变量提升
// 2、表达式可以修改提升后变量的值

test() // 2
var test = function() {
  console.log(1)
}
function test() {
  console.log(2)
}
test() // 1
  
alert(a);
var a=1;
alert(a); 
function a(){alert(2)};
alert(a);
var a=3;
alert(a);
function a(){alert(4)};
alert(a);
a();

// function a() {alert (4)}; 
// 1
// 1
// 3
// 3
// 报错
new 操作符都干了些什么?
// new共经历了四个阶段
// 1、创建一个空对象
var obj = new Object()
// 2、设置原型链
obj.__proto__ = Func.prototype
// 3、让Func中的this指向obj,并执行Func的函数体
var result = Func.call(obj)
// 4、判断Func的返回值类型
// 如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象
// (tip: 构造函数默认 return this,不用写)
if(typeof result === 'object') {
    return result
} eles {
    return obj
}

四、Vue

对MVVM的理解

MVVM分为Model、View、ViewModel三者

Model 代表数据模型,数据和业务逻辑都在Model层中定义;
View 代表UI视图,负责数据的展示;
ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;

Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。
这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom

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

简单来说就是使用数据劫持发布订阅的设计模式实现的

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>MVVM双向数据绑定</title>
</head>

<body>
  <script>
    class Vue {
      constructor(options) {
        this.options = options
        this.$data = options.data
        this.$el = document.querySelector(options.el)
        this._directives = {};
        // 数据劫持
        this.Observer(this.$data)
        // 解析指令
        this.Compile(this.$el)
      }
      Observer(data) {
        for(let key in data) {
          this._directives[key] = []
          let val = data[key]
          let _obj = this._directives[key]
          Object.defineProperty(this.$data, key, {
            get: function() {
              return val
            },
            set: function(newVal) {
              if(val !== newVal) {
                val = newVal
                _obj.forEach((item) => {
                  item.update()
                })
              }
            }
          })
        }
      }
      Compile(el) {
        let nodes = el.children
        for(let i=0; i<nodes.length; i++) {
          let node = nodes[i]
          if(node.children.length) {
            this.Compile(node)
          }
          if(node.hasAttribute('v-text')) {
            let attrValue = node.getAttribute("v-text");
            this._directives[attrValue].push(new Watch(node, this, attrValue, "innerHTML"))
          }
          if(node.hasAttribute('v-model')) {
            let _this = this
            let attrValue = node.getAttribute("v-model");
            this._directives[attrValue].push(new Watch(node, this, attrValue, "value"))

            node.addEventListener('input', (function(){
              return function() {
                _this.$data[attrValue] = node.value
              }
            })())
          }
        }
      }
    }

    class Watch {
      constructor(el, vm, exp, attr) {
        this.el = el
        this.vm = vm
        this.exp = exp
        this.attr = attr
        this.update()
      }
      update() {
        this.el[this.attr] = this.vm.$data[this.exp]
      }
    }
  </script>
  
  <div id="app">
    <h1>数据双向绑定</h1>
    <div>
      <div v-text="myText"></div>
      <input type="text" v-model="myText">
    </div>
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        myText: '今晚吃鸡呐,大吉大利!'
      }
    });
  </script>

</body>

</html>
Vue如何监听某个属性值的变化
watch: {
  'obj.a': {
    handler (newName, oldName) {
      console.log('obj.a changed')
    }
  }
}

computed: {
  a1 () {
    return this.obj.a
  }
}
Vue中给data中的对象属性添加一个新的属性时会发生什么,如何解决?

数据已经添加,但是视图并没有刷新;

原因在于在Vue实例创建时,obj.b并未声明,因此就没有被Vue转换为响应式的属性,自然就不会触发视图的更新,这时就需要使用Vue的全局api Vue.$set()

addObjB () {
  // this.obj.b = 'obj.b'
  this.$set(this.obj, 'b', 'obj.b')
  console.log(this.obj)
}
Vue组件通讯的方式
  1. 父子组件通讯:父->子:使用props,子->父:$emit方法传递参数
  2. 使用eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件
  3. Vuex

可以去掘金找这类文章看一下,Vue组件的通讯方式达6种之多

Vue常用的修饰符

.prevent: 提交事件不再重载页面;
.stop: 阻止单击事件冒泡;
.self: 当事件发生在该元素本身而不是子元素的时候会触发;
.capture: 事件侦听,事件发生的时候会调用;

$route 和 $router 的区别

$route 是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数
$router 是“路由实例”对象包括了路由的跳转方法,钩子函数等

Vue的路由实现:hash模式 和 history模式

hash模式: 在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;
特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

history模式: history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”

对keep-alive的了解?

keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染
在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。

delete和Vue.delete删除数组的区别?

delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变;
Vue.delete直接删除了数组 改变了数组的键值。

如何优化SPA应用的首屏加载速度慢的问题?
  1. 将公用的JS库通过script标签外部引入,减小app.bundel的大小,让浏览器并行下载资源文件,提高下载速度;
  2. 在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundel 的体积,在调用某个组件时再加载对应的js文件;
  3. 加一个首屏 loading 图,提升用户体验;
Vuex是什么?如何使用?哪种场景使用它?

将需要读取的状态集中放在store
改变状态的方式是提交mutations,这是一个同步的事务
异步逻辑应该封装在actions

state Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
mutations 定义的方法动态修改Vuexstore中的状态或数据。
getters 类似vue的计算属性,主要用来过滤一些数据。
action 可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view层通过store.dispath来分发action
modules 项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理

Vue等单页应用的优缺点

优点:Vue的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件,核心是响应式系统
缺点:不支持低版本浏览器,最低仅支持ie9;不利于SEO的优化,首页加载耗时相对偏长一些。

四、ES6

letvar的区别
  1. 不存在变量提升
  2. 暂时性的死区
if (true) {
  // --- 死区开始 ---
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp;
  // --- 死区结束 ---
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}
  1. let为JavaScript新增了块级作用域
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10
// 变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i,每一次循环,变量i的值都会发生改变

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6
// 变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。
解构赋值

定义:从数组或对象中提取值,对变量进行赋值

  1. 数组的解构赋值
let [a, b, c] = [1, 2, 3];

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

let [x, y = 10] = [7]
  1. 对象的解构赋值

    (tip: 数组的解构赋值对顺序有要求,而对象则没有要求,变量必须与属性同名,才能取到正确的值)
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

var {x, y = 5} = {x: 1};

// 如果变量名与属性名不一致,必须写成下面这样。
// foo 是模式, baz 才是变量,特别要注意区分模式和变量
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
  1. 嵌套解构赋值
let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

// p 是模式,不会被复制,若想被赋值,可以写成如下形式
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]
箭头函数和非箭头函数的区别
  1. 箭头函数的this指向在定义的时候继承自外层的第一个普通函数的this,若外层没有普通函数,指向的是window
  2. 不能直接修改箭头函数的this指向
  3. 箭头函数的this指向全局,使用arguments会报未声明的错误。
  4. 箭头函数的this指向普通函数时,它的argumens继承于该普通函数
  5. 使用new调用箭头函数会报错,因为箭头函数没有constructor
简单讲讲Promise

Promise的特点
状态一旦改变就再也不会发生改变了
Promise的缺点

  1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消;
  2. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;
  3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
如何创建Promise实例?
const promise = new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
简单讲讲Promise API
  • Promise.resolve();
// 1. 如果传入的 value 本身就是 Promise 对象,则该对象作为 Promise.resolve 方法的返回值返回。
function fn(resolve){
    setTimeout(function(){
        resolve(123);
    },3000);
}
let p0 = new Promise(fn);
let p1 = Promise.resolve(p0);
// 返回为true,返回的 Promise 即是 入参的 Promise 对象。
console.log(p0 === p1);

//2. 如果这个值是个 thenable(thenable对象指的是具有then方法的对象),返回的 Promise 对象会“跟随”这个 thenable 的对象,采用它的最终状态(指 resolved/rejected/pending/settled)
let promise = Promise.resolve($.ajax('/test/test.json'));// => promise对象
promise.then(function(value){
   console.log(value);
});
  • Promise.reject();
  • Promise.race
// 类方法,多个 Promise 任务同时执行,返回最先执行结束的 Promise
// 任务的结果,不管这个 Promise 结果是成功还是失败。 。
  • Promise.all
// 类方法,多个 Promise 任务同时执行。
// 如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。
// 如果有一个 Promise 任务 rejected,则只返回 rejected 任务的结果。
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});
  • Promise.prototype.then()
  • Promise.prototype.catch() 该方法可以捕获到then链上发生的任何一个错误
  • Promise.prototype.finally() 该方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {
    // finally方法的回调函数不接受任何参数,这意味着没有办法知道,
    //前面的 Promise 状态到底是fulfilled还是rejected。这表明,
    // finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
});

关于Promise,不同面试官考察的深度因人而异,反正做好手写实现Promise的准备即可

五、微信小程序

简述微信小程序原理
  1. 小程序本质就是一个单页应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原生的各种接口;
  2. 功能可分为渲染层webview和逻辑层appService两个部分;
    webview用来展现UI,appService有来处理业务逻辑、数据及接口调用;
    两个部分在两个进程中运行,通过系统层JSBridge实现通信,实现UI的渲染、事件的处理等。
为什么微信小程序的DOM API不完整?

因为微信小程序的渲染线程和脚本线程是分开的,脚本线程独立在 JSCore 中,没有一个完整的浏览器对象。

简单谈谈WXS?

WXS(weixin script),结合HTML,构建页面结构。

<wxs module="m1">var msg = "hello world"; module.exports.message = msg;</wxs>
<view>{{m1.message}}</view>
为什么setData操作会很昂贵?

频繁用户交互的效果在小程序上表现是比较卡顿的, 例如页面有 2 个元素 A 和 B,用户在 A 上做 touchmove 手势,要求 B 也跟随移动。一次 touchmove 事件的响应过程为:

a、touchmove 事件从视图层(Webview)抛到逻辑层(App Service)
b、逻辑层(App Service)处理 touchmove 事件,再通过 setData 来改变 B 的位置

一次 touchmove 的响应需要经过 2 次的逻辑层和渲染层的通信以及一次渲染,通信(JSBridge)的耗时比较大。此外 setData 渲染也会阻塞其它脚本执行,导致了整个用户交互的动画过程会有延迟。

谈谈小程序的生命周期函数

onLoad() 页面加载时触发,只会调用一次,可获取当前页面路径中的参数;
onShow() 页面显示/切入前台时触发,一般用来发送数据请求(或者 set to foreground);
onReady() 页面初次渲染完成时触发 只会调用一次,代表页面已可和视图层进行交互(当逻辑层通知渲染层 Send Initial Data,且渲染层 First Render 之后调用);
onHide() 页面隐藏/切入后台时触发(set to background), 如底部tab切换到其他页面或小程序切入后台等;
onUnload() 页面卸载时触发(destroy),如redirectTo或navigateBack到其他页面时。

小程序页面有哪些传递数据的方法
  1. 使用全局变量实现数据传递
  2. 页面跳转或重定向时,使用url带参数传递数据
  3. 使用组件模板 template传递参数
  4. 使用缓存传递参数
  5. 使用数据库传递数据
请谈谈微信小程序主要目录和文件的作用?
  • project.config.json 项目配置文件,用得最多的就是配置是否开启https校验;
  • App.js 设置一些全局的基础数据等;
  • App.json 底部tab, 标题栏, 小程序的window背景色和路由等设置;
  • App.wxss 公共样式,引入iconfont等;
  • pages 里面包含一个个具体的页面;
    • index.json (配置当前页面标题和引入组件等);
    • index.wxml (页面结构);
    • index.wxss (页面样式表);
    • index.js (页面的逻辑,请求和数据处理等);
谈谈微信小程序.wxss样式
  1. 新增加了尺寸单位rpx(tip: rpx换算px规则是屏幕宽度/750, iphone6下,1rpx = 0.5px)
  2. 提供了全局样式(app.wxss)和局部样式(page.wxss)
  3. WXSS 仅支持部分 CSS 选择器( > :first-of-type :nth-child 均不支持)

六、HTTP协议

HTTP请求中的内容
  • 请求行 GET /images/logo.gif HTTP/1.1
  • 首部
    • 通用首部
    • 请求首部
    • 响应首部
    • 实体首部
  • 实体
HTTP状态码
  • 1**: 信息,服务器收到请求,需要请求者继续执行操作
  • 2**: 成功,操作被成功接收并处理
  • 3**: 重定向,需要进一步的操作以完成请求
    • 304 Not Modified 所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。
  • 4**: 客户端错误,请求包含语法错误或无法完成请求
    • 400 客户端请求的语法错误,服务端无法理解
    • 403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
    • 404 Not Found 服务器无法根据客户端的请求找到资源(网页)
  • 5**: 服务器错误,服务器在处理请求的过程中发生了错误
    • 500 服务器内部错误
    • 502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
RTT

往返时延,在计算机网络中它也是一个重要的性能指标,它表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延;

UDP协议

只是数据报文的搬运工,不会对报文进行拆分和拼接操作,不保证有序且不丢失的传递到对端

  • 面向无连接
  • 不可靠 -> 高效

在直播行业和即时对战游戏等实时性要求高的行业,UDP协议使用广泛

TCP协议

TCP 基本是和 UDP 反着来,建立连接断开连接都需要先需要进行握手。在传输数据的过程中,通过各种算法保证数据的可靠性,当然带来的问题就是相比 UDP 来说不那么的高效。是全双工协议。

标识符
  • URG=1:该字段为一表示本数据报的数据部分包含紧急信息,是一个高优先级数据报文,此时紧急指针有效。紧急数据一定位于当前数据包数据部分的最前面,紧急指针标明了紧急数据的尾部。
  • ACK=1:代表确认接受,确认号字段有效。此外,TCP 还规定在连接建立后传送的所有报文段都必须把 ACK 置为一。
  • PSH=1:该字段为一表示接收端应该立即将数据 push 给应用层,而不是等到缓冲区满后再提交。
  • RST=1:该字段为一表示当前 TCP 连接出现严重问题,可能需要重新建立 TCP 连接,也可以用于拒绝非法的报文段和拒绝连接请求。
  • SYN=1:代表请求创立连接,当SYN=1,ACK=0时,表示当前报文段是一个连接请求报文。当SYN=1,ACK=1时,表示当前报文段是一个同意建立连接的应答报文。
  • FIN=1:代表请求释放连接。
  • seq:序列号,什么意思呢?当发送一个数据时,数据是被拆成多个数据包来发送,序列号就是对每个数据包进行编号,这样接受方才能对数据包进行再次拼接。
  • ack:这个代表下一个数据包的编号,这也就是为什么第二请求时,ack是seq + 1(tip: ack和ACK代表的意思不同)
三次握手

客户端 ------ SYN = 1, ACK = 0, seq = x ------- > 服务端

客户端 < ----- SYN = 1, ACK = 1, seq = y, ack = x + 1 -------- 服务端

客户端 ------ ACK = 1, seq = x + 1, ack = y + 1------- > 服务端

四次挥手

客户端 ------ FIN ------- > 服务端

客户端 < ----- ACK -------- 服务端

客户端 < ----- FIN -------- 服务端

客户端 ------ ACK ------- > 服务端

ARQ协议

ARQ协议其实就是超时重传机制。通过确认和超时重传机制保证数据的正确送达。

  • 停止等待ARQ协议
  • 连续ARQ协议
连续ARQ协议 - > 滑动窗口

发送端窗口包含已发送但未收到应答的数据待发送的数据

发送端窗口是由接收窗口剩余大小决定的。接收方会把当前接收窗口的剩余大小写入应答报文,发送端收到应答后根据该值和当前网络拥塞情况设置发送窗口的大小,所以发送窗口的大小是不断变化的。

滑动窗口解决了数据的丢包、顺序不对和流量控制问题

拥塞处理
  • 慢开始算法
  • 拥塞避免算法
  • 快速重传
  • 快速恢复

七、面向对象

面向对象的基本要素

继承 封装多态

JS适合用来面向对象吗?
你在工作中是如何使用面向对象思想的?

明天需要上班,后续跟进补充!

猜你喜欢

转载自blog.csdn.net/weixin_33806509/article/details/91032895