作者:cedar(https://www.jianshu.com/writer#/notebooks/28559629/notes/32531540)
一、基础知识
- JavaScript代码可以直接嵌在网页的任何地方,不过通常我们都把JavaScript代码放到
中。
由包含的代码就是JavaScript代码,它将直接被浏览器执行。
- 第二种方法是把JavaScript代码放到一个单独的.js文件,然后在HTML中通过
引入这个文件
把JavaScript代码放入一个单独的.js文件中更利于维护代码,并且多个页面可以各自引用同一份.js文件。
可以在同一个页面中引入多个.js文件,还可以在页面中多次编写,浏览器按照顺序依次执行。 - 编写JavaScript:Visual Studio Code,Sublime Text,Notepad++。
- 运行JavaScript:要让浏览器运行JavaScript,必须先有一个HTML页面,在HTML页面中引入JavaScript,然后,让浏览器加载该HTML页面,就可以执行JavaScript代码。
- 调试JavaScript:需要安装Google Chrome浏览器,Chrome浏览器对开发者非常友好,可以让你方便地调试JavaScript代码。打开浏览器中开发者工具,点击控制台(Console),可以在这个面板里直接输入Javascript代码,按回车执行。
1、基本语法
- JavaScript的语法和Java语言类似,每个语句以;结束,语句块用
{...}
。但是,JavaScript并不强制要求在每个语句的结尾加;,浏览器中负责执行JavaScript代码的引擎会自动在每个语句的结尾补上;。 - 注意花括号{...}内的语句具有缩进,通常是4个空格。缩进不是JavaScript语法要求必须的,但缩进有助于我们理解代码的层次,所以编写代码时要遵守缩进规则。
- JavaScript本身对嵌套的层级没有限制,但是过多的嵌套无疑会大大增加看懂代码的难度。遇到这种情况,需要把部分代码抽出来,作为函数来调用,这样可以减少代码的复杂度。
- 注释:以
//
开头直到行末的字符被视为行注释;另一种块注释是用/*...*/
把多行字符包裹起来,把一大“块”视为一个注释 - 大小写:JavaScript严格区分大小写,如果弄错了大小写,程序将报错或者运行不正常。
2、数据类型和变量
- Number:JavaScript不区分整数和浮点数,统一用Number表示。
- 字符串:字符串是以单引号'或双引号"括起来的任意文本。
- 布尔值:布尔值和布尔代数的表示完全一致。
- 比较运算符:JavaScript允许对任意数据类型作比较。
-
==
比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果; -
===
比较,它不会自动转换数据类型,如果数据类型不一致,返回false
,如果一致,再比较。 -
NaN
这个特殊的Number与所有其他值都不相等,包括它自己。
-
- null和undefined:
null
表示一个"空"的值。undefined
表示值未定义。 - 数组:数组是一组按顺序排列的集合,集合的每个值称为元素。JavaScript的数组可以包括任意数据类型。另一种创建数组的方法是通过Array()函数实现。
- 对象:JavaScript的对象是一组由键-值组成的无序集合。JavaScript对象的键都是字符串类型,值可以是任意数据类型。
- 变量:变量在JavaScript中就是用一个变量名表示,变量名是大小写英文、数字、$和_的组合,且不能用数字开头。变量名也不能是JavaScript的关键字,如if、while等。申明一个变量用
var
语句。要显示变量的内容,可以用console.log(x)
,打开Chrome的控制台就可以看到结果。 - strict模式:使用var申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内,同名变量在不同的函数体内互不冲突。ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。启用strict模式的方法是在JavaScript代码的第一行写上:
'use strict';
。
3、字符串
- 如果字符串内部既包含'又包含"怎么办?可以用转义字符\来标识。
- 多行字符串:最新的ES6标准新增了一种多行字符串的表示方法,用反引号`...` 表示。
- 模板字符串:ES6新增了一种模板字符串,表示方法和上面的多行字符串一样,但是它会自动替换字符串中的变量。
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
alert(message);
- 操作字符串:要获取字符串某个指定位置的字符,使用类似Array的下标操作,索引号从0开始。需要特别注意的是,字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果。
-
toUpperCase()
把一个字符串全部变为大写 -
toLowerCase()
把一个字符串全部变为小写 -
indexOf()
会搜索指定字符串出现的位置 -
substring()
返回指定索引区间的子串
-
4、数组
- JavaScript的
Array
可以包含任意数据类型,并通过索引来访问每个元素。 - 直接给
Array
的length
赋一个新的值会导致Array
大小的变化。 - 如果通过索引赋值时,索引超过了范围,同样会引起
Array
大小的变化。 - indexOf:与String类似,
Array
也可以通过indexOf()
来搜索一个指定的元素的位置。 - slice:
slice()
就是对应String的substring()
版本,它截取Array
的部分元素,然后返回一个新的Array
。 - push和pop:
push()
向Array
的末尾添加若干元素,pop()
则把Array
的最后一个元素删除掉。 - unshift和shift:如果要往
Array
的头部添加若干元素,使用unshift()
方法,shift()
方法则把Array
的第一个元素删掉。 - sort:
sort()
可以对当前Array
进行排序,它会直接修改当前Array
的元素位置,直接调用时,按照默认顺序排序。 - reverse:反转。
- splice:
splice()
方法是修改Array
的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素。 - concat:数组连接起来。
concat()
方法并没有修改当前Array
,而是返回了一个新的Array
。 - join:
join()
把当前Array
的每个元素都用指定的字符串连接起来,然后返回连接后的字符串。 - 多维数组:如果数组的某个元素又是一个
Array
,则可以形成多维数组。
5、对象
- JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。
- JavaScript规定,访问不存在的属性不报错,而是返回
undefined
。 - 由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性。
- 如果我们要检测
xiaoming
是否拥有某一属性,可以用in
操作符。不过要小心,如果in
判断一个属性存在,这个属性不一定是xiaoming
的,它可能是xiaoming
继承得到的。
6、条件判断
- JavaScript使用
if () { ... } else { ... }
来进行条件判断。 - 多行条件判断:如果还要更细致地判断条件,可以使用多个
if...else...
的组合。
7、循环
- JavaScript的循环有两种,一种是for循环,通过初始条件、结束条件和递增条件来循环执行语句块。
- while循环只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环。
8、Map和Set
- JavaScript的默认对象表示方式
{}
可以视为其他语言中的Map
或Dictionary
的数据结构,即一组键值对。但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。最新的ES6规范引入了新的数据类型Map。 - Map:
Map
是一组键值对的结构,具有极快的查找速度。 - Set:
Set
和Map
类似,也是一组key
的集合,但不存储value。由于key
不能重复,所以,在Set
中,没有重复的key
。
9、iterable
- 遍历
Array
可以采用下标循环,遍历Map
和Set
就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型。 - 具有
iterable
类型的集合可以通过新的for ... of
循环来遍历。 -
for ... in
循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array
数组实际上也是一个对象,它的每个元素的索引被视为一个属性。 - 更好的方式是直接使用
iterable
内置的forEach
方法,它接收一个函数,每次迭代就自动回调该函数。
二、函数
1、函数的定义和调用
1.定义函数:
function abs(x)
{
if (x >= 0)
{
return x;
}
else
{
return -x;
}
}
- 调用函数:JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数。传入的参数比定义的少也没有问题,要避免收到
undefined
,可以对参数进行检查。 - arguments:JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。利用
arguments
,你可以获得调用者传入的所有参数。实际上arguments
最常用于判断传入参数的个数。 - rest参数:
function foo(a, b) {
var i, rest = [];
if (arguments.length > 2) {
for (i = 2; i
为了获取除了已定义参数a
、b
之外的参数,我们不得不用arguments
,并且循环要从索引2开始以便排除前两个参数,这种写法很别扭。ES6标准引入了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 []
rest参数只能写在最后,前面用...
标识,从运行结果可知,传入的参数先绑定a
、b
,多余的参数以数组形式交给变量rest
,所以,不再需要arguments
我们就获取了全部参数。
2、变量作用域与解构赋值
- 在JavaScript中,用var申明的变量实际上是有作用域的。如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量。
- 如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,互不影响。
- 由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行。
- 变量提升:JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部。
- 全局作用域:不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象
window
,全局作用域的变量实际上被绑定到window
的一个属性。 - 名字空间:全局变量会绑定到
window
上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。 - 局部作用域:由于JavaScript的变量作用域实际上是函数内部,我们在
for
循环等语句块中是无法定义具有局部作用域的变量的。为了解决块级作用域,ES6引入了新的关键字let
,用let
替代var
可以申明一个块级作用域的变量。 - 常量:由于
var
和let
申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”。ES6标准引入了新的关键字const
来定义常量,const
与let
都具有块级作用域。 - 解构赋值:从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];
如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致。
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'
3、方法
在一个对象中绑定函数,称为这个对象的方法。在一个方法内部,
this
是一个特殊变量,它始终指向当前对象,也就是xiaoming
这个变量。所以,this.birth
可以拿到xiaoming
的birth
属性。ECMA决定,在strict模式下让函数的
this
指向undefined
,因此,在strict模式下,你会得到一个错误。-
apply:要指定函数的
this
指向哪个对象,可以用函数本身的apply
方法,它接收两个参数,第一个参数就是需要绑定的this
变量,第二个参数是Array
,表示函数本身的参数。另一个与apply()
类似的方法是call()
,唯一区别是:-
apply()
把参数打包成Array再传入; -
call()
把参数按顺序传入。
对普通函数调用,我们通常把this绑定为null。
-
装饰器:JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
4、高阶函数
- JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
- map/reduce:由于
map()
方法定义在JavaScript的Array
中,我们调用Array
的map()
方法,传入我们自己的函数,就得到了一个新的Array
作为结果。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
Array
的reduce()
把一个函数作用在这个Array
的[x1, x2, x3...]
上,这个函数必须接收两个参数,reduce()
把结果继续和序列的下一个元素做累积计算。
- filter:filter也是一个常用的操作,它用于把
Array
的某些元素过滤掉,然后返回剩下的元素。和map()
类似,Array
的filter()
也接收一个函数。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是true
还是false
决定保留还是丢弃该元素。filter()
接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array
的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身。 - sort:
Array
的sort()
方法默认把所有元素先转换为String再排序,结果'10'
排在了'2'
的前面,因为字符'1'
比字符'2'
的ASCII码小。sort()
方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。
5、闭包
- 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
- 在函数
lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。 - 注意到返回的函数在其定义内部引用了局部变量
arr
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。 - 在返回的对象中,实现了一个闭包,该闭包携带了局部变量
x
,并且,从外部代码根本无法访问到变量x
。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
6、箭头函数
var fn = x => x * x;
- 箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连
{ ... }
和return
都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }
和return
。 - 如果参数不是一个,就需要用括号
()
括起来。 - 箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的
this
是词法作用域,由上下文确定。
7、generator
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
-
generator和函数不同的是,generator由
function*
定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。function* fib(max) { var t, a = 0, b = 1, n = 0; while (n < max) { yield a; [a, b] = [b, a + b]; n ++; } return; }
直接调用一个generator和调用函数不一样,
fib(5)
仅仅是创建了一个generator对象,还没有去执行它。调用generator对象有两个方法,一是不断地调用generator对象的
next()
方法。next()
方法会执行generator的代码,然后,每次遇到yield x
;就返回一个对象{value: x, done: true/false}
,然后“暂停”。返回的value
就是yield
的返回值,done
表示这个generator是否已经执行结束了。第二个方法是直接用
for ... of
循环迭代generator对象,这种方式不需要我们自己判断done
。
三、标准对象
- 为了区分对象的类型,我们用
typeof
操作符获取对象的类型,它总是返回一个字符串。 - 特别注意
null
的类型是object
,Array
的类型也是object
,如果我们用typeof
将无法区分出null
、Array
和通常意义上的object——{}
。 - 包装对象:
number
、boolean
和string
都有包装对象。没错,在JavaScript中,字符串也区分string
类型和它的包装类型。包装对象用new
创建。 - 虽然包装对象看上去和原来的值一模一样,显示出来也是一模一样,但他们的类型已经变为
object
了。 -
typeof
操作符可以判断出number
、boolean
、string
、function
和undefined
;判断Array
要使用Array.isArray(arr)
;判断null
请使用myVar === null
。 - 判断某个全局变量是否存在用
typeof window.myVar === 'undefined'
;函数内部判断某个变量是否存在用typeof myVar === 'undefined'
。
1、Date
- 在JavaScript中,
Date
对象用来表示日期和时间。
2、RegExp
- 正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
- 要匹配变长的字符,在正则表达式中,用
*
表示任意个字符(包括0个),用+
表示至少一个字符,用?
表示0个或1个字符,用{n}
表示n个字符,用{n,m}
表示n-m个字符。 - 如果要匹配
'010-12345'
这样的号码呢?由于'-'
是特殊字符,在正则表达式中,要用'\'
转义,所以,上面的正则是\d{3}\-\d{3,8}
。、 - RegExp对象的
test()
方法用于测试给定的字符串是否符合条件。 - 分组:除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用
()
表示的就是要提取的分组(Group)。如果正则表达式中定义了组,就可以在RegExp对象上用exec()
方法提取出子串来。 - 贪婪匹配:正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。
- 全局搜索:JavaScript的正则表达式还有几个特殊的标志,最常用的是
g
,表示全局匹配。全局匹配可以多次执行exec()
方法来搜索一个匹配的字符串。当我们指定g
标志后,每次运行exec()
,正则表达式本身会更新lastIndex
属性,表示上次匹配到的最后索引。
3、JSON
- JSON是JavaScript Object Notation的缩写,它是一种数据交换格式。
- JSON还定死了字符集必须是UTF-8,表示多语言就没有问题了。为了统一解析,JSON的字符串规定必须用双引号
""
,Object的键也必须用双引号""
。 - 反序列化:拿到一个JSON格式的字符串,我们直接用
JSON.parse()
把它变成一个JavaScript对象。
学习参考:https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000