乐趣区

关于javascript:来自大厂-10-前端面试题附答案整理版

v-model 语法糖是怎么实现的

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- v-model 只是语法糖而已 -->
    <!-- v-model 在外部为不同的输出元素应用不同的 property 并抛出不同的事件 -->
    <!-- text 和 textarea 元素应用 value property 和 input 事件 -->
    <!-- checkbox 和 radio 应用 checked  property 和 change 事件 -->
    <!-- select 字段将 value 作为 prop 并将 change 作为事件 -->
    <!-- 留神:对于须要应用输入法 (如中文、日文、韩文等) 的语言,你将会发现 v -model 不会再输入法    组合文字过程中失去更新 -->
    <!-- 再一般标签上 -->
    <input v-model="sth" />  // 这一行等于下一行
    <input v-bind:value="sth" v-on:input="sth = $event.target.value" />
    <!-- 再组件上 -->
    <currency-input v-model="price"></currentcy-input>
        <!-- 上行代码是上行的语法糖         <currency-input :value="price" @input="price = arguments[0]"></currency-input>        --> 
        <!-- 子组件定义 -->
        Vue.component('currency-input', {
         template: `
          <span>
           <input
            ref="input"
            :value="value"
            @input="$emit('input', $event.target.value)"
           >
          </span>
         `,
         props: ['value'],
        })   
</body>
</html>

forEach 和 map 办法有什么区别

这办法都是用来遍历数组的,两者区别如下:

  • forEach()办法会针对每一个元素执行提供的函数,对数据的操作会扭转原数组,该办法没有返回值;
  • map()办法不会扭转原数组的值,返回一个新数组,新数组中的值为原数组调用函数解决之后的值;

深拷贝(思考到复制 Symbol 类型)

题目形容: 手写 new 操作符实现

实现代码如下:

function isObject(val) {return typeof val === "object" && val !== null;}

function deepClone(obj, hash = new WeakMap()) {if (!isObject(obj)) return obj;
  if (hash.has(obj)) {return hash.get(obj);
  }
  let target = Array.isArray(obj) ? [] : {};
  hash.set(obj, target);
  Reflect.ownKeys(obj).forEach((item) => {if (isObject(obj[item])) {target[item] = deepClone(obj[item], hash);
    } else {target[item] = obj[item];
    }
  });

  return target;
}

// var obj1 = {
// a:1,
// b:{a:2}
// };
// var obj2 = deepClone(obj1);
// console.log(obj1);

代码输入后果

function fn1(){console.log('fn1')
}
var fn2

fn1()
fn2()

fn2 = function() {console.log('fn2')
}

fn2()

输入后果:

fn1
Uncaught TypeError: fn2 is not a function
fn2

这里也是在考查变量晋升,关键在于第一个 fn2(),这时 fn2 仍是一个 undefined 的变量,所以会报错 fn2 不是一个函数。

过程和线程的区别

  • 过程能够看做独立利用,线程不能
  • 资源:过程是 cpu 资源分配的最小单位(是能领有资源和独立运行的最小单位);线程是 cpu 调度的最小单位(线程是建设在过程的根底上的一次程序运行单位,一个过程中能够有多个线程)。
  • 通信方面:线程间能够通过间接共享同一过程中的资源,而过程通信须要借助 过程间通信。
  • 调度:过程切换比线程切换的开销要大。线程是 CPU 调度的根本单位,线程的切换不会引起过程切换,但某个过程中的线程切换到另一个过程中的线程时,会引起过程切换。
  • 零碎开销:因为创立或撤销过程时,零碎都要为之调配或回收资源,如内存、I/O 等,其开销远大于创立或撤销线程时的开销。同理,在进行过程切换时,波及以后执行过程 CPU 环境还有各种各样状态的保留及新调度过程状态的设置,而线程切换时只需保留和设置大量寄存器内容,开销较小。

一个 tcp 连贯能发几个 http 申请?

如果是 HTTP 1.0 版本协定,个别状况下,不反对长连贯,因而在每次申请发送结束之后,TCP 连贯即会断开,因而一个 TCP 发送一个 HTTP 申请,然而有一种状况能够将一条 TCP 连贯放弃在沉闷状态,那就是通过 Connection 和 Keep-Alive 首部,在申请头带上 Connection: Keep-Alive,并且能够通过 Keep-Alive 通用首部中指定的,用逗号分隔的选项调节 keep-alive 的行为,如果客户端和服务端都反对,那么其实也能够发送多条,不过此形式也有限度,能够关注《HTTP 权威指南》4.5.5 节对于 Keep-Alive 连贯的限度和规定。

而如果是 HTTP 1.1 版本协定,反对了长连贯,因而只有 TCP 连接不断开,便能够始终发送 HTTP 申请,继续一直,没有下限;同样,如果是 HTTP 2.0 版本协定,反对多用复用,一个 TCP 连贯是能够并发多个 HTTP 申请的,同样也是反对长连贯,因而只有一直开 TCP 的连贯,HTTP 申请数也是能够没有下限地继续发送

Virtual Dom 的劣势在哪里?

Virtual Dom 的劣势」其实这道题目面试官更想听到的答案不是上来就说「间接操作 / 频繁操作 DOM 的性能差」,如果 DOM 操作的性能如此不堪,那么 jQuery 也不至于活到明天。所以面试官更想听到 VDOM 想解决的问题以及为什么频繁的 DOM 操作会性能差。

首先咱们须要晓得:

DOM 引擎、JS 引擎 互相独立,但又工作在同一线程(主线程)JS 代码调用 DOM API 必须 挂起 JS 引擎、转换传入参数数据、激活 DOM 引擎,DOM 重绘后再转换可能有的返回值,最初激活 JS 引擎并继续执行若有频繁的 DOM API 调用,且浏览器厂商不做“批量解决”优化,引擎间切换的单位代价将迅速积攒若其中有强制重绘的 DOM API 调用,从新计算布局、从新绘制图像会引起更大的性能耗费。

其次是 VDOM 和实在 DOM 的区别和优化:

  1. 虚构 DOM 不会立马进行排版与重绘操作
  2. 虚构 DOM 进行频繁批改,而后一次性比拟并批改实在 DOM 中须要改的局部,最初在实在 DOM 中进行排版与重绘,缩小过多 DOM 节点排版与重绘损耗
  3. 虚构 DOM 无效升高大面积实在 DOM 的重绘与排版,因为最终与实在 DOM 比拟差别,能够只渲染部分

参考:前端进阶面试题具体解答

陈说 http

基本概念:HTTP,全称为 HyperText Transfer Protocol,即为超文本传输协定。是互联网利用最为宽泛的一种网络协议
所有的 www 文件都必须恪守这个规范。http 个性:HTTP 是无连贯无状态的
HTTP 个别构建于 TCP/IP 协定之上,默认端口号是 80
HTTP 能够分为两个局部,即申请和响应。http 申请:HTTP 定义了在与服务器交互的不同形式,最罕用的办法有 4 种
别离是 GET,POST,PUT,DELETE。URL 全称为资源描述符,能够这么认为:一个 URL 地址
对应着一个网络上的资源,而 HTTP 中的 GET,POST,PUT,DELETE 
就对应着对这个资源的查问,批改,削减,删除 4 个操作。HTTP 申请由 3 个局部形成,别离是:状态行,申请头(Request Header),申请注释。HTTP 响应由 3 个局部形成,别离是:状态行,响应头(Response Header),响应注释。HTTP 响应中蕴含一个状态码,用来示意服务器对客户端响应的后果。状态码个别由 3 位形成:1xx : 示意申请曾经承受了,持续解决。2xx : 示意申请曾经解决掉了。3xx : 重定向。4xx : 个别示意客户端有谬误,申请无奈实现。5xx : 个别为服务器端的谬误。比方常见的状态码:200 OK 客户端申请胜利。301 Moved Permanently 申请永恒重定向。302 Moved Temporarily 申请长期重定向。304 Not Modified 文件未修改,能够间接应用缓存的文件。400 Bad Request 因为客户端申请有语法错误,不能被服务器所了解。401 Unauthorized 申请未经受权,无法访问。403 Forbidden 服务器收到申请,然而回绝提供服务。服务器通常会在响应注释中给出不提供服务的起因。404 Not Found 申请的资源不存在,比方输出了谬误的 URL。500 Internal Server Error 服务器产生不可预期的谬误,导致无奈实现客户端的申请。503 Service Unavailable 服务器以后不可能解决客户端的申请,在一段时间之后,服务器可能会恢复正常。大略还有一些对于 http 申请和响应头信息的介绍。

如何进攻 CSRF 攻打?

CSRF 攻打能够应用以下办法来防护:

  • 进行同源检测,服务器依据 http 申请头中 origin 或者 referer 信息来判断申请是否为容许拜访的站点,从而对申请进行过滤。当 origin 或者 referer 信息都不存在的时候,间接阻止申请。这种形式的毛病是有些状况下 referer 能够被伪造,同时还会把搜索引擎的链接也给屏蔽了。所以个别网站会容许搜索引擎的页面申请,然而相应的页面申请这种申请形式也可能被攻击者给利用。(Referer 字段会通知服务器该网页是从哪个页面链接过去的)
  • 应用 CSRF Token 进行验证,服务器向用户返回一个随机数 Token,当网站再次发动申请时,在申请参数中退出服务器端返回的 token,而后服务器对这个 token 进行验证。这种办法解决了应用 cookie 繁多验证形式时,可能会被冒用的问题,然而这种办法存在一个毛病就是,咱们须要给网站中的所有申请都增加上这个 token,操作比拟繁琐。还有一个问题是个别不会只有一台网站服务器,如果申请通过负载平衡转移到了其余的服务器,然而这个服务器的 session 中没有保留这个 token 的话,就没有方法验证了。这种状况能够通过扭转 token 的构建形式来解决。
  • 对 Cookie 进行双重验证,服务器在用户拜访网站页面时,向申请域名注入一个 Cookie,内容为随机字符串,而后当用户再次向服务器发送申请的时候,从 cookie 中取出这个字符串,增加到 URL 参数中,而后服务器通过对 cookie 中的数据和参数中的数据进行比拟,来进行验证。应用这种形式是利用了攻击者只能利用 cookie,然而不能拜访获取 cookie 的特点。并且这种办法比 CSRF Token 的办法更加不便,并且不波及到分布式拜访的问题。这种办法的毛病是如果网站存在 XSS 破绽的,那么这种形式会生效。同时这种形式不能做到子域名的隔离。
  • 在设置 cookie 属性的时候设置 Samesite,限度 cookie 不能作为被第三方应用,从而能够防止被攻击者利用。Samesite 一共有两种模式,一种是严格模式,在严格模式下 cookie 在任何状况下都不可能作为第三方 Cookie 应用,在宽松模式下,cookie 能够被申请是 GET 申请,且会产生页面跳转的申请所应用。

说一下 JSON.stringify 有什么毛病?

1. 如果 obj 外面有工夫对象,则 JSON.stringify 后再 JSON.parse 的后果,工夫将只是字符串的模式,而不是对象的模式
2. 如果 obj 里有 RegExp(正则表达式的缩写)、Error 对象,则序列化的后果将只失去空对象;3、如果 obj 里有函数,undefined,则序列化的后果会把函数或 undefined 失落;4、如果 obj 里有 NaN、Infinity 和 -Infinity,则序列化的后果会变成 null
5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果 obj 中的对象是有构造函数生成的,则应用 JSON.parse(JSON.stringify(obj))深拷贝后,会抛弃对象的 constructor;6、如果对象中存在循环援用的状况也无奈正确实现深拷贝;

setInterval 模仿 setTimeout

形容 :应用setInterval 模仿实现 setTimeout 的性能。

思路 setTimeout 的个性是在指定的工夫内只执行一次,咱们只有在 setInterval 外部执行 callback 之后,把定时器关掉即可。

实现

const mySetTimeout = (fn, time) => {
    let timer = null;
    timer = setInterval(() => {
        // 敞开定时器,保障只执行一次 fn,也就达到了 setTimeout 的成果了
        clearInterval(timer);
        fn();}, time);
    // 返回用于敞开定时器的办法
    return () => clearInterval(timer);
}

// 测试
const cancel = mySetTimeout(() => {console.log(1);
}, 1000);  
// 一秒后打印 1

代码输入后果

function runAsync (x) {const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log('result:', res))
  .catch(err => console.log(err))

输入后果如下:

1
'result:' 1
2
3

then 只会捕捉第一个胜利的办法,其余的函数尽管还会继续执行,然而不是被 then 捕捉了。

代码输入后果

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

输入后果如下:

1

看到这个题目,好多的 then,实际上只须要记住一个准则:.then.catch 的参数冀望是函数,传入非函数则会产生 值透传

第一个 then 和第二个 then 中传入的都不是函数,一个是数字,一个是对象,因而产生了透传,将resolve(1) 的值间接传到最初一个 then 里,间接打印出 1。

组件之间通信

  • 父子组件通信
  • 自定义事件
  • redux 和 context

context 如何使用

  • 父组件向其下所有子孙组件传递信息
  • 如一些简略的信息:主题、语言
  • 简单的公共信息用 redux

在跨层级通信中,次要分为一层或多层的状况

  • 如果只有一层,那么依照 React 的树形构造进行分类的话,次要有以下三种状况:父组件向子组件通信 子组件向父组件通信 以及 平级的兄弟组件间相互通信
  • 在父与子的状况下,因为 React 的设计实际上就是传递 Props 即可。那么场景体现在容器组件与展现组件之间,通过 Props 传递 state,让展现组件受控。
  • 在子与父的状况下 ,有两种形式,别离是回调函数与实例函数。回调函数,比方输入框向父级组件返回输出内容,按钮向父级组件传递点击事件等。实例函数的状况有些特地,次要是在父组件中 通过 React 的 ref API 获取子组件的实例 ,而后是 通过实例调用子组件的实例函数。这种形式在过来常见于 Modal 框的显示与暗藏
  • 多层级间的数据通信,有两种状况。第一种是一个容器中蕴含了多层子组件,须要最底部的子组件与顶部组件进行通信。在这种状况下,如果一直透传 Props 或回调函数,不仅代码层级太深,后续也很不好保护。第二种是两个组件不相干,在整个 React 的组件树的两侧,齐全不相交。那么基于多层级间的通信个别有三个计划。

    • 第一个是应用 React 的 Context API,最常见的用处是做语言包国际化
    • 第二个是应用全局变量与事件。
    • 第三个是应用状态治理框架,比方 Flux、Redux 及 Mobx。长处是因为引入了状态治理,使得我的项目的开发模式与代码构造得以束缚,毛病是学习老本绝对较高

数组扁平化

题目形容: 实现一个办法使多维数组变成一维数组

最常见的递归版本如下:

function flatter(arr) {if (!arr.length) return;
  return arr.reduce((pre, cur) =>
      Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur],
    []);
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

扩大思考:能用迭代的思路去实现吗?

实现代码如下:

function flatter(arr) {if (!arr.length) return;
  while (arr.some((item) => Array.isArray(item))) {arr = [].concat(...arr);
  }
  return arr;
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

常见的 DOM 操作有哪些

1)DOM 节点的获取

DOM 节点的获取的 API 及应用:

getElementById // 依照 id 查问
getElementsByTagName // 依照标签名查问
getElementsByClassName // 依照类名查问
querySelectorAll // 依照 css 选择器查问

// 依照 id 查问
var imooc = document.getElementById('imooc') // 查问到 id 为 imooc 的元素
// 依照标签名查问
var pList = document.getElementsByTagName('p')  // 查问到标签为 p 的汇合
console.log(divList.length)
console.log(divList[0])
// 依照类名查问
var moocList = document.getElementsByClassName('mooc') // 查问到类名为 mooc 的汇合
// 依照 css 选择器查问
var pList = document.querySelectorAll('.mooc') // 查问到类名为 mooc 的汇合

2)DOM 节点的创立

创立一个新节点,并把它增加到指定节点的前面。 已知的 HTML 构造如下:

<html>
  <head>
    <title>DEMO</title>
  </head>
  <body>
    <div id="container"> 
      <h1 id="title"> 我是题目 </h1>
    </div>   
  </body>
</html>

要求增加一个有内容的 span 节点到 id 为 title 的节点前面,做法就是:

// 首先获取父节点
var container = document.getElementById('container')
// 创立新节点
var targetSpan = document.createElement('span')
// 设置 span 节点的内容
targetSpan.innerHTML = 'hello world'
// 把新创建的元素塞进父节点里去
container.appendChild(targetSpan)

3)DOM 节点的删除

删除指定的 DOM 节点, 已知的 HTML 构造如下:

<html>
  <head>
    <title>DEMO</title>
  </head>
  <body>
    <div id="container">       <h1 id="title"> 我是题目 </h1>
    </div>     </body>
</html>

须要删除 id 为 title 的元素,做法是:

// 获取指标元素的父元素
var container = document.getElementById('container')
// 获取指标元素
var targetNode = document.getElementById('title')
// 删除指标元素
container.removeChild(targetNode)

或者通过子节点数组来实现删除:

// 获取指标元素的父元素 var container = document.getElementById('container')// 获取指标元素 var targetNode = container.childNodes[1]// 删除指标元素 container.removeChild(targetNode)

4)批改 DOM 元素

批改 DOM 元素这个动作能够分很多维度,比如说挪动 DOM 元素的地位,批改 DOM 元素的属性等。

将指定的两个 DOM 元素替换地位, 已知的 HTML 构造如下:

<html>
  <head>
    <title>DEMO</title>
  </head>
  <body>
    <div id="container">       <h1 id="title"> 我是题目 </h1>
      <p id="content"> 我是内容 </p>
    </div>     </body>
</html>

当初须要调换 title 和 content 的地位,能够思考 insertBefore 或者 appendChild:

// 获取父元素
var container = document.getElementById('container')   

// 获取两个须要被替换的元素
var title = document.getElementById('title')
var content = document.getElementById('content')
// 替换两个元素,把 content 置于 title 后面
container.insertBefore(content, title)

map 和 Object 的区别

Map Object
意外的键 Map 默认状况不蕴含任何键,只蕴含显式插入的键。 Object 有一个原型, 原型链上的键名有可能和本人在对象上的设置的键名产生抵触。
键的类型 Map 的键能够是任意值,包含函数、对象或任意根本类型。 Object 的键必须是 String 或是 Symbol。
键的程序 Map 中的 key 是有序的。因而,当迭代的时候,Map 对象以插入的程序返回键值。 Object 的键是无序的
Size Map 的键值对个数能够轻易地通过 size 属性获取 Object 的键值对个数只能手动计算
迭代 Map 是 iterable 的,所以能够间接被迭代。 迭代 Object 须要以某种形式获取它的键而后能力迭代。
性能 在频繁增删键值对的场景下体现更好。 在频繁增加和删除键值对的场景下未作出优化。

Loader 和 Plugin 有什么区别

Loader:直译为 ” 加载器 ”。Webpack 将所有文件视为模块,然而 webpack 原生是只能解析 js 文件,如果想将其余文件也打包的话,就会用到loader。所以 Loader 的作用是让 webpack 领有了加载和解析非 JavaScript 文件的能力。Plugin:直译为 ” 插件 ”。Plugin 能够扩大 webpack 的性能,让 webpack 具备更多的灵活性。在 Webpack 运行的生命周期中会播送出许多事件,Plugin 能够监听这些事件,在适合的机会通过 Webpack 提供的 API 扭转输入后果。

display 的属性值及其作用

属性值 作用
none 元素不显示,并且会从文档流中移除。
block 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。
inline 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。
inline-block 默认宽度为内容宽度,能够设置宽高,同行显示。
list-item 像块类型元素一样显示,并增加款式列表标记。
table 此元素会作为块级表格来显示。
inherit 规定应该从父元素继承 display 属性的值。

async/await 的劣势

繁多的 Promise 链并不能发现 async/await 的劣势,然而,如果须要解决由多个 Promise 组成的 then 链的时候,劣势就能体现进去了(很有意思,Promise 通过 then 链来解决多层回调的问题,当初又用 async/await 来进一步优化它)。

假如一个业务,分多个步骤实现,每个步骤都是异步的,而且依赖于上一个步骤的后果。依然用 setTimeout 来模仿异步操作:

/** * 传入参数 n,示意这个函数执行的工夫(毫秒)* 执行的后果是 n + 200,这个值将用于下一步骤 */
function takeLongTime(n) {
    return new Promise(resolve => {setTimeout(() => resolve(n + 200), n);
    });
}
function step1(n) {console.log(`step1 with ${n}`);
    return takeLongTime(n);
}
function step2(n) {console.log(`step2 with ${n}`);
    return takeLongTime(n);
}
function step3(n) {console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

当初用 Promise 形式来实现这三个步骤的解决:

function doIt() {console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

输入后果 resultstep3() 的参数 700 + 200 = 900doIt() 程序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的后果统一。

如果用 async/await 来实现呢,会是这样:

async function doIt() {console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}
doIt();

后果和之前的 Promise 实现是一样的,然而这个代码看起来是不是清晰得多,简直跟同步代码一样

退出移动版