Javascript基础
基础语法
- 语句最好以分号
;
结尾,以免自动补全时出错 - 严格区分大小写
- 变量、常量可以单独作为一个语句
- 变量名由字母、数字、下划线、$符合组成,且不能以数字开头
- 动态类型语言
typeof 变量
返回函数类型,该结果本身为字符串
注释
// Comment
/* Block Comment
块注释 */
数据类型
Number
- 不区分整数、浮点数,支持科学计数法(如1.2e3)
- 特殊值:NaN、Infinity
- 可直接做四则运算
字符串
-
用单引号或双引号括起来, 通过
\
转义- 十六进制数:
'\x##'
表示,如'\x41'表示A
- Unicode字符:
\u####
表示
- 十六进制数:
-
ES6新增多行字符串,通过反引号 `` 括起来
-
字符串拼接用
+
号,自动将非字符串转为字符串。ES6中新增模板字符串,反引号字符串中用${变量}代替变量var name = '小明'; var age = 20; var message = `你好, ${name}, 你今年${age}岁了!`; alert(message);
-
常见字符串操作
var s= 'Hello, world' s.length; // 13 s[0]; // 'H' s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined s[0] = 'X'; // 赋值不报错,但不会有任何效果 s.indexOf('world'); // 返回7 s.indexOf('World'); // 没有找到指定的子串,返回-1 s.toUpperCase(); s.toLowerCase(); s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello' s.substring(7); // 从索引7开始到结束,返回'world'
布尔值
true
/false
- 与运算
&&
、或运算||
||
返回第一个对应为true
的值,常用于返回备选组中第一个定义了且非空的值&&
返回第一个对应为false
的值!!
将变量转为boolean
类型,如!!'abc'
- 比较运算符:
> < >= <= === !
。建议用===
做相等比较(不会自动转换类型);==
会自动进行类型转换,可能得到奇怪的结果 - 浮点数比较有误差,可采用类似Math.abs( a - b ) < 0.000001比较
- 条件判断语句中把
null
、undefined
、0
、NaN
和空字符串''
视为false
,其他值一概视为true
- NaN和谁对比都是false,包括自己
特殊值
null
空值,不同于0或''
undefined
与null
差别不大,仅在判断函数参数是否传递的情况下有用
数组
-
推荐:通过方括号
[]
定义,可包含任意类型,如[1, 2, 3.14, 'Hello', null, true];
-
通过Array定义,如
new Array(1,2,3.14)
-
通过序号索引 0 ~ length-1
-
数组操作
arr.length; //返回字符串长度,可以赋值,不够补undefined,但不建议 arr[1] = '99'; // 赋值 arr.indexOf('30');// 搜索一个指定的元素的位置, 找不到返回-1 arr.slice(0,3); //返回[0,3)位置的子数组副本 arr.slice(3); //从索引3到结束 arr.slice(); // 返回整个数组副本,用于复制数组 arr.push('A', 'B'); // 往数组末尾添加任意元素,返回新长度 arr.unshift('A', 'B'); // 往数组前添加任意元素,返回新长度 arr.pop(); // 删除数组末尾元素,返回删除元素 arr.shift(); // 删除数组头部元素,返回删除元素 arr.sort(); //直接对当前元素排序,无返回值 arr.reverse(); // 数组逆序,无返回值 arr.splice(2, 3, 'a', 'b'); // 从指定的 索引2 开始删除 3个 元素,然后再从该位置添加若干元素'a' 'b',返回删除的元素;删除0个时相当于插入 arr.concat(1, 'a', array2); // 组合任意元素和数组,并自动拆开数组,返回新的数组副本,不修改原数组 arr.join('-'); // 将元素转为字符串后用连接符连接,返回结果字符串
-
map
/reduce
/filter
/sort
- map 接收一个函数
f
,逐个遍历数组元素,将函数f
应用到每个元素。传入的参数共3个,分别为数组元素、元素索引、数组本身;但形参中函数f
可以只有一个参数 - reduce 接收一个函数
f
,将函数结果和数组下一个元素做累计计算,比如[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
- filter 接收一个函数
f
,若f
返回true则保留,否则丢弃该元素。返回筛选后的新数组。 - sort 可以接收一个函数
f(x,y)
,返回值1代表x>y
, 0 代表x==y
, -1代表x<y
.- 不接收函数的sort()会先将元素转成字符串再比较,则数字排序是按字典序排列,反直觉
- sort()会直接修改原数组
// map function pow(x) { return x * x; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81] console.log(results); // reduce var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; }); // 25 // filter var r, arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry']; r = arr.filter(function (element, index, self) { return self.indexOf(element) === index; }); // 筛掉重复元素 var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; }); console.log(arr); // [1, 2, 10, 20], 若直接sort()则为1, 10, 2, 20
- map 接收一个函数
-
其余高阶函数
every(f)
方法可以判断数组的所有元素是否满足测试条件,均满足时返回truefind(f)
方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined
。类似于filter
,但仅返回第一个元素findIndex(f)
和find(f)
类似,也是查找符合条件的第一个元素,不同之处在于findIndex(f)
会返回这个元素的索引,如果没有找到,返回-1
forEach(f)
和map(f)
类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()
常用于遍历数组,因此,传入的函数不需要返回值
对象(map)
-
键(属性)为字符串类型,值可为任意数据类型
-
通过
对象变量名.属性名
获取值(建议);也可采用['xxx']
的形式,特别是当属性名包含特殊字符时,需用''
括起来,此时只能用[]
访问 -
新增、删除属性
var xiaoming = { name: '小明' }; xiaoming.age; // undefined delete xiaoming.age; // 删除一个不存在的school属性也不会报错 xiaoming.age = 18; // 新增一个age属性 xiaoming.age; // 18 delete xiaoming.age; // 删除age属性
-
检验是否包含某个属性
'name' in xiaoming; // true 'toString' in xiaoming; // true, 从父类继承 xiaoming.hasOwnProperty('toString'); // false, 检验自身非继承属性
变量定义
- 需通过var定义,仅作用在函数内部
- 若省略var,且未使用
'use strict';
或浏览器不支持strict模式,则为全局变量,会与html中其他js文件共用变量。使用'use strict'
后,会将其限制在函数内部
流程控制
-
条件判断
if () { ... } else { ... }
-
循环语句
-
for(初始条件; 判断条件; 迭代语句)
-
for...in... 遍历属性(针对对象和数组)
// 遍历对象 var o = { name: 'Jack', age: 20, city: 'Beijing' }; for (var key in o) { console.log(key); // 'name', 'age', 'city' if (o.hasOwnProperty(key)) { // 过滤对象继承属性 console.log(key); // 'name', 'age', 'city' } } // 遍历数组 var a = ['A', 'B', 'C']; for (var i in a) { console.log(i); // '0', '1', '2' 字符串类型索引 console.log(a[i]); // 'A', 'B', 'C' }
-
-
while (...) {}
-
do {} while(...);
Map和Set
-
ES6开始支持
-
Map支持非字符串的键,通过成员方法访问
var m = new Map(); // 定义空Map var m = new Map([[1, 'a'], ['b', 2]]); // 通过二维数组定义 m.set('t', 3); m.get('t'); // 3 m.delete('t'); // true m.has('t'); // false
-
Set对key自动去重
var s = new Set(); var s = new Set([1, 2, 3, 3, '3']); // 通过一维数组定义 s.add(4); s.delete(4); s.has(1); // true
Iterable
-
ES6开始支持,Array、Set、Map均为
iterable
类型 -
支持
for ... of
循环,只会循环集合本身元素; 而for ... in
遍历的实际上是对象属性名称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' }
-
更好的循环方式是
forEach
(ES 5.1开始支持) ,入参为函数var s = new Set(['A', 'B', 'C']); s.forEach(function (element, sameElement, set) { console.log(element); }); var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); m.forEach(function (value, key, map) { // 注意入参顺序 console.log(value); }); // 如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得Array的element a.forEach(function(element)) { // 可以添加第二参数为索引index,第三参数为类型array console.log(element) // 'A', 'B', 'C' }
函数
-
定义
// 函数定义 function abs(x) { if (x >= 0) { return x; } else { return -x; } } // 通过匿名函数定义 var abs = function (x) {...}; // 注意末尾分号
-
若没有返回值,返回
undefined
-
函数调用时,入参可大于定义数,此时多余参数为
undefined
;可少于定义数,此时函数中未赋值对应参数接收到undefined
-
arguments
,只在函数内部起作用,获得调用者传入的所有参数,类似于Array。如包含length属性,常用于判断入参个数 -
rest参数,获取多余参数(ES6新标准)
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 ]
-
注意return语句,由于JavaScript自动在行末补分号
;
,所以不要在此次直接换行function foo() { return // 返回undefined { name: 'foo' }; }
变量作用域
-
声明在函数内部的变量,只在函数体内起作用
-
嵌套函数,内部可以访问外部函数变量(支持覆盖);但外部不能访问内部。搜索时内部函数由内向外搜索变量。
-
变量提升:JavaScript会将所有变量声明提升至函数顶部,但不提升赋值。故规范做法是在函数顶部用一个
var
定义好所有变量function foo() { var x = 'Hello, ' + y; // 返回 Hello, undefined console.log(x); var y = 'Bob'; } // 定义方式举例: var t, a = 0, b = 1, arr = [0, 1];
-
全局作用域:不定义在任何函数内部的变量。实际上全局变量绑定在唯一全局变量——
window
变量上,key和value分别为变量名和值。alert也是window的变量之一 -
名字空间:不同Js文件使用相同的全局变量或同名顶层函数,将导致冲突。推荐做法是将自己的所有变量和函数绑定到一个全局变量中
// 唯一的全局变量MYAPP: var MYAPP = {}; // 其他变量: MYAPP.name = 'myapp'; MYAPP.version = 1.0; // 其他函数: MYAPP.foo = function () { return 'foo'; };
-
块级作用域: ES6中引入
let
关键字,定义的变量仅作用于函数块内,如for语句内 -
常量:ES6中引入
const
关键字,同样具有块级作用域
解构赋值(ES6)
-
同时对一组变量赋值
// 数组用方括号 var [x, y, z] = ['hello', 'JavaScript', 'ES6']; let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; // 支持嵌套,但结构需一致 // 对象用花括号,取出若干属性 var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school', address: { city: 'Beijing', street: 'No.1 Road', zipcode: '100001' } }; // 可嵌套,但层次需一致。address只用于说明层次,未定义 var {name, address: {city:location, zip, province="Beijing"}} = person; // 可用等号定义默认值,用冒号定义与属性不同名的变量 name; // '小明' location; // 'Beijing' zip; // undefined, 因为属性名是zipcode而不是zip province; // 'Beijing',而不是undefined address; // Uncaught ReferenceError: address is not defined var x, y; ({x, y} = { name: '小明', x: 100, y: 200}); // 对于已定义变量,可能需要加括号,否则报错 // 变量交换 [x, y] = [y, x] // 函数用对象作为参数 function buildDate({year, month, day, hour=0, minute=0, second=0}) { return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second); } buildDate({ year: 2017, month: 1, day: 1 });
方法
-
将函数赋值给属性
var xiaoming = { name: '小明', birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; } }; xiaoming.age; // function xiaoming.age() xiaoming.age(); // 今年调用是25,明年调用就变成26了
-
注意this指向,不支持嵌套(嵌套时在内部函数还原为
window
或null
)。嵌套时应在外层先用其他变量接收,如定义一个var that=this
,内部再调用that -
若函数在外部声明,必须用
obj.xxx()
的形式调用,此时this
正确;如直接调用外部函数,则this
指向全局变量window
(非strict)或为null
(strict模式) -
使用apply控制this指向,第一参数为需绑定this的变量,第二个为Array,为函数参数。直接调用外部函数,第一参数用
null
。call类似,除了第二参数不用Array,直接按顺序传入多个参数。function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: '小明', birth: 1990, age: getAge }; xiaoming.age(); // 25 getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
-
装饰器:比如替换内置函数
/* 计算parseInt调用次数 */ 'use strict'; var count = 0; var oldParseInt = parseInt; // 保存原函数 window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // 调用原函数 };
闭包
-
含义:将函数
f2
作为函数f1
返回值,同时该内部函数f2
可以引用外部f1
的参数和局部变量,并保存在返回的函数f2
. 函数f2
称为闭包。 -
闭包引用的局部变量不被回收,同时在
f2
被调用时才会被赋值。所以应避免引用循环遍变量,或者后续会发生变化的变量。或者再创建一个函数,将该函数参数绑定循环变量当前值
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
} // 调用数组中的三个函数,返回值均为16
function count() { // 引用循环变量的正确做法
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
-
可以用于将多参函数变单参函数
function make_pow(n) { return function (x) { return Math.pow(x, n); } }
-
封装私有变量
'use strict'; function create_counter(initial) { var x = initial || 0; return { inc: function () { x += 1; return x; } } } var c1 = create_counter(); c1.inc(); // 1 c1.inc(); // 2 c1.inc(); // 3 var c2 = create_counter(10); c2.inc(); // 11 c2.inc(); // 12 c2.inc(); // 13
箭头函数
-
ES6标准引入的新函数定义方式
// 单语句 var fn = x => x * x; // 多参数,多语句 (x, y, ...rest) => { var i, sum = x + y; for (i=0; i<rest.length; i++) { sum += rest[i]; } return sum; } // 与{...}函数体语法冲突,直接返回对象需加括号 x => ({ foo: x })
-
箭头函数中修复了
this
指向,使其总是指向词法作用域,也就是外层调用者obj
,不需要再用var that = this
保留外层this。但箭头函数中的this本质上继承于外侧的function,故需放置在function内部,不能作为独立的成员函数。此外apply和call的第一个参数将被忽略var obj = { birth: 1990, getAge: function (year) { var b = this.birth; // 1990 var fn = () => year - this.birth; // this指向obj对象 return fn.call({birth:2000}, year); //this.birth仍为1990 } }; obj.getAge(2015); // 25
生成器generator
-
与函数类似,但是使用
function*
定义 -
调用
next()
,每次运行到yield
关键字处输出value和done(true表示generator已结束)。直到return结束 -
调用方式
- generator赋值给一个变量后,使用
next()
获取一个元素 - 使用
for...of
,自动判断generator是否结束
function* fib(max) { var t, a = 0, b = 1, n = 0; while (n < max) { yield a; [a, b] = [b, a + b]; n ++; } return; } for (var x of fib(10)) { console.log(x); // 依次输出0, 1, 1, 2, 3, ... }
- generator赋值给一个变量后,使用
包装对象
-
强烈建议不要使用
-
null
的类型是object
,Array
的类型也是object
,用typeof
无法区分出null
、Array
和通常意义上的object——{}
-
包装对象用
new
创建:var n = new Number(123); // 123,生成了新的包装类型 var b = new Boolean(true); // true,生成了新的包装类型 var s = new String('str'); // 'str',生成了新的包装类型
-
如果省略了
new
关键字,Number()
、Boolean
和String()
被当做普通函数,把任何类型的数据转换为number
、boolean
和string
类型(注意不是其包装类型)var b2 = Boolean('false'); // true! 'false'字符串转换结果为true!因为它是非空字符串!
-
基本规则
- 不要使用
new Number()
、new Boolean()
、new String()
创建包装对象; - 用
parseInt()
或parseFloat()
来转换任意类型到number
; - 用
String()
来转换任意类型到string
,或者直接调用某个对象的toString()
方法; - 通常不必把任意类型转换为
boolean
再判断,因为可以直接写if (myVar) {...}
; typeof
操作符可以判断出number
、boolean
、string
、function
和undefined
;- 判断
Array
要使用Array.isArray(arr)
; - 判断
null
请使用myVar === null
; - 判断某个全局变量是否存在用
typeof window.myVar === 'undefined'
; - 函数内部判断某个变量是否存在用
typeof myVar === 'undefined'
- 不要使用
-
number调用
toString()
需先将整数转成浮点数,或带括号123..toString(); // '123', 注意是两个点,代表不带小数部分的浮点数 (123).toString(); // '123'
常见对象
-
Date
-
常见用法
var now = new Date(); now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST) now.getFullYear(); // 2015, 年份 now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月 now.getDate(); // 24, 表示24号 now.getDay(); // 3, 表示星期三 now.getHours(); // 19, 24小时制 now.getMinutes(); // 49, 分钟 now.getSeconds(); // 22, 秒 now.getMilliseconds(); // 875, 毫秒数 now.getTime(); // 1435146562875, 以number形式表示的时间戳 // Date对象创建方法一 var d = new Date(2015, 5, 19, 20, 15, 30, 123); d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST) // Date对象创建方法二(ISO 8601格式) var d = Date.parse('2015-06-24T19:49:22.875+08:00'); d; // 1435146562875 var d = new Date(1435146562875); d; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST) d.getMonth(); // 5
-
JavaScript的Date对象月份值从0开始,牢记0=1月,1=2月,2=3月,……,11=12月
-
使用Date.parse()时传入的字符串使用实际月份0112,转换为Date对象后getMonth()获取的月份值为011
-
转换到所在时区:先转为时间戳(表示从1970年1月1日零时整的GMT时区开始的那一刻,到现在的毫秒数)
var d; if (Date.now) { t = Date.now(); // 老版本IE没有now()方法 } else { t = new Date().getTime(); } // t=1435146562875 var d = new Date(t); // Date对象表示的时间总是按浏览器所在时区显示的 d.toLocaleString(); // '2015/6/24 下午7:49:22',本地时间(北京时区+8:00),显示的字符串与操作系统设定的格式有关 d.toUTCString(); // 'Wed, 24 Jun 2015 11:49:22 GMT',UTC时间,与本地时间相差8小时
-
-
RegExp 正则表达式
-
定义方式
var re1 = /ABC\-001/; // 使用两个斜杠 '/./' var re2 = new RegExp('ABC\\-001'); //因为字符串的转义问题,字符串的两个\\实际上是一个\ re1; // /ABC\-001/ re2; // /ABC\-001/
-
判断是否匹配
var re = /^\d{3}\-\d{3,8}$/; re.test('010-12345'); // true re.test('010-1234x'); // false re.test('010 12345'); // false
-
切分字符串
'a b c'.split(' '); // ['a', 'b', '', '', 'c'] // 空格划分 'a b c'.split(/\s+/); // 正则,['a', 'b', 'c']
-
分组
var re = /^(\d{3})-(\d{3,8})$/; re.exec('010-12345'); // ['010-12345', '010', '12345'] 分别为整个字符串,匹配到的子串 re.exec('010 12345'); // null 失败返回null
-
非贪婪匹配(加
?
)var re = /^(\d+?)(0*)$/; re.exec('102300'); // ['102300', '1023', '00'] // 若不加?,贪婪匹配结果为['102300', '102300', '']
-
全局搜索
- 全局匹配可以多次执行
exec()
方法来搜索一个匹配的字符串 - 当我们指定
g
标志后,每次运行exec()
,正则表达式本身会更新lastIndex
属性,表示上次匹配到的最后索引 - 全局匹配类似搜索,因此不能使用
/^...$/
,那样只会最多匹配一次 - 正则表达式还可以指定
i
标志,表示忽略大小写,m
标志,表示执行多行匹配
var r1 = /test/g; // 等价于: var r2 = new RegExp('test', 'g'); var s = 'JavaScript, VBScript, JScript and ECMAScript'; var re=/[a-zA-Z]+Script/g; // 使用全局匹配: re.exec(s); // ['JavaScript'] re.lastIndex; // 10 re.exec(s); // ['VBScript'] re.lastIndex; // 20
- 全局匹配可以多次执行
-
-
JSON
-
数据类型
- number:和JavaScript的
number
完全一致; - boolean:就是JavaScript的
true
或false
; - string:就是JavaScript的
string
; - null:就是JavaScript的
null
; - array:就是JavaScript的
Array
表示方式——[]
; - object:就是JavaScript的
{ ... }
表示方式。
- number:和JavaScript的
-
JSON写死了字符集必须为UTF-8
-
JSON中字符串规定必须用双引号
""
,object中的键也必须用双引号 -
序列化
- 将Javascript对象转为JSON对象
JSON.s tringify()
'use strict'; var xiaoming = { name: '小明', age: 14, gender: true, height: 1.65, grade: null, 'middle-school': '\"W3C\" Middle School', skills: ['JavaScript', 'Java', 'Python', 'Lisp'] }; // 直接输出转化后的JSON字符串 var s = JSON.stringify(xiaoming); console.log(s); // 按缩进输出 JSON.stringify(xiaoming, null, ' '); // 仅输出某些键值 JSON.stringify(xiaoming, ['name', 'skills'], ' '); // 可以传入一个函数,这样对象的每个键值对都会被函数先处理 function convert(key, value) { if (typeof value === 'string') { return value.toUpperCase(); } return value; } JSON.stringify(xiaoming, convert, ' '); // 可以给小明定义一个 toJSON()方法,直接返回JSON应该序列化的数据 var xiaoming = { name: '小明', age: 14, gender: true, toJSON: function () { return { // 只输出name和age,并且改变了key: 'Name': this.name, 'Age': this.age }; } }; JSON.stringify(xiaoming); // '{"Name":"小明","Age":14}'
-
反序列化
- 将JSON对象转为Javascript对象
JSON.parse()
JSON.parse('[1,2,3,true]'); // [1, 2, 3, true] JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14} JSON.parse('true'); // true JSON.parse('123.45'); // 123.45
-
JSON.parse()
还可以接收一个函数,用来转换解析出的属性:'use strict'; var obj = JSON.parse('{"name":"小明","age":14}', function (key, value) { if (key === 'name') { return value + '同学'; } return value; }); console.log(JSON.stringify(obj)); // {name: '小明同学', age: 14}
-
面向对象
未完待续。。。