用户态和内核态是CPU的两种状态,分别代表着两种权限。
当CPU处于用户态时是没有权限执行特权指令的,这是出于安全的考虑。
程序是静态的,进程是动态的,程序是存储在某种介质上的二进制代码,进程对应了程序的执行过程
对比维度 | 多进程 | 多线程 | 总结 |
---|---|---|---|
数据共享、同步 | 数据共享复杂,需要用IPC;数据是分开的,同步简单 | 因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂 | 各有优势 |
内存、CPU | 占用内存多,切换复杂,CPU利用率低 | 占用内存少,切换简单,CPU利用率高 | 线程占优 |
创建销毁、切换 | 创建销毁、切换复杂,速度慢 | 创建销毁、切换简单,速度很快 | 线程占优 |
编程、调试 | 编程简单,调试简单 | 编程复杂,调试复杂 | 进程占优 |
可靠性 | 进程间不会互相影响 | 一个线程挂掉将导致整个进程挂掉 | 进程占优 |
分布式 | 适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单 | 适应于多核分布式 | 进程占优 |
有,得不到cpu就会阻塞,得不到系统资源会导致进程阻塞
进程切换时要切页表,而且往往伴随着页调度
统一进程下的线程共享进程里的资源,线程只需要保存线程的上下文(相关寄存器状态和栈的信息)
堆、动态库、全局变量、打开的文件
悲观锁: 每次在拿数据的时候都会上锁(系统互斥量实现)
乐观锁: 不用加锁,出现冲突再想办法去解决
乐观解决冲突的办法:版本号控制。当数据被修改时,版本号会+1,提交更新时如果自己读到的版本号与数据库中的不同,就要重新提交更新。
要避免产生死锁
剥夺死锁的四个必要条件:互斥条件、不剥夺条件、请求和保持条件、循环等待条件
银行家算法
检测和解除
程序由源代码变成可执行文件,一般可以分解为四个步骤,分别是:
库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。
每个进程创建加载的时候,会被分配一个连续的虚拟地址空间,在进程眼里这片空间是真实有效的,但是它实际使用的内存空间往往比虚拟内存小,并且是不连续的,进程暂时用不到的数据会被放进磁盘里。
好处:提高了内存利用率
虚拟内存通过页表映射到物理内存,页表是内存中的一种数据结构,用来记录虚拟内存与物理内存的映射关系。
内存块号:某一页号对应的内存块号
状态位:标记页面是否在内存中
访问字段:记录最近被访问过几次,用来给页面置换算法提供参考
修改位:标记页面是否被修改过,如果被修改过则再换出的时候需要更新外存
外存地址:页面在外存中的地址
会产生缺页中断,由操作系统将页面从外存调入内存,如果内存不足,则通过页面置换算法挑选一个近期不用的页面调出内存
程序由源代码变成可执行文件,一般可以分解为四个步骤,分别是:
应用层
传输层
网络层
数据链路层
A–>B同步包(SYN=1,seq=x)
A<–B确认+同步包(SYN=1,ACK=1,seq=y,ack=x+1)
A–>B确认包(ACK=1,seq=x+1,ack=y+1)
第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。
A–>B终止(FIN)包
A<–B确认(ACK)包(此时A–>B的通道就释放了)
A<–B终止(FIN)包
A–>B确认(ACK)包(此时A<–B的通道就释放了)
UDP
(1)UDP不需要建立连接。
(2)UDP不保证可靠交付,也不使用拥塞控制。
(3)UDP是面向报文的,适合多媒体通信的要求。
(4)UDP支持一对一,一对多,多对一,多对多交互通信。
(5)UDP首部开销小,只有8个字节。
TCP
(1)TCP需要建立连接。(三次握手)
(2)TCP提供可靠交付,使用拥塞控制
(3)TCP面向字节流:把应用程序交付下来的数据看成一连串无结构的字节流。
(4)TCP只支持只一对一。
(5)TCP提供全双工通信。
渲染页面
基于Session实现的会话保持
在会话开始时(客户端第一次像服务器发送http请求),服务器将会话状态保存起来(本机内存或数据库中),然后分配一个会话标识(SessionId)给客户端,这个会话标识一般保存在客户端Cookie中,以后每次浏览器发送http请求都会带上Cookie中的SessionId到服务器,服务器拿到会话标识就可以把之前存储在服务器端的状态信息与会话联系起来,实现会话保持(如果遇到浏览器禁用Cookie的情况,则可以通过url重写的方式将会话标识放在url的参数里,也可实现会话保持)
基于Cookie实现的会话保持
基于Cookie实现会话保持与上述基于Session实现会话保持的最主要区别是前者完全将会话状态信息存储在浏览器Cookie中,这样一来每次浏览器发送HTTP请求的时候都会带上状态信息,因此也就可以实现状态保持。
http传输的是明文,https传输密文
http端口号80,https端口号443
https协议需要到CA证书
https = http + SSL
200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
302 - 临时重定向
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误
分类 | 分类描述 |
---|---|
1** | 信息,服务器收到请求 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
TCP支持任意数量http
http1.1之前,每次请求都需要建立连接
http1.1之后,在发送http的请求头中设置Connection: keep-alive,连接会长时间保持。
但是在同一个tcp连接中,新的请求需要等上次请求收到响应后,才能发送。
http建立在tcp的基础上
当你打电话时,TCP协议是电话线的电信号传输协议,而HTTP协议是普通话,如果TCP连接不通,意味着你电话没打通,如果HTTP协议不遵守,就是你打通了不说话,或者说方言。
DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转换的服务。
域名具有层次结构,从上到下依次为:根域名、顶级域名、二级域名。
DNS解析流程
解析域名的时候,DNS服务器返回了一个错误的地址
TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。
ARQ协议:每发完一个分组就停止发送,等待对方确认。超过一段时间仍然没有收到确认就重传。信道利用率低
连续ARQ协议:发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。不能向发送方反映出接收方已经正确收到的所有分组的信息
TCP 连接的每一方都有固定大小的缓冲空间,发送方和接收方各有一个窗口,窗口的大小限制了数据的传输速率。接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小,从而达到流量控制的目的。
如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离。接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。接收窗口只会对窗口内最后一个按序到达的字节进行确认
发送方停止发送数据
滑动窗口是接收方建议的窗口大小,只考虑了接受方的承载力
拥塞窗口是根据网络状况自己调整的窗口大小,考虑的是网络的承载力
发送方会维护一个拥塞窗口,发生拥塞就变小,否则变大
TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
慢开始与拥塞避免:
传输开始的时候,拥塞窗口会被设为1,并且会设置一个慢开始门限
每次收到确认以后,拥塞窗口就会翻倍
当拥塞窗口大于等于慢开始门限的时候,每次确认只会加1
如果出现超时的情况,慢开始门限设为拥塞窗口的一半,拥塞窗口变为1
快重传与快恢复:
在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。
在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。
在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,慢开始门限设为拥塞窗口的一半,拥塞窗口减半
https://www.cyc2018.xyz/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%20-%20%E4%BC%A0%E8%BE%93%E5%B1%82.html#tcp-%E9%A6%96%E9%83%A8%E6%A0%BC%E5%BC%8F
http中Header 里添加两个参数来实现的。这两个参数分别是客户端请求时发送的 Range 和服务器返回信息时返回的 Content-Range
在咱们国家到国外的防火墙上,设置一个类似白名单的东西,不在白名单的IP地址一律不可以把包发送到国外
优化1 采用三数取中法选择基准
取收尾中间元素,取中值作为基准,避免选到最大值最小值。
优化2 序列长度小于一定值后用插入排序,如长度=10
优化3 在一次分割结束后,把与基准值相等的元素聚到一起,不参与下次分割。
https://zhuanlan.zhihu.com/p/25149493
每一个进程在用户态对应一个调用栈结构(call stack)
程序中每一个未完成运行的函数对应一个栈帧(stack frame),栈帧中保存函数局部变量、传递给被调函数的参数等信息
栈底对应高地址,栈顶对应低地址,栈由内存高地址向低地址生长
然后cpu中有一些一些特殊的寄存器
在调用一个函数时
封装: 把客观的事物封装成抽象的类。类可以设置自身属性和方法的可见性,对外隐藏细节
继承: 可以在现有类的基础上扩展它的功能
多态: 允许将子类类型的指针赋值给父类类型的指针。
https://blog.csdn.net/djl806943371/article/details/88677634
继承是静态绑定,多态是动态绑定。
单例模式
懒汉式
volatile的作用
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){};
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){};
public static Singleton getInstance() {
return instance;
}
}
工厂模式
将对象的创建逻辑封装起来,由一个工厂对象代替创建。
public interface Shape {
void draw();
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Rectangle");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Square");
}
}
public class ShapeFactory {
public shape getShape(String shapeName) {
if (shapeName.equalsIgnoreCase("Rectangle")) {
return new Rectangle();
}
else if (shapeName.equalsIgnoreCase("Square")) {
return new Square();
}
else {
return null;
}
}
}
const修饰变量,使变量的值不可变,变量必须初始化
const修饰指针,左定值,右定向
int a = 8;
const int * const p = &a;
const修饰参数,既可使指针不篡改,也可const &不调用复制构造
const修饰返回值,不能被赋值和修改
const修饰类成员函数,防止成员函数修改被调用对象的值
继承 通过访问限定符:public,protected,private来实现
多态 通过虚函数的重写以及父类的指针和引用指向子类的对象
32位指针占4字节,64位指针占8字节
sizeof(int *)
或者
int main(void)
{
void *a, *b;
cout << (char *)(&a) - (char *)(&b) << endl;
return 0;
}
https://blog.csdn.net/weixin_50168448/article/details/113613371
深拷贝
基类中不能实现,相当于接口。
虚函数主要是用来实现多态的。子类重写了父类的虚函数,用父类指针接受子类对象,调用该函数,用的是子类的函数。
当一个类有虚函数时,成员中就会有一个虚指针,虚指针指向虚表,虚表中保存着虚函数的地址。继承多个类会有多个虚指针,也会有多个虚表。当子类重写父类虚函数时,重写函数的地址会覆盖虚表中原来父类虚函数的地址。
当调用对象的虚函数时,会通过查表来运行。
不能,虚函数是运行时才能确定调用哪个,而inline是在编译期对函数进行展开
main之前的工作
#define 只进行简单的字符替换,无类型检测
typedef:定义类型别名 用于处理复杂类型
inline: 内联函数对编译器提出建议,是否进行宏替换,编译器有权拒绝
许多计算机系统对基本数据类型合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2,4或8)的倍数。这种对齐限制简化了形成处理器和存储器系统之间的接口的硬件设计
智能指针是由类来实现的,当超出了类的实例对象的作用域时,对象的析构函数会自动调用。通过类的析构函数来自动销毁指针管理的内存。
c++11 的智能指针包括unique_ptr,shared_ptr, weak_ptr, 这三种,其中auto_ptr 已被遗弃。
unique_ptr保证同一时间内只有一个智能指针可以指向该对象
shared_ptr使用引用计数的智能指针。引用计数的智能指针可以跟踪引用同一个真实指针对象的智能指针实例的数目。这意味着,可以有多个std::shared_ptr实例可以指向同一块动态分配的内存,当最后一个引用对象离开其作用域时,才会释放这块内存。
weak_ptr在指向一个对象的时候不会增加其引用计数
shared_ptr循环引用问题
class Person {
public:
Person(const string& name): m_name{name} {
cout << m_name << " created" << endl;
}
virtual ~Person(){
cout << m_name << " destoryed" << endl;
}
friend bool partnerUp(std::shared_ptr<Person>& p1, std::shared_ptr<Person>& p2){
if (!p1 || !p2){
return false;
}
p1->m_partner = p2;
p2->m_partner = p1;
cout << p1->m_name << " is now partenered with " << p2->m_name << endl;
return true;
}
private:
string m_name;
std::shared_ptr<Person> m_partner;
};
int main() {
auto p1 = std::make_shared<Person>("Lucy");
auto p2 = std::make_shared<Person>("Ricky");
partnerUp(p1, p2); // 互相设为伙伴
return 0;
}
以上的程序输出为:
Lucy created
Ricky created
Lucy is now partnered with Ricky
p1和p2都没有释放,发生了内存泄漏。
解决方案:将类内指针shared_ptr换为weak_ptr,程序输出将是
Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky destroyed
Lucy destroyed
工作区、暂存区、库
MVC
Model: 用于封装数据以及对数据的处理方法
View: 渲染页面
Controller: 连接M和V,用于控制应用程序的流程,及页面的业务逻辑
MVP
MVP(Model-View-Presenter)是MVC的改良模式
设四位数ABCD.
ABCD × 4 = DCBA
显然,由积的个位看出,A是偶数,至少为2,又由积的千位看出,D≤9,推得A = 2
研究乘数 与 乘积的个位
D × 4 = …A = …2
推得D = 8
2BC8 × 4 = 8CB2的性质 ,列方程得:
(2008 + 100B + 10C)×4 = 8002 + 100C + 10B
化简整理得:
1+13B = 2C
结合B、C的范围和奇偶性可知,B 必 = 1,C = 7
综上解得ABCD = 2178
5位数21978
6位数219978
7位数2199978
设甲先抛。设甲胜率为x。当且仅当甲第一次抛出反面时乙才有获胜的机会,而此时乙又变成先抛,所以乙的胜率应该是0.5x,因x + 0.5x = 1, x = 2 3 x = {2\over3} x=32
详解
(1)依题意,灯泡按过的次数等于其编号的所有因数的个数;
(2)开始状态是熄的,后来是亮的,说明按过的次数是奇数;
(3)所有因数的个数为奇数的自然数只有完全平方数。
综上,编号是完全平方数的灯泡最后是亮的。
div {
height: 0px;
width: 0px;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 50px solid blue;
background-color: white;
}
https://www.jianshu.com/p/7bbc4860a45c
问题
var func1 = x => x
var func2 = x => { x }
var func3 = x => ({ x })
console.log(func1(1))
console.log(func2(1))
console.log(func3(1))
答案
1
undefined
{ x: 1 }
题目
Function.prototype.a = 'Function';
Object.prototype.a = 'Object';
function Person() {};
var child = new Person();
console.log(Person.a);
console.log(child.a);
console.log(child.__proto__.__proto__.constructor.constructor.constructor);
答案
Function
Object
[Function: Function]
if ([] == false) { console.log(1); };
if (![] == false) { console.log(2); };
if ({} == false) { console.log(3); };
if ([]) { console.log(4); };
if ([1] == [1]) { console.log(5); };
if (null == 0) { console.log(6); };
答案
1
2
4
解析
当判断中,发现有一边不是原始值类型,就会先调用valueOf方法进行转换
发现valueOf转化完后,依然不时原始值类型,那继续用toString方法转换,如果类型不相等就用Number转换
题目
const async1 = async() => {
console.log('第一个async函数开始');
await async2();
console.log('第一个async函数结束');
}
const async2 = async() => {
console.log('第二个async函数执行');
}
console.log('开始执行');
setTimeout(() => {
console.log('setTimeout执行');
}, 0)
new Promise(resolve => {
console.log('promise执行');
for (var i = 0; i < 100; i++) {
i == 99 && resolve();
}
}).then(() => {
console.log('执行then函数')
});
async1();
console.log('结束');
答案
开始执行
promise执行
第一个async函数开始
第二个async函数执行
结束
执行then函数
第一个async函数结束
setTimeout执行
题目
class Animal {
sayName = () => {
throw new Error('你应该自己实现这个方法');
}
}
class Monkey extends Animal {
sayName() {
console.log('I love coding');
}
}
const monkey = new Monkey();
monkey.sayName();
答案
Error: 你应该自己实现这个方法
解析
class 对于 = 号声明的方法、变量,都会将其作为实例的属性,而对于非 = 号声明的属性,则是放在原型链上。
子类使用普通函数的方式声明 sayName 的话,子类声明的 sayName 会被放在构造函数的 prototype 上。可是由于基类的 sayName 是使用箭头函数的方式,因此每一个实例都会直接有一个 sayName 变量。根据 javascript 变量的访问规则,首先会在变量本身上找,找不到后才会在原型链上找。因此,在查找 sayName 的时候,就直接找到基类声明的 sayName 函数了,就不会再在原型链上找,因此就出现了问题。
解决方法,子类使用等号赋值
题目
var length = 10;
function fn() {
console.log(this);
return this.length + 1;
}
var obj = {
length: 5,
test1: function() {
return fn();
}
};
obj.test2 = fn;
//下面代码输出是什么
console.log(obj.test1())
console.log(fn() === obj.test2())
答案
Window
11
Window
{ length: 5, test1: ƒ, test2: ƒ }
false
题目
function repeat(func, times, wait) {
//实现这个函数
}
// 使下面调用代码能正常工作
const repeatFunc = repeat(console.log, 4, 1000);
repeatFunc("hellworld"); //会输出4次 helloworld, 每次间隔3秒
答案
function repeat(func, times, wait) {
return (s) => {
let cnt = 0
let id = setInterval(() => {
if (++cnt === times) {
clearInterval(id)
}
func(s)
}, wait)
}
}
题目
用reduce实现map
答案
var arr = [1, 2, 3, 4, 5, 6, 7, 8]
function plusTen(num) {
return num * 10
}
console.log(arr.map(plusTen))
Array.prototype.myMap = function(fn, thisArg) {
return this.reduce((total, value, index, arr) => {
return total.concat([fn.call(thisArg, value, index, arr)])
}, [])
}
console.log(arr.myMap(plusTen))
题目
// 实现一个链式调用的串行Queue类
new Queue()
.task(1000, () => {
console.log(1)
})
.task(2000, () => {
console.log(2)
})
.task(1000, () => {
console.log(3)
})
.start() //调用start后才可以开始
答案
解法一
class Queue {
constructor() {
this.taskList = []
}
task(time, fn) {
this.taskList.push(() => new Promise((res) => {
setTimeout(() => {
fn()
res()
}, time)
}))
return this
}
async start() {
for (let v of this.taskList) {
await v()
}
}
}
解法二
class Queue {
constructor() {
this.taskList = []
}
task(time, fn) {
this.taskList.push(() => new Promise((res) => {
setTimeout(() => {
fn()
res()
}, time)
}))
return this
}
start() {
let res = Promise.resolve()
this.taskList.forEach((v) => {
res = res.then(v)
})
}
}
解法三
class Queue {
constructor() {
this.taskList = []
}
task(time, fn) {
this.taskList.push(() => new Promise((res) => {
setTimeout(() => {
fn()
res()
}, time)
}))
return this
}
start() {
this.taskList.reduce((pre, cur) => {
return pre.then(cur)
}, Promise.resolve())
}
}
题目
//实现一个TaskQueue类,实现以下功能
new TaskQueue().console("hello").settimeout(3000).console("world").settimeout(3000).console()
答案
class TaskQueue {
constructor() {
this.taskList = []
this.startTimer = null
}
start() {
clearTimeout(this.startTimer)
this.startTimer = setTimeout(async() => {
for (let v of this.taskList) {
await v()
}
}, 0)
}
console(v) {
this.taskList.push(() => {
console.log(v)
})
this.start()
return this
}
settimeout(delay) {
this.taskList.push(() => {
return new Promise(resolve => {
setTimeout(resolve, delay)
})
})
this.start()
return this
}
}
题目
//实现一个chain函数,eat函数打印eat,work打印work,sleep函数休息
chain().eat().sleep(5).work().eat().work().sleep(10);
答案
function chain() {
this.taskList = [];
this.eat = function() {
this.taskList.push(() => {
console.log("eat");
});
return this;
};
this.work = function() {
this.taskList.push(() => {
console.log("work");
});
return this;
};
this.sleep = function(time) {
this.taskList.push(
() =>
new Promise(res => {
setTimeout(res, time);
})
);
return this;
};
//自执行函数:setTimeout里加上执行函数(还是鹅厂那三种形式)
//这里我只写了第一种
setTimeout(async() => { //注意:这里必须为箭头函数,不然下面的this会变就取不到正确的taskList
for (let v of this.taskList) {
await v();
}
}, 0);
return this; //实现函数不是类,类会自动return,函数需要手动
}
chain().eat().sleep(2000).work().eat().work().sleep(1000);
题目
class Scheduler{
constructor(){
}
add(promiseCreator){
//code
}
//...
}
const timeout = time =>
new Promise(resolve => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time).then(() => console.log(order)));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.start();
// output:2 3 1 4
答案
class Scheduler {
constructor() {
this.taskList = []
this.maxNumber = 2
this.runNumber = 0
}
add(promiseCreator) {
this.taskList.push(promiseCreator)
}
start() {
while (this.taskList.length > 0 && this.runNumber < this.maxNumber) {
++this.runNumber
this.taskList.shift()().then(() => {
--this.runNumber
this.start()
})
}
}
}
const timeout = time =>
new Promise(resolve => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time).then(() => console.log(order)));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.start();
// output:2 3 1 4
题目
只修改start函数,使程序输出012345
function start(id){
//execute(id)
}
for (let i = 0; i < 5; i++) {
start(i);
}
function sleep() {
const duration = Math.floor(Math.random() * 500);
return new Promise(resolve => setTimeout(resolve, duration));
}
function execute(id) {
return sleep().then(() => {
console.log("id", id);
});
}
答案
function start(id) {
this.p = this.p ?
this.p.then(() => execute(id)) :
execute(id);
}
题目
实现数组扁平化
function flatten(arr) {
//code here
}
var arr = [1, [2, 3, [4]], 5, 6, [
[7], 8
], 9]
console.log(flatten(arr))
答案
function flatten(arr) {
let res = []
for (let v of arr) {
if (Array.isArray(v)) {
res.push(...flatten(v))
} else {
res.push(v)
}
}
return res
}
reduce实现
function flatten(arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, [])
}
栈实现
function flatten(arr) {
let res = []
let stack = [].concat(arr)
while (stack.length > 0) {
let v = stack.pop()
if (Array.isArray(v)) {
stack.push(...v)
} else {
res.unshift(v)
}
}
return res
}
原地算法
function flatten(arr) {
let len = 0
while (arr.length != len) {
let v = arr.pop()
if (Array.isArray(v)) {
arr.push(...v)
} else {
arr.unshift(v)
len++
}
}
}
题目
对象深拷贝
答案
方法一:
var obj1 = {a:13, b:'good', c: function(){console.log(hello)}}
var obj2 = {...obj1}
console.log(obj1 === obj2)
方法二:
var obj1 = {a:13, b:'good', c: function(){console.log(hello)}}
var obj2 = JSON.parse(JSON.stringify(obj1))
console.log(obj1 === obj2)
方法三:
function deepCopy(obj) {
var res
if (Array.isArray(obj)) {
res = []
obj.map(item => {
res.push(deepCopy(item))
})
} else if (typeof obj === 'Object') {
res = {}
Object.keys(obj).map(item => {
res[item] = deepCopy(obj[item])
})
} else {
res = obj
}
return res
}
var a = ['1', 2, { good: 'we', dd: () => {} },
[1, 2, 3]
]
console.log(a);
var b = deepCopy(a)
var c = a
console.log(b);
console.log(b === a);
console.log(c === a);
题目
实现以下功能
[['a', 'b'], ['n', 'm'], ['0', '1']] => ["an0", "an1", "am0", "am1", "bn0", "bn1", "bm0", "bm1"]
答案
const arr = [
['a', 'b'],
['n', 'm'],
['0', '1']
]
console.log(arr.reduce((pre, cur) => {
let res = []
for (let p of pre) {
for (let c of cur) {
res.push(p + c)
}
}
return res
}, ['']))
题目
手写Array.prototype.sort()函数
答案
Array.prototype.mysort = function(cmp) {
if (cmp === undefined) {
cmp = (a, b) => {
return a <= b
}
}
const _sort = (l, r) => {
if (l >= r) {
return
}
let i = l
let j = r
while (i < j) {
while (i < j && cmp(this[l], this[j])) {
--j
}
while (i < j && cmp(this[i], this[l])) {
++i
}
let temp = this[i]
this[i] = this[j]
this[j] = temp
}
let temp = this[l]
this[l] = this[i]
this[i] = temp
_sort(l, i - 1)
_sort(i + 1, r)
}
_sort(0, this.length - 1)
return this
}
let a = [3, 4, 1, 8, 3, 4, 6]
console.log(a.mysort((a, b) => {
return a >= b
}))
题目
对版本号进行排序
const arr=[
'1.1',
'2.3.3',
'4.3.5',
'0.3.1',
'0.302.1',
'4.20.0',
'4.3.5.1',
'1.2.3.4.5'
];
答案
const arr = [
'1.1',
'2.3.3',
'4.3.5',
'0.3.1',
'0.302.1',
'4.20.0',
'4.3.5.1',
'1.2.3.4.5'
];
arr.sort((a, b) => {
a = a.split('.')
b = b.split('.')
let n = Math.min(a.length, b.length)
for (let i = 0; i < n; ++i) {
if (a[i] != b[i]) {
return Number(a[i]) - Number(b[i])
}
}
return a.length - b.length
})
console.log(arr);
题目
实现数组的负值索引
答案
function myArray(arr) {
return new Proxy(arr, {
get(target, key) {
return Reflect.get(target, Number(key) < 0 ? String(target.length + Number(key)) : key)
},
set(target, key, value, receiver) {
return Reflect.set(target, Number(key) < 0 ? String(target.length + Number(key)) : key, value, receiver)
}
})
}
let arr = myArray([1, 2, 3, 4])
console.log(arr[-1]);
console.log(arr[-2]);
console.log(arr[-3]);
console.log(arr[-4]);
console.log(arr[1]);
arr[-4] = 9
console.log(arr[0]);
题目
将一个典型回调风格的功能函数变为promise风格的
答案
https://blog.csdn.net/watercatmiao/article/details/84261015
const isCycleObject = (obj, parent) => {
const parentArr = parent || [obj];
for (let i in obj) {
if (typeof obj[i] === 'object') {
for (let j in parentArr) {
if (parentArr[j] === obj[i]) {
return true
}
}
if (isCycleObject(obj[i], [...parentArr, obj[i]])) return true;
}
}
return false;
}
var a = {
b: null,
c: null
}
a.b = a
console.log(isCycleObject(a))
1.使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象;
2.使用let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升;
3.let不允许在相同作用域内,重复声明同一个变量。
函数及变量的声明都将被提升到函数的最顶部。
JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。
https://blog.csdn.net/u010176097/article/details/80348447
这些方法的作用都是使一个对象能够以自己的的名义去调用它不拥有的方法。
call和apply 的功能完全相同,区别在于传参的方式不一样
call中的参数是以参数列表的形式传入的,apply中是以数组的形式传入的
bind 和call/apply有一个很重要的区别,一个函数被 call/apply 的时候,会直接调用,但是bind 会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
number string null undefined boolean object symbol
因为浮点数在计算机中是二进制存储的,十进制小数转为二进制是不断乘二取整数,像0.1这种数乘2是乘不尽的,在保存的时候就会有精度损失。
解决办法就是都乘1000变成整数,再除1000
一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。
直观的说就是形成一个不销毁的栈环境
function outerFun() {
var cnt = 0
return () => {
console.log(++cnt)
}
}
const addOne = outerFun()
addOne()
addOne()
addOne()
函数科里化就是把多参数的函数分解为
function sum(a, b, c) {
console.log(a + b + c)
}
function curry(fn, currArgs) {
return function() {
let args = [].slice.call(arguments);
// 首次调用时,若未提供最后一个参数currArgs,则不用进行args的拼接
if (currArgs !== undefined) {
args = args.concat(currArgs);
}
// 递归调用
if (args.length < fn.length) {
return curry(fn, args);
}
// 递归出口
return fn.apply(null, args);
}
}
const fn = curry(sum)
fn(1, 2, 3)
fn(1, 2)(3)
fn(1)(2, 3)
fn(1)(2)(3)
防抖(debounce)和节流(throttle)都是用来控制某个函数在一定时间内执行多少次的技巧,两者相似而又不同。 背后的基本思想是某些代码不可以在没有间断的情况下连续重复执行,避免产生高额的开销。
函数防抖: 如果一个事件被频繁触发多次,并且触发的时间间隔过短,则防抖函数可以使得对应的事件处理函数只执行最后触发的一次。 函数防抖可以把多个顺序的调用合并成一次。
函数节流: 如果一个事件被频繁触发多次,节流函数可以按照固定频率去执行对应的事件处理方法。 函数节流保证一个事件一定时间内只执行一次。相当于函数有冷却时间。
类型 | 场景 |
---|---|
函数防抖 | 1. 手机号、邮箱输入检测 2. 搜索框搜索输入(只需最后一次输入完后,再放松Ajax请求) 3. 窗口大小resize(只需窗口调整完成后,计算窗口大小,防止重复渲染) 4.滚动事件scroll(只需执行触发的最后一次滚动事件的处理程序) 5. 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,(停止输入后)验证一次就好 |
函数节流 | 1. DOM元素的拖拽功能实现(mousemove) 2. 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹) 3. 计算鼠标移动的距离(mousemove) 4. 搜索联想(keyup) 5. 滚动事件scroll,(只要页面滚动就会间隔一段时间判断一次) |
具体实现
//函数防抖
function debounce(fn, delay, scope) {
let timer = null
return function() {
let caller = scope || this
clearTimeout(timer)
timer = setTimeout(() => {
fn.call(caller, ...arguments)
}, delay)
}
}
//函数节流
function throttle(fn, delay, scope) {
let pre = 0
return function() {
let caller = scope || this
let now = Date.now()
if (now - pre > delay) {
pre = now
fn.call(caller, ...arguments)
}
}
}
试验具体效果
<html lang="zh-CN">
<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>Documenttitle>
head>
<body>
<div class="wrap">
<div class="header">滚动事件:普通div>
<div class="container">
<div class="content">div>
div>
div>
<div class="wrap">
<div class="header">滚动事件:<strong>加了函数防抖strong>div>
<div class="container">
<div class="content">div>
div>
div>
<div class="wrap">
<div class="header">滚动事件:<strong>加了函数节流strong>div>
<div class="container">
<div class="content">div>
div>
div>
body>
html>
<script>
function debounce(fn, delay, scope) {
let timer = null
return function() {
let caller = scope || this
clearTimeout(timer)
timer = setTimeout(() => {
fn.call(caller, ...arguments)
}, delay)
}
}
function throttle(fn, delay, scope) {
let pre = 0
return function() {
let caller = scope || this
let now = Date.now()
if (now - pre > delay) {
pre = now
fn.call(caller, ...arguments)
}
}
}
let els = document.getElementsByClassName('container');
let count1 = 0,
count2 = 0,
count3 = 0;
const THRESHOLD = 200;
els[0].addEventListener('scroll', function() {
console.log('普通滚动事件!count1=', ++count1);
});
els[1].addEventListener('scroll', debounce(function() {
console.log('执行滚动事件!(函数防抖) count2=', ++count2);
}, THRESHOLD));
els[2].addEventListener('scroll', throttle(function() {
console.log(Date.now(), ', 执行滚动事件!(函数节流) count3=', ++count3);
}, THRESHOLD));
script>
<style>
.wrap {
width: 200px;
height: 330px;
margin: 50px;
margin-top: 200px;
float: left;
background-color: yellow;
}
.header {
width: 100%;
height: 30px;
background-color: #a8d4f4;
text-align: center;
line-height: 30px;
}
.container {
background-color: pink;
box-sizing: content-box;
width: 200px;
height: 300px;
overflow: scroll;
}
.content {
width: 140px;
height: 800px;
margin: auto;
background-color: #14ffb2;
}
style>
原型其实就是一个对象,每一个构造函数都有一个原型,通过构造函数创建的实例对象也可以访问这个原型。当调用一个对象的属性的时候,会先从这个对象自身去找,如果找不到就会从它的原型里找。如果在原型里没找到,因为原型也是对象,也有自己的原型,会从原型的原型里找,这就会形成一条链,这就是原型链。js的继承就是通过原型链来实现的,一个对象的原型就是它的父类对象。
宏任务和微任务都是异步任务,js是一个单线程的语言,它要实现异步处理就得依赖于事件循环,然后事件循环是通过任务队列来实现的,任务分为宏任务和微任务,分别用宏任务队列和微任务队列来接收。一次事件循环会先从宏任务队列中取出一个任务执行,然后将微任务队列中的所有任务执行,最后执行页面的渲染。
宏任务就是比较粗粒度的任务,它们之间可以穿插渲染操作,而微任务粒度细,讲求效率,会在宏任务之后全执行完。
宏任务包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
微任务包括: Promises, Object.observe, MutationObserver
https://zhuanlan.zhihu.com/p/26962590
setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到主线程把同步任务和"任务队列"现有的事件都处理完,才会得到执行。
在某种程度上,我们可以利用setTimeout(fn,0)的特性,修正浏览器的任务顺序。
GUI 渲染线程:负责渲染浏览器界面 HTML 元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在 Javascript 引擎运行脚本期间, GUI 渲染线程都是处于挂起状态的,也就是说被”冻结”。即 GUI 渲染线程与 JS 引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行。
浏览器的内核是多线程的
javascript 引擎线程: 也可以称为 JS 内核,主要负责处理 Javascript 脚本程序,例如 V8 引擎。Javascript 引擎线程理所当然是负责解析 Javascript 脚本,运行代码。浏览器无论什么时候都只有一个 JS 线程在运行 JS 程序。
浏览器事件触发线程: 当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于JS的单线程关系所有这些事件都得排队等待 JS 引擎处理。
定时触发器线程: 浏览器定时计数器并不是由 JavaScript 引擎计数的, 因为 javaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。
异步 http 请求线程: XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。
作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
alert([])是个空 alert(![]) 是个false javascript中一切空或者0在做比较的时候都会转化成boolean值false所以 答案很明显了 false equals false
执行类型转换的规则如下:
如果一个运算数是 Boolean 值,在检查相等性之前,把它转换成数字值。false 转换成 0,true 为 1。
如果一个运算数是字符串,另一个是数字,在检查相等性之前,要尝试把字符串转换成数字。
如果一个运算数是对象,另一个是字符串,在检查相等性之前,要尝试把对象转换成字符串。
如果一个运算数是对象,另一个是数字,在检查相等性之前,要尝试把对象转换成数字。
在比较时,该运算符还遵守下列规则:
值 null 和 undefined 相等。
在检查相等性时,不能把 null 和 undefined 转换成其他值。
如果某个运算数是 NaN,等号将返回 false,非等号将返回 true。
如果两个运算数都是对象,那么比较的是它们的引用值。如果两个运算数指向同一对象,那么等号返回 true,否则两个运算数不等。
var a = [1, 23]
a instanceof Array
XHR
fetch: 原生, 低级浏览器不支持
https://juejin.cn/post/6868898917950423054
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
// reject('failed')
}, 500)
})
function race(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach(v => {
v.then(res => { resolve(res) }).catch(err => { reject(err) })
});
})
}
function all(promiseArr) {
var resArr = new Array(promiseArr.length)
var count = 0
return new Promise((resolve, reject) => {
promiseArr.forEach((v, i) => {
v.then(res => {
resArr[i] = res
++count
if (count === resArr.length) {
resolve(resArr)
}
}).catch(err => {
reject(err)
})
})
})
}
// race([p1, p2]).then((res) => { console.log(res); }).catch(err => { console.log(err); })
all([p1, p2]).then((res) => { console.log(res); }).catch(err => { console.log(err); })
http是无状态的,要记录客户端的身份信息需要借助一定的数据结构。
cookie是把信息存放在浏览器中,以后每次发送请求都会把cookie放在请求头里。cookie比较方便,但是也有很多缺点:
session是把信息存到服务器中,给浏览器一个sessionid把它存到cookie里,每次发送请求会把id带上,服务器就可以知道用户信息了。
session的缺点是占用服务器内存,如果在线人数多的话,服务器压力会比较大
localStorage和sessionStorage都是存放到浏览器里的,它们不会随着请求自动发送,而且存储空间比较大。
localStorage用来保存永久的数据,只要不主动删除就会一直存在。在所有同源窗口中都是共享的
sessionStorage的生命周期在窗口会话内,关闭窗口就会销毁。不在不同的浏览器窗口中共享,即使是同一个页面
同源就是协议+域名+端口都相同,同源策略是浏览器的一种安全机制,目的就是防止一个站点随意使用另一个站点的资源。
为了方便,有一些标签是跨域的,比如script, link,img
script是支持跨域的,可以让服务器动态生成script,然后用前端用script引用,参数加载url后面。类似于get
后端可以通过加响应头Access-Control-Allow-Origin
来信任前端站点。
CDN(Content Delivery Network)即内容分发网络,它的作用是在用户和服务器之间增加一层缓存,来达到加速的目的。它实现的原理是接管DNS,将目标服务器的ip地址转换为为离用户最近的缓存服务器的ip地址。
CDN网络一般是由一个DNS服务器和几台缓存服务器运行起来的。
浏览器是有缓存机制的,每次请求的时候,会先检查本地的缓存,如果找到有效的缓存就直接返回。
强缓存: 有效性只跟时间有关。
HTTP/1.0 响应头Expires 配合缓存中Last-modified
HTTP/1.1 响应头Cache-Control
协商缓存: 当强缓存失效后,就会转为协商缓存。需要与服务器交互,通过协商缓存响应头检查本地缓存与服务器资源是否一致。协商缓存生效,返回304和Not Modified, 协商缓存失效,返回200和请求结果。对于静态文件,例如:CSS、图片,服务器会自动设置协商缓存。
HTTP/1.0 响应头Last-Modified 请求头If-Modified-Since。本质上根据修改时间判断,若某资源在一秒内修改多次,则该方法会失效
HTTP/1.1 响应头ETag 请求头If-None-Match 资源的一个唯一标识,只要资源有变化,Etag就会重新生成。
回流: 当渲染树中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。
重绘: 当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
回流一定会触发重绘,而重绘不一定会回流
会导致回流的操作:
浏览器的优化
浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。当你访问获取布局信息的属性或方法的时候,会强制队列刷新,这是为了保证及时性与准确性。
避免重绘与回流
推迟加载
如果页面初始的渲染并不依赖于js,可以最后再加载js。一种是方法把它们放在文档后面,另一种是在标签上加defer
属性
异步加载
边渲染边下载。添加async
属性
defer
与async
的区别
defer
是立即下载但延迟执行
async
是立即下载并执行
key是虚拟DOM的标识,当状态中的数据发生变化时,react会根据新数据
生成新虚拟DOM
。然后react使用diff算法将旧虚拟DOM与新虚拟DOM进行比较:
用index作为key可能有两个问题:
如果对列表进行逆序添加/删除等破坏顺序的操作,会导致产生没有必要的真实DOM的更新,导致性能下降
如果列表中含有输入框,输入框的内容会错位
子组件通过props属性接收
v-model双向数据绑定;
v-for循环;
v-if v-show 显示与隐藏;
v-on事件@;v-once: 只渲染一次
共同点:都能控制元素的显示和隐藏;
不同点:实现本质方法不同,v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次;v-if是动态的向DOM树内添加或者删除DOM元素,若初始值为false,就不会编译了。而且v-if不停的销毁和创建比较消耗性能。
总结:如果要频繁切换某节点,使用v-show(切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用v-if(初始渲染开销较小,切换开销比较大)。
在组件中的style前面加上scoped
assets文件夹是放静态资源;components是放组件;router是定义路由相关的配置; app.vue是一个应用主组件;main.js是入口文件。
computed:
当一个属性受多个属性影响的时候就需要用到computed
最典型的栗子: 购物车商品结算的时候
watch:
当一条数据影响多条数据的时候就需要用watch
栗子:搜索数据
因为JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。
组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐v-if和v-for同时使用。
如果v-if和v-for一起用的话,vue中的的会自动提示v-if应该放到外层去。
1 hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
2 history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id 如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”
.stop
阻止冒泡.prevent
阻止默认事件.capture
捕获事件.once
只触发一次事件.self
修饰符stop的作用是让事件不再向外传播
prevent的作用是阻止默认事件,比如a标签默认的点击事件是跳转页面,
capture的作用是事件一发生当前元素的回调函数先相应,多个capture的顺序是从外向内
vue每个组件都是独立的,每个组件都有一个属于它的生命周期,从一个组件创建、数据初始化、挂载、更新、销毁