乐趣区

关于javascript:2022秋招前端面试题一附答案

闭包产生的实质

以后环境中存在指向父级作用域的援用

手写 bind、apply、call

// call

Function.prototype.call = function (context, ...args) {
  context = context || window;

  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  context[fnSymbol](...args);
  delete context[fnSymbol];
}
复制代码
// apply

Function.prototype.apply = function (context, argsArr) {
  context = context || window;

  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  context[fnSymbol](...argsArr);
  delete context[fnSymbol];
}
复制代码
// bind

Function.prototype.bind = function (context, ...args) {
  context = context || window;
  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  return function (..._args) {args = args.concat(_args);

    context[fnSymbol](...args);
    delete context[fnSymbol];   
  }
}

复制代码

函数防抖

触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会从新计时。

简略版:函数外部反对应用 this 和 event 对象;

function debounce(func, wait) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        clearTimeout(timeout)
        timeout = setTimeout(function(){func.apply(context, args)
        }, wait);
    }
}
复制代码

应用:

var node = document.getElementById('layout')
function getUserAction(e) {console.log(this, e)  // 别离打印:node 这个节点 和 MouseEvent
    node.innerHTML = count++;
};
node.onmousemove = debounce(getUserAction, 1000)
复制代码

最终版:除了反对 this 和 event 外,还反对以下性能:

  • 反对立刻执行;
  • 函数可能有返回值;
  • 反对勾销性能;
function debounce(func, wait, immediate) {
    var timeout, result;

    var debounced = function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果曾经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){timeout = null;}, wait)
            if (callNow) result = func.apply(context, args)
        } else {timeout = setTimeout(function(){func.apply(context, args)
            }, wait);
        }
        return result;
    };

    debounced.cancel = function() {clearTimeout(timeout);
        timeout = null;
    };

    return debounced;
}
复制代码

应用:

var setUseAction = debounce(getUserAction, 10000, true);
// 应用防抖
node.onmousemove = setUseAction

// 勾销防抖
setUseAction.cancel()
复制代码

symbol 有什么用途

能够用来示意一个举世无双的变量避免命名抵触。然而面试官问还有吗?我没想出其余的用途就间接答我不晓得了,还能够利用 symbol 不会被惯例的办法(除了 Object.getOwnPropertySymbols 外)遍历到,所以能够用来模仿公有变量。

次要用来提供遍历接口,安排了 symbol.iterator 的对象才能够应用 for···of 循环,能够对立解决数据结构。调用之后回返回一个遍历器对象,蕴含有一个 next 办法,应用 next 办法后有两个返回值 value 和 done 别离示意函数以后执行地位的值和是否遍历结束。

Symbol.for() 能够在全局拜访 symbol

如何判断一个对象是不是空对象?

Object.keys(obj).length === 0

手写题:在线编程,getUrlParams(url,key); 就是很简略的获取 url 的某个参数的问题,但要思考边界状况,多个返回值等等

什么是作用域?

ES5 中只存在两种作用域:全局作用域和函数作用域。在 JavaScript 中,咱们将作用域定义为一套规定,这套规定用来治理引擎如何在以后作用域以及嵌套子作用域中依据标识符名称进行变量(变量名或者函数名)查找

AJAX

const getJSON = function(url) {return new Promise((resolve, reject) => {const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
        xhr.open('GET', url, false);
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.onreadystatechange = function() {if (xhr.readyState !== 4) return;
            if (xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);
            } else {reject(new Error(xhr.responseText));
            }
        }
        xhr.send();})
}
复制代码

实现数组原型办法

forEach

Array.prototype.forEach2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)  // this 就是以后的数组
    const len = O.length >>> 0  // 前面有解释
    let k = 0
    while (k < len) {if (k in O) {callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}
复制代码

O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保障转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型

map

基于 forEach 的实现可能很容易写出 map 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.map2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
-   let k = 0
+   let k = 0, res = []
    while (k < len) {if (k in O) {-           callback.call(thisArg, O[k], k, O);
+           res[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
+   return res
}
复制代码

filter

同样,基于 forEach 的实现可能很容易写出 filter 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.filter2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
-   let k = 0
+   let k = 0, res = []
    while (k < len) {if (k in O) {-           callback.call(thisArg, O[k], k, O);
+           if (callback.call(thisArg, O[k], k, O)) {+               res.push(O[k])                
+           }
        }
        k++;
    }
+   return res
}
复制代码

some

同样,基于 forEach 的实现可能很容易写出 some 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.some2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
    let k = 0
    while (k < len) {if (k in O) {-           callback.call(thisArg, O[k], k, O);
+           if (callback.call(thisArg, O[k], k, O)) {
+               return true
+           }
        }
        k++;
    }
+   return false
}
复制代码

reduce

Array.prototype.reduce2 = function(callback, initialValue) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
    let k = 0, acc

    if (arguments.length > 1) {acc = initialValue} else {
        // 没传入初始值的时候,取数组中第一个非 empty 的值为初始值
        while (k < len && !(k in O)) {k++}
        if (k > len) {throw new TypeError( 'Reduce of empty array with no initial value');
        }
        acc = O[k++]
    }
    while (k < len) {if (k in O) {acc = callback(acc, O[k], k, O)
        }
        k++
    }
    return acc
}
复制代码

深浅拷贝

浅拷贝:只思考对象类型。

function shallowCopy(obj) {if (typeof obj !== 'object') return

    let newObj = obj instanceof Array ? [] : {}
    for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = obj[key]
        }
    }
    return newObj
}
复制代码

简略版深拷贝:只思考一般对象属性,不思考内置对象和函数。

function deepClone(obj) {if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj;
}
复制代码

简单版深克隆:基于简略版的根底上,还思考了内置对象比方 Date、RegExp 等对象和函数以及解决了循环援用的问题。

const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;

function deepClone(target, map = new WeakMap()) {if (map.get(target)) {return target;}
    // 获取以后值的构造函数:获取它的类型
    let constructor = target.constructor;
    // 检测以后对象 target 是否与正则、日期格局对象匹配
    if (/^(RegExp|Date)$/i.test(constructor.name)) {// 创立一个新的非凡对象 (正则类 / 日期类) 的实例
        return new constructor(target);  
    }
    if (isObject(target)) {map.set(target, true);  // 为循环援用的对象做标记
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop], map);
            }
        }
        return cloneTarget;
    } else {return target;}
}
复制代码

事件流传机制(事件流)

冒泡和捕捉

为什么须要浏览器缓存?

对于浏览器的缓存,次要针对的是前端的动态资源,最好的成果就是,在发动申请之后,拉取相应的动态资源,并保留在本地。如果服务器的动态资源没有更新,那么在下次申请的时候,就间接从本地读取即可,如果服务器的动态资源曾经更新,那么咱们再次申请的时候,就到服务器拉取新的资源,并保留在本地。这样就大大的缩小了申请的次数,进步了网站的性能。这就要用到浏览器的缓存策略了。

所谓的 浏览器缓存 指的是浏览器将用户申请过的动态资源,存储到电脑本地磁盘中,当浏览器再次拜访时,就能够间接从本地加载,不须要再去服务端申请了。

应用浏览器缓存,有以下长处:

  • 缩小了服务器的累赘,进步了网站的性能
  • 放慢了客户端网页的加载速度
  • 缩小了多余网络数据传输

什么是作用域链?

首先要理解作用域链,当拜访一个变量时,编译器在执行这段代码时,会首先从以后的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到持续向上查找,直到全局作用域为止,,而作用域链,就是有以后作用域与下层作用域的一系列变量对象组成,它保障了以后执行的作用域对合乎拜访权限的变量和函数的有序拜访。

函数中的 arguments 是数组吗?类数组转数组的办法理解一下?

是类数组,是属于鸭子类型的领域,长得像数组,

  • … 运算符
  • Array.from
  • Array.prototype.slice.apply(arguments)

JS 数据类型

根本类型:Number、Boolean、String、null、undefined、symbol(ES6 新增的),BigInt(ES2020)
援用类型:Object,对象子类型(Array,Function)

什么是文档的预解析?

Webkit 和 Firefox 都做了这个优化,当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载前面须要通过网络加载的资源。这种形式能够使资源并行加载从而使整体速度更快。须要留神的是,预解析并不扭转 DOM 树,它将这个工作留给主解析过程,本人只解析内部资源的援用,比方内部脚本、样式表及图片。

如何进攻 XSS 攻打?

能够看到 XSS 危害如此之大,那么在开发网站时就要做好进攻措施,具体措施如下:

  • 能够从浏览器的执行来进行预防,一种是应用纯前端的形式,不必服务器端拼接后返回(不应用服务端渲染)。另一种是对须要插入到 HTML 中的代码做好充沛的本义。对于 DOM 型的攻打,次要是前端脚本的不牢靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能呈现的恶意代码状况进行判断。
  • 应用 CSP,CSP 的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行,从而避免恶意代码的注入攻打。
  1. CSP 指的是内容安全策略,它的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行。咱们只须要配置规定,如何拦挡由浏览器本人来实现。
  2. 通常有两种形式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的形式
  • 对一些敏感信息进行爱护,比方 cookie 应用 http-only,使得脚本无奈获取。也能够应用验证码,防止脚本伪装成用户执行一些操作。

用过 TypeScript 吗?它的作用是什么?

为 JS 增加类型反对,以及提供最新版的 ES 语法的反对,是的利于团队合作和排错,开发大型项目

浏览器本地存储形式及应用场景

(1)Cookie

Cookie 是最早被提出来的本地存储形式,在此之前,服务端是无奈判断网络中的两个申请是否是同一用户发动的,为解决这个问题,Cookie 就呈现了。Cookie 的大小只有 4kb,它是一种纯文本文件,每次发动 HTTP 申请都会携带 Cookie。

Cookie 的个性:

  • Cookie 一旦创立胜利,名称就无奈批改
  • Cookie 是无奈跨域名的,也就是说 a 域名和 b 域名下的 cookie 是无奈共享的,这也是由 Cookie 的隐衷安全性决定的,这样就可能阻止非法获取其余网站的 Cookie
  • 每个域名下 Cookie 的数量不能超过 20 个,每个 Cookie 的大小不能超过 4kb
  • 有平安问题,如果 Cookie 被拦挡了,那就可取得 session 的所有信息,即便加密也于事无补,无需晓得 cookie 的意义,只有转发 cookie 就能达到目标
  • Cookie 在申请一个新的页面的时候都会被发送过来

如果须要域名之间跨域共享 Cookie,有两种办法:

  1. 应用 Nginx 反向代理
  2. 在一个站点登陆之后,往其余网站写 Cookie。服务端的 Session 存储到一个节点,Cookie 存储 sessionId

Cookie 的应用场景:

  • 最常见的应用场景就是 Cookie 和 session 联合应用,咱们将 sessionId 存储到 Cookie 中,每次发申请都会携带这个 sessionId,这样服务端就晓得是谁发动的申请,从而响应相应的信息。
  • 能够用来统计页面的点击次数

(2)LocalStorage

LocalStorage 是 HTML5 新引入的个性,因为有的时候咱们存储的信息较大,Cookie 就不能满足咱们的需要,这时候 LocalStorage 就派上用场了。

LocalStorage 的长处:

  • 在大小方面,LocalStorage 的大小个别为 5MB,能够贮存更多的信息
  • LocalStorage 是长久贮存,并不会随着页面的敞开而隐没,除非被动清理,不然会永恒存在
  • 仅贮存在本地,不像 Cookie 那样每次 HTTP 申请都会被携带

LocalStorage 的毛病:

  • 存在浏览器兼容问题,IE8 以下版本的浏览器不反对
  • 如果浏览器设置为隐衷模式,那咱们将无奈读取到 LocalStorage
  • LocalStorage 受到同源策略的限度,即端口、协定、主机地址有任何一个不雷同,都不会拜访

LocalStorage 的罕用 API:

// 保留数据到 localStorage
localStorage.setItem('key', 'value');

// 从 localStorage 获取数据
let data = localStorage.getItem('key');

// 从 localStorage 删除保留的数据
localStorage.removeItem('key');

// 从 localStorage 删除所有保留的数据
localStorage.clear();

// 获取某个索引的 Key
localStorage.key(index)
复制代码

LocalStorage 的应用场景:

  • 有些网站有换肤的性能,这时候就能够将换肤的信息存储在本地的 LocalStorage 中,当须要换肤的时候,间接操作 LocalStorage 即可
  • 在网站中的用户浏览信息也会存储在 LocalStorage 中,还有网站的一些不常变动的个人信息等也能够存储在本地的 LocalStorage 中

(3)SessionStorage

SessionStorage 和 LocalStorage 都是在 HTML5 才提出来的存储计划,SessionStorage 次要用于长期保留同一窗口 (或标签页) 的数据,刷新页面时不会删除,敞开窗口或标签页之后将会删除这些数据。

SessionStorage 与 LocalStorage 比照:

  • SessionStorage 和 LocalStorage 都在 本地进行数据存储
  • SessionStorage 也有同源策略的限度,然而 SessionStorage 有一条更加严格的限度,SessionStorage只有在同一浏览器的同一窗口下才可能共享
  • LocalStorage 和 SessionStorage都不能被爬虫爬取

SessionStorage 的罕用 API:

// 保留数据到 sessionStorage
sessionStorage.setItem('key', 'value');

// 从 sessionStorage 获取数据
let data = sessionStorage.getItem('key');

// 从 sessionStorage 删除保留的数据
sessionStorage.removeItem('key');

// 从 sessionStorage 删除所有保留的数据
sessionStorage.clear();

// 获取某个索引的 Key
sessionStorage.key(index)
复制代码

SessionStorage 的应用场景

  • 因为 SessionStorage 具备时效性,所以能够用来存储一些网站的游客登录的信息,还有长期的浏览记录的信息。当敞开网站之后,这些信息也就随之打消了。

箭头函数和一般函数有啥区别?箭头函数能当构造函数吗?

  • 一般函数通过 function 关键字定义,this 无奈联合词法作用域应用,在运行时绑定,只取决于函数的调用形式,在哪里被调用,调用地位。(取决于调用者,和是否独立运行)
  • 箭头函数应用被称为“胖箭头”的操作 => 定义,箭头函数不利用一般函数 this 绑定的四种规定,而是依据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无奈被批改(new 也不行)。

    • 箭头函数罕用于回调函数中,包含事件处理器或定时器
    • 箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
    • 没有原型、没有 this、没有 super,没有 arguments,没有 new.target
    • 不能通过 new 关键字调用

      • 一个函数外部有两个办法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 办法,创立一个实例对象,而后再执行这个函数体,将函数的 this 绑定在这个实例对象上
      • 当间接调用时,执行 [[Call]] 办法,间接执行函数体
      • 箭头函数没有 [[Construct]] 办法,不能被用作结构函数调用,当应用 new 进行函数调用时会报错。
function foo() {return (a) => {console.log(this.a);
  }
}

var obj1 = {a: 2}

var obj2 = {a: 3}

var bar = foo.call(obj1);
bar.call(obj2);
复制代码

点击刷新按钮或者按 F5、按 Ctrl+F5(强制刷新)、地址栏回车有什么区别?

  • 点击刷新按钮或者按 F5: 浏览器间接对本地的缓存文件过期,然而会带上 If-Modifed-Since,If-None-Match,这就意味着服务器会对文件查看新鲜度,返回后果可能是 304,也有可能是 200。
  • 用户按 Ctrl+F5(强制刷新): 浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前素来没有申请过,返回后果是 200。
  • 地址栏回车:浏览器发动申请,依照失常流程,本地查看是否过期,而后服务器查看新鲜度,最初返回内容。
退出移动版