美文网首页
web前端面试宝典-基础篇(一)

web前端面试宝典-基础篇(一)

作者: 虚竹梦姑 | 来源:发表于2017-05-28 23:12 被阅读0次

    其实感觉前端的面试就是一些零碎的知识点,最后上升为架构而已。这篇博客我也会将架构设计融入其中。大致通过以下几个分类进行分析:

    • HTML/CSS部分
    • JavaScript部分
    • 数据结构部分(算法)
    • 框架之Angular.js
    • 框架之Vue.js
    • 框架之React.js
    • 移动端
    • HTTP
    • WEB安全
    • WEB渗透之Kali Linux
    • 前端性能
    • 设计模式
    • 正则表达式
    • 前端架构设计

    一、HTML/CSS部分

    1.1 什么是盒子模型?

    在网页中,一个元素占有空间的大小由几个部分构成,其中包括元素的内容(content),元素的内边距(padding),元素的边框(border),元素的外边距(margin)四个部分。这四个部分占有的空间中,有的部分可以显示相应的内容,而有的部分只用来分隔相邻的区域或区域。4个部分一起构成了css中元素的盒模型。

    1.2 什么是css Hack?

    一般来说,css Hack就是针对不同的浏览器写不同css样式。css hack一般来说是为了兼容IE浏览器的。
    IE浏览器Hack一般又分为三种: 条件Hack属性Hack选择器Hack

    // 1.条件Hack
    <!--[if IE]>
        <style>
            .test {color:red;}
        </style>
    <![endif]-->
    
    // 2.属性Hack
    .test {
        color: #090\9; /* For IE8+ */
        *color: #f00;  /* For IE7 and earlier */
        _color: #ff0;  /* For IE6 and earlier */
    }
    
    // 3.选择器Hack
    *html .test{
        color: #090; /* For IE6 and earlier */
    }
    
    *+html .test {
        color: #ff0; /* For IE7 */
    }
    
    

    1.3 什么是同步?异步?

    当我在学习前端的时候,看到这个问题的时候,让不不禁想起了学习Linux时,一直想的一个问题。什么是异步,同步?什么是阻塞,非阻塞当时这个问题困惑了我很久,一直搞不清楚有什么区别,异步不就是非阻塞吗,同步不就是阻塞吗?下面咱们就唠唠嗑来说说这里面的玄机。在这里咱们主要来以网络I/O为例:

    1.3.1 同步和异步

    实际上同步与异步是针对***应用程序***与***内核***的交互而言的。同步过程中进程触发IO操作并等待或者轮询的去查看IO操作是否完成。异步过程中进程触发IO操作后,直接返回,做自己的事情,IO交给内核来处理,完成后内核通知进程IO完成。
    

    (这里大家不禁要问了,什么是IO,如果你是做后端开发的,IO对于你来说很容易理解,但是纯做前端的话就不容易理解了。IO就是input/output,输入输出。比如文件的读写操作,网络的请求和得到数据的操作等等。不懂的童鞋要去学习操作系统了)
    直接上图吧:

    同步与异步.png

    1.3.2 阻塞与非阻塞

    简单理解为需要做一件事情能不能立即得到返回应答。如果不能立刻获得返回,需要等待,那就阻塞了,否则就可以理解为非阻塞。详细区别如下图所示:
    
    阻塞与非阻塞.png

    需要注意了, 这里的立即返回,是针对内核来说的。就是说这个应用程序的某些代码会不会阻塞内核(CPU)的执行。而同步异步是针对于应用程序来说的,到底某些代码会不会影响下面代码的执行。

    1.4 px和rem和em的区别?

    px和rem和em都是长度单位, 区别是px的值是固定的,指定是多少就是多少,计算比较容易。em的值不是固定的,它的值是继承父级元素的字体大小(其实也不能说是父级,应该说是自身,如果自身没有设置font-size, 则去找父级,然后父级没设置,在往上找,如果没有找到,就是浏览器默认的字体)。下面举例说明:

        /* 1. em的案列 */
        .test {
          font-size: 20px;  /* 这句话声明之后就代表了1em = 20px;但是后面的代码与这个没有关系 */
          text-indent: 2em;  /* 单位转换之后2em = 40px */
        }
        
        /* 2.rem的案列 -- rem是根据html的font-size大小定的,一般用于移动端网站开发时使用,进行屏幕适配。*/
        html {
            font-size: 40px;
        }
        
        .test {
            font-size: 1rem; /* 单位转换后为40px */
        }
    

    1.5 浏览器的内核分别是什么?

    IE: trident内核
    Firefox: gecko内核
    Safari: webkit内核
    Opera: 以前是presto内核,Opera现已改用Google Chrome的Blink内核。
    Chrome: Blink(基于Webkit内核,google和Opera公同开发)

    1.6 清除浮动的影响的方法?

    这个问题之前一直说一直说,没有时间去总结。咱们从下面几种方式进行探究。

    • 1.空的div方式(不是特别好,如果清除浮动地方多了,就会有多个空标签)
        /* 在要清除浮动的地方加上<div class="clearfix"></div> */
        .clearfix {
            clear: both;
            height: 0px;
        }
    
    • 2.使用overflow方式进行清除浮动带来的影响。
        /* 想要使用overflow清除浮动带来的影响,一般分为两步骤:
            1. 给它加一个父元素
            2. 在父元素上写overflow: hidden;
        */
        <div style="overflow:hidden;">
            <div class="right">
                    
            </div>
        </div>
    
    • 3.使用伪元素的方式进行清除浮动带来的影响。
        /* 这里说明一下,一般使用这种方式也是需要给它加一个父元素的... */
        .test::after {
             content:"";        
             display:block;        
             height:0;        
             clear:both;        
             /* visibility:hidden;    */
        }
    

    1.7 margin折叠现象?

    要想解释这个现象,还是要了解BFC的。这里的我就不啰嗦了,就直接说一下解决方案了。(我经常使用的方案, 其他方案自行百度)

        /* 这个很简单和上一个题目一样
        */
        在父层加上overflow:hidden;
    

    其他CSS的问题待续吧(CSS3动画、渐进、Canvas、SVG等等先忽略),特别是less、sass、stylus等预处理语言,面试中也是常问的重点。

    二、JavaScript部分

    这一部分涉及的内容就多了,那么我会从ES5和ES6都会讲解到。
    先列个目录

    2.1 如何快速合并两个数组?

    这个问题看起来很简单,实则没有那么简单。给大家提几个问题,合并数组什么方式最好也就是说性能最佳?合并之后用不用去重?等等都需要考虑。网上给出了push和concat的对比。(温馨提示:如果不懂call和apply的童鞋,自行学习。)

    2.1.1. 先使用代码来对比(concat函数与Array.prototype.push.apply)

        function testClass() {
                var testArray1 = [];
                var testArray2 = [];
                this.resetArray = function() {
                    for (var i = 0; i < 100000; i++) {
                        testArray1.push(i);
                        testArray2.push(i + 100000);
                    }
                }
                this.applyTest = function() {
                    var startTime = 0,
                        endTime = 0;
                    console.log('开始合并的时间是:' + (startTime = new Date().getTime()));
                    var aa = Array.prototype.push.apply(testArray1, testArray2);
                    console.log(aa);
                    console.log('合并完成的时间是:' + (endTime = new Date().getTime()));
                    console.log('合并数组所用的时间是:' + (endTime - startTime));
                }
                this.concatTest = function() {
                    var startTime = 0,
                        endTime = 0;
                    console.log('开始合并的时间是:' + (startTime = new Date().getTime()));
                    var aa = testArray1.concat(testArray2);
                    console.log(aa.length);
                    console.log('合并完成的时间是:' + (endTime = new Date().getTime()));
                    console.log('合并数组所用的时间是:' + (endTime - startTime));
                }
            }
    
    
            var apply = new testClass();
            apply.resetArray();
            apply.applyTest();
    
    
            var concat = new testClass();
            concat.resetArray();
            concat.concatTest();  
    

    通过测试结果可知:
    使用Array.prototype.push.apply 方式性能非常低下,在20w的数据下,执行时间将近是concat方法的11-17倍左右。

    2.1.2 Array.prototype.concat.apply 和 concat对比

    很多程序员喜欢直接使用Array构造函数下的原型对象上的方法,以此缩短搜索函数的时间,这种方式是否能够带来性能的提升?
    在这里只需要将第一个函数中的代码改成

        var aa = Array.prototype.concat.apply(testArray1, testArray2);
    

    根据测试的结果,使用Array.prototype.concat.apply的方式还是比直接使用concat函数慢些。之前我也没有注意过,这次测试,我感觉问题大了。难道之前想错了,经过又一轮的测试,证明了我的猜测。下一轮的对比您应该想到了,就是call和apply的性能。(这个对比大家自行研究,结论就是call的性能在chrome浏览上明显比apply性能要高很多)

    2.1.3 Array.prototype.concat.call 和 concat对比

    将第一个函数中的代码改成如下:

        var aa = Array.prototype.concat.call(testArray1, testArray2);
    

    最后的测试结果验证使用Array.prototype.concat.call比直接使用concat方法性能提升了一倍。因此最后的得出的结论就是数据合并性能最高的方式就是使用Array.prototype.concat.call
    各位兄弟姐妹,这只是我自己的测试结果,如果有出入,欢迎指正。至于去重的算法及其性能优化放到算法一节说吧。

    2.2 说说你对原型链的理解?

    这个问题也是相当有点难度的问题。首先要搞清楚什么是原型对象、什么是构造函数、什么是实例化对象。如果和我一样都清楚的童鞋,您只需看图即可。

    数组对象原型链 .png

    这张图表明了如果数组实例化之后,它原型的整个指向。还有一种情况就是当实例化对象时,采用var a = {}或者new Object();它的指向如下:

    Obj.png

    2.3 说说你对js中面向对象编程的理解?

    在没有学习面向对象编程的时候,大家更多的是函数(就是一些功能性代码的集合),而面向对象的思想是从另外一个角度出发来解决函数的局限性。它把对象作为程序的基本单元,其实对象就是对事物的一种抽象描述。
    人们发现,现实世界中的事物,都可以用「数据」和「能力」来描述。比如我要描述一个人,「数据」就是他的年龄、性别、身高体重,「能力」就是他能做什么工作,承担什么样的责任。描述一台电视,「数据」就是它的屏幕尺寸、亮度,「能力」就是播放《葫芦娃》。

    面向对象的世界里,到处都是对象。对象不光有「数据」和「能力」,还可以接受命令。例如你可以让「狗」这个对象「吃狗粮」,就可以把「吃狗粮」的命令发给「狗」让其执行,然后我们就实现了「狗吃狗粮」的需求。

    现在对象有了,如何进行面向对象的编程呢?很简单,依次向不同的对象发送命令就可以了。比如产品经理说要把大象装进冰箱里,用面向对象来实现,我们会先定义一个「冰箱」对象,它的「数据」就是当前的冷冻温度,或者该冰箱已经有了多少头大象,「能力」就是开门、关门。还有一个「大象」对象,它的「数据」可以是大象的智商、体积,「能力」就是「自己跑到冰箱里去」。然后我们依次:

    向冰箱下达「开门」的命令。

    向大象下达「进冰箱」的命令。

    向冰箱下达「关门」的命令。

    大家好好理解一下就可以整明白,当然还有其他的编程思想。比如面向接口、面向切面都是常用编程思想。(具体到后面再讲解吧)

    2.4 JavaScript(ES5)中封装、多态、继承的实现方式?

    2.4.1 JavaScript实现封装

    →原始模式

    var cat = {
        name: '',
        color: ''
    }
    

    这种方式缺点是: 1.如果生成的对象很多就会很麻烦。2.不能看出实例之间的联系

    →简单工厂模式

    function Cat(name, color) {
        var obj = {};
        obj.name = name;
        obj.color = color;
        return obj;
    }
    
    var cat1 = Cat("金毛", "黄色");
    var cat2 = Cat("泰迪", "棕色");
    

    这种方式可以解决代码重复的问题,但是依然不能反映出实例对象之间的关系。

    →构造函数模式

        function Cat(name, color) {
            this.name = name;
            this.color = color;
        }
        var cat1 = new Cat("金毛", "黄色");
        var cat2 = new Cat("泰迪", "棕色");
    

    构造函数的方式确实好用,但是存在一个浪费内存的问题。所以就要使用原型模式。

    →原型模式

        function Cat(name, color){
        this.name = name;
        this.color = color;
      }
      Cat.prototype.type = "猫科动物";
      Cat.prototype.eat = function(){alert("吃老鼠")};
    

    还有等等一些模式进行封装,大家自行去查询即可。

    2.4.2 JavaScript中继承的实现

    →原型继承

        // 基类
        var Person = function() {
            this.name = '张三';
            this.age = 20;
        };
        
        Person.prototype = {
            say: function() {
                console.log('Person.prototype');
            }
        };
        
        // 子类
        var Student = function() {
            
        };
        
        // 这样就可以继承了Person里面的所有的方法。
        Student.prototype = new Person();
        Student.prototype.getTeacher = function(){
            console.log('Student.prototype.getTeacher');
        };
        
    

    这种继承存在一些问题,就是无法通过参数定义对象。

    →构造函数实现继承

        function Person(){
            this.race = '人类';
        }
        Person.prototype={
            eat:function(){
                alert('123')
            }
        };
    
        /*学生对象*/
        function Student(name, skin) {
            // People(), 
            Person.apply(this, arguments);
            this.name = name;
            this.skin = skin;
        }
    

    构造函数这种方式的继承利用apply这个方法改变this的指针来完成继承的。

    →组合模式实现继承

    组合模式就是将上述的原型继承和构造函数继承的组合。

        function Person(name, age) {}
        Person.prototype.say = function() {}
    
    
        function Student(name, age, no) {
            /*会自动调用Person的方法,同时将name age传递过去*/
            Person.call(this, name, age);
            //自己的属性
            this.no = no;
        }
        Student.prototype = new Person();
    

    通过这种方式进行继承会更加完善。至于其他的继承方式在这就不一一列举了,比如寄生虫组合继承、拷贝继承等等。

    2.4.3 JavaScript中重载的实现

    这里只说一种方式实现重载(根据arguments个数)

        function add() {
            var sum = 0 ;
            for ( var i = 0 ; i < arguments.length; i ++ ) {
                sum += arguments[i];
            }
            return sum;
        }
        alert(add());
        alert(add( 1 , 2 ));
        alert(add( 1 , 2 , 3 ));
    

    内容太多了,请移步web前端面试宝典-基础篇(二)
    有问题请联系QQ:136062834,一直感觉良好的攻城狮。

    相关文章

      网友评论

          本文标题:web前端面试宝典-基础篇(一)

          本文链接:https://www.haomeiwen.com/subject/kotkfxtx.html