闭包产生的实质

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

手写 bind、apply、call

// callFunction.prototype.call = function (context, ...args) {  context = context || window;  const fnSymbol = Symbol("fn");  context[fnSymbol] = this;  context[fnSymbol](...args);  delete context[fnSymbol];}复制代码
// applyFunction.prototype.apply = function (context, argsArr) {  context = context || window;  const fnSymbol = Symbol("fn");  context[fnSymbol] = this;  context[fnSymbol](...argsArr);  delete context[fnSymbol];}复制代码
// bindFunction.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:

// 保留数据到 localStoragelocalStorage.setItem('key', 'value');// 从 localStorage 获取数据let data = localStorage.getItem('key');// 从 localStorage 删除保留的数据localStorage.removeItem('key');// 从 localStorage 删除所有保留的数据localStorage.clear();// 获取某个索引的KeylocalStorage.key(index)复制代码

LocalStorage的应用场景:

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

(3)SessionStorage

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

SessionStorage与LocalStorage比照:

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

SessionStorage的罕用API:

// 保留数据到 sessionStoragesessionStorage.setItem('key', 'value');// 从 sessionStorage 获取数据let data = sessionStorage.getItem('key');// 从 sessionStorage 删除保留的数据sessionStorage.removeItem('key');// 从 sessionStorage 删除所有保留的数据sessionStorage.clear();// 获取某个索引的KeysessionStorage.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。
  • 地址栏回车: 浏览器发动申请,依照失常流程,本地查看是否过期,而后服务器查看新鲜度,最初返回内容。