数据类型
原始值类型(值类型/基本数据类型)
- number
- string
- boolean
- undefined
- null
- symbol
- 创建唯一值/可以给对象设置唯一的属性名
- Symbol.iterator/aysncIterator/hasInstance/toPrimitive... 是某些js知识底层实现的机制
//利用=比较会转换数据类型,而对象转数字会经历一个详细步骤「Symbol.toPrimitive->valueOf->toString...」
let a = {
i: 0,
[Symbol.toPrimitive]() {
// this->a
return ++this.i;
}
}
if (a == 1 & a == 2 & a == 3) {
console.log('OK');
}
- bigint
- 大数类型
- 超过安全数字后进行运算或访问,结果不准确
// 2的53次方-1 = js中的最大安全数 9007199254740991
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
// 最小安全数 -9007199254740991
const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER;
// 解决方案:服务端返回给客户端的大数按照字符串歌手返回, 客户端转为BigInt进行运算, 传递给服务端时候再转成字符串
const num = BigInt("9007199254740991222222") + BigInt(123323)
const s = num.toString()
// 0.1 + 0.2 输出 0.30000000000000004
// 浮点数计算出新精准度问题丢失问题
// 原因:js所有值在计算底层都是以二进制存储的「浮点数为二进制可能导致出现无限循环的问题」,
// 在计算机底层存储最多64位,所以会舍弃一些值,导致值本身就失去了精准度
// 解决:扩大系数法、第三方库Math.js、big.js等
const coefficient = function coefficient(num) {
num = num + ''
let [, char = ''] = num.split('.')
let len = char.length;
return Math.pow(10, len);//-> 10**len
};
const plus = function plus(num1, num2) {
num1 = +num1;
num2 = +num2;
if (isNaN(num1) || isNaN(num2)) return NaN;
let max = Math.max(coefficient(num1), coefficient(num2));
return (num1 * max + num2 * max) / max;
};
console.log(plus(0.1, 0.2));
对象类型(引用数据类型)
- 标准对象 Object
- 标准特殊对象 Array、Date、Math、Error、RegExp
- 非标准特殊对象 Number、String、Boolean
- 可调用/执行对象 「函数」 Function
// 原始值类型
const num = 1
// 非标准特殊对象
const num2 = new Number(1);
数据类型检测
- typeof
- 所有数据类型值,在计算机底层都是按照 64位的 二进制存储!
- 如果电脑操作系统是64位就是64,操作系统是32位的就是32位存储
- typeof是根据二进制值来进行类型检测的
- 二进制的前三位是000,则被认定为对象,然后再去看有没有实现call方法,如果实现了则返回
"function",没有则返回"object" tyepof null === "object"null是64个0,所以返回"object"
- 二进制的前三位是000,则被认定为对象,然后再去看有没有实现call方法,如果实现了则返回
- 原始值类型直接返回相应的字符串类型,typeof 普通对象/数组对象/正则对象/日期对象返回
"object"
// 检测没有被声明的变量返回"undefined",不会报错
const t = typeof a
(function(){
let utils={};
if(typeof window !== "undefined") window.utils= utils;
if(typeof module=== "object" && typeof module.exports=== "object") {
module.exports = utils;
}
})();
- instanceof
- 检测机制:只要当前类出现在实例的原型链上,结果都是true
- 不能检测基本数据类型,原型的指向可以被修改所以可能不准确
// instanceof的实现
function instance_of(example, classFunc) {
let classFuncPrototype = classFunc.prototype
let proto = Object.getPrototypeOf(example); // example._proto_
while (true) {
if (proto ==== null) {
// Object.prototype._proto__ => null
return false;
}
if (proto === classFuncPrototype) {
//查找过程中发现有,则证明实例是这个类的一个实例
return true;
}
proto = Object.getPrototypeOf(proto);
}
- constructor
- 用法类似instanceof,但是constructor也是可以被修改的,所以也可能导致不准确
Number.prototype.constructor = String
console.log((1).constructor === String, "test")
- Object.prototype.toString.call()
- 返回当前实例所属类的信息
- 例如:[object Array/Number/Math/Symbol]
隐式类型转换
==、isNaN 、数字运算
const isFlag = 1 == "1"
const num = 10 - "2" // 8
// 等价于 isNaN(Number("2")),返回false说明不是NaN,那就是有效数字
const isNumber = isNan("2")
// null --> 0 undefined --> NaN
// 隐式转换过程
let d = new Date();
console.log(Number(d)) // 1689669219871
// 首先 d[Symbol.toPrimitive]("number") 参数 "default"/"string"/"number"
const arr = [111]
console.log(Number(arr))
// 1. 首先 arr[Symbol.toPrimitive] = "undefined"
// 2. 其次 调用 arr.valueOf 获取原始值,但是数组没有原始值拿到的还是 [111]
// 3. 再次 arr.toString() 转为字符串
// 4. 最后 把toString返回的结果转为 Number
const num = new Number(2)
// 首先 arr[Symbol.toPrimitive] = "undefined"
// 其次 调用 arr.valueOf 获取原始值。结束拿到2
// console.log([] == false) true // 同时转为Number类型比较
// console.log(![] == false) true 先处理 ![]
- 空字符串转数字为0,其他非有效数字字符的字符串都转换为NaN
- undefined转数字为NaN,null转数字为0,Symbol转换会报错,BigInt转换去除n
- 把对象类型转数字规则:
- 先调用对象Symbol.toPrimitive这个方法,如果不存在这个方法
- 在调用对象的valueOf获取原始值,如果获取的值不是原始值
- 在调用toString把期转为字符串
- 最后再把字符串基于Number这个方法进行转换
装箱拆箱
// num是基本数据类型,不是对象,按道理来说不能进行"成员访问"
const num = 10
// 默认装箱操作new Number(10) 变为非标准特殊对象,这样就可以调用toFxied了
console.log(num.toFxied(2)) // 10.00
console.log(new Number(5) + 10)
//在操作的过程中,浏览器会把new Number(5)这个非标准特殊对象转换为基本数据类型(原始值),
堆栈结构
- ECS(Euxction context Stack) 执行上下文栈
- GEC(gobal excution context) 全局执行上下文
- VO(variable object) 变量对象
- GO(Global object) 全局对象也就是window 包含settimeout Math Date 等
- FEC(function excution context) 函数执行上下文
- AO(acvition object) 活动对象 包含arguments this 函数的形参 变量
// 电脑内存分为:虚拟内存「内存条」,物理内存「硬盘」
// 浏览器打开一个页面时,首先会从计算机的虚拟内存中分配两块内存出来
// 1.栈内存 Stack 「ECStack」执行环境栈
// 作用:供代码执行,存储声明的变量和基本数据类型的值
// 2.堆内存 Heap
// 作用:存储对象类型的值
// 默认在堆内存中,开辟了一个空间「有一个16进制的地址」用来访问这个空间,这个空间就是 GO (global object) 全局对象,存储了浏览器为js提供的内置API
// 然后,创建一个全局的执行上下文 EC(G) == execution context
// 作用:供全局代码执行的环境,进栈执行,全局的执行上下文其实就是栈内存空间,
// 代码执行过程中,可能会声明变量。所以需要一个存放变量的地方,也称之为(变量对象VO/AO),全局的叫VO(G),函数内的叫AO
// VO(G)全局变量对象:存储声明的变量
// let a = 12
// 第一步:创建值:基本数据类型直接存储在栈内存中即可,对象类型需要中堆内存中重新开辟一块空间,把16进制的地址赋值给变量
// 第二步:声明(declare)变量,「放到变量对象中存储」
// 第三步:把创建的值赋值给声明的变量 「让变量和值关联值一起(建立指针指向)」"定义 defined"
执行上下文中不止包含AO,还有包含spoce chian:这个里面包含了AO和 parent spoce
初始化时,先将创建GO,然后将GO指针保存到全局执行上下文GEC中的VO,如果遇到函数作创建函数上下文FEC,同时有一个变量对象AO里面存放了arguments this 函数的形参 变量等
当查找变量时候的会先从自己的执行上下文中的AO【变量对象】中查找,没有的话则沿着scope chian找到父级作用域的执行上下文中的A0【变量对象】查找
function foo() {
var a = b = 10
// 转化后
var a = 10
b = 10 // b没有通过var声明,则在GO中
}
foo()
console.log(b)
函数创建时:开辟内存空间,创建(定义)函数时就声明了其作用域,把函数体内的代码当做字符串存储,当做普通存储键值对,name 函数名 length 形参个数 prototype 原型对象 __proto__原型链
函数调用时:
1.创建一个函数执行上下文包含AO,代码执行前还会初始化作用域链《函数执行上下文,函数的作用域》,初始化arguments,this,形参赋值,变量提升
2.函数执行
3.一般情况下函数执行完,所形成的函数上下文会被释放掉
作用域提升:在全局代码执行前,会将全局定义的变量、函数等加入到GO中,但不会赋值,这个过程就叫做作用域提升
代码执行中:才对变量进行赋值,或者执行其他函数
循环之间的区别
-
for循环是自己控制循环过程
- 基于var声明的时候,for和while性能差不多「不确定循环次数的情况下使用WHILE」
- 基于let声明的时候,for循环性能更好「原理:没有创造全局不释放的变量」
-
for of 是遍历键值对的键
- 让对象具备迭代器iterator规范「具备next方法,每次执行返回一个对象,具备 value/done 属性」
- 实现了[Symbol.iterator]方法的对象,才可使用for of循环
-
for in 岁遍历键值对的值
- 不能迭代Symbol属性、迭代顺序会以数字属性优先、公有可枚举的{一般是自定义属性}属性也会进行迭代
-
for await of 先遍历的异步请求返回,才会返回后面的异步请求,
function createAsyncIterable(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(delay);
}, delay);
});
}
const asyncIterable = [createAsyncIterable(2000), createAsyncIterable(5000), createAsyncIterable(3000)];
async function main() {
for await (const item of asyncIterable) {
console.log(item);
}
}
main();
入门算法
冒泡排序
const list = [12, 23, 45, 65, 1, 2, 4, 7, 9, 7, 5, 4]
// 冒泡排序
// 一轮轮比较,每一轮都从第一项开始,拿当前项和A和后一项B进行比较,如果A>B则交换位置
function bubbleSort(arr) {
const len = arr.length;
for (let i = 0; i <= len; i++) {
for (let j = 0; j <= len - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
return arr
}
插入排序
// 插入排序
function insertSort(arr) {
// 准备一个新数组,用来存储抓到手里的牌
let handle = []
handle.push(arr[0])
// 从第二张开始抓牌,一直到把牌面上的牌抓光
for (let i = 1; i < arr.length; i++) {
// A是新抓的牌
const A = arr[i]
// 使用A和手里的牌(handle)进行比较
for (let j = handle.length - 1; j >= 0; j--) {
// 如果当前新牌A比手里牌B大了则放到B的后面
if (A > handle[j]) {
// ,因为是从后往前比,所以直接插入到后面位置就可以了,跳出循环进入下一轮
handle.splice(j + 1, 0, A)
break
}
// 已经比到0说明是最小的牌直接放到最前面即可
if (j === 0) {
handle.unshift(A)
}
}
}
return handle
};
快速排序
function quickSort(arr) {
// 4.结束递归(当ary小于等于一项,则不用处理)
if (arr.length <= 1) {
return arr
}
// 1. 找到数组的中间项,在原有的数组中把它移除
const middleIndex = Math.floor(arr.length / 2)
const middleValue = arr.splice(middleIndex, 1)[0]
const arrLeft = [], arrRight = []
for (let i = 0; i < arr.length; i++) {
const current = arr[i];
// 当前项比中间值大则放入右边数组,小的放入左边数组
if (current > middleValue) {
arrRight.push(current)
} else {
arrLeft.push(current)
}
}
// 递归持续让两边数组进行排序处理,一直到左右两边都排好序为止
return quickSort(arrLeft).concat(middleValue, quickSort(arrRight))
}
斐波那契
function fibonacci(n) {
// 前2个位必定是1
if(n <= 1) {
return 1
}
const arr = [1, 1]
// 需要再创建的数量,例如:获取第4位,说明一共有5个索引 n + 1 - 2 = 5-2
let i = n + 1 - 2
while(i > 0) {
// 获取最后两个进行相加
const a = arr[arr.length - 2], b = arr[arr.length - 1]
arr.push(a + b)
i--
}
return arr[arr.length - 1]
}
console.log(fibonacci(5));
// 第二种
function fibonacci2(count) {
function fn (count, current = 1, next = 1) {
if(count === 0) {
return current
}else {
return fn(count - 1, next, current + next)
}
}
return fn(count)
}
console.log(fibonacci2(5));
js方法扩展
-
for in 遍历出来的是可枚举属性和非symbol的属性,而且会将自有属性和继承自原型的属性都进行遍历
-
Object.keys 遍历出来的是可枚举属性和非symbol的属性
-
0bject.getOwnPropertyNames 获取自身可枚举属性和不可枚举的属性
-
0bjectgetOwnPropertySymbols 获取自身可枚举和不可枚举属性的Symbol属性
-
Reflect.ownKeys 获取自身所有类型的属性,不包括继承自原型的属性
-
Object.freeze() 被冻结的对象:不能修改成员值、不能新增成员、不能删除现有成员、不能给成员做劫持 Object.defineProperty
-
- 检测是否被冻结: 0bject.isFrozen(obi) =>true/false
-
0bject.seal() 被密封的对象:可以修改成员的值,但也不能删、不能新增、不能劫持
-
- 检测是否被密封: 0bject.isSealed(obi)
-
0bject.preventExtensions(obj) 被设置不可扩展的对象: 除了不能新增成员、其余的操作都可以处理
-
- 检测是否可扩展: 0bject.isExtensible(obi)
-
Object.is 底层还是通过===进行判断,但是会对NaN === NaN的情况返回true(正常情况判断NaN===NaN 返回false)
-
Object.hasOwnProperty 判断对象自身是否有这个属性
类的基础知识
class Parent {
// 类(函数对象)自身的属性 -> Parent.age Parent.getAge() 把构造函数当做一个普通对象来添加私有的属性或方法
static age = 18
static getAge() {}
num = 10
construtor(name) {
// this -> 当前创建的实例
// 实例对象的私有属性,需要根据参数改变通过this.xxx来定义,不需要参数写死的可以通过 num = 10 这种方式来定义
this.name = name
}
// 等价于 Parent.prototype.getName,是不可枚举的getName
getName() {
}
}