前端常见面试题(二十二)@郝晨光


vw、vh、rem、em、px的区别

  • px
    px是绝对单位,1px就是一个像素点

  • em
    em是相对单位,是相对于父级的font-size

  • rem
    rem是相对单位,是相对于html的font-size

  • vw
    vw是相对单位,相对于视口宽度,1vw是视口宽度的 1%

  • vh
    vh是相对单位,相对于视口的高度,1vh是视口高度的 1%



前端安全:xss攻击和CSRF攻击

  • xss攻击就是通过用户输入的信息,比如input、textarea等,输入一段html、css、js代码,接着提交到数据库,其他用户访问对应页面的时候,就会触发攻击。
    • 常见的攻击:css攻击,body { display: none !important; },js攻击,通过script标签嵌入js代码,html攻击,嵌入a标签超链接,指向攻击网站。
    • 防御方式:对用户的输入进行过滤,将用户输入的标签内容或者事件内容过滤掉,让它形成不了代码块,只能显示对应文本即可。
    • 一般是后端进行替换。
  • CSRF攻击,是跨站请求伪造,是用户先打开一个正常页面,接着完成登录等身份验证,在本地浏览器存储了cookie值,接着在钓鱼网站或者攻击网站上,点击a标签超链接,或者触发其他事件,而这个链接指向的就是那个正常网页的API接口,当用户点击之后,就可以通过之前正常网站存储的cookie访问到接口的操作。
    • 常见的攻击:新浪微博用户粉丝增加,在支付的时候通过CSRF攻击直接调用支付接口
    • 防御方式:使用token验证,每次向后台发送请求都携带token,这样不是由前端发送的请求就过滤掉。或者对网页来源进行验证,如果不是我们自己的网页,就过滤。
  • XSS是像你的页面注入JS脚本执行,在JS里面去做他想做的事情;无需做权限认证;
  • CSRF是用你API本身的漏洞,帮你自动执行;需要登录认证;

http和https的区别

  • HTTPS相较于HTTP更加安全
  • HTTPS是密文传输,HTTP是明文传输
  • 所以HTTPS不会被劫持,而HTTP协议传输的内容可以被劫持
  • HTTPS默认使用443端口,而HTTP默认使用80端口



优雅降级和逐渐增强

  • 优雅降级相当于向下兼容,就是先针对最主流、最高级、最完善的浏览器进行产品设计,接着在开发的最后阶段,通过测试以及兼容处理,保证低级浏览器可以提供不影响使用的功能即可。
  • 优雅降级根据公司不同,项目不同,所需要兼容的浏览器等级也不同,一般可以是IE8就可以了
  • 渐进增强相当于向上兼容,一开始就针对低级浏览器进行产品设计,然后在针对高级浏览器设计更好的用户体验和交互动效。



什么是堆和栈

  • 在js中分为基本数据类型和引用数据类型,而基本数据类型(Number、String、null、undefined、Boolean)都是存放在栈内存中的,占据固定大小的空间,而引用类型(Object)是存放在堆内存中的,在栈内存中存放了对应的引用,按照引用访问堆内存的数据。
  • 对于引用类型,存放在栈内存中的就是一个引用指针,这个指针指向堆内存中的数据,而数据的大小也是不固定的。
  • 堆:先进先出,动态内存空间,不会自动释放
  • 栈:先进后出,自动分配内存空间,会自动释放



什么是BFC

  • BFC是块级格式化上下文,BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,并且在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。
  • 它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。
  • 具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。
  • 内部的Box会在垂直方向,一个接一个地放置
  • 在BFC垂直方向的边距会发生重叠
  • BFC的区域不会与float区域的box重叠
  • BFC是一个页面上的独立的容器,外面的元素不会影响BFC里的元素,反过来,里面的也不会影响外面的
  • 计算BFC高度的时候,浮动元素也会参与计算
  • 浮动元素和绝对定位元素,非块级盒子的块级容器(例如 inline-blocks, table-cells, 和 table-captions),以及overflow值不为“visiable”的块级盒子,都会为他们的内容创建新的BFC(块级格式上下文)。
  • 1、float的值不是none。
    2、position的值不是static或者relative。
    3、display的值是inline-block、table-cell、flex、table-caption或者inline-flex
    4、overflow的值不是visible
  • 什么时候使用BFC:上下边距重叠、高度塌陷



什么是闭包,闭包的优缺点,内存泄露怎么处理,闭包的使用场景

  • 闭包就是一种可以重复使用变量而且不会造成全局变量污染的机制。
  • 因为:全局变量可以重复使用,但是容易造成变量污染。局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。闭包结合了全局变量和局部变量的优点。
  • 闭包的优点:可以重复使用变量,并且不会造成变量污染,可以用来定义私有属性和私有方法。
  • 闭包的缺点:比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。
  • 内存泄漏的解决方案:在退出函数之前,将不使用的局部变量全部删除。手动释放闭包内的变量。
  • 闭包的使用场景:封装功能时(需要使用私有的属性和方法),函数节流、函数防抖、函数柯里化、给元素伪数组添加事件需要使用元素的索引值。



内存泄露

  • 造成内存泄露的原因:
    • 意外的全局变量(在函数内部没有使用var进行声明的变量)
    • console.log
    • 闭包
    • 对象的循环引用
    • 未清除的计时器
    • DOM泄露(获取到DOM节点之后,将DOM节点删除,但是没有手动释放变量,拿对应的DOM节点在变量中还可以访问到,就会造成泄露)
  • 解决方案:手动释放内存来避免内存泄露



将多维数组转化为一维数组

  • ·通过apply拉平数组,但是只能拉平二维数组

  • let arr = [1, 2, [3, 4], 5];
    let result = Array.prototype.concat.apply([], arr);
    // [1, 2, 3, 4, 5]
    
  • 通过ES6新增的flat方法拉平数组,默认拉平二维数组,参数默认为1,可以写n,则拉平n+1维数组,对于未知维度的数组,可以通过Infinity关键字作为参数

  • let arr = [1, 2, [3, 4, [5, 6]], 7];
    let result = arr.flat();
    // [1, 2, 3, 4, [5, 6], 7];
    let result2 = arr.flat(2);
    // [1, 2, 3, 4, 5, 6, 7];
    let result3 = arr.flat(Infinity);
    
  • 通过递归拉平数组

  • let arr = [1, 2, [3, 4, [5, 6]], 7];
    function flatArray(arr) {
        let result = [];
        for(let i = 0; i < arr.length; i++) {
            if(Array.isArray(arr[i])) {
                result = result.concat(arguments.callee(arr[i]));
            }else {
                result.push(arr[i])
            }
        }
        return result;
    }
    flatArray(arr);
    // [1, 2, 3, 4, 5, 6, 7];
    



JS继承的方法及优缺点

  1. 原型链继承

    • function Parent() {
          this.name = 'parent'
      }
      Parent.prototype.sayName = function() {
          alert(this.name)
      }
      
      function Child() {
          this.age = 20;
      }
      Child.prototype = new Parent(); // 改造子类构造函数的原型,使其指向父类的实例。
      let child = new Child();
      child.name; // parent
      child.sayName; // alert(parent);
      child.age;  // 20
      
    • 通过改造子类的原型prototype,使其指向父类的实例,达到继承父类所有属性和方法的效果。

    • 优点:父类的方法也可以达到复用

    • 缺点:父类的属性也达到了复用,子类实例没有属于自己的属性(如上例中的name属性)

  2. 构造函数借用继承

    • function Parent(name) {
          this.name = name;
      }
      Parent.prototype.sayName = function() {
          alert(this.name);
      }
      
      function Child(name, age) {
          Parent.call(this, name); // 通过call或者apply的特性实现将父类的属性复用
          this.age = age;
      }
      Child.prototype.sayAge = function() {
          alert(this.age);
      }
      
      let child = new Child('child', 24);
      child.name; // child
      child.age; // 24
      child.sayName(); // error
      child.sayAge(); // alert(child)
      
    • 通过call和apply改变this指向并且调用函数的特性,实现父类的属性复用。

    • 优点:可以达到父类的属性复用,并且可以定制子类自己的属性值。

    • 缺点:父类上的方法没有实现复用

  3. 组合继承

    • function Parent(name) {
          this.name = name;
      }
      Parent.prototype.sayName = function() {
          alert(this.name);
      }
      
      function Child(name, age) {
          Parent.call(this, name); // 通过call或者apply的特性实现将父类的属性复用
          this.age = age;
      }
      Child.prototype = new Parent(); // 改造子类构造函数的原型,使其指向父类的实例。
      Child.prototype.sayAge = function() {
          alert(this.age);
      }
      
      let child = new Child('child', 24);
      child.name; // child
      child.age; // 24
      child.sayName(); // alert(child)
      child.sayAge(); // alert(24)
      
    • 组合继承,其实就是将原型链继承和构造函数借用继承组合到一块,结合他们之间的优点,使得既可以继承父类的方法,又可以在子类上继承父类的属性并定制。

    • 优点:结合了原型链继承和构造函数借用继承的优点,既继承了父类的方法,又继承了父类的属性,并可以定制。

    • 缺点:父类的构造函数被调用了两次,在子类的构造函数内调用一次,在子类的prototype上调用一次,并且子类的prototype上的属性会被覆盖掉,造成内存浪费。

  4. 原型式继承

    • function Parent(name) {
          this.name = name;
      }
      Parent.prototype.sayName = function() {
          alert(this.name);
      }
      
      function createObject(obj) {
          function F() {};
          F.prototype = obj;
          return new F();
      }
      
      let parent = new Parent('parent');
      let child = createObject(parent);
      child.name; // parent;
      child.sayName(); // alert(parent)
      
    • 原型式继承是通过借助临时的构造函数,将原有的对象的属性和方法进行浅拷贝,然后赋予新的对象,实现继承。通过ES5新增的Objet.create也可以实现。

    • 优点:好像没什么优点

    • 缺点:缺点和原型链继承一样,只能实现属性和方法的复用,子类没有属于自己的属性,没有可定制性。

  5. 寄生式继承

    • function Parent(name) {
          this.name = name;
      }
      Parent.prototype.sayName = function() {
          alert(this.name);
      }
      
      function createObject(obj) {
          function F() {};
          F.prototype = obj;
          return new F();
      }
      
      function Child(name, age) {
          let parent = new Parent(name);
          let result = createObject(parent);
          result.age = age;
          return result;
      }
      
      let child = Child('child', 24);
      child.sayName(); // alert(child)
      
    • 寄生式继承是将原型式继承封装成为一个函数,并且进行参数传递,实现子类继承父类的属性和方法,并且可以定制属性。

    • 优点:子类可以继承父类的属性和方法,并且可以定制属性

    • 缺点:子类不是通过new关键字调用的,所以没有属于自己的方法,如果要创建子类的方法,那就是每一个子类都得重新创建一遍,不能达到子类的方法复用。

  6. 组合寄生式继承

    • function Parent(name) {
          this.name = name;
      }
      Parent.prototype.sayName = function() {
          alert(this.name);
      }
      
      function Child(name, age) {
          Parent.call(this, name);
          this.age = age;
      }
      Child.prototype = Object.create(Parent.prototype);
      Child.prototype.constructor = Child;
      Child.prototype.sayAge = function() {
          alert(this.age);
      }
      
      let child = new Child('child', 24);
      child.sayName(); // alert(child)
      child.sayAge(); // alert(24)
      
    • 组合寄生式继承,结合了组合继承的优点,并结合了寄生式继承的优点,目前为止完美的继承方式。

    • 优点:可以继承父类的属性和方法,并且可以定制,最主要的是对于父类的构造函数只需要调用一次。并且可以正常的使用intanceof和isPrototypeOf,原型链保持正常。

    • 缺点:好像没什么缺点



结言
感谢您的查阅,本文由郝晨光整理并总结,代码冗余或者有错误的地方望不吝赐教;菜鸟一枚,请多关照

你可能感兴趣的:(前端常见面试题(二十二)@郝晨光)