(adsbygoogle = window.adsbygoogle || []).push({});

# 继承

# 几种方式 (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)

  1. DNS 解析:将域名解析成 IP 地址
  2. TCP 连接:TCP 三次握手
  3. 发送 HTTP 请求
  4. 服务器处理请求并返回 HTTP 报文
  5. 浏览器解析渲染页面
  6. 断开连接:TCP 四次挥手

# 三次握手

三次握手用以同步客户端和服务端的序列号和确认号。为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误

是指建立一个 TCP 连接时,需要客户端和服务器总共发送 3 个数据包。

通俗版

  1. 由浏览器发起,告诉服务器我要发送请求了
  2. 由服务器发起,告诉浏览器我准备接收了
  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 的自身特点可靠传输决定的。客户端和服务端要进行可靠传输,那么就需要确认双方的接收和发送能力。第一次握手可以确认客服端的发送能力,第二次握手,确认了服务端的发送能力和接收能力,所以第三次握手才可以确认客户端的接收能力。不然容易出现丢包的现象。

# 四次挥手

通俗版

  1. 由浏览器发起的,发送给服务器,我请求报文发送完了,你准备关闭
  2. 由服务器发起的, 告诉浏览器,我请求报文接受完了,我准备关闭了,你也准备吧
  3. 由服务器发起,告诉浏 览器,我响应报文发送完了,你准备关闭吧)
  4. 由浏览器发起,告诉服务器,我响应报文接受完了,我准备关闭了,你也准备吧

原理版

  • 第一次挥手(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请求,不安全

  1. 创建一个回调函数, 当服务器返回时自动执行(callback(data)),通过函数的参数可以获取到服务发回来的数据
  2. 拼接好要传的参数,因为是 get 请求(params1=value&params2=value2)。并且约束好函数的keycallback,其他参数则是提交请求的参数
  3. 创建一个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-Typeapplication/x-www-form-urlencoded,这个是表单提交格式。文件上传的格式应该为multipart/form-dataFormData已经是这个格式,因此不使用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 结构发生变化,影响了元素的位置和尺寸大小,重新生成布局,重新排列元素。

# 减少重排次数

  1. 样式集中改变
  2. dom 操作,读和写,最好要分开
  3. 使用绝对定位、固定定位脱离文档流

# 小结

  • 重绘不一定导致重排,但重排一定会导致重绘。

  • 我们应该尽量以局部布局的形式组织 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)

垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

  1. 标记清除

    最常用的回收方式

    对内存中的变量都进行标记,后面用不到了,或者已经访问不到了,会加上准备删除的标记。最后垃圾回收器对标记删除的变量进行垃圾回收

    function test() {
      var a = 10; //被标记 ,进入环境
      var b = 20; //被标记 ,进入环境
    }
    test(); //执行完毕 之后 a、b又被标离开环境,被回收。
    
  2. 引用计数

    当垃圾回收器下次再运行时,它就会释放那些引用次数为 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,比如windowdom的 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

最后修改时间: 11/29/2022, 5:57:18 AM