js大王

148 阅读11分钟
数据类型

原始值类型(值类型/基本数据类型)

  1. number
  2. string
  3. boolean
  4. undefined
  5. null
  6. 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');
}
  1. 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));

对象类型(引用数据类型)

  1. 标准对象 Object
  2. 标准特殊对象 Array、Date、Math、Error、RegExp
  3. 非标准特殊对象 Number、String、Boolean
  4. 可调用/执行对象 「函数」 Function
// 原始值类型
const num = 1
// 非标准特殊对象
const num2 = new Number(1);
WechatIMG223.jpeg
数据类型检测
  1. typeof
  • 所有数据类型值,在计算机底层都是按照 64位的 二进制存储!
    • 如果电脑操作系统是64位就是64,操作系统是32位的就是32位存储
  • typeof是根据二进制值来进行类型检测的
    • 二进制的前三位是000,则被认定为对象,然后再去看有没有实现call方法,如果实现了则返回"function",没有则返回"object"
    • tyepof null === "object" null是64个0,所以返回"object"
  • 原始值类型直接返回相应的字符串类型,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;
    }
})();

  1. 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);
}
  1. constructor
  • 用法类似instanceof,但是constructor也是可以被修改的,所以也可能导致不准确
Number.prototype.constructor = String
console.log((1).constructor === String, "test")
  1. 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 先处理 ![]
  1. 空字符串转数字为0,其他非有效数字字符的字符串都转换为NaN
  2. undefined转数字为NaN,null转数字为0,Symbol转换会报错,BigInt转换去除n
  3. 把对象类型转数字规则:
  • 先调用对象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)这个非标准特殊对象转换为基本数据类型(原始值),
堆栈结构
  1. ECS(Euxction context Stack) 执行上下文栈
  2. GEC(gobal excution context) 全局执行上下文
  3. VO(variable object) 变量对象
  4. GO(Global object) 全局对象也就是window 包含settimeout Math Date 等
  5. FEC(function excution context) 函数执行上下文
  6. 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,代码执行前还会初始化作用域链《函数执行上下文,函数的作用域》,初始化argumentsthis,形参赋值,变量提升
2.函数执行
3.一般情况下函数执行完,所形成的函数上下文会被释放掉

作用域提升:在全局代码执行前,会将全局定义的变量、函数等加入到GO中,但不会赋值,这个过程就叫做作用域提升
代码执行中:才对变量进行赋值,或者执行其他函数

WechatIMG155.jpeg

WechatIMG157.jpeg

循环之间的区别
  1. for循环是自己控制循环过程

    • 基于var声明的时候,for和while性能差不多「不确定循环次数的情况下使用WHILE」
    • 基于let声明的时候,for循环性能更好「原理:没有创造全局不释放的变量」
  2. for of 是遍历键值对的键

    • 让对象具备迭代器iterator规范「具备next方法,每次执行返回一个对象,具备 value/done 属性」
    • 实现了[Symbol.iterator]方法的对象,才可使用for of循环
  3. for in 岁遍历键值对的值

    • 不能迭代Symbol属性、迭代顺序会以数字属性优先、公有可枚举的{一般是自定义属性}属性也会进行迭代
  4. 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方法扩展
  1. for in 遍历出来的是可枚举属性和非symbol的属性,而且会将自有属性和继承自原型的属性都进行遍历

  2. Object.keys 遍历出来的是可枚举属性和非symbol的属性

  3. 0bject.getOwnPropertyNames 获取自身可枚举属性和不可枚举的属性

  4. 0bjectgetOwnPropertySymbols 获取自身可枚举和不可枚举属性的Symbol属性

  5. Reflect.ownKeys 获取自身所有类型的属性,不包括继承自原型的属性

  6. Object.freeze() 被冻结的对象:不能修改成员值、不能新增成员、不能删除现有成员、不能给成员做劫持 Object.defineProperty

  7. - 检测是否被冻结: 0bject.isFrozen(obi) =>true/false

  8. 0bject.seal() 被密封的对象:可以修改成员的值,但也不能删、不能新增、不能劫持

  9. - 检测是否被密封: 0bject.isSealed(obi)

  10. 0bject.preventExtensions(obj) 被设置不可扩展的对象: 除了不能新增成员、其余的操作都可以处理

  11. - 检测是否可扩展: 0bject.isExtensible(obi)

  12. Object.is 底层还是通过===进行判断,但是会对NaN === NaN的情况返回true(正常情况判断NaN===NaN 返回false)

  13. 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() {

    }
  }