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 fn2fn1()fn2()fn2 = function() { console.log('fn2')}fn2()
输入后果:
fn1Uncaught TypeError: fn2 is not a functionfn2
这里也是在考查变量晋升,关键在于第一个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 的区别和优化:
- 虚构 DOM 不会立马进行排版与重绘操作
- 虚构 DOM 进行频繁批改,而后一次性比拟并批改实在 DOM 中须要改的局部,最初在实在 DOM 中进行排版与重绘,缩小过多DOM节点排版与重绘损耗
- 虚构 DOM 无效升高大面积实在 DOM 的重绘与排版,因为最终与实在 DOM 比拟差别,能够只渲染部分
参考:前端进阶面试题具体解答
陈说http
基本概念:HTTP,全称为 HyperText Transfer Protocol,即为超文本传输协定。是互联网利用最为宽泛的一种网络协议所有的 www 文件都必须恪守这个规范。http个性:HTTP 是无连贯无状态的HTTP 个别构建于 TCP/IP 协定之上,默认端口号是 80HTTP 能够分为两个局部,即申请和响应。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,则序列化的后果会变成null5、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: ' 123
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。长处是因为引入了状态治理,使得我的项目的开发模式与代码构造得以束缚,毛病是学习老本绝对较高
- 第一个是应用 React 的
数组扁平化
题目形容:实现一个办法使多维数组变成一维数组
最常见的递归版本如下:
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
输入后果 result
是 step3()
的参数 700 + 200
= 900
。doIt()
程序执行了三个步骤,一共用了 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 实现是一样的,然而这个代码看起来是不是清晰得多,简直跟同步代码一样