C++

C++的三大特性封装、继承和多态

封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。

封装:

  • 封装是在设计类的一个基本原理,是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与对数据进行的操作进行有机的结合,形成“类”,其中数据和函数都是类的成员。

继承

  • 如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。
  • 访问权限

    • public: 父类对象内部、父类对象外部、子类对象内部、子类对象外部都可以访问。
    • protected:父类对象内部、子类对象内部可以访问,父类对象外部、子类对象外部都不可访问。
    • private:父类对象内部可以访问,其他都不可以访问。

多态

多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphism),字面意思多种形状。

  • 静态多态:静态多态也称为静态绑定或早绑定。编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。

    • 函数重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定)。
    • 泛型编程:泛型编程就是指编写独立于特定类型的代码,泛型在C++中的主要实现为模板函数和模板类。
  • 动态多态:c++的动态多态是基于虚函数的。对于相关的对象类型,确定它们之间的一个共同功能集,然后在基类中,把这些共同的功能声明为多个公共的虚函数接口。各个子类重写这些虚函数,以完成具体的功能。客户端的代码(操作函数)通过指向基类的引用或指针来操作这些对象,对虚函数的调用会自动绑定到实际提供的子类对象上去。

引用和指针的区别

指针:对于一个类型T,T 就是指向T的指针类型,也即一个T类型的变量能够保存一个T对象的地址,而类型T是可以加一些限定词的,如const、volatile等等。
引用:引用是一个对象的别名,主要用于函数参数和返回值类型,符号X&表示X类型的引用。

不同点:

  • 指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名,引用不改变指向
  • 引用不可以为空,但指针可以为空。定义一个引用的时候,必须初始化。因此使用指针之前必须做判空操作,而引用就不必。
  • 引用不可以改变指向,指针可以
  • 引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节(32位)
  • 引用和指针的++自增运算符意义不同,指针的++表示的地址的变化,一般是向下4个字节的大小(一个只针的大小),引用的++就是对应元素的++操作。
  • 指针传递参数本质为值传递,传递是一个地址值,在传递过程中,被调函数对形式参数任何操作局部进行,不会影响主调函数的实参变量值

C++11的新特性

虚函数及其实现

派生类继承基类,重写虚函数,虚表结构是怎样的

C++中结构体与类

C与C++结构体的区别

  • C的结构体内不允许有内部成员函数,C++允许并且该函数是虚函数,所以C的结构体没有构造函数、析构函数、this指针
  • C的结构体对内部成员变量的访问权限只能是public,而C++允许private、public、protected
  • C的结构体不能被继承,C++可以从其它结构体或者类继承

C++结构体与类的区别

  1. C++结构体内部成员变量及成员函数默认访问级别是public;而类中的内部成员变量和函数是默认private
  2. 结构体继承默认public,类默认private

计算机网络

TCP和UDP的区别及应用场景

区别

  1. TCP建立一次连接需要3次握手IP数据表,断开要4次握手;另外断开连接时发起方可能进入TIME_WAIT长达数分钟,此状态下连接(端口)无法被释放
  2. UDP不需要建立连接,可以直接发起
  3. TCP利用握手、ACK和重传机制,UDP没有

    1. 校验和(校验数据是否损坏
    2. 定时器(分组丢失则重传
    3. 序列号(用于检测丢失的分组和重复分组
    4. 确认应答ACK(接收方告知发送发正确接收分组以及期望的下一个分组
    5. 否定确认(接收方通知发送发未被正确接收的分组
    6. 窗口和流水线(增加信道的吞吐量
  4. TCP利用seq序列号对包进行排序,UDP没有
  5. 面向报文

    1. 面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。(一个upd的最大报文长度2^16-1-20-8,20是ip报文头,8是udp报文头)
  6. 面向字节流

    1. 面向字节流的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。
  7. TCP有流量控制,UDP没有
  8. TCP头部20比特,UDP8比特

TCP应用场景

效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。

UDP

效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。

TCP连接的建立和断开

cookie和session的区别

及时性要求高的多人在线游戏,要选用什么协议,为什么

操作系统

死锁的条件以及解决办法

必须同时存在以下四个条件才能发生死锁

  1. 互斥条件:即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。
  2. 不可抢占条件:进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。
  3. 占有申请条件:进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。
  4. 循环等待条件:存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。

死锁的预防

破坏互斥条件:

  1. 不共享有些资源-基本没用
  2. 可抢占式,即要求申请失败的进程释放自己占有的资源给别人用,降低系统性能
  3. 直接申请自己所需要的所有资源。--1.不可预知自己需要什么资源 2.资源利用率低,长期占有自己可能不用的资源。
  4. 资源分类、编号,按序申请。 --·1.编号可能是困难的,维护相应的序列是困难的

死锁的避免

死锁的避免指的是不限制进程有关申请资源的命令,而是对进程所发出的每一个申请资源命令加以动态地检查,并根据检查结果决定是否进行资源分配。

线程与进程的区别

进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。

线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。

线程是处理器调度的基本单位,但是进程不是。

地址空间

线程共享本进程的地址空间,而进程之间是独立的地址空间。

资源

线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。

健壮性

多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。

执行过程

每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。

但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。

可并发性

都可以并发执行

切换

进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。

进程间共享内存通信,可以吗?

可以,不能直接共享,要映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问

数据结构

复杂度为$O(nlogn)$的排序算法有哪些?是否稳定?

快速排序

归并排序

堆排序

什么情况下栈会溢出

递归次数过多

介绍哈希表,哈希函数中若碰到冲突如何解决?

哈希表(Hash Table,也叫散列表),是根据关键码值 (Key-Value) 而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。[哈希表]()的实现主要需要解决两个问题,哈希函数和冲突解决。

哈希函数

哈希函数也叫散列函数,它对不同的输出值得到一个固定长度的消息摘要。理想的哈希函数对于不同的输入应该产生不同的结构,同时散列结果应当具有同一性(输出值尽量均匀)和雪崩效应(微小的输入值变化使得输出值发生巨大的变化)

冲突解决

开放定址法

当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:

Hi=(H(key)+di)% m i=1,2,…,n

其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:

  1. 线性探测再散列:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
  2. 二次探测再散列:冲突发生时,在表的左右进行跳跃式探测,比较灵活
  3. 伪随机探测再散列:具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。

再哈希法

同时构造多个不同的哈希函数。这种方法不易产生聚集,但增加了计算时间。

链地址法

将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

建立公共溢出区

将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。

二叉树三个顺序的遍历

如何判断链表是否有环

快慢指针:定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链;如果走得快的指针走到了链的末尾(next指向 NULL)都没有追上第一个指针,那么链就不是环形链。
map:如果不考虑空间复杂度,可以使用一个map记录走过的节点,当遇到第一个在map中存在的节点时,就说明回到了出发点,即链有环,同时也找到了环的入口。

所以寻找环入口的方法就是采用两个指针,一个从表头出发,一个从相遇点出发,一次都只移动一步,当二者相等时便是环入口的位置

如何判断链表是否相交

可以从头遍历两个链表。创建两个栈,第一个栈存储第一个链表的节点,第二个栈存储第二个链表的节点。每遍历到一个节点时,就将该节点入栈。两个链表都入栈结束后。则通过top判断栈顶的节点是否相等即可判断两个单链表是否相交。因为我们知道,若两个链表相交,则从第一个相交节点开始,后面的节点都相交。
若两链表相交,则循环出栈,直到遇到两个出栈的节点不相同,则这个节点的后一个节点就是第一个相交的节点。

  • 遍历链表记录长度。
    同时遍历两个链表到尾部,同时记录两个链表的长度。若两个链表最后的一个节点相同,则两个链表相交。

有两个链表的长度后,我们就可以知道哪个链表长,设较长的链表长度为len1,短的链表长度为len2。
则先让较长的[链表向后移动(len1-len2)个长度。然后开始从当前位置同时遍历两个链表,当遍历到的链表的节点相同时,则这个节点就是第一个相交的节点。

两个栈实现一个队列

二叉树广度优先

二叉树深度优先

一整数数组,一个整数n,判断数组中是否存在两个数之和为n

有m个玩家,取游戏排行榜中排名前k的玩家进行决斗,如何选取

快速排序,只排容量为k的最大堆

一个平面直角坐标系,一些线段平行于x轴,一些线段平行于y轴,求各个线段是否相交

0-9对应的单词,一个数字串转成单词串,再打乱,让还原。

二维数组排序,每行数组有序

求中位数并一直加数据查询

top-k问题

一个直线,分布n个村庄,k个邮局,如何确定邮局位置,使得村庄到邮局的最短距离的和,最短?

poj1160原题

游戏开发相关

  1. 游戏的帧率为60帧,每秒更新六十次,每次都要上传玩家数据,假设上传一次需要100ms,但是一次上传最多16ms,如何解决
  2. 开服创建账号时,需要对玩家在数据库中的ID进行分配(数据库只有一个,但玩家会被分配到多个物理机中进行处理),每个新玩家创建都需要去数据库中查找,如何减少查找数据库的次数(提示,一台物理机向数据库请求一千条ID,下一台请求下一千条ID,当一千条分配完后再向数据库请求)
  3. 玩家战斗结束后根据积分更新排行榜,在排行榜太大更新速度慢的情况下如何实时更新玩家排名(提示:可以区分粒度)
  4. 阴阳师的进度条如何实现?当一个式神的回合结束后进度条如何变化?有没有影响进度条的情况?
  5. 游戏加buff,buff取最大值,每个buff有存在时间和被指定消解,怎么处理人物的buff状态?

设计模式

SOLID
S(单一功能原则Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的(这个类的)服务都应该严密的和该功能平行(功能平行,意味着没有依赖)。
O(开闭原则)软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。
L(里氏替换原则Liskov Substitution principle)派生类必须能完全取代其基类
I(接口隔离原则interface-segregation principle) 客户不依赖于他不使用的方法。
D(控制反转原则Inversion of Control)高层级的模块不应该依赖于低层级的模块,他们应该都依赖于抽象。细节依赖于抽象,而不是抽象依赖于细节。
依赖注入:Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。
采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。

CSS

盒子模型

组成

  • Margin外边距:清除边框外的区域,外边距透明
  • Border边框:围绕内边距和内容外的边框
  • Padding内边距:清除内容周围区域,内边距透明
  • Content内容:盒子内容,显示文本和图像

元素宽度计算

最终元素的总宽度计算公式是这样的:

总元素的宽度=宽度+左填充+右填充+左边框+右边框+左边距+右边距

元素的总高度最终计算公式是这样的:

总元素的高度=高度+顶部填充+底部填充+上边框+下边框+上边距+下边距

兼容性

一旦为页面设置了恰当的 DTD,大多数浏览器都会按照上面的图示来呈现内容。然而 IE 5 和 6 的呈现却是不正确的。根据 W3C 的规范,元素内容占据的空间是由 width 属性设置的,而内容周围的 padding 和 border 值是另外计算的。不幸的是,IE5.X 和 6 在怪异模式中使用自己的非标准模型。这些浏览器的 width 属性不是内容的宽度,而是内容、内边距和边框的宽度的总和。

虽然有方法解决这个问题。但是目前最好的解决方案是回避这个问题。也就是,不要给元素添加具有指定宽度的内边距,而是尝试将内边距或外边距添加到元素的父元素和子元素。

IE8 及更早IE版本不支持设置填充的宽度和边框的宽度属性。

解决IE8及更早版本不兼容问题可以在HTML页面声明 <!DOCTYPE html>即可。

如何使一个盒子水平垂直居中

  1. 使用margin: 0 atuo; 只适用于子盒子有宽的时候
  2. text-align: center / display: inline-block
  3. Position:relative absolute 只适用于子盒子有宽度和高度时

样式优先级

规则1:最近的祖先样式比其他祖先样式优先

<div style="color: red">
    <div style="color: blue">
        <div class="son">
            this is son class, and the color is blue
        </div>
    </div>
</div>

规则2:直接样式比祖先样式优先级高

<div style="color: red">
    <div class="son" style="color: blue">
        It's blue
    </div>
</div>
  • ID 选择器, 如 #id{}
  • 类选择器, 如 .class{}
  • 属性选择器, 如 ahref="[http://zhihu.com"]{}
  • 伪类选择器, 如 :hover{}
  • 伪元素选择器, 如 ::before{}
  • 标签选择器, 如 span{}
  • 通配选择器, 如 *{}

规则3:优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器

// HTML
<div class="content-class" id="content-id" style="color: black">
    this will be black
</div>

// CSS
#content-id {
    color: red;
}
.content-class {
    color: blue;
}
div {
    color: grey;
}

规则4:计算选择符中 ID 选择器的个数(a),计算选择符中类选择器、属性选择器以及伪类选择器的个数之和(b),计算选择符中标签选择器和伪元素选择器的个数之和(c)。按 a、b、c 的顺序依次比较大小,大的则优先级高,相等则比较下一个。若最后两个的选择符中 a、b、c 都相等,则按照“就近原则”来判断。

CSS隐藏元素的方法

  1. display 属性依照词义真正隐藏元素。将 display 属性设为 none 确保元素不可见并且连盒模型也不生成。使用这个属性,被隐藏的元素不占据任何空间。不仅如此,一旦display 设为 none 任何对该元素直接打用户交互操作都不可能生效。此外,读屏软件也不会读到元素的内容。这种方式产生的效果就像元素完全不存在。

任何这个元素的子孙元素也会被同时隐藏。为这个属性添加过渡动画是无效的,它的任何不同状态值之间的切换总是会立即生效。

不过请注意,通过 DOM 依然可以访问到这个元素。因此你可以通过 DOM 来操作它,就像操作其他的元素。

  1. visibility。将它的值设为 hidden 将隐藏我们的元素。如同opacity 属性,被隐藏的元素依然会对我们的网页布局起作用。与 opacity 唯一不同的是它不会响应任何用户交互。此外,元素在读屏软件中也会被隐藏。

这个属性也能够实现动画效果,只要它的初始和结束状态不一样。这确保了 visibility 状态切换之间的过渡动画可以是时间平滑的(事实上可以用这一点来用 hidden 实现元素的延迟显示和隐藏

  1. opacity 属性的意思是设置一个元素的透明度。它不是为改变元素的边界框(bounding box)而设计的。这意味着将 opacity 设为 0 只能从视觉上隐藏元素而元素本身依然占据它自己的位置并对网页的布局起作用。它也将响应用户交互
  2. 假设有一个元素你想要与它交互,但是你又不想让它影响你的网页布局,没有合适的属性可以处理这种情况(opacity 和 visibility 影响布局, display 不影响布局但又无法直接交互——译者注)。在这种情况下,你只能考虑将元素移出可视区域。这个办法既不会影响布局,有能让元素保持可以操作。
  3. 隐藏元素的另一种方法是通过剪裁它们来实现。在以前,这可以通过 clip 属性来实现,但是这个属性被废弃了,换成一个更好的属性叫做 clip-path。Nitish Kumar 最近在 SitePoint 发表了“介绍 clicp-path 属性”这篇文章,通过阅读它可以了解这个属性的更多高级用法。

记住,clip-path 属性还没有在 IE 或者 Edge 下被完全支持。如果要在你的 clip-path 中使用外部的 SVG 文件,浏览器支持度还要更低。

多标签实现通信

cookie+setInterval

首先要想在多个窗口中通信,通信的内容一定不能放在window对象中,因为window是当前窗口的作用域,里面的内容只以属于当前窗口。有一种方式就是放在cookie中,cookie是浏览器的本地存储机制,和窗口无关,存在于硬盘上都可以读取。

<!-- send.html -->
<body>
  <input id="msg" type="text"> 
  <button id="send">发送</button>
  <script>
    send.onclick = function(){
      // 输入框里有值才会往cookie里存
      if(msg.value.trim()!==""){
        // cookie里面存的是‘变量名=值’的形式
        document.cookie = "msg=" + msg.value.trim();
      }
    }
    console.log(document.cookie);
  </script>
</body>

<!-- receive.html -->
<body>
  <h1>收到消息:<span id="recMsg"></span></h1>
  <script>
    var str = document.cookie;
    var i = str.indexOf("=");
    var msg = str.slice(i+1,);
    recMsg.innerHTML = msg;
  </script>
</body>

<!-- send.html -->
<body style="text-align: right">
  <input id="msg1" type="text"> 
  <input id="msg2" type="text"> 
  <button id="send">发送</button>
  <script>
    send.onclick = function(){
      if(msg1.value.trim()!=="" && msg2.value.trim()!==""){
        document.cookie = "msg1=" + msg1.value.trim();
        document.cookie = "msg2=" + msg2.value.trim();
      }
    }
    console.log(document.cookie);
  </script>
</body>

<body>
<!-- receive.html -->
  <h1>收到消息:<span id="recMsg"></span></h1>
  <script>
    // 与原字符串相比,JSON字符串要把等号换成冒号,分号空格换成逗号空格,两边加大括号
    var cookies = '{"'+document.cookie.replace(/=/g,'":"').replace(/;\s+/g,'", "')+'"}';
    cookies = JSON.parse(cookies);
    console.log(cookies);
    recMsg.innerHTML = cookies.msg1;
  </script>
</body>

JSON字符串中必须用双引号。现在还有一个问题,在send页面发送消息receive页面不能实时更新,需要手动刷新。我们可以用setInterval定时器解决,也把上面的字符串的转换封装成函数可以重用。

缺点:

cookie空间有限,浏览器在每一个域名下最多能设置30-50个cookie,容量最多为4k左右。
每次HHTP请求才会把当前域的cookie发送到服务器上,包括只在本地才用到的而服务器不用的,浪费带宽。
setInterval频率设置过大会影响浏览器的性能,过小会影响时效性。

优点:每个浏览器都兼容

localStorage

localStorage比cookie好在它在setItem存东西时会自动触发整个浏览器的storage事件,除了当前页面之外,所有打开的标签窗口都会受影响

<!-- send.html -->
<body style="text-align: right">
  <input id="msg1" type="text"> 
  <input id="msg2" type="text"> 
  <button id="send">发送</button>
  <script>
    send.onclick = function(){
      if(msg1.value.trim()!=="" && msg2.value.trim()!==""){
        localStorage.setItem("msg1",msg1.value.trim());
        localStorage.setItem("msg2",msg2.value.trim());
      }
    }
  </script>
</body>

<!-- receive.html -->
<body>
  <h1>收到消息:<span id="recMsg"></span></h1>
  <script>
    function load(){
      recMsg.innerHTML = localStorage.getItem("msg1");
    }
    load();
    // 任何页面修改了localStorage的值,都会自动触发其他页面中的storage事件
    // 只要storage一变化我们读取localStorage中对应的值显示到页面上
    window.addEventListener("storage",function(){
      load();
    });
  </script>
</body>
  • 缺点:1、localStorage是h5的属性,高版本的浏览器才支持,而且不同浏览器localStorage大小了限制不统一。2、localStorage只能监听非己页面的数据变化。
  • 优点:解决了cookie容量小和时效性的问题。

webSocket

前面两种方式只用到了客户端,没有用到服务端,只用到了浏览器就完成了多个窗口的通信。而webSocket需要用到服务端,send.html发送消息到WebSocketServer,WebSocketServer再实时把消息发给receive.html,其实这就是实时通信的原理.

新建webSocket文件夹,在webSocket目录下打开终端,运行npm init初始化一个简单的node项目(因为需要引入ws包),一直按回车到结束就初始了一个简单的node项目。再安装ws包,运行npm i -save ws,在webSocket目录下新建sever.js、send.html、receive.html文件

// server.js文件
//获得WebSocketServer类型
var WebSocketServer = require('ws').Server;
//创建WebSocketServer对象实例,监听指定端口
var wss = new WebSocketServer({ port: 8080 });
//创建保存所有已连接到服务器的客户端对象的数组
var clients=[]; 
//为服务器添加connection事件监听,当有客户端连接到服务端时,立刻将客户端对象保存进数组中。
wss.on('connection', function(client) {
  console.log("一个客户端连接到服务器");
  // 如果没有这个client对象,说明是第一次连接,就加入到clients中
  if(clients.indexOf(client)===-1){
    clients.push(client);
    console.log("有"+clients.length+"个客户端在线");
    //为每个client对象绑定message事件,当某个客户端发来消息时,自动触发
    client.on('message', function (msg) {
      console.log("收到消息:"+msg);
      //遍历clients数组中每个其他客户端对象,并发送消息给其他客户端
      for(var c of clients){
        if(c!=client){
          c.send(msg);
        }
      }
    })
  }
}); 
<!-- send.html文件 -->
<body style="text-align: right">
  <input id="msg" type="text"> 
  <button id="send">发送</button>
  <script>
    //建立到服务端webSocket连接
    var ws = new WebSocket("ws://localhost:8080");
    send.onclick = function(){
      if(msg.value.trim()!==""){
        // 将消息发到服务器
        ws.send(msg.value.trim());
      }
    }
  </script>
</body>

<!-- receive.html文件 -->
<body>
  <h1>收到消息:<span id="recMsg"></span></h1>
  <script>
    //建立到服务端webSocket连接
    var ws = new WebSocket("ws://localhost:8080");
    //当连接被打开时,注册接收消息的处理函数
    ws.onopen=function(event) {
      //当有消息发过来时,就将消息放到显示元素上
      ws.onmessage=function(event) {
        recMsg.innerHTML=event.data;
      }
    }
  </script>
</body>
  • 缺点:1、它需要服务端的支持才能完成任务。如果socket数据量比较大的话,会严重消耗服务器的资源。
    2、必须要在服务端项目中写服务端监听程序才能支持。
  • 优点:使用简单(客户端简单,服务端苦),功能灵活、强大,如果部署了WebSocket服务器,可以实现很多实时的功能。

SharedWorker

WebWorker的升级版,WebWorker只能在一个窗口内使用,而现在我们需求是多个窗口之间通信,就要用SharedWorker了。
SharedWorker原理和WebWorker几乎是一样的,只不过SharedWorker可以跨多个页面使用。
SharedWorker也是纯客户端的,没有服务端的参与。
SharedWorker在客户端有一个自己维护的对象worker.js,消息存储在worker.js中的data中
SharedWorker不如localStorage的是接收消息不是自动的,也要用定时器实时从worker.js中获取消息。

新建SharedWorker文件夹,并在该目录下创建worker.js、send.html和receive.html

// worker.js文件
//在所有SharedWorker共享的worker.js中,保存一个data变量,用于存储多个worker共享的数据
let data="连接成功";
//必须提供一个名为onconnect的事件处理函数
//每当一个页面中new SharedWorker("worker.js")时,就会为新创建的worker绑定onconnect事件处理函数
onconnect = function(e) {
  //获得当前连接上来的客户端对象
  var client = e.ports[0];
  client.postMessage(data);
  //当当前对象收到消息时
  client.onmessage = function(e) {
    //如果消息内容为空,说明该客户端想获取共享的数据data
    if(e.data===""){
      //就给当前客户端发送data数据
      client.postMessage(data);
    }else{//否则如果消息内容不为空,说明该客户端想要提供新的消息保存在共享的data中,供别人获取
      data=e.data;
    }
  }
}
<!-- send.html文件 -->
<body>
  <input type="text" id="msg"><button id="send">发送</button>
  <script>
    var worker=new SharedWorker("worker.js");
    worker.port.start();
    send.onclick=function(){
      if(msg.value.trim()!==""){
        worker.port.postMessage(msg.value.trim())
      }
    }
  </script>
</body>

<!-- receive.html文件 -->
<body>
  <h1>收到消息:<span id="recMsg"></span></h1>
  <script>
    var worker=new SharedWorker("worker.js");
    //3. 当worker.js中给当前客户端返回了data,会触发当前客户端的message事件。data的值,自动保存进事件对象e的data属性中
    worker.port.addEventListener("message",function(e){
      recMsg.innerHTML=e.data;
    })
    worker.port.start();
    //1. 接收端反复向共享的worker.js对象中发送空消息,意为想获取data的值
    setInterval(function(){
      worker.port.postMessage("");
      //2. 只要发送消息,就触发worker.js中的onmessage,onmessage判断是空消息内容,说明客户端想获得data。于是就用postMessage()方法,将data返回给当前客户端
    },500);
  </script>
</body>

外部导入CSS的两种方式

有链接式和导入式

<!DOCTYPE>
<html>
<head>
  <meta charset="utf-8" />
  <title>外部样式表</title>
  <!--链接式:推荐使用-->
  <link rel="stylesheet" type="text/css" href="css/style.css" /> 
  <!--导入式-->
  <style type="text/css">
    @import url("css/style.css");
  </style>
</head>
<body>
     <ol>
         <li>1111</li>
         <li>2222</li>
     </ol>
</html>

链接式和导入式的区别
link
1、属于XHTML
2、优先加载CSS文件到页面
@import
1、属于CSS2.1
2、先加载HTML结构再加载CSS文件。

BFC(Block Formatting Context) 是什么?

BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,只有Block-level
box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。

细节 什么是BFC? 怎么应用?_月将明的博客-CSDN博客_bfc应用](https://blog.csdn.net/weixin_43758377/article/details/109309257))

浮动原理及如何清除

解释](https://www.cnblogs.com/Ry-yuan/p/6816290.html))

绘制三角形

教程)

JavaScript

深拷贝相关

首先,JavaScript分为基础数据类型和引用数据类型。

基础类型的复制是指,将基础类型的值拷贝一份一模一样的数据,两者互不影响。

引用类型的复制,是传递引用对象的地址,存在多个变量指向同一个地址的情况。

深拷贝主要针对引用类型,也就是Object,String虽然有点特殊,但是它属于不可变引用类型。

浅拷贝方法

Object.assign(target, ...sources):不会拷贝对象的继承属性、不可枚举属性,但可以拷贝Symbol类型的属性

old_arrya.concat(value1[, value2[, ...[, valueN]]]):适用于基本数据类型值的数组

arr.slice([begin[, end]]):适用于基本类型值的数组

手动实现浅拷贝

function shallowClone(target) {
    if (typeof target === 'object' && target !== null) {
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {
            if (target.hasOwnProperty(prop)) {
                cloneTarget[prop] = target[prop];
            }
        }
        return cloneTarget;
    } else {
        return target;
    }
}


// 测试
const shallowCloneObj = shallowClone(obj)

shallowCloneObj === obj  // false,返回的是一个新对象
shallowCloneObj.arr === obj.arr  // true,对于对象类型只拷贝了引用

深拷贝

创建一个新的对象,将一个对象从内存中完整地拷贝出来一份给该新对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。

深拷贝的实现

JSON.stringfy()将一个 JavaScript 对象或值转换为 JSON 字符串,然后用JSON.parse()将JSON 字符串生成一个新的对象。但是会报错、键值对消失、空对象、无法拷贝不可枚举属性、对象的原型等问题

使用Map+递归实现:

判断是否引用类型,循环遍历复制元素;保证对象只克隆一次,防止循环引用

function deepClone(target) {
    // WeakMap作为记录对象Hash表(用于防止循环引用)
    const map = new WeakMap()

    // 判断是否为object类型的辅助函数,减少重复代码
    function isObject(target) {
        return (typeof target === 'object' && target ) || typeof target === 'function'
    }

    function clone(data) {

        // 基础类型直接返回值
        if (!isObject(data)) {
            return data
        }

        // 日期或者正则对象则直接构造一个新的对象返回
        if ([Date, RegExp].includes(data.constructor)) {
            return new data.constructor(data)
        }

        // 处理函数对象
        if (typeof data === 'function') {
            return new Function('return ' + data.toString())()
        }

        // 如果该对象已存在,则直接返回该对象
        const exist = map.get(data)
        if (exist) {
            return exist
        }
        
      //处理Array对象
         if(Array.isArray(data)){
             let ary=[];
             for(let i=0;i<data.length;i++){
                ary.push(clone(data[i]));
            }
            return ary;
          }

        // 处理Map对象
        if (data instanceof Map) {
            const result = new Map()
            map.set(data, result)
            data.forEach((val, key) => {
                // 注意:map中的值为object的话也得深拷贝
                if (isObject(val)) {
                    result.set(key, clone(val))
                } else {
                    result.set(key, val)
                }
            })
            return result
        }

        // 处理Set对象
        if (data instanceof Set) {
            const result = new Set()
            map.set(data, result)
            data.forEach(val => {
                // 注意:set中的值为object的话也得深拷贝
                if (isObject(val)) {
                    result.add(clone(val))
                } else {
                    result.add(val)
                }
            })
            return result
        }

        // 收集键名(考虑了以Symbol作为key以及不可枚举的属性)
        const keys = Reflect.ownKeys(data)
        // 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性以及对应的属性描述
        const allDesc = Object.getOwnPropertyDescriptors(data)
        // 结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链, 这里得到的result是对data的浅拷贝
        const result = Object.create(Object.getPrototypeOf(data), allDesc)

        // 新对象加入到map中,进行记录
        map.set(data, result)

        // Object.create()是浅拷贝,所以要判断并递归执行深拷贝
        keys.forEach(key => {
            const val = data[key]
            if (isObject(val)) {
                // 属性值为 对象类型 或 函数对象 的话也需要进行深拷贝
                result[key] = clone(val)
            } else {
                result[key] = val
            }
        })
        return result
    }

    return clone(target)
}



// 测试
const clonedObj = deepClone(obj)
clonedObj === obj  // false,返回的是一个新对象
clonedObj.arr === obj.arr  // false,说明拷贝的不是引用
clonedObj.func === obj.func  // false,说明function也复制了一份
clonedObj.proto  // proto,可以取到原型的属性

箭头函数

使用=>定义函数

优点

箭头函数可以与变量解构结合使用:const full = ({ first, last }) => first + ' ' + last;

箭头函数使表达式更简洁:const isEven = n => n % 2 === 0;

简化回调函数:[1,2,3].map(x => x * x);

使用注意

  1. 没有自己的this对象,内部的this就是定义时上层作用域中的this。也就是说,箭头函数内部的this指向是固定的,相比之下,普通函数的this指向是可变的
  2. 不可以作为构造函数
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数

数组

数组中有哪些方法会修改原数组

  1. push
  2. pop
  3. shift
  4. unshift
  5. reverse
  6. Slice

Promise函数

typeof的作用

typeof是一个一元运算符,返回的是字符串,说明运算数的数据类型,只能准确判断原始数据类型和函数,面对引用数据类型时,返回的是object

instanceof返回布尔值,检测某个实例是否属于某构造函数,可以准确判断引用数据类型,但是不能正确判断原始数据类型

使用 typeof 来判断基本数据类型是 ok 的,不过需要注意当用 typeof 来判断 null 类型时的问题,如果想要判断一个对象的具体类型可以考虑用 instanceof,但是 instanceof 也可能判断不准确,比如一个数组,他可以被 instanceof 判断为 Object。所以我们要想比较准确的判断对象实例的类型时,可以采取 Object.prototype.toString.call 方法

null和undefined的区别

null,类似于一个空对象指针,typeof的返回为object

undefined是一个处于原始状态变量的状态值

Vue

响应式原理

数据与模板的信息交流

Keep-alive的作用

虚拟DOM

Last modification:May 2nd, 2022 at 11:39 pm
如果觉得我的文章对你有用,请随意赞赏
END
本文作者:
文章标题:收集的面试经验
本文地址:http://feiqiuz.com/index.php/archives/17/
版权说明:若无注明,本文皆MixZou的小窝原创,转载请保留文章出处。