记19年8月前端面试总结

2,187 阅读16分钟
  • 本文是自己在面试时遇到的,面的都是十几k的职位
  • 所有的答案都是我在翻阅资料,思考验证之后写出的,但能力有限,本人的答案未必是最优的,如果您有更好的答案,欢迎给我留言
  • 本文答案也借用优秀答案,已经添加链接,如有侵权,请联系我删除
  • 如果有错误或者不严谨的地方,请务必给予指正,以免误导他人
  • 如果喜欢或者有所启发,欢迎点赞,对我也是一种鼓励

面面试,收获真的挺大的,感觉打开了前进的路, 建议看到题后自己先写一下试试,知其然还需知其所以然,把原理搞明白

最好重点看下js打印结果那些,考察的基础, 本来想着再花几天时间把下面那些js为什么打印解释下,但一想不如自己去花时间去找一下比较好,就没再细写,因为本文挺长的,更详细的会发到博客

js

1. 以 let str = 'abc' 实现reverse

    let str = 'abc'
    function rev(str) {
      let arr = str.split('')
      let tem = []
      for (let i = arr.length-1; i >= 0; i--) {
         tem.push(arr[i])
      }
      return tem.join('')
    }
    rev(str)
    console.log(rev(str))

2. 针对 var arr = [3,4,5,22,1,3]; 写一个排序函数,对数组进行由小到大排序 (注意不要使用js内置的排序相关函数)

// 冒泡排序: 随便拿出一个数与后者比较,如果前者比后者大,那么两者交换位置,
    let arr = [5,2,3,1,4]
    function bubbleSort(arr) {
      let arrLen = arr.length
      for (let i = 0; i < arrLen; i++){
        for (let j = i; j < arrLen; j++){
          let tempi = arr[i]; 
          let tempj = arr[j];
          if (tempi > tempj) {
            arr[i] = tempj;
            arr[j] = tempi;
          }
        }
      } 
      return arr
    }

    console.log(bubbleSort(arr))
  /* 快速排序 : 
    * 1. 以一个数为基准(基准值可以任意选择,选择中间的值容易理解 
    * 2. 按照顺序,将每个元素与"基准"进行比较,形成两个子集,一个"小于3",另一个"大于等于3"。
    * 3. 对两个子集不断重复第一步和第二步,直到所有子集只剩下一个元素为止
  */
    
  let arr = [5, 2, 3, 1, 4]
  function quickSort(arr) {
    // 如果数组为1那就没必要比较了
   if (arr.length <= 1) { return arr }
    // 取中间索引值
    let pivotIndex = Math.floor(arr.length / 2)
    // 获取中间值
    let pivot = arr.splice(pivotIndex, 1)[0]
    let left = []
    let right = []
    for (let i = 0; i < arr.length; i ++) {
      if(arr[i] < pivot) {
        left.push(arr[i])
      } else {
        right.push(arr[i])
      }
    }
    // 使用递归不断重复这个过程
    return quickSort(left).concat([pivot], quickSort(right))
  }
  console.log(quickSort(arr)) 
    

3. 数组去重至少2种

// 1. from && set
 let arr = [1, 1, 4, 50, 50, 6, 2, 2]
 let res = Array.from(new Set(arr))
 console.log(res)

// 2. filter && indexOf
 let arr = [2, 2, 33, 44, 33]
 let res = arr.filter((item, index, arr) => {
    return arr.indexOf(item) == index
 })
  console.log(res)
  
// 3. forEach && includes
  let arr = [2, 2, 33, 44, 33]
  let res = []
  arr.forEach((item) => {
    if (!res.includes(item)) {
      res.push(item)
    }
  })
  console.log(res)
  
// 4. filter &&  Map
 let arr = [2, 2, 33, 44, 33]
 const tem = new Map();
 let res = arr.filter((item) => !tem.has(item) && tem.set(item, 1))
 console.log(res)

3. 去掉 var str = “ abc beijing ”;中的所有空格

 let str = ' adb ddd '
 let res = str.replace(/\s+/g, "")
 console.log(res)

4. 判断字符串 var str = ‘abdcddsseeed’ 出现最多的字符,并统计出现次数

let str = 'asdasddsfdsfadsfdghdadsdfdgdasd';
function getMax(str) {
  let obj = {};
  for (let i in str) {
    if (obj[str[i]]) {
      obj[str[i]]++
    } else {
      obj[str[i]] = 1
    }
  }
  
  let num = 0
  let number = 0
  for (var i in obj) {
    //如果当前项大于下一项
    if (obj[i] > num) {
      //就让当前值更改为出现最多次数的值
      num = obj[i]
      number = i
    }
  }
  console.log('最多的值是' +number+ '出现次数为' + num);
}

getMax(str);

5. js 实现继承

// 构造函数继承
function Super(){
	this.name = ["张三","李四"]
}
function Sub(){
	Super.call(this)
}
let a = new Sub();
a.name.push("王五")
 // “张三","李四","王五"
console.log(a.name);

let b = new Sub()
//// “张三","李四"
console.log(b.name)


// Class 继承
class Fathter {
    constructor(name) {
      this.name = name
    }
    hello() {
      console.log('Hello, ' + this.name)
    }
}
class Son extends Fathter {
    constructor(name) {
      // 记得用super调用父类的构造方法
        super(name)
        console.log(name)
    }
}
let res = new Son('张三')
res.hello()

6. 实现一个数据的深度拷贝,必问原生如何实现

  1. 简单粗暴, 但函数、正则、Symbol都没有被正确的复制
  JSON.parse(JSON.stringify(obj));
  1. 考虑对象相互引用以及 Symbol 拷贝的情况

7. 各打印什么? 为什么?

  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(); // 3 相当于 var f = new Foo()  f.getName()     
   new new Foo().getName(); // 3
   
// 这道题相当经典了 花点时间自己去看下吧

8. 输出什么? 为什么

function fun() {
 this.a = 0
 this.b = function() {
   console.log(this)
   console.log(this.a)
 }
}
fun.prototype = {
 b: function() {
   this.a = 20
   console.log(this.a)
 },
 c: function() {
   this.a = 30
   console.log(this.a)
 }
}

var my_fun = new fun();
//私有方法	this=>my_fun
my_fun.b();	
console.log(my_fun.a);
//公有方法	this=>my_fun this.a = 30(私有属性a修改为30)
my_fun.c();	
console.log(my_fun.a);
var my_fun2 = new fun();
console.log(my_fun2.a);
//this=>my_fun2.__proto__ 当前实例·通过原型链在共有属性增加的了一a:30
my_fun2.__proto__.c();	
console.log(my_fun2.a);
console.log(my_fun2.__proto__.a);
// 0,0,30,30,0,30,0,30

9. 输出什么 为什么

 function setName(obj){
   obj.name = "Tom"
   obj = new Object()
   obj.name = 'Mike'
 }
  
 var person = new Object()
 setName(person)
 console.log(person.name) // "Tom"
 

10. 如何查找数组里的最大值或者最小值

  // ES5 的写法
  let arr = [ 2,4,5,1]
  console.log(Math.min.apply(null, arr))
  
  // ES6 的写法
   console.log(Math.max(...[14, 3, 77, 30]))

11. javascript 12345678 改为 12,345,678, 也就是千位分隔

  1. 写不出来才可以这样写哈 要不面试官打你啊哈哈
let num = 12345678
console.log(num.toLocaleString())

  1. 求模
let num = 12345678
function toThousands(num) {
    var result = '', counter = 0;
    num = (num || 0).toString();
    for (var i = num.length - 1; i >= 0; i--) {
        counter++;
        result = num.charAt(i) + result;
        if (!(counter % 3) && i != 0) { result = ',' + result; }
    }
    return result;
}
console.log(toThousands(num))

  1. 正则
// 3-1
let num = 12345678
function toThousands(num) {
    return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
}
console.log(toThousands(num))
// 3-2
let num = 12345678
function toCurrency(num){
    var parts = num.toString().split('.');
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    return parts.join('.');
}
console.log(toCurrency(num))

相关链接

12. 闭包问题

function f1(){
      var n=999;
      nAdd=function(){n+=1}
      function f2(){
          console.log(n);
      }
      return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

13. 输出结果, 为什么

var name = "The Window";
  var object = {
      name : "My Object",
      getNameFunc : function(){
          return function(){
              return this.name;
          };
      }
  };
console.log(object.getNameFunc()()); // the window

14. 输出结果, 为什么

 var length = 1
  function fn() {
      console.log(this.length)
  } 
  
  var obj = {
    length: 3,
    me:function(fn){
      fn() // 1
      arguments[0]() // 2
    }
  }
  obj.me(fn, 1)

15. let str = '你好,abc' 字符串长度

let str = '你好,abc'
function getLength(str){
  let length = 0
  let reg = /[\u4e00-\u9fa5]/
  for (let i = 0; i < str.length; i++) {
      if (reg.test(str.charAt(i))) {
        length +=2
      } else {
        length ++
      }
  }
  return length
}
console.log(getLength(str))

16. Object.defineProperty()有什么缺点 有什么方法

  1. 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。
  2. 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。

利用 Proxy

参考 -- 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索

17. prototype 和 proto 区别是什么

prototype是构造函数的属性。

proto 是每个实例都有的属性,可以访问 [[prototype]] 属性。

实例的__proto__ 与其构造函数的prototype指向的是同一个对象。

function Student(name) {
    this.name = name;
}
Student.prototype.setAge = function(){
    this.age=20;
}
let Jack = new Student('jack');
console.log(Jack.__proto__);
//console.log(Object.getPrototypeOf(Jack));;
console.log(Student.prototype);
console.log(Jack.__proto__ === Student.prototype);//true

参考刘小夕解答

18. 打印什么 为什么

var a = {n:1};  
var b = a; // 持有a,以回查  
a.x = a = {n:2};  
console.log(a.x);// --> undefined  
console.log(b.x);// --> {n:2}

19. 打印什么 为什么

var y = 1;
if (function f(){}) {
    y += typeof f;
}
console.log(y); // 1undefined

20. 打印什么 为什么

var a = 0
function b(){
  console.log(a) // fun a
  a = 10
  console.log(a) // 10
  return;
  function a(){}
}
b()
console.log(a) // 0

21. 说一下防抖和节流 什么场景下用

防抖

概念:
    1. 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
    2. 多次触发事件后,事件处理函数只执行一次,并且是在触发操作结束时执行。

应用场景: 
    1. 文本输入的验证(连续输入文字后发送 ajax 请求进行验证,验证一次就好)
    2. 给按钮加函数防抖防止表单多次提交。

生活实例:
    如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行事件监听器),这时如果又有人进电梯了(在10秒内再次触发该事件),我们又得等10秒再出发(重新计时)。
    
代码


function debounce(func, interval = 100){
    let timer;
    return (...args) => {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(() => {
        func.apply(func, args);
      }, interval);
    }
  }

节流

概念:
    规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效

应用场景: 
    滚动刷新如 touchmove, mousemove, scroll。

生活实例:(实例忘记参考谁的了,看到我添加连接)
   1. 我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源
   2. 假设电梯一次只能载一人的话,10 个人要上楼的话电梯就得走 10 次,是一种浪费资源的行为;而实际生活正显然不是这样的,当电梯里有人准备上楼的时候如果外面又有人按电梯的话,电梯会再次打开直到满载位置,从电梯的角度来说,这时一种节约资源的行为(相对于一次只能载一个人)。。
    
代码

function throttle(fn, interval = 100) {
      let canRun = true; // 通过闭包保存一个标记
      return (...args) => {
        if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
        canRun = false; // 立即设置为false
        setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
          fn.apply(fn, args)
          // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
          canRun = true;
        }, interval);
      };
    }

22. 监测数组类型有几种方式 有什么区别

  • instanceof
    • instanceof不一定能保证检测的结果一定正确,因为只要是在原型链上的都会返回true ,arr instanceof Object 也返回true
  • Array.isArray()
    • 兼容性问题
  • Object.prototype.toString.call()
    • 较好方案

23. 观察者模式和发布订阅模式的写一下 说一下也行 区别是什么

木易杨-观察者模式和订阅-发布模式的区别,适用场景

发布订阅模式,在工作中它的能量超乎你的想象

不好意思,观察者模式跟发布订阅模式就是不一样

24. 打印什么 为什么

function foo(a) {
  var a;
  return a;
}
function bar(a) {
    var a = 'bye';
    return a;
}
[foo('hello'), bar('hello')] // hello bye

// 在两个函数里, a作为参数其实已经声明了, 所以 var a; var a = 'bye' 其实就是 a; a ='bye'

25. 说一下原型链

原型链解决的主要是继承问题。

每个对象拥有一个原型对象,通过 proto (读音: dunder proto) 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(==Object.proptotype.proto 指向的是null==)。这种关系被称为原型链 (prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。 构造函数 Parent、Parent.prototype 和 实例 p 的关系如下:(==p.proto === Parent.prototype==)

参考刘小夕解答

26 输出什么

var name = "World!";
(function() {
    if(typeof name=== 'undefined'){
        var name='Jack';
        console.log('Goodbye'+name);

    } else {
      console.log('hello'+name);
    }
})()

// Goodbye Jack

27. 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

跳到 n 阶假设有 f(n)种方法。

往前倒退,如果青蛙最后一次是跳了 2 阶,那么之前有 f(n-2)种跳法; 如果最后一次跳了 1 阶,那么之前有 f(n-1)种跳法。

所以:f(n) = f(n-1) + f(n-2)。就是斐波那契数列

参考

28. 打印什么

class Chameleon {
  static colorChange(newColor) {
    this.newColor = newColor
    return this.newColor
  }

  constructor({ newColor = 'green' } = {}) {
    this.newColor = newColor
  }
}

const freddie = new Chameleon({ newColor: 'purple' })
freddie.colorChange('orange')

// 会报错
colorChange是一个静态方法。静态方法被设计为只能被创建它们的构造器使用(也就是 Chameleon),并且不能传递给实例。因为 freddie 是一个实例,静态方法不能被实例使用,因此抛出了 TypeError 错误。

解答参考

29. 打印什么 为什么

function* generator(i) {
  yield i;
  yield i * 2;
}

const gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 20

一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到yield关键字的时候,会生成yield后面的值。注意,生成器在这种情况下不 返回 (return )值,而是 生成 (yield)值。

首先,我们用10作为参数i来初始化生成器函数。然后使用next()方法一步步执行生成器。第一次执行生成器的时候,i的值为10,遇到第一个yield关键字,它要生成i的值。此时,生成器“暂停”,生成了10。

然后,我们再执行next()方法。生成器会从刚才暂停的地方继续,这个时候i还是10。于是我们走到了第二个yield关键字处,这时候需要生成的值是i*2,i为10,那么此时生成的值便是20。所以这道题的最终结果是10,20

参考MDN

解答参考

30 打印什么

const num = parseInt("7*6", 10);
console.log(num) // 7

parseInt检查字符串中的字符是否合法. 一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。

31. 继承优缺点

参考JS原型链与继承别再被问倒了

32. class 实现队列 链表

艾伦-前端数据结构与算法系列

33. array.sort如何实现的

参考githubarray源码-

34. 实现object.create


function create(proto) { 
    function F() {};
    F.prototype = proto; 
    F.prototype.constructor = F; 
    return new F();
}

关于JS中一些重要的api实现, 巩固你的原生JS功底

35. 说一下Reflect

只能贴下MDN了

36. 说一下generator

zhangxiang958-理解 ES6 generator

阮一峰

37 .说一下proxy

Proxy及其优势

快来围观一下JavaScript的Proxy

Proxy 的巧用

框架(vue)本人一直在用vue

1. mvvm

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <div id='app'>
        <h3>姓名</h3>
        <p>{{name}}</p>
        <h3>年龄</h3>
        <p>{{age}}</p>
    </div>
</body>
</html>
<script>
document.addEventListener('DOMContentLoaded', function(){
    let opt = {el:'#app', data:{name:'检索中...', age:30}}
    let vm = new Vue(opt)
    setTimeout(() => {
        opt.data.name = '王永峰'
    }, 2000);
}, false)
class Vue{
    constructor(opt){
        this.opt = opt
        this.observe(opt.data)
        let root = document.querySelector(opt.el)
        this.compile(root)
    }
    // 为响应式对象 data 里的每一个 key 绑定一个观察者对象
    observe(data){ 
        Object.keys(data).forEach(key => {
            let obv = new Observer() 
            data["_"+key] = data[key]
            // 通过 getter setter 暴露 for 循环中作用域下的 obv,闭包产生
            Object.defineProperty(data, key, {
                get(){
                    Observer.target && obv.addSubNode(Observer.target);
                    return data['_'+key]
                }, 
                set(newVal){
                    obv.update(newVal)
                    data['_'+key] = newVal
                }
            })
        })
    }
    // 初始化页面,遍历 DOM,收集每一个key变化时,随之调整的位置,以观察者方法存放起来    
    compile(node){
        [].forEach.call(node.childNodes, child =>{
            if(!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)){
                let key = RegExp.$1.trim()
                child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*'+ key +'\\s*\\}\\}', 'gm'),this.opt.data[key]) 
                Observer.target = child
                this.opt.data[key] 
                Observer.target = null
            }
            else if (child.firstElementChild) 
            this.compile(child)
        })
    }    
}
// 常规观察者类
class Observer{
    constructor(){
        this.subNode = []    
    }
    addSubNode(node){
        this.subNode.push(node)
    }
    update(newVal){
        this.subNode.forEach(node=>{
            node.innerHTML = newVal
        })
    }
}
</script>

参考-50行代码的MVVM,感受闭包的艺术

DMQ-剖析vue实现原理,自己动手实现mvvm

2. computed 和 methods 区别

  1. computed 会基于响应数据缓存,methods不会缓存
  2. diff之前先看data里的数据是否发生变化,如果没有变化computed的方法不会执行,但methods里的方法会执行

3. webpack常用配置有哪些, 如何进行打包优化 , 原理是什么

提取公共模块,区分开发环境,移除重复不必要的 css 和 js 文件等方面说。

Webpack打包优化

Webpack打包原理

Array-Huang-的博客

4. vuex数据流

参考- VUEX 中的数据流

5. 说一下数据双向绑定

yck-数据双向绑定

心谭-数据双向绑定

Vue 双向数据绑定原理分析

6. key的作用

使用 v-for更新已渲染的元素列表时,默认用就地复用策略。列表数据修改的时候,他会根据key值去判断某个值是否修改:如果修改,则重新渲染这一项;否则复用之前的dom,仅修改value值。

vue开发看这篇文章就够了

7. 什么是函数式组件

函数式组件,即无状态,无法实例化,内部没有任何生命周期处理方法,非常轻量,因而渲染性能高,特别适合用来只依赖外部数据传递而变化的组件。

官网

8. webpack 如何打包scss

webpack编译打包scss

关于webpack4的14个知识点,童叟无欺

webpack实战场景系列

webpack相关的面试题

9. template编译原理

关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,这三个部分是有前后关系的:

  • 第一步是将 模板字符串 转换成 element ASTs(解析器)
  • 第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
  • 第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)

Vue 模板编译原理

vue源码解析

其他

1. 说下你遇到的难题 讲下你的项目吧。不要说业务逻辑

该题是遇到问题最多的 根据自身情况回答

2. rem的原理 和 vw相比有什么区别

vw: 兼容性问题 ios8、安卓4.4及以上才完全支持

rem:和根元素font-size值强耦合,系统字体放大或缩小时,会导致布局错乱;弊端之二:html文件头部需插入一段js代码

3. 说下重绘和回流 怎么避免

参考yck面试经下面有链接

重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大。

  • 重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘
  • 回流是布局或者几何属性需要改变就称为回流。

回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。

  • 改变 window 大小
  • 改变字体
  • 添加或删除样式
  • 文字改变
  • 定位或者浮动
  • 盒模型

重绘和回流其实和 Event loop 有关

  1. 当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。
  2. 然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
  3. 判断是否触发了 media query 更新动画并且发送事件
  4. 判断是否有全屏操作事件
  5. 执行 requestAnimationFrame 回调
  6. 执行 IntersectionObserver
  7. 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好
  8. 更新界面
  9. 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调。

以上参考yck面试经

腾讯IVWEB团队-你真的了解回流和重绘吗

4. 说下跨域 原理是什么、

浪里行舟-九种跨域方式实现原理(完整版)

5. 怎么判断页面是否加载完成?

Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。

DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载。

6. cdn 的原理

由于用户访问源站业务有性能瓶颈,通过cdn技术把源站的内容缓存到多个节点。用户向源站域名发起请求时,请求会被调度至最接近用户的服务节点,直接由服务节点直接快速响应,有效降低用户访问延迟,提升可用性。

7. 从URL输入到页面展现到底发生什么

  • DNS 解析:将域名解析成 IP 地址
  • TCP 连接:TCP 三次握手
  • 发送 HTTP 请求
  • 服务器处理请求并返回 HTTP 报文
  • 浏览器解析渲染页面
  • 断开连接:TCP 四次挥手

浪里行舟-从URL输入到页面展现到底发生什么

说一下 import 和 require 的区别

参考import、require、export、module.exports 混合使用详解

css

1. 高度已知,三栏布局,左右宽度300,中间自适应

/* 浮动布局 */
  .layout.float .left{
    float:left;
    width:300px;
    background: red;
  }
  .layout.float .center{
    background: yellow;
  }
  .layout.float .right{
    float:right;
    width:300px;
    background: blue;
  }
  
  
 /* 绝对定位布局 */
 .layout.absolute .left-center-right>div{
  position: absolute;
 }
.layout.absolute .left{
  left:0;
  width: 300px;
  background: red;
}
.layout.absolute .center{
  left: 300px;
  right: 300px;
  background: yellow;
}
.layout.absolute .right{
  right:0;
  width: 300px;
  background: blue;
}

 /* flex布局 */
.layout.flexbox{
  margin-top: 110px;
}
.layout.flexbox .left-center-right{
  display: flex;
}
.layout.flexbox .left{
  width: 300px;
  background: red;
}
.layout.flexbox .center{
  flex:1;
  background: yellow;
}
.layout.flexbox .right{
  width: 300px;
  background: blue;
}

参考 snowLu作者

2. 水平垂直居中

按照固定宽高和不固定宽高分类各说几个方法

参考16种方法实现水平居中垂直居中

3. css动画和js动画有什么区别

developers

CSS动画 VS JavaScript

css动画与js动画的区别职位