写点什么

前端二面经典面试题指南

作者:hellocoder2029
  • 2023-02-28
    浙江
  • 本文字数:8344 字

    阅读完需:约 27 分钟

数组去重

ES5 实现:


function unique(arr) {    var res = arr.filter(function(item, index, array) {        return array.indexOf(item) === index    })    return res}
复制代码


ES6 实现:


var unique = arr => [...new Set(arr)]
复制代码

浏览器乱码的原因是什么?如何解决?

产生乱码的原因:


  • 网页源代码是gbk的编码,而内容中的中文字是utf-8编码的,这样浏览器打开即会出现html乱码,反之也会出现乱码;

  • html网页编码是gbk,而程序从数据库中调出呈现是utf-8编码的内容也会造成编码乱码;

  • 浏览器不能自动检测网页编码,造成网页乱码。


解决办法:


  • 使用软件编辑 HTML 网页内容;

  • 如果网页设置编码是gbk,而数据库储存数据编码格式是UTF-8,此时需要程序查询数据库数据显示数据前进程序转码;

  • 如果浏览器浏览时候出现网页乱码,在浏览器中找到转换编码的菜单进行转换。

代码输出结果

const first = () => (new Promise((resolve, reject) => {    console.log(3);    let p = new Promise((resolve, reject) => {        console.log(7);        setTimeout(() => {            console.log(5);            resolve(6);            console.log(p)        }, 0)        resolve(1);    });    resolve(2);    p.then((arg) => {        console.log(arg);    });}));first().then((arg) => {    console.log(arg);});console.log(4);
复制代码


输出结果如下:


374125Promise{<resolved>: 1}
复制代码


代码的执行过程如下:


  1. 首先会进入 Promise,打印出 3,之后进入下面的 Promise,打印出 7;

  2. 遇到了定时器,将其加入宏任务队列;

  3. 执行 Promise  p 中的 resolve,状态变为 resolved,返回值为 1;

  4. 执行 Promise first 中的 resolve,状态变为 resolved,返回值为 2;

  5. 遇到 p.then,将其加入微任务队列,遇到 first().then,将其加入任务队列;

  6. 执行外面的代码,打印出 4;

  7. 这样第一轮宏任务就执行完了,开始执行微任务队列中的任务,先后打印出 1 和 2;

  8. 这样微任务就执行完了,开始执行下一轮宏任务,宏任务队列中有一个定时器,执行它,打印出 5,由于执行已经变为 resolved 状态,所以resolve(6)不会再执行;

  9. 最后console.log(p)打印出Promise{<resolved>: 1}

什么是 XSS 攻击?

(1)概念

XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。


XSS 的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。


攻击者可以通过这种攻击方式可以进行以下操作:


  • 获取页面的数据,如 DOM、cookie、localStorage;

  • DOS 攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器;

  • 破坏页面结构;

  • 流量劫持(将链接指向某网站);

(2)攻击类型

XSS 可以分为存储型、反射型和 DOM 型:


  • 存储型指的是恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。

  • 反射型指的是攻击者诱导用户访问一个带有恶意代码的 URL 后,服务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终完成 XSS 攻击。 

  • DOM 型指的通过修改页面的 DOM 节点形成的 XSS。


1)存储型 XSS 的攻击步骤:


  1. 攻击者将恶意代码提交到⽬标⽹站的数据库中。

  2. ⽤户打开⽬标⽹站时,⽹站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。

  3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。

  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。


这种攻击常⻅于带有⽤户保存数据的⽹站功能,如论坛发帖、商品评论、⽤户私信等。


2)反射型 XSS 的攻击步骤:


  1. 攻击者构造出特殊的 URL,其中包含恶意代码。

  2. ⽤户打开带有恶意代码的 URL 时,⽹站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。

  3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。

  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。


反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。


反射型 XSS 漏洞常⻅于通过 URL 传递参数的功能,如⽹站搜索、跳转等。 由于需要⽤户主动打开恶意的 URL 才能⽣效,攻击者往往会结合多种⼿段诱导⽤户点击。


3)DOM 型 XSS 的攻击步骤:


  1. 攻击者构造出特殊的 URL,其中包含恶意代码。

  2. ⽤户打开带有恶意代码的 URL。

  3. ⽤户浏览器接收到响应后解析执⾏,前端 JavaScript 取出 URL 中的恶意代码并执⾏。

  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。


DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执⾏恶意代码由浏览器端完成,属于前端 JavaScript ⾃身的安全漏洞,⽽其他两种 XSS 都属于服务端的安全漏洞。

什么是原型什么是原型链?

<!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>
</body><script> function Person () { } var person = new Person(); person.name = 'Kevin'; console.log(person.name) // Kevin
// prototype function Person () { } Person.prototype.name = 'Kevin'; var person1 = new Person(); var person2 = new Person(); console.log(person1.name)// Kevin console.log(person2.name)// Kevin
// __proto__ function Person () { } var person = new Person(); console.log(person.__proto__ === Person.prototype) // true
//constructor function Person() { } console.log(Person === Person.prototype.constructor) // true
//综上所述 function Person () { } var person = new Person() console.log(person.__proto__ == Person.prototype) // true console.log(Person.prototype.constructor == Person) // true //顺便学习一下ES5得方法,可以获得对象得原型 console.log(Object.getPrototypeOf(person) === Person.prototype) // true
//实例与原型 function Person () { } Person.prototype.name = 'Kevin'; var person = new Person(); person.name = 'Daisy'; console.log(person.name) // Daisy delete person.name; console.log(person.name) // Kevin
//原型得原型 var obj = new Object(); obj.name = 'Kevin', console.log(obj.name) //Kevin
//原型链 console.log(Object.prototype.__proto__ === null) //true // null 表示"没用对象" 即该处不应该有值
// 补充 function Person() { } var person = new Person() console.log(person.constructor === Person) // true //当获取person.constructor时,其实person中并没有constructor属性,当不能读取到constructor属性时,会从person的原型 //也就是Person.prototype中读取时,正好原型中有该属性,所以 person.constructor === Person.prototype.constructor
//__proto__ //其次是__proto__,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于Person.prototype中,实际上,它 // 是来自与Object.prototype,与其说是一个属性,不如说是一个getter/setter,当使用obj.__proto__时,可以理解成返回了 // Object.getPrototypeOf(obj) 总结: 1、当一个对象查找属性和方法时会从自身查找,如果查找不到则会通过__proto__指向被实例化的构造函数的prototype 2、隐式原型也是一个对象,是指向我们构造函数的原型 3、除了最顶层的Object对象没有__proto_,其他所有的对象都有__proto__,这是隐式原型 4、隐式原型__proto__的作用是让对象通过它来一直往上查找属性或方法,直到找到最顶层的Object的__proto__属性,它的值是null,这个查找的过程就是原型链


</script></html>
复制代码

数组扁平化

数组扁平化就是将 [1, [2, [3]]] 这种多层的数组拍平成一层 [1, 2, 3]。使用 Array.prototype.flat 可以直接将多层数组拍平成一层:


[1, [2, [3]]].flat(2)  // [1, 2, 3]
复制代码


现在就是要实现 flat 这种效果。


ES5 实现:递归。


function flatten(arr) {    var result = [];    for (var i = 0, len = arr.length; i < len; i++) {        if (Array.isArray(arr[i])) {            result = result.concat(flatten(arr[i]))        } else {            result.push(arr[i])        }    }    return result;}
复制代码


ES6 实现:


function flatten(arr) {    while (arr.some(item => Array.isArray(item))) {        arr = [].concat(...arr);    }    return arr;}
复制代码


参考 前端进阶面试题详细解答

事件总线(发布订阅模式)

class EventEmitter {    constructor() {        this.cache = {}    }    on(name, fn) {        if (this.cache[name]) {            this.cache[name].push(fn)        } else {            this.cache[name] = [fn]        }    }    off(name, fn) {        let tasks = this.cache[name]        if (tasks) {            const index = tasks.findIndex(f => f === fn || f.callback === fn)            if (index >= 0) {                tasks.splice(index, 1)            }        }    }    emit(name, once = false, ...args) {        if (this.cache[name]) {            // 创建副本,如果回调函数内继续注册相同事件,会造成死循环            let tasks = this.cache[name].slice()            for (let fn of tasks) {                fn(...args)            }            if (once) {                delete this.cache[name]            }        }    }}
// 测试let eventBus = new EventEmitter()let fn1 = function(name, age) { console.log(`${name} ${age}`)}let fn2 = function(name, age) { console.log(`hello, ${name} ${age}`)}eventBus.on('aaa', fn1)eventBus.on('aaa', fn2)eventBus.emit('aaa', false, '布兰', 12)// '布兰 12'// 'hello, 布兰 12'
复制代码

防抖节流

题目描述:手写防抖节流


实现代码如下:


// 防抖function debounce(fn, delay = 300) {  //默认300毫秒  let timer;  return function () {    const args = arguments;    if (timer) {      clearTimeout(timer);    }    timer = setTimeout(() => {      fn.apply(this, args); // 改变this指向为调用debounce所指的对象    }, delay);  };}
window.addEventListener( "scroll", debounce(() => { console.log(111); }, 1000));
// 节流// 设置一个标志function throttle(fn, delay) { let flag = true; return () => { if (!flag) return; flag = false; timer = setTimeout(() => { fn(); flag = true; }, delay); };}
window.addEventListener( "scroll", throttle(() => { console.log(111); }, 1000));
复制代码

instanceof

题目描述:手写 instanceof 操作符实现


实现代码如下:


function myInstanceof(left, right) {  while (true) {    if (left === null) {      return false;    }    if (left.__proto__ === right.prototype) {      return true;    }    left = left.__proto__;  }}
复制代码

说一下常见的 HTTP 状态码?说一下状态码是 302 和 304 是什么意思?你在项目中出现过么?你是怎么解决的?

    <!-- 状态码:由3位数字组成,第一个数字定义了响应的类别 -->    <!-- 1xx:指示消息,表示请求已接收,继续处理 -->    <!-- 2xx:成功,表示请求已被成功接收,处理 -->    <!-- 200 OK:客户端请求成功         204 No Content:无内容。服务器成功处理,但未返回内容。一般用在只是客户端向服务器发送信息,而服务器不用向客户端返回什么信息的情况。不会刷新页面。         206 Partial Content:服务器已经完成了部分GET请求(客户端进行了范围请求)。响应报文中包含Content-Range指定范围的实体内容 -->    <!-- 3xx 重定向 -->    <!-- 301 Moved Permanently:永久重定向,表示请求的资源已经永久的搬到了其他位置。         302 Found:临时重定向,表示请求的资源临时搬到了其他位置         303 See Other:临时重定向,应使用GET定向获取请求资源。303功能与302一样,区别只是303明确客户端应该使用GET访问         307 Temporary Redirect:临时重定向,和302有着相同含义。POST不会变成GET         304 Not Modified:表示客户端发送附带条件的请求(GET方法请求报文中的IF…)时,条件不满足。返回304时,不包含任何响应主体。虽然304被划分在3XX,但和重定向一毛钱关系都没有 -->    <!-- 4xx:客户端错误 -->    <!-- 400 Bad Request:客户端请求有语法错误,服务器无法理解。         401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。         403 Forbidden:服务器收到请求,但是拒绝提供服务         404 Not Found:请求资源不存在。比如,输入了错误的url         415 Unsupported media type:不支持的媒体类型 -->    <!-- 5xx:服务器端错误,服务器未能实现合法的请求。 -->    <!-- 500 Internal Server Error:服务器发生不可预期的错误。         503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常, -->
复制代码

代码输出结果

Promise.resolve(1)  .then(res => {    console.log(res);    return 2;  })  .catch(err => {    return 3;  })  .then(res => {    console.log(res);  });
复制代码


输出结果如下:


1   2
复制代码


Promise 是可以链式调用的,由于每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般任务的链式调用一样 return this。


上面的输出结果之所以依次打印出 1 和 2,是因为resolve(1)之后走的是第一个 then 方法,并没有进 catch 里,所以第二个 then 中的 res 得到的实际上是第一个 then 的返回值。并且 return 2 会被包装成resolve(2),被最后的 then 打印输出 2。

Promise.resolve

Promise.resolve = function(value) {    // 1.如果 value 参数是一个 Promise 对象,则原封不动返回该对象    if(value instanceof Promise) return value;    // 2.如果 value 参数是一个具有 then 方法的对象,则将这个对象转为 Promise 对象,并立即执行它的then方法    if(typeof value === "object" && 'then' in value) {        return new Promise((resolve, reject) => {           value.then(resolve, reject);        });    }    // 3.否则返回一个新的 Promise 对象,状态为 fulfilled    return new Promise(resolve => resolve(value));}
复制代码

首屏和白屏时间如何计算

首屏时间的计算,可以由 Native WebView 提供的类似 onload 的方法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是 onPageFinished 事件。


白屏的定义有多种。可以认为“没有任何内容”是白屏,可以认为“网络或服务异常”是白屏,可以认为“数据加载中”是白屏,可以认为“图片加载不出来”是白屏。场景不同,白屏的计算方式就不相同。


方法 1:当页面的元素数小于 x 时,则认为页面白屏。比如“没有任何内容”,可以获取页面的 DOM 节点数,判断 DOM 节点数少于某个阈值 X,则认为白屏。 方法 2:当页面出现业务定义的错误码时,则认为是白屏。比如“网络或服务异常”。 方法 3:当页面出现业务定义的特征值时,则认为是白屏。比如“数据加载中”。

事件是如何实现的?

基于发布订阅模式,就是在浏览器加载的时候会读取事件相关的代码,但是只有实际等到具体的事件触发的时候才会执行。


比如点击按钮,这是个事件(Event),而负责处理事件的代码段通常被称为事件处理程序(Event Handler),也就是「启动对话框的显示」这个动作。


在 Web 端,我们常见的就是 DOM 事件:


  • DOM0 级事件,直接在 html 元素上绑定 on-event,比如 onclick,取消的话,dom.onclick = null,同一个事件只能有一个处理程序,后面的会覆盖前面的。

  • DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件可以有多个事件处理程序,按顺序执行,捕获事件和冒泡事件

  • DOM3 级事件,增加了事件类型,比如 UI 事件,焦点事件,鼠标事件

如何阻止事件冒泡

  • 普通浏览器使用:event.stopPropagation()

  • IE 浏览器使用:event.cancelBubble = true;

常见浏览器所用内核

(1) IE 浏览器内核:Trident 内核,也是俗称的 IE 内核;


(2) Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,现在是 Blink 内核;


(3) Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核;


(4) Safari 浏览器内核:Webkit 内核;


(5) Opera 浏览器内核:最初是自己的 Presto 内核,后来加入谷歌大军,从 Webkit 又到了 Blink 内核;


(6) 360 浏览器、猎豹浏览器内核:IE + Chrome 双内核;


(7) 搜狗、遨游、QQ 浏览器内核:Trident(兼容模式)+ Webkit(高速模式);


(8) 百度浏览器、世界之窗内核:IE 内核;


(9) 2345 浏览器内核:好像以前是 IE 内核,现在也是 IE + Chrome 双内核了;


(10)UC 浏览器内核:这个众口不一,UC 说是他们自己研发的 U3 内核,但好像还是基于 Webkit 和 Trident ,还有说是基于火狐内核。

寄生组合继承

题目描述:实现一个你认为不错的 js 继承方式


实现代码如下:


function Parent(name) {  this.name = name;  this.say = () => {    console.log(111);  };}Parent.prototype.play = () => {  console.log(222);};function Children(name) {  Parent.call(this);  this.name = name;}Children.prototype = Object.create(Parent.prototype);Children.prototype.constructor = Children;// let child = new Children("111");// // console.log(child.name);// // child.say();// // child.play();
复制代码

Set,Map 解构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 Set 本身是一个构造函数,用来生成 Set 数据结构。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
复制代码

事件委托的使用场景

场景:给页面的所有的 a 标签添加 click 事件,代码如下:


document.addEventListener("click", function(e) {    if (e.target.nodeName == "A")        console.log("a");}, false);
复制代码


但是这些 a 标签可能包含一些像 span、img 等元素,如果点击到了这些 a 标签中的元素,就不会触发 click 事件,因为事件绑定上在 a 标签元素上,而触发这些内部的元素时,e.target 指向的是触发 click 事件的元素(span、img 等其他元素)。


这种情况下就可以使用事件委托来处理,将事件绑定在 a 标签的内部元素上,当点击它的时候,就会逐级向上查找,知道找到 a 标签为止,代码如下:


document.addEventListener("click", function(e) {    var node = e.target;    while (node.parentNode.nodeName != "BODY") {        if (node.nodeName == "A") {            console.log("a");            break;        }        node = node.parentNode;    }}, false);
复制代码

代码输出结果

var A = {n: 4399};var B =  function(){this.n = 9999};var C =  function(){var n = 8888};B.prototype = A;C.prototype = A;var b = new B();var c = new C();A.n++console.log(b.n);console.log(c.n);
复制代码


输出结果:9999 4400


解析:


  1. console.log(b.n),在查找 b.n 是首先查找 b 对象自身有没有 n 属性,如果没有会去原型(prototype)上查找,当执行 var b = new B()时,函数内部 this.n=9999(此时 this 指向 b) 返回 b 对象,b 对象有自身的 n 属性,所以返回 9999。

  2. console.log(c.n),同理,当执行 var c = new C()时,c 对象没有自身的 n 属性,向上查找,找到原型 (prototype)上的 n 属性,因为 A.n++(此时对象 A 中的 n 为 4400), 所以返回 4400。


用户头像

还未添加个人签名 2022-09-08 加入

还未添加个人简介

评论

发布
暂无评论
前端二面经典面试题指南_JavaScript_hellocoder2029_InfoQ写作社区