数据类型和运算符
js一共有7种数据类型,简称‘四基两空一对象’。
7种数据类型分别为:
-
四基:String、Number、Symbol、Bool
-
两空:null、undefined
-
一对象:Object
其中string(字符串)、number数字、布尔值(bool)属于简单数据类型,它们已经分无可分了。
Object是复杂数据类型,还可以细分为狭义对象、数组、函数,我们一般说的对象不是指Object而是狭义对象。
特别要注意的是:函数、数组、日期都不是数据类型,他们都属于Object(考试要考)
typeof 运算符
js有三种方法可以确定数据类型到底是什么:
1、 typeof运算符
2、instanceof运算符
3、Object.prototype.toString方法
typeof 方法应用示例:
typeof 123 //"number"
typeof '123' //"string"
typeof false //"boolean"
typeof function(){} // "function"
typeof undefined // "undefined"
typeof null // "object"
typeof {a:1,b:2} // "object"
typeof [1,2,3] // "object"
typeof 数组或者null的时候返回的都是"object",如果想要区分数组和对象,则使用instanceof,数组是一种特殊的对象。
typeof null返回的是object是因为一开始设计js时,设计者把null作为一种特殊的对象。后来null独立出来了,为了兼容以前的代码,就没有对此做出修改。
Number
实际上JS的数字类型是64位浮点数,所以不管你打出来的是不是整数,它实际上都是浮点数的一种。
对于Number,能讲的可能是它的一些特殊值,比如说最大值Number.maxValue,最小值Number.minValue,正无穷+Infinity、负无穷-Infinity、和NaN(非数字)
可以使用Number.isNaN()方法来测试是不是NaN,如果是则返回true。
NaN与任何东西都不相等,包括它自己。
console.log(NaN === NaN);//false
目前数字具有精度不准的问题,例如:
for(var i= 0.1;i<2;i=i+0.1){
if (i ===1){
console.log(i);
break;
}
}
console.log(i)//2.0000000000000004
按照正常的逻辑,这段代码应当打出1才对,说明浮点数在相加的时候,会出现精度不准的情况。
parseInt
parseInt可以将浮点数转化成整数,直接截断小数点后面的数字。
parseInt(3.14) //3
parseInt()函数括号里的参数实际上是字符串,并且返回一个整数。
如果参数不是字符串,则会变成字符串进行转换。如果遇到不能转成数字的字符串,那么会停止,并返回已经转好的部分。
如果字符串第一个就不能转成数字,那么返回NaN
typeof parseInt('3.14') //3 number
parseInt('3a') //3
parseInt('a') //NaN
parseFloat
parseFloat 和parseInt类似,同样接受一个字符串,返回一个小数。当传入的字符串第一个就不能转化时,返回NaN。当参数是''时,同理。
parseFloat('')//NaN
parseFloat([])//NaN
isNaN
isNaN方法可以判断一个值是否为NaN,true代表是NaN。
isNaN(NaN) //true
isNaN(123) //false
isNaN([]) //false
isNaN('hello') //true
isNaN(['hello']) //true
isNaN([123]) // false
isNaN({}) // true
isNaN()括号中的参数会被先转化成数字,再返回布尔值。
比如,传入字符串,会被转化成数值,整个过程是类似于isNaN(Number('hello')),基于这个特性,只有一个数字的数组和空数组会返回false,因为Number([])会返回1,Number([123])返回123。
String
字符串的写法:
'hello'
"hello"
`hello`
现在我们都提倡用反引号来书写。这也是es6的最新写法,好处多多,反正干就对了。
在字符串中,如果想书写引号,有可能会被js当成是字符串,那么可以使用转义符\操作。
常见的转义符:
' 表示单引号
" 表示双引号
\n 表示换行
\r 表示回车
\t 表示制表符
\b 表示空格
字符串跟数字都不是对象,属于字面量。按理来说除了非对象都没有属性,但是字符串有对象的属性
例如:
str.length可以返回字符串的长度
字符串也可以用下标索引进行读取,就像数组一样。
例如:
'string'[0] // 返回s
's'[1] // 返回undefined
每个下标都是从0开始。如果索引超出字符串长度,则会返回undefined。跟数组不一样的是,字符无法像数组一样可以被更改。
boolean
布尔值只有两个,true和false。
以下操作会得到布尔值
- 否定运算
!value - 相等运算
1==2 2!=3 3===4 - 比较运算
1>2 3<4 3<=4
需要注意的是逻辑运算符&& || 不能直接得到布尔值。
例如:
0 && 1 //得到0而不是false
六个falsy值
0、null、undefined、NaN、'' 、false
这六个falsy值返回的就是false
null和undefined
只有js语言才有两个空,这俩货没有本质上的区别。
有一些细节:
如果一个变量声明了但是未定义,那么变量为undefined
如果一个函数没有return,那么返回的也是undefined(我记得我写过一篇博客,log返回的也是undefined。)
前端程序员如果需要设置空,会定一个非对象为undefined,定一个对象为null(仅仅是习惯问题)。
symbol
千年难得一用,我现在不会~
再来说说变量声明的一些事
let es6推荐使用,不会有一些莫名其妙的bug
1、这种声明方式遵循作用域块,实际上就是大括号{}
2、不能重复声明
3、可以赋值也可以不赋值
4、必须先声明后使用,否则报错
5、用let声明全局变量,不会变成window的属性
6、for和let搭配会产生一个隐藏作用域
var 不推荐使用,太垃圾了。。。
关于let,我还写了一篇博客,欢迎阅读let薛定谔的变量提升(读方应杭知乎的一点延伸)
const
声明了一个常量,一旦声明了,就不能修改了。其他与let没区别。
类型转换
转化成字符串
let a =1
a.toString()//`a` 数字转化成字符串
String(a)//`a`
a + '' //a
字符串转数字
let b ='1'
Number(b)
parseInt(b)
parseFloat(b)
b - 0
转换成布尔值
Boolean(x)
!!x 取反可以得到布尔值,再取反就是原值的布尔值
Object对象
说到对象,需要先说明的一点
{foo:1,}这串代码在chrome中不是一个对象,是一个lambol。如果是firefox,可以不加逗号。
对象的定义
对象是一组无序的数据集合,以键值对的形式储存。
let foo= 'bar'
var obj = {
foo: 'Hello',
bar: 'World'
};
obj[foo] ==='world' //true
obj.foo === obj['foo'] //true
细节:键名key是字符串,引号可以省略 通过obj.foo 可以获得值:'hello',也可以通过obj['foo']获得值:'hello'
特别需要注意这个foo是字符串。如果写成这样obj[foo],那么foo就会被当成一个变量来解析。
通过Object.keys(obj)方法可以得到obj对象的所有key,以数组的形式返回。
通过Obekect.values(obj)方法可以得到obj对象的所有value,以数组的形式返回。
如果我们希望键key是一个变量,变量内存的是键名,可以使用以下方法[]中括号包裹住变量名
let a ='name'
var obj={[a]:'我是name'}
实际上这是以前语法的衍生。以前我们会这样使用变量来给对象添加属性
let obj={}
let name='age'
obj[name]=18
console.log(obj)结果为{age:18}
通过中括号[]这种方法来添加属性,会先求里面的值,再转化为字符串,例如:
obj[1+2+3]='我是值' //得出obj为{6:我是值}
注意,数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。
var obj = {
123: 'hello world'
};
obj.123 // 报错
obj[123] // "hello world"
原型
这时候我们还可以思考一下变量在内存中的排列关系,例如内存图里面变量是怎么存储的。可以参见我的一篇博客。JS世界与内存
一句话总结:每个对象都有一个隐藏属性__proto__,里面保存了原型地址。原型实际上就是共有属性。Object函数实际上也是一个对象,它也有__proto__隐藏属性,指向了null。
in关键字
如果我们想知道属性名是否在对象中,可以使用in关键字。
'name' in obj 注意:因为key是字符串,所以请加引号,不加引号需要事先赋值才可以用,不加引号这种方式很容易出错!!
删
可以通过delete obj.name来删除对象的某一个属性。
或者使用obj.name =undefined来将属性值设置为undefined。
额外说一点
使用obj.xxx来查看属性值是不是undefined,如果没有xxx这个属性名也会返回undefined
let name = 'uname'
var obj = {
uname: 1,
age: undefined
}
console.log(name in obj); //true
console.log('age' in obj); //true
console.log(obj.age); //undefined age的属性值是undefined
console.log(obj.color); //undefined
没有color这个属性也出来undefined
可以使用以下方法获取值为undefined的属性xxx是否在对象中
'xxx' in obj && obj.xxx
查
使用Object.keys(obj)可以查看obj里面所有的属性名
使用console.dir(obj)查看自身属性+共有属性
查看共有属性可以直接打印obj.__proto__
判断一个属性是不是对象的私有属性
obj.hasOwnProperty('toString')
hasOwnProperty和in不能查看的区别在于,in无法判断是共有属性还是私有属性,而hasOwnProperty可以知道是不是私有属性
查看属性
可以使用obj.name或者使用obj['name']来查看对象的属性值。
注意:这里的属性名name是一个字符串。
请不要和obj[name]混淆了。
对象遍历
for..in 循环可以用于对象的遍历。
var obj = {a: 1, b: 2, c: 3};
for(let k in obj){
console.log(k)
console.log(obj[k])
}
// k: a b c
//obj[k] 1 2 3
这里有两个注意点:
- for in 会遍历对象所有可遍历的属性,会跳过不可遍历的属性
- 如果有一些继承来的属性可以遍历,那么也会遍历
举例来说,对象都继承了toString属性,但是遍历时不会遍历到,因为它是不可遍历的。
但是有一些继承的属性默认可以遍历,那么假设我们只要遍历对象自身的属性,那么可以通过hasOwnProperty判断一下
for (let k in obj){
if (obj.hasOwnproperty(k)){
console.log(obj[k])
}
}
增或者改
JS中增加或者修改对象都是差不多的,如果有就修改,没有就增加。
例如:
var obj={name:'姓名'}
obj.name ='邱彦兮' //修改
obj['age'] =8 //增加
我们也可以批量对对象的属性名进行赋值。
可以使用Object.assign(obj,{age:18,size:18})进行批量赋值
看下面的例子:
var obj = {};
Object.assign(obj, {
a: () => {
console.log(123)
},
b: '123'
})
console.log(obj);
打印结果为
我们可以修改对象的私有属性,那么如果修改对象的共有属性呢?
JS实际上是允许修改的,例如我要修改一个对象obj的共有属性
let obj = {};
obj.__proto__.toString = 'xxx';
Object.prototype.valueOf = 'yyy'
console.dir(obj)
打印出来看看
修改成功!!
一般来说不建议修改原型,会出现很多问题。
如果说想换掉整个原型,可以使用create方法,实际上是创建了一个新的原型,而js自生成的原型就挂在这个创建的新原型上。
let obj = Object.create({
uname: 'xxxx'
})
console.dir(obj)
用一张图来描述到底create做了啥吧