基本语法
数据变量与类型
JS中整数和浮点数统一对待,甚至可以对浮点数做%
运算,但是整数数值的范围是[-2^53, 2^53]
非常神秘
表达一个字符串既可以用'
也可以用"
。
JS有缺陷的一点在于,使用相等运算符==
时,可以对不同类型的元素进行比较,这往往会导致奇怪的结果;为此,建议总是使用===
这一相等运算符,该运算符会先比较类型是否相同,不同则返回false
,相同再比较变量是否相同。
浮点数的相等比较依然要设置eps
,而且我怀疑js中的精度可能比double垃圾很多
js中null
表示一个“空”的值,这和C中null表示0不同。
建议通过[]
来创建一个数组,下标一样从0开始。
对象的定义方式与C++类似。
变量通过var
来声明,因其类型不固定,因而这种语言称为动态语言
为了显示变量的值,可以使用语句console.log(variable)
如果没有使用``var来声明一个变量,那么它就会成为一个全局变量。可以通过
‘use strict’来强制加上
var`关键字。
字符串
可以通过'\x41'
十六进制地表示一个字符,通过\u4e2d
表示一个unicode字符。
在输入多行字符串的时候为了避免较多'\n'
带来的麻烦,可以使用`` `来表示多行字符串。
1`多
2行
3字符串`;
众所周知C++可以使用+
来连接若干个string,js更进一步可以使用模版字符串
来连接字符串。
1var name = 'rqdmap';
2var age = 1;
3var mes = `Hello ${name}, you are ${age} years old`;
4console.log(mes);
必须使用`` ,使用
’和
"`均无法成功显示字符串的原本信息。
通过s.length
来获得字符串的长度。
通过下标可以进行随机访问字符串的任意位置,但需要注意越界访问不会报错,只会返回undefined
js中的字符串是不可通过索引值进行改变的 大概是因为C++直接操作地址而这里不知道实现了什么别的结构
但是可以通过再次赋值改变整个字符串的值。
常用的一些操作方法
toUpperCase()
toLowerCase()
indexOf(“xxxx”): 搜索子串在主串中的位置,如果没有搜索到返回-1
substring(int a): 从索引a开始到字符串结束
substring(int a, int b): 返回子串[s[a], s[b])
数组
js中的数组元素可以是不同类型,通过索引进行访问。
直接给array.length
进行赋值会导致数组的长度发生变化。
可以通过索引值改变数组的某个元素。
至此可以推断出js中的字符串不是字符的数组
,而是一个基本的类型
如果越界赋值也会导致数组的长度发生改变。
1var arr = [1, 2, 3];
2arr[5] = 'x';
3arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
这种特性不知道是好是坏
常用操作
indeOf: 与string中的indexOf类似
slice: 与substring类似;如果不给slice传任何参数,就会整个截取该数组,据此可以很容易的完成数组的复制。
push/pop: 对数组的结尾进行添/删元素
unshift/shift:对数组的头部进行添/删元素
push和unshift可以一次添加若干个元素,而pop和shift只能删去一个元素;如果空数组继续删除,不会报错而会返回undefined.
sort: 按默认顺序排序
reverse: 反转
splice: 从指定的索引删除若干元素,然后再从该位置加入若干元素。
1var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
2
3// 从索引2开始删除3个元素,然后再添加两个元素:
4arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
5arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
6
7// 只删除,不添加:
8arr.splice(2, 2); // ['Google', 'Facebook']
9arr; // ['Microsoft', 'Apple', 'Oracle']
10
11// 只添加,不删除:
12arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
13arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
concat: 数组拼接
concat可以接受任意多个元素,并且将接收到的array不断拆开、将其元素拼接入原数组。
1var arr = ['A', 'B', 'C'];
2arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4]
join:将数组的每个元素用指定的字符串进行连接,如果元素不是字符串则会先自动将其转换成字符串。
1var arr = ['A', 'B', 'C', 1, 2, 3];
2arr.join('-'); // 'A-B-C-1-2-3'
对象
JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。
如果某个属性名中含有特殊字符,可以通过object['属性名']
来进行访问;当然建议按照规范进行命名,这样就可以直接通过.
操作符进行访问了。
类似的,如果访问一个不存在的属性,那么会返回undefined.
由于js的对象是动态属性,所以可以很容易为一个对象增添/删除属性。
1var xiaoming = {
2 name: '小明'
3};
4xiaoming.age; // undefined
5xiaoming.age = 18; // 新增一个age属性
6xiaoming.age; // 18
7
8delete xiaoming.age; // 删除age属性
9xiaoming.age; // undefined
10
11delete xiaoming['name']; // 删除name属性
12xiaoming.name; // undefined
13
14delete xiaoming.school; // 删除一个不存在的school属性也不会报错
可以使用in
来判断一个属性是否在一个对象中。
1var xiaoming = {
2 name: '小明',
3 birth: 1990,
4 school: 'No.1 Middle School',
5 height: 1.70,
6 weight: 65,
7 score: null
8};
9'name' in xiaoming; // true
10'grade' in xiaoming; // false
不过in也会判断那些继承来的属性,为了判断对象自身所拥有的属性,可以使用hasOwnProperty()
方法。
1var xiaoming = {
2 name: '小明'
3};
4xiaoming.hasOwnProperty('name'); // true
5xiaoming.hasOwnProperty('toString'); // false
语句结构
在if
语句中,js把null
, undefined
, 0
, NaN
和''
视为false,其余均视为true
循环语句结构与C++类似,可以在for语句初始化值的位置定义变量,也可以使用自增自减运算符,也可以使用+=
系列运算符。
类似于C++的范围for语句,js有for in语句,用于将对象、数组的属性
全部列出来:
1var o = {
2 name: 'Jack',
3 age: 20,
4 city: 'Beijing'
5};
6for (var key in o) {
7 console.log(key); // 'name', 'age', 'city'
8}
如果想要过滤掉父类的属性,可以在循环后面加一句o.hasOwnProperty(key)
的判断语句。
对array进行for in
循环时,数组的索引被认为是其属性,循环得到的不是number
而是string
!
1var a = ['A', 'B', 'C'];
2for (var i in a) {
3 console.log(i); // '0', '1', '2'
4 console.log(a[i]); // 'A', 'B', 'C'
5}
Map与Set
有两种方法对Map进行初始化
1//利用二维数组初始化一个Map
2var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
3m.get('Michael'); // 95
4
5//也可以初始化一个空Map,然后利用以下方法添加元素。
6var m = new Map(); // 空Map
7m.set('Adam', 67); // 添加新的key-value
8m.set('Bob', 59);
9m.has('Adam'); // 是否存在key 'Adam': true
10m.get('Adam'); // 67
11m.delete('Adam'); // 删除key 'Adam'
12m.get('Adam'); // undefined
一个键只能对应一个值,所以多次对一个键赋值会覆盖掉之前的值。
类似地,可以初始化一个空Set或者用一维数组初始化一个Set。重复元素自动被删除。
1var s = new Set([1, 2, 3]);
2s.add(4);
3s; // Set {1, 2, 3, 4}
4s.add(4);
5s; // 仍然是 Set {1, 2, 3, 4}
6s.delete(4);
7s; // Set{1, 2, 3}
iterable
通过for ... of ...
循环可以遍历具有iterable
类型的集合。
1var a = ['A', 'B', 'C'];
2var s = new Set(['A', 'B', 'C']);
3var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
4for (var x of a) { // 遍历Array
5 console.log(x);
6}
7for (var x of s) { // 遍历Set
8 console.log(x);
9}
10for (var x of m) { // 遍历Map
11 console.log(x[0] + '=' + x[1]);
12}
而以往的for in
就无法遍历set和map。
for in
循环访问的是对象的属性,而for of
只循环集合本身的元素。
1var a = ['A', 'B', 'C'];
2a.name = 'Hello';
3for (var x in a) {
4 console.log(x); // '0', '1', '2', 'name'
5}
1var a = ['A', 'B', 'C'];
2a.name = 'Hello';
3for (var x of a) {
4 console.log(x); // 'A', 'B', 'C'
5}
函数
函数定义
不同于C++,js中函数不定义返回值类型,直接用function进行定义即可。
1function abs(a){return a < 0? -a: a;}
如果函数没有返回值,那么该函数也会返回undefined.
此外,js中函数也是一个对象,所以可以使用以下的匿名函数定义一个函数
1var abs = function(a){return a < 0? -a: a;};
不过由于第二种方法是赋值语句,所以记得加上;
函数调用
js传入参数的数量不被严格的限制:如果多于需要的数量,那么多余的将不会被使用;如果少于需要的数量,函数中的参数会收到undefined
。
有一种方法可以判断是否收到undefined
:
1var abs = function(a){
2 if(typeof a !== 'number') throw 'Not a number';
3 return a < 0? -a: a;
4}
在函数内部可以使用**arguments
**来指向所有的传入参数,argument也有属性length
,可以用length来判断传入的参数是否足够。
如果传入的参数过多,可以使用ES6标准引入的rest
参数:
1function foo(a, b, ...rest) {
2 console.log('a = ' + a);
3 console.log('b = ' + b);
4 console.log(rest);
5}
6
7foo(1, 2, 3, 4, 5);
8// 结果:
9// a = 1
10// b = 2
11// Array [ 3, 4, 5 ]
12
13foo(1);
14// 结果:
15// a = 1
16// b = undefined
17// Array []
rest必须写在最后,并用...
标示。
js会自动在行末添加分号,所以要注意return和其返回值不要换行分隔。
变量作用域
打开'use strict'
后就禁止不加var声明变量,那么var变量作用域只在当前函数内。
如果嵌套的内层函数有与外层同名的变量,与C++一样的覆盖规则是成立的。
JS在函数中有变量提升这一特性,在运行函数前扫描整个函数,将变量的声明提升到函数的最前面。
所以可以写一些快乐的毒瘤代码
如果不在任何函数内声明一个变量,那么该变量就是全局变量。全局变量被绑定在了JS默认的一个全局对象window
的属性上。因而可以通过window.
来访问一个全局变量。
当多个文件同时工作时,为了避免全局变量命名的冲突,可以将所有的全局变量绑定在一个全局变量上,这样就可以实现“命名空间“的工作。
1// 唯一的全局变量MYAPP:
2var MYAPP = {};
3
4// 其他变量:
5MYAPP.name = 'myapp';
6MYAPP.version = 1.0;
7
8// 其他函数:
9MYAPP.foo = function () {
10 return 'foo';
11};
事实上,因为函数内的变量会被提升到首部,所以如下的C++风格代码并不能完成它本来想完成的功能:
1function fun(){
2 for(var i = 1; i <= n; i++)
3 //do something;
4}
为此,ES6引入新的关键字let
,可以声明一个块级作用域的变量,利用let关键字就可以完成上述的操作了。
ES6也引入了const
来声名一个常量。
1const pi = 3.14;
解构赋值
这是ES6引入的一种可以对多个变量同时赋值的语法。
1//直接对三个变量进行赋值
2var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
3
4//如果右值有嵌套,那么多个变量之间也要保持格式一致。
5let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
6
7//可以忽略掉某些元素
8let [, , z] = ['hello', 'JavaScript', 'ES6'];
也可以从一个对象中抽取出若干属性进行赋值
1var person = {
2 name: '小明',
3 age: 20,
4 gender: 'male',
5 passport: 'G-12345678',
6 school: 'No.4 middle school'
7};
8var {name, age, passport} = person;
如果使用了不存在的属性名将会获得undefined
可以通过如下语句将某属性的值赋值到另一个名称的属性上
1let {name, passport:id} = person;
解构赋值也可以设置默认值,避免产生undefined
的问题
1var person = {
2 name: '小明',
3 age: 20,
4 gender: 'male',
5 passport: 'G-12345678'
6};
7
8// 如果person对象没有single属性,默认赋值为true:
9var {name, single = true} = person;
如果想要对已经声明过的变量做解构赋值,需采用如下形式我也不知道为什么
1({x, y} = { name: '小明', x: 100, y: 200});
函数方法
在JS中可以使用如下语法定义一个对象专用的方法:
1var xiaoming = {
2 name: '小明',
3 birth: 1990,
4 age: function () {
5 var y = new Date().getFullYear();
6 return y - this.birth;
7 }
8};
对象也可以使用一种“全局方法”。
1function getage() {
2 var y = new Date().getFullYear();
3 return y - this.birth;
4}
5var xiaoming = {
6 name: '小明', birth: 1990,
7 age: getage,
8};
9console.log(xiaoming.age());
好处在于可以按需使用“全局方法”,但是如果不加任何对象的调用了这个方法则会返回undefined
。这是因为该对象默认成为window
,方法中的this指向了window。为了解决this的正确指向问题,可以通过strict
模式来使得这种情况下this总指向undefined
来及时的报错避免该问题。
此外,在对象某个方法中的子方法,无法通过this
指向原对象,只会指向undefined
,实际使用时可以通过创建局部变量来解决。
可以利用apply方法来手动指定全局方法的this对象并给出参数。
1function getAge() {
2 var y = new Date().getFullYear();
3 return y - this.birth;
4}
5var xiaoming = {
6 name: '小明',
7 birth: 1990,
8 age: getAge
9};
10getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
第一个参数为对象名,第二个参数为一个数组,其中元素为要传入给函数的参数。
方法call与apply基本类似,唯一不同的地方在于call将函数需要的参数一个个列出为形参。
利用apply可以对原函数进行加“壳”
1var oldfun = fun;
2window.fun = function(){
3 //dosomething
4 return oldfun.apply(xxx, [...])
5}
vscode可能没有配置完备,导致window.xxxx系列函数无法在vscode执行;然而切换到chrome的控制台便可以顺利执行。
window
is a browser thing that doesn’t exist on Node.
高阶函数
高阶函数是指接受其他函数作为参量的函数。
并附上回调函数的一个解释: A “callback” is any function that is called by another function which takes the first function as a parameter.
map方法
指定一个映射方法,通过map方法将原本的元素一一进行置换。
1'use strict'
2
3function pow(a){return a * a;}
4var arr = [1, 2, 3, 4];
5console.log(arr.map(pow));
6//[ 1, 4, 9, 16 ]
reduce方法
指定一个必须传入2个元素的方法,将序列前两项按照该方法做运算后的结果继续与第三个元素做运算,其后以此类推。
1[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
给出一些实例:
1var arr = [1, 3, 5, 7, 9];
2arr.reduce(function (x, y) {
3 return x + y;
4}); // 25
5
6var arr = [1, 3, 5, 7, 9];
7arr.reduce(function (x, y) {
8 return x * 10 + y;
9}); // 13579
JS中如何将字符转化为整数?
如果不使用parseInt系列方法:
-
使用Number(c) - Number(‘0’)来类似于C++的转化为Int
-
通过运算符自动解析字符串为数值
javascript1'use strict' 2 3var s = '12345'; 4var arr = new Array; 5for(let i = 0; i < s.length; i++) arr[i] = s[i] * 1; 6//[ 1, 2, 3, 4, 5 ] 7 8for(let i = 0; i < s.length; i++) arr[i] = s[i] - 0; 9//[ 1, 2, 3, 4, 5 ] 10 11//无法通过'+'将字符串解析为数值! 12for(let i = 0; i < s.length; i++) arr[i] = s[i] + 0; 13//[ '10', '20', '30', '40', '50' ]
如果想要使用这类不是很知道实现原理的内置函数时,就可能会产生如下意想不到结果。
1console.log(['1', '2', '3'].map(parseInt));
2//[ 1, NaN, NaN ]
这是因为parseInt()
实际会接受两个参数,第一个是表达式字符串,第二个是基数。而在使用map传入时会将字符作为字符串表达式、序号为基数传入parstInt函数,那么就无法正确解析{‘2’, 1}和{‘3’, 2}表达式了。