JavaScript基础篇(一)数据类型的种类、判断以及转换

数据的类型的种类

  • 基本数据类型:undefinednullbooleannumberstringsymbol ( ES6 新增,表示独一无二的值) 和 BigInt ( ES10 新增)
  • 引用数据类型:Object ( Object 本质上是由一组无序的键值对组成的)。里面包含 functionarraydate 等。JavaScript 不支持任何创建自定义类型的机制,而所有值最终都将是上述 8 种数据类型之一。

基本类型和引用类型中监听赋值变化

  • 基本类型中值定义和改变
let a = 100
let b = a
a = 200
console.log(a) // 200
console.log(b) // 100
  • 引用类型中值定义和改变
let a = { age: 20 }
let b = a
b.age = 21
console.log(a.age) // 21

思考:为什么基本类型中 a 的值变了,b 的值确不变;而引用类型中 b 的值变了,a 的值会跟着变呢

  • 基本类型中值都是存储在栈中,而在栈中,每一个 key 都对应一个 value,如下图:(图一为 a 和 b 的初始值都是 100,图二为 a 修改之后为 200,而 b 仍然是100)。
    图一.png
  • 图二.png
  • 而引用类型的值都是存储在堆中,我们定义的 a 和 b 这两个变量可以理解为存储在栈中,但是因为它们的值属于引用类型,计算机会将这些值放在堆中,也就是 a 和 b 两个变量其实指向的 value 都是堆中的同一个内存地址。如下图:


    变量 a 赋值,value 指向堆中的内存地址1.png

    a 和 b 同时存在,都指向堆中的内存地址1.png

    改变 b 的值,即改变堆中内存地址1的值,所以 a 的值也跟着改变.png

总结:基础类型的值存储在栈中,引用类型的值存储在堆中,栈中存储的值因为都比较小、易处理,所以值的存储是分开的;而堆中存储的值可能是一个很庞大的值(如很长很长的 json ),从性能的角度考虑,浅拷贝时就会出现两个变量共用一个堆的情况。


typeof 运算符进行数据类型的判断

  • 识别所有基础类型
let a;                   typeof a // 'undefined'
const str = 'abc'        typeof str // 'string'
const n = 100            typeof n // 'number'
const b = true           typeof b // 'boolean'
const s = Symbol('s')    typeof s // 'symbol'
  • 识别函数
typeof console.log       // function
typeof function() {}     // function
  • 可以判断该类型是否是引用类型(不可再向下细分,即不能区分该引用类型是否为 array 或者 object)
typeof null             // object
typeof ['a', 'b']      // object
typeof {x: 100}       // object

延伸思考:知道堆和栈的概念以及了解 typeof 的用法之后思考什么是浅拷贝?什么是深拷贝?如何手写一个深拷贝函数?

  • 我们上面的栗子中知道对象的直接重新赋值其实共用的是一个堆中的内存地址,改了 b 之后 a 对象中的相应的属性也会改变,这里我们就可以理解为 b 是通过浅拷贝 a 生成的。那么我们想 b 有自己的内存地址,a 和 b 的值在堆中完全独立分开,互不影响就需要用到深拷贝,这也是深拷贝的基本概念。相信聪明的小伙伴应该已经理解两者之间的差别了吧!那么就让我们来手写一个实现深拷贝的函数吧:
/**
   * 深拷贝 
   * @params {Object} obj 要拷贝的对象
   */
  function deepClone(obj = {}) {
    // 如果传入的 obj 不是 object 类型直接返回
    if (typeof obj !== 'object' || typeof obj == null) {
      return
    }
    // 初始化返回结果
    let result
    // 通过 instanceof 判断传入的 obj 是数组类型还是对象类型
    if (obj instanceof Array) {
      result = []
    } else {
      result = {}
    }
    // 遍历传入的 obj
    for (let key in obj) {
      // 保证 key 不是原型的属性
      if (obj.hasOwnProperty(key)) {
        // 递归调用,保证如果这个对象中有嵌套对象将嵌套对象传入当前函数继续执行
        result[key] = deepClone(obj[key])
      }
    }
    // 返回最后得到的结果
    return result
}

上面出现了一个 instanceofhasOwnProperty,我们前面说了 typeof 可以用来判断基础类型,而 instanceof 可以用来判断引用类型,直白点就是它可以判断你是数组还是对象;而 hasOwnProperty 在很多轮子的源码中其实都会看到,主要是判断该对象的属性是自身属性还是通过原型 prototype 添加的属性,我们深拷贝的话是不拷贝对象通过原型添加的属性和方法的。最后就是递归继续遍历拷贝对象中的子对象。

// 验证我们写的函数
let obj1 = {
  age: 20,
  name: 'zhangsan',
  address: {
    city: 'wuhan'
  },
  arr: ['a', 'b', 'c']
}
let obj2 = deepClone(obj1)
obj2.address.city = 'beijing'
console.log(obj1.address.city, obj2.address.city) // wuhan beijing 互不影响

instanceof 进行数据类型的判断

instanceof 其内部机制是通过原型链来判断的,如果对原型和原型链不理解可能比较绕,下一部分会去记录原型和原型链,到时候再来提。这里先总结:

  • instanceof 左为实例,右为构造函数。例如 A instanceof B,用来判断 A 是否为 B 的实例,返回 boolean 值。instanceof 用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
  • instanceof 可以精准判断引用数据类型 ArrayFunctionObject,而基本数据类型不能被 instanceof 精准判断,因为它本身不是一个实例对象
console.log(2 instanceof Number);                    // false
console.log(new Number(2) instanceof Number)         // true
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false  
console.log(Symbol('protein') instanceof Symbol)     // false 不支持语法:"new Symbol()"
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                  // true
new Date() instanceof Date;                          // true
new RegExp() instanceof RegExp                      // true  
null instanceof Null                               // 报错
undefined instanceof undefined                    // 报 错

Object.prototype.toString.call()

好吧,摊牌了!前面介绍的两种感觉都不是很满意对吧,王炸要来了。使用 Object 对象的原型方法 toString,使用 call 改变 this 指向

const a = Object.prototype.toString;
console.log(a.call(2));             // [object Number]
console.log(a.call(true));          // [object Boolean]
console.log(a.call('str'));         // [object String]
console.log(a.call(Symbol()))       // [object Symbol]
console.log(a.call([]));            // [object Array]
console.log(a.call(function(){}));  // [object Function]
console.log(a.call({}));            // [object Object]
console.log(a.call(undefined));     // [object Undefined]
console.log(a.call(null));          // [object Null]
console.log(a.call(new Date()))     // [object Date]
console.log(a.call(new Error()))    // [object Error]
console.log(a.call(new RegExp()))   // [object RegExp]

思考:为什么这样就能区分呢?

追根溯源我们还是要先了解 toString 方法 的用法,toString 方法返回反映这个对象的字符串,如下栗子:

console.log("jerry".toString());//jerry
console.log((1).toString());//1
console.log([1,2].toString());//1,2
console.log(new Date().toString());//Wed Dec 21 2016 20:35:48 GMT+0800 (中国标准时间)
console.log(function(){}.toString());//function (){}
console.log(null.toString());//error
console.log(undefined.toString());//error

那么新的问题来了:同样是检测对象 obj 调用 toString 方法,obj.toString() 的结果和Object.prototype.toString.call(obj) 的结果不一样,这是为什么?

这是因为 toStringObject 的原型方法,而 ArrayFunction 等类型作为 Object 的实例,都重写了 toString 方法。不同的对象类型调用 toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString 方法(Function 类型返回内容为函数体的字符串,Array 类型返回元素组成的字符串.....),而不会去调用 Object 上原型 toString 方法(返回对象的具体类型),所以采用 obj.toString() 不能得到其对象类型,只能将 obj 转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用 Object 上原型 toString 方法。


数据类型的转换

字符串转 Number
// 传统写法
var a = '1'
console.log(typeof a)
console.log(typeof Number(a)) // 普通写法
console.log(typeof +a) // 高端写法
Number 转字符串
var a = 1
console.log(typeof a.toString())
快速转 Boolean
var a = 0
console.log(typeof a)
console.log(typeof Boolean(a)) // 普通写法
console.log(typeof !!a) // 高端写法
console.log(!!a) // false
跟着(抄袭)大佬整理一份类型转换表:
原始值 转换目标 结果
number Boolean 除了 0、-0、NaN 都为 true
string Boolean 除了空字符串 ' ' 都为 true
undefined、null Boolean false
引用类型 Boolean true
number string 6 => '6'
Boolean、函数、Symbol string 'true'
数组 string [1, 2] => '1, 2'
对象 string '[ object, Object ]'
string number '6' => 6, 'p' => NaN
数组 number [] => 0, [ '6' ] => 6, 其他都是 NaN
非数组的引用类型 number NaN
null number 0
Symbol number 抛错
== 和 === 运算符

思考:为啥会有 ===== 呢,啥时候用 ==,啥时候用 ===

来个栗子瞅瞅吧:

100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true

可以看出 == 运算符对类型判断非常不严谨,不推荐使用,日常开发中除了 ==null 之外其余情况一律都用 === 。至于为啥 ==null 可以用呢,你咋这么多为什么?算了,你长得帅你有理,看如下栗子:

const obj = {x: 100}
if (obj.a == null) {}
// 相当于
if(obj.a === null || obj.a === undefined){}
四则运算符
  • 加法 + 运算符的特点
    运算中其中一方为字符串,那么就会把另一方也转化成字符串,如果一方不是字符串或者数字,那么会将他转化成数字或者字符串。
console.log(1 + '1'); // 11
console.log(true + true); // 2
console.log(4 + [1, 2, 3]); // 41, 2, 3
console.log('a' + + 'b'); // aNaN 因为 + 'b' 为 NaN
  • 非加法运算符来说只要其中一方不是数字就会被转化成数字,说实话感觉这个基本用的不多,可能更多出现在面试题里...
console.log(2 * '1'); // 2
console.log(2 * []); // 0
console.log(2 * ['2']); // 4
console.log(2 * [1, 2]); // NaN

本文部分内容参考于掘金大佬的文章:https://www.juejin.im/post/6844904201735110669
如果有理解错误或不足的地方,欢迎大家提出修改意见,此整理仅供学习参考!!!

你可能感兴趣的:(JavaScript基础篇(一)数据类型的种类、判断以及转换)