写点什么

2023 我的前端面试小结

作者:loveX001
  • 2023-03-13
    浙江
  • 本文字数:5950 字

    阅读完需:约 20 分钟

TCP/IP 五层协议

TCP/IP五层协议和OSI的七层协议对应关系如下:


  • **应用层 (application layer)**:直接为应用进程提供服务。应用层协议定义的是应用进程间通讯和交互的规则,不同的应用有着不同的应用层协议,如 HTTP 协议(万维网服务)、FTP 协议(文件传输)、SMTP 协议(电子邮件)、DNS(域名查询)等。

  • **传输层 (transport layer)**:有时也译为运输层,它负责为两台主机中的进程提供通信服务。该层主要有以下两种协议:

  • 传输控制协议 (Transmission Control Protocol,TCP):提供面向连接的、可靠的数据传输服务,数据传输的基本单位是报文段(segment);

  • 用户数据报协议 (User Datagram Protocol,UDP):提供无连接的、尽最大努力的数据传输服务,但不保证数据传输的可靠性,数据传输的基本单位是用户数据报。

  • **网络层 (internet layer)**:有时也译为网际层,它负责为两台主机提供通信服务,并通过选择合适的路由将数据传递到目标主机。

  • **数据链路层 (data link layer)**:负责将网络层交下来的 IP 数据报封装成帧,并在链路的两个相邻节点间传送帧,每一帧都包含数据和必要的控制信息(如同步信息、地址信息、差错控制等)。

  • **物理层 (physical Layer)**:确保数据可以在各种物理媒介上进行传输,为数据的传输提供可靠的环境。


从上图中可以看出,TCP/IP模型比OSI模型更加简洁,它把应用层/表示层/会话层全部整合为了应用层


在每一层都工作着不同的设备,比如我们常用的交换机就工作在数据链路层的,一般的路由器是工作在网络层的。 在每一层实现的协议也各不同,即每一层的服务也不同,下图列出了每层主要的传输协议:


同样,TCP/IP五层协议的通信方式也是对等通信:

说一下 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、如果对象中存在循环引用的情况也无法正确实现深拷贝;
复制代码

为什么 0.1 + 0.2 != 0.3,请详述理由

因为 JS 采用 IEEE 754 双精度版本(64 位),并且只要采用 IEEE 754 的语言都有该问题。


我们都知道计算机表示十进制是采用二进制表示的,所以 0.1 在二进制表示为


// (0011) 表示循环0.1 = 2^-4 * 1.10011(0011)
复制代码


那么如何得到这个二进制的呢,我们可以来演算下


小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且得到的第一位为最高位。所以我们得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)


回来继续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.10.2 都是无限循环的二进制了,所以在小数位末尾处需要判断是否进位(就和十进制的四舍五入一样)。


所以 2^-4 * 1.10011...001 进位后就变成了 2^-4 * 1.10011(0011 * 12次)010 。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11次)0100 , 这个值算成十进制就是 0.30000000000000004


下面说一下原生解决办法,如下代码所示


parseFloat((0.1 + 0.2).toFixed(10))
复制代码

原型

JavaScript中的对象都有一个特殊的 prototype 内置属性,其实就是对其他对象的引用几乎所有的对象在创建时 prototype 属性都会被赋予一个非空的值,我们可以把这个属性当作一个备用的仓库当试图引用对象的属性时会出发get操作,第一步时检查对象本身是否有这个属性,如果有就使用它,没有就去原型中查找。一层层向上直到Object.prototype顶层
基于原型扩展描述一下原型链,什么是原型链,原型的继承,ES5和ES6继承与不同点。
复制代码

Promise.all

描述:所有 promise 的状态都变成 fulfilled,就会返回一个状态为 fulfilled 的数组(所有promisevalue)。只要有一个失败,就返回第一个状态为 rejectedpromise 实例的 reason


实现


Promise.all = function(promises) {    return new Promise((resolve, reject) => {        if(Array.isArray(promises)) {            if(promises.length === 0) return resolve(promises);            let result = [];            let count = 0;            promises.forEach((item, index) => {                Promise.resolve(item).then(                    value => {                        count++;                        result[index] = value;                        if(count === promises.length) resolve(result);                    },                     reason => reject(reason)                );            })        }        else return reject(new TypeError("Argument is not iterable"));    });}
复制代码

说一下 data 为什么是一个函数而不是一个对象?

JavaScript中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。而在Vue中,我们更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。所以组件的数据不能写成对象的形式,而是要写成函数的形式。数据以函数返回值的形式定义,这样当我们每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行。


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

实现模板字符串解析

描述:实现函数使得将 template 字符串中的{{}}内的变量替换。


核心:使用字符串替换方法 str.replace(regexp|substr, newSubStr|function),使用正则匹配代换字符串。


实现


function render(template, data) {    // 模板字符串正则 /\{\{(\w+)\}\}/, 加 g 为全局匹配模式, 每次匹配都会调用后面的函数    let computed = template.replace(/\{\{(\w+)\}\}/g, function(match, key) {        // match: 匹配的子串;  key:括号匹配的字符串        return data[key];    });    return computed;}
// 测试let template = "我是{{name}},年龄{{age}},性别{{sex}}";let data = { name: "张三", age: 18}console.log(render(template, data)); // 我是张三,年龄18,性别undefined
复制代码

Set,Map 解构

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

localStorage  sessionStorage  cookies 有什么区别?

localStorage:以键值对的方式存储 储存时间没有限制 永久生效 除非自己删除记录sessionStorage:当页面关闭后被清理与其他相比不能同源窗口共享 是会话级别的存储方式cookies 数据不能超过4k 同时因为每次http请求都会携带cookie 所有cookie只适合保存很小的数据 如会话标识
复制代码

<script src=’xxx’ ’xxx’/>外部 js 文件先加载还是 onload 先执行,为什么?

onload 是所以加载完成之后执行的

Promise.reject

Promise.reject = function(reason) {    return new Promise((resolve, reject) => reject(reason));}
复制代码

代码输出问题

function fun(n, o) {  console.log(o)  return {    fun: function(m){      return fun(m, n);    }  };}var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);var b = fun(0).fun(1).fun(2).fun(3);var c = fun(0).fun(1);  c.fun(2);  c.fun(3);
复制代码


输出结果:


undefined  0  0  0undefined  0  1  2undefined  0  1  1
复制代码


这是一道关于闭包的题目,对于 fun 方法,调用之后返回的是一个对象。我们知道,当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为 undefined 值。所以 console.log(o); 会输出 undefined。而 a 就是是 fun(0)返回的那个对象。也就是说,函数 fun 中参数 n 的值是 0,而返回的那个对象中,需要一个参数 n,而这个对象的作用域中没有 n,它就继续沿着作用域向上一级的作用域中寻找 n,最后在函数 fun 中找到了 n,n 的值是 0。了解了这一点,其他运算就很简单了,以此类推。

说一下 web worker

在 HTML 页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行完成后,页面才变成可相应。web worker 是运行在后台的 js,独立于其他脚本,不会影响页面的性能。 并且通过 postMessage 将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程了。


如何创建 web worker:


  1. 检测浏览器对于 web worker 的支持性

  2. 创建 web worker 文件(js,回传函数等)

  3. 创建 web worker 对象

说一下常见的 git 操作

git branch 查看本地所有分支git status 查看当前状态 git commit 提交 git branch -a 查看所有的分支git branch -r 查看远程所有分支git commit -am "nit" 提交并且加注释 git remote add origin git@192.168.1.119:ndshowgit push origin master 将文件给推到服务器上 git remote show origin 显示远程库origin里的资源 git push origin master:developgit push origin master:hb-dev 将本地库与服务器上的库进行关联 git checkout --track origin/dev 切换到远程dev分支git branch -D master develop 删除本地库developgit checkout -b dev 建立一个新的本地分支devgit merge origin/dev 将分支dev与当前分支进行合并git checkout dev 切换到本地dev分支git remote show 查看远程库git add .git rm 文件名(包括路径) 从git中删除指定文件git clone git://github.com/schacon/grit.git 从服务器上将代码给拉下来git config --list 看所有用户git ls-files 看已经被提交的git rm [file name] 删除一个文件git commit -a 提交当前repos的所有的改变git add [file name] 添加一个文件到git indexgit commit -v 当你用-v参数的时候可以看commit的差异git commit -m "This is the message describing the commit" 添加commit信息git commit -a -a是代表add,把所有的change加到git index里然后再commitgit commit -a -v 一般提交命令git log 看你commit的日志git diff 查看尚未暂存的更新git rm a.a 移除文件(从暂存区和工作区中删除)git rm --cached a.a 移除文件(只从暂存区中删除)git commit -m "remove" 移除文件(从Git中删除)git rm -f a.a 强行移除修改后文件(从暂存区和工作区中删除)git diff --cached 或 $ git diff --staged 查看尚未提交的更新git stash push 将文件给push到一个临时空间中git stash pop 将文件从临时空间pop下来

复制代码

事件传播机制(事件流)

冒泡和捕获

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));}
复制代码

代码输出结果

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。

代码输出问题

window.number = 2;var obj = { number: 3, db1: (function(){   console.log(this);   this.number *= 4;   return function(){     console.log(this);     this.number *= 5;   } })()}var db1 = obj.db1;db1();obj.db1();console.log(obj.number);     // 15console.log(window.number);  // 40
复制代码


这道题目看清起来有点乱,但是实际上是考察 this 指向的:


  1. 执行 db1()时,this 指向全局作用域,所以 window.number * 4 = 8,然后执行匿名函数, 所以 window.number * 5 = 40;

  2. 执行 obj.db1();时,this 指向 obj 对象,执行匿名函数,所以 obj.numer * 5 = 15。

实现 JSONP 跨域

JSONP 核心原理script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;


实现


const jsonp = (url, params, callbackName) => {    const generateUrl = () => {        let dataSrc = "";        for(let key in params) {            if(params.hasOwnProperty(key)) {                dataSrc += `${key}=${params[key]}&`            }        }        dataSrc += `callback=${callbackName}`;        return `${url}?${dataSrc}`;    }    return new Promise((resolve, reject) => {        const scriptEle = document.createElement('script');        scriptEle.src = generateUrl();        document.body.appendChild(scriptEle);        window[callbackName] = data => {            resolve(data);            document.removeChild(scriptEle);        }    });}
复制代码

src 和 href 的区别

src 和 href 都是用来引用外部的资源,它们的区别如下:


  • src: 表示对资源的引用,它指向的内容会嵌入到当前标签所在的位置。src 会将其指向的资源下载并应⽤到⽂档内,如请求 js 脚本。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执⾏完毕,所以⼀般 js 脚本会放在页面底部。

  • href: 表示超文本引用,它指向一些网络资源,建立和当前元素或本文档的链接关系。当浏览器识别到它他指向的⽂件时,就会并⾏下载资源,不会停⽌对当前⽂档的处理。 常用在 a、link 等标签上。


用户头像

loveX001

关注

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

还未添加个人简介

评论

发布
暂无评论
2023我的前端面试小结_JavaScript_loveX001_InfoQ写作社区