# 继承
# 几种方式 (opens new window)
# 1. 原型链继承
将父类的实例赋值给子类的原型
数据在原型
缺点:多个实例对引用类型的操作会被篡改
Son.prototype = new Father();
# 2. 借用构造函数继承
通过
call方法
把父类的构造函数重定义指向子类的构造函数数据在构造函数
缺点:
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现复用,每个子类都有父类实例函数的副本,影响性能
function Son() {
Father.call(this);
}
let son = new Son();
# 3. 组合继承
利用原型链继承父类原型的属性方法;借用构造函数继承的方式继承实例上的属性和方法。结合起来就是组合继承
缺点:调用了两次父类函数,其原型中会存在两份相同的属性/方法
function Son() {
Father.call(this);
}
Son.prototype = new Father();
let son = new Son();
# 4. 原型式继承
利用一个空对象,把父类的属性方法赋值给空对象构造函数的原型,返回实例。
Object.create()
方法就是这样的意思
缺点:
- 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
- 无法传递参数
// 原型式
function Father(obj) {
function f() {}
f.prototype = obj;
return new f();
}
let person = {
name: "gauhar Person",
arr: [1, 2],
};
let s3 = Father(person);
console.dir(s3);
// 原型式 Object.create
let person = {
name: "gauhar Person",
arr: [1, 2],
};
let son = Object.create(person);
son.name = "gauhar Son";
son.arr.push(3);
console.dir(son);
# 5. 寄生式继承
使用原型式继承得到实例后,可以在实例的构造函数上绑定属性和方法
缺点:和原型式的缺点一样
function Father(obj) {
function f() {}
f.prototype = obj;
return new f();
}
let person = {
name: "gauhar 原型",
arr: [1, 2],
};
function jisheng(parms) {
let parasitism = Father(parms);
parasitism.name = "gauhar 构造";
parasitism.say = function () {
console.log("say 构造");
};
return parasitism;
}
let ji = jisheng(person);
console.dir(ji);
ji.say();
# 6. 寄生式组合继承
使用寄生式继承父类的原型,使用借用构造函数继承实例的属性方法和传参
function Father(obj) {
function f() {}
f.prototype = obj;
return new f();
}
let person = {
name: "gauhar 原型",
arr: [1, 2, "原型"],
};
function jisheng(parms) {
let parasitism = Father(parms);
parasitism.name = "gauhar 构造";
parasitism.say = function () {
console.log("say 构造");
};
return parasitism;
}
function composition(son, father) {
let obj = jisheng(father); // 寄生继承
son.prototype = obj; // 将父类的实例对象赋值给子类的原型
son.prototype.constructor = son;
}
function father(name) {
this.name = name;
this.age = 50;
this.colors = [1, 2, 3];
}
father.prototype.walk = function () {
console.log("这是father, 走路太慢了");
};
function son(name) {
father.call(this, name);
}
composition(son, father.prototype);
// 新增子类原型属性
son.prototype.sayAge = function () {
alert(this.age);
};
let s5 = new son("父亲");
console.dir(s5);
s5.walk();
s5.colors.push("s5");
let s6 = new son("s6");
s6.colors.push("s6");
console.log(s5.colors); // [1, 2, 3, "s5"]
console.log(s6.colors); // [1, 2, 3, "s6"]
# 7. 混入方式继承多个对象
通过借用构造函数的方式,可以继承多个对象的属性和方法
Object.create
继承一个父类的原型对象
Object.assign
混合继承其他父类的原型对象
function Father1() {
this.name = "Father1";
}
Father1.prototype.say = function () {
console.log(this.name);
};
function Father2() {
this.age = "Father2";
}
Father2.prototype.sayAge = function () {
console.log(this.age);
};
function Myclass() {
Father1.call(this);
Father2.call(this);
}
Myclass.prototype = Object.create(Father1.prototype); // 把父类1的原型对象赋值给子类原型的__proto__对象
// Myclass.prototype = Object.create(Father2.prototype) // 会把father1的属性方法覆盖
// 混合其他
Object.assign(Myclass.prototype, Father2.prototype); // 把父类2的原型拷贝赋值给子类的原型
Myclass.prototype.son = function () {};
Myclass.prototype.constructor = Myclass;
let my = new Myclass();
console.dir(my);
# 8. ES6 类继承 extends
其内部实现和寄生式组合继承一样,是语法糖
// es6
class Father {
constructor(like, hate) {
this.like = like;
this.hate = hate;
}
get fruits() {
return this.like;
}
bad() {
console.log("最不喜欢吃" + this.hate);
}
}
class Son extends Father {
constructor(sonLike, sonHate) {
super(sonLike, sonHate); // 调用父类的构造函数
this.name = "sonName";
}
}
let son = new Son("🍎", "🍌");
console.log(son.fruits);
son.bad();
console.log(son.name);
# 从 UR 输入到页面展现到底发生什么? (opens new window)
- DNS 解析:将域名解析成 IP 地址
- TCP 连接:TCP 三次握手
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 断开连接:TCP 四次挥手
# 三次握手
三次握手用以同步客户端和服务端的序列号和确认号。为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误
是指建立一个 TCP 连接时,需要客户端和服务器总共发送 3 个数据包。
通俗版
- 由浏览器发起,告诉服务器我要发送请求了
- 由服务器发起,告诉浏览器我准备接收了
- 由浏览器发起,告诉服务器我马上就发了
原理版
第一次握手(SYN=1, seq=x):
客户端发送一个 TCP 的 SYN 标志位置 1 的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。
发送完毕后,客户端进入
SYN_SEND
状态。第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为 1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加 1,即 X+1。 发送完毕后,服务器端进入
SYN_RCVD
状态。第三次握手(ACK=1,ACKnum=y+1)
客户端再次发送确认包(ACK),SYN 标志位为 0,ACK 标志位为 1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写 ISN 的+1
发送完毕后,客户端进入
ESTABLISHED
状态,当服务器端接收到这个包时,也进入ESTABLISHED
状态,TCP 握手结束。
# 为什么需要三次握手,两次不行吗?
其实这是由 TCP 的自身特点可靠传输决定的。客户端和服务端要进行可靠传输,那么就需要确认双方的接收和发送能力。第一次握手可以确认客服端的发送能力,第二次握手,确认了服务端的发送能力和接收能力,所以第三次握手才可以确认客户端的接收能力。不然容易出现丢包的现象。
# 四次挥手
通俗版
- 由浏览器发起的,发送给服务器,我请求报文发送完了,你准备关闭
- 由服务器发起的, 告诉浏览器,我请求报文接受完了,我准备关闭了,你也准备吧
- 由服务器发起,告诉浏 览器,我响应报文发送完了,你准备关闭吧)
- 由浏览器发起,告诉服务器,我响应报文接受完了,我准备关闭了,你也准备吧
原理版
第一次挥手(FIN=1,seq=x)
假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为 1 的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。
发送完毕后,客户端进入
FIN_WAIT_1
状态。第二次挥手(ACK=1,ACKnum=x+1)
服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。
发送完毕后,服务器端进入
CLOSE_WAIT
状态,客户端接收到这个确认包之后,进入FIN_WAIT_2
状态,等待服务器端关闭连接。第三次挥手(FIN=1,seq=y)
服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为 1。
发送完毕后,服务器端进入
LAST_ACK
状态,等待来自客户端的最后一个 ACK。第四次挥手(ACK=1,ACKnum=y+1)
客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入
TIME_WAIT
状态,等待可能出现的要求重传的 ACK 包。服务器端接收到这个确认包之后,关闭连接,进入
CLOSED
状态。客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入
CLOSED
状态
# ajax
# 工作过程
创建
XMLHttpRequest
对象,调用open
方法连接服务器,发送请求,获取服务端响应数据
# 原生用法
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", "demo.php", "true");
xmlHttp.send();
xmlHttp.onreadystatechange = function () {
if ((xmlHttp.readyState === 4) & (xmlHttp.status === 200)) {
console.log(xmlHttp.responseText);
}
};
# jq 用法 (opens new window)
$.ajax({
type: "method", // get, post
url: "url",
data: {
op: "",
},
success: function (response) {},
error(err) {},
});
# axios
axios({
method: 'post',
url: '/user/name',
data: {
firstName: 'Gauhar',
lastName: 'Chan'
}
});
// get
axios.get('demo/url', {
params: {
id: 1,
name: 'Gauhar',
},
timeout: 1000,
...//其他相关配置
})
// post
axios.post('/user', {
firstName: 'Gauhar',
lastName: 'Chan'
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
# 跨域 (opens new window)
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就是跨域
有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<script src=XXX>
# 解决方案
# 1. JSONP
JSONP 请求一定需要对方的服务器做支持才可以,简单兼容性好,但仅仅支持
get
请求,不安全
- 创建一个回调函数, 当服务器返回时自动执行(
callback(data)
),通过函数的参数可以获取到服务发回来的数据 - 拼接好要传的参数,因为是 get 请求(params1=value¶ms2=value2)。并且约束好函数的
key
是callback
,其他参数则是提交请求的参数 - 创建一个
script
标签,把url
和参数拼接起来,赋值给script
标签的src
,插入到body
中
// index.html
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement("script");
window[callback] = function (data) {
// 编写一个回调函数
resolve(data); // 返回服务器的数据
document.body.removeChild(script); // 删除script标签
};
// 拼接
params = { ...params, callback }; // name=gauhar&callback=sayHi
let arrs = [];
for (let key in params) {
arrs.push(`${key}=${params[key]}`);
}
script.src = `${url}?${arrs.join("&")}`;
document.body.appendChild(script);
});
}
jsonp({
url: "http://localhost:6688/say",
params: { name: "gauhar" },
callback: "sayHi",
}).then((data) => {
console.log(data); // hello gauhar
});
jq 写法
$.ajax({
url: "http://localhost:6688/say",
dataType: "jsonp", // 使用jsonp跨域
data: {
name: "gauhar",
},
success: function (data) {
console.log(data);
},
});
node.js
代码
let express = require("express");
let app = express();
app.get("/say", function (req, res) {
let { name, callback } = req.query;
console.log(name); // gauhar
console.log(callback); // sayHi
res.end(`${callback}('hello ${name}')`);
});
app.listen(6688);
# 2.代理
vue-cli 创建项目,在根目录下新建
vue.config.js
文件
module.exports = {
devServer: {
proxy: {
"/api": {
target: "https://gauhar.top", // 实际的请求地址
changeOrigin: true, // 开启跨域
pathRewrite: { "^/api": "" }, // 来重写地址,将前缀 '/api' 转为 '/'
},
"/j": {
target: "https://movie.douban.com",
changeOrigin: true,
},
},
},
};
# 3.cors
后端设置响应头:
Access-Control-Allow-Origin
# 简单请求
只要同时满足以下两大条件,就属于简单请求
条件 1:使用下列方法之一:
- GET
- HEAD
- POST
条件 2:Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
# 复杂请求
其他条件的情况就是复杂请求,复杂请求首先会发起一个option
请求,判断服务端是否允许跨域
// serve.js
const express = require("express");
const app = express();
app.use(function (req, res, next) {
// 设置哪个源可以访问我
res.setHeader("Access-Control-Allow-Origin", "*");
// 允许携带哪个头访问我
res.setHeader("Access-Control-Allow-Headers", "*");
// // 允许哪个方法访问我
res.setHeader("Access-Control-Allow-Methods", "*");
if (req.method === "OPTIONS") {
// OPTIONS请求不做任何处理
console.log(111);
}
next();
});
app.put("/put", function (req, res) {
console.log(222);
res.end("gauhar");
});
app.listen(4000, () => {
console.log("running");
});
// index.html
$.ajax({
type: "PUT",
url: "http://localhost:4000/put",
success: function (data) {
console.log(data);
},
});
运行代码可以看到,打印 111,222。这说明复杂请求的时候,先发了一个option
请求,确定允许跨域后再发起put
请求,返回数据
# promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大
是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
# vue
# mvvm
M - Model 代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑
V - View,代表 UI 组件,它负责将数据模型转化为 UI 展现出来
VM - ViewModel,监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步 View 和 Model 的对象,连接 Model 和 View
# 双向绑定原理 (opens new window)
# vue-router (opens new window)
# 传参和取参
path 和 query 一起使用
传参: this.$router.push({
path: "/xxx",
query: {
id: id,
},
});
接收参数: this.$route.query.id;
name 和 params 一起使用
传参: this.$router.push({
name: "xxx",
params: {
id: id,
},
});
接收参数: this.$route.params.id;
- 1.
$router
为实例,想要导航到不同,则使用 router.push 方法 - 2.
$route
为当前 router 跳转对象,里面可以获取 name、path、query、params 等
另外,二者还有点区别,直白的来说 query 相当于 get 请求,页面跳转的时候,可以在地址栏看到请求参数,而 params 相当于 post 请求,参数不会再地址栏中显示
# JQuery
# 文件上传
$("button").click(function () {
var files = $("#avatar").prop("files"); // 获取文件对象
// 使用Fromdata对象上传文件
var data = new FormData();
data.append("avatar", files[0]); // avatar是键名
$.ajax({
url: "/api/upload",
type: "POST",
data: data,
cache: false, // 兼容IE8
contentType: false,
processData: false,
});
});
contentType: false
:jq 默认设置content-Type
为application/x-www-form-urlencoded
,这个是表单提交格式。文件上传的格式应该为multipart/form-data
,FormData
已经是这个格式,因此不使用jq
的转换
processData: false
:jQuery 会将data
对象转换为字符串来发送 HTTP 请求,默认情况下会用 application/x-www-form-urlencoded
编码来进行转换。 我们设置contentType: false
后该转换会失败,因此设置processData: false
来禁止该转换过程
# http
tcp 连接需要 3 次握手,只能单播(一对一),面向字节流,可靠,堵塞(可控制)
udp 是无连接协议,不会进行压缩,发送源数据报文,直接发送,不管对方是否收到。不可靠,无堵塞
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅 8 字节 | 首部最小 20 字节,最大 60 字节 |
适用场景 | 适用于实时应用(IP 电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 |
# http 缓存 (opens new window)
强缓存,向浏览器查询是否有缓存,如果没有就进行协商缓存。协商缓存就是向服务器发起 http 查询服务器是否有缓存,
如果服务器有缓存就返回 304 状态码;如果没有就返回最新数据,状态码为 200。
# 强缓存
浏览器如果判断本地缓存未过期,就直接使用
HTTP 1.0
服务器使用的响应头字段为 Expires
,值为未来的绝对时间(时间戳),浏览器请求时的当前时间超过了 Expires 设置的时间,代表缓存失效,需要再次向服务器发送请求,否则都会直接从缓存数据库中获取数据。
HTTP 1.1
Cache-Control
是最重要的规则,默认为 private。
private 私有缓存
public 共享缓存
max-age 缓存的内容将在 xxx 秒后失效
no-cache 需要使用对比缓存来验证缓存数据
no-store 所有内容都不会缓存,强缓存、协商缓存都不会触发
# 协商缓存
根据请求头的 If-Modified-Since / If-None-Match的值和对应的服务端字段的值做匹配,一致则命中缓存,304 状态码
- http1.0 If-Modified-Since/Last-Modified 这两个是成对出现的,属于协商缓存的内容,其中浏览器的头部是
If-Modified-Since
,而服务端的是Last-Modified
,只能精确到1s以内
。 - http1.1 If-None-Match/E-tag 这两个是成对出现的,属于协商缓存的内容,其中浏览器的头部是
If-None-Match
,而服务端的是E-tag
# 浏览器渲染过程
- 解析 HTML 生成 DOM 树。
- 解析 CSS 生成 CSSOM 规则树。
- 将 DOM 树与 CSSOM 规则树合并在一起生成渲染树。
- 遍历渲染树开始布局,计算每个节点的位置大小信息。
- 将渲染树每个节点绘制到屏幕。
# 重排、重绘 (opens new window)
- 重绘:某些元素的外观被改变,但没有改变布局(如果影响到布局,大小就会触发重排),例如:元素的填充颜色
- 重排:当 dom 结构发生变化,影响了元素的位置和尺寸大小,重新生成布局,重新排列元素。
# 减少重排次数
- 样式集中改变
- dom 操作,读和写,最好要分开
- 使用绝对定位、固定定位脱离文档流
# 小结
重绘不一定导致重排,但重排一定会导致重绘。
我们应该尽量以局部布局的形式组织 html 结构,尽可能小的影响重排的范围。
# new.target
判断函数或构造方法是否是通过
new
运算符被调用的如果函数或构造方法是被
new
实例化的,则new.target
返回构造函数的引用(构造函数本身)如果是普通函数,则返回
undefined
function Person() {
this.age = 18;
console.log(new.target);
console.log(new.target.name);
}
Person(); // undefined 第二个打印报错,new.target是undefined了,肯定访问不了name
let p1 = new Person();
// ƒ Person () {
// this.age = 18
// console.log(new.target)
// console.log(new.target.name)
// }
// Person new.target.name
# 垃圾回收 (opens new window)
垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存
标记清除
最常用的回收方式
对内存中的变量都进行标记,后面用不到了,或者已经访问不到了,会加上准备删除的标记。最后垃圾回收器对标记删除的变量进行垃圾回收
function test() { var a = 10; //被标记 ,进入环境 var b = 20; //被标记 ,进入环境 } test(); //执行完毕 之后 a、b又被标离开环境,被回收。
引用计数
当垃圾回收器下次再运行时,它就会释放那些引用次数为 0 的值所占用的内存。
一个引用赋值给另一个变量,则引用计数+1。就是内存地址引用的次数。下面变量 b 初始化时,对 a 对象的引用,这个 a 的引用计数+1;后面重新定义了变量 b,并且不是 a 的引用,这时,a 的引用计数-1。
function test() { var a = {}; //a的引用次数为0 var b = a; //a的引用次数加1,为1 var c = a; //a的引用次数再加1,为2 var b = {}; //a的引用次数减1,为1 }
引用计数有缺点,就是引用循环。循环引用指的是对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的引用。
# js 运行原理
JS 引擎主要负责把 JS 代码转为机器能执行的机器码,而 JS 代码中调用的一些 WEB API 则由其运行环境提供,这里指的是浏览器。
JS 是单线程运行,每次都从调用栈出取出代码进行调用。如果当前代码非常耗时,则会阻塞当前线程导致浏览器卡顿。
回调函数是通过加入到事件队列中,等待 Event Loop 拿出并放到调用栈中进行调用。只有 Event Loop 监听到调用栈为空时,才会从事件队列中从队头拿出回调函数放进调用栈里。
# runTime
浏览器提供的一些 api,比如
window
、dom
的 api;js 的事件循环、事件队列被称为runTime
# 堆栈
堆::储存着引用类型的内存地址,闭包的变量也存在于此
栈:储存函数的调用,基本类型的数据。函数上下文执行完毕之后被消除,如果还有引用,那就继续存在于调用栈中。
# script标签
# defer
defer
会“在后台”下载,浏览器将继续处理 HTML,构建 DOM;然后等 **DOM 构建完成后,脚本才会执行。**也因此这中标签在文档中的位置不重要
# async
也是异步加载不会阻塞页面渲染,但是当script加载就绪后,就会根据在文档中的顺序优先执行代码
# Set无法对哪些值去重
Set成员是唯一且无序的,没有重复值。 向Set中加入值的时候,不会进行类型转换,类似于精确运算符
===
,主要的区别在于**NaN等于自身
**,==而精确运算符===
判断NaN不等于自身。==
- Symbol
- 引用类型 function/object/array/..
# 方法和属性
实例属性constructor:构造函数 size: 返回元素数量
遍历方法:
keys():返回一个包含集合中所有键的迭代器 values():返回一个包含集合中所有值的迭代器 entries():返回一个包含set对象中所有元素得键值对迭代器 forEach(callback,thisArg):用于对集合成员执行callback