前端知识点大杂烩 by ChatGPT

最近正值实习求职,楼主是个八股文小白。偶然间发现ChatGPT特别适合做这种技术型的问答,又标准又有逻辑(此处表白chatgpt),特此找个地方记录(搬运),利用好仅剩的伟大互联网(T_T)

JS相关

基础

list怎么循环

(呃呃顺便吐槽一下这个面试官,,,为什么不说遍历啊)

  • for (let i = 0; i < arr.length; i++)

  • arr.forEach(function(item, index)

  • for (let index in arr) // 遍历属性名!

  • for (let item of arr)

array的操作

  • push:在数组末尾添加一个或多个元素,并返回修改后的数组长度。

  • pop:删除数组末尾的元素,并返回删除的元素。

  • shift:删除数组开头的元素,并返回删除的元素。

  • unshift:在数组开头添加一个或多个元素,并返回修改后的数组长度。

  • slice:返回数组中指定部分的浅拷贝,不会修改原数组。

  • splice:从数组中添加或删除元素,可以同时修改原数组。

  • concat:连接两个或多个数组,并返回合并后的新数组,不会修改原数组。

  • reverse:反转数组中的元素,并返回修改后的数组。

  • map:返回一个由原数组中的每个元素调用一个指定方法后的结果组成的新数组。

let arr = [1, 2, 3];
let newArr = arr.map(function(item) {
  return item * 2;
});
console.log(newArr);  // [2, 4, 6]
array比较

stringify(如果有obj不行?)或者一个个比较

.filter
  • return a shadow copy

const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];

const result = words.filter(word => word.length > 6);

console.log(result);
// Expected output: Array ["exuberant", "destruction", "present"]

数据类型

在 JavaScript 中,数据类型可以分为两类:原始类型和引用类型。

  1. 原始类型:

  • 数字(Number):整数或浮点数,可以使用数学运算符进行计算。

  • 字符串(String):由一系列字符组成,可以使用字符串方法进行操作。

  • 布尔值(Boolean):true 或 false,用于表示逻辑上的真或假。

  • null:表示空值或不存在的对象。

  • undefined:表示未定义的值或变量。

  • Symbol:表示独一无二的值,用于创建对象的唯一属性名。

  1. 引用类型:

  • 对象(Object):由一组属性和方法组成的数据结构,可以通过点或方括号语法访问属性和方法。

  • 数组(Array):是对象的一种特殊形式,可以存储一组有序的值,并提供了一些方法来操作这些值。

  • 函数(Function):是一种可执行的对象,可以封装一段可重复使用的代码,并接受参数、返回值等。

  • 日期(Date):表示日期和时间的对象。

  • 正则表达式(RegExp):表示一个正则表达式的对象,用于进行字符串匹配和替换。

  • Map 和 Set:ES6 引入的新类型,分别表示键值对集合和无重复值的集合。

引用类型的==和===

除非指向同一个对象,否则是false。因为会直接比较他们的引用。

string访问超出的长度

返回undefined,不报错,因为 JavaScript 的字符串是一种类数组对象,它的长度是自动计算的

访问超出数组边界:同理

object的bool值

如果obj=null/undefined/0/-0/Nan/""/false

async, promise

【promise的出现】模拟multi thread后台运行。和event handler/callback关系:早期的async就是用一个event handler来handle一个慢慢运行的函数。但是会出现回调地狱。于是出现了promise。

XMLHTTPRequest vs fetch:fetch is the promise-based replacement for XMLHttpRequest.

原型链怎么理解

在 JavaScript 中,每个对象都有一个原型对象(prototype),原型对象也是一个对象,它可以有自己的原型对象,这样就形成了一个原型链(prototype chain)。

原型链是 JavaScript 实现继承的核心机制之一。当我们访问一个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript 引擎就会沿着该对象的原型链向上查找,直到找到该属性或方法为止,或者到达原型链的顶端(Object.prototype)为止。

原型链上属性的读取和修改

function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};

const p = new Person('Alice');

console.log(p.hasOwnProperty('name')); // true
console.log(p.hasOwnProperty('sayHello')); // false
console.log(p.__proto__ === Person.prototype); // true
console.log(p.__proto__.__proto__ === Object.prototype); // true
console.log(p.__proto__.__proto__.__proto__ === null); // true

p.age = 20;
console.log(p.hasOwnProperty('age')); // true
console.log(p.__proto__.hasOwnProperty('age')); // false

Person.prototype.gender = 'unknown';
console.log(p.gender); // "unknown"

Person.prototype.sayHi = function() {
  console.log(`Hi, my name is ${this.name}`);
};
p.sayHi(); // "Hi, my name is Alice"

在上面的代码中,我们定义了一个 Person 构造函数,并将其原型对象上定义了一个 sayHello() 方法。当我们创建一个 Person 的实例 p 后,可以通过 p.__proto__ 访问其原型对象,通过 p.__proto__.__proto__ 访问原型链上的下一个原型对象,以此类推。

继承和多重继承

function Animal(name) {
  this.name = name;
}

Animal.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

function Cat(name) {
  Animal.call(this, name);
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Cat.prototype.meow = function() {
  console.log(`${this.name} says "Meow"`);
};

function Bird(name) {
  Animal.call(this, name);
}

Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Bird;
Bird.prototype.fly = function() {
  console.log(`${this.name} is flying`);
};

function Parrot(name) {
  Bird.call(this, name);
  Cat.call(this, name); // 多重继承
}

Parrot.prototype = Object.create(Bird.prototype);
Parrot.prototype.constructor = Parrot;

Object.assign(Parrot.prototype, Cat.prototype);

Parrot.prototype.speak = function() {
  console.log(`${this.name} says "Hello"`);
};

const kitty = new Cat('Kitty');
kitty.sayHello(); // "Hello, I'm Kitty"
kitty.meow(); // "Kitty says "Meow""

const tweety = new Bird('Tweety');
tweety.sayHello(); // "Hello, I'm Tweety"
tweety.fly(); // "Tweety is flying"

const polly = new Parrot('Polly');
polly.sayHello(); // "Hello, I'm Polly"
polly.fly(); // "Polly is flying"
polly.meow(); // "Polly says "Meow""
polly.speak(); // "Polly says "Hello""

Function.prototype.call()

mozilla关于call的解释

This function is almost identical to apply(), except that call() accepts an argument list, while apply() accepts a single array of arguments — for example, func.apply(this, ['eat', 'bananas']) vs. func.call(this, 'eat', 'bananas').

prototype

Object()函数有一个匿名的对象,可以通过 prototype 属性访问

instanceof / typeof

在 JavaScript 中,instanceof 运算符用于检查一个对象是否属于某个特定的构造函数(Constructor)所创建的实例。instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

// 定义构造函数
function C(){}
function D(){}
var o = new C();
o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype

typeof(arr)

当传入一个数组时,typeof 返回的结果是 "object",而不是 "array"

不同于其它对象,数组具有数字索引和 length 属性。因此,如果我们需要判断一个值是否为数组,可以使用 Array.isArray() 方法。

typeof(Object)

在 JavaScript 中,Object 是一个预定义的构造函数,用于创建对象。因此,typeof Object 返回的结果是 "function",表示 Object 是一个函数类型。

es6为什么会出现箭头函数

ES6(ECMAScript 2015)中引入了箭头函数,它是一种更简洁的函数定义方式。箭头函数最初是出于简化回调函数的书写而被引入的,但后来被广泛应用于其他场景,因为它具有以下优点:

  1. 简洁明了:箭头函数的语法非常简洁,只需要一个箭头和一个表达式或一个代码块即可定义一个函数。相比于传统的函数定义方式,它省略了 function 关键字和大括号等冗长的语法。

  1. 绑定 this:箭头函数不会创建自己的 this,它会继承父级作用域的 this。因此,在箭头函数中可以访问到外层函数的 this,这样可以避免一些 this 绑定的问题。

  1. 简化回调函数:箭头函数非常适合作为回调函数使用,因为它们简洁明了,并且不会创建自己的作用域和 this,从而避免了回调函数中常见的作用域和 this 绑定问题。

以下是一些使用箭头函数的示例:

// 传统的函数定义方式
function double(x) {
  return x * 2;
}

// 箭头函数
const double = x => x * 2;

// 传统的回调函数
array.map(function(item) {
  return item * 2;
});

// 箭头函数作为回调函数
array.map(item => item * 2);

拓展:JS中的this

在 JavaScript 中,函数的 this 是非常容易出错的一个问题,因为它的指向会根据函数的调用方式而发生变化。在传统的匿名函数中,this 的指向是在函数调用时确定的,它可能会指向全局对象或者 undefined,这会导致一些难以排查的 bug。而在箭头函数中,它没有自己的 this,而是继承自父级作用域的 this,因此在箭头函数中使用 this 时会更加明确和直观。

举个例子,假设我们有一个对象 person,它有一个名为 sayHello 的方法:

const person = {
  name: 'John',
  sayHello: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

sayHello 方法中,我们希望使用 this 来访问 person 对象的属性。但是如果我们将 sayHello 方法作为回调函数传递给其他函数时,this 的指向会发生变化,这会导致输出结果出错。例如:

setTimeout(person.sayHello, 1000); // 输出 "Hello, my name is undefined"

这是因为在 setTimeout 调用 sayHello 方法时,this 的指向已经变成了全局对象,而不是 person 对象。this.name在执行时会从包含该箭头函数的作用域中继承,而不是从person类的实例中继承。在这种情况下,箭头函数所在的作用域是全局作用域,因此this.namethis.age都没有定义,它们的值为undefined。为了避免这种问题,我们可以使用箭头函数来定义 sayHello 方法:

const person = {
  name: 'John',
  sayHello: function() {
    setTimeout(() => {
      console.log(`Hello, my name is ${this.name}`);
    }, 1000);
  }
};

在上面的代码中,我们使用箭头函数作为 setTimeout 的回调函数,这样就可以继承 person 对象的 this,而不会受到 setTimeout 调用时的影响。

如果不想在 person 对象的定义中使用 setTimeout:

function delayedGreeting(callback, delay) {
  setTimeout(() => {
    callback();
  }, delay);
}

const person = {
  name: 'John',
  sayHello: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

delayedGreeting(person.sayHello.bind(person), 1000);

函数执行上下文中this的设置:call bind apply 显式this

但是,在全局环境中调用一个函数,函数内部的this指向的是全局变量window。

var myObj = {
  name : "极客时间",
  showThis: function(){
    this.name = "极客邦"
    console.log(this)
  }
}
var foo = myObj.showThis
foo()//window

当函数作为对象的方法调用时,函数中的this就是该对象;当函数被正常调用时,在严格模式下,this值是undefined,非严格模式下this指向的是全局对象window;嵌套函数中的this不会继承外层函数的this值。

因为箭头函数没有自己的执行上下文,所以箭头函数的this就是它外层函数的this。

拓展:箭头函数中没有 arguments 对象

箭头函数中不能通过 arguments 访问传入的参数。为了解决这个问题,我们可以使用 rest 参数来代替 arguments 对象,例如:

const sum = (...args) => {
  let total = 0;
  for (let i = 0; i < args.length; i++) {
    total += args[i];
  }
  return total;
};

垃圾回收机制

JavaScript 是一门动态语言,其垃圾回收机制是自动的,也就是说,当 JavaScript 的引擎发现一个对象不再被引用时,就会自动将其释放并回收其所占用的内存空间。

JavaScript 的垃圾回收机制采用了标记清除算法(mark-and-sweep algorithm)。具体来说,当一个对象被引用时,JavaScript 引擎会给这个对象打上标记,表示该对象正在被使用;当一个对象不再被引用时,JavaScript 引擎会将其标记清除,并将其所占用的内存空间加入到可回收的内存池中。当内存池的大小达到一定的阈值时,JavaScript 引擎就会启动垃圾回收器(garbage collector),将其中的可回收内存空间释放掉,以便将其重新分配给新的对象。

在现代的 JavaScript 引擎中,还有一种优化技术叫做分代垃圾回收(generational garbage collection)。它将内存空间分为两个代,一代存放新创建的对象,另一代存放存活时间较长的对象。因为新创建的对象往往有较短的生命周期,所以一代的垃圾回收较为频繁,而二代的垃圾回收则比较少。这种技术可以提高垃圾回收的效率,减少程序卡顿的情况。

标记清除算法

标记清除算法(mark-and-sweep algorithm)是一种常用的垃圾回收算法,它用于检测和回收不再使用的对象。

该算法分为两个阶段:标记阶段和清除阶段。

在标记阶段,垃圾回收器会从一个根集合(一般是全局对象或调用栈中的对象)开始,遍历所有的对象,对于遇到的每个对象,都打上一个标记,表示这个对象正在使用中,然后继续遍历这个对象引用的其他对象,打上标记,依次遍历直到所有的可达对象都被标记上。

在清除阶段,垃圾回收器会遍历整个内存空间,将未被标记的对象删除,并将其所占用的内存空间释放,以便将其重新分配给新的对象。

需要注意的是,由于 JavaScript 是一门动态语言,对象的引用关系可能会发生变化,因此垃圾回收器可能需要多次执行标记和清除操作才能回收所有的垃圾。

虽然标记清除算法能够高效地回收内存垃圾,但它也有一些缺点。例如,在清除阶段,需要暂停程序的执行,这可能会导致程序出现短暂的卡顿现象。此外,在标记阶段中,垃圾回收器需要遍历所有的对象,这可能会导致性能下降。因此,现代的 JavaScript 引擎通常采用了更为高级的垃圾回收算法,如增量标记算法和并发标记算法,以提高性能和响应速度。

“误判”和“漏判”

在标记清除算法中,可能会出现漏掉的对象或误判的情况,这通常被称为垃圾回收的“误判”和“漏判”问题。主要原因是在标记阶段中,垃圾回收器只会遍历所有从根对象出发能够到达的对象,如果某个对象不在这个可达图中,就会被误判为垃圾对象。

例如,如果在 JavaScript 中存在一个循环引用的对象,例如对象A引用对象B,而对象B又引用对象A,那么在垃圾回收时,这两个对象都会被标记为正在使用中,但是当其他对象不再引用这两个对象时,它们仍然不会被垃圾回收器清理掉,因为它们在标记阶段中被标记为正在使用中。

另一方面,由于 JavaScript 是一种动态语言,对象的引用关系可能会发生变化,例如删除对象的引用或者修改对象的属性等操作,这可能会导致垃圾回收器漏判某些垃圾对象。为了解决这些问题,现代的 JavaScript 引擎通常采用了更为高级的垃圾回收算法,如增量标记算法和并发标记算法等,可以更加准确地识别和回收垃圾对象。

根对象

在标记清除算法中,垃圾回收器需要从一个根集合开始,遍历所有的对象,对于遇到的每个对象,都打上一个标记,表示这个对象正在使用中。因此,选择合适的根对象非常重要,它直接影响了垃圾回收的效率和精度。

在 JavaScript 中,一般可以将全局对象(例如window对象)作为根对象,因为全局对象是所有对象的祖先对象,所有对象都可以通过全局对象进行引用。此外,调用栈中的对象也可以作为根对象之一,因为调用栈中的对象表示当前正在执行的代码,因此与当前代码无关的对象可以被视为垃圾对象。

需要注意的是,JavaScript 中还存在一些特殊对象,例如闭包、事件对象等,这些对象可能会长时间存在于内存中,即使它们没有被当前代码引用。因此,在选择根对象时,需要注意排除这些特殊对象,以避免造成误判。

增量标记算法和并发标记算法

增量标记算法:增量标记算法可以将垃圾回收的过程分解成多个小的阶段,每个阶段之间都会让应用程序运行一段时间。在每个阶段中,垃圾回收器会标记一部分对象,然后将控制权交还给应用程序,让应用程序继续执行。在接下来的阶段中,垃圾回收器会继续标记其他对象,直到所有的对象都被标记完毕。这样做的好处是,应用程序不需要等待整个垃圾回收过程结束,因此可以减少应用程序的停顿时间,提高应用程序的性能和响应能力。

并发标记算法:并发标记算法可以在应用程序运行时执行垃圾回收过程,而不需要暂停应用程序。在并发标记算法中,垃圾回收器会使用一些技术,如写屏障和读屏障等,来检测对象的引用关系是否发生了变化。当对象的引用关系发生变化时,垃圾回收器会及时更新垃圾回收的状态,以确保垃圾对象能够被准确地回收。由于并发标记算法可以在应用程序运行时执行垃圾回收,因此可以减少应用程序的停顿时间,提高应用程序的性能和响应能力。

宏任务微任务

通常我们把消息队列中的任务称为宏任务,每个宏任务中都包含了一个微任务队列,在执行宏任务的过程中,如果DOM有变化,那么就会将该变化添加到微任务列表中,这样就不会影响到宏任务的继续执行,因此也就解决了执行效率的问题。

等宏任务中的主要功能都直接完成之后,这时候,渲染引擎并不着急去执行下一个宏任务,而是执行当前宏任务中的微任务,因为DOM变化的事件都保存在这些微任务队列中,这样也就解决了实时性问题。

啥时候从主线程抽离,然后开始执行消息队列?

注意,setTimeOut是放在另一个消息队列。会检查是否到时间。执行是和消息队列平级。

在现代浏览器里面,产生微任务有两种方式。

第一种方式是使用MutationObserver监控某个DOM节点,然后再通过JavaScript来修改这个节点,或者为这个节点添加、删除部分子节点,当DOM节点发生变化时,就会产生DOM变化记录的微任务。

第二种方式是使用Promise,当调用Promise.resolve()或者Promise.reject()的时候,也会产生微任务。

为什么说在一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行?

这是因为在 JavaScript 中,微任务的执行时机是在当前宏任务执行结束之后,下一个宏任务开始之前。而在一个宏任务中,回调函数会被放入宏任务队列中,等待当前宏任务执行结束后才会被执行。因此,无论什么情况下,微任务都会先于宏任务执行,因为微任务的执行时机早于下一个宏任务的开始。这也是 JavaScript 中事件循环机制的一部分

监听DOM

相比较 Mutation Event,MutationObserver 到底做了哪些改进呢?

首先,MutationObserver将响应函数改成异步调用,可以不用在每次DOM变化都触发异步调用,而是等多次DOM变化后,一次触发异步调用,并且还会使用一个数据结构来记录这期间所有的DOM变化。这样即使频繁地操纵DOM,也不会对性能造成太大的影响。

我们通过异步调用和减少触发次数来缓解了性能问题,那么如何保持消息通知的及时性呢?如果采用setTimeout创建宏任务来触发回调的话,那么实时性就会大打折扣,因为上面我们分析过,在两个任务之间,可能会被渲染进程插入其他的事件,从而影响到响应的实时性。

这时候,微任务就可以上场了,在每次DOM节点发生变化的时候,渲染引擎将变化记录封装成微任务,并将微任务添加进当前的微任务队列中。这样当执行到检查点的时候,V8引擎就会按照顺序执行微任务了。

综上所述, MutationObserver采用了“异步+微任务”的策略。

  • 通过异步操作解决了同步操作的性能问题

  • 通过微任务解决了实时性的问题

作用域链

词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。也就是说,看起来它的形式是包起来的,就算在作用域上!如果是调用的,就不在!

function bar() {
    console.log(myName)
}
function foo() {
    var myName = "极客邦"
    bar()
}
var myName = "极客时间"
foo()

打印出来即可时间,因为foo和bar的外部引用outer都是全局。

function foo() {
    var myName = "极客邦"
    bar()
    function bar() {
        console.log(myName)
    }
}

var myName = "极客时间"
foo()

打印出来即可帮。

var bar = {
    myName:"time.geekbang.com",
    printName: function () {
        console.log(myName)
    }    
}
function foo() {
    let myName = "极客时间"
    return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()
bar.printName()

均打印即可邦。

闭包

【本人理解】闭包出现在函数的嵌套中,包在里面的小函数可以不断获取大函数的信息。

【本人理解2】A函数是大函数,它里面有变量a,函数B,并返回B。其中B使用了a。A执行完成之后,其执行上下文从栈顶弹出了,但变量a依然保存在内存中,B依然可以使用a。

对于一般的语言来说,一个函数只能含有它自己的作用域内的东西。【如下函数,一般来说line 9 处调用后就没法在line 10 找到name了】而js中的函数会形成闭包。 闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

闭包的作用-将函数与其操作的某些数据/环境关联起来,类似于面向对象编程。可以用来模拟私有方法。

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
})();

console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

在以上代码中,privateCounter和changeBy都无法在匿名函数外直接访问,但是在匿名函数中返回了3个公共函数,它们共享同一个函数的闭包,因此可以访问privateCounter和changeBy函数。

代码实现

Deep copy

适用于array和object

const deepCopyFunction = (inObject) => {
let outObject, value, key;

if (typeof inObject !== "object" || inObject === null) {
return inObject; // 如果 inObject 不是对象则返回 inObject
}

// 创建一个数组或者对象去保存值
outObject = Array.isArray(inObject) ? [] : {};

for (key in inObject) {
value = inObject[key];

// 递归复制嵌套的对象和数组的值
outObject[key] = deepCopyFunction(value);
}

return outObject;
};

// 测试:
const nestedArray = [[1], [2], [3]];

const nestedCopyWithStructuredClone = deepCopyFunction(nestedArray); // 深拷贝

console.log(nestedArray === nestedCopyWithStructuredClone); // false

nestedArray[0][0] = 4;
console.log(nestedArray); // [[4], [2], [3]] 修改了原对象
console.log(nestedCopyWithStructuredClone); // [[1], [2], [3]] 复制对象不受影响

注意:深拷贝浅拷贝都是针对object而言。heap存储全局变量,object的值存在heap中;stack存储原始值变量和obj的引用。

浅拷贝:= / ... / .slice() / .assign()

Vue

什么是

生命周期

Vue.js 中的组件生命周期和 `<script>` 执行顺序遵循特定的规则。以下是一个大致的顺序:

1. 组件实例化:首先,Vue.js 会创建一个新的组件实例。此时会执行组件中 `<script>` 标签内的代码,例如 `import` 语句,以及定义的 `ref`, `reactive`, `computed`, `watch` 等响应式数据和函数。

2. `beforeCreate` 和 `created` 钩子:然后,Vue.js 会执行 `beforeCreate` 和 `created` 生命周期钩子。在这两个钩子中,你可以访问和修改组件的数据,但还不能访问模板中的元素,因为它们还没有被创建和挂载。

3. 渲染和更新虚拟 DOM:Vue.js 将使用组件的数据来渲染模板,并创建虚拟 DOM。

4. `beforeMount` 钩子:在真实 DOM 被创建并替换 `el` 挂载点之前,Vue.js 会执行 `beforeMount` 生命周期钩子。

5. 创建和挂载真实 DOM:Vue.js 将使用虚拟 DOM 来创建真实的 DOM 元素,并替换 `el` 挂载点。

6. `mounted` 钩子:真实 DOM 创建和挂载完成后,Vue.js 会执行 `mounted` 生命周期钩子。在这个钩子中,你可以访问和操作模板中的所有元素,因为它们已经被创建和挂载了。

这个顺序适用于组件首次创建和挂载的情况。在组件更新(例如数据改变)和销毁时,还有其他的生命周期钩子,如 `beforeUpdate`, `updated`, `beforeUnmount`, `unmounted` 等等。

请注意,Vue 3 引入了 `onMounted` 等新的 Composition API 来替代 Vue 2 的 `mounted` 等选项式 API,但它们的作用和执行顺序是一样的。在 `onMounted` 回调中,你可以安全地访问和操作模板中的元素。

浏览器 / Web相关

代码实现TCP协议

以下是使用 Node.js 中内置的 net 模块实现的一个简单的 TCP 服务器和客户端示例:

// TCP 服务器
const net = require('net');

const server = net.createServer((socket) => {
  console.log('Client connected.');

  socket.on('data', (data) => {
    console.log(`Received data: ${data}`);

    // 向客户端发送数据
    socket.write(`Echo: ${data}`);
  });

  socket.on('close', () => {
    console.log('Client disconnected.');
  });
});

server.listen(3000, () => {
  console.log('Server listening on port 3000.');
});

// TCP 客户端
const client = net.createConnection({ port: 3000 }, () => {
  console.log('Connected to server.');

  // 向服务器发送数据
  client.write('Hello, server!');
});

client.on('data', (data) => {
  console.log(`Received data: ${data}`);
});

client.on('close', () => {
  console.log('Connection closed.');
});

在上面的代码中,我们首先创建了一个 TCP 服务器,使用 net.createServer 方法创建一个服务器实例,并在客户端连接到服务器时输出一条日志。然后,我们使用 socket.on 方法监听客户端发送的数据,并使用 socket.write 方法将接收到的数据发送回客户端。

接下来,我们创建了一个 TCP 客户端,使用 net.createConnection 方法创建一个客户端实例,并在连接到服务器时输出一条日志。然后,我们使用 client.write 方法向服务器发送数据,并使用 client.on 方法监听服务器发送的数据,并在连接关闭时输出一条日志。

跨域

由于浏览器的安全限制,不允许在一个域名下的网页直接访问另一个域名下的资源,包括文档、脚本、样式表、图片、音频等等。这种安全限制称为同源策略(Same-Origin Policy),即只有在同一个域名下的网页才可以自由地进行交互和访问。

出现的情况:域名、协议、端口不同

解决跨域问题的方法主要有以下几种:

  1. JSONP:利用 script 标签的 src 属性不受同源策略限制的特点,可以通过动态创建 script 标签的方式实现跨域请求。服务端返回的数据需要封装在一个回调函数中,该回调函数的名称由前端在请求时指定。

  1. CORS(跨域资源共享):CORS 是一种基于 HTTP 头部的机制,可以让服务器决定哪些源(域名、协议、端口)可以访问其资源,从而实现跨域访问。可以在服务端设置响应头 Access-Control-Allow-Origin,指定允许访问的源,或使用通配符 * 允许所有源访问。

  1. 代理服务器:通过在同域名下的服务器端发起请求,并将请求结果返回给前端,绕过同源策略限制。前端可以通过在服务端设置一个代理服务器来实现跨域请求。

  1. iframe:使用 iframe 标签嵌入一个跨域的页面,通过 window.postMessage() 方法实现跨域通信。在子页面中,可以通过 window.parent.postMessage() 方法将数据发送给父页面。

  1. WebSocket:WebSocket 是一种基于 TCP 协议的双向通信协议,不受同源策略限制。可以通过 WebSocket 进行跨域通信。

浏览器输入URL之后

在浏览器中输入 URL 后,会经过以下几个步骤:

  1. URL 解析

首先,浏览器会解析输入的 URL,将其分为协议、主机、端口、路径、查询参数等多个部分。这个过程被称为 URL 解析。

  1. DNS 解析

接下来,浏览器会对主机名进行 DNS 解析,将主机名解析为对应的 IP 地址。DNS 解析通常由本地 DNS 缓存和远程 DNS 服务器两部分组成。

  1. 建立 TCP 连接

一旦浏览器获得了目标主机的 IP 地址,就会使用 TCP 协议建立与该主机的连接。在建立连接的过程中,会进行三次握手,以确保连接的可靠性和稳定性。

  1. 发送 HTTP 请求

连接建立成功后,浏览器会向服务器发送 HTTP 请求,请求资源的路径、查询参数、请求方法、请求头等信息将被包含在请求中。

  1. 接收 HTTP 响应

服务器接收到请求后,会根据请求的内容生成相应的 HTTP 响应,包括响应状态码、响应头、响应体等信息,并将其发送回客户端。

  1. 渲染页面

浏览器接收到响应后,会解析响应内容,将 HTML、CSS、JavaScript 等资源分别解析、构建、执行,并将最终的渲染结果显示在用户界面上。

需要注意的是,实际上输入 URL 后的过程可能比上述步骤更为复杂,其中可能涉及到缓存、重定向、HTTPS 加密、压缩等多种技术。但总体来说,上述步骤描述了输入 URL 后浏览器的基本行为。

三次握手

三次握手(Three-way Handshake)是指在 TCP/IP 协议中,建立 TCP 连接时的一种可靠的通信方式,用于确保通信的双方都能收发数据。在 TCP 连接建立时,通信的双方需要进行三次握手来确认彼此的状态。

具体来说,三次握手的过程如下:

  1. 客户端向服务器发送 SYN 包(SYN = Synchronize Sequence Numbers,同步序列号),并等待服务器的确认。

  1. 服务器收到客户端的 SYN 包后,向客户端发送 SYN+ACK 包,表示确认收到客户端的请求,并发送自己的序列号。

  1. 客户端收到服务器的 SYN+ACK 包后,向服务器发送 ACK 包,表示确认收到服务器的响应。此时,连接建立成功,客户端和服务器可以开始通信。

通过三次握手,TCP 协议可以确保通信的双方都能收发数据,并可以防止无效连接和错误数据传输。其中,第一次握手是客户端发送 SYN 包,第二次握手是服务器回应 SYN+ACK 包,第三次握手是客户端回应 ACK 包。因此,三次握手也被称为“SYN-SYN-ACK”握手。

两次 四次?

TCP采用三次握手的原因是为了确保连接的可靠性和安全性。

在两次握手的情况下,客户端向服务器发送连接请求后,服务器会返回确认信息,这样看似已经建立连接,但此时客户端并不知道是否真的能够与服务器进行通信,因为确认信息可能是由一个过期的连接响应的。如果此时客户端开始发送数据,但服务器无法收到,因为连接并没有真正建立,这可能导致数据丢失或混乱。

TCP三次握手是为了建立一个可靠的连接,确保双方都能够收到对方的信息。两次握手无法达到这个目的,原因如下:

假设客户端发送SYN到服务端,服务端收到后发送SYN/ACK到客户端,但是由于某种原因(网络问题、延迟等等),客户端没有收到ACK。此时,客户端并不知道连接是否建立成功,因此会重新发送SYN请求。如果只有两次握手,那么服务端会认为客户端建立了连接,但客户端并不知道,因此会再次发送SYN请求,导致连接出现错误。

因此,TCP三次握手确保了双方都能够确认连接的建立,从而避免了类似的错误。

在四次握手的情况下,每次建立连接和关闭连接都需要发送更多的数据包,会增加网络负载和延迟。而在三次握手中,通过发送和接收三个数据包,可以确保双方都认可对方,并且开始传输数据的准备工作,从而确保连接的可靠性和安全性。

四次挥手

四次挥手(Four-way Handshake)是 TCP 协议用于终止一个已建立的连接的方法,以确保数据在通信双方之间的完整性、正确性和可靠性。

四次挥手的过程如下:

  1. 主动方发送 FIN 报文:主动方向被动方发送一个 FIN 报文,告诉被动方主动方已经没有数据要发送了,并请求被动方关闭连接。

  1. 被动方发送 ACK 报文:被动方接收到 FIN 报文后,发送一个 ACK 报文确认已经收到了主动方的 FIN 报文,并进入 CLOSE_WAIT 状态。此时被动方可以继续发送数据,但主动方不能再发送数据。

  1. 被动方发送 FIN 报文:被动方在完成数据发送后,向主动方发送一个 FIN 报文,告诉主动方被动方也已经没有数据要发送了,并请求主动方关闭连接。

  1. 主动方发送 ACK 报文:主动方接收到被动方的 FIN 报文后,发送一个 ACK 报文进行确认,然后进入 TIME_WAIT 状态。此时主动方等待一段时间后,如果没有收到被动方的 ACK 报文,则认为连接已经关闭,否则会重新发送 FIN 报文,重新开始四次挥手的过程。

四次挥手过程中,需要注意避免出现超时、重传等问题,以确保连接的正常终止。

改进,提高用户体验和性能

在输入 URL 后,浏览器会向服务器发送请求,并加载相应的网页资源。在这个过程中,一些基本的行为是不可避免的,比如建立 TCP 连接、发送 HTTP 请求、解析 HTML、执行 JavaScript 等等。

然而,也存在一些可以改进的地方,以提高用户的体验和性能,比如:

  1. DNS 预解析:浏览器可以在加载网页前预先解析网页中包含的域名,以加快 DNS 解析的速度。

  1. 缓存机制:浏览器可以缓存静态资源,如图片、CSS、JavaScript 等,以避免重复加载,从而提高加载速度。

  1. HTTP/2 协议:HTTP/2 是一种新的网络协议,相比于 HTTP/1.1,具有更好的性能和效率,可以实现多路复用、二进制传输等功能,从而提高页面加载速度。

  1. JavaScript 异步加载:在页面加载时,可以将 JavaScript 文件异步加载,以避免阻塞其他资源的加载,从而提高页面的加载速度。

  1. 图片懒加载:可以延迟加载图片,等到图片进入可视区域时再加载,从而减少不必要的网络请求,提高页面性能。

这些改进措施都是针对当前的网络环境和用户需求而提出的,可以帮助提高页面的性能和用户体验。

状态码

HTTP状态码是服务器返回给客户端的一个三位数字代码,用于表示服务器处理请求的结果和状态。常见的HTTP状态码包括:

1xx:信息性状态码,表示服务器已接收到请求,正在处理请求。

  • 100 Continue:服务器已接收请求头,并准备开始传输请求体(仅用于请求体较大时)。

  • 101 Switching Protocols:服务器正在切换协议,客户端需要切换请求方式。

2xx:成功状态码,表示服务器成功处理了请求。

  • 200 OK:请求成功,服务器正常返回数据。

  • 201 Created:请求成功并创建了新的资源。

  • 204 No Content:请求成功,但没有返回任何内容。

3xx:重定向状态码,表示客户端需要执行一些额外的操作才能完成请求。

  • 301 Moved Permanently:资源已被永久移动到新的URL。

  • 302 Found:资源已被临时移动到新的URL。

  • 304 Not Modified:客户端使用缓存数据,请求的资源没有被修改过。

4xx:客户端错误状态码,表示客户端请求有误或请求无法被服务器处理。

  • 400 Bad Request:请求错误,服务器无法理解请求。

  • 401 Unauthorized:需要身份验证或者身份验证失败。

  • 403 Forbidden:请求被禁止访问。

  • 404 Not Found:请求的资源不存在。

5xx:服务器错误状态码,表示服务器在处理请求时发生错误。

  • 500 Internal Server Error:服务器内部错误。

  • 503 Service Unavailable:服务器暂时无法处理请求,一般是由于维护或过载等原因。

HTTP缓存

HTTP缓存机制是指在客户端和服务器之间进行HTTP通信时,用于缓存已请求过的资源的一种机制。它可以有效地减少HTTP请求次数,提高页面的加载速度,节省带宽和服务器资源。

HTTP缓存机制主要包括两种类型:强制缓存和协商缓存。

  1. 强制缓存

强制缓存是指在一定时间内,浏览器直接从缓存中读取数据,不再向服务器发起请求。可以通过设置响应头中的Cache-Control和Expires字段来控制缓存时间。

例如:

Cache-Control: max-age=3600 //缓存时间为3600秒Expires: Sat, 02 Apr 2023 07:30:00 GMT //缓存过期时间为2023年4月2日7:30:00 GMT

  1. 协商缓存

协商缓存是指在强制缓存失效后,浏览器需要向服务器发起请求,由服务器判断资源是否有更新。如果资源没有更新,服务器会返回状态码304 Not Modified,浏览器会直接从缓存中读取数据,否则会返回最新的资源数据。可以通过设置响应头中的ETag和Last-Modified字段来控制协商缓存。

例如:

ETag: "5a3d8a2-1576-453c6d37440c0"Last-Modified: Thu, 14 Dec 2017 06:52:51 GMT

在下一次请求时,浏览器会将这些信息通过请求头中的If-None-Match和If-Modified-Since字段发送给服务器进行判断。

总的来说,HTTP缓存机制可以显著提高Web应用程序的性能,但需要根据具体的应用场景来合理地使用缓存机制,以免出现缓存不一致等问题。

缓存保存在哪里

HTTP缓存的保存位置取决于具体的实现和浏览器的设置,但通常有以下几个位置:

  1. 浏览器缓存:大多数浏览器都会将HTTP缓存保存在本地的浏览器缓存中,以便下次访问相同的资源时能够快速加载。不同的浏览器可能有不同的缓存策略和缓存位置,通常可以在浏览器设置中进行配置。

  1. 代理服务器缓存:当使用代理服务器时,缓存也可以保存在代理服务器中,以便多个客户端共享缓存。

  1. CDN缓存:CDN(内容分发网络)通常会在多个地理位置部署缓存节点,将静态资源缓存到离用户更近的节点上,以提高访问速度和降低带宽消耗。

需要注意的是,由于HTTP缓存是保存在本地客户端或代理服务器中的,因此可能存在缓存不一致的问题。为了避免这种情况,需要根据具体的应用场景和需求来合理地使用缓存机制,并设置适当的缓存时间和缓存控制策略。

页面内容落后?

是的,如果浏览器从缓存中加载页面而不是从服务器重新请求,那么页面的内容可能会落后于服务器上的最新内容。这是因为缓存的页面可能已经过时,而服务器上的页面已经更新。

为了解决这个问题,浏览器通常使用一些技术来确保缓存中的内容与服务器上的内容保持同步。其中一种常见的技术是使用缓存控制头来控制缓存的行为。例如,服务器可以在HTTP响应头中包含Cache-Control头,该头告诉浏览器应该如何缓存页面内容。如果Cache-Control头指定了“no-cache”或“must-revalidate”,浏览器将强制在每次请求时从服务器重新获取页面内容,即使页面已经缓存在本地。这可以确保浏览器中的页面内容始终与服务器上的最新版本保持同步。

另一种常见的技术是使用版本号或时间戳等标识符来标识页面的版本。每当服务器上的页面发生更改时,标识符也会更改,从而迫使浏览器重新获取最新版本的页面。这可以确保浏览器中的页面始终是最新的。

HTTP响应头

HTTP响应头是Web服务器在响应HTTP请求时,返回给浏览器的一组元数据信息。它们包含有关响应的各种信息,例如响应的状态码、内容类型、缓存控制、安全策略、跨域访问等。

HTTP响应头是由Web服务器动态生成的,并在HTTP响应中发送给浏览器。它们以“Header-Name: Header-Value”这种键值对的形式组成,每个键值对之间通过换行符分隔。

以下是一些常用的HTTP响应头:

  • Content-Type: 表示响应的MIME类型,用于告诉浏览器如何解析响应体的数据。

  • Content-Length: 表示响应体的长度,用于告诉浏览器何时停止接收响应体的数据。

  • Cache-Control: 用于指定缓存控制策略,例如no-cache、max-age等。

  • ETag: 表示响应资源的唯一标识符,用于实现条件请求和缓存验证。

  • Expires: 表示响应的过期时间,用于告诉浏览器何时应该重新请求资源。

  • Last-Modified: 表示资源的最后修改时间,用于实现条件请求和缓存验证。

HTTP响应头可以通过Web服务器或应用程序代码动态生成,并且可以通过修改Web服务器配置或应用程序代码进行配置和优化。正确配置和使用HTTP响应头可以提高网站的性能、安全性和可靠性。

浏览器渲染

浏览器渲染页面的过程通常包括以下几个步骤:

  1. 解析 HTML:当用户输入 URL 后,浏览器会向服务器发送请求,并接收到一个 HTML 文件作为响应。浏览器将解析 HTML 文件,构建 DOM 树。DOM 树表示页面上所有的 HTML 元素和它们之间的关系。

  1. 解析 CSS:浏览器将解析 CSS 文件,构建 CSSOM 树。CSSOM 树表示页面上所有的 CSS 样式规则及其作用的元素。

  1. 合并 DOM 和 CSSOM:浏览器将 DOM 树和 CSSOM 树结合在一起,生成渲染树。渲染树只包含需要显示的页面元素,如文本、图片等。

  1. 布局:浏览器确定渲染树中每个元素的大小、位置和关系。这个过程称为布局或回流。

  1. 绘制:浏览器将渲染树中的元素绘制到屏幕上。这个过程称为绘制或重绘。

  1. 合成:浏览器将多个层合并成一个单一的层,并将其显示在屏幕上。这个过程称为合成。

在浏览器的渲染过程中,布局和绘制是最消耗时间的两个步骤。因此,为了提高页面的渲染性能,可以采取一些措施,如减少 DOM 元素和样式规则的数量、使用 CSS 动画代替 JavaScript 动画等。

重绘与回流

单线程?

是的,大部分浏览器的渲染引擎都是单线程的。

在浏览器中,渲染引擎负责解析HTML、CSS和JavaScript代码,并将它们转换成页面的可视化呈现形式。由于渲染引擎的核心任务是在内存中构建和维护页面的视图层次结构和样式信息,因此它必须是线程安全的,并且不能同时访问和修改同一份数据。

为了保证线程安全性,大部分浏览器的渲染引擎采用了单线程模型,即在一个主线程中运行所有的渲染任务。这意味着,当浏览器开始加载和渲染页面时,它会创建一个主线程,并按照指定的顺序执行各种任务,包括解析HTML、构建DOM树、计算CSS样式、执行JavaScript代码、布局和绘制等。

当主线程执行JavaScript代码时,它会阻塞其他任务的执行,这也是为什么一些复杂的JavaScript代码会导致页面失去响应或变得缓慢的原因。为了避免这种情况,浏览器采用了异步编程模型和事件驱动机制,通过将某些任务放到消息队列中,等待主线程空闲时再进行处理,从而实现任务的并发执行。

需要注意的是,虽然渲染引擎是单线程的,但是浏览器本身是多线程的,例如网络线程、定时器线程、事件线程等。这些线程可以独立运行,与主线程进行通信,并协同工作,实现复杂的浏览器功能和用户体验。

js文件?

在浏览器的渲染过程中,JavaScript文件通常在HTML文件的<head>或<body>部分中的<script>标签中引入。在HTML文档解析过程中,当浏览器遇到<script>标签时,它会暂停HTML的解析,开始加载JavaScript文件,并在加载和执行完JavaScript文件后继续解析HTML文档。

当浏览器遇到<script>标签时,它会首先下载JavaScript文件(如果它尚未被缓存),然后执行JavaScript代码。由于JavaScript代码通常包含对HTML文档的操作,因此浏览器需要在HTML文档解析和渲染过程中根据JavaScript代码来更新页面内容、样式和行为。

在JavaScript文件执行期间,浏览器会在内存中创建一个称为JavaScript引擎的特殊组件,它会执行JavaScript代码,并与其他渲染引擎组件(如布局引擎、绘图引擎)协同工作,实现对页面的动态更新和渲染。

需要注意的是,如果JavaScript文件过大或者包含复杂的逻辑,它的加载和执行可能会占用大量的时间,导致页面变慢或失去响应。因此,在编写JavaScript代码时,需要注意代码的质量和性能,避免出现影响用户体验的问题。

五层模型

  1. 物理层(Physical Layer):物理层负责传输比特流(0 和 1 的电信号),它定义了物理介质(如光纤、铜缆等)、电缆、插头等硬件标准,以及如何将比特流转换成电信号和如何从电信号转换回比特流。例如,在计算机网络中,物理层的例子包括网卡、光纤、网线、RJ45 接口等。

  1. 数据链路层(Data Link Layer):数据链路层负责将物理层传输的比特流封装成帧(Frame),并对帧进行校验、错误检测和纠正等处理。数据链路层还定义了数据的访问方式,如 CSMA/CD(Ethernet)、令牌环等。例如,在计算机网络中,数据链路层的例子包括以太网(Ethernet)、无线局域网(WLAN)、网桥(Bridge)等。

  1. 网络层(Network Layer):网络层负责数据的路由选择、分组(Packet)传输、分组的错误检测和纠正等处理。网络层使用 IP 协议对数据包进行路由选择和传输,它还定义了如何处理分组的丢失和重复等情况。例如,在计算机网络中,网络层的例子包括 Internet Protocol(IP)协议、网关、路由器等。

  1. 传输层(Transport Layer):传输层负责端到端的数据传输,提供可靠性、流量控制、拥塞控制等功能。传输层使用 TCP 协议(面向连接)或 UDP 协议(无连接)进行数据传输,并提供错误检测、重传机制、流量控制等功能。例如,在计算机网络中,传输层的例子包括传输控制协议(TCP)、用户数据报协议(UDP)等。

  1. 应用层(Application Layer):应用层负责定义应用程序与网络之间的接口,为应用程序提供服务,如文件传输、电子邮件、网页浏览等。应用层协议通常使用 TCP 或 UDP 进行数据传输,例如 HTTP 协议用于 Web 页面的传输,SMTP 协议用于电子邮件的传输。例如,在计算机网络中,应用层的例子包括 HTTP 协议、FTP 协议、SMTP 协议、DNS 协议等。

与 OSI 模型相比,五层模型将会话层和表示层合并到了应用层,简化了模型的层次结构,使得它更容易理解和实现。同时,五层模型也更符合 TCP/IP 协议栈的设计和实现,因此在实际的网络应用中更为广泛地使用。

七层模型

OSI七层模型是计算机网络中的一个参考模型,它将网络协议分为七个层次,每个层次都定义了一组规范和标准,用于描述网络通信协议的功能和特性。

以下是七层模型的每个层及其具体例子:

  1. 物理层(Physical Layer):负责将比特流(Bit Stream)从一个节点传输到另一个节点。该层的主要任务是定义物理传输介质和传输速率,以及发送和接收比特流的电气、机械和功能接口标准。例如,RJ-45网线接口、无线电频段、电信电路等都是物理层的实现。

  1. 数据链路层(Data Link Layer):负责将物理层传输的比特流封装成帧(Frame),并对帧进行校验、错误检测和纠正等处理。该层还定义了数据的访问控制方法,以确保多个节点可以共享同一物理介质。例如,以太网、无线局域网(WLAN)、FDDI等都是数据链路层的实现。

  1. 网络层(Network Layer):负责数据的路由选择、分组(Packet)传输、分组的错误检测和纠正等处理。该层的主要任务是在源和目的节点之间选择合适的路径,使得数据能够正确到达目的节点。例如,IP协议、ICMP协议、ARP协议等都是网络层的实现。

  1. 传输层(Transport Layer):负责端到端的数据传输,提供可靠性、流量控制、拥塞控制等功能。该层的主要任务是将应用程序传输的数据拆分成小块,以便网络层传输,并确保这些块在接收端能够被正确地组装成完整的数据。例如,TCP协议、UDP协议都是传输层的实现。

  1. 会话层(Session Layer):负责建立、管理和终止两个节点之间的会话。该层的主要任务是在应用程序之间提供通信和协调服务,例如数据传输的同步、数据传输的检查点等。例如,RPC协议、NetBIOS协议等都是会话层的实现。

  1. 表示层(Presentation Layer):负责将数据在不同系统之间的表示方式进行转换。该层的主要任务是在应用程序之间提供数据格式转换、加密、压缩等服务。例如,ASCII码、EBCDIC码、JPEG图像格式、MPEG视频格式等都是表示层的实现。

  1. 应用层(Application Layer):负责定义应用程序与网络之间的通信接口和协议。该层的主要任务是定义各种网络应用的标准接口,使得不同操作系统和应用程序之间可以进行交互和通信。例如,HTTP协议、FTP协议、SMTP协议、DNS协议等都是应用层的实现。

综上所述,OSI七层模型将计算机网络通信协议分成了七个层次,每个层次都有其特定的功能和任务。各层之间通过一定的协议和标准进行交互和通信,从而实现了计算机之间的数据交换和通信。

TCP和UDP

TCP和UDP都是常用的传输层协议,但它们在很多方面有着不同的特点和适用场景。

  1. 连接性:TCP是面向连接的协议,而UDP是无连接的协议。TCP建立连接之后,通信双方可以通过三次握手协商数据传输的方式,建立可靠的通信信道,数据传输结束后,双方会四次挥手关闭连接。而UDP在发送数据之前不需要先建立连接,直接将数据封装成数据包(Datagram)进行发送,数据接收方也不需要确认是否接收到数据。

  1. 可靠性:TCP是一种可靠的协议,而UDP是不可靠的协议。TCP在传输数据时,会对数据进行分段,并通过序号、确认号、重传机制、流量控制等机制来保证数据的可靠传输。而UDP没有这些机制,如果发送的数据丢失或者出错,就无法进行自动纠正。

  1. 传输速度:UDP比TCP更快。TCP的可靠性机制会使其传输速度相对较慢,因为每次发送数据都需要等待对方确认,这样就会增加网络延迟。而UDP的无连接、无可靠性机制使得它的传输速度更快,适用于实时性要求较高的应用场景,如音视频传输、游戏等。

  1. 数据量:TCP适用于传输大量数据,UDP适用于传输小数据。TCP的分段和流量控制机制使得它适合传输大量数据,而UDP适合传输小量数据,例如DNS查询、SNMP等。

  1. 拥塞控制:TCP拥塞控制较好,UDP没有拥塞控制。TCP在传输数据时会通过拥塞窗口控制机制,根据网络状况动态调整发送数据的速度,以避免网络拥塞。而UDP没有这个机制,容易导致网络拥塞。

综上所述,TCP和UDP在连接性、可靠性、传输速度、数据量、拥塞控制等方面有着明显的不同。在选择使用哪种协议时,需要根据应用场景的具体要求来进行选择。例如,如果需要传输大量数据,需要可靠传输,则选择TCP;如果需要传输实时性要求较高的小数据,则选择UDP。

TCP为什么不能两次

TCP协议不能进行两次握手是因为第三次握手的目的是确保双方都能够接收和发送数据,从而避免因网络延迟、丢包等问题导致的通信故障。如果只进行两次握手,那么双方只能确认对方的接收能力,而无法确认自己的发送能力是否正常,这会增加通信出错的风险。

具体来说,如果只进行两次握手,那么客户端发送的SYN报文可能因为网络延迟等原因被服务端丢弃,导致服务端无法确认客户端的接收能力。此时,客户端认为连接已经建立,开始发送数据,而服务端却无法接收,从而导致通信故障。此外,在网络不稳定或遭受攻击时,也可能会出现类似的问题。

因此,为了确保通信的可靠性,TCP协议需要进行三次握手,即客户端发送SYN报文,服务端回复SYN+ACK报文,客户端再回复ACK报文,才能建立连接。这样双方都能够确认对方的接收和发送能力,从而减少通信故障的风险,保障通信的可靠性和安全性。

TCP拥塞控制

TCP拥塞控制是一种网络流量控制机制,通过动态调整发送方的传输速率,以避免网络拥塞和数据包丢失。TCP拥塞控制主要包括四个机制:慢启动、拥塞避免、快速重传和快速恢复。

  1. 慢启动:TCP连接刚建立时,发送方会以一个比较小的窗口大小进行发送,然后每经过一个往返时间(RTT),窗口大小就会翻倍。这个过程就是慢启动。慢启动的目的是为了避免在网络拥塞时一次性发送过多的数据,导致网络拥塞。

  1. 拥塞避免:当窗口大小达到一定阈值时,TCP进入拥塞避免阶段。在这个阶段,发送方会以一定的速率逐步增加窗口大小,而不是翻倍增加。这个过程的目的是为了避免突然发送大量数据导致网络拥塞。

  1. 快速重传:当接收方收到的数据包不连续或者有丢失的情况时,会发送重复确认(Duplicate ACK)给发送方。如果发送方连续收到三个以上的重复确认,就会认为有数据包丢失,立即重传丢失的数据包,而不是等待超时重传。

  1. 快速恢复:在快速重传的过程中,如果发送方收到三个以上的重复确认,则说明网络出现了拥塞。此时,发送方会将窗口大小减半,并进入快速恢复阶段,等待收到一个新的确认包之后,才会逐步恢复窗口大小。

通过以上四个机制,TCP可以自适应地调整传输速率,以适应网络状况的变化,从而避免了网络拥塞和数据包丢失的问题。

实现UDP转TCP

UDP和TCP是两种不同的传输层协议,它们在传输数据的方式、特点和可靠性上有所不同。因此,UDP转TCP的实现方式主要涉及两个方面:协议转换和数据可靠性保障。

  1. 协议转换

UDP转TCP的第一步是进行协议转换,将UDP协议转换为TCP协议。这可以通过使用转换设备或者代理服务器来实现。在这个过程中,需要将UDP数据包中的相关信息,如源地址、目标地址、端口号、数据内容等,转换为TCP数据包的格式,并将其发送到目标主机的TCP协议栈中。

  1. 数据可靠性保障

TCP是一种可靠的协议,它可以保证数据的可靠传输,而UDP是不可靠的协议,数据传输时可能会出现数据包丢失或者乱序等问题。因此,进行UDP转TCP时需要保证数据的可靠性。

一种可行的方法是在UDP数据包中添加一些额外的信息,例如序列号、校验和等,以提高数据传输的可靠性。这些信息可以在转换为TCP数据包之前进行添加,并在接收端进行校验,以确保数据的完整性和正确性。

另外,还可以采用数据重传的方式来提高数据的可靠性。当出现数据包丢失或者乱序时,可以将数据包进行重传,直到接收方成功接收到所有数据为止。

总之,UDP转TCP需要在协议转换和数据可靠性保障两个方面进行考虑和实现,以保证数据的有效传输和可靠性。

网络传输安全性

网络传输安全性的保障主要涉及两个方面:加密和认证。

  1. 加密

加密是保证网络传输安全的重要手段。通过加密,可以将传输的数据转换为一种密文形式,从而防止被未经授权的人窃取或者篡改。在网络传输中,常用的加密技术包括对称加密和非对称加密。

对称加密使用同一个密钥进行加密和解密,通信双方需要在传输前先协商好密钥,并确保密钥的安全性。对称加密的优点是加密速度快,缺点是密钥管理较为困难。

非对称加密使用一对密钥,即公钥和私钥,公钥用于加密,私钥用于解密。通信双方可以自己生成密钥,公钥可以公开,私钥需要保证安全。非对称加密的优点是安全性较高,密钥管理较为简单,缺点是加密速度较慢。

  1. 认证

认证是保证网络传输安全的另一个重要手段。通过认证,可以确保通信双方的身份和信息的真实性。在网络传输中,常用的认证技术包括数字证书和身份认证协议。

数字证书是一种由第三方机构颁发的电子凭证,用于证明某个公钥的拥有者的身份。数字证书包含了公钥的信息以及与之相关的信息,例如证书的颁发机构、证书持有人的名称、有效期等。通过数字证书,可以确保通信双方的身份和信息的真实性。

身份认证协议是一种用于确认通信双方身份的协议,例如Kerberos和SSL/TLS。通过身份认证协议,可以在通信前进行身份验证,并确保通信双方的身份和信息的真实性。

除了加密和认证技术,网络传输安全还需要考虑其他因素,例如数据完整性、访问控制和防火墙等。综合使用多种技术和手段,可以提高网络传输的安全性和可靠性。

https安全性

HTTPS 使用 SSL(Secure Sockets Layer)或者 TLS(Transport Layer Security)协议来加密通信内容,保证通信内容不被窃听和篡改。具体来说,当客户端发起 HTTPS 请求时,服务器会返回一个数字证书,客户端会验证数字证书的有效性并使用数字证书中的公钥加密一个随机生成的密钥,然后发送给服务器。服务器使用数字证书中的私钥解密这个密钥,然后使用该密钥对通信内容进行加密和解密。

具体来说,HTTPS实现安全性的过程如下:

  1. 客户端向服务器发起HTTPS请求。客户端向服务器发起HTTPS请求时,会在请求头中加入"Upgrade-Insecure-Requests"字段,表明客户端希望将HTTP协议升级到HTTPS协议。

  1. 服务器返回数字证书。服务器向客户端返回数字证书,包含了服务器公钥、证书有效期和颁发机构等信息。客户端通过数字证书验证服务器的身份,并获取服务器公钥。

  1. 客户端生成随机密钥。客户端生成一个随机的会话密钥,用于加密和解密数据。

  1. 客户端使用服务器公钥加密随机密钥。客户端使用服务器公钥加密随机密钥,并将加密后的密钥发送给服务器。

  1. 服务器使用私钥解密随机密钥。服务器使用私钥解密客户端发送的随机密钥,获取会话密钥。

  1. 服务器向客户端返回加密后的响应。服务器使用会话密钥对HTTP响应进行加密,并将加密后的数据发送给客户端。

  1. 客户端使用会话密钥解密响应。客户端使用会话密钥对服务器响应进行解密,并获取原始数据。

通过以上步骤,HTTPS实现了对网络传输过程中数据的加密和认证,防止了数据被窃听和篡改,提高了网络通信的安全性。

CDN(Content Delivery Network)加速

CDN(Content Delivery Network)加速,是一种通过在网络边缘部署节点服务器,将网站的静态内容和动态内容缓存到节点服务器中,从而提高网站访问速度和稳定性的技术。

CDN加速的工作原理如下:

  1. 用户向网站发起请求,DNS服务器解析域名,将请求转发到CDN加速节点。

  1. CDN节点根据用户所在地域、网络环境等条件,选择最优的缓存节点返回内容。

  1. 如果缓存节点没有所需内容,则向源站服务器请求数据,并将数据缓存到节点中。

  1. CDN节点将缓存的数据返回给用户,加速网站访问速度和提高访问稳定性。

CDN加速可以有效地降低网站的访问延迟和网络带宽消耗,提高用户访问体验和网站的可用性。同时,CDN加速也可以防止由于网络攻击、流量突增等原因导致的网站宕机,保障网站的正常运行。

和代理服务器的区别

CDN加速和代理服务器虽然都可以提高网络访问速度和安全性,但是它们的工作原理和应用场景有所不同。

CDN加速主要用于加速网站的访问速度和提高访问稳定性,通过在网络边缘部署节点服务器,将网站的静态和动态内容缓存到节点服务器中,从而提高网站的访问速度和稳定性。CDN加速主要应用于网站静态内容的加速,比如图片、视频、CSS、JS等文件,对于动态内容的加速效果不如代理服务器。

代理服务器主要用于隐藏客户端的真实IP地址,保护客户端的隐私和安全,同时也可以提高网络访问速度。代理服务器的工作原理是将客户端的请求转发到代理服务器,代理服务器再将请求转发到目标服务器,获取服务器返回的数据并将其返回给客户端,客户端无法知道目标服务器的真实IP地址。代理服务器可以实现负载均衡、访问控制、内容过滤等功能,可以应用于不同的场景,比如企业内网、代理访问、内容过滤等。

因此,CDN加速和代理服务器虽然有些相似,但它们的应用场景和功能不同。CDN加速主要用于网站内容的加速和优化,代理服务器主要用于隐藏客户端的真实IP地址和实现网络访问控制等功能。

CDN节点服务器和代理服务器是两个不同的概念,它们的作用和功能也有所不同。

CDN节点服务器是一种在全球范围内建立的分布式服务器集群,用于缓存和分发网络资源,提高网站的访问速度和用户体验。CDN节点服务器通常由CDN服务商管理和维护,能够自动识别用户位置,将用户请求转发到最近的服务器节点,从而减少网络延迟和传输时间,提高网络传输效率和速度。

而代理服务器则是一种网络服务器,用于代理客户端向目标服务器发起请求。代理服务器通常有多种用途,例如加速网络访问、提高安全性、控制网络访问等。代理服务器可以将客户端请求进行过滤和优化,然后将请求转发到目标服务器,从而提高网络传输效率和安全性。

因此,CDN节点服务器和代理服务器的主要区别在于它们的作用和功能不同。CDN节点服务器是一种用于加速网络资源传输的分布式服务器集群,而代理服务器是一种用于代理网络请求的网络服务器,可以对请求进行优化和过滤,提高网络传输效率和安全性。

代理服务器

代理服务器也可以缓存数据,以提高访问速度和降低网络流量消耗。代理服务器缓存的数据可以包括网站的静态文件、动态页面、API接口等内容,当客户端请求访问这些数据时,代理服务器会首先检查本地是否存在缓存的数据,如果存在则直接返回给客户端,否则代理服务器会将请求转发到目标服务器获取数据,并将数据缓存到本地,以便下次请求时直接从本地缓存中获取数据。

代理服务器的缓存功能可以提高网站的访问速度,减轻目标服务器的压力,节省网络流量,同时也可以增强网站的安全性,比如通过代理服务器缓存API接口数据,可以减少对目标服务器的直接访问,降低被恶意攻击的风险。

需要注意的是,代理服务器的缓存时间需要根据实际情况进行设置,过长的缓存时间可能会导致数据过期,而过短的缓存时间则可能会降低访问速度。同时,代理服务器缓存数据时还需要考虑缓存容量的限制,过大的缓存容量会占用过多的磁盘空间,过小的缓存容量则可能导致缓存命中率降低。

HTML/CSS

原生JavaScript DOM的属性

在原生的 JavaScript DOM(Document Object Model)中,每种类型的节点都有一些特定的属性。对于元素节点,一些常见的属性包括:

1. `innerHTML`:获取或设置一个元素的 HTML 内容,包括所有标签。例如,对于`<div><p>Hello</p></div>`,`innerHTML` 将会返回 `<p>Hello</p>`。

2. `innerText`:获取或设置一个元素的文本内容,它和 `textContent` 类似,但会考虑样式(例如 `display: none`)和脚本(例如 `<script>`)影响的可见性。

3. `outerHTML`:获取或设置一个元素的包括自身在内的完整 HTML,例如 `<div><p>Hello</p></div>` 的 `outerHTML` 将会返回 `<div><p>Hello</p></div>`。

4. `className` 和 `classList`:获取或设置元素的类。`className` 是一个字符串,包含所有类名,类名之间用空格分隔。`classList` 是一个类名的列表,提供了一些方法如 `add`, `remove`, `toggle`, `contains` 用于操作类。

5. `id`:获取或设置元素的 ID。

6. `style`:获取或设置元素的行内样式。这是一个对象,包含了 CSS 属性和值。例如,`element.style.color = 'red'` 将会设置元素的文字颜色为红色。

7. `attributes`:获取一个包含元素所有属性的列表。

8. `parentElement`, `children`, `nextSibling`, `previousSibling`:这些属性用于导航 DOM 树。

9. `dataset`:用于读写元素的 `data-*` 属性。

这些都是常见的属性,但实际上还有更多。你可以在 MDN Web Docs 上查看所有的属性:https://developer.mozilla.org/en-US/docs/Web/API/Element

另外,除了属性之外,元素节点还有很多方法,例如 `addEventListener`, `removeEventListener`, `appendChild`, `removeChild`, `getAttribute`, `setAttribute` 等等,用于处理事件,操作 DOM 结构和元素属性等。

target

在 JavaScript 中,`target` 常常被用在事件处理的上下文中,表示触发事件的元素。例如,如果你点击一个按钮,那么在相关的 `click` 事件处理函数中,`event.target` 将引用被点击的那个按钮元素。

以下是一个基本的例子,展示了如何使用 `event.target`:

document.querySelector('button').addEventListener('click', function(event) {
  console.log(event.target);  // 输出被点击的按钮元素
});

在这个例子中,我们给一个按钮添加了一个 `click` 事件监听器。当按钮被点击时,浏览器将创建一个事件对象,并将其作为参数传递给事件处理函数。这个事件对象有一个 `target` 属性,表示触发事件的元素。

表单

label:for属性,将标签链接到表单控件,它的属性引用对应的表单控件(input,textarea等)的id

表单控件的name:提供给浏览器/服务器

轮播

  1. 获取Carousel组件的DOM元素

const myCarousel = document.querySelector('#carouselExampleControls');
  1. 初始化Carousel组件,启用自动轮播

const carousel = new bootstrap.Carousel(myCarousel, {
  interval: 2000, // 每张图片轮播的时间间隔,单位为毫秒wrap: true, // 是否循环播放
});
  1. 监听Carousel组件的slide事件,在轮播切换时执行自定义代码

myCarousel.addEventListener('slide.bs.carousel', function (event) {

const prevSlideIndex = event.from;

const nextSlideIndex = event.to;

console.log(`Slide changed from ${prevSlideIndex} to ${nextSlideIndex}`);

});

在slide.bs.carousel事件处理程序中,可以使用event对象获取从哪个幻灯片切换到哪个幻灯片的信息,并执行自定义代码,例如改变幻灯片的标题、描述等。

  1. 控制Carousel组件的轮播通过JavaScript代码可以控制Carousel组件的轮播,包括手动切换到指定的幻灯片、启动、停止自动轮播等操作。

手动切换到指定的幻灯片:

javascriptCopy codecarousel.to(2); // 切换到第3个幻灯片

启动、停止自动轮播:

javascriptCopy codecarousel.cycle(); // 启动自动轮播

carousel.pause(); // 停止自动轮播

  1. 为Carousel组件添加事件监听器可以为Carousel组件添加事件监听器,例如click、mouseenter、mouseleave等,以实现交互效果。示例代码如下:

javascriptCopy codeconst carouselItems = myCarousel.querySelectorAll('.carousel-item');

carouselItems.forEach((item) => {

item.addEventListener('click', function () {

carousel.to(this);

});

});

标准盒模型和怪异盒模型

是用来描述HTML元素大小和布局的两种不同的盒模型。它们的主要区别在于计算元素的宽度和高度时包含的内容不同。

  1. 标准盒模型

在标准盒模型中,元素的宽度和高度包括内容区、内边距(padding)和边框(border)。当设置元素的宽度和高度时,实际指定的是内容区的宽度和高度,而内边距和边框会自动增加到指定的宽度和高度之外。

例如,如果一个元素的指定宽度为300px,内边距为20px,边框为2px,那么这个元素的实际宽度为324px(300 + 20*2 + 2*2)。

这是W3C的标准盒模型,也是现代浏览器的默认盒模型。

  1. 怪异盒模型

在怪异盒模型中,元素的宽度和高度包括内容区和边框,而不包括内边距。当设置元素的宽度和高度时,实际指定的是包括内边距和边框的宽度和高度,内容区的宽度和高度会自动减少相应的值。

例如,如果一个元素的指定宽度为300px,内边距为20px,边框为2px,那么这个元素的实际宽度为300px(指定的值),而内容区的宽度为256px(300 - 20*2 - 2*2)。

选择器优先级

当多个选择器应用于同一个元素时,它们的优先级将被计算和比较,以决定哪个样式将被应用。下面是CSS选择器的优先级列表,从最高到最低:

  1. !important标记

  1. 内联样式(style属性)

  1. ID选择器 (#id)

  1. 类选择器 (.class)、属性选择器([attribute])和伪类选择器(:hover等)

  1. 元素选择器 (element) 和伪元素选择器(::before, ::after等)

  1. 通用选择器(*)、子选择器(>)、相邻兄弟选择器(+)等关系选择器

请注意,选择器的优先级是基于它们的特定组件数量和类型计算的,因此具有更多特定组件的选择器将具有更高的优先级。例如,一个ID选择器的优先级比多个元素选择器的优先级高。

在CSS中,每个选择器都有一个优先级,这是用于确定哪个规则将应用于元素的重要性级别。优先级是基于选择器中的不同组件的数量和类型来计算的。优先级可以用四个值(0,0,0,0)的形式表示,其中第一个数字表示ID选择器的数量,第二个数字表示类选择器、属性选择器和伪类选择器的数量,第三个数字表示元素选择器和伪元素选择器的数量,第四个数字表示通配选择器和关系选择器的数量。

以下是优先级计算的一些例子:

  • #example:优先级为(1,0,0,0)

  • .example:优先级为(0,1,0,0)

  • div.example:优先级为(0,1,1,0)

  • #example p a:优先级为(1,0,3,0)

  • body #example .example:优先级为(1,2,0,1)

当应用多个规则时,将按照优先级最高的规则进行应用。如果两个或多个规则的优先级相等,则后面的规则将覆盖前面的规则。

flex

  • axis:默认是横轴 可以改变为纵轴(flex-direction: column)可以主轴方向reverse(: row-reverse)

  • flex的父级,它的子组件会在默认方向占据父组件的全部,直到达到子组件的本身大小

  • justify-content:主轴,flex-start/flex-end/center 设置从哪头开始 space-between/space-around 自动均匀分布,不同之处是头尾是否给父级留空 space-evenly around两头只有1/2 evenly则完全even

  • flex-wrap:尽量均匀分布整个父级,先考虑主轴,再往次轴走。加了开启换行/换列!不加就是一行?

  • align-items:次轴,相比justify增加了baseline

  • 子元素的变化实现://放在子元素

  • flex-basis:主轴方向的长度

  • flex-grow:沿主轴方向占满,可以配合max-width,限制最大长度。数字代表了可以占用的空间的比值,越大的能占用更多额外空间

  • flex-shrink:类似

怎么实现把两个div放在一行

未知宽高元素水平垂直居中方法

1. 设置元素相对父级定位`position:absolute;left:50%;right:50%`,让自身平移自身高度50% `transform: translate(-50%,-50%);`,这种方式兼容性好,被广泛使用的一种方式

2. 设置元素的父级为弹性盒子`display:flex`,设置父级和盒子内部子元素水平垂直都居中`justify-content:center; align-items:center` ,这种方式代码简洁,但是兼容性ie 11以上支持,由于目前ie版本都已经很高,很多网站现在也使用这种方式实现水平垂直居中

3. 设置元素的父级为网格元素`display: grid`,设置父级和盒子内部子元素水平垂直都居中`justify-content:center; align-items:center` ,这种方式代码简介,但是兼容性ie 10以上支持

4. 设置元素的父级为表格元素`display: table-cell`,其内部元素水平垂直都居中`text-align: center;vertical-align: middle;` ,设置子元素为行内块`display: inline-block; `,这种方式兼容性较好

项目相关

AJAX

页面已经加载之后,使用js发出请求(APIs);得到信息(JSON/XML),而不是网页。可能并没有点击链接或刷新。

json

名字带js object,但是格式比js的object严格

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值