4399前端面试题

2020-04-23 00:00:00 函数 元素 对象 请求 节流

2019年秋招

本人亲身经历的面试,已通过,面试时长30分钟,岗位:web前端开发工程师

以下为具体的面试题:

自我介绍

问:前端从什么时候开始学的?

问:现在还有在实习吗?

问:前端的学习方式?

问:看了哪些前端的书籍了?

JavaScript判断数组的方式

  1. arr instanceof Array
  2. arr.constructor == Array
  3. arr.__proto__ == Array.prototype
  4. Object.prototype.toString.call(arr) == '[object Array]'
  5. Array.isArray(arr)
  6. 补充不能用typeof arr来判断,因为该方式对数组和对象返回都是 object

深拷贝对象的方法

  1. JSON.parse(JSON.stringify(obj))
  2. 递归拷贝
function deepClone(obj) {
let cloneObj = {}; //在堆内存中新建一个对象
for(let key in obj){ //遍历参数的键
if(typeof obj[key] ==='object'){
cloneObj[key] = deepClone(obj[key]) //值是对象就再次调用函数
}else{
cloneObj[key] = obj[key] //基本类型直接复制值
}
}
return cloneObj
}

如果对象/数组只有一个层级的话

如果对象/数组的元素是基本类型的话,则可以实现深拷贝,如果是引用类型的话,则不会实现深拷贝,改变引用类型中的值,拷贝与被拷贝对象都改变。

  1. Object.assign(newobj,obj)
  2. let cloneObj = { ...obj };
  3. let cloneArr = oldArr.slice(0)

补充:

let target = {};
let source = { a: { b: 2 } };
Object.assign(target, source);
console.log(target); // { a: { b: 10 } };
source.a.b = 10;
console.log(source); // { a: { b: 10 } };
console.log(target); // { a: { b: 10 } };

注意,次打印的结果target竟然是操作后的值
因为target中含有引用元素,则target和sourse还有联系
而console.log是从内存中查值,改变sourse后内存中两者共有的这个对象元素{b:2}都改变了

通俗地讲:
控制台一开始显示的是{a: {...}}
展开的这个过程是从内存查值的过程,不是相当于照相(固定一瞬间的状态)

定义对象不可修改的属性

  1. Object.defineProperty()
Object.defineProperty(对象名,属性名,描述符)
描述符
{
    writable: false, // 不可读写
    configurable // 不可配置
}

// 举例
Object.defineProperty(objName, "name", {writable: false, value: "zhangSan"});
  1. 单例模式
// 匿名函数+立即执行+返回对象+闭包
var obj = function(){
    // 定义私有属性和方法

    return {
        // 共有属性和方法
    }
}()
  1. Object.freeze(obj)

    使用该方法后,不可给对象添加和删除属性,也不可对现有属性进行更改。

    但是,可以完全重写该对象,如obj = newObj

出题意图:

假设一个场景, 我们写了一个 JS, 在其中定义了一个对象, 会开放出来给第三方使用。 如果想让这个对象安全的被第三方使用, 需要避免这个对象被下钩子(hook), 也就是要避免这个对象被覆盖重写。如:给对象的一个属性重新赋值为一个带有恶意代码的函数。

闭包是什么

一个有权力访问另一个函数内部变量和方法的函数。

太基础了,懒得展开了。

给滚动条添加滚动事件,1秒触发一次,避免频繁操作

考察节流防抖

防抖(debounce)

所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

function debounce(func, wait) {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);
        
        timeout = setTimeout(() => {
            func.apply(context, args)
        }, wait);
    }
}

节流(throttle)

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。

function throttle(func, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

补充:

其实还分节流和防抖还分立即执行和非立即执行,节流还分时间戳版和定时器版

详细内容见:函数的节流和防抖

如何给ul下的li标签绑定事件

用事件委托,给外层的ul绑定事件,根据事件冒泡的原理内层的li也被绑定,好处是如果再多了几个li标签,就不用重复绑定了,而且比遍历绑定更省资源

还可以通过e.target.nodeName来指定某个标签才响应该事件

window.onload = function(){
    var oUl = document.getElementById("ul1");
    oUl.onclick = function(ev){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() == 'li'){
            alert(123);
            alert(target.innerHTML);
        }
    }
}

用token来防范csrf攻击要如何操作

我的回答:

因为csrf攻击无法获取表单中的内容,将token放在post请求的表单里,服务端会根据表单里的token来验证用户是否是真实用户。

权威资料:

CSRF(Cross-site request forgery)跨站请求伪造

攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

防范策略:

  1. 同源检测。

    根据请求头的Origin和Referer判断是否是信任的域名发起的请求,若不是则阻止,若无这两个个请求头参数,则直接阻止

  2. CSRF Token

    原理:让请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。

    具体操作:用户登录时根据用户名和密码等加密生成一个Token值,同时写入到浏览器的Cookie和Hidden中,然后根据每次的请求将Token和Cookie同时带入到服务端进行验证和判断。

前端实时更新

  1. 用meta标签refresh,设置前端多长时间再向服务端更新
  2. setInternal(func, 1000)
  3. 在ajax成功请求后用setTimeout()递归调用ajax请求
  4. web Socket

浏览器控制台的netWork分析加载缓慢的原因

一时没反应过来就是平时经常查看请求头和响应头的东西,直接说没有了解过【哭了】

有没有对网站的性能分析过,比如performance工具

window.performance.timing保存着各种时间

补充说了前端性能方面:白屏时间、首屏时间

更多详细内容可以看本人的另一篇博客:前端性能监控方案(首屏、白屏时间等)

Vue中的computed和watch的区别和应用场景

计算属性computed

当其依赖的变量变化,计算属性也会随之变化,默认定义的是getter方法,还可以设置setter方法,比方法method更节省性能

监听器watch

若自身变化,则会调用回调函数

Vue兄弟组件间通信的方式

基础的东西,详见Vue官方网站

有没有使用过Vue-router

额,这个记不太清了,就说没有了

冒泡排序是怎么排序的

元素两两比较,每次遍历都找出大或小的元素

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = ; i < len - 1; i++) {
        for (var j = ; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        // 相邻元素两两对比
                var temp = arr[j+1];        // 元素交换
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

快速排序

先用个元素当中间值,比它小的元素都放前面,比它大的元素都放后面,则一次遍历后把数组分为了两组,对每组用相同的方法继续划分直到排序完成。

function quickSort(arr, left, right) {
    var len = arr.length,
        partitionIndex,
        left = typeof left != 'number' ?  : left,
        right = typeof right != 'number' ? len - 1 : right;

    if (left < right) {
        partitionIndex = partition(arr, left, right);
        quickSort(arr, left, partitionIndex-1);
        quickSort(arr, partitionIndex+1, right);
    }
    return arr;
}

function partition(arr, left ,right) {     // 分区操作
    var pivot = left,                      // 设定基准值(pivot)
        index = pivot + 1;
    for (var i = index; i <= right; i++) {
        if (arr[i] < arr[pivot]) {
            swap(arr, i, index);
            index++;
        }        
    }
    swap(arr, pivot, index - 1);
    return index-1;
}

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
function partition2(arr, low, high) {
  let pivot = arr[low];
  while (low < high) {
    while (low < high && arr[high] > pivot) {
      --high;
    }
    arr[low] = arr[high];
    while (low < high && arr[low] <= pivot) {
      ++low;
    }
    arr[high] = arr[low];
  }
  arr[low] = pivot;
  return low;
}

function quickSort2(arr, low, high) {
  if (low < high) {
    let pivot = partition2(arr, low, high);
    quickSort2(arr, low, pivot - 1);
    quickSort2(arr, pivot + 1, high);
  }
  return arr;
}

一个300宽的div包含2个200宽的div,如何保持一个不变,另一个变为100宽

用flex布局,第二个div设置flex: 1即可



相关文章