第50题:Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。 为了解决这个问题,经过vue内部处理后可以使用以下几种方法来监听数组:
push() pop() shift() unshift() splice() sort() reverse()
由于只针对了以上八种方法进行了hack处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。
Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue 2.x里,是通过 递归 +
遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。
而要取代它的Proxy有以下如下优点;:
- 可以劫持整个对象,并返回一个新对象
- 有13种劫持操作
- Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
参考
- https://www.jianshu.com/p/860418f0785c
- https://juejin.im/post/5acd0c8a6fb9a028da7cdfaf
- http://www.10tiao.com/html/780/201812/2650588659/1.html
- http://es6.ruanyifeng.com/#docs/proxy
- https://zhuanlan.zhihu.com/p/35080324
第49题:实现 (5).add(3).minus(2) 功能,例: 5 + 3 - 2,结果为 6
Number.MAX_SAFE_DIGITS = Number.MAX_SAFE_INTEGER.toString().length-2 Number.prototype.digits = function(){ let result = (this.valueOf().toString().split('.')[1] || '').length return result > Number.MAX_SAFE_DIGITS ? Number.MAX_SAFE_DIGITS : result } Number.prototype.add = function(i=0){ if (typeof i !== 'number') { throw new Error('请输入正确的数字'); } const v = this.valueOf(); const thisDigits = this.digits(); const iDigits = i.digits(); const baseNum = Math.pow(10, Math.max(thisDigits, iDigits)); const result = (v * baseNum + i * baseNum) / baseNum; if(result>0){ return result > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : result } else{ return result < Number.MIN_SAFE_INTEGER ? Number.MIN_SAFE_INTEGER : result } } Number.prototype.minus = function(i=0){ if (typeof i !== 'number') { throw new Error('请输入正确的数字'); } const v = this.valueOf(); const thisDigits = this.digits(); const iDigits = i.digits(); const baseNum = Math.pow(10, Math.max(thisDigits, iDigits)); const result = (v * baseNum - i * baseNum) / baseNum; if(result>0){ return result > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : result } else{ return result < Number.MIN_SAFE_INTEGER ? Number.MIN_SAFE_INTEGER : result } }
- 大数加减:直接通过 Number 原生的安全极值来进行判断,超出则直接取安全极值
- 超级多位数的小数加减:取JS安全极值位数-2作为最高兼容小数位数
第 48 题:为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片
- 避免跨域(img 天然支持跨域)
- 利用空白gif或1x1 px的img是互联网广告或网站监测方面常用的手段,简单、安全、相比PNG/JPG体积小,1px 透明图,对网页内容的影响几乎没有影响,这种请求用在很多地方,比如浏览、点击、热点、心跳、ID颁发等等,
- 图片请求不占用 Ajax 请求限额
- 不会阻塞页面加载,影响用户的体验,只要new Image对象就好了,一般情况下也不需要append到DOM中,通过它的onerror和onload事件来检测发送状态。
示例:
<script type="text/javascript"> var thisPage = location.href; var referringPage = (document.referrer) ? document.referrer : "none"; var beacon = new Image(); beacon.src = "http://www.example.com/logger/beacon.gif?page=" + encodeURI(thisPage) + "&ref=" + encodeURI(referringPage); </script>
参考
- https://segmentfault.com/a/1190000015863478
- https://blog.csdn.net/zmx729618/article/details/58600620/
- https://www.zhihu.com/question/25488619?sort=created
- https://www.jishuwen.com/d/2CQv
第 47 题:call 和 apply 的区别是什么,哪个性能更好一些
- Function.prototype.apply和Function.prototype.call 的作用是一样的,区别在于传入参数的不同;
- 第一个参数都是,指定
函数体内this的指向
; - 第二个参数开始不同,apply是传入
带下标的集合,数组或者类数组
,apply把它传给函数作为参数,call从第二个开始传入的参数是不固定
的,都会传给函数作为参数。
例如:
fun.apply(thisArg, [argsArray])
fun.call(thisArg, arg1, arg2, ...)
- call比apply的性能要好,
平常可以多用call
, call传入参数的格式正是内部所需要的格式
参考
第 46 题:双向绑定和 vuex 是否冲突
官方文档解释:https://vuex.vuejs.org/zh/guide/forms.html
第 45 题:输出以下代码执行的结果并解释为什么
var obj = { '2': 3, '3': 4, 'length': 2, 'splice': Array.prototype.splice, 'push': Array.prototype.push } obj.push(1) obj.push(2) console.log(obj)
- 解释:我们现在控制台输入并输出,对照来看:
很显然,从结果上对照,obj.push(1)
访问的是对象obj的push属性,该push属性具有数组的push方法,因此,obj.push(1)和obj.push(2)
会向Array中追加并返回修改后的数组。但是,问题来了!怎么追加?往哪个位置追加?
我们看到打印输出时,obj是一个数组形式输出,但’obj instanceof Array’又不是一个真实的数组,所以这是一个类数组形式
,我们给这个类数组起一个名称S_Arr
。
细心的你,一定注意到empty x 2
,即输出了2个空。所以,我们对此可知,在进行obj.push(1)和obj.push(2)
操作时,S_Arr[2]=1,S_Arr[3]=2
,可见S_Arr[0]和S_Arr[1]为empty
,但这两个下标仍旧占位。现在,可以解释为什么length为4
了吧。
第 44 题:HTTPS 握手过程中,客户端如何验证证书的合法性
浏览器和系统会内置默认信任的证书。
如果只劫持了站点返回自己签发的别的证书,证书因为与域名不符会验证失败
。证书是没法伪造的,因为你没有证书的私钥。除非用给原证书签名的根证书重新给你签发一个同域名的证书。
rsa 加密唯一的缺点就是不能防中间人攻击。所以系统和浏览器内置了信任证书。
像 ssh 连接第一次会提示指纹一样。你得先信任指纹才能继续操作,指纹发生改变后就会提示你指纹错误。
除此之外,证书颁发机构会验证域名所有权,你得证明域名的所有权在你手里:
- 你往网站根目录放置一个机构提供的特定的文件,然后机构会定时抓取这个文件,如果能抓取到说明你确实有这个网站的管理权限。注意,只支持 80 和 443 端口,8080 登端口不认。
- 机构往域名信息里的管理员邮箱发一封验证邮件,邮件里有验证链接,域名管理员要点开链接输入验证码确认。
- 要求你在域名 DNS 控制面板里添加一条特定的域名记录。
以上操作都只有域名管理员或者网站管理员才能做到,避免了他人伪造证书。
还有一点很重要的:
如果发现申请的域名包含知名品牌、知名网站域名,证书机构会人工审核,有可能会要求你提供相关证明文件。比如我想申请 www.nikeshop.com 的证书,因为包含 Nike 字样,极有可能会被拒绝。
参考
- https://www.v2ex.com/amp/t/411144
- https://www.cnblogs.com/StephenWu/p/5720954.html
- https://blog.csdn.net/love_hot_girl/article/details/81164279
第 43题:介绍 HTTPS 握手过程
建议去看看计算机网络讲解HTTP,然后再看HTTPS。
HTTPS三次握手
握手要解决的问题:
- 客户端和服务器身份的互相确认
- 协商之后通信中对称加密的秘钥
握手流程
- 步骤1: 客户端发出请求(ClientHello)
首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求。
在这一步,客户端主要向服务器提供以下信息。
- 客户端支持的SSL的指定版本
- 客户端产生的随机数(Client Random, 稍后用于生成"对话密钥"
- 客户端支持的加密算法
- 步骤2:服务器回应(SeverHello)
服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello。服务器的回应包含以下内容。
- 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
- 一个服务器生成的随机数(Server Random),稍后用于生成"对话密钥"。
- 确认使用的加密方法,比如RSA公钥加密。
- 服务器证书
除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供"客户端证书"。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。
第一次握手结束
- 步骤3:客户端回应
客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。
如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息。
- 一个随机数(pre-master key), 稍后用于生成"对话密钥"。
- 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
- 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。
上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称"pre-master key"。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把"会话密钥"。
第二次握手结束
- 步骤4:服务器的最后回应
服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的"会话密钥"。然后,向客户端最后发送下面信息。
- 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
- 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。
至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用"会话密钥"加密内容,也就是对称加密。
第三次握手结束
参考
- Http协议理解
- https://www.cnblogs.com/zxh930508/p/5432700.html
- 协议理解之HTTPS
- https://developers.weixin.qq.com/community/develop/article/doc/000046a5fdc7802a15f7508b556413
- https://mp.weixin.qq.com/s/1ojSrhc9LZV8zlX6YblMtA
第 42 题:使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果
let arr = [3, 15, 8, 29, 102, 22]; arr.sort((a,b)=>{a-b}); // 升序 arr.sort((a,b)=>{b-a}); //倒序
- sort()函数解释:
如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,
是按照字符编码的顺序进行排序
。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。
如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值
,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:
- 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
- 若 a 等于 b,则返回 0。
- 若 a 大于b,则返回一个大于 0 的值。
第 41 题:实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现
分别给出4中方式:
//Promise const sleep = time => { return new Promise(resolve=>setTimeout(resolve,time)) } sleep(1000).then(()=>{ console.log(1) }) //Generator function* sleepGenerator(time) { yield new Promise(function(resolve,reject){ setTimeout(resolve,time); }) } sleepGenerator(1000).next().value.then(()=>{console.log(1)}) //async function sleep(time) { return new Promise(resolve=>setTimeout(resolve,time)) } async function output() { let out = await sleep(1000); console.log(1); return out; } output(); //ES5 function sleep(callback,time) { if(typeof callback === 'function') setTimeout(callback,time) } function output(){ console.log(1); } sleep(output,1000);
参考
第 40 题:下面代码将打印什么?
var a = 10; (function () { console.log(a); //undefined a = 5 console.log(window.a) // 10 var a = 20; console.log(a) //20 })()
原因:在内部声名var a = 20;相当于先声明var a
;然后再执行赋值操作,这是在IIFE内形成的独立作用域
。
B 情况:
var a = 10; (function () { console.log(a); //10 a = 5 console.log(window.a) // 5 //var a = 20; console.log(a) //5 })()
C情况:
var a = 10;
(function () {
console.log(a); //10
//a = 5
console.log(window.a) // 10
//var a = 20;
console.log(a) //10
})()
第 39 题:在 Vue 中,子组件为何不可以修改父组件传递的 Prop,如果修改了,Vue 是如何监控到属性的修改并给出警告的
- 子组件为何不可以修改父组件传递的 Prop:
单向数据流,易于监测数据的流动,出现了错误可以更加迅速的定位到错误发生的位置。同时,因为每当父组件属性值修改时,该值都将被覆盖;如果要有不同的改变,可以用基于prop的data或者computed
。 - Vue 是如何监控到属性的修改并给出警告的
在initProps
的时候,在defineReactive
时通过判断是否在开发环境,如果是开发环境,会在触发set
的时候判断是否此key
是否处于updatingChildren
中被修改,如果不是,说明此修改来自子组件,触发warning
提示。
if (process.env.NODE_ENV !== 'production') { var hyphenatedKey = hyphenate(key); if (isReservedAttribute(hyphenatedKey) || config.isReservedAttr(hyphenatedKey)) { warn( ("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."), vm ); } defineReactive$$1(props, key, value, function () { if (!isRoot && !isUpdatingChildComponent) { warn( "Avoid mutating a prop directly since the value will be " + "overwritten whenever the parent component re-renders. " + "Instead, use a data or computed property based on the prop's " + "value. Prop being mutated: \"" + key + "\"", vm ); } }); }
需要特别注意的是,由于值传递与地址传递的原因当你从子组件修改的prop,属于基础类型时会触发提示。 这种情况下,你是无法修改父组件的数据源的, 因为基础类型赋值时是值拷贝。你直接将另一个非基础类型(Object, array)赋值到此key时也会触发提示(但实际上不会影响父组件的数据源), 当你修改object的属性时不会触发提示,并且会修改父组件数据源的数据。(参考:https://blog.csdn.net/ImagineCode/article/details/54409272)
第 38 题:介绍下 BFC 及其应用
BFC 就是块级格式上下文,是页面盒模型布局中的一种 CSS 渲染模式,相当于一个独立的容器,里面的元素和外部的元素相互不影响。创建 BFC 的方式有:
- html 根元素
- float 浮动
- 绝对定位
- overflow 不为 visiable
- display 为表格布局或者弹性布局
BFC 主要的作用是:
- 清除浮动
- 防止同一 BFC 容器中的相邻元素间的外边距重叠问题
参考
第37题:下面代码中 a 在什么情况下会打印 1?
var a = ?; if(a == 1 && a == 2 && a == 3){ console.log(1); }
解析
因为==会进行隐式类型转换 。
- 利用toString方式
let a = { i: 1, toString () { return a.i++ } } if(a == 1 && a == 2 && a == 3) { console.log('1'); }
- 利用valueOf
let a = { i: 1, valueOf () { return a.i++ } } if(a == 1 && a == 2 && a == 3) { console.log('1'); }
- 数组方式
var a = [1,2,3]; a.join = a.shift; if(a == 1 && a == 2 && a == 3) { console.log('1'); }
- ES6的symbol
let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)}; if(a == 1 && a == 2 && a == 3) { console.log('1'); }
参考:
第36题:为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作?
参考:
第35题:使用迭代的方式实现 flatten 函数
flatten函数,即扁平化函数,是使一个嵌套数组变成一维数组的方式。
例如:
function flatten(arr=[1,[2],[[3]],[[[4]]]]) { return arr.toString().split(',') } alert(flatten());
那么怎么使用迭代的方式实现flatten函数呢?往下看。
function flatten(arr,result=[]) { for(let item of arr){ if(Array.isArray(item)) flatten(item,result) else result.push(item) } return result } var array = [[1,2,3],4,5,6,[[7]],[]]; var result = flatten(array); console.log(result);
上面我们使用迭代递归的方式,使用 result 变量存储结果,然后迭代当前数组,如果值也是数组则继续扁平化,否则将值放入 result 里
。
参考:
第34题:浏览器缓存可以分成 Service Worker、Memory Cache、Disk Cache 和 Push Cache,那请求的时候 from memory cache 和 from disk cache 的依据是什么,哪些数据什么时候存放在 Memory Cache 和 Disk Cache中?
参考
第33题:下面的代码打印什么内容,为什么?
var b = 10; (function b() { b = 20; console.log(b) })()
- 解析:
这上面的代码中,我们知道这里面涉及到作用域和IIFE的知识。
首先我们回顾下IIFE(立即调用函数表达式),参考MDN:
IIFE
IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数。
下面这句话很重要:
第一部分是包围在 圆括号运算符() 里的一个匿名函数,这个匿名函数拥有独立的词法作用域
。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域
。
第二部分再一次使用 () 创建了一个立即执行函数表达式
,JavaScript 引擎到此将直接执行函数。
当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问
:
(function () { var name = "Barry"; })(); // 外部不能访问变量 name name // undefined
将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果:
var result = (function () { var name = "Barry"; return name; })(); // IIFE 执行后返回的结果: result; // "Barry"
回头看
OK,了解完了IIFE,我们回过头来看这道题:
var b = 10; (function b() { b = 20; console.log(b) })()
- b()是个IIFE具名函数,而非匿名函数。它将被立即执行。
- 在内部作用域中,
IIFE函数无法对进行赋值,有些类似于const的意思
。所以b=20无效 console.log(b)
中,访问变量b,首先在IIFE内部中查找已声明的变量b
,结果查找到b(),于是,输出b()这个具名函数
。而b=20
并没有进行声明,所以,无效。
现在我们对代码进行改造,再来看看其他种情况:
var b = 10; (function b() { window.b = 20; console.log(b); // [Function b] console.log(window.b); // 20是必然的 })();
分别打印20与10:
var b = 10; (function b() { var b = 20; // IIFE内部变量 console.log(b); // 20 console.log(window.b); // 10 })();
第 32 题:Virtual DOM 真的比操作原生 DOM 快吗?
参考
第 31 题:改造下面的代码,使之输出0 - 9,写出你能想到的所有解法
for (var i = 0; i< 10; i++){ setTimeout(() => { console.log(i); }, 1000) }
改造
- 改造1
function fn() { for(var i=0;i<10;i++){//可以将for循环用while循环替换 console.log(i); clearTimeout(timer); } } var timer = setTimeout(fn,1000); 或者: for (let i = 0; i< 10; i++){ setTimeout(() => { console.log(i); }, 1000) } 或者 简写: for (var i = 0; i< 10; i++){ setTimeout(console.log, 1000, i) }
- 改造2
(function fn(i){ if(i<10){ console.log(i); i++; setTimeout(fn, 1000,i); } })(0)
-改造3 - 利用闭包特性
for(var i=0;i<10;i++){ (function(i){ setTimeout(()=>{ console.log(i); },1000); })(i); } 或者: for(var i=0;i<10;i++){ (function(){ var j = i; setTimeout(()=>{ console.log(i); },1000); })(); }
第30题:数组合并
题目描述:请把俩个数组 [‘A1’, ‘A2’, ‘B1’, ‘B2’, ‘C1’, ‘C2’, ‘D1’, ‘D2’, ‘E1’, ‘E2’] 和 [‘A’, ‘B’, ‘C’, ‘D’,‘E’],合并为 [“A1”, “A2”, “A”, “B1”, “B2”, “B”, “C1”, “C2”, “C”, “D1”, “D2”, “D”, “E1”, “E2”, “E”]
解析: 观察题目,数组是有规律的!
var arr1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2', 'E1', 'E2']; var arr2 = ['A', 'B', 'C', 'D','E']; var arr3 = []; arr1.forEach(function(item,index){ if((index+1)%2===0) { arr3.push(arr1[index]); arr3.push(arr2[(index+1)/2-1]); }else { arr3.push(arr1[index]); } }); console.log(arr3);
优化:
arr1.map(function(item,index){ (index+1)%2===0 ? arr3.push(arr1[index]) && arr3.push(arr2[(index+1)/2-1]) : arr3.push(arr1[index]) });