变量与基本数据类型
数据类型
Number
JavaScript不区分整数和浮点数,统一用Number表示。 以下为特殊的Number:
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
//把非数值转换为数值
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
Number('-12.4'); // -12.4
Number('121ewq'); // NaN
Number(00111); // 111
parseInt('1234budq'); // 1234
parseInt(''); // NaN
//转换时建议通过第二个参数指定基数,防止混乱
parseInt('10', 2); // 2
parseInt('10', 8); // 8
parseInt('10', 10); // 10
parseInt('10', 16); // 16
//parseFloat只解析十进制
parseFloat('1234bul'); //1234(整数)
parseFloat('0xA'); // 0
isNaN('qaz'); // true
对于NaN
只能使用isNaN(NaN)
判断。
字符串
字符串是以单引号'或双引号"括起来的任意文本,如果文本中既包含单引号也包含双引号,可用\
转义。
ES6中多行文本可用反引号`...`
表示。
var str = '字符\'串';
//ES6:多行文本可用反引号表示`...`
var moreStr = `Hello
World
!`;
常见字符串操作:
//把两个字符串连起来,使用 + 号
var str = str1 + str2;
//ES6:支持模板字符串
var message = `你好, ${name}, 你今年${age}岁了!`;
//获取字符串长度
str.length;
//可以像数组一样获取字符串指定位置的值,但不可赋值
str[2];
//转换为字符串
//数值,布尔值,对象,字符串都有toString()方法
var a = 11;
a.toString(); // "11"
// 数值以2进制输出
a.toString(2);
String(null); // "null"
String(undefined); // "undefined"
布尔值
一个布尔值只有true
、false
两种值。
JavaScript同样支持 &&
||
!
运算符。
true && true; //true,与运算
true || false; //true,或运算
!true; //false,非运算
2 >= 1; //true
6 == 6; //true
isNaN(NaN); // true
// == 会进行类型转换,===为完全相等
false == 0; // true
false === 0; // false
//转换为布尔值
Boolean(NaN); // false
Boolean(1111); //true
对象
对象类型包括:数组(Array)、函数(Function)、还有两个特殊的对象:正则(RegExp)和日期(Date)。
对象用{}表示,键值对以xxx: xxx形式申明,属性名是字符串,值可以是任意类型。
对象类型示例:
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null,
'middle-school':'No.1 Middle School' //如果属性名包含特殊字符,就必须用''括起来
};
//通过 对象变量.属性名 的方式获取值
person.name; // 'Bob'
//包含特殊字符的属性名需要用[]访问
person['middle-school'];
//访问不存在的属性将返回undefined
person.aabc;
//可以直接新增属性
person.phone = '13010011100';
//删除属性
delete person.phone;
//检查某个属性是否存在
'name' in person; //true
//判断一个属性是否是自身拥有的,而不是继承的
person.hasOwnProperty('name'); // true
person.hasOwnProperty('toString'); // false
数组(特殊对象)
用中括号定义,里面可以是任意数据类型,也可以嵌套。
var arr = [1, 2, 3.14, 'Hello', null, true];
arr[0]; //索引值从0开始
arr[6]; // 索引超出了范围,返回undefined
//多维数组
var arr2 = [[1, 2, 3], [400, 500, 600], '-'];
数组的常见操作
//获取数组长度,可以对长度直接赋值
arr.length; // 6
//如果通过索引赋值时,索引超过了范围,同样会引起数组长度变化:
arr[10] = 'x';
Map
Map是一组键值对的结构,具有极快的查找速度。
//初始化需要一个二维数组或者一个空Map
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
/* 常用操作 */
var m = new Map(); // 空Map
//设置一个新的key-value,对于已存在的key会覆盖旧值
m.set('Adam', 67);
//判断key是否存在
m.has('Adam'); // true
//获取一个key的值
m.get('Adam'); // 67
//删除一个key
m.delete('Adam');
//不存在的key,返回undefined
m.get('Adam'); // undefined
Set
Set是一组元素的集合,并且集合中的元素不会重复,类似于Redis中的集合(Set)。
//初始化需要一个一维数组或者空Set
var s1 = new Set();
var s2 = new Set([1, 2, 3]);
//重复的元素会被忽略
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
//添加元素,可以重复添加,但不会有效果
s.add(4);
//删除元素
s.delete(3);
undefined
undefined类型只有一个字面量也就是undefined
。
//只定义未赋值的变量
var message;
message == undefined; //true
//虽然都返回undefined,但是其他地方使用未定义的变量会产生错误
typeof message; //undefined
typeof abc; //undefined
null
同样只有一个字面量null
。
var a = null;
typeof a; //object, 因为null表示一个空对象指针
a == null; //true
//undefined派生自null
null == undefined //true
null === undefined //false
typeof 和 instanceof
typeof操作符返回一个字符串,表示未经计算的操作数的类型。instanceof用于判断原型链。
// Numbers
typeof 37 === 'number';
typeof NaN === 'number';
// Strings
typeof "" === 'string';
typeof (typeof 1) === 'string'; // typeof总是返回一个字符串
// Undefined
typeof undefined === 'undefined';
typeof declaredButUndefinedVariable === 'undefined';
typeof undeclaredVariable === 'undefined';
// Objects
typeof {a:1} === 'object';
// 使用Array.isArray 或者 Object.prototype.toString.call
// 区分数组,普通对象
typeof [1, 2, 4] === 'object';
typeof new Date() === 'object';
// 下面的容易令人迷惑,不要使用!
typeof new Boolean(true) === 'object';
typeof new Number(1) === 'object';
typeof new String("abc") === 'object';
// 函数
typeof function(){} === 'function';
typeof class C{} === 'function'
typeof Math.sin === 'function';
typeof new Function() === 'function';
//NULL
typeof null === 'object';
person instanceof Object;
person instanceof Array;
person instanceof RegExp;
数字、字符串、布尔值及null和undefined为基本数据类型,其他的为引用类型。
变量定义
JavaScript与其他大多数语言一样,变量名是大小写英文、数字、$和_的组合,且不能用数字开头。变量名也不能是JavaScript的关键字,如if、while等。 JavaScript为松散数据类型,不同类型可以相互赋值。
var
通过 var
关键字来申明变量,同一变量只能用var申明一次,如果一个变量在函数体内部用var申明,则该变量的作用域为整个函数体。
var a; // 申明了变量a,此时a的值为undefined
var $b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1
var s_007 = '007'; // s_007是一个字符串
var Answer = true; // Answer是一个布尔值true
let
ES6新增了let
,它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效(局部作用域)。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
//常用于for循环时定义临时变量
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i); // ReferenceError: i is not defined
对于下面这个例子,var定义的变量i都指向全局的i,而let定义的i只在本轮循环有效,每一次循环的i其实都是一个新的变量:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
const
const
声明一个只读的常量。一旦声明,常量的值就不能改变。其作用域与let命令相同:只在声明所在的块级作用域内有效
const PI = 3.1415;
//局部作用域
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
解构赋值
从ES6开始,引入解构赋值,可以同时对一组变量进行赋值。
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
//可以忽略元素
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
//可以对一个对象进行解构赋值,只要结构正确
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school',
address: {
city: 'Beijing',
street: 'No.1 Road',
zipcode: '100001'
}
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined
// 把passport属性赋值给变量id:
let {name, passport:id} = person;
id; // 'G-12345678'
//支持定义默认值
var {name, single=true} = person;
常见用法:
//交换变量
var x=1, y=2;
[x, y] = [y, x];
//快速获取当前页面的域名和路径
var {hostname:domain, pathname:path} = location;
注意:有些时候,如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:
// 声明变量:
var x, y;
// 解构赋值:
{x, y} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token =
//这是因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法。
//解决方法是用小括号括起来:
({x, y} = { name: '小明', x: 100, y: 200});
strict模式
如果变量没有通过var申明就使用,则为全局变量,为了修补这个问题,需在代码第一行加上 ‘use strict’;
JavaScript在设计之初,为了方便初学者学习,并不强制要求用var申明变量。导致如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量:
i = 10; // i现在是全局变量
在同一个页面的不同的JavaScript文件中,如果都不用var申明,恰好都使用了变量i,将造成变量i互相影响,产生难以调试的错误结果。 使用var申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内,同名变量在不同的函数体内互不冲突。 为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,强制通过var申明变量,否则将导致运行错误。
启用strict模式的方法是在JavaScript代码的第一行写上:
'use strict';
注释
//单行注释
/*
* 多行注释
*
*/
变量赋值
JavaScript的变量包含两种不同的数据类型值:基本数据类型值(String,Number,Null,Undefined,Boolean)和引用类型值(对象);
复制变量的值
对于基本类型,会在内存中创建一个新值,赋值给变量。
var a = 10;
var b = a;
b = 20;
a; //10
b; //20
对于引用类型,只是复制了指向内存实际存储位置的指针,两个变量指向的其实是同一块内存区域。
var arr = [1,2,3];
var brr = arr;
brr.pop(); // 3
brr; //[1,2]
arr; //[1,2]
//对于函数参数传递,有同样的情况
function setName(obj) {
obj.name = 'Amazing';
}
var person = {};
setName(person);
person; // {name: "Amazing"}
//如果在函数内声明obj变量,那么他就是个局部变量了,在函数结束后会被销毁,并不影响外面的person
//或者可以理解为声明新对象后,obj中保存的指针发生了变化,指向新的对象,但不会影响原值
function setName(obj) {
obj.name = 'Amazing';
obj = new Object();
obj.name = 'New';
}
var person = {};
setName(person);
person; // {name: "Amazing"}
条件判断与循环
if条件判断
var age = 3;
if (age >= 18) {
alert('adult');
} else if (age >= 6) {
alert('teenager');
} else {
alert('kid');
}
JavaScript把null
、undefined
、0
、NaN
和空字符串''
视为false
,其他值一概视为true
。
循环
for循环
var x = 0;
var i;
for (i=1; i<=10000; i++) {
x = x + i;
}
//遍历数组
var arr = ['Apple', 'Google', 'Microsoft'];
var i, x;
for (i=0; i<arr.length; i++) {
x = arr[i];
console.log(x);
}
for … in
//循环对象
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
//过滤掉继承属性
if (o.hasOwnProperty(key)) {
console.log(key); // 'name', 'age', 'city'
console.log(o[key]); // 'Jack', 20, 'Beijing'
}
}
//循环数组
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
请注意,for … in对Array的循环得到的是String而不是Number。
for … of
可以用来循环iterable类型,Array、Map和Set都属于iterable类型。另外还包括某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
//遍历数组
var a = ['A', 'B', 'C'];
for (let x of a) {
console.log(x); //A B C
}
//遍历Map
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (let x of m) {
console.log(x[0] + '=' + x[1]); //1=x 2=y 3=z
}
forEach
同样可以用来循环iterable类型。
//循环数组
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
//循环set
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
//对于set,element = sameElement
console.log(element);
});
//循环Map
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
while
var x = 0;
var n = 99;
while (n > 0) {
x = x + n;
n = n - 2;
}
//do...while
var n = 0;
do {
n = n + 1;
} while (n < 100);
break和continue
与大多数语言一样,break
为退出循环, continue
为跳过本次循环。
函数
函数的定义
函数的定义有两种形式:
function test(x) {
return true;
}
//匿名函数赋值给一个变量的形式,因为函数也是对象,也可赋值给一个变量。
var test = function (x) {
return true;
}; //完整语法:赋值语句不要漏掉分号
函数中如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined。
函数参数
JavaScript允许函数传入任意个参数,可以比定义的多,多出的将被忽略,可以比定义的少,缺少的参数将收到undefined
.
arguments参数
只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数
function abs() {
//常用于判断参数个数
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
rest参数
ES6新增,rest参数只能写在最后,前面用…标识,函数多余的参数将以数组形式交给变量rest,否则rest为一个空数组。
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 结果:
// a = 1
// b = undefined
// Array []
方法
在一个对象中绑定函数,称为这个对象的方法。
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 返回值根据当前时间改变
关于this
的指向:
- 如果以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming,符合预期。
- 如果在外部调用,则指向全局对象
window
,在strict模式下,会得到一个错误。 - 在函数内部定义的函数中调用,也会指向全局对象
window
(strict模式下,会得到一个错误)。
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25, 正常结果
getAge(); // NaN
/*在函数内部定义的函数中调用*/
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
装饰器
可以动态的改变函数的行为,apply
、call
详见常用方法及标准对象中的函数部分。
高阶函数
JavaScript的函数其实都指向某个变量。所以可以把一个函数当做参数传给另一个函数。
//高阶函数示例
function add(x, y, f) {
return f(x) + f(y);
}
闭包函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
//返回函数
var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
//执行
f(); // 15
在这个例子中,我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,每次调用都会返回一个新的函数,相互之间互不影响。
- TODO:待补充
箭头函数
ES6标准新增了一种新的函数:Arrow Function(箭头函数),相当于匿名函数。
x => x * x;
//上面的箭头函数相当于:
function (x) {
return x * x;
}
//另一种定义方式:包含{ }和return
x => {
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}
// 两个参数:
(x, y) => x * x + y * y
// 无参数:
() => 3.14
//返回对象
x => ({ foo: x })
箭头函数完全修复了this
的指向,this
总是指向词法作用域,也就是外层调用者obj,并且call()
和apply
无法改变其this
指向。
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25
这样就不需要之前
var that = this;
这种写法了。
generator
TODO
变量作用域
函数作用域
- 如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量;
- 由于JavaScript的函数可以嵌套,内部函数可以访问外部函数定义的变量,反过来则不行;
function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以访问foo的变量x!
}
var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}
//
- JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
变量提升
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:
'use strict';
function foo() {
var x = 'Hello, ' + y;
console.log(x);
var y = 'Bob';
}
虽然是严格模式,但是并不会报错,原因是变量y在稍后申明了。但是console.log显示Hello,undefined,说明变量y的值为undefined。这正是因为JavaScript引擎自动提升了变量y的声明,但不会提升变量y的赋值。
JavaScript引擎看到的代码相当于:
function foo() {
var y; // 提升变量y的申明,此时y为undefined
var x = 'Hello, ' + y;
console.log(x);
y = 'Bob';
}
鉴于这种情况,应将函数内所有用到的变量统一在函数头部进行声明。
全局作用域
不在任何函数内定义的变量就具有全局作用域。JavaScript默认有一个全局对象window
,全局作用域的变量实际上被绑定到window
的一个属性:
'use strict';
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'
//函数定义也相同
function foo() {
alert('foo');
}
foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用
命名空间
全局变量都会绑定到window
上,不同的JavaScript文件如果使用了相同的变量名可能会产生冲突。
所以可以把自己所有的变量和函数放到一个对象中,只将该对象注册到window
:
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
局部作用域
ES6新增,通过let
和const
声明的变量具有局部作用域(详见变量定义)。