开发者社区> 前端修罗场> 正文

【前端面试题】我靠它拿到了大厂Offer 三

简介: 【前端面试题】我靠它拿到了大厂Offer
+关注继续查看

第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不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

参考

第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 图片

  1. 避免跨域(img 天然支持跨域)
  2. 利用空白gif或1x1 px的img是互联网广告或网站监测方面常用的手段,简单、安全、相比PNG/JPG体积小,1px 透明图,对网页内容的影响几乎没有影响,这种请求用在很多地方,比如浏览、点击、热点、心跳、ID颁发等等,
  3. 图片请求不占用 Ajax 请求限额
  4. 不会阻塞页面加载,影响用户的体验,只要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>

参考

第 47 题:call 和 apply 的区别是什么,哪个性能更好一些

  1. Function.prototype.apply和Function.prototype.call 的作用是一样的,区别在于传入参数的不同;
  2. 第一个参数都是,指定函数体内this的指向
  3. 第二个参数开始不同,apply是传入带下标的集合,数组或者类数组,apply把它传给函数作为参数,call从第二个开始传入的参数是不固定的,都会传给函数作为参数。
    例如:
fun.apply(thisArg, [argsArray])
fun.call(thisArg, arg1, arg2, ...)
  1. 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)
  • 解释:我们现在控制台输入并输出,对照来看:
    image
    很显然,从结果上对照,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 字样,极有可能会被拒绝。

参考

第 43题:介绍 HTTPS 握手过程

建议去看看计算机网络讲解HTTP,然后再看HTTPS。

HTTPS三次握手

握手要解决的问题

  • 客户端和服务器身份的互相确认
  • 协商之后通信中对称加密的秘钥

握手流程

image

  • 步骤1: 客户端发出请求(ClientHello)
    首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求。
    在这一步,客户端主要向服务器提供以下信息。
  1. 客户端支持的SSL的指定版本
  2. 客户端产生的随机数(Client Random, 稍后用于生成"对话密钥"
  3. 客户端支持的加密算法
  • 步骤2:服务器回应(SeverHello)
    服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello。服务器的回应包含以下内容。
  1. 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
  2. 一个服务器生成的随机数(Server Random),稍后用于生成"对话密钥"。
  3. 确认使用的加密方法,比如RSA公钥加密。
  4. 服务器证书

除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供"客户端证书"。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。

第一次握手结束


  • 步骤3:客户端回应

客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。

如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息。

  1. 一个随机数(pre-master key), 稍后用于生成"对话密钥"。
  2. 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  3. 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。

上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称"pre-master key"。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把"会话密钥"。

第二次握手结束


  • 步骤4:服务器的最后回应
    服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的"会话密钥"。然后,向客户端最后发送下面信息。
  1. 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  2. 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。

至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用"会话密钥"加密内容,也就是对称加密。

第三次握手结束

参考

第 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 是如何监控到属性的修改并给出警告的

  1. 子组件为何不可以修改父组件传递的 Prop:
    单向数据流,易于监测数据的流动,出现了错误可以更加迅速的定位到错误发生的位置。同时,因为每当父组件属性值修改时,该值都将被覆盖;如果要有不同的改变,可以用基于prop的data或者computed
  2. 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中?

image

参考

第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])
    });

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
16428 0
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
19800 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
29133 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
22537 0
使用OpenApi弹性释放和设置云服务器ECS释放
云服务器ECS的一个重要特性就是按需创建资源。您可以在业务高峰期按需弹性的自定义规则进行资源创建,在完成业务计算的时候释放资源。本篇将提供几个Tips帮助您更加容易和自动化的完成云服务器的释放和弹性设置。
20909 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
13571 0
+关注
前端修罗场
前端修罗场,CSDN 博客专家 华为云享专家,微信公众号同名。为你提供优质原创内容。三门课程作者:《ElementUI 详解与实战》| 《ThreeJS 在网页中创建动画》|《PWA 渐进式Web应用开发》。蓝桥云课2021年度人气作者Top2。前端进阶之路 | 中大厂、外企、国企内推 | 面试培训
223
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载