начало
Это содержание в основном является кратким изложением моих собственных слабых мест.В настоящее время я готовлюсь к интервью.Часть содержания будет написана особенно подробно, в то время как часть содержания будет написана меньше, но гарантируется, что многие из содержание галантереи. , многие из них имеют подробные пояснения, и галантерейные товары находятся в конце, продолжайте читать. . . Дорогие братья и сестры, проходящие мимо, я надеюсь, что это будет полезно для вас после прочтения этого.Длинный текст длиной 10 000 символов~~~ Если вы прочитаете все содержимое, считайте меня неудачником! ! !
Let's go!!!
Несколько основных вопросов по JS🆒
Типы данных в Javascript (8 типов)
- Простые типы данных: Number, String, Boolean, undefined, null, Symbol, Bigint (новое в ES2020).
- Сложный тип данных: объект
- Объект имеет несколько подтипов, Array, Function, RegExp, Date — все типы объектов.
распечатка цикла setTimeout
Если вы не используете форму функции немедленного выполнения или пусть, 10 10 будут напечатаны напрямую.Принимая закрытие или блочную область видимости, такой проблемы не будет.
for (var i = 0; i < 10; i++) {
(function (j) {
setTimeout(() => {
console.log(j)
}, 1000)
})(i)
}
Передайте третий параметр таймеру. Таймер может передавать несколько параметров функции таймера. Здесь внешний i передается функции обратного вызова в таймере в качестве параметра.
for(var i = 1;i <= 5; i++){
setTimeout(function timer(j){
console.log(j)
}, 0, i)
}
Используйте let, чтобы указать область действия на уровне блоков.
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
Некоторые холодные знания о setTimeout
-
Из-за механизма очереди сообщений она может не выполняться в установленное вами время.
-
Когда settimeout вкладывает settimeout, система установит кратчайший временной интервал равным 4 мс.
-
Неактивные страницы, минимальный временной интервал settimeout 1000 мс
-
Максимальное время выполнения задержки составляет 2 147 483 647 (32 бита), превышение этого значения приведет к немедленному выполнению таймера.
setTimeout(()=> { console.log('这里会立即执行') } ,2147483648)
Как сгладить массив
//使用ES6中的Array.prototype.flat方法
arr.flat(Infinity)
//使用reduce的方式
function arrFlat(arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? arrFlat(cur) : cur)
}, [])
}
//使用递归加循环的方式
function arrFlat(arr) {
let result = []
arr.map((item, index) => {
if (Array.isArray(item)) {
result = result.concat(arrFlat(item))
} else {
result.push(item)
}
})
return result
}
//将数组先变成字符串,再复原 toString()
//这种方法存在缺陷,就是数组中元素都是Number或者String类型的才能展开
function arrFlat(arr) {
return arr.toString().split(',').map(item=> +item)
}
Дедупликация массива
Определение данных дедупликации
let arr = [1, 1, "1", "1", null, null, undefined, undefined, /a/, /a/, NaN, NaN, {}, {}, [], []]
Давайте сначала рассмотрим несколько способов записи ссылочных типов данных, которые нельзя удалить.
// 使用 Set
let res = [...new Set(arr)]
console.log(res)
Хотя этот метод очень прост, но с помощью этого метода мы можем видеть, что эталонный тип данных в нем не был успешно дедуплицирован, и может быть удален только базовый тип данных.
//使用filter
let res = arr.filter((item, index) => {
return arr.indexOf(item) === index
})
console.log(res)
//使用reduce
let res = arr.reduce((pre, cur) => {
return pre.includes(cur) ? pre : [...pre, cur]
}, [])
console.log(res)
Эти два метода используются так же, как и описанный выше, и ссылочный тип данных нельзя удалить.
Давайте посмотрим, как удалить повторяющиеся значения ссылочных типов
используя объектhasOwnProperty
Метод оценивает, содержит ли объект атрибут, если да, то он будет отфильтрован, а если нет, то будет возвращен в новый массив.
let obj = {}
let res = arr.filter(item => {
if (obj.hasOwnProperty(typeof item + item)) {
return false
} else {
obj[typeof item + item] = true
return true
}
})
console.log(res)
На этот раз вы можете видеть, что эталонный тип данных также успешно удален.
В дополнение к вышеперечисленным методам существуют также некоторые методы обхода цикла, аналогичные
массив в виде массива
Подобный массивуlengthсвойства, но не методы прототипа массива. Например, аргументы, результат, возвращаемый манипулированием DOM, подобен массиву. Так как же превратить массив классов в массив?
Array.from(document.querySelectorAll('div'))
Array.prototype.slice.call(document.querySelectorAll('div'))
[...document.querySelectorAll('div')]
Определение типа данных
typeof 1 // number
typeof '1' // string
typeof undefined // undefined
typeof true // boolean
typeof Symbol() // symbol
Вышеупомянутые типы могут быть обнаружены правильно, но типы справочных данных, кроме функций, будут отображаться какobject
, и дляtypeof null
Слишкомobject
Это ошибка, оставшаяся от истории, и она не была исправлена из опасения повлиять на некоторые существующие веб-проекты.
При проверке ссылочных типов данных используйтеinstanceof
Более того, он будет запрашивать на основе цепочки прототипов и возвращать true, если результат запроса находится в цепочке прототипов.
Object.prototype.toString.call (лучшее решение для определения типов данных)
Вызовите метод toString() для прототипа объекта и измените указатель this вызовом. Верните строку, давайте посмотрим на результаты, возвращаемые восемью типами данных.
function checkType(param) {
return Object.prototype.toString.call(param)
}
console.log(checkType(123)) //[object Number]
console.log(checkType("123")) //[object String]
console.log(checkType(true)) //[object Boolean]
console.log(checkType({ a: 123 })) //[object Object]
console.log(checkType(() => {})) //[object Function]
console.log(Symbol(1)) //Symbol(1)
console.log(null) //null
console.log(undefined) //undefined
Давайте снова обработаем вышеуказанную функцию
function checkType(param) {
return Object.prototype.toString.call(param).slice(8, -1).toLowerCase()
}
console.log(checkType(1)) // number
Разница между Object.is и ===
Object.is на основе строгого равенства исправляет некоторые ошибки особого случая, такие как NaN не равно NaN
function is(x, y){
if(x === y){
// 1/+0 = +Infinity 1/-0 = -Infinity 这两个是不相等的
// 当 x和y都等于0的时候,就对x/0和y/0做判断
return x !== 0 || y !== 0 || x / 0 === y / 0
}
}
Разница между == и === и неявным преобразованием типа данных
=== строго равен, должны быть равны не только значения левой и правой частей, но и тип должен быть равен, например'1'===1
Результат ложный, потому что левая часть — это строка, а правая — число.
== возвращает true, пока значения равны, и使用==时会发生隐式类型转化
, В js, когда оператор работает, если данные с обеих сторон неоднородны, ЦП не может их вычислить.В это время наш компилятор автоматически преобразует данные с обеих сторон оператора в тип данных, преобразуйте его к тому же типу данных, а затем вычислить .
-
Преобразование в тип строки: + конкатенатор строк, например
1 + "1" = "11"
-
Преобразование в числовой тип: ++, -- (операторы самоувеличения и самоуменьшения) + , -, *, /, % (арифметические операторы сложения, вычитания, умножения и деления остатка) >, =,
let i = "1" console.log(++i) // 2
-
Преобразование в логический тип: !(оператор логического отрицания), используйте логическое преобразование, чтобы получить false, за исключением следующих восьми случаев, все остальные случаи преобразуются в true.
0、-0、NaN、undefined、null、“”(空字符串)、false、document.all()
-
Если один из них является объектом, а другой — строкой, числом или символом, объект будет преобразован в строку, а затем сравнен
пример:
//字符串连接符
console.log(1 + 'true')// +是字符串连接符, String(1) + 'true',打印出'1true'
//算术运算符
console.log(1 + true) // +是算术运算符,true被Number(true)->1,打印出2
console.log(1 + undefined) // 1 + Number(undefined) -> 1 + NaN, 打印NaN
console.log(1 + null) // 1 + Number(null) -> 1 + 0,打印出1
//关系运算符
// 一边数字一边字符串,Number("2")
// 2 > 5,打印false
console.log("2" > 5)
// 两边字符串,调用"2".charCodeAt() -> 50
// "5".charAtCode()-> 53, 打印false
console.log("2" > "5")
//多个字符串从左往右匹配,也是调用charCodeAt方法进行比较
//比较"a".charCodeAt() < "b".charCodeAt(),打印false
console.log("abc" > "b")
// 左边第一个"a"和右边第一个"a"的unicode编码相等
// 继续比较两边第二个字符, "b" > "a",打印true
console.log("abc" > "aaa")
//无视上述规则自成体系
console.log(NaN == NaN) // NaN和任何数据比较都是 false
console.log(undefined == undefined) //true
console.log(undefined === undefined) //true
console.log(undefined == null) //true
console.log(undefined === null) //false
Для сложных типов данных, таких как объекты и массивы
Сравнение объектов, массивов и строковых типов: сначала используйте valueOf(), чтобы получить исходное значение, если исходное значение не имеет числового типа, используйте метод toString() для преобразования в строковый тип.valueOf -> toString
//发生了a.valueOf().toString()的转化,打印true
console.log([1,2] == "1,2")
// 发生了a.valueOf().toString()的转化,打印true
let a = {}
console.log(a == "[object Object]")
Преобразование объекта в примитивный тип вызовет встроенный[ToPrimitive]
функция, для которой логика следующая:
- если установлено
Symbol.toPrimitive()
метод, который будет вызываться первым и возвращать данные - перечислить
valueOf()
, который возвращается, если приведен к примитивному типу - перечислить
toString()
, который возвращается, если приведен к примитивному типу - Если исходный тип не возвращается, сообщается об ошибке
Давайте рассмотрим два примера👇
let obj = {
value: 3,
valueOf() {
return 4
},
toString() {
return 5
},
[Symbol.toPrimitive]() {
return 6
},
}
console.log(obj + 1) //打印7
позволять if(a ==1 && a == 2 && a == 3)
учредил
let a = {
value: 0,
valueOf() {
return ++a.value
},
}
// 每次调用这个a对象的时候都会在0的基础上加1,调用3次后就变成了3
console.log(a == 1 && a == 2 && a == 3) //true
Если массив и объект сравниваются с числовым типом, сначала используйте valueOf, чтобы получить исходное значение, если исходное значение не имеет числового типа, вызовите toString, а затем преобразуйте строковый тип в числовой с помощью Number, вызывающий последовательностьvalueOf() -> toString() -> Number()
空数组
Метод toString() получит пустую строку, а空对象
Метод toString() получит строку [object Object]
//发生了这样的转化:Number([].valueOf().toString()),打印true
console.log([] == 0)
//逻辑非运算符优先级大于关系运算符
//空数组转布尔得到true,然后取反得到false
//false = 0 ,打印true
console.log(![] == 0)
//左边:{}.valueOf().toString()得到”[object Object]“,Number(”[object Object]“)->NaN
//右边:!{}得到false ,Number(false) -> 0
//两边不相等,打印false
console.log({} == !{})
//左边:[].valueOf().toString()得到空字符串
//右边:![] 得到false
// Number("") = Number(false) 两边都为0
//打印true
console.log([] == ![])
//因为引用数据类型存储在堆中的地址,左边和右边分别属于两块不同的空间
//他们地址不相同,所以两边不相等
//下面两种情况都打印false
console.log([] == [])
console.log({} == {})
Задокументируйте еще одну возникшую связанную проблему
Следующие три результата печати
//typof null返回的是object
console.log(typeof null)
//从右往左看,先看右边的typeof null整体,返回object之后
//再将整体看成typeof object
//打印结果为string,原因是typeof null返回的是object字符串
console.log(typeof typeof null)
//到这里也是从右往左看,相当于typeof string
//结果打印是string
console.log(typeof typeof typeof null)
реализовать instanceof
function myInstanceof(left,right) {
if(typeof left !== 'object' || left === null) return false
//获取原型
let proto = Object.getPrototypeOf(left)
while(true){
//如果原型为null,则已经到了原型链顶端,判断结束
if(proto === null) return false
//左边的原型等于右边的原型,则返回结果
if(proto === right.prototype) return true
//否则就继续向上获取原型
proto = Object.getPrototypeOf(proto)
}
}
реализовать наследование
Наследование в ES5
//实现一下继承
function Parent() {
this.name = "大人"
this.hairColor = "黑色"
}
function Child() {
Parent.call(this)
this.name = "小孩"
}
Child.prototype = Object.create(Parent.prototype)
//将丢失的构造函数给添加回来
Child.prototype.constructor = Child
let c1 = new Child()
console.log(c1.name, c1.hairColor) //小孩,黑色
console.log(Object.getPrototypeOf(c1))
console.log(c1.constructor) //Child构造函数
let p1 = new Parent()
console.log(p1.name, p1.hairColor) //大人,黑色
console.log(Object.getPrototypeOf(p1))
console.log(p1.constructor) //Parent构造函数
Наследование в ES6
// ES6的继承
class Parent {
constructor() {
this.name = "大人"
this.hairColor = "黑色"
}
}
class Child extends Parent {
constructor() {
super() //调用父级的方法和属性
this.name = "小孩"
}
}
let c = new Child()
console.log(c.name, c.hairColor) //小孩 黑色
let p = new Parent()
console.log(p.name, p.hairColor) //大人 黑色
Как реализовать const в среде ES5
нужно использовать здесьObject.defineProperty(Obj,prop,desc)
этот API
function _const (key, value) {
const desc = {
value,
writable:false
}
Object.defineProperty(window,key,desc)
}
_const('obj',{a:1}) //定义obj
obj = {} //重新赋值不生效
рукописный вызов
//手写call
let obj = {
msg: "我叫王大锤",
}
function foo() {
console.log(this.msg)
}
// foo.call(obj)
//调用call的原理就跟这里一样,将函数挂载到对象上,然后在对象中执行这个函数
// obj.foo = foo
// obj.foo()
Function.prototype.myCall = function (thisArg, ...args) {
const fn = Symbol("fn") // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
thisArg = thisArg || window // 若没有传入this, 默认绑定window对象
thisArg[fn] = this //this指向调用者
const result = thisArg[fn](...args) //执行当前函数
delete thisArg[fn]
return result
}
foo.myCall(obj)
рукописный применить
// 手写apply (args传入一个数组的形式),原理其实和call差不多,只是入参不一样
Function.prototype.myApply = function (thisArg, args = []) {
const fn = Symbol("fn")
thisArg = thisArg || window
thisArg[fn] = this
//虽然apply()接收的是一个数组,但在调用原函数时,依然要展开参数数组
//可以对照原生apply(),原函数接收到展开的参数数组
const result = thisArg[fn](...args)
delete thisArg[fn]
return result
}
foo.myApply(obj)
рукописный переплет
Function.prototype.myBind = function (thisArg, ...args) {
let self = this //这里的this是指向thisArg(调用者)
let fnBound = function () {
//this instanceof self ? this : thisArg 判断是构造函数还是普通函数
//后面的args.concat(Array.prototype.slice.call(arguments))是利用函数柯里化来获取调用时传入的参数
self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(arguments)))
}
// 继承原型上的属性和方法
fnBound.prototype = Object.create(self.prototype)
//返回已经绑定的函数
return fnBound
}
//通过普通函数调用
// foo.myBind(obj, 1, 2, 3)()
//通过构造函数调用
function fn(name, age) {
this.test = "测试数据"
}
fn.prototype.protoData = "原型数据"
let fnBound = fn.myBind(obj, "王大锤", 18)
let newBind = new fnBound()
console.log(newBind.protoData) // "原型数据"
Кроме того, я видел вопрос о привязке ранее, который также включен здесь.
заfoo.bind(A).bind(B).bind(C)
Эта проблема
let obj = { a: 1 }
let obj2 = { a: 2 }
let obj3 = { a: 3 }
let obj4 = { a: 4 }
function foo() {
console.log(this.a)
}
let boundFn = foo.bind(obj).bind(obj2).bind(obj3)
boundFn.call(obj4) //打印结果为1
boundFn.apply(obj4) //打印结果为1
boundFn() //打印结果为1
Отсюда мы видим, чтоbind是永久绑定
, последующие операции не изменят его наведение
Mock реализует новый оператор
Имитирует реализацию нового оператора, передавая конструктор и параметры
function myNew(constructFn, ...args) {
// 创建新对象,并继承构造方法的prototype属性,
//把obj挂原型链上, 相当于obj.__proto__ = constructFn.prototype
let obj = Object.create(constructFn.prototype)
//执行构造函数,将args参数传入,主要是为了进行赋值this.name = name等操作
let res = constructFn.apply(obj, args)
//确保返回值是一个对象
return res instanceof Object ? res : obj
}
function Dog(name) {
this.name = name
this.woof = function () {
console.log("汪汪汪")
}
//构造函数可以返回一个对象
//return { a: 1 }
}
let dog = new Dog("阿狸")
console.log(dog.name) //阿狸
dog.woof() //汪汪汪
let dog2 = myNew(Dog, "大狗")
console.log(dog2.name) //大狗
dog2.woof() //汪汪汪
дросселирование
Дроссель может управлять частотой срабатывания события.Дросселирование похоже на маленькую водопроводную трубу.Если не добавить дросселирование, то вода будет вытекать, но как только будет добавлен дроссельный клапан, вы сможете управлять им самостоятельно.Скорость потока вода увеличивается. После добавления дросселирования вода может измениться с грохота на тиканье. Помещение его в наше событие функции может замедлить запуск события. Например, запуск события может заставить это происходить каждую секунду. Запускается только один раз, что может улучшить производительность.
function throttle(fn, wait) {
let prev = new Date()
return function() {
let now = new Date()
/*当下一次事件触发的时间和初始事件触发的时间的差值大于
等待时间时才触发新事件 */
if(now - prev > wait) {
fn.apply(this, arguments)
//重置初始触发时间
prev = new Date()
}
}
}
Стабилизатор
Anti-shake ограничивает событие, которое не должно запускаться несколько раз в течение определенного периода времени.Например, если вы отчаянно нажмете кнопку, операция будет такой же яростной, как тигр. Но как только анти-дрожание будет добавлено, независимо от того, сколько раз вы нажмете, оно будет выполнено только тогда, когда вы нажмете в последний раз. Anti-shake часто используется для отслеживания таких событий, как поля поиска или полосы прокрутки, что может повысить производительность.
function debounce(fn, wait = 50) {
//初始化一个定时器
let timer
return function() {
//如果timer存在就将其清除
if(timer) {
clearTimeout(timer)
}
//重置timer
timer = setTimeout(() => {
//将入参绑定给调用对象
fn.apply(this, arguments)
}, wait)
}
}
глубокая копия и мелкая копия
мелкая копия: Как следует из названия, так называемая неглубокая копия представляет собой неглубокую копию объекта, копируются только атрибуты одного слоя объекта, а данные ссылочного типа в объекте не включаются. , подобъекты будут влиять друг на друга. , изменение скопированного подобъекта также повлияет на исходный подобъект
глубокая копия: Глубокая копия заключается в копировании объекта и всех подобъектов объекта, то есть свойства в подобъектах вновь скопированного объекта не будут влиять на исходный объект.
Сначала мы определяем объект
let obj = {
a: 1,
b: 2,
c: {
d: 3,
e: 4
}
}
внедрить мелкое копирование
Используйте Object.assign()
let obj2 = Object.assign({}, obj)
obj2.a = 111
obj2.c.e = 555
console.log(obj)
console.log(obj2)
Использовать оператор спреда
let obj2 = {...obj}
obj2.a = 111
obj2.c.e = 555
console.log(obj)
console.log(obj2)
Глядя на результаты, обнаруживается, что a объекта первого слоя не влияют друг на друга, но данные в подобъекте c будут влиять друг на друга.
То же верно и для неглубоких копий массивов.
определить массив
let arr = [1, 2, { a: 3 }]
использоватьArray.prototype.slice()
let arr2 = arr.slice()
arr2[0] = 222
arr[2].a = 333
console.log(arr)
console.log(arr2)
использоватьArray.prototype.concat()
let arr2 = arr.concat()
arr2[0] = 222
arr[2].a = 333
console.log(arr)
console.log(arr2)
Используя оператор спреда...
let arr2 = [...arr]
arr2[0] = 222
arr[2].a = 333
console.log(arr)
console.log(arr2)
Их окончательные результаты печати одинаковы
Мы также можем использовать обход, чтобы написать функцию поверхностного копирования для оценки массивов и объектов.
source: исходный вход
target: целевой вывод
function shallowCopy(source) {
//开头可以判断一下入参是不是一个对象
let target = Array.isArray(source) ? [] : {}
for(let key in source) {
//使用 hasOwnProperty 限制循环只在对象自身,不去遍历原型上的属性
if(source.hasOwnProperty(key)) {
target[key] = source[key]
}
}
return target
}
Реализовать глубокое копирование
Самый простой способ глубокого копирования
let target = JSON.parse(JSON.stringify(source))
Но этот метод поддерживает толькоobject
,array
,string
,number
,true
,false
,null
Эти типы данных или значений не поддерживаются другими типами данных, такими как function, undefined, Date и RegExp. Это свойство просто игнорируется для данных, которые оно не поддерживает.
рекурсивный путь
Поскольку неглубокая копия копирует только свойства слоя объектов, когда есть подобъекты, мы можем рекурсивно вызывать неглубокую копию.
function deepCopy(source) {
//开头这里可以判断入参是不是一个对象
let target = Array.isArray(source) ? [] : {}
for (let key in source) {
if (source.hasOwnProperty(key)) {
//这里我们再做一层判断看看是否有子属性
//这里也可以直接调用上面写过的那个checkType函数进行判断,就不用写两个typeof了
if (source[key] && typeof source[key] !== null && typeof source[key] === "object")
{
target[key] = Array.isArray(source[key]) ? [] : {}
//递归调用
target[key] = deepCopy(source[key])
} else {
target[key] = source[key]
}
}
}
return target
}
Глубокая копия здесь может копировать только простые типы данных. Если это сложный тип данных, процесс копирования приведет к потере данных. Например, копируемый объект содержит регулярные выражения, функции и даты. Их нельзя копировать. Если нам нужно чтобы скопировать их, нам нужно судить о каждом типе отдельно.Кроме того, рекурсивная версия также будет иметь проблему циклической ссылки, такой как obj.obj = obj, так что стек взорвется, если бесконечный цикл продолжится, поэтому мы все еще нужно оптимизировать
Давайте еще раз взглянем на оптимизированную версию.
function deepCopy(source, cache = new Map()) {
if (cache.has(source)) {
//如果缓存中已经有值则直接返回,解决循环调用的问题
return cache.get(source)
}
//当入参属于Object复杂数据类型就开始做子类检测-> Function Array RegExp Date 都属于Object类型
if (source instanceof Object) {
let target
if (source instanceof Array) {
//判断数组的情况
target = []
} else if (source instanceof Function) {
//判断函数的情况
target = function () {
return source.apply(this, arguments)
}
} else if (source instanceof RegExp) {
//判断正则表达式的情况
target = source
} else if (source instanceof Date) {
target = new Date(source)
} else {
//普通对象
target = {}
}
// 将属性和拷贝后的值进行缓存
cache.set(source, target)
//开始做遍历递归调用
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = deepCopy(source[key], cache)
}
}
return target
} else {
//如果不是复杂数据类型的话就直接返回
return source
}
}
Давайте проверим это здесь
let obj = {
a: 1,
b: undefined,
c: null,
d: Symbol(),
e: new Date(),
f: new RegExp("123", "ig"),
g: function () {
console.log("我叫王大锤")
},
h: [1, 2, 3],
i: { a: 1, b: 2 },
}
let obj2 = deepCopy(obj)
obj2.g = function () {
console.log("我不叫王大锤")
}
obj.g() //我叫王大锤
obj2.g() //我不叫王大锤
obj2.h[0] = 111
console.log(obj)
console.log(obj2)
Видно, что на этот раз наша глубокая копия относительно завершена. Конечно, можно провести дальнейшую оптимизацию. Например, производительность цикла for in низкая, и его можно заменить на цикл while в качестве обхода. тут не изменишь.
механизм цикла событий
в среде браузера
задача макроса: включая общий сценарий кода, setTimeout, setInterval, setImmediate, операции ввода-вывода, рендеринг пользовательского интерфейса и т. д.
микрозадачи: Promise.then, MuationObserver
В частности, указывается, чтоnew Promise里面的内容是同步执行的,像new Promise(resolve(console.log('1')))同步执行
, после разрешения .then входит в очередь микрозадач, пожалуйста, продолжайте читать конкретный контент.
__В среде браузера:__Порядок цикла событий, который определяет порядок выполнения кода js. После ввода общего кода (макрозадачи) запустите первый цикл. Затем выполните все микрозадачи. Затем снова начните с макрозадачи, обнаружите, что одна из очередей задач была выполнена, а затем выполните все микрозадачи. Вероятно, сначала выполните код синхронизации, а затем поместите задачу макроса в очередь задач макроса.Если в очереди задач макроса есть микрозадача, поместите ее в очередь задач микрозадач. Очередь задач Очередь микрозадач Когда она пуста, начинается выполнение следующего раунда макрозадач, и цикл является возвратно-поступательным.宏任务 -> 微任务 -> 宏任务 -> 微任务一直循环
.
Давайте рассмотрим еще несколько тем, чтобы улучшить понимание
console.log(1);
setTimeout(() => {
console.log(2);
new Promise((resolve) => {
console.log(3);
resolve();
}).then(() => {
console.log(4);
});
});
new Promise((resolve) => {
console.log(5);
resolve();
}).then(() => {
console.log(6);
});
setTimeout(() => {
console.log(7);
new Promise((resolve) => {
console.log(8);
resolve();
}).then(() => {
console.log(9);
});
});
console.log(10)
первый цикл: Посмотрите на код сверху вниз -> напечатайте 1 (синхронный код), первый setTimeout входит в очередь задач макроса, чтобы дождаться выполнения, а затем выполняется до первого нового промиса, содержимое внутри выполняется синхронно, напрямую печатает 5 , а затем разрешается, Код в then помещается в очередь микрозадач для ожидания выполнения, а когда встречается второй setTimeout, он помещается в очередь макрозадач. Наконец напечатайте 10.
После выполнения задачи макроса сценария выведите 1 -> 5 -> 10 . Затем посмотрите на ситуацию в очереди
очередь макросов | очередь микрозадач |
---|---|
setTimeout1 | then1 |
setTimeout2 |
Находим микрозадачу в очереди микрозадач и выполняем ее.
then1 печатает 6, поэтому第一轮循环结束后打印了1 -> 5 -> 10 -> 6
второй цикл: Выполните setTimeout1 в очереди макрозадач, сначала выполните код синхронизации внутри, распечатайте 2 и 3, а затем войдите в очередь микрозадач.
очередь макросов | очередь микрозадач |
---|---|
setTimeout2 | then2 |
Затем перейдите к выполнению задач в очереди микрозадач, напечатайте 4 и напечатайте второй раунд циклов.2 -> 3 -> 4
Третий раунд: выполнить setTimeout2 в очереди задач макроса, сначала выполнить код синхронизации внутри, чтобы напечатать 7 и 8, затем войти в очередь микрозадач.
очередь макросов | очередь микрозадач |
---|---|
then3 |
Затем перейдите к выполнению задачи в очереди микрозадач, напечатайте 9 и напечатайте третий раунд цикла.7 -> 8 -> 9
Когда очереди макро- и микрозадач пусты, цикл завершается, и окончательный порядок печати следующий:
1 -> 5 -> 10 -> 6 -> 2 -> 3 -> 4 -> 7 -> 8 -> 9
上述结果在浏览器环境(谷歌浏览器86版本)和node v12.18.0环境中测试均一样
Последнее замечание: если вы столкнулисьasync / await
, вы можете понимать await как Promise.then. Потом закрепим очки знаний
console.log('start')
async function async1() {
await async2()
console.log('async1')
}
async function async2() {
console.log('async2')
}
async1()
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('promise')
resolve()
})
.then(() => {
console.log('then1')
})
.then(() => {
console.log('then2')
})
console.log('end')
Содержимое функции async1 приведенного выше кода можно рассматривать как async2(), которое будет выполнено немедленно, а затем содержимое .then войдет в очередь микрозадач.
async function async1() {
Promise.resolve(async2()).then(() => {
console.log('async1')
})
}
Согласно пунктам знаний, которые мы сказали выше, окончательный порядок печати:start -> async2 -> promise -> end -> async1 -> then1-> then2-> setTimeout
В среде узла (v12.18.0)
Механизм цикла событий в среде узла несколько отличается от механизма браузера.
Цикл событий в узле можно условно разделить на следующие этапы: приоритет idle观察者 > I/O观察者 > check观察者
.
- бездействующий наблюдатель: process.nextTick
- Наблюдатель ввода-вывода (опрос): общие обратные вызовы ввода-вывода, такие как сетевой, файловый ввод-вывод, ввод-вывод базы данных и т. д.
- проверить наблюдателя (обнаружение): setImmediate, setTimeout
На самом деле, в дополнение к вышеупомянутым трем этапам, есть еще несколько этапов, подробности вы можете увидеть здесь, но сейчас мы в основном вводим три этапа, упомянутые выше.
Давайте посмотрим на кусок кода и сравним process.nextTick и setImmediate.
process.nextTick(() => {
console.log('1');
})
setImmediate(() => {
console.log('2');
process.nextTick(() => {
console.log('3');
})
})
process.nextTick(() => {
console.log('4');
})
setImmediate(() => {
console.log('5');
})
process.nextTick(() => {
console.log('6');
})
В отличие от очереди макроса и микрозадачи в среде браузера, каждая очередь наблюдателя в узле выполняет свою собственную задачу перед началом следующего этапа выполнения.
Объяснение на официальном сайте: Каждый этап имеет очередь FIFO для выполнения обратных вызовов. Хотя каждый этап является особенным, обычно, когда цикл событий входит в определенный этап, он будет делать все, что характерно для этого этапа, а затем выполнять обратные вызовы в очереди этого этапа до тех пор, пока очередь не будет исчерпана или не будет исчерпано максимальное количество выполненных обратных вызовов. Когда очередь исчерпана или достигнут предел обратного вызова, цикл событий перейдет к следующему этапу и так далее.
Мы рассматриваем каждого наблюдателя как очередь, поэтому давайте посмотрим на порядок постановки в очередь.
свободная очередь | проверить очередь |
---|---|
process1 | setImemediate1 |
process2 | setImemediate2 |
process3 |
Здесь три процесса ставятся в очередь, а затем выполняются три процесса.1 -> 4 -> 6
.
Затем начните выполнение двух setImemediate в очереди проверки. Первый setImemediate сначала печатает 2, затем встречает process.nextTick, непосредственно печатает 3 в функции обратного вызова process.nextTick, а затем запускает второй вызов setImemediate, печатает 5, поэтому порядок печати следующий:2 -> 3 -> 5
Окончательный порядок печати1 -> 4 -> 6 -> 2 -> 3 -> 5
.
Объясните здесь,process.nextTick总是优先于setImemediate执行的
, почему, давайте посмотрим объяснение на официальном сайте
process.nextTick()
Технически не является частью цикла событий. Вместо этого все это будет обработано после завершения текущей операции.nextTickQueue
(очередь nextTick), независимо от текущей фазы цикла событий. Вызывается в любое время на данной фазеprocess.nextTick()
, все перешли наprocess.nextTick()
Обратный вызов для будет разрешен до продолжения цикла событий.
Итак, мы можем знать, почему process.nextTick в setImemediate1 будет выполняться до setImemediate2.
Второй вопрос, который необходимо здесь решить,О setTimeout и setImemediate
- setImmediate() предназначен дляголосованиеПо завершении этапа выполняется сценарий.
- setTimeout() запускает скрипт по истечении минимального порога (мс).
Если эти две вещи запускаются в основном модуле, порядок их выполнения неопределен (зависит от скорости калькулятора), как в следующем коде.
setImmediate(() => {
setImmediate(() => {
console.log('1')
setImmediate(() => {
console.log('2')
})
})
setImmediate(() => {
console.log('3')
setImmediate(() => {
console.log('4')
})
})
})
setTimeout(() => {
console.log('timeout')
}, 0);
Мы можем видеть два разных результата вывода несколько раз, а именно:
timeout -> 1 -> 3 -> 2 -> 4
1 -> 3 -> timeout -> 2 -> 4
Но почему это так, давайте разберемся. Откройте github и найдите исходный код узла, зайдите в lib/internal/timer.js, мы можем увидеть такой кусок кода (в строке 164)
Реализация setTimeout в исходном коде узла означает, что если это значение after не установлено, меньше 1 или больше TIMEOUT_MAX (2^31-1), оно будет принудительно установлено на 1 мс. То есть setTimeout(xxx,0) фактически эквивалентен setTimeout(xxx,1).
Итак, мы можем сделать вывод:setTimeout其实总是优先于setImemediate执行的
! ! ! Однако, если функция обратного вызова setTimeout не успеет выполниться в течение этой 1 мс, setImemediate выполнится первой, мы можем рассмотреть такой пример.
setImmediate(() => {
setImmediate(() => {
console.log('1')
setImmediate(() => {
console.log('2')
})
})
setImmediate(() => {
console.log('3')
setImmediate(() => {
console.log('4')
})
})
})
setTimeout(() => {
console.log('timeout')
}, 0);
for (let i = 0; i < 10000; i++) { }
Мы добавили цикл for после setTimeout, чтобы убедиться, что setTimeout был выполнен до setImemediate.
После выполнения обнаруживается, что каждый отпечатокtimeout -> 1 -> 3 -> 2 -> 4
В результате после выполнения setTimeout сначала выполняется обратный вызов для вывода времени ожидания, а затем выполнение обратного вызова setImemediate блокируется циклом for.Когда цикл for завершается, обратный вызов в setImemediate начинает печатать, поэтому мы можем проверить результат.
Давайте проверим еще один, почему печатается второй результат? 1 -> 3 -> timeout -> 2 -> 4
Выполните этот код на моем компьютере (процессор i7 10-го поколения), чтобы изменить время с 0 на 5
setImmediate(() => {
setImmediate(() => {
console.log('1')
setImmediate(() => {
console.log('2')
})
})
setImmediate(() => {
console.log('3')
setImmediate(() => {
console.log('4')
})
})
})
setTimeout(() => {
console.log('timeout')
}, 5);
После его выполнения здесь обнаруживается, что каждый раз, когда он выполняется, это второй результат. 1 -> 3 -> timeout -> 2 -> 4
, поэтому мы можем выяснить, почему второй результат иногда выводится, когда время таймера установлено на 0, потому что на это будет влиять скорость, с которой процессор компьютера анализирует код.
(Если вы измените 0 на 5 на своем компьютере и не получите такого результата, вы можете попробовать этот 5, а затем изменить его на больший размер, чтобы проверить, влияет ли скорость процессора на порядок выполнения)
Теперь давайте посмотрим на кусок кода для закрепления вышеизложенных знаний.
console.log('start');
async function async1() {
await async2()
console.log('async1')
}
async function async2() {
console.log('async2')
}
async1()
setTimeout(() => {
console.log('timeout');
})
setImmediate(() => {
console.log('immediate');
})
process.nextTick(() => {
console.log('nextTick');
})
new Promise(resolve => {
console.log('promise');
resolve()
})
.then(() => {
console.log('then');
})
console.log('end');
Окончательный напечатанный результат:start -> async2 -> promise -> end -> nextTick -> async1 -> then -> timeout -> immediate
Особое напоминание:process.nextTick занимает исключительно очередь, а очередь process.nextTick имеет приоритет над выполнением очереди микрозадач Promise.then.
process.nextTick(() => console.log(1));
Promise.resolve().then(() => console.log(2));
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4));
//打印结果是 1 -> 3 -> 2 -> 4
Суммировать
В среде браузера он делится на макрозадачи и микрозадачи.
Основные задачи макроса: основной код скрипта, setTimeout, setInterval, setImmediate
Основные микрозадачи: Promise.then, MuationObserver
выполнить первым宏任务队列 -> 微任务队列 -> 循环
В среде узла сначала выполните宏任务队列 -> process.nextTick队列 -> 微任务队列 -> setTimeout -> setImemediate
Включены некоторые темы программирования⌨
Реализовать функцию сна сна
Рассмотрим с точки зрения Promise, async/await, генератора и т. д.
//方法一:
function sleep(time) {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
sleep(1000).then(() => {
console.log('1秒过后执行这里')
})
//方法二:
//在函数中用async await调用上面封装好的sleep方法
async foo() {
await sleep(1000)
console.log('这里在一秒后打印')
}
foo()
//方法三:
//generator实现
function* generatorSleep(time){
yield new Promise(reslove => {
setTimeout(reslove, time)
})
}
generatorSleep(1000).next().value.then(()=>{
console.log('generator实现方式')
})
//方法四:
//通过回调函数来调用 cb->callback
function sleepCb(cb, time) {
if(typeof cb !== 'function') return
setTimeout(cb, time)
}
function foo() {
console.log('1秒后打印这个回调函数')
}
sleepCb(foo, 1000)
Реализовать функцию повтора
Передайте метод, затем выполняйте его время от времени и выполняйте его n раз.
//每隔2s输出一次helloworld,共输出4次 const repeatFunc = repeat(console.log, 4, 2000); //repeatFunc("helloworld")
function repeat(fn, n, interval) {
return (...args) => {
let timer
let counter = 0
timer = setInterval(() => {
counter++
fn.apply(this, args)
if (counter === n) {
clearInterval(timer)
}
}, interval);
}
}
const repeatFn = repeat(console.log, 4, 2000)
repeatFn('helloworld')
Реализовать функцию для преобразования имен подчеркивания в верблюжий регистр.
function formatHump(str) {
if (typeof str !== "string") return false
//将str分割成数组
str = str.split("_") // ["get", "element", "by", "id"]
if (str.length > 1) {
// 从1开始for循环遍历,因为数组第一个字符串的首字母不需要转大写
// 将数组里的每一个字符串第一个字母变成大写
for (let i = 1; i < str.length; i++) {
str[i] = str[i][0].toUpperCase() + str[i].substr(1)
}
//将数组拼接回字符串
return str.join("")
}
}
console.log(formatHump("get_element_by_id")) //getElementById
Реализовать асинхронный планировщик Планировщик с ограничениями параллелизма, выполняющий не более двух задач одновременно.
//异步调度器
class Scheduler {
constructor(maxNum) {
//等待执行的任务队列
this.taskList = []
//当前任务数
this.count = 0
//最大任务数
this.maxNum = maxNum
}
async add(promiseCreator) {
//当当前任务数超出最大任务数就将其加入等待执行的任务队列
if (this.count >= this.maxNum) {
await new Promise(resolve => {
this.taskList.push(resolve)
})
}
this.count++
const result = await promiseCreator()
this.count--
//当其它任务执行完任务队列中还有任务没执行就将其出队并执行
if (this.taskList.length > 0) {
this.taskList.shift()()
}
return result
}
}
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
const scheduler = new Scheduler(2)
const addTask = (time, value) => {
scheduler.add(() => {
return timeout(time).then(() => {
console.log(value)
})
})
}
addTask(1000, "1")
addTask(500, "2")
addTask(300, "3")
addTask(400, "4")
//此处输出2 -> 3 ->1 -> 4
//一开始1、2两个任务进入队列
//500ms时,2完成,输出2,任务3进入队列
//800ms时,3完成,输出3,任务4进入队列
//1000ms时,1完成,输出1
//1200ms时,4完成,输出4
Темы будут добавляться в будущем... (печень не двигается)
Некоторые другие объяснения 🥧
процессы и потоки
Многопоточность может обрабатывать задачи параллельно, но потоки не могут существовать сами по себе и должны запускаться и управляться процессом. Другими словами, процесс — это отец, нить — это сын, а у отца может быть много сыновей. Ниже представлена картинка о потоках: внешний фрейм — это процесс, а внутренний фрейм — это поток.
Процесс - это работающий экземпляр программы. Когда программа запускается, операционная система создает часть памяти для программы, чтобы хранить код, текущие данные и основной поток, выполняющий задачи, который является процессом.
Вот несколько особенностей:
-
Ошибка в любом потоке процесса приведет к сбою всего процесса.
-
Потоки могут обмениваться внутрипроцессными данными
-
После закрытия процесса операционная система освобождает память, занимаемую процессом.
-
Контент между процессами изолирован друг от друга
Текущая многопроцессная архитектура Google Chrome
Последний Google Chrome включает в себя:1 основной процесс браузера (Browser), 1 процесс GPU, один сетевой (NetWork) процесс, несколько процессов рендеринга и несколько процессов подключаемых модулей.
- Процесс браузера: отвечает за отображение страницы, взаимодействие с пользователем, управление дочерними процессами, предоставление хранилища и другие функции.
- Процесс рендеринга: основной задачей является преобразование HTML, CSS и Js в веб-страницы, которые могут взаимодействовать с пользователями Процесс компоновки Blink и движок V8 Js запускаются в этом процессе.по умолчанию, Chrome создаст новый процесс рендеринга для каждой новой вкладки (на него также повлияет тот же сайт, что объясняется в следующем вопросе), каждый процесс рендеринга выполняется в изолированной программной среде безопасности,
- Процесс GPU: реализация 3D-эффекта CSS, отрисовка пользовательского интерфейса веб-страницы.
- Сетевой процесс: отвечает за загрузку сетевых ресурсов страницы.
- Процесс плагина: отвечает за работу плагинов, каждый плагин соответствует потоку. Открытие отдельной темы в основном предназначено для предотвращения сбоя плагина и влияния на веб-страницу.
Что произошло после ввода URL-адреса на страницу, показывающую
Введите URL-адрес
- После того, как пользователь вводит адрес, браузер определяет, является ли введенная информация поиском или веб-сайтом.Если это поисковый контент, он использует поисковую систему по умолчанию для синтеза нового URL-адреса; если введенный URL-адрес соответствует правилам, браузер добавляет протокол к этому содержимому в соответствии с протоколом URL. Синтезирование допустимых URL-адресов, например вход на www.baidu.com, добавит синтез протокола.www.baidu.com
нажмите Ввод
- После того, как пользователь вводит содержимое и нажимает клавишу Enter, на панели навигации браузера отображается состояние загрузки, а на странице также отображается содержимое предыдущей страницы, поскольку запрошенная новая страница еще не ответила на данные.
запрос на сборку
- Браузер формирует заголовки запросов и информацию о строке запроса.
GET /index.html HTTP1.1
, сетевой процесс, который отправляет запрос URL в браузер через межпроцессное взаимодействие (IPC).
кэш поиска
- Перед тем, как сетевой процесс получит URL-адрес и инициирует сетевой запрос, он проверяет, есть ли в браузере файл, который нужно запросить.Если есть локальная кешированная копия, он перехватывает запрос, возвращает локальную копию ресурса и сразу завершает работу. запрос и возвращает 200. Если локального кеша нет, введите процесс сетевого запроса
Подготовьте IP-адрес и порт
- Сетевой процесс запрашивает у DNS возврат IP-адреса и номера порта, соответствующих доменному имени. Если текущая информация о доменном имени была ранее кэширована службой кэширования данных DNS, кэшированная информация возвращается напрямую. В противном случае инициируется запрос на получение IP-адрес и номер порта, извлеченные из имени домена. Если номер порта не указан, используется номер порта по умолчанию, http использует порт 80, а https использует порт 443. Если это https-запрос, вам также необходимо установить TLS-соединение.
Механизм очереди браузера Chrome
- Одно и то же доменное имя может одновременно устанавливать не более 6 TCP-подключений. Если одновременно происходит более 6 запросов от одного и того же доменного имени, оставшиеся запросы будут поставлены в очередь до тех пор, пока текущие запросы не будут выполнены. количество запросов меньше 6, затем установить TCP-соединение напрямую
сделать запрос
- После трехстороннего рукопожатия TCP запрос может быть инициирован, а затем запрос HTTP будет добавлен в заголовок TCP, включая номер исходного порта, номер порта назначения и порядковый номер для проверки целостности данных, и передан на нижний уровень. .
- Сетевой уровень добавляет заголовок IP к пакету данных, включая IP-адрес источника и IP-адрес назначения, и продолжает передачу на нижний уровень.
- Нижний уровень передается на сервер назначения через физическую сеть.
Целевой сервер анализирует запрос
- Сетевой уровень узла сервера назначения получает пакет данных и анализирует заголовок IP, идентифицирует часть данных и начинает передачу вверх на транспортный уровень после распаковки.
- Транспортный уровень получает данные и анализирует TCP-заголовок, идентифицирует порт, распаковывает и передает его на прикладной уровень.
- HTTP уровня приложения анализирует заголовок и тело запроса. Если требуется перенаправление, HTTP напрямую возвращает код состояния ответа HTTP 301 (постоянное перенаправление) или 302 (временное перенаправление) и прикрепляет адрес перенаправления к полю Location заголовка запроса. , Просмотрите сервер для перенаправления. Если это не редирект, сервер
If-None-Match
Значение параметра определяет, был ли обновлен запрошенный ресурс. Если обновления нет, вернуть 304, что сообщает браузеру, что предыдущий кеш все еще можно использовать. В противном случае возвращается новый код состояния данных 200, и сервер может установить его в заголовке ответа.Cache-Control:Max-age=2000(单位:秒)
чтобы позволить браузеру установить время кэширования данных. - Данные, наконец, проходят через
应用层 —> 传输层 —> 网络层 —> 底层 —> 底层 —> 网络层 —> 传输层 —> 应用层
порядок обратно в сетевой процесс браузера - После того, как передача данных завершена, TCP четыре раза сигнализирует о разрыве соединения. Если браузер или сервер добавляет в заголовок запроса
Connection:Keep-Alive
Поле может поддерживать состояние соединения между браузером и сервером, экономя время на повторное установление соединения в следующий раз.
Браузер анализирует данные ответа
- После того, как сетевой процесс браузера получит пакет данных, согласно заголовку ответа
Content-Type
Поле определяет тип данных ответа, и если это тип потока байтов, запрос передается менеджеру загрузки. Если он имеет тип text/html, процесс браузера уведомляется о получении документа для рендеринга. - Процесс браузера получает уведомление, определяет, открыта ли страница B со страницы A, и определяет, являются ли A и B одним и тем же сайтом (имя корневого домена и протокол принадлежат одному и тому же сайту), если условия соблюдены, страница B и страница Поделитесь одним и тем же рендерингом. Если в будущем будет открыто больше страниц в соответствии с теми же правилами сайта, процесс рендеринга страницы А будет повторно использован. Если это не тот же сайт, создайте отдельный процесс рендеринга.
- После того, как браузер получает сообщение о подтверждении отправки, он обновляет статус страницы браузера, включая статус безопасности, URL-адрес, исторический статус вперед и назад, и обновляет страницу.
- Процесс рендеринга запускает рендеринг страницы, анализируется HTML для создания дерева DOM, таблица стилей CSS преобразуется в таблицы стилей, понятные браузеру, и вычисляется стиль узла DOM.
- Создание дерева компоновки и расчет информации о компоновке элементов
- Слой дерева компоновки для создания иерархического дерева
- Создайте список прорисовки для каждого слоя, отправьте его в поток композитинга (принадлежащий процессу рендеринга), поток композитинга разделит слой на плитки и преобразует плитки в растровые изображения в пуле потоков растеризатора.
- Поток композиции отправляет команду DrawQuad блока рисования в процесс браузера, и процесс браузера генерирует и отображает страницу после получения информации.
Как перерабатывается мусор
Пространство памяти разделено на стек и кучу. Данные в стеке уничтожаются всякий раз, когда выполняется контекст выполнения функции, такой как функция showName
showName() {
let name = '王大锤'
console.log(name)
}
Когда эта функция выполняется, движок js создаст свой контекст выполнения и поместит его в стек вызовов. Когда функция будет выполнена, она выскочит из стека, и, наконец, память будет уничтожена.
Для уничтожения памяти в куче требуется помощь сборщика мусора Давайте рассмотрим еще одну функцию.
function bar() {
let obj = {name: '王大锤'} //obj是指向堆中保存这个对象的内存的引用
}
Когда функция bar выполняется, ее контекст выполнения будет помещен в стек, и в функции будет создан объект.В это время переменная obj является переменной ссылочного типа, которая указывает на адрес памяти в куче, и этот адрес памяти в куче хранит{name: '王大锤'}
эти данные. Когда контекст выполнения функции bar выталкивается из стека, переменная obj уничтожается, но объект obj является ссылкой на адрес в куче памяти, и он все еще существует в куче и не был уничтожен. Давайте посмотрим, как движок V8 уничтожает мусорные данные в куче.
молодой и старый
Двигатель V8 делит кучу на две области: молодое поколение и старое поколение. Обрабатывается двумя разными сборщиками мусора в двигателе V8.
- Кайнозой: запас времени выживания
短
Объект. Обычно поддерживает только емкость 1~8M. от двигателя V8副垃圾回收器
иметь дело с - Старое поколение: храните время выживания
长
Объект. Коэффициент мощности поддержки Кайнозой大得多
. от двигателя V8主垃圾回收器
иметь дело с
Рабочий процесс сборщика мусора
- Отметьте место для живых объектов (все еще используемых) и неактивных объектов (для переработки).
- После завершения маркировки освободить память, занятую неактивными объектами.
- Сортировка памяти и частое освобождение объектов вызовут прерывистое пространство (фрагментацию памяти) в памяти, и памяти будет недостаточно, когда в следующий раз потребуется выделить большую непрерывную память.
(主垃圾回收器产生内存碎片,副垃圾回收器不产生内存碎片)
Вторичный сборщик мусора
Маленькие объекты обычно сортируются по новым областям. Хотя места в новом районе не так много, вывоз мусора происходит чаще. Принято новое поколениеScavenge算法
: Разделите пространство новой области пополам, половина — это область объекта, а другая половина — свободная область. Новые добавленные объекты сохраняются в области объектов, а сборка мусора выполняется, когда область объектов почти заполнена. процесс уборки мусора
- спам
- Скопируйте уцелевшие объекты в свободную область и расположите их по порядку, что эквивалентно завершению сортировки памяти.
- После завершения копирования идентификаторы области объекта и свободной области меняются местами. Исходная область объекта становится свободной областью, а свободная область становится областью объекта. Такой обмен ролями можно повторять бесконечно.
- Если уцелевшие объекты после двух сборок мусора перемещаются в старую область (этот процесс называется
对象晋升策略
)
главный сборщик мусора
В дополнение к объектам, продвигаемым новым поколением, некоторые крупные объекты будут непосредственно отнесены к области старого поколения. Объекты в старой жилой зоне имеют две характеристики: они занимают много места и имеют длительный срок жизни.
Основной сборщик мусора использует标记—清除的算法
Выполнение сборки мусора: маркировка начинается с набора корневых элементов и рекурсивно проходит.Элементы, которые могут быть достигнуты, являются активными объектами, а элементы, которые не были достигнуты, являются данными мусора, и нет внешней ссылки для маркировки. Если у объекта нет переменных, ссылающихся на него, он будет удален сборщиком мусора.
Пометка блока адресов памяти несколько раз — алгоритм очистки создает множество несмежных фрагментов памяти, что требует еще одного标记—整理算法
Для обработки процесс маркировки аналогичен алгоритму маркировки-развертки.После маркировки все уцелевшие объекты перемещаются в один конец блока памяти, образуя непрерывный адрес памяти.
полная остановка
js выполняется в основном потоке.После выполнения алгоритма сборки мусора основной поток блокируется.По завершении сборки мусора работа js-скрипта возобновляется.Такая ситуация называется全停顿
. Если память в куче слишком велика, полная сборка мусора может занять более 1 с, что приведет к зависанию страницы. Как правило, явление полной паузы происходит в старом поколении, потому что объекты в нем относительно большие, а сборка мусора занимает много времени. Чтобы уменьшить это явление заикания, двигатель V8 принимает增量标记算法
, пусть логика маркировки сборки мусора и js чередуются до тех пор, пока фаза маркировки не будет завершена.Этот алгоритм используется для разделения большой сборки мусора на множество мелких задач и выполнения вперемежку, чтобы страница не застряла.
окончательное резюме
Ведь на то, чтобы писать вышеперечисленный контент у меня ушло пять дней.Каждый вечер после занятий я возвращался в общежитие, чтобы писать и писать. Через некоторое время я продолжу дополнять компьютерную сеть и какой-то другой контент. . Если есть какой-либо контент, который написан неправильно, пожалуйста, укажите на это, и я исправлю это вовремя! (Скромно учиться у больших парней)
Чтобы написать вышеуказанный контент, я также сослался на множество статей, различных поисков и расследований Baidu, собрал все точки знаний, которые я считаю слабыми, и, наконец, написал эту статью длиной в 10 000 символов, содержание которой в основном мой родной.Написано построчно,кодовое слово не простое,Я надеюсь, что все красивые парни и красотки, проходящие мимо, могут поднять большой палец вверх после прочтения, Всем мяч!
обновить:
Компьютерная сеть сделала несколько сводок, добро пожаловать на чтение
От транспортного уровня к прикладному уровню
Справочная статья
Фронтенд Основные добавки 90 вопросов
На этот раз освойте глубокую копию
Конечно, в дополнение к этим статьям он также включает различные другие статьи из Baidu, а также ссылается на другие вопросы интервью и, наконец, резюмирует их.
Done~