JavaScript基础 - 学习笔记
This is a Learn Notes for JavaScript.
ECMAScript 与 JavaScript
ECMAScript(以下简称 ES)是一种语言标准,而 JavaScript(以下简称 JS)是对 ES 的一种实现。
说 JavaScript 的版本,实际上就是说它实现了 ECMAScript 标准的哪个版本。
快速上手
嵌入位置
JavaScript 代码可以直接嵌在网页的任何地方,不过通常我们都把 JavaScript 代码放到 中的
也可以把 JavaScript 代码放在单独的文件中,使用 <script src="..."></script>
引入。
可以在同一个 html 中引入多个 JS 文件、多次编写
<script type="text/javascript">
中的 tpye 已不必显式书写,如今默认即 JS
运行 JS
直接在硬盘上创建好 HTML 和 JavaScript 文件,然后用浏览器打开。
这种方式运行部分 JavaScript 代码没有问题,但由于浏览器的安全限制,以 file:// 开头的地址无法执行如联网等 JS 代码。
最终,还是需要架设一个Web服务器,以 http:// 开头的地址来执行所有 JS 代码。
基本语法
- JS 语法中,每个语句以
;
结束,语句块用{...}
。但是 JS 并不强制要求语句结尾加;
,浏览器中的 JS 引擎会自动补上;
。
让 JS 引擎自动加分号,在某些情况下会改变程序的语义,导致运行结果与期望不一致。
- 注释使用
//
注释单行,使用/* ... */
注释多行 - JS 严格区分大小写,标识符的大小写敏感
数据类型简介
- Number
123; // normal number
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
- 字符串
使用 ' ...'
或者 "..."
表示字符串。
- 布尔值
与 C++、Java、C#等一致。
- 比较运算符
大于、小于、大于等于、小于等于,并无特殊。
JS 在设计时,有两种比较运算符:
第一种是==
比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
第二种是===
比较,它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。
由于 JS 这个设计缺陷,不要使用==
比较,始终坚持使用===
比较。
浮点运算要使用绝对值法,代替
===
比较符
例外:NaN
这个特殊的 Number 与所有其他值都不相等,包括它自己。唯一能判断NaN
的方法是通过isNaN()
函数:
NaN === NaN; // false
isNaN(NaN); // true
- null 和 undefined
null
表示一个“空”的值,它和0
以及空字符串''
不同:0
是一个数值,''
表示长度为0的字符串,而null
表示“空”。
在 JS 中,还有一个和null
类似的undefined
,它表示“未定义”。
区分两者的意义不大。大多数情况下,都应该用null
。undefined
仅仅在判断函数参数是否传递的情况下有用。
- 数组
数组用[]
表示,元素之间用,
分隔。
另一种创建数组的方法是通过Array()
函数实现:new Array(1, 2, 3); // 创建了数组[1, 2, 3]
数组的元素可以通过索引来访问。索引的起始值为0。
- 对象
JS 的对象是一组键-值组成的无序集合,例:
var person = {
name: 'Bob',
age: 20,
};
要获取一个对象的属性,用对象变量.属性名的方式:
person.name; // 'Bob'
- 变量
变量名是大小写英文、数字、$和_的组合,且不能用数字开头,也不能是JavaScript的关键字。
申明一个变量用var
语句,使用=
对变量进行赋值。
变量名也可以用中文,但这是极其危险的行为
关于strict 模式,通过在 JS 代码第一行写上 'use strict'
,即可进入,会严格要求变量使用 var
定义。
ES6 字符串
- 多行字符串:
`这是一个
多行
字符串`;
- 模板字符串:
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
需要特别注意的是,字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果。
Array 数组
- 直接给
Array
的length
赋一个新的值会导致Array
大小的变化 - 如果通过索引赋值时,索引超过了范围,同样会引起
Array
大小的变化 - 在编写代码时,不建议直接修改
Array
的大小,访问索引时要确保索引不会越界
对象
- 对象是一种无序的集合数据类型,它由若干键值对组成。
- 访问属性是通过
.
操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用''
括起来。访问这个属性也无法使用.
操作符,必须用['xxx']
来访问 - 访问一个不存在的属性会返回
undefined
- 对象可自由增删属性
条件、循环
条件判断:
if (){
}
else {
}
普通 for 循环:
for ( ; ; ){
}
for in 循环,可遍历对象、数组:
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
while 循环:
while (){
}
do while循环:
do{
}while()
ES6 的 Map、Set
- Map的键不必和对象一样(严格是string),可以是数值型,不去重
- Set会进行自动去重
ES6 的 iterable
具有iterable
类型的集合可以通过新的for ... of
循环来遍历:
var a = ['A', 'B', 'C'];
for (var x of a) { // 遍历Array
console.log(x);
}
那么for ... of
循环和for ... in
循环有何区别?
for ... in
循环由于历史遗留问题,它遍历的实际上是对象的属性名称。for ... of
则只循环集合本身的元素:
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x in a) {
console.log(x); // '0', '1', '2', 'name'
}
for (var x of a) {
console.log(x); // 'A', 'B', 'C'
}
更好的方式是直接使用iterable
内置的forEach
方法,它接收一个函数,每次迭代就自动回调该函数:
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
函数
定义函数
function funcname(x, y, z) {
......
return ...;
}
function
指出这是一个函数定义funcname
是函数的名称(x, y, z)
括号内列出函数的参数,多个参数以,
分隔{ ... }
之间的代码是函数体,可以包含若干语句,甚至可以没有任何语句
注意,函数体内部的语句,一旦执行到return
时,函数就执行完毕,并将结果返回。如果没有return
语句,函数执行完毕后,会返回undefined
。
由于JS 的函数也是一个对象,上述定义的函数实际上是一个函数对象,而函数名可以视为指向该函数的变量,即:
funcname = function(x, y, z) {
......
return ...;
};
两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;
,表示赋值语句结束。
ES6 函数参数
- JavaScript 允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多或者少也没有问题:多则舍弃,少则参数为
undefined
- 关键字
arguments
,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments
类似Array
但它不是一个Array
。利用arguments,即使函数不定义任何参数,还是可以拿到参数的值 function foo(a, b, ...rest)
中的rest
可获得额外参数
return 的 trap
JavaScript引擎有一个在行末自动添加分号的机制,这可能让你栽到return语句的一个大坑。
// 此样例会有问题
// return会被修改为return;
return
{ name: 'foo' };
// 所以需要这样写
return {
name: 'foo'
}
ES6 变量作用域
- JavaScript 的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。内部函数的变量会“屏蔽”外部函数的变量。
- JavaScript 的函数定义有个特点,它会先扫描整个函数体的语句,把所有变量的声明 提升到函数顶部,所以请***严格遵守“在函数内部首先申明所有变量”这一规则***
JavaScript 的变量作用域实际上是函数内部(不是块级),我们在for
循环等语句块中是无法定义具有局部作用域的变量的。
在ES6中:
- 使用
let
可以获得块级作用域的变量 - 使用
const
可以获得块级作用域的常量
ES6 解构赋值
// 1.
let [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// 2.
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; // 嵌套
// 3.
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
// 4.
let person = {
name: '小明',
age: 20,
gender: 'male',
address: {
city: 'Beijing',
street: 'No.1 Road',
zipcode: '100001'
}
};
let {name, address: {city, zip}} = person; // 对象的解构赋值 层次要一致; 对应的属性不存在,变量将被赋值为undefined
var {name, single=true} = person; // 使用了默认值 避免 undefined
// 有些时候,如果变量已经被声明了,再次赋值的时候,要用小括号
var x, y;
({x, y} = { name: '小明', x: 100, y: 200});
高阶函数
function add(x, y, f) {
return f(x) + f(y);
}
箭头函数
直接 return 版本:
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, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
返回对象:
x => ({ foo: x })