第3章 JS语言基础
1、变量
js的变量有三种:var、let、const。
- 在函数定义中,var变量就是函数作用域中的变量,在函数退出时销毁。如下的a函数中的b变量。
- 在函数定义中,var变量就是函数作用域中的变量,在函数退出时销毁。如下的a函数中的b变量。
var message
console.log(message) // undefined
function a() {
var b = 'hello';
c = 'cat';
console.log(b);
}
a() // hello
console.log(b) // b is not defined
console.log(c) // cat
- 使用var的变量会自动提升到函数作用域顶部。提升后不会报错,会给变量赋值undefined。foo函数和foo1函数是等价的。
- let、const与var的区别是:let、const会产生块级作用域。
- 对比foo与foo2的,foo中是因为var会进行变量提升,foo2中其实也会进行变量提升,但是提升后却没有添加属性值undefined,所以报错说没有初始化。
function foo() {
console.log(age); // undefined
var age = 26;
}
foo()
function foo1() {
var age;
console.log(age); // undefined
var age = 26;
}
foo1()
function foo2() {
console.log(age); // Cannot access 'aa' before initialization
let age = 26;
}
foo2()
- 最佳实践:不使用var,const优先、let次之。
2、数据类型、typeof、instanceof
- 数据类型分为原始数据类型和复杂数据类型 原始数据类型:Number、String、Boolean、Null、Undefined、Symbol、Bigint 复杂数据类型:Object,Object又可分为Object、Function、Array、Date、RegExp等
- typeof可以用来确定一个值是什么类型
typeof('123') // string
typeof(123) // number
typeof(NaN) // number
typeof(false) // boolean
typeof(undefined) // undefined
typeof(null) // object
typeof(Symbol()) // symbol
typeof(function() {}) // function
typeof({}) // object Object中除了function其他typeof值都为object
typeof([]) // object
typeof(new Date) // object
typeof(a) // undefined 变量a未定义
// 用typeof来判断类型比较粗糙,如果想要精确的判断类型可以使用Object.prototype.toString
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call('hi') // '[object String]'
Object.prototype.toString.call({a:'hi'}) // '[object Object]'
Object.prototype.toString.call([1,'a']) // '[object Array]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(() => {}) // '[object Function]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(Symbol(1)) // '[object Symbol]'
Object.prototype.toString.call(Math) // '[object Math]'
Object.prototype.toString.call(new Date) // '[object Date]'
- instanceof 用来验证,一个对象是否为指定的构造函数的实例,返回的是布尔值。 instanceof只能用于对象,不适用于基本数据类型。
object instanceof constructor
instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。
const d = new Date()
d instanceof Date // true
d instanceof Object // 由于instanceof是在原型链上查找,只要处于原型链中,判断永远为true。所以返回是true
undefined instanceof Object // false
null instanceof Object // false
2.1、Null和Undefined
Null和Undefined都表示没有,含义相似,语法效果几乎没有什么区别,但有如下特征。
- Null表示一个空指针对象 ,这也是typeof(Null)返回object的原因,所以在定义一个将来要保存对象的变量时,建议使用Null来初始化。
- Undefined表示未定义 ,所以typeof一个未初始化的变量会返回Undefined。注意,不要显式以Undefined来初始化变量。
- Null和Undefined都是假值,即都是false。
2.2、Boolean类型
Boolean类型有true和false。false有:Undefined、Null、false、0、NaN、'',其他的都是为true。 实践中经常发现使用:!!。使用双重的否运算符(!)也可以将任意值转为对应的布尔值。
2.3、Number类型
- 浮点值:浮点值的精度很高,但在算术计算中不如整数精确,所以计算时要非常小心。如 0.1 + 0.2 === 0.3 的结果为false。因为0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成0.30000000000000004。解决方法:可以使用 toFixed() 或者乘以100或者Lodash。
- Infinity:表示“无穷”。有两种场景:一种是正的数值太大,或者负的数值太小;另一种是非0数值除以0。
- 值的范围一个用Number.MAX_VALUE和Number.MIN_VALUE来查询,如果超出部分,js就会返回
Infinity
。要确定一个值是不是有限大,可以用 isFinite() 函数来检测。 -Infinity
有正负之分,Infinity
表示正的无穷,-Infinity
表示负的无穷。
console.log(Infinity === -Infinity) // false
- NaN:是js的特殊值,表示非数字。主要出现在将字符串解析成数字出错的场合。
console.log(0/0) // NaN
console.log(5 - 'x') // NaN
1. NaN
是特殊的数值,类型仍然是数值。
console.log(typeof(NaN)) // number
2. NaN
不等于任何值,包括它本身。
console.log(NaN === NaN) // false
3. NaN
与任何数(包括它自己)的运算,都是NaN
。
4. isNaN()
函数是用来判断接受的这个参数是否"不是数值"。isNaN()
可以用来判断一个值是否是NaN
。但有个更好的方法是,利用NaN
唯一不等于自身值的特点来判断。
function myIsNaN (value) {
return value !== value;
}
- 与数值相关的全局方法
Number.parseInt() // 用于将字符串转为整数。如果不能转化为数字,返回NaN
Number.parseFloat() // 用于将字符串转为浮点数。如果不能转化为数字,返回NaN
Number.isInteger() // 用来判断一个数值是否为整数。
Number.isNaN() // 用来判断接受的这个参数是否"不是数值"。间接的判断是否是NaN
Number.isFinite() // 表示某个值是否为正常的数值。除了Infinity、-Infinity、NaN和undefined这几个值会返回false,isFinite对于其他的数值都会返回true。
parseInt('123') // 123
parseFloat('3.14') // 3.14
Number.isInteger(123) // true
isNaN(NaN) // true
isFinite(Infinity) // false
2.4、String类型
- 字符串就是零个或多个排在一起的字符,可以使用单引号('')、双引号("")、反引号(``)表示。
- length属性返回字符串的长度。
- 模板标签:模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
2.5、Symbol类型
- 对象属性名都是字符串,这容易造成属性名的冲突。Symbol保证每个属性的名字都是独一无二,这样就从根本上防止属性名的冲突。
- 基本用法
let s = Symbol('foo');
// Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
typeof s
// "symbol"
s.description // "foo"
- Symbol作为属性名时,可以作为标识符,保证不会出现同名的属性。Symbol作为属性名可以用[],不能用点运算符。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 下面这是错误的,这样写导致属性名是一个字符串
a.mySymbol = 'Hello!';
- 应用:消除魔术字符串(ES6入门.12.4)
- Symbol 值作为属性名时,是公开属性。但是在遍历对象的时候,属性不会出现在
for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。我们可以用Reflect.ownKeys()
返回所有类型的键名。
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]
Symbol.for()
方法可以使用同一个 Symbol 值。Symbol.for()
不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。
2.6、BigInt类型
之前JS所有的数字都保存成 64 位浮点数。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
1234 // 普通整数
1234n // BigInt,为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n。
// BigInt 的运算
1n + 2n // 3n
// BigInt 与普通整数是两种值,它们之间并不相等。
42n === 42 // false
// BigInt 可以使用负号(-),但是不能使用正号(+)
-42n // 正确
+42n // 报错
2.7、对象
- 对象(object)就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。
const obj = {
foo: 'Hello',
bar: 'World'
};
obj.foo // 'Hello'
- 对象的引用 如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。
const o1 = {};
const o2 = o1;
o1.a = 1;
o2.a // 1
o2.b = 2;
o1.b // 2
- 对象属性的操作
// 读取和赋值
const obj = {
p: 'Hello World'
};
obj.p // "Hello World"
obj['p'] // "Hello World",这里的p如果不加单引号,就会被认为是变量,这个特性在操作复杂对象的时候很方便
// 删除
delete obj.p // delete命令只能删除对象本身的属性,无法删除继承的属性
// 遍历
// 用for…in来遍历对象,有个注意点,它不仅遍历对象但属性,还遍历继承属性。
// 如果不想遍历继承属性,可以使用Object.keys()、Object.values()、Object.entries()。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
sex: "男"
}
const man = new Person("张三", 18);
console.log(Object.keys(man));//["name","age"]
for (const key in man) {
console.log(key);//name age sex
}
3、运算符
3.1、加运算符
加运算符允许非数值的相加,如果其中一个是非数字,则加法运算会变成连接运算符,最后以字符形式输出。
1 + 1 // 2
true + true // 2
false + true // 1 对于布尔值:false -> 0,true -> 1
'a' + 'b' // 'ab'
'1' + '2' // '12'
1 + 'a' // '1a' 一个是字符串,一个是非字符串,这时非字符串会转为字符串,然后连接在一起。
1 + null // 1
1 + undefined // NaN
'a' + null // anull
'a' + undefined // aundefined
'3' + 4 + 5 // '345'
3 + 4 + '5' // '75' 注意:加运算符在运行时决定相加还是连接,这种现象叫‘重载’
const obj = { p: 1 }
obj + 2 // '[object Object]2' 对象转化:先调用valueOf方法返回对象自身{ p: 1 },再调用toString方法,将其转为字符串[object Object]。
下面的+可以将任何值转为数值,与Number函数的作用相同
+01 // 1
+'1' // 1
+'a' // NaN
+'' // 0
+false // 0
+true // 1
+[] // 0
+null // 0
+{} // NaN
+undefined // NaN
除了加法运算符,其他算术运算符(比如减法、除法和乘法)都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。
3.2、布尔运算符
- 取反运算符(!)
以下六个值取反后为true,其他值都为false。
undefined、null、false、0、NaN、''
let x = 6
!!x 等同于 Boolean(x)
- 且运算(&&)和或运算(||) &&:第一个为true,则返回第二个;第一个为false,则返回第一个,且不再对第二个求值。 ||:第一个为true,则返回第一个,且不再对第二个求值;第一个为false,则返回第二个。
3.3、比较运算符
- 非相等运算符的比较 如果都是字符串,就按Unicode码来比较;否则将两个转成数值再比较。 需要注意的是,任何值与NaN比较都是false。
- 相等运算符 比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。
// 原始类型值:原始类型的值会转换成数值再进行比较。
1 == true // true 等同于 1 === Number(true)
'true' == true // false 等同于 Number('true') === Number(true)
'' == 0 // true 等同于 Number('') === 0
// 对象与原始类型值比较:先调用对象的valueOf()方法,如果得到原始类型的值,就按照上一小节的规则,互相比较;
// 如果得到的还是对象,则再调用toString()方法,得到字符串形式,再进行比较。
[1] == 1 // true
[1] == '1' // true
// undefined和null:undefined和null只有与自身比较,或者互相比较时,才会返回true;与其他类型的值比较时,结果都为false。
undefined == undefined // true
null == null // true
undefined == null // true
false == null // false
false == undefined // false
0 == null // false
0 == undefined // false
- 严格相等运算符
1 === '1' // false 原始类型的值会转换成数值再进行比较。
true === 'true' // false
NaN === NaN // false
+0 === -0 // true
{} === {} // false 复合类型的数据比较,比较的是它们是否指向同一个地址。
[] === [] // false
(function () {} === function () {}) // false
undefined === undefined // true
null === null // true
3.4、其他运算符
- 逗号运算符 用于对两个表达式求值,并返回后一个表达式的值。
'a', 'b' // 'b'
const y = (1, 10) // 10
4、数据类型转换
强制转换:指的是使用Number()
、String()
、Boolean()
三个函数,手动换换为数字、字符串、布尔值。
Number()函数,可以将任意类型的值转化成数值。
Number('324') // 324
Number('324abc') // NaN
Number('') // 0
Number(false) // 0
Number(true) // 1
如果参数是对象:
第一步,调用对象自身的valueOf方法,如果返回的是原始类型的值,则对该值使用Number函数,不再进行后续步骤。
第二步,当valueOf返回的是对象,则对该值使用toString方法,如果返回是原始类型的值,则对该值使用Number函数,不再进行后续步骤。
第三步,当toString方法返回的是对象,就报错。
obj = { a: 1 }
Number(obj)
执行步骤:obj.valueOf -> { a: 1 } 返回对象本身
obj.toString -> [object Object] 得到字符串[object Object]
Number([object Object]) -> NaN
Number([]) // 0
Number([1, 2]) // NaN
Number([5]) // 5
Number(null) // 0
Number({}) // NaN
Number(undefined) // NaN
String()函数可以将任意类型的值转化成字符串。
String(123) // ’123‘
String('abc') // ‘abc’
String(true) // ’true‘
String(undefined) // ‘undefined’
String(null) // ’null‘
如果参数是对象:
第一步,调用对象自身的toString方法,如果返回的是原始类型的值,则对该值使用String函数,不再进行后续步骤。
第二步,当toString返回的是对象,则对该值使用valueOf方法,如果返回是原始类型的值,则对该值使用String函数,不再进行后续步骤。
第三步,当valueOf方法返回的是对象,就报错。
String({a: 1}) // ’[object Object]’
String([1, 2, 3]) // ‘1,2,3‘
Boolean()函数可以将任意类型的值转为布尔值。
除了以下五个值的转换结果为false,其他的值全部为true。
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
5、for循环
- 以下三种循环是等价的
for (let i = 0; i < 10; i++) {
console.log(i)
}
let i = 0
for (; i < 10;) {
console.log(i)
i++
}
let i = 0
while(i < 10) {
console.log(i)
i++
}
- for-in是用来遍历对象的
for (const item in obj) {
console.log(item)
}
- for-of是用来遍历数组
for (const item of arr) {
console.log(item)
}
- break和continue break是用于立即停止循环,强制执行循环之后的代码。 continue会退出当前这次循环,但会再次从循环顶部开始执行。