前端面试准备(课程总结)


(一) JS基础篇

一、JS基础

1.变量类型和计算

1.1 题目

  • JS中使用typeof能得到哪些类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    typeof undefined //undefined
    typeof 'abc' //string
    typeof 123 //number
    typeof true //boolean
    typeof {} //object
    typeof [] //object
    typeof null //object,这是一个历史错误
    typeof console.log //function
    //typeof可以详细区别值类型(undefined、string、number、boolean),但不能详细区分引用类型
  • 何时使用 === 何时使用 ==

    1
    2
    3
    4
    5
    if(obj.a == null){
    //这里相当于 obj.a === null || obj.a === undefined, 简写形式
    //这是 jquery 源码推荐的写法
    //除了这种情况外 其它情况一律采用===写法
    }
  • JS中有哪些内置函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //数据封装类对象(原生对象,需要new)
    Number
    String
    Boolean
    Object
    Array
    Function
    Date
    RegExp
    Error
  • JS变量按照存储方式分为哪些类型,并描述其特点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //值类型 (互不影响)
    var a=10
    var b=a
    a=11
    console.log(b) //10

    //引用类型(相互影响)
    var obj1={ x:100 }
    var obj2=obj1
    obj2.x=200
    console.log(obj1.x) //200
  • 如何理解JSON

    1
    2
    3
    //JSON不仅是一种数据格式,还是JS的内置对象
    JSON.stringfy({a:10,b:20})
    JSON.parse('{"a":10,"b":20}')

1.2 涉及知识点

  • 变量类型

    • 值类型 vs 引用类型({} [] function)
    • typeof运算符详解(number string boolean undefined object function)
  • 变量计算

    • 强制类型转换

      • 字符串拼接

        1
        100 + '10' // '10010'
      • ==运算符

        1
        2
        3
        100 == '100'
        '' == 0
        null == undefined
      • if语句

        1
        2
        3
        if(100) { console.log(110) }
        if('') { console.log(110) }
        //if语句的条件转换为false的情况:0 '' false NaN null undefined
      • 逻辑运算符

        1
        2
        3
        4
        5
        6
        7
        10 && 0 //0 , 10->true
        '' || 'abc' //"abc", ''->false
        //检验一个变量是true或false
        var a=100
        !!a //true
        var b=''
        !!b //false
2.原型和原型链

2.1 题目

  • 如何判断一个变量是数组类型

    1
    2
    3
    var arr=[];
    arr instanceof Array;//true
    typeof arr;//object,typeof 无法判断是否是数组类型
  • 写一个原型链继承的例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //封装一个 DOM 查询的例子
    function Elem(id){
    this.elem=document.getElementById(id);
    }

    Elem.prototype.html=function(val){
    var elem=this.elem
    if(val){
    elem.innerHTML=val;
    return this;//链式操作
    }else{
    return elem.innerHTML;
    }
    }

    Elem.prototype.on=function(type,fn){
    var elem=this.elem;
    elem.addEventListener(type,fn);
    return this;//链式操作,this指向构造函数的实例
    }

    var el=new Elem('div1');
    console.log(el.html());
    //链式操作
    el.html('<p>hello world!</p>').on('click',function(){
    alert('clicked');
    }).html('<p>javascript</p>');
  • 描述 new 一个对象的过程

    1
    2
    3
    4
    1. 创建一个新对象
    2. this指向这个新对象
    3. 执行代码,即对this赋值
    4. return this
  • zepto (或其他框架) 源码中如何使用原型链

2.2 涉及的知识点

  • 构造函数

  • 构造函数—扩展

  • 原型规则和实例

    • 所有的引用类型(对象 数组 函数),都具有对象特性,即可自由扩展属性(除了 null 以外)

      1
      2
      3
      4
      var obj={};obj.a=100;
      var arr=[];arr.a=100;
      function fn() {}
      fn.a=100;
    • 所有的引用类型(对象 数组 函数),都有一个 proto 属性(隐式原型),属性值是一个普通对象

    • 所有函数,都有一个prototype属性(显式原型),属性值也是一个普通对象

    • 所有的引用类型(对象 数组 函数), proto 属性指向它的构造函数的 prototype 属性值

    • 当试图得到一个对象的某个属性时,如果此对象本身没有这个属性,那么会去它的 proto (即它的构造函数的prototype)中寻找

补充

1.this 指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//构造函数
function Foo(name,age){
this.name=name;
}
Foo.prototype.alertName=function (){
alert(this.name);
}

//创建实例
var f=new Foo('winnie');
f.printName=function (){
console.log(this.name);
}

//测试
f.printName();
f.alertName();
//通过对象f执行的函数,不管是自身的属性还是原型上的属性,this都指向f

2.循环对象自身的属性

1
2
3
4
5
6
7
8
var item
for(item in f){
//高级浏览器已经在for in 中屏蔽了来自原型的属性
//但是还是建议加上以下的判断,保证程序的健壮性
if(f.hasOwnProperty(item)){
...
}
}
  • 原型链

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //构造函数
    function Foo(name,age){
    this.name=name;
    }
    Foo.prototype.alertName=function (){
    alert(this.name);
    }

    //创建实例
    var f=new Foo('winnie');
    f.printName=function (){
    console.log(this.name);
    }

    //测试
    f.printName();
    f.alertName();
    f.toString();//要去f.__proto__.__proto__中寻找

    原型链

    提醒:当试图得到一个对象的某个属性时,如果此对象本身没有这个属性,那么会去它的 proto (即它的构造函数的prototype)中寻找

  • instanceof

    用于判断 引用类型 属于哪个 构造函数 的方法

    判断逻辑:顺着 f 的 proto 一层一层往上

3.作用域和闭包

3.1 题目

  • 说一下对变量提升的理解

    • 变量定义
    • 函数声明(注意和函数表达式的区别)

    提示:与执行上下文相关

  • 说明 this 几种不同的使用场景

    • 作为构造函数执行

    • 作为对象属性执行

    • 作为普通函数执行

    • call apply bind

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      //构造函数
      function Person(name){
      this.name=name;
      console.log(this);//Person {name: "winnie"}
      }
      var f=new Person('winnie');

      //对象属性
      var obj={
      name:'winnie',
      printName:function(){
      console.log(this);//{name: "winnie", printName: ƒ}
      }
      }
      obj.printName();

      //普通函数
      function fn(){
      console.log(this);//window
      }
      fn();

      //call apply bind
      var fn1=function (name){
      alert(name);
      console.log(this);//{x:100}
      }.bind({x:100})
      fn1('winnie');
  • 创建10个标签,点击的时候弹出来对应的序号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var i;
    for(i=0;i<10;i++){
    (function(i){
    //函数作用域
    var a = document.createElement('a');
    a.innerHTML= i+'<br/>';
    a.addEventListener('click',function(e){
    e.preventDefault();
    alert(i);//自由变量,要在父级作用域获取值
    })
    document.body.appendChild(a);
    })(i)
    }
  • 如何理解作用域

    1.没有块级作用域

    2.只有全局和函数作用域(函数里面的变量,外面无法访问和修改,只能函数里面使用)

    要点:

    • 自由变量:当前作用域没有定义的变量

    • 作用域链,即自由变量的查找:函数的父级作用域,是函数定义时的父级作用域,并非执行时的父级作用域

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      var a=100;
      function F1(){
      var b=200;
      function F2(){
      var c=300;
      console.log(a);//a是自由变量,查询链:F2->F1->全局
      console.log(b);
      console.log(c);
      }
      F2();
      }
      F1();
    • 闭包的两个场景

      • 函数作为返回值
      • 函数作为参数传递
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      function F1(){
      var a=100;
      return function F2(){
      console.log(a);
      }
      }
      var f1=F1();//返回函数F2
      var a=200;
      f1();//100 自由变量查找的父级作用域,是函数定义时的父级作用域

      //函数作为参数传递
      function F1(){
      var a=100;
      return function (){
      console.log(a);
      }
      }
      var f1=F1();

      function F2(fn){
      var a=200
      fn();//fn定义时的父级作用域是F1,执行时的父级作用域是F2
      }
      F2(f1);//100
  • 实际开发中闭包的应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //闭包实际应用中主要用于封装变量,收敛权限
    function isFirstLoad(){
    var _list=[];
    return function(id){
    if(_list.indexOf(id)>=0){
    return false;
    }else{
    _list.push(id);
    return true;
    }
    }
    }

    // 使用
    var firstLoad=isFirstLoad();
    firstLoad(10);//true
    firstLoad(10);//false

3.2 涉及知识点

  • 执行上下文

    • 范围:一段< script >或者一个函数
    • 全局(一段< script > ):变量定义、函数声明在执行之前往上提升
    • 函数:变量定义、函数声明、this、arguments在执行之前往上提升
  • this

    this要在执行时才能确认值,定义时无法确认

  • 作用域

  • 作用域链

  • 闭包

4.异步与单线程

4.1 题目

  • 同步和异步的区别是什么?分别举一个同步和异步的例子

    • 同步会阻塞代码执行,而异步不会
    • alert是同步,setTimeout是异步
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //异步
    console.log(100);
    setTimeout(function(){
    console.log(200)
    },1000);
    console.log(300);

    //同步
    console.log(100);
    alert(200); //1s之后点击确定
    console.log(300);
  • 一个关于setTimeout的笔试题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    console.log(1);
    setTimeout(function(){
    console.log(2)
    },0);
    console.log(3);
    setTimeout(function(){
    console.log(4);
    },1000);
    console.log(5);

    //连续打印1 3 5 2,等待一秒后打印4
  • 前端使用异步的场景有哪些

    • 定时任务:setTimeout,setInterval
    • 网络请求:ajax请求,动态 加载
    • 事件绑定

    共同特点:都需要等待,不使用异步,会造成代码执行阻塞

4.2 涉及的知识点

  • 什么是异步(对比同步)
  • 前端使用异步的场景
  • 异步和单线程
5.其它知识

5.1 题目

  • 获取 2017-06-10 格式的日期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function formatDate(dt){
    if(!dt){
    var dt=new Date();
    }
    var year=dt.getFullYear();
    var month=dt.getMonth()+1;
    var date=dt.getDate();
    //强制类型转换
    if(month<10){
    month='0'+month;
    }
    if(date<10){
    date='0'+date;
    }
    return year+'-'+month+'-'+date;
    }
  • 获取随机数,要求是长度一致的字符串格式

    1
    2
    var random=(Math.random()+'0000000000').slice(0,10);
    console.log(random);
  • 写一个能遍历对象和数组的通用forEach函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function forEach(obj,fn){
    if(obj instanceof Array){
    obj.forEach(function(item,index){
    fn(index,item);
    });
    }
    else{
    for(key in obj){
    fn(key,obj[key]);
    }
    }
    }

5.2 涉及知识点

  • 日期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Date.now(); //获取当前时间毫秒数
    var dt=new Date();
    dt.getTime();//获取毫秒数
    dt.getFullYear();//年
    dt.getMonth();//月(0-11)
    dt.getDate();//日(0-31)
    dt.getHours();//时(0-23)
    dt.getMinutes();//分(0-59)
    dt.getSeconds();//秒(0-59)
  • Math

    • Math.random()
  • 对象API

    • for in
  • 数组API

    • forEach 遍历所有元素

      1
      2
      3
      4
      var arr=[1,2,3];
      arr.forEach(function(item,index){
      console.log(index,item);
      });
  • every 判断所有元素是否都符合条件

    1
    2
    3
    4
    5
    6
    7
    8
    var arr=[1,2,3];
    var res=arr.every(function(item,index){
    //用来判断数组的所有元素,全部都满足条件
    if(item<4){
    return true;
    }
    })
    console.log(res);//true
  • some 判断是否有至少一个元素符合条件

    1
    2
    3
    4
    5
    6
    7
    8
    var arr=[1,2,3];
    var res=arr.some(function(item,index){
    //用来判断数组的所有元素,只要有一个满足条件即可
    if(item<2){
    return true;
    }
    })
    console.log(res);//true
  • sort 排序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var arr=[1,4,2,3,5];
    var arr2=arr.sort(function(a,b){
    //从小到大排序
    return a-b;

    //从大到小排序
    //return b-a
    })
    console.log(arr2);//[1,2,3,4,5]
  • map 对数组重新组装,生成新数组

    1
    2
    3
    4
    5
    6
    var arr=[1,2,3,4];
    var arr2=arr.map(function(item,index){
    //将元素重新组装,并返回
    return '<b>'+item+'</b>';
    })
    console.log(arr2);//["<b>1</b>", "<b>2</b>", "<b>3</b>", "<b>4</b>"]
  • filter 过滤符合条件的元素

    1
    2
    3
    4
    5
    6
    7
    8
    var arr=[1,2,3];
    var arr2=arr.filter(function(item,index){
    //通过某一条件过滤数组
    if(item>=2){
    return true;
    }
    });
    console.log(arr2);//[2, 3]

二、JS-Web-API

1.DOM

1.1 题目

  • DOM是哪种基本的数据结构?

  • DOM操作常用的API有哪些?

    • 获取DOM节点,以及节点的property和Attribute
    • 获取父节点,获取子节点
    • 新增节点,删除节点
  • DOM节点的attr和property有何区别

    • property只是一个JS对象属性的修改
    • Attribute是对html标签属性的修改

1.2 涉及知识点

  • DOM(Document Object Model 文档对象模型)本质

    浏览器把拿到的html代码,结构化为一个浏览器能识别并且JS可操作的一个模型。

  • DOM节点操作

    • 获取DOM节点

      1
      2
      3
      4
      var div1=document.getElementById('div1');//元素
      var divList=document.getElementsByTagName('div');//集合
      var containerList=document.getElementsByClassName('.container');//集合
      var pList=document.querySelectorAll('p');//集合
    • property

      1
      2
      3
      4
      5
      6
      7
      8
      9
      var pList=document.querySelectorAll('p');
      var p=pList[0];//p本质上是一个JS对象,指向node节点
      console.log(p.style.width);//获取样式
      p.style.width='100px';//修改样式
      console.log(p.className);//获取class
      p.className='p1';//修改class
      //获取nodeName和nodeType
      console.log(p.nodeName);
      console.log(p.nodeType);
  • Attribute

    1
    2
    3
    4
    5
    6
    var pList=document.querySelectorAll('p');
    var p=pList[0];
    p.getAttribute('data-name');
    p.setAttribute('date-name','imooc');
    p.getAttribute('style');
    p.setAttribute('style','font-size:30px;');
  • DOM结构操作

    • 新增节点

      1
      2
      3
      4
      5
      6
      7
      8
      var div1=document.getElementById('div1');
      //添加新节点
      var p1=document.createElement('p');
      p1.innerHTML="This is p1";
      div1.appendChild(p1);//添加新创建的元素
      //移动已有节点
      var p2=document.getElementById('p2');
      div1.appendChild(p2);
  • 获取父元素

  • 获取子元素

  • 删除节点

    1
    2
    3
    4
    var div1=document.getElementById('div1');
    var parent=div1.parentElement;
    var child=div1.childNodes;
    div1.removeChild(child[0]);
2.BOM

2.1 题目

  • 如何检测浏览器的类型

    参考下面的navigator

  • 拆解url的各部分

    参考下面的location

2.2 涉及的知识点

  • navigator

    1
    2
    3
    var ua=navigator.userAgent;
    var isChrome=ua.indexOf('Chrome');
    console.log(isChrome);
  • screen

    1
    2
    console.log(screen.width);
    console.log(screen.height);
  • location

    1
    2
    3
    4
    5
    6
    7
    //针对url
    console.log(location.href);//完整的url
    console.log(location.host);//域名
    console.log(location.protocol);//协议
    console.log(location.pathname);//除域名和协议外的url部分,是路径
    console.log(location.search);//?后面的查询字符串,查询参数
    console.log(location.hash);//# 后面的hash参数
  • history

    1
    2
    history.forward();
    history.back();
3.事件

3.1题目

  • 编写一个通用的事件绑定函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function bindEvent(elem,type,selector,fn){
    if(fn==null){
    fn=selector;
    selector=null;
    }
    elem.addEventListener(type,function(e){
    var target;
    if(selector){
    target=e.target;
    if(target.matches(selector)){
    fn.call(target,e);
    }
    }else{
    fn(e);
    }
    })
    }

    //使用代理(四参数)
    var div1=document.getElementById('div1');
    bindEvent(div1,'click','a',function(e){
    console.log(this.innerHTML);
    })
    //不使用代理(三参数)
    var a=document.getElementById('a1');
    bindEvent(div1,'click',function(e){
    console.log(a.innerHTML);
    })
  • 描述事件冒泡流程

    • DOM树形结构
    • 事件冒泡
    • 阻止冒泡
    • 冒泡的应用(代理)
  • 对于一个无限下拉加载图片的页面,如何给每个图片绑定事件

    • 使用代理
    • 知道代理的两个优点

3.2 涉及的知识点

  • 通用事件绑定

  • 事件冒泡

  • 代理

    好处:

    • 代码简洁(不需要写很多事件绑定)
    • 减少浏览器内存占用(绑定一次事件和绑定一千次是不一样的)

    原理:事件冒泡

4.Ajax

4.1 题目

  • 手动编写一个ajax,不依赖第三方库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    function ajax(url,fnSucc,fnFail){
    //创建oAjax对象(拥有一个手机)
    if(window.XMLHttpRequest){
    var oAjax=new XMLHttpRequest();//非IE6
    }else{
    var oAjax=new ActiveXObject('MicroSoft.XMLHTTP');//IE6
    }

    //连接到服务器(拨号)
    oAjax.open("GET",url,true);
    //发送请求(说)
    oAjax.send();
    //接收返回值(听)
    oAjax.onreadystatechange=function (){
    if(oAjax.readyState==4){
    if(oAjax.status==200){
    fnSucc(oAjax.responseText);
    }else{
    if(fnFail){
    fnFail(oAjax.status);
    }
    }
    }
    }
    }
  • 跨域的几种实现方式

    详情见跨域

4.2 涉及的知识点

  • XMLHttpRequest
  • readyState(服务器和浏览器,进行到哪一步了,请求状态)
    • 0 ——未初始化,还没调用open方法,没有连接到服务器
    • 1——(载入)已调用send()方法,正在发送请求
    • 2——(载入完成)send()方法完成,浏览器已接收到全部响应内容
    • 3——(解析)正在解析响应内容
    • 4——(完成)响应内容解析完成,还要判断是否解析成功
  • 状态码说明
    • 2xx -表示成功处理请求。如:200
    • 3xx - 表示重定向,浏览器直接跳转
    • 4xx - 客户端请求错误,如404
    • 5xx - 服务端请求错误
  • 跨域
    • 定义:浏览器有同源策略,不允许 ajax 访问其他域接口
    • 条件:协议、域名、端口,有一个不同就算跨域
    • 有三个标签允许跨域加载资源:
      • < img src=xxx> 用于打点统计,统计的网站可能是其他域
      • < link href=xxx> 可以使用CDN ,CDN也是其他域
      • < script src=xxx> 可以使用CDN,也可以用于JSONP
5.存储

5.1 题目

  • 请描述一下cookie、sessionStorage和localStorage的区别
    • 容量:前者是4KB,后者是5MB
    • 是否会携带到ajax中:前者是每次http请求都会携带,后者是HTML5专门为存储而设计
    • API易用性:前者需要封装document.cookie,后者有专门获取和修改的getItem和setItem两个API

5.2 涉及的知识点

  • cookie

    • 本身用于客户端和服务器端通信
    • 但是它有本地存储的功能,于是就被”借用”
    • 使用 document.cookie=… 获取和修改即可(获取的是字符串)

    缺点:

    • 存储量太小,只有4KB
    • 所有 http 请求都带着,会影响获取资源的效率
    • API简单,需要封装才能用 document.cookie=……
  • locationStorage和sessionStorage

    • HTML5专门为存储而设计,最大容量5M
    • API简单易用
    • localStorage.setItem(key,value);localStorage.getItem(key)

    建议:

​ 统一使用try-catch封装(ios safari 隐藏模式下 localStorage.getItem会报错)

三、JS开发环境

1.git

常用Git命令

  • git add .

  • git status

  • git diff

  • git checkout xxx —还原到改动前

  • git commit -m “xxx” —提交到本地仓库

  • git push origin master —提交到远程仓库

  • git pull origin master —拉取远程仓库master分支上的代码

    下列命令常用于多人协作:

  • git branch —列出所有的分支

  • git checkout -b xxx —创建新的分支,并切换

  • git checkout xxx —切换分支

  • git merge xxx —将当前分支与xxx分支合并。将本地分支与master分支合并之前,要将master分支中最新的代码拉取到本地。

2. 模块化
  • 不使用模块化的情况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //util.js  getFormatDate函数
    //a-util.js aGetFormatDate函数 依赖getFormatDate
    //a.js 依赖aGetFormatDate函数

    //util.js
    function getFormatDate(date,type){
    //type==1 返回 2019-02-09
    //type==2 返回 2019年2月9日
    }

    //a-util.js
    function aGetFormatDate(date){
    //返回 2019年2月9日 格式
    return getFormatDate(date,2);
    }

    //a.js
    var dt=new Date();
    console.log(aGetFormatDate(dt));

    //使用,按顺序引用
    <script src='util.js'></script>
    <script src='a-util.js'></script>
    <script src='a.js'></script>
    //这些代码中的函数必须是全局变量,才能暴露给使用方,会造成全局变量污染
    //a.js知道要引用a-util.js,但并不知道要引用util.js,依赖关系不明确
  • 使用模块化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //util.js
    export {
    getFormatDate:function(date,type){
    //type==1 返回 2019-02-09
    //type==2 返回 2019年2月9日
    }
    }

    //a-util.js
    var getFormatDate=require('util.js');
    export{
    aGetFormatDate:function(date){
    //返回 2019年2月9日 格式
    return getFormatDate(date,2);
    }
    }

    //a.js
    var aGetFormatDate=require('a-util.js');
    var dt=new Date();
    console.log(aGetFormatDate(dt));

    //直接<script src='a.js'></script> 其他的根据依赖关系自动引用
    //输出的两个函数,不需要声明为全局变量,避免了变量污染和覆盖的问题
  • AMD(Asynchronous Module Definition 异步模块定义)

    • require.js
    • 全局 define 函数
    • 全局 require 函数
    • 依赖JS会自动、异步加载
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    //util.js
    define(function(){
    return {
    getFormatDate:function(date,type){
    if(type===1){
    return '2019-02-09';
    }
    if(type===2){
    return '2019年2月9日'
    }
    }
    }
    });

    //a-util.js
    define(['./util.js'],function(util){
    return {
    aGetFormatDate:function(date){
    return util.getFormatDate(date,2);
    }
    }
    });

    //a.js
    define(['./a-util.js'],function(aUtil){
    return {
    printDate:function(date){
    console.log(aUtil.aGetFormatDate(date));
    }
    }
    });

    //main.js 入口文件,define过的函数可以require
    require(['./a.js'],function(a){
    var date=new Date();
    a.printDate(date);
    });

    //使用:首先要下载requirejs文件
    <script src="./require.js" data-main='./main.js'></script>
  • CommonJS

    • nodejs 模块化规范,现在被前端大量使用,原因:
      • 前端开发依赖的插件和库,都可以从 npm 中获取
      • 构建工具的高度自动化,使得使用npm的成本非常低
      • CommonJS 不会异步加载JS,而是 同步 一次性加载出来
    • 需要构建工具支持
    • 一般和 npm 一起使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //util.js
    module.exports={
    getFormatDate:function(date,type){
    if(type===1){
    return '2019-02-09';
    }
    if(type===2){
    return '2019年2月9日'
    }
    }
    }

    //a-util.js
    var util=rquire('./util.js');
    module.exports={
    aGetFormatDate:function (date){
    return util.getFormatDate(date,2);
    }
    }

    //a.js
    var aUtil=require('./a-util');
    module.exports={
    print:function(date){
    console.log(aUtil.aGetFormatDate(date));
    }
    }
  • AMD 和 CommonJS的使用场景

    • 需要异步加载JS ,使用AMD
    • 使用了npm 之后建议使用CommonJS
3.上线和回滚
  • 上线流程要点
    • 将测试完成的代码提交到git版本库的master分支
    • 将当前服务器的代码全部打包并记录版本号,备份
    • 将master分支的代码提交覆盖到线上服务器,生成新的版本号
  • 回滚流程要点
    • 将当前服务器的代码打包并记录版本号,备份
    • 将上一个版本的代码解压,覆盖到线上服务器,并生成新的版本号

四、运行环境

1.页面加载过程

1.1 题目

  • 从输入url到得到html的详细过程

    见下述知识点中加载一个资源的过程

  • window.onload 和 DOMContentLoaded 的区别

    1
    2
    3
    4
    5
    6
    window.addEventListener('load',function(){
    //页面的全部资源加载完才会执行,包括图片、视频等
    });
    document.addEventListener('DOMContentLoaded',function(){
    //DOM 渲染完即可执行,此时图片、视频还可能没有加载完
    });

1.2 知识点

  • 加载资源的形式
    • 输入url( 或跳转页面 )加载html
    • 加载html中的静态资源(js、css文件、图片等)
  • 加载一个资源的过程
    • 浏览器根据 DNS 服务器得到域名的 IP 地址
    • 向这个 IP 的机器发送 http 请求
    • 服务器收到、处理并返回 http 请求
    • 浏览器得到返回内容
  • 浏览器渲染页面的过程
    • 根据 HTML 结构生成 DOM Tree
    • 根据 CSS 生成 CSSOM
    • 将 DOM Tree 和 CSSOM 整合形成 RenderTree
    • 根据 RenderTree 开始渲染和展示
    • 遇到 < script >时,会执行并阻塞渲染
2.性能优化
  • 原则

    • 多使用内存、缓存或其他方法
    • 减少 CPU 计算、减少网络请求
  • 加载资源优化

    • 静态资源的压缩合并
    • 静态资源缓存
      • 通过链接名称控制缓存
      • 只有内容改变的时候,链接名称才会改变
    • 使用CDN(content delivery network) 让资源加载更快
    • 使用 SSR (server side render) 后端渲染,数据直接输出到HTML
      • 现在 Vue React 提出了这样的概念
      • 早期的 jsp php asp 都属于后端渲染
  • 渲染优化

    • CSS 放前面,JS放后面

    • 懒加载(图片懒加载、下拉加载更多)

      1
      2
      3
      4
      5
      6
      7
      <img src="preview.png" data-realsrc="abc.png">
      <script type="text/javascript">
      var img1 = document.getElementById('img1');
      img1.src = img1.getAttribute('data-realsrc');
      </script>

      //先加载一个体积较小的图片(浏览器可能还有缓存),浏览器渲染会更快
    • 减少 DOM 查询,对 DOM 查询做缓存

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      //未缓存 DOM 查询
      var i
      for(i=0;i<document.getElementsByTagName('p').length;i++){//每循环一次就要重新查询一次,性能消耗大
      //todo
      }
      //缓存了 DOM 查询
      var pList = document.getElementsByTagName('p');//一次 DOM 查询
      var i ;
      for(i=0;i<pList.length;i++){
      //todo
      }
    • 减少 DOM 操作,多个操作尽量合并在一起执行

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      //合并 DOM 插入
      var listNode = document.getElementById('list');
      //要插入 10 个 li 标签,一般情况要进行10次 DOM 插入
      var frag=document.createDocumentFragment();
      var x,li;
      for(x=0;x<10;x++){
      li = document.createElement('li');
      li.innerHTML = "List item" + x;
      frag.appendChild(li);//这个插入不会触发 DOM 操作
      }
      listNode.appendChild(frag);//这里触发一次DOM插入
    • 事件节流和函数防抖

      • 防抖 原理:延迟一段时间再执行,如果在延迟的时间内继续触发,会重新计算。

      • 节流 原理:隔一段时间,执行一次。就像水龙头滴一样。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        //防抖
        let debounce=(func,delay)=>{
        let tId,context,args;
        return function(){
        context=this;
        args=[].slice.call(arguments,0);
        clearTimeout(tId);
        tId=setTimeout(fuction(){
        func.apply(context,args);
        },delay);
        }
        }

        //节流
        let throttle=(func,delay)=>{
        let timer=null,context,args;
        return function(){
        context=this;
        args=[].slice.call(arguments,0);
        if(!timer){
        timer=setTimeout(function(){
        timer=null;
        func.apply(context,args);
        },delay);
        }
        }
        }
    • 尽早执行操作(如DOMContentLoaded)

3.安全性
  • XSS (cross site script) 跨站脚本攻击

    • 简单事例

      • 在新浪博客写一篇文章,同时偷偷插入一段< script >。( 脚本代码中,获取cookie,并发送给攻击者的服务器)
      • 发布博客,有人查看博客内容
      • 会把查看者的 cookie 发送到攻击者的服务器
    • 预防方法

      • 前端替换关键字,例如替换 < 为 &lt ; > 为 &gt ; (影响性能)
      • 后端替换
  • CSRF/XSRF (Cross-site request forgery)跨站请求伪造

    • 简单事例
      • 你已登录一个购物网站,正在浏览商品。该网站的付费接口是 xxx.com/pay?id=100 但是没有任何验证。
      • 然后你收到一封邮件,隐藏着 < img src=”xxx.com/pay?id=100” >。一旦查看邮件,就会向付费接口发送请求。
      • 你查看邮件的时候,就已经悄悄的付费购买了。
    • 预防方法
      • 增加验证流程,如输入指纹、密码、短信验证码 (后端预防为主)

(二) JS高级篇

一、ES6

题目:

  • ES6 模块化如何使用,开发环境如何打包
    • 语法:import export (注意有无 defult)
    • 环境:babel 编译 ES6语法,模块化可用 webpack 和 rollup
    • 扩展:说一下自己对模块化标准统一的期待
  • Class 和普通构造函数有何区别
    • Class 在语法上更加贴合面向对象的写法
    • Class 实现继承更加易读、易理解
    • 更易于写 java 等后端语言人员的使用
    • 本质还是语法糖
  • Promise 的基本使用和原理
    • new Promise 实例 ,并且 return
    • new Promise 时要传入resolve、reject两个参数
    • 成功时执行resolve() 失败时执行reject()
    • then监听结果
  • 总结一下ES6其他常用功能
1.模块化
  • 模块化的基本语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //util.js
    export default{
    a:100
    }
    //util2.js
    export function fn1(){
    alert('fn1');
    }
    export function fn2(){
    alert('fn2');
    }

    //index.js
    import util from './util.js'
    import { fn1,fn2 } from './util2.js'

    console.log(util);
    fn1();
    fn2();
  • 开发环境配置

    • .babelrc
    • rollup.config.js 或 webpack.config.js ,rollup功能单一,webpack功能强大
  • 关于JS众多模块化标准

    • 一开始没有模块化
    • AMD 成为标准,require.js (也有CMD)
    • 前端打包工具,使得nodejs模块化可以被使用(CommonJS)
    • ES6出现 ,想统一现在所有模块化标准
    • nodejs 积极支持ES6 ,但浏览器尚未统一,需要babel转译
    • 可以自造lib,但是不要自造标准
2.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//Class 语法
class MathHandle{
constructor(x,y){
this.x=x;
this.y=y;
}
add(){
return this.x+this.y;
}
}

const m=new MathHandle(1,2);
console.log(m.add());

typeof MathHandle //"function"
MathHandle === Math.prototype.constructor //true

//继承
class Animal{
constructor(name){
this.name=name;
}
...
}
class Dog extends Animal{
constructor(name){
super(name);
this.name=name;
}
...
}
3.promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//所有图片都加载完再添加到页面
//加载过程
function loadImg(src){
return new Promise((resolve,reject)=>{
let img=document.createElement('img');
img.src=src;
img.onload=function(){
resolve(img);
};
img.onerror=function(){
reject();
}
})
}
//展示
function showImgs(imgs){
console.info(imgs);
imgs.forEach(function(img){
document.body.appendChild(img);
})
}

Promise.all([
loadImg('https://i.loli.net/2019/01/25/5c4b30cbd56ec.jpg'),
loadImg('https://i.loli.net/2019/01/25/5c4b30cbe101e.jpg'),
loadImg('https://i.loli.net/2019/01/25/5c4b30cc8a741.jpg')
]).then(showImgs);
4.Generator和async
1
2
3
4
5
6
7
8
9
10
11
12
13
//Generator函数与iterator遍历器的关系
let obj={};
//调用 Generator 函数后,返回的是一个指向内部状态的指针对象,也就是遍历器对象iterator
obj[Symbol.iterator]=function* (){
yield 1;
yield 2;
yield 3;
}

console.log(obj[Symbol.iterator]().next());
for(let value of obj){
console.log('value',value);
}
5.Proxy和Reflect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function validate(target,validator){
return new Proxy(target,{
_validator:validator,
set(target,key,value,proxy){//返回一个布尔值
if(target.hasOwnProperty(key)){
let vali=this._validator[key];//获取当前key的验证函数
//判断输入的值是否符合验证条件
if(vali(value)){
return Reflect.set(target,key,value,proxy);
}else{
throw Error(`不能设置 ${value}${key}`);
}
}else{
throw Error(`${key} 不存在`);
}
}
})
}
6.decorator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//修饰属性
let readonly=function(target,name,descriptor){
descriptor.writable=false;
return descriptor
}

class Test{
@readonly
time(){
return '2019-01-27'
}
}

//埋点
log=(type)=>{
return function(target,name,descriptor){
let src_method=descriptor.value;
descriptor.value=(...arg)=>{
src_method.apply(target,arg);
console.log(`log ${type}`);
}
}
}

class AD{
@log('show')
show(){
console.info('ad is show');
}
@log('click')
click(){
console.info('ad is click');
}
};
7.其他常用功能
  • let/const
  • 多行字符串/模板变量
  • 解构赋值
  • 块级作用域
  • 函数默认参数
  • 箭头函数

二、原型

题目:

  • 说一个原型的实际应用

    • 描述一下 jquery 如何使用原型
    • 描述一下 zepto 如何使用原型
    • 再结合自己的项目经验,说一个自己开发的例子
  • 原型如何体现它的扩展性

    • 说一下 jquery 和 zepto 的插件机制

      1
      2
      3
      4
      //简单的插件例子
      $.fn.getNodeName=function(){
      return this[0].nodeName;
      }

      扩展到 $.fn , 好处是:

      • 只有$ 会暴露在window全局变量(限制,一般一个库只暴露一个全局变量)
      • 将插件扩展统一到$.fn.xxx这一个接口,方便使用
    • 结合自己的开发经验,做过的基于原型的插件

1.jquery 原型实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 入口函数
var jQuery = function (selector) {
return new jQuery.fn.init(selector)
}
//可用于属性扩展
jQuery.fn = {
css: function (key, value) {
alert('css')
},
html: function (value) {
return 'html'
}
}
//定义构造函数
var init = jQuery.fn.init = function (selector) {
var slice = Array.prototype.slice
var dom = slice.call(document.querySelectorAll(selector))

var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
//定义原型
init.prototype = jQuery.fn
2.zepto 原型实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//空对象
var zepto={};

//构造函数
function Z(dom,selector){
var i,len=dom?dom.length:0;
for(i=0;i<len;i++){
this[i]=dom[i];
}
this.length=len;
this.selector=selector || '';
}

zepto.Z=function(dom,selector){
return new Z(dom,selector);
}
zepto.init=function(selector){
//源码中,这里的处理情况较复杂,因为我们只针对原型,所以做了弱化
var slice=Array.prototype.slice;
var dom=slice.call(document.querySelectorAll(selector));
return zepto.Z(dom,selector);
}
var $=function(selector){
return zepto.init(selector);
}

$.fn={
constructor:zepto.Z,
css:function(key,value){

},
html:function(value){

}
}

zepto.Z.prototype=Z.prototype=$.fn;

三、异步

题目:

  • 什么是单线程,和异步有什么关系

    • 单线程就是同一时间只能做一件事,两段 JS 不能同时执行
    • 原因是为了避免 DOM 渲染的冲突
    • 异步是一种 “无奈” 的解决方案,仍然存在很多问题
      • 没按照书写顺序执行,可读性差
      • callback中不容易模块化
  • 什么是event-loop

    事件轮询,JS 实现异步的具体解决方案:

    • 同步代码,直接执行

    • 异步函数先放在 异步队列

    • 待同步函数执行完毕后,轮询执行异步队列中的函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      $.ajax({
      url:"xxxx",
      success:function(result){
      console.log('a');
      }
      })
      setTimeout(function(){
      console.log('b')
      },100);
      setTimeout(function(){
      console.log('c')
      });
      console.log('d');

      //主进程
      console.log('d');

      //异步队列
      //立刻被放入
      function(){
      console.log('c');
      }
      //100ms 之后被放入
      function(){
      console.log('b');
      }
      //ajax 加载完成时放入
      function(result){
      console.log('a');
      }
  • 是否用过 jQuery的deferred
  • Promise的基本使用和原理
  • 介绍一下 async/await (和Promise 的区别、联系)
  • 总结一下当前 JS 解决异步的方案

四、virtual dom(虚拟DOM)

题目:

  • vdom是什么?为何会存在vdom?

    vdom是用 JS 模拟DOM结构,存在原因:

    • DOM 操作非常”昂贵”
    • 将DOM操作放在JS层,提高效率
  • vdom如何应用,核心API是什么?

    可用snabbdom的用法来举例,核心API:h函数、patch函数

  • 介绍一下diff算法

    • 什么是 diff 算法

      linux的基础命令

    • 去繁就简

      关注核心流程

    • vdom 为何用 diff 算法

      • DOM 操作是”昂贵”的,因此要尽量减少 DOM 操作

      • 找出本次DOM必须更新的节点来更新,其他的不更新

      • 这个”找出”的过程,就需要 diff 算法
    • diff 算法的实现流程

      • patch(container,vnode) —> 核心函数 createElement
      • patch(vnode,newVnode) —> 核心函数 updateChildren
1.设计一个需求场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//1.将以下数据展示成一个表格  2.随便修改数据中的一个信息,表格也随着修改
[
{
name: '张三',
age: '20',
address: '北京'
},
{
name: '李四',
age: '21',
address: '上海'
},
{
name: '王五',
age: '22',
address: '广州'
}
]
/* jquery 实现 */
//渲染函数
function render(data){
var $container=$('#container');

//清空容器
$container.html('');

//拼接table
var $table=$('<table>');
$table.append($('<tr><td>name</td><td>age</td><td>address</td>/tr>'));
data.forEach(function(item){
$table.append($('<tr><td>' + item.name + '</td><td>' + item.age + '</td><td>' + item.address + '</td>/tr>'));
});

//添加到页面
$container.append($table);
}

$('#btn-change').click(function(){
data[1].age= 30;
data[2].address='深圳'
//改变以后,再次渲染
render(data);
})
//初次渲染
render(data);

/* snabbdom 实现 */
var snabbdom=window.snabbdom;
//定义 patch
var patch=snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
]);
//定义关键函数 h
var h=snabbdom.h;
//渲染函数
var vnode;
function render(data){
var newVnode=h('table',{},data.map(function(item){
var tds=[];
var key;
for(key in item){
if(item.hasOwnProperty(key)){
tds.push(h('td',{},item[key]+''));
}
}
return h('tr',{},tds);
}));

if(vnode){
//re-render
patch(vnode,newVnode);
}else{
//初次渲染
patch(container,newVnode);
}
//存储当前 vnode 结果
vnode=newVnode;
}
//初次渲染
render(data);

var btnChange=document.getElementById('btn-change');
btnChange.addEventListener('click',function(){
data[1].age = 30
data[2].address = '深圳'
//re-render
render(data);
});
2.patch(container,vnode)和patch(vnode,newVnode)核心实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//vnode -> 真实dom节点
function createElement(vnode){
//获取vnode上的属性
var tag=vnode.tag;
var attrs=vnode.attrs || {};
var children=vnode.children || [];

//创建真实的 DOM 元素
var elem=document.createElement(tag);
// 给标签添加属性
var attrName
for(attrName in attrs){
elem.setAttribute(attrName,attrs[attrName]);
}
//添加标签下面的子元素
children.forEach(function(childVnode){
//添加的是真实 DOM 元素而非虚拟的 vnode
elem.appendChild(createElement(childVnode));
});
//返回真实的 DOM 元素
return elem;
}

//两个虚拟dom的对比和更新
function updateChildren(vnode,newVnode){
var children=vnode.children || [];
var newChildren=newVnode.children || [];

children.forEach(function(childVnode,index){
var newChildVnode=newChildren[index];
if(childVnode.tag === newChildVnode.tag){
//假设tag、属性等相同,进入深层对比,递归
updateChildren(childVnode,newChildVnode)
}else{
//不相同直接替换
replaceNode(childVnode,newChildVnode);
}
});
}

function replaceNode(vnode,newVnode){
var elem=vnode.elem; //真实的 DOM 节点
var newElen=createElement(newVnode);
// 节点替换
//todo
}

五、MVVM 和 Vue

题目:

  • 说一下使用 jQuery 和使用框架的区别

    • 数据和视图的分离,解耦(开放封闭原则:对扩展开放,对修改封闭)
      • jQuery 数据和视图没有分离;vue 数据和视图分离
    • 以数据驱动视图,只关心数据变化,DOM 操作被封装
      • vue 以数据驱动视图,并没有操作DOM;jQuery则相反
  • 说一下对MVVM的理解

    • MVC(M-Model V-View C-Controller)

      • M - Model 数据
      • V - View 视图、界面
      • C - Controller 控制器、逻辑处理


  • MVVM(M-Model V-View VM-ViewModel)

    • Model - 模型、数据
    • View - 视图、模板(视图和模型是分离的)
    • ViewModel - 连接 Model 和 View

    MVVM

    三者之间的联系,以及对应到示例代码:

    关于ViewModel

**MVVM/Vue三要素:**

* 响应式:vue 如何监听到data中每个属性的变化?
* 模板引擎:vue的模板如何被解析,指令如何处理?
* 渲染:vue的模板如何被渲染成html?以及渲染过程
  • vue 中如何实现响应式

    • 关键是理解 Object.defineProperty
    • 将 data 的属性代理到 vm 上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var vm={};
    var data={
    name:"winnie",
    age:20
    };
    var key,value;
    for(key in data){
    (function(key){
    Object.defineProperty(vm,key,{
    get:function(){
    console.log('get',data[key]);
    return data[key];
    },
    set:function(newVal){
    console.log('set',newVal);
    data[key]=newVal;
    }
    })
    })(key)
    }
  • vue中如何解析模板

    • 模板:本质上是一段字符串,与 html 格式很像,但有很大区别,不过最终还是转换为html来显示。有逻辑( 如 v-if v-for ),嵌入JS变量……

    • 模板必须转换为 JS 代码,原因如下:

      • 有逻辑(v-if v-for),必须用 js 才能实现(图灵完备)
      • 转换为 html 渲染页面,必须用 JS才能实现(JS可以操作DOM)

      因此,模板最终转换成一个JS函数(render函数)

    • render 函数 ,函数执行返回 vnode ——> 类比 snabbdom 中的 h函数

    • updateComponent ——>类比snabbdom中的 patch 函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    //模板
    <div id="app">
    <p>{{price}}</p>
    </div>

    //render 函数,执行之后返回的是vnode
    with(this){
    return _c(
    'div',
    {
    attrs:{"id":"app"}
    },
    [
    _c('p',[_v(_s(price))])
    ]
    )
    }
    /* this 即 vm ,price 即 this.price 也即 vm.price ,即 data 中的 price ,_c 即 this._c 即 vm._c */

    // vm._c 比较 snabbdom中 h 函数的写法
    var vnode=h('ul#list',{},[
    h('li.item',{},'Item 1'),
    h('li.item',{},'Item 2')
    ])

    //updateComponent 和 patch 函数
    vm._update(vnode){
    const preVnode=vm._vnode;
    vm._vnode=vnode;
    if(!preVnode){
    vm.$el=vm.__patch__(vm.$el,vnode); //snadddom 中 patch(container,vode)
    }else{
    vm.$el=vm.__patch__(preVnode,vnode); //snadddom 中 patch(vnode,newVnode)
    }
    }

    function updateComponent(){
    // vm._render 即上述的 render 函数,返回 vnode
    vm._update(vm._render())
    }
  • vue的整个实现流程 (关键点:render 函数/h函数、Object.defineProperty、updateComponent/patch 函数)

    • 第一步:解析模板成 render 函数

      • with 的用法

      • 模板中的所有信息都被render 函数包含

      • 模板中用到的data 中的属性,都变成了JS 变量
      • 模板中的 v-model v-for v-on 都变成了JS 逻辑
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      //模板
      <div id="app">
      <div>
      <input v-model="title">
      <button v-on:click="add">submit</button>
      </div>
      <div>
      <ul>
      <li v-for="item in list">{{item}}</li>
      </ul>
      </div>
      </div>

      //render 函数 ,返回 vnode
      with(this){ // this 就是 vm
      return _c(
      'div',
      {
      attrs:{"id":"app"}
      },
      [
      _c(
      'div',
      [
      _c(
      'input',
      {
      directives:[
      {
      name:"model",
      rawName:"v-model",
      value:(title),
      expression:"title"
      }
      ],
      domProps:{
      "value":(title)
      },
      on:{
      "input":function($event){
      if($event.target.composing)return;
      title=$event.target.value
      }
      }
      }
      ),
      _v(" "),
      _c(
      'button',
      {
      on:{
      "click":add
      }
      },
      [_v("submit")]
      )
      ]
      ),
      _v(" "),
      _c('div',
      [
      _c(
      'ul',
      _l((list),function(item){return _c('li',[_v(_s(item))])})
      )
      ]
      )
      ]
      )
      }
    • 第二步:响应式开始监听

      • Object.defineProperty
      • 将 data 的属性代理到vm上
      • 双向数据绑定就是既有 get 又有 set
    • 第三步:首次渲染,显示页面,且绑定依赖

      • 初次渲染,执行updateComponent,执行 vm._render()
      • 执行 render 函数,会访问到 vm.list vm.title(vm中的属性),并且返回vnode
      • 访问 vm 中的属性 会被响应式的 get 方法监听到
      • 执行updateComponent,会走到 vdom 的patch方法
      • patch 方法将 vnode 渲染成真实的DOM,初次渲染完成 [ patch(container,vnode)的核心函数是createElement ,功能是将vnode转换为真实dom节点 ]

      为何要监听get ,直接监听set 不行吗?

      • data中有很多属性,有些会被用到,有些可能不会被用到。
      • 被用到的会走到get,不被用到的不会走到get。
      • 未走到 get 的属性,set的时候我们也无需关心,可以避免不必要的重复渲染
    • 第四步:data 属性变化,触发 rerender

      • 修改属性,会被响应式的set监听到
      • set 中执行 updateComponent ,重新执行 vm.render() ——>异步
      • 生成的 vnode 和 preVnode,通过 patch 进行对比 [ patch(vnode,newVnode) 核心函数 updateChildren ,功能是两个虚拟dom的对比和更新 ]
      • 渲染到html中 (不相同的部分直接替换)

六、React 和 组件化

题目:

  • 说一下对组件化的理解

    • 组件的封装:封装视图 ( JSX )、数据( this.state )、变化逻辑( this.setState(…) )
    • 组件的复用:props传递、复用
  • JSX 本质是什么

    • JSX 本质上为我们提供了创建React元素方法的语法糖
      • React.createElement(‘div’,{ id:’div1’ },child1,child2,child3)
      • React.createElement(‘div’,{ id:’div1’ },[…])
    • JSX 需要被解析成 JS 才能在浏览器中运行( JSX 要转换成 JS 代码,最终这个代码要生成 vnode) ——> 类比Vue中的模板需要被转换成一个JS函数( render 函数 )
    • JSX 是独立的标准,可被其他项目使用(比如Preact)
  • JSX 和 vdom 的关系

    • JSX 是React.createElement 方法的语法糖,该方法返回一个记录了某个DOM节点所有信息的对象(vnode)。换言之,通过它我们就可以生成真正的DOM,这个记录信息的对象我们称之为虚拟DOM

    • 为何需要 vdom

      • vdom 是 React 初次推广开来的,结合 JSX

      • JSX 就是模板,最终要渲染成 html

      • 初次渲染 + 修改 state 后的 re-render
      • 正好符合 vdom 的应用场景:数据和视图分离、数据驱动视图
    • React.createElement 和 h,都生成 vnode

    • 何时应用 vdom 中的 patch 函数:ReactDOM.render(…) 和 setState

    • 自定义组件的解析:先初始化实例,然后执行实例的render方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <div>
    <Input addTitle={this.addTitle.bind(this)}/>
    <List data={this.state.list}/>
    </div>

    /*
    React.createElement(
    "div",
    null,
    React.createElement(Input, { addTitle:this.addTitle.bind(this) }),
    React.createElement(List, { data: this.state.list })
    );
    */

    // React.createElement(List, { data: this.state.list })
    // var list = new List({ data: this.state.list })
    // var vnode = list.render()
  • 说一下 setState 的过程

    • setState 是异步的

      • 你无法规定或者限制用户如何使用 setState,可能出现一次执行多次setState,没必要每次都重新渲染,降低性能
      • 即便是每次都重新渲染,用户也看不到中间效果,只看到最终结果
    • setState 的过程

      • 每个组件实例,都有 renderComponent 方法。因为每个组件都继承了 Component这个父类,此类中包含 renderComponent 方法。
      • 执行 renderComponent 方法时,会重新执行实例的 render 方法
      • render 函数返回newVnode ,然后拿到 preVnode
      • 最后,执行patch(preVnode,newVnode)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      //模拟 renderComponent
      class Component {
      constructor(props) {

      }
      renderComponent() {
      const newVnode = this.render()
      const preVnode = this._vnode
      patch(prevVnode, newVnode)
      this._vnode = newVnode
      }
      }
  • 阐述自己对 React 和 Vue 的认识

    • 两者的本质区别
      • Vue—本质是 MVVM 框架,由 MVC 发展而来
      • React— 本质是前端组件化框架,由后端组件化发展而来
    • 模板和组件化的区别
      • Vue 使用模板,React 使用JSX
      • React 本身就是组件化,没有组件化就不是React
      • Vue 也支持组件化,不过是在 MVVM 上的扩展
    • 两者共同点
      • 都支持组件化
      • 都是数据驱动视图(只关心数据的变化,DOM操作被封装)

七、hybrid


(三)全面篇

一、一面/二面

1.页面布局

题目:

假设高度已知,请写出三栏布局,其中左栏、右栏宽度各为300px,中间自适应

  • 解决方案

    代码详情见 自适应布局

  • 扩展延伸

    • 七种方案的优缺点
      • 浮动布局
        • 局限性:浮动元素是脱离文档流,需要清除浮动,这个处理不好的话,会带来很多问题,比如高度塌陷等
        • 优点:比较简单,兼容性也较好
      • 绝对定位布局
        • 局限性:绝对定位是脱离文档流的,意味着下面的所有子元素也会脱离文档流,这就导致了这种方法的有效性和可使用性是比较差的
        • 优点:快捷方便,而且也不容易出问题
      • flex 布局
        • 局限性:不能兼容IE8及以下浏览器
        • 优点:CSS3新出的布局方式,是为了解决上述两种布局方式的不足而出现的,是比较完美的。而且移动端的布局也常用flexbox
      • 表格布局
        • 局限性:当其中一个单元格高度超出的时候,两侧的单元格也是会跟着一起变高,而这种效果有时候不是我们想要的
        • 优点:兼容性很好,在flex布局不兼容时,可以尝试表格布局
      • grid 布局
        • 局限性:只支持高版本的浏览器;grid某些属性不易懂
        • 优点:真正的 css 框架,和 flex 相比,它是二维的,而 flex 是一维的;做大布局使用,grid-gap 属性非常好用
      • 圣杯布局 VS 双飞翼布局
        • 布局要求(相同点):
          • 三列布局,中间宽度自适应,两边定宽
          • 中间栏要在浏览器中优先展示渲染
          • 允许任意列的高度最高
        • 不同点:
          • 圣杯布局:借助的是非主要元素(left、right)覆盖了其父元素(container)的padding值所占据的宽度。也就是,同一个杯子,非主要元素只是占据了全部容器的padding值部分
          • 双飞翼布局:给中间部分(center)添加一个外层元素(container),非主要元素(left、right)所占据的空间是主要部分(center)的margin空间。也就是,像鸟的两个翅膀,与主要部分脱离
        • 优缺点:
          • 缺点:最好设置页面最小宽度,要不然当页面缩放到一定程度时,页面布局会被破坏,影响浏览
          • 优点:允许任意列的高度最高,比表格布局灵活性高
    • 当中间部分内容超出已知高度时,哪些方案可以正常显示
      • grid布局、flex布局、table布局、绝对定位布局、圣杯布局、双飞翼布局
      • 前三者是三个部分的高度一起增加,flex布局和grid布局可以分别通过设置align-items为flex-start、start解决;后三者是只有中间部分高度变高,两侧高度不受影响
  • 页面布局的变通

    • 三栏布局
      • 左右宽度固定,中间自适应
      • 上下高度固定,中间自适应
    • 两栏布局
      • 左宽度固定,右自适应
      • 右宽度固定,左自适应
      • 上高度固定,下自适应
      • 下高度固定,上自适应
  • 小结

    • 语义化掌握到位
    • 页面布局理解深刻
    • CSS基础知识扎实
    • 思维灵活且积极上进
    • 代码书写规范
2.CSS盒模型

题目:

谈谈你对CSS盒模型的认识?

  • 概念和分类
    • CSS盒模型就是一个盒子,封装周围的HTML元素,它包括内容content、边框border、内边距 padding、外边距margin
    • CSS盒模型分为标准模型和IE模型
  • 标准模型和IE模型的区别

    • 标准模型:width=内容content的宽度(默认) ; 设置方式为 box-sizing:content-box
    • IE模型:width=内容 content+内边距 padding+边框 border 的宽度 ;设置方式 为box-sizing:border-box
  • 通过js如何获取盒模型的宽高

    • dom.style.width/height 只能获取到dom的内联样式
    • dom.currentStyle.width/height 获取到的是dom实际宽高,但这种方式只在IE中可以使用
    • window.getComputedStyle(dom,null).width/height 获取到的是dom实际宽高,但是不支持IE
    • dom.offsetWidth/offsetHeight 最常用的,兼容性最好
    • dom.getBoundingClientRect().width/height 计算dom的绝对位置,根据视窗左上角对应位置的绝对位置,可以拿到四个值:left、top、width、height
  • 实例题(根据盒模型解释边距重叠)

    • 边距重叠:指两个或多个盒子相邻边界重合在一起形成一个边界。水平方向边界不会重叠,垂直方向会重叠,垂直方向的实际边界是边界中的最大值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <section class="box" id="sec">
    <style media="screen">
    #sec{
    background: #f00;
    }
    .child{
    height: 100px;
    margin-top: 10px;
    background: yellow
    }
    </style>
    <article class="child"></article>
    </section>
  • BFC(边距重叠解决方案)

    • BFC的基本概念

      Block Formatting context,块级格式化上下文

    • BFC的原理(BFC的渲染原则)

      • 属于同一个BFC的两个相邻box的margin会在垂直方向上发生重叠
      • BFC区域不会与float box重叠 ——>自适应两栏布局
      • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素
      • 计算BFC的高度时,浮动元素也参与计算 ——>清除内部浮动,防止高度塌陷

      补充:

      ​ BFC内部的元素和外部的元素绝对不会互相影响,因此,当BFC外部存在浮动时,它不应该影响BFC内部Box的布局,BFC会通过变窄,而不与浮动有重叠。同样的,当BFC内部有浮动时,为了不影响外部元素的布局,BFC计算高度时会包括浮动的高度

    • 如何创建BFC

      • 脱离文档流:float不为none;position为absolute或fixed
      • overflow不为visible (hidden,auto,scroll)
      • display为”table-cell”,”table-caption”,”inline-block”,”flex”等
    • BFC的使用场景

      • 自适应两栏布局
      • 清除内部浮动
      • 防止垂直margin重叠
3.DOM事件
4.HTTP协议类
  • HTTP协议的主要特点
    • 简单快速
    • 灵活
    • 无连接
    • 无状态
  • HTTP报文的组成部分
  • HTTP方法
    • GET 获取资源
    • POST 传输资源
    • PUT 更新资源
    • DELETE 删除资源
    • HEAD 获得报文首部
  • POST和GET的区别
  • HTTP状态码
  • 什么是持久连接
  • 什么是管线化
5.原型链
6.面向对象
7.通信类
  • 什么是同源策略及限制
  • 前后端如何通信
  • 如何创建Ajax
  • 跨域通信的几种方式
8.安全类
  • CSRF
  • XSS
9.算法类
  • 排序

    • 蛮力法:选择排序和冒泡排序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      //选择排序
      function selectionSort(arr){
      let len=arr.length,min,temp;
      for(let i=0;i<len-1;i++){
      min=i; //假定无序区的第一个元素为最小值
      for(let j=i+1;j<len;j++){
      if(arr[j]<arr[min]){
      min=j;
      }
      }
      temp=arr[i];
      arr[i]=arr[min];
      arr[min]=temp;
      }
      return arr;
      }
    • 减治法:插入排序和拓扑排序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      //插入排序(类似于扑克牌的插入)
      function insertSort(arr){
      let len=arr.length,temp,j;
      for(let i=1;i<len;i++){
      temp=arr[i];//待插入变量
      j=i-1;//循环有序区的变量
      while(j>=0 && arr[j]>temp){
      arr[j+1]=arr[j];//比插入元素大的元素后移
      j=j-1;
      }
      arr[j+1]=temp;
      }
      return arr;
      }
      //希尔排序,也称递减增量排序,是插入排序的一种更高效的改进版本。本质就是对每个子序列进行插入排序。
      function shellSort(arr){
      let len=arr.length;
      let gap=len;
      let temp,j;
      while(gap>1){
      gap=Math.floor(gap/3)+1;
      for(let i=gap;i<len;i++){//假设每个子序列的第一个元素已排好序,从第二个元素开始插入
      temp=arr[i];//待插入元素
      j=i-gap;//循环有序区的变量
      while(j>=0 && arr[j]>temp){
      arr[j+gap]=arr[j];//子序列的元素后移一位
      j=j-gap;//判断子序列的下一个元素
      }
      arr[j+gap]=temp;//插入到空位置
      }
      }
      return arr;
      }
    • 分治法:合并排序和快速排序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      //快速排序,递归实现
      function quickSort(arr){
      if(arr.length<=1){
      return arr;
      }
      let pivotIndex=Math.floor(arr.length/2);//基准下标
      let pivot=arr.splice(pivotIndex,1)[0];//基准元素,splice 会改变原始数组,此时数组长度减一
      let left=[],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));
      }
  • 堆栈、队列、链表

  • 递归

  • 波兰式和逆波兰式