JavaScript基础

变量与基本数据类型

数据类型

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"

布尔值

一个布尔值只有truefalse两种值。

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把nullundefined0NaN和空字符串''视为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();
    }
};

装饰器

可以动态的改变函数的行为,applycall 详见常用方法及标准对象中的函数部分。

高阶函数

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

变量作用域

函数作用域

  1. 如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量;
  2. 由于JavaScript的函数可以嵌套,内部函数可以访问外部函数定义的变量,反过来则不行;
function foo() {
    var x = 1;
    function bar() {
        var y = x + 1; // bar可以访问foo的变量x!
    }
    var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}

//
  1. 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新增,通过letconst声明的变量具有局部作用域(详见变量定义)。

Contents