前端知识体系-变量和类型

318 阅读13分钟

[toc]

github地址

js的变量和数据类型

js的数据类型分为:原始类型(即基本类型)和对象类型(即引用数据类型)

  • 基本类型:
    • UndefinedNullNumberBooleanStringSymbol
  • 引用数据类型:
    • ObjectArrayFunctionDateRegExp等等

一、基本类型和引用数据类型的区别

基本类型

  • 基本类型不能由属性

    let name = 'Tom';
    name.age = 18;
    
    console.log(name.age) // undefined
    
    // 只有引用值可以动态添加后面的属性
    let name = new Sttring('Tom')
    // name -> String {"Tom"}
    name.age = 18
    console.log(name.age) // 18
    // name -> String {"Tom", age: 19}
    
    typeof name // "object"
    
  • 基本类型的值是不变的

    let name = 'tom'
    name.toUpperCase() // "TOM"
    name // tom
    
  • 基本类型是按值传递

    let name = 'tom';
    let name2 = 'TOM';
    name = name2;
    console.log(name, name2) // TOM TOM
    
  • 基本类型的比较是比较它们的值

    let a = 1;
    let b = true;
    
    a == b // true
    a === b // false
    
  • 基本类型的变量是存放在stack(栈)

    let a = 'tom';
    let b = 123;
    

rskRFk

引用类型

  • 引用类型的值是可变

    const obj = {
      name: 'tom',
      age: 18
    }
    obj.sex = 'man';
    obj.getName = function () {
      console.log(this.name)
    }
    
  • 引用类型的比较是引用的比较

    const o1 = {};
    const o2 = {};
    
    o1 === o2 // false
    o1 == o2 // false
    

    o1o2 引用分别放在堆内存中的2个对象

  • 引用是类型的值是保存在堆内存中,指针存在栈中。

    RNaycO

一道题实战

function addNum (num) {
    num += 1;
    return num
}

let count = 10;

let result = addNum(count);

console.log(count); // 10
console.log(result) // 11


function setPhone (obj) {
    obj.phone = '123'
}

let p1 = new Object();
setPhone(p1)
console.log(p1) // {phone: '123'}


function setPhone (obj) {
    obj.phone = '123'
    // 创建一个新的对象
    obj = new Object();
    obj.phone = '456'
}

let p1 = new Object();
setPhone(p1)

console.log(p1) // {phone: '123'}

二、类型检测

  • typeof
    • 只能检测基本数据类型 但是typeof null 是"object",不能检测复杂类型
  • instanceof 通过原型链去查找的
    • 返回的是false 或者true
    • 可以识别内置对象类型、自定义类型及其父类型
    • 不能识别标准类型,会返回false
    • 不能识别undefined、null,会报错
  • constructor属性 实例对象的constructor属性指向其构造函数。如果是内置类型,则输出function 数据类型(){[native code]};如果是自定义类型,则输出function 数据类型(){}
    • 可以识别标准类型、内置对象类型及自定义类型
    • 不能识别undefinednul,会报错,因为它俩没有构造函数
  • Object.prototype.toString() 返回 [object 数据类型]
    • 可以识别标准类型及内置对象类型
    • 不能识别自定义类型
  • Array.isArray() 数组的检测

typeof

typeof只能判断基本数据类型,不能判断引用数据类型(返回 object

let s = 'tom';
let num = 123;
let bool = true;
let sy = Symbol('tom')
let u;
let n = null;
let o = new Object();


typeof s    // string
typeof num  // number
typeof bool // boolean
typeof u    // undefined
typeof sy   // symbol

typeof n    // object
typeof o    // object

注意typeof null object

为什么typeof null object

在 JavaScript 中 typeof null 的结果为 "object",这是从 JavaScript 的第一版遗留至今的一个 bug。在第一版的 JavaScript 中,变量的值被设计保存在一个 32 位的内存单元中。该单元包含一个 1 或 3 位的类型标志,和实际数据的值。类型标志存储在单元的最后。

数据类型机器码标识
整数1
浮点数010
字符串100
布尔110
undefined-2^31(即全为1)
null全为0
对象(Object)000

在判断数据类型时,是根据机器码低位标识来判断的,而null的机器码标识为全0,而对象的机器码低位标识为000。所以typeof null的结果被误判为Object

instanceof

  • 通过原型链去查找的
  • 返回的是false 或者true
  • 可以识别内置对象类型、自定义类型及其父类型
  • 不能识别标准类型,会返回false
  • 不能识别undefined、null,会报错
'a' instanceof String -> false
12 instanceof Number -> false
true instanceof Boolean -> false
undefined instanceof Undefined  ->报错
[] instanceof Array -> true
new Person instanceof Person -> true
new Person instanceof Object -> true

constructor

constructor属性 实例对象的constructor属性指向其构造函数。如果是内置类型,则输出function 数据类型(){[native code]};如果是自定义类型,则输出function 数据类型(){}

  • 可以识别标准类型、内置对象类型及自定义类型
  • 不能识别undefinednul,会报错,因为它俩没有构造函数
('a').constructor -> function String(){[native code]}
(undefined).constructor) -> 报错
null).constructor -> 报错
{name: "jerry"}).constructor -> function Object(){[native code]}
(new Person).constructor -> function Person(){}
// 封装成一个类型识别函数
function type(obj){
  var temp = obj.constructor.toString();
  return temp.replace(/^function (\w+)\(\).+$/,'$1');
}

Object.prototype.toString()

Object.prototype.toString() 返回 [object 数据类型]

  • 可以识别标准类型及内置对象类型
  • 不能识别自定义类型
Object.prototype.toString.call('a') -> [object String]
Object.prototype.toString.call(undefined) -> [object Undefined]
Object.prototype.toString.call(null) -> Null
Object.prototype.toString.call({name: "jerry"})) ->object Object]
Object.prototype.toString.call(function(){}) -> [object Function]
Object.prototype.toString.call(new Person)) -> [object Object]

封装成一个类型识别函数

 function type(obj){
   return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
 }

Array.isArray()

​ 数组检测

const a = [1,2,3]
Array.isArray(a) -> true
Array.isArray([]) -> true

三、其他

1.undefinednull的区别

  • nullundefined都是基本数据类型。

  • Undefined类型只有一个值,就是undefined。当声明的变量未初始化时,该变量的默认值是undefined。所以一般地,undefined表示变量没有初始化。

  • Null类型只有一个值,就是nullnulljavascript语言的关键字,它表示一个特殊值,常用来描述"空值",逻辑角度看,null值表示一个空对象指针

null == undefined -> ture
null === undefined -> false
null === null -> true
null == null -> true
undefined === undefined -> true
给一个全局变量赋值为`null`,相当于将这个变量的指针对象以及值清空,如果是给对象的属性赋值为`nul`l,或者局部变量赋值为`null`,相当于给这个属性分配了一块空的内存,然后值为`null`` JS`会回收全局变量为`null`的对象。

给一个全局变量赋值为`undefined`,相当于将这个对象的值清空,但是这个对象依旧存在,如果是给对象的属性赋值为`undefined`,说明这个值为空值

扩展下

​ 声明了一个变量,但未对其初始化时,这个变量的值就是undefined,它是 JavaScript 基本类型 之一

var data;
console.log(data === undefined); //true

​ 对于尚未声明过的变量,只能执行一项操作,即使用typeof操作符检测其数据类型,使用其他的操作都会报错。

//data变量未定义
console.log(typeof data); // "undefined"
console.log(data === undefined); //报错

null 特指对象的值未设置,它是 JavaScript 基本类型 之一。

null 是一个字面量,它不像undefined 是全局对象的一个属性。null 是表示缺少的标识,指示变量未指向任何对象。

// foo不存在,它从来没有被定义过或者是初始化过:
foo;
"ReferenceError: foo is not defined"
// foo现在已经是知存在的,但是它没有类型或者是值:
var foo = null; 
console.log(foo);   // null

2.JavaScript中的变量在内存中的具体存储形式

  • 基本类型 --> 保存在内存中,因为这些类型在内存中分别占有固定大小的空间,通过按值来访问。基本类型一共有6种:UndefinedNullBooleanNumberStringSymbol
  • 引用类型 --> 保存在内存中,因为这种值的大小不固定,因此不能把它们保存到栈内存中,但内存地址大小的固定的,因此保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。

WqmHvM

3.JavaScript对象的底层数据结构是什么

参考链接1 参考链接2

4.Symbol类型在实际开发中的应用、可手动实现一个简单的 Symbol

ES6 的 Symbol 类型及使用案例

5.javascript隐性转换

在 JS 中基本数据类型之间的转化只有三种情况:

  • 转换为布尔值
  • 转换为数字
  • 转换为字符串

ytyGAl

上图中包含了所有转换规则,这里总结几点需要注意的地方:

  • NaNnumber 类型的数据,所以 number 转其他类型时不要漏掉它。
  • 对象转字符串,结果为:[object Object]
  • 数组转数字的规则:空数组、只有一个数字元素的数组转为数字,其余情况均为 NaN
  • undefinednull 转数字,分别为:NaN0
  • 转为 Boolean 时,除了 undefinednullfalseNaN0-0 转为 false,其他的都转为 true,包括所有对象。

对于对象转换为基本类型时,其内部的原理如下:

  1. 如果已经是基本类型,直接返回
  2. 调用内置的 valueOftoString方法,如果转换为基本类型,则返回转换的值
  3. 如果没有转换为基本类型,则会抛出错误

类型转换的一些小技巧

  • 数据前置 + 号,转换为 number 类型
  • 数据与 0 相减,转换为 number 类型
  • 数据前置 !! 号,转换为 Boolean 类型

coding

    // 一、只有 + 两边有一边是字符串, 会把其它数据类型调用toString()方法转成字符串然后拼接
    // + 是字符串连接符: String(1) + 'true' = '1true'
    1 + "true"  // '1true'

    // 二、算术运算符+ :会把其它数据类型调用Number()方法转成数字然后做加法计算
    // + 是算法运算 1 + Number(true)
    1 + true // 2
    // 1 + Number(undefined) = 1 + NaN = NaN
    1 + undefined //NaN
    // 1 + Number(null) = 1+ 0 =1
    1 + null // 1

    // 三、关系运算符 会把其他数据类型转换成number之后再比较关系

    // 1.当关系运算符两边又一边是字符串时,会将其它数据类型使用Number()转换,然后比较关系
    // Number('2') > 10 = 2 > 10 = false
    '2' > 10 // false
    // 2.当关系运算符两边都是字符串,此时同时转成number然后比较关系
    // 重点:此时并不是按照Number()的形式转成数字,而是按照字符串对应的unicode编码转成数字
    // 使用这个查看字符串的unicode编码: 字符串的charCodeAt(字符下标,默认为0)
    // '2'.charCodeAt() > '10'.charCodeAt() = 50 > 49 = true
    '2' > '10' // true
    // 多个字符从左往右依次比较
    // 先比较'a' 和 'b', ‘a' 与 ‘b'不等,则直接得出结果
    "abc" > "b" //fasle
    // 先比较'a' 和'a',两者相等,继续比较第二个字符'b'与‘a',得出结果 a.charCodeA = 97 b.charCodeB=98
    "abc" > "aad" // true
    // 3.特殊情况:如果数据类型是undefined与null,得出固定的结果
    undefined == undefined // true
    undefined === undefined // true
    undefined == null // true
    null == null // ture
    null === null // ture
    // 4.NaN与任何数据比较都是NaN
    NaN == NaN // false

    //四、复杂数据类型在隐式转换时,
    // 如果是转换成number
    //1、如果输入的值已经是一个原始值,则直接返回它
    // 2、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
    // 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
    // 3、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
    // 4、否则,抛出TypeError异常。

    // 如果是转换成string
    // 1、如果输入的值已经是一个原始值,则直接返回它
    // 2、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
    // 3、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
    // 如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
    // 4、否则,抛出TypeError异常。

    //复杂数据类型转number顺序如下
    // 1.先使用valueOf()方法获取其原始值,如果原始值不是number类型,则使用toString()方法转换成string
    // 2.再将string 转换成 number运算。
    [1,2] == '1,2' // true  [1,2]转换成string [1,2].valueOf() -> [1,2] [1,2].toString() -> '1,2'

    var a  = {}
    a == "[object Object]" // true a.valueOf().toString() -> "[object Object]"

    // 一道题
    var a = ???
    if(a==1 && a==2 &&a==3) {
        console.log(1)
    }
    // 如何完善a,使其正确打印1

    var a = {
        i: 0, //声明一个属性i
        valueOf: function() {
            return ++ a.i //每次调用一次,让对象a的i属性自增一次并且返回
        }
    }
    if(a==1 && a==2 &&a==3) { // 每次运算时都会调用一次a的valueOf方法
        console.log(1)
    }

    // 五、逻辑非隐形转换与关系元算符隐式转换搞混淆
    // 空数组的toString()方法会得到空字符串,而空对象的toString()方法会得到字符串'[object Object]'

    // 1.关系运算法:将其它数据类型转成数字
    // 2.逻辑非:将其它数据类型使用Boolean()转成布尔类型
    //  以下八种情况转换为布尔类型会得到false
    // 0、-0、NaN、undefined、null、空字符、false、document.all()

    // [].valueOf().toString()得到空字符串 Number('') ==0 
    [] == 0 // ture

    //本质是 ![] 逻辑非与表达式结果与0比较关系
    // 1.逻辑非优先级高于关系运算法  ![] = false(空数组转布尔得到true,然后取反得到fasle)
    // 2.false == 0 成立
    ![] == 0 // true

    // 本质其实是空对象{} 与 !{} 这个逻辑非表达式结果做比较
    //1.{}.valueOf().toString()得到字符串'[object Object]'
    //2.!{} = false
    //3.Number('[object Object]') -> NaN == Number(false) -> 0
    {} == !{}

    //引用类型数据存在堆中,栈中存储的是地址,所以他们的结果false
    {} == {} // false

    // 本质是空数组[] 与 ![] 这个逻辑表达式结果做比较
    //1. [].valueOf().toString()得到空字符串
    //2. ![] = false
    //3.Number('') == Number(false) 成立 都是0
    [] == ![] // true

    [] == [] // false 引用类型数据在堆中,栈中存储的是地址,所以他们的结果都是fasle

    {}.valueOf().toString() // [obejct Obejct]
    [].valueOf().toString() //空字符串

掘金:你所忽略的js隐式转换

6.0.1+-0.2===0.3?

出现小数精度丢失的原因, JavaScript可以存储的最大数字、最大安全数字, JavaScript处理大数字的方法、避免精度丢失的方法

0.1 + 0.2 !== 0.3 详细讲解

7.== 和===

对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换

假如我们需要对比 xy 是否相同,就会进行如下判断流程:

  1. 首先会判断两者类型是否相同。相同的话就是比大小了

  2. 类型不相同的话,那么就会进行类型转换

  3. 会先判断是否在对比 nullundefined,是的话就会返回 true

  4. 判断两者类型是否为 stringnumber,是的话就会将字符串转换为 number

    1 == '1'1 ==  1
    
  5. 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断

    '1' == true'1' ==  11  ==  1
    
  6. 判断其中一方是否为 object 且另一方为 stringnumber 或者 symbol,是的话就会把 object 转为原始类型再进行判断

    '1' == { name: 'tom' }
            ↓
    '1' == '[object Object]'
    

    流程图:

    RxynGG

    1. 想了解更多的内容可以参考 标准文档

    对于 === 来说就简单多了,就是判断两者类型和值是否相同

    [] !== [] // true
    

8.varletconst区别

var

  • var 命令会发生“变量提升”现象,即变量可以在声明之前使用,值为 undefined

    console.log(name);
    var name = 'tom'; // undefined
    
  • 内层变量可能覆盖外层变量

    var name = 'tom';
    {
      var name = 'TOM'
    }
    
    name // 'TOM'
    
  • 用来计数的循环变量泄露为全局变量

    for(var i = 0; i < 5; i++) {}
    
  • var不存在块级作用域

    {
      var name = 'tom';
    }
    console.log(name); // tom
    
    

let

  • 声明的全局变量不会挂在顶层对象下面

  • let所声明的变量,只在let命令所在的代码块有效。

    {
      let a = 1;
      var b = 2
    }
    a // ReferenceError: a is not defined.
    b // 2
    
  • 变量的使用,一定要在声明前,否则会报引用错误(ReferenceError

    if (true) {
      let a;
    }
    console.log(a); // ReferenceError: a is not defined
    
  • letconst不允许在相同作用域内,重复声明同一个变量。

    //报错
    function func () {
      let name = 'tom';
      var name = 'TOM'
    }
    
    // 报错
    function func	() {
      let name = 'tom';
      let name = 'TOM';
    }
    
  • 暂时性死区,只要块级作用域内存在 let 命令,它所声明的变量就“绑定”( binding )这个区域,不再受外部的影响,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。

    var a = 1;
    if (true) {
      a = 2; // ReferenceError
      let a;
    }
    

cosnt

  • 声明的全局变量不会挂在顶层对象下面

  • const 声明之后必须马上赋值,否则会报错

    const name  //Uncaught SyntaxError: Missing initializer in const declaration
    
  • const声明一个只读的常量。一旦声明,常量的值就不能改变。

    const NAME = 'TOM'
    NAME = 'tom'; // TypeError: Assignment to constant variable.
    
  • const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

9.varletconst原理

JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。

  • var

    会直接在栈内存里预分配内存空间,然后等到实际语句执行的时候,再存储对应的变量,如果传的是引用类型,那么会在堆内存里开辟一个内存空间存储实际内容,栈内存会存储一个指向堆内存的指针。

  • let

    是不会在栈内存里预分配内存空间,而且在栈内存分配变量时,做一个检查,如果已经有相同变量名存在就会报错。

  • const

    也不会预分配内存空间,在栈内存分配变量时也会做同样的检查。不过const存储的变量是不可修改的,对于基本类型来说你无法修改定义的值,对于引用类型来说你无法修改栈内存里分配的指针,但是你可以修改指针指向的对象里面的属性。