20230313----重返学习-数据类型深入-symbol类型深入-typeof深入及在实战中的运用-内存类型-JavaScript进制转换

day-026-twenty-six-20230313-数据类型深入-symbol类型深入-typeof深入及在实战中的运用-内存类型-JavaScript进制转换

概念

API: 所有可以供别人调用方法/接口,统称为API

  • API全称是application programming interface,应用程序接口;

  • 要学习英语单词,因为一般中文文档会加一些作者的想法,但那想法不一定正确

  • ide就是开发工具

  • 程序员三种病

    • 腰稚,要坐直
    • 眼睛 别晚上黑灯看手机
    • 痔疮,别长久坐

数组方法

  • 增删改5个 [].push() [].pop() [].unshift() [].shift() [].splice()
  • 查找3个 [].indexOf() [].lastIndexOf() [].includes()
  • 转字符串2 [].toString() [].join()
  • 截取slice [].slice()
  • 合并concat [].concat()
  • 排序/排列2个 [].sort() [].reverse()
  • 迭代/循环2个 [].forEach() [].map()

对象

  • 多组键值对组成的属性名和属性值集合
  • 一组键值对就是对象的一个成员

obj.name

  • 学术说法: 访问obj对象中的name这个成员
  • 这个操作的学术说法: 对象的成员访问

字符串

  • 查找2 "string".charAt() "string".charCodeAt()
  • 截取3 "string".slice() "string".substring() "string".substr()
  • 验证是否存在3 "string".indexOf() "string".lastIndexOf() "string".includes()
  • 大小写转换2 "string".toUpperCase() "string".toLowerCase()
  • 替换1 "string".replace()
  • 转数组split "string".split()

Math

  • Math.abs()

  • Math.ceil()

  • Math.floor()

  • Math.round()

  • Math.random()

    • Math.floor(Math.random()*(max+1-min)+min)
  • Math.max()

  • Math.min()

  • Math.sqrt()

  • Math.pow()

    //2^10 -> 1024
    Math.pow(2,10)//2的10次方
    2**10//2的10次方
    

日期对象

  • new Date() 创建当前时间的日期对象
  • new Date().getFullYear()
  • new Date().getMonth()
  • new Date().getDay()
  • new Date().getDate()
  • new Date().getHours()
  • new Date().getMinutes()
  • new Date().getSeconds()
  • new Date().getMilliseconds()
  • new Date().getTime()

DOM相关方法

  • 获取DOM元素

  • 节点关系属性

  • 增删改[自定义属性]

    • createElement
    • createTextNode
    • appendChild
    • insertBefore
    • removeChild
    • cloneNode
    • get/set/removeAttribute

函数

  • 定义&执行
  • 参数
    • 形参
    • 实参
      • 获取参数
        • arguments
        • 剩余运算符
  • 箭头函数
    • 简写形式
  • 返回值

JS中的操作符

循环:for 、for in 、while、do-while
判断:if-else、switch-case、三元运算符

ECMAScript

ECMAScript是JS的语法规范

  • ES3[很老的规范]

  • ES5[在ES3的基础上,新增了很多便于开发的API方法,例如:forEach/map] =>不兼容IE6~8

  • 以ES5为分隔,ES5是JS的老版本语法规范!ES6是js的新语法规范。

  • ES6新语法规范[ES6+: ES6~ES12]

    • ES2015:在2015年,正式发布了ES6这个版本,并且规定,每一年都在ES6这个版本的基础上,每年6月份进行更新迭代。
      • 大方向不改,只是增补。
  • 以后习惯用===用来比较。

数据类型检测

JS中的数据类型

  • 原始值类型「基本数据类型 & 值类型」
    • number 整数、小数、零、负数、NaN(不是一个有效数字,但是属于数字类型)、Infinity(无穷大的值)…
    • string 字符串:“”、‘’、``(ES6中的模板字符串,更方便的实现字符串拼接)
    • boolean 布尔:true/false
    • null 空
    • undefined 未定义
    • symbol 唯一值(ES6+)
    • bigint 大数(ES6+)
  • 对象类型「引用数据类型」
    • 标准普通对象(纯粹的对象)
      • 例如:{x:10,y:20}
    • 标准特殊对象
      • new Array 数组
      • new RegExp 正则
      • new Date 日期对象
      • new Error 错误对象
      • Set/Map ES6+新增的数据结构
    • 非标准特殊对象(包装类型对象)
      • 例如:new Number(1) -> 原始值对应的对象数据类型值
    • 函数对象function

symbol类型

  • 每一次执行Symbol(描述)都会创建一个唯一值
  • 设置的描述仅仅是为了方便我们自己区分,但是对最后的结果没有影响!
console.log(Symbol() === Symbol()); //false
console.log(Symbol('AA') === Symbol('AA')); //false

symbol数据类型的作用

  1. 想创建一个和别人不相等的值(唯一值)
  2. 可以给对象设置一个symbol类型(唯一)的成员「属性名」
  3. 处理JS的一些底层机制!
给对象设置一个symbol类型属性名
  • 一个对象中,其成员值(属性值/value)可以是任意类型!

  • 在ES6之前,其成员(属性名/key),都是“字符串”类型!「不论是设置还是访问成员,只要成员的类型不是字符串,都先转换为字符串,再进行处理」

    let arr = [10, 20];
    let sym = Symbol('BB');
    let obj = {
      0: 100, //成员:'0'
      1: 200, //成员:'1'
      length: 2, //成员:'length'
      true: '哈哈哈', //成员:'true'
      null: '呵呵呵', //成员:'null'
      fn: function () { }, //成员:'fn'
      // 此处设置中括号,仅仅是语法要求:把变量的值作为成员
      [arr]: '嘿嘿嘿',
      [Symbol('AA')]: 1000,
      [sym]: 2000
    };
    obj[arr] = '嘿嘿嘿';  // obj[String([10, 20])] -> obj['10,20']
    console.log(obj);  // { '10,20':'嘿嘿嘿',.... }
    
    console.log(obj[0], obj['0']); //100 100
    // obj[0] -> obj['0'] 默认先把0变为字符串"0",再去对象中进行成员访问
    
    let length = 1;
    console.log(obj['length']); //2
    console.log(obj[length]);  //先获取length变量的值,再拿这个值作为对象的成员去访问  obj[length]->obj['1']
    console.log(obj.length); //2「同方式一」
    
  • 在ES6及以后,对象中的成员不单纯是字符串类型,还可以是symbol类型了!

    • 设置的唯一值成员,如果后期想访问
      1. 设置的唯一值成员,如果后期想访问:先用变量存储创建的唯一值,然后设置和获取的时候,都基于这个变量进行操作!先用变量存储创建的唯一值,然后设置和获取的时候,都基于这个变量进行操作!

        let arr = [10, 20];
        let sym = Symbol('BB');
        let obj = {
          0: 100, //成员:'0'
          1: 200, //成员:'1'
          length: 2, //成员:'length'
          true: '哈哈哈', //成员:'true'
          null: '呵呵呵', //成员:'null'
          fn: function () { }, //成员:'fn'
          // 此处设置中括号,仅仅是语法要求:把变量的值作为成员
          [arr]: '嘿嘿嘿',
          [Symbol('AA')]: 1000,
          [sym]: 2000
        };
        console.log(obj["Symbol('AA')"]); //undefined  此成员就是symbol类型,并没有给其转换为字符串
        console.log(obj[Symbol('AA')]); //undefined 按照symbol类型的成员获取值,获取不到的原因是:两次是不同的唯一值
        console.log(obj[sym]); //2000
        
      2. 可以基于一些API方法,获取对象中的所有成员(属性名):返回包含成员的数组集合

        let arr = [10, 20];
        let sym = Symbol('BB');
        let obj = {
          0: 100, //成员:'0'
          1: 200, //成员:'1'
          length: 2, //成员:'length'
          true: '哈哈哈', //成员:'true'
          null: '呵呵呵', //成员:'null'
          fn: function () { }, //成员:'fn'
          // 此处设置中括号,仅仅是语法要求:把变量的值作为成员
          [arr]: '嘿嘿嘿',
          [Symbol('AA')]: 1000,
          [sym]: 2000
        };
        console.log(Object.keys(obj)); //获取对象的私有属性,但是只能获取 可枚举、字符串类型(非symbol类型) 的私有属性  -> ['0', '1', 'length', 'true', 'null', 'fn', '10,20']
        console.log(Object.getOwnPropertyNames(obj)); // 获取对象的私有属性,但是只能获取 字符串类型(非symbol类型) 的私有属性「好处:不考虑枚举性」 -> ['0', '1', 'length', 'true', 'null', 'fn', '10,20']
        console.log(Object.getOwnPropertySymbols(obj)); // -> [Symbol(AA), Symbol(BB)] 获取当前对象中,所有 symbol 类型的私有属性!!「好处:不考虑枚举性」
        // 获取对象所有的私有属性「不论类型、不论是否可以枚举」
        // let keys = Object.getOwnPropertyNames(obj);
        // keys = keys.concat(Object.getOwnPropertySymbols(obj));
        let keys = Reflect.ownKeys(obj); //这一个操作,代替了上面两个操作「ES6新提供的方法」
        keys.forEach(key => {
          console.log(key); //迭代的每个成员
          console.log(obj[key]); //迭代的每个成员值
        });
        
对象属性的可枚举性
  • 对象中的每个成员都有自己的规则,其中一个规则是:是否可“枚举”「enumerable」
  • 所谓枚举,可以理解为列举、遍历!
  • 在JS中,对象中的某个成员,只要能够被for/in循环到,这样的成员就是可枚举的,反之则是不可枚举!
    • “一般”情况下:
      • 内置的成员都是不可枚举的(浅颜色)
      • 自定义的成员都是可枚举的(深颜色)
    • 在自己手动修改了某个属性的可枚举性,以自己修改并生效的可枚举性为准

数据类型检测的办法

  • typeof
    • 语法 typeof 值
    • null的学术名词:空对象指针
  • instanceof
  • constructor
  • Object.prototype.toString.call

变量声明过程

let obj={x:10};

  1. 先创建值
    • 如果是原始值,直接存储在栈内存中
    • 如果是对象类型值,首先开辟一个堆内存空间[有一个16进制的内存地址 假设0x000],把成员存储到空间中
  2. 声明变量
  3. 等号赋值[指针指向]

typeof数据类型检测的底层机制

  • 特点1:返回的结果是字符串,字符串中包含了对应的数据类型
    • typeof 值 返回这个值的数据类型
      • 字符串
      • 字符串中包含了相关的数据类型
    • typeof typeof typeof [1,2,3]//‘string’
  • 特点2:按照计算机底层存储的二进制进行检测「效率高」
    • 000 对象
    • 1 整数
    • 010 浮点数
    • 100 字符串
    • 110 布尔
    • 000000… null
    • -2^30 undefined
    • ……
  • 特点3:typeof null -> “object”
    • typeof按照二进制进行检测的时候,认为以“000”开始的就是对象类型
      • 因为null存储的是64个零,所以被识别为对象,导致:typeof null -> “object”
      • 如果检测出来是对象,再去看是否实现了call方法;如果实现了,说明其是一个函数对象,返回“function”;
      • 如果没有实现call,都返回“object”;
  • 特点4:typeof 对象 -> “object” && typeof 函数 -> “function”
    • typeof不能检测null,也无法对“对象”进行细分(除函数对象外)
  • 特点5:typeof 未被声明的变量 -> “undefined”

typeof在实战中的运用

  1. 检测除null以外的原始值类型「性能高」

  2. 笼统的校验是否为对象

    // 笼统的检测一个值是否为对象
    const isObject = function isObject(value) {
      let type = typeof value; //这个值的检测结果
      // 是对象的条件:不能是null,并且基于typeof检测的值是 'object'/'function' 中的一个
      return value !== null && (type === 'object' || type === 'function');
    };
    
    console.log(isObject(10)); //false
    console.log(isObject(null)); //false
    console.log(isObject([])); //true
    console.log(isObject({})); //true
    console.log(isObject(function () { })); //true
    
  3. 检测是否为函数 => if(typeof obj===“function”){…}

  4. 处理浏览器兼容「ES6+语法规范,都不兼容IE」

    if (typeof Symbol !== 'undefined') {
      // 当前浏览器支持Symbol
      // ...
    }
    
    • 兼容问题主要是由于浏览器内核引起
      • IE浏览器内核:Trident
      • Edge浏览器内核:Chromium「虽然都是微软搞的,但是Edge不是IE」
      • 谷歌浏览器内核:webkit「分支:blink」
      • Safari浏览器内核:webkit
      • 火狐浏览器内核:Gecko
      • 移动端浏览器 && 国产浏览器:现在基本上都是webkit内核

内存

内存类型

计算机的内存:

  • 运行内存[虚拟内存] -> 内存条
  • 物理内存[硬盘存储]-> 硬盘

在计算机的编程语言中,我们声明的变量 & 数据类型值,都存储在计算机的 虚拟内存

  • 都是以2进制的格式进行存储[而且存储64位(之前有32位)]
  • 不管是堆内存还是栈内存都是存在虚拟内存中

内存大小单位

1B 1个字节[一个字母/数字,是一个字节、一个汉字是两个字节]
1KB 1024B=1KB
1MB 1024KB=1MB
1GB 1024MB=1GB
1TB 1024GB=1TB

进制概念

  • 计算机中有一个非常重要的概念:进制的概念
    • 进制的种类 2进制~36进制
      • 2进制:0 1
      • 3进制:0 1 2
      • 8进制:0~7
      • 10进制:0~9
      • 16进制:0~9A-F
      • 36进制:09AZa~z
    • 在JS中编写的number类型的值的数字基本上都是10进制的

把10进制转2进制

  • 整数部分

    • 除以2,取余数,用本次“商值”继续除以2…直到商数为0结束

    • 把所有取的余数“倒过来”拼接

      let num = 18;
      console.log(num.toString(2)); //toString中支持传递进制,把数字转换为指定进制的字符串 '10010'
      /*
      18 / 2 = 9 余  0
        9 / 2 = 4 余  1
        4 / 2 = 2 余  0
        2 / 2 = 1 余  0
        1 / 2 = 1 余  1
      .....
      */
      
  • 小数部分

    • 乘以2,取整数部分,然后继续乘以2…

    • 直到乘积是1,这样取完整只剩0,则停止相乘

      let num = 0.3;
      console.log(num.toString(2)); //'01001100110011001....'
      /*
      0.3 * 2 = 0.6   0
      0.6 * 2 = 1.2   1 
      0.2 * 2 = 0.4   0
      0.4 * 2 = 0.8   0
      0.8 * 2 = 1.6   1
      0.6 * 2 = 1.2   1 
      0.2 * 2 = 0.4   0
      0.4 * 2 = 0.8   0
      0.8 * 2 = 1.6   1
      .....
      */
      
    • 问题:一定会有无限循环的

      • 所有编程语言中,只要是浮点数(小数),就可能存在“丢失精准度”的问题,因为:浮点数在转换为2进制的时候,可能出现无限循环的值,但是计算机只能存储64位…

思考问题

  1. 为什么0.1+0.2!==0.3?

    • console.log((0.1 + 0.2) === 0.3); //false

    • 原因: 0.1 + 0.2=0.30000000000000004?「以小组为单位探索这个问题」

      const the01All = (0.1).toString(2)
      const the02All = (0.2).toString(2)
      const the03All = (0.3).toString(2)
      const the034All = (0.30000000000000004).toString(2)
      console.log(the01All.slice(0,51),'0.1')
      console.log(the02All.slice(0,51),'0.2')
      console.log(the03All.slice(0,51),'0.3')//0.1与0.2的二进制相加得到0.3的二进制表示。但JavaScript中浮点数是有位数的,最多50位有效数字。故而0.3的二进制表示得到的是与0.30000000000000004相同的表示,而0.30000000000000004二进制表示方式中,反转成十进制时,得到的是0.30000000000000004。
      console.log(the034All.toString(2).slice(0,51),'0.30000000000000004')
      console.log(the034All.toString(2).slice(0,51)===the03All.slice(0,51),'比较')
      
  2. 怎么解决精准度丢失的问题?

    • 使用bigint数据类型计算
    • 使用string进行计算及存储

案例

题目

let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
console.log(arr);

map()语法

/*
 map的语法:
   + 数组中有多少项,就迭代多少次,对应的:传递进来的回调函数,就要被执行多少次
   + 每一次函数执行,都会把当前迭代这一项的值和索引传递给函数
   + 回调函数返回啥,就是把数组当前迭代这一项替换为啥「原始数组不变,以新数组返回」
 */
let arr = [10, 20, 30];
arr = arr.map((item, index) => {
    // 执行三次
    // @1 item=10  index=0  return 0;
    // @2 item=20  index=1  return 20;
    // @3 item=30  index=2  return 60;
    return item * index;
});
console.log(arr); //[0,20,60]

parseInt()语法及进制转换

/*
 parseInt([value]):必须要确保[value]是个字符串,如果不是,则先把其转换为字符串;然后从字符串左侧开始查找,找到有效的数字字符(10进制的),直到遇到一个非有效数字字符结束(不论后面是否还有)则停止查找;把找到数字字符转换为数字(10进制的),如果一个都没找到,则结果是NaN!
 */
console.log(parseInt('10px')); //'10' -> 10
console.log(parseInt('10.2px')); //'10' -> 10
console.log(parseInt('px10')); //'' -> NaN
console.log(parseInt(null)); //'null' -> NaN
console.log(parseInt('')); //->NaN
console.log(parseInt(true)); //->'true' -> NaN

/*
 parseInt([value],[radix]):
   + 如果没有写[radix],或者写的0,则默认是10进制「有特殊情况:如果字符串是以0x开始,则默认值是16」
   + 如果[radix]的范围不在2~36之间,则直接返回NaN
   + 从左侧[value]字符串中,找出所有符合[radix]进制的字符,最后把这些字符,看做[radix]进制,转为10进制!
 */
console.log(parseInt('14635px'));
/* 
 parseInt('14635px',10)
   找到所有符合10进制的字符 '14635' 
   最后转为10进制 14635
 */

console.log(parseInt('14635px', 5));
/* 
 找到所有符合5进制的字符 '14'
 把其看做5进制,转10进制的数字「知识点:把N进制转10进制  -> 按权展开求和」
   把N进制转10进制(个位数权重是0,十位数是1,百位数是2...) 
    --> ... + 百位数*N^2 + 十位数*N^1 + 个位数*N^0
   1*5^1 + 4*5^0 = 5 + 4 = 9
 */
/*
 parseInt也是一个函数;数组有五项,所以迭代五次;每一次迭代,都把parseInt函数执行,并且把当前迭代这一项及其索引传递给parseInt;最后只要分析出parseInt每一次执行的结果,我们就知道最终数组每一项都是啥了!

   第一次迭代  parseInt(27.2,0) => 27
     parseInt('27.2',10)
     找到符合10进制的字符 '27'
     把其转换为10进制的数字 27

   第二次迭代  parseInt(0,1) => NaN
     没有1进制

   第三次迭代  parseInt('0013',2) => 1
     找到符合2进制的字符 '001'
     把其转换为10进制的数字 
       0*2^2 + 0*2^1 + 1*2^0 = 0+0+1 

   第四次迭代  parseInt('14px',3) => 1
     找到符合3进制的字符 '1'
     把其转换为10进制的数字 
       1*3^0

   第五次迭代  parseInt(123,4) => 27
     parseInt('123',4)
     找到符合4进制的字符 '123'
     把其转换为10进制的数字 
       1*4^2+2*4^1+3*4^0 = 16+8+3 => 27
 */
let arr = [27.2, 0, '0013', '14px', 123];
arr = arr.map(parseInt);
console.log(arr);//[ 27, NaN, 1, 1, 27 ]

变种:

let arr = [27.2, 0, 0013, '14px', 123];
arr = arr.map(parseInt);
console.log(arr);

/* 
只看第三项 parseInt(0013,2)
  浏览器看到 0013 后,首先会默认把其转换为10进制(8->10)
    0*8^3+0*8^2+1*8^1+3*8^0 = 0+0+8+3 = 11
  parseInt(11,2)
    parseInt('11',2)
    找符合2进制的字符 '11'
    把其转换为10进制的值 
      1*2^1+1*2^0 = 2+1 = 3
这一块有一个特殊的知识点:在JS中,以“0”开始的“数字”,浏览器会默认把其作为8进制转10进制
*/

进阶参考

  1. ES6官方规范 - 全英文,工作时要花一两年来啃
  2. 把数字转成进制数
  3. ES13官方规范 - 全英文,工作时慢慢追,一年一版,改url可以到另一版

你可能感兴趣的:(重返学习,javascript,学习,前端)