JS
async & await
async/await其实是Generator的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。通过async关键字声明一个异步函数, await 用于等待一个异步方法执行完成,并且会阻塞执行。 async 函数返回的是一个 Promise 对象,如果在函数中 return 一个变量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。如果没有返回值,返回 Promise.resolve(undefined)
- 相较于
Promise代码可读性高,Promise虽然摆脱了回调地狱,但自身的链式调用会影响可读性。 - 相对
Promise更优雅,传值更方便。 - 对错误处理友好,可以通过
try/catch捕获,Promise的错误捕获⾮常冗余
ES6
1. 块级作用域
let和const:let和const用于声明变量,它们具有块级作用域,只在声明它们的块内有效。const声明常量,一旦赋值就不能再重新赋值(对于引用类型,其内部属性可以修改)。
2. 箭头函数
- 简洁语法:箭头函数提供了更简洁的函数定义方式,适合简单的函数逻辑。它没有自己的
this、arguments、super或new.target,this值继承自外层函数。
3. 模板字符串
- 字符串插值:使用反引号(`)定义模板字符串,可以在字符串中嵌入表达式,使用
${}语法。
4. 解构赋值
-
数组解构:可以从数组中提取值并赋值给变量。
-
对象解构:可以从对象中提取属性并赋值给变量。
5. 默认参数
- 函数参数默认值:在函数定义时可以为参数设置默认值,当调用函数时没有提供该参数,就会使用默认值。
6. 扩展运算符
-
数组扩展:可以将数组展开为多个元素,常用于数组的合并、复制等操作。
-
对象扩展:可以将对象的属性展开,常用于对象的合并、复制等操作。
7. 类和继承
-
类定义:引入了
class关键字,用于定义类,使得 JavaScript 有了更接近传统面向对象语言的类语法。 -
继承:使用
extends关键字实现类的继承。
8. Promise 对象
- 异步处理:
Promise用于处理异步操作,避免回调地狱,使异步代码更易读和维护。它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
9. 模块化
import和export:引入了模块系统,使用export关键字导出模块中的变量、函数或类,使用import关键字导入其他模块的内容。
10. 迭代器和生成器
- 迭代器:允许自定义对象的迭代行为,通过实现
Symbol.iterator方法。 - 生成器:使用
function*定义生成器函数,通过yield关键字暂停和恢复函数的执行。
原型
- prototype : js通过构造函数来创建对象,每个构造函数内部都会一个原型
prototype属性,它指向另外一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。 - proto: 当使用构造函数创建一个实例对象后,可以通过
__proto__访问到prototype属性。 - constructor:实例对象通过这个属性可以访问到构造函数
原型链
原型和原型链的基本概念
在 JavaScript 中,每个函数都有一个 prototype 属性,这个属性指向一个对象,这个对象包含了可以由该函数的所有实例共享的属性和方法。这个对象就是我们所说的原型对象。每个对象都有一个内部的 __proto__ 属性,指向创建该对象的函数的原型对象。这就形成了一个链式的结构,即原型链。
当我们尝试访问一个对象的属性时,如果这个对象本身没有这个属性,那么 JavaScript 会沿着这个对象的原型链向上查找,直到找到这个属性或者到达原型链的末端。
原型链的图解
原型链可以通过图解的方式更直观地理解。在原型链的图解中,我们可以看到:
- 每个构造函数都有一个
prototype属性,它指向一个原型对象。 - 每个实例对象都有一个
__proto__属性,它指向创建该实例对象的构造函数的原型对象。 - 原型对象本身也是一个对象,它的
__proto__属性指向Object.prototype。 Object.prototype的__proto__属性是null,表示原型链的末端。
原型链继承
利用Object.create()方法,将子类的原型指向父类,实现继承父类的方法属性,修改时也不影响父类。
function Parent(name) {
this.name = name;
this.colors = ['red', 'green', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
// 执行父类构造函数
Parent.call(this, name);
this.age = age;
}
// 将子类的原型 指向父类
Child.prototype = Object.create(Parent.prototype);
// 此时的狗早函数为父类的 需要指回自己
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
var child1 = new Child('Tom', 18);
child1.sayName(); // 'Tom'
child1.sayAge(); // 18
闭包
闭包是指有权访问另一个函数作用域中的变量的函数。简单来说,即使该函数已经执行完毕,其作用域内的变量也不会被销毁,而是会被闭包所引用。
原理
在 JavaScript 中,每个函数都会形成一个自己的作用域。当一个函数内部定义了另一个函数时,内部函数会形成一个闭包,它可以访问外部函数的变量和参数。这是因为 JavaScript 的作用域链机制,内部函数的作用域链包含了外部函数的作用域,所以即使外部函数执行结束,其作用域内的变量也不会被垃圾回收机制回收,而是会被闭包保留下来。
作用
- 读取函数内部的变量:通过闭包可以在函数外部访问函数内部的变量。
- 让这些变量的值始终保持在内存中:闭包可以让函数内部的变量的值始终保持在内存中,不会因为函数执行结束而被销毁,从而实现数据的持久化。
缺点
-
创建的变量不能被回收,容易消耗内存,使用不当会导致内存溢出
-
解决: 在不需要使用的时候把变量设为
null
call( ) & bind( ) & apply( )
- 三者都可以用作改变
this指向 call和apply的区别在于传参,call、bind都是传入对象。apply传入一个数组。call、apply改变this指向后会立即执行函数,bind在改变this后返回一个函数,不会立即执行函数,需要手动调用。(注意:连续调用bind时,this上下文由首个bind的参数决定)
浏览器垃圾回收机制
浏览器垃圾回收机制(Garbage Collection,简称 GC)是浏览器自动管理内存的一种机制,其目的是识别并回收不再使用的内存空间,以避免内存泄漏和提高内存使用效率。以下为你详细介绍浏览器垃圾回收机制的相关内容。
内存生命周期
在了解垃圾回收机制之前,需要先了解内存的生命周期,主要分为三个阶段:
- 分配内存:当声明变量、函数、对象等时,浏览器会为它们分配内存。
- 使用内存:即读写内存,也就是使用变量、函数等。
- 释放内存:当这些变量、函数等不再被使用时,需要释放它们所占用的内存,以便内存可以被重新利用。垃圾回收机制主要负责这一阶段。
常见的垃圾回收算法
标记清除算法(Mark and Sweep)
-
原理
- 标记阶段:从根对象(如全局对象
window)开始,遍历所有可以访问到的对象,并将这些对象标记为 “活动对象”。 - 清除阶段:遍历整个内存空间,将未被标记的对象(即不可达对象)视为垃圾,并回收它们所占用的内存空间。
- 标记阶段:从根对象(如全局对象
-
示例
let obj1 = { name: 'object1' };
let obj2 = { name: 'object2' };
obj1.ref = obj2;
obj2.ref = obj1;
// 现在将 obj1 和 obj2 的引用都置为 null
obj1 = null;
obj2 = null;
在上述代码中,原本 obj1 和 obj2 相互引用,但当将它们的引用都置为 null 后,从根对象无法访问到这两个对象,在标记清除算法的下一次执行时,它们将被标记为不可达对象并被清除。
- 缺点:标记清除算法会产生内存碎片,即回收后的内存空间会被分割成许多不连续的小块,当需要分配较大的连续内存空间时,可能会因为找不到足够大的连续空间而导致内存分配失败。
标记整理算法(Mark and Compact)
-
原理
- 标记阶段:与标记清除算法相同,从根对象开始遍历,标记所有活动对象。
- 整理阶段:将所有活动对象向内存的一端移动,使它们连续存储,然后清除掉边界之外的内存空间。
-
优点:解决了标记清除算法产生内存碎片的问题,使得内存空间更加连续,有利于后续的内存分配。
-
缺点:整理阶段需要移动对象,会带来一定的性能开销。
引用计数算法(Reference Counting)
-
原理:每个对象都有一个引用计数,当有一个新的引用指向该对象时,引用计数加 1;当引用被移除时,引用计数减 1。当引用计数为 0 时,该对象被视为垃圾,可以被回收。
-
示例
let obj = { name: 'example' };
let anotherObj = obj;
// 移除引用
obj = null;
anotherObj = null;
在上述代码中,最初 obj 和 anotherObj 都引用同一个对象,该对象的引用计数为 2。当将 obj 和 anotherObj 都置为 null 后,对象的引用计数变为 0,此时该对象可以被回收。
- 缺点:无法处理循环引用的问题。例如,两个对象相互引用,即使它们在其他地方没有引用,它们的引用计数也不会为 0,从而导致内存泄漏。
let obj1 = {};
let obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
在这个例子中,obj1 和 obj2 相互引用,它们的引用计数都为 1,即使它们在其他地方没有引用,引用计数算法也无法回收它们。
现代浏览器的垃圾回收策略
现代浏览器通常会结合多种垃圾回收算法,根据不同的情况选择合适的算法。例如,对于新生代(新创建的对象)和老生代(存活时间较长的对象)采用不同的回收策略:
- 新生代(副垃圾回收器):对象通常存活时间较短,使用标记清除算法或复制算法(将内存分为两个相等的区域,每次只使用其中一个,当该区域满时,将活动对象复制到另一个区域,然后清空当前区域)进行回收,效率较高。
- 老生代(主垃圾回收器):对象存活时间较长,使用标记清除算法和标记整理算法相结合的方式进行回收,以平衡内存碎片和性能开销。
哪些情况会导致内存泄漏
-
意外的全局变量:由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
-
被遗忘的计时器或回调函数:设置了
setInterval定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。 -
脱离
DOM的引用:获取一个DOM元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。 -
闭包:不合理的使用闭包,从而导致某些变量一直被留在内存当中。
浏览器垃圾回收机制是一个复杂的系统,其目的是在保证内存使用效率的同时,尽可能减少对程序性能的影响。开发者在编写代码时,也应该注意避免不必要的内存占用和内存泄漏问题。