一文带你快速入门JavaScript
JavaScript(简称 JS)是一种高级、解释型、动态的编程语言,最初由 Netscape 在 1995 年开发,用于在网页上实现交互功能。它是 Web 前端三大核心技术之一(HTML + CSS + JavaScript)。
常见的运行环境为Nodejs和浏览器。主要应用有:
- 动态网页交互
- 前端框架开发
- 后端开发
- 脚本工具及自动化
数据类型
js一共有7种数据类型,三种基本数据类型:数值,字符串,布尔,两种派生数据类型:数组,对象,两种特殊数据类型:undefined,null
严格模式
启用strict模式的方法是在JavaScript代码的第一行写上:
'use strict';严格模式是为了防止没有使用var声明的变量变成全局变量
数值
数值类型共有以下几种:整数、浮点数、负数、NaN、Infinity
其中NaN表示 not a number,无法计算结果用NaN表示,例:0/0 结果是NaN
Infinity表示无穷大,当数值超过js的Number所能表示的最大值时,就表示为Infinity。例如以0为分子:3/0
字符串
用单引号或者双引号包括起来的字符表示就是字符串。
- 字符串转义 字符串可以用转义字符\来标识 ASCII字符可以以\x##形式的十六进制表示 还可以用\u####表示一个Unicode字符
- 多行字符串 除了使用换行符,可以使用反引号将字符括起来。例: `多行 字符串`
- 字符串拼接 使用 “+”号 var name = '小明'; var age = 20; var message = '你好, ' + name + ', 你今年' + age + '岁了!'; alert(message);
- 使用变量替换字符串 使用反引号 `: var name = '小明'; var age = 20; var message = `你好, ${name}, 你今年${age}岁了!`; alert(message);
- 获取字符串某个指定位置的字符 var s = 'Hello, world!'; s[0]; // 'H' s[6]; // ' ' s[7]; // 'w' s[12]; // '!' s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
- 字符串转大小写 var s = 'Hello'; s.toUpperCase(); // 返回'HELLO' var s = 'Hello'; s.toLowerCase(); // 返回'hello'
- 搜索指定字符串 var s = 'hello, world'; s.indexOf('world'); // 返回7 s.indexOf('World'); // 没有找到指定的子串,返回-1
- 截取字符串 var s = 'hello, world' s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello' s.substring(7); // 从索引7开始到结束,返回'world'
布尔
有true、false两种值。一般用来做判断
null和undefined
null表示一个空的值,而undefined表示值未定义。
数组
数组是一组按顺序排列的集合,集合的每个值称为元素。js的数组可以包含任意数据类型,并通过索引来访问每个元素。
- 获取数组长度 var arr = [1, 2, 3.14, 'Hello', null, true]; arr.length; // 6 请注意,直接给Array的length赋一个新的值会导致Array大小的变化:js
var arr = [1, 2, 3]; arr.length; // 3 arr.length = 6; arr; // arr变为[1, 2, 3, undefined, undefined, undefined] arr.length = 2; arr; // arr变为[1, 2] - 数组赋值 Array可以通过索引把对应的元素修改为新的值,因此,对Array的索引进行赋值会直接修改这个Arrayjs
var arr = ['A', 'B', 'C']; arr[1] = 99; arr; // arr现在变为['A', 99, 'C'] 请注意,如果通过索引赋值时,索引超过了范围,同样会引起Array大小的变化: var arr = [1, 2, 3]; arr[5] = 'x'; arr; // arr变为[1, 2, 3, undefined, undefined, 'x'] - 获取元素索引js
var arr = [10, 20, '30', 'xyz']; arr.indexOf(10); // 元素10的索引为0 arr.indexOf(20); // 元素20的索引为1 arr.indexOf(30); // 元素30没有找到,返回-1 arr.indexOf('30'); // 元素'30'的索引为2 - 截取数组jsslice()的起止参数包括开始索引,不包括结束索引。 如果不传任何参数,则可以复制一个数组
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C'] arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']jsvar arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; var aCopy = arr.slice(); aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G'] aCopy === arr; // false - push 和 popjs
push()向Array的末尾添加若干元素,pop()则把Array的最后一个元素删除掉: var arr = [1, 2]; arr.push('A', 'B'); // 返回Array新的长度: 4 arr; // [1, 2, 'A', 'B'] arr.pop(); // pop()返回'B' arr; // [1, 2, 'A'] arr.pop(); arr.pop(); arr.pop(); // 连续pop 3次 arr; // [] arr.pop(); // 空数组继续pop不会报错,而是返回undefined arr; // [] - unshift 和 shift 如果要往Array的头部添加若干元素,使用unshift()方法,shift()方法则把Array的第一个元素删掉:js
var arr = [1, 2]; arr.unshift('A', 'B'); // 返回Array新的长度: 4 arr; // ['A', 'B', 1, 2] arr.shift(); // 'A' arr; // ['B', 1, 2] arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次 arr; // [] arr.shift(); // 空数组继续shift不会报错,而是返回undefined arr; // [] - 数组排序 sort()可以对当前Array进行排序,它会直接修改当前Array的元素位置,直接调用时,按照默认顺序排序:js
var arr = ['B', 'C', 'A']; arr.sort(); arr; // ['A', 'B', 'C'] - 数组反转js
var arr = ['one', 'two', 'three']; arr.reverse(); arr; // ['three', 'two', 'one'] - 删除数组元素 splice()方法可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:js
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle']; // 从索引2开始删除3个元素,然后再添加两个元素: arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite'] arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle'] // 只删除,不添加: arr.splice(2, 2); // ['Google', 'Facebook'] arr; // ['Microsoft', 'Apple', 'Oracle'] // 只添加,不删除: arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素 arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle'] - 合并数组js
var arr = ['A', 'B', 'C']; var added = arr.concat([1, 2, 3]); added; // ['A', 'B', 'C', 1, 2, 3] arr; // ['A', 'B', 'C'] - 将数组变成字符串js
var arr = ['A', 'B', 'C', 1, 2, 3]; arr.join('-'); // 'A-B-C-1-2-3'
对象
对象是一组由键-值组成的无序集合。js对象的键都是字符串类型,值可以是任意数据类型。 访问属性是通过.操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名(键)包含特殊字符,就必须用 ' ' 括起来:
var xiaohong = {
name: '小红',
'middle-school': 'No.1 Middle School'
};xiaohong的属性名middle-school不是一个有效的变量,就需要用''括起来。访问这个属性也无法使用.操作符,必须用['xxx']来访问:
xiaohong['middle-school']; // 'No.1 Middle School' xiaohong['name']; // '小红' xiaohong.name; // '小红' 访问一个不存在的属性 则会返回undefined
- 为对象增加属性
var xiaoming = {
name: '小明'
};
xiaoming.age = 18;- 删除属性
delete xiaoming.age- 检测属性
'name' in xiaoming; // true注意:如果in判断一个属性存在,这个属性不一定是xiaoming的,它可能是xiaoming继承得到的
'toString' in xiaoming; // true要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法:
var xiaoming = {
name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false条件和循环
条件和循环用于控制程序的执行逻辑。
条件
JavaScript使用if () { ... } else { ... }来进行条件判断。例如,根据年龄显示不同内容,可以用if语句实现如下:
var age = 20;
if (age >= 18) { // 如果age >= 18为true,则执行if语句块
alert('adult');
} else { // 否则执行else语句块
alert('teenager');
}JavaScript把null、undefined、0、NaN和空字符串''视为false,其他值一概视为true
循环
循环计算 1到10000的和
var x = 0;
var i;
for (i=1; i<=10000; i++) {
x = x + i;
}使用for来遍历数组
var arr = ['Apple', 'Google', 'Microsoft'];
var i, x;
for (i=0; i<arr.length; i++) {
x = arr[i];
console.log(x);
}省略写法 for循环的3个条件都是可以省略的,如果没有退出循环的判断条件,就必须使用break语句退出循环,否则就是死循环:
var x = 0;
for (;;) { // 将无限循环下去
if (x > 100) {
break; // 通过if判断来退出循环
}
x ++;
}遍历对象 for循环的一个变体是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'
}
}由于Array也是对象,而它的每个元素的索引被视为对象的属性,因此,for ... in循环可以直接循环出Array的索引
while
for循环在已知循环的初始和结束条件时非常有用。而上述忽略了条件的for循环容易让人看不清循环的逻辑,此时用while循环。while循环只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环。比如我们要计算100以内所有奇数之和,可以用while循环实现:
var x = 0;
var n = 99;
while (n > 0) {
x = x + n;
n = n - 2;
}
x; // 2500do ... while
最后一种循环是do { ... } while()循环,它和while循环的唯一区别在于,不是在每次循环开始的时候判断条件,而是在每次循环完成的时候判断条件:
var n = 0;
do {
n = n + 1;
} while (n < 100);
n; // 100Map和Set
Map
Map是一组键值对的结构,具有极快的查找速度。
使用Javascript创建一个学生姓名和成绩的map: var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); m.get('Michael'); // 95
初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉
Set
Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
// 创建Set,并输入值
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
// 重复元素自动过滤
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
// 添加元素到set
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
// 删除元素
var s = new Set([1, 2, 3]);
s.delete(3);
s; // Set {1, 2}iterable
ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型。 具有iterable类型的集合可以通过新的for ... of循环来遍历。
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
console.log(x);
}
for (var x of s) { // 遍历Set
console.log(x);
}
for (var x of m) { // 遍历Map
console.log(x[0] + '=' + x[1]);
}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
更好的方式是直接使用iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
console.log(element);
});函数
JavaScript 函数是被设计为执行特定任务的代码块。会在某代码调用它时被执行。
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}函数赋值给变量
js的函数本质也是一个对象,因此可以赋值给变量。
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};function (x) { ... }是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs,所以,通过变量abs就可以调用该函数。这种方式和直接命名函数是等价的。
不加()调用函数
不加()使用函数,返回的是函数对象的声明。
<p id="demo"></p>
<script>
function toCelsius(f) {
return (5/9) * (f-32);
}
document.getElementById("demo").innerHTML = toCelsius;
</script>上边代码返回:
function toCelsius(f) { return (5/9) * (f-32); } //<p id="demo"></p>的内容函数调用
函数调用的时候,传入任意个参数并不会影响调用,因此传入的参数比定义的参数多也没有问题。如果参数传的少。未传的参数默认为undefined 单数js有个关键字arguments,可以获取传递的参数。它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array:
function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);不传参数也可以获取到值, 我们经常用它判断入参的个数:
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参数表示剩余的参数。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 a = 1;
function test()
{
console.log(a); // 1
var b = 2;
console.log(b); // 2
for(var i=10; i<13; i++) {
console.log(i)
}
console.log(i) // 13
}
test()
// console.log(b) 直接报错:Uncaught ReferenceError: b is not defined根据上边的运行结果,我们可以得到关于变量作用域的以下几点结论:
- 函数体内声明的变量只能函数内部使用。
- 函数外部声明的变量函数内部依然可以使用。
- 结构语句中使用var声明的变量,在当前函数中仍然可以使用。(使用let则只在块作用域内生效)
变量提升
先看个例子:
function test () {
console.log(a); //undefined
var a = 123;
};
test();我们发现,打印后面声明的变量并没有报错。说明同一作用域下的变量会被提前声明,变量提升了! 再来看一道面试题:
console.log(v1); // undefined
var v1 = 100;
function foo() {
console.log(v1); // undefined
var v1 = 200;
console.log(v1); // 200
}
foo();
console.log(v1); // 100我们可以看到:当v1变量重复声明的时候,会优先从函数内部查找变量,外部重名变量会被自动忽略。
解构赋值
ES6开始支持解构赋值, 解构赋值很多语言都会支持,这个功能是为了方便获取数组或者对象内部的变量,而不需去遍历它。 示例:
var [x, y] = ['hello', 'world'];支持嵌套和忽略赋值:
let [x, [y, z]] = ['hello', ['my', 'world']];
x; // 'hello'
y; // 'my'
z; // 'world'
let [, , z] = ['hello', 'my', 'world']; // 忽略前两个元素
z; // 'world'当然对于对象也是一样的:
var person = {
name: '小明',
age: 20,
address: {
city: 'Beijing',
street: 'No.1 Road'
}
};
// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
// 对象也支持嵌套
var {name, address: {city, street}} = person;来看几个实用的小栗子:
// 交换变量
var x=1, y=2;
[x, y] = [y, x]
// 获取页面的域名和路径
var {hostname:domain, pathname:path} = location;
console.log(domain);
console.log(path);
// 如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量
function buildDate({year, month, day, hour=0, minute=0, second=0}) {
return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}this和箭头函数
变量和常量
js中使用const声明一个常量
const PI = 3.14使用var 和 let声明变量
var a = 1;
let b = 2;那么使用let和var声明变量有什么区别呢?
- let可以定义块级作用域变量
- let没有变量提升
- let声明的变量不能重复声明
js中的this
先来看一个对象中的一个方法:
var xiaoming = {
name: '小明',
birth: 1996,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age(); // 25在一个方法内部,this是一个特殊变量,它始终指向当前对象。现在我们将上边的方法单独拎出来,作为一个函数。
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1996,
age: getAge()
};
xiaoming.age; // NaN纳尼?发生了什么?分析帝分析一波: 首先调用一个函数,只是获取这个函数的返回值,函数体内部的实现并不能在调用的地方体现。所以,在xiaoming这个对象调用getAge()的方法的时候,getAge()方法里面的this并不是指向xiaoming这个对象,而是全局window。所以运行结果并不能获取我们想要的。js为了让这个误区显露出来,严格模式下运行,会直接提示一个错误。
'use strict';
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1996,
age: getAge()
};
xiaoming.age; // Uncaught TypeError: Cannot read property 'birth' of undefined严格模式下,全局函数中的this指向了undefined,不使用严格模式,则会指向window
讲函数的时候,我们提到,如果不加() ,我们会获取这个函数的函数体。如果不加(),可不可以得到我们想要的结果呢?
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1996,
age: getAge
};
xiaoming.age(); // 25事实证明可以。 但是如果,这个函数是对象内部方法之中定义的呢,会发生什么?
'use strict'
var xiaoming = {
name: '小明',
birth: 1996,
age: function() {
return getAge();
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
}
};
xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined无情报错。 有没有解决办法呢?我们尝试用别的变量代替this。
var xiaoming = {
name: '小明',
birth: 1996,
age: function() {
var that = this;
return getAge();
function getAge() {
var y = new Date().getFullYear();
return y - that.birth;
}
}
};
xiaoming.age(); // 25我们用that先获取this,这个节点this指向的是xiaoming对象,所以避免了在函数内部this指向错误的问题。
还有没有别的办法呢?我们使用函数本身的apply方法修复this的指向。
'use strict'
var xiaoming = {
name: '小明',
birth: 1996,
age: function() {
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAge.apply(xiaoming, []);
}
};
xiaoming.age(); // 25apply方法接收两个参数,第一个参数就是需要绑定的this变量(设置为null则为全局),第二个参数是Array,表示函数本身的参数。我们还可以用apply实现系统函数的重写。
'use strict';
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};除了使用apply方法,有没有别的方法修复this的指向问题呢。有的:箭头函数。
箭头函数
箭头函数相当于简写的匿名函数。
// 单参数
x => x * x
// 多参数
(x, y) => x * x + y * y
// 空参数
() => new Date().getFullYear()
// 因为花括号在js中的特殊性,返回对象需要用()包起来
x => ({ foo: x })
// 将箭头函数赋值给变量
var func = (x, y) => x * x + y * y箭头函数中的this总是指向词法作用域,解决了this的指向问题。
var xiaoming = {
birth: 1996,
age: function () {
var b = this.birth; // 1990
var getAge = () => new Date().getFullYear() - this.birth; // this指向xiaoming 对象
return getAge ();
}
};
xiaoming .age(); // 25闭包
闭包是一种实现方式(俺是这样想的),可以是一个可执行函数,js的每个对象其实都是闭包的实现方式。
闭包的特性
- 闭包可以访问外部作用域,即使这个外部作用域已经执行结束。
- 当你定义一个函数时候,实际就是一种闭包的实现方式。只有当这个函数不被其他任何地方调用的时候,闭包就结束了。
- 闭包可以让作用域里的 变量,在函数执行完之后依旧保持没有被垃圾回收处理掉
使用示例
举个栗子:
"use strict";
var myClosure = (function outerFunction() {
var hidden = 1;
return {
inc: function innerFunction() {
return hidden++;
}
};
}());
console.log(myClosure.inc()); // 1
console.log(myClosure.inc()); // 2
console.log(myClosure.inc()); // 3我们可以看到,当我们运用了外部作用域的变量的时候,闭包并没有结束,而是当不在有调用的时候,才将闭包销毁。
经典题**
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);会怎么输出呢?正确答案是:5, 5, 5,5,5,5。 他们之间的输出时间是怎么样的呢? 用箭头表示其前后的两次输出之间有 1 秒的时间间隔,而逗号表示其前后的两次输出之间的时间间隔可以忽略。则结果是这样的: 5 -> 5,5,5,5,5。因为循环过程中,几乎是同时设置了5个定时器。
那么应该怎么修改代码,让期望结果变成:5 -> 0,1,2,3,4。走着:
for (var i = 0; i < 5; i++) {
(function(j) { // j = i
setTimeout(function() {
console.log(new Date, j);
}, 1000);
})(i);
}
console.log(new Date, i);我们 “创建了一个匿名函数并立刻执行”。达到了期望的效果。那么还有没别的办法呢?我们知道setTimeOut方法第三个参数是可选的。当定时器到期,它们会作为参数传递给function 。所以可以这样写:
for (var i = 0; i < 5; i++) {
setTimeout(function(j) {
console.log(new Date, j);
}, 1000, i);
}
console.log(new Date, i);或者我们可以将i重新赋值:
var output = function (j) {
setTimeout(function() {
console.log(new Date, j);
}, 1000);
};
for (var i = 0; i < 5; i++) {
output(i);
}
console.log(new Date, i);或者使用let声明变量i:
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
// console.log(new Date, i); // 会报错。时间的转换
js中使用Date对象来表示时间和日期:
获取年月日时分秒和星期等
var now = new Date();
now;
now.getFullYear(); // 2021, 年份
now.getMonth(); // 2, 月份,月份范围是0~11,2表示3月
now.getDate(); // 4, 表示4号
now.getDay(); // 3, 星期三
now.getHours(); // 16, 表示19h
now.getMinutes(); // 41, 分钟
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 473, 毫秒数
now.getTime(); // 1614847074473, 以number形式表示的时间戳创建指定日期的时间对象
var d = new Date(2021, 3, 4, 16, 15, 30, 123);将日期解析为时间戳
var d = Date.parse('2021-03-04 16:49:22.123');
d; // 1614847762123
// 尝试更多方式
(new Date()).valueOf();
new Date().getTime();
Number(new Date());时间戳转日期
var d = new Date(1614847762123);
d.toUTCString(); // "Thu, 04 Mar 2021 08:49:22 GMT",UTC时间,与本地时间相差8小时
var d = new Date(1614847762123);
d.toLocaleString(); // "2021/3/4下午4:49:22",本地时间(北京时区+8:00),显示的字符串与操作系统或浏览器设定的格式有关时间戳转自定义格式的日期
因为操作系统或者浏览器显示格式的不确定性,固定格式的日期只能自己拼接:
function getDate() {
var now = new Date(),
y = now.getFullYear(),
m = now.getMonth() + 1,
d = now.getDate();
return y + "-" + (m < 10 ? "0" + m : m) + "-" + (d < 10 ? "0" + d : d) + " " + now.toTimeString().substr(0, 8);
}
getDate() // "2021-03-04 16:56:39"可以使用成熟的三方库: moment.js或者dayjs等
JSON序列化和反序列化
JSON是JavaScript Object Notation的缩写,是一种数据交换格式。
json中的数据类型
一共有六种:
- number:JavaScript的number;
- boolean:JavaScript的true或false;
- string:JavaScript的string;
- null:JavaScript的null;
- array:JavaScript的Array表示方式——[];
- object:JavaScript的{ ... }表示方式。
json序列化
就是把JavaScript对象或数组转为json格式的字符串。
'use strict';
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
skills: ['JavaScript', 'Java', 'Python', 'PHP']
};
var s = JSON.stringify(xiaoming);
console.log(s); // {"name":"小明","age":14,"gender":true,"height":1.65,"grade":null,"skills":["JavaScript","Java","Python","PHP"]}
// 按缩进输出
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, ' ');我们可以将对象需要返回的json数据预先定义:
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
skills: ['JavaScript', 'Java', 'Python', 'Lisp'],
toJSON: function () {
return { // 只输出name和age,并且改变了key:
'Name': this.name,
'Age': this.age
};
}
};
JSON.stringify(xiaoming); // '{"Name":"小明","Age":14}'反序列化
将JSON格式的字符串,用JSON.parse()把它变成一个JavaScript对象:
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正则表达式
几乎每种语言字符串类型都支持正则匹配,固定场景的字符串可能都有固定的规则,我们设定好一个规则,验证目标字符串是否符合规则。比如验证手机号码,验证邮箱等等。
正则匹配规则
先看几个简单的正则匹配规则示例:
- 用\d可以匹配一个数字,\w可以匹配一个字母或数字。比如:
'11\d' 可以匹配'110',因为0是数字;'\d\d\d'也可以匹配'110',因为1、1、0都是数字;'\w\w\w' 可以匹配'abc',因为a、b、c都是数字; - 使用 . 可以匹配任意字符。比如:
'shabi.' 可以匹配'shabi?',也可以匹配'shabi!',也可以匹配'shabi。'; - 表示变长字符串,有这样几种匹配方式:用 * 表示任意个字符(包括0个),用+表示至少一个字符, 用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符。例如: '
ba*' 可以匹配b,ba,baa,baaa\d{2,8}可以匹配2-8个数字 - \s可以匹配空格。例如:
\s+表示至少有一个空格 - 特殊字符,用 \ 转义匹配
\-表示匹配-号 - 可以用 [] 进行范围匹配。例如:
[0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线; - 还有一些其他常见的规则:
A|B可以匹配A或B,所以(J|j)ava(S|s)cript可以匹配'JavaScript'、'Javascript'、'javaScript'或者'javascript'。A|B可以匹配A或B,所以(J|j)ava(S|s)cript可以匹配'JavaScript'、'Javascript'、'javaScript'或者'javascript'。^表示行的开头,^\d表示必须以数字开头。$表示行的结束,\d$表示必须以数字结束。
常见匹配示例
- 匹配一个1到20个字符的javascript合法变量名称:
[a-zA-Z\_\$][0-9a-zA-Z\_\$]{0, 19}[a-zA-Z\_\$]表示任意一个字母下划线开头,[0-9a-zA-Z\_\$]{0, 19}表示0到19位数字字母或者下划线 - 匹配一个11位的手机号码:
^[1][3,4,5,6,7,8,9][0-9]{9}$^[1]表示以1开头, [3,4,5,6,7,8,9]表示第二个数字是3到9的任意一个数,[0-9]{9}$表示0到9的任意数,一共是9个数字。 - 匹配一个合法的邮箱
^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$圆括号()表示分组。^(\w)+表示至少以一个字母或者数字开头;(\.\w+)*表示任意个 . 或者数字或字母。((\.\w+)+)$表示至少一个. 或者数字字母结束
js中正则校验
两种方法,一种是使用正则对象,一种是使用/ /包括,我们前面的手机号码做测试。
var re1 = /^[1][3,4,5,6,7,8,9][0-9]{9}$/;
var re2 = new RegExp('^[1][3,4,5,6,7,8,9][0\-9]{9}$');
re1.test('11012345678'); // false
re2.test('11012345678'); // false
re1.test('18012345678'); // true字符串分组
前面已经有提到过,使用()可以分组,分组的意思就是我们可以获取正则匹配到的分组子串。用前面的邮箱为例:
var re = /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/;
re.test('abc@163.com'); // true
re.exec('abc@163.com'); // ["abc@163.com", "c", undefined, "3", ".com", ".com", index: 0, input: "abc@163.com", groups: undefined]第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。没有匹配到exec则返回null。
Promise和async/await
JavaScript是一门单线程语言,所有的代码都是在一个线程中执行的。如果有耗时的操作,比如读取文件、网络请求等,就会阻塞主线程,导致页面无响应。为了解决这个问题,JavaScript引入了异步编程的概念。
Promises
顾名思义,Promise表示一个承诺。承诺未来某个时间会执行某个操作。
Promise主要是为了解决js编程中回调地狱的问题。先看一个旧式的例子:
chooseToppings(function(toppings) {
placeOrder(toppings, function(order) {
collectOrder(order, function(pizza) {
eatPizza(pizza);
}, failureCallback);
}, failureCallback);
}, failureCallback);可以看到,回调函数的多层调用,使代码可读性变差,而且不利于维护。
使用Promise则会变成这样:
chooseToppings()
.then(function(toppings) {
return placeOrder(toppings);
})
.then(function(order) {
return collectOrder(order);
})
.then(function(pizza) {
eatPizza(pizza);
})
.catch(failureCallback);或者更加简洁:
chooseToppings()
.then(toppings => placeOrder(toppings))
.then(order => collectOrder(order))
.then(pizza => eatPizza(pizza))
.catch(failureCallback);使用promise
我们通过new Promise(func)来实例化一个Promise对象,其中func表示我们事先已经实现的一个函数,这个函数正常有两个参数:resolve, reject。两个参数都是回调函数,分别在当Promise执行成功或失败的时候触发。使用示例:
function test(resolve, reject) {
console.log(Math.random())
var timeOut = Math.random() * 2;
console.log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function () {
if (timeOut < 1) {
console.log('call resolve()...');
resolve('200 OK');
} else {
console.log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
}
var p = new Promise(test);
p.then(function (result) {
console.log('成功:' + result);
}).catch(function (reason) {
console.log('失败:' + reason);
});当然,then()返回的依旧是一个Promise对象,所以如果有多层回调,我们可以继续这样写:then().then().....
async/await
顾名思义,async表示异步,await表示异步的等待。
async/await 是配套使用的,他们是对Promise的进一步改良,主要是为了使代码更好看,多层Promise的时候传参更加方便。
async是做了一个规范:使用async修饰的函数或方法,一定返回的是一个Promise对象。
await修饰一个返回值为Promise对象的方法,获取这个Promise执行成功的返回值。await所在的函数或方法必须使用async进行修饰。
示例:
function takeTime(n) {
return new Promise((resolve) =>
setTimeout(() => {
resolve(n + 200);
}, 1000)
);
}
var time1 = 300;
async function test() {
var res = await takeTime(time1);
console.log(res);
}
test();明显看到,我们的代码单行编写,只接收了成功的返回值,看起来更加简洁,如果出现多层调用,也更加方便赋值。