以下为阅读《红宝书》3.4 3.5 4.1所总结笔记~
数据类型
简单数据类型(原始类型)
Undefined Null Boolean Number String Symbol(符号):ES6新增
复杂数据类型(引用类型)
Object(对象)
- 那么如何确定任何变量的数据类型呢,就需要typeof 操作符
typeof
-
该操作符会返回下列字符串之一
'undefined':表示值未定义
'boolean':表示值为布尔值
'number':表示值为数值
'string':表示值为字符串
'symbol':表示值为符号
'object':表示值为对象(而不是函数)或null
'function':表示值为函数
console.log(typeof 42); // "number"
console.log(typeof 'bp'); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undeclaredVariable); // "undefined"
console . log ( typeof null ); // "object"
console.log(typeof {a:'aaa'}) // "object"
console.log(typeof function(){}) // "function"
console.log(typeof Symbol) // "symbol"
console.log(typeof NAN) // "number"
// 对象
typeof [1, 2, 4] === "object";
typeof new Date() === "object";
typeof /regex/ === "object";
// 函数
typeof function () {} === "function";
typeof class C {} === "function";
typeof Math.sin === "function";
- 注意,调用 typeof null 返回的是'object' ,这是因为特殊值null被认为是一个对空对象的引用
- 严格来讲,函数并不代表一种数据类型,但是它有自己特殊的属性,有必要通过 typeof 区分函数和其它对象
- typeof操作符适合判断原始值的类型,除了null
instanceof
-
返回的是布尔值,用于检测构造函数的
prototype属性是否出现在某个实例对象的原型链上 -
语法:
变量 instanceof constructor
参数: object 某个实例对象 constructor 某个构造函数
function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } const auto = new Car('Honda', 'Accord', 1998); console.log(auto instanceof Car); // true console.log(auto instanceof Object); // true var myDate = new Date(); myDate instanceof Date; // 返回 true myDate instanceof Object; // 返回 true myDate instanceof String; // 返回 false // 要检测对象不是某个构造函数的实例时,应这样子做 if (!(mycar instanceof Car)) { // Do something, like mycar = new Car(mycar) } if (!mycar instanceof Car) // 始终返回false 因为!myCar在instanceof之前被处理,总是在验证一个布尔值是否为Car的实例 -
可以判断引用数据类型,但不能判断原始数据类型
-
如果变量是给定引用类型(由其原型链决定),则返回true 因此instanceof操作符检测任何引用值和Object构造函数都会返回true 如果检测原始值,始终返回false
Undefined类型
-
只有一个值,就是特殊值 undefined
-
当使用 var let 声明了变量但是没有初始值,就相当于给变量赋值了 undefined 值
-
undefined 是一个假值,如果需要,可以用简洁的方式判断
let message; if(!message) { // 这个块会执行 }
Null类型
-
同样只有一个值,即特殊值 null
-
逻辑上将,null值表示一个空对象指针
-
null 是一个假值,如果需要,可以用简洁的方式判断
let message = null; if(!message) { // 这个块会执行 } -
用
等于操作符(==)比较null和undefined始终返回true,注意,该操作符会为了比较而转换它的操作数undefined值是由null派生而来的,将它们定义为表面上相等
console.log(null == undefined) // true
Boolean类型
-
两个字面量:
truefalse -
其他类型的值都有相应布尔值的等价形式,要将一个其他类型的值转换为布尔值,可以调用特定的
Boolean()转型函数,该函数可以在任意类型的数据上调用,而且始终返回一个布尔值,规则如下:数据类型 转换为true的值 转换为false的值 Boolean true false String 非空字符串 ''(空字符串) Number 非零数值(包括无穷值) 0、NaN Object 任意对象 null Undefined undefined
理解以上转换非常重重要!因为if等流控制语句会自动执行其他类型值到布尔值的转换
Number类型
-
Number类型表示整数和浮点值,不同的数值类型相应地也有不同的数值字面量格式
十进制整数: let intNum = 55
八进制整数:(第一个数字必须是0,然后是八进制数字0~7):let octalNum = 070
十六进制整数: let hexNum = 0xA
使用八进制和十六进制创建的数值在所有数字操作中都被视为十进制
-
浮点值
定义浮点值,数值必须包含小数点,而且小数点后必须至少有一个数字
let floatNum1 = 1.1 let floatNum2 = .1 (有效,但不推荐)-
存储浮点数使用的内存空间是整数值的两倍
-
对于非常大或者非常小的数值,浮点数可以用科学记数法表示
-
浮点值的精度最高可达17位小数,但是在算术计算中远不如整数精确
- 例如 0.1+0.2 得到的不是0.3 而是0.300000000000004
let floatNum = 3.125e7 // 等于 31250000 let floatNum = 3e-6 // 等于 0.000006 if(a + b == 0.3) {} // 别这么做
-
-
值的范围
无穷
Infinity -
NaN(不是数值)
用来表示本来要返回数值的操作失败了(不是抛出错误)
console.log(0 / 0); // NaN
console.log(-0 / +0); // NaN
console.log(5 / +0); // Infinity
console.log(5 / -0); // -Infinity
任何设计NaN的操作始终返回NaN(NaN/10),其次 NaN不等于包括NaN在内的任何值
console.log(NaN == NaN) // false
isNaN函数,接收一个参数,可以是任意数据类型,然后判断这个参数是否 '不是数值',该函数会尝试将它转换为数值,某些非数值可以直接转换为数值,如'10',布尔值,任何不能转换为数值的值都会返回true
console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false 10是数值
console.log(isNaN('10')); // false 可以转为数字 10
console.log(isNaN('10a')); // true
console.log(isNaN('blue'));// true 不可以转为数值
console.log(isNaN(true)); // false 可以转换为数值 1
- 数值转换
Number()
-
转型函数,可用于任何数据类型
-
基于以下规则转换
- 布尔值:true-1 false-0
- 数值:直接返回
- null:返回0
- undefined:返回NaN
- 字符串
-
console.log(Number('-123')); // -123 console.log(Number('011')); // 11 console.log(Number('01.1')); // 1.1 console.log(Number('')); // 0 console.log(Number('a')); // NaN - 对象:调用valueOf()方法,并按照上述规则转换返回的值,若转换结果为NaN,则调用toString()方法,再按照转换字符串的规则转换
parseInt()
- 该函数更专注于字符串是否包含数值,从位置0开始检测每个字符
- 注意空字符串会返回NaN
- 接收第二个参数,用于指定底数(进制数),以便正确解析
console.log(parseInt("1234Blue1")); // 1234
console.log(parseInt("")); // NaN
console.log(parseInt("0xA")); // 10 解析为十六进制整数
console.log(parseInt("22.5")); // 22
console.log(parseInt("70")); // 70
console.log(parseInt("AF",16)) // 175 则字符串前面的0x可以省略
parseFloat()
- 也是从位置0开始检测每个字符,第一个出现的小数点有效,后面的无效
console.log(parseFloat("1234Blue")); // 1234 按整数解析
console.log(parseFloat("0xA")); // 0 十六进制值始终返回0
console.log(parseFloat("22.5")); // 22.5
console.log(parseFloat("0908.5")); // 908.5
console.log(parseFloat("3.125e7")); // 31250000
String类型
- 字符串可以使用双引号''、单引号'、反引号`标示
- 转换为字符串
.toString()
- 该方法可用于数值、布尔值、对象和字符串值(返回自身),null和undefined值没有该方法
let num = 10
console.log(num.toString()); // '10'
let found = true
console.log(found.toString()); // 'true
String()
- 如果值有toString方法,则调用该方法并返回结果
- null,返回'null'
- undefined,返回'undefined'
let v1 = 10;
let v2 = false;
let v3 = null;
let v4
console.log(String(v1)); // '10'
console.log(String(v2)); // 'false'
console.log(String(v3)); // 'null'
console.log(String(v4)); // 'undefined'
- 模板字面量
- ES6新增使用模板字面量定义字符串的能力,可以保留换行字符,可以跨行定义字符串
- 在定义模板时特别有用,比如以下pageHTML :
let s1 = 'first Line\nsecond Line'
let s2 = `first Line
second Line`
console.log(s1);
console.log(s2);
console.log(s2.length) // 30 这个模板字面量再换行符之后有18个空格符
let pageHTML = `
<div>
<a href='#'></a>
</div>`;
-
字符串插值,通过在
${}中使用一个js表达式实现- 在插值表达式中可以调用函数和方法
- 模板也可以插入自己之前的值
let value = 5 let fin = `the value is ${value} and ${value * value}` console.log(fin); // the value is 5 and 25 let val = '' function append() { val = `${val}abc` console.log(val) } append() // abc append() // abcabc
Symbol类型
-
ES6新增的数据类型。符号是原始值,且符号实例唯一、不可变。用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险
-
基本用法
使用
Symbol()函数初始化 -
使用符号作属性
凡是可以用字符串或数值作为属性的地方,都可以使用符号
Object类型
-
对象通过
new操作符后跟对象类型的名称来创建,可以给对象添加属性和方法let o = new Object() -
每个Object实例都有以下属性和方法
- constructor:用于创建当前对象的函数,如上,这个属性的值就是Object()函数
- hasOwnProperty(属性名):判断对象实例上(不是原型)是否存在该属性
- propertyIsEnumerable
- toLocaleString()
- toString():返回对象的字符串标识
- valueOf():返回对象对应的字符串、数值或布尔值表示
操作符
相等操作符
等于== 和 不等于!==
-
这两个操作符都会先进行类型转换(强制类型转换)再确定操作数是否相等
-
在转换操作数的类型时,相等和不相等操作符遵循以下规则
- 如果任一操作数是布尔值,则将其转换为数值再比较是否相等,false转换为0,true转换为1
- 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等
- 如果一个操作数是对象,另一个不是,则调用对象的
valueOf方法取得其原始值,再根据前面的规则进行比较
-
在进行比较时,这两个操作符会遵循如下规则
null和undefined相等null和undefined不能转换为其他类型的值进行比较- 如果有任一操作数是
NaN,则相等操作符始终返回 false,不相等操作符返回 true,注意!即使两个操作数都是NaN,相等操作符也返回false,因为按照规则,NaN不等于NaN - 如果两个操作数都是对象,则比较它们是不是同一个对象,如果两个操作数都指向同一个对象,则返回true
-
总结一些特殊情况及比较结果
-
null == undefined // true "NaN" == NaN // false NaN与任何操作数比较均返回false NaN == NaN // false false == 0 // true true == 1 // true true == 2 // false undefined == 0 // false null == 0 // false "5" == 5 // true
-
全等 === 和 不全等 !==
-
与上述相等和不相等类似,但是它们在比较时不转换操作数,只有两个操作数在不转换的前提下相等才返回true
"5" == 5 // true '5'会被转换为5 转换后相等 "5" === 5 // false 不相等 因为数据类型不同 -
虽然 null == undefined 返回true(因为这两个值类似),但是 null === undefined 返回false,因为数据类型不同
原始值与引用值
原始值:最简单的数据,以上6种 Undefined Null Boolean Number String Symbol
引用值:有多个值构成的对象
- 在把一个值赋值给变量时,JS引擎必须确定这个值是原始值还是引用值
- 保存 原始值 的变量是 按值 访问的,因为我们操作的就是存储在变量中的实际值
- 引用值是保存在内存中的对象,JS不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是对该对象的引用而非实际的对象本身。为此,保存 引用值 的变量是按 引用 访问的
动态属性
- 原始值不能有属性
- 引用值可以动态添加后面可以使用的属性
- 原始类型的初始化可以只使用原始字面量形式,若使用 new 关键字,则会创建一个Object类型的实例,但其行为类似原始值
let n1 = 'a'
n1.age = 18
console.log(n1.age) // undefined
let n2 = new String('b')
n2.age = 20
console.log(n2.age) // 20
console.log(typeof n2) // object
复制值
-
原始值赋值给新变量,两个变量完全独立,互不干扰
-
引用值赋值,存储在变量中的值也会被复制到新变量所在位置,但是这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。赋值操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另外一个对象上反映出来
传递参数
-
所有函数的参数都是按 值传递的,意味着函数外的值会被复制到函数内部的参数中,就像一个变量复制到另外一个变量一样。若为原始值,就跟原始值变量复制一样,引用值就跟引用值复制一样。变量有按值和按引用访问,而传参只有按值传递
-
按值传递参数,值会被复制到一个局部变量(即一个命名参数,arguments对象中的一个槽位);按引用传递参数时,值在内存中的位置会被保存到一个局部变量,意味着对本地变量的修改会反映到函数外部,例如
function addNum(num) { num++ return num } let count = 10 let result = addNum(count) console.log(count); // 10 console.log(result); // 11 function setName(obj) { obj.name = 'a' obj = {} obj.name = 'b' return obj } let person = {} let obj = setName(person) console.log(person.name); // 'a' console.log(obj.name); // 'b'-
对于函数addNum():有一个参数num,它其实是一个局部变量 在调用时,count作为参数传入,count的值为10,这个值被复制到参数num以便在addNum内部使用。在函数内部,参数num的值被加上了10,但这不会影响函数外部的原始变量count,它们互不干扰,只不过碰巧保存了一样的值。如果是按引用传递的,那么count的值也会被修改为11
-
对于函数setName():我们创建了一个对象person,传给setName方法,并被复制到obj中,在函数内部,obj和person指向同一个对象。结果就是即使是按值传递进去的,obj也会通过引用访问对象,给obj添加了name属性,函数外部的person也会反映这个变化。 注意这看似像传递了引用,实际上不是的,因为接下去我们将obj重新定义为一个有着不同name:'b'的新对象,如果是按引用传递的,那么person的name最终也应该是'b',但是我们看到访问的person.name为'a'
-
总结
- JS变量可以保存两种类型的值:原始值和引用值
- 原始值的大小固定,因此保存在栈内存上,从一个变量到另一个变量的复制原始值会创建该值的第二个副本
- 引用值是对象,存储在堆内存上,复制引用值只会复制指针,而不是对象本身
- typeof可以确定值的原始类型,instanceof用于确保值的引用类型
- ==强制类型转换的规则
- 注意NaN与包括NaN在内所有值都不相等