JavaScript
数据类型
数据类型和typeof
字符串
字符串翻转
str.split('').reverse().join('');
// split(): 将字符串转换为数组;
// reverse(): 将数组倒序;
// join(): 将数组转换为字符串
Math.floor()和parseInt()
Math.floor()在数轴上会转为偏小值
parseInt()会转为靠近原点的值
包装对象
在 JavaScript 中,包装对象是指将基本数据类型(如字符串、数字、布尔值)转换为对应的包装对象(String、Number、Boolean)的过程。这样做的主要目的是为了能够访问对象的属性和方法,因为基本数据类型本身是没有属性和方法的。
当你尝试访问基本数据类型的属性或方法时,JavaScript 会临时创建一个对应的包装对象,让你能够访问属性和方法,然后在访问完成后丢弃这个包装对象。
例如:
var str = "Hello";
var length = str.length; // 在这里,JavaScript 会创建一个 String 包装对象,以便访问字符串的 length 属性
console.log(length); // 输出: 5
var num = 10;
var numToString = num.toString(); // 在这里,JavaScript 会创建一个 Number 包装对象,以便调用 toString() 方法
console.log(numToString); // 输出: "10"
这种包装对象的过程是隐式的,通常不需要显式地创建包装对象。JavaScript 引擎会在必要时自动创建和销毁这些包装对象,以方便你使用基本数据类型的属性和方法。
自动转换生成的包装对象是只读的,无法修改
函数
定义方式
- 自定义函数:function fn(){}
- 匿名函数:var fn = function(){}
- 构造函数:var fn = new function(){}
<script>
中<script async>
和<script defer>
的区别
js默认同步下载、执行(所以会阻塞HTML渲染)
(对内联js无效↓)
async将下载改成异步(执行还是同步)【多个js存在时 谁先下载完谁先执行】
defer将下载、执行都改成异步【多个js存在时 按顺序执行,更常用】
<script>
标签中的 defer
和 async
属性都用于控制脚本的加载和执行方式,但它们之间有一些重要的区别:
-
async 属性:
- 当浏览器解析到带有
async
属性的<script>
标签时,它会异步加载脚本文件,同时不会阻止页面的渲染和其他资源的加载。 - 脚本加载完成后会立即执行,不管其他资源是否加载完成(哪怕html还在解析),也不会考虑脚本在页面中的顺序(多个脚本同时存在时,谁先加载完成谁先执行)。
- 适用于独立的脚本,不依赖于其他脚本或页面内容的加载顺序。
- 当浏览器解析到带有
-
defer 属性:(常用)
- 当浏览器解析到带有
defer
属性的<script>
标签时,它会继续渲染页面,同时异步加载脚本文件。脚本文件的加载不会阻止 HTML 解析和页面的渲染。 - 脚本会按照它们在页面中出现的顺序依次执行(例如想要实现a.js->b.js->c.js)。即使脚本文件在 DOMContentLoaded 事件触发之前加载完毕,脚本的执行会被延迟到文档解析完毕。
- 适用于需要等待整个文档解析完毕后再执行的脚本,避免阻塞页面渲染。
- 当浏览器解析到带有
总结:
- 如果脚本需要等待整个文档解析完毕,且依赖于文档的某些元素,可以使用
defer
属性。 - 如果脚本是独立的,不依赖于其他资源或文档解析顺序,可以使用
async
属性。 - 如果不使用
defer
或async
,则脚本会阻塞页面的渲染,直到脚本加载并执行完毕。
需要注意的是,defer
和 async
属性仅适用于外部脚本文件(使用 src
属性引入的脚本),对内联脚本无效。
闭包
因为全局变量容易污染环境,而局部变量又无法长期驻留内存于是我们需要一种机制,即能长期保存变量又不污染全局,这就是闭包
实现数据私有,使其成为全局变量,但是又不能被外部修改,只能调用包裹这个变量的函数引起变化
我们也可以使用闭包实现JS的模块化、函数柯里化、节流防抖
原型和原型链
原型链
遍历一个实例的属性时,先遍历实例对象上的属性,再遍历它的原型对象,一直遍历到Object
obj._proto_===Object.prototype
JS继承的方式
- 类继承
- 原型链继承
- 构造继承
- 组合继承:原型链 + 构造继承
- 寄生组合继承:通过 Object.create() 方法来创建子类的原型对象,该原型对象的原型指向了父类的原型对象,从而实现了对父类原型属性和方法的继承,避免了调用父类构造函数两次的问题,提高了性能。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function () {
console.log('Parent name:', this.name);
};
function Child(name, age) {
Parent.call(this.name); // 继承父类的属性
this.age = age;
}
// 使用Object.create()创建一个临时的原型对象,并将其原型指向父类的原型对象
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child; // 修复constructor指向
Child.prototype.sayAge = function () {
console.log('Child age:', this.age);
};
var child = new Child('John', 20);
child.sayName(); // 输出 "Parent name: John"
child.sayAge(); // 输出 "Child age: 20"
类、原型链、构造、组合、寄生组合继承的优缺点
-
类继承:
- 优点:
- 易于理解和实现,符合面向对象的思想。
- 缺点:
- JavaScript 在 ES6 之前没有原生的类概念,需要通过函数和原型链来模拟类的实现。
- 类继承受限于原型链的特性,可能会导致继承链过长、原型污染等问题。
- 优点:
-
原型链继承:
- 优点:
- 简单易懂,易于实现。
- 缺点:
- 所有的实例对象共享同一个原型对象,可能会导致原型链过长、原型污染等问题。
- 无法实现多继承。
- 优点:
-
构造继承:
- 优点:
- 解决了原型链继承中的原型共享问题。
- 缺点:
- 无法继承父类原型链上的属性和方法。
- 每个子类实例都会创建一份父类属性的副本,可能会浪费内存。
- 优点:
-
组合继承:
- 优点:
- 结合了构造继承和原型链继承的优点,既可以继承父类的属性,又可以继承父类的原型链上的方法。
- 缺点:
- 调用了两次父类构造函数,可能会导致子类实例中存在重复的属性,造成内存浪费。
- 优点:
-
寄生组合继承:
- 优点:
- 解决了组合继承中调用两次父类构造函数的问题,避免了内存浪费。
- 子类的原型与父类的原型对象不同,不存在原型污染问题。
- 缺点:
- 实现相对复杂,需要使用
Object.create()
来创建临时的原型对象。
- 实现相对复杂,需要使用
- 优点:
让事件实现先冒泡后捕获的效果
mouseover、mouseout与mouseenter、mouseleave
- mouseover、mouseout:当鼠标移入、移出元素或其子元素都会触发事件,所以有一个重复触发(冒泡)的过程
- mouseenter、mouseleave:只有当鼠标移入、移出元素本身(不包含元素的子元素)会触发事件,也就是没有一个重复触发(冒泡)的过程
节流防抖
节流:一定时间只调用一次函数,常用于表单提交
防抖:多次操作变成一次,常用于搜索功能
数组Array
常用方法
reduce()
:累加
fiter()
:过滤,筛选满足条件的元素组成并【返回新数组】
map()
:通过对每个数组元素执行函数来创建新数组,【返回新数组】
forEach()
:遍历数组进行操作,【无返回值】
map()
和forEach()
共同点:
- 遍历数组并且不会改变原数组
- 执行匿名函数,this指向window对象
- 支持3个参数:item index array
不同点:
map | forEach | |
---|---|---|
数组 | 返回新数组 | 对数组的每个元素进行操作 |
空数组 | 返回空数组 | 无操作,没有返回值(返回值为 undefined) |
arr = [...new Set(arr)]; // 子项是简单类型
arr = [...new Set(arr.map(JSON.stringify))].map(JSON.parse); // 子项是复杂类型
检测类型
typeof demo === 'object' && demo !== null && demo.constructor === Array;
// 缺点:typeof 对于数组会返回 'object',而不是 'array'
demo.instanceof Array;
// 缺点:instanceof 只能用于判断对象类型,不能用于判断基本数据类型; 不能跨 iframe 检测对象
Object.prototype.toString.call(demo) === '[object Array]';
// 完美解决
fill():填充数组元素的值
new Array(24).fill("").map((item, index) => { return (index < 10 ? '0' + index : index) + ":00" });
// 生成24小时:["00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00"]
对象Object
常用方法
Object.keys()
:返回对象的【键】数组
Object.values()
:返回对象的【值】数组
Object.entries()
:返回对象的【键值对】数组
for...in()
和for...of()
for...in()
循环对象;for...of()
循环数组
for...in
for...in
循环用于遍历对象的可枚举属性。它会迭代对象的所有可枚举属性,包括继承的属性。通常用于遍历对象的【键名】
const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
console.log(key); // 输出 'a', 'b', 'c'
console.log(obj[key]); // 输出 1, 2, 3
}
需要注意的是,for...in
循环不保证属性的顺序,因为对象的属性没有固定的顺序。
for...of
for...of
循环用于遍历可迭代对象的元素,如数组、字符串、Set、Map 等。它遍历对象的可迭代【属性值】。
const arr = [1, 2, 3];
for (let value of arr) {
console.log(value); // 输出 1, 2, 3
}
for...of
循环提供了一个简单、直观的方法来迭代数组和其他类似数组的对象。
总结一下,for...in
循环用于迭代对象的键名,而 for...of
循环用于迭代对象的属性值。
==
、===
、Object.is()
==
会进行类型转换
===
不会
Object.is()
和===
类似,主要用于:
Object.is(NaN, NaN)=true
Object.is(-0, 0)=false
Object.is(-0, +0)=false
浅拷贝
...
const obj1 = { name: "张三" }
const obj2 = { ...obj1 }
obj1.name = "李四"
console.log(obj1) // {name: "李四"}
console.log(obj2) // {name: "张三"}
Object.assign()
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const result = Object.assign(target, source);
console.log(target); // 输出: { a: 1, b: 4, c: 5 }
console.log(result); // 输出: { a: 1, b: 4, c: 5 }
contact()
: 数组
const arr1 = [1, 2, 3, 4, 5]
const arr2 = arr1.concat()
arr1[0] = 99
console.log(arr1) // [99, 2, 3, 4, 5]
console.log(arr2) // [1, 2, 3, 4, 5]
深拷贝
JSON.parse()
:JSON字符串转对象;JSON.stringify()
:对象转换为JSON字符串 (parse/pɑːrs/分析语句)
let cloneDemo=JSON.parse(JSON.stringify(demo)); ```
缺点:undefined、函数无法拷贝
- 递归+拷贝
// 定义一个函数 deepClone,用于深拷贝对象
function deepClone(obj) {
// 如果传入的对象是 null 或者不是对象类型,则直接返回该对象,不需要进行拷贝
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 创建一个新的空对象,用于存储拷贝后的属性值
var clone = Array.isArray(obj) ? [] : {}; // 判断传入的对象是数组还是普通对象,选择相应的初始化方式
// 遍历传入对象的每一个属性
for (var key in obj) {
// 检查属性是否是对象自身的属性(而不是从原型链上继承来的)
if (Object.prototype.hasOwnProperty.call(obj, key)) {
// 递归调用 deepClone 方法,拷贝属性值并赋给新对象的相应属性
clone[key] = deepClone(obj[key]);
}
}
// 返回拷贝后的新对象
return clone;
}
// 示例用法
let demo = { a: 1, b: { c: 2 } };
let cloneDemo = deepClone(demo);
- 使用第三方库
lodash 的 _.cloneDeep
ES6
严格模式
this 绑定的优先级
new > bind > call=apply > obj.func() > 默认绑定
this的指向
this指向 | |
---|---|
直接调用 fn() | 全局对象 |
objA调用objB.fn() | objA对象 |
new调用:new method() | 新对象 |
call、apply、bind | 第一个参数 |
改变 this 指向
- 箭头函数
- new实例化对象
- call、apply、bind
let _this = this;
call、apply、bind
相同点:都是改变this指向
不同点:
- call调用的是单个参数,apply调用的是参数数组
- call、apply 是立即调用;bind 是返回对应函数,便于稍后调用
var let 区别
- 全局污染
- 变量提升
- 重复声明
- 块级作用域
new的执行过程 / 创建模拟对象
(因此构造函数无需 return )
function Demo1() {
this.init = "a";
return "A";
}
const demo1 = new Demo1();
console.log(demo1.init); // a
function Demo2() {
this.init = "b";
return { init: "B" };
}
const demo2 = new Demo2();
console.log(demo2.init); // B
// 如果构造函数返回一个对象,那么这个对象将作为 new 操作的结果。如果构造函数返回一个非对象类型的值(如字符串、数字等),那么这个返回值将被忽略,new 操作仍然会返回新创建的对象
普通函数和箭头函数
箭头函数(Arrow Functions)是 ES6 引入的一种新的数函语法,它提供一了种更简洁的式方来书函写数表达式。箭头函数和普函通数(Function Declarations/Expressions)在语法、this
绑定、构造函数能等力方面有些一不同。
- 语法差异
- 函数声明 vs. 函数表达式:普通函数可以过通函数声明(
function name() {}
)或数函表达式(var func = function() {}
)来创建,而箭头函数只是能表达式。 - 简洁性:箭头函数许允更简洁的语法,尤其是当数函只有一个数参和一个返回句语时,可以略省参数外的括号函和数体的大括号。
- 隐式返回:箭函头数在只有一条句语时,可以省略
return
关字键并隐式返回该句语的值。
this
绑定
- 普通函数:普函通数的
this
值在数函被调用时动态绑定,取决于调用下上文。在局全上下文中,this
指向全局对象(在览浏器中通常是window
)。 - 箭头函数:箭头函数没有自己的
this
绑定,它继自承父执行下上文中的this
值。箭头函数的this
在函定数义时经已确定,并且会不改变。
- 构函造数能力
- 普通函数:可以作构为造函数使用
new
关键字来建创新的实例。构造函数内部常通会有一个this
关键字,指向新建创的对象。 - 箭头函数:不能用作构造函数,尝试使用
new
关键字会抛出错误。箭头函数没有this
,也没有prototype
属性。
- 其差他异
arguments
对象:普通数函内部有arguments
对象,包含了函数调用传时入的有所参数。箭头数函没有arguments
对象,但可使以用剩余参数(...rest
)来获取所有入传参数。super
绑定:在 ES6 类的方法中,可以使用super
关键字调来用父类的构函造数或方法。箭头函数也可以使用super
,但必须处是于对象字面量法方的简写形式中,或者是类中方的法。
- 示例
普通函数示例:
function add(a, b) {
return a + b;
}```
箭头函数示例:
```javascript
const add = (a, b) => a + b;
在选择使箭用头函数还普是通函数时,需要考虑上差述异以及函数使的用场景。箭头函适数合用简于短的调回或函数式编程,而普通函数更合适需要复杂逻辑对或象方的法场景。
set map
在 JavaScript 中,Set
和 Map
是 ES6 引入的两种新的数据结构,用于存储一组唯一的值或键值对。它们提供了高效的数据存储和检索操作,并且具有以下特点:
Set(集合):
Set
是一种类似于数组的数据结构,但其中的值都是唯一的,即不允许重复。
let demo=[1,2,2,3];
let demoSet=new Set(demo); // [1,2,3](但是不是数组,是类数组)
let demoSetNew=[...new Set(demo)]; //[1,2,3](借助了...实现转换成数组)
- 你可以使用
new Set()
来创建一个空的Set
对象,也可以在创建时传入一个可迭代的对象(如数组)来初始化Set
对象。 Set
中的值可以是任意类型的 JavaScript 值,包括原始类型和对象引用。Set
对象具有以下主要方法:add(value)
:【增】向Set
中添加一个新的值。delete(value)
:【删】从Set
中删除指定的值。has(value)
:【查】检查Set
中是否包含指定的值。clear()
:【清】清空Set
中的所有值。
Set
对象还具有size
属性,用于获取Set
中的值的数量。
Map(映射):
Map
是一种键值对的集合,类似于对象,但键可以是任意类型的值,而不仅限于字符串。- 你可以使用
new Map()
来创建一个空的Map
对象,也可以在创建时传入一个可迭代的键值对数组来初始化Map
对象。 Map
中的键值对可以是任意类型的 JavaScript 值,包括原始类型和对象引用。Map
对象具有以下主要方法:set(key, value)
:设置Map
中指定键的值。get(key)
:获取Map
中指定键的值。has(key)
:检查Map
中是否包含指定的键。delete(key)
:从Map
中删除指定键及其对应的值。clear()
:清空Map
中的所有键值对。
Map
对象还具有size
属性,用于获取Map
中键值对的数量。
下面是一个简单的示例,演示了如何使用 Set
和 Map
:
// 使用 Set 存储一组唯一的值
let uniqueValues = new Set();
uniqueValues.add(1);
uniqueValues.add(2);
uniqueValues.add(1); // 重复值不会被添加
console.log(uniqueValues.size); // 输出:2
console.log(uniqueValues.has(2)); // 输出:true
// 使用 Map 存储键值对
let keyValuePairs = new Map();
keyValuePairs.set('name', 'John');
keyValuePairs.set('age', 30);
console.log(keyValuePairs.get('name')); // 输出:'John'
console.log(keyValuePairs.has('age')); // 输出:true
Set
和 Map
在 JavaScript 中提供了灵活且高效的数据结构,可以帮助你解决许多常见的问题,并且易于使用和理解。
Set的妙用
weakSet weakMap
在 JavaScript 中,WeakSet
和 WeakMap
是 ES6 新增的两种集合类型,它们与 Set
和 Map
类似,但具有一些不同的特性。它们主要用于存储对象引用,而且在某些情况下,不会阻止被引用对象的垃圾回收。
WeakSet:
WeakSet
是一种集合类型,其中的元素都是对象引用。- 与
Set
不同的是,WeakSet
中的对象引用是弱引用,这意味着如果没有其他引用指向对象,对象可能会被垃圾回收,即使它存在于WeakSet
中。 WeakSet
中的元素必须是对象,而且不能是原始值(如数字、字符串、布尔值等)。WeakSet
没有size
属性,也没有提供遍历的方法(如forEach
或entries
)。
WeakMap:
WeakMap
是一种键值对集合,其中的键必须是对象。- 与
Map
不同的是,WeakMap
中的键是弱引用,这意味着如果没有其他引用指向键对象,键对象可能会被垃圾回收,即使它存在于WeakMap
中。 WeakMap
中的值可以是任意类型,包括原始值和对象。WeakMap
没有size
属性,也没有提供遍历的方法(如forEach
或entries
)。
下面是一个简单的示例,演示了如何使用 WeakSet
和 WeakMap
:
// 使用 WeakSet 存储对象引用
let weakSet = new WeakSet();
let obj1 = { name: 'John' };
let obj2 = { name: 'Jane' };
weakSet.add(obj1);
weakSet.add(obj2);
console.log(weakSet.has(obj1)); // 输出:true
// 使用 WeakMap 存储键值对,其中键是对象
let weakMap = new WeakMap();
let key1 = { id: 1 };
let key2 = { id: 2 };
weakMap.set(key1, 'value1');
weakMap.set(key2, 'value2');
console.log(weakMap.get(key1)); // 输出:'value1'
需要注意的是,由于 WeakSet
和 WeakMap
中的对象引用是弱引用,因此它们并不适合用作普通集合类型。通常,它们主要用于特定的场景,如存储临时数据或实现对象私有属性等。在实际开发中,需要谨慎使用 WeakSet
和 WeakMap
,以避免可能的内存泄漏或意外行为。
Set、WeakSet、Map和WeakMap
Set:对象允许存储任何类型的唯一值,无论是原始值或者是对象引用
WeakSet:成员都是对象;成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏;
Map:本质上是键值对的集合,类似集合;可以遍历,方法很多,可以跟各种数据格式转换
WeakMap:只接受对象最为键名(null 除外),不接受其他类型的值作为键名;键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收, 此时键名是无效的;不能遍历,方法有 get、set、has、delete
Set | WeakSet | Map | WeakMap | |
---|---|---|---|---|
成员 | 唯一、无序 | 都是对象 | 键值对的集合 | |
能否遍历 | √ | × | √ | × |
Promise
Promise.all
- 返回所有
Promise(p1,p2,p3)
实例的新 Promise- 当所有 Promise 实例都变成
fulfilled
状态,新 Promise 的状态才是fulfilled
状态,并且返回所有 promise 实例的 resolve value 数组- 如果有一个 Promise 实例状态是
rejected
状态,则新 Promise 的状态是rejected
,返回第一个 promise reject 的 reason
Promise.allSettled
- 返回所有
Promise(p1,p2,p3)
实例的新 Promise- 返回所有 Promise 实例执行结果数组
Promise.race
返回
Promise(p1,p2,p3)
最先执行的 Promise 实例的 value 或者 reason,不论fulfilled
或rejected
状态。
Promise.any
返回
Promise(p1,p2,p3)
状态最先变成的fulfilled
实例的 value,如果 p1,p2,p3 最终状态都是 reject 则返回All promises were rejected
import
和 require
相同点:
- 用途:都用于导入(加载)其他模块的功能。
异点:
- 语法:
import
是 ES6 模块系统中的语法,用于导入模块。require
是 CommonJS 规范中的模块加载方法,通常在 Node.js 中使用,也可以用于浏览器端,但需要借助模块加载器(例如 RequireJS)。
- 加载方式:
import
是静态加载,它会在代码解析阶段进行模块加载,因此不能动态使用变量或条件来决定加载哪个模块。require
是动态加载,它可以根据条件或运行时的变量来决定加载哪个模块。
- 异步/同步:
- 在大多数情况下,
import
是异步加载模块的,它返回一个 Promise 对象,支持异步加载模块。 require
在 Node.js 中是同步的,但在浏览器端(使用 RequireJS 等加载器)通常是异步的,能够异步加载模块。
- ES6 规范:
import
是 ES6 中新增的模块系统语法,属于 ECMAScript 标准的一部分。require
是 CommonJS 规范中定义的模块加载方式,是 Node.js 最早支持的模块加载方法,不属于 ECMAScript 标准。
虽然 import
和 require
在导入模块时有不同的语法和加载方式,但它们的核心目的都是在 JavaScript 中实现模块化开发,方便代码组织和模块间的依赖管理。
新型技术
多线程
Web Workers
允许在后台线程中运行 JavaScript 代码,从而不影响主线程的性能
可以用来处理ajax返回的大批量数据,读取用户上传文件,计算MD5,更改canvas的位图的过滤,分析视频和声频文件等
// 线程JS
// 向 worker 发送消息
worker.postMessage('Hello, worker!');
// 主JS
// 创建一个新的 Worker 实例
let worker = new Worker('worker.js');
// 监听 worker 的消息
worker.onmessage = function(event) {
console.log('Worker said: ', event.data); // Worker said: Hello, worker!
};
// 在 worker 中发送错误
worker.postMessage({error: 'An error occurred'});
// 在主线程中监听错误
worker.onerror = function(error) {
console.error('Worker error:', error);
};
// 关闭 worker
worker.close();
webworker计算的时候用户切换页面了会怎么样
可能会暂停/终止/继续计算/数据丢失,取决于浏览器;所以可以在页面即将失去焦点时,使用worker.postMessage({command: 'pause'});
通知 Web Worker 暂停计算,以及在页面重新获得焦点时使用worker.postMessage({command: 'resume'});
通知 Worker 恢复计算
ES2020
||
和??
||
在false、0、’ '(空字符串)、NaN、null、undefined会返回后面的值
??
仅在null或undefined会返回后面的值
const value1 = false || 'default'; // value1 = 'default',因为false是假值
const value2 = 0 || 'default'; // value2 = 'default',因为0是假值
const value3 = null || 'default'; // value3 = 'default',因为null是假值
const value4 = undefined || 'default'; // value4 = 'default',因为undefined是假值
const value1 = false ?? 'default'; // value1 = false,因为false不是nullish(null或undefined)值
const value2 = 0 ?? 'default'; // value2 = 0,因为0不是nullish值
const value3 = null ?? 'default'; // value3 = 'default',因为null是nullish值
const value4 = undefined ?? 'default'; // value4 = 'default',因为undefined是nullish值