JS基础 类型和变量(一)

内置类型

null、undefined、string、number、boolean、symbol(ES6)、object

基本类型
  • null
    空值,指示变量未指向任何对象。给尚未创建对象但以后会创建具体对象的变量赋值为 null 可以避免变量值为 undefined。虽然 typeof null 会输出 object ,但不是对象类型,因为最初 JS 底层为了性能考虑使用低 3 位存储变量的类型信息,000 开头代表是对象,而 null 表示为全零,所以将它错误的判断为 object ,通常在不再使用对象时手动置为 null 解除引用,便于内存回收
  • undefined
    变量声明未初始化时访问变量,值为 undefined
    (而变量未声明就访问是报错 not defined )

null 和 undefined 的区别和联系

  1. null 是一个关键字,值 null 是一个字面量;而 undefined 是全局对象的一个属性。即它是全局作用域的一个变量。undefined的初始值就是原始数据类型undefined。但在 ES5中 undefined被修改为一个不能被配置(non-configurable),不能被重写(non-writable)的属性。对 undefined 赋值不会报错但这是无效的,没有意义的
    在这里插入图片描述
  2. null == undefined 而 null !== undefined
    检测变量是否已经赋值必须使用 严格相等(全等) 而不是 标准相等,否则会因为标准相等进行类型转换,将值为 null 的变量误判为 undefined 变量未赋值。
 var d = null;
console.log(d == undefined); // true
  1. 数值转换
    Number() 强制转换:
    在这里插入图片描述

    因此注意使用加号运算符进行加法运算时,若数值与 null 相加会得到 NaN,而数值与 undefined 相加相对于数值与 0 相加

    parseInt() 转换
    在这里插入图片描述

  • number
    JS 中的 number 是浮点类型,而计算机是二进制来表示浮点数的,无法准确表示一个浮点数,只能逼近取近似值。在0.1 + 0.2这个式子中,0.1 和 0.2 都是近似表示的,在他们相加的时候,两个近似值进行了计算,导致最后得到的值是0.30000000000000004,此时对于JS来说,其不够近似于0.3,于是就出现了0.1 + 0.2 != 0.3 这个现象。 这是其他语言也无法避免的。
    number 中有一个特殊的值 NaN(not a number), NaN 与任何都不相等包括它自己

  • string
    string 的值是不可变的,调用 String 的方法时,实际上后台进行了自动处理,返回的结果是一个新的字符串

  • symbol
    表示独一无二的值,两变量不相等也不严格相等,但不表示不可以有两个相同的值; Symbol( ‘desc1’) 不会将字符串 ‘desc1’ 强制转换为 symbol 类型,而是每次都创建一个新的 Symbol 类型

    可用来定义对象的唯一属性名
    symbol 值通过 Symbol() 生成;Symbol()前不能使用 new 否则报错;作为对象属性名时用 [] 不能用 .

    let sy = Symbol (“ key1”)
    let obj = {}
    obj [sy] = “ val1 ”
    obj.sy = " val2 "
    console.log(sy)// Symbol( key1) 与字符串区分
    console.log(obj.sy)// " val2 "
    console.log(obj[sy])// “ val1 ”

    因为 . 运算符后面是字符串,所以取到的是obj 的字符串 sy 属性,而不是 Symbol 值 sy 属性。

    symbol值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问,但是不会出现在 for…in 、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames() JSON.stringify() 返回,可通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys()获取。

    typeof 能检测 symbol 类型而 instanceof 结果为 false

    如果 Symbol() 的参数是一个对象,就会调用该对象的 toString 方法,将其转为字符串,然后才生成一个 Symbol 值。

    Symbol.for(key)
    使用给定的key搜索现有的symbol,如果找到则返回该symbol。否则将在全局作用域注册一个 symbol

    Symbol.keyFor(sym)
    在全局 symbol 注册表中查找

引用类型

Object(Array、Date、RegExp、Function、基本包装类型(Boolean、Number、String)),Object 本质上是由一组无序的名值对组成的。JavaScript不支持任何创建自定义类型的机制,而所有值最终都将是上述数据类型之一。

基本类型和引用类型的区别
  1. 内存上(保存和复制)
    基本类型按值存放在栈中,可直接访问,占据空间小,大小固定,由系统自动分配内存空间,自动释放。
    引用类型同时在栈和堆中占据空间,在栈中存放该对象在堆中的地址(指针),该对象的值存放在堆中,因此不可直接操作对象的值;当创建对象时,计算机会在堆中存放对象的值,将堆中的地址值存在栈中,访问对象时先在栈中找到该对象在堆中的地址,再根据该地址到堆中寻找该对象的内存空间。引用类型占据空间较大,大小不固定,动态分配内存,不会自动释放;

    复制变量值 a = b (假设已声明初始化)
    (1)若变量 b 是基本类型值,则为 a 在堆中创建一块内存空间,初始化时,访问 b ,取出它的值复制一份副本,赋值给 a 完成初始化,a 和 b 在不同的内存空间中,互相独立
    (2)若变量 b 是引用类型值,该值是一个指针,存放对象在堆中的地址,同样在栈创建一个新内存空间给 a 并完成初始化,但是这个值是一个地址,访问 a 时它也可以在堆中找到与 b 一样指向的对象,即 a 和 b 指向了同一个堆空间,同一个对象

    此处涉及 浅拷贝 和 深拷贝
    浅拷贝:
    对象的属性都是基本类型值,则会创建独立的内存空间,拷贝值初始化,a 和 b 是两个结构相同的独立的变量。但若拷贝的对象的属性含有引用类型,此时只会在栈中开辟内存空间,复制该引用的地址,不会在堆中开辟内存空间,进一步访问复制引用类型的值;即两变量的引用类型属性指向同一内存,会出现修改其中一个变量的引用类型属性,另一个也随之改变的问题

    有哪些操作是浅拷贝:
    (1) Object.assign()
    (2) 对象扩展运算符
    (3) 数组的 slice()和 concat()

    深拷贝
    深拷贝是指拷贝对象引用类型属性时,会为该引用类型属性值在堆中开辟新的内存空间,复制该属性的对象值,并将该副本对象值的地址存放在栈中,而不是存放原对象在堆的地址,两个变量的引用类型属性也会各自拥有内存空间,相互独立
    原生JS中没有实现深拷贝(实现思路:递归拷贝对象属性)

下面涉及函数的形参与实参的问题,虽然变量有按值和按引用访问两种方式,但要理解函数的参数只能是按值传递
在这里插入图片描述
将对象 { name:‘p1’, age:25 } (下文用对象1代替)存放在堆中,给变量 p1 在栈中开辟一块内存空间,并将对象堆中的首地址赋值给变量 p1 完成初始化;
随后调用函数 test() 并将 p1 作为实参传递给形参 person,注意形参 person 只是 p1 的一个副本,person 有了 对象1 的地址,在 test () 中通过点运算符,访问了地址操作对象,即将对象 1 的 age 属性值改为 26
随后创建了另一个对象{ name:‘p2’, age:30 },并将它在堆中的地址给了 person,此时person 被重新赋值了新地址,指向了新的对象,注意这个过程跟 p1 没有关系 ,因为 person 和 p1 不是同一个变量,它们都是值类型,只是初始化 person 时给了它 p1 的值,重新赋值 person 并不会改变 p1 (回想一下上面 a = b ,b(此处的 p1 )是基本类型时,创建新变量拷贝值初始化,a (此处的person) 和 b 互不影响)
最后将 person 的值(对象2的堆地址)返回给新创建的变量 p2,即栈中的 p2 指向了对象2,因此 p1 和 p2 是两个对象。
在这里插入图片描述
在这里插入图片描述

  1. 属性、方法的操作
    在这里插入图片描述
    在这里插入图片描述
    基本类型值不是对象,逻辑上没有方法可以调用,但实际上我们可以对基本类型调用方法(仅限string、boolean、number)是因为后台自动进行处理(创建基本包装类型对象)
    图 2 中,先将 number 类型的 1 存放到变量中,在第二行访问 numb 时处于读取模式,即从内存中将该值取出,读取模式中后台进行下列处理:
    (1)创建该值的基本包装类型实例(如此处创建Number类型的实例)
    (2)在实例上调用指定方法 (基本包装类型是对象类型)
    (3)返回结果,销毁该实例

    回到图1, ‘1’.toString() 同理,后台读取 ‘1’ 自动进行上述处理,而 undefined 没有基本包装类型(逻辑上也不需要), 而 1.toString() 是因为解析时把点解析为小数点,而不是点运算符,因此浮点数中出现非数字值,语法错误
    在这里插入图片描述
    通过括号,将点排除了小数点的情况,即解析为点运算符,因此 (1).toString() 相当于 String ((1)) , 返回 1

    由后台自动处理操作可知,最后实例被销毁,因此当我们为基本类型添加属性时,最后会被销毁失效,因此我们不能给基本类型值添加属性;而引用类型添加属性时会被存在堆空间,一直伴随着对象的生命周期;

    基本包装类型和其他引用类型的主要区别就是对象的生命周期。使用 new 操作符创建的引用类型实例,在执行流离开当前作用域前一直保存在内存中,而自动创建的基本包装类型的对象值存在于一行代码的执行瞬间,然后立即被销毁。

类型检测

typeof

原理:
js 在底层存储变量的时候,变量的机器码的低 3 位存储其类型信息。

000:对象
010:浮点数
100:字符串
110:布尔
1:整数

undefined 和 null 这两个值的信息存储是特殊的。

null:所有机器码均为0
undefined:用 −2^30 整数来表示。

因此 typeof 可以判断所有基本类型值的类型,除了 null 判断为 object;但还需注意 typeof 可以判断 函数对象,返回function,其他引用类型均不能具体判断,均返回 object
在这里插入图片描述

instanceof

对于引用类型,可以使用 instanceof
原理:
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

target instanceof Constructor
若 target 不是对象,是基本类型值则返回 false;
可以检测到自定义对象类型的继承关系

需要注意的是,如果表达式 obj instanceof Foo 返回 true,并不意味着该表达式会永远返回 true。
因为 Foo.prototype 属性的值有可能会改变,或者对象 obj 的原型链的也能被改变。

Object.prototype.toString.call()

因为Array、Function 等引用类型会重写 toString(), 而调用 Object 原型上的方法即可检测到该对象的类型,对于所有对象都能正确返回其类型
在这里插入图片描述

但对于自定义类型对象,不可检测到继承的关系

constructor

在这里插入图片描述

判断数组的方法

  1. instanceof 操作符判断
    arr instanceof Array
  2. 对象构造函数的 constructor判断
    Object的每个实例都有构造函数 constructor,用于保存着用于创建当前对象的函数
    arr.constructor === Array
  3. Array 原型链上的 isPrototypeOf
    Array 构造函数的原型的一个方法: isPrototypeOf() 用于测试一个对象是否存在于数组对象的原型链上。
    Array.prototype.isPrototypeOf(arr)
  4. Object.getPrototypeOf()
    返回指定对象的原型,再与Array的原型比较
    Object.getPrototypeOf(arr) === Array.prototype
  5. Object.prototype.toString.call ()
    Object.prototype.toString.call(arr) === ‘[object Array]’
  6. Array.isArray
    Array.isArray(arr), es5新增的方法,ie8及以下不支持

判断空对象

  1. 将 json 对象转化为 json 字符串,再判断该字符串是否为"{}"
    let data = {};
    let b = (JSON.stringify(data) == “{}”);
    alert(b); //true

  2. for in 循环判断对象中是否有键值对
    let obj = {};
    let b = () => { for(let key in obj) { return false; } return true; }
    alert(b()); //true

  3. Object.getOwnPropertyNames()方法
    此方法是使用Object对象的getOwnPropertyNames方法,获取到对象中的属性名,存到一个数组中,返回数组对象,我们可以通过判断数组的length来判断此对象是否为空
    let data = {};
    let arr = Object.getOwnPropertyNames(data);
    alert(arr.length == 0); //true

  4. 使用ES6的Object.keys()
    与4方法类似,是ES6的新方法, 返回值也是对象中属性名组成的数组
    let data = {}; let arr = Object.keys(data); alert(arr.length == 0);//true

这些方法都不可检测到 Symbol作为属性名的属性,除了 Object.getOwnPropertyNames() 外的其他方法都不可检测出不可枚举属性,除 for in 外其他方法都不能检测到对象的继承属性

ES6 中的 Reflect.ownKeys(obj)
包含对象自身的所有属性,即可检测键名是 Symbol 和不可枚举的属性,只是不包含继承属性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值