JavaScript基础与数据类型
一. Number
JavaScript不区分整数和浮点数,统一用Number表示,以下都是合法的Number类型
123; // 整数 123
0.456; // 浮点数 0.456
1.2345e3; // 科学计数法表示 1.2345x1000,等同于 1234.5
-99; // 负数
NaN; // NaN 表示 Not a Number,当无法计算结果时用 NaN表示
Infinity; // Infinity 表示无限大,当数值超过了 JavaScript 的 Number所能表示的最大值时,就表示为 Infinity
要特别注意相等运算符==
,JavaScript 在设计时,有两种比较运算符:
- 第一种是
==
比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果; - 第二种是
===
比较,它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。
由于 JavaScript 这个设计缺陷,不要使用==
比较,始终坚持使用===
比较。
二. 字符串
字符串是以单引号'或双引号"括起来的任意文本,比如'abc',"xyz"等等。
// 多行字符串
var i = `this
is multi
line`
// 模板字符串
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`; // 你好, 小明, 你今年20岁了!
// 操作字符串
var s = 'Hello, world!';
s.length; // 13
s[0]; // 'H'
s[6]; // ' '
s[7]; // 'w'
s[12]; // '!'
s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
s.indexOf('World'); // 返回7
s.indexOf('world'); // 没有找到指定的子串,返回-1
三. Array
JavaScript 的数组可以包括任意数据类型。
// 创建方式 1
var arr = [1, 2, 3.14, 'Hello', null, true];
arr[0]; // 返回索引为0的元素,即1
arr[5]; // 返回索引为5的元素,即true
arr[6]; // 索引超出了范围,返回undefined
// 创建方式 2
new Array(1, 2, 3); // 创建了数组[1, 2, 3]
// 对 length 赋值会改变 array 元素
var arr = [1, 2, 3];
arr.length; // 3
arr.length = 6;
arr; // arr变为[1, 2, 3, undefined, undefined, undefined]
arr.length = 2;
arr; // arr变为[1, 2]
// 对不存在的 array 赋值也会改变 array 元素
var arr = [1, 2, 3];
arr[5] = 'x';
arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
// array 的 slice,相当于Python中的切片,但是用法有区别
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C']
arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']
var aCopy = arr.slice(); // ['A', 'B', 'C', 'D', 'E', 'F', 'G']
// push和pop
// push()向Array的末尾添加若干元素,pop()则把Array的最后一个元素删除掉:
四. 对象
4.1 对象属性
JavaScript 的对象是一组由键-值组成的无序集合,对象的键都是字符串类型,值可以是任意数据类型。其中每个键又称为对象的属性,例如:
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
'middle-school': 'No.1 Middle School'
hasCar: true,
zipcode: null
};
// 获取对象的属性
person.name; // 'Bob'
person.zipcode; // null
// key是特殊字符串时,不能用 点 来访问属性。
xiaohong['middle-school']; // 'No.1 Middle School'
var xiaoming = {
name: '小明'
};
xiaoming.age; // undefined
xiaoming.age = 18; // 新增一个age属性
xiaoming.age; // 18
delete xiaoming.age; // 删除age属性
xiaoming.age; // undefined
delete xiaoming['name']; // 删除name属性
xiaoming.name; // undefined
delete xiaoming.school; // 删除一个不存在的school属性也不会报错
// 如果in判断一个属性存在,这个属性不一定是xiaoming的,它可能是xiaoming继承得到的
'name' in xiaoming; // true
'grade' in xiaoming; // false
'toString' in xiaoming; // true
// 要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法
var xiaoming = {
name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false
4.2 对象方法
在一个对象中绑定函数,称为这个对象的方法。和普通函数也没啥区别,但是它在内部使用了一个 this
关键字。
- 在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量。所以,
this.birth
可以拿到 xiaoming 的 birth 属性。
// 定义方法1
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是25,明年调用就变成26了
// 定义方法2
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25, 正常结果
getAge(); // NaN
注意:JavaScript 的函数内部如果调用了this,那么这个this到底指向谁?答案是,视情况而定!
- 在定义方法2中,如果以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming。
- 如果单独调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window。
- 如果单独调用函数,在 strict模式 下让函数的 this 指向 undefined 。
- 解决 this 指定的问题可以使用:函数本身的两个方法解决(apply、call)
4.3 装饰器
这个装饰也同 Python 中的装饰器,对原来的函数进行修饰添加新功能,使用apply来实现。
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
// 调用原函数,null表示oldParseInt本身,arguments是原本的参数
return oldParseInt.apply(null, arguments);
};
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3
五. Map
JavaScript 的默认对象表示方式 {} 可以视为其他语言中的Map或Dictionary的数据结构,即一组键值对。但是JavaScript的对象有个小问题,就是键必须是字符串。为了解决这个问题,最新的ES6规范引入了新的数据类型Map,Map是一组键值对的结构,具有极快的查找速度。
// 创建 Map
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
// 创建 Map
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
六. Set
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
var s = new Set([1, 2, 3]);
s.add(4);
s.delete(3);
七. iterable
ES6 标准引入了新的 iterable
类型,Array、Map
和 Set
都属于 iterable
类型。具有 iterable
类型的集合可以通过for...in
、for...of
循环来遍历。iterable
内置的forEach
方法也可以循环
八. 空值与strict模式
空值
null
表示一个“空”的值- 0 数字 0
- '' 长度为 0 的字符串
undefined
表示值未定义,仅仅在判断函数参数是否传递的情况下有用。
strict模式
如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量
// 不使用 strict 模式时
i = 10; // i现在是全局变量
// 在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。
// 启用strict模式的方法是在JavaScript代码的第一行写上:
'use strict';
九. for 循环
9.1 循环 for...in
它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
// 数组循环
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
// 其实可以给数组定义属性名称,遍历时是对数组名称进行遍历的
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x in a) {
console.log(x); // '0', '1', '2', 'name'
}
// 对象循环
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
console.log(key); // 'name', 'age', 'city'
}
9.2 循环 for...of
// 用 for ... of 循环遍历集合
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
console.log(x); // A B C
}
for (var x of s) { // 遍历Set
console.log(x); // A B C
}
for (var x of m) { // 遍历Map
console.log(x[0], x[1]); // 1, 'x' 2, 'y' 3, 'z'
}
9.3 循环 forEach
然而,更好的方式是直接使用 iterable
内置的 forEach
方法,它接收一个函数,每次迭代就自动回调该函数。
- array 的 forEach
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element, index);
});
A 0
B 1
C 2
- set 的 forEach
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
console.log(element);
});
A
B
C
- map 的 forEach
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
x
y
z
十. 变量与常量
全局变量 局部变量
定义
- 在函数内部定义的是局部变量。
- 不在任何函数内定义的变量就是全局变量,具有全局作用域。
- 如果局部变量、全局变量有重名,那么会使用局部变量。
- JavaScript默认有一个全局对象window,全局作用域的变量、函数实际上被绑定到window的一个属性
- JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。
// 全局变量被绑定到了 window 对象上
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'
// 顶层函数的定义也被视为一个全局变量,并绑定到window对象
function foo() {
alert('foo');
}
foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用
名字空间
全局变量会绑定到 window 上,不同的 JavaScript 文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。许多著名的 JavaScript 库都是这么干的:jQuery,YUI,underscore 等等。
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
局部作用域(块级)
// 由于使用了var定义的i,在 for 循环结束以后 i 任然可以使用
function foo() {
for (var i=0; i<100; i++) {
}
i += 100; // 仍然可以引用变量 i
}
// 用let替代var可以申明一个块级作用域的变量
function foo() {
for (let i=0; i<100; i++) {
}
i += 1; // 语法错误,这里已经没有了变量 i 了
}
常量
// 常量无法被修改
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14
解构赋值
解构可以同时对一组变量进行赋值。
// 注意,对数组元素进行解构赋值时,多个变量要用[...]括起来
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// 嵌套解构
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'
// 忽略前两个元素,只对z赋值第三个元素
let [, , z] = ['hello', 'JavaScript', 'ES6'];
z; // 'ES6'
// 对象解构
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;
// name = 小明, age = 20, passport = G-12345678
// 解构默认值:解构时指定默认值,如果不存在则使用默认值代替
var [x, y, z, a='999'] = ['hello', 'JavaScript', 'ES6']; // a 不存在,使用 999 代替
// 解构默认值:如果 person 对象没有 single 属性,默认赋值为 true
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678'
};
var {name, single=true} = person;
name; // '小明'
single; // true
解构函数参数
// 如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。
function buildDate({year, month, day, hour=0, minute=0, second=0}) {
return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}
// 它的方便之处在于传入的对象只需要 year month day 这三个属性
buildDate({ year: 2017, month: 1, day: 1 });
// 也可以传入hour、minute和second属性
buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });